diff --git a/whitebox_neutron_tempest_plugin/tests/scenario/base.py b/whitebox_neutron_tempest_plugin/tests/scenario/base.py index 4f7d63e..ccba7f1 100644 --- a/whitebox_neutron_tempest_plugin/tests/scenario/base.py +++ b/whitebox_neutron_tempest_plugin/tests/scenario/base.py @@ -264,12 +264,13 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase): @classmethod def get_pod_of_service(cls, service='neutron'): - # (rsafrono) at this moment only neutron service pod handled - # since it's the only that existing tests are using + pods_list = "oc get pods" if service == 'neutron': - return cls.proxy_host_client.exec_command( - "oc get pods | grep neutron | grep -v meta | " - "cut -d' ' -f1").strip() + filters = "grep neutron | grep -v meta | cut -d' ' -f1" + else: + filters = "grep {} | cut -d' ' -f1".format(service) + return cls.proxy_host_client.exec_command( + "{} | {}".format(pods_list, filters)).strip() @classmethod def get_configs_of_service(cls, service='neutron'): diff --git a/whitebox_neutron_tempest_plugin/tests/scenario/test_sriov_provider_network.py b/whitebox_neutron_tempest_plugin/tests/scenario/test_sriov_provider_network.py new file mode 100644 index 0000000..4d88419 --- /dev/null +++ b/whitebox_neutron_tempest_plugin/tests/scenario/test_sriov_provider_network.py @@ -0,0 +1,851 @@ +# Copyright 2024 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import re +import time + +import netaddr +from neutron_lib import constants as lib_constants +from neutron_tempest_plugin.common import ssh +from neutron_tempest_plugin import config +from neutron_tempest_plugin.scenario import constants +from oslo_log import log +from oslo_serialization import jsonutils +from tempest.common.utils.linux import remote_client +from tempest.common import waiters +from tempest import exceptions +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc +import testtools + +from whitebox_neutron_tempest_plugin.common import utils +from whitebox_neutron_tempest_plugin.tests.scenario import base + + +CONF = config.CONF +WB_CONF = config.CONF.whitebox_neutron_plugin_options +LOG = log.getLogger(__name__) + + +class ProviderNetworkSriovBaseTest(base.ProviderBaseTest): + """Base class for SRIOV tests using provisioning networks + Admin user is needed to create ports on the existing provisioning network + """ + MAC_RE = re.compile(r'([0-9a-f]{2}(?::[0-9a-f]{2}){5})', re.IGNORECASE) + # Interface names might match any of these patterns + # Examples: + # 1) PF: p5p7, p2p1; VF: p5p7_2, p2p1_0 + # 2) PF: enp6s0f3, enp1s1f2; VF: enp6s0f3v4, enp1s1f2v1 + # 3) PF: enp6s0, enp1s1; VF: enp6s0v4, enp1s1v1 + PF_NAME_REGEX1 = re.compile(r'p\d+p\d+') + VF_NAME_SEPARATOR1 = '_' + PF_NAME_REGEX2 = re.compile(r'enp\d+s\d+f\d+') + VF_NAME_SEPARATOR2 = 'v' + PF_NAME_REGEX3 = re.compile(r'enp\d+s\d+') + VF_NAME_SEPARATOR3 = 'v' + PF_VF_REGEX_LIST = [{'pf_name_regex': PF_NAME_REGEX1, + 'vf_name_separator': VF_NAME_SEPARATOR1}, + {'pf_name_regex': PF_NAME_REGEX2, + 'vf_name_separator': VF_NAME_SEPARATOR2}, + {'pf_name_regex': PF_NAME_REGEX3, + 'vf_name_separator': VF_NAME_SEPARATOR3}] + + @classmethod + def skip_checks(cls): + super(ProviderNetworkSriovBaseTest, cls).skip_checks() + if not (CONF.neutron_plugin_options.advanced_image_ref or + CONF.neutron_plugin_options.default_image_is_advanced): + raise cls.skipException( + 'Advanced image is required to run these tests.') + if WB_CONF.openstack_type == 'devstack': + raise cls.skipException("The tests are currently not supported " + "on devstack environment") + + @classmethod + def setup_clients(cls): + super(ProviderNetworkSriovBaseTest, cls).setup_clients() + cls.client = cls.os_adm.network_client + cls.keypairs_client = cls.os_adm.keypairs_client + cls.servers_client = cls.os_adm.servers_client + + @classmethod + def resource_setup(cls): + super(ProviderNetworkSriovBaseTest, cls).resource_setup() + if not cls.has_sriov_support: + raise cls.skipException("Skipped because SRIOV is not supported") + # info about SRIOV MACs + cls._get_initial_sriov_macs() + # initialize mysql nova command + cls._get_nova_db_cmd() + # verify number of available VF and PF before tests start + cls._check_vfpf_nova_db() + # set image and flavor in case default image is not advanced + if not CONF.neutron_plugin_options.default_image_is_advanced: + cls.flavor_ref = \ + CONF.neutron_plugin_options.advanced_image_flavor_ref + cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref + cls.username = CONF.neutron_plugin_options.advanced_image_ssh_user + + @classmethod + def _get_nova_db_cmd(cls): + # check that number of available VF and PF ports is correct before + # any server has been created + # (rsafrono) This code below experimental. + # I tried to avoid hard-coding existing cell1 that exist on VA1 + # podified environment since the name can probably change + # or more than one cell will be supported in the future + nova_scheduler_pod = cls.get_pod_of_service("nova-scheduler") + cells = cls.proxy_host_client.exec_command( + "oc rsh {} nova-manage cell_v2 list_hosts | grep compute | " + "tr -d '|' | tr -s ' ' ".format(nova_scheduler_pod) + "| " + "awk '{print $1}' | uniq").strip().split() + if len(cells) != 1: + cls.fail("Currently only environments with a single cell " + "are supported") + galera_pod = cls.get_pod_of_service( + 'openstack-{}-galera-0'.format(cells[0])) + galera_db_exec = "oc rsh {}".format(galera_pod) + data_filter = ".data.Nova{}DatabasePassword|base64decode".format( + cells[0].capitalize()) + db_password = cls.proxy_host_client.exec_command( + "oc get secret osp-secret -o go-template --template=" + "\"{{" + data_filter + "}}\"").strip() + db_credentials = "-u root -p{}".format(db_password) + mysql_cmd = ('mysql --skip-column-names {} nova_{} -e ' + '"select pci_stats from compute_nodes;"'.format( + db_credentials, cells[0])) + cls.nova_db_cmd = "{} {} ".format(galera_db_exec, mysql_cmd) + + @classmethod + def _check_vfpf_nova_db(cls, timeout=60, interval=5): + def _get_nova_objects(): + output = cls.run_on_master_controller(cls.nova_db_cmd) + LOG.debug("pci_stats obtained from nova DB:") + LOG.debug(output) + output_rows = output.splitlines() + # number of expected rows equals number of compute nodes + assert (len(output_rows) == CONF.compute.min_compute_nodes) + nova_objects = [ + jsonutils.loads(row)['nova_object.data']['objects'] + for row in output_rows + ] + return nova_objects + # check all PF and all VF are available + db_checkings = [] + start = time.time() + while time.time() - start < timeout: + nova_objects = _get_nova_objects() + db_checkings = [False for i in range(len(nova_objects))] + for i, nova_object in enumerate(nova_objects): + # each nova_object includes: + # - an entry with information about the available PF ports + # - an entry for each PF port, with information about their + # available VF ports + bool_nova_objects = len(nova_object) == ( + WB_CONF.sriov_pfs_per_host + 1) + # initialize other booleans to False + pf_bool_count = False + vf_bool_count = False + pf_found = False + vf_found = False + for pci_device_pool in nova_object: + dev_type = pci_device_pool['nova_object.data']['tags'][ + 'dev_type'] + count = pci_device_pool['nova_object.data']['count'] + if dev_type == 'type-PF': + pf_found = True + # expected available PF ports + pf_bool_count = count == WB_CONF.sriov_pfs_per_host + elif dev_type == 'type-VF': + vf_found = True + # expected available VF ports + vf_bool_count = count == WB_CONF.sriov_vfs_per_pf + else: + raise RuntimeError('Unexpected dev_type %s' % dev_type) + db_checkings[i] = (bool_nova_objects and pf_bool_count and + vf_bool_count and pf_found and vf_found) + if not all(checking for checking in db_checkings): + time.sleep(interval) + else: + break + # TODO(eolivare): uncomment this assert when rhbz#2101328 is resolved + # assert(db_checkings and all(checking for checking in db_checkings)) + + @classmethod + def _get_initial_sriov_macs(cls): + cls.sriov_macs = {} + compute_nodes = [node for node in cls.nodes + if node['is_compute'] is True] + cls.compute_ssh_clients = {} + for node in compute_nodes: + cls.sriov_macs[node['name']] = [] + cls.compute_ssh_clients.update( + {node['name']: node['client']}) + nova_config_path = ( + '/var/lib/config-data/nova_libvirt/etc/nova/nova.conf') + passthrough_whitelist_str = node['client'].exec_command( + ('sudo cat %s | grep "^passthrough_whitelist" | ' + 'cut -d"=" -f2') % nova_config_path) + for line in passthrough_whitelist_str.splitlines(): + passthrough_whitelist = eval(line.rstrip()) + pf_name = passthrough_whitelist.get('devname') + trusted = passthrough_whitelist.get('trusted', 'false') + cmd_pf = 'PATH=$PATH:/usr/sbin ip link show %s' % pf_name + try: + output_pf = node['client'].exec_command( + cmd_pf).rstrip() + except lib_exc.SSHExecCommandFailed: + # This interface does not exist on current compute node + continue + else: + # Multiple SRIOV interfaces can be found per compute node + cls.sriov_macs[node['name']].append({ + 'pf_name': pf_name, + 'trusted': trusted}) + mac_pf = re.findall(cls.MAC_RE, output_pf)[0] + cls.sriov_macs[node['name']][-1].update( + {pf_name: mac_pf}) + # Obtain MAC for each VF associated to the PF + for i in range(WB_CONF.sriov_vfs_per_pf): + cmd_vf = None + for pf_vf_regex in cls.PF_VF_REGEX_LIST: + if pf_vf_regex['pf_name_regex'].findall(pf_name): + cmd_vf = (cmd_pf + + pf_vf_regex['vf_name_separator'] + + str(i)) + if cmd_vf is None: + raise RuntimeError('Unexpected pf_name %s' % pf_name) + output_vf = node['client'].exec_command( + cmd_vf).rstrip() + mac_vf = re.findall(cls.MAC_RE, output_vf)[0] + cls.sriov_macs[node['name']][-1].update( + {cmd_vf.split()[-1]: mac_vf}) + + def check_port_status(self, port_type, + port_index=-1, server_index=-1): + port_details = (super(ProviderNetworkSriovBaseTest, self) + .check_port_status(port_type, + port_index, + server_index)) + self._check_port_mac(port_details=port_details, + port_index=port_index, + server_index=server_index) + + @classmethod + def resource_cleanup(cls): + super(ProviderNetworkSriovBaseTest, cls).resource_cleanup() + # verify number of available VF and PF after tests end + cls._check_vfpf_nova_db() + + def _test_create_instance_with_network_port(self, port_type, + vm_name=None, + reuse_port=False): + security_groups, port, user_data, config_drive = \ + self._create_network_port(port_type, + reuse_port=reuse_port, + use_provider_net=True) + vm_name = vm_name or "vm-" + port_type + "-" + self._testMethodName + self._create_server( + security_groups=security_groups, networks=[port], + user_data=user_data, config_drive=config_drive, + name=vm_name) + + def _test_create_instance_with_two_ports(self, port_types, cidr, + vm_name=None, + reused_tenant_net=None): + """Two ports will be used for each VM. The first one will be connected + to the existing provider network and the second one to a tenant network + """ + # Fist, create external port (connected to provider network) + _, port1, user_data, config_drive = \ + self._create_network_port(port_types[0], + use_provider_net=True) + # Second, create internal port (connected to tenant network) + # This port should not have a default route, hence its subnet is + # created without a default gateway + _, port2, user_data, config_drive = \ + self._create_network_port(port_types[1], + use_provider_net=False, + reused_tenant_net=reused_tenant_net, + cidr=cidr, + gateway=False) + vm_name = vm_name or "vm-" + self._testMethodName + self._create_server( + networks=[port1, port2], user_data=user_data, + config_drive=config_drive, name=vm_name) + + def _test_instance_with_one_port_prov_net(self, port_type): + self._test_create_instance_with_network_port(port_type) + self.check_connectivity(self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + def _check_port_mac(self, port_details, port_index=-1, server_index=-1): + # MAC obtained via OSP API after server creation/migration/reboot + port_mac_api = port_details['mac_address'] + # Initial port MAC assigned by neutron at port creation + mac_neutron_port_creation = self.ports[port_index]['mac_address'] + # MAC obtained from VM instance + vm_ip = port_details['fixed_ips'][0]['ip_address'] + cmd = 'PATH=$PATH:/usr/sbin ; ip link show %s' % \ + WB_CONF.default_instance_interface + vm_ssh_client = ssh.Client(vm_ip, + self.username, + pkey=self.keypair['private_key']) + output = vm_ssh_client.exec_command(cmd).rstrip() + vm_mac = re.findall(self.MAC_RE, output)[0] + self.assertEqual(port_mac_api, vm_mac) + # MAC obtained from hypervisor + server_id = self.servers[server_index]['id'] + host = self.servers_client.show_server( + server_id)['server']['OS-EXT-SRV-ATTR:host'].split('.')[0] + host_macs = self.sriov_macs[host] + if port_details['binding:vnic_type'] in ('direct', 'macvtap'): + vf_pci_slot = port_details['binding:profile']['pci_slot'] + vf_id = vf_pci_slot.split('.')[-1] + pf_name = self._get_pf_name_from_vf(host, vf_pci_slot) + host_macs_this_pf = None + for host_macs_item in host_macs: + if host_macs_item['pf_name'] == pf_name: + host_macs_this_pf = host_macs_item + self.assertIsNotNone(host_macs_this_pf) + if host_macs_this_pf['trusted'].lower() == 'true': + # Verify initial neutron MAC has not changed + self.assertEqual(port_mac_api, mac_neutron_port_creation) + # Updated VF MAC value applies when trusted (neutron applies + # its own MAC value to the interface) + vf_mac = self._get_updated_sriov_mac(host, host_macs_this_pf, + vf_id=vf_id) + else: + # Verify initial neutron MAC has changed + self.assertNotEqual(port_mac_api, mac_neutron_port_creation) + # Initial VF MAC value applies when not trusted + vf_name = None + for pf_vf_regex in self.PF_VF_REGEX_LIST: + if pf_vf_regex['pf_name_regex'].findall(pf_name): + vf_name = (pf_name + + pf_vf_regex['vf_name_separator'] + + vf_id) + if vf_name is None: + raise RuntimeError('Unexpected pf_name %s' % pf_name) + vf_mac = host_macs_this_pf[vf_name] + self.assertEqual(port_mac_api, vf_mac) + elif 'direct-physical' == port_details['binding:vnic_type']: + # Verify initial neutron MAC has changed + self.assertNotEqual(port_mac_api, mac_neutron_port_creation) + # Initial PF MAC value applies + pf_macs = [host_macs_item[host_macs_item['pf_name']] + for host_macs_item in host_macs] + self.assertIn(port_mac_api, pf_macs) + # HV MAC does not need to be validated for normal ports + + def _get_updated_sriov_mac(self, host, host_macs, vf_id=None): + compute_ssh_client = self.compute_ssh_clients[host] + cmd = 'PATH=$PATH:/usr/sbin ; ip link show %s' % host_macs['pf_name'] + if vf_id is not None: + cmd += ' | grep "vf %s"' % vf_id + output = compute_ssh_client.exec_command(cmd).rstrip() + mac = re.findall(self.MAC_RE, output)[0] + return mac + + def _get_pf_name_from_vf(self, host, vf_pci_slot): + compute_ssh_client = self.compute_ssh_clients[host] + cmd = 'ls /sys/bus/pci/devices/%s/physfn/net' % vf_pci_slot + output = compute_ssh_client.exec_command(cmd).rstrip() + return output + + +class OneServerProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Class for tests creating one VM with one SRIOV port on provisioning + networks + These tests can be executed without needing to cleanup VMs or ports between + them + """ + @decorators.idempotent_id('bd4d51e7-49d3-4b56-9ef6-7ba68761d122') + def test_create_instance_with_normal_port_prov_net(self): + self._test_instance_with_one_port_prov_net('normal') + + @decorators.idempotent_id('a6df084c-b1ff-46a8-a269-47d3008c0572') + def test_create_instance_with_direct_port_prov_net(self): + self._test_instance_with_one_port_prov_net('direct') + + @decorators.idempotent_id('fef94a15-ddf5-4094-9b3f-ecac370dafad') + def test_create_instance_with_direct_physical_port_prov_net(self): + self._test_instance_with_one_port_prov_net('direct-physical') + + @decorators.idempotent_id('3f3a4ec4-9a92-41d3-836c-59b5809d1d18') + def test_create_instance_with_macvtap_port_prov_net(self): + self._test_instance_with_one_port_prov_net('macvtap') + + +class ExtraDhcpOptsSriovTest(ProviderNetworkSriovBaseTest): + """Class for tests creating one VM with one SRIOV port on provisioning + networks using extra_dhcp_options parameter at port creation + These tests can be executed without needing to cleanup VMs or ports between + them + """ + required_extensions = ['extra_dhcp_opt'] + extra_dhcp_opts = [ + {'opt_value': '8.8.8.8', + 'opt_name': 'dns-server', + 'ip_version': lib_constants.IP_VERSION_4}, + {'opt_value': '1600', + 'opt_name': 'mtu'}] + + def _validate_dhcp_opts(self): + vm_ip = self.ports[-1]['fixed_ips'][0]['ip_address'] + vm_ssh_client = ssh.Client(vm_ip, + self.username, + pkey=self.keypair['private_key']) + if self.ports[-1]['binding:vnic_type'] == 'direct-physical': + network_id = self.ports[-1]['network_id'] + vlan = self.client.show_network( + network_id)['network']['provider:segmentation_id'] + else: + vlan = None + obtained_dhcp_options = utils.parse_dhcp_options_from_nmcli( + vm_ssh_client, lib_constants.IP_VERSION_4, vlan=vlan) + for extra_dhcp_opt in self.extra_dhcp_opts: + self.assertIn(extra_dhcp_opt['opt_name'], obtained_dhcp_options) + self.assertEqual(extra_dhcp_opt['opt_value'], + obtained_dhcp_options[extra_dhcp_opt['opt_name']]) + + @decorators.idempotent_id('5f676e8a-3d74-49c7-ab87-1679b9b7155a') + def test_extra_dhcp_opts_direct_port(self): + self._test_instance_with_one_port_prov_net('direct') + self._validate_dhcp_opts() + + @decorators.idempotent_id('0bd26a9e-9c0f-40e4-a317-374ef48ac5b6') + def test_extra_dhcp_opts_direct_physical_port(self): + self._test_instance_with_one_port_prov_net('direct-physical') + self._validate_dhcp_opts() + + @decorators.idempotent_id('667df5dd-9302-4bfd-8d8c-7326abff2bb6') + def test_extra_dhcp_opts_macvtap_port(self): + self._test_instance_with_one_port_prov_net('macvtap') + self._validate_dhcp_opts() + + +class RebootServerProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Class for SRIOV test that creates VM instances with different SRIOV port + types, reboot those instances and checks their behavior is correct after + reboot + """ + @decorators.idempotent_id('a44deae1-2f84-48fd-a644-faf6390e88e7') + def test_reboot_vm_with_sriov_port_prov_net(self): + for port_type in ('direct', 'direct-physical'): + self._test_instance_with_one_port_prov_net(port_type) + for reboot_type in ('SOFT', 'HARD'): + self.servers_client.reboot_server(self.servers[-1]['id'], + type=reboot_type) + waiters.wait_for_server_status(self.servers_client, + self.servers[-1]['id'], + constants.SERVER_STATUS_ACTIVE) + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + +class UnshelveServerProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Class for SRIOV test that creates VM instances with different SRIOV port + types, shelves those instances, unshelves them and checks their behavior + is correct after unshelve + """ + @decorators.idempotent_id('7b9d6590-c61e-46e1-834b-c02e6be71866') + def test_unshelve_vm_with_sriov_port_prov_net(self): + # TODO(eolivare): remove the skip exception once the BZ is fixed + raise self.skipException("Not supported due to BZ1767797") + offload_time = CONF.compute.shelved_offload_time + for port_type in ('direct', 'direct-physical'): + self._test_instance_with_one_port_prov_net(port_type) + self.servers_client.shelve_server(self.servers[-1]['id']) + waiters.wait_for_server_status(self.servers_client, + self.servers[-1]['id'], + 'SHELVED_OFFLOADED', + extra_timeout=offload_time) + self.servers_client.unshelve_server(self.servers[-1]['id']) + waiters.wait_for_server_status(self.servers_client, + self.servers[-1]['id'], + constants.SERVER_STATUS_ACTIVE) + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + +class ReusePortProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Class for SRIOV test that creates VM instances with different SRIOV port + types, delete those instances and reuse the existing SRIOV ports on new VM + instances + """ + @decorators.idempotent_id('9ad4aed9-0985-49fa-886e-fa6286a26492') + def test_reuse_sriov_port_prov_net(self): + # TODO(eolivare): only VF ports supported at this moment + # for port_type in ('direct', 'direct-physical'): + for port_type in ('direct',): + self._test_instance_with_one_port_prov_net(port_type) + old_port_id = self.ports[-1]['id'] + old_num_ports = len(self.ports) + # delete the server and create a new server attached to the + # existing port + self.servers_client.delete_server(self.servers[-1]['id']) + waiters.wait_for_server_termination(self.servers_client, + self.servers[-1]['id']) + del self.servers[-1] + # validate port is not reachable anymore + self.assertRaises( + lib_exc.SSHTimeout, + self.check_connectivity, + host=self.ports[-1]['fixed_ips'][0]['ip_address'], + ssh_user=self.username, + ssh_key=self.keypair['private_key'], + ssh_timeout=10) + # create a new server reusing the existing port + self._test_create_instance_with_network_port( + port_type, reuse_port=True) + # check no new ports have been created + self.assertEqual(old_num_ports, len(self.ports)) + self.assertEqual(old_port_id, self.ports[-1]['id']) + # validate port is reachable again + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + @decorators.idempotent_id('6a382f97-50ea-4a1b-b012-aaa955f1a51a') + def test_detach_attach_sriov_port_prov_net(self): + interfaces_client = self.os_adm.interfaces_client + # TODO(eolivare): only VF ports supported at this moment + # for port_type in ('direct', 'direct-physical'): + for port_type in ('direct',): + self._test_instance_with_one_port_prov_net(port_type) + old_port_id = self.ports[-1]['id'] + old_num_ports = len(self.ports) + # remove port from server + req_id = interfaces_client.delete_interface( + self.servers[-1]['id'], + self.ports[-1]['id']).response['x-openstack-request-id'] + # wait until port is really removed + waiters.wait_for_interface_detach( + self.servers_client, + self.servers[-1]['id'], + self.ports[-1]['id'], + req_id) + # validate port is not reachable anymore + self.assertRaises( + lib_exc.SSHTimeout, + self.check_connectivity, + host=self.ports[-1]['fixed_ips'][0]['ip_address'], + ssh_user=self.username, + ssh_key=self.keypair['private_key'], + ssh_timeout=10) + # reattach port on a new server (reattaching to a running VM is + # not supported) + self._test_create_instance_with_network_port( + port_type, reuse_port=True) + # check no new ports have been created + self.assertEqual(old_num_ports, len(self.ports)) + self.assertEqual(old_port_id, self.ports[-1]['id']) + # validate port is reachable again + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + +class NegativeProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Assuming we have hosts with one SRIOV port and it is already occupied, + then creation of VM with VF or PF port should fail + We create a VM with PF port on each compute node in order to occupy SRIOV + port; creating VM with PF or VF should fail as there is no available port + """ + @decorators.idempotent_id('3cf78b03-c88d-41c3-a891-d446518aca9f') + def test_try_create_instance_with_sriov_port(self): + for i in range(CONF.compute.min_compute_nodes * + WB_CONF.sriov_pfs_per_host): + self._test_create_instance_with_network_port('direct-physical') + for port_type in ('direct-physical', 'direct'): + vm_name = 'vm-%s-error' % port_type + self.assertRaises( + exceptions.BuildErrorException, + self._test_create_instance_with_network_port, + port_type=port_type, + vm_name=vm_name) + + +class TwoServersProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Base class for SRIOV tests creating two VMs with one SRIOV port on + provisioning networks + """ + + def _test_create_two_instances_with_ports(self, port_type1, port_type2): + self._test_create_instance_with_network_port(port_type1) + ip1 = self.ports[-1]['fixed_ips'][0]['ip_address'] + self._test_create_instance_with_network_port(port_type2) + ip2 = self.ports[-1]['fixed_ips'][0]['ip_address'] + for ip in (ip1, ip2): + self.check_connectivity(ip, + self.username, + self.keypair['private_key']) + server_ssh_client = remote_client.RemoteClient( + ip1, + self.username, + pkey=self.keypair['private_key'], server=self.servers[0]) + server_ssh_client.ping_host(ip2) + + +class NormalAndOtherServersProvNetSriovTest(TwoServersProvNetSriovTest): + """The following tests can be executed without needing to cleanup between + them because in total one normal, one VF and one PF ports are created + """ + @decorators.idempotent_id('61a19404-b819-4451-b4a6-d834bed6f3e9') + def test_create_instances_with_normal_normal_ports_prov_net(self): + self._test_create_two_instances_with_ports('normal', 'normal') + + @decorators.idempotent_id('060c08ed-dc11-41e3-a2aa-1b28702986f2') + def test_create_instances_with_normal_vf_ports_prov_net(self): + self._test_create_two_instances_with_ports('direct', 'normal') + + @decorators.idempotent_id('c12bee5f-7d11-425c-b75e-1a198793d7b0') + def test_create_instances_with_normal_pf_ports_prov_net(self): + self._test_create_two_instances_with_ports('direct-physical', 'normal') + + +class VfServersProvNetSriovTest(TwoServersProvNetSriovTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + @decorators.idempotent_id('97c176c7-f1a0-45d5-879f-e8715b320d06') + def test_create_instances_with_vf_vf_ports_prov_net(self): + self._test_create_two_instances_with_ports('direct', 'direct') + + +class VfAndPfServersProvNetSriovTest(TwoServersProvNetSriovTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + @decorators.idempotent_id('98f8b841-8193-4b68-90e5-030879cbf774') + def test_create_instances_with_vf_pf_ports_prov_net(self): + self._test_create_two_instances_with_ports('direct', 'direct-physical') + + +class PfServersProvNetSriovTest(TwoServersProvNetSriovTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after it) + """ + @decorators.idempotent_id('935fb660-763f-4393-a780-f519c8f7dc95') + def test_create_instances_with_pf_pf_ports_prov_net(self): + self._test_create_two_instances_with_ports('direct-physical', + 'direct-physical') + + +class BaseMigrationProvNetSriovTest(ProviderNetworkSriovBaseTest): + """Class for SRIOV test that verify that connectivity with SRIOV ports is + correct after the server where this port was attached has been migrated to + a different compute node + """ + + def _test_migration_with_sriov_port_prov_net(self, migration_method): + # TODO(eolivare): PF ports do not support neither cold nor live + # migration at this moment - see BZ1851417 and BZ1745842 + # for port_type in ('direct', 'macvtap', 'direct-physical'): + for port_type in ('direct', 'macvtap'): + self._test_instance_with_one_port_prov_net(port_type) + server_id = self.servers[-1]['id'] + # get the host where the VM is running + host = self.servers_client.show_server( + server_id)['server']['OS-EXT-SRV-ATTR:host'] + # migrate the VM + if migration_method == 'cold-migration': + self.servers_client.migrate_server(server_id) + waiters.wait_for_server_status(self.servers_client, + server_id, + 'VERIFY_RESIZE') + # confirm migration + self.servers_client.confirm_resize_server(server_id) + elif migration_method == 'live-migration': + block_migration = (CONF.compute_feature_enabled. + block_migration_for_live_migration) + self.servers_client.live_migrate_server( + server_id, host=None, + block_migration=block_migration, disk_over_commit=False) + else: + raise RuntimeError('Unsupported migration method %s' + % migration_method) + # wait until server status is active + waiters.wait_for_server_status(self.servers_client, + server_id, + constants.SERVER_STATUS_ACTIVE) + # get the host where the VM after the migration + new_host = self.servers_client.show_server( + server_id)['server']['OS-EXT-SRV-ATTR:host'] + self.assertNotEqual(host, new_host, 'VM did not migrate') + # check port connectivity and status after migration + waiters.wait_for_interface_status(self.os_adm.interfaces_client, + server_id, + self.ports[-1]['id'], + lib_constants.PORT_STATUS_ACTIVE) + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + # delete the server before next iteration + # otherwise PF server could not be migrated + self.servers_client.delete_server(server_id) + waiters.wait_for_server_termination(self.servers_client, + server_id) + del self.servers[-1] + + +class ColdMigrationProvNetSriovTest(BaseMigrationProvNetSriovTest): + """Class for SRIOV test that verify that connectivity with SRIOV ports is + correct after the server where this port was attached has been migrated to + a different compute node, by means of cold migration procedure + """ + @decorators.idempotent_id('339f3bf7-48a6-461c-99d7-2b3b49126e65') + @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, + 'Cold migration is not available.') + def test_cold_migration_with_sriov_port_prov_net(self): + self._test_migration_with_sriov_port_prov_net('cold-migration') + + +class LiveMigrationProvNetSriovTest(BaseMigrationProvNetSriovTest): + """Class for SRIOV test that verify that connectivity with SRIOV ports is + correct after the server where this port was attached has been migrated to + a different compute node, by means of live migration procedure + """ + @decorators.idempotent_id('1a261026-3ef9-4254-a309-37e9db3dc33a') + @testtools.skipUnless(CONF.compute_feature_enabled.live_migration, + 'Live migration is not available.') + def test_live_migration_with_sriov_port_prov_net(self): + self._test_migration_with_sriov_port_prov_net('live-migration') + + +class AdminStateProvNetSriovTest(BaseMigrationProvNetSriovTest): + """Class for SRIOV test that verify that both SRIOV agents and ports + behave properly when admin state is set to UP or DOWN + """ + @decorators.idempotent_id('f884cb13-05e0-48a1-a393-20fc47bc458e') + def test_sriov_agent_admin_state_with_sriov_port_prov_net(self): + # TODO(eolivare): not supported due to LP1892741 (both ovs and ovn) + raise self.skipException("Not supported due to LP1892741") + for port_type in ('direct', 'direct-physical'): + self._test_instance_with_one_port_prov_net(port_type) + # get all SRIOV agents + sriov_agents = self.client.list_agents( + binary='neutron-sriov-nic-agent')['agents'] + # set agents admin-state to DOWN + state_up = {"admin_state_up": True} + state_down = {"admin_state_up": False} + for sriov_agent in sriov_agents: + self.addCleanup(self.client.update_agent, + sriov_agent['id'], agent_info=state_up) + self.client.update_agent(sriov_agent['id'], + agent_info=state_down) + server_id = self.servers[-1]['id'] + port_id = self.ports[-1]['id'] + # check port connectivity and status after update + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + waiters.wait_for_interface_status(self.os_adm.interfaces_client, + server_id, + port_id, + lib_constants.PORT_STATUS_ACTIVE) + vm_name = 'vm-%s-error' % port_type + self.assertRaises( + exceptions.BuildErrorException, + self._test_create_instance_with_network_port, + port_type=port_type, + vm_name=vm_name) + # set agents admin-state to UP before next iteration + for sriov_agent in sriov_agents: + self.client.update_agent(sriov_agent['id'], + agent_info=state_up) + + @decorators.idempotent_id('d1599884-c507-4724-ae17-c5034b532543') + def test_port_admin_state_with_sriov_port_prov_net(self): + # TODO(eolivare): only VF ports supported at this moment + # for port_type in ('direct', 'direct-physical'): + for port_type in ('direct',): + self._test_instance_with_one_port_prov_net(port_type) + # set port admin-state to DOWN + server_id = self.servers[-1]['id'] + port_id = self.ports[-1]['id'] + self.client.update_port(port_id, admin_state_up=False) + # check port connectivity and status after update + waiters.wait_for_interface_status(self.os_adm.interfaces_client, + server_id, + port_id, + lib_constants.PORT_STATUS_DOWN) + # validate port is not reachable + # it might take a moment until the port becomes unreachable + time.sleep(.5) + self.assertRaises( + lib_exc.SSHTimeout, + self.check_connectivity, + host=self.ports[-1]['fixed_ips'][0]['ip_address'], + ssh_user=self.username, + ssh_key=self.keypair['private_key'], + ssh_timeout=10) + # set port admin-state to UP + self.client.update_port(port_id, admin_state_up=True) + # check port connectivity and status after update + waiters.wait_for_interface_status(self.os_adm.interfaces_client, + server_id, + port_id, + lib_constants.PORT_STATUS_ACTIVE) + # validate port is reachable again + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + + +class TwoPortsServersProvNetSriovTest(ProviderNetworkSriovBaseTest): + """VM instances are created with two ports: + - an SRIOV VF port on a provider network + - a normal port on a tenant network + """ + @decorators.idempotent_id('21ffa191-26a7-4f83-99f6-56ed09b4116a') + def test_create_two_instances_with_two_ports(self): + port_types = ['direct', 'normal'] + int_cidr = netaddr.IPNetwork('10.100.0.0/16') + self._test_create_instance_with_two_ports(port_types, int_cidr) + ext_ip1 = self.ports[-2]['fixed_ips'][0]['ip_address'] + self._test_create_instance_with_two_ports( + port_types, int_cidr, reused_tenant_net=self.networks[-1]) + ext_ip2 = self.ports[-2]['fixed_ips'][0]['ip_address'] + # check connectivity with external IPs (SRIOV ports connected to + # provider network) + for ip in (ext_ip1, ext_ip2): + self.check_connectivity(ip, + self.username, + self.keypair['private_key']) + # check connectivity from both VMs to all 4 ports + for ip in (ext_ip1, ext_ip2): + server_ssh_client = remote_client.RemoteClient( + ip, + self.username, + pkey=self.keypair['private_key']) + for port in self.ports: + fixed_ip = port['fixed_ips'][0]['ip_address'] + server_ssh_client.ping_host(fixed_ip) diff --git a/whitebox_neutron_tempest_plugin/tests/scenario/test_sriov_routed_provider_networks.py b/whitebox_neutron_tempest_plugin/tests/scenario/test_sriov_routed_provider_networks.py new file mode 100644 index 0000000..98e53e9 --- /dev/null +++ b/whitebox_neutron_tempest_plugin/tests/scenario/test_sriov_routed_provider_networks.py @@ -0,0 +1,484 @@ +# Copyright 2024 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from neutron_tempest_plugin import config +from neutron_tempest_plugin.scenario import constants +from oslo_log import log +from tempest.common.utils.linux import remote_client +from tempest.common import waiters +from tempest import exceptions +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +from whitebox_neutron_tempest_plugin.tests.scenario import \ + test_sriov_provider_network + + +CONF = config.CONF +WB_CONF = config.CONF.whitebox_neutron_plugin_options +LOG = log.getLogger(__name__) + + +class RoutedProviderNetworkSriovBaseTest( + test_sriov_provider_network.ProviderNetworkSriovBaseTest): + """Base class for tests using provisioning networks + These tests need the same deployment: + - datacentre with controllers and 1 compute over subnet 1 + - leaf1 1 computes over subnet 2 + and the next preconfiguration: + - Admin user is needed to create ports on the existing provisioning network + - 2 network segments called segment1, segment2 + - 2 subnets segment1(subnet1), segment2(subnet2) + - 2 AZs segment1(central site), segment2(leaf1) + """ + + @classmethod + def skip_checks(cls): + super(RoutedProviderNetworkSriovBaseTest, cls).skip_checks() + if not WB_CONF.provroutednetwork_support: + raise cls.skipException( + "Skipped since according to provroutednetwork_support config " + "value Provider Routed Networks are not supported") + + def _test_create_instance_with_network_port(self, port_type, + subnet_id='1', + vm_name=None, + reuse_port=False): + # Create vm in segmentX, az segmentX, subnet segmentX + # Vm created directly over the external network + # az segmentX + # X=subnet_id + security_groups, port, user_data, config_drive = \ + self._create_network_port(port_type, + reuse_port=reuse_port, + use_provider_net=True, + subnet_id=subnet_id) + vm_name = vm_name or "vm-" + port_type + "-" + self._testMethodName + subnet_name = 'segment' + subnet_id + self._create_server( + security_groups=security_groups, networks=[port], + user_data=user_data, config_drive=config_drive, + availability_zone=subnet_name, name=vm_name) + + def _test_instance_with_one_port_prov_net(self, port_type, subnet_id='1'): + # Create vm in segmentX, az segmentX, subnet segmentX + # Vm created over a pre created port in subnet + # segmentX and az segmentX + # X=subnet_id + self._test_create_instance_with_network_port(port_type, subnet_id) + self.check_connectivity(self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key'], ssh_timeout=60) + self.check_port_status(port_type) + + +class OneServerRoutedProvNetSriovTest(RoutedProviderNetworkSriovBaseTest): + """Class for SRIOV tests creating one VM with one SRIOV port on routed + provider networks + These tests can be executed without needing to cleanup VMs or ports between + them + """ + + @decorators.idempotent_id('57bc00f2-d166-41c1-8a87-600bb087f248') + def test_create_instance_with_normal_port_prov_net_seg1(self): + # Create vm in datacentre network, segment1, az segment1, + # subnet segment1 + # Vm created over a pre created port in subnet + # segment1 and az segment1 + self._test_instance_with_one_port_prov_net('normal', '1') + + @decorators.idempotent_id('d6356ffd-e521-48d2-bc69-13f2cd72690a') + def test_create_instance_with_normal_port_prov_net_seg2(self): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + self._test_instance_with_one_port_prov_net('normal', '2') + + @decorators.idempotent_id('aea44620-6e01-4cf1-8b9c-9171717092b0') + def test_create_instance_with_direct_port_prov_net_seg1(self): + # Create vm in datacentre network, segment1, az segment1, + # subnet segment1 + # Vm created over a pre created port in subnet + # segment1 and az segment1 + self._test_instance_with_one_port_prov_net('direct', '1') + + @decorators.idempotent_id('17e96d5d-c5b6-4731-8027-7810a0761da3') + def test_create_instance_with_direct_port_prov_net_seg2(self): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + self._test_instance_with_one_port_prov_net('direct', '2') + + @decorators.idempotent_id('6d2f7a06-6f41-42e7-a239-f722914914da') + def test_create_instance_with_macvtap_port_prov_net_seg1(self): + # Create vm in datacentre network, segment1, az segment1, + # subnet segment1 + # Vm created over a pre created port in subnet + # segment1 and az segment1 + self._test_instance_with_one_port_prov_net('macvtap', '1') + + @decorators.idempotent_id('955c6502-9cc6-4c7e-bb12-726be03a2c75') + def test_create_instance_with_macvtap_port_prov_net_seg2(self): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + self._test_instance_with_one_port_prov_net('macvtap', '2') + + +class OneServerPfRoutedProvNetSriovTest(RoutedProviderNetworkSriovBaseTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + + @decorators.idempotent_id('18e1fc90-440a-43ea-b269-524064332bb0') + def test_create_instance_with_direct_physical_port_prov_net_seg1(self): + # Create vm in datacentre network, segment1, az segment1, + # subnet segment1 + # Vm created over a pre created port in subnet + # segment1 and az segment1 + self._test_instance_with_one_port_prov_net('direct-physical', '1') + + @decorators.idempotent_id('a9fa54d0-ec91-4d46-978d-48e3cef4492e') + def test_create_instance_with_direct_physical_port_prov_net_seg2(self): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + self._test_instance_with_one_port_prov_net('direct-physical', '2') + + +class RebootServerRoutedProvNetSriovRoutedTest( + RoutedProviderNetworkSriovBaseTest): + """Class for SRIOV test that creates VM instances with different SRIOV port + types, reboot those instances and checks their behavior is correct after + reboot + """ + + @decorators.idempotent_id('43482b46-bb26-422f-96cd-99508eb30a75') + def test_reboot_vm_with_sriov_port_prov_net_seg2(self): + # Create vm in datacentre network, segment1, az segment1, + # subnet segment1 + # Vm created over a pre created port in subnet + # segment1 and az segment1 + # Reboot the vm + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + # Reboot the vm + cont = 0 + for port_type in ('direct', 'direct-physical'): + cont += 1 + self._test_instance_with_one_port_prov_net(port_type, str(cont)) + for reboot_type in ('SOFT', 'HARD'): + self.servers_client.reboot_server(self.servers[-1]['id'], + type=reboot_type) + waiters.wait_for_server_status(self.servers_client, + self.servers[-1]['id'], + constants.SERVER_STATUS_ACTIVE) + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + +class ReusePortRoutedProvNetSriovRoutedTest( + RoutedProviderNetworkSriovBaseTest): + """Class for SRIOV test that creates VM instances with different SRIOV port + types, delete those instances and reuse the existing SRIOV ports on new VM + instances + """ + + @decorators.idempotent_id('eab71091-8a5f-4609-b259-91de74ad8744') + def test_reuse_sriov_port_prov_net_seg2(self): + # TODO(eolivare): only VF ports supported at this moment + # for port_type in ('direct', 'direct-physical'): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + # Delete vm + # Create vm reusing the port + for port_type in ('direct',): + self._test_instance_with_one_port_prov_net(port_type, '2') + old_port_id = self.ports[-1]['id'] + old_num_ports = len(self.ports) + # delete the server and create a new server attached to the + # existing port + self.servers_client.delete_server(self.servers[-1]['id']) + waiters.wait_for_server_termination(self.servers_client, + self.servers[-1]['id']) + del self.servers[-1] + # validate port is not reachable anymore + self.assertRaises( + lib_exc.SSHTimeout, + self.check_connectivity, + host=self.ports[-1]['fixed_ips'][0]['ip_address'], + ssh_user=self.username, + ssh_key=self.keypair['private_key'], + ssh_timeout=10) + + # create a new server reusing the existing port + self._test_create_instance_with_network_port( + port_type, '2', reuse_port=True) + waiters.wait_for_server_status(self.servers_client, + self.servers[-1]['id'], + constants.SERVER_STATUS_ACTIVE) + # check no new ports have been created + self.assertEqual(old_num_ports, len(self.ports)) + self.assertEqual(old_port_id, self.ports[-1]['id']) + # validate port is reachable again + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + @decorators.idempotent_id('b7bed0e4-9c34-4bf1-b411-246345ef7338') + def test_detach_attach_sriov_port_prov_net_seg2(self): + interfaces_client = self.os_adm.interfaces_client + # TODO(eolivare): only VF ports supported at this moment + # for port_type in ('direct', 'direct-physical'): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + # Remove interface from vm + # Create vm reusing the port + for port_type in ('direct',): + self._test_instance_with_one_port_prov_net(port_type, '2') + old_port_id = self.ports[-1]['id'] + old_num_ports = len(self.ports) + # remove port from server + interfaces_client.delete_interface(self.servers[-1]['id'], + self.ports[-1]['id']) + # validate port is not reachable anymore + self.assertRaises( + lib_exc.SSHTimeout, + self.check_connectivity, + host=self.ports[-1]['fixed_ips'][0]['ip_address'], + ssh_user=self.username, + ssh_key=self.keypair['private_key'], + ssh_timeout=10) + + # reattach port on a new server (reattaching to a running VM is + # not supported) + self._test_create_instance_with_network_port( + port_type, '2', reuse_port=True) + waiters.wait_for_server_status(self.servers_client, + self.servers[-1]['id'], + constants.SERVER_STATUS_ACTIVE) + # check no new ports have been created + self.assertEqual(old_num_ports, len(self.ports)) + self.assertEqual(old_port_id, self.ports[-1]['id']) + # validate port is reachable again + self.check_connectivity( + self.ports[-1]['fixed_ips'][0]['ip_address'], + self.username, + self.keypair['private_key']) + self.check_port_status(port_type) + + +class NegativeProvRoutedNetSriovRoutedTest(RoutedProviderNetworkSriovBaseTest): + """Assuming we have hosts with one SRIOV port and it is already occupied, + then creation of VM with VF or PF port should fail + + We create a VM with PF port on each compute node in order to occupy SRIOV + port; creating VM with PF or VF should fail as there is no available port + """ + + @decorators.idempotent_id('94dd5592-3cc9-4636-bad7-077c0b6b5558') + def test_try_create_instance_with_sriov_port_seg2(self): + # Create vm in leaf1 network, segment2, az segment2, + # subnet segment2 + # Vm created over a pre created port in subnet + # segment2 and az segment2 + # Try to create a second vm with vp and pf + self._test_create_instance_with_network_port('direct-physical', '2') + + for port_type in ('direct-physical', 'direct'): + vm_name = 'vm-%s-error' % port_type + self.assertRaises( + exceptions.BuildErrorException, + self._test_create_instance_with_network_port, + port_type=port_type, subnet_id='2', + vm_name=vm_name) + + +class TwoServersProvNetSriovRoutedTest(RoutedProviderNetworkSriovBaseTest): + """Base class for SRIOV tests creating two VMs with one SRIOV port on + routed provider networks + # Create 2 vm: + # 1 segmentX1, az segmentX1, subnet segmentX1 + # 2 segmentX2, az segmentX2, subnet segmentX2 + # Vms created over a pre created port + # az segmentX, subnet segmentX + # X1=subnet_id1 + # X2=subnet_id2 + """ + + def _test_create_two_instances_with_ports_seg(self, + port_type1, segment1, + port_type2, segment2): + self._test_create_instance_with_network_port(port_type1, segment1) + ip1 = self.ports[0]['fixed_ips'][0]['ip_address'] + self._test_create_instance_with_network_port(port_type2, segment2) + ip2 = self.ports[1]['fixed_ips'][0]['ip_address'] + for ip in (ip1, ip2): + self.check_connectivity(ip, + self.username, + self.keypair['private_key']) + server_ssh_client = remote_client.RemoteClient( + ip1, + self.username, + pkey=self.keypair['private_key'], server=self.servers[0]) + server_ssh_client.ping_host(ip2) + + +class TwoPortsTwoServersRoutedProvNetSriovTest( + TwoServersProvNetSriovRoutedTest): + """The following tests can be executed without needing to cleanup between + them because only normal and Vf ports are created + """ + + @decorators.idempotent_id('4523524a-b70e-4f44-9984-f6e622e652d1') + def test_create_instances_with_normal_normal_ports_seg1_seg2(self): + # Create 2 vm: + # 1 segment1, az segment1, subnet segment1 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the different segment(central site and leaf) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('normal', '1', + 'normal', '2') + + @decorators.idempotent_id('b5a96f9f-68b7-4559-b98f-b2c1b3f3a940') + def test_create_instances_with_normal_normal_ports_seg2(self): + # Create 2 vm: + # 1 segment2, az segment2, subnet segment2 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the same segment and compute(leaf1) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('normal', '2', + 'normal', '2') + + @decorators.idempotent_id('6cbfaf93-b3d0-4a52-83c2-69a387e815e1') + def test_create_instances_with_normal_vf_ports_seg1_seg2(self): + # Create 2 vm: + # 1 segment1, az segment1, subnet segment1 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the different segment(central site and leaf) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct', '1', + 'normal', '2') + + @decorators.idempotent_id('b45d44e4-aa13-484a-86d8-19482bdd9c25') + def test_create_instances_with_normal_vf_ports_seg2(self): + # Create 2 vm: + # 1 segment2, az segment2, subnet segment2 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the same segment and compute(leaf1) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct', '2', + 'normal', '2') + + @decorators.idempotent_id('bec613f7-57ab-44ea-879f-e6a21647493a') + def test_create_instances_with_vf_vf_ports_seg1_seg2(self): + # Create 2 vm: + # 1 segment1, az segment1, subnet segment1 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the different segment(central site and leaf) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct', '1', + 'direct', '2') + + +class PfAndPfServersProvNetSriovTest( + TwoServersProvNetSriovRoutedTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + + @decorators.idempotent_id('128f6070-b0fc-4dc3-a815-2aeaf43f2815') + def test_create_instances_with_pf_pf_ports_seg1_seg2(self): + # Create 2 vm: + # 1 segment1, az segment1, subnet segment1 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the different segment(central site and leaf) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct-physical', '1', + 'direct-physical', '2') + + +class VfAndPfServersRoutedProvNetSriovTest( + TwoServersProvNetSriovRoutedTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + + @decorators.idempotent_id('7eecb5f4-2dde-4cf6-bf60-e3663bade307') + def test_create_instances_with_vf_pf_ports_seg1_seg2(self): + # Create 2 vm: + # 1 segment1, az segment1, subnet segment1 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the different segment(central site and leaf) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct-physical', '1', + 'direct', '2') + + +class NormalAndPf1SegServersRoutedProvNetSriovTest( + TwoServersProvNetSriovRoutedTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + + @decorators.idempotent_id('9de6e9c3-b1e1-4509-b97b-dd9800fee2b9') + def test_create_instances_with_normal_pf_ports_seg2(self): + # Create 2 vm: + # 1 segment2, az segment2, subnet segment2 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the same segment and compute(leaf1) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct-physical', '2', + 'normal', '2') + + +class NormalAndPfServersRoutedProvNetSriovTest( + TwoServersProvNetSriovRoutedTest): + """The following test need to be executed isolated (VM and port cleanup is + needed before and after its execution) + """ + + @decorators.idempotent_id('5360a20f-9168-494a-9c32-72841a9f7563') + def test_create_instances_with_normal_pf_ports_seg1_seg2(self): + # Create 2 vm: + # 1 segment1, az segment1, subnet segment1 + # 2 segment2, az segment2, subnet segment2 + # 2 vms in the different segment(central site and leaf) + # Vms created over a pre created port + # az segmentX, subnet segmentX + self._test_create_two_instances_with_ports_seg('direct-physical', '1', + 'normal', '2')