
Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com> On 10/08/2014 06:08 AM, Zhou Zheng Sheng wrote:
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.
v12: Add "error" field for each new parameter and API call in API.json. Define Mock device information directly instead of parsing fake XML.
Signed-off-by: Zhou Zheng Sheng <zhshzhou@linux.vnet.ibm.com> --- docs/API.md | 57 ++++++++++++++++++++++++++--- src/kimchi/API.json | 38 ++++++++++++++++++++ src/kimchi/i18n.py | 5 +++ src/kimchi/mockmodel.py | 95 ++++++++++++++++++++++++++++++++++++++++++++----- tests/test_model.py | 31 ++++++++++++++++ tests/test_rest.py | 6 ++-- 6 files changed, 215 insertions(+), 17 deletions(-)
diff --git a/docs/API.md b/docs/API.md index b65f211..92fbbd5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -168,6 +168,21 @@ Represents a snapshot of the Virtual Machine's primary monitor. **Actions (POST):**
+### 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 @@ -897,11 +912,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_affected_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
@@ -909,14 +930,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 5b752dc..d9e13f0 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -721,6 +721,44 @@ }, "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$", + "error": "KCHDEVS0001E" + }, + "_passthrough": { + "description": "List only devices eligible to be assigned to guest", + "type": "string", + "pattern": "^true|false$", + "error": "KCHDEVS0002E" + }, + "_passthrough_affected_by": { + "description": "List the affected devices in the same group of a certain device to be assigned to guest", + "type": "string", + "pattern": "^[_A-Za-z0-9-]+$", + "error": "KCHDEVS0003E" + } + }, + "additionalProperties": false, + "error": "KCHAPI0001E" + }, + "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, + "error": "KCHVMHDEV0004E" + } + }, + "error": "KCHAPI0001E" } } } diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index ad65775..75fb076 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -40,6 +40,10 @@ messages = { "KCHAUTH0002E": _("You are not authorized to access Kimchi"), "KCHAUTH0003E": _("Specify %(item)s to login into Kimchi"),
+ "KCHDEVS0001E": _('Unknown "_cap" specified'), + "KCHDEVS0002E": _('"_passthrough" should be "true" or "false"'), + "KCHDEVS0003E": _('"_passthrough_affected_by" should be a device name string'), + "KCHDISKS0001E": _("Error while getting block devices. Details: %(err)s"), "KCHDISKS0002E": _("Error while getting block device information for %(device)s."),
@@ -104,6 +108,7 @@ messages = { "Please enable Intel VT-d or AMD IOMMU in your BIOS, then verify the Kernel is compiled with IOMMU support. " "For Intel CPU, add intel_iommu=on to your Kernel parameter in /boot/grub2/grub.conf. " "For AMD CPU, add iommu=pt iommu=1."), + "KCHVMHDEV0004E": _('"name" should be a device name string'),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 0fa16e8..2d0135a 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -91,6 +91,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'] @@ -611,16 +612,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_affected_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', @@ -1418,6 +1418,83 @@ class MockRepositories(object): del self._repos[repo_id]
+class MockDevices(object): + def __init__(self): + self.devices = { + 'computer': {'device_type': 'system', + 'firmware': {'release_date': '01/01/2012', + 'vendor': 'LENOVO', + 'version': 'XXXXX (X.XX )'}, + 'hardware': {'serial': 'PXXXXX', + 'uuid': + '9d660370-820f-4241-8731-5a60c97e8aa6', + 'vendor': 'LENOVO', + 'version': 'ThinkPad T420'}, + 'name': 'computer', + 'parent': None, + 'product': '4180XXX'}, + 'pci_0000_03_00_0': {'bus': 3, + 'device_type': 'pci', + 'domain': 0, + 'driver': {'name': 'iwlwifi'}, + 'function': 0, + 'iommuGroup': 7, + 'name': 'pci_0000_03_00_0', + 'parent': 'computer', + 'path': + '/sys/devices/pci0000:00/0000:03:00.0', + 'product': { + 'description': + 'Centrino Advanced-N 6205 [Taylor Peak]', + 'id': '0x0085'}, + 'slot': 0, + 'vendor': {'description': 'Intel Corporation', + 'id': '0x8086'}}, + 'pci_0000_0d_00_0': {'bus': 13, + 'device_type': 'pci', + 'domain': 0, + 'driver': {'name': 'sdhci-pci'}, + 'function': 0, + 'iommuGroup': 7, + 'name': 'pci_0000_0d_00_0', + 'parent': 'computer', + 'path': + '/sys/devices/pci0000:00/0000:0d:00.0', + 'product': {'description': + 'PCIe SDXC/MMC Host Controller', + 'id': '0xe823'}, + 'slot': 0, + 'vendor': {'description': 'Ricoh Co Ltd', + 'id': '0x1180'}}, + 'scsi_host0': {'adapter': {'fabric_wwn': '37df6c1efa1b4388', + 'type': 'fc_host', + 'wwnn': 'efb6563f06434a98', + 'wwpn': '742f32073aab45d7'}, + 'device_type': 'scsi_host', + 'host': 0, + 'name': 'scsi_host0', + 'parent': 'computer', + 'path': '/sys/devices/pci0000:00/0000:40:00.0/0'}, + 'scsi_host1': {'adapter': {'fabric_wwn': '542efa5dced34123', + 'type': 'fc_host', + 'wwnn': 'b7433a40c9b84092', + 'wwpn': '25c1f485ae42497f'}, + 'device_type': 'scsi_host', + 'host': 0, + 'name': 'scsi_host1', + 'parent': 'computer', + 'path': '/sys/devices/pci0000:00/0000:40:00.0/1'}, + 'scsi_host2': {'adapter': {'fabric_wwn': '5c373c334c20478d', + 'type': 'fc_host', + 'wwnn': 'f2030bec4a254e6b', + 'wwpn': '07dbca4164d44096'}, + 'device_type': 'scsi_host', + 'host': 0, + 'name': 'scsi_host2', + 'parent': 'computer', + 'path': '/sys/devices/pci0000:00/0000:40:00.0/2'}} + + def get_mock_environment(): model = MockModel() for i in xrange(5): diff --git a/tests/test_model.py b/tests/test_model.py index 1f2e79c..f0f5fd8 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1059,6 +1059,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_affected_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 f0b828c..a1ebb32 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -157,13 +157,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']))