[PATCH v5 0/6] Async VM Creation

v5 Changes: - Rebase - Remove all the unimperative target_uri changes (see below) If a guest has a large disk, and uses a filesystem that requires preallocation, it can take several minutes to create a VM. During that time, kimchi is tied up by the VM creation. This patch changes the VMs Collection to be an AsyncCollection. Another change required for this was to create a more granular way to query vm-related tasks. The original idea was to add another field to the task database, but then Aline suggested just modifying the task_uri. Since the task_uri cretation will be changed in a future patchset, this is now only modified for the conflicting (clone) tasks. Christy Perez (6): Append clone to target_uri for vm clone task Tests for new clone target_uri UI changes for new clone target_uri Create VMs asynchronously: Backend Create VMs Asynchronously: Tests Create VMs Asynchronously: UI src/kimchi/control/vms.py | 4 +-- src/kimchi/model/vms.py | 32 +++++++++++++++++---- tests/test_authorization.py | 23 +++++++++------ tests/test_mockmodel.py | 12 ++++++-- tests/test_model.py | 49 +++++++++++++++++++++----------- tests/test_model_storagevolume.py | 2 +- tests/test_rest.py | 60 +++++++++++++++++++++++++++++---------- ui/css/theme-default/list.css | 18 ++++++++++++ ui/js/src/kimchi.guest_main.js | 29 +++++++++++++++---- ui/pages/guest.html.tmpl | 3 ++ 10 files changed, 175 insertions(+), 57 deletions(-) -- 2.1.0

So that we can differentiate between clone and create tasks for VMs. Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index c8267c1..eac9fdf 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -230,9 +230,8 @@ def clone(self, name): new_name = get_next_clone_name(current_vm_names, name) # create a task with the actual clone function - taskid = add_task(u'/vms/%s' % new_name, self._clone_task, - self.objstore, - {'name': name, 'new_name': new_name}) + taskid = add_task(u'/vms/%s/clone' % new_name, self._clone_task, + self.objstore, {'name': name, 'new_name': new_name}) return self.task.lookup(taskid) -- 2.1.0

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- tests/test_model.py | 4 ++-- tests/test_model_storagevolume.py | 2 +- tests/test_rest.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index daddb97..16ec239 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -869,9 +869,9 @@ def test_vm_clone(self): # and make sure both of them complete successfully task1 = inst.vm_clone(name) task2 = inst.vm_clone(name) - clone1_name = task1['target_uri'].split('/')[-1] + clone1_name = task1['target_uri'].split('/')[-2] rollback.prependDefer(inst.vm_delete, clone1_name) - clone2_name = task2['target_uri'].split('/')[-1] + clone2_name = task2['target_uri'].split('/')[-2] rollback.prependDefer(inst.vm_delete, clone2_name) inst.task_wait(task1['id']) task1 = inst.task_lookup(task1['id']) diff --git a/tests/test_model_storagevolume.py b/tests/test_model_storagevolume.py index a3c3ce3..11fd90d 100644 --- a/tests/test_model_storagevolume.py +++ b/tests/test_model_storagevolume.py @@ -124,7 +124,7 @@ def _task_lookup(taskid): resp = self.request(vol_uri + '/clone', '{}', 'POST') self.assertEquals(202, resp.status) task = json.loads(resp.read()) - cloned_vol_name = task['target_uri'].split('/')[-1] + cloned_vol_name = task['target_uri'].split('/')[-2] rollback.prependDefer(model.storagevolume_delete, pool_name, cloned_vol_name) wait_task(_task_lookup, task['id']) diff --git a/tests/test_rest.py b/tests/test_rest.py index 5bde044..6b37b9b 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -305,7 +305,7 @@ def test_vm_lifecycle(self): wait_task(self._task_lookup, task['id']) task = json.loads(self.request('/tasks/%s' % task['id'], '{}').read()) self.assertEquals('finished', task['status']) - clone_vm_name = task['target_uri'].split('/')[-1] + clone_vm_name = task['target_uri'].split('/')[-2] self.assertTrue(re.match(u'test-vm-clone-\d+', clone_vm_name)) resp = self.request('/vms/test-vm', '{}') -- 2.1.0

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index b66177c..c712bb4 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -203,10 +203,10 @@ kimchi.listVmsAuto = function() { } var getCloningGuests = function(){ var guests = []; - kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/.+/clone'), function(tasks) { for(var i=0;i<tasks.length;i++){ var guestUri = tasks[i].target_uri; - var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + var guestName = guestUri.split('/')[2] guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) kimchi.trackTask(tasks[i].id, null, function(err){ -- 2.1.0

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- src/kimchi/control/vms.py | 4 ++-- src/kimchi/model/vms.py | 27 ++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 6352a26..a40b56e 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -17,13 +17,13 @@ # 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.control.base import Collection, Resource +from kimchi.control.base import AsyncCollection, Resource from kimchi.control.utils import internal_redirect, UrlSubNode from kimchi.control.vm import sub_nodes @UrlSubNode('vms', True) -class VMs(Collection): +class VMs(AsyncCollection): def __init__(self, model): super(VMs, self).__init__(model) self.resource = VM diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index eac9fdf..16f6e3f 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -79,9 +79,9 @@ def __init__(self, **kargs): self.conn = kargs['conn'] self.objstore = kargs['objstore'] self.caps = CapabilitiesModel(**kargs) + self.task = TaskModel(**kargs) def create(self, params): - conn = self.conn.get() t_name = template_name_from_uri(params['template']) vm_uuid = str(uuid.uuid4()) vm_list = self.get_list() @@ -102,7 +102,26 @@ def create(self, params): raise InvalidOperation("KCHVM0005E") t.validate() + taskid = add_task(u'/vms/%s' % name, + self._create_task, self.objstore, + {'vm_uuid': vm_uuid, 'template': t, 'name': name}) + + return self.task.lookup(taskid) + + def _create_task(self, cb, params): + """ + params: A dict with the following values: + - vm_uuid: The UUID of the VM being created + - template: The template being used to create the VM + - name: The name for the new VM + """ + + vm_uuid = params['vm_uuid'] + t = params['template'] + name = params['name'] + conn = self.conn.get() + cb('Storing VM icon') # Store the icon for displaying later icon = t.info.get('icon') if icon: @@ -117,6 +136,7 @@ def create(self, params): # If storagepool is SCSI, volumes will be LUNs and must be passed by # the user from UI or manually. + cb('Provisioning storage for new VM') vol_list = [] if t._get_storage_type() not in ["iscsi", "scsi"]: vol_list = t.fork_vm_storage(vm_uuid) @@ -129,6 +149,7 @@ def create(self, params): graphics=graphics, volumes=vol_list) + cb('Defining new VM') try: conn.defineXML(xml.encode('utf-8')) except libvirt.libvirtError as e: @@ -139,10 +160,10 @@ def create(self, params): raise OperationFailed("KCHVM0007E", {'name': name, 'err': e.get_error_message()}) + cb('Updating VM metadata') VMModel.vm_update_os_metadata(VMModel.get_vm(name, self.conn), t.info, self.caps.metadata_support) - - return name + cb('OK', True) def get_list(self): return VMsModel.get_vms(self.conn) -- 2.1.0

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- tests/test_authorization.py | 23 +++++++++++------- tests/test_mockmodel.py | 12 +++++++--- tests/test_model.py | 45 ++++++++++++++++++++++++----------- tests/test_rest.py | 58 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 98 insertions(+), 40 deletions(-) diff --git a/tests/test_authorization.py b/tests/test_authorization.py index 4fcc496..eae837c 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -26,7 +26,7 @@ import kimchi.mockmodel from iso_gen import construct_fake_iso from utils import get_free_port, patch_auth, request -from utils import run_server +from utils import run_server, wait_task test_server = None @@ -118,19 +118,24 @@ def test_nonroot_access(self): # Non-root users can only get vms authorized to them model.templates_create({'name': u'test', 'cdrom': fake_iso}) - model.vms_create({'name': u'test-me', 'template': '/templates/test'}) + task_info = model.vms_create({'name': u'test-me', + 'template': '/templates/test'}) + wait_task(model.task_lookup, task_info['id']) + model.vm_update(u'test-me', {'users': [kimchi.mockmodel.fake_user.keys()[0]], 'groups': []}) - model.vms_create({'name': u'test-usera', - 'template': '/templates/test'}) + task_info = model.vms_create({'name': u'test-usera', + 'template': '/templates/test'}) + wait_task(model.task_lookup, task_info['id']) non_root = list(set(model.users_get_list()) - set(['root']))[0] model.vm_update(u'test-usera', {'users': [non_root], 'groups': []}) - model.vms_create({'name': u'test-groupa', - 'template': '/templates/test'}) + task_info = model.vms_create({'name': u'test-groupa', + 'template': '/templates/test'}) + wait_task(model.task_lookup, task_info['id']) a_group = model.groups_get_list()[0] model.vm_update(u'test-groupa', {'groups': [a_group]}) @@ -143,9 +148,9 @@ def test_nonroot_access(self): self.assertEquals(403, resp.status) # Create a vm using mockmodel directly to test Resource access - model.vms_create({'name': 'kimchi-test', - 'template': '/templates/test'}) - + task_info = model.vms_create({'name': 'kimchi-test', + 'template': '/templates/test'}) + wait_task(model.task_lookup, task_info['id']) resp = self.request('/vms/kimchi-test', '{}', 'PUT') self.assertEquals(403, resp.status) resp = self.request('/vms/kimchi-test', '{}', 'DELETE') diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index c7e733e..528c084 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -67,7 +67,9 @@ def test_screenshot_refresh(self): req = json.dumps({'name': 'test', 'cdrom': fake_iso}) request(host, ssl_port, '/templates', req, 'POST') req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) - request(host, ssl_port, '/vms', req, 'POST') + resp = request(host, ssl_port, '/vms', req, 'POST') + task = json.loads(resp.read()) + wait_task(model.task_lookup, task['id']) # Test screenshot refresh for running vm request(host, ssl_port, '/vms/test-vm/start', '{}', 'POST') @@ -95,7 +97,9 @@ def test_vm_list_sorted(self): def add_vm(name): # Create a VM req = json.dumps({'name': name, 'template': '/templates/test'}) - request(host, ssl_port, '/vms', req, 'POST') + task = json.loads(request(host, ssl_port, '/vms', req, + 'POST').read()) + wait_task(model.task_lookup, task['id']) vms = [u'abc', u'bca', u'cab', u'xba'] for vm in vms: @@ -107,7 +111,9 @@ def add_vm(name): def test_vm_info(self): model.templates_create({'name': u'test', 'cdrom': fake_iso}) - model.vms_create({'name': u'test-vm', 'template': '/templates/test'}) + task = model.vms_create({'name': u'test-vm', + 'template': '/templates/test'}) + wait_task(model.task_lookup, task['id']) vms = model.vms_get_list() self.assertEquals(2, len(vms)) self.assertIn(u'test-vm', vms) diff --git a/tests/test_model.py b/tests/test_model.py index 16ec239..7c9285b 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -133,8 +133,11 @@ def test_vm_lifecycle(self): rollback.prependDefer(inst.template_delete, 'test') params = {'name': 'kimchi-vm', 'template': '/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) rollback.prependDefer(inst.vm_delete, 'kimchi-vm') + inst.task_wait(task['id'], 10) + task = inst.task_lookup(task['id']) + self.assertEquals('finished', task['status']) vms = inst.vms_get_list() self.assertTrue('kimchi-vm' in vms) @@ -271,7 +274,8 @@ def test_image_based_template(self): session.store('template', tmpl_name, tmpl_info) params = {'name': 'kimchi-vm', 'template': '/templates/img-tmpl'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(inst.vm_delete, 'kimchi-vm') vms = inst.vms_get_list() @@ -290,7 +294,8 @@ def test_vm_graphics(self): inst.templates_create(params) with RollbackContext() as rollback: params = {'name': 'kimchi-vnc', 'template': '/templates/test'} - inst.vms_create(params) + task1 = inst.vms_create(params) + inst.task_wait(task1['id']) rollback.prependDefer(inst.vm_delete, 'kimchi-vnc') info = inst.vm_lookup('kimchi-vnc') @@ -300,7 +305,8 @@ def test_vm_graphics(self): graphics = {'type': 'spice', 'listen': '127.0.0.1'} params = {'name': 'kimchi-spice', 'template': '/templates/test', 'graphics': graphics} - inst.vms_create(params) + task2 = inst.vms_create(params) + inst.task_wait(task2['id']) rollback.prependDefer(inst.vm_delete, 'kimchi-spice') info = inst.vm_lookup('kimchi-spice') @@ -317,7 +323,8 @@ def test_vm_ifaces(self): inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') params = {'name': 'kimchi-ifaces', 'template': '/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(inst.vm_delete, 'kimchi-ifaces') # Create a network @@ -424,7 +431,8 @@ def _attach_disk(expect_bus=modern_disk_bus): inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') params = {'name': vm_name, 'template': '/templates/test'} - inst.vms_create(params) + task1 = inst.vms_create(params) + inst.task_wait(task1['id']) rollback.prependDefer(inst.vm_delete, vm_name) prev_count = len(inst.vmstorages_get_list(vm_name)) @@ -467,7 +475,8 @@ def _attach_disk(expect_bus=modern_disk_bus): rollback.prependDefer(inst.template_delete, 'old_distro_template') params = {'name': vm_name, 'template': '/templates/old_distro_template'} - inst.vms_create(params) + task2 = inst.vms_create(params) + inst.task_wait(task2['id']) rollback.prependDefer(inst.vm_delete, vm_name) # Need to check the right disk_bus for old distro @@ -488,7 +497,8 @@ def test_vm_cdrom(self): inst.templates_create(params) rollback.prependDefer(inst.template_delete, 'test') params = {'name': vm_name, 'template': '/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(inst.vm_delete, vm_name) prev_count = len(inst.vmstorages_get_list(vm_name)) @@ -584,7 +594,8 @@ def test_vm_storage_provisioning(self): rollback.prependDefer(inst.template_delete, 'test') params = {'name': 'test-vm-1', 'template': '/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(inst.vm_delete, 'test-vm-1') vm_info = inst.vm_lookup(params['name']) @@ -605,10 +616,12 @@ def test_vm_edit(self): 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) + task1 = inst.vms_create(params_1) + inst.task_wait(task1['id']) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, 'kimchi-vm1') - inst.vms_create(params_2) + task2 = inst.vms_create(params_2) + inst.task_wait(task2['id']) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, 'kimchi-vm2') @@ -818,11 +831,13 @@ def test_delete_running_vm(self): rollback.prependDefer(inst.template_delete, 'test') params = {'name': u'kīмсhī-∨м', 'template': u'/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'kīмсhī-∨м') inst.vm_start(u'kīмсhī-∨м') + self.assertEquals(inst.vm_lookup(u'kīмсhī-∨м')['state'], 'running') rollback.prependDefer(utils.rollback_wrapper, inst.vm_poweroff, u'kīмсhī-∨м') @@ -841,7 +856,8 @@ def test_vm_list_sorted(self): rollback.prependDefer(inst.template_delete, 'test') params = {'name': 'kimchi-vm', 'template': '/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(inst.vm_delete, 'kimchi-vm') vms = inst.vms_get_list() @@ -913,7 +929,8 @@ def test_use_test_host(self): params = {'name': 'kimchi-vm', 'template': '/templates/test'} - inst.vms_create(params) + task = inst.vms_create(params) + inst.task_wait(task['id']) rollback.prependDefer(inst.vm_delete, 'kimchi-vm') vms = inst.vms_get_list() diff --git a/tests/test_rest.py b/tests/test_rest.py index 6b37b9b..ab7a783 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -125,7 +125,9 @@ def test_get_vms(self): req = json.dumps({'name': name, 'template': '/templates/test', 'users': test_users, 'groups': test_groups}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) vms = json.loads(self.request('/vms').read()) self.assertEquals(11, len(vms)) @@ -143,7 +145,9 @@ def test_edit_vm(self): req = json.dumps({'name': 'vm-1', 'template': '/templates/test'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) vm = json.loads(self.request('/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) @@ -260,7 +264,9 @@ def test_vm_lifecycle(self): # Create a VM req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) + self.assertEquals(202, resp.status) # Verify the VM vm = json.loads(self.request('/vms/test-vm').read()) @@ -442,7 +448,9 @@ def test_vm_graphics(self): # Create a VM with default args req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Verify the VM vm = json.loads(self.request('/vms/test-vm').read()) self.assertEquals('127.0.0.1', vm['graphics']['listen']) @@ -456,7 +464,9 @@ def test_vm_graphics(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Verify the VM vm = json.loads(self.request('/vms/test-vm').read()) self.assertEquals('127.0.0.1', vm['graphics']['listen']) @@ -470,7 +480,9 @@ def test_vm_graphics(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Verify the VM vm = json.loads(self.request('/vms/test-vm').read()) self.assertEquals('fe00::0', vm['graphics']['listen']) @@ -484,7 +496,9 @@ def test_vm_graphics(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Verify the VM vm = json.loads(self.request('/vms/test-vm').read()) self.assertEquals('127.0.0.1', vm['graphics']['listen']) @@ -526,7 +540,9 @@ def test_vm_storage_devices(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Delete the VM rollback.prependDefer(self.request, '/vms/test-vm', '{}', 'DELETE') @@ -671,7 +687,9 @@ def test_vm_iface(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Delete the VM rollback.prependDefer(self.request, '/vms/test-vm', '{}', 'DELETE') @@ -746,7 +764,10 @@ def test_vm_customise_storage(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'storagepool': '/storagepools/alt'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) + resp = self.request('/vms/test-vm', {}, 'GET') vm_info = json.loads(resp.read()) # Test template not changed after vm customise its pool @@ -799,7 +820,9 @@ def test_scsi_fc_storage(self): req = json.dumps({'name': 'test-vm', 'template': '/templates/test_fc_pool'}) resp = self.request('/vms', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Start the VM resp = self.request('/vms/test-vm/start', '{}', 'POST') @@ -824,8 +847,10 @@ def test_unnamed_vms(self): # Create 5 unnamed vms from this template for i in xrange(1, 6): req = json.dumps({'template': '/templates/test'}) - vm = json.loads(self.request('/vms', req, 'POST').read()) - self.assertEquals('test-vm-%i' % i, vm['name']) + task = json.loads(self.request('/vms', req, 'POST').read()) + wait_task(self._task_lookup, task['id']) + resp = self.request('/vms/test-vm-%i' % i, {}, 'GET') + self.assertEquals(resp.status, 200) count = len(json.loads(self.request('/vms').read())) self.assertEquals(6, count) @@ -857,7 +882,10 @@ def test_create_vm_with_img_based_template(self): self.assertEquals(201, resp.status) req = json.dumps({'template': '/templates/test'}) - json.loads(self.request('/vms', req, 'POST').read()) + resp = self.request('/vms', req, 'POST') + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Test storage volume created with backing store of base file resp = json.loads( @@ -947,6 +975,8 @@ def test_screenshot_refresh(self): resp = self.request('/templates', req, 'POST') req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) resp = self.request('/vms', req, 'POST') + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) # Test screenshot for shut-off state vm resp = self.request('/vms/test-vm/screenshot') -- 2.1.0

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++++++++++ ui/js/src/kimchi.guest_main.js | 25 ++++++++++++++++++++++--- ui/pages/guest.html.tmpl | 3 +++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 7b32ea6..62d1539 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -306,3 +306,21 @@ margin-left: 5px; text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; } + +.guest-create { + margin: 10px; +} + +.guest-create .icon { + background: url('../../images/theme-default/kimchi-loading15x15.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-create .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index c712bb4..dbcc162 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -201,6 +201,21 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCreatingGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/[^/]+$'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.split('/')[1] + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCreating: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; var getCloningGuests = function(){ var guests = []; kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/.+/clone'), function(tasks) { @@ -219,6 +234,7 @@ kimchi.listVmsAuto = function() { kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { result = getCloningGuests().concat(result); + result = getCreatingGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -281,7 +297,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src); //Link the stopped tile to the start action, the running tile to open the console - if(!vmObject.isCloning){ + if(!(vmObject.isCloning || vmObject.isCreating)){ if (vmRunningBool) { liveTile.off("click", kimchi.vmstart); liveTile.on("click", kimchi.openVmConsole); @@ -329,7 +345,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } //Setup action event handlers - if(!vmObject.isCloning){ + if(!(vmObject.isCloning || vmObject.isCreating)){ guestActions.find("[name=vm-start]").on({click : kimchi.vmstart}); guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff}); if (vmRunningBool) { //If the guest is not running, do not enable reset @@ -362,7 +378,10 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { }else{ guestActions.find('.btn').attr('disabled', true); - result.find('.guest-clone').removeClass('hide-content'); + if(vmObject.isCloning) + result.find('.guest-clone').removeClass('hide-content'); + else + result.find('.guest-create').removeClass('hide-content'); $('.popover', guestActions.find("div[name=actionmenu]")).remove(); } diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 17d41ac..aaf41a2 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -29,6 +29,9 @@ <div class="guest-clone hide-content"> <span class="icon"></span><span class="text">$_("Cloning")...</span> </div> + <div class="guest-create hide-content"> + <span class="icon"></span><span class="text">$_("Creating")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> -- 2.1.0

On 27/04/2015 14:28, Christy Perez wrote:
Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++++++++++ ui/js/src/kimchi.guest_main.js | 25 ++++++++++++++++++++++--- ui/pages/guest.html.tmpl | 3 +++ 3 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 7b32ea6..62d1539 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -306,3 +306,21 @@ margin-left: 5px; text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; } + +.guest-create { + margin: 10px; +} + +.guest-create .icon { + background: url('../../images/theme-default/kimchi-loading15x15.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-create .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +}
I think we can create a single config to work for VM cloning and creation. For example, replace the current .guest-clone to .guest-pending
diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index c712bb4..dbcc162 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -201,6 +201,21 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCreatingGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/[^/]+$'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.split('/')[1] + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCreating: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; var getCloningGuests = function(){ var guests = []; kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/.+/clone'), function(tasks) { @@ -219,6 +234,7 @@ kimchi.listVmsAuto = function() { kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { result = getCloningGuests().concat(result); + result = getCreatingGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -281,7 +297,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if(!vmObject.isCloning){ + if(!(vmObject.isCloning || vmObject.isCreating)){ if (vmRunningBool) { liveTile.off("click", kimchi.vmstart); liveTile.on("click", kimchi.openVmConsole); @@ -329,7 +345,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { }
//Setup action event handlers - if(!vmObject.isCloning){ + if(!(vmObject.isCloning || vmObject.isCreating)){ guestActions.find("[name=vm-start]").on({click : kimchi.vmstart}); guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff}); if (vmRunningBool) { //If the guest is not running, do not enable reset @@ -362,7 +378,10 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) {
}else{ guestActions.find('.btn').attr('disabled', true); - result.find('.guest-clone').removeClass('hide-content');
+ if(vmObject.isCloning) + result.find('.guest-clone').removeClass('hide-content'); + else + result.find('.guest-create').removeClass('hide-content'); $('.popover', guestActions.find("div[name=actionmenu]")).remove(); }
Same I commented above. But this "if" conditional will still be needed to properly set the label (Clonning... or Creating...) Reusing the same UI for both will make sure they will not appears different in future.
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 17d41ac..aaf41a2 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -29,6 +29,9 @@ <div class="guest-clone hide-content"> <span class="icon"></span><span class="text">$_("Cloning")...</span> </div> + <div class="guest-create hide-content"> + <span class="icon"></span><span class="text">$_("Creating")...</span> + </div>
The comment I made applies to here too.
</div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div>

Christy, I reviewed the backend patches and I don't have any comments to make. I am going to apply them so you just need to resend the UI part. On 27/04/2015 14:28, Christy Perez wrote:
v5 Changes: - Rebase - Remove all the unimperative target_uri changes (see below)
If a guest has a large disk, and uses a filesystem that requires preallocation, it can take several minutes to create a VM. During that time, kimchi is tied up by the VM creation.
This patch changes the VMs Collection to be an AsyncCollection.
Another change required for this was to create a more granular way to query vm-related tasks. The original idea was to add another field to the task database, but then Aline suggested just modifying the task_uri. Since the task_uri cretation will be changed in a future patchset, this is now only modified for the conflicting (clone) tasks.
Christy Perez (6): Append clone to target_uri for vm clone task Tests for new clone target_uri UI changes for new clone target_uri Create VMs asynchronously: Backend Create VMs Asynchronously: Tests Create VMs Asynchronously: UI
src/kimchi/control/vms.py | 4 +-- src/kimchi/model/vms.py | 32 +++++++++++++++++---- tests/test_authorization.py | 23 +++++++++------ tests/test_mockmodel.py | 12 ++++++-- tests/test_model.py | 49 +++++++++++++++++++++----------- tests/test_model_storagevolume.py | 2 +- tests/test_rest.py | 60 +++++++++++++++++++++++++++++---------- ui/css/theme-default/list.css | 18 ++++++++++++ ui/js/src/kimchi.guest_main.js | 29 +++++++++++++++---- ui/pages/guest.html.tmpl | 3 ++ 10 files changed, 175 insertions(+), 57 deletions(-)

On 28/04/2015 14:37, Aline Manera wrote:
Christy, I reviewed the backend patches and I don't have any comments to make. I am going to apply them so you just need to resend the UI part.
Ops... I could not apply them. Could you do a rebase and resend? Thanks, Aline Manera
On 27/04/2015 14:28, Christy Perez wrote:
v5 Changes: - Rebase - Remove all the unimperative target_uri changes (see below)
If a guest has a large disk, and uses a filesystem that requires preallocation, it can take several minutes to create a VM. During that time, kimchi is tied up by the VM creation.
This patch changes the VMs Collection to be an AsyncCollection.
Another change required for this was to create a more granular way to query vm-related tasks. The original idea was to add another field to the task database, but then Aline suggested just modifying the task_uri. Since the task_uri cretation will be changed in a future patchset, this is now only modified for the conflicting (clone) tasks.
Christy Perez (6): Append clone to target_uri for vm clone task Tests for new clone target_uri UI changes for new clone target_uri Create VMs asynchronously: Backend Create VMs Asynchronously: Tests Create VMs Asynchronously: UI
src/kimchi/control/vms.py | 4 +-- src/kimchi/model/vms.py | 32 +++++++++++++++++---- tests/test_authorization.py | 23 +++++++++------ tests/test_mockmodel.py | 12 ++++++-- tests/test_model.py | 49 +++++++++++++++++++++----------- tests/test_model_storagevolume.py | 2 +- tests/test_rest.py | 60 +++++++++++++++++++++++++++++---------- ui/css/theme-default/list.css | 18 ++++++++++++ ui/js/src/kimchi.guest_main.js | 29 +++++++++++++++---- ui/pages/guest.html.tmpl | 3 ++ 10 files changed, 175 insertions(+), 57 deletions(-)
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel
participants (2)
-
Aline Manera
-
Christy Perez