XenAPI: device tagging

XenAPI implementation of device tagging

Partially-Implements: blueprint virt-device-role-tagging-xenapi

Change-Id: I565617e05acf33e6254ea091b88d975270ffde05
Depends-On: I9fe520bc9b68d0bc7f879617f2cd27dd1029e4de
This commit is contained in:
John Hua 2016-06-24 14:13:26 +08:00 committed by Naichuan Sun
parent 3a5d592e60
commit 0c0361e8bf
8 changed files with 227 additions and 4 deletions

View File

@ -403,6 +403,8 @@ class InstanceMetadata(object):
bus = 'scsi'
elif isinstance(device.bus, metadata_obj.IDEDeviceBus):
bus = 'ide'
elif isinstance(device.bus, metadata_obj.XenDeviceBus):
bus = 'xen'
else:
LOG.debug('Metadata for device with unknown bus %s '
'has not been included in the '

View File

@ -1032,6 +1032,14 @@ class IDEAddress(AddressBase):
return AddressBase.coerce(IDEAddress, attr, value)
class XenAddress(AddressBase):
PATTERN = '(00[0-9]{2}00)|[1-9][0-9]+'
@staticmethod
def coerce(obj, attr, value):
return AddressBase.coerce(XenAddress, attr, value)
class PCIAddressField(AutoTypedField):
AUTO_TYPE = PCIAddress()
@ -1048,6 +1056,10 @@ class IDEAddressField(AutoTypedField):
AUTO_TYPE = IDEAddress()
class XenAddressField(AutoTypedField):
AUTO_TYPE = XenAddress()
class ArchitectureField(BaseEnumField):
AUTO_TYPE = Architecture()

View File

@ -61,6 +61,15 @@ class IDEDeviceBus(DeviceBus):
}
@base.NovaObjectRegistry.register
class XenDeviceBus(DeviceBus):
VERSION = '1.0'
fields = {
'address': fields.XenAddressField(),
}
@base.NovaObjectRegistry.register
class DeviceMetadata(base.NovaObject):
VERSION = '1.0'

View File

@ -707,6 +707,20 @@ class TestIDEAddress(TestField):
self.from_primitive_values = self.coerce_good_values
class TestXenAddress(TestField):
def setUp(self):
super(TestXenAddress, self).setUp()
self.field = fields.Field(fields.XenAddressField())
self.coerce_good_values = [('000100', '000100'),
('768', '768')]
self.coerce_bad_values = [
'1',
'00100',
]
self.to_primitive_values = self.coerce_good_values
self.from_primitive_values = self.coerce_good_values
class TestSecureBoot(TestField):
def setUp(self):
super(TestSecureBoot, self).setUp()

View File

@ -1179,6 +1179,7 @@ object_data = {
'VirtualInterface': '1.3-efd3ca8ebcc5ce65fff5a25f31754c54',
'VirtualInterfaceList': '1.0-9750e2074437b3077e46359102779fc6',
'VolumeUsage': '1.0-6c8190c46ce1469bb3286a1f21c2e475',
'XenDeviceBus': '1.0-272a4f899b24e31e42b2b9a7ed7e9194',
'XenapiLiveMigrateData': '1.1-79e69f5ac9abfbcfcbaec18e8280bec6',
}

View File

@ -302,6 +302,8 @@ class SpawnTestCase(VMOpsTestBase):
self.mox.StubOutWithMock(self.vmops, '_create_vm_record')
self.mox.StubOutWithMock(self.vmops, '_destroy')
self.mox.StubOutWithMock(self.vmops, '_attach_disks')
self.mox.StubOutWithMock(self.vmops, '_save_device_metadata')
self.mox.StubOutWithMock(self.vmops, '_prepare_disk_metadata')
self.mox.StubOutWithMock(pci_manager, 'get_instance_pci_devs')
self.mox.StubOutWithMock(vm_utils, 'set_other_config_pci')
self.mox.StubOutWithMock(self.vmops, '_attach_orig_disks')
@ -325,13 +327,22 @@ class SpawnTestCase(VMOpsTestBase):
self.mox.StubOutWithMock(self.vmops, '_update_last_dom_id')
self.mox.StubOutWithMock(self.vmops._session, 'call_xenapi')
@staticmethod
def _new_instance(obj):
class _Instance(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
return _Instance(**obj)
def _test_spawn(self, name_label_param=None, block_device_info_param=None,
rescue=False, include_root_vdi=True, throw_exception=None,
attach_pci_dev=False, neutron_exception=False,
network_info=None):
self._stub_out_common()
instance = {"name": "dummy", "uuid": "fake_uuid"}
instance = self._new_instance({"name": "dummy", "uuid": "fake_uuid",
"device_metadata": None})
name_label = name_label_param
if name_label is None:
name_label = "dummy"
@ -383,6 +394,7 @@ class SpawnTestCase(VMOpsTestBase):
step += 1
self.vmops._update_instance_progress(context, instance, step, steps)
self.vmops._save_device_metadata(context, instance, block_device_info)
self.vmops._attach_disks(context, instance, image_meta, vm_ref,
name_label, vdis, di_type, network_info, rescue,
admin_password, injected_files)
@ -508,6 +520,103 @@ class SpawnTestCase(VMOpsTestBase):
'_neutron_failed_callback')
self._test_spawn(network_info=network_info)
@staticmethod
def _dev_mock(obj):
dev = mock.MagicMock(**obj)
dev.__contains__.side_effect = (
lambda attr: getattr(dev, attr, None) is not None)
return dev
@mock.patch.object(objects, 'XenDeviceBus')
@mock.patch.object(objects, 'IDEDeviceBus')
@mock.patch.object(objects, 'DiskMetadata')
def test_prepare_disk_metadata(self, mock_DiskMetadata,
mock_IDEDeviceBus, mock_XenDeviceBus):
mock_IDEDeviceBus.side_effect = \
lambda **kw: \
self._dev_mock({"address": kw.get("address"), "bus": "ide"})
mock_XenDeviceBus.side_effect = \
lambda **kw: \
self._dev_mock({"address": kw.get("address"), "bus": "xen"})
mock_DiskMetadata.side_effect = \
lambda **kw: self._dev_mock(dict(**kw))
bdm = self._dev_mock({"device_name": "/dev/xvda", "tag": "disk_a"})
disk_metadata = self.vmops._prepare_disk_metadata(bdm)
self.assertEqual(disk_metadata[0].tags, ["disk_a"])
self.assertEqual(disk_metadata[0].bus.bus, "ide")
self.assertEqual(disk_metadata[0].bus.address, "0:0")
self.assertEqual(disk_metadata[1].tags, ["disk_a"])
self.assertEqual(disk_metadata[1].bus.bus, "xen")
self.assertEqual(disk_metadata[1].bus.address, "000000")
self.assertEqual(disk_metadata[2].tags, ["disk_a"])
self.assertEqual(disk_metadata[2].bus.bus, "xen")
self.assertEqual(disk_metadata[2].bus.address, "51712")
self.assertEqual(disk_metadata[3].tags, ["disk_a"])
self.assertEqual(disk_metadata[3].bus.bus, "xen")
self.assertEqual(disk_metadata[3].bus.address, "768")
bdm = self._dev_mock({"device_name": "/dev/xvdc", "tag": "disk_c"})
disk_metadata = self.vmops._prepare_disk_metadata(bdm)
self.assertEqual(disk_metadata[0].tags, ["disk_c"])
self.assertEqual(disk_metadata[0].bus.bus, "ide")
self.assertEqual(disk_metadata[0].bus.address, "1:0")
self.assertEqual(disk_metadata[1].tags, ["disk_c"])
self.assertEqual(disk_metadata[1].bus.bus, "xen")
self.assertEqual(disk_metadata[1].bus.address, "000200")
self.assertEqual(disk_metadata[2].tags, ["disk_c"])
self.assertEqual(disk_metadata[2].bus.bus, "xen")
self.assertEqual(disk_metadata[2].bus.address, "51744")
self.assertEqual(disk_metadata[3].tags, ["disk_c"])
self.assertEqual(disk_metadata[3].bus.bus, "xen")
self.assertEqual(disk_metadata[3].bus.address, "5632")
bdm = self._dev_mock({"device_name": "/dev/xvde", "tag": "disk_e"})
disk_metadata = self.vmops._prepare_disk_metadata(bdm)
self.assertEqual(disk_metadata[0].tags, ["disk_e"])
self.assertEqual(disk_metadata[0].bus.bus, "xen")
self.assertEqual(disk_metadata[0].bus.address, "000400")
self.assertEqual(disk_metadata[1].tags, ["disk_e"])
self.assertEqual(disk_metadata[1].bus.bus, "xen")
self.assertEqual(disk_metadata[1].bus.address, "51776")
@mock.patch.object(objects.VirtualInterfaceList, 'get_by_instance_uuid')
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
@mock.patch.object(objects, 'NetworkInterfaceMetadata')
@mock.patch.object(objects, 'InstanceDeviceMetadata')
@mock.patch.object(objects, 'PCIDeviceBus')
@mock.patch.object(vmops.VMOps, '_prepare_disk_metadata')
def test_save_device_metadata(self, mock_prepare_disk_metadata,
mock_PCIDeviceBus, mock_InstanceDeviceMetadata,
mock_NetworkInterfaceMetadata, mock_get_bdms, mock_get_vifs):
context = {}
instance = {"uuid": "fake_uuid"}
block_device_info = {'block_device_mapping': []}
vif = self._dev_mock({"address": "fake_address", "tag": "vif_tag"})
bdm = self._dev_mock({"device_name": "/dev/xvdx", "tag": "bdm_tag"})
mock_get_vifs.return_value = [vif]
mock_get_bdms.return_value = [bdm]
mock_InstanceDeviceMetadata.side_effect = \
lambda **kw: {"devices": kw.get("devices")}
mock_NetworkInterfaceMetadata.return_value = mock.sentinel.vif_metadata
mock_prepare_disk_metadata.return_value = [mock.sentinel.bdm_metadata]
dev_meta = self.vmops._save_device_metadata(context, instance,
block_device_info)
mock_get_vifs.assert_called_once_with(context, instance["uuid"])
mock_NetworkInterfaceMetadata.assert_called_once_with(mac=vif.address,
bus=mock_PCIDeviceBus.return_value,
tags=[vif.tag])
mock_prepare_disk_metadata.assert_called_once_with(bdm)
self.assertEqual(dev_meta["devices"],
[mock.sentinel.vif_metadata, mock.sentinel.bdm_metadata])
def test_spawn_with_neutron_exception(self):
self.mox.StubOutWithMock(self.vmops, '_get_neutron_events')
self.assertRaises(exception.VirtualInterfaceCreateException,
@ -523,8 +632,8 @@ class SpawnTestCase(VMOpsTestBase):
context = "context"
migration = {}
name_label = "dummy"
instance = {"name": name_label, "uuid": "fake_uuid",
"root_device_name": "/dev/xvda"}
instance = self._new_instance({"name": name_label, "uuid": "fake_uuid",
"root_device_name": "/dev/xvda", "device_metadata": None})
disk_info = "disk_info"
network_info = "net_info"
image_meta = objects.ImageMeta.from_dict({"id": uuids.image_id})
@ -566,6 +675,7 @@ class SpawnTestCase(VMOpsTestBase):
if resize_instance:
self.vmops._resize_up_vdis(instance, vdis)
self.vmops._save_device_metadata(context, instance, block_device_info)
self.vmops._attach_disks(context, instance, image_meta, vm_ref,
name_label, vdis, di_type, network_info, False,
None, None)

View File

@ -71,7 +71,7 @@ class XenAPIDriver(driver.ComputeDriver):
"supports_recreate": False,
"supports_migrate_to_same_host": False,
"supports_attach_interface": True,
"supports_device_tagging": False,
"supports_device_tagging": True,
}
def __init__(self, virtapi, read_only=False):

View File

@ -458,6 +458,8 @@ class VMOps(object):
if resize:
self._resize_up_vdis(instance, vdis)
instance.device_metadata = self._save_device_metadata(
context, instance, block_device_info)
self._attach_disks(context, instance, image_meta, vm_ref,
name_label, vdis, disk_image_type,
network_info, rescue,
@ -816,6 +818,79 @@ class VMOps(object):
admin_password=admin_password,
files=files)
@staticmethod
def _prepare_disk_metadata(bdm):
"""Returns the disk metadata with dual disk buses - ide and xen. More
details about Xen device number can be found in
http://xenbits.xen.org/docs/4.2-testing/misc/vbd-interface.txt
"""
path = bdm.device_name
disk_num = volume_utils.get_device_number(path)
xen0 = objects.XenDeviceBus(address=("00%02d00" % disk_num))
registry = ('HKLM\\SYSTEM\\ControlSet001\\Enum\\SCSI\\'
'Disk&Ven_XENSRC&Prod_PVDISK\\')
vbd_prefix = '/sys/devices/vbd-'
if disk_num < 4:
ide = objects.IDEDeviceBus(
address=("%d:%d" % (disk_num / 2, disk_num % 2)))
xen1 = objects.XenDeviceBus(
address=("%d" % (202 << 8 | disk_num << 4)))
xen2 = objects.XenDeviceBus()
if disk_num < 2:
xen2.address = "%d" % (3 << 8 | disk_num << 6)
else:
xen2.address = "%d" % (22 << 8 | (disk_num - 2) << 6)
return [objects.DiskMetadata(path=path, bus=ide, tags=[bdm.tag]),
objects.DiskMetadata(path=registry + xen0.address,
bus=xen0, tags=[bdm.tag]),
objects.DiskMetadata(path=vbd_prefix + xen1.address,
bus=xen1, tags=[bdm.tag]),
objects.DiskMetadata(path=vbd_prefix + xen2.address,
bus=xen2, tags=[bdm.tag])]
else:
xen1 = objects.XenDeviceBus()
if disk_num < 16:
xen1.address = "%d" % (202 << 8 | disk_num << 4)
else:
xen1.address = "%d" % (1 << 28 | disk_num << 8)
return [objects.DiskMetadata(path=registry + xen0.address,
bus=xen0, tags=[bdm.tag]),
objects.DiskMetadata(path=vbd_prefix + xen1.address,
bus=xen1, tags=[bdm.tag])]
def _save_device_metadata(self, context, instance, block_device_info):
"""Builds a metadata object for instance devices, that maps the user
provided tag to the hypervisor assigned device address.
"""
vifs = objects.VirtualInterfaceList.get_by_instance_uuid(
context, instance["uuid"])
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
context, instance["uuid"])
metadata = []
for vif in vifs:
if 'tag' in vif and vif.tag:
device = objects.NetworkInterfaceMetadata(
mac=vif.address,
bus=objects.PCIDeviceBus(),
tags=[vif.tag])
metadata.append(device)
if block_device_info:
for bdm in bdms:
if 'tag' in bdm and bdm.tag:
metadata.extend(self._prepare_disk_metadata(bdm))
if metadata:
return objects.InstanceDeviceMetadata(devices=metadata)
def _wait_for_instance_to_start(self, instance, vm_ref):
LOG.debug('Waiting for instance state to become running',
instance=instance)