[Kimchi-devel] [PATCH 13/13] refactor model: Update server.py and root.py to use new models
Aline Manera
alinefm at linux.vnet.ibm.com
Fri Jan 17 02:24:49 UTC 2014
From: Aline Manera <alinefm at br.ibm.com>
Also remove former model.py and mockmodel.py files.
And rename model_ to model
Signed-off-by: Aline Manera <alinefm at br.ibm.com>
---
src/kimchi/control/base.py | 32 +-
src/kimchi/control/config.py | 30 +-
src/kimchi/control/debugreports.py | 18 +-
src/kimchi/control/host.py | 24 +-
src/kimchi/control/interfaces.py | 11 +-
src/kimchi/control/networks.py | 11 +-
src/kimchi/control/plugins.py | 13 +-
src/kimchi/control/storagepools.py | 32 +-
src/kimchi/control/storagevolumes.py | 24 +-
src/kimchi/control/tasks.py | 11 +-
src/kimchi/control/templates.py | 11 +-
src/kimchi/control/utils.py | 2 +-
src/kimchi/control/vms.py | 17 +-
src/kimchi/mockmodel.py | 784 ----------------
src/kimchi/model.py | 1536 -------------------------------
src/kimchi/model/__init__.py | 21 +
src/kimchi/model/config.py | 52 ++
src/kimchi/model/debugreports.py | 86 ++
src/kimchi/model/host.py | 49 +
src/kimchi/model/interfaces.py | 48 +
src/kimchi/model/libvirtbackend.py | 955 +++++++++++++++++++
src/kimchi/model/libvirtconnection.py | 123 +++
src/kimchi/model/libvirtstoragepool.py | 225 +++++
src/kimchi/model/mockbackend.py | 338 +++++++
src/kimchi/model/networks.py | 115 +++
src/kimchi/model/plugins.py | 29 +
src/kimchi/model/storagepools.py | 86 ++
src/kimchi/model/storagevolumes.py | 95 ++
src/kimchi/model/tasks.py | 45 +
src/kimchi/model/templates.py | 89 ++
src/kimchi/model/vms.py | 164 ++++
src/kimchi/model_/__init__.py | 21 -
src/kimchi/model_/config.py | 52 --
src/kimchi/model_/debugreports.py | 86 --
src/kimchi/model_/host.py | 49 -
src/kimchi/model_/interfaces.py | 48 -
src/kimchi/model_/libvirtbackend.py | 955 -------------------
src/kimchi/model_/libvirtconnection.py | 123 ---
src/kimchi/model_/libvirtstoragepool.py | 225 -----
src/kimchi/model_/mockbackend.py | 338 -------
src/kimchi/model_/networks.py | 115 ---
src/kimchi/model_/plugins.py | 29 -
src/kimchi/model_/storagepools.py | 86 --
src/kimchi/model_/storagevolumes.py | 95 --
src/kimchi/model_/tasks.py | 45 -
src/kimchi/model_/templates.py | 89 --
src/kimchi/model_/vms.py | 164 ----
src/kimchi/root.py | 24 +-
src/kimchi/server.py | 16 +-
49 files changed, 2669 insertions(+), 4967 deletions(-)
delete mode 100644 src/kimchi/mockmodel.py
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/libvirtbackend.py
create mode 100644 src/kimchi/model/libvirtconnection.py
create mode 100644 src/kimchi/model/libvirtstoragepool.py
create mode 100644 src/kimchi/model/mockbackend.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/storagevolumes.py
create mode 100644 src/kimchi/model/tasks.py
create mode 100644 src/kimchi/model/templates.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_/libvirtbackend.py
delete mode 100644 src/kimchi/model_/libvirtconnection.py
delete mode 100644 src/kimchi/model_/libvirtstoragepool.py
delete mode 100644 src/kimchi/model_/mockbackend.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_/storagevolumes.py
delete mode 100644 src/kimchi/model_/tasks.py
delete mode 100644 src/kimchi/model_/templates.py
delete mode 100644 src/kimchi/model_/vms.py
diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py
index 185c8d8..8ee9f89 100644
--- a/src/kimchi/control/base.py
+++ b/src/kimchi/control/base.py
@@ -52,8 +52,8 @@ class Resource(object):
- Set the 'data' property to a JSON-serializable representation of the
Resource.
"""
- def __init__(self, model, ident=None):
- self.model = model
+ def __init__(self, backend, ident=None):
+ self.backend = backend
self.ident = ident
self.model_args = (ident,)
self.update_params = []
@@ -66,7 +66,7 @@ class Resource(object):
if action_args is not None:
model_args.extend(parse_request()[key]
for key in action_args)
- fn = getattr(self.model, model_fn(self, action_name))
+ fn = getattr(self.model, action_name)
fn(*model_args)
uri_params = tuple(self.model_args)
raise internal_redirect(self.uri_fmt % uri_params)
@@ -89,15 +89,13 @@ class Resource(object):
def lookup(self):
try:
- lookup = getattr(self.model, model_fn(self, 'lookup'))
- self.info = lookup(*self.model_args)
+ self.info = self.model.lookup(*self.model_args)
except AttributeError:
self.info = {}
def delete(self):
try:
- fn = getattr(self.model, model_fn(self, 'delete'))
- fn(*self.model_args)
+ self.model.delete(*self.model_args)
cherrypy.response.status = 204
except AttributeError:
error = "Delete is not allowed for %s" % get_class_name(self)
@@ -136,7 +134,7 @@ class Resource(object):
def update(self):
try:
- update = getattr(self.model, model_fn(self, 'update'))
+ update = getattr(self.model, 'update')
except AttributeError:
error = "%s does not implement update method"
raise cherrypy.HTTPError(405, error % get_class_name(self))
@@ -189,15 +187,15 @@ class Collection(object):
- Implement the base operations of 'create' and 'get_list' in the model.
"""
- def __init__(self, model):
- self.model = model
+ def __init__(self, backend):
+ self.backend = backend
self.resource = Resource
self.resource_args = []
self.model_args = []
def create(self, *args):
try:
- create = getattr(self.model, model_fn(self, 'create'))
+ create = getattr(self.model, 'create')
except AttributeError:
error = 'Create is not allowed for %s' % get_class_name(self)
raise cherrypy.HTTPError(405, error)
@@ -208,19 +206,19 @@ class Collection(object):
name = create(*args)
cherrypy.response.status = 201
args = self.resource_args + [name]
- res = self.resource(self.model, *args)
+ res = self.resource(self.backend, *args)
return res.get()
def _get_resources(self):
try:
- get_list = getattr(self.model, model_fn(self, 'get_list'))
- idents = get_list(*self.model_args)
+ get_list = getattr(self.model, 'get_list')
+ idents = self.model.get_list(*self.model_args)
res_list = []
for ident in idents:
# internal text, get_list changes ident to unicode for sorted
args = self.resource_args + [ident]
- res = self.resource(self.model, *args)
+ res = self.resource(self.backend, *args)
res.lookup()
res_list.append(res)
return res_list
@@ -232,7 +230,7 @@ class Collection(object):
ident = vpath.pop(0)
# incoming text, from URL, is not unicode, need decode
args = self.resource_args + [ident.decode("utf-8")]
- return self.resource(self.model, *args)
+ return self.resource(self.backend, *args)
def get(self):
resources = self._get_resources()
@@ -280,7 +278,7 @@ class AsyncCollection(Collection):
def create(self, *args):
try:
- create = getattr(self.model, model_fn(self, 'create'))
+ create = getattr(self.model, 'create')
except AttributeError:
error = 'Create is not allowed for %s' % get_class_name(self)
raise cherrypy.HTTPError(405, error)
diff --git a/src/kimchi/control/config.py b/src/kimchi/control/config.py
index 5186ddd..6720c1a 100644
--- a/src/kimchi/control/config.py
+++ b/src/kimchi/control/config.py
@@ -25,16 +25,17 @@
import cherrypy
+from kimchi.model import config as model_config
from kimchi.config import config
from kimchi.control.base import Collection, Resource
class Config(Resource):
- def __init__(self, model, id=None):
- super(Config, self).__init__(model, id)
- self.capabilities = Capabilities(self.model)
+ def __init__(self, backend, id=None):
+ super(Config, self).__init__(backend, id)
+ self.capabilities = Capabilities(backend)
self.capabilities.exposed = True
- self.distros = Distros(model)
+ self.distros = Distros(backend)
self.distros.exposed = True
@property
@@ -44,27 +45,26 @@ class Config(Resource):
class Capabilities(Resource):
- def __init__(self, model, id=None):
- super(Capabilities, self).__init__(model, id)
+ def __init__(self, backend, id=None):
+ super(Capabilities, self).__init__(backend, id)
+ self.model = model_config.Capabilities(backend)
@property
def data(self):
- caps = ['libvirt_stream_protocols', 'qemu_stream',
- 'screenshot', 'system_report_tool']
- ret = dict([(x, None) for x in caps])
- ret.update(self.model.get_capabilities())
- return ret
+ return self.info
class Distros(Collection):
- def __init__(self, model):
- super(Distros, self).__init__(model)
+ def __init__(self, backend):
+ super(Distros, self).__init__(backend)
+ self.model = model_config.Distros()
self.resource = Distro
class Distro(Resource):
- def __init__(self, model, ident):
- super(Distro, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(Distro, self).__init__(backend, ident)
+ self.model = model_config.Distro()
@property
def data(self):
diff --git a/src/kimchi/control/debugreports.py b/src/kimchi/control/debugreports.py
index a55ba38..ef3c597 100644
--- a/src/kimchi/control/debugreports.py
+++ b/src/kimchi/control/debugreports.py
@@ -21,20 +21,23 @@
# 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.model import debugreports
from kimchi.control.base import AsyncCollection, Resource
from kimchi.control.utils import internal_redirect
class DebugReports(AsyncCollection):
- def __init__(self, model):
- super(DebugReports, self).__init__(model)
+ def __init__(self, backend):
+ super(DebugReports, self).__init__(backend)
+ self.model = debugreports.DebugReports(backend)
self.resource = DebugReport
class DebugReport(Resource):
- def __init__(self, model, ident):
- super(DebugReport, self).__init__(model, ident)
- self.content = DebugReportContent(model, ident)
+ def __init__(self, backend, ident):
+ super(DebugReport, self).__init__(backend, ident)
+ self.model = debugreports.DebugReport(backend)
+ self.content = DebugReportContent(backend, ident)
@property
def data(self):
@@ -44,8 +47,9 @@ class DebugReport(Resource):
class DebugReportContent(Resource):
- def __init__(self, model, ident):
- super(DebugReportContent, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(DebugReportContent, self).__init__(backend, ident)
+ self.model = debugreports.DebugReportContent(backend)
def get(self):
self.lookup()
diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py
index 9b19577..c638fc0 100644
--- a/src/kimchi/control/host.py
+++ b/src/kimchi/control/host.py
@@ -23,18 +23,20 @@
# 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.model import host
from kimchi.control.base import Collection, Resource
class Host(Resource):
- def __init__(self, model, id=None):
- super(Host, self).__init__(model, id)
+ def __init__(self, backend, id=None):
+ super(Host, self).__init__(backend, id)
+ self.model = host.Host(backend)
self.uri_fmt = '/host/%s'
self.reboot = self.generate_action_handler('reboot')
self.shutdown = self.generate_action_handler('shutdown')
- self.stats = HostStats(self.model)
+ self.stats = HostStats(backend)
self.stats.exposed = True
- self.partitions = Partitions(self.model)
+ self.partitions = Partitions(backend)
self.partitions.exposed = True
@property
@@ -43,20 +45,26 @@ class Host(Resource):
class HostStats(Resource):
+ def __init__(self, backend, id=None):
+ super(HostStats, self).__init__(backend, id)
+ self.model = host.HostStats(backend)
+
@property
def data(self):
return self.info
class Partitions(Collection):
- def __init__(self, model):
- super(Partitions, self).__init__(model)
+ def __init__(self, backend):
+ super(Partitions, self).__init__(backend)
+ self.model = host.Partitions()
self.resource = Partition
class Partition(Resource):
- def __init__(self, model, id):
- super(Partition, self).__init__(model, id)
+ def __init__(self, backend, id):
+ super(Partition, self).__init__(backend, id)
+ self.model = host.Partition()
@property
def data(self):
diff --git a/src/kimchi/control/interfaces.py b/src/kimchi/control/interfaces.py
index 28be26e..319b6a9 100644
--- a/src/kimchi/control/interfaces.py
+++ b/src/kimchi/control/interfaces.py
@@ -22,18 +22,21 @@
# 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.model import interfaces
from kimchi.control.base import Collection, Resource
class Interfaces(Collection):
- def __init__(self, model):
- super(Interfaces, self).__init__(model)
+ def __init__(self, backend):
+ super(Interfaces, self).__init__(backend)
+ self.model = interfaces.Interfaces(backend)
self.resource = Interface
class Interface(Resource):
- def __init__(self, model, ident):
- super(Interface, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(Interface, self).__init__(backend, ident)
+ self.model = interfaces.Interface()
self.uri_fmt = "/interfaces/%s"
@property
diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py
index f3f0b41..2fae93e 100644
--- a/src/kimchi/control/networks.py
+++ b/src/kimchi/control/networks.py
@@ -21,18 +21,21 @@
# 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.model import networks
from kimchi.control.base import Collection, Resource
class Networks(Collection):
- def __init__(self, model):
- super(Networks, self).__init__(model)
+ def __init__(self, backend):
+ super(Networks, self).__init__(backend)
+ self.model = networks.Networks(backend)
self.resource = Network
class Network(Resource):
- def __init__(self, model, ident):
- super(Network, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(Network, self).__init__(backend, ident)
+ self.model = networks.Network(backend)
self.uri_fmt = "/networks/%s"
self.activate = self.generate_action_handler('activate')
self.deactivate = self.generate_action_handler('deactivate')
diff --git a/src/kimchi/control/plugins.py b/src/kimchi/control/plugins.py
index af32709..24903ac 100644
--- a/src/kimchi/control/plugins.py
+++ b/src/kimchi/control/plugins.py
@@ -23,23 +23,20 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import kimchi.template
+from kimchi.model import plugins
from kimchi.control.base import Collection, Resource
from kimchi.control.utils import get_class_name, model_fn
class Plugins(Collection):
- def __init__(self, model):
- super(Plugins, self).__init__(model)
+ def __init__(self, backend):
+ super(Plugins, self).__init__(backend)
+ self.model = plugins.Plugins()
@property
def data(self):
return self.info
def get(self):
- res_list = []
- try:
- get_list = getattr(self.model, model_fn(self, 'get_list'))
- res_list = get_list(*self.model_args)
- except AttributeError:
- pass
+ res_list = self.model.get_list()
return kimchi.template.render(get_class_name(self), res_list)
diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py
index 782f5a6..fc847bb 100644
--- a/src/kimchi/control/storagepools.py
+++ b/src/kimchi/control/storagepools.py
@@ -25,25 +25,26 @@
import cherrypy
-
+from kimchi.model import storagepools
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 import ISO_POOL_NAME
+from kimchi.model.storagepools import ISO_POOL_NAME
class StoragePools(Collection):
- def __init__(self, model):
- super(StoragePools, self).__init__(model)
+ def __init__(self, backend):
+ super(StoragePools, self).__init__(backend)
+ self.model = storagepools.StoragePools(backend)
self.resource = StoragePool
- isos = IsoPool(model)
+ isos = IsoPool(backend)
isos.exposed = True
setattr(self, ISO_POOL_NAME, isos)
def create(self, *args):
try:
- create = getattr(self.model, model_fn(self, 'create'))
+ create = self.model.create
except AttributeError:
error = 'Create is not allowed for %s' % get_class_name(self)
raise cherrypy.HTTPError(405, error)
@@ -53,7 +54,7 @@ class StoragePools(Collection):
args = self.model_args + [params]
name = create(*args)
args = self.resource_args + [name]
- res = self.resource(self.model, *args)
+ res = self.resource(self.backend, *args)
resp = res.get()
if 'task_id' in res.data:
@@ -77,8 +78,9 @@ class StoragePools(Collection):
class StoragePool(Resource):
- def __init__(self, model, ident):
- super(StoragePool, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(StoragePool, self).__init__(backend, ident)
+ self.model = storagepools.StoragePool(backend)
self.update_params = ["autostart"]
self.uri_fmt = "/storagepools/%s"
self.activate = self.generate_action_handler('activate')
@@ -108,22 +110,22 @@ class StoragePool(Resource):
subcollection = vpath.pop(0)
if subcollection == 'storagevolumes':
# incoming text, from URL, is not unicode, need decode
- return StorageVolumes(self.model, self.ident.decode("utf-8"))
+ return StorageVolumes(self.backend, self.ident.decode("utf-8"))
class IsoPool(Resource):
- def __init__(self, model):
- super(IsoPool, self).__init__(model, ISO_POOL_NAME)
+ def __init__(self, backend):
+ super(IsoPool, self).__init__(backend, ISO_POOL_NAME)
@property
def data(self):
return {'name': self.ident,
- 'state': self.info['state'],
- 'type': self.info['type']}
+ 'state': 'active',
+ 'type': 'kimchi-iso'}
def _cp_dispatch(self, vpath):
if vpath:
subcollection = vpath.pop(0)
if subcollection == 'storagevolumes':
# incoming text, from URL, is not unicode, need decode
- return IsoVolumes(self.model, self.ident.decode("utf-8"))
+ return IsoVolumes(self.backend, self.ident.decode("utf-8"))
diff --git a/src/kimchi/control/storagevolumes.py b/src/kimchi/control/storagevolumes.py
index d541807..f07dfb9 100644
--- a/src/kimchi/control/storagevolumes.py
+++ b/src/kimchi/control/storagevolumes.py
@@ -24,13 +24,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import kimchi.template
+from kimchi.model import storagevolumes
from kimchi.control.base import Collection, Resource
from kimchi.control.utils import get_class_name, model_fn
class StorageVolumes(Collection):
- def __init__(self, model, pool):
- super(StorageVolumes, self).__init__(model)
+ def __init__(self, backend, pool):
+ super(StorageVolumes, self).__init__(backend)
+ self.model = storagevolumes.StorageVolumes(backend)
self.resource = StorageVolume
self.pool = pool
self.resource_args = [self.pool, ]
@@ -38,8 +40,9 @@ class StorageVolumes(Collection):
class StorageVolume(Resource):
- def __init__(self, model, pool, ident):
- super(StorageVolume, self).__init__(model, ident)
+ def __init__(self, backend, pool, ident):
+ super(StorageVolume, self).__init__(backend, ident)
+ self.model = storagevolumes.StorageVolume(backend)
self.pool = pool
self.ident = ident
self.info = {}
@@ -66,16 +69,11 @@ class StorageVolume(Resource):
class IsoVolumes(Collection):
- def __init__(self, model, pool):
- super(IsoVolumes, self).__init__(model)
+ def __init__(self, backend, pool):
+ super(IsoVolumes, self).__init__(backend)
+ self.model = storagevolumes.IsoVolumes(backend)
self.pool = pool
def get(self):
- res_list = []
- try:
- get_list = getattr(self.model, model_fn(self, 'get_list'))
- res_list = get_list(*self.model_args)
- except AttributeError:
- pass
-
+ res_list = self.model.get_list(self.pool)
return kimchi.template.render(get_class_name(self), res_list)
diff --git a/src/kimchi/control/tasks.py b/src/kimchi/control/tasks.py
index b799422..3bd63f9 100644
--- a/src/kimchi/control/tasks.py
+++ b/src/kimchi/control/tasks.py
@@ -21,18 +21,21 @@
# 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.model import tasks
from kimchi.control.base import Collection, Resource
class Tasks(Collection):
- def __init__(self, model):
- super(Tasks, self).__init__(model)
+ def __init__(self, backend):
+ super(Tasks, self).__init__(backend)
+ self.model = tasks.Tasks(backend)
self.resource = Task
class Task(Resource):
- def __init__(self, model, id):
- super(Task, self).__init__(model, id)
+ def __init__(self, backend, id):
+ super(Task, self).__init__(backend, id)
+ self.model = tasks.Task(backend)
@property
def data(self):
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py
index a77936e..bc65eb7 100644
--- a/src/kimchi/control/templates.py
+++ b/src/kimchi/control/templates.py
@@ -21,18 +21,21 @@
# 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.model import templates
from kimchi.control.base import Collection, Resource
class Templates(Collection):
- def __init__(self, model):
- super(Templates, self).__init__(model)
+ def __init__(self, backend):
+ super(Templates, self).__init__(backend)
+ self.model = templates.Templates(backend)
self.resource = Template
class Template(Resource):
- def __init__(self, model, ident):
- super(Template, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(Template, self).__init__(backend, ident)
+ self.model = templates.Template(backend)
self.update_params = ["name", "folder", "icon", "os_distro",
"storagepool", "os_version", "cpus",
"memory", "cdrom", "disks", "networks",
diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py
index 814ba20..ee3ecce 100644
--- a/src/kimchi/control/utils.py
+++ b/src/kimchi/control/utils.py
@@ -94,7 +94,7 @@ def validate_params(params, instance, action):
else:
return
- operation = model_fn(instance, action)
+ operation = "%s.%s" % (instance.__class__.__name__, action)
validator = Draft3Validator(api_schema, format_checker=FormatChecker())
request = {operation: params}
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index 7843be7..c50b6b1 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -22,21 +22,24 @@
# 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.model import vms
from kimchi.control.base import Collection, Resource
from kimchi.control.utils import internal_redirect
class VMs(Collection):
- def __init__(self, model):
- super(VMs, self).__init__(model)
+ def __init__(self, backend):
+ super(VMs, self).__init__(backend)
+ self.model = vms.VMs(backend)
self.resource = VM
class VM(Resource):
- def __init__(self, model, ident):
- super(VM, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(VM, self).__init__(backend, ident)
+ self.model = vms.VM(self.backend)
self.update_params = ["name"]
- self.screenshot = VMScreenShot(model, ident)
+ self.screenshot = VMScreenShot(backend, ident)
self.uri_fmt = '/vms/%s'
self.start = self.generate_action_handler('start')
self.stop = self.generate_action_handler('stop')
@@ -59,8 +62,8 @@ class VM(Resource):
class VMScreenShot(Resource):
- def __init__(self, model, ident):
- super(VMScreenShot, self).__init__(model, ident)
+ def __init__(self, backend, ident):
+ super(VMScreenShot, self).__init__(backend, ident)
def get(self):
self.lookup()
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
deleted file mode 100644
index 4ef3fa6..0000000
--- a/src/kimchi/mockmodel.py
+++ /dev/null
@@ -1,784 +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 glob
-import ipaddr
-import os
-import psutil
-import random
-import subprocess
-import time
-import uuid
-
-
-try:
- from PIL import Image
- from PIL import ImageDraw
-except ImportError:
- import Image
- import ImageDraw
-
-
-import kimchi.model
-from kimchi import config
-from kimchi import network as knetwork
-from kimchi.asynctask import AsyncTask
-from kimchi.distroloader import DistroLoader
-from kimchi.exception import InvalidOperation, InvalidParameter
-from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
-from kimchi.objectstore import ObjectStore
-from kimchi.screenshot import VMScreenshot
-from kimchi.utils import is_digit
-from kimchi.vmtemplate import VMTemplate
-
-
-class MockModel(object):
- def __init__(self, objstore_loc=None):
- self.reset()
- self.objstore = ObjectStore(objstore_loc)
- self.distros = self._get_distros()
-
- def get_capabilities(self):
- return {'libvirt_stream_protocols': ['http', 'https', 'ftp', 'ftps', 'tftp'],
- 'qemu_stream': True,
- 'screenshot': True,
- 'system_report_tool': True}
-
- def reset(self):
- self._mock_vms = {}
- self._mock_screenshots = {}
- self._mock_templates = {}
- self._mock_storagepools = {'default': MockStoragePool('default')}
- self._mock_networks = {'default': MockNetwork('default')}
- self._mock_interfaces = self.dummy_interfaces()
- self.next_taskid = 1
- self.storagepool_activate('default')
-
- def _static_vm_update(self, dom, params):
- state = dom.info['state']
-
- if 'name' in params:
- if state == 'running' or params['name'] in self.vms_get_list():
- raise InvalidParameter("VM name existed or vm not shutoff.")
- else:
- del self._mock_vms[dom.name]
- dom.name = params['name']
- self._mock_vms[dom.name] = dom
-
- for key, val in params.items():
- if key in kimchi.model.VM_STATIC_UPDATE_PARAMS and key in dom.info:
- dom.info[key] = val
-
- def _live_vm_update(self, dom, params):
- pass
-
- def vm_update(self, name, params):
- dom = self._get_vm(name)
- self._static_vm_update(dom, params)
- self._live_vm_update(dom, params)
-
- return dom.name
-
- def vm_lookup(self, name):
- vm = self._get_vm(name)
- if vm.info['state'] == 'running':
- vm.info['screenshot'] = self.vmscreenshot_lookup(name)
- else:
- vm.info['screenshot'] = None
- return vm.info
-
- def vm_delete(self, name):
- vm = self._get_vm(name)
- self._vmscreenshot_delete(vm.uuid)
- for disk in vm.disk_paths:
- self.storagevolume_delete(disk['pool'], disk['volume'])
-
- del self._mock_vms[vm.name]
-
- def vm_start(self, name):
- self._get_vm(name).info['state'] = 'running'
- info = self._get_vm(name).info
-
- def vm_stop(self, name):
- self._get_vm(name).info['state'] = 'shutoff'
-
- def vm_connect(self, name):
- pass
-
- def vms_create(self, params):
- t_name = kimchi.model.template_name_from_uri(params['template'])
- name = kimchi.model.get_vm_name(params.get('name'), t_name,
- self._mock_vms.keys())
- if name in self._mock_vms:
- raise InvalidOperation("VM already exists")
-
- vm_uuid = str(uuid.uuid4())
- vm_overrides = dict()
- pool_uri = params.get('storagepool')
- if pool_uri:
- vm_overrides['storagepool'] = pool_uri
-
- t = self._get_template(t_name, vm_overrides)
- t.validate()
-
- t_info = copy.deepcopy(t.info)
- graphics = params.get('graphics')
- if graphics:
- t_info.update({'graphics': graphics})
-
- vm = MockVM(vm_uuid, name, t_info)
- icon = t_info.get('icon')
- if icon:
- vm.info['icon'] = icon
-
- vm.disk_paths = t.fork_vm_storage(vm_uuid)
- self._mock_vms[name] = vm
- return name
-
- def vms_get_list(self):
- names = self._mock_vms.keys()
- return sorted(names, key=unicode.lower)
-
- def vmscreenshot_lookup(self, name):
- vm = self._get_vm(name)
- if vm.info['state'] != 'running':
- raise NotFoundError('No screenshot for stopped vm')
- screenshot = self._mock_screenshots.setdefault(
- vm.uuid, MockVMScreenshot({'uuid': vm.uuid}))
- return screenshot.lookup()
-
- def _vmscreenshot_delete(self, vm_uuid):
- screenshot = self._mock_screenshots.get(vm_uuid)
- if screenshot:
- screenshot.delete()
- del self._mock_screenshots[vm_uuid]
-
- def template_lookup(self, name):
- t = self._get_template(name)
- return t.info
-
- def template_delete(self, name):
- try:
- del self._mock_templates[name]
- except KeyError:
- raise NotFoundError()
-
- def templates_create(self, params):
- name = params['name']
- if name in self._mock_templates:
- raise InvalidOperation("Template already exists")
- 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)
-
- t = MockVMTemplate(params, self)
- self._mock_templates[name] = t
- 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(kimchi.model.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):
- return self._mock_templates.keys()
-
- def _get_template(self, name, overrides=None):
- try:
- t = self._mock_templates[name]
- if overrides:
- args = copy.copy(t.info)
- args.update(overrides)
- return MockVMTemplate(args, self)
- else:
- return t
- except KeyError:
- raise NotFoundError()
-
- def debugreport_lookup(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name + '.txt')
- 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 + '.txt')
- 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, '*.txt')
- 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 _get_vm(self, name):
- try:
- return self._mock_vms[name]
- except KeyError:
- raise NotFoundError()
-
- def storagepools_create(self, params):
- try:
- name = params['name']
- pool = MockStoragePool(name)
- pool.info['type'] = params['type']
- pool.info['path'] = params['path']
- if params['type'] == 'dir':
- pool.info['autostart'] = True
- else:
- pool.info['autostart'] = False
- except KeyError, item:
- raise MissingParameter(item)
- if name in self._mock_storagepools or name in (kimchi.model.ISO_POOL_NAME,):
- raise InvalidOperation("StoragePool already exists")
- self._mock_storagepools[name] = pool
- return name
-
- def storagepool_lookup(self, name):
- storagepool = self._get_storagepool(name)
- storagepool.refresh()
- return storagepool.info
-
- def storagepool_update(self, name, params):
- autostart = params['autostart']
- if autostart not in [True, False]:
- raise InvalidOperation("Autostart flag must be true or false")
- storagepool = self._get_storagepool(name)
- storagepool.info['autostart'] = autostart
- ident = storagepool.name
- return ident
-
- def storagepool_activate(self, name):
- self._get_storagepool(name).info['state'] = 'active'
-
- def storagepool_deactivate(self, name):
- self._get_storagepool(name).info['state'] = 'inactive'
-
- def storagepool_delete(self, name):
- # firstly, we should check the pool actually exists
- pool = self._get_storagepool(name)
- del self._mock_storagepools[pool.name]
-
- def storagepools_get_list(self):
- return sorted(self._mock_storagepools.keys())
-
- def _get_storagepool(self, name):
- try:
- return self._mock_storagepools[name]
- except KeyError:
- raise NotFoundError()
-
- def storagevolumes_create(self, pool_name, params):
- pool = self._get_storagepool(pool_name)
- if pool.info['state'] == 'inactive':
- raise InvalidOperation("StoragePool not active")
- try:
- name = params['name']
- volume = MockStorageVolume(pool, name, params)
- volume.info['type'] = params['type']
- volume.info['format'] = params['format']
- volume.info['path'] = os.path.join(
- pool.info['path'], name)
- except KeyError, item:
- raise MissingParameter(item)
- if name in pool._volumes:
- raise InvalidOperation("StorageVolume already exists")
- pool._volumes[name] = volume
- return name
-
- def storagevolume_lookup(self, pool, name):
- if self._get_storagepool(pool).info['state'] != 'active':
- raise InvalidOperation("StoragePool %s is not active" % pool)
- storagevolume = self._get_storagevolume(pool, name)
- return storagevolume.info
-
- def storagevolume_wipe(self, pool, name):
- volume = self._get_storagevolume(pool, name)
- volume.info['allocation'] = 0
-
- def storagevolume_delete(self, pool, name):
- # firstly, we should check the pool actually exists
- volume = self._get_storagevolume(pool, name)
- del self._get_storagepool(pool)._volumes[volume.name]
-
- def storagevolume_resize(self, pool, name, size):
- volume = self._get_storagevolume(pool, name)
- volume.info['capacity'] = size
-
- def storagevolumes_get_list(self, pool):
- res = self._get_storagepool(pool)
- if res.info['state'] == 'inactive':
- raise InvalidOperation(
- "Unable to list volumes of inactive storagepool %s" % pool)
- return res._volumes.keys()
-
- 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':
- # prevent iso from different pool having same volume name
- res['name'] = '%s-%s' % (pool, volume)
- iso_volumes.append(res)
- return iso_volumes
-
- def dummy_interfaces(self):
- interfaces = {}
- ifaces = {"eth1": "nic", "bond0": "bonding",
- "eth1.10": "vlan", "bridge0": "bridge"}
- for i, name in enumerate(ifaces.iterkeys()):
- iface = Interface(name)
- iface.info['type'] = ifaces[name]
- iface.info['ipaddr'] = '192.168.%s.101' % (i + 1)
- interfaces[name] = iface
- interfaces['eth1'].info['ipaddr'] = '192.168.0.101'
- return interfaces
-
- def interfaces_get_list(self):
- return self._mock_interfaces.keys()
-
- def interface_lookup(self, name):
- return self._mock_interfaces[name].info
-
- def networks_create(self, params):
- name = params['name']
- if name in self.networks_get_list():
- raise InvalidOperation("Network %s already exists" % name)
- network = MockNetwork(name)
- connection = params['connection']
- network.info['connection'] = connection
- if connection == "bridge":
- try:
- interface = params['interface']
- network.info['interface'] = interface
- except KeyError, key:
- raise MissingParameter(key)
-
- subnet = params.get('subnet', '')
- if subnet:
- network.info['subnet'] = subnet
- try:
- net = ipaddr.IPNetwork(subnet)
- except ValueError, e:
- raise InvalidParameter(e)
-
- network.info['dhcp'] = {
- 'start': str(net.network + net.numhosts / 2),
- 'stop': str(net.network + net.numhosts - 2)}
- if name in self._mock_networks:
- raise InvalidOperation("Network already exists")
- self._mock_networks[name] = network
- return name
-
- def _get_network(self, name):
- try:
- return self._mock_networks[name]
- except KeyError:
- raise NotFoundError("Network '%s'" % name)
-
- def _get_vms_attach_to_a_network(self, network):
- vms = []
- for name, dom in self._mock_vms.iteritems():
- if network in dom.networks:
- vms.append(name)
- return vms
-
- def network_lookup(self, name):
- network = self._get_network(name)
- network.info['vms'] = self._get_vms_attach_to_a_network(name)
- return network.info
-
- def network_activate(self, name):
- self._get_network(name).info['state'] = 'active'
-
- def network_deactivate(self, name):
- self._get_network(name).info['state'] = 'inactive'
-
- def network_delete(self, name):
- # firstly, we should check the network actually exists
- network = self._get_network(name)
- del self._mock_networks[network.name]
-
- def networks_get_list(self):
- return sorted(self._mock_networks.keys())
-
- 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 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 _get_storagevolume(self, pool, name):
- try:
- return self._get_storagepool(pool)._volumes[name]
- except KeyError:
- raise NotFoundError()
-
- 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 _gen_debugreport_file(self, ident):
- return self.add_task('', self._create_log, ident)
-
- def _create_log(self, cb, name):
- path = config.get_debugreports_path()
- tmpf = os.path.join(path, name + '.tmp')
- realf = os.path.join(path, name + '.txt')
- length = random.randint(1000, 10000)
- with open(tmpf, 'w') as fd:
- while length:
- fd.write('I am logged')
- length = length - 1
- os.rename(tmpf, realf)
- cb("OK", True)
-
- def host_lookup(self, *name):
- res = {}
- res['memory'] = 6114058240
- res['cpu'] = 'Intel(R) Core(TM) i5 CPU M 560 @ 2.67GHz'
- res['os_distro'] = 'Red Hat Enterprise Linux Server'
- res['os_version'] = '6.4'
- res['os_codename'] = 'Santiago'
-
- return res
-
- def hoststats_lookup(self, *name):
- virt_mem = psutil.virtual_memory()
- memory_stats = {'total': virt_mem.total,
- 'free': virt_mem.free,
- 'cached': virt_mem.cached,
- 'buffers': virt_mem.buffers,
- 'avail': virt_mem.available}
- return {'cpu_utilization': round(random.uniform(0, 100), 1),
- 'memory': memory_stats,
- 'disk_read_rate': round(random.uniform(0, 4000), 1),
- 'disk_write_rate': round(random.uniform(0, 4000), 1),
- 'net_recv_rate': round(random.uniform(0, 4000), 1),
- 'net_sent_rate': round(random.uniform(0, 4000), 1)}
-
- def vms_get_list_by_state(self, state):
- ret_list = []
- for name in self.vms_get_list():
- if (self._mock_vms[name].info['state']) == 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!")
- cherrypy.engine.exit()
-
- 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!")
- cherrypy.engine.stop()
- time.sleep(10)
- cherrypy.engine.start()
-
- 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)
-
-class MockVMTemplate(VMTemplate):
- def __init__(self, args, mockmodel_inst=None):
- VMTemplate.__init__(self, args)
- self.model = mockmodel_inst
-
- def _storage_validate(self):
- pool_uri = self.info['storagepool']
- pool_name = kimchi.model.pool_name_from_uri(pool_uri)
- try:
- pool = self.model._get_storagepool(pool_name)
- except NotFoundError:
- raise InvalidParameter('Storage specified by template does not exist')
- if pool.info['state'] != 'active':
- raise InvalidParameter('Storage specified by template is not active')
-
- return pool
-
- def _get_storage_path(self):
- pool = self._storage_validate()
- return pool.info['path']
-
- def fork_vm_storage(self, vm_name):
- pool = self._storage_validate()
- volumes = self.to_volume_list(vm_name)
- disk_paths = []
- for vol_info in volumes:
- vol_info['capacity'] = vol_info['capacity'] << 10
- self.model.storagevolumes_create(pool.name, vol_info)
- disk_paths.append({'pool': pool.name, 'volume': vol_info['name']})
- return disk_paths
-
-
-class MockVM(object):
- def __init__(self, uuid, name, template_info):
- self.uuid = uuid
- self.name = name
- self.disk_paths = []
- self.networks = template_info['networks']
- self.info = {'state': 'shutoff',
- 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \
- 'net_throughput_peak': 100, 'io_throughput': 45, \
- 'io_throughput_peak': 100}",
- 'uuid': self.uuid,
- 'memory': template_info['memory'],
- 'cpus': template_info['cpus'],
- 'icon': None,
- 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None}
- }
- self.info['graphics'].update(template_info['graphics'])
-
-
-class MockStoragePool(object):
- def __init__(self, name):
- self.name = name
- self.info = {'state': 'inactive',
- 'capacity': 1024 << 20,
- 'allocated': 512 << 20,
- 'available': 512 << 20,
- 'path': '/var/lib/libvirt/images',
- 'source': {},
- 'type': 'dir',
- 'nr_volumes': 0,
- 'autostart': 0}
- self._volumes = {}
-
- def refresh(self):
- state = self.info['state']
- self.info['nr_volumes'] = len(self._volumes) \
- if state == 'active' else 0
-
-
-class Interface(object):
- def __init__(self, name):
- self.name = name
- self.info = {'type': 'nic',
- 'ipaddr': '192.168.0.101',
- 'netmask': '255.255.255.0',
- 'status': 'active'}
-
-
-class MockNetwork(object):
- def __init__(self, name):
- self.name = name
- self.info = {'state': 'inactive',
- 'autostart': True,
- 'connection': 'nat',
- 'interface': 'virbr0',
- 'subnet': '192.168.122.0/24',
- 'dhcp': {'start': '192.168.122.128',
- 'stop': '192.168.122.254'},
- }
-
-
-class MockTask(object):
- def __init__(self, id):
- self.id = id
-
-class MockStorageVolume(object):
- def __init__(self, pool, name, params={}):
- self.name = name
- self.pool = pool
- fmt = params.get('format', 'raw')
- capacity = params.get('capacity', 1024)
- self.info = {'type': 'disk',
- 'capacity': capacity << 20,
- 'allocation': 512,
- 'format': fmt}
- if fmt == 'iso':
- self.info['allocation'] = self.info['capacity']
- self.info['os_version'] = '17'
- self.info['os_distro'] = 'fedora'
- self.info['bootable'] = True
-
-
-class MockVMScreenshot(VMScreenshot):
- OUTDATED_SECS = 5
- BACKGROUND_COLOR = ['blue', 'green', 'purple', 'red', 'yellow']
- BOX_COORD = (50, 115, 206, 141)
- BAR_COORD = (50, 115, 50, 141)
-
- def __init__(self, vm_name):
- VMScreenshot.__init__(self, vm_name)
- self.coord = MockVMScreenshot.BAR_COORD
- self.background = random.choice(MockVMScreenshot.BACKGROUND_COLOR)
-
- def _generate_scratch(self, thumbnail):
- self.coord = (self.coord[0],
- self.coord[1],
- min(MockVMScreenshot.BOX_COORD[2],
- self.coord[2]+random.randrange(50)),
- self.coord[3])
-
- image = Image.new("RGB", (256, 256), self.background)
- d = ImageDraw.Draw(image)
- d.rectangle(MockVMScreenshot.BOX_COORD, outline='black')
- d.rectangle(self.coord, outline='black', fill='black')
- image.save(thumbnail)
-
-
-def get_mock_environment():
- model = MockModel()
- for i in xrange(5):
- name = 'test-template-%i' % i
- params = {'name': name}
- t = MockVMTemplate(params, model)
- model._mock_templates[name] = t
-
- for name in ('test-template-1', 'test-template-3'):
- model._mock_templates[name].info.update({'folder': ['rhel', '6']})
-
- for i in xrange(10):
- name = u'test-vm-%i' % i
- vm_uuid = str(uuid.uuid4())
- vm = MockVM(vm_uuid, name, model.template_lookup('test-template-0'))
- model._mock_vms[name] = vm
-
- #mock storagepool
- for i in xrange(5):
- name = 'default-pool-%i' % i
- defaultstoragepool = MockStoragePool(name)
- defaultstoragepool.info['path'] += '/%i' % i
- model._mock_storagepools[name] = defaultstoragepool
- for j in xrange(5):
- vol_name = 'volume-%i' % j
- defaultstoragevolume = MockStorageVolume(name, vol_name)
- defaultstoragevolume.info['path'] = '%s/%s' % (
- defaultstoragepool.info['path'], vol_name)
- mockpool = model._mock_storagepools[name]
- mockpool._volumes[vol_name] = defaultstoragevolume
- vol_name = 'Fedora17.iso'
- defaultstoragevolume = MockStorageVolume(name, vol_name,
- {'format': 'iso'})
- defaultstoragevolume.info['path'] = '%s/%s' % (
- defaultstoragepool.info['path'], vol_name)
- mockpool = model._mock_storagepools[name]
- mockpool._volumes[vol_name] = defaultstoragevolume
-
- #mock network
- for i in xrange(5):
- name = 'test-network-%i' % i
- testnetwork = MockNetwork(name)
- testnetwork.info['interface'] = 'virbr%i' % (i + 1)
- testnetwork.info['subnet'] = '192.168.%s.0/24' % (i + 1)
- testnetwork.info['dhcp']['start'] = '192.168.%s.128' % (i + 1)
- testnetwork.info['dhcp']['end'] = '192.168.%s.254' % (i + 1)
- model._mock_networks[name] = testnetwork
-
- return model
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
deleted file mode 100644
index 7b0eafc..0000000
--- a/src/kimchi/model.py
+++ /dev/null
@@ -1,1536 +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 os
-import platform
-import psutil
-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 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.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.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.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()
- ids = conn.listDomainsID()
- names = map(lambda x: conn.lookupByID(x).name(), ids)
- names += conn.listDefinedDomains()
- names = map(lambda x: x.decode('utf-8'), names)
- 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({'subnet': 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 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 _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')
-
-
-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 _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..7c0a5d6
--- /dev/null
+++ b/src/kimchi/model/config.py
@@ -0,0 +1,52 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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.distroloader import DistroLoader
+from kimchi.exception import NotFoundError
+
+ERR_DISTRO_NOT_FOUND = "Distro '%s' not found."
+
+class Config(object):
+ pass
+
+class Capabilities(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def lookup(self, ident):
+ return self.backend.get_capabilities()
+
+class Distros(object):
+ def __init__(self):
+ self._distroloader = DistroLoader()
+ self._distros = self._distroloader.get()
+
+ def get_list(self):
+ return self._distros.keys()
+
+class Distro(Distros):
+ def lookup(self, ident):
+ if ident not in self.get_list():
+ raise NotFoundError(ERR_DISTRO_NOT_FOUND % ident)
+
+ return self._distros[ident]
diff --git a/src/kimchi/model/debugreports.py b/src/kimchi/model/debugreports.py
new file mode 100644
index 0000000..4db1ace
--- /dev/null
+++ b/src/kimchi/model/debugreports.py
@@ -0,0 +1,86 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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 glob
+import os
+import time
+
+from kimchi import config
+from kimchi.exception import NotFoundError
+
+class DebugReports(object):
+ def __init__(self, backend):
+ pass
+
+ 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
+
+class DebugReport(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def lookup(self, name):
+ file_target = self._get_debugreport(name)
+ 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 create(self, params):
+ ident = params['name']
+ taskid = self.backend.gen_debugreport_file(ident)
+ if taskid is None:
+ raise OperationFailed("Debug report tool not found.")
+
+ with self.backend.objstore as session:
+ return session.get('task', str(taskid))
+
+ def delete(self, name):
+ file_target = self._get_debugreports(name)
+ os.remove(file_target)
+
+ def _get_debugreport(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("Debug report '%s' not found.")
+
+ return file_target
+
+class DebugReportContent(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def lookup(self, name):
+ debugreport = DebugReports(backend)
+ 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..f5ef0d7
--- /dev/null
+++ b/src/kimchi/model/host.py
@@ -0,0 +1,49 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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 disks
+
+class Host(object):
+ def __init__(self, backend):
+ self.info = backend.get_host()
+
+ def lookup(self, ident):
+ return self.info
+
+class HostStats(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def lookup(self, ident):
+ return self.backend.host_stats
+
+class Partitions(object):
+ def get_list(self):
+ return disks.get_partitions_names()
+
+class Partition(object):
+ def lookup(self, ident):
+ if ident not in disks.get_partitions_names():
+ raise NotFoundError("Partition %s not found in the host" % name)
+
+ return disks.get_partition_details(ident)
diff --git a/src/kimchi/model/interfaces.py b/src/kimchi/model/interfaces.py
new file mode 100644
index 0000000..7b1d156
--- /dev/null
+++ b/src/kimchi/model/interfaces.py
@@ -0,0 +1,48 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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
+
+class Interfaces(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def get_list(self):
+ return list(set(netinfo.all_favored_interfaces()) -
+ set(self.get_used_ifaces()))
+
+ def get_used_ifaces(self):
+ net_names = self.backend.get_networks()
+ interfaces = []
+ for name in net_names:
+ net_dict = self.backend.get_network_by_name(name)
+ net_dict['interface'] and interfaces.append(net_dict['interface'])
+
+ return interfaces
+
+class Interface(object):
+ def lookup(self, name):
+ try:
+ return netinfo.get_interface_info(name)
+ except ValueError, e:
+ raise NotFoundError(e)
diff --git a/src/kimchi/model/libvirtbackend.py b/src/kimchi/model/libvirtbackend.py
new file mode 100644
index 0000000..3fb1de2
--- /dev/null
+++ b/src/kimchi/model/libvirtbackend.py
@@ -0,0 +1,955 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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
+import fnmatch
+import glob
+import ipaddr
+import logging
+import os
+import platform
+import psutil
+import shutil
+import subprocess
+import time
+
+from cherrypy.process.plugins import BackgroundTask
+from collections import defaultdict
+
+from kimchi import config
+from kimchi import network
+from kimchi import netinfo
+from kimchi import networkxml
+from kimchi import xmlutils
+from kimchi.asynctask import AsyncTask
+from kimchi.exception import IsoFormatError, 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 kimchi_log
+
+GUESTS_STATS_INTERVAL = 5
+HOST_STATS_INTERVAL = 1
+STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
+ 'path': '/pool/source/dir/@path'}}
+
+class LibvirtBackend(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'}
+
+ TEMPLATE_SCAN = True
+
+ def __init__(self, libvirt_uri=None, objstore_loc=None):
+ self.libvirt_uri = libvirt_uri or 'qemu:///system'
+ self.conn = LibvirtConnection(self.libvirt_uri)
+ self.objstore = ObjectStore(objstore_loc)
+ self.next_taskid = 1
+ self.scanner = Scanner(self._clean_scan)
+ self.stats = {}
+ self.host_stats = defaultdict(int)
+ 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()
+
+ # 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)
+
+ # 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},)
+
+ 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.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):
+ conn = self.conn.get()
+ vm_list = self.get_vms()
+
+ for name in vm_list:
+ info = self.get_vm_by_name(name)
+ vm_uuid = info['uuid']
+ state = info['state']
+ 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})
+
+ dom = conn.lookupByName(name.encode("utf-8"))
+ self._get_percentage_cpu_usage(vm_uuid, dom.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 = 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 _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)
+
+ # get cpu utilization
+ self.host_stats['cpu_utilization'] = psutil.cpu_percent(None)
+
+ # get memory stats
+ virt_mem = psutil.virtual_memory()
+ self.host_stats['memory'] = {'total': virt_mem.total,
+ 'free': virt_mem.free,
+ 'cached': virt_mem.cached,
+ 'buffers': virt_mem.buffers,
+ 'avail': virt_mem.available}
+
+ 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
+
+ ifaces = set(netinfo.nics() + netinfo.wlans()) & set(net_ios.iterkeys())
+ for key in ifaces:
+ 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 gen_debugreport_file(self, name):
+ gen_cmd = self._get_system_report_tool()
+ if gen_cmd is None:
+ return None
+
+ return self.add_task('', gen_cmd, name)
+
+ 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 _sosreport_generate(self, cb, name):
+ command = 'sosreport --batch --name "%s"' % name
+ try:
+ retcode = subprocess.call(command, shell=True,
+ stdout=subprocess.PIPE,
+ stderr=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)
+ 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 add_task(self, target_uri, fn, opaque=None):
+ id = self.next_taskid
+ self.next_taskid += 1
+ task = AsyncTask(id, target_uri, fn, self.objstore, opaque)
+ return id
+
+ def get_host(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
+ 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_storagepools(self):
+ try:
+ conn = self.conn.get()
+ names = conn.listStoragePools()
+ names += conn.listDefinedStoragePools()
+ return names
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def _clean_scan(self, pool_name):
+ try:
+ self.deactivate_storagepool(pool_name)
+ with self.objstore as session:
+ session.delete('scanning', pool_name)
+ except Exception, e:
+ kimchi_log.debug("Error while cleaning deep scan results" %
+ e.message)
+
+ def do_deep_scan(self, params):
+ scan_params = dict(ignore_list=[])
+ scan_params['scan_path'] = params['path']
+ params['type'] = 'dir'
+
+ for pool in self.get_storagepools():
+ try:
+ res = self.get_storagepool_by_name(pool)
+ if res['state'] == 'active':
+ scan_params['ignore_list'].append(res['path'])
+ except Exception, e:
+ kimchi_log.debug("Error while preparing for deep scan: %s" %
+ e.message)
+
+ params['path'] = self.scanner.scan_dir_prepare(params['name'])
+ scan_params['pool_path'] = params['path']
+ task_id = self.add_task('', self.scanner.start_scan, scan_params)
+ # Record scanning-task/storagepool mapping for future querying
+ with self.objstore as session:
+ session.store('scanning', params['name'], task_id)
+ return task_id
+
+ def create_storagepool(self, params):
+ conn = self.conn.get()
+ try:
+ poolDef = StoragePoolDef.create(params)
+ poolDef.prepare(conn)
+ xml = poolDef.xml
+ except KeyError, key:
+ raise MissingParameter("You need to specify '%s' in order to "
+ "create storage pool" % key)
+
+ try:
+ if task_id:
+ # Create transient pool for deep scan
+ conn.storagePoolCreateXML(xml, 0)
+ return name
+
+ pool = conn.storagePoolDefineXML(xml, 0)
+ if params['type'] in ['logical', 'dir', 'netfs']:
+ pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
+ # autostart dir and logical storage pool created from kimchi
+ pool.setAutostart(1)
+ else:
+ # disable autostart for others
+ pool.setAutostart(0)
+ except libvirt.libvirtError as e:
+ msg = "Problem creating Storage Pool: %s"
+ kimchi_log.error(msg, e)
+ raise OperationFailed(e.get_error_message())
+
+ def get_storagepool_by_name(self, name):
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(name)
+ info = pool.info()
+ nr_volumes = self._get_storagepool_vols_num(pool)
+ autostart = True if pool.autostart() else False
+ xml = pool.XMLDesc(0)
+ path = xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
+ pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+ source = self._get_storage_source(pool_type, xml)
+ res = {'state': self.pool_state_map[info[0]],
+ 'path': path,
+ 'source': source,
+ 'type': pool_type,
+ 'autostart': autostart,
+ 'capacity': info[1],
+ 'allocated': info[2],
+ 'available': info[3],
+ 'nr_volumes': nr_volumes}
+
+ if not pool.isPersistent():
+ # Deal with deep scan generated pool
+ try:
+ with self.objstore as session:
+ task_id = session.get('scanning', name)
+ res['task_id'] = str(task_id)
+ res['type'] = 'kimchi-iso'
+ except NotFoundError:
+ # User created normal pool
+ pass
+ return res
+
+ def _get_storagepool_vols_num(self, pool):
+ try:
+ if pool.isActive():
+ pool.refresh(0)
+ return pool.numOfVolumes()
+ else:
+ return 0
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def _get_storage_source(self, pool_type, pool_xml):
+ source = {}
+ if pool_type not in STORAGE_SOURCES:
+ return source
+
+ for key, val in STORAGE_SOURCES[pool_type].items():
+ res = xmlutils.xpath_get_text(pool_xml, val)
+ source[key] = res[0] if len(res) == 1 else res
+
+ return source
+
+ def activate_storagepool(self, name):
+ try:
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(name)
+ pool.create(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def deactivate_storagepool(self, name):
+ try:
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(name)
+ pool.destroy()
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def delete_storagepool(self, name):
+ try:
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(name)
+ pool.undefine()
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def autostart_storagepool(self, name, value):
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(name)
+ if autostart:
+ pool.setAutostart(1)
+ else:
+ pool.setAutostart(0)
+
+ def create_storagevolume(self, pool, params):
+ storagevol_xml = """
+ <volume>
+ <name>%(name)s</name>
+ <allocation unit="MiB">%(allocation)s</allocation>
+ <capacity unit="MiB">%(capacity)s</capacity>
+ <target>
+ <format type='%(format)s'/>
+ <path>%(path)s</path>
+ </target>
+ </volume>"""
+
+ params.setdefault('allocation', 0)
+ params.setdefault('format', 'qcow2')
+ try:
+ xml = storagevol_xml % params
+ except KeyError, key:
+ raise MissingParameter("You need to specify '%s' in order to "
+ "create the storage volume." % key)
+
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(pool)
+ try:
+ pool.createXML(xml, 0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def _get_storagevolume(self, pool, name):
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(pool)
+ return pool.storageVolLookupByName(name)
+
+ def get_storagevolumes_by_pool(self, pool):
+ try:
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(pool)
+ pool.refresh(0)
+ return pool.listVolumes()
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def get_storagevolume(self, pool, name):
+ vol = self._get_storagevolume(pool, name)
+ path = vol.path()
+ info = vol.info()
+ xml = vol.XMLDesc(0)
+ fmt = xmlutils.xpath_get_text(xml, "/volume/target/format/@type")[0]
+ res = dict(type=self.volume_type_map[info[0]], capacity=info[1],
+ allocation=info[2], path=path, format=fmt)
+
+ if fmt == 'iso':
+ if os.path.islink(path):
+ path = os.path.join(os.path.dirname(path), os.readlink(path))
+
+ try:
+ iso_img = IsoImage(path)
+ os_distro, os_version = iso_img.probe()
+ bootable = True
+ except IsoFormatError:
+ bootable = False
+
+ res.update(dict(os_distro=os_distro, os_version=os_version,
+ path=path, bootable=bootable))
+
+ return res
+
+ def wipe_storagevolume(self, pool, name):
+ try:
+ vol = self._get_storagevolume(pool, name)
+ vol.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def resize_storagevolume(self, pool, name, size):
+ size = size << 20
+ try:
+ vol = pool.storageVolLookupByName(name)
+ vol.resize(size, 0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def delete_storagevolume(self, pool, name):
+ try:
+ vol = pool.storageVolLookupByName(name)
+ volume.delete(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ def create_network(self, params):
+ connection = params["connection"]
+ # set forward mode, isolated do not need forward
+ if connection != 'isolated':
+ params['forward'] = {'mode': connection}
+
+ if connection == 'bridge':
+ 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']))
+
+ xml = networkxml.to_network_xml(**params)
+ try:
+ conn = self.conn.get()
+ network = conn.networkDefineXML(xml)
+ network.setAutostart(True)
+ except libvirt.libvirtError as e:
+ raise OperationFailed(e.get_error_message())
+
+ 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
+
+ def get_networks(self):
+ conn = self.conn.get()
+ return conn.listNetworks() + conn.listDefinedNetworks()
+
+ def get_network_by_name(self, name):
+ conn = self.conn.get()
+ net = conn.networkLookupByName(name)
+ xml = net.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 = network.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_network(name),
+ 'autostart': net.autostart() == 1,
+ 'state': net.isActive() and "active" or "inactive"}
+
+ 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_vms_attach_to_network(self, network):
+ vms = []
+ xpath = "/domain/devices/interface[@type='network']/source/@network"
+ conn = self.conn.get()
+ for dom in conn.listAllDomains(0):
+ xml = dom.XMLDesc(0)
+ networks = xmlutils.xpath_get_text(xml, xpath)
+ if network in networks:
+ vms.append(dom.name())
+ return vms
+
+ def activate_network(self, name):
+ conn = self.conn.get()
+ net = conn.networkLookupByName(name)
+ net.create()
+
+ def deactivate_network(self, name):
+ conn = self.conn.get()
+ net = conn.networkLookupByName(name)
+ net.destroy()
+
+ def delete_network(self, name):
+ conn = self.conn.get()
+ net = conn.networkLookupByName(name)
+ self._remove_vlan_tagged_bridge(net)
+ net.undefine()
+
+ 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 create_template(self, name, tmpl):
+ with self.objstore as session:
+ session.store('template', name, tmpl.info)
+
+ def get_templates(self):
+ with self.objstore as session:
+ return session.get_list('template')
+
+ def get_template_by_name(self, name):
+ with self.objstore as session:
+ return session.get('template', name)
+
+ def delete_template(self, name):
+ with self.objstore as session:
+ session.delete('template', name)
+
+ def create_vm(self, name, uuid, tmpl, vol_list):
+ # Store the icon for displaying later
+ icon = tmpl.info.get('icon', None)
+ if icon is not None:
+ with self.objstore as session:
+ session.store('vm', vm_uuid, {'icon': icon})
+
+ libvirt_stream = False
+ if len(self.libvirt_stream_protocols) != 0:
+ libvirt_stream = True
+
+ xml = tmpl.to_vm_xml(name, vm_uuid, libvirt_stream,
+ self.qemu_stream_dns)
+ 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())
+
+ def get_vms(self):
+ conn = self.conn.get()
+ ids = conn.listDomainsID()
+ names = map(lambda x: conn.lookupByID(x).name(), ids)
+ names += conn.listDefinedDomains()
+ names = map(lambda x: x.decode('utf-8'), names)
+ return sorted(names, key=unicode.lower)
+
+ def get_screenshot_by_name(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)
+
+ screenshot = LibvirtVMScreenshot(params, 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
+
+ def delete_screenshot(self, vm_uuid):
+ os.remove(self.get_screenshot_by_name(vm_uuid))
+ with self.objstore as session:
+ session.delete('screenshot', vm_uuid)
+
+ def get_vm_by_name(self, name):
+ conn = self.conn.get()
+ dom = conn.lookupByName(name.encode("utf-8"))
+ info = dom.info()
+ state = self.dom_state_map[info[0]]
+ screenshot = None
+ graphics = self._get_vm_graphics(dom)
+ graphics_type, graphics_listen, graphics_port = graphics
+ graphics_port = graphics_port if state == 'running' else None
+ if state == 'running':
+ screenshot = self.get_screenshot_by_name(name)
+ elif state == 'shutoff':
+ # reset vm stats when it is powered off to avoid sending
+ # incorrect (old) data
+ self.stats[dom.UUIDString()] = {}
+
+ 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], 'icon': icon,
+ 'screenshot': screenshot,
+ 'graphics': {'type': graphics_type, 'listen': graphics_listen,
+ 'port': graphics_port}
+ }
+
+ def _get_vm_graphics(self, dom):
+ 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 static_vm_update(self, name, params):
+ conn = self.conn.get()
+ dom = conn.lookupByName(name.encode("utf-8"))
+ 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:
+ dom.undefine()
+ conn.defineXML(new_xml)
+ except libvirt.libvirtError as e:
+ conn.defineXML(old_xml)
+ raise OperationFailed(e.get_error_message())
+
+ def live_vm_update(self, name, params):
+ pass
+
+ def delete_vm(self, name):
+ info = self.get_vm_by_name(name)
+ if info['state'] == 'running':
+ self.stop_vm(name)
+
+ conn = self.conn.get()
+ dom = conn.lookupByName(name.encode("utf-8"))
+ dom.undefine()
+
+ xml = dom.XMLDesc(0)
+ xpath = "/domain/devices/disk[@device='disk']/source/@file"
+ paths = xmlutils.xpath_get_text(xml, xpath)
+ for path in paths:
+ vol = conn.storageVolLookupByPath(path)
+ vol.delete(0)
+
+ with self.objstore as session:
+ session.delete('vm', dom.UUIDString(), ignore_missing=True)
+
+ self.delete_screenshot(dom.UUIDString())
+ vnc.remove_proxy_token(name)
+
+ def start_vm(self, name):
+ conn = self.conn.get()
+ dom = conn.lookupByName(name.encode("utf-8"))
+ dom.create()
+
+ def stop_vm(self, name):
+ conn = self.conn.get()
+ dom = conn.lookupByName(name.encode("utf-8"))
+ dom.destroy()
+
+ def connect_vm(self, name):
+ graphics = self._get_vm_graphics(name)
+ graphics_type, graphics_listen, graphics_port = get_graphics
+ if graphics_port is None:
+ raise OperationFailed("Only able to connect to running vm's vnc "
+ "graphics.")
+ vnc.add_proxy_token(name, graphics_port)
+
+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)
diff --git a/src/kimchi/model/libvirtconnection.py b/src/kimchi/model/libvirtconnection.py
new file mode 100644
index 0000000..9276acc
--- /dev/null
+++ b/src/kimchi/model/libvirtconnection.py
@@ -0,0 +1,123 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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
+import libvirt
+import threading
+import time
+
+
+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:
+ msg = 'Libvirt is not available, exiting.'
+ kimchi_log.error(msg)
+ 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..e9b9aa8
--- /dev/null
+++ b/src/kimchi/model/libvirtstoragepool.py
@@ -0,0 +1,225 @@
+#
+# 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.exception import OperationFailed
+from kimchi.iscsi import TargetClient
+
+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):
+ # TODO: Verify the NFS export can be actually mounted.
+ pass
+
+ @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/mockbackend.py b/src/kimchi/model/mockbackend.py
new file mode 100644
index 0000000..d4314ca
--- /dev/null
+++ b/src/kimchi/model/mockbackend.py
@@ -0,0 +1,338 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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 random
+
+from kimchi import config
+from kimchi.asynctask import AsyncTask
+from kimchi.objectstore import ObjectStore
+from kimchi.screenshot import VMScreenshot
+
+class MockBackend(object):
+ _network_info = {'state': 'inactive', 'autostart': True, 'connection': '',
+ 'interface': '', 'subnet': '',
+ 'dhcp': {'start': '', 'stop': ''}}
+
+ TEMPLATE_SCAN = False
+
+ def __init__(self, objstore_loc=None):
+ self.objstore = ObjectStore(objstore_loc)
+ self.next_taskid = 1
+ self.host_stats = self._get_host_stats()
+ self._storagepools = {}
+ self._networks = {}
+ self._templates = {}
+ self._vms = {}
+ self._screenshots = {}
+
+ def _get_host_stats(self):
+ memory_stats = {'total': 3934908416L,
+ 'free': round(random.uniform(0, 3934908416L), 1),
+ 'cached': round(random.uniform(0, 3934908416L), 1),
+ 'buffers': round(random.uniform(0, 3934908416L), 1),
+ 'avail': round(random.uniform(0, 3934908416L), 1)}
+
+ return {'cpu_utilization': round(random.uniform(0, 100), 1),
+ 'memory': memory_stats,
+ 'disk_read_rate': round(random.uniform(0, 4000), 1),
+ 'disk_write_rate': round(random.uniform(0, 4000), 1),
+ 'net_recv_rate': round(random.uniform(0, 4000), 1),
+ 'net_sent_rate': round(random.uniform(0, 4000), 1)}
+
+ def get_capabilities(self):
+ protocols = ['http', 'https', 'ftp', 'ftps', 'tftp']
+ return {'libvirt_stream_protocols': protocols,
+ 'qemu_stream': True,
+ 'screenshot': True,
+ 'system_report_tool': True}
+
+ def gen_debugreport_file(self, ident):
+ return self.add_task('', self._create_debugreport, ident)
+
+ def _create_debugreport(self, cb, name):
+ path = config.get_debugreports_path()
+ tmpf = os.path.join(path, name + '.tmp')
+ realf = os.path.join(path, name + '.txt')
+ length = random.randint(1000, 10000)
+ with open(tmpf, 'w') as fd:
+ while length:
+ fd.write('mock debug report\n')
+ length = length - 1
+ os.rename(tmpf, realf)
+ cb("OK", True)
+
+ def add_task(self, target_uri, fn, opaque=None):
+ id = self.next_taskid
+ self.next_taskid += 1
+ task = AsyncTask(id, target_uri, fn, self.objstore, opaque)
+ return id
+
+ def get_host(self):
+ res = {}
+ res['memory'] = 6114058240
+ res['cpu'] = 'Intel(R) Core(TM) i5 CPU M 560 @ 2.67GHz'
+ res['os_distro'] = 'Red Hat Enterprise Linux Server'
+ res['os_version'] = '6.4'
+ res['os_codename'] = 'Santiago'
+
+ return res
+
+ def get_storagepools(self):
+ return self._storagepools.keys()
+
+ def do_deep_scan(self):
+ return self.add_task('', time.sleep, 25)
+
+ def create_storagepool(self, params):
+ name = params['name']
+ pool = MockStoragePool(name)
+ pool.info.update(params)
+ if params['type'] == 'dir':
+ pool.info['autostart'] = True
+ else:
+ pool.info['autostart'] = False
+
+ self._storagepools[name] = pool
+
+ def get_storagepool_by_name(self, name):
+ pool = self._storagepools[name]
+ pool.refresh()
+ return pool.info
+
+ def activate_storagepool(self, name):
+ self._storagepools[name].info['state'] = 'active'
+
+ def deactivate_storagepool(self, name):
+ self._storagepools[name].info['state'] = 'inactive'
+
+ def delete_storagepool(self, name):
+ del self._storagepools[name]
+
+ def autostart_storagepool(self, name, value):
+ self._storagepools[name].info['autostart'] = value
+
+ def create_storagevolume(self, pool, params):
+ try:
+ name = params['name']
+ volume = MockStorageVolume(pool, name, params)
+ volume.info['type'] = params['type']
+ volume.info['format'] = params['format']
+ volume.info['path'] = os.path.join(pool.info['path'], name)
+ except KeyError, item:
+ raise MissingParameter(item)
+
+ pool._volumes[name] = volume
+
+ def get_storagevolumes_by_pool(self, pool):
+ return self._storagepools[pool]._volumes.keys()
+
+ def get_storagevolume(self, pool, name):
+ vol = self._storagevolumes[pool]._volumes[name]
+ return vol.info
+
+ def wipe_storagevolume(self, pool, name):
+ self._storagepools[pool]._volumes[name].info['allocation'] = 0
+
+ def resize_storagevolume(self, pool, name, size):
+ self._storagepools[pool]._volumes[name].info['capacity'] = size
+
+ def delete_storagevolume(self, pool, name):
+ del self._storagepools[pool]._volumes[name]
+
+ def create_network(self, params):
+ name = params['name']
+ info = copy.deepcopy(self._network_info)
+ info.update(params)
+ self._networks[name] = info
+
+ def get_networks(self):
+ return self._networks.keys()
+
+ def get_network_by_name(self, name):
+ info = self._networks[name]
+ info['vms'] = self._get_vms_attach_to_network(name)
+ return info
+
+ def _get_vms_attach_to_network(self, network):
+ vms = []
+ for name, dom in self._vms.iteritems():
+ if network in dom.networks:
+ vms.append(name)
+ return vms
+
+ def activate_network(self, name):
+ self._networks[name]['state'] = 'active'
+
+ def deactivate_network(self, name):
+ self._networks[name]['state'] = 'inactive'
+
+ def delete_network(self, name):
+ del self._networks[name]
+
+ def create_template(self, name, tmpl):
+ self._templates[name] = tmpl.info
+
+ def get_templates(self):
+ return self._templates.keys()
+
+ def get_template_by_name(self, name):
+ return self._templates[name]
+
+ def delete_template(self, name):
+ del self._templates[name]
+
+ def create_vm(self, name, uuid, tmpl, vol_list):
+ vm = MockVM(vm_uuid, name, tmpl.info)
+ icon = tmpl.info.get('icon', None)
+ if icon is not None:
+ vm.info['icon'] = icon
+
+ disk_paths = []
+ for vol in vol_list:
+ disk_paths.append({'pool': pool.name, 'volume': vol_info['name']})
+
+ vm.disk_paths = disk_paths
+ self._vms[name] = vm
+
+ def get_vms(self):
+ return self._vms.keys()
+
+ def get_screenshot_by_name(self, vm_uuid):
+ mockscreenshot = MockVMScreenshot({'uuid': vm_uuid})
+ screenshot = self._screenshots.setdefault(vm_uuid, mockscreenshot)
+ return screenshot.lookup()
+
+ def get_vm_by_name(self, name):
+ vm = self._vms[name]
+ if vm.info['state'] == 'running':
+ vm.info['screenshot'] = self.get_screenshot_by_name(name)
+ else:
+ vm.info['screenshot'] = None
+ return vm.info
+
+ def static_vm_update(self, name, params):
+ vm_info = copy.copy(self._vms[name])
+ for key, val in params.items():
+ if key in VM_STATIC_UPDATE_PARAMS and key in vm_info:
+ vm_info[key] = val
+
+ if 'name' in params:
+ del self._vms[name]
+ self._vms[params['name']] = vm_info
+
+ def live_vm_update(self, name, params):
+ pass
+
+ def delete_vm(self, name):
+ vm = self._vms[name]
+ screenshot = self._screenshots.get(vm.uuid, None)
+ if screenshot is not None:
+ screenshot.delete()
+ del self._screenshots[vm_uuid]
+
+ for disk in vm.disk_paths:
+ self.delete_storagevolume(disk['pool'], disk['volume'])
+
+ del self._vms[name]
+
+ def start_vm(self, name):
+ self._vms[name].info['state'] = 'running'
+
+ def stop_vm(self, name):
+ self._vms[name].info['state'] = 'shutoff'
+
+ def connect_vm(self, name):
+ pass
+
+class MockStoragePool(object):
+ def __init__(self, name):
+ self.name = name
+ self._volumes = {}
+ self.info = {'state': 'inactive', 'capacity': 1024 << 20,
+ 'allocated': 512 << 20, 'available': 512 << 20,
+ 'path': '/var/lib/libvirt/images', 'source': {},
+ 'type': 'dir', 'nr_volumes': 0, 'autostart': 0}
+
+ def refresh(self):
+ state = self.info['state']
+ self.info['nr_volumes'] = 0
+ if state == 'active':
+ self.info['nr_volumes'] = len(self._volumes)
+
+class MockStorageVolume(object):
+ def __init__(self, pool, name, params={}):
+ self.name = name
+ self.pool = pool
+ self.info = {'type': 'disk', 'allocation': 512,
+ 'capacity': params.get('capacity', 1024) << 20,
+ 'format': params.get('format', 'raw')}
+
+ if fmt == 'iso':
+ self.info['allocation'] = self.info['capacity']
+ self.info['os_version'] = '19'
+ self.info['os_distro'] = 'fedora'
+ self.info['bootable'] = True
+
+class MockVM(object):
+ def __init__(self, uuid, name, template_info):
+ self.uuid = uuid
+ self.name = name
+ self.disk_paths = []
+ self.networks = template_info['networks']
+ self.info = {'state': 'shutoff',
+ 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \
+ 'net_throughput_peak': 100, 'io_throughput': 45, \
+ 'io_throughput_peak': 100}",
+ 'uuid': self.uuid,
+ 'memory': template_info['memory'],
+ 'cpus': template_info['cpus'],
+ 'icon': None,
+ 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None}
+ }
+ self.info['graphics'].update(template_info['graphics'])
+
+class MockVMScreenshot(VMScreenshot):
+ OUTDATED_SECS = 5
+ BACKGROUND_COLOR = ['blue', 'green', 'purple', 'red', 'yellow']
+ BOX_COORD = (50, 115, 206, 141)
+ BAR_COORD = (50, 115, 50, 141)
+
+ def __init__(self, vm_name):
+ VMScreenshot.__init__(self, vm_name)
+ self.coord = MockVMScreenshot.BAR_COORD
+ self.background = random.choice(MockVMScreenshot.BACKGROUND_COLOR)
+
+ def _generate_scratch(self, thumbnail):
+ self.coord = (self.coord[0],
+ self.coord[1],
+ min(MockVMScreenshot.BOX_COORD[2],
+ self.coord[2]+random.randrange(50)),
+ self.coord[3])
+
+ image = Image.new("RGB", (256, 256), self.background)
+ d = ImageDraw.Draw(image)
+ d.rectangle(MockVMScreenshot.BOX_COORD, outline='black')
+ d.rectangle(self.coord, outline='black', fill='black')
+ image.save(thumbnail)
diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py
new file mode 100644
index 0000000..dfefa81
--- /dev/null
+++ b/src/kimchi/model/networks.py
@@ -0,0 +1,115 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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.model import interfaces
+
+class Networks(object):
+ def __init__(self, backend):
+ self.backend = backend
+ self.ifaces = interfaces.Interfaces(backend)
+
+ def create(self, params):
+ name = params['name']
+ if name in self.get_list():
+ raise InvalidOperation("Network %s already exists" % name)
+
+ connection = params['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':
+ iface = params.get('interface', None)
+ if iface is None:
+ raise MissingParameter("You need to specify interface to create"
+ " a bridged network.")
+
+ if iface in self.ifaces.get_used_ifaces():
+ raise InvalidParameter("interface '%s' already in use." % iface)
+
+ if not (netinfo.is_bridge(iface) or netinfo.is_bare_nic(iface) or
+ netinfo.is_bonding(iface)):
+ raise InvalidParameter("The interface should be bare nic, bonding "
+ "or bridge device.")
+
+ self.backend.create_network(params)
+ return name
+
+ def get_list(self):
+ return sorted(self.backend.get_networks())
+
+ 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():
+ net = self.backend.get_network_by_name(net_name)
+ subnet = net['subnet']
+ subnet and net_addrs.append(ipaddr.IPNetwork(subnet))
+
+ netaddr = network.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({'subnet': str(ip),
+ 'dhcp': {'range': {'start': dhcp_start,
+ 'end': dhcp_end}}})
+
+class Network(Networks):
+ def _network_exist(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Network '%s' not found.")
+
+ return True
+
+ def lookup(self, name):
+ if self._network_exist(name):
+ return self.backend.get_network_by_name(name)
+
+ def activate(self, name):
+ if self._network_exist(name):
+ return self.backend.activate_network(name)
+
+ def deactivate(self, name):
+ if self._network_exist(name):
+ return self.backend.deactivate_network(name)
+
+ def delete(self, name):
+ if self.lookup(name)['state'] == 'active':
+ raise InvalidOperation("Unable to delete the active network %s" %
+ name)
+
+ return self.backend.delete_network(name)
diff --git a/src/kimchi/model/plugins.py b/src/kimchi/model/plugins.py
new file mode 100644
index 0000000..3cbae77
--- /dev/null
+++ b/src/kimchi/model/plugins.py
@@ -0,0 +1,29 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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 utils
+
+class Plugins(object):
+ def get_list(self):
+ return [plugin for (plugin, config) in utils.get_enabled_plugins()]
+
diff --git a/src/kimchi/model/storagepools.py b/src/kimchi/model/storagepools.py
new file mode 100644
index 0000000..abdebd8
--- /dev/null
+++ b/src/kimchi/model/storagepools.py
@@ -0,0 +1,86 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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
+
+ISO_POOL_NAME = u'kimchi_isos'
+
+from kimchi.exception import InvalidParameter
+
+class StoragePools(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def get_list(self):
+ return sorted(self.backend.get_storagepools())
+
+ def create(self, params):
+ name = params['name']
+ if name in self.get_list() or name in (ISO_POOL_NAME,):
+ raise InvalidParameter("Storage pool '%s' already exists" % name)
+
+ task_id = None
+ if params['type'] == 'kimchi-iso':
+ task_id = self.backend.do_deep_scan(params)
+
+ self.backend.create_storagepool(params)
+ return name
+
+class StoragePool(StoragePools):
+ def lookup(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Storage pool '%s' not found.")
+
+ return self.backend.get_storagepool_by_name(name)
+
+ def activate(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Storage pool '%s' not found.")
+
+ self.backend.activate_storagepool()
+
+ def deactivate(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Storage pool '%s' not found.")
+
+ self.backend.deactivate_storagepool()
+
+ def delete(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Storage pool '%s' not found.")
+
+ if self.get_storagepool_by_name(name)['state'] == 'active':
+ raise InvalidOperation("Unable to delete active storage pool '%s'" %
+ name)
+
+ self.backend.delete_storagepool()
+
+ def update(self, name, params):
+ if name not in self.get_list():
+ raise NotFoundError("Storage pool '%s' not found.")
+
+ autostart = params['autostart']
+ if autostart not in [True, False]:
+ raise InvalidOperation("Autostart flag must be true or false")
+
+ self.backend.autostart_storagepool(name, autostart)
+
+ return name
diff --git a/src/kimchi/model/storagevolumes.py b/src/kimchi/model/storagevolumes.py
new file mode 100644
index 0000000..9f3c93b
--- /dev/null
+++ b/src/kimchi/model/storagevolumes.py
@@ -0,0 +1,95 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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 InvalidOperation, InvalidParameter, NotFoundError
+from kimchi.model import storagepools
+
+class StorageVolumes(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def create(self, pool, params):
+ if name in self.get_list(pool):
+ raise InvalidParameter("Storage volume '%s' already exists.")
+
+ self.backend.create_storagevolume(pool, params)
+ return name
+
+ def get_list(self, pool):
+ info = self.backend.get_storagepool_by_name(pool)
+ if info['state'] != 'active':
+ raise InvalidOperation("Unable to list volumes in inactive "
+ "storagepool %s" % pool)
+
+ return self.backend.get_storagevolumes_by_pool(pool)
+
+class StorageVolume(StorageVolumes):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def _storagevolume_exist(self, pool, name):
+ if name not in self.get_list(pool):
+ raise NotFoundError("Storage volume '%' not found in '%' pool" %
+ (name, pool))
+ return True
+
+ def lookup(self, pool, name):
+ if self._storagevolume_exist(pool, name):
+ return self.backend.get_storagevolume(pool, name)
+
+ def resize(self, pool, name, size):
+ if self._storagevolume_exist(pool, name):
+ self.backend.resize_storagevolume(pool, name, size)
+
+ def wipe(self, pool, name):
+ if self._storagevolume_exist(pool, name):
+ self.backend.wipe_storagevolume(pool, name)
+
+ def delete(self, pool, name):
+ if self._storagevolume_exist(pool, name):
+ self.backend.delete_storagevolume(pool, name)
+
+class IsoVolumes(StorageVolumes):
+ def __init__(self, backend):
+ self.backend = backend
+ self.storagepools = storagepools.StoragePools(self.backend)
+
+ def get_list(self, pool):
+ iso_volumes = []
+
+ for pool in self.storagepools.get_list():
+ try:
+ volumes = self.get_list(pool)
+ except InvalidOperation:
+ # Skip inactive pools
+ continue
+
+ for volume in volumes:
+ res = self.lookup(pool, volume)
+ if res['format'] == 'iso':
+ # prevent iso from different pool having same volume name
+ res['name'] = '%s-%s' % (pool, volume)
+ iso_volumes.append(res)
+
+ return iso_volumes
diff --git a/src/kimchi/model/tasks.py b/src/kimchi/model/tasks.py
new file mode 100644
index 0000000..29eaddf
--- /dev/null
+++ b/src/kimchi/model/tasks.py
@@ -0,0 +1,45 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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
+
+ERROR_TASK_NOT_FOUND = "Task id '%s' not found."
+
+class Tasks(object):
+ def __init__(self, backend):
+ self.objstore = backend.objstore
+
+ def get_list(self):
+ with self.objstore as session:
+ return session.get_list('task')
+
+class Task(object):
+ def __init__(self, backend):
+ self.objstore = backend.objstore
+
+ def lookup(self, ident):
+ if ident not in self.get_list():
+ raise NotFoundError(ERROR_TASK_NOT_FOUND % ident)
+
+ with self.objstore as session:
+ return session.get('task', str(ident))
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
new file mode 100644
index 0000000..86cd54f
--- /dev/null
+++ b/src/kimchi/model/templates.py
@@ -0,0 +1,89 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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.vmtemplate import VMTemplate
+from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError
+from kimchi.exception import OperationFailed
+
+class Templates(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def create(self, params):
+ name = params['name']
+ if name in self.get_list:
+ raise InvalidOperation("Template '%s' already exists." % name)
+
+ for net_name in params.get(u'networks', []):
+ if net_name not in self.backend.get_networks():
+ raise InvalidParameter("Network '%s' not found," % net_name)
+
+ try:
+ tmpl = VMTemplate(params, self.backend.TEMPLATE_SCAN)
+ self.backend.create_template(name, tmpl)
+ except Exception, e:
+ raise OperationFailed("Unable to create template '%s': %s" %
+ (name, e.message))
+
+ return name
+
+ def get_list(self):
+ return sorted(self.backend.get_templates())
+
+class Template(Templates):
+ def lookup(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Template '%s' not found." % name)
+
+ params = self.backend.get_template_by_name(name)
+ tmpl = VMTemplate(params, False)
+ return tmpl.info
+
+ def delete(self, name):
+ if name not in self.get_list():
+ raise NotFoundError("Template '%s' not found." % name)
+
+ self.backend.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
+
+ new_storagepool = new_t.get(u'storagepool', '')
+ if new_storagepool not in self.backend.get_storagepools():
+ raise InvalidParameter("Storage pool '%s' not found." % name)
+
+ for net_name in params.get(u'networks', []):
+ if net_name not in self.backend.get_networks():
+ raise InvalidParameter("Network '%s' not found." % net_name)
+
+ self.delete(name)
+ try:
+ ident = self.create(new_t)
+ except:
+ ident = self.create(old_t)
+
+ return ident
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
new file mode 100644
index 0000000..3f1b6c3
--- /dev/null
+++ b/src/kimchi/model/vms.py
@@ -0,0 +1,164 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl at linux.vnet.ibm.com>
+# 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 uuid
+
+VM_STATIC_UPDATE_PARAMS = {'name': './name'}
+
+class VMs(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def create(self, params):
+ vm_uuid = str(uuid.uuid4())
+ vm_list = self.get_list()
+ name = self._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 '%s' already exists" % name)
+
+ vm_overrides = dict()
+ override_params = ['storagepool', 'graphics']
+ for param in override_params:
+ value = params.get(param, None)
+ if value is not None:
+ vm_overrides[param] = value
+
+ t_name = self._uri_to_name('templates', params['template'])
+ t_params = self.backend.get_template_by_name(t_name)
+ t_params.update(vm_overrides)
+ tmpl = VMTemplate(t_params, False)
+
+ caps = self.backend.get_capabilities()
+ if not caps.qemu_stream and t.info.get('iso_stream', False):
+ raise InvalidOperation("Remote ISO image is not supported by this"
+ " server.")
+
+ pool_name = self._uri_to_name('storagepools', pool_uri)
+ self._validate_storagepool(pool_name)
+ self._validate_network(tmpl)
+ vol_list = tmpl.to_volume_list(vm_uuid)
+ for vol_info in vol_list:
+ self.backend.create_storagevolume(pool_name, vol_info)
+
+ self.backend.create_vm(name, vm_uuid, tmpl, vol_list)
+ return name
+
+ def _validate_storagepool(self, pool_name):
+ try:
+ pool_info = self.backend.get_storagepool_by_name(pool_name)
+ except Exception:
+ raise InvalidParameter("Storage pool '%s' specified by template "
+ "does not exist" % pool_name)
+
+ if not pool_info['state'] != 'active':
+ raise InvalidParameter("Storage pool '%s' specified by template "
+ "is not active" % pool_name)
+
+ def _validate_network(self, tmpl):
+ names = tmpl.info['networks']
+ for name in names:
+ if name not in self.backend.get_networks():
+ raise InvalidParameter("Network '%s' specified by template "
+ "does not exist.")
+
+ net_info = self.backend.get_network_by_name(name)
+ if net_info['state'] != 'active':
+ raise InvalidParameter("Network '%s' specified by template is "
+ "not active.")
+
+ def _uri_to_name(self, collection, uri):
+ expr = '/%s/(.*?)/?$' % collection
+ m = re.match(expr, uri)
+ if not m:
+ raise InvalidParameter(uri)
+ return m.group(1)
+
+ def _get_vm_name(self, 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")
+
+ def get_list(self):
+ return sorted(self.backend.get_vms())
+
+class VM(VMs):
+ def _vm_exists(self, name):
+ if name not in self.backend.get_vms():
+ raise NotFoundError("VM '%s' not found." % name)
+
+ return True
+
+ def lookup(self, name):
+ if self._vm_exists(name):
+ return self.backend.get_vm_by_name(name)
+
+ def update(self, name, params):
+ if self._vm_exists(name):
+ if 'name' in params:
+ state = self.get_vm_by_name(name)['state']
+ if state == 'running':
+ raise InvalidParameter("The VM needs to be shutted off for"
+ "renaming.")
+
+ if params['name'] in self.get_list():
+ raise InvalidParameter("VM name '%s' already exists" %
+ params['name'])
+
+ self.backend.static_vm_update(name, params)
+ self.backend.live_vm_update(name, params)
+
+ return params.get('name', None) or name
+
+ def delete(self, name):
+ if self._vm_exists(name):
+ self.backend.delete_vm(name)
+
+ def start(self, name):
+ if self._vm_exists(name):
+ self.backend.start_vm(self, name)
+
+ def stop(self, name):
+ if self._vm_exists(name):
+ self.backend.stop_vm(self, name)
+
+ def connect(self, name):
+ if self._vm_exists(name):
+ self.backend.connect_vm(self, name)
+
+class VMScreenshot(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def lookup(self, name):
+ vm_info = self.backend.get_vm_by_name(name)
+ if vm_info['state'] != 'running':
+ raise NotFoundError('No screenshot for stopped vm')
+
+ return self.backend.get_screenshot_by_name(vm_info['uuid'])
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 7c0a5d6..0000000
--- a/src/kimchi/model_/config.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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.distroloader import DistroLoader
-from kimchi.exception import NotFoundError
-
-ERR_DISTRO_NOT_FOUND = "Distro '%s' not found."
-
-class Config(object):
- pass
-
-class Capabilities(object):
- def __init__(self, backend):
- self.backend = backend
-
- def lookup(self, ident):
- return self.backend.get_capabilities()
-
-class Distros(object):
- def __init__(self):
- self._distroloader = DistroLoader()
- self._distros = self._distroloader.get()
-
- def get_list(self):
- return self._distros.keys()
-
-class Distro(Distros):
- def lookup(self, ident):
- if ident not in self.get_list():
- raise NotFoundError(ERR_DISTRO_NOT_FOUND % ident)
-
- return self._distros[ident]
diff --git a/src/kimchi/model_/debugreports.py b/src/kimchi/model_/debugreports.py
deleted file mode 100644
index 4db1ace..0000000
--- a/src/kimchi/model_/debugreports.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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 glob
-import os
-import time
-
-from kimchi import config
-from kimchi.exception import NotFoundError
-
-class DebugReports(object):
- def __init__(self, backend):
- pass
-
- 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
-
-class DebugReport(object):
- def __init__(self, backend):
- self.backend = backend
-
- def lookup(self, name):
- file_target = self._get_debugreport(name)
- 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 create(self, params):
- ident = params['name']
- taskid = self.backend.gen_debugreport_file(ident)
- if taskid is None:
- raise OperationFailed("Debug report tool not found.")
-
- with self.backend.objstore as session:
- return session.get('task', str(taskid))
-
- def delete(self, name):
- file_target = self._get_debugreports(name)
- os.remove(file_target)
-
- def _get_debugreport(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("Debug report '%s' not found.")
-
- return file_target
-
-class DebugReportContent(object):
- def __init__(self, backend):
- self.backend = backend
-
- def lookup(self, name):
- debugreport = DebugReports(backend)
- return self.debugreport.lookup(name)
diff --git a/src/kimchi/model_/host.py b/src/kimchi/model_/host.py
deleted file mode 100644
index f5ef0d7..0000000
--- a/src/kimchi/model_/host.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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 disks
-
-class Host(object):
- def __init__(self, backend):
- self.info = backend.get_host()
-
- def lookup(self, ident):
- return self.info
-
-class HostStats(object):
- def __init__(self, backend):
- self.backend = backend
-
- def lookup(self, ident):
- return self.backend.host_stats
-
-class Partitions(object):
- def get_list(self):
- return disks.get_partitions_names()
-
-class Partition(object):
- def lookup(self, ident):
- if ident not in disks.get_partitions_names():
- raise NotFoundError("Partition %s not found in the host" % name)
-
- return disks.get_partition_details(ident)
diff --git a/src/kimchi/model_/interfaces.py b/src/kimchi/model_/interfaces.py
deleted file mode 100644
index 7b1d156..0000000
--- a/src/kimchi/model_/interfaces.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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
-
-class Interfaces(object):
- def __init__(self, backend):
- self.backend = backend
-
- def get_list(self):
- return list(set(netinfo.all_favored_interfaces()) -
- set(self.get_used_ifaces()))
-
- def get_used_ifaces(self):
- net_names = self.backend.get_networks()
- interfaces = []
- for name in net_names:
- net_dict = self.backend.get_network_by_name(name)
- net_dict['interface'] and interfaces.append(net_dict['interface'])
-
- return interfaces
-
-class Interface(object):
- def lookup(self, name):
- try:
- return netinfo.get_interface_info(name)
- except ValueError, e:
- raise NotFoundError(e)
diff --git a/src/kimchi/model_/libvirtbackend.py b/src/kimchi/model_/libvirtbackend.py
deleted file mode 100644
index bf29c78..0000000
--- a/src/kimchi/model_/libvirtbackend.py
+++ /dev/null
@@ -1,955 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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
-import fnmatch
-import glob
-import ipaddr
-import logging
-import os
-import platform
-import psutil
-import shutil
-import subprocess
-import time
-
-from cherrypy.process.plugins import BackgroundTask
-from collections import defaultdict
-
-from kimchi import config
-from kimchi import network
-from kimchi import netinfo
-from kimchi import networkxml
-from kimchi import xmlutils
-from kimchi.asynctask import AsyncTask
-from kimchi.exception import IsoFormatError, 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 kimchi_log
-
-GUESTS_STATS_INTERVAL = 5
-HOST_STATS_INTERVAL = 1
-STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
- 'path': '/pool/source/dir/@path'}}
-
-class LibvirtBackend(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'}
-
- TEMPLATE_SCAN = True
-
- def __init__(self, libvirt_uri=None, objstore_loc=None):
- self.libvirt_uri = libvirt_uri or 'qemu:///system'
- self.conn = LibvirtConnection(self.libvirt_uri)
- self.objstore = ObjectStore(objstore_loc)
- self.next_taskid = 1
- self.scanner = Scanner(self._clean_scan)
- self.stats = {}
- self.host_stats = defaultdict(int)
- 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()
-
- # 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)
-
- # 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},)
-
- 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.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):
- conn = self.conn.get()
- vm_list = self.get_vms()
-
- for name in vm_list:
- info = self.get_vm_by_name(name)
- vm_uuid = info['uuid']
- state = info['state']
- 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})
-
- dom = conn.lookupByName(name.encode("utf-8"))
- self._get_percentage_cpu_usage(vm_uuid, dom.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 = 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 _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)
-
- # get cpu utilization
- self.host_stats['cpu_utilization'] = psutil.cpu_percent(None)
-
- # get memory stats
- virt_mem = psutil.virtual_memory()
- self.host_stats['memory'] = {'total': virt_mem.total,
- 'free': virt_mem.free,
- 'cached': virt_mem.cached,
- 'buffers': virt_mem.buffers,
- 'avail': virt_mem.available}
-
- 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
-
- ifaces = set(netinfo.nics() + netinfo.wlans()) & set(net_ios.iterkeys())
- for key in ifaces:
- 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 gen_debugreport_file(self, name):
- gen_cmd = self._get_system_report_tool()
- if gen_cmd is None:
- return None
-
- return self.add_task('', gen_cmd, name)
-
- 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 _sosreport_generate(self, cb, name):
- command = 'sosreport --batch --name "%s"' % name
- try:
- retcode = subprocess.call(command, shell=True,
- stdout=subprocess.PIPE,
- stderr=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)
- 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 add_task(self, target_uri, fn, opaque=None):
- id = self.next_taskid
- self.next_taskid += 1
- task = AsyncTask(id, target_uri, fn, self.objstore, opaque)
- return id
-
- def get_host(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
- 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_storagepools(self):
- try:
- conn = self.conn.get()
- names = conn.listStoragePools()
- names += conn.listDefinedStoragePools()
- return names
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _clean_scan(self, pool_name):
- try:
- self.deactivate_storagepool(pool_name)
- with self.objstore as session:
- session.delete('scanning', pool_name)
- except Exception, e:
- kimchi_log.debug("Error while cleaning deep scan results" %
- e.message)
-
- def do_deep_scan(self, params):
- scan_params = dict(ignore_list=[])
- scan_params['scan_path'] = params['path']
- params['type'] = 'dir'
-
- for pool in self.get_storagepools():
- try:
- res = self.get_storagepool_by_name(pool)
- if res['state'] == 'active':
- scan_params['ignore_list'].append(res['path'])
- except Exception, e:
- kimchi_log.debug("Error while preparing for deep scan: %s" %
- e.message)
-
- params['path'] = self.scanner.scan_dir_prepare(params['name'])
- scan_params['pool_path'] = params['path']
- task_id = self.add_task('', self.scanner.start_scan, scan_params)
- # Record scanning-task/storagepool mapping for future querying
- with self.objstore as session:
- session.store('scanning', params['name'], task_id)
- return task_id
-
- def create_storagepool(self, params):
- conn = self.conn.get()
- try:
- poolDef = StoragePoolDef.create(params)
- poolDef.prepare(conn)
- xml = poolDef.xml
- except KeyError, key:
- raise MissingParameter("You need to specify '%s' in order to "
- "create storage pool" % key)
-
- try:
- if task_id:
- # Create transient pool for deep scan
- conn.storagePoolCreateXML(xml, 0)
- return name
-
- pool = conn.storagePoolDefineXML(xml, 0)
- if params['type'] in ['logical', 'dir', 'netfs']:
- pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
- # autostart dir and logical storage pool created from kimchi
- pool.setAutostart(1)
- else:
- # disable autostart for others
- pool.setAutostart(0)
- except libvirt.libvirtError as e:
- msg = "Problem creating Storage Pool: %s"
- kimchi_log.error(msg, e)
- raise OperationFailed(e.get_error_message())
-
- def get_storagepool_by_name(self, name):
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(name)
- info = pool.info()
- nr_volumes = self._get_storagepool_vols_num(pool)
- autostart = True if pool.autostart() else False
- xml = pool.XMLDesc(0)
- path = xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
- pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
- source = self._get_storage_source(pool_type, xml)
- res = {'state': self.pool_state_map[info[0]],
- 'path': path,
- 'source': source,
- 'type': pool_type,
- 'autostart': autostart,
- 'capacity': info[1],
- 'allocated': info[2],
- 'available': info[3],
- 'nr_volumes': nr_volumes}
-
- if not pool.isPersistent():
- # Deal with deep scan generated pool
- try:
- with self.objstore as session:
- task_id = session.get('scanning', name)
- res['task_id'] = str(task_id)
- res['type'] = 'kimchi-iso'
- except NotFoundError:
- # User created normal pool
- pass
- return res
-
- def _get_storagepool_vols_num(self, pool):
- try:
- if pool.isActive():
- pool.refresh(0)
- return pool.numOfVolumes()
- else:
- return 0
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _get_storage_source(self, pool_type, pool_xml):
- source = {}
- if pool_type not in STORAGE_SOURCES:
- return source
-
- for key, val in STORAGE_SOURCES[pool_type].items():
- res = xmlutils.xpath_get_text(pool_xml, val)
- source[key] = res[0] if len(res) == 1 else res
-
- return source
-
- def activate_storagepool(self, name):
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(name)
- pool.create(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def deactivate_storagepool(self, name):
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(name)
- pool.destroy()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def delete_storagepool(self, name):
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(name)
- pool.undefine()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def autostart_storagepool(self, name, value):
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(name)
- if autostart:
- pool.setAutostart(1)
- else:
- pool.setAutostart(0)
-
- def create_storagevolume(self, pool, params):
- storagevol_xml = """
- <volume>
- <name>%(name)s</name>
- <allocation unit="MiB">%(allocation)s</allocation>
- <capacity unit="MiB">%(capacity)s</capacity>
- <target>
- <format type='%(format)s'/>
- <path>%(path)s</path>
- </target>
- </volume>"""
-
- params.setdefault('allocation', 0)
- params.setdefault('format', 'qcow2')
- try:
- xml = storagevol_xml % params
- except KeyError, key:
- raise MissingParameter("You need to specify '%s' in order to "
- "create the storage volume." % key)
-
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(pool)
- try:
- pool.createXML(xml, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def _get_storagevolume(self, pool, name):
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(pool)
- return pool.storageVolLookupByName(name)
-
- def get_storagevolumes_by_pool(self, pool):
- try:
- conn = self.conn.get()
- pool = conn.storagePoolLookupByName(pool)
- pool.refresh(0)
- return pool.listVolumes()
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def get_storagevolume(self, pool, name):
- vol = self._get_storagevolume(pool, name)
- path = vol.path()
- info = vol.info()
- xml = vol.XMLDesc(0)
- fmt = xmlutils.xpath_get_text(xml, "/volume/target/format/@type")[0]
- res = dict(type=self.volume_type_map[info[0]], capacity=info[1],
- allocation=info[2], path=path, format=fmt)
-
- if fmt == 'iso':
- if os.path.islink(path):
- path = os.path.join(os.path.dirname(path), os.readlink(path))
-
- try:
- iso_img = IsoImage(path)
- os_distro, os_version = iso_img.probe()
- bootable = True
- except IsoFormatError:
- bootable = False
-
- res.update(dict(os_distro=os_distro, os_version=os_version,
- path=path, bootable=bootable))
-
- return res
-
- def wipe_storagevolume(self, pool, name):
- try:
- vol = self._get_storagevolume(pool, name)
- vol.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def resize_storagevolume(self, pool, name, size):
- size = size << 20
- try:
- vol = pool.storageVolLookupByName(name)
- vol.resize(size, 0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def delete_storagevolume(self, pool, name):
- try:
- vol = pool.storageVolLookupByName(name)
- volume.delete(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- def create_network(self, params):
- connection = params["connection"]
- # set forward mode, isolated do not need forward
- if connection != 'isolated':
- params['forward'] = {'mode': connection}
-
- if connection == 'bridge':
- 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']))
-
- xml = networkxml.to_network_xml(**params)
- try:
- conn = self.conn.get()
- network = conn.networkDefineXML(xml)
- network.setAutostart(True)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.get_error_message())
-
- 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
-
- def get_networks(self):
- conn = self.conn.get()
- return conn.listNetworks() + conn.listDefinedNetworks()
-
- def get_network_by_name(self, name):
- conn = self.conn.get()
- net = conn.networkLookupByName(name)
- xml = net.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 = network.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_network(name),
- 'autostart': net.autostart() == 1,
- 'state': net.isActive() and "active" or "inactive"}
-
- 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_vms_attach_to_network(self, network):
- vms = []
- xpath = "/domain/devices/interface[@type='network']/source/@network"
- conn = self.conn.get()
- for dom in conn.listAllDomains(0):
- xml = dom.XMLDesc(0)
- networks = xmlutils.xpath_get_text(xml, xpath)
- if network in networks:
- vms.append(dom.name())
- return vms
-
- def activate_network(self, name):
- conn = self.conn.get()
- net = conn.networkLookupByName(name)
- net.create()
-
- def deactivate_network(self, name):
- conn = self.conn.get()
- net = conn.networkLookupByName(name)
- net.destroy()
-
- def delete_network(self, name):
- conn = self.conn.get()
- net = conn.networkLookupByName(name)
- self._remove_vlan_tagged_bridge(net)
- net.undefine()
-
- 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 create_template(self, name, tmpl):
- with self.objstore as session:
- session.store('template', name, tmpl.info)
-
- def get_templates(self):
- with self.objstore as session:
- return session.get_list('template')
-
- def get_template_by_name(self, name):
- with self.objstore as session:
- return session.get('template', name)
-
- def delete_template(self, name):
- with self.objstore as session:
- session.delete('template', name)
-
- def create_vm(self, name, uuid, tmpl, vol_list):
- # Store the icon for displaying later
- icon = tmpl.info.get('icon', None)
- if icon is not None:
- with self.objstore as session:
- session.store('vm', vm_uuid, {'icon': icon})
-
- libvirt_stream = False
- if len(self.libvirt_stream_protocols) != 0:
- libvirt_stream = True
-
- xml = tmpl.to_vm_xml(name, vm_uuid, libvirt_stream,
- self.qemu_stream_dns)
- 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())
-
- def get_vms(self):
- conn = self.conn.get()
- ids = conn.listDomainsID()
- names = map(lambda x: conn.lookupByID(x).name(), ids)
- names += conn.listDefinedDomains()
- names = map(lambda x: x.decode('utf-8'), names)
- return sorted(names, key=unicode.lower)
-
- def get_screenshot_by_name(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)
-
- screenshot = LibvirtVMScreenshot(params, 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
-
- def delete_screenshot(self, vm_uuid):
- os.remove(self.get_screenshot_by_name(vm_uuid))
- with self.objstore as session:
- session.delete('screenshot', vm_uuid)
-
- def get_vm_by_name(self, name):
- conn = self.conn.get()
- dom = conn.lookupByName(name.encode("utf-8"))
- info = dom.info()
- state = self.dom_state_map[info[0]]
- screenshot = None
- graphics = self._get_vm_graphics(dom)
- graphics_type, graphics_listen, graphics_port = graphics
- graphics_port = graphics_port if state == 'running' else None
- if state == 'running':
- screenshot = self.get_screenshot_by_name(name)
- elif state == 'shutoff':
- # reset vm stats when it is powered off to avoid sending
- # incorrect (old) data
- self.stats[dom.UUIDString()] = {}
-
- 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], 'icon': icon,
- 'screenshot': screenshot,
- 'graphics': {'type': graphics_type, 'listen': graphics_listen,
- 'port': graphics_port}
- }
-
- def _get_vm_graphics(self, dom):
- 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 static_vm_update(self, name, params):
- conn = self.conn.get()
- dom = conn.lookupByName(name.encode("utf-8"))
- 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:
- dom.undefine()
- conn.defineXML(new_xml)
- except libvirt.libvirtError as e:
- conn.defineXML(old_xml)
- raise OperationFailed(e.get_error_message())
-
- def live_vm_update(self, name, params):
- pass
-
- def delete_vm(self, name):
- info = self.get_vm_by_name(name)
- if info['state'] == 'running':
- self.stop_vm(name)
-
- conn = self.conn.get()
- dom = conn.lookupByName(name.encode("utf-8"))
- dom.undefine()
-
- xml = dom.XMLDesc(0)
- xpath = "/domain/devices/disk[@device='disk']/source/@file"
- paths = xmlutils.xpath_get_text(xml, xpath)
- for path in paths:
- vol = conn.storageVolLookupByPath(path)
- vol.delete(0)
-
- with self.objstore as session:
- session.delete('vm', dom.UUIDString(), ignore_missing=True)
-
- self.delete_screenshot(dom.UUIDString())
- vnc.remove_proxy_token(name)
-
- def start_vm(self, name):
- conn = self.conn.get()
- dom = conn.lookupByName(name.encode("utf-8"))
- dom.create()
-
- def stop_vm(self, name):
- conn = self.conn.get()
- dom = conn.lookupByName(name.encode("utf-8"))
- dom.destroy()
-
- def connect_vm(self, name):
- graphics = self._get_vm_graphics(name)
- graphics_type, graphics_listen, graphics_port = get_graphics
- if graphics_port is None:
- raise OperationFailed("Only able to connect to running vm's vnc "
- "graphics.")
- vnc.add_proxy_token(name, graphics_port)
-
-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)
diff --git a/src/kimchi/model_/libvirtconnection.py b/src/kimchi/model_/libvirtconnection.py
deleted file mode 100644
index 9276acc..0000000
--- a/src/kimchi/model_/libvirtconnection.py
+++ /dev/null
@@ -1,123 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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
-import libvirt
-import threading
-import time
-
-
-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:
- msg = 'Libvirt is not available, exiting.'
- kimchi_log.error(msg)
- 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 e9b9aa8..0000000
--- a/src/kimchi/model_/libvirtstoragepool.py
+++ /dev/null
@@ -1,225 +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.exception import OperationFailed
-from kimchi.iscsi import TargetClient
-
-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):
- # TODO: Verify the NFS export can be actually mounted.
- pass
-
- @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_/mockbackend.py b/src/kimchi/model_/mockbackend.py
deleted file mode 100644
index d4314ca..0000000
--- a/src/kimchi/model_/mockbackend.py
+++ /dev/null
@@ -1,338 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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 random
-
-from kimchi import config
-from kimchi.asynctask import AsyncTask
-from kimchi.objectstore import ObjectStore
-from kimchi.screenshot import VMScreenshot
-
-class MockBackend(object):
- _network_info = {'state': 'inactive', 'autostart': True, 'connection': '',
- 'interface': '', 'subnet': '',
- 'dhcp': {'start': '', 'stop': ''}}
-
- TEMPLATE_SCAN = False
-
- def __init__(self, objstore_loc=None):
- self.objstore = ObjectStore(objstore_loc)
- self.next_taskid = 1
- self.host_stats = self._get_host_stats()
- self._storagepools = {}
- self._networks = {}
- self._templates = {}
- self._vms = {}
- self._screenshots = {}
-
- def _get_host_stats(self):
- memory_stats = {'total': 3934908416L,
- 'free': round(random.uniform(0, 3934908416L), 1),
- 'cached': round(random.uniform(0, 3934908416L), 1),
- 'buffers': round(random.uniform(0, 3934908416L), 1),
- 'avail': round(random.uniform(0, 3934908416L), 1)}
-
- return {'cpu_utilization': round(random.uniform(0, 100), 1),
- 'memory': memory_stats,
- 'disk_read_rate': round(random.uniform(0, 4000), 1),
- 'disk_write_rate': round(random.uniform(0, 4000), 1),
- 'net_recv_rate': round(random.uniform(0, 4000), 1),
- 'net_sent_rate': round(random.uniform(0, 4000), 1)}
-
- def get_capabilities(self):
- protocols = ['http', 'https', 'ftp', 'ftps', 'tftp']
- return {'libvirt_stream_protocols': protocols,
- 'qemu_stream': True,
- 'screenshot': True,
- 'system_report_tool': True}
-
- def gen_debugreport_file(self, ident):
- return self.add_task('', self._create_debugreport, ident)
-
- def _create_debugreport(self, cb, name):
- path = config.get_debugreports_path()
- tmpf = os.path.join(path, name + '.tmp')
- realf = os.path.join(path, name + '.txt')
- length = random.randint(1000, 10000)
- with open(tmpf, 'w') as fd:
- while length:
- fd.write('mock debug report\n')
- length = length - 1
- os.rename(tmpf, realf)
- cb("OK", True)
-
- def add_task(self, target_uri, fn, opaque=None):
- id = self.next_taskid
- self.next_taskid += 1
- task = AsyncTask(id, target_uri, fn, self.objstore, opaque)
- return id
-
- def get_host(self):
- res = {}
- res['memory'] = 6114058240
- res['cpu'] = 'Intel(R) Core(TM) i5 CPU M 560 @ 2.67GHz'
- res['os_distro'] = 'Red Hat Enterprise Linux Server'
- res['os_version'] = '6.4'
- res['os_codename'] = 'Santiago'
-
- return res
-
- def get_storagepools(self):
- return self._storagepools.keys()
-
- def do_deep_scan(self):
- return self.add_task('', time.sleep, 25)
-
- def create_storagepool(self, params):
- name = params['name']
- pool = MockStoragePool(name)
- pool.info.update(params)
- if params['type'] == 'dir':
- pool.info['autostart'] = True
- else:
- pool.info['autostart'] = False
-
- self._storagepools[name] = pool
-
- def get_storagepool_by_name(self, name):
- pool = self._storagepools[name]
- pool.refresh()
- return pool.info
-
- def activate_storagepool(self, name):
- self._storagepools[name].info['state'] = 'active'
-
- def deactivate_storagepool(self, name):
- self._storagepools[name].info['state'] = 'inactive'
-
- def delete_storagepool(self, name):
- del self._storagepools[name]
-
- def autostart_storagepool(self, name, value):
- self._storagepools[name].info['autostart'] = value
-
- def create_storagevolume(self, pool, params):
- try:
- name = params['name']
- volume = MockStorageVolume(pool, name, params)
- volume.info['type'] = params['type']
- volume.info['format'] = params['format']
- volume.info['path'] = os.path.join(pool.info['path'], name)
- except KeyError, item:
- raise MissingParameter(item)
-
- pool._volumes[name] = volume
-
- def get_storagevolumes_by_pool(self, pool):
- return self._storagepools[pool]._volumes.keys()
-
- def get_storagevolume(self, pool, name):
- vol = self._storagevolumes[pool]._volumes[name]
- return vol.info
-
- def wipe_storagevolume(self, pool, name):
- self._storagepools[pool]._volumes[name].info['allocation'] = 0
-
- def resize_storagevolume(self, pool, name, size):
- self._storagepools[pool]._volumes[name].info['capacity'] = size
-
- def delete_storagevolume(self, pool, name):
- del self._storagepools[pool]._volumes[name]
-
- def create_network(self, params):
- name = params['name']
- info = copy.deepcopy(self._network_info)
- info.update(params)
- self._networks[name] = info
-
- def get_networks(self):
- return self._networks.keys()
-
- def get_network_by_name(self, name):
- info = self._networks[name]
- info['vms'] = self._get_vms_attach_to_network(name)
- return info
-
- def _get_vms_attach_to_network(self, network):
- vms = []
- for name, dom in self._vms.iteritems():
- if network in dom.networks:
- vms.append(name)
- return vms
-
- def activate_network(self, name):
- self._networks[name]['state'] = 'active'
-
- def deactivate_network(self, name):
- self._networks[name]['state'] = 'inactive'
-
- def delete_network(self, name):
- del self._networks[name]
-
- def create_template(self, name, tmpl):
- self._templates[name] = tmpl.info
-
- def get_templates(self):
- return self._templates.keys()
-
- def get_template_by_name(self, name):
- return self._templates[name]
-
- def delete_template(self, name):
- del self._templates[name]
-
- def create_vm(self, name, uuid, tmpl, vol_list):
- vm = MockVM(vm_uuid, name, tmpl.info)
- icon = tmpl.info.get('icon', None)
- if icon is not None:
- vm.info['icon'] = icon
-
- disk_paths = []
- for vol in vol_list:
- disk_paths.append({'pool': pool.name, 'volume': vol_info['name']})
-
- vm.disk_paths = disk_paths
- self._vms[name] = vm
-
- def get_vms(self):
- return self._vms.keys()
-
- def get_screenshot_by_name(self, vm_uuid):
- mockscreenshot = MockVMScreenshot({'uuid': vm_uuid})
- screenshot = self._screenshots.setdefault(vm_uuid, mockscreenshot)
- return screenshot.lookup()
-
- def get_vm_by_name(self, name):
- vm = self._vms[name]
- if vm.info['state'] == 'running':
- vm.info['screenshot'] = self.get_screenshot_by_name(name)
- else:
- vm.info['screenshot'] = None
- return vm.info
-
- def static_vm_update(self, name, params):
- vm_info = copy.copy(self._vms[name])
- for key, val in params.items():
- if key in VM_STATIC_UPDATE_PARAMS and key in vm_info:
- vm_info[key] = val
-
- if 'name' in params:
- del self._vms[name]
- self._vms[params['name']] = vm_info
-
- def live_vm_update(self, name, params):
- pass
-
- def delete_vm(self, name):
- vm = self._vms[name]
- screenshot = self._screenshots.get(vm.uuid, None)
- if screenshot is not None:
- screenshot.delete()
- del self._screenshots[vm_uuid]
-
- for disk in vm.disk_paths:
- self.delete_storagevolume(disk['pool'], disk['volume'])
-
- del self._vms[name]
-
- def start_vm(self, name):
- self._vms[name].info['state'] = 'running'
-
- def stop_vm(self, name):
- self._vms[name].info['state'] = 'shutoff'
-
- def connect_vm(self, name):
- pass
-
-class MockStoragePool(object):
- def __init__(self, name):
- self.name = name
- self._volumes = {}
- self.info = {'state': 'inactive', 'capacity': 1024 << 20,
- 'allocated': 512 << 20, 'available': 512 << 20,
- 'path': '/var/lib/libvirt/images', 'source': {},
- 'type': 'dir', 'nr_volumes': 0, 'autostart': 0}
-
- def refresh(self):
- state = self.info['state']
- self.info['nr_volumes'] = 0
- if state == 'active':
- self.info['nr_volumes'] = len(self._volumes)
-
-class MockStorageVolume(object):
- def __init__(self, pool, name, params={}):
- self.name = name
- self.pool = pool
- self.info = {'type': 'disk', 'allocation': 512,
- 'capacity': params.get('capacity', 1024) << 20,
- 'format': params.get('format', 'raw')}
-
- if fmt == 'iso':
- self.info['allocation'] = self.info['capacity']
- self.info['os_version'] = '19'
- self.info['os_distro'] = 'fedora'
- self.info['bootable'] = True
-
-class MockVM(object):
- def __init__(self, uuid, name, template_info):
- self.uuid = uuid
- self.name = name
- self.disk_paths = []
- self.networks = template_info['networks']
- self.info = {'state': 'shutoff',
- 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \
- 'net_throughput_peak': 100, 'io_throughput': 45, \
- 'io_throughput_peak': 100}",
- 'uuid': self.uuid,
- 'memory': template_info['memory'],
- 'cpus': template_info['cpus'],
- 'icon': None,
- 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None}
- }
- self.info['graphics'].update(template_info['graphics'])
-
-class MockVMScreenshot(VMScreenshot):
- OUTDATED_SECS = 5
- BACKGROUND_COLOR = ['blue', 'green', 'purple', 'red', 'yellow']
- BOX_COORD = (50, 115, 206, 141)
- BAR_COORD = (50, 115, 50, 141)
-
- def __init__(self, vm_name):
- VMScreenshot.__init__(self, vm_name)
- self.coord = MockVMScreenshot.BAR_COORD
- self.background = random.choice(MockVMScreenshot.BACKGROUND_COLOR)
-
- def _generate_scratch(self, thumbnail):
- self.coord = (self.coord[0],
- self.coord[1],
- min(MockVMScreenshot.BOX_COORD[2],
- self.coord[2]+random.randrange(50)),
- self.coord[3])
-
- image = Image.new("RGB", (256, 256), self.background)
- d = ImageDraw.Draw(image)
- d.rectangle(MockVMScreenshot.BOX_COORD, outline='black')
- d.rectangle(self.coord, outline='black', fill='black')
- image.save(thumbnail)
diff --git a/src/kimchi/model_/networks.py b/src/kimchi/model_/networks.py
deleted file mode 100644
index dfefa81..0000000
--- a/src/kimchi/model_/networks.py
+++ /dev/null
@@ -1,115 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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.model import interfaces
-
-class Networks(object):
- def __init__(self, backend):
- self.backend = backend
- self.ifaces = interfaces.Interfaces(backend)
-
- def create(self, params):
- name = params['name']
- if name in self.get_list():
- raise InvalidOperation("Network %s already exists" % name)
-
- connection = params['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':
- iface = params.get('interface', None)
- if iface is None:
- raise MissingParameter("You need to specify interface to create"
- " a bridged network.")
-
- if iface in self.ifaces.get_used_ifaces():
- raise InvalidParameter("interface '%s' already in use." % iface)
-
- if not (netinfo.is_bridge(iface) or netinfo.is_bare_nic(iface) or
- netinfo.is_bonding(iface)):
- raise InvalidParameter("The interface should be bare nic, bonding "
- "or bridge device.")
-
- self.backend.create_network(params)
- return name
-
- def get_list(self):
- return sorted(self.backend.get_networks())
-
- 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():
- net = self.backend.get_network_by_name(net_name)
- subnet = net['subnet']
- subnet and net_addrs.append(ipaddr.IPNetwork(subnet))
-
- netaddr = network.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({'subnet': str(ip),
- 'dhcp': {'range': {'start': dhcp_start,
- 'end': dhcp_end}}})
-
-class Network(Networks):
- def _network_exist(self, name):
- if name not in self.get_list():
- raise NotFoundError("Network '%s' not found.")
-
- return True
-
- def lookup(self, name):
- if self._network_exist(name):
- return self.backend.get_network_by_name(name)
-
- def activate(self, name):
- if self._network_exist(name):
- return self.backend.activate_network(name)
-
- def deactivate(self, name):
- if self._network_exist(name):
- return self.backend.deactivate_network(name)
-
- def delete(self, name):
- if self.lookup(name)['state'] == 'active':
- raise InvalidOperation("Unable to delete the active network %s" %
- name)
-
- return self.backend.delete_network(name)
diff --git a/src/kimchi/model_/plugins.py b/src/kimchi/model_/plugins.py
deleted file mode 100644
index 3cbae77..0000000
--- a/src/kimchi/model_/plugins.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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 utils
-
-class Plugins(object):
- def get_list(self):
- return [plugin for (plugin, config) in utils.get_enabled_plugins()]
-
diff --git a/src/kimchi/model_/storagepools.py b/src/kimchi/model_/storagepools.py
deleted file mode 100644
index abdebd8..0000000
--- a/src/kimchi/model_/storagepools.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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
-
-ISO_POOL_NAME = u'kimchi_isos'
-
-from kimchi.exception import InvalidParameter
-
-class StoragePools(object):
- def __init__(self, backend):
- self.backend = backend
-
- def get_list(self):
- return sorted(self.backend.get_storagepools())
-
- def create(self, params):
- name = params['name']
- if name in self.get_list() or name in (ISO_POOL_NAME,):
- raise InvalidParameter("Storage pool '%s' already exists" % name)
-
- task_id = None
- if params['type'] == 'kimchi-iso':
- task_id = self.backend.do_deep_scan(params)
-
- self.backend.create_storagepool(params)
- return name
-
-class StoragePool(StoragePools):
- def lookup(self, name):
- if name not in self.get_list():
- raise NotFoundError("Storage pool '%s' not found.")
-
- return self.backend.get_storagepool_by_name(name)
-
- def activate(self, name):
- if name not in self.get_list():
- raise NotFoundError("Storage pool '%s' not found.")
-
- self.backend.activate_storagepool()
-
- def deactivate(self, name):
- if name not in self.get_list():
- raise NotFoundError("Storage pool '%s' not found.")
-
- self.backend.deactivate_storagepool()
-
- def delete(self, name):
- if name not in self.get_list():
- raise NotFoundError("Storage pool '%s' not found.")
-
- if self.get_storagepool_by_name(name)['state'] == 'active':
- raise InvalidOperation("Unable to delete active storage pool '%s'" %
- name)
-
- self.backend.delete_storagepool()
-
- def update(self, name, params):
- if name not in self.get_list():
- raise NotFoundError("Storage pool '%s' not found.")
-
- autostart = params['autostart']
- if autostart not in [True, False]:
- raise InvalidOperation("Autostart flag must be true or false")
-
- self.backend.autostart_storagepool(name, autostart)
-
- return name
diff --git a/src/kimchi/model_/storagevolumes.py b/src/kimchi/model_/storagevolumes.py
deleted file mode 100644
index 9f3c93b..0000000
--- a/src/kimchi/model_/storagevolumes.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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 InvalidOperation, InvalidParameter, NotFoundError
-from kimchi.model import storagepools
-
-class StorageVolumes(object):
- def __init__(self, backend):
- self.backend = backend
-
- def create(self, pool, params):
- if name in self.get_list(pool):
- raise InvalidParameter("Storage volume '%s' already exists.")
-
- self.backend.create_storagevolume(pool, params)
- return name
-
- def get_list(self, pool):
- info = self.backend.get_storagepool_by_name(pool)
- if info['state'] != 'active':
- raise InvalidOperation("Unable to list volumes in inactive "
- "storagepool %s" % pool)
-
- return self.backend.get_storagevolumes_by_pool(pool)
-
-class StorageVolume(StorageVolumes):
- def __init__(self, backend):
- self.backend = backend
-
- def _storagevolume_exist(self, pool, name):
- if name not in self.get_list(pool):
- raise NotFoundError("Storage volume '%' not found in '%' pool" %
- (name, pool))
- return True
-
- def lookup(self, pool, name):
- if self._storagevolume_exist(pool, name):
- return self.backend.get_storagevolume(pool, name)
-
- def resize(self, pool, name, size):
- if self._storagevolume_exist(pool, name):
- self.backend.resize_storagevolume(pool, name, size)
-
- def wipe(self, pool, name):
- if self._storagevolume_exist(pool, name):
- self.backend.wipe_storagevolume(pool, name)
-
- def delete(self, pool, name):
- if self._storagevolume_exist(pool, name):
- self.backend.delete_storagevolume(pool, name)
-
-class IsoVolumes(StorageVolumes):
- def __init__(self, backend):
- self.backend = backend
- self.storagepools = storagepools.StoragePools(self.backend)
-
- def get_list(self, pool):
- iso_volumes = []
-
- for pool in self.storagepools.get_list():
- try:
- volumes = self.get_list(pool)
- except InvalidOperation:
- # Skip inactive pools
- continue
-
- for volume in volumes:
- res = self.lookup(pool, volume)
- if res['format'] == 'iso':
- # prevent iso from different pool having same volume name
- res['name'] = '%s-%s' % (pool, volume)
- iso_volumes.append(res)
-
- return iso_volumes
diff --git a/src/kimchi/model_/tasks.py b/src/kimchi/model_/tasks.py
deleted file mode 100644
index 29eaddf..0000000
--- a/src/kimchi/model_/tasks.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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
-
-ERROR_TASK_NOT_FOUND = "Task id '%s' not found."
-
-class Tasks(object):
- def __init__(self, backend):
- self.objstore = backend.objstore
-
- def get_list(self):
- with self.objstore as session:
- return session.get_list('task')
-
-class Task(object):
- def __init__(self, backend):
- self.objstore = backend.objstore
-
- def lookup(self, ident):
- if ident not in self.get_list():
- raise NotFoundError(ERROR_TASK_NOT_FOUND % ident)
-
- with self.objstore as session:
- return session.get('task', str(ident))
diff --git a/src/kimchi/model_/templates.py b/src/kimchi/model_/templates.py
deleted file mode 100644
index 86cd54f..0000000
--- a/src/kimchi/model_/templates.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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.vmtemplate import VMTemplate
-from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError
-from kimchi.exception import OperationFailed
-
-class Templates(object):
- def __init__(self, backend):
- self.backend = backend
-
- def create(self, params):
- name = params['name']
- if name in self.get_list:
- raise InvalidOperation("Template '%s' already exists." % name)
-
- for net_name in params.get(u'networks', []):
- if net_name not in self.backend.get_networks():
- raise InvalidParameter("Network '%s' not found," % net_name)
-
- try:
- tmpl = VMTemplate(params, self.backend.TEMPLATE_SCAN)
- self.backend.create_template(name, tmpl)
- except Exception, e:
- raise OperationFailed("Unable to create template '%s': %s" %
- (name, e.message))
-
- return name
-
- def get_list(self):
- return sorted(self.backend.get_templates())
-
-class Template(Templates):
- def lookup(self, name):
- if name not in self.get_list():
- raise NotFoundError("Template '%s' not found." % name)
-
- params = self.backend.get_template_by_name(name)
- tmpl = VMTemplate(params, False)
- return tmpl.info
-
- def delete(self, name):
- if name not in self.get_list():
- raise NotFoundError("Template '%s' not found." % name)
-
- self.backend.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
-
- new_storagepool = new_t.get(u'storagepool', '')
- if new_storagepool not in self.backend.get_storagepools():
- raise InvalidParameter("Storage pool '%s' not found." % name)
-
- for net_name in params.get(u'networks', []):
- if net_name not in self.backend.get_networks():
- raise InvalidParameter("Network '%s' not found." % net_name)
-
- self.delete(name)
- try:
- ident = self.create(new_t)
- except:
- ident = self.create(old_t)
-
- return ident
diff --git a/src/kimchi/model_/vms.py b/src/kimchi/model_/vms.py
deleted file mode 100644
index 3f1b6c3..0000000
--- a/src/kimchi/model_/vms.py
+++ /dev/null
@@ -1,164 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013
-#
-# Authors:
-# Adam Litke <agl at linux.vnet.ibm.com>
-# 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 uuid
-
-VM_STATIC_UPDATE_PARAMS = {'name': './name'}
-
-class VMs(object):
- def __init__(self, backend):
- self.backend = backend
-
- def create(self, params):
- vm_uuid = str(uuid.uuid4())
- vm_list = self.get_list()
- name = self._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 '%s' already exists" % name)
-
- vm_overrides = dict()
- override_params = ['storagepool', 'graphics']
- for param in override_params:
- value = params.get(param, None)
- if value is not None:
- vm_overrides[param] = value
-
- t_name = self._uri_to_name('templates', params['template'])
- t_params = self.backend.get_template_by_name(t_name)
- t_params.update(vm_overrides)
- tmpl = VMTemplate(t_params, False)
-
- caps = self.backend.get_capabilities()
- if not caps.qemu_stream and t.info.get('iso_stream', False):
- raise InvalidOperation("Remote ISO image is not supported by this"
- " server.")
-
- pool_name = self._uri_to_name('storagepools', pool_uri)
- self._validate_storagepool(pool_name)
- self._validate_network(tmpl)
- vol_list = tmpl.to_volume_list(vm_uuid)
- for vol_info in vol_list:
- self.backend.create_storagevolume(pool_name, vol_info)
-
- self.backend.create_vm(name, vm_uuid, tmpl, vol_list)
- return name
-
- def _validate_storagepool(self, pool_name):
- try:
- pool_info = self.backend.get_storagepool_by_name(pool_name)
- except Exception:
- raise InvalidParameter("Storage pool '%s' specified by template "
- "does not exist" % pool_name)
-
- if not pool_info['state'] != 'active':
- raise InvalidParameter("Storage pool '%s' specified by template "
- "is not active" % pool_name)
-
- def _validate_network(self, tmpl):
- names = tmpl.info['networks']
- for name in names:
- if name not in self.backend.get_networks():
- raise InvalidParameter("Network '%s' specified by template "
- "does not exist.")
-
- net_info = self.backend.get_network_by_name(name)
- if net_info['state'] != 'active':
- raise InvalidParameter("Network '%s' specified by template is "
- "not active.")
-
- def _uri_to_name(self, collection, uri):
- expr = '/%s/(.*?)/?$' % collection
- m = re.match(expr, uri)
- if not m:
- raise InvalidParameter(uri)
- return m.group(1)
-
- def _get_vm_name(self, 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")
-
- def get_list(self):
- return sorted(self.backend.get_vms())
-
-class VM(VMs):
- def _vm_exists(self, name):
- if name not in self.backend.get_vms():
- raise NotFoundError("VM '%s' not found." % name)
-
- return True
-
- def lookup(self, name):
- if self._vm_exists(name):
- return self.backend.get_vm_by_name(name)
-
- def update(self, name, params):
- if self._vm_exists(name):
- if 'name' in params:
- state = self.get_vm_by_name(name)['state']
- if state == 'running':
- raise InvalidParameter("The VM needs to be shutted off for"
- "renaming.")
-
- if params['name'] in self.get_list():
- raise InvalidParameter("VM name '%s' already exists" %
- params['name'])
-
- self.backend.static_vm_update(name, params)
- self.backend.live_vm_update(name, params)
-
- return params.get('name', None) or name
-
- def delete(self, name):
- if self._vm_exists(name):
- self.backend.delete_vm(name)
-
- def start(self, name):
- if self._vm_exists(name):
- self.backend.start_vm(self, name)
-
- def stop(self, name):
- if self._vm_exists(name):
- self.backend.stop_vm(self, name)
-
- def connect(self, name):
- if self._vm_exists(name):
- self.backend.connect_vm(self, name)
-
-class VMScreenshot(object):
- def __init__(self, backend):
- self.backend = backend
-
- def lookup(self, name):
- vm_info = self.backend.get_vm_by_name(name)
- if vm_info['state'] != 'running':
- raise NotFoundError('No screenshot for stopped vm')
-
- return self.backend.get_screenshot_by_name(vm_info['uuid'])
diff --git a/src/kimchi/root.py b/src/kimchi/root.py
index 3cc6321..a064ccd 100644
--- a/src/kimchi/root.py
+++ b/src/kimchi/root.py
@@ -44,7 +44,7 @@ from kimchi.exception import OperationFailed
class Root(Resource):
- def __init__(self, model, dev_env):
+ def __init__(self, backend, dev_env):
self._handled_error = ['error_page.400', 'error_page.404',
'error_page.405', 'error_page.406',
'error_page.415', 'error_page.500']
@@ -56,17 +56,17 @@ class Root(Resource):
self._cp_config = dict([(key, self.error_development_handler)
for key in self._handled_error])
- Resource.__init__(self, model)
- self.vms = VMs(model)
- self.templates = Templates(model)
- self.storagepools = StoragePools(model)
- self.interfaces = Interfaces(model)
- self.networks = Networks(model)
- self.tasks = Tasks(model)
- self.config = Config(model)
- self.host = Host(model)
- self.debugreports = DebugReports(model)
- self.plugins = Plugins(model)
+ Resource.__init__(self, backend)
+ self.vms = VMs(backend)
+ self.templates = Templates(backend)
+ self.storagepools = StoragePools(backend)
+ self.interfaces = Interfaces(backend)
+ self.networks = Networks(backend)
+ self.tasks = Tasks(backend)
+ self.config = Config(backend)
+ self.host = Host(backend)
+ self.debugreports = DebugReports(backend)
+ self.plugins = Plugins(backend)
self.api_schema = json.load(open(get_api_schema_file()))
def error_production_handler(self, status, message, traceback, version):
diff --git a/src/kimchi/server.py b/src/kimchi/server.py
index b820263..ffa9324 100644
--- a/src/kimchi/server.py
+++ b/src/kimchi/server.py
@@ -30,9 +30,9 @@ import sslcert
from kimchi import auth
from kimchi import config
-from kimchi import model
-from kimchi import mockmodel
from kimchi import vnc
+from kimchi.model.libvirtbackend import LibvirtBackend
+from kimchi.model.mockbackend import MockBackend
from kimchi.root import Root
from kimchi.utils import get_enabled_plugins, import_class
@@ -182,18 +182,18 @@ class Server(object):
if not dev_env:
cherrypy.config.update({'environment': 'production'})
- if hasattr(options, 'model'):
- model_instance = options.model
+ if hasattr(options, 'backend'):
+ backend_instance = options.backend
elif options.test:
- model_instance = mockmodel.get_mock_environment()
+ backend_instance = MockBackend()
else:
- model_instance = model.Model()
+ backend_instance = LibvirtBackend()
- if isinstance(model_instance, model.Model):
+ if isinstance(backend_instance, LibvirtBackend):
vnc_ws_proxy = vnc.new_ws_proxy()
cherrypy.engine.subscribe('exit', vnc_ws_proxy.kill)
- self.app = cherrypy.tree.mount(Root(model_instance, dev_env),
+ self.app = cherrypy.tree.mount(Root(backend_instance, dev_env),
config=self.configObj)
self._load_plugins()
--
1.7.10.4
More information about the Kimchi-devel
mailing list