Skip to content

Commit

Permalink
VMware: Config Drive Support
Browse files Browse the repository at this point in the history
Create the config drive iso, upload it to the correct location,
then create a VirtualMachineConfigSpec with appropriate contents
and run the ReconfigVM_Task to enable the iso.

DocImpact

Fixes LP# 1206584
Fixes BP vmware-config-drive-support

Change-Id: I1df1e61edbff9f3a68940d3e3e87fea3be838d11
  • Loading branch information
Davanum Srinivas committed Aug 30, 2013
1 parent 3951958 commit 3c59d99
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 7 deletions.
21 changes: 21 additions & 0 deletions nova/tests/virt/vmwareapi/db_fakes.py
Expand Up @@ -38,6 +38,27 @@ def stub_out_db_instance_api(stubs):
'm1.xlarge':
dict(memory_mb=16384, vcpus=8, root_gb=160, flavorid=5)}

class FakeModel(object):
"""Stubs out for model."""

def __init__(self, values):
self.values = values

def __getattr__(self, name):
return self.values[name]

def get(self, attr):
try:
return self.__getattr__(attr)
except KeyError:
return None

def __getitem__(self, key):
if key in self.values:
return self.values[key]
else:
raise NotImplementedError()

def fake_instance_create(context, values):
"""Stubs out the db.instance_create method."""

Expand Down
150 changes: 150 additions & 0 deletions nova/tests/virt/vmwareapi/test_configdrive.py
@@ -0,0 +1,150 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 IBM Corp.
# Copyright 2011 OpenStack Foundation
#
# 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 copy
import fixtures
import mox

from nova import context
from nova import test
import nova.tests.image.fake
from nova.tests import utils
from nova.tests.virt.vmwareapi import stubs
from nova.virt import fake
from nova.virt.vmwareapi import driver
from nova.virt.vmwareapi import fake as vmwareapi_fake
from nova.virt.vmwareapi import vmops
from nova.virt.vmwareapi import vmware_images


class ConfigDriveTestCase(test.TestCase):
def setUp(self):
super(ConfigDriveTestCase, self).setUp()
self.context = context.RequestContext('fake', 'fake', is_admin=False)
self.flags(host_ip='test_url',
host_username='test_username',
host_password='test_pass',
use_linked_clone=False, group='vmware')
self.flags(vnc_enabled=False)
vmwareapi_fake.reset()
stubs.set_stubs(self.stubs)
nova.tests.image.fake.stub_out_image_service(self.stubs)
self.conn = driver.VMwareVCDriver(fake.FakeVirtAPI)
self.network_info = utils.get_test_network_info()
self.image = {
'id': 'c1c8ce3d-c2e0-4247-890c-ccf5cc1c004c',
'disk_format': 'vhd',
'size': 512,
}
self.test_instance = {'node': 'test_url',
'vm_state': 'building',
'project_id': 'fake',
'user_id': 'fake',
'name': '1',
'kernel_id': '1',
'ramdisk_id': '1',
'mac_addresses': [
{'address': 'de:ad:be:ef:be:ef'}
],
'memory_mb': 8192,
'instance_type': 'm1.large',
'vcpus': 4,
'root_gb': 80,
'image_ref': '1',
'host': 'fake_host',
'task_state':
'scheduling',
'reservation_id': 'r-3t8muvr0',
'id': 1,
'uuid': 'fake-uuid'}

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

def fake_make_drive(_self, _path):
pass
# We can't actually make a config drive v2 because ensure_tree has
# been faked out
self.stubs.Set(nova.virt.configdrive.ConfigDriveBuilder,
'make_drive', fake_make_drive)

def fake_upload_iso_to_datastore(iso_path, instance, **kwargs):
pass
self.stubs.Set(vmware_images,
'upload_iso_to_datastore',
fake_upload_iso_to_datastore)

def tearDown(self):
super(ConfigDriveTestCase, self).tearDown()
vmwareapi_fake.cleanup()
nova.tests.image.fake.FakeImageService_reset()

def test_create_vm_with_config_drive_verify_method_invocation(self):
self.instance = copy.deepcopy(self.test_instance)
self.instance['config_drive'] = True
self.mox.StubOutWithMock(vmops.VMwareVMOps, '_create_config_drive')
self.mox.StubOutWithMock(vmops.VMwareVMOps, '_attach_cdrom_to_vm')
self.conn._vmops._create_config_drive(self.instance,
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
self.conn._vmops._attach_cdrom_to_vm(mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
self.mox.ReplayAll()
# if spawn does not call the _create_config_drive or
# _attach_cdrom_to_vm call with the correct set of parameters
# then mox's VerifyAll will throw a Expected methods never called
# Exception
self.conn.spawn(self.context, self.instance, self.image,
injected_files=[], admin_password=None,
network_info=self.network_info,
block_device_info=None)

def test_create_vm_without_config_drive(self):
self.instance = copy.deepcopy(self.test_instance)
self.instance['config_drive'] = False
self.mox.StubOutWithMock(vmops.VMwareVMOps, '_create_config_drive')
self.mox.StubOutWithMock(vmops.VMwareVMOps, '_attach_cdrom_to_vm')
self.mox.ReplayAll()
# if spawn ends up calling _create_config_drive or
# _attach_cdrom_to_vm then mox will log a Unexpected method call
# exception
self.conn.spawn(self.context, self.instance, self.image,
injected_files=[], admin_password=None,
network_info=self.network_info,
block_device_info=None)

def test_create_vm_with_config_drive(self):
self.instance = copy.deepcopy(self.test_instance)
self.instance['config_drive'] = True
self.conn.spawn(self.context, self.instance, self.image,
injected_files=[], admin_password=None,
network_info=self.network_info,
block_device_info=None)
44 changes: 44 additions & 0 deletions nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
Expand Up @@ -221,3 +221,47 @@ def test_get_datastore_ref_and_name_inaccessible_ds(self):
self.assertRaises(exception.DatastoreNotFound,
vm_util.get_datastore_ref_and_name,
fake_session(fake_objects))

def test_get_cdrom_attach_config_spec(self):

result = vm_util.get_cdrom_attach_config_spec(fake.FakeFactory(),
fake.Datastore(),
"/tmp/foo.iso",
0)
expected = """{
'deviceChange': [
{
'device': {
'connectable': {
'allowGuestControl': False,
'startConnected': True,
'connected': True,
'obj_name': 'ns0: VirtualDeviceConnectInfo'
},
'backing': {
'datastore': {
"summary.type": "VMFS",
"summary.freeSpace": 536870912000,
"summary.capacity": 1099511627776,
"summary.accessible":true,
"summary.name": "fake-ds"
},
'fileName': '/tmp/foo.iso',
'obj_name': 'ns0: VirtualCdromIsoBackingInfo'
},
'controllerKey': 200,
'unitNumber': 0,
'key': -1,
'obj_name': 'ns0: VirtualCdrom'
},
'operation': 'add',
'obj_name': 'ns0: VirtualDeviceConfigSpec'
}
],
'obj_name': 'ns0: VirtualMachineConfigSpec'
}
"""

expected = re.sub(r'\s+', '', expected)
result = re.sub(r'\s+', '', repr(result))
self.assertEqual(expected, result)
4 changes: 2 additions & 2 deletions nova/virt/vmwareapi/driver.py
Expand Up @@ -179,8 +179,8 @@ def list_instances(self):
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
"""Create VM instance."""
self._vmops.spawn(context, instance, image_meta, network_info,
block_device_info)
self._vmops.spawn(context, instance, image_meta, injected_files,
admin_password, network_info, block_device_info)

def snapshot(self, context, instance, name, update_task_state):
"""Create snapshot from a running VM instance."""
Expand Down
11 changes: 11 additions & 0 deletions nova/virt/vmwareapi/fake.py
Expand Up @@ -27,6 +27,7 @@

from nova import exception
from nova.openstack.common.gettextutils import _
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.virt.vmwareapi import error_util

Expand Down Expand Up @@ -200,12 +201,19 @@ def __getattr__(self, attr):
raise exception.NovaException(msg % {'attr': attr,
'name': self.objName})

def __repr__(self):
return jsonutils.dumps(dict([(elem.name, elem.val)
for elem in self.propSet]))


class DataObject(object):
"""Data object base class."""
def __init__(self, obj_name=None):
self.obj_name = obj_name

def __repr__(self):
return str(self.__dict__)


class HostInternetScsiHba():
pass
Expand Down Expand Up @@ -284,6 +292,9 @@ def reconfig(self, factory, val):
setting of the Virtual Machine object.
"""
try:
if len(val.deviceChange) < 2:
return

# Case of Reconfig of VM to attach disk
controller_key = val.deviceChange[1].device.controllerKey
filename = val.deviceChange[1].device.backing.fileName
Expand Down
56 changes: 56 additions & 0 deletions nova/virt/vmwareapi/vm_util.py
Expand Up @@ -224,6 +224,29 @@ def get_vmdk_attach_config_spec(client_factory,
return config_spec


def get_cdrom_attach_config_spec(client_factory,
datastore,
file_path,
cdrom_unit_number):
"""Builds and returns the cdrom attach config spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')

device_config_spec = []
# For IDE devices, there are these two default controllers created in the
# VM having keys 200 and 201
controller_key = 200
virtual_device_config_spec = create_virtual_cdrom_spec(client_factory,
datastore,
controller_key,
file_path,
cdrom_unit_number)

device_config_spec.append(virtual_device_config_spec)

config_spec.deviceChange = device_config_spec
return config_spec


def get_vmdk_detach_config_spec(client_factory, device):
"""Builds the vmdk detach config spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
Expand Down Expand Up @@ -320,6 +343,39 @@ def get_rdm_create_spec(client_factory, device, adapter_type="lsiLogic",
return create_vmdk_spec


def create_virtual_cdrom_spec(client_factory,
datastore,
controller_key,
file_path,
cdrom_unit_number):
"""Builds spec for the creation of a new Virtual CDROM to the VM."""
config_spec = client_factory.create(
'ns0:VirtualDeviceConfigSpec')
config_spec.operation = "add"

cdrom = client_factory.create('ns0:VirtualCdrom')

cdrom_device_backing = client_factory.create(
'ns0:VirtualCdromIsoBackingInfo')
cdrom_device_backing.datastore = datastore
cdrom_device_backing.fileName = file_path

cdrom.backing = cdrom_device_backing
cdrom.controllerKey = controller_key
cdrom.unitNumber = cdrom_unit_number
cdrom.key = -1

connectable_spec = client_factory.create('ns0:VirtualDeviceConnectInfo')
connectable_spec.startConnected = True
connectable_spec.allowGuestControl = False
connectable_spec.connected = True

cdrom.connectable = connectable_spec

config_spec.device = cdrom
return config_spec


def create_virtual_disk_spec(client_factory, controller_key,
disk_type="preallocated",
file_path=None,
Expand Down

0 comments on commit 3c59d99

Please sign in to comment.