[Kimchi-devel] [PATCH v5 3/5] CDROM Management: Devices management model implementation

Royce Lv lvroyce at linux.vnet.ibm.com
Fri Feb 14 08:47:37 UTC 2014


On 2014年02月14日 09:45, Daniel Barboza wrote:
> From: Rodrigo Trujillo <rodrigo.trujillo at linux.vnet.ibm.com>
>
> This patch adds the CREATE, LOOKUP, UPDATE and DELETE functionalities
> to guest storage devices support.
>
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo at linux.vnet.ibm.com>
>
> Using lxml and objectify to parse the XML info.
> Pep8 adjustments in model/vms.py
> Removing hard disk support from model/vms.py
> Including xml creation using lxml in _get_storage_xml
> Including existing device name verification
> Changed model/vms.py to use the new exception model.
>
> Signed-off-by: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>
> ---
>   src/kimchi/model/vms.py | 173 +++++++++++++++++++++++++++++++++++++++++++++++-
>   src/kimchi/xmlutils.py  |   2 +-
>   2 files changed, 172 insertions(+), 3 deletions(-)
>
> diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
> index 2585082..6774c7e 100644
> --- a/src/kimchi/model/vms.py
> +++ b/src/kimchi/model/vms.py
> @@ -21,12 +21,18 @@
>   # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
>
>   import os
> +import socket
> +import string
>   import time
> +import urlparse
>   import uuid
>   from xml.etree import ElementTree
>
>   import libvirt
> +import lxml.etree as ET
>   from cherrypy.process.plugins import BackgroundTask
> +from lxml import etree, objectify
> +from lxml.builder import E
>
>   from kimchi import vnc
>   from kimchi import xmlutils
> @@ -36,7 +42,8 @@ from kimchi.model.config import CapabilitiesModel
>   from kimchi.model.templates import TemplateModel
>   from kimchi.model.utils import get_vm_name
>   from kimchi.screenshot import VMScreenshot
> -from kimchi.utils import run_setfacl_set_attr, template_name_from_uri
> +from kimchi.utils import check_url_path, kimchi_log, run_setfacl_set_attr
> +from kimchi.utils import template_name_from_uri
>
>
>   DOM_STATE_MAP = {0: 'nostate',
> @@ -47,6 +54,10 @@ DOM_STATE_MAP = {0: 'nostate',
>                    5: 'shutoff',
>                    6: 'crashed'}
>
> +DEV_TYPE_SRC_ATTR_MAP = {'file': 'file',
> +                         'block': 'dev',
> +                         'dir': 'dir'}
'dir' source type still here.
> +
>   GUESTS_STATS_INTERVAL = 5
>   VM_STATIC_UPDATE_PARAMS = {'name': './name'}
>   VM_LIVE_UPDATE_PARAMS = {}
> @@ -243,7 +254,7 @@ class VMModel(object):
>           dom = self.get_vm(name, self.conn)
>           dom = self._static_vm_update(dom, params)
>           self._live_vm_update(dom, params)
> -        return dom.name().decode('utf-8')
> +        return dom.name()
>
>       def _static_vm_update(self, dom, params):
>           state = DOM_STATE_MAP[dom.info()[0]]
> @@ -474,3 +485,161 @@ class LibvirtVMScreenshot(VMScreenshot):
>               stream.finish()
>           finally:
>               os.close(fd)
> +
> +
> +class StoragesModel(object):
> +    def __init__(self, **kargs):
> +        self.conn = kargs['conn']
> +
> +    def _get_storage_xml(self, params):
> +        src_type = params.get('src_type')
> +        disk = E.disk(type=src_type, device=params.get('type'))
> +        disk.append(E.driver(name='qemu', type='raw'))
> +        # Working with url paths
> +        if src_type == 'network':
> +            output = urlparse.urlparse(params.get('path'))
> +            host = E.host(name=output.hostname, port=
> +                          output.port or socket.getservbyname(output.scheme))
> +            source = E.source(protocol=output.scheme, name=output.path)
> +            source.append(host)
> +            disk.append(source)
> +        else:
> +            # Fixing source attribute
> +            source = E.source()
> +            source.set(DEV_TYPE_SRC_ATTR_MAP[src_type], params.get('path'))
> +            disk.append(source)
> +
> +        disk.append(E.target(dev=params.get('dev'), bus='ide'))
> +        return ET.tostring(disk)
> +
> +    def create(self, vm_name, params):
> +        # Use device name passed or pick next
> +        dev_name = params.get('dev')
> +        if not dev_name:
> +            params['dev'] = self._get_storage_device_name(vm_name)
> +        else:
> +            devices = self.get_list(vm_name)
> +            if dev_name in devices:
> +                raise OperationFailed("KCHCDROM0004E",
> +                                      {'dev_name': dev_name,
> +                                       'vm_name': vm_name})
> +
> +        # Path will never be blank due to API.json verification.
> +        # There is no need to cover this case here.
> +        path = params.get('path')
> +
> +        # Checking if path exist, if not url
> +        if os.path.exists(path):
> +            if os.path.isfile(path):
> +                params['src_type'] = 'file'
> +            elif os.path.isdir(path):
> +                raise InvalidParameter("KCHCDROM0006E")
> +            else:
> +                params['src_type'] = 'block'
At least we need to make sure it is a block device, and we need to limit 
the block device we want to expose. I'm worried about there will be data 
corruption if we expose all.
> +        elif check_url_path(path):
> +            params['src_type'] = network
> +        else:
> +            raise InvalidParameter("KCHCDROM0005E", {'path': path})
> +
> +        # Add device to VM
> +        dev_xml = self._get_storage_xml(params)
> +        try:
CDROM is IDE device, which does not allow hotplug, so pls only allow 
device update when vm is shutoff.
> +            conn = self.conn.get()
> +            dom = conn.lookupByName(vm_name)
> +            dom.attachDeviceFlags(dev_xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
> +        except Exception as e:
> +            raise OperationFailed("KCHCDROM0011E", {'error': e.message})
> +        return params['dev']
> +
> +    def _get_storage_device_name(self, vm_name):
> +        dev_list = [dev for dev in self.get_list(vm_name)
> +                    if dev.startswith('hd')]
> +        if len(dev_list) == 0:
> +            return 'hda'
> +        dev_list.sort()
> +        last_dev = dev_list.pop()
> +        # TODO: Improve to device names "greater then" hdz
> +        next_dev_letter_pos = string.ascii_lowercase.index(last_dev[2]) + 1
> +        return 'hd' + string.ascii_lowercase[next_dev_letter_pos]
> +
> +    def get_list(self, vm_name):
> +        dom = VMModel.get_vm(vm_name, self.conn)
> +        xml = dom.XMLDesc(0)
> +        devices = objectify.fromstring(xml).devices
> +        storages = [disk.target.attrib['dev']
> +                    for disk in devices.xpath("./disk[@device='disk']")]
> +        storages += [disk.target.attrib['dev']
> +                     for disk in devices.xpath("./disk[@device='cdrom']")]
> +        return storages
> +
> +
> +class StorageModel(object):
> +    def __init__(self, **kargs):
> +        self.conn = kargs['conn']
> +        self.kargs = kargs
> +
> +    def _get_device_xml(self, vm_name, dev_name):
> +        # Get VM xml and then devices xml
> +        dom = VMModel.get_vm(vm_name, self.conn)
> +        xml = dom.XMLDesc(0)
> +        devices = objectify.fromstring(xml).devices
> +        disk = devices.xpath("./disk/target[@dev='%s']/.." % dev_name)
> +        if not disk:
> +            return None
> +        return disk[0]
> +
> +    def lookup(self, vm_name, dev_name):
> +        # Retrieve disk xml and format return dict
> +        disk = self._get_device_xml(vm_name, dev_name)
> +        if disk is None:
> +            raise NotFoundError("KCHCDROM0007E", {'dev_name': dev_name,
> +                                                  'vm_name': vm_name})
> +        source = disk.source
> +        path = ""
> +        if source is not None:
> +            src_type = disk.attrib['type']
> +            if src_type == 'network':
> +                host = source.host
> +                path = (source.attrib['protocol'] + '://' +
> +                        host.attrib['name'] + ':' +
> +                        host.attrib['port'] + source.attrib['name'])
> +            else:
> +                path = source.attrib[DEV_TYPE_SRC_ATTR_MAP[src_type]]
> +        dev_type = disk.attrib['device']
> +        return {'dev': dev_name,
> +                'type': dev_type,
> +                'path': path}
> +
> +    def delete(self, vm_name, dev_name):
> +        # Get storage device xml
> +        disk = self._get_device_xml(vm_name, dev_name)
> +        if disk is None:
> +            raise NotFoundError("KCHCDROM0007E",
> +                                {'dev_name': dev_name,
> +                                 'vm_name': vm_name})
> +        try:
> +            conn = self.conn.get()
> +            dom = conn.lookupByName(vm_name)
> +            dom.detachDeviceFlags(etree.tostring(disk),
> +                                  libvirt.VIR_DOMAIN_AFFECT_CURRENT)
> +        except Exception as e:
> +            raise OperationFailed("KCHCDROM0010E", {'error': e.message})
> +
> +    def update(self, vm_name, dev_name, params):
> +        info = self.lookup(vm_name, dev_name)
> +        backup_params = info.copy()
> +        try:
> +            self.delete(vm_name, dev_name)
> +        except Exception as e:
> +            raise OperationFailed("KCHCDROM0009E", {'error': e.message})
> +
> +        info.update(params)
> +        kargs = {'conn': self.conn}
> +        stgModel = StoragesModel(**kargs)
> +        try:
> +            dev_name = stgModel.create(vm_name, info)
> +            return dev_name
> +        except Exception as e:
> +            # Restoring previous device
> +            dev_name = stgModel.create(vm_name, backup_params)
> +            raise OperationFailed("KCHCDROM0009E", {'error': e.message})
> diff --git a/src/kimchi/xmlutils.py b/src/kimchi/xmlutils.py
> index 51ff0ec..44facbd 100644
> --- a/src/kimchi/xmlutils.py
> +++ b/src/kimchi/xmlutils.py
> @@ -29,7 +29,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




More information about the Kimchi-devel mailing list