Added unit tests for the xenapi-glance integration. This adds a glance

simulator that can stub in place of glance.client.Client, and enhances the
xapi simulator to add the additional calls that the Glance-specific path
requires.

The test itself is just the spawn test, but now we run through with
xenapi_image_service set to "objectstore", and then again set to "glance".
This commit is contained in:
Ewan Mellor 2011-01-12 11:08:08 +00:00
parent d47183b268
commit 6f9408d7ac
6 changed files with 191 additions and 39 deletions

View File

@ -0,0 +1,20 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 Citrix Systems, Inc.
#
# 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.
"""
:mod:`glance` -- Stubs for Glance
=================================
"""

View File

@ -0,0 +1,37 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 Citrix Systems, Inc.
#
# 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 StringIO
import glance.client
def stubout_glance_client(stubs, cls):
"""Stubs out glance.client.Client"""
stubs.Set(glance.client, 'Client',
lambda *args, **kwargs: cls(*args, **kwargs))
class FakeGlance(object):
def __init__(self, host, port=None, use_ssl=False):
pass
def get_image(self, image):
meta = {
'size': 0,
}
image_file = StringIO.StringIO('')
return meta, image_file

View File

@ -33,6 +33,7 @@ from nova.virt.xenapi import fake as xenapi_fake
from nova.virt.xenapi import volume_utils
from nova.tests.db import fakes as db_fakes
from nova.tests.xenapi import stubs
from nova.tests.glance import stubs as glance_stubs
FLAGS = flags.FLAGS
@ -107,18 +108,16 @@ class XenAPIVolumeTestCase(test.TestCase):
conn = xenapi_conn.get_connection(False)
volume = self._create_volume()
instance = db.instance_create(self.values)
xenapi_fake.create_vm(instance.name, 'Running')
vm = xenapi_fake.create_vm(instance.name, 'Running')
result = conn.attach_volume(instance.name, volume['id'], '/dev/sdc')
def check():
# check that the VM has a VBD attached to it
# Get XenAPI reference for the VM
vms = xenapi_fake.get_all('VM')
# Get XenAPI record for VBD
vbds = xenapi_fake.get_all('VBD')
vbd = xenapi_fake.get_record('VBD', vbds[0])
vm_ref = vbd['VM']
self.assertEqual(vm_ref, vms[0])
self.assertEqual(vm_ref, vm)
check()
@ -156,9 +155,14 @@ class XenAPIVMTestCase(test.TestCase):
FLAGS.xenapi_connection_url = 'test_url'
FLAGS.xenapi_connection_password = 'test_pass'
xenapi_fake.reset()
xenapi_fake.create_local_srs()
db_fakes.stub_out_db_instance_api(self.stubs)
xenapi_fake.create_network('fake', FLAGS.flat_network_bridge)
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
stubs.stubout_get_this_vm_uuid(self.stubs)
stubs.stubout_stream_disk(self.stubs)
glance_stubs.stubout_glance_client(self.stubs,
glance_stubs.FakeGlance)
self.conn = xenapi_conn.get_connection(False)
def test_list_instances_0(self):
@ -206,7 +210,15 @@ class XenAPIVMTestCase(test.TestCase):
check()
def test_spawn(self):
def test_spawn_glance(self):
FLAGS.xenapi_image_service = 'glance'
self._test_spawn()
def test_spawn_objectstore(self):
FLAGS.xenapi_image_service = 'objectstore'
self._test_spawn()
def _test_spawn(self):
instance = self._create_instance()
def check():
@ -217,8 +229,10 @@ class XenAPIVMTestCase(test.TestCase):
vm_info = self.conn.get_info(1)
# Get XenAPI record for VM
vms = xenapi_fake.get_all('VM')
vm = xenapi_fake.get_record('VM', vms[0])
vms = [rec for ref, rec
in xenapi_fake.get_all_records('VM').iteritems()
if not rec['is_control_domain']]
vm = vms[0]
# Check that m1.large above turned into the right thing.
instance_type = instance_types.INSTANCE_TYPES['m1.large']

View File

@ -91,6 +91,21 @@ def stub_out_get_target(stubs):
stubs.Set(volume_utils, '_get_target', fake_get_target)
def stubout_get_this_vm_uuid(stubs):
def f():
vms = [rec['uuid'] for ref, rec
in fake.get_all_records('VM').iteritems()
if rec['is_control_domain']]
return vms[0]
stubs.Set(vm_utils, 'get_this_vm_uuid', f)
def stubout_stream_disk(stubs):
def f(_):
pass
stubs.Set(vm_utils, '_stream_disk', f)
class FakeSessionForVMTests(fake.SessionBase):
""" Stubs out a XenAPISession for VM tests """
def __init__(self, uri):
@ -100,7 +115,10 @@ class FakeSessionForVMTests(fake.SessionBase):
return self.xenapi.network.get_all_records()
def host_call_plugin(self, _1, _2, _3, _4, _5):
return ''
sr_ref = fake.get_all('SR')[0]
vdi_ref = fake.create_vdi('', False, sr_ref, False)
vdi_rec = fake.get_record('VDI', vdi_ref)
return '<string>%s</string>' % vdi_rec['uuid']
def VM_start(self, _1, ref, _2, _3):
vm = fake.get_record('VM', ref)
@ -135,10 +153,6 @@ class FakeSessionForVolumeTests(fake.SessionBase):
def __init__(self, uri):
super(FakeSessionForVolumeTests, self).__init__(uri)
def VBD_plug(self, _1, ref):
rec = fake.get_record('VBD', ref)
rec['currently-attached'] = True
def VDI_introduce(self, _1, uuid, _2, _3, _4, _5,
_6, _7, _8, _9, _10, _11):
valid_vdi = False

View File

@ -74,6 +74,7 @@ def reset():
for c in _CLASSES:
_db_content[c] = {}
create_host('fake')
create_vm('fake', 'Running', is_a_template=False, is_control_domain=True)
def create_host(name_label):
@ -134,14 +135,20 @@ def create_vdi(name_label, read_only, sr_ref, sharable):
def create_vbd(vm_ref, vdi_ref):
vbd_rec = {'VM': vm_ref, 'VDI': vdi_ref}
vbd_rec = {
'VM': vm_ref,
'VDI': vdi_ref,
'currently_attached': False,
}
vbd_ref = _create_object('VBD', vbd_rec)
after_VBD_create(vbd_ref, vbd_rec)
return vbd_ref
def after_VBD_create(vbd_ref, vbd_rec):
"""Create backref from VM to VBD when VBD is created"""
"""Create read-only fields and backref from VM to VBD when VBD is created"""
vbd_rec['currently_attached'] = False
vbd_rec['device'] = ''
vm_ref = vbd_rec['VM']
vm_rec = _db_content['VM'][vm_ref]
vm_rec['VBDs'] = [vbd_ref]
@ -150,9 +157,10 @@ def after_VBD_create(vbd_ref, vbd_rec):
vbd_rec['vm_name_label'] = vm_name_label
def create_pbd(config, sr_ref, attached):
def create_pbd(config, host_ref, sr_ref, attached):
return _create_object('PBD', {
'device-config': config,
'host': host_ref,
'SR': sr_ref,
'currently-attached': attached,
})
@ -165,6 +173,33 @@ def create_task(name_label):
})
def create_local_srs():
"""Create an SR that looks like the one created on the local disk by
default by the XenServer installer. Do this one per host."""
for host_ref in _db_content['host'].keys():
_create_local_sr(host_ref)
def _create_local_sr(host_ref):
sr_ref = _create_object('SR', {
'name_label': 'Local storage',
'type': 'lvm',
'content_type': 'user',
'shared': False,
'physical_size': str(1 << 30),
'physical_utilisation': str(0),
'virtual_allocation': str(0),
'other_config': {
'i18n-original-value-name_label': 'Local storage',
'i18n-key': 'local-storage',
},
'VDIs': []
})
pbd_ref = create_pbd('', host_ref, sr_ref, True)
_db_content['SR'][sr_ref]['PBDs'] = [pbd_ref]
return sr_ref
def _create_object(table, obj):
ref = str(uuid.uuid4())
obj['uuid'] = str(uuid.uuid4())
@ -177,9 +212,10 @@ def _create_sr(table, obj):
# Forces fake to support iscsi only
if sr_type != 'iscsi':
raise Failure(['SR_UNKNOWN_DRIVER', sr_type])
host_ref = _db_content['host'].keys()[0]
sr_ref = _create_object(table, obj[2])
vdi_ref = create_vdi('', False, sr_ref, False)
pbd_ref = create_pbd('', sr_ref, True)
pbd_ref = create_pbd('', host_ref, sr_ref, True)
_db_content['SR'][sr_ref]['VDIs'] = [vdi_ref]
_db_content['SR'][sr_ref]['PBDs'] = [pbd_ref]
_db_content['VDI'][vdi_ref]['SR'] = sr_ref
@ -231,6 +267,20 @@ class SessionBase(object):
def __init__(self, uri):
self._session = None
def VBD_plug(self, _1, ref):
rec = get_record('VBD', ref)
if rec['currently_attached']:
raise Failure(['DEVICE_ALREADY_ATTACHED', ref])
rec['currently_attached'] = True
rec['device'] = rec['userdevice']
def VBD_unplug(self, _1, ref):
rec = get_record('VBD', ref)
if not rec['currently_attached']:
raise Failure(['DEVICE_ALREADY_DETACHED', ref])
rec['currently_attached'] = False
rec['device'] = ''
def xenapi_request(self, methodname, params):
if methodname.startswith('login'):
self._login(methodname, params)
@ -287,6 +337,8 @@ class SessionBase(object):
return lambda *params: self._getter(name, params)
elif self._is_create(name):
return lambda *params: self._create(name, params)
elif self._is_destroy(name):
return lambda *params: self._destroy(name, params)
else:
return None
@ -297,10 +349,16 @@ class SessionBase(object):
bits[1].startswith(getter and 'get_' or 'set_'))
def _is_create(self, name):
return self._is_method(name, 'create')
def _is_destroy(self, name):
return self._is_method(name, 'destroy')
def _is_method(self, name, meth):
bits = name.split('.')
return (len(bits) == 2 and
bits[0] in _CLASSES and
bits[1] == 'create')
bits[1] == meth)
def _getter(self, name, params):
self._check_session(params)
@ -368,10 +426,9 @@ class SessionBase(object):
_create_sr(cls, params) or _create_object(cls, params[1])
# Call hook to provide any fixups needed (ex. creating backrefs)
try:
globals()["after_%s_create" % cls](ref, params[1])
except KeyError:
pass
after_hook = 'after_%s_create' % cls
if after_hook in globals():
globals()[after_hook](ref, params[1])
obj = get_record(cls, ref)
@ -381,6 +438,15 @@ class SessionBase(object):
return ref
def _destroy(self, name, params):
self._check_session(params)
self._check_arg_count(params, 2)
table, _ = name.split('.')
ref = params[1]
if ref not in _db_content[table]:
raise Failure(['HANDLE_INVALID', table, ref])
del _db_content[table][ref]
def _async(self, name, params):
task_ref = create_task(name)
task = _db_content['task'][task_ref]
@ -418,7 +484,7 @@ class SessionBase(object):
try:
return result[0]
except IndexError:
return None
raise Failure(['UUID_INVALID', v, result, recs, k])
return result

View File

@ -296,7 +296,7 @@ class VMHelper(HelperBase):
access, type)
else:
return cls._fetch_image_objectstore(session, instance_id, image,
access, type)
access, user.secret, type)
@classmethod
def _fetch_image_glance(cls, session, instance_id, image, access, type):
@ -318,18 +318,7 @@ class VMHelper(HelperBase):
vdi = cls.create_vdi(session, sr, _('Glance image %s') % image,
vdi_size, False)
def stream(dev):
offset = 0
if type == ImageType.DISK:
offset = MBR_SIZE_BYTES
_write_partition(virtual_size, dev)
with open('/dev/%s' % dev, 'wb') as f:
f.seek(offset)
for chunk in image_file:
f.write(chunk)
with_vdi_attached_here(session, vdi, False, stream)
with_vdi_attached_here(session, vdi, False, _stream_disk)
if (type==ImageType.KERNEL_RAMDISK):
#we need to invoke a plugin for copying VDI's content into proper path
fn = "copy_kernel_vdi"
@ -345,14 +334,14 @@ class VMHelper(HelperBase):
@classmethod
def _fetch_image_objectstore(cls, session, instance_id, image, access,
type):
secret, type):
url = images.image_url(image)
logging.debug("Asking xapi to fetch %s as %s", url, access)
fn = (type != ImageType.KERNEL_RAMDISK) and 'get_vdi' or 'get_kernel'
args = {}
args['src_url'] = url
args['username'] = access
args['password'] = user.secret
args['password'] = secret
args['add_partition'] = 'false'
args['raw'] = 'false'
if type != ImageType.KERNEL_RAMDISK:
@ -629,7 +618,7 @@ def vbd_unplug_with_retry(session, vbd):
session.get_xenapi().VBD.unplug(vbd)
logging.debug(_('VBD.unplug successful first time.'))
return
except XenAPI.Failure, e:
except VMHelper.XenAPI.Failure, e:
if (len(e.details) > 0 and
e.details[0] == 'DEVICE_DETACH_REJECTED'):
logging.debug(_('VBD.unplug rejected: retrying...'))
@ -647,7 +636,7 @@ def vbd_unplug_with_retry(session, vbd):
def ignore_failure(func, *args, **kwargs):
try:
return func(*args, **kwargs)
except XenAPI.Failure, e:
except VMHelper.XenAPI.Failure, e:
logging.error(_('Ignoring XenAPI.Failure %s'), e)
return None
@ -661,6 +650,18 @@ def get_this_vm_ref(session):
return session.get_xenapi().VM.get_by_uuid(get_this_vm_uuid())
def _stream_disk(dev):
offset = 0
if type == ImageType.DISK:
offset = MBR_SIZE_BYTES
_write_partition(virtual_size, dev)
with open('/dev/%s' % dev, 'wb') as f:
f.seek(offset)
for chunk in image_file:
f.write(chunk)
def _write_partition(virtual_size, dev):
dest = '/dev/%s' % dev
mbr_last = MBR_SIZE_SECTORS - 1