[Kimchi-devel] [PATCH v6 1/4] Host device passthrough: List all types of host devices

Zhou Zheng Sheng zhshzhou at linux.vnet.ibm.com
Mon Jun 23 02:40:42 UTC 2014


on 2014/06/16 17:56, Mark Wu wrote:
> On 06/09/2014 05:28 PM, Zhou Zheng Sheng wrote:
>> 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 -k -u root -H "Content-Type: application/json" \
>>    -H  "Accept: application/json" \
>>    https://127.0.0.1:8001/host/devices
>>
>> To get only fc_host devices, change the URL to
>>    "https://127.0.0.1:8001/host/devices?_cap=fc_host"
>>
>> To get only pci device, change the URL to
>>    "https://127.0.0.1:8001/host/devices?_cap=pci"
>>
>> v1:
>>    Parse the node device XML using xpath.
>>
>> v2:
>>    Write a "dictize" function and parse the node device XML using
>> dictize.
>>
>> v3:
>>    Fix a naming mistake.
>>
>> v4:
>>    It is observed that sometimes the parent devices is not listed by
>>    libvirt but the child device is listed. In previous version we catch
>>    this exception and ignore it. The root cause is unknown, and we failed
>>    to re-produce the problem. In v4 we do not catch it. It seems to be
>>    related to USB removable disk, and the problem is gone after we
>>    upgraded Linux kernel.
>>
>> Signed-off-by: Zhou Zheng Sheng <zhshzhou at linux.vnet.ibm.com>
>> ---
>>   docs/API.md                            |  11 +-
>>   src/kimchi/hostdev.py                  | 209
>> +++++++++++++++++++++++++++++++++
>>   src/kimchi/mockmodel.py                |   7 +-
>>   src/kimchi/model/host.py               |  15 +--
>>   src/kimchi/model/libvirtstoragepool.py |  18 +--
>>   src/kimchi/xmlutils.py                 |  26 +++-
>>   tests/test_rest.py                     |   6 +-
>>   tests/test_storagepool.py              |   7 +-
>>   8 files changed, 262 insertions(+), 37 deletions(-)
>>   create mode 100644 src/kimchi/hostdev.py
>>
>> diff --git a/docs/API.md b/docs/API.md
>> index ef5bf14..5026771 100644
>> --- a/docs/API.md
>> +++ b/docs/API.md
>> @@ -869,12 +869,11 @@ stats history
>>   * **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..d4c142d
>> --- /dev/null
>> +++ b/src/kimchi/hostdev.py
>> @@ -0,0 +1,209 @@
>> +#
>> +# 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.model.libvirtconnection import LibvirtConnection
>> +from kimchi.utils import kimchi_log
>> +from kimchi.xmlutils import dictize
>> +
>> +
>> +def _get_all_host_dev_infos():
>> +    libvirt_conn = LibvirtConnection('qemu:///system').get()
>> +    node_devs = libvirt_conn.listAllDevices()
>> +    return [get_dev_info(node_dev) for node_dev in node_devs]
>> +
>> +
>> +def _get_dev_info_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
>> +        parent = devs[dev_info['parent']]
>> +
>> +        try:
>> +            children = parent['children']
>> +        except KeyError:
>> +            parent['children'] = [dev_info]
>> +        else:
>> +            children.append(dev_info)
>> +    return root
>> +
>> +
>> +def get_dev_info(node_dev):
>> +    ''' Parse the node device XML string into dict according to
>> +    http://libvirt.org/formatnode.html. '''
>> +
>> +    def shift_subdict(d, toshift):
> It only has one usage, so we can remove the second parameter to make it
> purpose more clear.
> We can call it pop_capability or sth similar.

OK. Thanks.

>> +        subdict = d.pop(toshift)
>> +        d.update(subdict)
>> +        return d
>> +
>> +    xmlstr = node_dev.XMLDesc()
>> +    info = dictize(xmlstr)['device']
>> +    dev_type = info['capability'].pop('type')
>> +    info['device_type'] = dev_type
>> +    shift_subdict(info, 'capability')
>> +    info['parent'] = node_dev.parent()
>> +
>> +    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_detail_info = get_dev_type_info[dev_type]
>> +    except KeyError:
>> +        kimchi_log.error("Unknown device type: %s", dev_type)
>> +        return info
>> +
>> +    return get_detail_info(info)
>> +
>> +
>> +def _get_net_dev_info(info):
>> +    cap = info.pop('capability')
>> +    links = {"80203": "IEEE 802.3", "80211": "IEEE 802.11"}
>> +    link_raw = cap['type']
>> +    info['link_type'] = links.get(link_raw, link_raw)
>> +
>> +    return info
>> +
>> +
>> +def _get_pci_dev_info(info):
>> +    for k in ('vendor', 'product'):
>> +        info[k]['description'] = info[k].pop('pyval')
>> +    try:
>> +        info['iommuGroup'] = info['iommuGroup']['number']
>> +    except KeyError:
>> +        # No IOMMU group support.
>> +        pass
>> +    return info
>> +
>> +
>> +def _get_scsi_dev_info(info):
>> +    return info
>> +
>> +
>> +def _get_scsi_generic_dev_info(info):
>> +    # 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
>> +    return info
>> +
>> +
>> +def _get_scsi_host_dev_info(info):
>> +    try:
>> +        cap_info = info.pop('capability')
>> +    except KeyError:
>> +        # kimchi.model.libvirtstoragepool.ScsiPoolDef assumes
>> +        # info['adapter']['type'] always exists.
>> +        info['adapter'] = {'type': ''}
>> +        return info
>> +    info['adapter'] = cap_info
>> +    return info
>> +
>> +
>> +def _get_scsi_target_dev_info(info):
>> +    # scsi_target is not documented in libvirt official website. Try to
>> +    # parse scsi_target according to the libvirt commit db19834a0a.
>> +    return info
>> +
>> +
>> +def _get_storage_dev_info(info):
>> +    try:
>> +        cap_info = info.pop('capability')
>> +    except KeyError:
>> +        return info
>> +
>> +    if cap_info['type'] == 'removable':
>> +        cap_info['available'] = bool(cap_info.pop('media_available'))
>> +        if cap_info['available']:
>> +            cap_info.update({'size': cap_info.pop('media_size'),
>> +                             'label': cap_info.pop('media_label')})
>> +    info['media'] = cap_info
>> +    return info
>> +
>> +
>> +def _get_system_dev_info(info):
>> +    return info
>> +
>> +
>> +def _get_usb_dev_info(info):
>> +    return info
>> +
>> +
>> +def _get_usb_device_dev_info(info):
>> +    for k in ('vendor', 'product'):
>> +        try:
>> +            info[k]['description'] = info[k].pop('pyval')
>> +        except KeyError:
>> +            # Some USB devices don't provide vendor/product description.
>> +            pass
>> +    return info
>> +
>> +
>> +# For test and debug
>> +def _print_host_dev_tree():
>> +    dev_infos = _get_all_host_dev_infos()
>> +    root = _get_dev_info_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 05720f4..7420e7e 100644
>> --- a/src/kimchi/mockmodel.py
>> +++ b/src/kimchi/mockmodel.py
>> @@ -493,9 +493,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 5844f4b..1ea97f0 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
>> @@ -279,20 +280,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/src/kimchi/xmlutils.py b/src/kimchi/xmlutils.py
>> index 76f0696..56517f2 100644
>> --- a/src/kimchi/xmlutils.py
>> +++ b/src/kimchi/xmlutils.py
>> @@ -18,6 +18,7 @@
>>   # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
>> 02110-1301 USA
>>
>>   import libxml2
>> +from lxml import objectify
>>
>>
>>   from xml.etree import ElementTree
>> @@ -26,7 +27,7 @@ from xml.etree import ElementTree
>>   def xpath_get_text(xml, expr):
>>       doc = libxml2.parseDoc(xml)
>>       res = doc.xpathEval(expr)
>> -    ret = [None if x.children == None else x.children.content for x
>> in res]
>> +    ret = [None if x.children is None else x.children.content for x
>> in res]
>>
>>       doc.freeDoc()
>>       return ret
>> @@ -37,3 +38,26 @@ def xml_item_update(xml, xpath, value):
>>       item = root.find(xpath)
>>       item.text = value
>>       return ElementTree.tostring(root, encoding="utf-8")
>> +
>> +
>> +def dictize(xmlstr):
>> +    root = objectify.fromstring(xmlstr)
>> +    return {root.tag: _dictize(root)}
>> +
>> +
>> +def _dictize(e):
>> +    d = {}
>> +    if e.text is not None:
>> +        if not e.attrib and e.countchildren() == 0:
>> +            return e.pyval
>> +        d['pyval'] = e.pyval
>> +    d.update(e.attrib)
>> +    for child in e.iterchildren():
>> +        if child.tag in d:
>> +            continue
>> +        if len(child) > 1:
>> +            d[child.tag] = [
>> +                _dictize(same_tag_child) for same_tag_child in child]
>> +        else:
>> +            d[child.tag] = _dictize(child)
>> +    return d
>> diff --git a/tests/test_rest.py b/tests/test_rest.py
>> index 7ed94cb..cb1ae9a 100644
>> --- a/tests/test_rest.py
>> +++ b/tests/test_rest.py
>> @@ -158,9 +158,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'>
> 


-- 
Zhou Zheng Sheng / 周征晟
E-mail: zhshzhou at linux.vnet.ibm.com
Telephone: 86-10-82454397




More information about the Kimchi-devel mailing list