[PATCH V3 0/5] template cloning

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> V2 -> V3 raise HTTPredirect in controller. V1 -> V2 clone will just "duplicate the template" with all default in new template. The user may clone a template from an existing template with different name. Later he can customize some parts of the template to save the effort to create a full new template. For example, he can update the network of the template cloned to have a new different template. ShaoHe Feng (5): clone template: update API.md Enhance generate_action_handler to redirect a new resource clone template: update controller clone template: update model and mockmodel clone template: update test case docs/API.md | 4 +++- src/kimchi/control/base.py | 14 +++++++++----- src/kimchi/control/templates.py | 1 + src/kimchi/mockmodel.py | 16 ++++++++++++++++ src/kimchi/model/templates.py | 16 ++++++++++++++++ tests/test_model.py | 16 ++++++++++++++++ tests/test_rest.py | 15 +++++++++++++++ 7 files changed, 76 insertions(+), 6 deletions(-) -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> The user may clone a template from an existing template with different name. He can update some attributes when he clone a template. And he can also customize some parts of the template to save the effort to create a full new template later. $ curl -u <usre> -H "Content-Type: application/json" \ -H "Accept: application/json" \ http://localhost:80/test-template-4/clone -X POST -d '' Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- docs/API.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 8fafb91..7f5e4d6 100644 --- a/docs/API.md +++ b/docs/API.md @@ -280,7 +280,9 @@ A interface represents available network interface on VM. **Actions (POST):** -* *No actions defined* +* clone: clone a template from an existing template with different name. + It will provide a reasonable default name with "-cloneN" as suffix + for the new clone template. The "N" means the number of clone times. ### Collection: Storage Pools -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> POST method may create a new resource. It is better to raise a new HTTPRedirect to let the client know it. However there's already a HTTPRedirect for PUT method. Make use of these codes of PUT method, and make a new method for HTTPRedirect. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/control/base.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py index 1e233ea..e8f03ae 100644 --- a/src/kimchi/control/base.py +++ b/src/kimchi/control/base.py @@ -58,6 +58,12 @@ class Resource(object): self.model_args = (ident,) self.update_params = [] + def _redirect(self, ident, code=303): + if ident is not None and ident != self.ident: + uri_params = list(self.model_args[:-1]) + uri_params += [urllib2.quote(ident.encode('utf-8'))] + raise cherrypy.HTTPRedirect(self.uri_fmt % tuple(uri_params), code) + def generate_action_handler(self, action_name, action_args=None): def wrapper(*args, **kwargs): validate_method(('POST')) @@ -67,7 +73,8 @@ class Resource(object): model_args.extend(parse_request()[key] for key in action_args) fn = getattr(self.model, model_fn(self, action_name)) - fn(*model_args) + ident = fn(*model_args) + self._redirect(ident) uri_params = tuple(self.model_args) raise internal_redirect(self.uri_fmt % uri_params) except MissingParameter, e: @@ -155,10 +162,7 @@ class Resource(object): args = list(self.model_args) + [params] ident = update(*args) - if ident != self.ident: - uri_params = list(self.model_args[:-1]) - uri_params += [urllib2.quote(ident.encode('utf-8'))] - raise cherrypy.HTTPRedirect(self.uri_fmt % tuple(uri_params), 303) + self._redirect(ident) return self.get() -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Add clone action in controller Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/control/templates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index ce1dda4..58dafcc 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -40,6 +40,7 @@ class Template(Resource): "memory", "cdrom", "disks", "networks", "graphics"] self.uri_fmt = "/templates/%s" + self.clone = self.generate_action_handler('clone') @property def data(self): -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> When clone template, if no name in parameters, kimchi will set a default name. Then copy the info from existing template and create a new one. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 16 ++++++++++++++++ src/kimchi/model/templates.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index b7b5952..0c2da8a 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -231,6 +231,22 @@ class MockModel(object): self._mock_templates[name] = t return name + def template_clone(self, name): + # set default name + subfixs = [v[len(name):] for v in self.templates_get_list() + if v.startswith(name)] + indexs = [int(v.lstrip("-clone")) for v in subfixs + if v.startswith("-clone") and + v.lstrip("-clone").isdigit()] + indexs.sort() + index = "1" if not indexs else str(indexs[-1] + 1) + clone_name = name + "-clone" + index + + temp = self.template_lookup(name) + temp['name'] = clone_name + ident = self.templates_create(temp) + return ident + def template_update(self, name, params): old_t = self.template_lookup(name) new_t = copy.copy(old_t) diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py index 3d05e80..5d09813 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -85,6 +85,22 @@ class TemplateModel(object): t = self.get_template(name, self.objstore, self.conn) return t.info + def clone(self, name): + # set default name + subfixs = [v[len(name):] for v in self.templates.get_list() + if v.startswith(name)] + indexs = [int(v.lstrip("-clone")) for v in subfixs + if v.startswith("-clone") and + v.lstrip("-clone").isdigit()] + indexs.sort() + index = "1" if not indexs else str(indexs[-1] + 1) + clone_name = name + "-clone" + index + + temp = self.lookup(name) + temp['name'] = clone_name + ident = self.templates.create(temp) + return ident + def delete(self, name): with self.objstore as session: session.delete('template', name) -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> update test_rest.py and test_model.py Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- tests/test_model.py | 16 ++++++++++++++++ tests/test_rest.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index bf6f27a..859f7bd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -441,6 +441,22 @@ class ModelTests(unittest.TestCase): self.assertEquals(params[key], info[key]) @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') + def test_template_clone(self): + inst = model.Model('qemu:///system', + objstore_loc=self.tmp_store) + with RollbackContext() as rollback: + orig_params = {'name': 'test-template', 'memory': 1024, 'cpus': 1} + inst.templates_create(orig_params) + orig_temp = inst.template_lookup(orig_params['name']) + + ident = inst.template_clone('test-template') + clone_temp = inst.template_lookup(ident) + + clone_temp['name'] = orig_temp['name'] + for key in clone_temp.keys(): + self.assertEquals(clone_temp[key], orig_temp[key]) + + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_template_update(self): inst = model.Model('qemu:///system', objstore_loc=self.tmp_store) diff --git a/tests/test_rest.py b/tests/test_rest.py index 0e6a253..5aac211 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -949,6 +949,21 @@ class RestTests(unittest.TestCase): res = json.loads(self.request('/templates/test').read()) verify_template(t, res) + # clone a template + resp = self.request('/templates/%s/clone' % t['name'], '{}', 'POST') + self.assertEquals(303, resp.status) + + # Verify the clone template + res = json.loads(self.request('/templates/%s-clone1' % + t['name']).read()) + old_temp = t['name'] + t['name'] = res['name'] + verify_template(t, res) + # Delete the clone template + resp = self.request('/templates/%s' % t['name'], '{}', 'DELETE') + self.assertEquals(204, resp.status) + t['name'] = old_temp + # Create a template with same name fails with 400 t = {'name': 'test', 'os_distro': 'ImagineOS', 'os_version': '1.0', 'memory': 1024, 'cpus': 1, -- 1.8.4.2
participants (2)
-
Aline Manera
-
shaohef@linux.vnet.ibm.com