diff options
author | Balazs Gibizer <balazs.gibizer@est.tech> | 2021-07-21 14:07:34 +0200 |
---|---|---|
committer | Balazs Gibizer <balazs.gibizer@est.tech> | 2021-08-21 09:38:44 +0200 |
commit | 1039aa64435341938013ad599356fca1b7b47a4a (patch) | |
tree | bebdacf3a3ee261b7fb345e4f4622f54103725af /nova/tests/functional/test_servers.py | |
parent | 033af941792a9ae510c8d6b2cc318f062f0e1c66 (diff) | |
download | nova-1039aa64435341938013ad599356fca1b7b47a4a.tar.gz |
[func test] move port resource request tests
The functional.test_servers module is already huge. Before this patch
series starts adding more to it we move all the port resource request
related test cases to a new module.
Change-Id: I1aa55cfea23dc787c28baa86732907eb43cba2ea
blueprint: qos-minimum-guaranteed-packet-rate
Diffstat (limited to 'nova/tests/functional/test_servers.py')
-rw-r--r-- | nova/tests/functional/test_servers.py | 2388 |
1 files changed, 0 insertions, 2388 deletions
diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index d20de6385f..1ac8f3adb8 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -21,8 +21,6 @@ import zlib from keystoneauth1 import adapter import mock -from neutronclient.common import exceptions as neutron_exception -import os_resource_classes as orc from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import base64 @@ -37,12 +35,9 @@ from nova.compute import manager as compute_manager from nova.compute import rpcapi as compute_rpcapi from nova import context from nova import exception -from nova.network import constants from nova.network import neutron as neutronapi from nova import objects from nova.objects import block_device as block_device_obj -from nova.policies import base as base_policies -from nova.policies import servers as servers_policies from nova.scheduler import utils from nova import test from nova.tests import fixtures as nova_fixtures @@ -5519,2278 +5514,6 @@ class ServerMovingTestsFromFlatToNested( self._delete_and_check_allocations(server) -class PortResourceRequestBasedSchedulingTestBase( - integrated_helpers.ProviderUsageBaseTestCase): - - compute_driver = 'fake.FakeDriverWithPciResources' - - CUSTOM_VNIC_TYPE_NORMAL = 'CUSTOM_VNIC_TYPE_NORMAL' - CUSTOM_VNIC_TYPE_DIRECT = 'CUSTOM_VNIC_TYPE_DIRECT' - CUSTOM_VNIC_TYPE_MACVTAP = 'CUSTOM_VNIC_TYPE_MACVTAP' - CUSTOM_PHYSNET1 = 'CUSTOM_PHYSNET1' - CUSTOM_PHYSNET2 = 'CUSTOM_PHYSNET2' - CUSTOM_PHYSNET3 = 'CUSTOM_PHYSNET3' - PF1 = 'pf1' - PF2 = 'pf2' - PF3 = 'pf3' - - def setUp(self): - # enable PciPassthroughFilter to support SRIOV before the base class - # starts the scheduler - if 'PciPassthroughFilter' not in CONF.filter_scheduler.enabled_filters: - self.flags( - enabled_filters=CONF.filter_scheduler.enabled_filters + - ['PciPassthroughFilter'], - group='filter_scheduler') - - self.useFixture( - fake.FakeDriverWithPciResources. - FakeDriverWithPciResourcesConfigFixture()) - - super(PortResourceRequestBasedSchedulingTestBase, self).setUp() - # Make ComputeManager._allocate_network_async synchronous to detect - # errors in tests that involve rescheduling. - self.useFixture(nova_fixtures.SpawnIsSynchronousFixture()) - self.compute1 = self._start_compute('host1') - self.compute1_rp_uuid = self._get_provider_uuid_by_host('host1') - self.compute1_service_id = self.admin_api.get_services( - host='host1', binary='nova-compute')[0]['id'] - self.ovs_bridge_rp_per_host = {} - self.sriov_dev_rp_per_host = {} - self.flavor = self.api.get_flavors()[0] - self.flavor_with_group_policy = self.api.get_flavors()[1] - - # Setting group policy for placement. This is mandatory when more than - # one request group is included in the allocation candidate request and - # we have tests with two ports both having resource request modelled as - # two separate request groups. - self.admin_api.post_extra_spec( - self.flavor_with_group_policy['id'], - {'extra_specs': {'group_policy': 'isolate'}}) - - self._create_networking_rp_tree('host1', self.compute1_rp_uuid) - - # add extra ports and the related network to the neutron fixture - # specifically for these tests. It cannot be added globally in the - # fixture init as it adds a second network that makes auto allocation - # based test to fail due to ambiguous networks. - self.neutron._ports[ - self.neutron.port_with_sriov_resource_request['id']] = \ - copy.deepcopy(self.neutron.port_with_sriov_resource_request) - self.neutron._ports[self.neutron.sriov_port['id']] = \ - copy.deepcopy(self.neutron.sriov_port) - self.neutron._networks[ - self.neutron.network_2['id']] = self.neutron.network_2 - self.neutron._subnets[ - self.neutron.subnet_2['id']] = self.neutron.subnet_2 - macvtap = self.neutron.port_macvtap_with_resource_request - self.neutron._ports[macvtap['id']] = copy.deepcopy(macvtap) - - def assertComputeAllocationMatchesFlavor( - self, allocations, compute_rp_uuid, flavor): - compute_allocations = allocations[compute_rp_uuid]['resources'] - self.assertEqual( - self._resources_from_flavor(flavor), - compute_allocations) - - def _create_server(self, flavor, networks, host=None): - server_req = self._build_server( - image_uuid='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', - flavor_id=flavor['id'], - networks=networks, - host=host) - return self.api.post_server({'server': server_req}) - - def _set_provider_inventories(self, rp_uuid, inventories): - rp = self.placement.get( - '/resource_providers/%s' % rp_uuid).body - inventories['resource_provider_generation'] = rp['generation'] - return self._update_inventory(rp_uuid, inventories) - - def _create_ovs_networking_rp_tree(self, compute_rp_uuid): - # we need uuid sentinel for the test to make pep8 happy but we need a - # unique one per compute so here is some ugliness - ovs_agent_rp_uuid = getattr(uuids, compute_rp_uuid + 'ovs agent') - agent_rp_req = { - "name": ovs_agent_rp_uuid, - "uuid": ovs_agent_rp_uuid, - "parent_provider_uuid": compute_rp_uuid - } - self.placement.post('/resource_providers', - body=agent_rp_req, - version='1.20') - ovs_bridge_rp_uuid = getattr(uuids, ovs_agent_rp_uuid + 'ovs br') - ovs_bridge_req = { - "name": ovs_bridge_rp_uuid, - "uuid": ovs_bridge_rp_uuid, - "parent_provider_uuid": ovs_agent_rp_uuid - } - self.placement.post('/resource_providers', - body=ovs_bridge_req, - version='1.20') - self.ovs_bridge_rp_per_host[compute_rp_uuid] = ovs_bridge_rp_uuid - - self._set_provider_inventories( - ovs_bridge_rp_uuid, - {"inventories": { - orc.NET_BW_IGR_KILOBIT_PER_SEC: {"total": 10000}, - orc.NET_BW_EGR_KILOBIT_PER_SEC: {"total": 10000}, - }}) - - self._create_trait(self.CUSTOM_VNIC_TYPE_NORMAL) - self._create_trait(self.CUSTOM_PHYSNET2) - - self._set_provider_traits( - ovs_bridge_rp_uuid, - [self.CUSTOM_VNIC_TYPE_NORMAL, self.CUSTOM_PHYSNET2]) - - def _create_pf_device_rp( - self, device_rp_uuid, parent_rp_uuid, inventories, traits, - device_rp_name=None): - """Create a RP in placement for a physical function network device with - traits and inventories. - """ - - if not device_rp_name: - device_rp_name = device_rp_uuid - - sriov_pf_req = { - "name": device_rp_name, - "uuid": device_rp_uuid, - "parent_provider_uuid": parent_rp_uuid - } - self.placement.post('/resource_providers', - body=sriov_pf_req, - version='1.20') - - self._set_provider_inventories( - device_rp_uuid, - {"inventories": inventories}) - - for trait in traits: - self._create_trait(trait) - - self._set_provider_traits( - device_rp_uuid, - traits) - - def _create_sriov_networking_rp_tree(self, hostname, compute_rp_uuid): - # Create a matching RP tree in placement for the PCI devices added to - # the passthrough_whitelist config during setUp() and PCI devices - # present in the FakeDriverWithPciResources virt driver. - # - # * PF1 represents the PCI device 0000:01:00, it will be mapped to - # physnet1 and it will have bandwidth inventory. - # * PF2 represents the PCI device 0000:02:00, it will be mapped to - # physnet2 it will have bandwidth inventory. - # * PF3 represents the PCI device 0000:03:00 and, it will be mapped to - # physnet2 but it will not have bandwidth inventory. - self.sriov_dev_rp_per_host[compute_rp_uuid] = {} - - sriov_agent_rp_uuid = getattr(uuids, compute_rp_uuid + 'sriov agent') - agent_rp_req = { - "name": "%s:NIC Switch agent" % hostname, - "uuid": sriov_agent_rp_uuid, - "parent_provider_uuid": compute_rp_uuid - } - self.placement.post('/resource_providers', - body=agent_rp_req, - version='1.20') - dev_rp_name_prefix = ("%s:NIC Switch agent:" % hostname) - - sriov_pf1_rp_uuid = getattr(uuids, sriov_agent_rp_uuid + 'PF1') - self.sriov_dev_rp_per_host[ - compute_rp_uuid][self.PF1] = sriov_pf1_rp_uuid - - inventories = { - orc.NET_BW_IGR_KILOBIT_PER_SEC: {"total": 100000}, - orc.NET_BW_EGR_KILOBIT_PER_SEC: {"total": 100000}, - } - traits = [self.CUSTOM_VNIC_TYPE_DIRECT, self.CUSTOM_PHYSNET1] - self._create_pf_device_rp( - sriov_pf1_rp_uuid, sriov_agent_rp_uuid, inventories, traits, - device_rp_name=dev_rp_name_prefix + "%s-ens1" % hostname) - - sriov_pf2_rp_uuid = getattr(uuids, sriov_agent_rp_uuid + 'PF2') - self.sriov_dev_rp_per_host[ - compute_rp_uuid][self.PF2] = sriov_pf2_rp_uuid - inventories = { - orc.NET_BW_IGR_KILOBIT_PER_SEC: {"total": 100000}, - orc.NET_BW_EGR_KILOBIT_PER_SEC: {"total": 100000}, - } - traits = [self.CUSTOM_VNIC_TYPE_DIRECT, self.CUSTOM_VNIC_TYPE_MACVTAP, - self.CUSTOM_PHYSNET2] - self._create_pf_device_rp( - sriov_pf2_rp_uuid, sriov_agent_rp_uuid, inventories, traits, - device_rp_name=dev_rp_name_prefix + "%s-ens2" % hostname) - - sriov_pf3_rp_uuid = getattr(uuids, sriov_agent_rp_uuid + 'PF3') - self.sriov_dev_rp_per_host[ - compute_rp_uuid][self.PF3] = sriov_pf3_rp_uuid - inventories = {} - traits = [self.CUSTOM_VNIC_TYPE_DIRECT, self.CUSTOM_PHYSNET2] - self._create_pf_device_rp( - sriov_pf3_rp_uuid, sriov_agent_rp_uuid, inventories, traits, - device_rp_name=dev_rp_name_prefix + "%s-ens3" % hostname) - - def _create_networking_rp_tree(self, hostname, compute_rp_uuid): - # let's simulate what the neutron would do - self._create_ovs_networking_rp_tree(compute_rp_uuid) - self._create_sriov_networking_rp_tree(hostname, compute_rp_uuid) - - def assertPortMatchesAllocation(self, port, allocations): - port_request = port[constants.RESOURCE_REQUEST]['resources'] - for rc, amount in allocations.items(): - self.assertEqual(port_request[rc], amount, - 'port %s requested %d %s ' - 'resources but got allocation %d' % - (port['id'], port_request[rc], rc, - amount)) - - def _create_server_with_ports(self, *ports): - server = self._create_server( - flavor=self.flavor_with_group_policy, - networks=[{'port': port['id']} for port in ports], - host='host1') - return self._wait_for_state_change(server, 'ACTIVE') - - def _check_allocation( - self, server, compute_rp_uuid, non_qos_port, qos_port, - qos_sriov_port, flavor, migration_uuid=None, - source_compute_rp_uuid=None, new_flavor=None): - - updated_non_qos_port = self.neutron.show_port( - non_qos_port['id'])['port'] - updated_qos_port = self.neutron.show_port(qos_port['id'])['port'] - updated_qos_sriov_port = self.neutron.show_port( - qos_sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # if there is new_flavor then we either have an in progress resize or - # a confirmed resize. In both cases the instance allocation should be - # according to the new_flavor - current_flavor = (new_flavor if new_flavor else flavor) - - # We expect one set of allocations for the compute resources on the - # compute rp and two sets for the networking resources one on the ovs - # bridge rp due to the qos_port resource request and one one the - # sriov pf2 due to qos_sriov_port resource request - self.assertEqual(3, len(allocations)) - self.assertComputeAllocationMatchesFlavor( - allocations, compute_rp_uuid, current_flavor) - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[compute_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(qos_port, ovs_allocations) - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[compute_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation(qos_sriov_port, sriov_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - qos_binding_profile = updated_qos_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[compute_rp_uuid], - qos_binding_profile['allocation']) - qos_sriov_binding_profile = updated_qos_sriov_port['binding:profile'] - self.assertEqual(self.sriov_dev_rp_per_host[compute_rp_uuid][self.PF2], - qos_sriov_binding_profile['allocation']) - - # And we expect not to have any allocation set in the port binding for - # the port that doesn't have resource request - self.assertEqual({}, updated_non_qos_port['binding:profile']) - - if migration_uuid: - migration_allocations = self.placement.get( - '/allocations/%s' % migration_uuid).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute rp and two sets for the networking resources one on the - # ovs bridge rp due to the qos_port resource request and one one - # the sriov pf2 due to qos_sriov_port resource request - self.assertEqual(3, len(migration_allocations)) - self.assertComputeAllocationMatchesFlavor( - migration_allocations, source_compute_rp_uuid, flavor) - ovs_allocations = migration_allocations[ - self.ovs_bridge_rp_per_host[ - source_compute_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(qos_port, ovs_allocations) - sriov_allocations = migration_allocations[ - self.sriov_dev_rp_per_host[ - source_compute_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation(qos_sriov_port, sriov_allocations) - - def _delete_server_and_check_allocations( - self, server, qos_port, qos_sriov_port): - self._delete_and_check_allocations(server) - - # assert that unbind removes the allocation from the binding of the - # ports that got allocation during the bind - updated_qos_port = self.neutron.show_port(qos_port['id'])['port'] - binding_profile = updated_qos_port['binding:profile'] - self.assertNotIn('allocation', binding_profile) - updated_qos_sriov_port = self.neutron.show_port( - qos_sriov_port['id'])['port'] - binding_profile = updated_qos_sriov_port['binding:profile'] - self.assertNotIn('allocation', binding_profile) - - def _create_server_with_ports_and_check_allocation( - self, non_qos_normal_port, qos_normal_port, qos_sriov_port): - server = self._create_server_with_ports( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - # check that the server allocates from the current host properly - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - return server - - def _assert_pci_request_pf_device_name(self, server, device_name): - ctxt = context.get_admin_context() - pci_requests = objects.InstancePCIRequests.get_by_instance_uuid( - ctxt, server['id']) - self.assertEqual(1, len(pci_requests.requests)) - self.assertEqual(1, len(pci_requests.requests[0].spec)) - self.assertEqual( - device_name, - pci_requests.requests[0].spec[0]['parent_ifname']) - - -class UnsupportedPortResourceRequestBasedSchedulingTest( - PortResourceRequestBasedSchedulingTestBase): - """Tests for handling servers with ports having resource requests """ - - def _add_resource_request_to_a_bound_port(self, port_id): - # NOTE(gibi): self.neutron._ports contains a copy of each neutron port - # defined on class level in the fixture. So modifying what is in the - # _ports list is safe as it is re-created for each Neutron fixture - # instance therefore for each individual test using that fixture. - bound_port = self.neutron._ports[port_id] - bound_port[constants.RESOURCE_REQUEST] = ( - self.neutron.port_with_resource_request[ - constants.RESOURCE_REQUEST]) - - def test_interface_attach_with_resource_request_old_compute(self): - # create a server - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - # simulate that the compute the instance is running on is older than - # when support is added for attach, older than service version 55 - orig_get_service = objects.Service.get_by_host_and_binary - - def fake_get_service(context, host, binary): - service = orig_get_service(context, host, binary) - service.version = 54 - return service - - with mock.patch( - 'nova.objects.Service.get_by_host_and_binary', - side_effect=fake_get_service - ): - # try to add a port with resource request - post = { - 'interfaceAttachment': { - 'port_id': self.neutron.port_with_resource_request['id'] - }} - ex = self.assertRaises( - client.OpenStackApiException, self.api.attach_interface, - server['id'], post) - self.assertEqual(400, ex.response.status_code) - self.assertIn('Attaching interfaces with QoS policy is ' - 'not supported for instance', - str(ex)) - - @mock.patch('nova.tests.fixtures.NeutronFixture.create_port') - def test_interface_attach_with_network_create_port_has_resource_request( - self, mock_neutron_create_port): - # create a server - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - # the interfaceAttach operation below will result in a new port being - # created in the network that is attached. Make sure that neutron - # returns a port that has resource request. - mock_neutron_create_port.return_value = ( - {'port': copy.deepcopy(self.neutron.port_with_resource_request)}) - - # try to attach a network - post = { - 'interfaceAttachment': { - 'net_id': self.neutron.network_1['id'] - }} - ex = self.assertRaises(client.OpenStackApiException, - self.api.attach_interface, - server['id'], post) - self.assertEqual(400, ex.response.status_code) - self.assertIn('Using networks with QoS policy is not supported for ' - 'instance', - str(ex)) - - @mock.patch('nova.tests.fixtures.NeutronFixture.create_port') - def test_create_server_with_network_create_port_has_resource_request( - self, mock_neutron_create_port): - # the server create operation below will result in a new port being - # created in the network. Make sure that neutron returns a port that - # has resource request. - mock_neutron_create_port.return_value = ( - {'port': copy.deepcopy(self.neutron.port_with_resource_request)}) - - server = self._create_server( - flavor=self.flavor, - networks=[{'uuid': self.neutron.network_1['id']}]) - server = self._wait_for_state_change(server, 'ERROR') - - self.assertEqual(500, server['fault']['code']) - self.assertIn('Failed to allocate the network', - server['fault']['message']) - - def test_create_server_with_port_resource_request_old_microversion(self): - - # NOTE(gibi): 2.71 is the last microversion where nova does not support - # this kind of create server - self.api.microversion = '2.71' - ex = self.assertRaises( - client.OpenStackApiException, self._create_server, - flavor=self.flavor, - networks=[{'port': self.neutron.port_with_resource_request['id']}]) - - self.assertEqual(400, ex.response.status_code) - self.assertIn( - "Creating servers with ports having resource requests, like a " - "port with a QoS minimum bandwidth policy, is not supported " - "until microversion 2.72.", - str(ex)) - - def test_unshelve_not_offloaded_server_with_port_resource_request( - self): - """If the server is not offloaded then unshelving does not cause a new - resource allocation therefore having port resource request is - irrelevant. This test asserts that such unshelve request is not - rejected. - """ - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - # avoid automatic shelve offloading - self.flags(shelved_offload_time=-1) - req = { - 'shelve': {} - } - self.api.post_server_action(server['id'], req) - self._wait_for_server_parameter(server, {'status': 'SHELVED'}) - - # We need to simulate that the above server has a port that has - # resource request; we cannot boot with such a port but legacy servers - # can exist with such a port. - self._add_resource_request_to_a_bound_port(self.neutron.port_1['id']) - - self.api.post_server_action(server['id'], {'unshelve': None}) - self._wait_for_state_change(server, 'ACTIVE') - - -class NonAdminUnsupportedPortResourceRequestBasedSchedulingTest( - UnsupportedPortResourceRequestBasedSchedulingTest): - - def setUp(self): - super( - NonAdminUnsupportedPortResourceRequestBasedSchedulingTest, - self).setUp() - # switch to non admin api - self.api = self.api_fixture.api - self.api.microversion = self.microversion - - # allow non-admin to call the operations - self.policy.set_rules({ - 'os_compute_api:servers:create': '@', - 'os_compute_api:servers:create:attach_network': '@', - 'os_compute_api:servers:show': '@', - 'os_compute_api:os-attach-interfaces': '@', - 'os_compute_api:os-attach-interfaces:create': '@', - 'os_compute_api:os-shelve:shelve': '@', - 'os_compute_api:os-shelve:unshelve': '@', - 'os_compute_api:os-migrate-server:migrate_live': '@', - 'os_compute_api:os-evacuate': '@', - }) - - -class PortResourceRequestBasedSchedulingTest( - PortResourceRequestBasedSchedulingTestBase): - """Tests creating a server with a pre-existing port that has a resource - request for a QoS minimum bandwidth policy. - """ - - def test_boot_server_with_two_ports_one_having_resource_request(self): - non_qos_port = self.neutron.port_1 - qos_port = self.neutron.port_with_resource_request - - server = self._create_server( - flavor=self.flavor, - networks=[{'port': non_qos_port['id']}, - {'port': qos_port['id']}]) - server = self._wait_for_state_change(server, 'ACTIVE') - updated_non_qos_port = self.neutron.show_port( - non_qos_port['id'])['port'] - updated_qos_port = self.neutron.show_port(qos_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the ovs bridge - # rp due to the qos_port resource request - self.assertEqual(2, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - network_allocations = allocations[ - self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(qos_port, network_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - qos_binding_profile = updated_qos_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[self.compute1_rp_uuid], - qos_binding_profile['allocation']) - - # And we expect not to have any allocation set in the port binding for - # the port that doesn't have resource request - self.assertEqual({}, updated_non_qos_port['binding:profile']) - - self._delete_and_check_allocations(server) - - # assert that unbind removes the allocation from the binding of the - # port that got allocation during the bind - updated_qos_port = self.neutron.show_port(qos_port['id'])['port'] - binding_profile = updated_qos_port['binding:profile'] - self.assertNotIn('allocation', binding_profile) - - def test_one_ovs_one_sriov_port(self): - ovs_port = self.neutron.port_with_resource_request - sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server(flavor=self.flavor_with_group_policy, - networks=[{'port': ovs_port['id']}, - {'port': sriov_port['id']}]) - - server = self._wait_for_state_change(server, 'ACTIVE') - - ovs_port = self.neutron.show_port(ovs_port['id'])['port'] - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the ovs bridge - # rp and on the sriov PF rp. - self.assertEqual(3, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor_with_group_policy) - - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]]['resources'] - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[ - self.compute1_rp_uuid][self.PF2]]['resources'] - - self.assertPortMatchesAllocation(ovs_port, ovs_allocations) - self.assertPortMatchesAllocation(sriov_port, sriov_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - ovs_binding = ovs_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[self.compute1_rp_uuid], - ovs_binding['allocation']) - sriov_binding = sriov_port['binding:profile'] - self.assertEqual( - self.sriov_dev_rp_per_host[self.compute1_rp_uuid][self.PF2], - sriov_binding['allocation']) - - def test_interface_attach_with_resource_request(self): - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - # start a second compute to show that resources are only allocated from - # the compute the instance currently runs on - self.compute2 = self._start_compute('host2') - self.compute2_rp_uuid = self._get_provider_uuid_by_host('host2') - self._create_networking_rp_tree('host2', self.compute2_rp_uuid) - self.compute2_service_id = self.admin_api.get_services( - host='host2', binary='nova-compute')[0]['id'] - - # attach an OVS port with resource request - ovs_port = self.neutron.port_with_resource_request - post = { - 'interfaceAttachment': { - 'port_id': ovs_port['id'] - }} - self.api.attach_interface(server['id'], post) - - ovs_port = self.neutron.show_port(ovs_port['id'])['port'] - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute RP and one set for the networking resources on the OVS - # bridge RP. - self.assertEqual(2, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(ovs_port, ovs_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - ovs_binding = ovs_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[self.compute1_rp_uuid], - ovs_binding['allocation']) - - # now attach an SRIOV port - sriov_port = self.neutron.port_with_sriov_resource_request - post = { - 'interfaceAttachment': { - 'port_id': sriov_port['id'] - }} - self.api.attach_interface(server['id'], post) - - ovs_port = self.neutron.show_port(ovs_port['id'])['port'] - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute RP and one set each for the networking resources on the OVS - # bridge RP and on the SRIOV PF RP. - self.assertEqual(3, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]]['resources'] - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[ - self.compute1_rp_uuid][self.PF2]]['resources'] - - self.assertPortMatchesAllocation(ovs_port, ovs_allocations) - self.assertPortMatchesAllocation(sriov_port, sriov_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - ovs_binding = ovs_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[self.compute1_rp_uuid], - ovs_binding['allocation']) - sriov_binding = sriov_port['binding:profile'] - self.assertEqual( - self.sriov_dev_rp_per_host[self.compute1_rp_uuid][self.PF2], - sriov_binding['allocation']) - - def test_interface_attach_with_resource_request_no_candidates(self): - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - # attach an OVS port with too big resource request - ovs_port = self.neutron.port_with_resource_request - resources = self.neutron._ports[ - ovs_port['id']]['resource_request']['resources'] - resources['NET_BW_IGR_KILOBIT_PER_SEC'] = 1000000 - - post = { - 'interfaceAttachment': { - 'port_id': ovs_port['id'] - }} - ex = self.assertRaises( - client.OpenStackApiException, self.api.attach_interface, - server['id'], post) - - self.assertEqual(400, ex.response.status_code) - self.assertIn('Failed to allocate additional resources', str(ex)) - - def test_interface_attach_with_resource_request_pci_claim_fails(self): - # boot a server with a single SRIOV port that has no resource request - sriov_port = self.neutron.sriov_port - server = self._create_server( - flavor=self.flavor, - networks=[{'port': sriov_port['id']}]) - - self._wait_for_state_change(server, 'ACTIVE') - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - sriov_binding = sriov_port['binding:profile'] - - # We expect that this consume the last available VF from the PF2 - self.assertEqual( - fake.FakeDriverWithPciResources.PCI_ADDR_PF2_VF1, - sriov_binding['pci_slot']) - - # Now attach a second port to this server that has resource request - # At this point PF2 has available bandwidth but no available VF - # and PF3 has available VF but no available bandwidth so we expect - # the attach to fail. - sriov_port_with_res_req = self.neutron.port_with_sriov_resource_request - post = { - 'interfaceAttachment': { - 'port_id': sriov_port_with_res_req['id'] - }} - ex = self.assertRaises( - client.OpenStackApiException, self.api.attach_interface, - server['id'], post) - - self.assertEqual(400, ex.response.status_code) - self.assertIn('Failed to claim PCI device', str(ex)) - - sriov_port_with_res_req = self.neutron.show_port( - sriov_port_with_res_req['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect only one allocations that is on the compute RP as the - # allocation made towards the PF2 RP has been rolled back when the PCI - # claim failed - self.assertEqual([self.compute1_rp_uuid], list(allocations)) - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - # We expect that the port binding is not updated with any RP uuid as - # the attach failed. - sriov_binding = sriov_port_with_res_req['binding:profile'] - self.assertNotIn('allocation', sriov_binding) - - def test_interface_attach_sriov_with_qos_pci_update_fails(self): - # Update the name of the network device RP of PF2 on host2 to something - # unexpected. This will cause - # update_pci_request_spec_with_allocated_interface_name() to raise - # when the sriov interface is attached. - rsp = self.placement.put( - '/resource_providers/%s' - % self.sriov_dev_rp_per_host[self.compute1_rp_uuid][self.PF2], - {"name": "invalid-device-rp-name"}) - self.assertEqual(200, rsp.status) - - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - sriov_port = self.neutron.port_with_sriov_resource_request - post = { - 'interfaceAttachment': { - 'port_id': sriov_port['id'] - }} - ex = self.assertRaises( - client.OpenStackApiException, self.api.attach_interface, - server['id'], post) - - self.assertEqual(500, ex.response.status_code) - self.assertIn('UnexpectedResourceProviderNameForPCIRequest', str(ex)) - - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect only one allocations that is on the compute RP as the - # allocation made towards the PF2 RP has been rolled back when the PCI - # update failed - self.assertEqual([self.compute1_rp_uuid], list(allocations)) - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - # We expect that the port binding is not updated with any RP uuid as - # the attach failed. - sriov_binding = sriov_port['binding:profile'] - self.assertNotIn('allocation', sriov_binding) - - def test_interface_attach_sriov_with_qos_pci_update_fails_cleanup_fails( - self - ): - # Update the name of the network device RP of PF2 on host2 to something - # unexpected. This will cause - # update_pci_request_spec_with_allocated_interface_name() to raise - # when the sriov interface is attached. - rsp = self.placement.put( - '/resource_providers/%s' - % self.sriov_dev_rp_per_host[self.compute1_rp_uuid][self.PF2], - {"name": "invalid-device-rp-name"}) - self.assertEqual(200, rsp.status) - - server = self._create_server( - flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - sriov_port = self.neutron.port_with_sriov_resource_request - post = { - 'interfaceAttachment': { - 'port_id': sriov_port['id'] - }} - - orig_put = adapter.Adapter.put - - conflict_rsp = fake_requests.FakeResponse( - 409, - jsonutils.dumps( - {'errors': [ - {'code': 'placement.concurrent_update', - 'detail': 'consumer generation conflict'}]})) - - self.adapter_put_call_count = 0 - - def fake_put(_self, url, **kwargs): - self.adapter_put_call_count += 1 - if self.adapter_put_call_count == 1: - # allocation update to add the port resource request - return orig_put(_self, url, **kwargs) - else: - # cleanup calls to remove the port resource allocation - return conflict_rsp - - # this mock makes sure that the placement cleanup will fail with - # conflict - with mock.patch('keystoneauth1.adapter.Adapter.put', new=fake_put): - ex = self.assertRaises( - client.OpenStackApiException, self.api.attach_interface, - server['id'], post) - - self.assertEqual(500, ex.response.status_code) - self.assertIn('AllocationUpdateFailed', str(ex)) - # we have a proper log about the leak - PF_rp_uuid = self.sriov_dev_rp_per_host[ - self.compute1_rp_uuid][self.PF2] - self.assertIn( - "nova.exception.AllocationUpdateFailed: Failed to update " - "allocations for consumer %s. Error: Cannot remove " - "resources {'%s': " - "{'resources': {'NET_BW_EGR_KILOBIT_PER_SEC': 10000, " - "'NET_BW_IGR_KILOBIT_PER_SEC': 10000}}} from the allocation " - "due to multiple successive generation conflicts in " - "placement." % (server['id'], PF_rp_uuid), - self.stdlog.logger.output) - - # assert that we retried the cleanup multiple times - self.assertEqual(5, self.adapter_put_call_count) - - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # As the cleanup failed we leaked allocation in placement - self.assertEqual(2, len(allocations)) - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - sriov_dev_rp = self.sriov_dev_rp_per_host[ - self.compute1_rp_uuid][self.PF2] - sriov_allocations = allocations[sriov_dev_rp]['resources'] - - # this is the leaked allocation in placement - self.assertPortMatchesAllocation(sriov_port, sriov_allocations) - - allocations[sriov_dev_rp].pop('generation') - leaked_allocation = {sriov_dev_rp: allocations[sriov_dev_rp]} - self.assertIn( - f'Failed to update allocations for consumer {server["id"]}. ' - f'Error: Cannot remove resources {leaked_allocation} from the ' - f'allocation due to multiple successive generation conflicts in ' - f'placement. To clean up the leaked resource allocation you can ' - f'use nova-manage placement audit.', - self.stdlog.logger.output) - - # We expect that the port binding is not updated with any RP uuid as - # the attach failed. - sriov_binding = sriov_port['binding:profile'] - self.assertNotIn('allocation', sriov_binding) - - def test_interface_detach_with_port_with_bandwidth_request(self): - port = self.neutron.port_with_resource_request - - # create a server - server = self._create_server( - flavor=self.flavor, - networks=[{'port': port['id']}]) - self._wait_for_state_change(server, 'ACTIVE') - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the ovs bridge - # rp due to the port resource request - self.assertEqual(2, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - network_allocations = allocations[ - self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(port, network_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - updated_port = self.neutron.show_port(port['id'])['port'] - binding_profile = updated_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[self.compute1_rp_uuid], - binding_profile['allocation']) - - self.api.detach_interface( - server['id'], self.neutron.port_with_resource_request['id']) - - self.notifier.wait_for_versioned_notifications( - 'instance.interface_detach.end') - - updated_port = self.neutron.show_port( - self.neutron.port_with_resource_request['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect that the port related resource allocations are removed - self.assertEqual(1, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - # We expect that the allocation is removed from the port too - binding_profile = updated_port['binding:profile'] - self.assertNotIn('allocation', binding_profile) - - def test_delete_bound_port_in_neutron_with_resource_request(self): - """Neutron sends a network-vif-deleted os-server-external-events - notification to nova when a bound port is deleted. Nova detaches the - vif from the server. If the port had a resource allocation then that - allocation is leaked. This test makes sure that 1) an ERROR is logged - when the leak happens. 2) the leaked resource is reclaimed when the - server is deleted. - """ - port = self.neutron.port_with_resource_request - - # create a server - server = self._create_server( - flavor=self.flavor, - networks=[{'port': port['id']}]) - server = self._wait_for_state_change(server, 'ACTIVE') - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the ovs bridge - # rp due to the port resource request - self.assertEqual(2, len(allocations)) - compute_allocations = allocations[self.compute1_rp_uuid]['resources'] - network_allocations = allocations[ - self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]]['resources'] - - self.assertEqual(self._resources_from_flavor(self.flavor), - compute_allocations) - self.assertPortMatchesAllocation(port, network_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - updated_port = self.neutron.show_port(port['id'])['port'] - binding_profile = updated_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[self.compute1_rp_uuid], - binding_profile['allocation']) - - # neutron is faked in the functional test so this test just sends in - # a os-server-external-events notification to trigger the - # detach + ERROR log. - events = { - "events": [ - { - "name": "network-vif-deleted", - "server_uuid": server['id'], - "tag": port['id'], - } - ] - } - response = self.api.api_post('/os-server-external-events', events).body - self.assertEqual(200, response['events'][0]['code']) - - port_rp_uuid = self.ovs_bridge_rp_per_host[self.compute1_rp_uuid] - - # 1) Nova logs an ERROR about the leak - self._wait_for_log( - 'ERROR [nova.compute.manager] The bound port %(port_id)s is ' - 'deleted in Neutron but the resource allocation on the resource ' - 'provider %(rp_uuid)s is leaked until the server %(server_uuid)s ' - 'is deleted.' - % {'port_id': port['id'], - 'rp_uuid': port_rp_uuid, - 'server_uuid': server['id']}) - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # Nova leaks the port allocation so the server still has the same - # allocation before the port delete. - self.assertEqual(2, len(allocations)) - compute_allocations = allocations[self.compute1_rp_uuid]['resources'] - network_allocations = allocations[port_rp_uuid]['resources'] - - self.assertEqual(self._resources_from_flavor(self.flavor), - compute_allocations) - self.assertPortMatchesAllocation(port, network_allocations) - - # 2) Also nova will reclaim the leaked resource during the server - # delete - self._delete_and_check_allocations(server) - - def test_two_sriov_ports_one_with_request_two_available_pfs(self): - """Verify that the port's bandwidth allocated from the same PF as - the allocated VF. - - One compute host: - * PF1 (0000:01:00) is configured for physnet1 - * PF2 (0000:02:00) is configured for physnet2, with 1 VF and bandwidth - inventory - * PF3 (0000:03:00) is configured for physnet2, with 1 VF but without - bandwidth inventory - - One instance will be booted with two neutron ports, both ports - requested to be connected to physnet2. One port has resource request - the other does not have resource request. The port having the resource - request cannot be allocated to PF3 and PF1 while the other port that - does not have resource request can be allocated to PF2 or PF3. - - For the detailed compute host config see the FakeDriverWithPciResources - class. For the necessary passthrough_whitelist config see the setUp of - the PortResourceRequestBasedSchedulingTestBase class. - """ - - sriov_port = self.neutron.sriov_port - sriov_port_with_res_req = self.neutron.port_with_sriov_resource_request - server = self._create_server( - flavor=self.flavor_with_group_policy, - networks=[ - {'port': sriov_port_with_res_req['id']}, - {'port': sriov_port['id']}]) - - server = self._wait_for_state_change(server, 'ACTIVE') - - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - sriov_port_with_res_req = self.neutron.show_port( - sriov_port_with_res_req['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the sriov PF2 - # rp. - self.assertEqual(2, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor_with_group_policy) - - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[ - self.compute1_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation( - sriov_port_with_res_req, sriov_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - sriov_with_req_binding = sriov_port_with_res_req['binding:profile'] - self.assertEqual( - self.sriov_dev_rp_per_host[self.compute1_rp_uuid][self.PF2], - sriov_with_req_binding['allocation']) - # and the port without resource request does not have allocation - sriov_binding = sriov_port['binding:profile'] - self.assertNotIn('allocation', sriov_binding) - - # We expect that the selected PCI device matches with the RP from - # where the bandwidth is allocated from. The bandwidth is allocated - # from 0000:02:00 (PF2) so the PCI device should be a VF of that PF - self.assertEqual( - fake.FakeDriverWithPciResources.PCI_ADDR_PF2_VF1, - sriov_with_req_binding['pci_slot']) - # But also the port that has no resource request still gets a pci slot - # allocated. The 0000:02:00 has no more VF available but 0000:03:00 has - # one VF available and that PF is also on physnet2 - self.assertEqual( - fake.FakeDriverWithPciResources.PCI_ADDR_PF3_VF1, - sriov_binding['pci_slot']) - - def test_one_sriov_port_no_vf_and_bandwidth_available_on_the_same_pf(self): - """Verify that if there is no PF that both provides bandwidth and VFs - then the boot will fail. - """ - - # boot a server with a single sriov port that has no resource request - sriov_port = self.neutron.sriov_port - server = self._create_server( - flavor=self.flavor_with_group_policy, - networks=[{'port': sriov_port['id']}]) - - self._wait_for_state_change(server, 'ACTIVE') - sriov_port = self.neutron.show_port(sriov_port['id'])['port'] - sriov_binding = sriov_port['binding:profile'] - - # We expect that this consume the last available VF from the PF2 - self.assertEqual( - fake.FakeDriverWithPciResources.PCI_ADDR_PF2_VF1, - sriov_binding['pci_slot']) - - # Now boot a second server with a port that has resource request - # At this point PF2 has available bandwidth but no available VF - # and PF3 has available VF but no available bandwidth so we expect - # the boot to fail. - - sriov_port_with_res_req = self.neutron.port_with_sriov_resource_request - server = self._create_server( - flavor=self.flavor_with_group_policy, - networks=[{'port': sriov_port_with_res_req['id']}]) - - # NOTE(gibi): It should be NoValidHost in an ideal world but that would - # require the scheduler to detect the situation instead of the pci - # claim. However that is pretty hard as the scheduler does not know - # anything about allocation candidates (e.g. that the only candidate - # for the port in this case is PF2) it see the whole host as a - # candidate and in our host there is available VF for the request even - # if that is on the wrong PF. - server = self._wait_for_state_change(server, 'ERROR') - self.assertIn( - 'Exceeded maximum number of retries. Exhausted all hosts ' - 'available for retrying build failures for instance', - server['fault']['message']) - - def test_sriov_macvtap_port_with_resource_request(self): - """Verify that vnic type macvtap is also supported""" - - port = self.neutron.port_macvtap_with_resource_request - - server = self._create_server( - flavor=self.flavor, - networks=[{'port': port['id']}]) - - server = self._wait_for_state_change(server, 'ACTIVE') - - port = self.neutron.show_port(port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the sriov PF2 - # rp. - self.assertEqual(2, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, self.compute1_rp_uuid, self.flavor) - - sriov_allocations = allocations[self.sriov_dev_rp_per_host[ - self.compute1_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation( - port, sriov_allocations) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding for the port having resource - # request - port_binding = port['binding:profile'] - self.assertEqual( - self.sriov_dev_rp_per_host[self.compute1_rp_uuid][self.PF2], - port_binding['allocation']) - - # We expect that the selected PCI device matches with the RP from - # where the bandwidth is allocated from. The bandwidth is allocated - # from 0000:02:00 (PF2) so the PCI device should be a VF of that PF - self.assertEqual( - fake.FakeDriverWithPciResources.PCI_ADDR_PF2_VF1, - port_binding['pci_slot']) - - -class ServerMoveWithPortResourceRequestTest( - PortResourceRequestBasedSchedulingTestBase): - - def setUp(self): - # Use our custom weigher defined above to make sure that we have - # a predictable host order in the alternate list returned by the - # scheduler for migration. - self.useFixture(nova_fixtures.HostNameWeigherFixture()) - super(ServerMoveWithPortResourceRequestTest, self).setUp() - self.compute2 = self._start_compute('host2') - self.compute2_rp_uuid = self._get_provider_uuid_by_host('host2') - self._create_networking_rp_tree('host2', self.compute2_rp_uuid) - self.compute2_service_id = self.admin_api.get_services( - host='host2', binary='nova-compute')[0]['id'] - - # create a bigger flavor to use in resize test - self.flavor_with_group_policy_bigger = self.admin_api.post_flavor( - {'flavor': { - 'ram': self.flavor_with_group_policy['ram'], - 'vcpus': self.flavor_with_group_policy['vcpus'], - 'name': self.flavor_with_group_policy['name'] + '+', - 'disk': self.flavor_with_group_policy['disk'] + 1, - }}) - self.admin_api.post_extra_spec( - self.flavor_with_group_policy_bigger['id'], - {'extra_specs': {'group_policy': 'isolate'}}) - - def _test_resize_or_migrate_server_with_qos_ports(self, new_flavor=None): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - if new_flavor: - self.api_fixture.api.post_server_action( - server['id'], {'resize': {"flavorRef": new_flavor['id']}}) - else: - self.api.post_server_action(server['id'], {'migrate': None}) - - self._wait_for_state_change(server, 'VERIFY_RESIZE') - - migration_uuid = self.get_migration_uuid_for_instance(server['id']) - - # check that server allocates from the new host properly - self._check_allocation( - server, self.compute2_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy, - migration_uuid, source_compute_rp_uuid=self.compute1_rp_uuid, - new_flavor=new_flavor) - - self._assert_pci_request_pf_device_name(server, 'host2-ens2') - - self._confirm_resize(server) - - # check that allocation is still OK - self._check_allocation( - server, self.compute2_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy, - new_flavor=new_flavor) - migration_allocations = self.placement.get( - '/allocations/%s' % migration_uuid).body['allocations'] - self.assertEqual({}, migration_allocations) - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_migrate_server_with_qos_ports(self): - self._test_resize_or_migrate_server_with_qos_ports() - - def test_resize_server_with_qos_ports(self): - self._test_resize_or_migrate_server_with_qos_ports( - new_flavor=self.flavor_with_group_policy_bigger) - - def _test_resize_or_migrate_revert_with_qos_ports(self, new_flavor=None): - non_qos_port = self.neutron.port_1 - qos_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_port, qos_port, qos_sriov_port) - - if new_flavor: - self.api_fixture.api.post_server_action( - server['id'], {'resize': {"flavorRef": new_flavor['id']}}) - else: - self.api.post_server_action(server['id'], {'migrate': None}) - - self._wait_for_state_change(server, 'VERIFY_RESIZE') - - migration_uuid = self.get_migration_uuid_for_instance(server['id']) - - # check that server allocates from the new host properly - self._check_allocation( - server, self.compute2_rp_uuid, non_qos_port, qos_port, - qos_sriov_port, self.flavor_with_group_policy, migration_uuid, - source_compute_rp_uuid=self.compute1_rp_uuid, - new_flavor=new_flavor) - - self.api.post_server_action(server['id'], {'revertResize': None}) - self._wait_for_state_change(server, 'ACTIVE') - - # check that allocation is moved back to the source host - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_port, qos_port, - qos_sriov_port, self.flavor_with_group_policy) - - # check that the target host allocation is cleaned up. - self.assertRequestMatchesUsage( - {'VCPU': 0, 'MEMORY_MB': 0, 'DISK_GB': 0, - 'NET_BW_IGR_KILOBIT_PER_SEC': 0, 'NET_BW_EGR_KILOBIT_PER_SEC': 0}, - self.compute2_rp_uuid) - migration_allocations = self.placement.get( - '/allocations/%s' % migration_uuid).body['allocations'] - self.assertEqual({}, migration_allocations) - - self._delete_server_and_check_allocations( - server, qos_port, qos_sriov_port) - - def test_migrate_revert_with_qos_ports(self): - self._test_resize_or_migrate_revert_with_qos_ports() - - def test_resize_revert_with_qos_ports(self): - self._test_resize_or_migrate_revert_with_qos_ports( - new_flavor=self.flavor_with_group_policy_bigger) - - def _test_resize_or_migrate_server_with_qos_port_reschedule_success( - self, new_flavor=None): - self._start_compute('host3') - compute3_rp_uuid = self._get_provider_uuid_by_host('host3') - self._create_networking_rp_tree('host3', compute3_rp_uuid) - - non_qos_port = self.neutron.port_1 - qos_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_port, qos_port, qos_sriov_port) - - # Yes this isn't great in a functional test, but it's simple. - original_prep_resize = compute_manager.ComputeManager._prep_resize - - prep_resize_calls = [] - - def fake_prep_resize(_self, *args, **kwargs): - # Make the first prep_resize fail and the rest passing through - # the original _prep_resize call - if not prep_resize_calls: - prep_resize_calls.append(_self.host) - raise test.TestingException('Simulated prep_resize failure.') - prep_resize_calls.append(_self.host) - original_prep_resize(_self, *args, **kwargs) - - # The patched compute manager will raise from _prep_resize on the - # first host of the migration. Then the migration - # is reschedule on the other host where it will succeed - with mock.patch.object( - compute_manager.ComputeManager, '_prep_resize', - new=fake_prep_resize): - if new_flavor: - self.api_fixture.api.post_server_action( - server['id'], {'resize': {"flavorRef": new_flavor['id']}}) - else: - self.api.post_server_action(server['id'], {'migrate': None}) - self._wait_for_state_change(server, 'VERIFY_RESIZE') - - # ensure that resize is tried on two hosts, so we had a re-schedule - self.assertEqual(['host2', 'host3'], prep_resize_calls) - - migration_uuid = self.get_migration_uuid_for_instance(server['id']) - - # check that server allocates from the final host properly while - # the migration holds the allocation on the source host - self._check_allocation( - server, compute3_rp_uuid, non_qos_port, qos_port, qos_sriov_port, - self.flavor_with_group_policy, migration_uuid, - source_compute_rp_uuid=self.compute1_rp_uuid, - new_flavor=new_flavor) - - self._assert_pci_request_pf_device_name(server, 'host3-ens2') - - self._confirm_resize(server) - - # check that allocation is still OK - self._check_allocation( - server, compute3_rp_uuid, non_qos_port, qos_port, qos_sriov_port, - self.flavor_with_group_policy, new_flavor=new_flavor) - migration_allocations = self.placement.get( - '/allocations/%s' % migration_uuid).body['allocations'] - self.assertEqual({}, migration_allocations) - - self._delete_server_and_check_allocations( - server, qos_port, qos_sriov_port) - - def test_migrate_server_with_qos_port_reschedule_success(self): - self._test_resize_or_migrate_server_with_qos_port_reschedule_success() - - def test_resize_server_with_qos_port_reschedule_success(self): - self._test_resize_or_migrate_server_with_qos_port_reschedule_success( - new_flavor=self.flavor_with_group_policy_bigger) - - def _test_resize_or_migrate_server_with_qos_port_reschedule_failure( - self, new_flavor=None): - non_qos_port = self.neutron.port_1 - qos_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_port, qos_port, qos_sriov_port) - - # The patched compute manager on host2 will raise from _prep_resize. - # Then the migration is reschedule but there is no other host to - # choose from. - with mock.patch.object( - compute_manager.ComputeManager, '_prep_resize', - side_effect=test.TestingException( - 'Simulated prep_resize failure.')): - if new_flavor: - self.api_fixture.api.post_server_action( - server['id'], {'resize': {"flavorRef": new_flavor['id']}}) - else: - self.api.post_server_action(server['id'], {'migrate': None}) - self._wait_for_server_parameter(server, - {'OS-EXT-SRV-ATTR:host': 'host1', - 'status': 'ERROR'}) - self._wait_for_migration_status(server, ['error']) - - migration_uuid = self.get_migration_uuid_for_instance(server['id']) - - # as the migration is failed we expect that the migration allocation - # is deleted - migration_allocations = self.placement.get( - '/allocations/%s' % migration_uuid).body['allocations'] - self.assertEqual({}, migration_allocations) - - # and the instance allocates from the source host - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_port, qos_port, - qos_sriov_port, self.flavor_with_group_policy) - - def test_migrate_server_with_qos_port_reschedule_failure(self): - self._test_resize_or_migrate_server_with_qos_port_reschedule_failure() - - def test_resize_server_with_qos_port_reschedule_failure(self): - self._test_resize_or_migrate_server_with_qos_port_reschedule_failure( - new_flavor=self.flavor_with_group_policy_bigger) - - def test_migrate_server_with_qos_port_pci_update_fail_not_reschedule(self): - # Update the name of the network device RP of PF2 on host2 to something - # unexpected. This will cause - # update_pci_request_spec_with_allocated_interface_name() to raise - # when the instance is migrated to the host2. - rsp = self.placement.put( - '/resource_providers/%s' - % self.sriov_dev_rp_per_host[self.compute2_rp_uuid][self.PF2], - {"name": "invalid-device-rp-name"}) - self.assertEqual(200, rsp.status) - - self._start_compute('host3') - compute3_rp_uuid = self._get_provider_uuid_by_host('host3') - self._create_networking_rp_tree('host3', compute3_rp_uuid) - - non_qos_port = self.neutron.port_1 - qos_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_port, qos_port, qos_sriov_port) - - # The compute manager on host2 will raise from - # update_pci_request_spec_with_allocated_interface_name which will - # intentionally not trigger a re-schedule even if there is host3 as an - # alternate. - self.api.post_server_action(server['id'], {'migrate': None}) - server = self._wait_for_server_parameter(server, - {'OS-EXT-SRV-ATTR:host': 'host1', - # Note that we have to wait for the task_state to be reverted - # to None since that happens after the fault is recorded. - 'OS-EXT-STS:task_state': None, - 'status': 'ERROR'}) - self._wait_for_migration_status(server, ['error']) - - self.assertIn( - 'Build of instance %s aborted' % server['id'], - server['fault']['message']) - - self._wait_for_action_fail_completion( - server, instance_actions.MIGRATE, 'compute_prep_resize') - - self.notifier.wait_for_versioned_notifications( - 'instance.resize_prep.end') - self.notifier.wait_for_versioned_notifications( - 'compute.exception') - - migration_uuid = self.get_migration_uuid_for_instance(server['id']) - - # as the migration is failed we expect that the migration allocation - # is deleted - migration_allocations = self.placement.get( - '/allocations/%s' % migration_uuid).body['allocations'] - self.assertEqual({}, migration_allocations) - - # and the instance allocates from the source host - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_port, qos_port, - qos_sriov_port, self.flavor_with_group_policy) - - def _check_allocation_during_evacuate( - self, server, flavor, source_compute_rp_uuid, dest_compute_rp_uuid, - non_qos_port, qos_port, qos_sriov_port): - # evacuate is the only case when the same consumer has allocation from - # two different RP trees so we need special checks - - updated_non_qos_port = self.neutron.show_port( - non_qos_port['id'])['port'] - updated_qos_port = self.neutron.show_port(qos_port['id'])['port'] - updated_qos_sriov_port = self.neutron.show_port( - qos_sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect two sets of allocations. One set for the source compute - # and one set for the dest compute. Each set we expect 3 allocations - # one for the compute RP according to the flavor, one for the ovs port - # and one for the SRIOV port. - self.assertEqual(6, len(allocations), allocations) - - # 1. source compute allocation - compute_allocations = allocations[source_compute_rp_uuid]['resources'] - self.assertEqual( - self._resources_from_flavor(flavor), - compute_allocations) - - # 2. source ovs allocation - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[source_compute_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(qos_port, ovs_allocations) - - # 3. source sriov allocation - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[ - source_compute_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation(qos_sriov_port, sriov_allocations) - - # 4. dest compute allocation - compute_allocations = allocations[dest_compute_rp_uuid]['resources'] - self.assertEqual( - self._resources_from_flavor(flavor), - compute_allocations) - - # 5. dest ovs allocation - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[dest_compute_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(qos_port, ovs_allocations) - - # 6. dest SRIOV allocation - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[ - dest_compute_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation(qos_sriov_port, sriov_allocations) - - # the qos ports should have their binding pointing to the RPs in the - # dest compute RP tree - qos_binding_profile = updated_qos_port['binding:profile'] - self.assertEqual( - self.ovs_bridge_rp_per_host[dest_compute_rp_uuid], - qos_binding_profile['allocation']) - - qos_sriov_binding_profile = updated_qos_sriov_port['binding:profile'] - self.assertEqual( - self.sriov_dev_rp_per_host[dest_compute_rp_uuid][self.PF2], - qos_sriov_binding_profile['allocation']) - - # And we expect not to have any allocation set in the port binding for - # the port that doesn't have resource request - self.assertEqual({}, updated_non_qos_port['binding:profile']) - - def _check_allocation_after_evacuation_source_recovered( - self, server, flavor, dest_compute_rp_uuid, non_qos_port, - qos_port, qos_sriov_port): - # check that source allocation is cleaned up and the dest allocation - # and the port bindings are not touched. - - updated_non_qos_port = self.neutron.show_port( - non_qos_port['id'])['port'] - updated_qos_port = self.neutron.show_port(qos_port['id'])['port'] - updated_qos_sriov_port = self.neutron.show_port( - qos_sriov_port['id'])['port'] - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - self.assertEqual(3, len(allocations), allocations) - - # 1. dest compute allocation - compute_allocations = allocations[dest_compute_rp_uuid]['resources'] - self.assertEqual( - self._resources_from_flavor(flavor), - compute_allocations) - - # 2. dest ovs allocation - ovs_allocations = allocations[ - self.ovs_bridge_rp_per_host[dest_compute_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(qos_port, ovs_allocations) - - # 3. dest SRIOV allocation - sriov_allocations = allocations[ - self.sriov_dev_rp_per_host[ - dest_compute_rp_uuid][self.PF2]]['resources'] - self.assertPortMatchesAllocation(qos_sriov_port, sriov_allocations) - - # the qos ports should have their binding pointing to the RPs in the - # dest compute RP tree - qos_binding_profile = updated_qos_port['binding:profile'] - self.assertEqual( - self.ovs_bridge_rp_per_host[dest_compute_rp_uuid], - qos_binding_profile['allocation']) - - qos_sriov_binding_profile = updated_qos_sriov_port['binding:profile'] - self.assertEqual( - self.sriov_dev_rp_per_host[dest_compute_rp_uuid][self.PF2], - qos_sriov_binding_profile['allocation']) - - # And we expect not to have any allocation set in the port binding for - # the port that doesn't have resource request - self.assertEqual({}, updated_non_qos_port['binding:profile']) - - def test_evacuate_with_qos_port(self, host=None): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - # force source compute down - self.compute1.stop() - self.admin_api.put_service( - self.compute1_service_id, {'forced_down': 'true'}) - - self._evacuate_server( - server, {'host': host} if host else {}, expected_host='host2') - - self._check_allocation_during_evacuate( - server, self.flavor_with_group_policy, self.compute1_rp_uuid, - self.compute2_rp_uuid, non_qos_normal_port, qos_normal_port, - qos_sriov_port) - - self._assert_pci_request_pf_device_name(server, 'host2-ens2') - - # recover source compute - self.compute1 = self.restart_compute_service(self.compute1) - self.admin_api.put_service( - self.compute1_service_id, {'forced_down': 'false'}) - - # check that source allocation is cleaned up and the dest allocation - # and the port bindings are not touched. - self._check_allocation_after_evacuation_source_recovered( - server, self.flavor_with_group_policy, self.compute2_rp_uuid, - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_evacuate_with_target_host_with_qos_port(self): - self.test_evacuate_with_qos_port(host='host2') - - def test_evacuate_with_qos_port_fails_recover_source_compute(self): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - # force source compute down - self.compute1.stop() - self.admin_api.put_service( - self.compute1_service_id, {'forced_down': 'true'}) - - with mock.patch( - 'nova.compute.resource_tracker.ResourceTracker.rebuild_claim', - side_effect=exception.ComputeResourcesUnavailable( - reason='test evacuate failure')): - # Evacuate does not have reschedule loop so evacuate expected to - # simply fail and the server remains on the source host - server = self._evacuate_server( - server, expected_host='host1', expected_task_state=None, - expected_migration_status='failed') - - # As evacuation failed the resource allocation should be untouched - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - # recover source compute - self.compute1 = self.restart_compute_service(self.compute1) - self.admin_api.put_service( - self.compute1_service_id, {'forced_down': 'false'}) - - # check again that even after source host recovery the source - # allocation is intact - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_evacuate_with_qos_port_pci_update_fail(self): - # Update the name of the network device RP of PF2 on host2 to something - # unexpected. This will cause - # update_pci_request_spec_with_allocated_interface_name() to raise - # when the instance is evacuated to the host2. - rsp = self.placement.put( - '/resource_providers/%s' - % self.sriov_dev_rp_per_host[self.compute2_rp_uuid][self.PF2], - {"name": "invalid-device-rp-name"}) - self.assertEqual(200, rsp.status) - - non_qos_port = self.neutron.port_1 - qos_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_port, qos_port, qos_sriov_port) - - # force source compute down - self.compute1.stop() - self.admin_api.put_service( - self.compute1_service_id, {'forced_down': 'true'}) - - # The compute manager on host2 will raise from - # update_pci_request_spec_with_allocated_interface_name - server = self._evacuate_server( - server, expected_host='host1', expected_state='ERROR', - expected_task_state=None, expected_migration_status='failed') - - self.assertIn( - 'does not have a properly formatted name', - server['fault']['message']) - - self._wait_for_action_fail_completion( - server, instance_actions.EVACUATE, 'compute_rebuild_instance') - - self.notifier.wait_for_versioned_notifications( - 'instance.rebuild.error') - self.notifier.wait_for_versioned_notifications( - 'compute.exception') - - # and the instance allocates from the source host - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_port, qos_port, - qos_sriov_port, self.flavor_with_group_policy) - - def test_live_migrate_with_qos_port(self, host=None): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - self.api.post_server_action( - server['id'], - { - 'os-migrateLive': { - 'host': host, - 'block_migration': 'auto' - } - } - ) - - self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host2', - 'status': 'ACTIVE'}) - - self._check_allocation( - server, self.compute2_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - self._assert_pci_request_pf_device_name(server, 'host2-ens2') - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_live_migrate_with_qos_port_with_target_host(self): - self.test_live_migrate_with_qos_port(host='host2') - - def test_live_migrate_with_qos_port_reschedule_success(self): - self._start_compute('host3') - compute3_rp_uuid = self._get_provider_uuid_by_host('host3') - self._create_networking_rp_tree('host3', compute3_rp_uuid) - - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - orig_check = fake.FakeDriver.check_can_live_migrate_destination - - def fake_check_can_live_migrate_destination( - context, instance, src_compute_info, dst_compute_info, - block_migration=False, disk_over_commit=False): - if dst_compute_info['host'] == 'host2': - raise exception.MigrationPreCheckError( - reason='test_live_migrate_pre_check_fails') - else: - return orig_check( - context, instance, src_compute_info, dst_compute_info, - block_migration, disk_over_commit) - - with mock.patch('nova.virt.fake.FakeDriver.' - 'check_can_live_migrate_destination', - side_effect=fake_check_can_live_migrate_destination): - self.api.post_server_action( - server['id'], - { - 'os-migrateLive': { - 'host': None, - 'block_migration': 'auto' - } - } - ) - # The first migration attempt was to host2. So we expect that the - # instance lands on host3. - self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host3', - 'status': 'ACTIVE'}) - - self._check_allocation( - server, compute3_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - self._assert_pci_request_pf_device_name(server, 'host3-ens2') - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_live_migrate_with_qos_port_reschedule_fails(self): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - with mock.patch( - 'nova.virt.fake.FakeDriver.check_can_live_migrate_destination', - side_effect=exception.MigrationPreCheckError( - reason='test_live_migrate_pre_check_fails')): - self.api.post_server_action( - server['id'], - { - 'os-migrateLive': { - 'host': None, - 'block_migration': 'auto' - } - } - ) - # The every migration target host will fail the pre check so - # the conductor will run out of target host and the migration will - # fail - self._wait_for_migration_status(server, ['error']) - - # the server will remain on host1 - self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host1', - 'status': 'ACTIVE'}) - - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - # Assert that the InstancePCIRequests also rolled back to point to - # host1 - self._assert_pci_request_pf_device_name(server, 'host1-ens2') - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_live_migrate_with_qos_port_pci_update_fails(self): - # Update the name of the network device RP of PF2 on host2 to something - # unexpected. This will cause - # update_pci_request_spec_with_allocated_interface_name() to raise - # when the instance is live migrated to the host2. - rsp = self.placement.put( - '/resource_providers/%s' - % self.sriov_dev_rp_per_host[self.compute2_rp_uuid][self.PF2], - {"name": "invalid-device-rp-name"}) - self.assertEqual(200, rsp.status) - - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - self.api.post_server_action( - server['id'], - { - 'os-migrateLive': { - 'host': None, - 'block_migration': 'auto' - } - } - ) - - # pci update will fail after scheduling to host2 - self._wait_for_migration_status(server, ['error']) - server = self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host1', - 'status': 'ERROR'}) - self.assertIn( - 'does not have a properly formatted name', - server['fault']['message']) - - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - # Assert that the InstancePCIRequests still point to host1 - self._assert_pci_request_pf_device_name(server, 'host1-ens2') - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_unshelve_offloaded_server_with_qos_port(self): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - # with default config shelve means immediate offload as well - req = { - 'shelve': {} - } - self.api.post_server_action(server['id'], req) - self._wait_for_server_parameter( - server, {'status': 'SHELVED_OFFLOADED'}) - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - self.assertEqual(0, len(allocations)) - - self.api.post_server_action(server['id'], {'unshelve': None}) - self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host1', - 'status': 'ACTIVE'}) - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - self._assert_pci_request_pf_device_name(server, 'host1-ens2') - - # shelve offload again and then make host1 unusable so the subsequent - # unshelve needs to select host2 - req = { - 'shelve': {} - } - self.api.post_server_action(server['id'], req) - self._wait_for_server_parameter( - server, {'status': 'SHELVED_OFFLOADED'}) - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - self.assertEqual(0, len(allocations)) - - self.admin_api.put_service( - self.compute1_service_id, {"status": "disabled"}) - - self.api.post_server_action(server['id'], {'unshelve': None}) - self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host2', - 'status': 'ACTIVE'}) - - self._check_allocation( - server, self.compute2_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - self._assert_pci_request_pf_device_name(server, 'host2-ens2') - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_unshelve_offloaded_server_with_qos_port_pci_update_fails(self): - # Update the name of the network device RP of PF2 on host2 to something - # unexpected. This will cause - # update_pci_request_spec_with_allocated_interface_name() to raise - # when the instance is unshelved to the host2. - rsp = self.placement.put( - '/resource_providers/%s' - % self.sriov_dev_rp_per_host[self.compute2_rp_uuid][self.PF2], - {"name": "invalid-device-rp-name"}) - self.assertEqual(200, rsp.status) - - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - # with default config shelve means immediate offload as well - req = { - 'shelve': {} - } - self.api.post_server_action(server['id'], req) - self._wait_for_server_parameter( - server, {'status': 'SHELVED_OFFLOADED'}) - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - self.assertEqual(0, len(allocations)) - - # make host1 unusable so the subsequent unshelve needs to select host2 - self.admin_api.put_service( - self.compute1_service_id, {"status": "disabled"}) - - self.api.post_server_action(server['id'], {'unshelve': None}) - - # Unshelve fails on host2 due to - # update_pci_request_spec_with_allocated_interface_name fails so the - # instance goes back to shelve offloaded state - self.notifier.wait_for_versioned_notifications( - 'instance.unshelve.start') - error_notification = self.notifier.wait_for_versioned_notifications( - 'compute.exception')[0] - self.assertEqual( - 'UnexpectedResourceProviderNameForPCIRequest', - error_notification['payload']['nova_object.data']['exception']) - server = self._wait_for_server_parameter( - server, - {'OS-EXT-STS:task_state': None, - 'status': 'SHELVED_OFFLOADED'}) - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - self.assertEqual(0, len(allocations)) - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - def test_unshelve_offloaded_server_with_qos_port_fails_due_to_neutron( - self): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - # with default config shelve means immediate offload as well - req = { - 'shelve': {} - } - self.api.post_server_action(server['id'], req) - self._wait_for_server_parameter( - server, {'status': 'SHELVED_OFFLOADED'}) - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - self.assertEqual(0, len(allocations)) - - # Simulate that port update fails during unshelve due to neutron is - # unavailable - with mock.patch( - 'nova.tests.fixtures.NeutronFixture.' - 'update_port') as mock_update_port: - mock_update_port.side_effect = neutron_exception.ConnectionFailed( - reason='test') - req = {'unshelve': None} - self.api.post_server_action(server['id'], req) - self.notifier.wait_for_versioned_notifications( - 'instance.unshelve.start') - self._wait_for_server_parameter( - server, - {'status': 'SHELVED_OFFLOADED', - 'OS-EXT-STS:task_state': None}) - - # As the instance went back to offloaded state we expect no allocation - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - self.assertEqual(0, len(allocations)) - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - -class LiveMigrateAbortWithPortResourceRequestTest( - PortResourceRequestBasedSchedulingTestBase): - - compute_driver = "fake.FakeLiveMigrateDriverWithPciResources" - - def setUp(self): - # Use a custom weigher to make sure that we have a predictable host - # order in the alternate list returned by the scheduler for migration. - self.useFixture(nova_fixtures.HostNameWeigherFixture()) - super(LiveMigrateAbortWithPortResourceRequestTest, self).setUp() - self.compute2 = self._start_compute('host2') - self.compute2_rp_uuid = self._get_provider_uuid_by_host('host2') - self._create_networking_rp_tree('host2', self.compute2_rp_uuid) - self.compute2_service_id = self.admin_api.get_services( - host='host2', binary='nova-compute')[0]['id'] - - def test_live_migrate_with_qos_port_abort_migration(self): - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - # The special virt driver will keep the live migration running until it - # is aborted. - self.api.post_server_action( - server['id'], - { - 'os-migrateLive': { - 'host': None, - 'block_migration': 'auto' - } - } - ) - - # wait for the migration to start - migration = self._wait_for_migration_status(server, ['running']) - - # delete the migration to abort it - self.api.delete_migration(server['id'], migration['id']) - - self._wait_for_migration_status(server, ['cancelled']) - self._wait_for_server_parameter( - server, - {'OS-EXT-SRV-ATTR:host': 'host1', - 'status': 'ACTIVE'}) - - self._check_allocation( - server, self.compute1_rp_uuid, non_qos_normal_port, - qos_normal_port, qos_sriov_port, self.flavor_with_group_policy) - - # Assert that the InstancePCIRequests rolled back to point to host1 - self._assert_pci_request_pf_device_name(server, 'host1-ens2') - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) - - -class PortResourceRequestReSchedulingTest( - PortResourceRequestBasedSchedulingTestBase): - """Similar to PortResourceRequestBasedSchedulingTest - except this test uses FakeRescheduleDriver which will test reschedules - during server create work as expected, i.e. that the resource request - allocations are moved from the initially selected compute to the - alternative compute. - """ - - compute_driver = 'fake.FakeRescheduleDriver' - - def setUp(self): - super(PortResourceRequestReSchedulingTest, self).setUp() - self.compute2 = self._start_compute('host2') - self.compute2_rp_uuid = self._get_provider_uuid_by_host('host2') - self._create_networking_rp_tree('host2', self.compute2_rp_uuid) - - def _create_networking_rp_tree(self, hostname, compute_rp_uuid): - # let's simulate what the neutron would do - self._create_ovs_networking_rp_tree(compute_rp_uuid) - - def test_boot_reschedule_success(self): - port = self.neutron.port_with_resource_request - - server = self._create_server( - flavor=self.flavor, - networks=[{'port': port['id']}]) - server = self._wait_for_state_change(server, 'ACTIVE') - updated_port = self.neutron.show_port(port['id'])['port'] - - dest_hostname = server['OS-EXT-SRV-ATTR:host'] - dest_compute_rp_uuid = self._get_provider_uuid_by_host(dest_hostname) - - failed_compute_rp = (self.compute1_rp_uuid - if dest_compute_rp_uuid == self.compute2_rp_uuid - else self.compute2_rp_uuid) - - allocations = self.placement.get( - '/allocations/%s' % server['id']).body['allocations'] - - # We expect one set of allocations for the compute resources on the - # compute rp and one set for the networking resources on the ovs bridge - # rp - self.assertEqual(2, len(allocations)) - - self.assertComputeAllocationMatchesFlavor( - allocations, dest_compute_rp_uuid, self.flavor) - - network_allocations = allocations[ - self.ovs_bridge_rp_per_host[dest_compute_rp_uuid]]['resources'] - self.assertPortMatchesAllocation(port, network_allocations) - - # assert that the allocations against the host where the spawn - # failed are cleaned up properly - self.assertEqual( - {'VCPU': 0, 'MEMORY_MB': 0, 'DISK_GB': 0}, - self._get_provider_usages(failed_compute_rp)) - self.assertEqual( - {'NET_BW_EGR_KILOBIT_PER_SEC': 0, 'NET_BW_IGR_KILOBIT_PER_SEC': 0}, - self._get_provider_usages( - self.ovs_bridge_rp_per_host[failed_compute_rp])) - - # We expect that only the RP uuid of the networking RP having the port - # allocation is sent in the port binding - binding_profile = updated_port['binding:profile'] - self.assertEqual(self.ovs_bridge_rp_per_host[dest_compute_rp_uuid], - binding_profile['allocation']) - - self._delete_and_check_allocations(server) - - # assert that unbind removes the allocation from the binding - updated_port = self.neutron.show_port(port['id'])['port'] - binding_profile = updated_port['binding:profile'] - self.assertNotIn('allocation', binding_profile) - - def test_boot_reschedule_fill_provider_mapping_raises(self): - """Verify that if the _fill_provider_mapping raises during re-schedule - then the instance is properly put into ERROR state. - """ - - port = self.neutron.port_with_resource_request - - # First call is during boot, we want that to succeed normally. Then the - # fake virt driver triggers a re-schedule. During that re-schedule the - # fill is called again, and we simulate that call raises. - original_fill = utils.fill_provider_mapping - - def stub_fill_provider_mapping(*args, **kwargs): - if not mock_fill.called: - return original_fill(*args, **kwargs) - raise exception.ResourceProviderTraitRetrievalFailed( - uuid=uuids.rp1) - - with mock.patch( - 'nova.scheduler.utils.fill_provider_mapping', - side_effect=stub_fill_provider_mapping) as mock_fill: - server = self._create_server( - flavor=self.flavor, - networks=[{'port': port['id']}]) - server = self._wait_for_state_change(server, 'ERROR') - - self.assertIn( - 'Failed to get traits for resource provider', - server['fault']['message']) - - self._delete_and_check_allocations(server) - - # assert that unbind removes the allocation from the binding - updated_port = self.neutron.show_port(port['id'])['port'] - binding_profile = neutronapi.get_binding_profile(updated_port) - self.assertNotIn('allocation', binding_profile) - - class AcceleratorServerBase(integrated_helpers.ProviderUsageBaseTestCase): compute_driver = 'fake.SmallFakeDriver' @@ -8747,114 +6470,3 @@ class PortAndFlavorAccelsServerCreateTest(AcceleratorServerBase): binding_profile = neutronapi.get_binding_profile(updated_port) self.assertNotIn('arq_uuid', binding_profile) self.assertNotIn('pci_slot', binding_profile) - - -class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase): - NUMBER_OF_CELLS = 2 - - def setUp(self): - # Use our custom weigher defined above to make sure that we have - # a predictable host order in the alternate list returned by the - # scheduler for migration. - self.useFixture(nova_fixtures.HostNameWeigherFixture()) - super(CrossCellResizeWithQoSPort, self).setUp() - # start compute2 in cell2, compute1 is started in cell1 by default - self.compute2 = self._start_compute('host2', cell_name='cell2') - self.compute2_rp_uuid = self._get_provider_uuid_by_host('host2') - self._create_networking_rp_tree('host2', self.compute2_rp_uuid) - self.compute2_service_id = self.admin_api.get_services( - host='host2', binary='nova-compute')[0]['id'] - - # Enable cross-cell resize policy since it defaults to not allow - # anyone to perform that type of operation. For these tests we'll - # just allow admins to perform cross-cell resize. - self.policy.set_rules({ - servers_policies.CROSS_CELL_RESIZE: - base_policies.RULE_ADMIN_API}, - overwrite=False) - - def test_cross_cell_migrate_server_with_qos_ports(self): - """Test that cross cell migration is not supported with qos ports and - nova therefore falls back to do a same cell migration instead. - To test this properly we first make sure that there is no valid host - in the same cell but there is valid host in another cell and observe - that the migration fails with NoValidHost. Then we start a new compute - in the same cell the instance is in and retry the migration that is now - expected to pass. - """ - - non_qos_normal_port = self.neutron.port_1 - qos_normal_port = self.neutron.port_with_resource_request - qos_sriov_port = self.neutron.port_with_sriov_resource_request - - server = self._create_server_with_ports_and_check_allocation( - non_qos_normal_port, qos_normal_port, qos_sriov_port) - - orig_create_binding = self.neutron.create_port_binding - - hosts = { - 'host1': self.compute1_rp_uuid, 'host2': self.compute2_rp_uuid, - } - - # Add an extra check to our neutron fixture. This check makes sure that - # the RP sent in the binding corresponds to host of the binding. In a - # real deployment this is checked by the Neutron server. As bug - # 1907522 showed we fail this check for cross cell migration with qos - # ports in a real deployment. So to reproduce that bug we need to have - # the same check in our test env too. - def spy_on_create_binding(port_id, data): - host_rp_uuid = hosts[data['binding']['host']] - device_rp_uuid = data['binding']['profile'].get('allocation') - if port_id == qos_normal_port['id']: - if device_rp_uuid != self.ovs_bridge_rp_per_host[host_rp_uuid]: - raise exception.PortBindingFailed(port_id=port_id) - elif port_id == qos_sriov_port['id']: - if ( - device_rp_uuid not in - self.sriov_dev_rp_per_host[host_rp_uuid].values() - ): - raise exception.PortBindingFailed(port_id=port_id) - - return orig_create_binding(port_id, data) - - with mock.patch( - 'nova.tests.fixtures.NeutronFixture.create_port_binding', - side_effect=spy_on_create_binding, autospec=True - ): - # We expect the migration to fail as the only available target - # host is in a different cell and while cross cell migration is - # enabled it is not supported for neutron ports with resource - # request. - self.api.post_server_action(server['id'], {'migrate': None}) - self._wait_for_migration_status(server, ['error']) - self._wait_for_server_parameter( - server, - {'status': 'ACTIVE', 'OS-EXT-SRV-ATTR:host': 'host1'}) - event = self._wait_for_action_fail_completion( - server, 'migrate', 'conductor_migrate_server') - self.assertIn( - 'exception.NoValidHost', event['traceback']) - self.assertIn( - 'Request is allowed by policy to perform cross-cell resize ' - 'but the instance has ports with resource request and ' - 'cross-cell resize is not supported with such ports.', - self.stdlog.logger.output) - self.assertNotIn( - 'nova.exception.PortBindingFailed: Binding failed for port', - self.stdlog.logger.output) - - # Now start a new compute in the same cell as the instance and retry - # the migration. - self._start_compute('host3', cell_name='cell1') - self.compute3_rp_uuid = self._get_provider_uuid_by_host('host3') - self._create_networking_rp_tree('host3', self.compute3_rp_uuid) - - with mock.patch( - 'nova.tests.fixtures.NeutronFixture.create_port_binding', - side_effect=spy_on_create_binding, autospec=True - ): - server = self._migrate_server(server) - self.assertEqual('host3', server['OS-EXT-SRV-ATTR:host']) - - self._delete_server_and_check_allocations( - server, qos_normal_port, qos_sriov_port) |