Change in vdsm[master]: virt: factor out _getUnderlying* methods to another class.

ibarkan at redhat.com ibarkan at redhat.com
Fri Mar 13 07:00:30 UTC 2015


Ido Barkan has uploaded a new change for review.

Change subject: virt: factor out _getUnderlying* methods to another class.
......................................................................

virt: factor out _getUnderlying* methods to another class.

This serves several purposes:
a. Prepare the ground for proper unit testing of those methods so that unit test
classes will have to create only a VmDomainParser instances with proper
data and call _getUnderlying* methods to later make assertions on this data.

b. Reduce the number of lines in vm.py in order to ease future rebases and
collaborative work.

c. Separate VM domain xml elements parsing task to a different file should
make the reading of vm.py less intimidating.

Future task that should follow this commit:
a. Add unit tests for this class.

b. Function names were left the same to make reviewing easier but some of
them, used directly from vm.py should be made public.

b. The technique in which getUnderlyingVmDevicesInfo changes the 'conf'
reference instead of returning data is cumbersome and hard to read. It
should return an updated copy of of the conf dictionary to the caller.
it will ease testing and debugging.

Change-Id: Ia04b6ee17da207f772f77059e45be97594e6be78
Signed-off-by: Ido Barkan <ibarkan at redhat.com>
---
M vdsm/virt/vm.py
A vdsm/virt/vm_doman_parser.py
2 files changed, 536 insertions(+), 497 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/04/38704/1

diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py
index bb2ab62..2133c20 100644
--- a/vdsm/virt/vm.py
+++ b/vdsm/virt/vm.py
@@ -67,6 +67,7 @@
 from .vmtune import update_io_tune_dom, collect_inner_elements
 from .vmtune import io_tune_values_to_dom, io_tune_dom_to_values
 from . import vmxml
+from .vm_doman_parser import VmDomainParser, _getDefaultDiskInterface
 
 from .sampling import AdvancedStatsFunction, AdvancedStatsThread
 from .utils import isVdsmImage
@@ -765,11 +766,6 @@
     def _makeChannelPath(self, deviceName):
         return constants.P_LIBVIRT_VMCHANNELS + self.id + '.' + deviceName
 
-    def _getDefaultDiskInterface(self):
-        DEFAULT_DISK_INTERFACES = {caps.Architecture.X86_64: 'ide',
-                                   caps.Architecture.PPC64: 'scsi'}
-        return DEFAULT_DISK_INTERFACES[self.arch]
-
     def __init__(self, cif, params, recover=False):
         """
         Initialize a new VM instance.
@@ -853,6 +849,8 @@
         self._shutdownLock = threading.Lock()
         self._shutdownReason = None
         self._vcpuLimit = None
+        self._domainParser = VmDomainParser(self._domain, self.conf,
+                                            self._devices, self.log)
 
     def _get_lastStatus(self):
         # note that we don't use _statusLock here. One of the reasons is the
@@ -927,7 +925,7 @@
         removables = [{
             'type': hwclass.DISK,
             'device': 'cdrom',
-            'iface': self._getDefaultDiskInterface(),
+            'iface': self._getDefaultDiskInterface(self.arch),
             'path': self.conf.get('cdrom', ''),
             'index': 2,
             'truesize': 0}]
@@ -1137,7 +1135,7 @@
             # FIXME: For BC we have now two identical keys: iface = if
             # Till the day that conf will not returned as a status anymore.
             drv['iface'] = drv.get('iface') or \
-                drv.get('if', self._getDefaultDiskInterface())
+                drv.get('if', self._getDefaultDiskInterface(aelf.arch))
 
         return confDrives
 
@@ -2054,24 +2052,6 @@
         else:
             return status[0] == libvirt.VIR_DOMAIN_RUNNING
 
-    def _getUnderlyingVmDevicesInfo(self):
-        """
-        Obtain underlying vm's devices info from libvirt.
-        """
-        self._getUnderlyingNetworkInterfaceInfo()
-        self._getUnderlyingDriveInfo()
-        self._getUnderlyingSoundDeviceInfo()
-        self._getUnderlyingVideoDeviceInfo()
-        self._getUnderlyingGraphicsDeviceInfo()
-        self._getUnderlyingControllerDeviceInfo()
-        self._getUnderlyingBalloonDeviceInfo()
-        self._getUnderlyingWatchdogDeviceInfo()
-        self._getUnderlyingSmartcardDeviceInfo()
-        self._getUnderlyingConsoleDeviceInfo()
-        self._getUnderlyingHostDeviceInfo()
-        # Obtain info of all unknown devices. Must be last!
-        self._getUnderlyingUnknownDeviceInfo()
-
     def _updateAgentChannels(self):
         """
         We moved the naming of guest agent channel sockets. To keep backwards
@@ -2118,7 +2098,7 @@
             raise MissingLibvirtDomainError(vmexitreason.LIBVIRT_START_FAILED)
 
         self._updateDomainDescriptor()
-        self._getUnderlyingVmDevicesInfo()
+        self._domainParser._getUnderlyingVmDevicesInfo()
         self._updateAgentChannels()
 
         # Currently there is no protection agains mirroring a network twice,
@@ -2354,7 +2334,7 @@
             with self._confLock:
                 self.conf['devices'].append(nicParams)
             self.saveState()
-            self._getUnderlyingNetworkInterfaceInfo()
+            self._domainParser._getUnderlyingNetworkInterfaceInfo()
             hooks.after_nic_hotplug(nicXml, self.conf,
                                     params=nic.custom)
 
@@ -2929,7 +2909,7 @@
             with self._confLock:
                 self.conf['devices'].append(diskParams)
             self.saveState()
-            self._getUnderlyingDriveInfo()
+            self._domainParser._getUnderlyingDriveInfo()
             hooks.after_disk_hotplug(driveXml, self.conf,
                                      params=drive.custom)
 
@@ -4057,475 +4037,6 @@
         """
         self.log.exception("Operation failed")
         return self._reportError(key, msg)
-
-    def _getUnderlyingDeviceAddress(self, devXml, index=0):
-        """
-        Obtain device's address from libvirt
-        """
-        address = {}
-        adrXml = devXml.getElementsByTagName('address')[index]
-        # Parse address to create proper dictionary.
-        # Libvirt device's address definition is:
-        # PCI = {'type':'pci', 'domain':'0x0000', 'bus':'0x00',
-        #        'slot':'0x0c', 'function':'0x0'}
-        # IDE = {'type':'drive', 'controller':'0', 'bus':'0', 'unit':'0'}
-        for key in adrXml.attributes.keys():
-            address[key.strip()] = adrXml.getAttribute(key).strip()
-
-        return address
-
-    def _getUnderlyingUnknownDeviceInfo(self):
-        """
-        Obtain unknown devices info from libvirt.
-
-        Unknown device is a device that has an address but wasn't
-        passed during VM creation request.
-        """
-        def isKnownDevice(alias):
-            for dev in self.conf['devices']:
-                if dev.get('alias') == alias:
-                    return True
-            return False
-
-        for x in self._domain.devices.childNodes:
-            # Ignore empty nodes and devices without address
-            if (x.nodeName == '#text' or
-                    not x.getElementsByTagName('address')):
-                continue
-
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            if not isKnownDevice(alias):
-                address = self._getUnderlyingDeviceAddress(x)
-                # I general case we assume that device has attribute 'type',
-                # if it hasn't getAttribute returns ''.
-                device = x.getAttribute('type')
-                newDev = {'type': x.nodeName,
-                          'alias': alias,
-                          'device': device,
-                          'address': address}
-                self.conf['devices'].append(newDev)
-
-    def _getUnderlyingControllerDeviceInfo(self):
-        """
-        Obtain controller devices info from libvirt.
-        """
-        for x in self._domain.get_device_elements('controller'):
-            # Ignore controller devices without address
-            if not x.getElementsByTagName('address'):
-                continue
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            device = x.getAttribute('type')
-            # Get model and index. Relevant for USB controllers.
-            model = x.getAttribute('model')
-            index = x.getAttribute('index')
-
-            # Get controller address
-            address = self._getUnderlyingDeviceAddress(x)
-
-            # In case the controller has index and/or model, they
-            # are compared. Currently relevant for USB controllers.
-            for ctrl in self._devices[hwclass.CONTROLLER]:
-                if ((ctrl.device == device) and
-                        (not hasattr(ctrl, 'index') or ctrl.index == index) and
-                        (not hasattr(ctrl, 'model') or ctrl.model == model)):
-                    ctrl.alias = alias
-                    ctrl.address = address
-            # Update vm's conf with address for known controller devices
-            # In case the controller has index and/or model, they
-            # are compared. Currently relevant for USB controllers.
-            knownDev = False
-            for dev in self.conf['devices']:
-                if ((dev['type'] == hwclass.CONTROLLER) and
-                        (dev['device'] == device) and
-                        ('index' not in dev or dev['index'] == index) and
-                        ('model' not in dev or dev['model'] == model)):
-                    dev['address'] = address
-                    dev['alias'] = alias
-                    knownDev = True
-            # Add unknown controller device to vm's conf
-            if not knownDev:
-                self.conf['devices'].append(
-                    {'type': hwclass.CONTROLLER,
-                     'device': device,
-                     'address': address,
-                     'alias': alias})
-
-    def _getUnderlyingBalloonDeviceInfo(self):
-        """
-        Obtain balloon device info from libvirt.
-        """
-        for x in self._domain.get_device_elements('memballoon'):
-            # Ignore balloon devices without address.
-            if not x.getElementsByTagName('address'):
-                address = None
-            else:
-                address = self._getUnderlyingDeviceAddress(x)
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-
-            for dev in self._devices[hwclass.BALLOON]:
-                if address and not hasattr(dev, 'address'):
-                    dev.address = address
-                if not hasattr(dev, 'alias'):
-                    dev.alias = alias
-
-            for dev in self.conf['devices']:
-                if dev['type'] == hwclass.BALLOON:
-                    if address and not dev.get('address'):
-                        dev['address'] = address
-                    if not dev.get('alias'):
-                        dev['alias'] = alias
-
-    def _getUnderlyingConsoleDeviceInfo(self):
-        """
-        Obtain the alias for the console device from libvirt
-        """
-        for x in self._domain.get_device_elements('console'):
-            # All we care about is the alias
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            for dev in self._devices[hwclass.CONSOLE]:
-                if not hasattr(dev, 'alias'):
-                    dev.alias = alias
-
-            for dev in self.conf['devices']:
-                if dev['device'] == hwclass.CONSOLE and \
-                        not dev.get('alias'):
-                    dev['alias'] = alias
-
-    def _getUnderlyingSmartcardDeviceInfo(self):
-        """
-        Obtain smartcard device info from libvirt.
-        """
-        for x in self._domain.get_device_elements('smartcard'):
-            if not x.getElementsByTagName('address'):
-                continue
-
-            address = self._getUnderlyingDeviceAddress(x)
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-
-            for dev in self._devices[hwclass.SMARTCARD]:
-                if not hasattr(dev, 'address'):
-                    dev.address = address
-                    dev.alias = alias
-
-            for dev in self.conf['devices']:
-                if dev['device'] == hwclass.SMARTCARD and \
-                        not dev.get('address'):
-                    dev['address'] = address
-                    dev['alias'] = alias
-
-    def _getUnderlyingHostDeviceInfo(self):
-        """
-        Obtain host device info from libvirt
-        """
-        for x in self._domain.get_device_elements('hostdev'):
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            address = self._getUnderlyingDeviceAddress(x)
-            source = x.getElementsByTagName('source')[0]
-            device = hostdev.pci_address_to_name(
-                **self._getUnderlyingDeviceAddress(source))
-
-            # We can assume the device name to be correct since we're
-            # inspecting source element. For the address, we may look at
-            # both addresses and determine the correct one.
-            if (hostdev.pci_address_to_name(**address) == device):
-                address = self._getUnderlyingDeviceAddress(x, 1)
-
-            known_device = False
-            for dev in self.conf['devices']:
-                if dev['device'] == device:
-                    dev['alias'] = alias
-                    dev['address'] = address
-
-            for dev in self._devices[hwclass.HOSTDEV]:
-                if dev.device == device:
-                    dev.alias = alias
-                    dev.address = address
-                    known_device = True
-
-            if not known_device:
-                device = hostdev.pci_address_to_name(
-                    **self._getUnderlyingDeviceAddress(source))
-
-                hostdevice = {'type': hwclass.HOSTDEV,
-                              'device': device,
-                              'alias': alias,
-                              'address': address}
-                self.conf['devices'].append(hostdevice)
-
-    def _getUnderlyingWatchdogDeviceInfo(self):
-        """
-        Obtain watchdog device info from libvirt.
-        """
-        for x in self._domain.get_device_elements('watchdog'):
-
-            # PCI watchdog has "address" different from ISA watchdog
-            if x.getElementsByTagName('address'):
-                address = self._getUnderlyingDeviceAddress(x)
-                alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-
-                for wd in self._devices[hwclass.WATCHDOG]:
-                    if not hasattr(wd, 'address') or not hasattr(wd, 'alias'):
-                        wd.address = address
-                        wd.alias = alias
-
-                for dev in self.conf['devices']:
-                    if ((dev['type'] == hwclass.WATCHDOG) and
-                            (not dev.get('address') or not dev.get('alias'))):
-                        dev['address'] = address
-                        dev['alias'] = alias
-
-    def _getUnderlyingVideoDeviceInfo(self):
-        """
-        Obtain video devices info from libvirt.
-        """
-        for x in self._domain.get_device_elements('video'):
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            # Get video card address
-            address = self._getUnderlyingDeviceAddress(x)
-
-            # FIXME. We have an identification problem here.
-            # Video card device has not unique identifier, except the alias
-            # (but backend not aware to device's aliases). So, for now
-            # we can only assign the address according to devices order.
-            for vc in self._devices[hwclass.VIDEO]:
-                if not hasattr(vc, 'address') or not hasattr(vc, 'alias'):
-                    vc.alias = alias
-                    vc.address = address
-                    break
-            # Update vm's conf with address
-            for dev in self.conf['devices']:
-                if ((dev['type'] == hwclass.VIDEO) and
-                        (not dev.get('address') or not dev.get('alias'))):
-                    dev['address'] = address
-                    dev['alias'] = alias
-                    break
-
-    def _getUnderlyingSoundDeviceInfo(self):
-        """
-        Obtain sound devices info from libvirt.
-        """
-        for x in self._domain.get_device_elements('sound'):
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            # Get sound card address
-            address = self._getUnderlyingDeviceAddress(x)
-
-            # FIXME. We have an identification problem here.
-            # Sound device has not unique identifier, except the alias
-            # (but backend not aware to device's aliases). So, for now
-            # we can only assign the address according to devices order.
-            for sc in self._devices[hwclass.SOUND]:
-                if not hasattr(sc, 'address') or not hasattr(sc, 'alias'):
-                    sc.alias = alias
-                    sc.address = address
-                    break
-            # Update vm's conf with address
-            for dev in self.conf['devices']:
-                if ((dev['type'] == hwclass.SOUND) and
-                        (not dev.get('address') or not dev.get('alias'))):
-                    dev['address'] = address
-                    dev['alias'] = alias
-                    break
-
-    def _getDriveIdentification(self, dom):
-        sources = dom.getElementsByTagName('source')
-        if sources:
-            devPath = (sources[0].getAttribute('file') or
-                       sources[0].getAttribute('dev') or
-                       sources[0].getAttribute('name'))
-        else:
-            devPath = ''
-        target = dom.getElementsByTagName('target')
-        name = target[0].getAttribute('dev') if target else ''
-        alias = dom.getElementsByTagName('alias')[0].getAttribute('name')
-        return alias, devPath, name
-
-    def _getUnderlyingDriveInfo(self):
-        """
-        Obtain block devices info from libvirt.
-        """
-        # FIXME!  We need to gather as much info as possible from the libvirt.
-        # In the future we can return this real data to management instead of
-        # vm's conf
-        for x in self._domain.get_device_elements('disk'):
-            alias, devPath, name = self._getDriveIdentification(x)
-            readonly = bool(x.getElementsByTagName('readonly'))
-            boot = x.getElementsByTagName('boot')
-            bootOrder = boot[0].getAttribute('order') if boot else ''
-
-            devType = x.getAttribute('device')
-            if devType == 'disk':
-                # raw/qcow2
-                drv = x.getElementsByTagName('driver')[0].getAttribute('type')
-            else:
-                drv = 'raw'
-            # Get disk address
-            address = self._getUnderlyingDeviceAddress(x)
-
-            # Keep data as dict for easier debugging
-            deviceDict = {'path': devPath, 'name': name,
-                          'readonly': readonly, 'bootOrder': bootOrder,
-                          'address': address, 'type': devType, 'boot': boot}
-
-            # display indexed pairs of ordered values from 2 dicts
-            # such as {key_1: (valueA_1, valueB_1), ...}
-            def mergeDicts(deviceDef, dev):
-                return dict((k, (deviceDef[k], getattr(dev, k, None)))
-                            for k in deviceDef.iterkeys())
-
-            self.log.debug('Looking for drive with attributes %s', deviceDict)
-            for d in self._devices[hwclass.DISK]:
-                # When we analyze a disk device that was already discovered in
-                # the past (generally as soon as the VM is created) we should
-                # verify that the cached path is the one used in libvirt.
-                # We already hit few times the problem that after a live
-                # migration the paths were not in sync anymore (BZ#1059482).
-                if (hasattr(d, 'alias') and d.alias == alias
-                        and d.path != devPath):
-                    self.log.warning('updating drive %s path from %s to %s',
-                                     d.alias, d.path, devPath)
-                    d.path = devPath
-                if d.path == devPath:
-                    d.name = name
-                    d.type = devType
-                    d.drv = drv
-                    d.alias = alias
-                    d.address = address
-                    d.readonly = readonly
-                    if bootOrder:
-                        d.bootOrder = bootOrder
-                    self.log.debug('Matched %s', mergeDicts(deviceDict, d))
-            # Update vm's conf with address for known disk devices
-            knownDev = False
-            for dev in self.conf['devices']:
-                # See comment in previous loop. This part is used to update
-                # the vm configuration as well.
-                if ('alias' in dev and dev['alias'] == alias
-                        and dev['path'] != devPath):
-                    self.log.warning('updating drive %s config path from %s '
-                                     'to %s', dev['alias'], dev['path'],
-                                     devPath)
-                    dev['path'] = devPath
-                if (dev['type'] == hwclass.DISK and
-                        dev['path'] == devPath):
-                    dev['name'] = name
-                    dev['address'] = address
-                    dev['alias'] = alias
-                    dev['readonly'] = str(readonly)
-                    if bootOrder:
-                        dev['bootOrder'] = bootOrder
-                    self.log.debug('Matched %s', mergeDicts(deviceDict, dev))
-                    knownDev = True
-            # Add unknown disk device to vm's conf
-            if not knownDev:
-                archIface = self._getDefaultDiskInterface()
-                iface = archIface if address['type'] == 'drive' else 'pci'
-                diskDev = {'type': hwclass.DISK, 'device': devType,
-                           'iface': iface, 'path': devPath, 'name': name,
-                           'address': address, 'alias': alias,
-                           'readonly': str(readonly)}
-                if bootOrder:
-                    diskDev['bootOrder'] = bootOrder
-                self.log.warn('Found unknown drive: %s', diskDev)
-                self.conf['devices'].append(diskDev)
-
-    def _getUnderlyingGraphicsDeviceInfo(self):
-        """
-        Obtain graphic framebuffer devices info from libvirt.
-        Libvirt only allows at most one device of each type, so mapping between
-        _devices and conf.devices is based on this fact.
-        The libvirt docs are a bit sparse on this topic, but as of libvirt
-        1.2.3 if one tries to create a domain with 2+ SPICE graphics devices,
-        virsh responds: error: unsupported configuration: only 1 graphics
-        device of each type (sdl, vnc, spice) is supported
-        """
-
-        for gxml in self._domain.get_device_elements('graphics'):
-            port = gxml.getAttribute('port')
-            tlsPort = gxml.getAttribute('tlsPort')
-            graphicsType = gxml.getAttribute('type')
-
-            for d in self._devices[hwclass.GRAPHICS]:
-                if d.device == graphicsType:
-                    if port:
-                        d.port = port
-                    if tlsPort:
-                        d.tlsPort = tlsPort
-                    break
-
-            for dev in self.conf['devices']:
-                if (dev.get('type') == hwclass.GRAPHICS and
-                        dev.get('device') == graphicsType):
-                    if port:
-                        dev['port'] = port
-                    if tlsPort:
-                        dev['tlsPort'] = tlsPort
-                    break
-
-        # the first graphic device is duplicated in the legacy conf
-        vmdevices.graphics.updateLegacyConf(self.conf)
-
-    def _getUnderlyingNetworkInterfaceInfo(self):
-        """
-        Obtain network interface info from libvirt.
-        """
-        for x in self._domain.get_device_elements('interface'):
-            devType = x.getAttribute('type')
-            mac = x.getElementsByTagName('mac')[0].getAttribute('address')
-            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
-            if devType == 'hostdev':
-                name = alias
-                model = 'passthrough'
-            else:
-                name = x.getElementsByTagName('target')[0].getAttribute('dev')
-                model = x.getElementsByTagName('model')[0].getAttribute('type')
-
-            network = None
-            try:
-                if x.getElementsByTagName('link')[0].getAttribute('state') == \
-                        'down':
-                    linkActive = False
-                else:
-                    linkActive = True
-            except IndexError:
-                linkActive = True
-            source = x.getElementsByTagName('source')
-            if source:
-                network = source[0].getAttribute('bridge')
-                if not network:
-                    network = source[0].getAttribute('network')
-                    network = network[len(netinfo.LIBVIRT_NET_PREFIX):]
-
-            # Get nic address
-            address = self._getUnderlyingDeviceAddress(x)
-            for nic in self._devices[hwclass.NIC]:
-                if nic.macAddr.lower() == mac.lower():
-                    nic.name = name
-                    nic.alias = alias
-                    nic.address = address
-                    nic.linkActive = linkActive
-            # Update vm's conf with address for known nic devices
-            knownDev = False
-            for dev in self.conf['devices']:
-                if (dev['type'] == hwclass.NIC and
-                        dev['macAddr'].lower() == mac.lower()):
-                    dev['address'] = address
-                    dev['alias'] = alias
-                    dev['name'] = name
-                    dev['linkActive'] = linkActive
-                    knownDev = True
-            # Add unknown nic device to vm's conf
-            if not knownDev:
-                nicDev = {'type': hwclass.NIC,
-                          'device': devType,
-                          'macAddr': mac,
-                          'nicModel': model,
-                          'address': address,
-                          'alias': alias,
-                          'name': name,
-                          'linkActive': linkActive}
-                if network:
-                    nicDev['network'] = network
-                self.conf['devices'].append(nicDev)
 
     def _setWriteWatermarks(self):
         """
diff --git a/vdsm/virt/vm_doman_parser.py b/vdsm/virt/vm_doman_parser.py
new file mode 100644
index 0000000..98da179
--- /dev/null
+++ b/vdsm/virt/vm_doman_parser.py
@@ -0,0 +1,528 @@
+#
+# Copyright 2008-2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+from vdsm import netinfo
+
+import hostdev
+import caps
+
+from . import vmdevices
+from .vmdevices import hwclass
+
+
+class VmDomainParser(object):
+    def __init__(self, domain, conf, devices, log):
+        self._domain = domain
+        self.conf = conf
+        self._devices = devices
+        self.log = log
+
+    def _getUnderlyingVmDevicesInfo(self):
+        """
+        Obtain underlying vm's devices info from libvirt.
+        """
+        self._getUnderlyingNetworkInterfaceInfo()
+        self._getUnderlyingDriveInfo()
+        self._getUnderlyingSoundDeviceInfo()
+        self._getUnderlyingVideoDeviceInfo()
+        self._getUnderlyingGraphicsDeviceInfo()
+        self._getUnderlyingControllerDeviceInfo()
+        self._getUnderlyingBalloonDeviceInfo()
+        self._getUnderlyingWatchdogDeviceInfo()
+        self._getUnderlyingSmartcardDeviceInfo()
+        self._getUnderlyingConsoleDeviceInfo()
+        self._getUnderlyingHostDeviceInfo()
+        # Obtain info of all unknown devices. Must be last!
+        self._getUnderlyingUnknownDeviceInfo()
+
+    def _getUnderlyingNetworkInterfaceInfo(self):
+        """
+        Obtain network interface info from libvirt.
+        """
+        for x in self._domain.get_device_elements('interface'):
+            devType = x.getAttribute('type')
+            mac = x.getElementsByTagName('mac')[0].getAttribute('address')
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            if devType == 'hostdev':
+                name = alias
+                model = 'passthrough'
+            else:
+                name = x.getElementsByTagName('target')[0].getAttribute('dev')
+                model = x.getElementsByTagName('model')[0].getAttribute('type')
+
+            network = None
+            try:
+                if x.getElementsByTagName('link')[0].getAttribute('state') == \
+                        'down':
+                    linkActive = False
+                else:
+                    linkActive = True
+            except IndexError:
+                linkActive = True
+            source = x.getElementsByTagName('source')
+            if source:
+                network = source[0].getAttribute('bridge')
+                if not network:
+                    network = source[0].getAttribute('network')
+                    network = network[len(netinfo.LIBVIRT_NET_PREFIX):]
+
+            # Get nic address
+            address = self._getUnderlyingDeviceAddress(x)
+            for nic in self._devices[hwclass.NIC]:
+                if nic.macAddr.lower() == mac.lower():
+                    nic.name = name
+                    nic.alias = alias
+                    nic.address = address
+                    nic.linkActive = linkActive
+            # Update vm's conf with address for known nic devices
+            knownDev = False
+            for dev in self.conf['devices']:
+                if (dev['type'] == hwclass.NIC and
+                        dev['macAddr'].lower() == mac.lower()):
+                    dev['address'] = address
+                    dev['alias'] = alias
+                    dev['name'] = name
+                    dev['linkActive'] = linkActive
+                    knownDev = True
+            # Add unknown nic device to vm's conf
+            if not knownDev:
+                nicDev = {'type': hwclass.NIC,
+                          'device': devType,
+                          'macAddr': mac,
+                          'nicModel': model,
+                          'address': address,
+                          'alias': alias,
+                          'name': name,
+                          'linkActive': linkActive}
+                if network:
+                    nicDev['network'] = network
+                self.conf['devices'].append(nicDev)
+
+    def _getUnderlyingDriveInfo(self):
+        """
+        Obtain block devices info from libvirt.
+        """
+        # FIXME!  We need to gather as much info as possible from the libvirt.
+        # In the future we can return this real data to management instead of
+        # vm's conf
+        for x in self._domain.get_device_elements('disk'):
+            alias, devPath, name = self._getDriveIdentification(x)
+            readonly = bool(x.getElementsByTagName('readonly'))
+            boot = x.getElementsByTagName('boot')
+            bootOrder = boot[0].getAttribute('order') if boot else ''
+
+            devType = x.getAttribute('device')
+            if devType == 'disk':
+                # raw/qcow2
+                drv = x.getElementsByTagName('driver')[0].getAttribute('type')
+            else:
+                drv = 'raw'
+            # Get disk address
+            address = self._getUnderlyingDeviceAddress(x)
+
+            # Keep data as dict for easier debugging
+            deviceDict = {'path': devPath, 'name': name,
+                          'readonly': readonly, 'bootOrder': bootOrder,
+                          'address': address, 'type': devType, 'boot': boot}
+
+            # display indexed pairs of ordered values from 2 dicts
+            # such as {key_1: (valueA_1, valueB_1), ...}
+            def mergeDicts(deviceDef, dev):
+                return dict((k, (deviceDef[k], getattr(dev, k, None)))
+                            for k in deviceDef.iterkeys())
+
+            self.log.debug('Looking for drive with attributes %s', deviceDict)
+            for d in self._devices[hwclass.DISK]:
+                # When we analyze a disk device that was already discovered in
+                # the past (generally as soon as the VM is created) we should
+                # verify that the cached path is the one used in libvirt.
+                # We already hit few times the problem that after a live
+                # migration the paths were not in sync anymore (BZ#1059482).
+                if (hasattr(d, 'alias') and d.alias == alias
+                        and d.path != devPath):
+                    self.log.warning('updating drive %s path from %s to %s',
+                                     d.alias, d.path, devPath)
+                    d.path = devPath
+                if d.path == devPath:
+                    d.name = name
+                    d.type = devType
+                    d.drv = drv
+                    d.alias = alias
+                    d.address = address
+                    d.readonly = readonly
+                    if bootOrder:
+                        d.bootOrder = bootOrder
+                    self.log.debug('Matched %s', mergeDicts(deviceDict, d))
+            # Update vm's conf with address for known disk devices
+            knownDev = False
+            for dev in self.conf['devices']:
+                # See comment in previous loop. This part is used to update
+                # the vm configuration as well.
+                if ('alias' in dev and dev['alias'] == alias
+                        and dev['path'] != devPath):
+                    self.log.warning('updating drive %s config path from %s '
+                                     'to %s', dev['alias'], dev['path'],
+                                     devPath)
+                    dev['path'] = devPath
+                if (dev['type'] == hwclass.DISK and
+                        dev['path'] == devPath):
+                    dev['name'] = name
+                    dev['address'] = address
+                    dev['alias'] = alias
+                    dev['readonly'] = str(readonly)
+                    if bootOrder:
+                        dev['bootOrder'] = bootOrder
+                    self.log.debug('Matched %s', mergeDicts(deviceDict, dev))
+                    knownDev = True
+            # Add unknown disk device to vm's conf
+            if not knownDev:
+                archIface = self._getDefaultDiskInterface()
+                iface = archIface if address['type'] == 'drive' else 'pci'
+                diskDev = {'type': hwclass.DISK, 'device': devType,
+                           'iface': iface, 'path': devPath, 'name': name,
+                           'address': address, 'alias': alias,
+                           'readonly': str(readonly)}
+                if bootOrder:
+                    diskDev['bootOrder'] = bootOrder
+                self.log.warn('Found unknown drive: %s', diskDev)
+                self.conf['devices'].append(diskDev)
+
+    def _getUnderlyingSoundDeviceInfo(self):
+        """
+        Obtain sound devices info from libvirt.
+        """
+        for x in self._domain.get_device_elements('sound'):
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            # Get sound card address
+            address = self._getUnderlyingDeviceAddress(x)
+
+            # FIXME. We have an identification problem here.
+            # Sound device has not unique identifier, except the alias
+            # (but backend not aware to device's aliases). So, for now
+            # we can only assign the address according to devices order.
+            for sc in self._devices[hwclass.SOUND]:
+                if not hasattr(sc, 'address') or not hasattr(sc, 'alias'):
+                    sc.alias = alias
+                    sc.address = address
+                    break
+            # Update vm's conf with address
+            for dev in self.conf['devices']:
+                if ((dev['type'] == hwclass.SOUND) and
+                        (not dev.get('address') or not dev.get('alias'))):
+                    dev['address'] = address
+                    dev['alias'] = alias
+                    break
+
+    def _getUnderlyingVideoDeviceInfo(self):
+        """
+        Obtain video devices info from libvirt.
+        """
+        for x in self._domain.get_device_elements('video'):
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            # Get video card address
+            address = self._getUnderlyingDeviceAddress(x)
+
+            # FIXME. We have an identification problem here.
+            # Video card device has not unique identifier, except the alias
+            # (but backend not aware to device's aliases). So, for now
+            # we can only assign the address according to devices order.
+            for vc in self._devices[hwclass.VIDEO]:
+                if not hasattr(vc, 'address') or not hasattr(vc, 'alias'):
+                    vc.alias = alias
+                    vc.address = address
+                    break
+            # Update vm's conf with address
+            for dev in self.conf['devices']:
+                if ((dev['type'] == hwclass.VIDEO) and
+                        (not dev.get('address') or not dev.get('alias'))):
+                    dev['address'] = address
+                    dev['alias'] = alias
+                    break
+
+    def _getUnderlyingGraphicsDeviceInfo(self):
+        """
+        Obtain graphic framebuffer devices info from libvirt.
+        Libvirt only allows at most one device of each type, so mapping between
+        _devices and conf.devices is based on this fact.
+        The libvirt docs are a bit sparse on this topic, but as of libvirt
+        1.2.3 if one tries to create a domain with 2+ SPICE graphics devices,
+        virsh responds: error: unsupported configuration: only 1 graphics
+        device of each type (sdl, vnc, spice) is supported
+        """
+
+        for gxml in self._domain.get_device_elements('graphics'):
+            port = gxml.getAttribute('port')
+            tlsPort = gxml.getAttribute('tlsPort')
+            graphicsType = gxml.getAttribute('type')
+
+            for d in self._devices[hwclass.GRAPHICS]:
+                if d.device == graphicsType:
+                    if port:
+                        d.port = port
+                    if tlsPort:
+                        d.tlsPort = tlsPort
+                    break
+
+            for dev in self.conf['devices']:
+                if (dev.get('type') == hwclass.GRAPHICS and
+                        dev.get('device') == graphicsType):
+                    if port:
+                        dev['port'] = port
+                    if tlsPort:
+                        dev['tlsPort'] = tlsPort
+                    break
+
+        # the first graphic device is duplicated in the legacy conf
+        vmdevices.graphics.updateLegacyConf(self.conf)
+
+    def _getUnderlyingControllerDeviceInfo(self):
+        """
+        Obtain controller devices info from libvirt.
+        """
+        for x in self._domain.get_device_elements('controller'):
+            # Ignore controller devices without address
+            if not x.getElementsByTagName('address'):
+                continue
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            device = x.getAttribute('type')
+            # Get model and index. Relevant for USB controllers.
+            model = x.getAttribute('model')
+            index = x.getAttribute('index')
+
+            # Get controller address
+            address = self._getUnderlyingDeviceAddress(x)
+
+            # In case the controller has index and/or model, they
+            # are compared. Currently relevant for USB controllers.
+            for ctrl in self._devices[hwclass.CONTROLLER]:
+                if ((ctrl.device == device) and
+                        (not hasattr(ctrl, 'index') or ctrl.index == index) and
+                        (not hasattr(ctrl, 'model') or ctrl.model == model)):
+                    ctrl.alias = alias
+                    ctrl.address = address
+            # Update vm's conf with address for known controller devices
+            # In case the controller has index and/or model, they
+            # are compared. Currently relevant for USB controllers.
+            knownDev = False
+            for dev in self.conf['devices']:
+                if ((dev['type'] == hwclass.CONTROLLER) and
+                        (dev['device'] == device) and
+                        ('index' not in dev or dev['index'] == index) and
+                        ('model' not in dev or dev['model'] == model)):
+                    dev['address'] = address
+                    dev['alias'] = alias
+                    knownDev = True
+            # Add unknown controller device to vm's conf
+            if not knownDev:
+                self.conf['devices'].append(
+                    {'type': hwclass.CONTROLLER,
+                     'device': device,
+                     'address': address,
+                     'alias': alias})
+
+    def _getUnderlyingBalloonDeviceInfo(self):
+        """
+        Obtain balloon device info from libvirt.
+        """
+        for x in self._domain.get_device_elements('memballoon'):
+            # Ignore balloon devices without address.
+            if not x.getElementsByTagName('address'):
+                address = None
+            else:
+                address = self._getUnderlyingDeviceAddress(x)
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+
+            for dev in self._devices[hwclass.BALLOON]:
+                if address and not hasattr(dev, 'address'):
+                    dev.address = address
+                if not hasattr(dev, 'alias'):
+                    dev.alias = alias
+
+            for dev in self.conf['devices']:
+                if dev['type'] == hwclass.BALLOON:
+                    if address and not dev.get('address'):
+                        dev['address'] = address
+                    if not dev.get('alias'):
+                        dev['alias'] = alias
+
+    def _getUnderlyingWatchdogDeviceInfo(self):
+        """
+        Obtain watchdog device info from libvirt.
+        """
+        for x in self._domain.get_device_elements('watchdog'):
+
+            # PCI watchdog has "address" different from ISA watchdog
+            if x.getElementsByTagName('address'):
+                address = self._getUnderlyingDeviceAddress(x)
+                alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+
+                for wd in self._devices[hwclass.WATCHDOG]:
+                    if not hasattr(wd, 'address') or not hasattr(wd, 'alias'):
+                        wd.address = address
+                        wd.alias = alias
+
+                for dev in self.conf['devices']:
+                    if ((dev['type'] == hwclass.WATCHDOG) and
+                            (not dev.get('address') or not dev.get('alias'))):
+                        dev['address'] = address
+                        dev['alias'] = alias
+
+    def _getUnderlyingSmartcardDeviceInfo(self):
+        """
+        Obtain smartcard device info from libvirt.
+        """
+        for x in self._domain.get_device_elements('smartcard'):
+            if not x.getElementsByTagName('address'):
+                continue
+
+            address = self._getUnderlyingDeviceAddress(x)
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+
+            for dev in self._devices[hwclass.SMARTCARD]:
+                if not hasattr(dev, 'address'):
+                    dev.address = address
+                    dev.alias = alias
+
+            for dev in self.conf['devices']:
+                if dev['device'] == hwclass.SMARTCARD and \
+                        not dev.get('address'):
+                    dev['address'] = address
+                    dev['alias'] = alias
+
+    def _getUnderlyingConsoleDeviceInfo(self):
+        """
+        Obtain the alias for the console device from libvirt
+        """
+        for x in self._domain.get_device_elements('console'):
+            # All we care about is the alias
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            for dev in self._devices[hwclass.CONSOLE]:
+                if not hasattr(dev, 'alias'):
+                    dev.alias = alias
+
+            for dev in self.conf['devices']:
+                if dev['device'] == hwclass.CONSOLE and \
+                        not dev.get('alias'):
+                    dev['alias'] = alias
+
+    def _getUnderlyingHostDeviceInfo(self):
+        """
+        Obtain host device info from libvirt
+        """
+        for x in self._domain.get_device_elements('hostdev'):
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            address = self._getUnderlyingDeviceAddress(x)
+            source = x.getElementsByTagName('source')[0]
+            device = hostdev.pci_address_to_name(
+                **self._getUnderlyingDeviceAddress(source))
+
+            # We can assume the device name to be correct since we're
+            # inspecting source element. For the address, we may look at
+            # both addresses and determine the correct one.
+            if hostdev.pci_address_to_name(**address) == device:
+                address = self._getUnderlyingDeviceAddress(x, 1)
+
+            known_device = False
+            for dev in self.conf['devices']:
+                if dev['device'] == device:
+                    dev['alias'] = alias
+                    dev['address'] = address
+
+            for dev in self._devices[hwclass.HOSTDEV]:
+                if dev.device == device:
+                    dev.alias = alias
+                    dev.address = address
+                    known_device = True
+
+            if not known_device:
+                device = hostdev.pci_address_to_name(
+                    **self._getUnderlyingDeviceAddress(source))
+
+                hostdevice = {'type': hwclass.HOSTDEV,
+                              'device': device,
+                              'alias': alias,
+                              'address': address}
+                self.conf['devices'].append(hostdevice)
+
+    def _getUnderlyingUnknownDeviceInfo(self):
+        """
+        Obtain unknown devices info from libvirt.
+
+        Unknown device is a device that has an address but wasn't
+        passed during VM creation request.
+        """
+        def isKnownDevice(alias):
+            for dev in self.conf['devices']:
+                if dev.get('alias') == alias:
+                    return True
+            return False
+
+        for x in self._domain.devices.childNodes:
+            # Ignore empty nodes and devices without address
+            if (x.nodeName == '#text' or
+                    not x.getElementsByTagName('address')):
+                continue
+
+            alias = x.getElementsByTagName('alias')[0].getAttribute('name')
+            if not isKnownDevice(alias):
+                address = self._getUnderlyingDeviceAddress(x)
+                # I general case we assume that device has attribute 'type',
+                # if it hasn't getAttribute returns ''.
+                device = x.getAttribute('type')
+                newDev = {'type': x.nodeName,
+                          'alias': alias,
+                          'device': device,
+                          'address': address}
+                self.conf['devices'].append(newDev)
+
+    def _getUnderlyingDeviceAddress(self, devXml, index=0):
+        """
+        Obtain device's address from libvirt
+        """
+        address = {}
+        adrXml = devXml.getElementsByTagName('address')[index]
+        # Parse address to create proper dictionary.
+        # Libvirt device's address definition is:
+        # PCI = {'type':'pci', 'domain':'0x0000', 'bus':'0x00',
+        #        'slot':'0x0c', 'function':'0x0'}
+        # IDE = {'type':'drive', 'controller':'0', 'bus':'0', 'unit':'0'}
+        for key in adrXml.attributes.keys():
+            address[key.strip()] = adrXml.getAttribute(key).strip()
+
+        return address
+
+    def _getDriveIdentification(self, dom):
+        sources = dom.getElementsByTagName('source')
+        if sources:
+            devPath = (sources[0].getAttribute('file') or
+                       sources[0].getAttribute('dev') or
+                       sources[0].getAttribute('name'))
+        else:
+            devPath = ''
+        target = dom.getElementsByTagName('target')
+        name = target[0].getAttribute('dev') if target else ''
+        alias = dom.getElementsByTagName('alias')[0].getAttribute('name')
+        return alias, devPath, name
+
+
+def _getDefaultDiskInterface(arch):
+        DEFAULT_DISK_INTERFACES = {caps.Architecture.X86_64: 'ide',
+                                   caps.Architecture.PPC64: 'scsi'}
+        return DEFAULT_DISK_INTERFACES[arch]


-- 
To view, visit https://gerrit.ovirt.org/38704
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia04b6ee17da207f772f77059e45be97594e6be78
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Ido Barkan <ibarkan at redhat.com>


More information about the vdsm-patches mailing list