
On 02/13/2014 04:47 PM, Daniel Barboza wrote:
From: Rodrigo Trujillo <rodrigo.trujillo@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@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@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 173 +++++++++++++++++++++++++++++++++++++++++++++++- src/kimchi/xmlutils.py | 8 ++- 2 files changed, 178 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 7a84122..e20dc38 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 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'} + GUESTS_STATS_INTERVAL = 5 VM_STATIC_UPDATE_PARAMS = {'name': './name'} VM_LIVE_UPDATE_PARAMS = {} @@ -222,7 +233,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]] @@ -453,3 +464,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 path.startswith('/'): + if not os.path.exists(path): + raise InvalidParameter("KCHCDROM0005E") + elif path.endswith('.iso') or os.path.isfile(path): + params['src_type'] = 'file' + elif os.path.isdir(path): + raise InvalidParameter("KCHCDROM0006E") + else: + params['src_type'] = 'block' + else: + params['src_type'] = 'network'
Use chech_url_path() to check network cdrom if os.path.exists(path): if os.path.iffile(path) params['src_type'] = 'file' else: params['src_type'] = 'block' elif check_url_path(path): params['src_type'] = network else: raise InvalidParamter(<cdrom path is not a local or remote>)
+ + # Add device to VM + dev_xml = self._get_storage_xml(params) + try: + 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..fd19da1 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 @@ -40,3 +40,9 @@ def xml_item_update(xml, xpath, value): item = root.find(xpath) item.text = value return ElementTree.tostring(root, encoding="utf-8") +
+ +def xml_get_child(xml, xpath): + root = ElementTree.fromstring(xml) + item = root.find(xpath) + return ElementTree.tostring(item, encoding="utf-8")
This code can be removed as you are using lxml now