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:
89
nova/tests/virt/xenapi/test_vm_utils.py
Normal file
89
nova/tests/virt/xenapi/test_vm_utils.py
Normal 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')
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user