
Add some basice unit tests for fetching host device information. Update MockModel as well. Update "API.json" and "API.md" to reflect the change in API. Signed-off-by: Zhou Zheng Sheng <zhshzhou@linux.vnet.ibm.com> --- docs/API.md | 57 +++++++++++++++++++-- src/kimchi/API.json | 31 ++++++++++++ src/kimchi/mockmodel.py | 128 ++++++++++++++++++++++++++++++++++++++++++++---- tests/test_model.py | 31 ++++++++++++ tests/test_rest.py | 6 +-- 5 files changed, 236 insertions(+), 17 deletions(-) diff --git a/docs/API.md b/docs/API.md index ab035bc..7260395 100644 --- a/docs/API.md +++ b/docs/API.md @@ -167,6 +167,21 @@ Represents a snapshot of the Virtual Machine's primary monitor. * eject: Eject cdrom from device. +### Sub-collection: Virtual Machine Passthrough Devices +**URI:** /vms/*:name*/hostdevs +* **GET**: Retrieve a summarized list of all directly assigned host device of + specified guest. +* **POST**: Directly assign a host device to guest. + * name: The name of the host device to be assigned to vm. + +### Sub-resource: Device +**URI:** /vms/*:name*/hostdevs/*:dev* +* **GET**: Retrieve assigned device information + * name: The name of the assigned device. + * type: The type of the assigned device. +* **DELETE**: Detach the host device from VM. + + ### Collection: Templates **URI:** /templates @@ -890,11 +905,17 @@ stats history **Methods:** -* **GET**: Retrieves list of host pci devices (Node Devices). - Currently only scsi_host devices are supported: +* **GET**: Retrieves list of host devices (Node Devices). * Parameters: * _cap: Filter node device list with given node device capability. To list Fibre Channel SCSI Host devices, use "_cap=fc_host". + Other available values are "fc_host", "net", "pci", "scsi", + "storage", "system", "usb" and "usb_device". + * _passthrough: Filter devices eligible to be assigned to guest + directly. Possible values are "ture" and "false". + * _passthrough_group_by: Filter the affected devices in the same + group of a certain directly assigned device. + The value should be the name of a device. ### Resource: Device @@ -902,14 +923,40 @@ stats history **Methods:** -* **GET**: Retrieve information of a single pci device. - Currently only scsi_host devices are supported: +* **GET**: Retrieve information of a single host device. + * device_type: Type of the device, supported types are "net", "pci", "scsi", + "storage", "system", "usb" and "usb_device". * name: The name of the device. * path: Path of device in sysfs. - * adapter: Host adapter information. Empty if pci device is not scsi_host. + * parent: The name of the parent parent device. + * adapter: Host adapter information of a "scsi_host" or "fc_host" device. * type: The capability type of the scsi_host device (fc_host, vport_ops). * wwnn: The HBA Word Wide Node Name. Empty if pci device is not fc_host. * wwpn: The HBA Word Wide Port Name. Empty if pci device is not fc_host. + * domain: Domain number of a "pci" device. + * bus: Bus number of a "pci" device. + * slot: Slot number of a "pci" device. + * function: Function number of a "pci" device. + * vendor: Vendor information of a "pci" device. + * id: Vendor id of a "pci" device. + * description: Vendor description of a "pci" device. + * product: Product information of a "pci" device. + * id: Product id of a "pci" device. + * description: Product description of a "pci" device. + * iommuGroup: IOMMU group number of a "pci" device. Would be None/null if + host does not enable IOMMU support. + + +### Sub-collection: VMs with the device assigned. +**URI:** /host/devices/*:name*/vmholders +* **GET**: Retrieve a summarized list of all VMs holding the device. + +### Sub-resource: VM holder +**URI:** /host/devices/*:name*/vmholders/*:vm* +* **GET**: Retrieve information of the VM which is holding the device + * name: The name of the VM. + * state: The power state of the VM. Could be "running" and "shutdown". + ### Collection: Host Packages Update diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 4b432a2..fe52c83 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -717,6 +717,37 @@ }, "additionalProperties": false, "error": "KCHAPI0001E" + }, + "devices_get_list": { + "type": "object", + "properties": { + "_cap": { + "description": "List specific type of device", + "type": "string", + "pattern": "^fc_host|net|pci|scsi|scsi_host|storage|system|usb|usb_device$" + }, + "_passthrough": { + "description": "List only devices eligible to be assigned to guest", + "type": "string", + "pattern": "^true|false$" + }, + "_passthrough_group_by": { + "description": "List the affected devices in the same group of a certain device to be assigned to guest", + "type": "string" + } + }, + "additionalProperties": false + }, + "vmhostdevs_create": { + "type": "object", + "properties": { + "name": { + "description": "Then name of the device to assign to VM", + "type": "string", + "pattern": "^[_A-Za-z0-9-]+$", + "required": true + } + } } } } diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 7cbac72..495092b 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -47,6 +47,7 @@ from kimchi.config import config as kconfig from kimchi.distroloader import DistroLoader from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import MissingParameter, NotFoundError, OperationFailed +from kimchi.model import hostdev from kimchi.model.storagepools import ISO_POOL_NAME from kimchi.model.storageservers import STORAGE_SERVERS from kimchi.model.utils import get_vm_name @@ -87,6 +88,7 @@ class MockModel(object): self.next_taskid = 1 self.storagepool_activate('default') self._mock_host_repositories = MockRepositories() + self._mock_devices = MockDevices() def _static_vm_update(self, dom, params): state = dom.info['state'] @@ -525,16 +527,15 @@ class MockModel(object): raise InvalidOperation("KCHVOL0006E", {'pool': pool}) return res._volumes.keys() - def devices_get_list(self, _cap=None): - return ['scsi_host3', 'scsi_host4', 'scsi_host5'] + def devices_get_list(self, _cap=None, _passthrough=None, + _passthrough_group_by=None): + if _cap is None: + return self._mock_devices.devices.keys() + return [dev['name'] for dev in self._mock_devices.devices.values() + if dev['device_type'] == _cap] - def device_lookup(self, nodedev_name): - return { - 'name': nodedev_name, - 'adapter': { - 'type': 'fc_host', - 'wwnn': uuid.uuid4().hex[:16], - 'wwpn': uuid.uuid4().hex[:16]}} + def device_lookup(self, dev_name): + return self._mock_devices.devices[dev_name] def isopool_lookup(self, name): return {'state': 'active', @@ -1306,6 +1307,115 @@ class MockRepositories(object): del self._repos[repo_id] +class MockNodeDevice(object): + dev_xmls = { + "computer": """ +<device> + <name>computer</name> + <capability type='system'> + <product>4180XXX</product> + <hardware> + <vendor>LENOVO</vendor> + <version>ThinkPad T420</version> + <serial>PXXXXX</serial> + <uuid>9d660370-820f-4241-8731-5a60c97e8aa6</uuid> + </hardware> + <firmware> + <vendor>LENOVO</vendor> + <version>XXXXX (X.XX )</version> + <release_date>01/01/2012</release_date> + </firmware> + </capability> +</device>""", + "pci_0000_03_00_0": """ +<device> + <name>pci_0000_03_00_0</name> + <path>/sys/devices/pci0000:00/0000:03:00.0</path> + <parent>computer</parent> + <driver> + <name>iwlwifi</name> + </driver> + <capability type='pci'> + <domain>0</domain> + <bus>3</bus> + <slot>0</slot> + <function>0</function> + <product id='0x0085'>Centrino Advanced-N 6205 [Taylor Peak]</product> + <vendor id='0x8086'>Intel Corporation</vendor> + <iommuGroup number='7'> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x0'/> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x1'/> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x3'/> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x4'/> + <address domain='0x0000' bus='0x03' slot='0x00' function='0x0'/> + <address domain='0x0000' bus='0x0d' slot='0x00' function='0x0'/> + </iommuGroup> + </capability> +</device>""", + "pci_0000_0d_00_0": """ +<device> + <name>pci_0000_0d_00_0</name> + <path>/sys/devices/pci0000:00/0000:0d:00.0</path> + <parent>computer</parent> + <driver> + <name>sdhci-pci</name> + </driver> + <capability type='pci'> + <domain>0</domain> + <bus>13</bus> + <slot>0</slot> + <function>0</function> + <product id='0xe823'>PCIe SDXC/MMC Host Controller</product> + <vendor id='0x1180'>Ricoh Co Ltd</vendor> + <iommuGroup number='7'> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x0'/> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x1'/> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x3'/> + <address domain='0x0000' bus='0x00' slot='0x1c' function='0x4'/> + <address domain='0x0000' bus='0x03' slot='0x00' function='0x0'/> + <address domain='0x0000' bus='0x0d' slot='0x00' function='0x0'/> + </iommuGroup> + </capability> +</device>""", + } + for i in range(3): + dev_xmls['scsi_host%s' % i] = """ +<device> + <name>scsi_host%(ind)s</name> + <path>/sys/devices/pci0000:00/0000:40:00.0/%(ind)s</path> + <parent>computer</parent> + <capability type='scsi_host'> + <host>0</host> + <capability type='fc_host'> + <wwnn>%(wwnn)s</wwnn> + <wwpn>%(wwpn)s</wwpn> + <fabric_wwn>%(fabric_wwn)s</fabric_wwn> + </capability> + </capability> +</device>""" % {"ind": i, + "wwnn": uuid.uuid4().hex[:16], + "wwpn": uuid.uuid4().hex[:16], + "fabric_wwn": uuid.uuid4().hex[:16]} + + def __init__(self, dev_name): + self._dev_name = dev_name + + def XMLDesc(self, flag=0): + return MockNodeDevice.dev_xmls[self._dev_name] + + def parent(self): + return None if self._dev_name == 'computer' else 'computer' + + +class MockDevices(object): + def __init__(self): + self.devices = {} + dev_xmls = MockNodeDevice.dev_xmls + for dev_name, dev_xml in dev_xmls.items(): + self.devices[dev_name] = \ + hostdev.get_dev_info(MockNodeDevice(dev_name)) + + def get_mock_environment(): model = MockModel() for i in xrange(5): diff --git a/tests/test_model.py b/tests/test_model.py index cab8288..d9fc7bc 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -939,6 +939,37 @@ class ModelTests(unittest.TestCase): self.assertIn('ipaddr', iface) self.assertIn('netmask', iface) + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') + def test_get_devices(self): + def asset_devices_type(devices, dev_type): + for dev in devices: + self.assertEquals(dev['device_type'], dev_type) + + inst = model.Model('qemu:///system', + objstore_loc=self.tmp_store) + + devs = inst.devices_get_list() + + for dev_type in ('pci', 'usb_device', 'scsi'): + names = inst.devices_get_list(_cap=dev_type) + self.assertTrue(set(names) <= set(devs)) + infos = [inst.device_lookup(name) for name in names] + asset_devices_type(infos, dev_type) + + passthru_devs = inst.devices_get_list(_passthrough='true') + self.assertTrue(set(passthru_devs) <= set(devs)) + + for dev_type in ('pci', 'usb_device', 'scsi'): + names = inst.devices_get_list(_cap=dev_type, _passthrough='true') + self.assertTrue(set(names) <= set(devs)) + infos = [inst.device_lookup(name) for name in names] + asset_devices_type(infos, dev_type) + + for dev_name in passthru_devs: + affected_devs = inst.devices_get_list( + _passthrough_group_by=dev_name) + self.assertTrue(set(affected_devs) <= set(devs)) + def test_async_tasks(self): class task_except(Exception): pass diff --git a/tests/test_rest.py b/tests/test_rest.py index 82326cf..0fc94ea 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -155,13 +155,13 @@ class RestTests(unittest.TestCase): self.assertHTTPStatus(406, "/", None, 'GET', h) def test_host_devices(self): - nodedevs = json.loads(self.request('/host/devices').read()) + nodedevs = json.loads(self.request('/host/devices?_cap=scsi_host').read()) # Mockmodel brings 3 preconfigured scsi fc_host self.assertEquals(3, len(nodedevs)) - nodedev = json.loads(self.request('/host/devices/scsi_host4').read()) + nodedev = json.loads(self.request('/host/devices/scsi_host2').read()) # Mockmodel generates random wwpn and wwnn - self.assertEquals('scsi_host4', nodedev['name']) + self.assertEquals('scsi_host2', nodedev['name']) self.assertEquals('fc_host', nodedev['adapter']['type']) self.assertEquals(16, len(nodedev['adapter']['wwpn'])) self.assertEquals(16, len(nodedev['adapter']['wwnn'])) -- 1.9.3