[PATCH 0/3 V2] Make Template defaults configurable

v1 -> v2: - Fix error message format Aline Manera (3): Make Template defaults configurable Verify all networks set as Template defaults prior to server start up Verify storage pool set as Template default prior to server starts up contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 2 ++ contrib/kimchi.spec.suse.in | 2 ++ docs/README.md | 8 ++--- src/Makefile.am | 2 +- src/kimchi/model/model.py | 44 +---------------------- src/kimchi/model/networks.py | 63 +++++++++++++++----------------- src/kimchi/model/storagepools.py | 66 ++++++++++++++++++++++++++++++++++ src/kimchi/model/templates.py | 2 -- src/kimchi/osinfo.py | 78 ++++++++++++++++++++++++++++++++++------ src/template.conf | 47 ++++++++++++++++++++++++ 11 files changed, 220 insertions(+), 95 deletions(-) create mode 100644 src/template.conf -- 2.1.0

Create a template configuration file (template.conf) to handle all the template default values. Also parse it to set the right values on osinfo.py As template.conf file uses nested sections, it is required to install an additional package python-configobj to parse it. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 2 ++ contrib/kimchi.spec.suse.in | 2 ++ docs/README.md | 8 ++--- src/Makefile.am | 2 +- src/kimchi/model/templates.py | 2 -- src/kimchi/osinfo.py | 78 +++++++++++++++++++++++++++++++++++++------ src/template.conf | 47 ++++++++++++++++++++++++++ 8 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 src/template.conf diff --git a/contrib/DEBIAN/control.in b/contrib/DEBIAN/control.in index 069e14b..0747d0f 100644 --- a/contrib/DEBIAN/control.in +++ b/contrib/DEBIAN/control.in @@ -6,6 +6,7 @@ Architecture: all Depends: python-cherrypy3 (>= 3.2.0), python-cheetah, python-imaging, + python-configobj, websockify, novnc, python-jsonschema (>= 1.3.0), diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in index 8217640..30f8417 100644 --- a/contrib/kimchi.spec.fedora.in +++ b/contrib/kimchi.spec.fedora.in @@ -15,6 +15,7 @@ Requires: libvirt-daemon-config-network Requires: python-cherrypy >= 3.2.0 Requires: python-cheetah Requires: python-websockify +Requires: python-configobj Requires: novnc Requires: m2crypto Requires: python-imaging @@ -162,6 +163,7 @@ rm -rf $RPM_BUILD_ROOT %{_sysconfdir}/nginx/conf.d/kimchi.conf.in %{_sysconfdir}/nginx/conf.d/kimchi.conf %{_sysconfdir}/kimchi/kimchi.conf +%{_sysconfdir}/kimchi/template.conf %{_sysconfdir}/kimchi/distros.d/debian.json %{_sysconfdir}/kimchi/distros.d/fedora.json %{_sysconfdir}/kimchi/distros.d/opensuse.json diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in index 22c651e..bf85661 100644 --- a/contrib/kimchi.spec.suse.in +++ b/contrib/kimchi.spec.suse.in @@ -15,6 +15,7 @@ Requires: libvirt-daemon-config-network Requires: python-CherryPy >= 3.2.0 Requires: python-Cheetah Requires: python-websockify +Requires: python-configobj Requires: novnc Requires: python-imaging Requires: python-M2Crypto @@ -103,6 +104,7 @@ rm -rf $RPM_BUILD_ROOT %{_sysconfdir}/nginx/conf.d/kimchi.conf.in %{_sysconfdir}/nginx/conf.d/kimchi.conf %{_sysconfdir}/kimchi/kimchi.conf +%{_sysconfdir}/kimchi/template.conf %{_sysconfdir}/kimchi/distros.d/debian.json %{_sysconfdir}/kimchi/distros.d/fedora.json %{_sysconfdir}/kimchi/distros.d/opensuse.json diff --git a/docs/README.md b/docs/README.md index b9458ac..034a591 100644 --- a/docs/README.md +++ b/docs/README.md @@ -55,7 +55,7 @@ Install Dependencies python-ipaddr python-ldap python-lxml nfs-utils \ iscsi-initiator-utils libxslt pyparted nginx \ python-libguestfs libguestfs-tools python-websockify \ - novnc spice-html5 + novnc spice-html5 python-configobj # If using RHEL, install the following additional packages: $ sudo yum install python-unittest2 python-ordereddict @@ -81,7 +81,7 @@ channel at RHN Classic or Red Hat Satellite. $ sudo apt-get install gcc make autoconf automake gettext git \ python-cherrypy3 python-cheetah python-libvirt \ - libvirt-bin python-imaging \ + libvirt-bin python-imaging python-configobj \ python-pam python-m2crypto python-jsonschema \ qemu-kvm libtool python-psutil python-ethtool \ sosreport python-ipaddr python-ldap \ @@ -104,8 +104,8 @@ channel at RHN Classic or Red Hat Satellite. python-imaging python-M2Crypto python-jsonschema \ rpm-build kvm python-psutil python-ethtool \ python-ipaddr python-ldap python-lxml nfs-client \ - open-iscsi libxslt-tools python-xml \ - python-parted nginx python-libguestfs \ + open-iscsi libxslt-tools python-xml python-parted \ + nginx python-libguestfs python-configobj \ guestfs-tools python-websockify novnc Packages version requirement: diff --git a/src/Makefile.am b/src/Makefile.am index f57d121..38d231c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,7 +27,7 @@ EXTRA_DIST = kimchid.in \ bin_SCRIPTS = kimchid confdir = $(sysconfdir)/kimchi -dist_conf_DATA = kimchi.conf +dist_conf_DATA = kimchi.conf template.conf BUILT_SOURCES = kimchi.conf diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py index 8a1820d..ef83706 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -68,8 +68,6 @@ class TemplatesModel(object): # exception if a topology is invalid. CPUInfoModel(conn=self.conn).\ check_topology(params['cpus'], topology) - else: - params['cpu_info'] = dict() conn = self.conn.get() pool_uri = params.get(u'storagepool', '') diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py index 05c685e..67d85be 100644 --- a/src/kimchi/osinfo.py +++ b/src/kimchi/osinfo.py @@ -21,10 +21,10 @@ import copy import glob import os - +from collections import defaultdict +from configobj import ConfigObj from distutils.version import LooseVersion - from kimchi.config import paths @@ -85,14 +85,72 @@ icon_available_distros = [icon[5:-4] for icon in glob.glob1('%s/images/' % paths.ui_dir, 'icon-*.png')] -defaults = {'networks': ['default'], - 'storagepool': '/storagepools/default', - 'domain': 'kvm', 'arch': os.uname()[4], - 'graphics': {'type': 'vnc', 'listen': '127.0.0.1'}, - 'cpus': 1, - 'memory': 1024, - 'disks': [{'index': 0, 'size': 10, 'format': 'qcow2'}], - 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'} +def _get_tmpl_defaults(): + """ + ConfigObj returns a dict like below when no changes were made in the + template configuration file (template.conf) + + {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + + The default values should be like below: + + {'main': {'networks': ['default'], 'memory': '1024'}, + 'storage': {'pool': 'default', + 'disk.0': {'format': 'qcow2', 'size': '10'}}, + 'processor': {'cpus': '1'}, + 'graphics': {'type': 'spice', 'listen': '127.0.0.1'}} + """ + # Create dict with default values + tmpl_defaults = defaultdict(dict) + tmpl_defaults['main']['networks'] = ['default'] + tmpl_defaults['main']['memory'] = 1024 + tmpl_defaults['storage']['pool'] = 'default' + tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2'} + tmpl_defaults['processor']['cpus'] = 1 + tmpl_defaults['graphics'] = {'type': 'vnc', 'listen': '127.0.0.1'} + + default_config = ConfigObj(tmpl_defaults) + + # Load template configuration file + config_file = os.path.join(paths.conf_dir, 'template.conf') + config = ConfigObj(config_file) + + # Merge default configuration with file configuration + default_config.merge(config) + + # Create a dict with default values according to data structure + # expected by VMTemplate + defaults = {'domain': 'kvm', 'arch': os.uname()[4], + 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'} + + # Parse main section to get networks and memory values + main_section = default_config.pop('main') + defaults.update(main_section) + + # Parse storage section to get storage pool and disks values + storage_section = default_config.pop('storage') + defaults['storagepool'] = '/storagepools/' + storage_section.pop('pool') + defaults['disks'] = [] + for disk in storage_section.keys(): + data = storage_section[disk] + data['index'] = int(disk.split('.')[1]) + defaults['disks'].append(data) + + # Parse processor section to get cpus and cpu_topology values + processor_section = default_config.pop('processor') + defaults['cpus'] = processor_section.pop('cpus') + defaults['cpu_info'] = {} + if len(processor_section.keys()) > 0: + defaults['cpu_info']['topology'] = processor_section + + # Update defaults values with graphics values + defaults['graphics'] = default_config.pop('graphics') + + return defaults + + +# Set defaults values according to template.conf file +defaults = _get_tmpl_defaults() def _get_arch(): diff --git a/src/template.conf b/src/template.conf new file mode 100644 index 0000000..f3615e6 --- /dev/null +++ b/src/template.conf @@ -0,0 +1,47 @@ +# +# Configuration file for Kimchi Templates +# + +[main] +# Memory in MB +#memory = 1024 + +# List of networks separated by comma +# Represents the virtual network interfaces to be assigned to guest +#networks = default, + +[storage] +# Storage pool used to handle the guest disk +#pool = default + +# Specify multiple [[disk.X]] sub-sections to add multiples disks to guest +# All the disk files will be created in the same storage pool as set above +[[disk.0]] +# Disk size in GB +#size = 10 + +# Disk format +#format = qcow2 + +[graphics] +# Graphics type +# Valid options: vnc | spice +#type = vnc + +# The network which the vnc/spice server listens on +#listen = 127.0.0.1 + +[processor] +# Number of vcpus +# When specifying CPU topology, make sure cpus value is equal to the product +# of sockets, cores, and threads. +#cpus = 1 + +# Number of sockets (not set by default) +#sockets = + +# Number of cores per socket (not set by default) +#cores = + +# Number of threads per core (not set by default) +#threads = -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/model/networks.py | 63 ++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py index 92aed92..f6ccee3 100644 --- a/src/kimchi/model/networks.py +++ b/src/kimchi/model/networks.py @@ -29,6 +29,7 @@ from kimchi import netinfo from kimchi import network as knetwork from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import MissingParameter, NotFoundError, OperationFailed +from kimchi.osinfo import defaults as tmpl_defaults from kimchi.rollbackcontext import RollbackContext from kimchi.utils import kimchi_log, run_command from kimchi.xmlutils.network import create_vlan_tagged_bridge_xml @@ -43,42 +44,34 @@ class NetworksModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] if self.conn.isQemuURI(): - self._default_network_check() - - def _default_network_check(self): - def create_default_network(): - try: - subnet = self._get_available_address(knetwork.DefaultNetsPool) - params = {"name": "default", "connection": "nat", - "subnet": subnet} - self.create(params) - return conn.networkLookupByName("default") - except Exception as e: - kimchi_log.error("Fatal: Cannot create default network " - "because of %s, exit kimchid", e.message) - sys.exit(1) + self._check_default_networks() + def _check_default_networks(self): + networks = list(set(tmpl_defaults['networks'])) conn = self.conn.get() - try: - net = conn.networkLookupByName("default") - except libvirt.libvirtError: - net = create_default_network() - if net.isActive() == 0: + error_msg = ("Please, check the configuration in" + "/etc/kimchi/template.conf to ensure it lists only valid " + "networks.") + + for net_name in networks: try: - net.create() - except libvirt.libvirtError as e: - # FIXME we can not distinguish this error from other internal - # error by error code. - if ("network is already in use by interface" - in e.message.lower()): - # libvirt do not support update IP element, so delete the - # the network and create new one. - net.undefine() - create_default_network() - else: - kimchi_log.error("Fatal: Cannot activate default network " - "because of %s, exit kimchid", e.message) + net = conn.networkLookupByName(net_name) + except libvirt.libvirtError, e: + msg = "Fatal: Unable to find network %s." + kimchi_log.error(msg, net_name) + kimchi_log.error(error_msg) + kimchi_log.error("Details: %s", e.message) + sys.exit(1) + + if net.isActive() == 0: + try: + net.create() + except libvirt.libvirtError as e: + msg = "Fatal: Unable to activate network %s." + kimchi_log.error(msg, net_name) + kimchi_log.error(error_msg) + kimchi_log.error("Details: %s", e.message) sys.exit(1) def create(self, params): @@ -274,10 +267,10 @@ class NetworkModel(object): 'persistent': True if network.isPersistent() else False} def _is_network_in_use(self, name): - # The network "default" is used for Kimchi proposal and should not be - # deactivate or deleted. Otherwise, we will allow user create + # All the networks listed as default in template.conf file should not + # be deactivate or deleted. Otherwise, we will allow user create # inconsistent templates from scratch - if name == 'default': + if name in tmpl_defaults['networks']: return True vms = self._get_vms_attach_to_a_network(name) -- 2.1.0

The verification was being done in model/model.py but as it is only related to storage pools, it was added to model/storagepools.py This patch also adds a new option 'create_iso_pool' to kimchid, so the user can choose to create or not the ISO pool during server starts up. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/model/model.py | 44 +-------------------------- src/kimchi/model/storagepools.py | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/src/kimchi/model/model.py b/src/kimchi/model/model.py index 566be66..40ebc98 100644 --- a/src/kimchi/model/model.py +++ b/src/kimchi/model/model.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2014 +# Copyright IBM, Corp. 2014-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -18,14 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import inspect -import logging import os -import sys - -import cherrypy -import libvirt -import lxml.etree as ET -from lxml.builder import E from kimchi.basemodel import BaseModel from kimchi.model.libvirtconnection import LibvirtConnection @@ -33,10 +26,6 @@ from kimchi.objectstore import ObjectStore from kimchi.utils import import_module, listPathModules -DEFAULT_POOLS = {'default': {'path': '/var/lib/libvirt/images'}, - 'ISO': {'path': '/var/lib/kimchi/isos'}} - - class Model(BaseModel): def __init__(self, libvirt_uri=None, objstore_loc=None): @@ -44,10 +33,6 @@ class Model(BaseModel): self.conn = LibvirtConnection(libvirt_uri) kargs = {'objstore': self.objstore, 'conn': self.conn} - if self.conn.isQemuURI(): - for pool_name, pool_arg in DEFAULT_POOLS.iteritems(): - self._default_pool_check(pool_name, pool_arg) - this = os.path.basename(__file__) this_mod = os.path.splitext(this)[0] @@ -64,30 +49,3 @@ class Model(BaseModel): models.append(instance(**kargs)) return super(Model, self).__init__(models) - - def _default_pool_check(self, pool_name, pool_arg): - conn = self.conn.get() - pool = E.pool(E.name(pool_name), type='dir') - pool.append(E.target(E.path(pool_arg['path']))) - xml = ET.tostring(pool) - try: - pool = conn.storagePoolLookupByName(pool_name) - except libvirt.libvirtError: - try: - pool = conn.storagePoolDefineXML(xml, 0) - # Add build step to make sure target directory created - pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW) - pool.setAutostart(1) - except libvirt.libvirtError, e: - cherrypy.log.error("Fatal: Cannot create default pool because " - "of %s, exit kimchid" % e.message, - severity=logging.ERROR) - sys.exit(1) - - if pool.isActive() == 0: - try: - pool.create(0) - except libvirt.libvirtError, e: - err = "Fatal: Default pool cannot be activated, exit kimchid" - cherrypy.log.error(err, severity=logging.ERROR) - sys.exit(1) diff --git a/src/kimchi/model/storagepools.py b/src/kimchi/model/storagepools.py index b85f3b4..0618314 100644 --- a/src/kimchi/model/storagepools.py +++ b/src/kimchi/model/storagepools.py @@ -18,18 +18,28 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import libvirt +import lxml.etree as ET +import sys +from lxml.builder import E + +from kimchi.config import config from kimchi.scan import Scanner from kimchi.exception import InvalidOperation, MissingParameter from kimchi.exception import NotFoundError, OperationFailed from kimchi.model.config import CapabilitiesModel from kimchi.model.host import DeviceModel from kimchi.model.libvirtstoragepool import StoragePoolDef +from kimchi.osinfo import defaults as tmpl_defaults from kimchi.utils import add_task, kimchi_log, pool_name_from_uri, run_command from kimchi.xmlutils.utils import xpath_get_text +DEFAULT_POOLS = {'default': {'path': '/var/lib/libvirt/images'}, + 'ISO': {'path': '/var/lib/kimchi/isos'}} + ISO_POOL_NAME = u'kimchi_isos' + POOL_STATE_MAP = {0: 'inactive', 1: 'initializing', 2: 'active', @@ -57,6 +67,62 @@ class StoragePoolsModel(object): self.caps = CapabilitiesModel(**kargs) self.device = DeviceModel(**kargs) + if self.conn.isQemuURI(): + self._check_default_pools() + + def _check_default_pools(self): + default_pool = tmpl_defaults['storagepool'] + default_pool = default_pool.split('/')[2] + + if default_pool != 'default': + del DEFAULT_POOLS['default'] + DEFAULT_POOLS[default_pool] = {} + + if config.get("server", "create_iso_pool") != "true": + del DEFAULT_POOLS['ISO'] + + error_msg = ("Please, check the configuration in " + "/etc/kimchi/template.conf to ensure it has a valid " + "storage pool.") + + conn = self.conn.get() + for pool_name in DEFAULT_POOLS: + try: + pool = conn.storagePoolLookupByName(pool_name) + except libvirt.libvirtError, e: + pool_path = DEFAULT_POOLS[pool_name].get('path') + if pool_path is None: + msg = "Fatal: Unable to find storage pool %s. " + error_msg + kimchi_log.error(msg % pool_name) + kimchi_log.error("Details: %s", e.message) + sys.exit(1) + + # Try to create the pool + pool = E.pool(E.name(pool_name), type='dir') + pool.append(E.target(E.path(pool_path))) + xml = ET.tostring(pool) + try: + pool = conn.storagePoolDefineXML(xml, 0) + # Add build step to make sure target directory created + pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW) + pool.setAutostart(1) + except libvirt.libvirtError, e: + msg = "Fatal: Unable to create storage pool %s. " + msg += error_msg + kimchi_log.error(msg % pool_name) + kimchi_log.error("Details: %s", e.message) + sys.exit(1) + + if pool.isActive() == 0: + try: + pool.create(0) + except libvirt.libvirtError, e: + msg = "Fatal: Unable to craete storage pool %s. " + msg += error_msg + kimchi_log.error(msg % pool_name) + kimchi_log.error("Details: %s", e.message) + sys.exit(1) + def get_list(self): try: conn = self.conn.get() -- 2.1.0

Reviewed-by: Crístian Deives <cristiandeives@gmail.com> On 04-05-2015 12:24, Aline Manera wrote:
v1 -> v2: - Fix error message format
Aline Manera (3): Make Template defaults configurable Verify all networks set as Template defaults prior to server start up Verify storage pool set as Template default prior to server starts up
contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 2 ++ contrib/kimchi.spec.suse.in | 2 ++ docs/README.md | 8 ++--- src/Makefile.am | 2 +- src/kimchi/model/model.py | 44 +---------------------- src/kimchi/model/networks.py | 63 +++++++++++++++----------------- src/kimchi/model/storagepools.py | 66 ++++++++++++++++++++++++++++++++++ src/kimchi/model/templates.py | 2 -- src/kimchi/osinfo.py | 78 ++++++++++++++++++++++++++++++++++------ src/template.conf | 47 ++++++++++++++++++++++++ 11 files changed, 220 insertions(+), 95 deletions(-) create mode 100644 src/template.conf
participants (2)
-
Aline Manera
-
Crístian Deives