[Kimchi-devel] [PATCH 22/23] Delete former model.py and rename model_ to model
Aline Manera
alinefm at linux.vnet.ibm.com
Wed Jan 29 23:35:03 UTC 2014
From: Aline Manera <alinefm at br.ibm.com>
model.py can be deleted as all resources have their own model
implementations
Also rename model_ to model
Signed-off-by: Aline Manera <alinefm at br.ibm.com>
---
Makefile.am | 2 +-
src/kimchi/control/storagepools.py | 2 +-
src/kimchi/mockmodel.py | 6 +-
src/kimchi/model.py | 1712 -------------------------------
src/kimchi/model/__init__.py | 21 +
src/kimchi/model/config.py | 98 ++
src/kimchi/model/debugreports.py | 167 +++
src/kimchi/model/host.py | 201 ++++
src/kimchi/model/interfaces.py | 46 +
src/kimchi/model/libvirtconnection.py | 122 +++
src/kimchi/model/libvirtstoragepool.py | 257 +++++
src/kimchi/model/model.py | 125 +++
src/kimchi/model/networks.py | 265 +++++
src/kimchi/model/plugins.py | 31 +
src/kimchi/model/storagepools.py | 246 +++++
src/kimchi/model/storageservers.py | 78 ++
src/kimchi/model/storagetargets.py | 86 ++
src/kimchi/model/storagevolumes.py | 176 ++++
src/kimchi/model/tasks.py | 39 +
src/kimchi/model/templates.py | 172 ++++
src/kimchi/model/utils.py | 33 +
src/kimchi/model/vmifaces.py | 135 +++
src/kimchi/model/vms.py | 447 ++++++++
src/kimchi/model_/__init__.py | 21 -
src/kimchi/model_/config.py | 98 --
src/kimchi/model_/debugreports.py | 167 ---
src/kimchi/model_/host.py | 201 ----
src/kimchi/model_/interfaces.py | 46 -
src/kimchi/model_/libvirtconnection.py | 122 ---
src/kimchi/model_/libvirtstoragepool.py | 257 -----
src/kimchi/model_/model.py | 125 ---
src/kimchi/model_/networks.py | 265 -----
src/kimchi/model_/plugins.py | 31 -
src/kimchi/model_/storagepools.py | 246 -----
src/kimchi/model_/storageservers.py | 78 --
src/kimchi/model_/storagetargets.py | 86 --
src/kimchi/model_/storagevolumes.py | 176 ----
src/kimchi/model_/tasks.py | 39 -
src/kimchi/model_/templates.py | 172 ----
src/kimchi/model_/utils.py | 33 -
src/kimchi/model_/vmifaces.py | 135 ---
src/kimchi/model_/vms.py | 447 --------
src/kimchi/server.py | 2 +-
tests/test_model.py | 2 +-
tests/test_storagepool.py | 2 +-
45 files changed, 2753 insertions(+), 4465 deletions(-)
delete mode 100644 src/kimchi/model.py
create mode 100644 src/kimchi/model/__init__.py
create mode 100644 src/kimchi/model/config.py
create mode 100644 src/kimchi/model/debugreports.py
create mode 100644 src/kimchi/model/host.py
create mode 100644 src/kimchi/model/interfaces.py
create mode 100644 src/kimchi/model/libvirtconnection.py
create mode 100644 src/kimchi/model/libvirtstoragepool.py
create mode 100644 src/kimchi/model/model.py
create mode 100644 src/kimchi/model/networks.py
create mode 100644 src/kimchi/model/plugins.py
create mode 100644 src/kimchi/model/storagepools.py
create mode 100644 src/kimchi/model/storageservers.py
create mode 100644 src/kimchi/model/storagetargets.py
create mode 100644 src/kimchi/model/storagevolumes.py
create mode 100644 src/kimchi/model/tasks.py
create mode 100644 src/kimchi/model/templates.py
create mode 100644 src/kimchi/model/utils.py
create mode 100644 src/kimchi/model/vmifaces.py
create mode 100644 src/kimchi/model/vms.py
delete mode 100644 src/kimchi/model_/__init__.py
delete mode 100644 src/kimchi/model_/config.py
delete mode 100644 src/kimchi/model_/debugreports.py
delete mode 100644 src/kimchi/model_/host.py
delete mode 100644 src/kimchi/model_/interfaces.py
delete mode 100644 src/kimchi/model_/libvirtconnection.py
delete mode 100644 src/kimchi/model_/libvirtstoragepool.py
delete mode 100644 src/kimchi/model_/model.py
delete mode 100644 src/kimchi/model_/networks.py
delete mode 100644 src/kimchi/model_/plugins.py
delete mode 100644 src/kimchi/model_/storagepools.py
delete mode 100644 src/kimchi/model_/storageservers.py
delete mode 100644 src/kimchi/model_/storagetargets.py
delete mode 100644 src/kimchi/model_/storagevolumes.py
delete mode 100644 src/kimchi/model_/tasks.py
delete mode 100644 src/kimchi/model_/templates.py
delete mode 100644 src/kimchi/model_/utils.py
delete mode 100644 src/kimchi/model_/vmifaces.py
delete mode 100644 src/kimchi/model_/vms.py
diff --git a/Makefile.am b/Makefile.am
index 1ffe6db..c092f42 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -54,7 +54,7 @@ PEP8_WHITELIST = \
src/kimchi/iscsi.py \
src/kimchi/isoinfo.py \
src/kimchi/kvmusertests.py \
- src/kimchi/model_/*.py \
+ src/kimchi/model/*.py \
src/kimchi/rollbackcontext.py \
src/kimchi/root.py \
src/kimchi/server.py \
diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py
index 7fb83f8..ae5185c 100644
--- a/src/kimchi/control/storagepools.py
+++ b/src/kimchi/control/storagepools.py
@@ -30,7 +30,7 @@ from kimchi.control.base import Collection, Resource
from kimchi.control.storagevolumes import IsoVolumes, StorageVolumes
from kimchi.control.utils import get_class_name, model_fn, parse_request
from kimchi.control.utils import validate_params
-from kimchi.model_.storagepools import ISO_POOL_NAME
+from kimchi.model.storagepools import ISO_POOL_NAME
from kimchi.control.utils import UrlSubNode
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index 814e9b0..4e276eb 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -47,9 +47,9 @@ from kimchi.config import config as kconfig
from kimchi.distroloader import DistroLoader
from kimchi.exception import InvalidOperation, InvalidParameter
from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
-from kimchi.model_.storagepools import ISO_POOL_NAME, STORAGE_SOURCES
-from kimchi.model_.utils import get_vm_name
-from kimchi.model_.vms import VM_STATIC_UPDATE_PARAMS
+from kimchi.model.storagepools import ISO_POOL_NAME, STORAGE_SOURCES
+from kimchi.model.utils import get_vm_name
+from kimchi.model.vms import VM_STATIC_UPDATE_PARAMS
from kimchi.objectstore import ObjectStore
from kimchi.screenshot import VMScreenshot
from kimchi.utils import template_name_from_uri, pool_name_from_uri
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
deleted file mode 100644
index 2e89598..0000000
--- a/src/kimchi/model.py
+++ /dev/null
@@ -1,1712 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at 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
-
-import cherrypy
-import copy
-import disks
-import fnmatch
-import functools
-import glob
-import ipaddr
-import json
-import libvirt
-import logging
-import lxml.etree as ET
-import os
-import platform
-import psutil
-import random
-import re
-import shutil
-import subprocess
-import sys
-import threading
-import time
-import uuid
-
-
-from cherrypy.process.plugins import BackgroundTask
-from cherrypy.process.plugins import SimplePlugin
-from collections import defaultdict
-from lxml import etree, objectify
-from lxml.builder import E
-from xml.etree import ElementTree
-
-
-try:
- from collections import OrderedDict
-except ImportError:
- from ordereddict import OrderedDict
-
-
-from kimchi import config
-from kimchi import netinfo
-from kimchi import network as knetwork
-from kimchi import networkxml
-from kimchi import vnc
-from kimchi import xmlutils
-from kimchi.asynctask import AsyncTask
-from kimchi.distroloader import DistroLoader
-from kimchi.config import config as kconfig
-from kimchi.exception import InvalidOperation, InvalidParameter, IsoFormatError
-from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
-from kimchi.featuretests import FeatureTests
-from kimchi.isoinfo import IsoImage
-from kimchi.model_.libvirtconnection import LibvirtConnection
-from kimchi.model_.libvirtstoragepool import StoragePoolDef
-from kimchi.objectstore import ObjectStore
-from kimchi.scan import Scanner
-from kimchi.screenshot import VMScreenshot
-from kimchi.utils import get_enabled_plugins, is_digit, kimchi_log
-from kimchi.utils import patch_find_nfs_target
-from kimchi.vmtemplate import VMTemplate
-
-
-ISO_POOL_NAME = u'kimchi_isos'
-GUESTS_STATS_INTERVAL = 5
-HOST_STATS_INTERVAL = 1
-VM_STATIC_UPDATE_PARAMS = {'name': './name'}
-VM_LIVE_UPDATE_PARAMS = {}
-STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
- 'path': '/pool/source/dir/@path'}}
-
-
-def _uri_to_name(collection, uri):
- expr = '/%s/(.*?)/?$' % collection
- m = re.match(expr, uri)
- if not m:
- raise InvalidParameter(uri)
- return m.group(1)
-
-def template_name_from_uri(uri):
- return _uri_to_name('templates', uri)
-
-def pool_name_from_uri(uri):
- return _uri_to_name('storagepools', uri)
-
-def get_vm_name(vm_name, t_name, name_list):
- if vm_name:
- return vm_name
- for i in xrange(1, 1000):
- vm_name = "%s-vm-%i" % (t_name, i)
- if vm_name not in name_list:
- return vm_name
- raise OperationFailed("Unable to choose a VM name")
-
-class Model(object):
- dom_state_map = {0: 'nostate',
- 1: 'running',
- 2: 'blocked',
- 3: 'paused',
- 4: 'shutdown',
- 5: 'shutoff',
- 6: 'crashed'}
-
- pool_state_map = {0: 'inactive',
- 1: 'initializing',
- 2: 'active',
- 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)
- self.objstore = ObjectStore(objstore_loc)
- self.next_taskid = 1
- self.stats = {}
- self.host_stats = defaultdict(int)
- self.host_info = {}
- self.qemu_stream = False
- self.qemu_stream_dns = False
- self.libvirt_stream_protocols = []
- # Subscribe function to set host capabilities to be run when cherrypy
- # server is up
- # It is needed because some features tests depends on the server
- cherrypy.engine.subscribe('start', self._set_capabilities)
- self.scanner = Scanner(self._clean_scan)
- self.scanner.delete()
- self.guests_stats_thread = BackgroundTask(GUESTS_STATS_INTERVAL,
- self._update_guests_stats)
- self.host_stats_thread = BackgroundTask(HOST_STATS_INTERVAL,
- self._update_host_stats)
- self.guests_stats_thread.start()
- self.host_stats_thread.start()
-
- # Please add new possible debug report command here
- # and implement the report generating function
- # based on the new report command
- self.report_tools = ({'cmd': 'sosreport --help', 'fn': self._sosreport_generate},
- {'cmd': 'supportconfig -h', 'fn':None},
- {'cmd': 'linuxexplorers --help', 'fn':None})
-
- self.distros = self._get_distros()
- if 'qemu:///' in self.libvirt_uri:
- self.host_info = self._get_host_info()
- self._default_pool_check()
- self._default_network_check()
-
- def _default_network_check(self):
- conn = self.conn.get()
- xml = """
- <network>
- <name>default</name>
- <forward mode='nat'/>
- <bridge name='virbr0' stp='on' delay='0' />
- <ip address='192.168.122.1' netmask='255.255.255.0'>
- <dhcp>
- <range start='192.168.122.2' end='192.168.122.254' />
- </dhcp>
- </ip>
- </network>
- """
- try:
- net = conn.networkLookupByName("default")
- except libvirt.libvirtError:
- try:
- net = conn.networkDefineXML(xml)
- except libvirt.libvirtError, e:
- cherrypy.log.error(
- "Fatal: Cannot create default network because of %s, exit kimchid" % e.message,
- severity=logging.ERROR)
- sys.exit(1)
-
- if net.isActive() == 0:
- try:
- net.create()
- except libvirt.libvirtError, e:
- cherrypy.log.error(
- "Fatal: Cannot activate default network because of %s, exit kimchid" % e.message,
- severity=logging.ERROR)
- sys.exit(1)
-
- def _default_pool_check(self):
- default_pool = {'name': 'default',
- 'path': '/var/lib/libvirt/images',
- 'type': 'dir'}
- try:
- self.storagepools_create(default_pool)
- except InvalidOperation:
- # ignore error when pool existed
- pass
- except OperationFailed as e:
- # path used by other pool or other reasons of failure, exit
- cherrypy.log.error(
- "Fatal: Cannot create default pool because of %s, exit kimchid" % e.message,
- severity=logging.ERROR)
- sys.exit(1)
-
- if self.storagepool_lookup('default')['state'] == 'inactive':
- try:
- self.storagepool_activate('default')
- except OperationFailed:
- cherrypy.log.error(
- "Fatal: Default pool cannot be activated, exit kimchid",
- severity=logging.ERROR)
- sys.exit(1)
-
- def _set_capabilities(self):
- kimchi_log.info("*** Running feature tests ***")
- self.qemu_stream = FeatureTests.qemu_supports_iso_stream()
- self.qemu_stream_dns = FeatureTests.qemu_iso_stream_dns()
- self.nfs_target_probe = FeatureTests.libvirt_support_nfs_probe()
-
- self.libvirt_stream_protocols = []
- for p in ['http', 'https', 'ftp', 'ftps', 'tftp']:
- if FeatureTests.libvirt_supports_iso_stream(p):
- self.libvirt_stream_protocols.append(p)
-
- kimchi_log.info("*** Feature tests completed ***")
- _set_capabilities.priority = 90
-
- def get_capabilities(self):
- report_tool = self._get_system_report_tool()
-
- return {'libvirt_stream_protocols': self.libvirt_stream_protocols,
- 'qemu_stream': self.qemu_stream,
- 'screenshot': VMScreenshot.get_stream_test_result(),
- 'system_report_tool': bool(report_tool)}
-
- def _update_guests_stats(self):
- vm_list = self.vms_get_list()
-
- for name in vm_list:
- dom = self._get_vm(name)
- vm_uuid = dom.UUIDString()
- info = dom.info()
- state = Model.dom_state_map[info[0]]
-
- if state != 'running':
- self.stats[vm_uuid] = {}
- continue
-
- if self.stats.get(vm_uuid, None) is None:
- self.stats[vm_uuid] = {}
-
- timestamp = time.time()
- prevStats = self.stats.get(vm_uuid, {})
- seconds = timestamp - prevStats.get('timestamp', 0)
- self.stats[vm_uuid].update({'timestamp': timestamp})
-
- self._get_percentage_cpu_usage(vm_uuid, info, seconds)
- self._get_network_io_rate(vm_uuid, dom, seconds)
- self._get_disk_io_rate(vm_uuid, dom, seconds)
-
- def _get_host_info(self):
- res = {}
- with open('/proc/cpuinfo') as f:
- for line in f.xreadlines():
- if "model name" in line:
- res['cpu'] = line.split(':')[1].strip()
- break
-
- res['memory'] = psutil.TOTAL_PHYMEM
- # 'fedora' '17' 'Beefy Miracle'
- distro, version, codename = platform.linux_distribution()
- res['os_distro'] = distro
- res['os_version'] = version
- res['os_codename'] = unicode(codename,"utf-8")
-
- return res
-
- def _get_percentage_cpu_usage(self, vm_uuid, info, seconds):
- prevCpuTime = self.stats[vm_uuid].get('cputime', 0)
-
- cpus = info[3]
- cpuTime = info[4] - prevCpuTime
-
- base = (((cpuTime) * 100.0) / (seconds * 1000.0 * 1000.0 * 1000.0))
- percentage = max(0.0, min(100.0, base / cpus))
-
- self.stats[vm_uuid].update({'cputime': info[4], 'cpu': percentage})
-
- def _get_network_io_rate(self, vm_uuid, dom, seconds):
- prevNetRxKB = self.stats[vm_uuid].get('netRxKB', 0)
- prevNetTxKB = self.stats[vm_uuid].get('netTxKB', 0)
- currentMaxNetRate = self.stats[vm_uuid].get('max_net_io', 100)
-
- rx_bytes = 0
- tx_bytes = 0
-
- tree = ElementTree.fromstring(dom.XMLDesc(0))
- for target in tree.findall('devices/interface/target'):
- dev = target.get('dev')
- io = dom.interfaceStats(dev)
- rx_bytes += io[0]
- tx_bytes += io[4]
-
- netRxKB = float(rx_bytes) / 1000
- netTxKB = float(tx_bytes) / 1000
-
- rx_stats = (netRxKB - prevNetRxKB) / seconds
- tx_stats = (netTxKB - prevNetTxKB) / seconds
-
- rate = rx_stats + tx_stats
- max_net_io = round(max(currentMaxNetRate, int(rate)), 1)
-
- self.stats[vm_uuid].update({'net_io': rate, 'max_net_io': max_net_io,
- 'netRxKB': netRxKB, 'netTxKB': netTxKB})
-
- def _get_disk_io_rate(self, vm_uuid, dom, seconds):
- prevDiskRdKB = self.stats[vm_uuid].get('diskRdKB', 0)
- prevDiskWrKB = self.stats[vm_uuid].get('diskWrKB', 0)
- currentMaxDiskRate = self.stats[vm_uuid].get('max_disk_io', 100)
-
- rd_bytes = 0
- wr_bytes = 0
-
- tree = ElementTree.fromstring(dom.XMLDesc(0))
- for target in tree.findall("devices/disk/target"):
- dev = target.get("dev")
- io = dom.blockStats(dev)
- rd_bytes += io[1]
- wr_bytes += io[3]
-
- diskRdKB = float(rd_bytes) / 1024
- diskWrKB = float(wr_bytes) / 1024
-
- rd_stats = (diskRdKB - prevDiskRdKB) / seconds
- wr_stats = (diskWrKB - prevDiskWrKB) / seconds
-
- rate = rd_stats + wr_stats
- max_disk_io = round(max(currentMaxDiskRate, int(rate)), 1)
-
- self.stats[vm_uuid].update({'disk_io': rate, 'max_disk_io': max_disk_io,
- 'diskRdKB': diskRdKB, 'diskWrKB': diskWrKB})
-
- def debugreport_lookup(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name)
- file_pattern = file_pattern + '.*'
- try:
- file_target = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError('no such report')
-
- ctime = os.stat(file_target).st_ctime
- ctime = time.strftime("%Y-%m-%d-%H:%M:%S", time.localtime(ctime))
- file_target = os.path.split(file_target)[-1]
- file_target = os.path.join("/data/debugreports", file_target)
- return {'file': file_target,
- 'ctime': ctime}
-
- def debugreportcontent_lookup(self, name):
- return self.debugreport_lookup(name)
-
- def debugreport_delete(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name + '.*')
- try:
- file_target = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError('no such report')
-
- os.remove(file_target)
-
- def debugreports_create(self, params):
- ident = params['name']
- taskid = self._gen_debugreport_file(ident)
- return self.task_lookup(taskid)
-
- def debugreports_get_list(self):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, '*.*')
- file_lists = glob.glob(file_pattern)
- file_lists = [os.path.split(file)[1] for file in file_lists]
- name_lists = [file.split('.', 1)[0] for file in file_lists]
-
- return name_lists
-
- def _update_host_stats(self):
- preTimeStamp = self.host_stats['timestamp']
- timestamp = time.time()
- # FIXME when we upgrade psutil, we can get uptime by psutil.uptime
- # we get uptime by float(open("/proc/uptime").readline().split()[0])
- # and calculate the first io_rate after the OS started.
- seconds = (timestamp - preTimeStamp if preTimeStamp else
- float(open("/proc/uptime").readline().split()[0]))
-
- self.host_stats['timestamp'] = timestamp
- self._get_host_disk_io_rate(seconds)
- self._get_host_network_io_rate(seconds)
-
- self._get_percentage_host_cpu_usage()
- self._get_host_memory_stats()
-
- def _get_percentage_host_cpu_usage(self):
- # This is cpu usage producer. This producer will calculate the usage
- # at an interval of HOST_STATS_INTERVAL.
- # The psutil.cpu_percent works as non blocking.
- # psutil.cpu_percent maintains a cpu time sample.
- # It will update the cpu time sample when it is called.
- # So only this producer can call psutil.cpu_percent in kimchi.
- self.host_stats['cpu_utilization'] = psutil.cpu_percent(None)
-
- def _get_host_memory_stats(self):
- virt_mem = psutil.virtual_memory()
- # available:
- # the actual amount of available memory that can be given
- # instantly to processes that request more memory in bytes; this
- # is calculated by summing different memory values depending on
- # the platform (e.g. free + buffers + cached on Linux)
- memory_stats = {'total': virt_mem.total,
- 'free': virt_mem.free,
- 'cached': virt_mem.cached,
- 'buffers': virt_mem.buffers,
- 'avail': virt_mem.available}
- self.host_stats['memory'] = memory_stats
-
- def _get_host_disk_io_rate(self, seconds):
- prev_read_bytes = self.host_stats['disk_read_bytes']
- prev_write_bytes = self.host_stats['disk_write_bytes']
-
- disk_io = psutil.disk_io_counters(False)
- read_bytes = disk_io.read_bytes
- write_bytes = disk_io.write_bytes
-
- rd_rate = int(float(read_bytes - prev_read_bytes) / seconds + 0.5)
- wr_rate = int(float(write_bytes - prev_write_bytes) / seconds + 0.5)
-
- self.host_stats.update({'disk_read_rate': rd_rate,
- 'disk_write_rate': wr_rate,
- 'disk_read_bytes': read_bytes,
- 'disk_write_bytes': write_bytes})
-
- def _get_host_network_io_rate(self, seconds):
- prev_recv_bytes = self.host_stats['net_recv_bytes']
- prev_sent_bytes = self.host_stats['net_sent_bytes']
-
- net_ios = psutil.network_io_counters(True)
- recv_bytes = 0
- sent_bytes = 0
- for key in set(netinfo.nics() +
- netinfo.wlans()) & set(net_ios.iterkeys()):
- recv_bytes = recv_bytes + net_ios[key].bytes_recv
- sent_bytes = sent_bytes + net_ios[key].bytes_sent
-
- rx_rate = int(float(recv_bytes - prev_recv_bytes) / seconds + 0.5)
- tx_rate = int(float(sent_bytes - prev_sent_bytes) / seconds + 0.5)
-
- self.host_stats.update({'net_recv_rate': rx_rate,
- 'net_sent_rate': tx_rate,
- 'net_recv_bytes': recv_bytes,
- 'net_sent_bytes': sent_bytes})
-
- def _static_vm_update(self, dom, params):
- state = Model.dom_state_map[dom.info()[0]]
-
- old_xml = new_xml = dom.XMLDesc(0)
-
- for key, val in params.items():
- if key in VM_STATIC_UPDATE_PARAMS:
- new_xml = xmlutils.xml_item_update(new_xml, VM_STATIC_UPDATE_PARAMS[key], val)
-
- try:
- if 'name' in params:
- if state == 'running':
- raise InvalidParameter("vm name can just updated when vm shutoff")
- else:
- dom.undefine()
- conn = self.conn.get()
- dom = conn.defineXML(new_xml)
- except libvirt.libvirtError as e:
- dom = conn.defineXML(old_xml)
- raise OperationFailed(e.get_error_message())
- return dom
-
- def _live_vm_update(self, dom, params):
- pass
-
- def vm_update(self, name, params):
- dom = self._get_vm(name)
- dom = self._static_vm_update(dom, params)
- self._live_vm_update(dom, params)
- return dom.name()
-
- def vm_lookup(self, name):
- dom = self._get_vm(name)
- info = dom.info()
- state = Model.dom_state_map[info[0]]
- screenshot = None
- graphics_type, graphics_listen, graphics_port = self._vm_get_graphics(name)
- graphics_port = graphics_port if state == 'running' else None
- try:
- if state == 'running':
- screenshot = self.vmscreenshot_lookup(name)
- elif state == 'shutoff':
- # reset vm stats when it is powered off to avoid sending
- # incorrect (old) data
- self.stats[dom.UUIDString()] = {}
- except NotFoundError:
- pass
-
- with self.objstore as session:
- try:
- extra_info = session.get('vm', dom.UUIDString())
- except NotFoundError:
- extra_info = {}
- icon = extra_info.get('icon')
-
- vm_stats = self.stats.get(dom.UUIDString(), {})
- stats = {}
- stats['cpu_utilization'] = vm_stats.get('cpu', 0)
- stats['net_throughput'] = vm_stats.get('net_io', 0)
- stats['net_throughput_peak'] = vm_stats.get('max_net_io', 100)
- stats['io_throughput'] = vm_stats.get('disk_io', 0)
- stats['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
-
- return {'state': state,
- 'stats': str(stats),
- 'uuid': dom.UUIDString(),
- 'memory': info[2] >> 10,
- 'cpus': info[3],
- 'screenshot': screenshot,
- 'icon': icon,
- 'graphics': {"type": graphics_type,
- "listen": graphics_listen,
- "port": graphics_port}
- }
-
- def _vm_get_disk_paths(self, dom):
- xml = dom.XMLDesc(0)
- xpath = "/domain/devices/disk[@device='disk']/source/@file"
- return xmlutils.xpath_get_text(xml, xpath)
-
- def _vm_get_networks(self, dom):
- xml = dom.XMLDesc(0)
- xpath = "/domain/devices/interface[@type='network']/source/@network"
- return xmlutils.xpath_get_text(xml, xpath)
-
- def vm_delete(self, name):
- if self._vm_exists(name):
- conn = self.conn.get()
- dom = self._get_vm(name)
- self._vmscreenshot_delete(dom.UUIDString())
- paths = self._vm_get_disk_paths(dom)
- info = self.vm_lookup(name)
-
- if info['state'] == 'running':
- self.vm_stop(name)
-
- dom.undefine()
-
- for path in paths:
- vol = conn.storageVolLookupByPath(path)
- vol.delete(0)
-
- with self.objstore as session:
- session.delete('vm', dom.UUIDString(), ignore_missing=True)
-
- vnc.remove_proxy_token(name)
-
- def vm_start(self, name):
- dom = self._get_vm(name)
- dom.create()
-
- def vm_stop(self, name):
- if self._vm_exists(name):
- dom = self._get_vm(name)
- dom.destroy()
-
- def _vm_get_graphics(self, name):
- dom = self._get_vm(name)
- xml = dom.XMLDesc(0)
- expr = "/domain/devices/graphics/@type"
- res = xmlutils.xpath_get_text(xml, expr)
- graphics_type = res[0] if res else None
- expr = "/domain/devices/graphics/@listen"
- res = xmlutils.xpath_get_text(xml, expr)
- graphics_listen = res[0] if res else None
- graphics_port = None
- if graphics_type:
- expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type
- res = xmlutils.xpath_get_text(xml, expr)
- graphics_port = int(res[0]) if res else None
- return graphics_type, graphics_listen, graphics_port
-
- def vm_connect(self, name):
- graphics_type, graphics_listen, graphics_port \
- = self._vm_get_graphics(name)
- if graphics_port is not None:
- vnc.add_proxy_token(name, graphics_port)
- else:
- raise OperationFailed("Only able to connect to running vm's vnc "
- "graphics.")
-
- def vms_create(self, params):
- conn = self.conn.get()
- t_name = template_name_from_uri(params['template'])
- vm_uuid = str(uuid.uuid4())
- vm_list = self.vms_get_list()
- name = get_vm_name(params.get('name'), t_name, vm_list)
- # incoming text, from js json, is unicode, do not need decode
- if name in vm_list:
- raise InvalidOperation("VM already exists")
-
- vm_overrides = dict()
- pool_uri = params.get('storagepool')
- if pool_uri:
- vm_overrides['storagepool'] = pool_uri
- t = self._get_template(t_name, vm_overrides)
-
- if not self.qemu_stream and t.info.get('iso_stream', False):
- raise InvalidOperation("Remote ISO image is not supported by this server.")
-
- t.validate()
- vol_list = t.fork_vm_storage(vm_uuid)
-
- # Store the icon for displaying later
- icon = t.info.get('icon')
- if icon:
- with self.objstore as session:
- session.store('vm', vm_uuid, {'icon': icon})
-
- libvirt_stream = False if len(self.libvirt_stream_protocols) == 0 else True
- graphics = params.get('graphics')
-
- xml = t.to_vm_xml(name, vm_uuid,
- libvirt_stream=libvirt_stream,
- qemu_stream_dns=self.qemu_stream_dns,
- graphics=graphics)
- try:
- dom = conn.defineXML(xml.encode('utf-8'))
- except libvirt.libvirtError as e:
- for v in vol_list:
- vol = conn.storageVolLookupByPath(v['path'])
- vol.delete(0)
- raise OperationFailed(e.get_error_message())
- return name
-
- def vms_get_list(self):
- conn = self.conn.get()
- names = [dom.name().decode('utf-8') for dom in conn.listAllDomains(0)]
- return sorted(names, key=unicode.lower)
-
- def vmscreenshot_lookup(self, name):
- dom = self._get_vm(name)
- d_info = dom.info()
- vm_uuid = dom.UUIDString()
- if Model.dom_state_map[d_info[0]] != 'running':
- raise NotFoundError('No screenshot for stopped vm')
-
- screenshot = self._get_screenshot(vm_uuid)
- img_path = screenshot.lookup()
- # screenshot info changed after scratch generation
- with self.objstore as session:
- session.store('screenshot', vm_uuid, screenshot.info)
- return img_path
-
- def _vmscreenshot_delete(self, vm_uuid):
- screenshot = self._get_screenshot(vm_uuid)
- screenshot.delete()
- with self.objstore as session:
- session.delete('screenshot', vm_uuid)
-
- def template_lookup(self, name):
- t = self._get_template(name)
- return t.info
-
- def template_delete(self, name):
- with self.objstore as session:
- session.delete('template', name)
-
- def templates_create(self, params):
- name = params['name']
- for net_name in params.get(u'networks', []):
- try:
- self._get_network(net_name)
- except NotFoundError:
- raise InvalidParameter("Network '%s' specified by template "
- "does not exist" % net_name)
-
- with self.objstore as session:
- if name in session.get_list('template'):
- raise InvalidOperation("Template already exists")
- t = LibvirtVMTemplate(params, scan=True)
- session.store('template', name, t.info)
- return name
-
- def template_update(self, name, params):
- old_t = self.template_lookup(name)
- new_t = copy.copy(old_t)
-
- new_t.update(params)
- ident = name
-
- new_storagepool = new_t.get(u'storagepool', '')
- try:
- self._get_storagepool(pool_name_from_uri(new_storagepool))
- except Exception as e:
- raise InvalidParameter("Storagepool specified is not valid: %s." % e.message)
-
- for net_name in params.get(u'networks', []):
- try:
- self._get_network(net_name)
- except NotFoundError:
- raise InvalidParameter("Network '%s' specified by template "
- "does not exist" % net_name)
-
- self.template_delete(name)
- try:
- ident = self.templates_create(new_t)
- except:
- ident = self.templates_create(old_t)
- raise
- return ident
-
- def templates_get_list(self):
- with self.objstore as session:
- return session.get_list('template')
-
- def interfaces_get_list(self):
- return list(set(netinfo.all_favored_interfaces()) -
- set(self._get_all_networks_interfaces()))
-
- def interface_lookup(self, name):
- try:
- return netinfo.get_interface_info(name)
- except ValueError, e:
- raise NotFoundError(e)
-
- def _get_network(self, name):
- conn = self.conn.get()
- try:
- return conn.networkLookupByName(name)
- except libvirt.libvirtError as e:
- raise NotFoundError("Network '%s' not found: %s" %
- (name, e.get_error_message()))
-
- def _get_network_from_xml(self, xml):
- address = xmlutils.xpath_get_text(xml, "/network/ip/@address")
- address = address and address[0] or ''
- netmask = xmlutils.xpath_get_text(xml, "/network/ip/@netmask")
- netmask = netmask and netmask[0] or ''
- net = address and netmask and "/".join([address, netmask]) or ''
-
- dhcp_start = xmlutils.xpath_get_text(xml,
- "/network/ip/dhcp/range/@start")
- dhcp_start = dhcp_start and dhcp_start[0] or ''
- dhcp_end = xmlutils.xpath_get_text(xml, "/network/ip/dhcp/range/@end")
- dhcp_end = dhcp_end and dhcp_end[0] or ''
- dhcp = {'start': dhcp_start, 'end': dhcp_end}
-
- forward_mode = xmlutils.xpath_get_text(xml, "/network/forward/@mode")
- forward_mode = forward_mode and forward_mode[0] or ''
- forward_if = xmlutils.xpath_get_text(xml,
- "/network/forward/interface/@dev")
- forward_pf = xmlutils.xpath_get_text(xml, "/network/forward/pf/@dev")
- bridge = xmlutils.xpath_get_text(xml, "/network/bridge/@name")
- bridge = bridge and bridge[0] or ''
- return {'subnet': net, 'dhcp': dhcp, 'bridge': bridge,
- 'forward': {'mode': forward_mode,
- 'interface': forward_if,
- 'pf': forward_pf}}
-
- def _get_all_networks_interfaces(self):
- net_names = self.networks_get_list()
- interfaces = []
- for name in net_names:
- network = self._get_network(name)
- xml = network.XMLDesc(0)
- net_dict = self._get_network_from_xml(xml)
- forward = net_dict['forward']
- (forward['mode'] == 'bridge' and forward['interface'] and
- interfaces.append(forward['interface'][0]) is None or
- interfaces.extend(forward['interface'] + forward['pf']))
- net_dict['bridge'] and interfaces.append(net_dict['bridge'])
- return interfaces
-
- def _set_network_subnet(self, params):
- netaddr = params.get('subnet', '')
- net_addrs = []
- # lookup a free network address for nat and isolated automatically
- if not netaddr:
- for net_name in self.networks_get_list():
- network = self._get_network(net_name)
- xml = network.XMLDesc(0)
- subnet = self._get_network_from_xml(xml)['subnet']
- subnet and net_addrs.append(ipaddr.IPNetwork(subnet))
- netaddr = knetwork.get_one_free_network(net_addrs)
- if not netaddr:
- raise OperationFailed("can not find a free IP address "
- "for network '%s'" %
- params['name'])
- try:
- ip = ipaddr.IPNetwork(netaddr)
- except ValueError as e:
- raise InvalidParameter("%s" % e)
- if ip.ip == ip.network:
- ip.ip = ip.ip + 1
- dhcp_start = str(ip.ip + ip.numhosts / 2)
- dhcp_end = str(ip.ip + ip.numhosts - 2)
- params.update({'net': str(ip),
- 'dhcp': {'range': {'start': dhcp_start,
- 'end': dhcp_end}}})
-
- def _set_network_bridge(self, params):
- try:
- iface = params['interface']
- if iface in self._get_all_networks_interfaces():
- raise InvalidParameter("interface '%s' already in use." %
- iface)
- except KeyError, e:
- raise MissingParameter(e)
- if netinfo.is_bridge(iface):
- params['bridge'] = iface
- elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface):
- if params.get('vlan_id') is None:
- params['forward']['dev'] = iface
- else:
- params['bridge'] = \
- self._create_vlan_tagged_bridge(str(iface),
- str(params['vlan_id']))
- else:
- raise InvalidParameter("the interface should be bare nic, "
- "bonding or bridge device.")
-
- def networks_create(self, params):
- conn = self.conn.get()
- name = params['name']
- if name in self.networks_get_list():
- raise InvalidOperation("Network %s already exists" % name)
-
- connection = params["connection"]
- # set forward mode, isolated do not need forward
- if connection != 'isolated':
- params['forward'] = {'mode': connection}
-
- # set subnet, bridge network do not need subnet
- if connection in ["nat", 'isolated']:
- self._set_network_subnet(params)
-
- # only bridge network need bridge(linux bridge) or interface(macvtap)
- if connection == 'bridge':
- self._set_network_bridge(params)
-
- xml = networkxml.to_network_xml(**params)
-
- try:
- network = conn.networkDefineXML(xml)
- network.setAutostart(True)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- return name
-
- def networks_get_list(self):
- conn = self.conn.get()
- return sorted(conn.listNetworks() + conn.listDefinedNetworks())
-
- def _get_vms_attach_to_a_network(self, network):
- vms = []
- conn = self.conn.get()
- for dom in conn.listAllDomains(0):
- networks = self._vm_get_networks(dom)
- if network in networks:
- vms.append(dom.name())
- return vms
-
- def network_lookup(self, name):
- network = self._get_network(name)
- xml = network.XMLDesc(0)
- net_dict = self._get_network_from_xml(xml)
- subnet = net_dict['subnet']
- dhcp = net_dict['dhcp']
- forward = net_dict['forward']
- interface = net_dict['bridge']
-
- connection = forward['mode'] or "isolated"
- # FIXME, if we want to support other forward mode well.
- if connection == 'bridge':
- # macvtap bridge
- interface = interface or forward['interface'][0]
- # exposing the network on linux bridge or macvtap interface
- interface_subnet = knetwork.get_dev_netaddr(interface)
- subnet = subnet if subnet else interface_subnet
-
- # libvirt use format 192.168.0.1/24, standard should be 192.168.0.0/24
- # http://www.ovirt.org/File:Issue3.png
- if subnet:
- subnet = ipaddr.IPNetwork(subnet)
- subnet = "%s/%s" % (subnet.network, subnet.prefixlen)
-
- return {'connection': connection,
- 'interface': interface,
- 'subnet': subnet,
- 'dhcp': dhcp,
- 'vms': self._get_vms_attach_to_a_network(name),
- 'autostart': network.autostart() == 1,
- 'state': network.isActive() and "active" or "inactive"}
-
- def network_activate(self, name):
- network = self._get_network(name)
- network.create()
-
- def network_deactivate(self, name):
- network = self._get_network(name)
- network.destroy()
-
- def network_delete(self, name):
- network = self._get_network(name)
- if network.isActive():
- raise InvalidOperation(
- "Unable to delete the active network %s" % name)
- self._remove_vlan_tagged_bridge(network)
- network.undefine()
-
- def _get_vlan_tagged_bridge_name(self, interface, vlan_id):
- return '-'.join(('kimchi', interface, vlan_id))
-
- def _is_vlan_tagged_bridge(self, bridge):
- return bridge.startswith('kimchi-')
-
- def _create_vlan_tagged_bridge(self, interface, vlan_id):
- br_name = self._get_vlan_tagged_bridge_name(interface, vlan_id)
- br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface,
- vlan_id)
- conn = self.conn.get()
- conn.changeBegin()
- try:
- vlan_tagged_br = conn.interfaceDefineXML(br_xml)
- vlan_tagged_br.create()
- except libvirt.libvirtError as e:
- conn.changeRollback()
- raise OperationFailed(e.message)
- else:
- conn.changeCommit()
- return br_name
-
- def _remove_vlan_tagged_bridge(self, network):
- try:
- bridge = network.bridgeName()
- except libvirt.libvirtError:
- pass
- else:
- if self._is_vlan_tagged_bridge(bridge):
- conn = self.conn.get()
- iface = conn.interfaceLookupByName(bridge)
- if iface.isActive():
- iface.destroy()
- iface.undefine()
-
- def vmifaces_create(self, vm, params):
- def randomMAC():
- mac = [0x52, 0x54, 0x00,
- random.randint(0x00, 0x7f),
- random.randint(0x00, 0xff),
- random.randint(0x00, 0xff)]
- return ':'.join(map(lambda x: "%02x" % x, mac))
-
- if (params["type"] == "network" and
- params["network"] not in self.networks_get_list()):
- raise InvalidParameter("%s is not an available network" %
- params["network"])
-
- dom = self._get_vm(vm)
- if Model.dom_state_map[dom.info()[0]] != "shutoff":
- raise InvalidOperation("do not support hot plugging attach "
- "guest interface")
-
- macs = (iface.mac.get('address')
- for iface in self._get_vmifaces(vm))
-
- mac = randomMAC()
- while True:
- if mac not in macs:
- break
- mac = randomMAC()
-
- children = [E.mac(address=mac)]
- ("network" in params.keys() and
- children.append(E.source(network=params['network'])))
- ("model" in params.keys() and
- children.append(E.model(type=params['model'])))
- attrib = {"type": params["type"]}
-
- xml = etree.tostring(E.interface(*children, **attrib))
-
- dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
-
- return mac
-
- def _get_vmifaces(self, vm):
- dom = self._get_vm(vm)
- xml = dom.XMLDesc(0)
- root = objectify.fromstring(xml)
-
- return root.devices.findall("interface")
-
- def _get_vmiface(self, vm, mac):
- ifaces = self._get_vmifaces(vm)
-
- for iface in ifaces:
- if iface.mac.get('address') == mac:
- return iface
- return None
-
- def vmifaces_get_list(self, vm):
- return [iface.mac.get('address') for iface in self._get_vmifaces(vm)]
-
- def vmiface_lookup(self, vm, mac):
- info = {}
-
- iface = self._get_vmiface(vm, mac)
- if iface is None:
- raise NotFoundError('iface: "%s"' % mac)
-
- info['type'] = iface.attrib['type']
- info['mac'] = iface.mac.get('address')
- if iface.find("model") is not None:
- info['model'] = iface.model.get('type')
- if info['type'] == 'network':
- info['network'] = iface.source.get('network')
- if info['type'] == 'bridge':
- info['bridge'] = iface.source.get('bridge')
-
- return info
-
- def vmiface_delete(self, vm, mac):
- dom = self._get_vm(vm)
- iface = self._get_vmiface(vm, mac)
-
- if Model.dom_state_map[dom.info()[0]] != "shutoff":
- raise InvalidOperation("do not support hot plugging detach "
- "guest interface")
- if iface is None:
- raise NotFoundError('iface: "%s"' % mac)
-
- dom.detachDeviceFlags(etree.tostring(iface),
- libvirt.VIR_DOMAIN_AFFECT_CURRENT)
-
- def add_task(self, target_uri, fn, opaque=None):
- id = self.next_taskid
- self.next_taskid = self.next_taskid + 1
-
- task = AsyncTask(id, target_uri, fn, self.objstore, opaque)
-
- return id
-
- def tasks_get_list(self):
- with self.objstore as session:
- return session.get_list('task')
-
- def task_lookup(self, id):
- with self.objstore as session:
- return session.get('task', str(id))
-
- def _vm_exists(self, name):
- try:
- self._get_vm(name)
- return True
- except NotFoundError:
- return False
- except:
- raise
-
-
- def _get_vm(self, name):
- conn = self.conn.get()
- try:
- # outgoing text to libvirt, encode('utf-8')
- return conn.lookupByName(name.encode("utf-8"))
- except libvirt.libvirtError as e:
- if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
- raise NotFoundError("Virtual Machine '%s' not found" % name)
- else:
- raise
-
- def _get_template(self, name, overrides=None):
- with self.objstore as session:
- params = session.get('template', name)
- if overrides:
- params.update(overrides)
- return LibvirtVMTemplate(params, False, self.conn)
-
- def isopool_lookup(self, name):
- return {'state': 'active',
- 'type': 'kimchi-iso'}
-
- def isovolumes_get_list(self):
- iso_volumes = []
- pools = self.storagepools_get_list()
-
- for pool in pools:
- try:
- volumes = self.storagevolumes_get_list(pool)
- except InvalidOperation:
- # Skip inactive pools
- continue
- for volume in volumes:
- res = self.storagevolume_lookup(pool, volume)
- if res['format'] == 'iso':
- res['name'] = '%s' % volume
- iso_volumes.append(res)
- return iso_volumes
-
- def _clean_scan(self, pool_name):
- try:
- self.storagepool_deactivate(pool_name)
- with self.objstore as session:
- session.delete('scanning', pool_name)
- except Exception, e:
- kimchi_log.debug("Exception %s occured when cleaning scan result" % 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.storagepools_get_list():
- try:
- res = self.storagepool_lookup(pool)
- if res['state'] == 'active':
- scan_params['ignore_list'].append(res['path'])
- except Exception, e:
- kimchi_log.debug("Exception %s occured when get ignore path" % e.message)
-
- params['path'] = scan_params['pool_path'] = self.scanner.scan_dir_prepare(
- params['name'])
- 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 storagepools_create(self, params):
- task_id = None
- conn = self.conn.get()
- try:
- name = params['name']
- if name in (ISO_POOL_NAME, ):
- raise InvalidOperation("StoragePool already exists")
-
- if params['type'] == 'kimchi-iso':
- task_id = self._do_deep_scan(params)
- poolDef = StoragePoolDef.create(params)
- poolDef.prepare(conn)
- xml = poolDef.xml
- except KeyError, key:
- raise MissingParameter(key)
-
- if name in self.storagepools_get_list():
- raise InvalidOperation(
- "The name %s has been used by a pool" % name)
-
- 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())
- return name
-
- 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 storagepool_lookup(self, name):
- pool = self._get_storagepool(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': Model.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 storagepool_update(self, name, params):
- autostart = params['autostart']
- if autostart not in [True, False]:
- raise InvalidOperation("Autostart flag must be true or false")
- pool = self._get_storagepool(name)
- if autostart:
- pool.setAutostart(1)
- else:
- pool.setAutostart(0)
- ident = pool.name()
- return ident
-
- def storagepool_activate(self, name):
- pool = self._get_storagepool(name)
- try:
- pool.create(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def storagepool_deactivate(self, name):
- pool = self._get_storagepool(name)
- try:
- pool.destroy()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _pool_refresh(self, pool):
- try:
- pool.refresh(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _get_storagepool_vols_num(self, pool):
- try:
- if pool.isActive():
- self._pool_refresh(pool)
- return pool.numOfVolumes()
- else:
- return 0
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def storagepool_delete(self, name):
- pool = self._get_storagepool(name)
- if pool.isActive():
- raise InvalidOperation(
- "Unable to delete the active storagepool %s" % name)
- try:
- pool.undefine()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def storagepools_get_list(self):
- try:
- conn = self.conn.get()
- names = conn.listStoragePools()
- names += conn.listDefinedStoragePools()
- return sorted(names)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _get_storagepool(self, name):
- conn = self.conn.get()
- try:
- return conn.storagePoolLookupByName(name)
- except libvirt.libvirtError as e:
- if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_POOL:
- raise NotFoundError("Storage Pool '%s' not found" % name)
- else:
- raise
-
- def storagevolumes_create(self, pool, params):
- info = self.storagepool_lookup(pool)
- try:
- name = params['name']
- xml = _get_volume_xml(**params)
- except KeyError, key:
- raise MissingParameter(key)
- pool = self._get_storagepool(pool)
- try:
- pool.createXML(xml, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
- return name
-
- def storagevolume_lookup(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=Model.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))
- os_distro = os_version = 'unknown'
- 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 storagevolume_wipe(self, pool, name):
- volume = self._get_storagevolume(pool, name)
- try:
- volume.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def storagevolume_delete(self, pool, name):
- volume = self._get_storagevolume(pool, name)
- try:
- volume.delete(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def storagevolume_resize(self, pool, name, size):
- size = size << 20
- volume = self._get_storagevolume(pool, name)
- try:
- volume.resize(size, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def storagevolumes_get_list(self, pool):
- pool = self._get_storagepool(pool)
- if not pool.isActive():
- raise InvalidOperation(
- "Unable to list volumes in inactive storagepool %s" % pool.name())
- try:
- self._pool_refresh(pool)
- return pool.listVolumes()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _get_storagevolume(self, pool, name):
- pool = self._get_storagepool(pool)
- if not pool.isActive():
- raise InvalidOperation(
- "Unable to list volumes in inactive storagepool %s" % pool.name())
- try:
- return pool.storageVolLookupByName(name)
- except libvirt.libvirtError as e:
- if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL:
- raise NotFoundError("Storage Volume '%s' not found" % name)
- else:
- raise
-
- def storageservers_get_list(self, _target_type=None):
- target_type = STORAGE_SOURCES.keys() if not _target_type else [_target_type]
- pools = self.storagepools_get_list()
- server_list = []
- for pool in pools:
- try:
- pool_info = self.storagepool_lookup(pool)
- if (pool_info['type'] in target_type and
- pool_info['source']['addr'] not in server_list):
- # Avoid to add same server for multiple times
- # if it hosts more than one storage type
- server_list.append(pool_info['source']['addr'])
- except NotFoundError:
- pass
-
- return server_list
-
- def storageserver_lookup(self, server):
- pools = self.storagepools_get_list()
- for pool in pools:
- try:
- pool_info = self.storagepool_lookup(pool)
- if pool_info['source'] and pool_info['source']['addr'] == server:
- return dict(host=server)
- except NotFoundError:
- # Avoid inconsistent pool result because of lease between list and lookup
- pass
-
- raise NotFoundError('server %s does not used by kimchi' % server)
-
- def storagetargets_get_list(self, storage_server, _target_type=None):
- target_types = STORAGE_SOURCES.keys() if not _target_type else [_target_type]
- target_list = list()
-
- for target_type in target_types:
- if not self.nfs_target_probe and target_type == 'netfs':
- targets = patch_find_nfs_target(storage_server)
- else:
- xml = _get_storage_server_spec(server=storage_server, target_type=target_type)
- conn = self.conn.get()
-
- try:
- ret = conn.findStoragePoolSources(target_type, xml, 0)
- except libvirt.libvirtError as e:
- kimchi_log.warning("Query storage pool source fails because of %s",
- e.get_error_message())
- continue
- targets = _parse_target_source_result(target_type, ret)
-
- target_list.extend(targets)
- return target_list
-
- def _get_screenshot(self, vm_uuid):
- with self.objstore as session:
- try:
- params = session.get('screenshot', vm_uuid)
- except NotFoundError:
- params = {'uuid': vm_uuid}
- session.store('screenshot', vm_uuid, params)
- return LibvirtVMScreenshot(params, self.conn)
-
- def _sosreport_generate(self, cb, name):
- command = 'sosreport --batch --name "%s"' % name
- try:
- retcode = subprocess.call(command, shell=True, stdout=subprocess.PIPE)
- if retcode < 0:
- raise OperationFailed('Command terminated with signal')
- elif retcode > 0:
- raise OperationFailed('Command failed: rc = %i' % retcode)
- pattern = '/tmp/sosreport-%s-*' % name
- for reportFile in glob.glob(pattern):
- if not fnmatch.fnmatch(reportFile, '*.md5'):
- output = reportFile
- break
- else:
- # sosreport tends to change the name mangling rule and
- # compression file format between different releases.
- # It's possible to fail to match a report file even sosreport
- # runs successfully. In future we might have a general name
- # mangling function in kimchi to format the name before passing
- # it to sosreport. Then we can delete this exception.
- raise OperationFailed('Can not find generated debug report '
- 'named by %s' % pattern)
- ext = output.split('.', 1)[1]
- path = config.get_debugreports_path()
- target = os.path.join(path, name)
- target_file = '%s.%s' % (target, ext)
- shutil.move(output, target_file)
- os.remove('%s.md5' % output)
- cb('OK', True)
-
- return
-
- except OSError:
- raise
-
- except Exception, e:
- # No need to call cb to update the task status here.
- # The task object will catch the exception rasied here
- # and update the task status there
- log = logging.getLogger('Model')
- log.warning('Exception in generating debug file: %s', e)
- raise OperationFailed(e)
-
- def _get_system_report_tool(self):
- # check if the command can be found by shell one by one
- for helper_tool in self.report_tools:
- try:
- retcode = subprocess.call(helper_tool['cmd'], shell=True,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- if retcode == 0:
- return helper_tool['fn']
- except Exception, e:
- kimchi_log.info('Exception running command: %s', e)
-
- return None
-
- def _gen_debugreport_file(self, name):
- gen_cmd = self._get_system_report_tool()
-
- if gen_cmd is not None:
- return self.add_task('', gen_cmd, name)
-
- raise OperationFailed("debugreport tool not found")
-
- def _get_distros(self):
- distroloader = DistroLoader()
- return distroloader.get()
-
- def distros_get_list(self):
- return self.distros.keys()
-
- def distro_lookup(self, name):
- try:
- return self.distros[name]
- except KeyError:
- raise NotFoundError("distro '%s' not found" % name)
-
- def host_lookup(self, *name):
- return self.host_info
-
- def hoststats_lookup(self, *name):
- return {'cpu_utilization': self.host_stats['cpu_utilization'],
- 'memory': self.host_stats.get('memory'),
- 'disk_read_rate': self.host_stats['disk_read_rate'],
- 'disk_write_rate': self.host_stats['disk_write_rate'],
- 'net_recv_rate': self.host_stats['net_recv_rate'],
- 'net_sent_rate': self.host_stats['net_sent_rate']}
-
- def plugins_get_list(self):
- return [plugin for (plugin, config) in get_enabled_plugins()]
-
- def partitions_get_list(self):
- result = disks.get_partitions_names()
- return result
-
- def partition_lookup(self, name):
- if name not in disks.get_partitions_names():
- raise NotFoundError("Partition %s not found in the host"
- % name)
- return disks.get_partition_details(name)
-
- def vms_get_list_by_state(self, state):
- ret_list = []
- for name in self.vms_get_list():
- info = self._get_vm(name).info()
- if (Model.dom_state_map[info[0]]) == state:
- ret_list.append(name)
- return ret_list
-
- def host_shutdown(self, args=None):
- # Check for running vms before shutdown
- running_vms = self.vms_get_list_by_state('running')
- if len(running_vms) > 0:
- raise OperationFailed("Shutdown not allowed: VMs are running!")
- kimchi_log.info('Host is going to shutdown.')
- os.system('shutdown -h now')
-
- def host_reboot(self, args=None):
- # Find running VMs
- running_vms = self.vms_get_list_by_state('running')
- if len(running_vms) > 0:
- raise OperationFailed("Reboot not allowed: VMs are running!")
- kimchi_log.info('Host is going to reboot.')
- os.system('reboot')
-
- def config_lookup(self, name):
- return {'http_port': cherrypy.server.socket_port,
- 'display_proxy_port':
- kconfig.get('display', 'display_proxy_port')}
-
-
-
-class LibvirtVMTemplate(VMTemplate):
- def __init__(self, args, scan=False, conn=None):
- VMTemplate.__init__(self, args, scan)
- self.conn = conn
-
- def _storage_validate(self):
- pool_uri = self.info['storagepool']
- pool_name = pool_name_from_uri(pool_uri)
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(pool_name)
- except libvirt.libvirtError:
- raise InvalidParameter('Storage specified by template does not exist')
- if not pool.isActive():
- raise InvalidParameter('Storage specified by template is not active')
-
- return pool
-
- def _network_validate(self):
- names = self.info['networks']
- for name in names:
- try:
- conn = self.conn.get()
- network = conn.networkLookupByName(name)
- except libvirt.libvirtError:
- raise InvalidParameter('Network specified by template does not exist')
- if not network.isActive():
- raise InvalidParameter('Network specified by template is not active')
-
- def _get_storage_path(self):
- pool = self._storage_validate()
- xml = pool.XMLDesc(0)
- return xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
-
- def fork_vm_storage(self, vm_uuid):
- # Provision storage:
- # TODO: Rebase on the storage API once upstream
- pool = self._storage_validate()
- vol_list = self.to_volume_list(vm_uuid)
- for v in vol_list:
- # outgoing text to libvirt, encode('utf-8')
- pool.createXML(v['xml'].encode('utf-8'), 0)
- return vol_list
-
-
-class LibvirtVMScreenshot(VMScreenshot):
- def __init__(self, vm_uuid, conn):
- VMScreenshot.__init__(self, vm_uuid)
- self.conn = conn
-
- def _generate_scratch(self, thumbnail):
- def handler(stream, buf, opaque):
- fd = opaque
- os.write(fd, buf)
-
- fd = os.open(thumbnail, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0644)
- try:
- conn = self.conn.get()
- dom = conn.lookupByUUIDString(self.vm_uuid)
- vm_name = dom.name()
- stream = conn.newStream(0)
- mimetype = dom.screenshot(stream, 0, 0)
- stream.recvAll(handler, fd)
- except libvirt.libvirtError:
- try:
- stream.abort()
- except:
- pass
- raise NotFoundError("Screenshot not supported for %s" % vm_name)
- else:
- stream.finish()
- finally:
- os.close(fd)
-
-
-def _parse_target_source_result(target_type, xml_str):
- root = objectify.fromstring(xml_str)
- ret = []
- for source in root.getchildren():
- if target_type == 'netfs':
- host_name = source.host.get('name')
- target_path = source.dir.get('path')
- type = source.format.get('type')
- ret.append(dict(host=host_name, target_type=type, target=target_path))
- return ret
-
-
-def _get_storage_server_spec(**kwargs):
- # Required parameters:
- # server:
- # target_type:
- extra_args = []
- if kwargs['target_type'] == 'netfs':
- extra_args.append(E.format(type='nfs'))
- obj = E.source(E.host(name=kwargs['server']), *extra_args)
- xml = ET.tostring(obj)
- return xml
-
-
-def _get_volume_xml(**kwargs):
- # Required parameters
- # name:
- # capacity:
- #
- # Optional:
- # allocation:
- # format:
- kwargs.setdefault('allocation', 0)
- kwargs.setdefault('format', 'qcow2')
-
- xml = """
- <volume>
- <name>%(name)s</name>
- <allocation unit="MiB">%(allocation)s</allocation>
- <capacity unit="MiB">%(capacity)s</capacity>
- <source>
- </source>
- <target>
- <format type='%(format)s'/>
- </target>
- </volume>
- """ % kwargs
- return xml
diff --git a/src/kimchi/model/__init__.py b/src/kimchi/model/__init__.py
new file mode 100644
index 0000000..8a37cc4
--- /dev/null
+++ b/src/kimchi/model/__init__.py
@@ -0,0 +1,21 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
diff --git a/src/kimchi/model/config.py b/src/kimchi/model/config.py
new file mode 100644
index 0000000..0e66e02
--- /dev/null
+++ b/src/kimchi/model/config.py
@@ -0,0 +1,98 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import cherrypy
+
+from kimchi.basemodel import Singleton
+from kimchi.config import config as kconfig
+from kimchi.distroloader import DistroLoader
+from kimchi.exception import NotFoundError
+from kimchi.featuretests import FeatureTests
+from kimchi.model.debugreports import DebugReportsModel
+from kimchi.screenshot import VMScreenshot
+from kimchi.utils import kimchi_log
+
+
+class ConfigModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ proxy_port = kconfig.get('display', 'display_proxy_port')
+ return {'http_port': cherrypy.server.socket_port,
+ 'display_proxy_port': proxy_port}
+
+
+class CapabilitiesModel(object):
+ __metaclass__ = Singleton
+
+ def __init__(self, **kargs):
+ self.qemu_stream = False
+ self.qemu_stream_dns = False
+ self.libvirt_stream_protocols = []
+
+ # Subscribe function to set host capabilities to be run when cherrypy
+ # server is up
+ # It is needed because some features tests depends on the server
+ cherrypy.engine.subscribe('start', self._set_capabilities)
+
+ def _set_capabilities(self):
+ kimchi_log.info("*** Running feature tests ***")
+ self.qemu_stream = FeatureTests.qemu_supports_iso_stream()
+ self.qemu_stream_dns = FeatureTests.qemu_iso_stream_dns()
+ self.nfs_target_probe = FeatureTests.libvirt_support_nfs_probe()
+
+ self.libvirt_stream_protocols = []
+ for p in ['http', 'https', 'ftp', 'ftps', 'tftp']:
+ if FeatureTests.libvirt_supports_iso_stream(p):
+ self.libvirt_stream_protocols.append(p)
+
+ kimchi_log.info("*** Feature tests completed ***")
+ _set_capabilities.priority = 90
+
+ def lookup(self, *ident):
+ report_tool = DebugReportsModel.get_system_report_tool()
+
+ return {'libvirt_stream_protocols': self.libvirt_stream_protocols,
+ 'qemu_stream': self.qemu_stream,
+ 'screenshot': VMScreenshot.get_stream_test_result(),
+ 'system_report_tool': bool(report_tool)}
+
+
+class DistrosModel(object):
+ def __init__(self, **kargs):
+ distroloader = DistroLoader()
+ self.distros = distroloader.get()
+
+ def get_list(self):
+ return self.distros.keys()
+
+
+class DistroModel(object):
+ def __init__(self, **kargs):
+ self._distros = DistrosModel()
+
+ def lookup(self, name):
+ try:
+ return self._distros.distros[name]
+ except KeyError:
+ raise NotFoundError("Distro '%s' not found." % name)
diff --git a/src/kimchi/model/debugreports.py b/src/kimchi/model/debugreports.py
new file mode 100644
index 0000000..a1cb19c
--- /dev/null
+++ b/src/kimchi/model/debugreports.py
@@ -0,0 +1,167 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import fnmatch
+import glob
+import logging
+import os
+import shutil
+import subprocess
+import time
+
+from kimchi import config
+from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.model.tasks import TaskModel
+from kimchi.utils import add_task, kimchi_log
+
+
+class DebugReportsModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+ self.task = TaskModel(**kargs)
+
+ def create(self, params):
+ ident = params['name']
+ taskid = self._gen_debugreport_file(ident)
+ return self.task.lookup(taskid)
+
+ def get_list(self):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, '*.*')
+ file_lists = glob.glob(file_pattern)
+ file_lists = [os.path.split(file)[1] for file in file_lists]
+ name_lists = [file.split('.', 1)[0] for file in file_lists]
+
+ return name_lists
+
+ def _gen_debugreport_file(self, name):
+ gen_cmd = self.get_system_report_tool()
+
+ if gen_cmd is not None:
+ return add_task('', gen_cmd, self.objstore, name)
+
+ raise OperationFailed("debugreport tool not found")
+
+ @staticmethod
+ def sosreport_generate(cb, name):
+ command = 'sosreport --batch --name "%s"' % name
+ try:
+ retcode = subprocess.call(command, shell=True,
+ stdout=subprocess.PIPE)
+ if retcode < 0:
+ raise OperationFailed('Command terminated with signal')
+ elif retcode > 0:
+ raise OperationFailed('Command failed: rc = %i' % retcode)
+ pattern = '/tmp/sosreport-%s-*' % name
+ for reportFile in glob.glob(pattern):
+ if not fnmatch.fnmatch(reportFile, '*.md5'):
+ output = reportFile
+ break
+ else:
+ # sosreport tends to change the name mangling rule and
+ # compression file format between different releases.
+ # It's possible to fail to match a report file even sosreport
+ # runs successfully. In future we might have a general name
+ # mangling function in kimchi to format the name before passing
+ # it to sosreport. Then we can delete this exception.
+ raise OperationFailed('Can not find generated debug report '
+ 'named by %s' % pattern)
+ ext = output.split('.', 1)[1]
+ path = config.get_debugreports_path()
+ target = os.path.join(path, name)
+ target_file = '%s.%s' % (target, ext)
+ shutil.move(output, target_file)
+ os.remove('%s.md5' % output)
+ cb('OK', True)
+
+ return
+
+ except OSError:
+ raise
+
+ except Exception, e:
+ # No need to call cb to update the task status here.
+ # The task object will catch the exception rasied here
+ # and update the task status there
+ log = logging.getLogger('Model')
+ log.warning('Exception in generating debug file: %s', e)
+ raise OperationFailed(e)
+
+ @staticmethod
+ def get_system_report_tool():
+ # Please add new possible debug report command here
+ # and implement the report generating function
+ # based on the new report command
+ report_tools = ({'cmd': 'sosreport --help',
+ 'fn': DebugReportsModel.sosreport_generate},)
+
+ # check if the command can be found by shell one by one
+ for helper_tool in report_tools:
+ try:
+ retcode = subprocess.call(helper_tool['cmd'], shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ if retcode == 0:
+ return helper_tool['fn']
+ except Exception, e:
+ kimchi_log.info('Exception running command: %s', e)
+
+ return None
+
+
+class DebugReportModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, name)
+ file_pattern = file_pattern + '.*'
+ try:
+ file_target = glob.glob(file_pattern)[0]
+ except IndexError:
+ raise NotFoundError('no such report')
+
+ ctime = os.stat(file_target).st_ctime
+ ctime = time.strftime("%Y-%m-%d-%H:%M:%S", time.localtime(ctime))
+ file_target = os.path.split(file_target)[-1]
+ file_target = os.path.join("/data/debugreports", file_target)
+ return {'file': file_target,
+ 'ctime': ctime}
+
+ def delete(self, name):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, name + '.*')
+ try:
+ file_target = glob.glob(file_pattern)[0]
+ except IndexError:
+ raise NotFoundError('no such report')
+
+ os.remove(file_target)
+
+
+class DebugReportContentModel(object):
+ def __init__(self, **kargs):
+ self._debugreport = DebugReportModel()
+
+ def lookup(self, name):
+ return self._debugreport.lookup(name)
diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
new file mode 100644
index 0000000..d5fc124
--- /dev/null
+++ b/src/kimchi/model/host.py
@@ -0,0 +1,201 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import os
+import time
+import platform
+from collections import defaultdict
+
+import psutil
+from cherrypy.process.plugins import BackgroundTask
+
+from kimchi import disks
+from kimchi import netinfo
+from kimchi.basemodel import Singleton
+from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.model.vms import DOM_STATE_MAP
+from kimchi.utils import kimchi_log
+
+
+HOST_STATS_INTERVAL = 1
+
+
+class HostModel(object):
+ def __init__(self, **kargs):
+ self.host_info = self._get_host_info()
+
+ def _get_host_info(self):
+ res = {}
+ with open('/proc/cpuinfo') as f:
+ for line in f.xreadlines():
+ if "model name" in line:
+ res['cpu'] = line.split(':')[1].strip()
+ break
+
+ res['memory'] = psutil.TOTAL_PHYMEM
+ # 'fedora' '17' 'Beefy Miracle'
+ distro, version, codename = platform.linux_distribution()
+ res['os_distro'] = distro
+ res['os_version'] = version
+ res['os_codename'] = unicode(codename, "utf-8")
+
+ return res
+
+ def lookup(self, *name):
+ return self.host_info
+
+ def shutdown(self, args=None):
+ # Check for running vms before shutdown
+ running_vms = self.vms_get_list_by_state('running')
+ if len(running_vms) > 0:
+ raise OperationFailed("Shutdown not allowed: VMs are running!")
+ kimchi_log.info('Host is going to shutdown.')
+ os.system('shutdown -h now')
+
+ def reboot(self, args=None):
+ # Find running VMs
+ running_vms = self._get_vms_list_by_state('running')
+ if len(running_vms) > 0:
+ raise OperationFailed("Reboot not allowed: VMs are running!")
+ kimchi_log.info('Host is going to reboot.')
+ os.system('reboot')
+
+ def _get_vms_list_by_state(self, state):
+ ret_list = []
+ for name in self.vms_get_list():
+ info = self._get_vm(name).info()
+ if (DOM_STATE_MAP[info[0]]) == state:
+ ret_list.append(name)
+ return ret_list
+
+
+class HostStatsModel(object):
+ __metaclass__ = Singleton
+
+ def __init__(self, **kargs):
+ self.host_stats = defaultdict(int)
+ self.host_stats_thread = BackgroundTask(HOST_STATS_INTERVAL,
+ self._update_host_stats)
+ self.host_stats_thread.start()
+
+ def lookup(self, *name):
+ return {'cpu_utilization': self.host_stats['cpu_utilization'],
+ 'memory': self.host_stats.get('memory'),
+ 'disk_read_rate': self.host_stats['disk_read_rate'],
+ 'disk_write_rate': self.host_stats['disk_write_rate'],
+ 'net_recv_rate': self.host_stats['net_recv_rate'],
+ 'net_sent_rate': self.host_stats['net_sent_rate']}
+
+ def _update_host_stats(self):
+ preTimeStamp = self.host_stats['timestamp']
+ timestamp = time.time()
+ # FIXME when we upgrade psutil, we can get uptime by psutil.uptime
+ # we get uptime by float(open("/proc/uptime").readline().split()[0])
+ # and calculate the first io_rate after the OS started.
+ seconds = (timestamp - preTimeStamp if preTimeStamp else
+ float(open("/proc/uptime").readline().split()[0]))
+
+ self.host_stats['timestamp'] = timestamp
+ self._get_host_disk_io_rate(seconds)
+ self._get_host_network_io_rate(seconds)
+
+ self._get_percentage_host_cpu_usage()
+ self._get_host_memory_stats()
+
+ def _get_percentage_host_cpu_usage(self):
+ # This is cpu usage producer. This producer will calculate the usage
+ # at an interval of HOST_STATS_INTERVAL.
+ # The psutil.cpu_percent works as non blocking.
+ # psutil.cpu_percent maintains a cpu time sample.
+ # It will update the cpu time sample when it is called.
+ # So only this producer can call psutil.cpu_percent in kimchi.
+ self.host_stats['cpu_utilization'] = psutil.cpu_percent(None)
+
+ def _get_host_memory_stats(self):
+ virt_mem = psutil.virtual_memory()
+ # available:
+ # the actual amount of available memory that can be given
+ # instantly to processes that request more memory in bytes; this
+ # is calculated by summing different memory values depending on
+ # the platform (e.g. free + buffers + cached on Linux)
+ memory_stats = {'total': virt_mem.total,
+ 'free': virt_mem.free,
+ 'cached': virt_mem.cached,
+ 'buffers': virt_mem.buffers,
+ 'avail': virt_mem.available}
+ self.host_stats['memory'] = memory_stats
+
+ def _get_host_disk_io_rate(self, seconds):
+ prev_read_bytes = self.host_stats['disk_read_bytes']
+ prev_write_bytes = self.host_stats['disk_write_bytes']
+
+ disk_io = psutil.disk_io_counters(False)
+ read_bytes = disk_io.read_bytes
+ write_bytes = disk_io.write_bytes
+
+ rd_rate = int(float(read_bytes - prev_read_bytes) / seconds + 0.5)
+ wr_rate = int(float(write_bytes - prev_write_bytes) / seconds + 0.5)
+
+ self.host_stats.update({'disk_read_rate': rd_rate,
+ 'disk_write_rate': wr_rate,
+ 'disk_read_bytes': read_bytes,
+ 'disk_write_bytes': write_bytes})
+
+ def _get_host_network_io_rate(self, seconds):
+ prev_recv_bytes = self.host_stats['net_recv_bytes']
+ prev_sent_bytes = self.host_stats['net_sent_bytes']
+
+ net_ios = psutil.network_io_counters(True)
+ recv_bytes = 0
+ sent_bytes = 0
+ for key in set(netinfo.nics() +
+ netinfo.wlans()) & set(net_ios.iterkeys()):
+ recv_bytes = recv_bytes + net_ios[key].bytes_recv
+ sent_bytes = sent_bytes + net_ios[key].bytes_sent
+
+ rx_rate = int(float(recv_bytes - prev_recv_bytes) / seconds + 0.5)
+ tx_rate = int(float(sent_bytes - prev_sent_bytes) / seconds + 0.5)
+
+ self.host_stats.update({'net_recv_rate': rx_rate,
+ 'net_sent_rate': tx_rate,
+ 'net_recv_bytes': recv_bytes,
+ 'net_sent_bytes': sent_bytes})
+
+
+class PartitionsModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def get_list(self):
+ result = disks.get_partitions_names()
+ return result
+
+
+class PartitionModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ if name not in disks.get_partitions_names():
+ raise NotFoundError("Partition %s not found in the host"
+ % name)
+ return disks.get_partition_details(name)
diff --git a/src/kimchi/model/interfaces.py b/src/kimchi/model/interfaces.py
new file mode 100644
index 0000000..52c6bae
--- /dev/null
+++ b/src/kimchi/model/interfaces.py
@@ -0,0 +1,46 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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 import netinfo
+from kimchi.exception import NotFoundError
+from kimchi.model.networks import NetworksModel
+
+
+class InterfacesModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.networks = NetworksModel(**kargs)
+
+ def get_list(self):
+ return list(set(netinfo.all_favored_interfaces()) -
+ set(self.networks.get_all_networks_interfaces()))
+
+
+class InterfaceModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ try:
+ return netinfo.get_interface_info(name)
+ except ValueError, e:
+ raise NotFoundError(e)
diff --git a/src/kimchi/model/libvirtconnection.py b/src/kimchi/model/libvirtconnection.py
new file mode 100644
index 0000000..7bbb668
--- /dev/null
+++ b/src/kimchi/model/libvirtconnection.py
@@ -0,0 +1,122 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import threading
+import time
+
+import cherrypy
+import libvirt
+
+from kimchi.utils import kimchi_log
+
+
+class LibvirtConnection(object):
+ def __init__(self, uri):
+ self.uri = uri
+ self._connections = {}
+ self._connectionLock = threading.Lock()
+ self.wrappables = self.get_wrappable_objects()
+
+ def get_wrappable_objects(self):
+ """
+ When a wrapped function returns an instance of another libvirt object,
+ we also want to wrap that object so we can catch errors that happen
+ when calling its methods.
+ """
+ objs = []
+ for name in ('virDomain', 'virDomainSnapshot', 'virInterface',
+ 'virNWFilter', 'virNetwork', 'virNodeDevice', 'virSecret',
+ 'virStoragePool', 'virStorageVol', 'virStream'):
+ try:
+ attr = getattr(libvirt, name)
+ except AttributeError:
+ pass
+ objs.append(attr)
+ return tuple(objs)
+
+ def get(self, conn_id=0):
+ """
+ Return current connection to libvirt or open a new one. Wrap all
+ callable libvirt methods so we can catch connection errors and handle
+ them by restarting the server.
+ """
+ def wrapMethod(f):
+ def wrapper(*args, **kwargs):
+ try:
+ ret = f(*args, **kwargs)
+ return ret
+ except libvirt.libvirtError as e:
+ edom = e.get_error_domain()
+ ecode = e.get_error_code()
+ EDOMAINS = (libvirt.VIR_FROM_REMOTE,
+ libvirt.VIR_FROM_RPC)
+ ECODES = (libvirt.VIR_ERR_SYSTEM_ERROR,
+ libvirt.VIR_ERR_INTERNAL_ERROR,
+ libvirt.VIR_ERR_NO_CONNECT,
+ libvirt.VIR_ERR_INVALID_CONN)
+ if edom in EDOMAINS and ecode in ECODES:
+ kimchi_log.error('Connection to libvirt broken. '
+ 'Recycling. ecode: %d edom: %d' %
+ (ecode, edom))
+ with self._connectionLock:
+ self._connections[conn_id] = None
+ raise
+ wrapper.__name__ = f.__name__
+ wrapper.__doc__ = f.__doc__
+ return wrapper
+
+ with self._connectionLock:
+ conn = self._connections.get(conn_id)
+ if not conn:
+ retries = 5
+ while True:
+ retries = retries - 1
+ try:
+ conn = libvirt.open(self.uri)
+ break
+ except libvirt.libvirtError:
+ kimchi_log.error('Unable to connect to libvirt.')
+ if not retries:
+ err = 'Libvirt is not available, exiting.'
+ kimchi_log.error(err)
+ cherrypy.engine.stop()
+ raise
+ time.sleep(2)
+
+ for name in dir(libvirt.virConnect):
+ method = getattr(conn, name)
+ if callable(method) and not name.startswith('_'):
+ setattr(conn, name, wrapMethod(method))
+
+ for cls in self.wrappables:
+ for name in dir(cls):
+ method = getattr(cls, name)
+ if callable(method) and not name.startswith('_'):
+ setattr(cls, name, wrapMethod(method))
+
+ self._connections[conn_id] = conn
+ # In case we're running into troubles with keeping the
+ # connections alive we should place here:
+ # conn.setKeepAlive(interval=5, count=3)
+ # However the values need to be considered wisely to not affect
+ # hosts which are hosting a lot of virtual machines
+ return conn
diff --git a/src/kimchi/model/libvirtstoragepool.py b/src/kimchi/model/libvirtstoragepool.py
new file mode 100644
index 0000000..f4dbf2e
--- /dev/null
+++ b/src/kimchi/model/libvirtstoragepool.py
@@ -0,0 +1,257 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import copy
+import os
+import tempfile
+
+import libvirt
+
+from kimchi.exception import InvalidParameter, OperationFailed, TimeoutExpired
+from kimchi.iscsi import TargetClient
+from kimchi.rollbackcontext import RollbackContext
+from kimchi.utils import parse_cmd_output, run_command
+
+
+class StoragePoolDef(object):
+ @classmethod
+ def create(cls, poolArgs):
+ for klass in cls.__subclasses__():
+ if poolArgs['type'] == klass.poolType:
+ return klass(poolArgs)
+ raise OperationFailed('Unsupported pool type: %s' % poolArgs['type'])
+
+ def __init__(self, poolArgs):
+ self.poolArgs = poolArgs
+
+ def prepare(self, conn):
+ ''' Validate pool arguments and perform preparations. Operation which
+ would cause side effect should be put here. Subclasses can optionally
+ override this method, or it always succeeds by default. '''
+ pass
+
+ @property
+ def xml(self):
+ ''' Subclasses have to override this method to actually generate the
+ storage pool XML definition. Should cause no side effect and be
+ idempotent'''
+ # TODO: When add new pool type, should also add the related test in
+ # tests/test_storagepool.py
+ raise OperationFailed('self.xml is not implemented: %s' % self)
+
+
+class DirPoolDef(StoragePoolDef):
+ poolType = 'dir'
+
+ @property
+ def xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # path:
+ xml = """
+ <pool type='dir'>
+ <name>{name}</name>
+ <target>
+ <path>{path}</path>
+ </target>
+ </pool>
+ """.format(**self.poolArgs)
+ return xml
+
+
+class NetfsPoolDef(StoragePoolDef):
+ poolType = 'netfs'
+
+ def __init__(self, poolArgs):
+ super(NetfsPoolDef, self).__init__(poolArgs)
+ self.path = '/var/lib/kimchi/nfs_mount/' + self.poolArgs['name']
+
+ def prepare(self, conn):
+ mnt_point = tempfile.mkdtemp(dir='/tmp')
+ export_path = "%s:%s" % (
+ self.poolArgs['source']['host'], self.poolArgs['source']['path'])
+ mount_cmd = ["mount", "-o", 'soft,timeo=100,retrans=3,retry=0',
+ export_path, mnt_point]
+ umount_cmd = ["umount", "-f", export_path]
+ mounted = False
+
+ with RollbackContext() as rollback:
+ rollback.prependDefer(os.rmdir, mnt_point)
+ try:
+ run_command(mount_cmd, 30)
+ rollback.prependDefer(run_command, umount_cmd)
+ except TimeoutExpired:
+ err = "Export path %s may block during nfs mount"
+ raise InvalidParameter(err % export_path)
+
+ with open("/proc/mounts", "rb") as f:
+ rawMounts = f.read()
+ output_items = ['dev_path', 'mnt_point', 'type']
+ mounts = parse_cmd_output(rawMounts, output_items)
+ for item in mounts:
+ if 'dev_path' in item and item['dev_path'] == export_path:
+ mounted = True
+
+ if not mounted:
+ err = "Export path %s mount failed during nfs mount"
+ raise InvalidParameter(err % export_path)
+
+ @property
+ def xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # source[host]:
+ # source[path]:
+ poolArgs = copy.deepcopy(self.poolArgs)
+ poolArgs['path'] = self.path
+ xml = """
+ <pool type='netfs'>
+ <name>{name}</name>
+ <source>
+ <host name='{source[host]}'/>
+ <dir path='{source[path]}'/>
+ </source>
+ <target>
+ <path>{path}</path>
+ </target>
+ </pool>
+ """.format(**poolArgs)
+ return xml
+
+
+class LogicalPoolDef(StoragePoolDef):
+ poolType = 'logical'
+
+ def __init__(self, poolArgs):
+ super(LogicalPoolDef, self).__init__(poolArgs)
+ self.path = '/var/lib/kimchi/logical_mount/' + self.poolArgs['name']
+
+ @property
+ def xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # source[devices]:
+ poolArgs = copy.deepcopy(self.poolArgs)
+ devices = []
+ for device_path in poolArgs['source']['devices']:
+ devices.append('<device path="%s" />' % device_path)
+
+ poolArgs['source']['devices'] = ''.join(devices)
+ poolArgs['path'] = self.path
+
+ xml = """
+ <pool type='logical'>
+ <name>{name}</name>
+ <source>
+ {source[devices]}
+ </source>
+ <target>
+ <path>{path}</path>
+ </target>
+ </pool>
+ """.format(**poolArgs)
+ return xml
+
+
+class IscsiPoolDef(StoragePoolDef):
+ poolType = 'iscsi'
+
+ def prepare(self, conn):
+ source = self.poolArgs['source']
+ if not TargetClient(**source).validate():
+ raise OperationFailed("Can not login to iSCSI host %s target %s" %
+ (source['host'], source['target']))
+ self._prepare_auth(conn)
+
+ def _prepare_auth(self, conn):
+ try:
+ auth = self.poolArgs['source']['auth']
+ except KeyError:
+ return
+
+ try:
+ virSecret = conn.secretLookupByUsage(
+ libvirt.VIR_SECRET_USAGE_TYPE_ISCSI, self.poolArgs['name'])
+ except libvirt.libvirtError:
+ xml = '''
+ <secret ephemeral='no' private='yes'>
+ <description>Secret for iSCSI storage pool {name}</description>
+ <auth type='chap' username='{username}'/>
+ <usage type='iscsi'>
+ <target>{name}</target>
+ </usage>
+ </secret>'''.format(name=self.poolArgs['name'],
+ username=auth['username'])
+ virSecret = conn.secretDefineXML(xml)
+
+ virSecret.setValue(auth['password'])
+
+ def _format_port(self, poolArgs):
+ try:
+ port = poolArgs['source']['port']
+ except KeyError:
+ return ""
+ return "port='%s'" % port
+
+ def _format_auth(self, poolArgs):
+ try:
+ auth = poolArgs['source']['auth']
+ except KeyError:
+ return ""
+
+ return '''
+ <auth type='chap' username='{username}'>
+ <secret type='iscsi' usage='{name}'/>
+ </auth>'''.format(name=poolArgs['name'], username=auth['username'])
+
+ @property
+ def xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # source[host]:
+ # source[target]:
+ #
+ # Optional parameters
+ # source[port]:
+ poolArgs = copy.deepcopy(self.poolArgs)
+ poolArgs['source'].update({'port': self._format_port(poolArgs),
+ 'auth': self._format_auth(poolArgs)})
+ poolArgs['path'] = '/dev/disk/by-id'
+
+ xml = """
+ <pool type='iscsi'>
+ <name>{name}</name>
+ <source>
+ <host name='{source[host]}' {source[port]}/>
+ <device path='{source[target]}'/>
+ {source[auth]}
+ </source>
+ <target>
+ <path>{path}</path>
+ </target>
+ </pool>
+ """.format(**poolArgs)
+ return xml
diff --git a/src/kimchi/model/model.py b/src/kimchi/model/model.py
new file mode 100644
index 0000000..ac0779d
--- /dev/null
+++ b/src/kimchi/model/model.py
@@ -0,0 +1,125 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import inspect
+import logging
+import os
+import sys
+
+import cherrypy
+import libvirt
+
+from kimchi.basemodel import BaseModel
+from kimchi.model.libvirtconnection import LibvirtConnection
+from kimchi.objectstore import ObjectStore
+from kimchi.utils import import_module, listPathModules
+
+
+class Model(BaseModel):
+ def __init__(self, libvirt_uri='qemu:///system', objstore_loc=None):
+ self.objstore = ObjectStore(objstore_loc)
+ self.conn = LibvirtConnection(libvirt_uri)
+ kargs = {'objstore': self.objstore, 'conn': self.conn}
+
+ if 'qemu:///' in libvirt_uri:
+ self._default_pool_check()
+ self._default_network_check()
+
+ this = os.path.basename(__file__)
+ this_mod = os.path.splitext(this)[0]
+
+ models = []
+ for mod_name in listPathModules(os.path.dirname(__file__)):
+ if mod_name.startswith("_") or mod_name == this_mod:
+ continue
+
+ module = import_module('model.' + mod_name)
+ members = inspect.getmembers(module, inspect.isclass)
+ for cls_name, instance in members:
+ if inspect.getmodule(instance) == module:
+ if cls_name.endswith('Model'):
+ models.append(instance(**kargs))
+
+ return super(Model, self).__init__(models)
+
+ def _default_network_check(self):
+ conn = self.conn.get()
+ xml = """
+ <network>
+ <name>default</name>
+ <forward mode='nat'/>
+ <bridge name='virbr0' stp='on' delay='0' />
+ <ip address='192.168.122.1' netmask='255.255.255.0'>
+ <dhcp>
+ <range start='192.168.122.2' end='192.168.122.254' />
+ </dhcp>
+ </ip>
+ </network>
+ """
+ try:
+ net = conn.networkLookupByName("default")
+ except libvirt.libvirtError:
+ try:
+ net = conn.networkDefineXML(xml)
+ except libvirt.libvirtError, e:
+ cherrypy.log.error("Fatal: Cannot create default network "
+ "because of %s, exit kimchid" % e.message,
+ severity=logging.ERROR)
+ sys.exit(1)
+
+ if net.isActive() == 0:
+ try:
+ net.create()
+ except libvirt.libvirtError, e:
+ cherrypy.log.error("Fatal: Cannot activate default network "
+ "because of %s, exit kimchid" % e.message,
+ severity=logging.ERROR)
+ sys.exit(1)
+
+ def _default_pool_check(self):
+ conn = self.conn.get()
+ xml = """
+ <pool type='dir'>
+ <name>default</name>
+ <target>
+ <path>/var/lib/libvirt/images</path>
+ </target>
+ </pool>
+ """
+ try:
+ pool = conn.storagePoolLookupByName("default")
+ except libvirt.libvirtError:
+ try:
+ pool = conn.storagePoolCreateXML(xml, 0)
+ 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/networks.py b/src/kimchi/model/networks.py
new file mode 100644
index 0000000..b164141
--- /dev/null
+++ b/src/kimchi/model/networks.py
@@ -0,0 +1,265 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import ipaddr
+import libvirt
+
+from kimchi import netinfo
+from kimchi import network as knetwork
+from kimchi import networkxml
+from kimchi import xmlutils
+from kimchi.exception import InvalidOperation, InvalidParameter
+from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
+
+
+class NetworksModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+
+ def create(self, params):
+ conn = self.conn.get()
+ name = params['name']
+ if name in self.get_list():
+ raise InvalidOperation("Network %s already exists" % name)
+
+ connection = params["connection"]
+ # set forward mode, isolated do not need forward
+ if connection != 'isolated':
+ params['forward'] = {'mode': connection}
+
+ # set subnet, bridge network do not need subnet
+ if connection in ["nat", 'isolated']:
+ self._set_network_subnet(params)
+
+ # only bridge network need bridge(linux bridge) or interface(macvtap)
+ if connection == 'bridge':
+ self._set_network_bridge(params)
+
+ xml = networkxml.to_network_xml(**params)
+
+ try:
+ network = conn.networkDefineXML(xml)
+ network.setAutostart(True)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ return name
+
+ def get_list(self):
+ conn = self.conn.get()
+ return sorted(conn.listNetworks() + conn.listDefinedNetworks())
+
+ def _set_network_subnet(self, params):
+ netaddr = params.get('subnet', '')
+ net_addrs = []
+ # lookup a free network address for nat and isolated automatically
+ if not netaddr:
+ for net_name in self.get_list():
+ network = self._get_network(net_name)
+ xml = network.XMLDesc(0)
+ subnet = NetworkModel.get_network_from_xml(xml)['subnet']
+ subnet and net_addrs.append(ipaddr.IPNetwork(subnet))
+ netaddr = knetwork.get_one_free_network(net_addrs)
+ if not netaddr:
+ raise OperationFailed("can not find a free IP address for "
+ "network '%s'" % params['name'])
+
+ try:
+ ip = ipaddr.IPNetwork(netaddr)
+ except ValueError as e:
+ raise InvalidParameter("%s" % e)
+
+ if ip.ip == ip.network:
+ ip.ip = ip.ip + 1
+
+ dhcp_start = str(ip.ip + ip.numhosts / 2)
+ dhcp_end = str(ip.ip + ip.numhosts - 2)
+ params.update({'net': str(ip),
+ 'dhcp': {'range': {'start': dhcp_start,
+ 'end': dhcp_end}}})
+
+ def _set_network_bridge(self, params):
+ try:
+ iface = params['interface']
+ if iface in self.get_all_networks_interfaces():
+ raise InvalidParameter("interface '%s' already in use." %
+ iface)
+ except KeyError, e:
+ raise MissingParameter(e)
+ if netinfo.is_bridge(iface):
+ params['bridge'] = iface
+ elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface):
+ if params.get('vlan_id') is None:
+ params['forward']['dev'] = iface
+ else:
+ params['bridge'] = \
+ self._create_vlan_tagged_bridge(str(iface),
+ str(params['vlan_id']))
+ else:
+ raise InvalidParameter("the interface should be bare nic, "
+ "bonding or bridge device.")
+
+ def get_all_networks_interfaces(self):
+ net_names = self.get_list()
+ interfaces = []
+ for name in net_names:
+ conn = self.conn.get()
+ network = conn.networkLookupByName(name)
+ xml = network.XMLDesc(0)
+ net_dict = NetworkModel.get_network_from_xml(xml)
+ forward = net_dict['forward']
+ (forward['mode'] == 'bridge' and forward['interface'] and
+ interfaces.append(forward['interface'][0]) is None or
+ interfaces.extend(forward['interface'] + forward['pf']))
+ net_dict['bridge'] and interfaces.append(net_dict['bridge'])
+ return interfaces
+
+ def _create_vlan_tagged_bridge(self, interface, vlan_id):
+ br_name = '-'.join(('kimchi', interface, vlan_id))
+ br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface,
+ vlan_id)
+ conn = self.conn.get()
+ conn.changeBegin()
+ try:
+ vlan_tagged_br = conn.interfaceDefineXML(br_xml)
+ vlan_tagged_br.create()
+ except libvirt.libvirtError as e:
+ conn.changeRollback()
+ raise OperationFailed(e.message)
+ else:
+ conn.changeCommit()
+ return br_name
+
+
+class NetworkModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+
+ def lookup(self, name):
+ network = self._get_network(name)
+ xml = network.XMLDesc(0)
+ net_dict = self.get_network_from_xml(xml)
+ subnet = net_dict['subnet']
+ dhcp = net_dict['dhcp']
+ forward = net_dict['forward']
+ interface = net_dict['bridge']
+
+ connection = forward['mode'] or "isolated"
+ # FIXME, if we want to support other forward mode well.
+ if connection == 'bridge':
+ # macvtap bridge
+ interface = interface or forward['interface'][0]
+ # exposing the network on linux bridge or macvtap interface
+ interface_subnet = knetwork.get_dev_netaddr(interface)
+ subnet = subnet if subnet else interface_subnet
+
+ # libvirt use format 192.168.0.1/24, standard should be 192.168.0.0/24
+ # http://www.ovirt.org/File:Issue3.png
+ if subnet:
+ subnet = ipaddr.IPNetwork(subnet)
+ subnet = "%s/%s" % (subnet.network, subnet.prefixlen)
+
+ return {'connection': connection,
+ 'interface': interface,
+ 'subnet': subnet,
+ 'dhcp': dhcp,
+ 'vms': self._get_vms_attach_to_a_network(name),
+ 'autostart': network.autostart() == 1,
+ 'state': network.isActive() and "active" or "inactive"}
+
+ def _get_vms_attach_to_a_network(self, network):
+ vms = []
+ conn = self.conn.get()
+ for dom in conn.listAllDomains(0):
+ networks = self._vm_get_networks(dom)
+ if network in networks:
+ vms.append(dom.name())
+ return vms
+
+ def _vm_get_networks(self, dom):
+ xml = dom.XMLDesc(0)
+ xpath = "/domain/devices/interface[@type='network']/source/@network"
+ return xmlutils.xpath_get_text(xml, xpath)
+
+ def activate(self, name):
+ network = self._get_network(name)
+ network.create()
+
+ def deactivate(self, name):
+ network = self._get_network(name)
+ network.destroy()
+
+ def delete(self, name):
+ network = self._get_network(name)
+ if network.isActive():
+ raise InvalidOperation(
+ "Unable to delete the active network %s" % name)
+ self._remove_vlan_tagged_bridge(network)
+ network.undefine()
+
+ def _get_network(self, name):
+ conn = self.conn.get()
+ try:
+ return conn.networkLookupByName(name)
+ except libvirt.libvirtError as e:
+ raise NotFoundError("Network '%s' not found: %s" %
+ (name, e.get_error_message()))
+
+ @staticmethod
+ def get_network_from_xml(xml):
+ address = xmlutils.xpath_get_text(xml, "/network/ip/@address")
+ address = address and address[0] or ''
+ netmask = xmlutils.xpath_get_text(xml, "/network/ip/@netmask")
+ netmask = netmask and netmask[0] or ''
+ net = address and netmask and "/".join([address, netmask]) or ''
+
+ dhcp_start = xmlutils.xpath_get_text(xml,
+ "/network/ip/dhcp/range/@start")
+ dhcp_start = dhcp_start and dhcp_start[0] or ''
+ dhcp_end = xmlutils.xpath_get_text(xml, "/network/ip/dhcp/range/@end")
+ dhcp_end = dhcp_end and dhcp_end[0] or ''
+ dhcp = {'start': dhcp_start, 'end': dhcp_end}
+
+ forward_mode = xmlutils.xpath_get_text(xml, "/network/forward/@mode")
+ forward_mode = forward_mode and forward_mode[0] or ''
+ forward_if = xmlutils.xpath_get_text(xml,
+ "/network/forward/interface/@dev")
+ forward_pf = xmlutils.xpath_get_text(xml, "/network/forward/pf/@dev")
+ bridge = xmlutils.xpath_get_text(xml, "/network/bridge/@name")
+ bridge = bridge and bridge[0] or ''
+ return {'subnet': net, 'dhcp': dhcp, 'bridge': bridge,
+ 'forward': {'mode': forward_mode,
+ 'interface': forward_if,
+ 'pf': forward_pf}}
+
+ def _remove_vlan_tagged_bridge(self, network):
+ try:
+ bridge = network.bridgeName()
+ except libvirt.libvirtError:
+ pass
+ else:
+ if bridge.startswith('kimchi-'):
+ conn = self.conn.get()
+ iface = conn.interfaceLookupByName(bridge)
+ if iface.isActive():
+ iface.destroy()
+ iface.undefine()
diff --git a/src/kimchi/model/plugins.py b/src/kimchi/model/plugins.py
new file mode 100644
index 0000000..d6756d0
--- /dev/null
+++ b/src/kimchi/model/plugins.py
@@ -0,0 +1,31 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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.utils import get_enabled_plugins
+
+
+class PluginsModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def get_list(self):
+ return [plugin for (plugin, config) in get_enabled_plugins()]
diff --git a/src/kimchi/model/storagepools.py b/src/kimchi/model/storagepools.py
new file mode 100644
index 0000000..233a8a7
--- /dev/null
+++ b/src/kimchi/model/storagepools.py
@@ -0,0 +1,246 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import libvirt
+
+from kimchi import xmlutils
+from kimchi.scan import Scanner
+from kimchi.exception import InvalidOperation, MissingParameter
+from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.model.libvirtstoragepool import StoragePoolDef
+from kimchi.utils import add_task, kimchi_log
+
+
+ISO_POOL_NAME = u'kimchi_isos'
+POOL_STATE_MAP = {0: 'inactive',
+ 1: 'initializing',
+ 2: 'active',
+ 3: 'degraded',
+ 4: 'inaccessible'}
+
+STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
+ 'path': '/pool/source/dir/@path'}}
+
+
+class StoragePoolsModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.objstore = kargs['objstore']
+ self.scanner = Scanner(self._clean_scan)
+ self.scanner.delete()
+
+ def get_list(self):
+ try:
+ conn = self.conn.get()
+ names = conn.listStoragePools()
+ names += conn.listDefinedStoragePools()
+ return sorted(names)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def create(self, params):
+ task_id = None
+ conn = self.conn.get()
+ try:
+ name = params['name']
+ if name in (ISO_POOL_NAME, ):
+ raise InvalidOperation("StoragePool already exists")
+
+ if params['type'] == 'kimchi-iso':
+ task_id = self._do_deep_scan(params)
+ poolDef = StoragePoolDef.create(params)
+ poolDef.prepare(conn)
+ xml = poolDef.xml
+ except KeyError, key:
+ raise MissingParameter(key)
+
+ if name in self.get_list():
+ err = "The name %s has been used by a pool"
+ raise InvalidOperation(err % name)
+
+ 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())
+ return name
+
+ def _clean_scan(self, pool_name):
+ try:
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(pool_name)
+ pool.destroy()
+ with self.objstore as session:
+ session.delete('scanning', pool_name)
+ except Exception, e:
+ err = "Exception %s occured when cleaning scan result"
+ kimchi_log.debug(err % 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_list():
+ try:
+ res = self.storagepool_lookup(pool)
+ if res['state'] == 'active':
+ scan_params['ignore_list'].append(res['path'])
+ except Exception, e:
+ err = "Exception %s occured when get ignore path"
+ kimchi_log.debug(err % e.message)
+
+ params['path'] = self.scanner.scan_dir_prepare(params['name'])
+ scan_params['pool_path'] = params['path']
+ task_id = add_task('', self.scanner.start_scan, self.objstore,
+ 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
+
+
+class StoragePoolModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.objstore = kargs['objstore']
+
+ @staticmethod
+ def get_storagepool(name, conn):
+ conn = conn.get()
+ try:
+ return conn.storagePoolLookupByName(name)
+ except libvirt.libvirtError as e:
+ if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_POOL:
+ raise NotFoundError("Storage Pool '%s' not found" % name)
+ else:
+ raise
+
+ 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 lookup(self, name):
+ pool = self.get_storagepool(name, self.conn)
+ 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': 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 update(self, name, params):
+ autostart = params['autostart']
+ if autostart not in [True, False]:
+ raise InvalidOperation("Autostart flag must be true or false")
+ pool = self.get_storagepool(name, self.conn)
+ if autostart:
+ pool.setAutostart(1)
+ else:
+ pool.setAutostart(0)
+ ident = pool.name()
+ return ident
+
+ def activate(self, name):
+ pool = self.get_storagepool(name, self.conn)
+ try:
+ pool.create(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def deactivate(self, name):
+ pool = self.get_storagepool(name, self.conn)
+ try:
+ pool.destroy()
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def delete(self, name):
+ pool = self.get_storagepool(name, self.conn)
+ if pool.isActive():
+ err = "Unable to delete the active storagepool %s"
+ raise InvalidOperation(err % name)
+ try:
+ pool.undefine()
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+
+class IsoPoolModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ return {'state': 'active',
+ 'type': 'kimchi-iso'}
diff --git a/src/kimchi/model/storageservers.py b/src/kimchi/model/storageservers.py
new file mode 100644
index 0000000..6a7c14a
--- /dev/null
+++ b/src/kimchi/model/storageservers.py
@@ -0,0 +1,78 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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 NotFoundError
+from kimchi.model.storagepools import StoragePoolModel, STORAGE_SOURCES
+
+
+class StorageServersModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.pool = StoragePoolModel(**kargs)
+
+ def get_list(self, _target_type=None):
+ if not _target_type:
+ target_type = STORAGE_SOURCES.keys()
+ else:
+ target_type = [_target_type]
+ pools = self.pools.get_list()
+
+ conn = self.conn.get()
+ pools = conn.listStoragePools()
+ pools += conn.listDefinedStoragePools()
+
+ server_list = []
+ for pool in pools:
+ try:
+ pool_info = self.pool.lookup(pool)
+ if (pool_info['type'] in target_type and
+ pool_info['source']['addr'] not in server_list):
+ # Avoid to add same server for multiple times
+ # if it hosts more than one storage type
+ server_list.append(pool_info['source']['addr'])
+ except NotFoundError:
+ pass
+
+ return server_list
+
+
+class StorageServerModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.pool = StoragePoolModel(**kargs)
+
+ def lookup(self, server):
+ conn = self.conn.get()
+ pools = conn.listStoragePools()
+ pools += conn.listDefinedStoragePools()
+ for pool in pools:
+ try:
+ pool_info = self.pool.lookup(pool)
+ if pool_info['source'] and \
+ pool_info['source']['addr'] == server:
+ return dict(host=server)
+ except NotFoundError:
+ # Avoid inconsistent pool result because of lease between list
+ # lookup
+ pass
+
+ raise NotFoundError('server %s does not used by kimchi' % server)
diff --git a/src/kimchi/model/storagetargets.py b/src/kimchi/model/storagetargets.py
new file mode 100644
index 0000000..be7cdaf
--- /dev/null
+++ b/src/kimchi/model/storagetargets.py
@@ -0,0 +1,86 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import libvirt
+import lxml.etree as ET
+from lxml import objectify
+from lxml.builder import E
+
+from kimchi.model.config import CapabilitiesModel
+from kimchi.model.storagepools import STORAGE_SOURCES
+from kimchi.utils import kimchi_log, patch_find_nfs_target
+
+
+class StorageTargetsModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.caps = CapabilitiesModel()
+
+ def get_list(self, storage_server, _target_type=None):
+ target_list = list()
+
+ if not _target_type:
+ target_types = STORAGE_SOURCES.keys()
+ else:
+ target_types = [_target_type]
+
+ for target_type in target_types:
+ if not self.caps.nfs_target_probe and target_type == 'netfs':
+ targets = patch_find_nfs_target(storage_server)
+ else:
+ xml = self._get_storage_server_spec(server=storage_server,
+ target_type=target_type)
+ conn = self.conn.get()
+ try:
+ ret = conn.findStoragePoolSources(target_type, xml, 0)
+ except libvirt.libvirtError as e:
+ err = "Query storage pool source fails because of %s"
+ kimchi_log.warning(err, e.get_error_message())
+ continue
+
+ targets = self._parse_target_source_result(target_type, ret)
+
+ target_list.extend(targets)
+ return target_list
+
+ def _get_storage_server_spec(**kwargs):
+ # Required parameters:
+ # server:
+ # target_type:
+ extra_args = []
+ if kwargs['target_type'] == 'netfs':
+ extra_args.append(E.format(type='nfs'))
+ obj = E.source(E.host(name=kwargs['server']), *extra_args)
+ xml = ET.tostring(obj)
+ return xml
+
+ def _parse_target_source_result(target_type, xml_str):
+ root = objectify.fromstring(xml_str)
+ ret = []
+ for source in root.getchildren():
+ if target_type == 'netfs':
+ host_name = source.host.get('name')
+ target_path = source.dir.get('path')
+ type = source.format.get('type')
+ ret.append(dict(host=host_name, target_type=type,
+ target=target_path))
+ return ret
diff --git a/src/kimchi/model/storagevolumes.py b/src/kimchi/model/storagevolumes.py
new file mode 100644
index 0000000..7ff570b
--- /dev/null
+++ b/src/kimchi/model/storagevolumes.py
@@ -0,0 +1,176 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import os
+
+import libvirt
+
+from kimchi import xmlutils
+from kimchi.exception import InvalidOperation, IsoFormatError
+from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
+from kimchi.isoinfo import IsoImage
+from kimchi.model.storagepools import StoragePoolModel
+
+
+VOLUME_TYPE_MAP = {0: 'file',
+ 1: 'block',
+ 2: 'directory',
+ 3: 'network'}
+
+
+class StorageVolumesModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+
+ def create(self, pool, params):
+ vol_xml = """
+ <volume>
+ <name>%(name)s</name>
+ <allocation unit="MiB">%(allocation)s</allocation>
+ <capacity unit="MiB">%(capacity)s</capacity>
+ <source>
+ </source>
+ <target>
+ <format type='%(format)s'/>
+ </target>
+ </volume>
+ """
+ params.setdefault('allocation', 0)
+ params.setdefault('format', 'qcow2')
+
+ try:
+ pool = StoragePoolModel.get_storagepool(pool, self.conn)
+ name = params['name']
+ xml = vol_xml % params
+ except KeyError, key:
+ raise MissingParameter(key)
+
+ try:
+ pool.createXML(xml, 0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+ return name
+
+ def get_list(self, pool):
+ pool = StoragePoolModel.get_storagepool(pool, self.conn)
+ if not pool.isActive():
+ err = "Unable to list volumes in inactive storagepool %s"
+ raise InvalidOperation(err % pool.name())
+ try:
+ pool.refresh(0)
+ return pool.listVolumes()
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+
+class StorageVolumeModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+
+ def _get_storagevolume(self, pool, name):
+ pool = StoragePoolModel.get_storagepool(pool, self.conn)
+ if not pool.isActive():
+ err = "Unable to list volumes in inactive storagepool %s"
+ raise InvalidOperation(err % pool.name())
+ try:
+ return pool.storageVolLookupByName(name)
+ except libvirt.libvirtError as e:
+ if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL:
+ raise NotFoundError("Storage Volume '%s' not found" % name)
+ else:
+ raise
+
+ def lookup(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=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))
+ os_distro = os_version = 'unknown'
+ 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(self, pool, name):
+ volume = self._get_storagevolume(pool, name)
+ try:
+ volume.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def delete(self, pool, name):
+ volume = self._get_storagevolume(pool, name)
+ try:
+ volume.delete(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def resize(self, pool, name, size):
+ size = size << 20
+ volume = self._get_storagevolume(pool, name)
+ try:
+ volume.resize(size, 0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+
+class IsoVolumesModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.storagevolume = StorageVolumeModel(**kargs)
+
+ def get_list(self):
+ iso_volumes = []
+ conn = self.conn.get()
+ pools = conn.listStoragePools()
+ pools += conn.listDefinedStoragePools()
+
+ for pool in pools:
+ try:
+ pool.refresh(0)
+ volumes = pool.listVolumes()
+ except InvalidOperation:
+ # Skip inactive pools
+ continue
+
+ for volume in volumes:
+ res = self.storagevolume.lookup(pool, volume)
+ if res['format'] == 'iso':
+ res['name'] = '%s' % volume
+ iso_volumes.append(res)
+ return iso_volumes
diff --git a/src/kimchi/model/tasks.py b/src/kimchi/model/tasks.py
new file mode 100644
index 0000000..40ca1d6
--- /dev/null
+++ b/src/kimchi/model/tasks.py
@@ -0,0 +1,39 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+
+class TasksModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+
+ def get_list(self):
+ with self.objstore as session:
+ return session.get_list('task')
+
+
+class TaskModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+
+ def lookup(self, id):
+ with self.objstore as session:
+ return session.get('task', str(id))
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
new file mode 100644
index 0000000..03632a6
--- /dev/null
+++ b/src/kimchi/model/templates.py
@@ -0,0 +1,172 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import copy
+
+import libvirt
+
+from kimchi import xmlutils
+from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError
+from kimchi.utils import pool_name_from_uri
+from kimchi.vmtemplate import VMTemplate
+
+
+class TemplatesModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+ self.conn = kargs['conn']
+
+ def create(self, params):
+ name = params['name']
+ conn = self.conn.get()
+
+ pool_uri = params.get(u'storagepool', '')
+ if pool_uri:
+ pool_name = pool_name_from_uri(pool_uri)
+ try:
+ conn.storagePoolLookupByName(pool_name)
+ except Exception as e:
+ err = "Storagepool specified is not valid: %s."
+ raise InvalidParameter(err % e.message)
+
+ for net_name in params.get(u'networks', []):
+ try:
+ conn.networkLookupByName(net_name)
+ except Exception, e:
+ raise InvalidParameter("Network '%s' specified by template "
+ "does not exist." % net_name)
+
+ with self.objstore as session:
+ if name in session.get_list('template'):
+ raise InvalidOperation("Template already exists")
+ t = LibvirtVMTemplate(params, scan=True)
+ session.store('template', name, t.info)
+ return name
+
+ def get_list(self):
+ with self.objstore as session:
+ return session.get_list('template')
+
+
+class TemplateModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+ self.conn = kargs['conn']
+ self.templates = TemplatesModel(**kargs)
+
+ @staticmethod
+ def get_template(name, objstore, conn, overrides=None):
+ with objstore as session:
+ params = session.get('template', name)
+ if overrides:
+ params.update(overrides)
+ return LibvirtVMTemplate(params, False, conn)
+
+ def lookup(self, name):
+ t = self.get_template(name, self.objstore, self.conn)
+ return t.info
+
+ def delete(self, name):
+ with self.objstore as session:
+ session.delete('template', name)
+
+ def update(self, name, params):
+ old_t = self.lookup(name)
+ new_t = copy.copy(old_t)
+ new_t.update(params)
+ ident = name
+
+ pool_uri = new_t.get(u'storagepool', '')
+ pool_name = pool_name_from_uri(pool_uri)
+ try:
+ conn = self.conn.get()
+ conn.storagePoolLookupByName(pool_name)
+ except Exception as e:
+ err = "Storagepool specified is not valid: %s."
+ raise InvalidParameter(err % e.message)
+
+ for net_name in params.get(u'networks', []):
+ try:
+ conn = self.conn.get()
+ conn.networkLookupByName(net_name)
+ except Exception, e:
+ raise InvalidParameter("Network '%s' specified by template "
+ "does not exist" % net_name)
+
+ self.delete(name)
+ try:
+ ident = self.templates.create(new_t)
+ except:
+ ident = self.templates.create(old_t)
+ raise
+ return ident
+
+
+class LibvirtVMTemplate(VMTemplate):
+ def __init__(self, args, scan=False, conn=None):
+ VMTemplate.__init__(self, args, scan)
+ self.conn = conn
+
+ def _storage_validate(self):
+ pool_uri = self.info['storagepool']
+ pool_name = pool_name_from_uri(pool_uri)
+ try:
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(pool_name)
+ except libvirt.libvirtError:
+ err = 'Storage specified by template does not exist'
+ raise InvalidParameter(err)
+
+ if not pool.isActive():
+ err = 'Storage specified by template is not active'
+ raise InvalidParameter(err)
+
+ return pool
+
+ def _network_validate(self):
+ names = self.info['networks']
+ for name in names:
+ try:
+ conn = self.conn.get()
+ network = conn.networkLookupByName(name)
+ except libvirt.libvirtError:
+ err = 'Network specified by template does not exist'
+ raise InvalidParameter(err)
+
+ if not network.isActive():
+ err = 'Network specified by template is not active'
+ raise InvalidParameter(err)
+
+ def _get_storage_path(self):
+ pool = self._storage_validate()
+ xml = pool.XMLDesc(0)
+ return xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
+
+ def fork_vm_storage(self, vm_uuid):
+ # Provision storage:
+ # TODO: Rebase on the storage API once upstream
+ pool = self._storage_validate()
+ vol_list = self.to_volume_list(vm_uuid)
+ for v in vol_list:
+ # outgoing text to libvirt, encode('utf-8')
+ pool.createXML(v['xml'].encode('utf-8'), 0)
+ return vol_list
diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py
new file mode 100644
index 0000000..a27b867
--- /dev/null
+++ b/src/kimchi/model/utils.py
@@ -0,0 +1,33 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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 OperationFailed
+
+
+def get_vm_name(vm_name, t_name, name_list):
+ if vm_name:
+ return vm_name
+ for i in xrange(1, 1000):
+ vm_name = "%s-vm-%i" % (t_name, i)
+ if vm_name not in name_list:
+ return vm_name
+ raise OperationFailed("Unable to choose a VM name")
diff --git a/src/kimchi/model/vmifaces.py b/src/kimchi/model/vmifaces.py
new file mode 100644
index 0000000..f3eddb2
--- /dev/null
+++ b/src/kimchi/model/vmifaces.py
@@ -0,0 +1,135 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import random
+
+import libvirt
+from lxml import etree, objectify
+from lxml.builder import E
+
+from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError
+from kimchi.model.vms import DOM_STATE_MAP, VMModel
+
+
+class VMIfacesModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+
+ def get_list(self, vm):
+ macs = []
+ for iface in self.get_vmifaces(vm, self.conn):
+ macs.append(iface.mac.get('address'))
+ return macs
+
+ def create(self, vm, params):
+ def randomMAC():
+ mac = [0x52, 0x54, 0x00,
+ random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff)]
+ return ':'.join(map(lambda x: "%02x" % x, mac))
+
+ conn = self.conn.get()
+ networks = conn.listNetworks() + conn.listDefinedNetworks()
+
+ if params["type"] == "network" and params["network"] not in networks:
+ raise InvalidParameter("%s is not an available network" %
+ params["network"])
+
+ dom = VMModel.get_vm(vm, self.conn)
+ if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
+ raise InvalidOperation("do not support hot plugging attach "
+ "guest interface")
+
+ macs = (iface.mac.get('address')
+ for iface in self.get_vmifaces(vm, self.conn))
+
+ mac = randomMAC()
+ while True:
+ if mac not in macs:
+ break
+ mac = randomMAC()
+
+ children = [E.mac(address=mac)]
+ ("network" in params.keys() and
+ children.append(E.source(network=params['network'])))
+ ("model" in params.keys() and
+ children.append(E.model(type=params['model'])))
+ attrib = {"type": params["type"]}
+
+ xml = etree.tostring(E.interface(*children, **attrib))
+
+ dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
+
+ return mac
+
+ @staticmethod
+ def get_vmifaces(vm, conn):
+ dom = VMModel.get_vm(vm, conn)
+ xml = dom.XMLDesc(0)
+ root = objectify.fromstring(xml)
+
+ return root.devices.findall("interface")
+
+
+class VMIfaceModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+
+ def _get_vmiface(self, vm, mac):
+ ifaces = VMIfacesModel.get_vmifaces(vm, self.conn)
+
+ for iface in ifaces:
+ if iface.mac.get('address') == mac:
+ return iface
+ return None
+
+ def lookup(self, vm, mac):
+ info = {}
+
+ iface = self._get_vmiface(vm, mac)
+ if iface is None:
+ raise NotFoundError('iface: "%s"' % mac)
+
+ info['type'] = iface.attrib['type']
+ info['mac'] = iface.mac.get('address')
+ if iface.find("model") is not None:
+ info['model'] = iface.model.get('type')
+ if info['type'] == 'network':
+ info['network'] = iface.source.get('network')
+ if info['type'] == 'bridge':
+ info['bridge'] = iface.source.get('bridge')
+
+ return info
+
+ def delete(self, vm, mac):
+ dom = VMModel.get_vm(vm, self.conn)
+ iface = self._get_vmiface(vm, mac)
+
+ if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
+ raise InvalidOperation("do not support hot plugging detach "
+ "guest interface")
+ if iface is None:
+ raise NotFoundError('iface: "%s"' % mac)
+
+ dom.detachDeviceFlags(etree.tostring(iface),
+ libvirt.VIR_DOMAIN_AFFECT_CURRENT)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
new file mode 100644
index 0000000..e9f7753
--- /dev/null
+++ b/src/kimchi/model/vms.py
@@ -0,0 +1,447 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm at 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
+
+import os
+import time
+import uuid
+from xml.etree import ElementTree
+
+import libvirt
+from cherrypy.process.plugins import BackgroundTask
+
+from kimchi import vnc
+from kimchi import xmlutils
+from kimchi.exception import InvalidOperation, InvalidParameter
+from kimchi.exception import NotFoundError, OperationFailed
+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 template_name_from_uri
+
+
+DOM_STATE_MAP = {0: 'nostate',
+ 1: 'running',
+ 2: 'blocked',
+ 3: 'paused',
+ 4: 'shutdown',
+ 5: 'shutoff',
+ 6: 'crashed'}
+
+GUESTS_STATS_INTERVAL = 5
+VM_STATIC_UPDATE_PARAMS = {'name': './name'}
+VM_LIVE_UPDATE_PARAMS = {}
+
+stats = {}
+
+
+class VMsModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.objstore = kargs['objstore']
+ self.caps = CapabilitiesModel()
+ self.guests_stats_thread = BackgroundTask(GUESTS_STATS_INTERVAL,
+ self._update_guests_stats)
+ self.guests_stats_thread.start()
+
+ def _update_guests_stats(self):
+ vm_list = self.get_list()
+
+ for name in vm_list:
+ dom = VMModel.get_vm(name, self.conn)
+ vm_uuid = dom.UUIDString()
+ info = dom.info()
+ state = DOM_STATE_MAP[info[0]]
+
+ if state != 'running':
+ stats[vm_uuid] = {}
+ continue
+
+ if stats.get(vm_uuid, None) is None:
+ stats[vm_uuid] = {}
+
+ timestamp = time.time()
+ prevStats = stats.get(vm_uuid, {})
+ seconds = timestamp - prevStats.get('timestamp', 0)
+ stats[vm_uuid].update({'timestamp': timestamp})
+
+ self._get_percentage_cpu_usage(vm_uuid, info, seconds)
+ self._get_network_io_rate(vm_uuid, dom, seconds)
+ self._get_disk_io_rate(vm_uuid, dom, seconds)
+
+ def _get_percentage_cpu_usage(self, vm_uuid, info, seconds):
+ prevCpuTime = stats[vm_uuid].get('cputime', 0)
+
+ cpus = info[3]
+ cpuTime = info[4] - prevCpuTime
+
+ base = (((cpuTime) * 100.0) / (seconds * 1000.0 * 1000.0 * 1000.0))
+ percentage = max(0.0, min(100.0, base / cpus))
+
+ stats[vm_uuid].update({'cputime': info[4], 'cpu': percentage})
+
+ def _get_network_io_rate(self, vm_uuid, dom, seconds):
+ prevNetRxKB = stats[vm_uuid].get('netRxKB', 0)
+ prevNetTxKB = stats[vm_uuid].get('netTxKB', 0)
+ currentMaxNetRate = stats[vm_uuid].get('max_net_io', 100)
+
+ rx_bytes = 0
+ tx_bytes = 0
+
+ tree = ElementTree.fromstring(dom.XMLDesc(0))
+ for target in tree.findall('devices/interface/target'):
+ dev = target.get('dev')
+ io = dom.interfaceStats(dev)
+ rx_bytes += io[0]
+ tx_bytes += io[4]
+
+ netRxKB = float(rx_bytes) / 1000
+ netTxKB = float(tx_bytes) / 1000
+
+ rx_stats = (netRxKB - prevNetRxKB) / seconds
+ tx_stats = (netTxKB - prevNetTxKB) / seconds
+
+ rate = rx_stats + tx_stats
+ max_net_io = round(max(currentMaxNetRate, int(rate)), 1)
+
+ stats[vm_uuid].update({'net_io': rate, 'max_net_io': max_net_io,
+ 'netRxKB': netRxKB, 'netTxKB': netTxKB})
+
+ def _get_disk_io_rate(self, vm_uuid, dom, seconds):
+ prevDiskRdKB = stats[vm_uuid].get('diskRdKB', 0)
+ prevDiskWrKB = stats[vm_uuid].get('diskWrKB', 0)
+ currentMaxDiskRate = stats[vm_uuid].get('max_disk_io', 100)
+
+ rd_bytes = 0
+ wr_bytes = 0
+
+ tree = ElementTree.fromstring(dom.XMLDesc(0))
+ for target in tree.findall("devices/disk/target"):
+ dev = target.get("dev")
+ io = dom.blockStats(dev)
+ rd_bytes += io[1]
+ wr_bytes += io[3]
+
+ diskRdKB = float(rd_bytes) / 1024
+ diskWrKB = float(wr_bytes) / 1024
+
+ rd_stats = (diskRdKB - prevDiskRdKB) / seconds
+ wr_stats = (diskWrKB - prevDiskWrKB) / seconds
+
+ rate = rd_stats + wr_stats
+ max_disk_io = round(max(currentMaxDiskRate, int(rate)), 1)
+
+ stats[vm_uuid].update({'disk_io': rate,
+ 'max_disk_io': max_disk_io,
+ 'diskRdKB': diskRdKB,
+ 'diskWrKB': diskWrKB})
+
+ def create(self, params):
+ conn = self.conn.get()
+ t_name = template_name_from_uri(params['template'])
+ vm_uuid = str(uuid.uuid4())
+ vm_list = self.get_list()
+ name = get_vm_name(params.get('name'), t_name, vm_list)
+ # incoming text, from js json, is unicode, do not need decode
+ if name in vm_list:
+ raise InvalidOperation("VM already exists")
+
+ vm_overrides = dict()
+ pool_uri = params.get('storagepool')
+ if pool_uri:
+ vm_overrides['storagepool'] = pool_uri
+ t = TemplateModel.get_template(t_name, self.objstore, self.conn,
+ vm_overrides)
+
+ if not self.caps.qemu_stream and t.info.get('iso_stream', False):
+ err = "Remote ISO image is not supported by this server."
+ raise InvalidOperation(err)
+
+ t.validate()
+ vol_list = t.fork_vm_storage(vm_uuid)
+
+ # Store the icon for displaying later
+ icon = t.info.get('icon')
+ if icon:
+ with self.objstore as session:
+ session.store('vm', vm_uuid, {'icon': icon})
+
+ libvirt_stream = False
+ if len(self.caps.libvirt_stream_protocols) == 0:
+ libvirt_stream = True
+
+ graphics = params.get('graphics')
+ xml = t.to_vm_xml(name, vm_uuid,
+ libvirt_stream=libvirt_stream,
+ qemu_stream_dns=self.caps.qemu_stream_dns,
+ graphics=graphics)
+
+ try:
+ conn.defineXML(xml.encode('utf-8'))
+ except libvirt.libvirtError as e:
+ for v in vol_list:
+ vol = conn.storageVolLookupByPath(v['path'])
+ vol.delete(0)
+ raise OperationFailed(e.get_error_message())
+
+ return name
+
+ def get_list(self):
+ conn = self.conn.get()
+ names = [dom.name().decode('utf-8') for dom in conn.listAllDomains(0)]
+ return sorted(names, key=unicode.lower)
+
+
+class VMModel(object):
+ def __init__(self, **kargs):
+ self.conn = kargs['conn']
+ self.objstore = kargs['objstore']
+ self.vms = VMsModel(**kargs)
+ self.vmscreenshot = VMScreenshotModel(**kargs)
+
+ def update(self, name, params):
+ dom = self.get_vm(name, self.conn)
+ dom = self._static_vm_update(dom, params)
+ self._live_vm_update(dom, params)
+ return dom.name()
+
+ def _static_vm_update(self, dom, params):
+ state = DOM_STATE_MAP[dom.info()[0]]
+
+ old_xml = new_xml = dom.XMLDesc(0)
+
+ for key, val in params.items():
+ if key in VM_STATIC_UPDATE_PARAMS:
+ xpath = VM_STATIC_UPDATE_PARAMS[key]
+ new_xml = xmlutils.xml_item_update(new_xml, xpath, val)
+
+ try:
+ if 'name' in params:
+ if state == 'running':
+ err = "VM name only can be updated when vm is powered off."
+ raise InvalidParameter(err)
+ else:
+ dom.undefine()
+ conn = self.conn.get()
+ dom = conn.defineXML(new_xml)
+ except libvirt.libvirtError as e:
+ dom = conn.defineXML(old_xml)
+ raise OperationFailed(e.get_error_message())
+ return dom
+
+ def _live_vm_update(self, dom, params):
+ pass
+
+ def lookup(self, name):
+ dom = self.get_vm(name, self.conn)
+ info = dom.info()
+ state = DOM_STATE_MAP[info[0]]
+ screenshot = None
+ graphics = self._vm_get_graphics(name)
+ graphics_type, graphics_listen, graphics_port = graphics
+ graphics_port = graphics_port if state == 'running' else None
+ try:
+ if state == 'running':
+ screenshot = self.vmscreenshot.lookup(name)
+ elif state == 'shutoff':
+ # reset vm stats when it is powered off to avoid sending
+ # incorrect (old) data
+ stats[dom.UUIDString()] = {}
+ except NotFoundError:
+ pass
+
+ with self.objstore as session:
+ try:
+ extra_info = session.get('vm', dom.UUIDString())
+ except NotFoundError:
+ extra_info = {}
+ icon = extra_info.get('icon')
+
+ vm_stats = stats.get(dom.UUIDString(), {})
+ res = {}
+ res['cpu_utilization'] = vm_stats.get('cpu', 0)
+ res['net_throughput'] = vm_stats.get('net_io', 0)
+ res['net_throughput_peak'] = vm_stats.get('max_net_io', 100)
+ res['io_throughput'] = vm_stats.get('disk_io', 0)
+ res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
+
+ return {'state': state,
+ 'stats': str(res),
+ 'uuid': dom.UUIDString(),
+ 'memory': info[2] >> 10,
+ 'cpus': info[3],
+ 'screenshot': screenshot,
+ 'icon': icon,
+ 'graphics': {"type": graphics_type,
+ "listen": graphics_listen,
+ "port": graphics_port}
+ }
+
+ def _vm_get_disk_paths(self, dom):
+ xml = dom.XMLDesc(0)
+ xpath = "/domain/devices/disk[@device='disk']/source/@file"
+ return xmlutils.xpath_get_text(xml, xpath)
+
+ def _vm_exists(self, name):
+ try:
+ self.get_vm(name, self.conn)
+ return True
+ except NotFoundError:
+ return False
+ except Exception, e:
+ err = "Unable to retrieve VM '%s': %s"
+ raise OperationFailed(err % (name, e.message))
+
+ @staticmethod
+ def get_vm(name, conn):
+ conn = conn.get()
+ try:
+ # outgoing text to libvirt, encode('utf-8')
+ return conn.lookupByName(name.encode("utf-8"))
+ except libvirt.libvirtError as e:
+ if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
+ raise NotFoundError("Virtual Machine '%s' not found" % name)
+ else:
+ raise
+
+ def delete(self, name):
+ if self._vm_exists(name):
+ conn = self.conn.get()
+ dom = self.get_vm(name, self.conn)
+ self._vmscreenshot_delete(dom.UUIDString())
+ paths = self._vm_get_disk_paths(dom)
+ info = self.lookup(name)
+
+ if info['state'] == 'running':
+ self.stop(name)
+
+ dom.undefine()
+
+ for path in paths:
+ vol = conn.storageVolLookupByPath(path)
+ vol.delete(0)
+
+ with self.objstore as session:
+ session.delete('vm', dom.UUIDString(), ignore_missing=True)
+
+ vnc.remove_proxy_token(name)
+
+ def start(self, name):
+ dom = self.get_vm(name, self.conn)
+ dom.create()
+
+ def stop(self, name):
+ if self._vm_exists(name):
+ dom = self.get_vm(name, self.conn)
+ dom.destroy()
+
+ def _vm_get_graphics(self, name):
+ dom = self.get_vm(name, self.conn)
+ xml = dom.XMLDesc(0)
+ expr = "/domain/devices/graphics/@type"
+ res = xmlutils.xpath_get_text(xml, expr)
+ graphics_type = res[0] if res else None
+ expr = "/domain/devices/graphics/@listen"
+ res = xmlutils.xpath_get_text(xml, expr)
+ graphics_listen = res[0] if res else None
+ graphics_port = None
+ if graphics_type:
+ expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type
+ res = xmlutils.xpath_get_text(xml, expr)
+ graphics_port = int(res[0]) if res else None
+ return graphics_type, graphics_listen, graphics_port
+
+ def connect(self, name):
+ graphics = self._vm_get_graphics(name)
+ graphics_type, graphics_listen, graphics_port = graphics
+ if graphics_port is not None:
+ vnc.add_proxy_token(name, graphics_port)
+ else:
+ raise OperationFailed("Only able to connect to running vm's vnc "
+ "graphics.")
+
+ def _vmscreenshot_delete(self, vm_uuid):
+ screenshot = VMScreenshotModel.get_screenshot(vm_uuid, self.objstore,
+ self.conn)
+ screenshot.delete()
+ with self.objstore as session:
+ session.delete('screenshot', vm_uuid)
+
+
+class VMScreenshotModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+ self.conn = kargs['conn']
+
+ def lookup(self, name):
+ dom = VMModel.get_vm(name, self.conn)
+ d_info = dom.info()
+ vm_uuid = dom.UUIDString()
+ if DOM_STATE_MAP[d_info[0]] != 'running':
+ raise NotFoundError('No screenshot for stopped vm')
+
+ screenshot = self.get_screenshot(vm_uuid, self.objstore, self.conn)
+ img_path = screenshot.lookup()
+ # screenshot info changed after scratch generation
+ with self.objstore as session:
+ session.store('screenshot', vm_uuid, screenshot.info)
+ return img_path
+
+ @staticmethod
+ def get_screenshot(vm_uuid, objstore, conn):
+ with objstore as session:
+ try:
+ params = session.get('screenshot', vm_uuid)
+ except NotFoundError:
+ params = {'uuid': vm_uuid}
+ session.store('screenshot', vm_uuid, params)
+ return LibvirtVMScreenshot(params, conn)
+
+
+class LibvirtVMScreenshot(VMScreenshot):
+ def __init__(self, vm_uuid, conn):
+ VMScreenshot.__init__(self, vm_uuid)
+ self.conn = conn
+
+ def _generate_scratch(self, thumbnail):
+ def handler(stream, buf, opaque):
+ fd = opaque
+ os.write(fd, buf)
+
+ fd = os.open(thumbnail, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0644)
+ try:
+ conn = self.conn.get()
+ dom = conn.lookupByUUIDString(self.vm_uuid)
+ vm_name = dom.name()
+ stream = conn.newStream(0)
+ dom.screenshot(stream, 0, 0)
+ stream.recvAll(handler, fd)
+ except libvirt.libvirtError:
+ try:
+ stream.abort()
+ except:
+ pass
+ raise NotFoundError("Screenshot not supported for %s" % vm_name)
+ else:
+ stream.finish()
+ finally:
+ os.close(fd)
diff --git a/src/kimchi/model_/__init__.py b/src/kimchi/model_/__init__.py
deleted file mode 100644
index 8a37cc4..0000000
--- a/src/kimchi/model_/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
diff --git a/src/kimchi/model_/config.py b/src/kimchi/model_/config.py
deleted file mode 100644
index ae744ef..0000000
--- a/src/kimchi/model_/config.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import cherrypy
-
-from kimchi.basemodel import Singleton
-from kimchi.config import config as kconfig
-from kimchi.distroloader import DistroLoader
-from kimchi.exception import NotFoundError
-from kimchi.featuretests import FeatureTests
-from kimchi.model_.debugreports import DebugReportsModel
-from kimchi.screenshot import VMScreenshot
-from kimchi.utils import kimchi_log
-
-
-class ConfigModel(object):
- def __init__(self, **kargs):
- pass
-
- def lookup(self, name):
- proxy_port = kconfig.get('display', 'display_proxy_port')
- return {'http_port': cherrypy.server.socket_port,
- 'display_proxy_port': proxy_port}
-
-
-class CapabilitiesModel(object):
- __metaclass__ = Singleton
-
- def __init__(self, **kargs):
- self.qemu_stream = False
- self.qemu_stream_dns = False
- self.libvirt_stream_protocols = []
-
- # Subscribe function to set host capabilities to be run when cherrypy
- # server is up
- # It is needed because some features tests depends on the server
- cherrypy.engine.subscribe('start', self._set_capabilities)
-
- def _set_capabilities(self):
- kimchi_log.info("*** Running feature tests ***")
- self.qemu_stream = FeatureTests.qemu_supports_iso_stream()
- self.qemu_stream_dns = FeatureTests.qemu_iso_stream_dns()
- self.nfs_target_probe = FeatureTests.libvirt_support_nfs_probe()
-
- self.libvirt_stream_protocols = []
- for p in ['http', 'https', 'ftp', 'ftps', 'tftp']:
- if FeatureTests.libvirt_supports_iso_stream(p):
- self.libvirt_stream_protocols.append(p)
-
- kimchi_log.info("*** Feature tests completed ***")
- _set_capabilities.priority = 90
-
- def lookup(self, *ident):
- report_tool = DebugReportsModel.get_system_report_tool()
-
- return {'libvirt_stream_protocols': self.libvirt_stream_protocols,
- 'qemu_stream': self.qemu_stream,
- 'screenshot': VMScreenshot.get_stream_test_result(),
- 'system_report_tool': bool(report_tool)}
-
-
-class DistrosModel(object):
- def __init__(self, **kargs):
- distroloader = DistroLoader()
- self.distros = distroloader.get()
-
- def get_list(self):
- return self.distros.keys()
-
-
-class DistroModel(object):
- def __init__(self, **kargs):
- self._distros = DistrosModel()
-
- def lookup(self, name):
- try:
- return self._distros.distros[name]
- except KeyError:
- raise NotFoundError("Distro '%s' not found." % name)
diff --git a/src/kimchi/model_/debugreports.py b/src/kimchi/model_/debugreports.py
deleted file mode 100644
index 39402aa..0000000
--- a/src/kimchi/model_/debugreports.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import fnmatch
-import glob
-import logging
-import os
-import shutil
-import subprocess
-import time
-
-from kimchi import config
-from kimchi.exception import NotFoundError, OperationFailed
-from kimchi.model_.tasks import TaskModel
-from kimchi.utils import add_task, kimchi_log
-
-
-class DebugReportsModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
- self.task = TaskModel(**kargs)
-
- def create(self, params):
- ident = params['name']
- taskid = self._gen_debugreport_file(ident)
- return self.task.lookup(taskid)
-
- def get_list(self):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, '*.*')
- file_lists = glob.glob(file_pattern)
- file_lists = [os.path.split(file)[1] for file in file_lists]
- name_lists = [file.split('.', 1)[0] for file in file_lists]
-
- return name_lists
-
- def _gen_debugreport_file(self, name):
- gen_cmd = self.get_system_report_tool()
-
- if gen_cmd is not None:
- return add_task('', gen_cmd, self.objstore, name)
-
- raise OperationFailed("debugreport tool not found")
-
- @staticmethod
- def sosreport_generate(cb, name):
- command = 'sosreport --batch --name "%s"' % name
- try:
- retcode = subprocess.call(command, shell=True,
- stdout=subprocess.PIPE)
- if retcode < 0:
- raise OperationFailed('Command terminated with signal')
- elif retcode > 0:
- raise OperationFailed('Command failed: rc = %i' % retcode)
- pattern = '/tmp/sosreport-%s-*' % name
- for reportFile in glob.glob(pattern):
- if not fnmatch.fnmatch(reportFile, '*.md5'):
- output = reportFile
- break
- else:
- # sosreport tends to change the name mangling rule and
- # compression file format between different releases.
- # It's possible to fail to match a report file even sosreport
- # runs successfully. In future we might have a general name
- # mangling function in kimchi to format the name before passing
- # it to sosreport. Then we can delete this exception.
- raise OperationFailed('Can not find generated debug report '
- 'named by %s' % pattern)
- ext = output.split('.', 1)[1]
- path = config.get_debugreports_path()
- target = os.path.join(path, name)
- target_file = '%s.%s' % (target, ext)
- shutil.move(output, target_file)
- os.remove('%s.md5' % output)
- cb('OK', True)
-
- return
-
- except OSError:
- raise
-
- except Exception, e:
- # No need to call cb to update the task status here.
- # The task object will catch the exception rasied here
- # and update the task status there
- log = logging.getLogger('Model')
- log.warning('Exception in generating debug file: %s', e)
- raise OperationFailed(e)
-
- @staticmethod
- def get_system_report_tool():
- # Please add new possible debug report command here
- # and implement the report generating function
- # based on the new report command
- report_tools = ({'cmd': 'sosreport --help',
- 'fn': DebugReportsModel.sosreport_generate},)
-
- # check if the command can be found by shell one by one
- for helper_tool in report_tools:
- try:
- retcode = subprocess.call(helper_tool['cmd'], shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- if retcode == 0:
- return helper_tool['fn']
- except Exception, e:
- kimchi_log.info('Exception running command: %s', e)
-
- return None
-
-
-class DebugReportModel(object):
- def __init__(self, **kargs):
- pass
-
- def lookup(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name)
- file_pattern = file_pattern + '.*'
- try:
- file_target = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError('no such report')
-
- ctime = os.stat(file_target).st_ctime
- ctime = time.strftime("%Y-%m-%d-%H:%M:%S", time.localtime(ctime))
- file_target = os.path.split(file_target)[-1]
- file_target = os.path.join("/data/debugreports", file_target)
- return {'file': file_target,
- 'ctime': ctime}
-
- def delete(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name + '.*')
- try:
- file_target = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError('no such report')
-
- os.remove(file_target)
-
-
-class DebugReportContentModel(object):
- def __init__(self, **kargs):
- self._debugreport = DebugReportModel()
-
- def lookup(self, name):
- return self._debugreport.lookup(name)
diff --git a/src/kimchi/model_/host.py b/src/kimchi/model_/host.py
deleted file mode 100644
index d5f92ef..0000000
--- a/src/kimchi/model_/host.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import os
-import time
-import platform
-from collections import defaultdict
-
-import psutil
-from cherrypy.process.plugins import BackgroundTask
-
-from kimchi import disks
-from kimchi import netinfo
-from kimchi.basemodel import Singleton
-from kimchi.exception import NotFoundError, OperationFailed
-from kimchi.model_.vms import DOM_STATE_MAP
-from kimchi.utils import kimchi_log
-
-
-HOST_STATS_INTERVAL = 1
-
-
-class HostModel(object):
- def __init__(self, **kargs):
- self.host_info = self._get_host_info()
-
- def _get_host_info(self):
- res = {}
- with open('/proc/cpuinfo') as f:
- for line in f.xreadlines():
- if "model name" in line:
- res['cpu'] = line.split(':')[1].strip()
- break
-
- res['memory'] = psutil.TOTAL_PHYMEM
- # 'fedora' '17' 'Beefy Miracle'
- distro, version, codename = platform.linux_distribution()
- res['os_distro'] = distro
- res['os_version'] = version
- res['os_codename'] = unicode(codename, "utf-8")
-
- return res
-
- def lookup(self, *name):
- return self.host_info
-
- def shutdown(self, args=None):
- # Check for running vms before shutdown
- running_vms = self.vms_get_list_by_state('running')
- if len(running_vms) > 0:
- raise OperationFailed("Shutdown not allowed: VMs are running!")
- kimchi_log.info('Host is going to shutdown.')
- os.system('shutdown -h now')
-
- def reboot(self, args=None):
- # Find running VMs
- running_vms = self._get_vms_list_by_state('running')
- if len(running_vms) > 0:
- raise OperationFailed("Reboot not allowed: VMs are running!")
- kimchi_log.info('Host is going to reboot.')
- os.system('reboot')
-
- def _get_vms_list_by_state(self, state):
- ret_list = []
- for name in self.vms_get_list():
- info = self._get_vm(name).info()
- if (DOM_STATE_MAP[info[0]]) == state:
- ret_list.append(name)
- return ret_list
-
-
-class HostStatsModel(object):
- __metaclass__ = Singleton
-
- def __init__(self, **kargs):
- self.host_stats = defaultdict(int)
- self.host_stats_thread = BackgroundTask(HOST_STATS_INTERVAL,
- self._update_host_stats)
- self.host_stats_thread.start()
-
- def lookup(self, *name):
- return {'cpu_utilization': self.host_stats['cpu_utilization'],
- 'memory': self.host_stats.get('memory'),
- 'disk_read_rate': self.host_stats['disk_read_rate'],
- 'disk_write_rate': self.host_stats['disk_write_rate'],
- 'net_recv_rate': self.host_stats['net_recv_rate'],
- 'net_sent_rate': self.host_stats['net_sent_rate']}
-
- def _update_host_stats(self):
- preTimeStamp = self.host_stats['timestamp']
- timestamp = time.time()
- # FIXME when we upgrade psutil, we can get uptime by psutil.uptime
- # we get uptime by float(open("/proc/uptime").readline().split()[0])
- # and calculate the first io_rate after the OS started.
- seconds = (timestamp - preTimeStamp if preTimeStamp else
- float(open("/proc/uptime").readline().split()[0]))
-
- self.host_stats['timestamp'] = timestamp
- self._get_host_disk_io_rate(seconds)
- self._get_host_network_io_rate(seconds)
-
- self._get_percentage_host_cpu_usage()
- self._get_host_memory_stats()
-
- def _get_percentage_host_cpu_usage(self):
- # This is cpu usage producer. This producer will calculate the usage
- # at an interval of HOST_STATS_INTERVAL.
- # The psutil.cpu_percent works as non blocking.
- # psutil.cpu_percent maintains a cpu time sample.
- # It will update the cpu time sample when it is called.
- # So only this producer can call psutil.cpu_percent in kimchi.
- self.host_stats['cpu_utilization'] = psutil.cpu_percent(None)
-
- def _get_host_memory_stats(self):
- virt_mem = psutil.virtual_memory()
- # available:
- # the actual amount of available memory that can be given
- # instantly to processes that request more memory in bytes; this
- # is calculated by summing different memory values depending on
- # the platform (e.g. free + buffers + cached on Linux)
- memory_stats = {'total': virt_mem.total,
- 'free': virt_mem.free,
- 'cached': virt_mem.cached,
- 'buffers': virt_mem.buffers,
- 'avail': virt_mem.available}
- self.host_stats['memory'] = memory_stats
-
- def _get_host_disk_io_rate(self, seconds):
- prev_read_bytes = self.host_stats['disk_read_bytes']
- prev_write_bytes = self.host_stats['disk_write_bytes']
-
- disk_io = psutil.disk_io_counters(False)
- read_bytes = disk_io.read_bytes
- write_bytes = disk_io.write_bytes
-
- rd_rate = int(float(read_bytes - prev_read_bytes) / seconds + 0.5)
- wr_rate = int(float(write_bytes - prev_write_bytes) / seconds + 0.5)
-
- self.host_stats.update({'disk_read_rate': rd_rate,
- 'disk_write_rate': wr_rate,
- 'disk_read_bytes': read_bytes,
- 'disk_write_bytes': write_bytes})
-
- def _get_host_network_io_rate(self, seconds):
- prev_recv_bytes = self.host_stats['net_recv_bytes']
- prev_sent_bytes = self.host_stats['net_sent_bytes']
-
- net_ios = psutil.network_io_counters(True)
- recv_bytes = 0
- sent_bytes = 0
- for key in set(netinfo.nics() +
- netinfo.wlans()) & set(net_ios.iterkeys()):
- recv_bytes = recv_bytes + net_ios[key].bytes_recv
- sent_bytes = sent_bytes + net_ios[key].bytes_sent
-
- rx_rate = int(float(recv_bytes - prev_recv_bytes) / seconds + 0.5)
- tx_rate = int(float(sent_bytes - prev_sent_bytes) / seconds + 0.5)
-
- self.host_stats.update({'net_recv_rate': rx_rate,
- 'net_sent_rate': tx_rate,
- 'net_recv_bytes': recv_bytes,
- 'net_sent_bytes': sent_bytes})
-
-
-class PartitionsModel(object):
- def __init__(self, **kargs):
- pass
-
- def get_list(self):
- result = disks.get_partitions_names()
- return result
-
-
-class PartitionModel(object):
- def __init__(self, **kargs):
- pass
-
- def lookup(self, name):
- if name not in disks.get_partitions_names():
- raise NotFoundError("Partition %s not found in the host"
- % name)
- return disks.get_partition_details(name)
diff --git a/src/kimchi/model_/interfaces.py b/src/kimchi/model_/interfaces.py
deleted file mode 100644
index b3b6ccd..0000000
--- a/src/kimchi/model_/interfaces.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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 import netinfo
-from kimchi.exception import NotFoundError
-from kimchi.model_.networks import NetworksModel
-
-
-class InterfacesModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.networks = NetworksModel(**kargs)
-
- def get_list(self):
- return list(set(netinfo.all_favored_interfaces()) -
- set(self.networks.get_all_networks_interfaces()))
-
-
-class InterfaceModel(object):
- def __init__(self, **kargs):
- pass
-
- def lookup(self, name):
- try:
- return netinfo.get_interface_info(name)
- except ValueError, e:
- raise NotFoundError(e)
diff --git a/src/kimchi/model_/libvirtconnection.py b/src/kimchi/model_/libvirtconnection.py
deleted file mode 100644
index 7bbb668..0000000
--- a/src/kimchi/model_/libvirtconnection.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import threading
-import time
-
-import cherrypy
-import libvirt
-
-from kimchi.utils import kimchi_log
-
-
-class LibvirtConnection(object):
- def __init__(self, uri):
- self.uri = uri
- self._connections = {}
- self._connectionLock = threading.Lock()
- self.wrappables = self.get_wrappable_objects()
-
- def get_wrappable_objects(self):
- """
- When a wrapped function returns an instance of another libvirt object,
- we also want to wrap that object so we can catch errors that happen
- when calling its methods.
- """
- objs = []
- for name in ('virDomain', 'virDomainSnapshot', 'virInterface',
- 'virNWFilter', 'virNetwork', 'virNodeDevice', 'virSecret',
- 'virStoragePool', 'virStorageVol', 'virStream'):
- try:
- attr = getattr(libvirt, name)
- except AttributeError:
- pass
- objs.append(attr)
- return tuple(objs)
-
- def get(self, conn_id=0):
- """
- Return current connection to libvirt or open a new one. Wrap all
- callable libvirt methods so we can catch connection errors and handle
- them by restarting the server.
- """
- def wrapMethod(f):
- def wrapper(*args, **kwargs):
- try:
- ret = f(*args, **kwargs)
- return ret
- except libvirt.libvirtError as e:
- edom = e.get_error_domain()
- ecode = e.get_error_code()
- EDOMAINS = (libvirt.VIR_FROM_REMOTE,
- libvirt.VIR_FROM_RPC)
- ECODES = (libvirt.VIR_ERR_SYSTEM_ERROR,
- libvirt.VIR_ERR_INTERNAL_ERROR,
- libvirt.VIR_ERR_NO_CONNECT,
- libvirt.VIR_ERR_INVALID_CONN)
- if edom in EDOMAINS and ecode in ECODES:
- kimchi_log.error('Connection to libvirt broken. '
- 'Recycling. ecode: %d edom: %d' %
- (ecode, edom))
- with self._connectionLock:
- self._connections[conn_id] = None
- raise
- wrapper.__name__ = f.__name__
- wrapper.__doc__ = f.__doc__
- return wrapper
-
- with self._connectionLock:
- conn = self._connections.get(conn_id)
- if not conn:
- retries = 5
- while True:
- retries = retries - 1
- try:
- conn = libvirt.open(self.uri)
- break
- except libvirt.libvirtError:
- kimchi_log.error('Unable to connect to libvirt.')
- if not retries:
- err = 'Libvirt is not available, exiting.'
- kimchi_log.error(err)
- cherrypy.engine.stop()
- raise
- time.sleep(2)
-
- for name in dir(libvirt.virConnect):
- method = getattr(conn, name)
- if callable(method) and not name.startswith('_'):
- setattr(conn, name, wrapMethod(method))
-
- for cls in self.wrappables:
- for name in dir(cls):
- method = getattr(cls, name)
- if callable(method) and not name.startswith('_'):
- setattr(cls, name, wrapMethod(method))
-
- self._connections[conn_id] = conn
- # In case we're running into troubles with keeping the
- # connections alive we should place here:
- # conn.setKeepAlive(interval=5, count=3)
- # However the values need to be considered wisely to not affect
- # hosts which are hosting a lot of virtual machines
- return conn
diff --git a/src/kimchi/model_/libvirtstoragepool.py b/src/kimchi/model_/libvirtstoragepool.py
deleted file mode 100644
index f4dbf2e..0000000
--- a/src/kimchi/model_/libvirtstoragepool.py
+++ /dev/null
@@ -1,257 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import copy
-import os
-import tempfile
-
-import libvirt
-
-from kimchi.exception import InvalidParameter, OperationFailed, TimeoutExpired
-from kimchi.iscsi import TargetClient
-from kimchi.rollbackcontext import RollbackContext
-from kimchi.utils import parse_cmd_output, run_command
-
-
-class StoragePoolDef(object):
- @classmethod
- def create(cls, poolArgs):
- for klass in cls.__subclasses__():
- if poolArgs['type'] == klass.poolType:
- return klass(poolArgs)
- raise OperationFailed('Unsupported pool type: %s' % poolArgs['type'])
-
- def __init__(self, poolArgs):
- self.poolArgs = poolArgs
-
- def prepare(self, conn):
- ''' Validate pool arguments and perform preparations. Operation which
- would cause side effect should be put here. Subclasses can optionally
- override this method, or it always succeeds by default. '''
- pass
-
- @property
- def xml(self):
- ''' Subclasses have to override this method to actually generate the
- storage pool XML definition. Should cause no side effect and be
- idempotent'''
- # TODO: When add new pool type, should also add the related test in
- # tests/test_storagepool.py
- raise OperationFailed('self.xml is not implemented: %s' % self)
-
-
-class DirPoolDef(StoragePoolDef):
- poolType = 'dir'
-
- @property
- def xml(self):
- # Required parameters
- # name:
- # type:
- # path:
- xml = """
- <pool type='dir'>
- <name>{name}</name>
- <target>
- <path>{path}</path>
- </target>
- </pool>
- """.format(**self.poolArgs)
- return xml
-
-
-class NetfsPoolDef(StoragePoolDef):
- poolType = 'netfs'
-
- def __init__(self, poolArgs):
- super(NetfsPoolDef, self).__init__(poolArgs)
- self.path = '/var/lib/kimchi/nfs_mount/' + self.poolArgs['name']
-
- def prepare(self, conn):
- mnt_point = tempfile.mkdtemp(dir='/tmp')
- export_path = "%s:%s" % (
- self.poolArgs['source']['host'], self.poolArgs['source']['path'])
- mount_cmd = ["mount", "-o", 'soft,timeo=100,retrans=3,retry=0',
- export_path, mnt_point]
- umount_cmd = ["umount", "-f", export_path]
- mounted = False
-
- with RollbackContext() as rollback:
- rollback.prependDefer(os.rmdir, mnt_point)
- try:
- run_command(mount_cmd, 30)
- rollback.prependDefer(run_command, umount_cmd)
- except TimeoutExpired:
- err = "Export path %s may block during nfs mount"
- raise InvalidParameter(err % export_path)
-
- with open("/proc/mounts", "rb") as f:
- rawMounts = f.read()
- output_items = ['dev_path', 'mnt_point', 'type']
- mounts = parse_cmd_output(rawMounts, output_items)
- for item in mounts:
- if 'dev_path' in item and item['dev_path'] == export_path:
- mounted = True
-
- if not mounted:
- err = "Export path %s mount failed during nfs mount"
- raise InvalidParameter(err % export_path)
-
- @property
- def xml(self):
- # Required parameters
- # name:
- # type:
- # source[host]:
- # source[path]:
- poolArgs = copy.deepcopy(self.poolArgs)
- poolArgs['path'] = self.path
- xml = """
- <pool type='netfs'>
- <name>{name}</name>
- <source>
- <host name='{source[host]}'/>
- <dir path='{source[path]}'/>
- </source>
- <target>
- <path>{path}</path>
- </target>
- </pool>
- """.format(**poolArgs)
- return xml
-
-
-class LogicalPoolDef(StoragePoolDef):
- poolType = 'logical'
-
- def __init__(self, poolArgs):
- super(LogicalPoolDef, self).__init__(poolArgs)
- self.path = '/var/lib/kimchi/logical_mount/' + self.poolArgs['name']
-
- @property
- def xml(self):
- # Required parameters
- # name:
- # type:
- # source[devices]:
- poolArgs = copy.deepcopy(self.poolArgs)
- devices = []
- for device_path in poolArgs['source']['devices']:
- devices.append('<device path="%s" />' % device_path)
-
- poolArgs['source']['devices'] = ''.join(devices)
- poolArgs['path'] = self.path
-
- xml = """
- <pool type='logical'>
- <name>{name}</name>
- <source>
- {source[devices]}
- </source>
- <target>
- <path>{path}</path>
- </target>
- </pool>
- """.format(**poolArgs)
- return xml
-
-
-class IscsiPoolDef(StoragePoolDef):
- poolType = 'iscsi'
-
- def prepare(self, conn):
- source = self.poolArgs['source']
- if not TargetClient(**source).validate():
- raise OperationFailed("Can not login to iSCSI host %s target %s" %
- (source['host'], source['target']))
- self._prepare_auth(conn)
-
- def _prepare_auth(self, conn):
- try:
- auth = self.poolArgs['source']['auth']
- except KeyError:
- return
-
- try:
- virSecret = conn.secretLookupByUsage(
- libvirt.VIR_SECRET_USAGE_TYPE_ISCSI, self.poolArgs['name'])
- except libvirt.libvirtError:
- xml = '''
- <secret ephemeral='no' private='yes'>
- <description>Secret for iSCSI storage pool {name}</description>
- <auth type='chap' username='{username}'/>
- <usage type='iscsi'>
- <target>{name}</target>
- </usage>
- </secret>'''.format(name=self.poolArgs['name'],
- username=auth['username'])
- virSecret = conn.secretDefineXML(xml)
-
- virSecret.setValue(auth['password'])
-
- def _format_port(self, poolArgs):
- try:
- port = poolArgs['source']['port']
- except KeyError:
- return ""
- return "port='%s'" % port
-
- def _format_auth(self, poolArgs):
- try:
- auth = poolArgs['source']['auth']
- except KeyError:
- return ""
-
- return '''
- <auth type='chap' username='{username}'>
- <secret type='iscsi' usage='{name}'/>
- </auth>'''.format(name=poolArgs['name'], username=auth['username'])
-
- @property
- def xml(self):
- # Required parameters
- # name:
- # type:
- # source[host]:
- # source[target]:
- #
- # Optional parameters
- # source[port]:
- poolArgs = copy.deepcopy(self.poolArgs)
- poolArgs['source'].update({'port': self._format_port(poolArgs),
- 'auth': self._format_auth(poolArgs)})
- poolArgs['path'] = '/dev/disk/by-id'
-
- xml = """
- <pool type='iscsi'>
- <name>{name}</name>
- <source>
- <host name='{source[host]}' {source[port]}/>
- <device path='{source[target]}'/>
- {source[auth]}
- </source>
- <target>
- <path>{path}</path>
- </target>
- </pool>
- """.format(**poolArgs)
- return xml
diff --git a/src/kimchi/model_/model.py b/src/kimchi/model_/model.py
deleted file mode 100644
index 6f3b6ab..0000000
--- a/src/kimchi/model_/model.py
+++ /dev/null
@@ -1,125 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import inspect
-import logging
-import os
-import sys
-
-import cherrypy
-import libvirt
-
-from kimchi.basemodel import BaseModel
-from kimchi.model_.libvirtconnection import LibvirtConnection
-from kimchi.objectstore import ObjectStore
-from kimchi.utils import import_module, listPathModules
-
-
-class Model(BaseModel):
- def __init__(self, libvirt_uri='qemu:///system', objstore_loc=None):
- self.objstore = ObjectStore(objstore_loc)
- self.conn = LibvirtConnection(libvirt_uri)
- kargs = {'objstore': self.objstore, 'conn': self.conn}
-
- if 'qemu:///' in libvirt_uri:
- self._default_pool_check()
- self._default_network_check()
-
- this = os.path.basename(__file__)
- this_mod = os.path.splitext(this)[0]
-
- models = []
- for mod_name in listPathModules(os.path.dirname(__file__)):
- if mod_name.startswith("_") or mod_name == this_mod:
- continue
-
- module = import_module('model_.' + mod_name)
- members = inspect.getmembers(module, inspect.isclass)
- for cls_name, instance in members:
- if inspect.getmodule(instance) == module:
- if cls_name.endswith('Model'):
- models.append(instance(**kargs))
-
- return super(Model, self).__init__(models)
-
- def _default_network_check(self):
- conn = self.conn.get()
- xml = """
- <network>
- <name>default</name>
- <forward mode='nat'/>
- <bridge name='virbr0' stp='on' delay='0' />
- <ip address='192.168.122.1' netmask='255.255.255.0'>
- <dhcp>
- <range start='192.168.122.2' end='192.168.122.254' />
- </dhcp>
- </ip>
- </network>
- """
- try:
- net = conn.networkLookupByName("default")
- except libvirt.libvirtError:
- try:
- net = conn.networkDefineXML(xml)
- except libvirt.libvirtError, e:
- cherrypy.log.error("Fatal: Cannot create default network "
- "because of %s, exit kimchid" % e.message,
- severity=logging.ERROR)
- sys.exit(1)
-
- if net.isActive() == 0:
- try:
- net.create()
- except libvirt.libvirtError, e:
- cherrypy.log.error("Fatal: Cannot activate default network "
- "because of %s, exit kimchid" % e.message,
- severity=logging.ERROR)
- sys.exit(1)
-
- def _default_pool_check(self):
- conn = self.conn.get()
- xml = """
- <pool type='dir'>
- <name>default</name>
- <target>
- <path>/var/lib/libvirt/images</path>
- </target>
- </pool>
- """
- try:
- pool = conn.storagePoolLookupByName("default")
- except libvirt.libvirtError:
- try:
- pool = conn.storagePoolCreateXML(xml, 0)
- 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_/networks.py b/src/kimchi/model_/networks.py
deleted file mode 100644
index b164141..0000000
--- a/src/kimchi/model_/networks.py
+++ /dev/null
@@ -1,265 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import ipaddr
-import libvirt
-
-from kimchi import netinfo
-from kimchi import network as knetwork
-from kimchi import networkxml
-from kimchi import xmlutils
-from kimchi.exception import InvalidOperation, InvalidParameter
-from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
-
-
-class NetworksModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
-
- def create(self, params):
- conn = self.conn.get()
- name = params['name']
- if name in self.get_list():
- raise InvalidOperation("Network %s already exists" % name)
-
- connection = params["connection"]
- # set forward mode, isolated do not need forward
- if connection != 'isolated':
- params['forward'] = {'mode': connection}
-
- # set subnet, bridge network do not need subnet
- if connection in ["nat", 'isolated']:
- self._set_network_subnet(params)
-
- # only bridge network need bridge(linux bridge) or interface(macvtap)
- if connection == 'bridge':
- self._set_network_bridge(params)
-
- xml = networkxml.to_network_xml(**params)
-
- try:
- network = conn.networkDefineXML(xml)
- network.setAutostart(True)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- return name
-
- def get_list(self):
- conn = self.conn.get()
- return sorted(conn.listNetworks() + conn.listDefinedNetworks())
-
- def _set_network_subnet(self, params):
- netaddr = params.get('subnet', '')
- net_addrs = []
- # lookup a free network address for nat and isolated automatically
- if not netaddr:
- for net_name in self.get_list():
- network = self._get_network(net_name)
- xml = network.XMLDesc(0)
- subnet = NetworkModel.get_network_from_xml(xml)['subnet']
- subnet and net_addrs.append(ipaddr.IPNetwork(subnet))
- netaddr = knetwork.get_one_free_network(net_addrs)
- if not netaddr:
- raise OperationFailed("can not find a free IP address for "
- "network '%s'" % params['name'])
-
- try:
- ip = ipaddr.IPNetwork(netaddr)
- except ValueError as e:
- raise InvalidParameter("%s" % e)
-
- if ip.ip == ip.network:
- ip.ip = ip.ip + 1
-
- dhcp_start = str(ip.ip + ip.numhosts / 2)
- dhcp_end = str(ip.ip + ip.numhosts - 2)
- params.update({'net': str(ip),
- 'dhcp': {'range': {'start': dhcp_start,
- 'end': dhcp_end}}})
-
- def _set_network_bridge(self, params):
- try:
- iface = params['interface']
- if iface in self.get_all_networks_interfaces():
- raise InvalidParameter("interface '%s' already in use." %
- iface)
- except KeyError, e:
- raise MissingParameter(e)
- if netinfo.is_bridge(iface):
- params['bridge'] = iface
- elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface):
- if params.get('vlan_id') is None:
- params['forward']['dev'] = iface
- else:
- params['bridge'] = \
- self._create_vlan_tagged_bridge(str(iface),
- str(params['vlan_id']))
- else:
- raise InvalidParameter("the interface should be bare nic, "
- "bonding or bridge device.")
-
- def get_all_networks_interfaces(self):
- net_names = self.get_list()
- interfaces = []
- for name in net_names:
- conn = self.conn.get()
- network = conn.networkLookupByName(name)
- xml = network.XMLDesc(0)
- net_dict = NetworkModel.get_network_from_xml(xml)
- forward = net_dict['forward']
- (forward['mode'] == 'bridge' and forward['interface'] and
- interfaces.append(forward['interface'][0]) is None or
- interfaces.extend(forward['interface'] + forward['pf']))
- net_dict['bridge'] and interfaces.append(net_dict['bridge'])
- return interfaces
-
- def _create_vlan_tagged_bridge(self, interface, vlan_id):
- br_name = '-'.join(('kimchi', interface, vlan_id))
- br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface,
- vlan_id)
- conn = self.conn.get()
- conn.changeBegin()
- try:
- vlan_tagged_br = conn.interfaceDefineXML(br_xml)
- vlan_tagged_br.create()
- except libvirt.libvirtError as e:
- conn.changeRollback()
- raise OperationFailed(e.message)
- else:
- conn.changeCommit()
- return br_name
-
-
-class NetworkModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
-
- def lookup(self, name):
- network = self._get_network(name)
- xml = network.XMLDesc(0)
- net_dict = self.get_network_from_xml(xml)
- subnet = net_dict['subnet']
- dhcp = net_dict['dhcp']
- forward = net_dict['forward']
- interface = net_dict['bridge']
-
- connection = forward['mode'] or "isolated"
- # FIXME, if we want to support other forward mode well.
- if connection == 'bridge':
- # macvtap bridge
- interface = interface or forward['interface'][0]
- # exposing the network on linux bridge or macvtap interface
- interface_subnet = knetwork.get_dev_netaddr(interface)
- subnet = subnet if subnet else interface_subnet
-
- # libvirt use format 192.168.0.1/24, standard should be 192.168.0.0/24
- # http://www.ovirt.org/File:Issue3.png
- if subnet:
- subnet = ipaddr.IPNetwork(subnet)
- subnet = "%s/%s" % (subnet.network, subnet.prefixlen)
-
- return {'connection': connection,
- 'interface': interface,
- 'subnet': subnet,
- 'dhcp': dhcp,
- 'vms': self._get_vms_attach_to_a_network(name),
- 'autostart': network.autostart() == 1,
- 'state': network.isActive() and "active" or "inactive"}
-
- def _get_vms_attach_to_a_network(self, network):
- vms = []
- conn = self.conn.get()
- for dom in conn.listAllDomains(0):
- networks = self._vm_get_networks(dom)
- if network in networks:
- vms.append(dom.name())
- return vms
-
- def _vm_get_networks(self, dom):
- xml = dom.XMLDesc(0)
- xpath = "/domain/devices/interface[@type='network']/source/@network"
- return xmlutils.xpath_get_text(xml, xpath)
-
- def activate(self, name):
- network = self._get_network(name)
- network.create()
-
- def deactivate(self, name):
- network = self._get_network(name)
- network.destroy()
-
- def delete(self, name):
- network = self._get_network(name)
- if network.isActive():
- raise InvalidOperation(
- "Unable to delete the active network %s" % name)
- self._remove_vlan_tagged_bridge(network)
- network.undefine()
-
- def _get_network(self, name):
- conn = self.conn.get()
- try:
- return conn.networkLookupByName(name)
- except libvirt.libvirtError as e:
- raise NotFoundError("Network '%s' not found: %s" %
- (name, e.get_error_message()))
-
- @staticmethod
- def get_network_from_xml(xml):
- address = xmlutils.xpath_get_text(xml, "/network/ip/@address")
- address = address and address[0] or ''
- netmask = xmlutils.xpath_get_text(xml, "/network/ip/@netmask")
- netmask = netmask and netmask[0] or ''
- net = address and netmask and "/".join([address, netmask]) or ''
-
- dhcp_start = xmlutils.xpath_get_text(xml,
- "/network/ip/dhcp/range/@start")
- dhcp_start = dhcp_start and dhcp_start[0] or ''
- dhcp_end = xmlutils.xpath_get_text(xml, "/network/ip/dhcp/range/@end")
- dhcp_end = dhcp_end and dhcp_end[0] or ''
- dhcp = {'start': dhcp_start, 'end': dhcp_end}
-
- forward_mode = xmlutils.xpath_get_text(xml, "/network/forward/@mode")
- forward_mode = forward_mode and forward_mode[0] or ''
- forward_if = xmlutils.xpath_get_text(xml,
- "/network/forward/interface/@dev")
- forward_pf = xmlutils.xpath_get_text(xml, "/network/forward/pf/@dev")
- bridge = xmlutils.xpath_get_text(xml, "/network/bridge/@name")
- bridge = bridge and bridge[0] or ''
- return {'subnet': net, 'dhcp': dhcp, 'bridge': bridge,
- 'forward': {'mode': forward_mode,
- 'interface': forward_if,
- 'pf': forward_pf}}
-
- def _remove_vlan_tagged_bridge(self, network):
- try:
- bridge = network.bridgeName()
- except libvirt.libvirtError:
- pass
- else:
- if bridge.startswith('kimchi-'):
- conn = self.conn.get()
- iface = conn.interfaceLookupByName(bridge)
- if iface.isActive():
- iface.destroy()
- iface.undefine()
diff --git a/src/kimchi/model_/plugins.py b/src/kimchi/model_/plugins.py
deleted file mode 100644
index d6756d0..0000000
--- a/src/kimchi/model_/plugins.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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.utils import get_enabled_plugins
-
-
-class PluginsModel(object):
- def __init__(self, **kargs):
- pass
-
- def get_list(self):
- return [plugin for (plugin, config) in get_enabled_plugins()]
diff --git a/src/kimchi/model_/storagepools.py b/src/kimchi/model_/storagepools.py
deleted file mode 100644
index 2fca8e4..0000000
--- a/src/kimchi/model_/storagepools.py
+++ /dev/null
@@ -1,246 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import libvirt
-
-from kimchi import xmlutils
-from kimchi.scan import Scanner
-from kimchi.exception import InvalidOperation, MissingParameter
-from kimchi.exception import NotFoundError, OperationFailed
-from kimchi.model_.libvirtstoragepool import StoragePoolDef
-from kimchi.utils import add_task, kimchi_log
-
-
-ISO_POOL_NAME = u'kimchi_isos'
-POOL_STATE_MAP = {0: 'inactive',
- 1: 'initializing',
- 2: 'active',
- 3: 'degraded',
- 4: 'inaccessible'}
-
-STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
- 'path': '/pool/source/dir/@path'}}
-
-
-class StoragePoolsModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.objstore = kargs['objstore']
- self.scanner = Scanner(self._clean_scan)
- self.scanner.delete()
-
- def get_list(self):
- try:
- conn = self.conn.get()
- names = conn.listStoragePools()
- names += conn.listDefinedStoragePools()
- return sorted(names)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def create(self, params):
- task_id = None
- conn = self.conn.get()
- try:
- name = params['name']
- if name in (ISO_POOL_NAME, ):
- raise InvalidOperation("StoragePool already exists")
-
- if params['type'] == 'kimchi-iso':
- task_id = self._do_deep_scan(params)
- poolDef = StoragePoolDef.create(params)
- poolDef.prepare(conn)
- xml = poolDef.xml
- except KeyError, key:
- raise MissingParameter(key)
-
- if name in self.get_list():
- err = "The name %s has been used by a pool"
- raise InvalidOperation(err % name)
-
- 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())
- return name
-
- def _clean_scan(self, pool_name):
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(pool_name)
- pool.destroy()
- with self.objstore as session:
- session.delete('scanning', pool_name)
- except Exception, e:
- err = "Exception %s occured when cleaning scan result"
- kimchi_log.debug(err % 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_list():
- try:
- res = self.storagepool_lookup(pool)
- if res['state'] == 'active':
- scan_params['ignore_list'].append(res['path'])
- except Exception, e:
- err = "Exception %s occured when get ignore path"
- kimchi_log.debug(err % e.message)
-
- params['path'] = self.scanner.scan_dir_prepare(params['name'])
- scan_params['pool_path'] = params['path']
- task_id = add_task('', self.scanner.start_scan, self.objstore,
- 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
-
-
-class StoragePoolModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.objstore = kargs['objstore']
-
- @staticmethod
- def get_storagepool(name, conn):
- conn = conn.get()
- try:
- return conn.storagePoolLookupByName(name)
- except libvirt.libvirtError as e:
- if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_POOL:
- raise NotFoundError("Storage Pool '%s' not found" % name)
- else:
- raise
-
- 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 lookup(self, name):
- pool = self.get_storagepool(name, self.conn)
- 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': 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 update(self, name, params):
- autostart = params['autostart']
- if autostart not in [True, False]:
- raise InvalidOperation("Autostart flag must be true or false")
- pool = self.get_storagepool(name, self.conn)
- if autostart:
- pool.setAutostart(1)
- else:
- pool.setAutostart(0)
- ident = pool.name()
- return ident
-
- def activate(self, name):
- pool = self.get_storagepool(name, self.conn)
- try:
- pool.create(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def deactivate(self, name):
- pool = self.get_storagepool(name, self.conn)
- try:
- pool.destroy()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def delete(self, name):
- pool = self.get_storagepool(name, self.conn)
- if pool.isActive():
- err = "Unable to delete the active storagepool %s"
- raise InvalidOperation(err % name)
- try:
- pool.undefine()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
-
-class IsoPoolModel(object):
- def __init__(self, **kargs):
- pass
-
- def lookup(self, name):
- return {'state': 'active',
- 'type': 'kimchi-iso'}
diff --git a/src/kimchi/model_/storageservers.py b/src/kimchi/model_/storageservers.py
deleted file mode 100644
index 1728394..0000000
--- a/src/kimchi/model_/storageservers.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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 NotFoundError
-from kimchi.model_.storagepools import StoragePoolModel, STORAGE_SOURCES
-
-
-class StorageServersModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.pool = StoragePoolModel(**kargs)
-
- def get_list(self, _target_type=None):
- if not _target_type:
- target_type = STORAGE_SOURCES.keys()
- else:
- target_type = [_target_type]
- pools = self.pools.get_list()
-
- conn = self.conn.get()
- pools = conn.listStoragePools()
- pools += conn.listDefinedStoragePools()
-
- server_list = []
- for pool in pools:
- try:
- pool_info = self.pool.lookup(pool)
- if (pool_info['type'] in target_type and
- pool_info['source']['addr'] not in server_list):
- # Avoid to add same server for multiple times
- # if it hosts more than one storage type
- server_list.append(pool_info['source']['addr'])
- except NotFoundError:
- pass
-
- return server_list
-
-
-class StorageServerModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.pool = StoragePoolModel(**kargs)
-
- def lookup(self, server):
- conn = self.conn.get()
- pools = conn.listStoragePools()
- pools += conn.listDefinedStoragePools()
- for pool in pools:
- try:
- pool_info = self.pool.lookup(pool)
- if pool_info['source'] and \
- pool_info['source']['addr'] == server:
- return dict(host=server)
- except NotFoundError:
- # Avoid inconsistent pool result because of lease between list
- # lookup
- pass
-
- raise NotFoundError('server %s does not used by kimchi' % server)
diff --git a/src/kimchi/model_/storagetargets.py b/src/kimchi/model_/storagetargets.py
deleted file mode 100644
index be73732..0000000
--- a/src/kimchi/model_/storagetargets.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import libvirt
-import lxml.etree as ET
-from lxml import objectify
-from lxml.builder import E
-
-from kimchi.model_.config import CapabilitiesModel
-from kimchi.model_.storagepools import STORAGE_SOURCES
-from kimchi.utils import kimchi_log, patch_find_nfs_target
-
-
-class StorageTargetsModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.caps = CapabilitiesModel()
-
- def get_list(self, storage_server, _target_type=None):
- target_list = list()
-
- if not _target_type:
- target_types = STORAGE_SOURCES.keys()
- else:
- target_types = [_target_type]
-
- for target_type in target_types:
- if not self.caps.nfs_target_probe and target_type == 'netfs':
- targets = patch_find_nfs_target(storage_server)
- else:
- xml = self._get_storage_server_spec(server=storage_server,
- target_type=target_type)
- conn = self.conn.get()
- try:
- ret = conn.findStoragePoolSources(target_type, xml, 0)
- except libvirt.libvirtError as e:
- err = "Query storage pool source fails because of %s"
- kimchi_log.warning(err, e.get_error_message())
- continue
-
- targets = self._parse_target_source_result(target_type, ret)
-
- target_list.extend(targets)
- return target_list
-
- def _get_storage_server_spec(**kwargs):
- # Required parameters:
- # server:
- # target_type:
- extra_args = []
- if kwargs['target_type'] == 'netfs':
- extra_args.append(E.format(type='nfs'))
- obj = E.source(E.host(name=kwargs['server']), *extra_args)
- xml = ET.tostring(obj)
- return xml
-
- def _parse_target_source_result(target_type, xml_str):
- root = objectify.fromstring(xml_str)
- ret = []
- for source in root.getchildren():
- if target_type == 'netfs':
- host_name = source.host.get('name')
- target_path = source.dir.get('path')
- type = source.format.get('type')
- ret.append(dict(host=host_name, target_type=type,
- target=target_path))
- return ret
diff --git a/src/kimchi/model_/storagevolumes.py b/src/kimchi/model_/storagevolumes.py
deleted file mode 100644
index 0edac52..0000000
--- a/src/kimchi/model_/storagevolumes.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import os
-
-import libvirt
-
-from kimchi import xmlutils
-from kimchi.exception import InvalidOperation, IsoFormatError
-from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
-from kimchi.isoinfo import IsoImage
-from kimchi.model_.storagepools import StoragePoolModel
-
-
-VOLUME_TYPE_MAP = {0: 'file',
- 1: 'block',
- 2: 'directory',
- 3: 'network'}
-
-
-class StorageVolumesModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
-
- def create(self, pool, params):
- vol_xml = """
- <volume>
- <name>%(name)s</name>
- <allocation unit="MiB">%(allocation)s</allocation>
- <capacity unit="MiB">%(capacity)s</capacity>
- <source>
- </source>
- <target>
- <format type='%(format)s'/>
- </target>
- </volume>
- """
- params.setdefault('allocation', 0)
- params.setdefault('format', 'qcow2')
-
- try:
- pool = StoragePoolModel.get_storagepool(pool, self.conn)
- name = params['name']
- xml = vol_xml % params
- except KeyError, key:
- raise MissingParameter(key)
-
- try:
- pool.createXML(xml, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
- return name
-
- def get_list(self, pool):
- pool = StoragePoolModel.get_storagepool(pool, self.conn)
- if not pool.isActive():
- err = "Unable to list volumes in inactive storagepool %s"
- raise InvalidOperation(err % pool.name())
- try:
- pool.refresh(0)
- return pool.listVolumes()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
-
-class StorageVolumeModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
-
- def _get_storagevolume(self, pool, name):
- pool = StoragePoolModel.get_storagepool(pool, self.conn)
- if not pool.isActive():
- err = "Unable to list volumes in inactive storagepool %s"
- raise InvalidOperation(err % pool.name())
- try:
- return pool.storageVolLookupByName(name)
- except libvirt.libvirtError as e:
- if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL:
- raise NotFoundError("Storage Volume '%s' not found" % name)
- else:
- raise
-
- def lookup(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=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))
- os_distro = os_version = 'unknown'
- 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(self, pool, name):
- volume = self._get_storagevolume(pool, name)
- try:
- volume.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def delete(self, pool, name):
- volume = self._get_storagevolume(pool, name)
- try:
- volume.delete(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def resize(self, pool, name, size):
- size = size << 20
- volume = self._get_storagevolume(pool, name)
- try:
- volume.resize(size, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
-
-class IsoVolumesModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.storagevolume = StorageVolumeModel(**kargs)
-
- def get_list(self):
- iso_volumes = []
- conn = self.conn.get()
- pools = conn.listStoragePools()
- pools += conn.listDefinedStoragePools()
-
- for pool in pools:
- try:
- pool.refresh(0)
- volumes = pool.listVolumes()
- except InvalidOperation:
- # Skip inactive pools
- continue
-
- for volume in volumes:
- res = self.storagevolume.lookup(pool, volume)
- if res['format'] == 'iso':
- res['name'] = '%s' % volume
- iso_volumes.append(res)
- return iso_volumes
diff --git a/src/kimchi/model_/tasks.py b/src/kimchi/model_/tasks.py
deleted file mode 100644
index 40ca1d6..0000000
--- a/src/kimchi/model_/tasks.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-
-class TasksModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
-
- def get_list(self):
- with self.objstore as session:
- return session.get_list('task')
-
-
-class TaskModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
-
- def lookup(self, id):
- with self.objstore as session:
- return session.get('task', str(id))
diff --git a/src/kimchi/model_/templates.py b/src/kimchi/model_/templates.py
deleted file mode 100644
index 03632a6..0000000
--- a/src/kimchi/model_/templates.py
+++ /dev/null
@@ -1,172 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import copy
-
-import libvirt
-
-from kimchi import xmlutils
-from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError
-from kimchi.utils import pool_name_from_uri
-from kimchi.vmtemplate import VMTemplate
-
-
-class TemplatesModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
- self.conn = kargs['conn']
-
- def create(self, params):
- name = params['name']
- conn = self.conn.get()
-
- pool_uri = params.get(u'storagepool', '')
- if pool_uri:
- pool_name = pool_name_from_uri(pool_uri)
- try:
- conn.storagePoolLookupByName(pool_name)
- except Exception as e:
- err = "Storagepool specified is not valid: %s."
- raise InvalidParameter(err % e.message)
-
- for net_name in params.get(u'networks', []):
- try:
- conn.networkLookupByName(net_name)
- except Exception, e:
- raise InvalidParameter("Network '%s' specified by template "
- "does not exist." % net_name)
-
- with self.objstore as session:
- if name in session.get_list('template'):
- raise InvalidOperation("Template already exists")
- t = LibvirtVMTemplate(params, scan=True)
- session.store('template', name, t.info)
- return name
-
- def get_list(self):
- with self.objstore as session:
- return session.get_list('template')
-
-
-class TemplateModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
- self.conn = kargs['conn']
- self.templates = TemplatesModel(**kargs)
-
- @staticmethod
- def get_template(name, objstore, conn, overrides=None):
- with objstore as session:
- params = session.get('template', name)
- if overrides:
- params.update(overrides)
- return LibvirtVMTemplate(params, False, conn)
-
- def lookup(self, name):
- t = self.get_template(name, self.objstore, self.conn)
- return t.info
-
- def delete(self, name):
- with self.objstore as session:
- session.delete('template', name)
-
- def update(self, name, params):
- old_t = self.lookup(name)
- new_t = copy.copy(old_t)
- new_t.update(params)
- ident = name
-
- pool_uri = new_t.get(u'storagepool', '')
- pool_name = pool_name_from_uri(pool_uri)
- try:
- conn = self.conn.get()
- conn.storagePoolLookupByName(pool_name)
- except Exception as e:
- err = "Storagepool specified is not valid: %s."
- raise InvalidParameter(err % e.message)
-
- for net_name in params.get(u'networks', []):
- try:
- conn = self.conn.get()
- conn.networkLookupByName(net_name)
- except Exception, e:
- raise InvalidParameter("Network '%s' specified by template "
- "does not exist" % net_name)
-
- self.delete(name)
- try:
- ident = self.templates.create(new_t)
- except:
- ident = self.templates.create(old_t)
- raise
- return ident
-
-
-class LibvirtVMTemplate(VMTemplate):
- def __init__(self, args, scan=False, conn=None):
- VMTemplate.__init__(self, args, scan)
- self.conn = conn
-
- def _storage_validate(self):
- pool_uri = self.info['storagepool']
- pool_name = pool_name_from_uri(pool_uri)
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(pool_name)
- except libvirt.libvirtError:
- err = 'Storage specified by template does not exist'
- raise InvalidParameter(err)
-
- if not pool.isActive():
- err = 'Storage specified by template is not active'
- raise InvalidParameter(err)
-
- return pool
-
- def _network_validate(self):
- names = self.info['networks']
- for name in names:
- try:
- conn = self.conn.get()
- network = conn.networkLookupByName(name)
- except libvirt.libvirtError:
- err = 'Network specified by template does not exist'
- raise InvalidParameter(err)
-
- if not network.isActive():
- err = 'Network specified by template is not active'
- raise InvalidParameter(err)
-
- def _get_storage_path(self):
- pool = self._storage_validate()
- xml = pool.XMLDesc(0)
- return xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
-
- def fork_vm_storage(self, vm_uuid):
- # Provision storage:
- # TODO: Rebase on the storage API once upstream
- pool = self._storage_validate()
- vol_list = self.to_volume_list(vm_uuid)
- for v in vol_list:
- # outgoing text to libvirt, encode('utf-8')
- pool.createXML(v['xml'].encode('utf-8'), 0)
- return vol_list
diff --git a/src/kimchi/model_/utils.py b/src/kimchi/model_/utils.py
deleted file mode 100644
index a27b867..0000000
--- a/src/kimchi/model_/utils.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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 OperationFailed
-
-
-def get_vm_name(vm_name, t_name, name_list):
- if vm_name:
- return vm_name
- for i in xrange(1, 1000):
- vm_name = "%s-vm-%i" % (t_name, i)
- if vm_name not in name_list:
- return vm_name
- raise OperationFailed("Unable to choose a VM name")
diff --git a/src/kimchi/model_/vmifaces.py b/src/kimchi/model_/vmifaces.py
deleted file mode 100644
index 4ec0c7b..0000000
--- a/src/kimchi/model_/vmifaces.py
+++ /dev/null
@@ -1,135 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import random
-
-import libvirt
-from lxml import etree, objectify
-from lxml.builder import E
-
-from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError
-from kimchi.model_.vms import DOM_STATE_MAP, VMModel
-
-
-class VMIfacesModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
-
- def get_list(self, vm):
- macs = []
- for iface in self.get_vmifaces(vm, self.conn):
- macs.append(iface.mac.get('address'))
- return macs
-
- def create(self, vm, params):
- def randomMAC():
- mac = [0x52, 0x54, 0x00,
- random.randint(0x00, 0x7f),
- random.randint(0x00, 0xff),
- random.randint(0x00, 0xff)]
- return ':'.join(map(lambda x: "%02x" % x, mac))
-
- conn = self.conn.get()
- networks = conn.listNetworks() + conn.listDefinedNetworks()
-
- if params["type"] == "network" and params["network"] not in networks:
- raise InvalidParameter("%s is not an available network" %
- params["network"])
-
- dom = VMModel.get_vm(vm, self.conn)
- if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
- raise InvalidOperation("do not support hot plugging attach "
- "guest interface")
-
- macs = (iface.mac.get('address')
- for iface in self.get_vmifaces(vm, self.conn))
-
- mac = randomMAC()
- while True:
- if mac not in macs:
- break
- mac = randomMAC()
-
- children = [E.mac(address=mac)]
- ("network" in params.keys() and
- children.append(E.source(network=params['network'])))
- ("model" in params.keys() and
- children.append(E.model(type=params['model'])))
- attrib = {"type": params["type"]}
-
- xml = etree.tostring(E.interface(*children, **attrib))
-
- dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
-
- return mac
-
- @staticmethod
- def get_vmifaces(vm, conn):
- dom = VMModel.get_vm(vm, conn)
- xml = dom.XMLDesc(0)
- root = objectify.fromstring(xml)
-
- return root.devices.findall("interface")
-
-
-class VMIfaceModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
-
- def _get_vmiface(self, vm, mac):
- ifaces = VMIfacesModel.get_vmifaces(vm, self.conn)
-
- for iface in ifaces:
- if iface.mac.get('address') == mac:
- return iface
- return None
-
- def lookup(self, vm, mac):
- info = {}
-
- iface = self._get_vmiface(vm, mac)
- if iface is None:
- raise NotFoundError('iface: "%s"' % mac)
-
- info['type'] = iface.attrib['type']
- info['mac'] = iface.mac.get('address')
- if iface.find("model") is not None:
- info['model'] = iface.model.get('type')
- if info['type'] == 'network':
- info['network'] = iface.source.get('network')
- if info['type'] == 'bridge':
- info['bridge'] = iface.source.get('bridge')
-
- return info
-
- def delete(self, vm, mac):
- dom = VMModel.get_vm(vm, self.conn)
- iface = self._get_vmiface(vm, mac)
-
- if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
- raise InvalidOperation("do not support hot plugging detach "
- "guest interface")
- if iface is None:
- raise NotFoundError('iface: "%s"' % mac)
-
- dom.detachDeviceFlags(etree.tostring(iface),
- libvirt.VIR_DOMAIN_AFFECT_CURRENT)
diff --git a/src/kimchi/model_/vms.py b/src/kimchi/model_/vms.py
deleted file mode 100644
index 3de7fde..0000000
--- a/src/kimchi/model_/vms.py
+++ /dev/null
@@ -1,447 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Aline Manera <alinefm at 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
-
-import os
-import time
-import uuid
-from xml.etree import ElementTree
-
-import libvirt
-from cherrypy.process.plugins import BackgroundTask
-
-from kimchi import vnc
-from kimchi import xmlutils
-from kimchi.exception import InvalidOperation, InvalidParameter
-from kimchi.exception import NotFoundError, OperationFailed
-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 template_name_from_uri
-
-
-DOM_STATE_MAP = {0: 'nostate',
- 1: 'running',
- 2: 'blocked',
- 3: 'paused',
- 4: 'shutdown',
- 5: 'shutoff',
- 6: 'crashed'}
-
-GUESTS_STATS_INTERVAL = 5
-VM_STATIC_UPDATE_PARAMS = {'name': './name'}
-VM_LIVE_UPDATE_PARAMS = {}
-
-stats = {}
-
-
-class VMsModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.objstore = kargs['objstore']
- self.caps = CapabilitiesModel()
- self.guests_stats_thread = BackgroundTask(GUESTS_STATS_INTERVAL,
- self._update_guests_stats)
- self.guests_stats_thread.start()
-
- def _update_guests_stats(self):
- vm_list = self.get_list()
-
- for name in vm_list:
- dom = VMModel.get_vm(name, self.conn)
- vm_uuid = dom.UUIDString()
- info = dom.info()
- state = DOM_STATE_MAP[info[0]]
-
- if state != 'running':
- stats[vm_uuid] = {}
- continue
-
- if stats.get(vm_uuid, None) is None:
- stats[vm_uuid] = {}
-
- timestamp = time.time()
- prevStats = stats.get(vm_uuid, {})
- seconds = timestamp - prevStats.get('timestamp', 0)
- stats[vm_uuid].update({'timestamp': timestamp})
-
- self._get_percentage_cpu_usage(vm_uuid, info, seconds)
- self._get_network_io_rate(vm_uuid, dom, seconds)
- self._get_disk_io_rate(vm_uuid, dom, seconds)
-
- def _get_percentage_cpu_usage(self, vm_uuid, info, seconds):
- prevCpuTime = stats[vm_uuid].get('cputime', 0)
-
- cpus = info[3]
- cpuTime = info[4] - prevCpuTime
-
- base = (((cpuTime) * 100.0) / (seconds * 1000.0 * 1000.0 * 1000.0))
- percentage = max(0.0, min(100.0, base / cpus))
-
- stats[vm_uuid].update({'cputime': info[4], 'cpu': percentage})
-
- def _get_network_io_rate(self, vm_uuid, dom, seconds):
- prevNetRxKB = stats[vm_uuid].get('netRxKB', 0)
- prevNetTxKB = stats[vm_uuid].get('netTxKB', 0)
- currentMaxNetRate = stats[vm_uuid].get('max_net_io', 100)
-
- rx_bytes = 0
- tx_bytes = 0
-
- tree = ElementTree.fromstring(dom.XMLDesc(0))
- for target in tree.findall('devices/interface/target'):
- dev = target.get('dev')
- io = dom.interfaceStats(dev)
- rx_bytes += io[0]
- tx_bytes += io[4]
-
- netRxKB = float(rx_bytes) / 1000
- netTxKB = float(tx_bytes) / 1000
-
- rx_stats = (netRxKB - prevNetRxKB) / seconds
- tx_stats = (netTxKB - prevNetTxKB) / seconds
-
- rate = rx_stats + tx_stats
- max_net_io = round(max(currentMaxNetRate, int(rate)), 1)
-
- stats[vm_uuid].update({'net_io': rate, 'max_net_io': max_net_io,
- 'netRxKB': netRxKB, 'netTxKB': netTxKB})
-
- def _get_disk_io_rate(self, vm_uuid, dom, seconds):
- prevDiskRdKB = stats[vm_uuid].get('diskRdKB', 0)
- prevDiskWrKB = stats[vm_uuid].get('diskWrKB', 0)
- currentMaxDiskRate = stats[vm_uuid].get('max_disk_io', 100)
-
- rd_bytes = 0
- wr_bytes = 0
-
- tree = ElementTree.fromstring(dom.XMLDesc(0))
- for target in tree.findall("devices/disk/target"):
- dev = target.get("dev")
- io = dom.blockStats(dev)
- rd_bytes += io[1]
- wr_bytes += io[3]
-
- diskRdKB = float(rd_bytes) / 1024
- diskWrKB = float(wr_bytes) / 1024
-
- rd_stats = (diskRdKB - prevDiskRdKB) / seconds
- wr_stats = (diskWrKB - prevDiskWrKB) / seconds
-
- rate = rd_stats + wr_stats
- max_disk_io = round(max(currentMaxDiskRate, int(rate)), 1)
-
- stats[vm_uuid].update({'disk_io': rate,
- 'max_disk_io': max_disk_io,
- 'diskRdKB': diskRdKB,
- 'diskWrKB': diskWrKB})
-
- def create(self, params):
- conn = self.conn.get()
- t_name = template_name_from_uri(params['template'])
- vm_uuid = str(uuid.uuid4())
- vm_list = self.get_list()
- name = get_vm_name(params.get('name'), t_name, vm_list)
- # incoming text, from js json, is unicode, do not need decode
- if name in vm_list:
- raise InvalidOperation("VM already exists")
-
- vm_overrides = dict()
- pool_uri = params.get('storagepool')
- if pool_uri:
- vm_overrides['storagepool'] = pool_uri
- t = TemplateModel.get_template(t_name, self.objstore, self.conn,
- vm_overrides)
-
- if not self.caps.qemu_stream and t.info.get('iso_stream', False):
- err = "Remote ISO image is not supported by this server."
- raise InvalidOperation(err)
-
- t.validate()
- vol_list = t.fork_vm_storage(vm_uuid)
-
- # Store the icon for displaying later
- icon = t.info.get('icon')
- if icon:
- with self.objstore as session:
- session.store('vm', vm_uuid, {'icon': icon})
-
- libvirt_stream = False
- if len(self.caps.libvirt_stream_protocols) == 0:
- libvirt_stream = True
-
- graphics = params.get('graphics')
- xml = t.to_vm_xml(name, vm_uuid,
- libvirt_stream=libvirt_stream,
- qemu_stream_dns=self.caps.qemu_stream_dns,
- graphics=graphics)
-
- try:
- conn.defineXML(xml.encode('utf-8'))
- except libvirt.libvirtError as e:
- for v in vol_list:
- vol = conn.storageVolLookupByPath(v['path'])
- vol.delete(0)
- raise OperationFailed(e.get_error_message())
-
- return name
-
- def get_list(self):
- conn = self.conn.get()
- names = [dom.name().decode('utf-8') for dom in conn.listAllDomains(0)]
- return sorted(names, key=unicode.lower)
-
-
-class VMModel(object):
- def __init__(self, **kargs):
- self.conn = kargs['conn']
- self.objstore = kargs['objstore']
- self.vms = VMsModel(**kargs)
- self.vmscreenshot = VMScreenshotModel(**kargs)
-
- def update(self, name, params):
- dom = self.get_vm(name, self.conn)
- dom = self._static_vm_update(dom, params)
- self._live_vm_update(dom, params)
- return dom.name()
-
- def _static_vm_update(self, dom, params):
- state = DOM_STATE_MAP[dom.info()[0]]
-
- old_xml = new_xml = dom.XMLDesc(0)
-
- for key, val in params.items():
- if key in VM_STATIC_UPDATE_PARAMS:
- xpath = VM_STATIC_UPDATE_PARAMS[key]
- new_xml = xmlutils.xml_item_update(new_xml, xpath, val)
-
- try:
- if 'name' in params:
- if state == 'running':
- err = "VM name only can be updated when vm is powered off."
- raise InvalidParameter(err)
- else:
- dom.undefine()
- conn = self.conn.get()
- dom = conn.defineXML(new_xml)
- except libvirt.libvirtError as e:
- dom = conn.defineXML(old_xml)
- raise OperationFailed(e.get_error_message())
- return dom
-
- def _live_vm_update(self, dom, params):
- pass
-
- def lookup(self, name):
- dom = self.get_vm(name, self.conn)
- info = dom.info()
- state = DOM_STATE_MAP[info[0]]
- screenshot = None
- graphics = self._vm_get_graphics(name)
- graphics_type, graphics_listen, graphics_port = graphics
- graphics_port = graphics_port if state == 'running' else None
- try:
- if state == 'running':
- screenshot = self.vmscreenshot.lookup(name)
- elif state == 'shutoff':
- # reset vm stats when it is powered off to avoid sending
- # incorrect (old) data
- stats[dom.UUIDString()] = {}
- except NotFoundError:
- pass
-
- with self.objstore as session:
- try:
- extra_info = session.get('vm', dom.UUIDString())
- except NotFoundError:
- extra_info = {}
- icon = extra_info.get('icon')
-
- vm_stats = stats.get(dom.UUIDString(), {})
- res = {}
- res['cpu_utilization'] = vm_stats.get('cpu', 0)
- res['net_throughput'] = vm_stats.get('net_io', 0)
- res['net_throughput_peak'] = vm_stats.get('max_net_io', 100)
- res['io_throughput'] = vm_stats.get('disk_io', 0)
- res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
-
- return {'state': state,
- 'stats': str(res),
- 'uuid': dom.UUIDString(),
- 'memory': info[2] >> 10,
- 'cpus': info[3],
- 'screenshot': screenshot,
- 'icon': icon,
- 'graphics': {"type": graphics_type,
- "listen": graphics_listen,
- "port": graphics_port}
- }
-
- def _vm_get_disk_paths(self, dom):
- xml = dom.XMLDesc(0)
- xpath = "/domain/devices/disk[@device='disk']/source/@file"
- return xmlutils.xpath_get_text(xml, xpath)
-
- def _vm_exists(self, name):
- try:
- self.get_vm(name, self.conn)
- return True
- except NotFoundError:
- return False
- except Exception, e:
- err = "Unable to retrieve VM '%s': %s"
- raise OperationFailed(err % (name, e.message))
-
- @staticmethod
- def get_vm(name, conn):
- conn = conn.get()
- try:
- # outgoing text to libvirt, encode('utf-8')
- return conn.lookupByName(name.encode("utf-8"))
- except libvirt.libvirtError as e:
- if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
- raise NotFoundError("Virtual Machine '%s' not found" % name)
- else:
- raise
-
- def delete(self, name):
- if self._vm_exists(name):
- conn = self.conn.get()
- dom = self.get_vm(name, self.conn)
- self._vmscreenshot_delete(dom.UUIDString())
- paths = self._vm_get_disk_paths(dom)
- info = self.lookup(name)
-
- if info['state'] == 'running':
- self.stop(name)
-
- dom.undefine()
-
- for path in paths:
- vol = conn.storageVolLookupByPath(path)
- vol.delete(0)
-
- with self.objstore as session:
- session.delete('vm', dom.UUIDString(), ignore_missing=True)
-
- vnc.remove_proxy_token(name)
-
- def start(self, name):
- dom = self.get_vm(name, self.conn)
- dom.create()
-
- def stop(self, name):
- if self._vm_exists(name):
- dom = self.get_vm(name, self.conn)
- dom.destroy()
-
- def _vm_get_graphics(self, name):
- dom = self.get_vm(name, self.conn)
- xml = dom.XMLDesc(0)
- expr = "/domain/devices/graphics/@type"
- res = xmlutils.xpath_get_text(xml, expr)
- graphics_type = res[0] if res else None
- expr = "/domain/devices/graphics/@listen"
- res = xmlutils.xpath_get_text(xml, expr)
- graphics_listen = res[0] if res else None
- graphics_port = None
- if graphics_type:
- expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type
- res = xmlutils.xpath_get_text(xml, expr)
- graphics_port = int(res[0]) if res else None
- return graphics_type, graphics_listen, graphics_port
-
- def connect(self, name):
- graphics = self._vm_get_graphics(name)
- graphics_type, graphics_listen, graphics_port = graphics
- if graphics_port is not None:
- vnc.add_proxy_token(name, graphics_port)
- else:
- raise OperationFailed("Only able to connect to running vm's vnc "
- "graphics.")
-
- def _vmscreenshot_delete(self, vm_uuid):
- screenshot = VMScreenshotModel.get_screenshot(vm_uuid, self.objstore,
- self.conn)
- screenshot.delete()
- with self.objstore as session:
- session.delete('screenshot', vm_uuid)
-
-
-class VMScreenshotModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
- self.conn = kargs['conn']
-
- def lookup(self, name):
- dom = VMModel.get_vm(name, self.conn)
- d_info = dom.info()
- vm_uuid = dom.UUIDString()
- if DOM_STATE_MAP[d_info[0]] != 'running':
- raise NotFoundError('No screenshot for stopped vm')
-
- screenshot = self.get_screenshot(vm_uuid, self.objstore, self.conn)
- img_path = screenshot.lookup()
- # screenshot info changed after scratch generation
- with self.objstore as session:
- session.store('screenshot', vm_uuid, screenshot.info)
- return img_path
-
- @staticmethod
- def get_screenshot(vm_uuid, objstore, conn):
- with objstore as session:
- try:
- params = session.get('screenshot', vm_uuid)
- except NotFoundError:
- params = {'uuid': vm_uuid}
- session.store('screenshot', vm_uuid, params)
- return LibvirtVMScreenshot(params, conn)
-
-
-class LibvirtVMScreenshot(VMScreenshot):
- def __init__(self, vm_uuid, conn):
- VMScreenshot.__init__(self, vm_uuid)
- self.conn = conn
-
- def _generate_scratch(self, thumbnail):
- def handler(stream, buf, opaque):
- fd = opaque
- os.write(fd, buf)
-
- fd = os.open(thumbnail, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0644)
- try:
- conn = self.conn.get()
- dom = conn.lookupByUUIDString(self.vm_uuid)
- vm_name = dom.name()
- stream = conn.newStream(0)
- dom.screenshot(stream, 0, 0)
- stream.recvAll(handler, fd)
- except libvirt.libvirtError:
- try:
- stream.abort()
- except:
- pass
- raise NotFoundError("Screenshot not supported for %s" % vm_name)
- else:
- stream.finish()
- finally:
- os.close(fd)
diff --git a/src/kimchi/server.py b/src/kimchi/server.py
index 9cc4c3c..90b2a27 100644
--- a/src/kimchi/server.py
+++ b/src/kimchi/server.py
@@ -30,7 +30,7 @@ import sslcert
from kimchi import auth
from kimchi import config
-from kimchi.model_ import model
+from kimchi.model import model
from kimchi import mockmodel
from kimchi import vnc
from kimchi.control import sub_nodes
diff --git a/tests/test_model.py b/tests/test_model.py
index 74f3dd9..51b216c 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -38,7 +38,7 @@ from kimchi import netinfo
from kimchi.exception import InvalidOperation, InvalidParameter
from kimchi.exception import NotFoundError, OperationFailed
from kimchi.iscsi import TargetClient
-from kimchi.model_ import model
+from kimchi.model import model
from kimchi.rollbackcontext import RollbackContext
from kimchi.utils import add_task
diff --git a/tests/test_storagepool.py b/tests/test_storagepool.py
index 869b608..a3f4983 100644
--- a/tests/test_storagepool.py
+++ b/tests/test_storagepool.py
@@ -24,7 +24,7 @@ import libxml2
import unittest
-from kimchi.model_.libvirtstoragepool import StoragePoolDef
+from kimchi.model.libvirtstoragepool import StoragePoolDef
from kimchi.rollbackcontext import RollbackContext
--
1.7.10.4
More information about the Kimchi-devel
mailing list