diff --git a/nova/tests/virt/libvirt/test_driver.py b/nova/tests/virt/libvirt/test_driver.py index 60df28726002..fe5e57f03e0f 100644 --- a/nova/tests/virt/libvirt/test_driver.py +++ b/nova/tests/virt/libvirt/test_driver.py @@ -2175,7 +2175,6 @@ class LibvirtConnTestCase(test.TestCase, """ self.flags(virt_type='lxc', group='libvirt') conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) - mock_domain = mock.MagicMock() mock_instance = mock.MagicMock() mock_get_inst_path.return_value = '/tmp/' mock_image_backend = mock.MagicMock() @@ -2185,14 +2184,22 @@ class LibvirtConnTestCase(test.TestCase, conn.image_backend.image.return_value = mock_image mock_setup_container.return_value = '/dev/nbd0' mock_get_info.side_effect = exception.InstanceNotFound( - instance_id='foo') + instance_id='foo') + conn._conn.defineXML = mock.Mock() + conn._conn.defineXML.side_effect = ValueError('somethingbad') + with contextlib.nested( + mock.patch.object(conn, '_is_booted_from_volume', + return_value=False), + mock.patch.object(conn, 'plug_vifs'), + mock.patch.object(conn, 'firewall_driver'), + mock.patch.object(conn, 'cleanup')): + self.assertRaises(ValueError, + conn._create_domain_and_network, + self.context, + 'xml', + mock_instance, None) - mock_domain.createWithFlags.side_effect = ValueError('somethingbad') - - self.assertRaises(ValueError, conn._create_domain, domain=mock_domain, - instance=mock_instance) - - mock_teardown.assert_called_with(container_dir='/tmp/rootfs') + mock_teardown.assert_called_with(container_dir='/tmp/rootfs') def test_video_driver_flavor_limit_not_set(self): self.flags(virt_type='kvm', group='libvirt') @@ -6000,6 +6007,95 @@ class LibvirtConnTestCase(test.TestCase, self.assertTrue(self.cache_called_for_disk) db.instance_destroy(self.context, instance['uuid']) + def test_start_lxc_from_volume(self): + self.flags(virt_type="lxc", + group='libvirt') + + def check_setup_container(path, container_dir=None, use_cow=False): + self.assertEqual(path, '/dev/path/to/dev') + self.assertTrue(use_cow) + return '/dev/nbd1' + + bdm = { + 'guest_format': None, + 'boot_index': 0, + 'mount_device': '/dev/sda', + 'connection_info': { + 'driver_volume_type': 'iscsi', + 'serial': 'afc1', + 'data': { + 'access_mode': 'rw', + 'target_discovered': False, + 'encrypted': False, + 'qos_specs': None, + 'target_iqn': 'iqn: volume-afc1', + 'target_portal': 'ip: 3260', + 'volume_id': 'afc1', + 'target_lun': 1, + 'auth_password': 'uj', + 'auth_username': '47', + 'auth_method': 'CHAP' + } + }, + 'disk_bus': 'scsi', + 'device_type': 'disk', + 'delete_on_termination': False + } + + def _get(key, opt=None): + return bdm.get(key, opt) + + def getitem(key): + return bdm[key] + + def setitem(key, val): + bdm[key] = val + + bdm_mock = mock.MagicMock() + bdm_mock.__getitem__.side_effect = getitem + bdm_mock.__setitem__.side_effect = setitem + bdm_mock.get = _get + + disk_mock = mock.MagicMock() + disk_mock.source_path = '/dev/path/to/dev' + + block_device_info = {'block_device_mapping': [bdm_mock], + 'root_device_name': '/dev/sda'} + + # Volume-backed instance created without image + instance_ref = self.test_instance + instance_ref['image_ref'] = '' + instance_ref['root_device_name'] = '/dev/sda' + instance_ref['ephemeral_gb'] = 0 + instance_ref['uuid'] = uuidutils.generate_uuid() + instance_ref['system_metadata']['image_disk_format'] = 'qcow2' + instance = db.instance_create(self.context, instance_ref) + self.addCleanup(db.instance_destroy, self.context, instance['uuid']) + inst_obj = objects.Instance.get_by_uuid(self.context, instance['uuid']) + + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + with contextlib.nested( + mock.patch.object(conn, '_create_images_and_backing'), + mock.patch.object(conn, 'plug_vifs'), + mock.patch.object(conn.firewall_driver, 'setup_basic_filtering'), + mock.patch.object(conn.firewall_driver, 'prepare_instance_filter'), + mock.patch.object(conn.firewall_driver, 'apply_instance_filter'), + mock.patch.object(conn, '_create_domain'), + mock.patch.object(conn, '_connect_volume', + return_value=disk_mock), + mock.patch.object(conn, 'get_info', + return_value={'state': power_state.RUNNING}), + mock.patch('nova.virt.disk.api.setup_container', + side_effect=check_setup_container), + mock.patch('nova.virt.disk.api.teardown_container'),): + + conn.spawn(self.context, inst_obj, None, [], None, + network_info=[], + block_device_info=block_device_info) + self.assertEqual('/dev/nbd1', + inst_obj.system_metadata.get( + 'rootfs_device_name')) + def test_spawn_with_pci_devices(self): def fake_none(*args, **kwargs): return None @@ -8487,7 +8583,6 @@ Active: 8381604 kB mock_setup_container, mock_get_info, mock_clean): self.flags(virt_type='lxc', group='libvirt') conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) - mock_domain = mock.MagicMock() mock_instance = mock.MagicMock() inst_sys_meta = dict() mock_instance.system_metadata = inst_sys_meta @@ -8500,13 +8595,20 @@ Active: 8381604 kB mock_setup_container.return_value = '/dev/nbd0' mock_get_info.return_value = {'state': power_state.RUNNING} - domain = conn._create_domain(domain=mock_domain, - instance=mock_instance) + with contextlib.nested( + mock.patch.object(conn, '_create_images_and_backing'), + mock.patch.object(conn, '_is_booted_from_volume', + return_value=False), + mock.patch.object(conn, '_create_domain'), + mock.patch.object(conn, 'plug_vifs'), + mock.patch.object(conn.firewall_driver, 'setup_basic_filtering'), + mock.patch.object(conn.firewall_driver, 'prepare_instance_filter'), + mock.patch.object(conn.firewall_driver, 'apply_instance_filter')): + conn._create_domain_and_network(self.context, 'xml', + mock_instance, []) - self.assertEqual(mock_domain, domain) self.assertEqual('/dev/nbd0', inst_sys_meta['rootfs_device_name']) mock_instance.save.assert_has_calls([mock.call()]) - mock_domain.createWithFlags.assert_has_calls([mock.call(0)]) mock_get_inst_path.assert_has_calls([mock.call(mock_instance)]) mock_ensure_tree.assert_has_calls([mock.call('/tmp/rootfs')]) conn.image_backend.image.assert_has_calls([mock.call(mock_instance, @@ -8542,7 +8644,6 @@ Active: 8381604 kB self.assertEqual(100, id_maps[1].count) conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) - mock_domain = mock.MagicMock() mock_instance = mock.MagicMock() inst_sys_meta = dict() mock_instance.system_metadata = inst_sys_meta @@ -8556,13 +8657,20 @@ Active: 8381604 kB mock_chown.side_effect = chown_side_effect mock_get_info.return_value = {'state': power_state.RUNNING} - domain = conn._create_domain(domain=mock_domain, - instance=mock_instance) + with contextlib.nested( + mock.patch.object(conn, '_create_images_and_backing'), + mock.patch.object(conn, '_is_booted_from_volume', + return_value=False), + mock.patch.object(conn, '_create_domain'), + mock.patch.object(conn, 'plug_vifs'), + mock.patch.object(conn.firewall_driver, 'setup_basic_filtering'), + mock.patch.object(conn.firewall_driver, 'prepare_instance_filter'), + mock.patch.object(conn.firewall_driver, 'apply_instance_filter')): + conn._create_domain_and_network(self.context, 'xml', + mock_instance, []) - self.assertEqual(mock_domain, domain) self.assertEqual('/dev/nbd0', inst_sys_meta['rootfs_device_name']) mock_instance.save.assert_has_calls([mock.call()]) - mock_domain.createWithFlags.assert_has_calls([mock.call(0)]) mock_get_inst_path.assert_has_calls([mock.call(mock_instance)]) mock_ensure_tree.assert_has_calls([mock.call('/tmp/rootfs')]) conn.image_backend.image.assert_has_calls([mock.call(mock_instance, @@ -8585,7 +8693,6 @@ Active: 8381604 kB mock_get_info, mock_teardown): self.flags(virt_type='lxc', group='libvirt') conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) - mock_domain = mock.MagicMock() mock_instance = mock.MagicMock() inst_sys_meta = dict() mock_instance.system_metadata = inst_sys_meta @@ -8598,13 +8705,20 @@ Active: 8381604 kB mock_setup_container.return_value = '/dev/nbd0' mock_get_info.return_value = {'state': power_state.SHUTDOWN} - domain = conn._create_domain(domain=mock_domain, - instance=mock_instance) + with contextlib.nested( + mock.patch.object(conn, '_create_images_and_backing'), + mock.patch.object(conn, '_is_booted_from_volume', + return_value=False), + mock.patch.object(conn, '_create_domain'), + mock.patch.object(conn, 'plug_vifs'), + mock.patch.object(conn.firewall_driver, 'setup_basic_filtering'), + mock.patch.object(conn.firewall_driver, 'prepare_instance_filter'), + mock.patch.object(conn.firewall_driver, 'apply_instance_filter')): + conn._create_domain_and_network(self.context, 'xml', + mock_instance, []) - self.assertEqual(mock_domain, domain) self.assertEqual('/dev/nbd0', inst_sys_meta['rootfs_device_name']) mock_instance.save.assert_has_calls([mock.call()]) - mock_domain.createWithFlags.assert_has_calls([mock.call(0)]) mock_get_inst_path.assert_has_calls([mock.call(mock_instance)]) mock_ensure_tree.assert_has_calls([mock.call('/tmp/rootfs')]) conn.image_backend.image.assert_has_calls([mock.call(mock_instance, @@ -9098,15 +9212,18 @@ Active: 8381604 kB self.flags(virt_type='lxc', group='libvirt') conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - instance = objects.Instance(id=1, uuid='fake-uuid') + instance = objects.Instance(id=1, uuid='fake-uuid', + image_ref='my_fake_image') with contextlib.nested( + mock.patch.object(conn, '_is_booted_from_volume', + return_value=False), mock.patch.object(conn, 'plug_vifs'), mock.patch.object(conn, 'firewall_driver'), mock.patch.object(conn, '_create_domain', side_effect=exception.NovaException), mock.patch.object(conn, 'cleanup')) as ( - cleanup, firewall_driver, create, plug_vifs): + cleanup, firewall_driver, create, plug_vifs, boot): self.assertRaises(exception.NovaException, conn._create_domain_and_network, self.context, @@ -9116,43 +9233,26 @@ Active: 8381604 kB def test_create_without_pause(self): self.flags(virt_type='lxc', group='libvirt') + @contextlib.contextmanager + def fake_lxc_disk_handler(*args, **kwargs): + yield + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) instance = objects.Instance(id=1, uuid='fake-uuid') with contextlib.nested( + mock.patch.object(conn, '_lxc_disk_handler', + side_effect=fake_lxc_disk_handler), mock.patch.object(conn, 'plug_vifs'), mock.patch.object(conn, 'firewall_driver'), mock.patch.object(conn, '_create_domain'), mock.patch.object(conn, 'cleanup')) as ( - cleanup, firewall_driver, create, plug_vifs): + _handler, cleanup, firewall_driver, create, plug_vifs): domain = conn._create_domain_and_network(self.context, 'xml', instance, None) self.assertEqual(0, create.call_args_list[0][1]['launch_flags']) self.assertEqual(0, domain.resume.call_count) - def test_lxc_create_and_rootfs_saved(self): - self.flags(virt_type='lxc', group='libvirt') - - conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - instance = db.instance_create(self.context, self.test_instance) - inst_obj = objects.Instance.get_by_uuid(self.context, instance['uuid']) - - with contextlib.nested( - mock.patch('nova.virt.disk.api.setup_container', - return_value='/dev/nbd1'), - mock.patch('nova.virt.disk.api.clean_lxc_namespace'), - mock.patch('nova.openstack.common.fileutils.ensure_tree'), - mock.patch.object(conn.image_backend, 'image'), - mock.patch.object(conn, '_enable_hairpin'), - mock.patch.object(conn, 'get_info', - return_value={'state': power_state.RUNNING}) - ): - conn._conn.defineXML = mock.Mock() - conn._create_domain('xml', instance=inst_obj) - self.assertEqual('/dev/nbd1', - inst_obj.system_metadata.get( - 'rootfs_device_name')) - def _test_create_with_network_events(self, neutron_failure=None, power_on=True): generated_events = [] diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 0e8050e09920..a643e2a6f48e 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -25,6 +25,7 @@ Supports KVM, LXC, QEMU, UML, and XEN. """ +import contextlib import errno import functools import glob @@ -2589,9 +2590,8 @@ class LibvirtDriver(driver.ComputeDriver): disk_info, image_meta, block_device_info=block_device_info, write_to_disk=True) - self._create_domain_and_network(context, xml, instance, network_info, - block_device_info) + block_device_info, disk_info=disk_info) LOG.debug("Instance is running", instance=instance) def _wait_for_boot(): @@ -4031,15 +4031,33 @@ class LibvirtDriver(driver.ComputeDriver): 'cpu_time': dom_info[4], 'id': virt_dom.ID()} - def _create_domain_setup_lxc(self, instance): + def _create_domain_setup_lxc(self, instance, block_device_info, disk_info): inst_path = libvirt_utils.get_instance_path(instance) + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) + disk_info = disk_info or {} + disk_mapping = disk_info.get('mapping', []) + + if self._is_booted_from_volume(instance, disk_mapping): + root_disk = block_device.get_root_bdm(block_device_mapping) + disk_path = root_disk['connection_info']['data']['device_path'] + disk_info = blockinfo.get_info_from_bdm( + CONF.libvirt.virt_type, root_disk) + self._connect_volume(root_disk['connection_info'], disk_info) + + # Get the system metadata from the instance + system_meta = utils.instance_sys_meta(instance) + use_cow = system_meta['image_disk_format'] == 'qcow2' + else: + image = self.image_backend.image(instance, 'disk') + disk_path = image.path + use_cow = CONF.use_cow_images + container_dir = os.path.join(inst_path, 'rootfs') fileutils.ensure_tree(container_dir) - - image = self.image_backend.image(instance, 'disk') - rootfs_dev = disk.setup_container(image.path, - container_dir=container_dir, - use_cow=CONF.use_cow_images) + rootfs_dev = disk.setup_container(disk_path, + container_dir=container_dir, + use_cow=use_cow) try: # Save rootfs device to disconnect it when deleting the instance @@ -4072,6 +4090,28 @@ class LibvirtDriver(driver.ComputeDriver): else: disk.teardown_container(container_dir=container_dir) + @contextlib.contextmanager + def _lxc_disk_handler(self, instance, block_device_info, disk_info): + """Context manager to handle the pre and post instance boot, + LXC specific disk operations. + + An image or a volume path will be prepared and setup to be + used by the container, prior to starting it. + The disk will be disconnected and unmounted if a container has + failed to start. + """ + + if CONF.libvirt.virt_type != 'lxc': + yield + return + + self._create_domain_setup_lxc(instance, block_device_info, disk_info) + + try: + yield + finally: + self._create_domain_cleanup_lxc(instance) + def _create_domain(self, xml=None, domain=None, instance=None, launch_flags=0, power_on=True): """Create a domain. @@ -4080,8 +4120,6 @@ class LibvirtDriver(driver.ComputeDriver): the domain definition is overwritten from the xml. """ err = None - if instance and CONF.libvirt.virt_type == 'lxc': - self._create_domain_setup_lxc(instance) try: if xml: err = _LE('Error defining a domain with XML: %s') % xml @@ -4100,9 +4138,6 @@ class LibvirtDriver(driver.ComputeDriver): with excutils.save_and_reraise_exception(): if err: LOG.error(err) - finally: - if instance and CONF.libvirt.virt_type == 'lxc': - self._create_domain_cleanup_lxc(instance) return domain @@ -4124,7 +4159,8 @@ class LibvirtDriver(driver.ComputeDriver): def _create_domain_and_network(self, context, xml, instance, network_info, block_device_info=None, power_on=True, - reboot=False, vifs_already_plugged=False): + reboot=False, vifs_already_plugged=False, + disk_info=None): """Do required network setup and create domain.""" block_device_mapping = driver.block_device_info_get_mapping( @@ -4132,9 +4168,9 @@ class LibvirtDriver(driver.ComputeDriver): for vol in block_device_mapping: connection_info = vol['connection_info'] - disk_info = blockinfo.get_info_from_bdm( - CONF.libvirt.virt_type, vol) - conf = self._connect_volume(connection_info, disk_info) + info = blockinfo.get_info_from_bdm( + CONF.libvirt.virt_type, vol) + conf = self._connect_volume(connection_info, info) # cache device_path in connection_info -- required by encryptors if 'data' in connection_info: @@ -4172,10 +4208,12 @@ class LibvirtDriver(driver.ComputeDriver): network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) - domain = self._create_domain( - xml, instance=instance, - launch_flags=launch_flags, - power_on=power_on) + with self._lxc_disk_handler(instance, block_device_info, + disk_info): + domain = self._create_domain( + xml, instance=instance, + launch_flags=launch_flags, + power_on=power_on) self.firewall_driver.apply_instance_filter(instance, network_info)