On 2014年02月14日 09:45, Daniel Barboza wrote:
From: Rodrigo Trujillo <rodrigo.trujillo(a)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(a)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(a)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