
From: Aline Manera <alinefm@br.ibm.com> To avoid duplicating code in model and mockmodel, the code related to storagepool resource was added to model_/storagepools.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 | 171 ++++++++++++++++++++++++++++++++++- src/kimchi/model_/mockbackend.py | 51 +++++++++++ src/kimchi/model_/storagepools.py | 86 ++++++++++++++++++ 3 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/kimchi/model_/storagepools.py diff --git a/src/kimchi/model_/libvirtbackend.py b/src/kimchi/model_/libvirtbackend.py index 4ab4db6..28bfc01 100644 --- a/src/kimchi/model_/libvirtbackend.py +++ b/src/kimchi/model_/libvirtbackend.py @@ -36,18 +36,33 @@ from cherrypy.process.plugins import BackgroundTask from collections import defaultdict 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.model_.libvirtconnection import LibvirtConnection +from kimchi.model_.libvirtstoragepool import StoragePoolDef from kimchi.objectstore import ObjectStore +from kimchi.scan import Scanner from kimchi.utils import kimchi_log HOST_STATS_INTERVAL = 1 +STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name', + 'path': '/pool/source/dir/@path'}} class LibvirtBackend(object): - def __init__(self, objstore_loc=None): + pool_state_map = {0: 'inactive', + 1: 'initializing', + 2: 'active', + 3: 'degraded', + 4: 'inaccessible'} + + def __init__(self, libvirt_uri=None, objstore_loc=None): + self.libvirt_uri = libvirt_uri or 'qemu:///system' + self.conn = LibvirtConnection(self.libvirt_uri) self.objstore = ObjectStore(objstore_loc) self.next_taskid = 1 + self.scanner = Scanner(self._clean_scan) self.host_stats = defaultdict(int) self.host_stats_thread = BackgroundTask(HOST_STATS_INTERVAL, self._update_host_stats) @@ -228,3 +243,157 @@ class LibvirtBackend(object): res['os_codename'] = unicode(codename,"utf-8") return res + + def get_storagepools(self): + try: + conn = self.conn.get() + names = conn.listStoragePools() + names += conn.listDefinedStoragePools() + return names + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def _clean_scan(self, pool_name): + try: + self.deactivate_storagepool(pool_name) + with self.objstore as session: + session.delete('scanning', pool_name) + except Exception, e: + kimchi_log.debug("Error while cleaning deep scan results" % + e.message) + + def do_deep_scan(self, params): + scan_params = dict(ignore_list=[]) + scan_params['scan_path'] = params['path'] + params['type'] = 'dir' + + for pool in self.get_storagepools(): + try: + res = self.get_storagepool_by_name(pool) + if res['state'] == 'active': + scan_params['ignore_list'].append(res['path']) + except Exception, e: + kimchi_log.debug("Error while preparing for deep scan: %s" % + e.message) + + params['path'] = self.scanner.scan_dir_prepare(params['name']) + scan_params['pool_path'] = params['path'] + task_id = self.add_task('', self.scanner.start_scan, scan_params) + # Record scanning-task/storagepool mapping for future querying + with self.objstore as session: + session.store('scanning', params['name'], task_id) + return task_id + + def create_storagepool(self, params): + conn = self.conn.get() + try: + poolDef = StoragePoolDef.create(params) + poolDef.prepare(conn) + xml = poolDef.xml + except KeyError, key: + raise MissingParameter("You need to specify '%s' in order to " + "create storage pool" % key) + + try: + if task_id: + # Create transient pool for deep scan + conn.storagePoolCreateXML(xml, 0) + return name + + pool = conn.storagePoolDefineXML(xml, 0) + if params['type'] in ['logical', 'dir', 'netfs']: + pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW) + # autostart dir and logical storage pool created from kimchi + pool.setAutostart(1) + else: + # disable autostart for others + pool.setAutostart(0) + except libvirt.libvirtError as e: + msg = "Problem creating Storage Pool: %s" + kimchi_log.error(msg, e) + raise OperationFailed(e.get_error_message()) + + def get_storagepool_by_name(self, name): + conn = self.conn.get() + pool = conn.storagePoolLookupByName(name) + info = pool.info() + nr_volumes = self._get_storagepool_vols_num(pool) + autostart = True if pool.autostart() else False + xml = pool.XMLDesc(0) + path = xmlutils.xpath_get_text(xml, "/pool/target/path")[0] + pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0] + source = self._get_storage_source(pool_type, xml) + res = {'state': self.pool_state_map[info[0]], + 'path': path, + 'source': source, + 'type': pool_type, + 'autostart': autostart, + 'capacity': info[1], + 'allocated': info[2], + 'available': info[3], + 'nr_volumes': nr_volumes} + + if not pool.isPersistent(): + # Deal with deep scan generated pool + try: + with self.objstore as session: + task_id = session.get('scanning', name) + res['task_id'] = str(task_id) + res['type'] = 'kimchi-iso' + except NotFoundError: + # User created normal pool + pass + return res + + def _get_storagepool_vols_num(self, pool): + try: + if pool.isActive(): + pool.refresh(0) + return pool.numOfVolumes() + else: + return 0 + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def _get_storage_source(self, pool_type, pool_xml): + source = {} + if pool_type not in STORAGE_SOURCES: + return source + + for key, val in STORAGE_SOURCES[pool_type].items(): + res = xmlutils.xpath_get_text(pool_xml, val) + source[key] = res[0] if len(res) == 1 else res + + return source + + def activate_storagepool(self, name): + try: + conn = self.conn.get() + pool = conn.storagePoolLookupByName(name) + pool.create(0) + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def deactivate_storagepool(self, name): + try: + conn = self.conn.get() + pool = conn.storagePoolLookupByName(name) + pool.destroy() + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def delete_storagepool(self, name): + try: + conn = self.conn.get() + pool = conn.storagePoolLookupByName(name) + pool.undefine() + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + def autostart_storagepool(self, name, value): + conn = self.conn.get() + pool = conn.storagePoolLookupByName(name) + if autostart: + pool.setAutostart(1) + else: + pool.setAutostart(0) diff --git a/src/kimchi/model_/mockbackend.py b/src/kimchi/model_/mockbackend.py index 5fb332e..01f4c22 100644 --- a/src/kimchi/model_/mockbackend.py +++ b/src/kimchi/model_/mockbackend.py @@ -21,6 +21,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +import copy import os import random @@ -33,6 +34,7 @@ class MockBackend(object): self.objstore = ObjectStore(objstore_loc) self.next_taskid = 1 self.host_stats = self._get_host_stats() + self._storagepools = {} def _get_host_stats(self): memory_stats = {'total': 3934908416L, @@ -85,3 +87,52 @@ class MockBackend(object): res['os_codename'] = 'Santiago' return res + + def get_storagepools(self): + return self._storagepools.keys() + + def do_deep_scan(self): + return self.add_task('', time.sleep, 25) + + def create_storagepool(self, params): + name = params['name'] + pool = MockStoragePool(name) + pool.info.update(params) + if params['type'] == 'dir': + pool.info['autostart'] = True + else: + pool.info['autostart'] = False + + self._storagepools[name] = pool + + def get_storagepool_by_name(self, name): + pool = self._storagepools[name] + pool.refresh() + return pool.info + + def activate_storagepool(self, name): + self._storagepools[name].info['state'] = 'active' + + def deactivate_storagepool(self, name): + self._storagepools[name].info['state'] = 'inactive' + + def delete_storagepool(self, name): + del self._storagepools[name] + + def autostart_storagepool(self, name, value): + self._storagepools[name].info['autostart'] = value + +class MockStoragePool(object): + def __init__(self, name): + self.name = name + self._volumes = {} + self.info = {'state': 'inactive', 'capacity': 1024 << 20, + 'allocated': 512 << 20, 'available': 512 << 20, + 'path': '/var/lib/libvirt/images', 'source': {}, + 'type': 'dir', 'nr_volumes': 0, 'autostart': 0} + + def refresh(self): + state = self.info['state'] + self.info['nr_volumes'] = 0 + if state == 'active': + self.info['nr_volumes'] = len(self._volumes) diff --git a/src/kimchi/model_/storagepools.py b/src/kimchi/model_/storagepools.py new file mode 100644 index 0000000..abdebd8 --- /dev/null +++ b/src/kimchi/model_/storagepools.py @@ -0,0 +1,86 @@ +# +# 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 + +ISO_POOL_NAME = u'kimchi_isos' + +from kimchi.exception import InvalidParameter + +class StoragePools(object): + def __init__(self, backend): + self.backend = backend + + def get_list(self): + return sorted(self.backend.get_storagepools()) + + def create(self, params): + name = params['name'] + if name in self.get_list() or name in (ISO_POOL_NAME,): + raise InvalidParameter("Storage pool '%s' already exists" % name) + + task_id = None + if params['type'] == 'kimchi-iso': + task_id = self.backend.do_deep_scan(params) + + self.backend.create_storagepool(params) + return name + +class StoragePool(StoragePools): + def lookup(self, name): + if name not in self.get_list(): + raise NotFoundError("Storage pool '%s' not found.") + + return self.backend.get_storagepool_by_name(name) + + def activate(self, name): + if name not in self.get_list(): + raise NotFoundError("Storage pool '%s' not found.") + + self.backend.activate_storagepool() + + def deactivate(self, name): + if name not in self.get_list(): + raise NotFoundError("Storage pool '%s' not found.") + + self.backend.deactivate_storagepool() + + def delete(self, name): + if name not in self.get_list(): + raise NotFoundError("Storage pool '%s' not found.") + + if self.get_storagepool_by_name(name)['state'] == 'active': + raise InvalidOperation("Unable to delete active storage pool '%s'" % + name) + + self.backend.delete_storagepool() + + def update(self, name, params): + if name not in self.get_list(): + raise NotFoundError("Storage pool '%s' not found.") + + autostart = params['autostart'] + if autostart not in [True, False]: + raise InvalidOperation("Autostart flag must be true or false") + + self.backend.autostart_storagepool(name, autostart) + + return name -- 1.7.10.4