[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