[Kimchi-devel] [PATCH 2/4] host pci pass through: list all types of host devices

Zhou Zheng Sheng zhshzhou at linux.vnet.ibm.com
Wed Apr 23 09:48:10 UTC 2014


The URI /host/devices only presents scsi_host (particularly fc_host)
device information. To implement host pci pass through, we should list
all types of host devices. This patch adds support for parsing various host
devices information, and listing them on /host/devices. So the user is free
to choose any listed PCI device to pass through to guest. Since the
patch changes the device information dictionary format, the existing code
consuming the device information is also changed accordingly.

To get all types of host device, access the following URL.

curl -u root -H "Content-Type: application/json" \
  -H  "Accept: application/json" \
  http://127.0.0.1:8000/host/devices

To get only fc_host devices, change the URL to
  "http://127.0.0.1:8000/host/devices?_cap=fc_host"

To get only pci device, change the URL to
  "http://127.0.0.1:8000/host/devices?_cap=pci

Signed-off-by: Zhou Zheng Sheng <zhshzhou at linux.vnet.ibm.com>
---
 docs/API.md                            |  11 +-
 src/kimchi/hostdev.py                  | 307 +++++++++++++++++++++++++++++++++
 src/kimchi/mockmodel.py                |   7 +-
 src/kimchi/model/host.py               |  15 +-
 src/kimchi/model/libvirtstoragepool.py |  18 +-
 tests/test_rest.py                     |   6 +-
 tests/test_storagepool.py              |   7 +-
 7 files changed, 335 insertions(+), 36 deletions(-)
 create mode 100644 src/kimchi/hostdev.py

diff --git a/docs/API.md b/docs/API.md
index 716c983..009c58d 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -822,12 +822,11 @@ Contains the host sample data.
 * **GET**: Retrieve information of a single pci device.
            Currently only scsi_host devices are supported:
     * name: The name of the device.
-    * adapter_type: The capability type of the scsi_host device (fc_host).
-                    Empty if pci device is not scsi_host.
-    * wwnn: The HBA Word Wide Node Name.
-            Empty if pci device is not scsi_host.
-    * wwpn: The HBA Word Wide Port Name
-            Empty if pci device is not scsi_host.
+    * path: Path of device in sysfs.
+    * adapter: Host adapter information. Empty if pci device is not scsi_host.
+        * 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.
 
 ### Collection: Host Packages Update
 
diff --git a/src/kimchi/hostdev.py b/src/kimchi/hostdev.py
new file mode 100644
index 0000000..638b654
--- /dev/null
+++ b/src/kimchi/hostdev.py
@@ -0,0 +1,307 @@
+#
+# Kimchi
+#
+# Copyright IBM Corp, 2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+from kimchi.utils import kimchi_log
+from kimchi.xmlutils import xpath_get_text
+
+
+def _xpath(xml_str):
+    def get_first_text(the_path):
+        r = xpath_get_text(xml_str, the_path)
+        if r == []:
+            return None
+        r = r[0]
+        if r is None:
+            return ''
+        return r
+    return get_first_text
+
+
+def get_all_host_devices(libvirt_conn):
+    devs = libvirt_conn.listAllDevices()
+    return [get_dev_info(dev) for dev in devs]
+
+
+def get_devices_tree(dev_infos):
+    devs = dict([(dev_info['name'], dev_info) for dev_info in dev_infos])
+    root = None
+    for dev_info in dev_infos:
+        if dev_info['parent'] is None:
+            root = dev_info
+            continue
+        try:
+            parent = devs[dev_info['parent']]
+        except KeyError:
+            continue
+
+        try:
+            children = parent['children']
+        except KeyError:
+            parent['children'] = [dev_info]
+        else:
+            children.append(dev_info)
+    return root
+
+
+def get_dev_info(dev):
+    ''' Parse the node device XML string into dict according to
+    http://libvirt.org/formatnode.html. '''
+
+    info = {'name': dev.name(), 'parent': dev.parent()}
+
+    xml_str = dev.XMLDesc()
+    xpath = _xpath(xml_str)
+    info['path'] = xpath('/device/path')
+    dev_type = xpath('/device/capability/@type')
+    info['type'] = dev_type
+
+    get_dev_type_info = {
+        'net': _get_net_dev_info,
+        'pci': _get_pci_dev_info,
+        'scsi': _get_scsi_dev_info,
+        'scsi_generic': _get_scsi_generic_dev_info,
+        'scsi_host': _get_scsi_host_dev_info,
+        'scsi_target': _get_scsi_target_dev_info,
+        'storage': _get_storage_dev_info,
+        'system': _get_system_dev_info,
+        'usb': _get_usb_dev_info,
+        'usb_device': _get_usb_device_dev_info,
+    }
+    try:
+        get_info = get_dev_type_info[dev_type]
+    except KeyError:
+        kimchi_log.error("Unknown device type: %s", dev_type)
+        return info
+
+    info.update(get_info(xml_str))
+    return info
+
+
+def _get_net_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'interface': xpath('/device/capability/interface'),
+        'address': xpath('/device/capability/address')}
+
+    links = {"80203": "IEEE 802.3", "80211": "IEEE 802.11"}
+    link_raw = xpath('/device/capability/capability/@type')
+    info['link_type'] = links.get(link_raw, link_raw)
+
+    return info
+
+
+def _get_pci_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'domain': xpath('/device/capability/domain'),
+        'bus': xpath('/device/capability/bus'),
+        'slot': xpath('/device/capability/slot'),
+        'function': xpath('/device/capability/function'),
+        'product': {
+            'id': xpath('/device/capability/product/@id'),
+            'description': xpath('/device/capability/product')},
+        'vendor': {
+            'id': xpath('/device/capability/vendor/@id'),
+            'name': xpath('/device/capability/vendor')}}
+
+    iommuGroup_num = xpath('/device/capability/iommuGroup/@number')
+    if iommuGroup_num is not None:
+        info['iommuGroup'] = iommuGroup_num
+    return info
+
+
+def _get_scsi_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'host': xpath('/device/capability/host'),
+        'bus': xpath('/device/capability/bus'),
+        'target': xpath('/device/capability/target'),
+        'lun': xpath('/device/capability/lun'),
+        'type': xpath('/device/capability/type')}
+    return info
+
+
+def _get_scsi_generic_dev_info(xml_str):
+    # scsi_generic is not documented in libvirt official website. Try to
+    # parse scsi_generic according to the following libvirt path series.
+    # https://www.redhat.com/archives/libvir-list/2013-June/msg00014.html
+    xpath = _xpath(xml_str)
+    info = {
+        'char': xpath('/device/capability/char')}
+    return info
+
+
+def _get_scsi_host_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'host': xpath('/device/capability/host')}
+
+    cap_type = xpath('/device/capability/capability/@type')
+    if cap_type is None:
+        info['adapter'] = {'type': ''}
+        return info
+    info['adapter'] = {'type': cap_type}
+
+    if cap_type == 'fc_host':
+        info['adapter'] = {
+            'wwnn': xpath('/device/capability/capability//wwnn'),
+            'wwpn': xpath('/device/capability/capability/wwpn'),
+            'fabric_wwn': xpath('/device/capability/capability/fabric_wwn')}
+    elif cap_type == 'vports_ops':
+        vport_in_use_count = xpath('/device/capability/capability/vports')
+        if vport_in_use_count is not None:
+            info['adapter']['vports'] = vport_in_use_count
+        max_vports = xpath('/device/capability/capability/max_vports')
+        if max_vports is not None:
+            info['adapter']['max_vports'] = max_vports
+    else:
+        kimchi_log.error("Unknown scsi host adapter type: %s", cap_type)
+
+    return info
+
+
+def _get_scsi_target_dev_info(xml_str):
+    # scsi_target is not documented in libvirt official website. Try to
+    # parse scsi_target according to the libvirt commit db19834a0a.
+    xpath = _xpath(xml_str)
+    info = {
+        'target': xpath('/device/capability/target')}
+    return info
+
+
+def _get_storage_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'block': xpath('/device/capability/block'),
+        'bus': xpath('/device/capability/bus'),
+        'drive_type': xpath('/device/capability/drive_type'),
+        'model': xpath('/device/capability/model'),
+        'vendor': xpath('/device/capability/vendor'),
+        'serial': xpath('/device/capability/serial'),
+        'size': xpath('/device/capability/size')}
+
+    media_type = xpath('/device/capability/capability/@type')
+    if media_type is not None:
+        info['media'] = {'type': media_type}
+        if media_type == 'removable':
+            available = xpath('/device/capability/capability/media_available')
+            if available == '1':
+                info['media']['available'] = True
+                info['media'].update({
+                    'media_size':
+                    xpath('/device/capability/capability/media_size'),
+                    'media_label':
+                    xpath('/device/capability/capability/media_label')
+                })
+            else:
+                info['media']['available'] = False
+    return info
+
+
+def _get_system_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'hardware': {
+            'vendor': xpath('/device/capability/hardware/vendor'),
+            'version': xpath('/device/capability/hardware/version'),
+            'serial': xpath('/device/capability/hardware/serial'),
+            'uuid': xpath('/device/capability/hardware/uuid')},
+        'firmware': {
+            'vendor': xpath('/device/capability/firmware/vendor'),
+            'version': xpath('/device/capability/firmware/version'),
+            'release_date': xpath('/device/capability/firmware/version')}}
+
+    product = xpath('/device/capability/product')
+    if product is not None:
+        info['product'] = product
+    return info
+
+
+def _get_usb_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'number': xpath('/device/capability/number'),
+        'class': xpath('/device/capability/class'),
+        'subclass': xpath('/device/capability/subclass'),
+        'protocol': xpath('/device/capability/protocol')}
+
+    description = xpath('/device/capability/description')
+    if description is not None:
+        info['description'] = description
+    return info
+
+
+def _get_usb_device_dev_info(xml_str):
+    xpath = _xpath(xml_str)
+    info = {
+        'bus': xpath('/device/capability/bus'),
+        'device': xpath('/device/capability/device'),
+        'product': {
+            'id': xpath('/device/capability/product/@id'),
+            'description': xpath('/device/capability/product')},
+        'vendor': {
+            'id': xpath('/device/capability/vendor/@id'),
+            'description': xpath('/device/capability/vendor')}}
+    return info
+
+
+# For test and debug
+def _print_host_dev_tree():
+    from kimchi.model.libvirtconnection import LibvirtConnection
+    conn = LibvirtConnection('qemu:///system').get()
+    dev_infos = get_all_host_devices(conn)
+    root = get_devices_tree(dev_infos)
+    if root is None:
+        print "No device found"
+        return
+    print '-----------------'
+    print '\n'.join(_format_dev_node(root))
+
+
+def _format_dev_node(node):
+    from pprint import pformat
+
+    try:
+        children = node['children']
+        del node['children']
+    except KeyError:
+        children = []
+
+    lines = []
+    lines.extend([' ~' + line for line in pformat(node).split('\n')])
+
+    count = len(children)
+    for i, child in enumerate(children):
+        if count == 1:
+            lines.append('   \-----------------')
+        else:
+            lines.append('   +-----------------')
+        clines = _format_dev_node(child)
+        if i == count - 1:
+            p = '    '
+        else:
+            p = '   |'
+        lines.extend([p + cline for cline in clines])
+    lines.append('')
+
+    return lines
+
+
+if __name__ == '__main__':
+    _print_host_dev_tree()
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index 866ad2c..a3e92f7 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -490,9 +490,10 @@ class MockModel(object):
     def device_lookup(self, nodedev_name):
         return {
             'name': nodedev_name,
-            'adapter_type': 'fc_host',
-            'wwnn': uuid.uuid4().hex[:16],
-            'wwpn': uuid.uuid4().hex[:16]}
+            'adapter': {
+                'type': 'fc_host',
+                'wwnn': uuid.uuid4().hex[:16],
+                'wwpn': uuid.uuid4().hex[:16]}}
 
     def isopool_lookup(self, name):
         return {'state': 'active',
diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
index e0ac760..112141c 100644
--- a/src/kimchi/model/host.py
+++ b/src/kimchi/model/host.py
@@ -28,6 +28,7 @@ import psutil
 from cherrypy.process.plugins import BackgroundTask
 
 from kimchi import disks
+from kimchi import hostdev
 from kimchi import netinfo
 from kimchi import xmlutils
 from kimchi.basemodel import Singleton
@@ -265,20 +266,10 @@ class DeviceModel(object):
     def lookup(self, nodedev_name):
         conn = self.conn.get()
         try:
-            dev_xml = conn.nodeDeviceLookupByName(nodedev_name).XMLDesc(0)
+            dev = conn.nodeDeviceLookupByName(nodedev_name)
         except:
             raise NotFoundError('KCHHOST0003E', {'name': nodedev_name})
-        cap_type = xmlutils.xpath_get_text(
-            dev_xml, '/device/capability/capability/@type')
-        wwnn = xmlutils.xpath_get_text(
-            dev_xml, '/device/capability/capability/wwnn')
-        wwpn = xmlutils.xpath_get_text(
-            dev_xml, '/device/capability/capability/wwpn')
-        return {
-            'name': nodedev_name,
-            'adapter_type': cap_type[0] if len(cap_type) >= 1 else '',
-            'wwnn': wwnn[0] if len(wwnn) == 1 else '',
-            'wwpn': wwpn[0] if len(wwpn) == 1 else ''}
+        return hostdev.get_dev_info(dev)
 
 
 class PackagesUpdateModel(object):
diff --git a/src/kimchi/model/libvirtstoragepool.py b/src/kimchi/model/libvirtstoragepool.py
index 47b239b..b15bf1a 100644
--- a/src/kimchi/model/libvirtstoragepool.py
+++ b/src/kimchi/model/libvirtstoragepool.py
@@ -180,34 +180,34 @@ class ScsiPoolDef(StoragePoolDef):
         self.poolArgs['source']['name'] = tmp_name.replace('scsi_', '')
         # fc_host adapters type are only available in libvirt >= 1.0.5
         if not self.poolArgs['fc_host_support']:
-            self.poolArgs['source']['adapter_type'] = 'scsi_host'
+            self.poolArgs['source']['adapter']['type'] = 'scsi_host'
             msg = "Libvirt version <= 1.0.5. Setting SCSI host name as '%s'; "\
                   "setting SCSI adapter type as 'scsi_host'; "\
                   "ignoring wwnn and wwpn." % tmp_name
             kimchi_log.info(msg)
         # Path for Fibre Channel scsi hosts
         self.poolArgs['path'] = '/dev/disk/by-path'
-        if not self.poolArgs['source']['adapter_type']:
-            self.poolArgs['source']['adapter_type'] = 'scsi_host'
+        if not self.poolArgs['source']['adapter']['type']:
+            self.poolArgs['source']['adapter']['type'] = 'scsi_host'
 
     @property
     def xml(self):
         # Required parameters
         # name:
-        # source[adapter_type]:
+        # source[adapter][type]:
         # source[name]:
-        # source[wwnn]:
-        # source[wwpn]:
+        # source[adapter][wwnn]:
+        # source[adapter][wwpn]:
         # path:
 
         xml = """
         <pool type='scsi'>
           <name>{name}</name>
           <source>
-            <adapter type='{source[adapter_type]}'\
+            <adapter type='{source[adapter][type]}'\
                      name='{source[name]}'\
-                     wwnn='{source[wwnn]}'\
-                     wwpn='{source[wwpn]}'/>
+                     wwnn='{source[adapter][wwnn]}'\
+                     wwpn='{source[adapter][wwpn]}'/>
           </source>
           <target>
             <path>{path}</path>
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 06396db..966718d 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -155,9 +155,9 @@ class RestTests(unittest.TestCase):
         nodedev = json.loads(self.request('/host/devices/scsi_host4').read())
         # Mockmodel generates random wwpn and wwnn
         self.assertEquals('scsi_host4', nodedev['name'])
-        self.assertEquals('fc_host', nodedev['adapter_type'])
-        self.assertEquals(16, len(nodedev['wwpn']))
-        self.assertEquals(16, len(nodedev['wwnn']))
+        self.assertEquals('fc_host', nodedev['adapter']['type'])
+        self.assertEquals(16, len(nodedev['adapter']['wwpn']))
+        self.assertEquals(16, len(nodedev['adapter']['wwnn']))
 
     def test_get_vms(self):
         vms = json.loads(self.request('/vms').read())
diff --git a/tests/test_storagepool.py b/tests/test_storagepool.py
index 22b4943..3e3ad83 100644
--- a/tests/test_storagepool.py
+++ b/tests/test_storagepool.py
@@ -145,9 +145,10 @@ class storagepoolTests(unittest.TestCase):
                  'path': '/dev/disk/by-path',
                  'source': {
                      'name': 'scsi_host3',
-                     'adapter_type': 'fc_host',
-                     'wwpn': '0123456789abcdef',
-                     'wwnn': 'abcdef0123456789'}},
+                     'adapter': {
+                         'type': 'fc_host',
+                         'wwpn': '0123456789abcdef',
+                         'wwnn': 'abcdef0123456789'}}},
              'xml':
              """
              <pool type='scsi'>
-- 
1.9.0




More information about the Kimchi-devel mailing list