Adding configdrive to xenapi.

This version implements a configdrive v2 for xenapi as discussed
in the xenapi IRC meetings.

 - the config drive is now iso9660
 - the admin password is injected (if we know it)
 - vbd's are not reused
 - review comments have been addressed

Progresses blueprint xenapi-config-drive. DocImpact.

(Dear documentation people, if using config drive with xenapi,
the user should consider disabling the agent as well with
xenapi_disable_agent. This mostly depends on if they want to
support post-boot password updates still.)

Change-Id: Ib23d117ad4cd5dc92298a0812eb468f7d557417c
This commit is contained in:
Michael Still
2012-12-19 17:57:13 +11:00
parent 8204ab8aa0
commit 39f80b8cc0
4 changed files with 159 additions and 10 deletions

View File

@@ -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')

View File

@@ -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')

View File

@@ -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.

View File

@@ -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."""