def test_tree_ops(self): cn1 = self.compute_node1 cn2 = self.compute_node2 cns = self.compute_nodes pt = provider_tree.ProviderTree(cns) self.assertRaises( ValueError, pt.new_root, cn1.hypervisor_hostname, cn1.uuid, 1, ) self.assertTrue(pt.exists(cn1.uuid)) self.assertTrue(pt.exists(cn1.hypervisor_hostname)) self.assertFalse(pt.exists(uuids.non_existing_rp)) self.assertFalse(pt.exists('noexist')) self.assertEqual(set([cn1.uuid]), pt.get_provider_uuids(name_or_uuid=cn1.uuid)) self.assertEqual(set([cn1.uuid, cn2.uuid]), pt.get_provider_uuids()) numa_cell0_uuid = pt.new_child('numa_cell0', cn1.uuid) numa_cell1_uuid = pt.new_child('numa_cell1', cn1.uuid) self.assertTrue(pt.exists(numa_cell0_uuid)) self.assertTrue(pt.exists('numa_cell0')) self.assertTrue(pt.exists(numa_cell1_uuid)) self.assertTrue(pt.exists('numa_cell1')) pf1_cell0_uuid = pt.new_child('pf1_cell0', numa_cell0_uuid) self.assertTrue(pt.exists(pf1_cell0_uuid)) self.assertTrue(pt.exists('pf1_cell0')) # Now we've got a 3-level tree under cn1 - check provider UUIDs again self.assertEqual( set([cn1.uuid, numa_cell0_uuid, pf1_cell0_uuid, numa_cell1_uuid]), pt.get_provider_uuids(name_or_uuid=cn1.uuid)) self.assertEqual( set([cn1.uuid, cn2.uuid, numa_cell0_uuid, pf1_cell0_uuid, numa_cell1_uuid]), pt.get_provider_uuids()) self.assertRaises( ValueError, pt.new_child, 'pf1_cell0', uuids.non_existing_rp, ) # Test data(). # Root, by UUID cn1_snap = pt.data(cn1.uuid) # Fields were faithfully copied self.assertEqual(cn1.uuid, cn1_snap.uuid) self.assertEqual(cn1.hypervisor_hostname, cn1_snap.name) self.assertIsNone(cn1_snap.parent_uuid) self.assertEqual({}, cn1_snap.inventory) self.assertEqual(set(), cn1_snap.traits) self.assertEqual(set(), cn1_snap.aggregates) # Validate read-only-ness self.assertRaises(AttributeError, setattr, cn1_snap, 'name', 'foo') cn3 = objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='compute-node-3', ) self.assertFalse(pt.exists(cn3.uuid)) self.assertFalse(pt.exists(cn3.hypervisor_hostname)) pt.new_root(cn3.hypervisor_hostname, cn3.uuid, 1) self.assertTrue(pt.exists(cn3.uuid)) self.assertTrue(pt.exists(cn3.hypervisor_hostname)) self.assertRaises( ValueError, pt.new_root, cn3.hypervisor_hostname, cn3.uuid, 1, ) self.assertRaises( ValueError, pt.remove, uuids.non_existing_rp, ) pt.remove(numa_cell1_uuid) self.assertFalse(pt.exists(numa_cell1_uuid)) self.assertTrue(pt.exists(pf1_cell0_uuid)) self.assertTrue(pt.exists(numa_cell0_uuid)) self.assertTrue(pt.exists(uuids.cn1)) # Now remove the root and check that children no longer exist pt.remove(uuids.cn1) self.assertFalse(pt.exists(pf1_cell0_uuid)) self.assertFalse(pt.exists(numa_cell0_uuid)) self.assertFalse(pt.exists(uuids.cn1))
def test_execute_with_destination(self, mock_get_az): dest_node = objects.ComputeNode(hypervisor_hostname='dest_node') with test.nested( mock.patch.object(self.task, '_check_host_is_up'), mock.patch.object(self.task, '_check_requested_destination'), mock.patch.object(scheduler_utils, 'claim_resources_on_destination'), mock.patch.object(self.migration, 'save'), mock.patch.object(self.task.compute_rpcapi, 'live_migration'), mock.patch('nova.conductor.tasks.migrate.' 'replace_allocation_with_migration'), mock.patch.object(self.task, '_check_destination_is_not_source'), mock.patch.object(self.task, '_check_destination_has_enough_memory'), mock.patch.object(self.task, '_check_compatible_with_source_hypervisor', return_value=(mock.sentinel.source_node, dest_node)), ) as (mock_check_up, mock_check_dest, mock_claim, mock_save, mock_mig, m_alloc, m_check_diff, m_check_enough_mem, m_check_compatible): mock_mig.return_value = "bob" m_alloc.return_value = (mock.MagicMock(), mock.sentinel.allocs) self.assertEqual("bob", self.task.execute()) mock_check_up.assert_has_calls([ mock.call(self.instance_host), mock.call(self.destination)]) mock_check_dest.assert_called_once_with() m_check_diff.assert_called_once() m_check_enough_mem.assert_called_once() m_check_compatible.assert_called_once() allocs = mock.sentinel.allocs mock_claim.assert_called_once_with( self.context, self.task.report_client, self.instance, mock.sentinel.source_node, dest_node, source_allocations=allocs, consumer_generation=None) mock_mig.assert_called_once_with( self.context, host=self.instance_host, instance=self.instance, dest=self.destination, block_migration=self.block_migration, migration=self.migration, migrate_data=None) self.assertTrue(mock_save.called) mock_get_az.assert_called_once_with(self.context, self.destination) self.assertEqual('fake-az', self.instance.availability_zone) # make sure the source/dest fields were set on the migration object self.assertEqual(self.instance.node, self.migration.source_node) self.assertEqual(dest_node.hypervisor_hostname, self.migration.dest_node) self.assertEqual(self.task.destination, self.migration.dest_compute) m_alloc.assert_called_once_with(self.context, self.instance, self.migration) # When the task is executed with a destination it means the host is # being forced and we don't call the scheduler, so we don't need to # heal the request spec. self.heal_reqspec_is_bfv_mock.assert_not_called() # When the task is executed with a destination it means the host is # being forced and we don't call the scheduler, so we don't need to # modify the request spec self.ensure_network_metadata_mock.assert_not_called()
def test_tree_ops(self): cn1 = self.compute_node1 cn2 = self.compute_node2 cns = self.compute_nodes pt = provider_tree.ProviderTree(cns) self.assertRaises( ValueError, pt.new_root, cn1.hypervisor_hostname, cn1.uuid, 1, ) self.assertTrue(pt.exists(cn1.uuid)) self.assertTrue(pt.exists(cn1.hypervisor_hostname)) self.assertFalse(pt.exists(uuids.non_existing_rp)) self.assertFalse(pt.exists('noexist')) self.assertEqual(set([cn1.uuid]), pt.get_provider_uuids(name_or_uuid=cn1.uuid)) self.assertEqual(set([cn1.uuid, cn2.uuid]), pt.get_provider_uuids()) numa_cell0_uuid = pt.new_child('numa_cell0', cn1.uuid) numa_cell1_uuid = pt.new_child('numa_cell1', cn1.uuid) self.assertTrue(pt.exists(numa_cell0_uuid)) self.assertTrue(pt.exists('numa_cell0')) self.assertTrue(pt.exists(numa_cell1_uuid)) self.assertTrue(pt.exists('numa_cell1')) pf1_cell0_uuid = pt.new_child('pf1_cell0', numa_cell0_uuid) self.assertTrue(pt.exists(pf1_cell0_uuid)) self.assertTrue(pt.exists('pf1_cell0')) # Now we've got a 3-level tree under cn1 - check provider UUIDs again self.assertEqual( set([cn1.uuid, numa_cell0_uuid, pf1_cell0_uuid, numa_cell1_uuid]), pt.get_provider_uuids(name_or_uuid=cn1.uuid)) self.assertEqual( set([ cn1.uuid, cn2.uuid, numa_cell0_uuid, pf1_cell0_uuid, numa_cell1_uuid ]), pt.get_provider_uuids()) self.assertRaises( ValueError, pt.new_child, 'pf1_cell0', uuids.non_existing_rp, ) cn3 = objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='compute-node-3', ) self.assertFalse(pt.exists(cn3.uuid)) self.assertFalse(pt.exists(cn3.hypervisor_hostname)) pt.new_root(cn3.hypervisor_hostname, cn3.uuid, 1) self.assertTrue(pt.exists(cn3.uuid)) self.assertTrue(pt.exists(cn3.hypervisor_hostname)) self.assertRaises( ValueError, pt.new_root, cn3.hypervisor_hostname, cn3.uuid, 1, ) self.assertRaises( ValueError, pt.remove, uuids.non_existing_rp, ) pt.remove(numa_cell1_uuid) self.assertFalse(pt.exists(numa_cell1_uuid)) self.assertTrue(pt.exists(pf1_cell0_uuid)) self.assertTrue(pt.exists(numa_cell0_uuid)) self.assertTrue(pt.exists(uuids.cn1)) # Now remove the root and check that children no longer exist pt.remove(uuids.cn1) self.assertFalse(pt.exists(pf1_cell0_uuid)) self.assertFalse(pt.exists(numa_cell0_uuid)) self.assertFalse(pt.exists(uuids.cn1))
'numa_topology': fake_numa_topology_db_format, 'hypervisor_type': 'fake-type', 'hypervisor_version': 1, 'hypervisor_hostname': 'fake-host', 'disk_available_least': 256, 'host_ip': fake_host_ip, 'supported_instances': fake_supported_instances } fake_compute_with_resources = objects.ComputeNode( vcpus=fake_resources['vcpus'], memory_mb=fake_resources['memory_mb'], local_gb=fake_resources['local_gb'], cpu_info=fake_resources['cpu_info'], vcpus_used=fake_resources['vcpus_used'], memory_mb_used=fake_resources['memory_mb_used'], local_gb_used =fake_resources['local_gb_used'], numa_topology=fake_resources['numa_topology'], hypervisor_type=fake_resources['hypervisor_type'], hypervisor_version=fake_resources['hypervisor_version'], hypervisor_hostname=fake_resources['hypervisor_hostname'], disk_available_least=fake_resources['disk_available_least'], host_ip=netaddr.IPAddress(fake_resources['host_ip']), supported_hv_specs=fake_supported_hv_specs, ) class _TestComputeNodeObject(object): def supported_hv_specs_comparator(self, expected, obj_val): obj_val = [inst.to_list() for inst in obj_val] self.assertJsonEqual(expected, obj_val) def pci_device_pools_comparator(self, expected, obj_val):
def test_compat_service_id_compute_host_not_found(self, mock_get): mock_get.side_effect = exception.ComputeHostNotFound(host='fake-host') compute = objects.ComputeNode(host='fake-host', service_id=None) primitive = compute.obj_to_primitive(target_version='1.12') self.assertEqual(-1, primitive['nova_object.data']['service_id'])
def setUp(self): super(NovaManageDBIronicTest, self).setUp() self.commands = manage.DbCommands() self.context = context.RequestContext('fake-user', 'fake-project') self.service1 = objects.Service(context=self.context, host='fake-host1', binary='nova-compute', topic='fake-host1', report_count=1, disabled=False, disabled_reason=None, availability_zone='nova', forced_down=False) self.service1.create() self.service2 = objects.Service(context=self.context, host='fake-host2', binary='nova-compute', topic='fake-host2', report_count=1, disabled=False, disabled_reason=None, availability_zone='nova', forced_down=False) self.service2.create() self.service3 = objects.Service(context=self.context, host='fake-host3', binary='nova-compute', topic='fake-host3', report_count=1, disabled=False, disabled_reason=None, availability_zone='nova', forced_down=False) self.service3.create() self.cn1 = objects.ComputeNode(context=self.context, service_id=self.service1.id, host='fake-host1', hypervisor_type='ironic', vcpus=1, memory_mb=1024, local_gb=10, vcpus_used=1, memory_mb_used=1024, local_gb_used=10, hypervisor_version=0, hypervisor_hostname='fake-node1', cpu_info='{}') self.cn1.create() self.cn2 = objects.ComputeNode(context=self.context, service_id=self.service1.id, host='fake-host1', hypervisor_type='ironic', vcpus=1, memory_mb=1024, local_gb=10, vcpus_used=1, memory_mb_used=1024, local_gb_used=10, hypervisor_version=0, hypervisor_hostname='fake-node2', cpu_info='{}') self.cn2.create() self.cn3 = objects.ComputeNode(context=self.context, service_id=self.service2.id, host='fake-host2', hypervisor_type='ironic', vcpus=1, memory_mb=1024, local_gb=10, vcpus_used=1, memory_mb_used=1024, local_gb_used=10, hypervisor_version=0, hypervisor_hostname='fake-node3', cpu_info='{}') self.cn3.create() self.cn4 = objects.ComputeNode(context=self.context, service_id=self.service3.id, host='fake-host3', hypervisor_type='libvirt', vcpus=1, memory_mb=1024, local_gb=10, vcpus_used=1, memory_mb_used=1024, local_gb_used=10, hypervisor_version=0, hypervisor_hostname='fake-node4', cpu_info='{}') self.cn4.create() self.cn5 = objects.ComputeNode(context=self.context, service_id=self.service2.id, host='fake-host2', hypervisor_type='ironic', vcpus=1, memory_mb=1024, local_gb=10, vcpus_used=1, memory_mb_used=1024, local_gb_used=10, hypervisor_version=0, hypervisor_hostname='fake-node5', cpu_info='{}') self.cn5.create() self.insts = [] for cn in (self.cn1, self.cn2, self.cn3, self.cn4, self.cn4, self.cn5): flavor = objects.Flavor(extra_specs={}) inst = objects.Instance(context=self.context, user_id=self.context.user_id, project_id=self.context.project_id, flavor=flavor, node=cn.hypervisor_hostname) inst.create() self.insts.append(inst) self.ironic_insts = [ i for i in self.insts if i.node != self.cn4.hypervisor_hostname ] self.virt_insts = [ i for i in self.insts if i.node == self.cn4.hypervisor_hostname ]
class IronicResourceTrackerTest(test.TestCase): """Tests the behaviour of the resource tracker with regards to the transitional period between adding support for custom resource classes in the placement API and integrating inventory and allocation records for Ironic baremetal nodes with those custom resource classes. """ FLAVOR_FIXTURES = { 'CUSTOM_SMALL_IRON': objects.Flavor( name='CUSTOM_SMALL_IRON', flavorid=42, vcpus=4, memory_mb=4096, root_gb=1024, swap=0, ephemeral_gb=0, extra_specs={}, ), 'CUSTOM_BIG_IRON': objects.Flavor( name='CUSTOM_BIG_IRON', flavorid=43, vcpus=16, memory_mb=65536, root_gb=1024, swap=0, ephemeral_gb=0, extra_specs={}, ), } COMPUTE_NODE_FIXTURES = { uuids.cn1: objects.ComputeNode( uuid=uuids.cn1, hypervisor_hostname='cn1', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=4, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=4096, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=1024, local_gb_used=0, disk_allocation_ratio=1.0, ), uuids.cn2: objects.ComputeNode( uuid=uuids.cn2, hypervisor_hostname='cn2', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=4, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=4096, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=1024, local_gb_used=0, disk_allocation_ratio=1.0, ), uuids.cn3: objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='cn3', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=16, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=65536, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=2048, local_gb_used=0, disk_allocation_ratio=1.0, ), } INSTANCE_FIXTURES = { uuids.instance1: objects.Instance( uuid=uuids.instance1, flavor=FLAVOR_FIXTURES['CUSTOM_SMALL_IRON'], vm_state=vm_states.BUILDING, task_state=task_states.SPAWNING, power_state=power_state.RUNNING, project_id='project', user_id=uuids.user, ), } def _set_client(self, client): """Set up embedded report clients to use the direct one from the interceptor. """ self.report_client = client self.rt.reportclient = client def setUp(self): super(IronicResourceTrackerTest, self).setUp() self.flags( reserved_host_memory_mb=0, cpu_allocation_ratio=1.0, ram_allocation_ratio=1.0, disk_allocation_ratio=1.0, ) self.ctx = context.RequestContext('user', 'project') driver = mock.MagicMock(autospec=virt_driver.ComputeDriver) driver.node_is_available.return_value = True def fake_upt(provider_tree, nodename, allocations=None): inventory = { 'CUSTOM_SMALL_IRON': { 'total': 1, 'reserved': 0, 'min_unit': 1, 'max_unit': 1, 'step_size': 1, 'allocation_ratio': 1.0, }, } provider_tree.update_inventory(nodename, inventory) driver.update_provider_tree.side_effect = fake_upt self.driver_mock = driver self.rt = resource_tracker.ResourceTracker(COMPUTE_HOST, driver) self.instances = self.create_fixtures() def create_fixtures(self): for flavor in self.FLAVOR_FIXTURES.values(): # Clone the object so the class variable isn't # modified by reference. flavor = flavor.obj_clone() flavor._context = self.ctx flavor.obj_set_defaults() flavor.create() # We create some compute node records in the Nova cell DB to simulate # data before adding integration for Ironic baremetal nodes with the # placement API... for cn in self.COMPUTE_NODE_FIXTURES.values(): # Clone the object so the class variable isn't # modified by reference. cn = cn.obj_clone() cn._context = self.ctx cn.obj_set_defaults() cn.create() instances = {} for instance in self.INSTANCE_FIXTURES.values(): # Clone the object so the class variable isn't # modified by reference. instance = instance.obj_clone() instance._context = self.ctx instance.obj_set_defaults() instance.create() instances[instance.uuid] = instance return instances @mock.patch('nova.compute.utils.is_volume_backed_instance', new=mock.Mock(return_value=False)) @mock.patch('nova.objects.compute_node.ComputeNode.save', new=mock.Mock()) def test_node_stats_isolation(self): """Regression test for bug 1784705 introduced in Ocata. The ResourceTracker.stats field is meant to track per-node stats so this test registers three compute nodes with a single RT where each node has unique stats, and then makes sure that after updating usage for an instance, the nodes still have their unique stats and nothing is leaked from node to node. """ self.useFixture(func_fixtures.PlacementFixture()) # Before the resource tracker is "initialized", we shouldn't have # any compute nodes or stats in the RT's cache... self.assertEqual(0, len(self.rt.compute_nodes)) self.assertEqual(0, len(self.rt.stats)) # Now "initialize" the resource tracker. This is what # nova.compute.manager.ComputeManager does when "initializing" the # nova-compute service. Do this in a predictable order so cn1 is # first and cn3 is last. for cn in sorted(self.COMPUTE_NODE_FIXTURES.values(), key=lambda _cn: _cn.hypervisor_hostname): nodename = cn.hypervisor_hostname # Fake that each compute node has unique extra specs stats and # the RT makes sure those are unique per node. stats = {'node:%s' % nodename: nodename} self.driver_mock.get_available_resource.return_value = { 'hypervisor_hostname': nodename, 'hypervisor_type': 'ironic', 'hypervisor_version': 0, 'vcpus': cn.vcpus, 'vcpus_used': cn.vcpus_used, 'memory_mb': cn.memory_mb, 'memory_mb_used': cn.memory_mb_used, 'local_gb': cn.local_gb, 'local_gb_used': cn.local_gb_used, 'numa_topology': None, 'resource_class': None, # Act like admin hasn't set yet... 'stats': stats, } self.rt.update_available_resource(self.ctx, nodename) self.assertEqual(3, len(self.rt.compute_nodes)) self.assertEqual(3, len(self.rt.stats)) def _assert_stats(): # Make sure each compute node has a unique set of stats and # they don't accumulate across nodes. for _cn in self.rt.compute_nodes.values(): node_stats_key = 'node:%s' % _cn.hypervisor_hostname self.assertIn(node_stats_key, _cn.stats) node_stat_count = 0 for stat in _cn.stats: if stat.startswith('node:'): node_stat_count += 1 self.assertEqual(1, node_stat_count, _cn.stats) _assert_stats() # Now "spawn" an instance to the first compute node by calling the # RT's instance_claim(). cn1_obj = self.COMPUTE_NODE_FIXTURES[uuids.cn1] cn1_nodename = cn1_obj.hypervisor_hostname inst = self.instances[uuids.instance1] with self.rt.instance_claim(self.ctx, inst, cn1_nodename, {}): _assert_stats()
siblings=[], pinned_cpus=set([])) ]) COMPUTE_NODES = [ objects.ComputeNode(id=1, local_gb=1024, memory_mb=1024, vcpus=1, disk_available_least=None, free_ram_mb=512, vcpus_used=1, free_disk_gb=512, local_gb_used=0, updated_at=None, host='host1', hypervisor_hostname='node1', host_ip='127.0.0.1', hypervisor_version=0, numa_topology=None, hypervisor_type='foo', supported_hv_specs=[], pci_device_pools=None, cpu_info=None, stats=None, metrics=None), objects.ComputeNode(id=2, local_gb=2048, memory_mb=2048, vcpus=2, disk_available_least=1024,
topic="compute_topic", report_count=5, disabled=False, disabled_reason=None, availability_zone="nova"), objects.Service(id=2, host="compute2", binary="nova-compute", topic="compute_topic", report_count=5, disabled=False, disabled_reason=None, availability_zone="nova"), ] TEST_HYPERS_OBJ = [objects.ComputeNode(**hyper_dct) for hyper_dct in TEST_HYPERS] TEST_HYPERS[0].update({'service': TEST_SERVICES[0]}) TEST_HYPERS[1].update({'service': TEST_SERVICES[1]}) TEST_SERVERS = [dict(name="inst1", uuid=uuids.instance_1, host="compute1"), dict(name="inst2", uuid=uuids.instance_2, host="compute2"), dict(name="inst3", uuid=uuids.instance_3, host="compute1"), dict(name="inst4", uuid=uuids.instance_4, host="compute2")] def fake_compute_node_get_all(context, limit=None, marker=None): if marker in ['99999']: raise exception.MarkerNotFound(marker) marker_found = True if marker is None else False
def _create_instance_data(self): """Creates an instance record and associated data like BDMs, VIFs, migrations, etc in the source cell and returns the Instance object. The idea is to create as many things from the Instance.INSTANCE_OPTIONAL_ATTRS list as possible. :returns: The created Instance and Migration objects """ # Create the nova-compute services record first. fake_service = test_service._fake_service() fake_service.pop('version', None) # version field is immutable fake_service.pop('id', None) # cannot create with an id set service = objects.Service(self.source_context, **fake_service) service.create() # Create the compute node using the service. fake_compute_node = copy.copy(test_compute_node.fake_compute_node) fake_compute_node['host'] = service.host fake_compute_node['hypervisor_hostname'] = service.host fake_compute_node['stats'] = {} # the object requires a dict fake_compute_node['service_id'] = service.id fake_compute_node.pop('id', None) # cannot create with an id set compute_node = objects.ComputeNode(self.source_context, **fake_compute_node) compute_node.create() # Build an Instance object with basic fields set. updates = { 'metadata': { 'foo': 'bar' }, 'system_metadata': { 'roles': ['member'] }, 'host': compute_node.host, 'node': compute_node.hypervisor_hostname } inst = fake_instance.fake_instance_obj(self.source_context, **updates) delattr(inst, 'id') # cannot create an instance with an id set # Now we have to dirty all of the fields because fake_instance_obj # uses Instance._from_db_object to create the Instance object we have # but _from_db_object calls obj_reset_changes() which resets all of # the fields that were on the object, including the basic stuff like # the 'host' field, which means those fields don't get set in the DB. # TODO(mriedem): This should live in fake_instance_obj with a # make_creatable kwarg. for field in inst.obj_fields: if field in inst: setattr(inst, field, getattr(inst, field)) # Make sure at least one expected basic field is dirty on the Instance. self.assertIn('host', inst.obj_what_changed()) # Set the optional fields on the instance before creating it. inst.pci_requests = objects.InstancePCIRequests(requests=[ objects.InstancePCIRequest( **test_instance_pci_requests.fake_pci_requests[0]) ]) inst.numa_topology = objects.InstanceNUMATopology( cells=test_instance_numa.fake_obj_numa_topology.cells) inst.trusted_certs = objects.TrustedCerts(ids=[uuids.cert]) inst.vcpu_model = test_vcpu_model.fake_vcpumodel inst.keypairs = objects.KeyPairList( objects=[objects.KeyPair(**test_keypair.fake_keypair)]) inst.device_metadata = ( test_instance_device_metadata.get_fake_obj_device_metadata( self.source_context)) # FIXME(mriedem): db.instance_create does not handle tags inst.obj_reset_changes(['tags']) inst.create() bdm = { 'instance_uuid': inst.uuid, 'source_type': 'volume', 'destination_type': 'volume', 'volume_id': uuids.volume_id, 'volume_size': 1, 'device_name': '/dev/vda', } bdm = objects.BlockDeviceMapping( self.source_context, **fake_block_device.FakeDbBlockDeviceDict(bdm_dict=bdm)) delattr(bdm, 'id') # cannot create a bdm with an id set bdm.obj_reset_changes(['id']) bdm.create() vif = objects.VirtualInterface(self.source_context, address='de:ad:be:ef:ca:fe', uuid=uuids.port, instance_uuid=inst.uuid) vif.create() info_cache = objects.InstanceInfoCache().new(self.source_context, inst.uuid) info_cache.network_info = network_model.NetworkInfo( [network_model.VIF(id=vif.uuid, address=vif.address)]) info_cache.save(update_cells=False) objects.TagList.create(self.source_context, inst.uuid, ['test']) try: raise test.TestingException('test-fault') except test.TestingException as fault: compute_utils.add_instance_fault_from_exc(self.source_context, inst, fault) objects.InstanceAction().action_start(self.source_context, inst.uuid, 'resize', want_result=False) objects.InstanceActionEvent().event_start(self.source_context, inst.uuid, 'migrate_server', want_result=False) # Create a fake migration for the cross-cell resize operation. migration = objects.Migration( self.source_context, **test_migration.fake_db_migration(instance_uuid=inst.uuid, cross_cell_move=True, migration_type='resize')) delattr(migration, 'id') # cannot create a migration with an id set migration.obj_reset_changes(['id']) migration.create() # Create an old non-resize migration to make sure it is copied to the # target cell database properly. old_migration = objects.Migration( self.source_context, **test_migration.fake_db_migration(instance_uuid=inst.uuid, migration_type='live-migration', status='completed', uuid=uuids.old_migration)) delattr(old_migration, 'id') # cannot create a migration with an id old_migration.obj_reset_changes(['id']) old_migration.create() fake_pci_device = copy.copy(test_pci_device.fake_db_dev) fake_pci_device['extra_info'] = {} # the object requires a dict fake_pci_device['compute_node_id'] = compute_node.id pci_device = objects.PciDevice.create(self.source_context, fake_pci_device) pci_device.allocate(inst) # sets the status and instance_uuid fields pci_device.save() # Return a fresh copy of the instance from the DB with as many joined # fields loaded as possible. expected_attrs = copy.copy(instance_obj.INSTANCE_OPTIONAL_ATTRS) # Cannot load fault from get_by_uuid. expected_attrs.remove('fault') inst = objects.Instance.get_by_uuid(self.source_context, inst.uuid, expected_attrs=expected_attrs) return inst, migration
class IronicResourceTrackerTest(test_base.SchedulerReportClientTestBase): """Tests the behaviour of the resource tracker with regards to the transitional period between adding support for custom resource classes in the placement API and integrating inventory and allocation records for Ironic baremetal nodes with those custom resource classes. """ FLAVOR_FIXTURES = { 'CUSTOM_SMALL_IRON': objects.Flavor( name='CUSTOM_SMALL_IRON', flavorid=42, vcpus=4, memory_mb=4096, root_gb=1024, swap=0, ephemeral_gb=0, extra_specs={}, ), 'CUSTOM_BIG_IRON': objects.Flavor( name='CUSTOM_BIG_IRON', flavorid=43, vcpus=16, memory_mb=65536, root_gb=1024, swap=0, ephemeral_gb=0, extra_specs={}, ), } COMPUTE_NODE_FIXTURES = { uuids.cn1: objects.ComputeNode( uuid=uuids.cn1, hypervisor_hostname='cn1', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=4, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=4096, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=1024, local_gb_used=0, disk_allocation_ratio=1.0, ), uuids.cn2: objects.ComputeNode( uuid=uuids.cn2, hypervisor_hostname='cn2', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=4, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=4096, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=1024, local_gb_used=0, disk_allocation_ratio=1.0, ), uuids.cn3: objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='cn3', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=16, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=65536, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=2048, local_gb_used=0, disk_allocation_ratio=1.0, ), } INSTANCE_FIXTURES = { uuids.instance1: objects.Instance( uuid=uuids.instance1, flavor=FLAVOR_FIXTURES['CUSTOM_SMALL_IRON'], vm_state=vm_states.BUILDING, task_state=task_states.SPAWNING, power_state=power_state.RUNNING, project_id='project', user_id=uuids.user, ), } def _set_client(self, client): """Set up embedded report clients to use the direct one from the interceptor. """ self.report_client = client self.rt.scheduler_client.reportclient = client self.rt.reportclient = client def setUp(self): super(IronicResourceTrackerTest, self).setUp() self.flags( reserved_host_memory_mb=0, cpu_allocation_ratio=1.0, ram_allocation_ratio=1.0, disk_allocation_ratio=1.0, ) self.ctx = context.RequestContext('user', 'project') driver = mock.MagicMock(autospec=virt_driver.ComputeDriver) driver.node_is_available.return_value = True driver.update_provider_tree.side_effect = NotImplementedError self.driver_mock = driver self.rt = resource_tracker.ResourceTracker(COMPUTE_HOST, driver) self.instances = self.create_fixtures() def create_fixtures(self): for flavor in self.FLAVOR_FIXTURES.values(): # Clone the object so the class variable isn't # modified by reference. flavor = flavor.obj_clone() flavor._context = self.ctx flavor.obj_set_defaults() flavor.create() # We create some compute node records in the Nova cell DB to simulate # data before adding integration for Ironic baremetal nodes with the # placement API... for cn in self.COMPUTE_NODE_FIXTURES.values(): # Clone the object so the class variable isn't # modified by reference. cn = cn.obj_clone() cn._context = self.ctx cn.obj_set_defaults() cn.create() instances = {} for instance in self.INSTANCE_FIXTURES.values(): # Clone the object so the class variable isn't # modified by reference. instance = instance.obj_clone() instance._context = self.ctx instance.obj_set_defaults() instance.create() instances[instance.uuid] = instance return instances def placement_get_inventory(self, rp_uuid): url = '/resource_providers/%s/inventories' % rp_uuid resp = self.report_client.get(url) if 200 <= resp.status_code < 300: return resp.json()['inventories'] else: return resp.status_code def placement_get_allocations(self, consumer_uuid): url = '/allocations/%s' % consumer_uuid resp = self.report_client.get(url) if 200 <= resp.status_code < 300: return resp.json()['allocations'] else: return resp.status_code def placement_get_custom_rcs(self): url = '/resource_classes' resp = self.report_client.get(url) if 200 <= resp.status_code < 300: all_rcs = resp.json()['resource_classes'] return [ rc['name'] for rc in all_rcs if rc['name'] not in fields.ResourceClass.STANDARD ] @mock.patch('nova.compute.utils.is_volume_backed_instance', new=mock.Mock(return_value=False)) @mock.patch('nova.objects.compute_node.ComputeNode.save', new=mock.Mock()) def test_ironic_ocata_to_pike(self): """Check that when going from an Ocata installation with Ironic having node's resource class attributes set, that we properly "auto-heal" the inventory and allocation records in the placement API to account for both the old-style VCPU/MEMORY_MB/DISK_GB resources as well as the new custom resource class from Ironic's node.resource_class attribute. """ with self._interceptor(): # Before the resource tracker is "initialized", we shouldn't have # any compute nodes in the RT's cache... self.assertEqual(0, len(self.rt.compute_nodes)) # There should not be any records in the placement API since we # haven't yet run update_available_resource() in the RT. for cn in self.COMPUTE_NODE_FIXTURES.values(): self.assertEqual(404, self.placement_get_inventory(cn.uuid)) for inst in self.INSTANCE_FIXTURES.keys(): self.assertEqual({}, self.placement_get_allocations(inst)) # Nor should there be any custom resource classes in the placement # API, since we haven't had an Ironic node's resource class set yet self.assertEqual(0, len(self.placement_get_custom_rcs())) # Now "initialize" the resource tracker as if the compute host is a # Ocata host, with Ironic virt driver, but the admin has not yet # added a resource_class attribute to the Ironic baremetal nodes in # her system. # NOTE(jaypipes): This is what nova.compute.manager.ComputeManager # does when "initializing" the service... for cn in self.COMPUTE_NODE_FIXTURES.values(): nodename = cn.hypervisor_hostname self.driver_mock.get_available_resource.return_value = { 'hypervisor_hostname': nodename, 'hypervisor_type': 'ironic', 'hypervisor_version': 0, 'vcpus': cn.vcpus, 'vcpus_used': cn.vcpus_used, 'memory_mb': cn.memory_mb, 'memory_mb_used': cn.memory_mb_used, 'local_gb': cn.local_gb, 'local_gb_used': cn.local_gb_used, 'numa_topology': None, 'resource_class': None, # Act like admin hasn't set yet... } self.driver_mock.get_inventory.return_value = { VCPU: { 'total': cn.vcpus, 'reserved': 0, 'min_unit': 1, 'max_unit': cn.vcpus, 'step_size': 1, 'allocation_ratio': 1.0, }, MEMORY_MB: { 'total': cn.memory_mb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn.memory_mb, 'step_size': 1, 'allocation_ratio': 1.0, }, DISK_GB: { 'total': cn.local_gb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn.local_gb, 'step_size': 1, 'allocation_ratio': 1.0, }, } self.rt.update_available_resource(self.ctx, nodename) self.assertEqual(3, len(self.rt.compute_nodes)) # A canary just to make sure the assertion below about the custom # resource class being added wasn't already added somehow... crcs = self.placement_get_custom_rcs() self.assertNotIn('CUSTOM_SMALL_IRON', crcs) # Verify that the placement API has the "old-style" resources in # inventory and allocations for cn in self.COMPUTE_NODE_FIXTURES.values(): inv = self.placement_get_inventory(cn.uuid) self.assertEqual(3, len(inv)) # Now "spawn" an instance to the first compute node by calling the # RT's instance_claim(). cn1_obj = self.COMPUTE_NODE_FIXTURES[uuids.cn1] cn1_nodename = cn1_obj.hypervisor_hostname inst = self.instances[uuids.instance1] # Since we're pike, the scheduler would have created our # allocation for us. So, we can use our old update routine # here to mimic that before we go do the compute RT claim, # and then the checks below. self.rt.reportclient.update_instance_allocation( self.ctx, cn1_obj, inst, 1) with self.rt.instance_claim(self.ctx, inst, cn1_nodename): pass allocs = self.placement_get_allocations(inst.uuid) self.assertEqual(1, len(allocs)) self.assertIn(uuids.cn1, allocs) resources = allocs[uuids.cn1]['resources'] self.assertEqual(3, len(resources)) for rc in (VCPU, MEMORY_MB, DISK_GB): self.assertIn(rc, resources) # Now we emulate the operator setting ONE of the Ironic node's # resource class attribute to the value of a custom resource class # and re-run update_available_resource(). We will expect to see the # inventory and allocations reset for the first compute node that # had an instance on it. The new inventory and allocation records # will be for VCPU, MEMORY_MB, DISK_GB, and also a new record for # the custom resource class of the Ironic node. self.driver_mock.get_available_resource.return_value = { 'hypervisor_hostname': cn1_obj.hypervisor_hostname, 'hypervisor_type': 'ironic', 'hypervisor_version': 0, 'vcpus': cn1_obj.vcpus, 'vcpus_used': cn1_obj.vcpus_used, 'memory_mb': cn1_obj.memory_mb, 'memory_mb_used': cn1_obj.memory_mb_used, 'local_gb': cn1_obj.local_gb, 'local_gb_used': cn1_obj.local_gb_used, 'numa_topology': None, 'resource_class': 'small-iron', } self.driver_mock.get_inventory.return_value = { VCPU: { 'total': cn1_obj.vcpus, 'reserved': 0, 'min_unit': 1, 'max_unit': cn1_obj.vcpus, 'step_size': 1, 'allocation_ratio': 1.0, }, MEMORY_MB: { 'total': cn1_obj.memory_mb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn1_obj.memory_mb, 'step_size': 1, 'allocation_ratio': 1.0, }, DISK_GB: { 'total': cn1_obj.local_gb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn1_obj.local_gb, 'step_size': 1, 'allocation_ratio': 1.0, }, 'CUSTOM_SMALL_IRON': { 'total': 1, 'reserved': 0, 'min_unit': 1, 'max_unit': 1, 'step_size': 1, 'allocation_ratio': 1.0, }, } self.rt.update_available_resource(self.ctx, cn1_nodename) # Verify the auto-creation of the custom resource class, normalized # to what the placement API expects self.assertIn('CUSTOM_SMALL_IRON', self.placement_get_custom_rcs()) allocs = self.placement_get_allocations(inst.uuid) self.assertEqual(1, len(allocs)) self.assertIn(uuids.cn1, allocs) resources = allocs[uuids.cn1]['resources'] self.assertEqual(3, len(resources)) for rc in (VCPU, MEMORY_MB, DISK_GB): self.assertIn(rc, resources) # TODO(jaypipes): Check allocations include the CUSTOM_SMALL_IRON # resource class. At the moment, we do not add an allocation record # for the Ironic custom resource class. Once the flavor is updated # to store a resources:$CUSTOM_RESOURCE_CLASS=1 extra_spec key and # the scheduler is constructing the request_spec to actually # request a single amount of that custom resource class, we will # modify the allocation/claim to consume only the custom resource # class and not the VCPU, MEMORY_MB and DISK_GB. @mock.patch('nova.compute.utils.is_volume_backed_instance', new=mock.Mock(return_value=False)) @mock.patch('nova.objects.compute_node.ComputeNode.save', new=mock.Mock()) def test_node_stats_isolation(self): """Regression test for bug 1784705 introduced in Ocata. The ResourceTracker.stats field is meant to track per-node stats so this test registers three compute nodes with a single RT where each node has unique stats, and then makes sure that after updating usage for an instance, the nodes still have their unique stats and nothing is leaked from node to node. """ self.useFixture(nova_fixtures.PlacementFixture()) # Before the resource tracker is "initialized", we shouldn't have # any compute nodes or stats in the RT's cache... self.assertEqual(0, len(self.rt.compute_nodes)) self.assertEqual(0, len(self.rt.stats)) # Now "initialize" the resource tracker. This is what # nova.compute.manager.ComputeManager does when "initializing" the # nova-compute service. Do this in a predictable order so cn1 is # first and cn3 is last. for cn in sorted(self.COMPUTE_NODE_FIXTURES.values(), key=lambda _cn: _cn.hypervisor_hostname): nodename = cn.hypervisor_hostname # Fake that each compute node has unique extra specs stats and # the RT makes sure those are unique per node. stats = {'node:%s' % nodename: nodename} self.driver_mock.get_available_resource.return_value = { 'hypervisor_hostname': nodename, 'hypervisor_type': 'ironic', 'hypervisor_version': 0, 'vcpus': cn.vcpus, 'vcpus_used': cn.vcpus_used, 'memory_mb': cn.memory_mb, 'memory_mb_used': cn.memory_mb_used, 'local_gb': cn.local_gb, 'local_gb_used': cn.local_gb_used, 'numa_topology': None, 'resource_class': None, # Act like admin hasn't set yet... 'stats': stats, } self.driver_mock.get_inventory.return_value = { 'CUSTOM_SMALL_IRON': { 'total': 1, 'reserved': 0, 'min_unit': 1, 'max_unit': 1, 'step_size': 1, 'allocation_ratio': 1.0, }, } self.rt.update_available_resource(self.ctx, nodename) self.assertEqual(3, len(self.rt.compute_nodes)) self.assertEqual(3, len(self.rt.stats)) def _assert_stats(): # Make sure each compute node has a unique set of stats and # they don't accumulate across nodes. for _cn in self.rt.compute_nodes.values(): node_stats_key = 'node:%s' % _cn.hypervisor_hostname self.assertIn(node_stats_key, _cn.stats) node_stat_count = 0 for stat in _cn.stats: if stat.startswith('node:'): node_stat_count += 1 self.assertEqual(1, node_stat_count, _cn.stats) _assert_stats() # Now "spawn" an instance to the first compute node by calling the # RT's instance_claim(). cn1_obj = self.COMPUTE_NODE_FIXTURES[uuids.cn1] cn1_nodename = cn1_obj.hypervisor_hostname inst = self.instances[uuids.instance1] with self.rt.instance_claim(self.ctx, inst, cn1_nodename): _assert_stats()
import os from nova import objects import json from oslo_utils import units import build_new_host_topology objects.register_all() primitive_dir = os.path.join(os.curdir, 'test_primitives') inst_primitive_files = [ os.path.join(primitive_dir, x) for x in os.listdir(primitive_dir) if 'instance-primitive' in x ] host_primitive_files = [ os.path.join(primitive_dir, x) for x in os.listdir(primitive_dir) if 'host-primitive' in x ] Inst = objects.Instance() Host = objects.ComputeNode() inst_primitives = [json.load(open(x)) for x in inst_primitive_files] insts = [Inst.obj_from_primitive(x) for x in inst_primitives] host_primitives = [json.load(open(x)) for x in host_primitive_files] hosts = [Host.obj_from_primitive(x) for x in host_primitives] print "insts" print "hosts"
# under the License. """ Fake nodes for Ironic host manager tests. """ from nova import objects COMPUTE_NODES = [ objects.ComputeNode( id=1, local_gb=10, memory_mb=1024, vcpus=1, vcpus_used=0, local_gb_used=0, memory_mb_used=0, updated_at=None, cpu_info='baremetal cpu', host='host1', hypervisor_hostname='node1uuid', host_ip='127.0.0.1', hypervisor_version=1, hypervisor_type='ironic', stats=dict(ironic_driver= "nova.virt.ironic.driver.IronicDriver", cpu_arch='i386'), supported_hv_specs=[objects.HVSpec.from_list( ["i386", "baremetal", "baremetal"])], free_disk_gb=10, free_ram_mb=1024, cpu_allocation_ratio=16.0, ram_allocation_ratio=1.5), objects.ComputeNode( id=2, local_gb=20, memory_mb=2048, vcpus=1, vcpus_used=0, local_gb_used=0, memory_mb_used=0, updated_at=None, cpu_info='baremetal cpu', host='host2', hypervisor_hostname='node2uuid', host_ip='127.0.0.1', hypervisor_version=1, hypervisor_type='ironic', stats=dict(ironic_driver= "nova.virt.ironic.driver.IronicDriver",
def test_execute_with_destination(self, new_mode=True): dest_node = objects.ComputeNode(hypervisor_hostname='dest_node') with test.nested( mock.patch.object(self.task, '_check_host_is_up'), mock.patch.object(self.task, '_check_requested_destination', return_value=(mock.sentinel.source_node, dest_node)), mock.patch.object(scheduler_utils, 'claim_resources_on_destination'), mock.patch.object(self.migration, 'save'), mock.patch.object(self.task.compute_rpcapi, 'live_migration'), mock.patch('nova.conductor.tasks.migrate.' 'replace_allocation_with_migration'), mock.patch( 'nova.conductor.tasks.live_migrate.' 'should_do_migration_allocation')) as (mock_check_up, mock_check_dest, mock_claim, mock_save, mock_mig, m_alloc, mock_sda): mock_mig.return_value = "bob" m_alloc.return_value = (mock.MagicMock(), mock.sentinel.allocs) mock_sda.return_value = new_mode self.assertEqual("bob", self.task.execute()) mock_check_up.assert_called_once_with(self.instance_host) mock_check_dest.assert_called_once_with() if new_mode: allocs = mock.sentinel.allocs else: allocs = None mock_claim.assert_called_once_with( self.context, self.task.scheduler_client.reportclient, self.instance, mock.sentinel.source_node, dest_node, source_node_allocations=allocs) mock_mig.assert_called_once_with( self.context, host=self.instance_host, instance=self.instance, dest=self.destination, block_migration=self.block_migration, migration=self.migration, migrate_data=None) self.assertTrue(mock_save.called) # make sure the source/dest fields were set on the migration object self.assertEqual(self.instance.node, self.migration.source_node) self.assertEqual(dest_node.hypervisor_hostname, self.migration.dest_node) self.assertEqual(self.task.destination, self.migration.dest_compute) if new_mode: m_alloc.assert_called_once_with(self.context, self.instance, self.migration) else: m_alloc.assert_not_called() # When the task is executed with a destination it means the host is # being forced and we don't call the scheduler, so we don't need to # heal the request spec. self.heal_reqspec_is_bfv_mock.assert_not_called() # When the task is executed with a destination it means the host is # being forced and we don't call the scheduler, so we don't need to # modify the request spec self.ensure_network_metadata_mock.assert_not_called()
def test_tree_ops(self): cn1 = self.compute_node1 cns = self.compute_nodes pt = provider_tree.ProviderTree(cns) self.assertRaises( ValueError, pt.new_root, cn1.hypervisor_hostname, cn1.uuid, 1, ) self.assertTrue(pt.exists(cn1.uuid)) self.assertTrue(pt.exists(cn1.hypervisor_hostname)) self.assertFalse(pt.exists(uuids.non_existing_rp)) self.assertFalse(pt.exists('noexist')) numa_cell0 = pt.new_child('numa_cell0', cn1.uuid) numa_cell1 = pt.new_child('numa_cell1', cn1.uuid) self.assertEqual(numa_cell0, pt.find('numa_cell0')) self.assertEqual(numa_cell0, pt.find(numa_cell0.uuid)) self.assertTrue(pt.exists(numa_cell0.uuid)) self.assertTrue(pt.exists('numa_cell0')) self.assertTrue(pt.exists(numa_cell1.uuid)) self.assertTrue(pt.exists('numa_cell1')) pf1_cell0 = pt.new_child('pf1_cell0', numa_cell0.uuid) self.assertTrue(pt.exists(pf1_cell0.uuid)) self.assertTrue(pt.exists('pf1_cell0')) self.assertRaises( ValueError, pt.new_child, 'pf1_cell0', uuids.non_existing_rp, ) cn3 = objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='compute-node-3', ) self.assertFalse(pt.exists(cn3.uuid)) self.assertFalse(pt.exists(cn3.hypervisor_hostname)) pt.new_root(cn3.hypervisor_hostname, cn3.uuid, 1) self.assertTrue(pt.exists(cn3.uuid)) self.assertTrue(pt.exists(cn3.hypervisor_hostname)) self.assertRaises( ValueError, pt.new_root, cn3.hypervisor_hostname, cn3.uuid, 1, ) self.assertRaises( ValueError, pt.remove, uuids.non_existing_rp, ) # Save numa cell1 uuid since removing will invalidate the object cell0_uuid = numa_cell0.uuid cell1_uuid = numa_cell1.uuid pf1_uuid = pf1_cell0.uuid pt.remove(cell1_uuid) self.assertFalse(pt.exists(cell1_uuid)) self.assertTrue(pt.exists(pf1_uuid)) self.assertTrue(pt.exists(cell0_uuid)) self.assertTrue(pt.exists(uuids.cn1)) # Now remove the root and check that children no longer exist pt.remove(uuids.cn1) self.assertFalse(pt.exists(pf1_uuid)) self.assertFalse(pt.exists(cell0_uuid)) self.assertFalse(pt.exists(uuids.cn1))
def test_update_instance_allocation_delete(self, mock_delete): cn = objects.ComputeNode(uuid=uuids.cn) inst = objects.Instance(uuid=uuids.inst) self.client.update_instance_allocation(cn, inst, -1) mock_delete.assert_called_once_with('/allocations/%s' % inst.uuid)
class ComputeHostAPITestCase(test.TestCase): def setUp(self): super(ComputeHostAPITestCase, self).setUp() self.host_api = compute.HostAPI() self.aggregate_api = compute_api.AggregateAPI() self.ctxt = context.get_admin_context() fake_notifier.stub_notifier(self) self.addCleanup(fake_notifier.reset) self.req = fakes.HTTPRequest.blank('') self.controller = services.ServiceController() self.useFixture(nova_fixtures.SingleCellSimple()) def _compare_obj(self, obj, db_obj): test_objects.compare_obj(self, obj, db_obj, allow_missing=test_service.OPTIONAL) def _compare_objs(self, obj_list, db_obj_list): self.assertEqual(len(obj_list), len(db_obj_list), "The length of two object lists are different.") for index, obj in enumerate(obj_list): self._compare_obj(obj, db_obj_list[index]) def test_set_host_enabled(self): fake_notifier.NOTIFICATIONS = [] @mock.patch.object(self.host_api.rpcapi, 'set_host_enabled', return_value='fake-result') @mock.patch.object(self.host_api, '_assert_host_exists', return_value='fake_host') def _do_test(mock_assert_host_exists, mock_set_host_enabled): result = self.host_api.set_host_enabled(self.ctxt, 'fake_host', 'fake_enabled') self.assertEqual('fake-result', result) self.assertEqual(2, len(fake_notifier.NOTIFICATIONS)) msg = fake_notifier.NOTIFICATIONS[0] self.assertEqual('HostAPI.set_enabled.start', msg.event_type) self.assertEqual('api.fake_host', msg.publisher_id) self.assertEqual('INFO', msg.priority) self.assertEqual('fake_enabled', msg.payload['enabled']) self.assertEqual('fake_host', msg.payload['host_name']) msg = fake_notifier.NOTIFICATIONS[1] self.assertEqual('HostAPI.set_enabled.end', msg.event_type) self.assertEqual('api.fake_host', msg.publisher_id) self.assertEqual('INFO', msg.priority) self.assertEqual('fake_enabled', msg.payload['enabled']) self.assertEqual('fake_host', msg.payload['host_name']) _do_test() def test_host_name_from_assert_hosts_exists(self): @mock.patch.object(self.host_api.rpcapi, 'set_host_enabled', return_value='fake-result') @mock.patch.object(self.host_api, '_assert_host_exists', return_value='fake_host') def _do_test(mock_assert_host_exists, mock_set_host_enabled): result = self.host_api.set_host_enabled(self.ctxt, 'fake_host', 'fake_enabled') self.assertEqual('fake-result', result) _do_test() def test_get_host_uptime(self): @mock.patch.object(self.host_api.rpcapi, 'get_host_uptime', return_value='fake-result') @mock.patch.object(self.host_api, '_assert_host_exists', return_value='fake_host') def _do_test(mock_assert_host_exists, mock_get_host_uptime): result = self.host_api.get_host_uptime(self.ctxt, 'fake_host') self.assertEqual('fake-result', result) _do_test() def test_get_host_uptime_service_down(self): @mock.patch.object(self.host_api.db, 'service_get_by_compute_host', return_value=dict(test_service.fake_service, id=1)) @mock.patch.object(self.host_api.servicegroup_api, 'service_is_up', return_value=False) def _do_test(mock_service_is_up, mock_service_get_by_compute_host): self.assertRaises(exception.ComputeServiceUnavailable, self.host_api.get_host_uptime, self.ctxt, 'fake_host') _do_test() def test_host_power_action(self): fake_notifier.NOTIFICATIONS = [] @mock.patch.object(self.host_api.rpcapi, 'host_power_action', return_value='fake-result') @mock.patch.object(self.host_api, '_assert_host_exists', return_value='fake_host') def _do_test(mock_assert_host_exists, mock_host_power_action): result = self.host_api.host_power_action(self.ctxt, 'fake_host', 'fake_action') self.assertEqual('fake-result', result) self.assertEqual(2, len(fake_notifier.NOTIFICATIONS)) msg = fake_notifier.NOTIFICATIONS[0] self.assertEqual('HostAPI.power_action.start', msg.event_type) self.assertEqual('api.fake_host', msg.publisher_id) self.assertEqual('INFO', msg.priority) self.assertEqual('fake_action', msg.payload['action']) self.assertEqual('fake_host', msg.payload['host_name']) msg = fake_notifier.NOTIFICATIONS[1] self.assertEqual('HostAPI.power_action.end', msg.event_type) self.assertEqual('api.fake_host', msg.publisher_id) self.assertEqual('INFO', msg.priority) self.assertEqual('fake_action', msg.payload['action']) self.assertEqual('fake_host', msg.payload['host_name']) _do_test() def test_set_host_maintenance(self): fake_notifier.NOTIFICATIONS = [] @mock.patch.object(self.host_api.rpcapi, 'host_maintenance_mode', return_value='fake-result') @mock.patch.object(self.host_api, '_assert_host_exists', return_value='fake_host') def _do_test(mock_assert_host_exists, mock_host_maintenance_mode): result = self.host_api.set_host_maintenance( self.ctxt, 'fake_host', 'fake_mode') self.assertEqual('fake-result', result) self.assertEqual(2, len(fake_notifier.NOTIFICATIONS)) msg = fake_notifier.NOTIFICATIONS[0] self.assertEqual('HostAPI.set_maintenance.start', msg.event_type) self.assertEqual('api.fake_host', msg.publisher_id) self.assertEqual('INFO', msg.priority) self.assertEqual('fake_host', msg.payload['host_name']) self.assertEqual('fake_mode', msg.payload['mode']) msg = fake_notifier.NOTIFICATIONS[1] self.assertEqual('HostAPI.set_maintenance.end', msg.event_type) self.assertEqual('api.fake_host', msg.publisher_id) self.assertEqual('INFO', msg.priority) self.assertEqual('fake_host', msg.payload['host_name']) self.assertEqual('fake_mode', msg.payload['mode']) _do_test() def test_service_get_all_cells(self): cells = objects.CellMappingList.get_all(self.ctxt) for cell in cells: with context.target_cell(self.ctxt, cell) as cctxt: objects.Service(context=cctxt, binary='nova-compute', host='host-%s' % cell.uuid).create() services = self.host_api.service_get_all(self.ctxt, all_cells=True) self.assertEqual(sorted(['host-%s' % cell.uuid for cell in cells]), sorted([svc.host for svc in services])) @mock.patch('nova.context.scatter_gather_cells') def test_service_get_all_cells_with_failures(self, mock_sg): service = objects.Service(binary='nova-compute', host='host-%s' % uuids.cell1) mock_sg.return_value = { uuids.cell1: [service], uuids.cell2: context.raised_exception_sentinel } services = self.host_api.service_get_all(self.ctxt, all_cells=True) # returns the results from cell1 and ignores cell2. self.assertEqual(['host-%s' % uuids.cell1], [svc.host for svc in services]) def test_service_get_all_no_zones(self): services = [ dict(test_service.fake_service, id=1, topic='compute', host='host1'), dict(test_service.fake_service, topic='compute', host='host2') ] @mock.patch.object(self.host_api.db, 'service_get_all') def _do_test(mock_service_get_all): mock_service_get_all.return_value = services # Test no filters result = self.host_api.service_get_all(self.ctxt) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, services) # Test no filters #2 mock_service_get_all.reset_mock() result = self.host_api.service_get_all(self.ctxt, filters={}) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, services) # Test w/ filter mock_service_get_all.reset_mock() result = self.host_api.service_get_all(self.ctxt, filters=dict(host='host2')) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, [services[1]]) _do_test() def test_service_get_all(self): services = [ dict(test_service.fake_service, topic='compute', host='host1'), dict(test_service.fake_service, topic='compute', host='host2') ] exp_services = [] for service in services: exp_service = {} exp_service.update(availability_zone='nova', **service) exp_services.append(exp_service) @mock.patch.object(self.host_api.db, 'service_get_all') def _do_test(mock_service_get_all): mock_service_get_all.return_value = services # Test no filters result = self.host_api.service_get_all(self.ctxt, set_zones=True) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, exp_services) # Test no filters #2 mock_service_get_all.reset_mock() result = self.host_api.service_get_all(self.ctxt, filters={}, set_zones=True) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, exp_services) # Test w/ filter mock_service_get_all.reset_mock() result = self.host_api.service_get_all(self.ctxt, filters=dict(host='host2'), set_zones=True) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, [exp_services[1]]) # Test w/ zone filter but no set_zones arg. mock_service_get_all.reset_mock() filters = {'availability_zone': 'nova'} result = self.host_api.service_get_all(self.ctxt, filters=filters) mock_service_get_all.assert_called_once_with(self.ctxt, disabled=None) self._compare_objs(result, exp_services) _do_test() def test_service_get_by_compute_host(self): @mock.patch.object(self.host_api.db, 'service_get_by_compute_host', return_value=test_service.fake_service) def _do_test(mock_service_get_by_compute_host): result = self.host_api.service_get_by_compute_host( self.ctxt, 'fake-host') self.assertEqual(test_service.fake_service['id'], result.id) _do_test() def test_service_update(self): host_name = 'fake-host' binary = 'nova-compute' params_to_update = dict(disabled=True) service_id = 42 expected_result = dict(test_service.fake_service, id=service_id) @mock.patch.object(self.host_api.db, 'service_get_by_host_and_binary') @mock.patch.object(self.host_api.db, 'service_update') def _do_test(mock_service_update, mock_service_get_by_host_and_binary): mock_service_get_by_host_and_binary.return_value = expected_result mock_service_update.return_value = expected_result result = self.host_api.service_update(self.ctxt, host_name, binary, params_to_update) self._compare_obj(result, expected_result) _do_test() @mock.patch.object(objects.InstanceList, 'get_by_host', return_value=['fake-responses']) def test_instance_get_all_by_host(self, mock_get): result = self.host_api.instance_get_all_by_host(self.ctxt, 'fake-host') self.assertEqual(['fake-responses'], result) def test_task_log_get_all(self): @mock.patch.object(self.host_api.db, 'task_log_get_all', return_value='fake-response') def _do_test(mock_task_log_get_all): result = self.host_api.task_log_get_all(self.ctxt, 'fake-name', 'fake-begin', 'fake-end', host='fake-host', state='fake-state') self.assertEqual('fake-response', result) _do_test() @mock.patch.object( objects.CellMappingList, 'get_all', return_value=objects.CellMappingList(objects=[ objects.CellMapping(uuid=uuids.cell1_uuid, transport_url='mq://fake1', database_connection='db://fake1'), objects.CellMapping(uuid=uuids.cell2_uuid, transport_url='mq://fake2', database_connection='db://fake2'), objects.CellMapping(uuid=uuids.cell3_uuid, transport_url='mq://fake3', database_connection='db://fake3') ])) @mock.patch.object( objects.Service, 'get_by_uuid', side_effect=[ exception.ServiceNotFound(service_id=uuids.service_uuid), objects.Service(uuid=uuids.service_uuid) ]) def test_service_get_by_id_using_uuid(self, service_get_by_uuid, cell_mappings_get_all): """Tests that we can lookup a service in the HostAPI using a uuid. There are two calls to objects.Service.get_by_uuid and the first raises ServiceNotFound so that we ensure we keep looping over the cells. We'll find the service in the second cell and break the loop so that we don't needlessly check in the third cell. """ def _fake_set_target_cell(ctxt, cell_mapping): if cell_mapping: # These aren't really what would be set for values but let's # keep this simple so we can assert something is set when a # mapping is provided. ctxt.db_connection = cell_mapping.database_connection ctxt.mq_connection = cell_mapping.transport_url # We have to override the SingleCellSimple fixture. self.useFixture( fixtures.MonkeyPatch('nova.context.set_target_cell', _fake_set_target_cell)) ctxt = context.get_admin_context() self.assertIsNone(ctxt.db_connection) self.host_api.service_get_by_id(ctxt, uuids.service_uuid) # We should have broken the loop over the cells and set the target cell # on the context. service_get_by_uuid.assert_has_calls( [mock.call(ctxt, uuids.service_uuid)] * 2) self.assertEqual('db://fake2', ctxt.db_connection) @mock.patch('nova.context.set_target_cell') @mock.patch('nova.compute.api.load_cells') @mock.patch('nova.objects.Service.get_by_id') def test_service_delete(self, get_by_id, load_cells, set_target): compute_api.CELLS = [ objects.CellMapping(), objects.CellMapping(), objects.CellMapping(), ] service = mock.MagicMock() get_by_id.side_effect = [ exception.ServiceNotFound(service_id=1), service, exception.ServiceNotFound(service_id=1) ] self.host_api.service_delete(self.ctxt, 1) get_by_id.assert_has_calls([ mock.call(self.ctxt, 1), mock.call(self.ctxt, 1), mock.call(self.ctxt, 1) ]) service.destroy.assert_called_once_with() set_target.assert_called_once_with(self.ctxt, compute_api.CELLS[1]) @mock.patch('nova.context.set_target_cell') @mock.patch('nova.compute.api.load_cells') @mock.patch('nova.objects.Service.get_by_id') def test_service_delete_ambiguous(self, get_by_id, load_cells, set_target): compute_api.CELLS = [ objects.CellMapping(), objects.CellMapping(), objects.CellMapping(), ] service1 = mock.MagicMock() service2 = mock.MagicMock() get_by_id.side_effect = [ exception.ServiceNotFound(service_id=1), service1, service2 ] self.assertRaises(exception.ServiceNotUnique, self.host_api.service_delete, self.ctxt, 1) self.assertFalse(service1.destroy.called) self.assertFalse(service2.destroy.called) self.assertFalse(set_target.called) @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' 'aggregate_remove_host') @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' 'aggregate_add_host') @mock.patch.object(objects.ComputeNodeList, 'get_all_by_host') @mock.patch.object(objects.HostMapping, 'get_by_host') def test_service_delete_compute_in_aggregate(self, mock_hm, mock_get_cn, mock_add_host, mock_remove_host): compute = self.host_api.db.service_create( self.ctxt, { 'host': 'fake-compute-host', 'binary': 'nova-compute', 'topic': 'compute', 'report_count': 0 }) # This is needed because of lazy-loading service.compute_node cn = objects.ComputeNode(uuid=uuids.cn, host="fake-compute-host", hypervisor_hostname="fake-compute-host") mock_get_cn.return_value = [cn] aggregate = self.aggregate_api.create_aggregate( self.ctxt, 'aggregate', None) self.aggregate_api.add_host_to_aggregate(self.ctxt, aggregate.id, 'fake-compute-host') mock_add_host.assert_called_once_with(mock.ANY, aggregate.uuid, 'fake-compute-host') self.controller.delete(self.req, compute.id) result = self.aggregate_api.get_aggregate(self.ctxt, aggregate.id).hosts self.assertEqual([], result) mock_hm.return_value.destroy.assert_called_once_with() mock_remove_host.assert_called_once_with(mock.ANY, aggregate.uuid, 'fake-compute-host') @mock.patch('nova.db.api.compute_node_statistics') def test_compute_node_statistics(self, mock_cns): # Note this should only be called twice mock_cns.side_effect = [ { 'stat1': 1, 'stat2': 4.0 }, { 'stat1': 5, 'stat2': 1.2 }, ] compute_api.CELLS = [ objects.CellMapping(uuid=uuids.cell1), objects.CellMapping(uuid=objects.CellMapping.CELL0_UUID), objects.CellMapping(uuid=uuids.cell2) ] stats = self.host_api.compute_node_statistics(self.ctxt) self.assertEqual({'stat1': 6, 'stat2': 5.2}, stats) @mock.patch.object( objects.CellMappingList, 'get_all', return_value=objects.CellMappingList(objects=[ objects.CellMapping(uuid=objects.CellMapping.CELL0_UUID, transport_url='mq://cell0', database_connection='db://cell0'), objects.CellMapping(uuid=uuids.cell1_uuid, transport_url='mq://fake1', database_connection='db://fake1'), objects.CellMapping(uuid=uuids.cell2_uuid, transport_url='mq://fake2', database_connection='db://fake2') ])) @mock.patch.object(objects.ComputeNode, 'get_by_uuid', side_effect=[ exception.ComputeHostNotFound(host=uuids.cn_uuid), objects.ComputeNode(uuid=uuids.cn_uuid) ]) def test_compute_node_get_using_uuid(self, compute_get_by_uuid, cell_mappings_get_all): """Tests that we can lookup a compute node in the HostAPI using a uuid. """ self.host_api.compute_node_get(self.ctxt, uuids.cn_uuid) # cell0 should have been skipped, and the compute node wasn't found # in cell1 so we checked cell2 and found it self.assertEqual(2, compute_get_by_uuid.call_count) compute_get_by_uuid.assert_has_calls( [mock.call(self.ctxt, uuids.cn_uuid)] * 2) @mock.patch.object( objects.CellMappingList, 'get_all', return_value=objects.CellMappingList(objects=[ objects.CellMapping(uuid=objects.CellMapping.CELL0_UUID, transport_url='mq://cell0', database_connection='db://cell0'), objects.CellMapping(uuid=uuids.cell1_uuid, transport_url='mq://fake1', database_connection='db://fake1'), objects.CellMapping(uuid=uuids.cell2_uuid, transport_url='mq://fake2', database_connection='db://fake2') ])) @mock.patch.object( objects.ComputeNode, 'get_by_uuid', side_effect=exception.ComputeHostNotFound(host=uuids.cn_uuid)) def test_compute_node_get_not_found(self, compute_get_by_uuid, cell_mappings_get_all): """Tests that we can lookup a compute node in the HostAPI using a uuid and will fail with ComputeHostNotFound if we didn't find it in any cell. """ self.assertRaises(exception.ComputeHostNotFound, self.host_api.compute_node_get, self.ctxt, uuids.cn_uuid) # cell0 should have been skipped, and the compute node wasn't found # in cell1 or cell2. self.assertEqual(2, compute_get_by_uuid.call_count) compute_get_by_uuid.assert_has_calls( [mock.call(self.ctxt, uuids.cn_uuid)] * 2)
def _create_fake_node(host, total_mem, total_disk, free_mem, free_disk): return objects.ComputeNode(host=host, memory_mb=total_mem, local_gb=total_disk, free_ram_mb=free_mem, free_disk_gb=free_disk)
fake_compute_obj = objects.ComputeNode( host=_HOSTNAME, vcpus=_VIRT_DRIVER_AVAIL_RESOURCES['vcpus'], memory_mb=_VIRT_DRIVER_AVAIL_RESOURCES['memory_mb'], local_gb=_VIRT_DRIVER_AVAIL_RESOURCES['local_gb'], vcpus_used=_VIRT_DRIVER_AVAIL_RESOURCES['vcpus_used'], memory_mb_used=_VIRT_DRIVER_AVAIL_RESOURCES['memory_mb_used'], local_gb_used=_VIRT_DRIVER_AVAIL_RESOURCES['local_gb_used'], hypervisor_type='fake', hypervisor_version=0, hypervisor_hostname=_HOSTNAME, free_ram_mb=(_VIRT_DRIVER_AVAIL_RESOURCES['memory_mb'] - _VIRT_DRIVER_AVAIL_RESOURCES['memory_mb_used']), free_disk_gb=(_VIRT_DRIVER_AVAIL_RESOURCES['local_gb'] - _VIRT_DRIVER_AVAIL_RESOURCES['local_gb_used']), current_workload=0, running_vms=0, cpu_info='{}', disk_available_least=0, host_ip='1.1.1.1', supported_hv_specs=[ objects.HVSpec.from_list([ obj_fields.Architecture.I686, obj_fields.HVType.KVM, obj_fields.VMMode.HVM]) ], metrics=None, pci_device_pools=None, extra_resources=None, stats={}, numa_topology=None, cpu_allocation_ratio=16.0, ram_allocation_ratio=1.5, disk_allocation_ratio=1.0, )
class IronicResourceTrackerTest(test.TestCase): """Tests the behaviour of the resource tracker with regards to the transitional period between adding support for custom resource classes in the placement API and integrating inventory and allocation records for Ironic baremetal nodes with those custom resource classes. """ FLAVOR_FIXTURES = { 'CUSTOM_SMALL_IRON': objects.Flavor( name='CUSTOM_SMALL_IRON', flavorid=42, vcpus=4, memory_mb=4096, root_gb=1024, swap=0, ephemeral_gb=0, extra_specs={}, ), 'CUSTOM_BIG_IRON': objects.Flavor( name='CUSTOM_BIG_IRON', flavorid=43, vcpus=16, memory_mb=65536, root_gb=1024, swap=0, ephemeral_gb=0, extra_specs={}, ), } COMPUTE_NODE_FIXTURES = { uuids.cn1: objects.ComputeNode( uuid=uuids.cn1, hypervisor_hostname='cn1', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=4, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=4096, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=1024, local_gb_used=0, disk_allocation_ratio=1.0, ), uuids.cn2: objects.ComputeNode( uuid=uuids.cn2, hypervisor_hostname='cn2', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=4, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=4096, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=1024, local_gb_used=0, disk_allocation_ratio=1.0, ), uuids.cn3: objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='cn3', hypervisor_type='ironic', hypervisor_version=0, cpu_info="", host=COMPUTE_HOST, vcpus=16, vcpus_used=0, cpu_allocation_ratio=1.0, memory_mb=65536, memory_mb_used=0, ram_allocation_ratio=1.0, local_gb=2048, local_gb_used=0, disk_allocation_ratio=1.0, ), } INSTANCE_FIXTURES = { uuids.instance1: objects.Instance( uuid=uuids.instance1, flavor=FLAVOR_FIXTURES['CUSTOM_SMALL_IRON'], vm_state=vm_states.BUILDING, task_state=task_states.SPAWNING, power_state=power_state.RUNNING, project_id='project', user_id=uuids.user, ), } def setUp(self): super(IronicResourceTrackerTest, self).setUp() self.flags(auth_strategy='noauth2', group='api') self.flags( reserved_host_memory_mb=0, cpu_allocation_ratio=1.0, ram_allocation_ratio=1.0, disk_allocation_ratio=1.0, ) self.ctx = context.RequestContext('user', 'project') self.app = lambda: deploy.loadapp(CONF) self.report_client = test_report_client.NoAuthReportClient() driver = mock.MagicMock(autospec='nova.virt.driver.ComputeDriver') driver.node_is_available.return_value = True self.driver_mock = driver self.rt = resource_tracker.ResourceTracker(COMPUTE_HOST, driver) self.rt.scheduler_client.reportclient = self.report_client self.rt.reportclient = self.report_client self.url = 'http://localhost/placement' self.create_fixtures() def create_fixtures(self): for flavor in self.FLAVOR_FIXTURES.values(): flavor._context = self.ctx flavor.obj_set_defaults() flavor.create() # We create some compute node records in the Nova cell DB to simulate # data before adding integration for Ironic baremetal nodes with the # placement API... for cn in self.COMPUTE_NODE_FIXTURES.values(): cn._context = self.ctx cn.obj_set_defaults() cn.create() for instance in self.INSTANCE_FIXTURES.values(): instance._context = self.ctx instance.obj_set_defaults() instance.create() def placement_get_inventory(self, rp_uuid): url = '/resource_providers/%s/inventories' % rp_uuid resp = self.report_client.get(url) if 200 <= resp.status_code < 300: return resp.json()['inventories'] else: return resp.status_code def placement_get_allocations(self, consumer_uuid): url = '/allocations/%s' % consumer_uuid resp = self.report_client.get(url) if 200 <= resp.status_code < 300: return resp.json()['allocations'] else: return resp.status_code def placement_get_custom_rcs(self): url = '/resource_classes' resp = self.report_client.get(url) if 200 <= resp.status_code < 300: all_rcs = resp.json()['resource_classes'] return [ rc['name'] for rc in all_rcs if rc['name'] not in fields.ResourceClass.STANDARD ] @mock.patch('nova.compute.utils.is_volume_backed_instance', return_value=False) @mock.patch('nova.objects.compute_node.ComputeNode.save') @mock.patch('keystoneauth1.session.Session.get_auth_headers', return_value={'x-auth-token': 'admin'}) @mock.patch('keystoneauth1.session.Session.get_endpoint', return_value='http://localhost/placement') def test_ironic_ocata_to_pike(self, mock_vbi, mock_endpoint, mock_auth, mock_cn): """Check that when going from an Ocata installation with Ironic having node's resource class attributes set, that we properly "auto-heal" the inventory and allocation records in the placement API to account for both the old-style VCPU/MEMORY_MB/DISK_GB resources as well as the new custom resource class from Ironic's node.resource_class attribute. """ with interceptor.RequestsInterceptor(app=self.app, url=self.url): # Before the resource tracker is "initialized", we shouldn't have # any compute nodes in the RT's cache... self.assertEqual(0, len(self.rt.compute_nodes)) # There should not be any records in the placement API since we # haven't yet run update_available_resource() in the RT. for cn in self.COMPUTE_NODE_FIXTURES.values(): self.assertEqual(404, self.placement_get_inventory(cn.uuid)) for inst in self.INSTANCE_FIXTURES.keys(): self.assertEqual({}, self.placement_get_allocations(inst)) # Nor should there be any custom resource classes in the placement # API, since we haven't had an Ironic node's resource class set yet self.assertEqual(0, len(self.placement_get_custom_rcs())) # Now "initialize" the resource tracker as if the compute host is a # Ocata host, with Ironic virt driver, but the admin has not yet # added a resource_class attribute to the Ironic baremetal nodes in # her system. # NOTE(jaypipes): This is what nova.compute.manager.ComputeManager # does when "initializing" the service... for cn in self.COMPUTE_NODE_FIXTURES.values(): nodename = cn.hypervisor_hostname self.driver_mock.get_available_resource.return_value = { 'hypervisor_hostname': nodename, 'hypervisor_type': 'ironic', 'hypervisor_version': 0, 'vcpus': cn.vcpus, 'vcpus_used': cn.vcpus_used, 'memory_mb': cn.memory_mb, 'memory_mb_used': cn.memory_mb_used, 'local_gb': cn.local_gb, 'local_gb_used': cn.local_gb_used, 'numa_topology': None, 'resource_class': None, # Act like admin hasn't set yet... } self.driver_mock.get_inventory.return_value = { VCPU: { 'total': cn.vcpus, 'reserved': 0, 'min_unit': 1, 'max_unit': cn.vcpus, 'step_size': 1, 'allocation_ratio': 1.0, }, MEMORY_MB: { 'total': cn.memory_mb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn.memory_mb, 'step_size': 1, 'allocation_ratio': 1.0, }, DISK_GB: { 'total': cn.local_gb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn.local_gb, 'step_size': 1, 'allocation_ratio': 1.0, }, } self.rt.update_available_resource(self.ctx, nodename) self.assertEqual(3, len(self.rt.compute_nodes)) # A canary just to make sure the assertion below about the custom # resource class being added wasn't already added somehow... crcs = self.placement_get_custom_rcs() self.assertNotIn('CUSTOM_SMALL_IRON', crcs) # Verify that the placement API has the "old-style" resources in # inventory and allocations for cn in self.COMPUTE_NODE_FIXTURES.values(): inv = self.placement_get_inventory(cn.uuid) self.assertEqual(3, len(inv)) # Now "spawn" an instance to the first compute node by calling the # RT's instance_claim(). cn1_obj = self.COMPUTE_NODE_FIXTURES[uuids.cn1] cn1_nodename = cn1_obj.hypervisor_hostname inst = self.INSTANCE_FIXTURES[uuids.instance1] # Since we're pike, the scheduler would have created our # allocation for us. So, we can use our old update routine # here to mimic that before we go do the compute RT claim, # and then the checks below. self.rt.reportclient.update_instance_allocation( self.ctx, cn1_obj, inst, 1) with self.rt.instance_claim(self.ctx, inst, cn1_nodename): pass allocs = self.placement_get_allocations(inst.uuid) self.assertEqual(1, len(allocs)) self.assertIn(uuids.cn1, allocs) resources = allocs[uuids.cn1]['resources'] self.assertEqual(3, len(resources)) for rc in (VCPU, MEMORY_MB, DISK_GB): self.assertIn(rc, resources) # Now we emulate the operator setting ONE of the Ironic node's # resource class attribute to the value of a custom resource class # and re-run update_available_resource(). We will expect to see the # inventory and allocations reset for the first compute node that # had an instance on it. The new inventory and allocation records # will be for VCPU, MEMORY_MB, DISK_GB, and also a new record for # the custom resource class of the Ironic node. self.driver_mock.get_available_resource.return_value = { 'hypervisor_hostname': cn1_obj.hypervisor_hostname, 'hypervisor_type': 'ironic', 'hypervisor_version': 0, 'vcpus': cn1_obj.vcpus, 'vcpus_used': cn1_obj.vcpus_used, 'memory_mb': cn1_obj.memory_mb, 'memory_mb_used': cn1_obj.memory_mb_used, 'local_gb': cn1_obj.local_gb, 'local_gb_used': cn1_obj.local_gb_used, 'numa_topology': None, 'resource_class': 'small-iron', } self.driver_mock.get_inventory.return_value = { VCPU: { 'total': cn1_obj.vcpus, 'reserved': 0, 'min_unit': 1, 'max_unit': cn1_obj.vcpus, 'step_size': 1, 'allocation_ratio': 1.0, }, MEMORY_MB: { 'total': cn1_obj.memory_mb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn1_obj.memory_mb, 'step_size': 1, 'allocation_ratio': 1.0, }, DISK_GB: { 'total': cn1_obj.local_gb, 'reserved': 0, 'min_unit': 1, 'max_unit': cn1_obj.local_gb, 'step_size': 1, 'allocation_ratio': 1.0, }, 'CUSTOM_SMALL_IRON': { 'total': 1, 'reserved': 0, 'min_unit': 1, 'max_unit': 1, 'step_size': 1, 'allocation_ratio': 1.0, }, } self.rt.update_available_resource(self.ctx, cn1_nodename) # Verify the auto-creation of the custom resource class, normalized # to what the placement API expects self.assertIn('CUSTOM_SMALL_IRON', self.placement_get_custom_rcs()) allocs = self.placement_get_allocations(inst.uuid) self.assertEqual(1, len(allocs)) self.assertIn(uuids.cn1, allocs) resources = allocs[uuids.cn1]['resources'] self.assertEqual(3, len(resources)) for rc in (VCPU, MEMORY_MB, DISK_GB): self.assertIn(rc, resources)
from nova.objects import pci_device_pool from nova.pci import device from nova import test from nova.tests.unit.api.openstack import fakes from nova.tests.unit.objects import test_pci_device pci_stats = [{ "count": 3, "vendor_id": "8086", "product_id": "1520", "extra_info": { "phys_function": '[["0x0000", "0x04", ' '"0x00", "0x1"]]' } }] fake_compute_node = objects.ComputeNode( pci_device_pools=pci_device_pool.from_pci_stats(pci_stats)) class FakeResponse(wsgi.ResponseObject): pass class PciServerControllerTestV21(test.NoDBTestCase): def setUp(self): super(PciServerControllerTestV21, self).setUp() self.controller = pci.PciServerController() self.fake_obj = { 'server': { 'addresses': {}, 'id': 'fb08', 'name': 'a3',
def test_create_ironic_node_state(self, init_mock): init_mock.return_value = None compute = objects.ComputeNode(**{'hypervisor_type': 'ironic'}) host_state = self.host_manager.host_state_cls('fake-host', 'fake-node', compute=compute) self.assertIs(ironic_host_manager.IronicNodeState, type(host_state))
def test_compat_service_id(self, mock_get): mock_get.return_value = objects.Service(id=1) compute = objects.ComputeNode(host='fake-host', service_id=None) primitive = compute.obj_to_primitive(target_version='1.12') self.assertEqual(1, primitive['nova_object.data']['service_id'])
def test_create_non_ironic_host_state(self, init_mock): init_mock.return_value = None compute = objects.ComputeNode(**{'cpu_info': 'other cpu'}) host_state = self.host_manager.host_state_cls('fake-host', 'fake-node', compute=compute) self.assertIs(host_manager.HostState, type(host_state))
def test_check_instance_has_no_numa_passes_no_numa(self, mock_get): self.flags(enable_numa_live_migration=False, group='workarounds') self.task.instance.numa_topology = None mock_get.return_value = objects.ComputeNode( uuid=uuids.cn1, hypervisor_type='qemu') self.task._check_instance_has_no_numa()
def test_tree_ops(self): cn1 = self.compute_node1 cn2 = self.compute_node2 pt = self._pt_with_cns() self.assertRaises( ValueError, pt.new_root, cn1.hypervisor_hostname, cn1.uuid, ) self.assertTrue(pt.exists(cn1.uuid)) self.assertTrue(pt.exists(cn1.hypervisor_hostname)) self.assertFalse(pt.exists(uuids.non_existing_rp)) self.assertFalse(pt.exists('noexist')) self.assertEqual([cn1.uuid], pt.get_provider_uuids(name_or_uuid=cn1.uuid)) # Same with ..._in_tree self.assertEqual([cn1.uuid], pt.get_provider_uuids_in_tree(cn1.uuid)) self.assertEqual(set([cn1.uuid, cn2.uuid]), set(pt.get_provider_uuids())) numa_cell0_uuid = pt.new_child('numa_cell0', cn1.uuid) numa_cell1_uuid = pt.new_child('numa_cell1', cn1.hypervisor_hostname) self.assertEqual(cn1.uuid, pt.data(numa_cell1_uuid).parent_uuid) self.assertTrue(pt.exists(numa_cell0_uuid)) self.assertTrue(pt.exists('numa_cell0')) self.assertTrue(pt.exists(numa_cell1_uuid)) self.assertTrue(pt.exists('numa_cell1')) pf1_cell0_uuid = pt.new_child('pf1_cell0', numa_cell0_uuid) self.assertTrue(pt.exists(pf1_cell0_uuid)) self.assertTrue(pt.exists('pf1_cell0')) # Now we've got a 3-level tree under cn1 - check provider UUIDs again all_cn1 = [cn1.uuid, numa_cell0_uuid, pf1_cell0_uuid, numa_cell1_uuid] self.assertEqual(set(all_cn1), set(pt.get_provider_uuids(name_or_uuid=cn1.uuid))) # Same with ..._in_tree if we're asking for the root self.assertEqual(set(all_cn1), set(pt.get_provider_uuids_in_tree(cn1.uuid))) # Asking for a subtree. self.assertEqual([numa_cell0_uuid, pf1_cell0_uuid], pt.get_provider_uuids(name_or_uuid=numa_cell0_uuid)) # With ..._in_tree, get the whole tree no matter which we specify. for node in all_cn1: self.assertEqual(set(all_cn1), set(pt.get_provider_uuids_in_tree(node))) # With no provider specified, get everything self.assertEqual( set([ cn1.uuid, cn2.uuid, numa_cell0_uuid, pf1_cell0_uuid, numa_cell1_uuid ]), set(pt.get_provider_uuids())) self.assertRaises( ValueError, pt.new_child, 'pf1_cell0', uuids.non_existing_rp, ) # Fail attempting to add a child that already exists in the tree # Existing provider is a child; search by name self.assertRaises(ValueError, pt.new_child, 'numa_cell0', cn1.uuid) # Existing provider is a root; search by UUID self.assertRaises(ValueError, pt.new_child, cn1.uuid, cn2.uuid) # Test data(). # Root, by UUID cn1_snap = pt.data(cn1.uuid) # Fields were faithfully copied self.assertEqual(cn1.uuid, cn1_snap.uuid) self.assertEqual(cn1.hypervisor_hostname, cn1_snap.name) self.assertIsNone(cn1_snap.parent_uuid) self.assertEqual({}, cn1_snap.inventory) self.assertEqual(set(), cn1_snap.traits) self.assertEqual(set(), cn1_snap.aggregates) # Validate read-only-ness self.assertRaises(AttributeError, setattr, cn1_snap, 'name', 'foo') cn3 = objects.ComputeNode( uuid=uuids.cn3, hypervisor_hostname='compute-node-3', ) self.assertFalse(pt.exists(cn3.uuid)) self.assertFalse(pt.exists(cn3.hypervisor_hostname)) pt.new_root(cn3.hypervisor_hostname, cn3.uuid) self.assertTrue(pt.exists(cn3.uuid)) self.assertTrue(pt.exists(cn3.hypervisor_hostname)) self.assertRaises( ValueError, pt.new_root, cn3.hypervisor_hostname, cn3.uuid, ) self.assertRaises( ValueError, pt.remove, uuids.non_existing_rp, ) pt.remove(numa_cell1_uuid) self.assertFalse(pt.exists(numa_cell1_uuid)) self.assertTrue(pt.exists(pf1_cell0_uuid)) self.assertTrue(pt.exists(numa_cell0_uuid)) self.assertTrue(pt.exists(uuids.cn1)) # Now remove the root and check that children no longer exist pt.remove(uuids.cn1) self.assertFalse(pt.exists(pf1_cell0_uuid)) self.assertFalse(pt.exists(numa_cell0_uuid)) self.assertFalse(pt.exists(uuids.cn1))
def _create_tracker(self, fake_devs): self.fake_devs = fake_devs self.tracker = manager.PciDevTracker( self.fake_context, objects.ComputeNode(id=1, numa_topology=None))
l3_size=0, l3_granularity=0, l3_both_used=0, l3_code_used=0, l3_data_used=0), ]), ] COMPUTE_NODES = [ objects.ComputeNode( uuid=uuidsentinel.cn1, id=1, local_gb=1024, memory_mb=1024, vcpus=1, disk_available_least=None, free_ram_mb=512, vcpus_used=1, free_disk_gb=512, local_gb_used=0, updated_at=datetime.datetime(2015, 11, 11, 11, 0, 0), host='host1', hypervisor_hostname='node1', host_ip='127.0.0.1', hypervisor_version=0, numa_topology=None, hypervisor_type='foo', supported_hv_specs=[], pci_device_pools=None, cpu_info=None, stats=None, metrics=None, cpu_allocation_ratio=16.0, ram_allocation_ratio=1.5, disk_allocation_ratio=1.0, l3_closids=16, l3_closids_used=1), objects.ComputeNode( uuid=uuidsentinel.cn2, id=2, local_gb=2048, memory_mb=2048, vcpus=2, disk_available_least=1024, free_ram_mb=1024, vcpus_used=2, free_disk_gb=1024, local_gb_used=0, updated_at=datetime.datetime(2015, 11, 11, 11, 0, 0), host='host2', hypervisor_hostname='node2', host_ip='127.0.0.1', hypervisor_version=0, numa_topology=None, hypervisor_type='foo', supported_hv_specs=[],
report_count=5, disabled=False, disabled_reason=None, availability_zone="nova"), objects.Service(id=2, host="compute2", binary="nova-compute", topic="compute_topic", report_count=5, disabled=False, disabled_reason=None, availability_zone="nova"), ] TEST_HYPERS_OBJ = [ objects.ComputeNode(**hyper_dct) for hyper_dct in TEST_HYPERS ] TEST_HYPERS[0].update({'service': TEST_SERVICES[0]}) TEST_HYPERS[1].update({'service': TEST_SERVICES[1]}) TEST_SERVERS = [ dict(name="inst1", uuid="uuid1", host="compute1"), dict(name="inst2", uuid="uuid2", host="compute2"), dict(name="inst3", uuid="uuid3", host="compute1"), dict(name="inst4", uuid="uuid4", host="compute2") ] def fake_compute_node_get_all(context): return TEST_HYPERS_OBJ
import nova.conf from nova import config from nova import objects from nova import context from nova import db from nova.db.sqlalchemy import models LOG = logging.getLogger(__name__) CONF = nova.conf.CONF argv = [] default_config_files = ['/etc/nova/nova.conf'] config.parse_args(argv, default_config_files=default_config_files) objects.register_all() context = context.get_admin_context() nodes = objects.ComputeNodeList.get_all(context) [it['deleted'] for it in nodes] import nova.db.sqlalchemy.api as api cxt = api.get_context_manager(context) t = cxt.writer.using(context) s = t.__enter__() s.query(models.ComputeNode).first() s.rollback() s.close() cn = objects.ComputeNode(context) cn.memory_mb_used