
From: Aline Manera <alinefm@br.ibm.com> To avoid duplicating code in model and mockmodel, the code related to storagevolume resource was added to model_/storagevolumes.py and the specific code for each backend (libvirt or mock) was added to model_/libvirtbackend.py and model_/mockbackend.py Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- src/kimchi/model_/libvirtbackend.py | 96 ++++++++++++++++++++++++++++++++++- src/kimchi/model_/mockbackend.py | 42 +++++++++++++++ src/kimchi/model_/storagevolumes.py | 95 ++++++++++++++++++++++++++++++++++ src/kimchi/vmtemplate.py | 12 ----- 4 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 src/kimchi/model_/storagevolumes.py diff --git a/src/kimchi/model_/libvirtbackend.py b/src/kimchi/model_/libvirtbackend.py index 28bfc01..ee263c6 100644 --- a/src/kimchi/model_/libvirtbackend.py +++ b/src/kimchi/model_/libvirtbackend.py @@ -39,7 +39,8 @@ from kimchi import config from kimchi import xmlutils from kimchi.asynctask import AsyncTask from kimchi.featuretests import FeatureTests -from kimchi.exception import OperationFailed +from kimchi.isoinfo import IsoImage +from kimchi.exception import IsoFormatError, OperationFailed from kimchi.model_.libvirtconnection import LibvirtConnection from kimchi.model_.libvirtstoragepool import StoragePoolDef from kimchi.objectstore import ObjectStore @@ -57,6 +58,11 @@ class LibvirtBackend(object): 3: 'degraded', 4: 'inaccessible'} + volume_type_map = {0: 'file', + 1: 'block', + 2: 'directory', + 3: 'network'} + def __init__(self, libvirt_uri=None, objstore_loc=None): self.libvirt_uri = libvirt_uri or 'qemu:///system' self.conn = LibvirtConnection(self.libvirt_uri) @@ -397,3 +403,91 @@ class LibvirtBackend(object): pool.setAutostart(1) else: pool.setAutostart(0) + + def create_storagevolume(self, pool, params): + storagevol_xml = """ + <volume> + <name>%(name)s</name> + <allocation unit="MiB">%(allocation)s</allocation> + <capacity unit="MiB">%(capacity)s</capacity> + <target> + <format type='%(format)s'/> + <path>%(path)s</path> + </target> + </volume>""" + + params.setdefault('allocation', 0) + params.setdefault('format', 'qcow2') + try: + xml = storagevol_xml % params + except KeyError, key: + raise MissingParameter("You need to specify '%s' in order to " + "create the storage volume." % key) + + conn = self.conn.get() + pool = conn.storagePoolLookupByName(pool) + try: + pool.createXML(xml, 0) + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def _get_storagevolume(self, pool, name): + conn = self.conn.get() + pool = conn.storagePoolLookupByName(pool) + return pool.storageVolLookupByName(name) + + def get_storagevolumes_by_pool(self, pool): + try: + conn = self.conn.get() + pool = conn.storagePoolLookupByName(pool) + pool.refresh(0) + return pool.listVolumes() + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def get_storagevolume(self, pool, name): + vol = self._get_storagevolume(pool, name) + path = vol.path() + info = vol.info() + xml = vol.XMLDesc(0) + fmt = xmlutils.xpath_get_text(xml, "/volume/target/format/@type")[0] + res = dict(type=self.volume_type_map[info[0]], capacity=info[1], + allocation=info[2], path=path, format=fmt) + + if fmt == 'iso': + if os.path.islink(path): + path = os.path.join(os.path.dirname(path), os.readlink(path)) + + try: + iso_img = IsoImage(path) + os_distro, os_version = iso_img.probe() + bootable = True + except IsoFormatError: + bootable = False + + res.update(dict(os_distro=os_distro, os_version=os_version, + path=path, bootable=bootable)) + + return res + + def wipe_storagevolume(self, pool, name): + try: + vol = self._get_storagevolume(pool, name) + vol.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0) + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def resize_storagevolume(self, pool, name, size): + size = size << 20 + try: + vol = pool.storageVolLookupByName(name) + vol.resize(size, 0) + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def delete_storagevolume(self, pool, name): + try: + vol = pool.storageVolLookupByName(name) + volume.delete(0) + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) diff --git a/src/kimchi/model_/mockbackend.py b/src/kimchi/model_/mockbackend.py index 01f4c22..1f93d36 100644 --- a/src/kimchi/model_/mockbackend.py +++ b/src/kimchi/model_/mockbackend.py @@ -122,6 +122,34 @@ class MockBackend(object): def autostart_storagepool(self, name, value): self._storagepools[name].info['autostart'] = value + def create_storagevolume(self, pool, params): + try: + name = params['name'] + volume = MockStorageVolume(pool, name, params) + volume.info['type'] = params['type'] + volume.info['format'] = params['format'] + volume.info['path'] = os.path.join(pool.info['path'], name) + except KeyError, item: + raise MissingParameter(item) + + pool._volumes[name] = volume + + def get_storagevolumes_by_pool(self, pool): + return self._storagepools[pool]._volumes.keys() + + def get_storagevolume(self, pool, name): + vol = self._storagevolumes[pool]._volumes[name] + return vol.info + + def wipe_storagevolume(self, pool, name): + self._storagepools[pool]._volumes[name].info['allocation'] = 0 + + def resize_storagevolume(self, pool, name, size): + self._storagepools[pool]._volumes[name].info['capacity'] = size + + def delete_storagevolume(self, pool, name): + del self._storagepools[pool]._volumes[name] + class MockStoragePool(object): def __init__(self, name): self.name = name @@ -136,3 +164,17 @@ class MockStoragePool(object): self.info['nr_volumes'] = 0 if state == 'active': self.info['nr_volumes'] = len(self._volumes) + +class MockStorageVolume(object): + def __init__(self, pool, name, params={}): + self.name = name + self.pool = pool + self.info = {'type': 'disk', 'allocation': 512, + 'capacity': params.get('capacity', 1024) << 20, + 'format': params.get('format', 'raw')} + + if fmt == 'iso': + self.info['allocation'] = self.info['capacity'] + self.info['os_version'] = '19' + self.info['os_distro'] = 'fedora' + self.info['bootable'] = True diff --git a/src/kimchi/model_/storagevolumes.py b/src/kimchi/model_/storagevolumes.py new file mode 100644 index 0000000..9f3c93b --- /dev/null +++ b/src/kimchi/model_/storagevolumes.py @@ -0,0 +1,95 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# Adam Litke <agl@linux.vnet.ibm.com> +# Aline Manera <alinefm@linux.vnet.ibm.com> +# +# 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.exception import InvalidOperation, InvalidParameter, NotFoundError +from kimchi.model import storagepools + +class StorageVolumes(object): + def __init__(self, backend): + self.backend = backend + + def create(self, pool, params): + if name in self.get_list(pool): + raise InvalidParameter("Storage volume '%s' already exists.") + + self.backend.create_storagevolume(pool, params) + return name + + def get_list(self, pool): + info = self.backend.get_storagepool_by_name(pool) + if info['state'] != 'active': + raise InvalidOperation("Unable to list volumes in inactive " + "storagepool %s" % pool) + + return self.backend.get_storagevolumes_by_pool(pool) + +class StorageVolume(StorageVolumes): + def __init__(self, backend): + self.backend = backend + + def _storagevolume_exist(self, pool, name): + if name not in self.get_list(pool): + raise NotFoundError("Storage volume '%' not found in '%' pool" % + (name, pool)) + return True + + def lookup(self, pool, name): + if self._storagevolume_exist(pool, name): + return self.backend.get_storagevolume(pool, name) + + def resize(self, pool, name, size): + if self._storagevolume_exist(pool, name): + self.backend.resize_storagevolume(pool, name, size) + + def wipe(self, pool, name): + if self._storagevolume_exist(pool, name): + self.backend.wipe_storagevolume(pool, name) + + def delete(self, pool, name): + if self._storagevolume_exist(pool, name): + self.backend.delete_storagevolume(pool, name) + +class IsoVolumes(StorageVolumes): + def __init__(self, backend): + self.backend = backend + self.storagepools = storagepools.StoragePools(self.backend) + + def get_list(self, pool): + iso_volumes = [] + + for pool in self.storagepools.get_list(): + try: + volumes = self.get_list(pool) + except InvalidOperation: + # Skip inactive pools + continue + + for volume in volumes: + res = self.lookup(pool, volume) + if res['format'] == 'iso': + # prevent iso from different pool having same volume name + res['name'] = '%s-%s' % (pool, volume) + iso_volumes.append(res) + + return iso_volumes diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py index 58147e3..e7f6c81 100644 --- a/src/kimchi/vmtemplate.py +++ b/src/kimchi/vmtemplate.py @@ -192,18 +192,6 @@ class VMTemplate(object): 'type': 'disk', 'format': 'qcow2', 'path': '%s/%s' % (storage_path, volume)} - - info['xml'] = """ - <volume> - <name>%(name)s</name> - <allocation>0</allocation> - <capacity unit="G">%(capacity)s</capacity> - <target> - <format type='%(format)s'/> - <path>%(path)s</path> - </target> - </volume> - """ % info ret.append(info) return ret -- 1.7.10.4