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

Aline Manera alinefm at linux.vnet.ibm.com
Fri Oct 3 17:29:45 UTC 2014


On 09/30/2014 07:00 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.
>
> 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 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..d906288 100644
> --- a/src/kimchi/API.json
> +++ b/src/kimchi/API.json
> @@ -721,6 +721,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_affected_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
> +                }
> +            }
>           }

You need to set "error" to each of those new parameters/structures.

>       }
>   }
> diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
> index d807eec..9b520bf 100644
> --- a/src/kimchi/mockmodel.py
> +++ b/src/kimchi/mockmodel.py
> @@ -47,6 +47,7 @@ from kimchi.config import READONLY_POOL_TYPE, 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
> @@ -91,6 +92,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 +613,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 +1419,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))
> +

Wow! I don't think we need all that for the mockmodel.
I mean, you can get the values and input them on a dict, instead of 
having the XML and parse them.

Example:

self.devices['my-dev'] = {...}
self.devices['my-dev2'] = {...}
self.devices['my-dev3'] = {...}

> +
>   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 0d4e822..29d31eb 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']))




More information about the Kimchi-devel mailing list