[PATCH V3] [Kimchi] Handle Libvirt host ENOSPC event
by Lucio Correia
When storage pool is out of space and guest requires more,
guest is paused and nothing is told to the user. This patch
uses Libvirt Event handling and Asynchronous Notifications
mechanism to tell web users about what happened.
Signed-off-by: Lucio Correia <luciojhc(a)linux.vnet.ibm.com>
---
i18n.py | 1 +
model/libvirtevents.py | 25 +++++++++++++++++++++++++
model/model.py | 3 +++
3 files changed, 29 insertions(+)
Changes in v3: rebased with master
diff --git a/i18n.py b/i18n.py
index 3a5a9d1..2d8390f 100644
--- a/i18n.py
+++ b/i18n.py
@@ -335,6 +335,7 @@ messages = {
"KCHEVENT0001E": _("Failed to register the default event implementation."),
"KCHEVENT0002E": _("Failed to register timeout event."),
"KCHEVENT0003E": _("Failed to Run the default event implementation."),
+ "KCHEVENT0004W": _("I/O error on guest '%(vm)s': storage pool out of space for %(devAlias)s (%(srcPath)s)."),
# These messages (ending with L) are for user log purposes
"KCHNET0001L": _("Created %(connection)s virtual network '%(name)s'"),
diff --git a/model/libvirtevents.py b/model/libvirtevents.py
index ab5c17d..00e9433 100644
--- a/model/libvirtevents.py
+++ b/model/libvirtevents.py
@@ -22,6 +22,9 @@ import libvirt
import time
from wok.exception import OperationFailed
+from wok.message import WokMessage
+from wok.model.notifications import add_notification
+from wok.utils import wok_log
class LibvirtEvents(object):
@@ -57,3 +60,25 @@ class LibvirtEvents(object):
# Event loop handler used to limit length of waiting for any other event.
def _kimchi_EventTimeout(self, timer, opaque):
time.sleep(1)
+
+ def event_enospc_cb(self, conn, dom, path, dev, action, reason, args):
+ if reason == "enospc":
+ info = {
+ "vm": dom.name(),
+ "srcPath": path,
+ "devAlias": dev,
+ }
+ add_notification("KCHEVENT0004W", info, '/plugins/kimchi')
+ msg = WokMessage("KCHEVENT0004W", info, '/plugins/kimchi')
+ wok_log.warning(msg.get_text())
+
+ def handleEnospc(self, conn):
+ """
+ Register Libvirt IO_ERROR_REASON event to handle host ENOSPC
+ """
+ conn.get().domainEventRegisterAny(
+ None,
+ libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON,
+ self.event_enospc_cb,
+ libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON
+ )
diff --git a/model/model.py b/model/model.py
index ed474d2..327d5a7 100644
--- a/model/model.py
+++ b/model/model.py
@@ -50,6 +50,9 @@ class Model(BaseModel):
'eventsloop': self.events}
models = []
+ # Register for Libvirt's host ENOSPC event and notify UI if it happens
+ self.events.handleEnospc(self.conn)
+
# Import task model from Wok
instances = get_instances('wok.model.tasks')
for instance in instances:
--
1.9.1
8 years, 7 months
[PATCH V2] [Kimchi] Add support to Libvirt Events.
by pvital@linux.vnet.ibm.com
From: Paulo Vital <pvital(a)linux.vnet.ibm.com>
This patch adds support to handle in Kimchi any Libvirt Event, just by adding
a callback to process the event and register the event with the callback.
None event is being registered by this patch and they need be added by developer
by demand in the code. To see how register an event, the unit test
test_libvirtevents.py register two events and then use them in the code.
Signed-off-by: Paulo Vital <pvital(a)linux.vnet.ibm.com>
---
i18n.py | 4 +
model/libvirtevents.py | 59 +++++++++++
model/model.py | 5 +-
tests/test_model_libvirtevents.py | 212 ++++++++++++++++++++++++++++++++++++++
4 files changed, 279 insertions(+), 1 deletion(-)
create mode 100644 model/libvirtevents.py
create mode 100644 tests/test_model_libvirtevents.py
diff --git a/i18n.py b/i18n.py
index db245c0..9d7a597 100644
--- a/i18n.py
+++ b/i18n.py
@@ -331,4 +331,8 @@ messages = {
"KCHLVMS0001E": _("Invalid volume group name parameter: %(name)s."),
+ "KCHEVENT0001E": _("Failed to register the default event implementation."),
+ "KCHEVENT0002E": _("Failed to register timeout event."),
+ "KCHEVENT0003E": _("Failed to Run the default event implementation."),
+
}
diff --git a/model/libvirtevents.py b/model/libvirtevents.py
new file mode 100644
index 0000000..ab5c17d
--- /dev/null
+++ b/model/libvirtevents.py
@@ -0,0 +1,59 @@
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2016
+#
+# 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 time
+
+from wok.exception import OperationFailed
+
+
+class LibvirtEvents(object):
+ def __init__(self):
+ # Register default implementation of event handlers
+ if libvirt.virEventRegisterDefaultImpl() < 0:
+ raise OperationFailed('KCHEVENT0001E')
+
+ # Run a background thread with the event loop. Using cherrypy
+ # BackgroundTask class due to issues when using threading module with
+ # cherrypy.
+ self.event_loop_thread = cherrypy.process.plugins.BackgroundTask(
+ 2,
+ self._event_loop_run
+ )
+ self.event_loop_thread.setName('KimchiLibvirtEventLoop')
+ self.event_loop_thread.setDaemon(True)
+ self.event_loop_thread.start()
+
+ # Set an event timeout to control the self._event_loop_run
+ if libvirt.virEventAddTimeout(0, self._kimchi_EventTimeout, None) < 0:
+ raise OperationFailed('KCHEVENT0002E')
+
+ # Event loop method to be executed in background as thread
+ def _event_loop_run(self):
+ while True:
+ if libvirt.virEventRunDefaultImpl() < 0:
+ raise OperationFailed('KCHEVENT0003E')
+
+ def is_event_loop_alive(self):
+ return self.event_loop_thread.isAlive()
+
+ # Event loop handler used to limit length of waiting for any other event.
+ def _kimchi_EventTimeout(self, timer, opaque):
+ time.sleep(1)
diff --git a/model/model.py b/model/model.py
index e44f804..ed474d2 100644
--- a/model/model.py
+++ b/model/model.py
@@ -26,6 +26,7 @@ from wok.plugins.kimchi import config
from wok.utils import import_module, listPathModules
from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection
+from wok.plugins.kimchi.model.libvirtevents import LibvirtEvents
class Model(BaseModel):
@@ -43,8 +44,10 @@ class Model(BaseModel):
return instances
self.objstore = ObjectStore(objstore_loc or config.get_object_store())
+ self.events = LibvirtEvents()
self.conn = LibvirtConnection(libvirt_uri)
- kargs = {'objstore': self.objstore, 'conn': self.conn}
+ kargs = {'objstore': self.objstore, 'conn': self.conn,
+ 'eventsloop': self.events}
models = []
# Import task model from Wok
diff --git a/tests/test_model_libvirtevents.py b/tests/test_model_libvirtevents.py
new file mode 100644
index 0000000..420e63a
--- /dev/null
+++ b/tests/test_model_libvirtevents.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2016
+#
+# 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 json
+import libvirt
+import os
+import shutil
+import tempfile
+import time
+import unittest
+
+import tests.utils as utils
+
+from wok.basemodel import Singleton
+from wok.rollbackcontext import RollbackContext
+
+from wok.plugins.kimchi.model import model
+
+import iso_gen
+
+
+TMP_DIR = '/var/lib/kimchi/tests/'
+UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
+TMP_EVENT = None
+EVENT_ID = 0
+
+
+def setUpModule():
+ global TMP_DIR, TMP_EVENT
+
+ if not os.path.exists(TMP_DIR):
+ os.makedirs(TMP_DIR)
+
+ TMP_EVENT = temp.mktemp()
+
+ iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
+
+ # Some FeatureTests functions depend on server to validate their result.
+ # As CapabilitiesModel is a Singleton class it will get the first result
+ # from FeatureTests which may be wrong when using the Model instance
+ # directly - the case of this test_model.py
+ # So clean Singleton instances to make sure to get the right result when
+ # running the following tests.
+ Singleton._instances = {}
+
+
+def tearDownModule():
+ global TMP_DIR, TMP_EVENT
+
+ os.unlink(TMP_EVENT)
+ shutil.rmtree(TMP_DIR)
+
+
+def _get_next_event_id():
+ global EVENT_ID
+ EVENT_ID += 1
+ return EVENT_ID
+
+
+def _get_event_id():
+ global EVENT_ID
+ return EVENT_ID
+
+
+def _store_event(data):
+ global TMP_EVENT
+ with open(TMP_EVENT, "a") as file:
+ file.write("%s\n" % data)
+
+
+def _get_event(id):
+ global TMP_EVENT
+ with open(TMP_EVENT, "r") as file:
+ for event in [line.rstrip('\n') for line in file.readlines()]:
+ fields = event.split('|')
+ if fields[0] == id:
+ return fields[1]
+
+
+class LibvirtEventsTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp_store = tempfile.mktemp()
+
+ def tearDown(self):
+ os.unlink(self.tmp_store)
+
+ def domain_event_lifecycle_cb(self, conn, dom, event, detail, *args):
+ """
+ Callback to handle Domain (VMs) events - VM Livecycle.
+ """
+ evStrings = ("Defined", "Undefined", "Started", "Suspended", "Resumed",
+ "Stopped", "Shutdown", "PMSuspended", "Crashed")
+ evDetails = (("Added", "Updated"),
+ ("Removed", ),
+ ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup"),
+ ("Paused", "Migrated", "IOError", "Watchdog", "Restored",
+ "Snapshot", "API error"),
+ ("Unpaused", "Migrated", "Snapshot"),
+ ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved",
+ "Failed", "Snapshot"),
+ ("Finished", ),
+ ("Memory", "Disk"),
+ ("Panicked"))
+
+ data = {'domain': dom.name(), 'event': evStrings[event],
+ 'event_detail': evDetails[event][detail]}
+ _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
+
+ def domain_event_reboot_cb(self, conn, dom, *args):
+ """
+ Callback to handle Domain (VMs) events - VM Reboot.
+ """
+ data = {'domain': dom.name(), 'event': 'Rebooted'}
+ _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
+
+ @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
+ def test_events_vm_lifecycle(self):
+ inst = model.Model(objstore_loc=self.tmp_store)
+ self.objstore = inst.objstore
+
+ # Register the most common Libvirt domain events to be handled.
+ conn = inst.conn.get()
+ event_map = [(libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+ self.domain_event_lifecycle_cb),
+ (libvirt.VIR_DOMAIN_EVENT_ID_REBOOT,
+ self.domain_event_reboot_cb)]
+
+ for event, event_cb in event_map:
+ try:
+ conn.domainEventRegisterAny(None, event, event_cb, None)
+ except libvirt.libvirtError as e:
+ # It's not a big deal if an event could not be registered.
+ print "Could not register event %s. Details: %s" % (event, e)
+
+ # Create a template and VM to test, and start lifecycle tests
+ with RollbackContext() as rollback:
+ template_params = {'name': 'ttest',
+ 'source_media': {'type': 'disk',
+ 'path': UBUNTU_ISO}}
+
+ inst.templates_create(template_params)
+ rollback.prependDefer(inst.template_delete, 'ttest')
+
+ vm_params = {'name': 'kimchi-vm1',
+ 'template': '/plugins/kimchi/templates/ttest'}
+ task = inst.vms_create(vm_params)
+ inst.task_wait(task['id'], 10)
+ task = inst.task_lookup(task['id'])
+ self.assertEquals('finished', task['status'])
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Defined', res['event'])
+ self.assertEquals('Added', res['event_detail'])
+
+ inst.vm_start('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Started', res['event'])
+ self.assertEquals('Booted', res['event_detail'])
+
+ inst.vm_suspend('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Suspended', res['event'])
+ self.assertEquals('Paused', res['event_detail'])
+
+ inst.vm_resume('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Resumed', res['event'])
+ self.assertEquals('Unpaused', res['event_detail'])
+
+ inst.vm_reset('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Rebooted', res['event'])
+
+ inst.vm_poweroff('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Stopped', res['event'])
+ self.assertEquals('Destroyed', res['event_detail'])
+
+ inst.vm_delete('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Undefined', res['event'])
+ self.assertEquals('Removed', res['event_detail'])
--
2.5.5
8 years, 7 months
Re: [Kimchi-devel] [PATCH V2] [Kimchi] Handle Libvirt host ENOSPC event
by Aline Manera
Hi Lucio,
I was not able to apply this patch. Could you rebase and resend?
Thanks,
Aline Manera
On 05/17/2016 11:39 AM, Lucio Correia wrote:
> When storage pool is out of space and guest requires more,
> guest is paused and nothing is told to the user. This patch
> uses Libvirt Event handling and Asynchronous Notifications
> mechanism to tell web users about what happened.
>
> Signed-off-by: Lucio Correia <luciojhc(a)linux.vnet.ibm.com>
> ---
> i18n.py | 1 +
> model/libvirtevents.py | 25 +++++++++++++++++++++++++
> model/model.py | 3 +++
> 3 files changed, 29 insertions(+)
>
> Change in V2:
> - Rebase with latest master
>
> diff --git a/i18n.py b/i18n.py
> index 49b7f1c..c697d52 100644
> --- a/i18n.py
> +++ b/i18n.py
> @@ -337,4 +337,5 @@ messages = {
> "KCHEVENT0001E": _("Failed to register the default event implementation."),
> "KCHEVENT0002E": _("Failed to register timeout event."),
> "KCHEVENT0003E": _("Failed to Run the default event implementation."),
> + "KCHEVENT0004W": _("I/O error on guest '%(vm)s': storage pool out of space for %(devAlias)s (%(srcPath)s)."),
> }
> diff --git a/model/libvirtevents.py b/model/libvirtevents.py
> index ab5c17d..00e9433 100644
> --- a/model/libvirtevents.py
> +++ b/model/libvirtevents.py
> @@ -22,6 +22,9 @@ import libvirt
> import time
>
> from wok.exception import OperationFailed
> +from wok.message import WokMessage
> +from wok.model.notifications import add_notification
> +from wok.utils import wok_log
>
>
> class LibvirtEvents(object):
> @@ -57,3 +60,25 @@ class LibvirtEvents(object):
> # Event loop handler used to limit length of waiting for any other event.
> def _kimchi_EventTimeout(self, timer, opaque):
> time.sleep(1)
> +
> + def event_enospc_cb(self, conn, dom, path, dev, action, reason, args):
> + if reason == "enospc":
> + info = {
> + "vm": dom.name(),
> + "srcPath": path,
> + "devAlias": dev,
> + }
> + add_notification("KCHEVENT0004W", info, '/plugins/kimchi')
> + msg = WokMessage("KCHEVENT0004W", info, '/plugins/kimchi')
> + wok_log.warning(msg.get_text())
> +
> + def handleEnospc(self, conn):
> + """
> + Register Libvirt IO_ERROR_REASON event to handle host ENOSPC
> + """
> + conn.get().domainEventRegisterAny(
> + None,
> + libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON,
> + self.event_enospc_cb,
> + libvirt.VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON
> + )
> diff --git a/model/model.py b/model/model.py
> index ed474d2..327d5a7 100644
> --- a/model/model.py
> +++ b/model/model.py
> @@ -50,6 +50,9 @@ class Model(BaseModel):
> 'eventsloop': self.events}
> models = []
>
> + # Register for Libvirt's host ENOSPC event and notify UI if it happens
> + self.events.handleEnospc(self.conn)
> +
> # Import task model from Wok
> instances = get_instances('wok.model.tasks')
> for instance in instances:
8 years, 7 months
Re: [Kimchi-devel] [PATCH V2] [Kimchi] Add support to Libvirt Events.
by Paulo Ricardo Paz Vital
On May 23 03:46PM, Aline Manera wrote:
>
> Hi Paulo,
>
> Should I apply this patch with "[PATCH V2] [Kimchi] Isolate unit tests
> execution."?
>
Yes
> If so, could you rebase and resend? I was not able to apply it on top of
> master branch.
>
Rebased and V4 patch submitted.
V4 also needs "[PATCH V2] [Kimchi] Isolate unit tests execution." to be applied
before.
>
> Thanks,
>
> Aline Manera
>
>
> On 05/12/2016 04:07 PM, pvital(a)linux.vnet.ibm.com wrote:
> > From: Paulo Vital <pvital(a)linux.vnet.ibm.com>
> >
> > This patch adds support to handle in Kimchi any Libvirt Event, just by adding
> > a callback to process the event and register the event with the callback.
> >
> > None event is being registered by this patch and they need be added by developer
> > by demand in the code. To see how register an event, the unit test
> > test_libvirtevents.py register two events and then use them in the code.
> >
> > Signed-off-by: Paulo Vital <pvital(a)linux.vnet.ibm.com>
> > ---
> > i18n.py | 4 +
> > model/libvirtevents.py | 59 +++++++++++
> > model/model.py | 5 +-
> > tests/test_model_libvirtevents.py | 212 ++++++++++++++++++++++++++++++++++++++
> > 4 files changed, 279 insertions(+), 1 deletion(-)
> > create mode 100644 model/libvirtevents.py
> > create mode 100644 tests/test_model_libvirtevents.py
> >
> > diff --git a/i18n.py b/i18n.py
> > index db245c0..9d7a597 100644
> > --- a/i18n.py
> > +++ b/i18n.py
> > @@ -331,4 +331,8 @@ messages = {
> >
> > "KCHLVMS0001E": _("Invalid volume group name parameter: %(name)s."),
> >
> > + "KCHEVENT0001E": _("Failed to register the default event implementation."),
> > + "KCHEVENT0002E": _("Failed to register timeout event."),
> > + "KCHEVENT0003E": _("Failed to Run the default event implementation."),
> > +
> > }
> > diff --git a/model/libvirtevents.py b/model/libvirtevents.py
> > new file mode 100644
> > index 0000000..ab5c17d
> > --- /dev/null
> > +++ b/model/libvirtevents.py
> > @@ -0,0 +1,59 @@
> > +#
> > +# Project Kimchi
> > +#
> > +# Copyright IBM Corp, 2016
> > +#
> > +# 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 time
> > +
> > +from wok.exception import OperationFailed
> > +
> > +
> > +class LibvirtEvents(object):
> > + def __init__(self):
> > + # Register default implementation of event handlers
> > + if libvirt.virEventRegisterDefaultImpl() < 0:
> > + raise OperationFailed('KCHEVENT0001E')
> > +
> > + # Run a background thread with the event loop. Using cherrypy
> > + # BackgroundTask class due to issues when using threading module with
> > + # cherrypy.
> > + self.event_loop_thread = cherrypy.process.plugins.BackgroundTask(
> > + 2,
> > + self._event_loop_run
> > + )
> > + self.event_loop_thread.setName('KimchiLibvirtEventLoop')
> > + self.event_loop_thread.setDaemon(True)
> > + self.event_loop_thread.start()
> > +
> > + # Set an event timeout to control the self._event_loop_run
> > + if libvirt.virEventAddTimeout(0, self._kimchi_EventTimeout, None) < 0:
> > + raise OperationFailed('KCHEVENT0002E')
> > +
> > + # Event loop method to be executed in background as thread
> > + def _event_loop_run(self):
> > + while True:
> > + if libvirt.virEventRunDefaultImpl() < 0:
> > + raise OperationFailed('KCHEVENT0003E')
> > +
> > + def is_event_loop_alive(self):
> > + return self.event_loop_thread.isAlive()
> > +
> > + # Event loop handler used to limit length of waiting for any other event.
> > + def _kimchi_EventTimeout(self, timer, opaque):
> > + time.sleep(1)
> > diff --git a/model/model.py b/model/model.py
> > index e44f804..ed474d2 100644
> > --- a/model/model.py
> > +++ b/model/model.py
> > @@ -26,6 +26,7 @@ from wok.plugins.kimchi import config
> > from wok.utils import import_module, listPathModules
> >
> > from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection
> > +from wok.plugins.kimchi.model.libvirtevents import LibvirtEvents
> >
> >
> > class Model(BaseModel):
> > @@ -43,8 +44,10 @@ class Model(BaseModel):
> > return instances
> >
> > self.objstore = ObjectStore(objstore_loc or config.get_object_store())
> > + self.events = LibvirtEvents()
> > self.conn = LibvirtConnection(libvirt_uri)
> > - kargs = {'objstore': self.objstore, 'conn': self.conn}
> > + kargs = {'objstore': self.objstore, 'conn': self.conn,
> > + 'eventsloop': self.events}
> > models = []
> >
> > # Import task model from Wok
> > diff --git a/tests/test_model_libvirtevents.py b/tests/test_model_libvirtevents.py
> > new file mode 100644
> > index 0000000..420e63a
> > --- /dev/null
> > +++ b/tests/test_model_libvirtevents.py
> > @@ -0,0 +1,212 @@
> > +# -*- coding: utf-8 -*-
> > +#
> > +# Project Kimchi
> > +#
> > +# Copyright IBM Corp, 2016
> > +#
> > +# 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 json
> > +import libvirt
> > +import os
> > +import shutil
> > +import tempfile
> > +import time
> > +import unittest
> > +
> > +import tests.utils as utils
> > +
> > +from wok.basemodel import Singleton
> > +from wok.rollbackcontext import RollbackContext
> > +
> > +from wok.plugins.kimchi.model import model
> > +
> > +import iso_gen
> > +
> > +
> > +TMP_DIR = '/var/lib/kimchi/tests/'
> > +UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
> > +TMP_EVENT = None
> > +EVENT_ID = 0
> > +
> > +
> > +def setUpModule():
> > + global TMP_DIR, TMP_EVENT
> > +
> > + if not os.path.exists(TMP_DIR):
> > + os.makedirs(TMP_DIR)
> > +
> > + TMP_EVENT = temp.mktemp()
> > +
> > + iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
> > +
> > + # Some FeatureTests functions depend on server to validate their result.
> > + # As CapabilitiesModel is a Singleton class it will get the first result
> > + # from FeatureTests which may be wrong when using the Model instance
> > + # directly - the case of this test_model.py
> > + # So clean Singleton instances to make sure to get the right result when
> > + # running the following tests.
> > + Singleton._instances = {}
> > +
> > +
> > +def tearDownModule():
> > + global TMP_DIR, TMP_EVENT
> > +
> > + os.unlink(TMP_EVENT)
> > + shutil.rmtree(TMP_DIR)
> > +
> > +
> > +def _get_next_event_id():
> > + global EVENT_ID
> > + EVENT_ID += 1
> > + return EVENT_ID
> > +
> > +
> > +def _get_event_id():
> > + global EVENT_ID
> > + return EVENT_ID
> > +
> > +
> > +def _store_event(data):
> > + global TMP_EVENT
> > + with open(TMP_EVENT, "a") as file:
> > + file.write("%s\n" % data)
> > +
> > +
> > +def _get_event(id):
> > + global TMP_EVENT
> > + with open(TMP_EVENT, "r") as file:
> > + for event in [line.rstrip('\n') for line in file.readlines()]:
> > + fields = event.split('|')
> > + if fields[0] == id:
> > + return fields[1]
> > +
> > +
> > +class LibvirtEventsTests(unittest.TestCase):
> > + def setUp(self):
> > + self.tmp_store = tempfile.mktemp()
> > +
> > + def tearDown(self):
> > + os.unlink(self.tmp_store)
> > +
> > + def domain_event_lifecycle_cb(self, conn, dom, event, detail, *args):
> > + """
> > + Callback to handle Domain (VMs) events - VM Livecycle.
> > + """
> > + evStrings = ("Defined", "Undefined", "Started", "Suspended", "Resumed",
> > + "Stopped", "Shutdown", "PMSuspended", "Crashed")
> > + evDetails = (("Added", "Updated"),
> > + ("Removed", ),
> > + ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup"),
> > + ("Paused", "Migrated", "IOError", "Watchdog", "Restored",
> > + "Snapshot", "API error"),
> > + ("Unpaused", "Migrated", "Snapshot"),
> > + ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved",
> > + "Failed", "Snapshot"),
> > + ("Finished", ),
> > + ("Memory", "Disk"),
> > + ("Panicked"))
> > +
> > + data = {'domain': dom.name(), 'event': evStrings[event],
> > + 'event_detail': evDetails[event][detail]}
> > + _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
> > +
> > + def domain_event_reboot_cb(self, conn, dom, *args):
> > + """
> > + Callback to handle Domain (VMs) events - VM Reboot.
> > + """
> > + data = {'domain': dom.name(), 'event': 'Rebooted'}
> > + _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
> > +
> > + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
> > + def test_events_vm_lifecycle(self):
> > + inst = model.Model(objstore_loc=self.tmp_store)
> > + self.objstore = inst.objstore
> > +
> > + # Register the most common Libvirt domain events to be handled.
> > + conn = inst.conn.get()
> > + event_map = [(libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
> > + self.domain_event_lifecycle_cb),
> > + (libvirt.VIR_DOMAIN_EVENT_ID_REBOOT,
> > + self.domain_event_reboot_cb)]
> > +
> > + for event, event_cb in event_map:
> > + try:
> > + conn.domainEventRegisterAny(None, event, event_cb, None)
> > + except libvirt.libvirtError as e:
> > + # It's not a big deal if an event could not be registered.
> > + print "Could not register event %s. Details: %s" % (event, e)
> > +
> > + # Create a template and VM to test, and start lifecycle tests
> > + with RollbackContext() as rollback:
> > + template_params = {'name': 'ttest',
> > + 'source_media': {'type': 'disk',
> > + 'path': UBUNTU_ISO}}
> > +
> > + inst.templates_create(template_params)
> > + rollback.prependDefer(inst.template_delete, 'ttest')
> > +
> > + vm_params = {'name': 'kimchi-vm1',
> > + 'template': '/plugins/kimchi/templates/ttest'}
> > + task = inst.vms_create(vm_params)
> > + inst.task_wait(task['id'], 10)
> > + task = inst.task_lookup(task['id'])
> > + self.assertEquals('finished', task['status'])
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Defined', res['event'])
> > + self.assertEquals('Added', res['event_detail'])
> > +
> > + inst.vm_start('kimchi-vm1')
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Started', res['event'])
> > + self.assertEquals('Booted', res['event_detail'])
> > +
> > + inst.vm_suspend('kimchi-vm1')
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Suspended', res['event'])
> > + self.assertEquals('Paused', res['event_detail'])
> > +
> > + inst.vm_resume('kimchi-vm1')
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Resumed', res['event'])
> > + self.assertEquals('Unpaused', res['event_detail'])
> > +
> > + inst.vm_reset('kimchi-vm1')
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Rebooted', res['event'])
> > +
> > + inst.vm_poweroff('kimchi-vm1')
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Stopped', res['event'])
> > + self.assertEquals('Destroyed', res['event_detail'])
> > +
> > + inst.vm_delete('kimchi-vm1')
> > + time.sleep(5)
> > + res = json.loads(_get_event(str(_get_event_id())))
> > + self.assertEquals('kimchi-vm1', res['domain'])
> > + self.assertEquals('Undefined', res['event'])
> > + self.assertEquals('Removed', res['event_detail'])
> > --
> > 2.5.5
>
--
Paulo Ricardo Paz Vital
Linux Technology Center, IBM Systems
http://www.ibm.com/linux/ltc/
8 years, 7 months
[PATCH V4] [Kimchi] Add support to Libvirt Events.
by pvital@linux.vnet.ibm.com
From: Paulo Vital <pvital(a)linux.vnet.ibm.com>
This patch adds support to handle in Kimchi any Libvirt Event, just by adding
a callback to process the event and register the event with the callback.
None event is being registered by this patch and they need be added by developer
by demand in the code. To see how register an event, the unit test
test_libvirtevents.py register two events and then use them in the code.
Signed-off-by: Paulo Vital <pvital(a)linux.vnet.ibm.com>
---
i18n.py | 4 +
model/libvirtevents.py | 59 ++++++++++
model/model.py | 5 +-
tests/test_model_libvirtevents.py | 219 ++++++++++++++++++++++++++++++++++++++
4 files changed, 286 insertions(+), 1 deletion(-)
create mode 100644 model/libvirtevents.py
create mode 100644 tests/test_model_libvirtevents.py
diff --git a/i18n.py b/i18n.py
index c789b7f..3a5a9d1 100644
--- a/i18n.py
+++ b/i18n.py
@@ -332,6 +332,10 @@ messages = {
"KCHCONN0001E": _("Unable to establish connection with libvirt. Please check your libvirt URI which is often defined in /etc/libvirt/libvirt.conf"),
"KCHCONN0002E": _("Libvirt service is not active. Please start the libvirt service in your host system."),
+ "KCHEVENT0001E": _("Failed to register the default event implementation."),
+ "KCHEVENT0002E": _("Failed to register timeout event."),
+ "KCHEVENT0003E": _("Failed to Run the default event implementation."),
+
# These messages (ending with L) are for user log purposes
"KCHNET0001L": _("Created %(connection)s virtual network '%(name)s'"),
"KCHNET0002L": _("Removed virtual network '%(ident)s'"),
diff --git a/model/libvirtevents.py b/model/libvirtevents.py
new file mode 100644
index 0000000..ab5c17d
--- /dev/null
+++ b/model/libvirtevents.py
@@ -0,0 +1,59 @@
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2016
+#
+# 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 time
+
+from wok.exception import OperationFailed
+
+
+class LibvirtEvents(object):
+ def __init__(self):
+ # Register default implementation of event handlers
+ if libvirt.virEventRegisterDefaultImpl() < 0:
+ raise OperationFailed('KCHEVENT0001E')
+
+ # Run a background thread with the event loop. Using cherrypy
+ # BackgroundTask class due to issues when using threading module with
+ # cherrypy.
+ self.event_loop_thread = cherrypy.process.plugins.BackgroundTask(
+ 2,
+ self._event_loop_run
+ )
+ self.event_loop_thread.setName('KimchiLibvirtEventLoop')
+ self.event_loop_thread.setDaemon(True)
+ self.event_loop_thread.start()
+
+ # Set an event timeout to control the self._event_loop_run
+ if libvirt.virEventAddTimeout(0, self._kimchi_EventTimeout, None) < 0:
+ raise OperationFailed('KCHEVENT0002E')
+
+ # Event loop method to be executed in background as thread
+ def _event_loop_run(self):
+ while True:
+ if libvirt.virEventRunDefaultImpl() < 0:
+ raise OperationFailed('KCHEVENT0003E')
+
+ def is_event_loop_alive(self):
+ return self.event_loop_thread.isAlive()
+
+ # Event loop handler used to limit length of waiting for any other event.
+ def _kimchi_EventTimeout(self, timer, opaque):
+ time.sleep(1)
diff --git a/model/model.py b/model/model.py
index e44f804..ed474d2 100644
--- a/model/model.py
+++ b/model/model.py
@@ -26,6 +26,7 @@ from wok.plugins.kimchi import config
from wok.utils import import_module, listPathModules
from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection
+from wok.plugins.kimchi.model.libvirtevents import LibvirtEvents
class Model(BaseModel):
@@ -43,8 +44,10 @@ class Model(BaseModel):
return instances
self.objstore = ObjectStore(objstore_loc or config.get_object_store())
+ self.events = LibvirtEvents()
self.conn = LibvirtConnection(libvirt_uri)
- kargs = {'objstore': self.objstore, 'conn': self.conn}
+ kargs = {'objstore': self.objstore, 'conn': self.conn,
+ 'eventsloop': self.events}
models = []
# Import task model from Wok
diff --git a/tests/test_model_libvirtevents.py b/tests/test_model_libvirtevents.py
new file mode 100644
index 0000000..d7dc675
--- /dev/null
+++ b/tests/test_model_libvirtevents.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2016
+#
+# 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 json
+import libvirt
+import os
+import shutil
+import tempfile
+import time
+import unittest
+
+import tests.utils as utils
+
+from wok.basemodel import Singleton
+from wok.rollbackcontext import RollbackContext
+
+from wok.plugins.kimchi.model import model
+
+import iso_gen
+
+
+TMP_DIR = '/var/lib/kimchi/tests/'
+UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
+TMP_EVENT = None
+EVENT_ID = 0
+
+
+def setUpModule():
+ global TMP_DIR, TMP_EVENT
+
+ if not os.path.exists(TMP_DIR):
+ os.makedirs(TMP_DIR)
+
+ TMP_EVENT = tempfile.mktemp()
+
+ iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
+
+ # Some FeatureTests functions depend on server to validate their result.
+ # As CapabilitiesModel is a Singleton class it will get the first result
+ # from FeatureTests which may be wrong when using the Model instance
+ # directly - the case of this test_model.py
+ # So clean Singleton instances to make sure to get the right result when
+ # running the following tests.
+ Singleton._instances = {}
+
+
+def tearDownModule():
+ global TMP_DIR, TMP_EVENT
+
+ os.unlink(TMP_EVENT)
+ shutil.rmtree(TMP_DIR)
+
+
+def _get_next_event_id():
+ global EVENT_ID
+ EVENT_ID += 1
+ return EVENT_ID
+
+
+def _get_event_id():
+ global EVENT_ID
+ return EVENT_ID
+
+
+def _store_event(data):
+ global TMP_EVENT
+ with open(TMP_EVENT, "a") as file:
+ file.write("%s\n" % data)
+
+
+def _get_event(id):
+ global TMP_EVENT
+ with open(TMP_EVENT, "r") as file:
+ for event in [line.rstrip('\n') for line in file.readlines()]:
+ fields = event.split('|')
+ if fields[0] == id:
+ return fields[1]
+
+
+class LibvirtEventsTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp_store = tempfile.mktemp()
+
+ def tearDown(self):
+ os.unlink(self.tmp_store)
+
+ def domain_event_lifecycle_cb(self, conn, dom, event, detail, *args):
+ """
+ Callback to handle Domain (VMs) events - VM Livecycle.
+ """
+ evStrings = ("Defined", "Undefined", "Started", "Suspended", "Resumed",
+ "Stopped", "Shutdown", "PMSuspended", "Crashed")
+ evDetails = (("Added", "Updated"),
+ ("Removed", ),
+ ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup"),
+ ("Paused", "Migrated", "IOError", "Watchdog", "Restored",
+ "Snapshot", "API error"),
+ ("Unpaused", "Migrated", "Snapshot"),
+ ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved",
+ "Failed", "Snapshot"),
+ ("Finished", ),
+ ("Memory", "Disk"),
+ ("Panicked"))
+
+ data = {'domain': dom.name(), 'event': evStrings[event],
+ 'event_detail': evDetails[event][detail]}
+ _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
+
+ def domain_event_reboot_cb(self, conn, dom, *args):
+ """
+ Callback to handle Domain (VMs) events - VM Reboot.
+ """
+ data = {'domain': dom.name(), 'event': 'Rebooted'}
+ _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
+
+ @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
+ def test_events_vm_lifecycle(self):
+ inst = model.Model(objstore_loc=self.tmp_store)
+ self.objstore = inst.objstore
+ conn = inst.conn.get()
+
+ # Create a template and VM to test, and start lifecycle tests
+ with RollbackContext() as rollback:
+ # Register the most common Libvirt domain events to be handled.
+ event_map = [(libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+ self.domain_event_lifecycle_cb),
+ (libvirt.VIR_DOMAIN_EVENT_ID_REBOOT,
+ self.domain_event_reboot_cb)]
+
+ for event, event_cb in event_map:
+ ev_id = conn.domainEventRegisterAny(None, event, event_cb,
+ None)
+ rollback.prependDefer(conn.domainEventDeregisterAny, ev_id)
+
+ # Create a template
+ template_params = {'name': 'ttest',
+ 'source_media': {'type': 'disk',
+ 'path': UBUNTU_ISO}}
+
+ inst.templates_create(template_params)
+ rollback.prependDefer(inst.template_delete, 'ttest')
+
+ # Create a VM (guest)
+ vm_params = {'name': 'kimchi-vm1',
+ 'template': '/plugins/kimchi/templates/ttest'}
+ task = inst.vms_create(vm_params)
+ inst.task_wait(task['id'], 10)
+ task = inst.task_lookup(task['id'])
+ self.assertEquals('finished', task['status'])
+ time.sleep(5)
+ # Check event of domain definition (addition)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Defined', res['event'])
+ self.assertEquals('Added', res['event_detail'])
+
+ # Start the VM and check the event
+ inst.vm_start('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Started', res['event'])
+ self.assertEquals('Booted', res['event_detail'])
+
+ # Suspend the VM and check the event
+ inst.vm_suspend('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Suspended', res['event'])
+ self.assertEquals('Paused', res['event_detail'])
+
+ # Resume the VM and check the event
+ inst.vm_resume('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Resumed', res['event'])
+ self.assertEquals('Unpaused', res['event_detail'])
+
+ # Reboot the VM and check the event
+ inst.vm_reset('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Rebooted', res['event'])
+
+ # PowerOff (hard stop) the VM and check the event
+ inst.vm_poweroff('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Stopped', res['event'])
+ self.assertEquals('Destroyed', res['event_detail'])
+
+ # Delete the VM and check the event
+ inst.vm_delete('kimchi-vm1')
+ time.sleep(5)
+ res = json.loads(_get_event(str(_get_event_id())))
+ self.assertEquals('kimchi-vm1', res['domain'])
+ self.assertEquals('Undefined', res['event'])
+ self.assertEquals('Removed', res['event_detail'])
--
2.5.5
8 years, 7 months
[PATCH V2] [Kimchi] Isolate unit tests execution.
by pvital@linux.vnet.ibm.com
From: Paulo Vital <pvital(a)linux.vnet.ibm.com>
This patch isolates each unit test execution reducing the occurrences of tests
using data from previous unit tests and making results not reliable.
In addition, it also simplifies the output of tests execution, printing the tags
PASSED and FAILED and the error/failed banners only when necessary.
Signed-off-by: Paulo Vital <pvital(a)linux.vnet.ibm.com>
---
tests/run_tests.sh.in | 52 +++++++++++++++++++++++++++++++++++++++------------
1 file changed, 40 insertions(+), 12 deletions(-)
diff --git a/tests/run_tests.sh.in b/tests/run_tests.sh.in
index 68dfa2e..a43a90a 100644
--- a/tests/run_tests.sh.in
+++ b/tests/run_tests.sh.in
@@ -21,6 +21,10 @@
HAVE_UNITTEST=@HAVE_PYMOD_UNITTEST@
PYTHON_VER=@PYTHON_VERSION@
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+NC='\033[0m' # No Color
+
if [ "$1" = "-v" ]; then
OPTS="-v"
shift
@@ -40,19 +44,43 @@ else
CMD="python -m unittest"
fi
-LIST=($ARGS)
-MODEL_LIST=()
-MOCK_LIST=()
-for ((i=0;i<${#LIST[@]};i++)); do
-
- if [[ ${LIST[$i]} == test_model* ]]; then
- MODEL_LIST+=(${LIST[$i]})
+NUM_TESTS=0
+TIME=0
+declare -A FAILED_UT # dict to store failed unit tests and its OUTPUT_FILE
+OUTPUT_FILE=$(mktemp)
+for UT in $ARGS; do
+ echo -n "***** Running unit test: $UT... "
+ # ../../../../../ refers to the project root
+ # ../../../../ refers to wok directory
+ # ../../../ refers to plugins directory
+ OUTPUT=$(PYTHONPATH=../../../../../:../../../../:../../../ \
+ PYTHONWARNINGS="ignore:Unverified HTTPS request" \
+ $CMD $OPTS $UT 2>&1)
+ RES=$?
+ if [ $RES -ne 0 ]; then
+ # unit test has failed, so keep the OUTPUT_FILE and print the results
+ echo -e "\t ${RED}FAILED${NC}"
+ ERROR_LOG_BEGIN=$(echo "$OUTPUT" | grep -n "^\==*" | head -n1 | cut -d":" -f1)
+ ERROR_LOG_END=$(echo "$OUTPUT" | wc -l)
+ echo "$OUTPUT" | tail -n $((${ERROR_LOG_END}-${ERROR_LOG_BEGIN}+1))
+ STATS=$(echo "$OUTPUT" | grep "Ran")
+ FAILED_UT+=([$UT]=$(echo "$OUTPUT" | grep FAILED | cut -d" " -f2-4))
+ echo "$OUTPUT" >> $OUTPUT_FILE
else
- MOCK_LIST+=(${LIST[$i]})
+ # unit test has passed, so print the results and delete the OUTPUT_FILE
+ STATS=$(echo "$OUTPUT" | grep "Ran")
+ echo -e "\t ${GREEN}PASSED${NC} - $STATS"
fi
+ TES=$(echo $STATS | cut -d" " -f2)
+ NUM_TESTS=$(echo "$NUM_TESTS + $TES" | bc)
+ TES=$(echo $STATS | cut -d" " -f5)
+ TIME=$(echo "$TIME + ${TES:0:-1}" | bc)
done
-# ../../../../../ refers to the project root
-# ../../../../ refers to wok directory
-# ../../../ refers to plugins directory
-PYTHONPATH=../../../../../:../../../../:../../../ PYTHONWARNINGS="ignore:Unverified HTTPS request" $CMD $OPTS ${MODEL_LIST[@]} ${MOCK_LIST[@]}
+# Print summary results
+echo -e "======================================================================"
+echo -e "===================== Kimchi Unit Tests Summary ======================"
+echo -e "Ran $NUM_TESTS tests in $TIME seconds."
+for i in "${!FAILED_UT[@]}"; do
+ echo -e "$i FAILED: ${FAILED_UT[$i]} - full log available at $OUTPUT_FILE"
+done
--
2.5.5
8 years, 7 months
[PATCH] [Kimchi] Storage Volume management
by sguimaraes943@gmail.com
From: Samuel Guimarães <sguimaraes943(a)gmail.com>
This patch adds Storage Volume management functions Wipe, Clone, Resize and Delete with multiple selection. It also includes a filter input for each Storage Pool and Gallery View for Storage Volumes.
Changes from [RFC] version:
v1:
- HTML and CSS
v2:
- Delete and Wipe with multi-selection
- Confirm messages with list of selected volumes when wiping or deleting volumes (requires SCSS/CSS patch sent to Wok)
- Filter working
- Removed "Add Volume" link from Storage Pool action button
- Added "Add Volume" to Volume box action button
v3 (this patch):
- Clone function working with multiple selection
- Progress bar working for clone and create volume
- Temporary volume added to the volumes when cloning
- Seamless refresh on the volumes once each task is finished
- Fixed issue when list wouldn't refresh when all volumes are removed from the storage pool.
Samuel Guimarães (1):
Storage Volume management
model/storagevolumes.py | 2 +-
ui/css/kimchi.css | 287 ++++++++++---
ui/css/src/modules/_storage.scss | 309 +++++++++++---
ui/js/src/kimchi.api.js | 50 +++
ui/js/src/kimchi.storage_main.js | 443 ++++++++++++++++-----
ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +-
ui/js/src/kimchi.storagepool_resize_volume_main.js | 59 +++
ui/pages/i18n.json.tmpl | 6 +
ui/pages/storagepool-resize-volume.html.tmpl | 51 +++
ui/pages/tabs/storage.html.tmpl | 155 +++----
10 files changed, 1107 insertions(+), 257 deletions(-)
create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js
create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl
--
1.9.3
8 years, 7 months
[PATCH] [Wok 2/2] Issue #115: _get_resources wok_log.error result into unicode error
by archus@linux.vnet.ibm.com
From: Archana Singh <archus(a)linux.vnet.ibm.com>
As ident is in encoded value but e.message is in unicode value,
combination of which result into unicode value having encoded ident value.
So when wok_log.error try to encode(encoded value) it result into error.
This patch fix the unicode error by encoding e.message.
Signed-off-by: Archana Singh <archus(a)linux.vnet.ibm.com>
---
src/wok/control/base.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/wok/control/base.py b/src/wok/control/base.py
index be5f618..d4fd4e4 100644
--- a/src/wok/control/base.py
+++ b/src/wok/control/base.py
@@ -373,9 +373,13 @@ class Collection(object):
res.lookup()
except Exception as e:
# In case of errors when fetching a resource info, pass and
- # log the error, so, other resources are returned
+ # log the error, so, other resources are returned.
+ # Encoding error message as ident is also encoded value.
+ # This has to be done to avoid unicode error,
+ # as combination of encoded and unicode value results into
+ # unicode error.
wok_log.error("Problem in lookup of resource '%s'. "
- "Detail: %s" % (ident, e.message))
+ "Detail: %s" % (ident, encode_value(e.message)))
continue
res_list.append(res)
return res_list
--
1.8.3.1
8 years, 7 months