[Kimchi-devel] [PATCH 2/4] host pci pass through: list all types of host devices
Mark Wu
wudxw at linux.vnet.ibm.com
Thu Apr 24 08:49:24 UTC 2014
On 04/23/2014 05:48 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 -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
It can serve for general purpose. So do you think it's better move to
utils.
> +
> +
> +def get_all_host_devices(libvirt_conn):
Any reason to keep an argument for libvirt connection? I mean you can
get the connection inside this function.
> + 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')}}
the xml path of device info can be categorized into a few patterns, then
you can use helper functions
to get device info for all kinds of devices. It will make the code look
clean.
> +
> + 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')}}
should be release_date, not 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'>
More information about the Kimchi-devel
mailing list