[PATCH V2 0/4] template cloning

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> 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 (4): clone template: update API.md clone template: update controller clone template: update model and mockmodel clone template: update test case docs/API.md | 4 +++- src/kimchi/control/templates.py | 1 + src/kimchi/mockmodel.py | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ tests/test_model.py | 18 ++++++++++++++++++ tests/test_rest.py | 15 +++++++++++++++ 6 files changed, 71 insertions(+), 1 deletion(-) -- 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 48a293f..f489aff 100644 --- a/docs/API.md +++ b/docs/API.md @@ -257,7 +257,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> 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 | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 441c0e4..b2f67dd 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -202,6 +202,23 @@ 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) + print cherrypy.url() + raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + 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 a5c73cc..30673f2 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -22,6 +22,7 @@ import copy +import cherrypy import libvirt from kimchi import xmlutils @@ -85,6 +86,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) + raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + def delete(self, name): with self.objstore as session: session.delete('template', name) -- 1.8.4.2

On 02/13/2014 07:35 AM, shaohef@linux.vnet.ibm.com wrote:
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 | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 441c0e4..b2f67dd 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -202,6 +202,23 @@ 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)
+ print cherrypy.url()
Need to remove this line
+ raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + 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 a5c73cc..30673f2 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -22,6 +22,7 @@
import copy
+import cherrypy import libvirt
from kimchi import xmlutils @@ -85,6 +86,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) + raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + def delete(self, name): with self.objstore as session: session.delete('template', name)

On 02/14/2014 05:51 PM, Aline Manera wrote:
On 02/13/2014 07:35 AM, shaohef@linux.vnet.ibm.com wrote:
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 | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 441c0e4..b2f67dd 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -202,6 +202,23 @@ 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)
+ print cherrypy.url()
Need to remove this line
I will do it before applying :-)
+ raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + 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 a5c73cc..30673f2 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -22,6 +22,7 @@
import copy
+import cherrypy import libvirt
from kimchi import xmlutils @@ -85,6 +86,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) + raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + def delete(self, name): with self.objstore as session: session.delete('template', name)
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On 02/13/2014 07:35 AM, shaohef@linux.vnet.ibm.com wrote:
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 | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 441c0e4..b2f67dd 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -202,6 +202,23 @@ 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) + print cherrypy.url()
+ raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303)
cherrypy should be restrict to control I think we don't need to return anything here
+ 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 a5c73cc..30673f2 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -22,6 +22,7 @@
import copy
+import cherrypy import libvirt
from kimchi import xmlutils @@ -85,6 +86,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) + raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + def delete(self, name): with self.objstore as session: session.delete('template', name)

On 02/15/2014 04:12 AM, Aline Manera wrote:
On 02/13/2014 07:35 AM, shaohef@linux.vnet.ibm.com wrote:
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 | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 441c0e4..b2f67dd 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -202,6 +202,23 @@ 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) + print cherrypy.url()
+ raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303)
cherrypy should be restrict to control agree. when I write code here, I have known that I should not HTTPRedirect here. But this POST is different with POST method, other POST method will not create a resource. Here we will create a resource like PUT method.
So we should HTTPRedirect explicitly, let the client know there is a new resource. I will touch the control to enhance it. seen next version.
I think we don't need to return anything here
+ 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 a5c73cc..30673f2 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -22,6 +22,7 @@
import copy
+import cherrypy import libvirt
from kimchi import xmlutils @@ -85,6 +86,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) + raise cherrypy.HTTPRedirect("/templates/%s" % ident, 303) + def delete(self, name): with self.objstore as session: session.delete('template', name)
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

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 | 18 ++++++++++++++++++ tests/test_rest.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index b374d2d..74cff87 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -21,6 +21,7 @@ # 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 os import platform import psutil @@ -380,6 +381,23 @@ 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']) + + self.assertRaises(cherrypy.HTTPRedirect, + inst.template_clone, 'test-template') + clone_temp = inst.template_lookup('test-template%s' % "-clone1") + + 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 69b8316..6401d95 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -811,6 +811,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

Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com> On 02/13/2014 07:35 AM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
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 (4): clone template: update API.md clone template: update controller clone template: update model and mockmodel clone template: update test case
docs/API.md | 4 +++- src/kimchi/control/templates.py | 1 + src/kimchi/mockmodel.py | 17 +++++++++++++++++ src/kimchi/model/templates.py | 17 +++++++++++++++++ tests/test_model.py | 18 ++++++++++++++++++ tests/test_rest.py | 15 +++++++++++++++ 6 files changed, 71 insertions(+), 1 deletion(-)
participants (3)
-
Aline Manera
-
shaohef@linux.vnet.ibm.com
-
Sheldon