[Kimchi-devel] [PATCH v9 5/5] Host device passthrough: Add unit tests and documents

Zhou Zheng Sheng zhshzhou at linux.vnet.ibm.com
Wed Jul 30 15:29:27 UTC 2014


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 at 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




More information about the Kimchi-devel mailing list