Reviewed-by: Aline Manera <alinefm(a)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(a)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']))