# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime from unittest import mock from oslo_config import cfg from oslo_utils import timeutils from nova import context from nova import exception from nova.network import model as network_model from nova import objects from nova.objects import virtual_interface from nova.tests.functional import integrated_helpers from nova.tests.unit import fake_network CONF = cfg.CONF FAKE_UUID = '00000000-0000-0000-0000-000000000000' def _delete_vif_list(context, instance_uuid): vif_list = objects.VirtualInterfaceList.\ get_by_instance_uuid(context, instance_uuid) # Set old VirtualInterfaces as deleted. for vif in vif_list: vif.destroy() def _verify_list_fulfillment(context, instance_uuid): try: info_cache = objects.InstanceInfoCache.\ get_by_instance_uuid(context, instance_uuid) except exception.InstanceInfoCacheNotFound: info_cache = [] vif_list = objects.VirtualInterfaceList.\ get_by_instance_uuid(context, instance_uuid) vif_list = filter(lambda x: not x.deleted, vif_list) cached_vif_ids = [vif['id'] for vif in info_cache.network_info] db_vif_ids = [vif.uuid for vif in vif_list] return cached_vif_ids == db_vif_ids class VirtualInterfaceListMigrationTestCase( integrated_helpers._IntegratedTestBase): ADMIN_API = True api_major_version = 'v2.1' def setUp(self): super(VirtualInterfaceListMigrationTestCase, self).setUp() self.context = context.get_admin_context() fake_network.set_stub_network_methods(self) self.cells = objects.CellMappingList.get_all(self.context) self._start_compute('compute2') self.instances = [] def _create_instances(self, pre_newton=2, deleted=0, total=5, target_cell=None): if not target_cell: target_cell = self.cells[1] instances = [] with context.target_cell(self.context, target_cell) as cctxt: flav_dict = objects.Flavor._flavor_get_from_db(cctxt, 1) flavor = objects.Flavor(**flav_dict) for i in range(0, total): inst = objects.Instance( context=cctxt, project_id=self.api.project_id, user_id=FAKE_UUID, vm_state='active', flavor=flavor, created_at=datetime.datetime(1985, 10, 25, 1, 21, 0), launched_at=datetime.datetime(1985, 10, 25, 1, 22, 0), host=self.computes['compute2'].host, hostname='%s-inst%i' % (target_cell.name, i)) inst.create() info_cache = objects.InstanceInfoCache(context=cctxt) info_cache.updated_at = timeutils.utcnow() info_cache.network_info = network_model.NetworkInfo() info_cache.instance_uuid = inst.uuid info_cache.save() instances.append(inst) im = objects.InstanceMapping(context=cctxt, project_id=inst.project_id, user_id=inst.user_id, instance_uuid=inst.uuid, cell_mapping=target_cell) im.create() # Attach fake interfaces to instances network_id = list(self.neutron._networks.keys())[0] for i in range(0, len(instances)): for k in range(0, 4): self.api.attach_interface(instances[i].uuid, {"interfaceAttachment": {"net_id": network_id}}) with context.target_cell(self.context, target_cell) as cctxt: # Fake the pre-newton behaviour by removing the # VirtualInterfacesList objects. if pre_newton: for i in range(0, pre_newton): _delete_vif_list(cctxt, instances[i].uuid) if deleted: # Delete from the end of active instances list for i in range(total - deleted, total): instances[i].destroy() self.instances += instances def test_migration_nothing_to_migrate(self): """This test when there already populated VirtualInterfaceList objects for created instances. """ self._create_instances(pre_newton=0, total=5) match, done = virtual_interface.fill_virtual_interface_list( self.context, 5) self.assertEqual(5, match) self.assertEqual(0, done) def test_migration_verify_max_count(self): """This verifies if max_count is respected to avoid migration of bigger set of data, than user specified. """ self._create_instances(pre_newton=0, total=3) match, done = virtual_interface.fill_virtual_interface_list( self.context, 2) self.assertEqual(2, match) self.assertEqual(0, done) def test_migration_do_not_step_to_next_cell(self): """This verifies if script doesn't step into next cell when max_count is reached. """ # Create 2 instances in cell0 self._create_instances( pre_newton=0, total=2, target_cell=self.cells[0]) # Create 2 instances in cell1 self._create_instances( pre_newton=0, total=2, target_cell=self.cells[1]) with mock.patch('nova.objects.InstanceList.get_by_filters', side_effect=[self.instances[0:2], self.instances[2:]]) \ as mock_get: match, done = virtual_interface.fill_virtual_interface_list( self.context, 2) self.assertEqual(2, match) self.assertEqual(0, done) mock_get.assert_called_once() def test_migration_pre_newton_instances(self): """This test when there is an instance created in release older than Newton. For those instances the VirtualInterfaceList needs to be re-created from cache. """ # Lets spawn 3 pre-newton instances and 2 new ones self._create_instances(pre_newton=3, total=5) match, done = virtual_interface.fill_virtual_interface_list( self.context, 5) self.assertEqual(5, match) self.assertEqual(3, done) # Make sure we ran over all the instances - verify if marker works match, done = virtual_interface.fill_virtual_interface_list( self.context, 50) self.assertEqual(0, match) self.assertEqual(0, done) for i in range(0, 5): _verify_list_fulfillment(self.context, self.instances[i].uuid) def test_migration_pre_newton_instance_new_vifs(self): """This test when instance was created before Newton but in meantime new interfaces where attached and VirtualInterfaceList is not populated. """ self._create_instances(pre_newton=0, total=1) vif_list = objects.VirtualInterfaceList.get_by_instance_uuid( self.context, self.instances[0].uuid) # Drop first vif from list to pretend old instance vif_list[0].destroy() match, done = virtual_interface.fill_virtual_interface_list( self.context, 5) # The whole VirtualInterfaceList should be rewritten and base # on cache. self.assertEqual(1, match) self.assertEqual(1, done) _verify_list_fulfillment(self.context, self.instances[0].uuid) def test_migration_attach_in_progress(self): """This test when number of vifs (db) is bigger than number taken from network cache. Potential port-attach is taking place. """ self._create_instances(pre_newton=0, total=1) instance_info_cache = objects.InstanceInfoCache.get_by_instance_uuid( self.context, self.instances[0].uuid) # Delete last interface to pretend that's still in progress instance_info_cache.network_info.pop() instance_info_cache.updated_at = datetime.datetime(2015, 1, 1) instance_info_cache.save() match, done = virtual_interface.fill_virtual_interface_list( self.context, 5) # I don't know what's going on so instance VirtualInterfaceList # should stay untouched. self.assertEqual(1, match) self.assertEqual(0, done) def test_migration_empty_network_info(self): """This test if migration is not executed while NetworkInfo is empty, like instance without interfaces attached. """ self._create_instances(pre_newton=0, total=1) instance_info_cache = objects.InstanceInfoCache.get_by_instance_uuid( self.context, self.instances[0].uuid) # Clean NetworkInfo. Pretend instance without interfaces. instance_info_cache.network_info = None instance_info_cache.save() match, done = virtual_interface.fill_virtual_interface_list( self.context, 5) self.assertEqual(0, match) self.assertEqual(0, done) def test_migration_inconsistent_data(self): """This test when vif (db) are in completely different comparing to network cache and we don't know how to deal with it. It's the corner-case. """ self._create_instances(pre_newton=0, total=1) instance_info_cache = objects.InstanceInfoCache.get_by_instance_uuid( self.context, self.instances[0].uuid) # Change order of interfaces in NetworkInfo to fake # inconsistency between cache and db. nwinfo = instance_info_cache.network_info interface = nwinfo.pop() nwinfo.insert(0, interface) instance_info_cache.updated_at = datetime.datetime(2015, 1, 1) instance_info_cache.network_info = nwinfo # Update the cache instance_info_cache.save() match, done = virtual_interface.fill_virtual_interface_list( self.context, 5) # Cache is corrupted, so must be rewritten self.assertEqual(1, match) self.assertEqual(1, done) def test_migration_dont_touch_deleted_objects(self): """This test if deleted instances are skipped during migration. """ self._create_instances( pre_newton=1, deleted=1, total=3) match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(2, match) self.assertEqual(1, done) def test_migration_multiple_cells(self): """This test if marker and max_rows limit works properly while running in multi-cell environment. """ # Create 2 instances in cell0 self._create_instances( pre_newton=1, total=2, target_cell=self.cells[0]) # Create 4 instances in cell1 self._create_instances( pre_newton=3, total=5, target_cell=self.cells[1]) # Fill vif list limiting to 4 instances - it should # touch cell0 and cell1 instances (migrate 3 due 1 is post newton). match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(4, match) self.assertEqual(3, done) # Verify that the marker instance has project_id/user_id set properly. with context.target_cell(self.context, self.cells[1]) as cctxt: # The marker record is destroyed right after it's created, since # only the presence of the row is needed to satisfy the fkey # constraint. cctxt = cctxt.elevated(read_deleted='yes') marker_instance = objects.Instance.get_by_uuid(cctxt, FAKE_UUID) self.assertEqual(FAKE_UUID, marker_instance.project_id) self.assertEqual(FAKE_UUID, marker_instance.user_id) # Try again - should fill 3 left instances from cell1 match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(3, match) self.assertEqual(1, done) # Try again - should be nothing to migrate match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(0, match) self.assertEqual(0, done) def test_migration_multiple_cells_new_instances_in_meantime(self): """This test if marker is created per-cell and we're able to verify instanced that were added in meantime. """ # Create 2 instances in cell0 self._create_instances( pre_newton=1, total=2, target_cell=self.cells[0]) # Create 2 instances in cell1 self._create_instances( pre_newton=1, total=2, target_cell=self.cells[1]) # Migrate instances in both cells. match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(4, match) self.assertEqual(2, done) # Add new instances to cell1 self._create_instances( pre_newton=0, total=2, target_cell=self.cells[1]) # Try again, should find instances in cell1 match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(2, match) self.assertEqual(0, done) # Try again - should be nothing to migrate match, done = virtual_interface.fill_virtual_interface_list( self.context, 4) self.assertEqual(0, match) self.assertEqual(0, done)