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 = cfg.CONF
|
||||||
CONF.register_opts(configdrive_opts)
|
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
|
@contextlib.contextmanager
|
||||||
def config_drive_helper(instance_md=None):
|
def config_drive_helper(instance_md=None):
|
||||||
@@ -116,10 +119,9 @@ class _ConfigDriveBuilder(object):
|
|||||||
|
|
||||||
def _make_vfat(self, path):
|
def _make_vfat(self, path):
|
||||||
# NOTE(mikal): This is a little horrible, but I couldn't find an
|
# NOTE(mikal): This is a little horrible, but I couldn't find an
|
||||||
# equivalent to genisoimage for vfat filesystems. vfat images are
|
# equivalent to genisoimage for vfat filesystems.
|
||||||
# always 64mb.
|
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
f.truncate(64 * 1024 * 1024)
|
f.truncate(CONFIGDRIVESIZE_BYTES)
|
||||||
|
|
||||||
utils.mkfs('vfat', path, label='config-2')
|
utils.mkfs('vfat', path, label='config-2')
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from xml.parsers import expat
|
|||||||
|
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
|
|
||||||
|
from nova.api.metadata import base as instance_metadata
|
||||||
from nova import block_device
|
from nova import block_device
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
from nova.compute import task_states
|
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 excutils
|
||||||
from nova.openstack.common import log as logging
|
from nova.openstack.common import log as logging
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
from nova.virt import configdrive
|
||||||
from nova.virt.disk import api as disk
|
from nova.virt.disk import api as disk
|
||||||
from nova.virt.disk.vfs import localfs as vfsimpl
|
from nova.virt.disk.vfs import localfs as vfsimpl
|
||||||
from nova.virt import driver
|
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
|
| 4 - vhd disk image (local SR, NOT inspected by XS, PV assumed for
|
||||||
| linux, HVM assumed for Windows)
|
| linux, HVM assumed for Windows)
|
||||||
| 5 - ISO disk image (local SR, NOT partitioned by plugin)
|
| 5 - ISO disk image (local SR, NOT partitioned by plugin)
|
||||||
|
| 6 - config drive
|
||||||
"""
|
"""
|
||||||
|
|
||||||
KERNEL = 0
|
KERNEL = 0
|
||||||
@@ -161,7 +164,9 @@ class ImageType(object):
|
|||||||
DISK_RAW = 3
|
DISK_RAW = 3
|
||||||
DISK_VHD = 4
|
DISK_VHD = 4
|
||||||
DISK_ISO = 5
|
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"
|
KERNEL_STR = "kernel"
|
||||||
RAMDISK_STR = "ramdisk"
|
RAMDISK_STR = "ramdisk"
|
||||||
@@ -169,8 +174,9 @@ class ImageType(object):
|
|||||||
DISK_RAW_STR = "os_raw"
|
DISK_RAW_STR = "os_raw"
|
||||||
DISK_VHD_STR = "vhd"
|
DISK_VHD_STR = "vhd"
|
||||||
DISK_ISO_STR = "iso"
|
DISK_ISO_STR = "iso"
|
||||||
|
DISK_CONFIGDRIVE_STR = "configdrive"
|
||||||
_strs = (KERNEL_STR, RAMDISK_STR, DISK_STR, DISK_RAW_STR, DISK_VHD_STR,
|
_strs = (KERNEL_STR, RAMDISK_STR, DISK_STR, DISK_RAW_STR, DISK_VHD_STR,
|
||||||
DISK_ISO_STR)
|
DISK_ISO_STR, DISK_CONFIGDRIVE_STR)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_string(cls, image_type):
|
def to_string(cls, image_type):
|
||||||
@@ -178,14 +184,15 @@ class ImageType(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_role(cls, image_type_id):
|
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 {
|
return {
|
||||||
cls.KERNEL: 'kernel',
|
cls.KERNEL: 'kernel',
|
||||||
cls.RAMDISK: 'ramdisk',
|
cls.RAMDISK: 'ramdisk',
|
||||||
cls.DISK: 'root',
|
cls.DISK: 'root',
|
||||||
cls.DISK_RAW: 'root',
|
cls.DISK_RAW: 'root',
|
||||||
cls.DISK_VHD: 'root',
|
cls.DISK_VHD: 'root',
|
||||||
cls.DISK_ISO: 'iso'
|
cls.DISK_ISO: 'iso',
|
||||||
|
cls.DISK_CONFIGDRIVE: 'configdrive'
|
||||||
}.get(image_type_id)
|
}.get(image_type_id)
|
||||||
|
|
||||||
|
|
||||||
@@ -868,6 +875,42 @@ def generate_ephemeral(session, instance, vm_ref, userdevice, name_label,
|
|||||||
CONF.default_ephemeral_format)
|
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,
|
def create_kernel_image(context, session, instance, name_label, image_id,
|
||||||
image_type):
|
image_type):
|
||||||
"""Creates kernel/ramdisk file from the image stored in the cache.
|
"""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 log as logging
|
||||||
from nova.openstack.common import timeutils
|
from nova.openstack.common import timeutils
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
from nova.virt import configdrive
|
||||||
from nova.virt import driver as virt_driver
|
from nova.virt import driver as virt_driver
|
||||||
from nova.virt import firewall
|
from nova.virt import firewall
|
||||||
from nova.virt.xenapi import agent as xapi_agent
|
from nova.virt.xenapi import agent as xapi_agent
|
||||||
@@ -77,6 +78,7 @@ DEVICE_RESCUE = '1'
|
|||||||
DEVICE_SWAP = '2'
|
DEVICE_SWAP = '2'
|
||||||
DEVICE_EPHEMERAL = '3'
|
DEVICE_EPHEMERAL = '3'
|
||||||
DEVICE_CD = '4'
|
DEVICE_CD = '4'
|
||||||
|
DEVICE_CONFIGDRIVE = '5'
|
||||||
|
|
||||||
|
|
||||||
def cmp_version(a, b):
|
def cmp_version(a, b):
|
||||||
@@ -344,7 +346,8 @@ class VMOps(object):
|
|||||||
@step
|
@step
|
||||||
def attach_disks_step(undo_mgr, vm_ref, vdis, disk_image_type):
|
def attach_disks_step(undo_mgr, vm_ref, vdis, disk_image_type):
|
||||||
self._attach_disks(instance, vm_ref, name_label, vdis,
|
self._attach_disks(instance, vm_ref, name_label, vdis,
|
||||||
disk_image_type)
|
disk_image_type, admin_password,
|
||||||
|
injected_files)
|
||||||
|
|
||||||
if rescue:
|
if rescue:
|
||||||
# NOTE(johannes): Attach root disk to rescue VM now, before
|
# NOTE(johannes): Attach root disk to rescue VM now, before
|
||||||
@@ -437,7 +440,12 @@ class VMOps(object):
|
|||||||
disk_image_type)
|
disk_image_type)
|
||||||
self._setup_vm_networking(instance, vm_ref, vdis, network_info,
|
self._setup_vm_networking(instance, vm_ref, vdis, network_info,
|
||||||
rescue)
|
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
|
return vm_ref
|
||||||
|
|
||||||
def _setup_vm_networking(self, instance, vm_ref, vdis, network_info,
|
def _setup_vm_networking(self, instance, vm_ref, vdis, network_info,
|
||||||
@@ -491,7 +499,7 @@ class VMOps(object):
|
|||||||
return vm_ref
|
return vm_ref
|
||||||
|
|
||||||
def _attach_disks(self, instance, vm_ref, name_label, vdis,
|
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()
|
ctx = nova_context.get_admin_context()
|
||||||
instance_type = instance['instance_type']
|
instance_type = instance['instance_type']
|
||||||
|
|
||||||
@@ -537,6 +545,13 @@ class VMOps(object):
|
|||||||
DEVICE_EPHEMERAL, name_label,
|
DEVICE_EPHEMERAL, name_label,
|
||||||
ephemeral_gb)
|
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,
|
def _boot_new_instance(self, instance, vm_ref, injected_files,
|
||||||
admin_password):
|
admin_password):
|
||||||
"""Boot a new instance and configure it."""
|
"""Boot a new instance and configure it."""
|
||||||
|
|||||||
Reference in New Issue
Block a user