diff options
-rw-r--r-- | nova/tests/virt/xenapi/test_vm_utils.py | 89 | ||||
-rw-r--r-- | nova/virt/configdrive.py | 8 | ||||
-rw-r--r-- | nova/virt/xenapi/vm_utils.py | 51 | ||||
-rw-r--r-- | nova/virt/xenapi/vmops.py | 21 |
4 files changed, 159 insertions, 10 deletions
diff --git a/nova/tests/virt/xenapi/test_vm_utils.py b/nova/tests/virt/xenapi/test_vm_utils.py new file mode 100644 index 000000000..275088af0 --- /dev/null +++ b/nova/tests/virt/xenapi/test_vm_utils.py @@ -0,0 +1,89 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import contextlib +import fixtures +import mox +import uuid + +from nova import test +from nova.tests.xenapi import stubs +from nova import utils +from nova.virt.xenapi import vm_utils + + +@contextlib.contextmanager +def contextified(result): + yield result + + +def _fake_noop(*args, **kwargs): + return + + +class GenerateConfigDriveTestCase(test.TestCase): + def test_no_admin_pass(self): + # This is here to avoid masking errors, it shouldn't be used normally + self.useFixture(fixtures.MonkeyPatch( + 'nova.virt.xenapi.vm_utils.destroy_vdi', _fake_noop)) + + # Mocks + instance = {} + + self.mox.StubOutWithMock(vm_utils, 'safe_find_sr') + vm_utils.safe_find_sr('session').AndReturn('sr_ref') + + self.mox.StubOutWithMock(vm_utils, 'create_vdi') + vm_utils.create_vdi('session', 'sr_ref', instance, 'config-2', + 'configdrive', + 64 * 1024 * 1024).AndReturn('vdi_ref') + + self.mox.StubOutWithMock(vm_utils, 'vdi_attached_here') + vm_utils.vdi_attached_here( + 'session', 'vdi_ref', read_only=False).AndReturn( + contextified('mounted_dev')) + + class FakeInstanceMetadata(object): + def __init__(self, instance, content=None, extra_md=None): + pass + + def metadata_for_config_drive(self): + return [] + + self.useFixture(fixtures.MonkeyPatch( + 'nova.api.metadata.base.InstanceMetadata', + FakeInstanceMetadata)) + + self.mox.StubOutWithMock(utils, 'execute') + utils.execute('genisoimage', '-o', mox.IgnoreArg(), '-ldots', + '-allow-lowercase', '-allow-multidot', '-l', + '-publisher', mox.IgnoreArg(), '-quiet', + '-J', '-r', '-V', 'config-2', mox.IgnoreArg(), + attempts=1, run_as_root=False).AndReturn(None) + utils.execute('dd', mox.IgnoreArg(), mox.IgnoreArg(), + run_as_root=True).AndReturn(None) + + self.mox.StubOutWithMock(vm_utils, 'create_vbd') + vm_utils.create_vbd('session', 'vm_ref', 'vdi_ref', mox.IgnoreArg(), + bootable=False, read_only=True).AndReturn(None) + + self.mox.ReplayAll() + + # And the actual call we're testing + vm_utils.generate_configdrive('session', instance, 'vm_ref', + 'userdevice') diff --git a/nova/virt/configdrive.py b/nova/virt/configdrive.py index 321bf8389..9255ab851 100644 --- a/nova/virt/configdrive.py +++ b/nova/virt/configdrive.py @@ -54,6 +54,9 @@ configdrive_opts = [ CONF = cfg.CONF CONF.register_opts(configdrive_opts) +# Config drives are 64mb, if we can't size to the exact size of the data +CONFIGDRIVESIZE_BYTES = 64 * 1024 * 1024 + @contextlib.contextmanager def config_drive_helper(instance_md=None): @@ -116,10 +119,9 @@ class _ConfigDriveBuilder(object): def _make_vfat(self, path): # NOTE(mikal): This is a little horrible, but I couldn't find an - # equivalent to genisoimage for vfat filesystems. vfat images are - # always 64mb. + # equivalent to genisoimage for vfat filesystems. with open(path, 'w') as f: - f.truncate(64 * 1024 * 1024) + f.truncate(CONFIGDRIVESIZE_BYTES) utils.mkfs('vfat', path, label='config-2') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 40d43da8d..ac35a4f2b 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -34,6 +34,7 @@ from xml.parsers import expat from eventlet import greenthread +from nova.api.metadata import base as instance_metadata from nova import block_device from nova.compute import power_state from nova.compute import task_states @@ -43,6 +44,7 @@ from nova.openstack.common import cfg from nova.openstack.common import excutils from nova.openstack.common import log as logging from nova import utils +from nova.virt import configdrive from nova.virt.disk import api as disk from nova.virt.disk.vfs import localfs as vfsimpl from nova.virt import driver @@ -153,6 +155,7 @@ class ImageType(object): | 4 - vhd disk image (local SR, NOT inspected by XS, PV assumed for | linux, HVM assumed for Windows) | 5 - ISO disk image (local SR, NOT partitioned by plugin) + | 6 - config drive """ KERNEL = 0 @@ -161,7 +164,9 @@ class ImageType(object): DISK_RAW = 3 DISK_VHD = 4 DISK_ISO = 5 - _ids = (KERNEL, RAMDISK, DISK, DISK_RAW, DISK_VHD, DISK_ISO) + DISK_CONFIGDRIVE = 6 + _ids = (KERNEL, RAMDISK, DISK, DISK_RAW, DISK_VHD, DISK_ISO, + DISK_CONFIGDRIVE) KERNEL_STR = "kernel" RAMDISK_STR = "ramdisk" @@ -169,8 +174,9 @@ class ImageType(object): DISK_RAW_STR = "os_raw" DISK_VHD_STR = "vhd" DISK_ISO_STR = "iso" + DISK_CONFIGDRIVE_STR = "configdrive" _strs = (KERNEL_STR, RAMDISK_STR, DISK_STR, DISK_RAW_STR, DISK_VHD_STR, - DISK_ISO_STR) + DISK_ISO_STR, DISK_CONFIGDRIVE_STR) @classmethod def to_string(cls, image_type): @@ -178,14 +184,15 @@ class ImageType(object): @classmethod def get_role(cls, image_type_id): - " Get the role played by the image, based on its type " + """Get the role played by the image, based on its type.""" return { cls.KERNEL: 'kernel', cls.RAMDISK: 'ramdisk', cls.DISK: 'root', cls.DISK_RAW: 'root', cls.DISK_VHD: 'root', - cls.DISK_ISO: 'iso' + cls.DISK_ISO: 'iso', + cls.DISK_CONFIGDRIVE: 'configdrive' }.get(image_type_id) @@ -868,6 +875,42 @@ def generate_ephemeral(session, instance, vm_ref, userdevice, name_label, CONF.default_ephemeral_format) +def generate_configdrive(session, instance, vm_ref, userdevice, + admin_password=None, files=None): + sr_ref = safe_find_sr(session) + vdi_ref = create_vdi(session, sr_ref, instance, 'config-2', + 'configdrive', configdrive.CONFIGDRIVESIZE_BYTES) + + try: + with vdi_attached_here(session, vdi_ref, read_only=False) as dev: + dev_path = utils.make_dev_path(dev) + + # NOTE(mikal): libvirt supports injecting the admin password as + # well. This is not currently implemented for xenapi as it is not + # supported by the existing file injection + extra_md = {} + if admin_password: + extra_md['admin_pass'] = admin_password + inst_md = instance_metadata.InstanceMetadata(instance, + content=files, + extra_md=extra_md) + with configdrive.config_drive_helper(instance_md=inst_md) as cdb: + with utils.tempdir() as tmp_path: + tmp_file = os.path.join(tmp_path, 'configdrive') + cdb.make_drive(tmp_file) + + utils.execute('dd', + 'if=%s' % tmp_file, + 'of=%s' % dev_path, + run_as_root=True) + + create_vbd(session, vm_ref, vdi_ref, userdevice, bootable=False, + read_only=True) + except Exception: + with excutils.save_and_reraise_exception(): + destroy_vdi(session, vdi_ref) + + def create_kernel_image(context, session, instance, name_label, image_id, image_type): """Creates kernel/ramdisk file from the image stored in the cache. diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e8e0f3cb0..4a8372cda 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -40,6 +40,7 @@ from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova.openstack.common import timeutils from nova import utils +from nova.virt import configdrive from nova.virt import driver as virt_driver from nova.virt import firewall from nova.virt.xenapi import agent as xapi_agent @@ -77,6 +78,7 @@ DEVICE_RESCUE = '1' DEVICE_SWAP = '2' DEVICE_EPHEMERAL = '3' DEVICE_CD = '4' +DEVICE_CONFIGDRIVE = '5' def cmp_version(a, b): @@ -344,7 +346,8 @@ class VMOps(object): @step def attach_disks_step(undo_mgr, vm_ref, vdis, disk_image_type): self._attach_disks(instance, vm_ref, name_label, vdis, - disk_image_type) + disk_image_type, admin_password, + injected_files) if rescue: # NOTE(johannes): Attach root disk to rescue VM now, before @@ -437,7 +440,12 @@ class VMOps(object): disk_image_type) self._setup_vm_networking(instance, vm_ref, vdis, network_info, rescue) - self.inject_instance_metadata(instance, vm_ref) + + # NOTE(mikal): file injection only happens if we are _not_ using a + # configdrive. + if not configdrive.required_by(instance): + self.inject_instance_metadata(instance, vm_ref) + return vm_ref def _setup_vm_networking(self, instance, vm_ref, vdis, network_info, @@ -491,7 +499,7 @@ class VMOps(object): return vm_ref def _attach_disks(self, instance, vm_ref, name_label, vdis, - disk_image_type): + disk_image_type, admin_password=None, files=None): ctx = nova_context.get_admin_context() instance_type = instance['instance_type'] @@ -537,6 +545,13 @@ class VMOps(object): DEVICE_EPHEMERAL, name_label, ephemeral_gb) + # Attach (optional) configdrive v2 disk + if configdrive.required_by(instance): + vm_utils.generate_configdrive(self._session, instance, vm_ref, + DEVICE_CONFIGDRIVE, + admin_password=admin_password, + files=files) + def _boot_new_instance(self, instance, vm_ref, injected_files, admin_password): """Boot a new instance and configure it.""" |