
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Then kimchi source code can make use of it Also add src/kimchi/rollbackcontext.py in dist list Signed-off-by: Zhou Zheng Sheng <zhshzhou@linux.vnet.ibm.com> Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/Makefile.am | 59 +++++++++++++++++------------------ src/kimchi/rollbackcontext.py | 71 +++++++++++++++++++++++++++++++++++++++++++ tests/test_model.py | 25 +++++++-------- tests/test_rest.py | 3 +- tests/utils.py | 45 --------------------------- 5 files changed, 116 insertions(+), 87 deletions(-) create mode 100644 src/kimchi/rollbackcontext.py diff --git a/src/kimchi/Makefile.am b/src/kimchi/Makefile.am index 47a3bd2..2f05ab7 100644 --- a/src/kimchi/Makefile.am +++ b/src/kimchi/Makefile.am @@ -21,35 +21,36 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA kimchi_PYTHON = \ - asynctask.py \ - auth.py \ - controller.py \ - disks.py \ - distroloader.py \ - exception.py \ - __init__.py \ - isoinfo.py \ - netinfo.py \ - network.py \ - networkxml.py \ - mockmodel.py \ - model.py \ - objectstore.py \ - osinfo.py \ - root.py \ - scan.py \ - screenshot.py \ - server.py \ - sslcert.py \ - template.py \ - vmtemplate.py \ - vnc.py \ - websocket.py \ - websockify.py \ - xmlutils.py \ - utils.py \ - cachebust.py \ - featuretests.py + __init__.py \ + asynctask.py \ + auth.py \ + cachebust.py \ + controller.py \ + disks.py \ + distroloader.py \ + exception.py \ + featuretests.py \ + isoinfo.py \ + mockmodel.py \ + model.py \ + netinfo.py \ + network.py \ + networkxml.py \ + objectstore.py \ + osinfo.py \ + rollbackcontext.py \ + root.py \ + scan.py \ + screenshot.py \ + server.py \ + sslcert.py \ + template.py \ + utils.py \ + vmtemplate.py \ + vnc.py \ + websocket.py \ + websockify.py \ + xmlutils.py nodist_kimchi_PYTHON = config.py diff --git a/src/kimchi/rollbackcontext.py b/src/kimchi/rollbackcontext.py new file mode 100644 index 0000000..2afd114 --- /dev/null +++ b/src/kimchi/rollbackcontext.py @@ -0,0 +1,71 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# Adam Litke <agl@linux.vnet.ibm.com> +# ShaoHe Feng <shaohef@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 sys + + +class RollbackContext(object): + ''' + A context manager for recording and playing rollback. + The first exception will be remembered and re-raised after rollback + + Sample usage: + with RollbackContext() as rollback: + step1() + rollback.prependDefer(lambda: undo step1) + def undoStep2(arg): pass + step2() + rollback.prependDefer(undoStep2, arg) + ''' + def __init__(self, *args): + self._finally = [] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + firstException = exc_value + + for undo, args, kwargs in self._finally: + try: + undo(*args, **kwargs) + except Exception as e: + # keep the earliest exception info + if not firstException: + firstException = e + # keep the original traceback info + traceback = sys.exc_info()[2] + + # re-raise the earliest exception + if firstException is not None: + if type(firstException) is str: + sys.stderr.write(firstException) + else: + raise firstException, None, traceback + + def defer(self, func, *args, **kwargs): + self._finally.append((func, args, kwargs)) + + def prependDefer(self, func, *args, **kwargs): + self._finally.insert(0, (func, args, kwargs)) diff --git a/tests/test_model.py b/tests/test_model.py index e19364f..7ecd8c6 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -38,6 +38,7 @@ import utils from kimchi import netinfo from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import NotFoundError, OperationFailed +from kimchi.rollbackcontext import RollbackContext class ModelTests(unittest.TestCase): @@ -72,7 +73,7 @@ class ModelTests(unittest.TestCase): def test_vm_lifecycle(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: params = {'name': 'test', 'disks': []} inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') @@ -97,7 +98,7 @@ class ModelTests(unittest.TestCase): def test_vm_storage_provisioning(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: params = {'name': 'test', 'disks': [{'size': 1}]} inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') @@ -115,7 +116,7 @@ class ModelTests(unittest.TestCase): def test_storagepool(self): inst = kimchi.model.Model('qemu:///system', self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: path = '/tmp/kimchi-images' name = 'test-pool' if not os.path.exists(path): @@ -168,7 +169,7 @@ class ModelTests(unittest.TestCase): def test_storagevolume(self): inst = kimchi.model.Model('qemu:///system', self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: path = '/tmp/kimchi-images' pool = 'test-pool' vol = 'test-volume.img' @@ -223,7 +224,7 @@ class ModelTests(unittest.TestCase): def test_template_storage_customise(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: path = '/tmp/kimchi-images' pool = 'test-pool' if not os.path.exists(path): @@ -295,7 +296,7 @@ class ModelTests(unittest.TestCase): orig_params = {'name': 'test', 'memory': '1024', 'cpus': '1'} inst.templates_create(orig_params) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: params_1 = {'name': 'kimchi-vm1', 'template': '/templates/test'} params_2 = {'name': 'kimchi-vm2', 'template': '/templates/test'} inst.vms_create(params_1) @@ -326,7 +327,7 @@ class ModelTests(unittest.TestCase): def test_network(self): inst = kimchi.model.Model('qemu:///system', self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: name = 'test-network' networks = inst.networks_get_list() @@ -480,7 +481,7 @@ class ModelTests(unittest.TestCase): def test_delete_running_vm(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: params = {'name': u'test', 'disks': []} inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') @@ -501,7 +502,7 @@ class ModelTests(unittest.TestCase): def test_vm_list_sorted(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: params = {'name': 'test', 'disks': []} inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') @@ -517,7 +518,7 @@ class ModelTests(unittest.TestCase): def test_use_test_host(self): inst = kimchi.model.Model('test:///default', objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: params = {'name': 'test', 'disks': [], 'storagepool': '/storagepools/default-pool', 'domain': 'test', @@ -546,7 +547,7 @@ class ModelTests(unittest.TestCase): inst.debugreport_delete(namePrefix + '*') except NotFoundError: pass - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: report_list = inst.debugreports_get_list() self.assertFalse(reportName in report_list) try: @@ -617,7 +618,7 @@ class ModelTests(unittest.TestCase): @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_deep_scan(self): inst = kimchi.model.Model('qemu:///system', objstore_loc=self.tmp_store) - with utils.RollbackContext() as rollback: + with RollbackContext() as rollback: path = '/tmp/kimchi-images/tmpdir' if not os.path.exists(path): os.makedirs(path) diff --git a/tests/test_rest.py b/tests/test_rest.py index 73946c0..1b7312f 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -33,8 +33,9 @@ from functools import partial import kimchi.mockmodel import kimchi.server from kimchi.asynctask import AsyncTask +from kimchi.rollbackcontext import RollbackContext from utils import fake_user, get_free_port, https_request, patch_auth, request -from utils import RollbackContext, run_server +from utils import run_server test_server = None diff --git a/tests/utils.py b/tests/utils.py index a7596e8..960e0be 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -140,51 +140,6 @@ def https_request(host, port, path, data=None, method='GET', headers=None): return _request(conn, path, data, method, headers) -class RollbackContext(object): - ''' - A context manager for recording and playing rollback. - The first exception will be remembered and re-raised after rollback - - Sample usage: - with RollbackContext() as rollback: - step1() - rollback.prependDefer(lambda: undo step1) - def undoStep2(arg): pass - step2() - rollback.prependDefer(undoStep2, arg) - ''' - def __init__(self, *args): - self._finally = [] - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - firstException = exc_value - - for undo, args, kwargs in self._finally: - try: - undo(*args, **kwargs) - except Exception as e: - # keep the earliest exception info - if not firstException: - firstException = e - # keep the original traceback info - traceback = sys.exc_info()[2] - - # re-raise the earliest exception - if firstException is not None: - if type(firstException) is str: - sys.stderr.write(firstException) - else: - raise firstException, None, traceback - - def defer(self, func, *args, **kwargs): - self._finally.append((func, args, kwargs)) - - def prependDefer(self, func, *args, **kwargs): - self._finally.insert(0, (func, args, kwargs)) - def patch_auth(): """ Override the authenticate function with a simple test against an -- 1.8.4.2