[PATCH] [Kimchi 0/2] Live Migration v6

From: Samuel Guimarães <sguimaraes943@gmail.com> v5 - Rebased initial checkin for live migration to new-ui; - Added form validation to optional fields (e.g. disable submit button if username is filled but password is empty, added warn classes to form fields); - Fixed error messages in modal window; - Delete VM once Live Migration is finished checkbox is working; v6 - wok.window.close() destroys modal content when called. Before sending the necessary parameters to perform a migration to Kimchi API, all the input values including the "Delete..." checkbox had to be stored in an array including the VM name and then called a function to keep track of the ongoing tasks and verify if the VM should be deleted based on the checkbox value. - Rewrote most of the JS in kimchi.guest_livemigration.js - Fixed asynchronous live migration proccess. - Fixed some minor issues with Guests main page when Cloning, Creating and Migrating VMs (this wasn't listed as a bug nor enhancement on Github but it had to be fixed in order to show "Migrating..." text on the Action button for List and Gallery views) - Small change in SCSS and CSS to fix Action button. - Now when a VM is migrating it is removed from the list and attached to the top just like when it is cloning or creating a new VM. In order to keep the migrating guest in the same position it would require other js libraries or more prototype functions just to handle the arrays in this screen. - Had to change a line in /models/vms.py because getTasksByFilter() wasn't working when trying to get all the migrating guests by "/plugins/kimchi/vms/.+/migrate" Socorro Stoppler (1): Initial checkin for live migration UI support samhenri (1): Live Migration front-end model/vms.py | 2 +- ui/css/kimchi.css | 21 +++-- ui/css/src/modules/_guests.scss | 6 +- ui/js/src/kimchi.api.js | 3 +- ui/js/src/kimchi.guest_livemigration.js | 136 ++++++++++++++++++++++++++++++++ ui/js/src/kimchi.guest_main.js | 58 +++++++++++--- ui/pages/guest-migration.html.tmpl | 67 ++++++++++++++++ ui/pages/guest.html.tmpl | 16 ++-- ui/pages/i18n.json.tmpl | 2 + ui/pages/tabs/guests.html.tmpl | 4 +- 10 files changed, 290 insertions(+), 25 deletions(-) create mode 100644 ui/js/src/kimchi.guest_livemigration.js create mode 100644 ui/pages/guest-migration.html.tmpl -- 1.8.3.1

From: Socorro Stoppler <socorro@linux.vnet.ibm.com> Signed-off-by: Socorro Stoppler <socorro@linux.vnet.ibm.com> --- ui/js/src/kimchi.api.js | 3 +- ui/js/src/kimchi.guest_main.js | 46 +++++++++++++++++++++++++-- ui/pages/guest-migration.html.tmpl | 65 ++++++++++++++++++++++++++++++++++++++ ui/pages/guest.html.tmpl | 7 ++-- 4 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 ui/pages/guest-migration.html.tmpl diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index af3034b..da6e856 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -1046,12 +1046,13 @@ var kimchi = { }); }, - migrateGuest: function(vm, suc, err) { + migrateGuest: function(vm, data, suc, err) { wok.requestJSON({ url : 'plugins/kimchi/vms/' + encodeURIComponent(vm) + "/migrate", type : 'POST', contentType : 'application/json', dataType : 'json', + data : JSON.stringify(data), success : suc, error : err ? err : function(data) { wok.message.error(data.responseJSON.reason); diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index 257d76e..2d3ccf3 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -326,8 +326,24 @@ kimchi.listVmsAuto = function() { }, null, true); return guests; }; + var getMigratingGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/.+/migrate'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.split('/')[4] + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isMigrating: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + wok.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus == "success") { + result = getMigratingGuests().concat(result); result = getCloningGuests().concat(result); result = getCreatingGuests().concat(result); if (result.length) { @@ -385,6 +401,26 @@ kimchi.listVmsAuto = function() { kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { var result = kimchi.guestElem.clone(); + var initializeMigratePanel = function() { + $("#migrateFormOk").on("click", function() { + //TODO: Get values from UI for remote_host, user, password + var data = { + "remote_host" : "ltc-hab1.aus.stglabs.ibm.com", + "user" : "root", + "password" : "passw0rd" + }; + //TODO: Need to get guest to be passed in here + kimchi.migrateGuest(guest, data, function(){ + kimchi.listVmsAuto(); + wok.window.close(); + }, function(err) { + wok.message.error(err.responseJSON.reason); + }); + }); + } + + initializeMigratePanel(); + //Setup the VM list entry var currentState = result.find('.guest-state'); var vmRunningBool = (vmObject.state == "running"); @@ -595,7 +631,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { result.addClass('inactive'); result.find('.distro-icon').addClass('inactive'); result.find('.vnc-link').css("display", "none"); - result.find('.column-vnc').html('--'); + result.find('.column-vnc').html('--'); //Hide PowerOff guestActions.find(".shutoff-hidden").hide(); //Hide Pause @@ -630,7 +666,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } //Setup action event handlers - if (!(vmObject.isCloning || vmObject.isCreating)) { + if(!(vmObject.isCloning || vmObject.isCreating || vmObject.isMigrating)){ guestActions.find("[name=vm-start]").on("click", function(event) { event.preventDefault(); @@ -689,12 +725,18 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { }); }, null); }); + guestActions.find("[name=vm-migrate]").click(function(){ + var guest = $(this).closest('li[name=guest]').attr("id"); + wok.window.open('plugins/kimchi/guest-migration.html'); + }); } else { guestActions.find('.btn').attr('disabled', true); result.find('.guest-pending').removeClass('hide-content'); pendingText = result.find('.guest-pending .text') if (vmObject.isCloning) pendingText.text(i18n['KCHAPI6009M']); + else if(vmObject.isMigrating) + pendingText.text("Migrating"); else pendingText.text(i18n['KCHAPI6008M']); } diff --git a/ui/pages/guest-migration.html.tmpl b/ui/pages/guest-migration.html.tmpl new file mode 100644 index 0000000..278d22c --- /dev/null +++ b/ui/pages/guest-migration.html.tmpl @@ -0,0 +1,65 @@ +#* + * Project Kimchi + * + * Copyright IBM, Corp. 2015 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *# +#unicode UTF-8 +#import gettext +#from wok.cachebust import href +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang, fallback=True) +#silent _ = t.gettext +#silent _t = t.gettext +<div id="migrate-guest-window" class="window modal-content"> + <div class="modal-header"> + <h4 class="modal-title" id="migrateModalLabel">$_("Migrate a Guest")</h4> + </div> + <div id="migrateInfo" class="modal-body"> + <div class="alert alert-warning" role="alert">Disclaimer: This process cannot be stopped after started, + can take a long time to complete and will turn off the VM on this Hypervisor when it is successfully + migrated to the remote destination. + </div> + <div class="form-group"> + <label for="remoteHostName">$_("Remote Server")</label> + <input type="text" class="form-control" id="remoteHostName" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("IP Address or Hostname")</p> + </div> + <div class="alert alert-info" role="alert">The following fields are optional. Fill them if you want Kimchi to + setup a password-less ssh session between the localhost and the remote host. The setup process will only + be successful if the user has 'SUDO ALL' permission in the remote machine. + </div> + <div class="form-group"> + <label for="user">$_("User")</label> + <input type="text" class="form-control" id="user" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("Username of the remote host")</p> + </div> + <div class="form-group"> + <label for="password">$_("Password")</label> + <input type="password" class="form-control" id="password" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("Password of the user in the remote host")</p> + </div> + <div class="form-group"> + <input id="deleteVM" class="wok-checkbox" type="checkbox" value="" /> + <label for="deleteVM" id="labelDeleteVM">$_("Delete this VM when the migration is completed") </label> + </div> + </div> + <div class="modal-footer"> + <button type="submit" id="migrateFormOk" class="btn btn-default">$_("Start")</button> + <button type="button" id="migrateFormCancel" data-dismiss="modal" class="btn btn-default">$_("Cancel")</button> + </div> +</div> + diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index c2d9ba8..080d918 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -40,7 +40,8 @@ <li role="presentation"><a nwAct="connect-vnc" class='shutoff-disabled' name="vm-console" href="#"><i class="fa fa-list-alt"></i>$_("Connect VNC")</a></li> <!-- <li role="presentation"><a nwAct="view-vnc" class='shutoff-disabled' name="vm-view-vnc" href="#"><i class="fa fa-eye"></i>$_("View VNC Console")</a></li> --> <li role="presentation"><a nwAct="edit" name="vm-edit" href="#"><i class="fa fa-pencil"></i>$_("Edit")</a></li> - <li role="presentation"><a href="#" nwAct="clone" class='running-disabled' name="vm-clone"><i class="fa fa-copy"></i>$_("Clone")</a></li> + <li role="presentation"><a nwAct="clone" class='running-disabled' name="vm-clone" href="#"><i class="fa fa-copy"></i>$_("Clone")</a></li> + <li role="presentation"><a nwAct="migrate" name="vm-migrate" href="#"><i class="fa fa-exchange"></i>$_("Migrate")</a></li> <li role="presentation"><a nwAct="reset" class='shutoff-hidden non-persistent-disabled' name="vm-reset" href="#"><i class="fa fa-refresh"></i>$_("Reset")</a></li> <li role="presentation"><a nwAct="pause" class='pause-hidden non-persistent-disabled' name="vm-pause" href="#"><i class="fa fa-pause"></i>$_("Pause")</a></li> <li role="presentation"><a nwAct="resume" class='resume-hidden' name="vm-resume" href="#"><i class="fa fa-play-circle"></i>$_("Resume")</a></li> @@ -95,6 +96,4 @@ </div> </span><!-- --><span class="item-hidden">$_("Network I/O")</span> - </li> - - + </li> \ No newline at end of file -- 1.8.3.1

From: samhenri <samuel.guimaraes@eldorado.org.br> Signed-off-by: samhenri <samuel.guimaraes@eldorado.org.br> --- model/vms.py | 2 +- ui/css/kimchi.css | 21 +++-- ui/css/src/modules/_guests.scss | 6 +- ui/js/src/kimchi.guest_livemigration.js | 136 ++++++++++++++++++++++++++++++++ ui/js/src/kimchi.guest_main.js | 94 +++++++++++----------- ui/pages/guest-migration.html.tmpl | 78 +++++++++--------- ui/pages/guest.html.tmpl | 9 ++- ui/pages/i18n.json.tmpl | 2 + ui/pages/tabs/guests.html.tmpl | 4 +- 9 files changed, 255 insertions(+), 97 deletions(-) create mode 100644 ui/js/src/kimchi.guest_livemigration.js diff --git a/model/vms.py b/model/vms.py index 3faee5a..5775ff3 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1729,7 +1729,7 @@ class VMModel(object): 'non_shared': non_shared, 'remote_host': remote_host, 'user': user} - task_id = add_task('/vms/%s/migrate' % name, self._migrate_task, + task_id = add_task('/plugins/kimchi/vms/%s/migrate' % name, self._migrate_task, self.objstore, params) return self.task.lookup(task_id) diff --git a/ui/css/kimchi.css b/ui/css/kimchi.css index ec57c93..d74d5cc 100644 --- a/ui/css/kimchi.css +++ b/ui/css/kimchi.css @@ -78,7 +78,7 @@ #guest-add-window.modal-content label.box-iso-outer span.box-iso-border { display: block; border: 3px solid transparent; - transition: all .1s ease-in-out; + transition: all 0.1s ease-in-out; } #template-add-window.modal-content label.box-iso-outer .iso-radio-hidden:checked + span.box-iso-border, @@ -100,7 +100,7 @@ display: block; border: 1px solid transparent; background: #fff; - transition: all .1s ease-in-out; + transition: all 0.1s ease-in-out; } #template-add-window.modal-content ul.list-template, @@ -606,6 +606,12 @@ display: none !important; } +#guest-content-container .wok-guest-list .wok-guest-list-header .btn .guest-pending, +#guest-content-container .wok-guest-list .wok-guest-list-body .btn .guest-pending { + margin-left: -38px; + margin-right: -45px; +} + #guest-content-container .wok-guest-list .distro-icon { background-color: transparent; background-size: 27px 27px; @@ -997,7 +1003,7 @@ border: 1px solid #eee !important; margin-bottom: 3px; display: block; - padding: .2em .6em .3em; + padding: 0.2em 0.6em 0.3em; font-weight: 700; line-height: 1; text-align: left; @@ -1240,7 +1246,7 @@ height: 664px; width: 2164px; left: 0; - transition: left .2s ease-in-out; + transition: left 0.2s ease-in-out; } #template-add-window.modal-content p { @@ -1455,7 +1461,9 @@ padding-right: 35px !important; } -#templates-root-container .wok-vm-gallery .item-hidden.column-type, #templates-root-container .wok-vm-gallery .item-hidden.column-version, #templates-root-container .wok-vm-gallery .item-hidden.column-processors { +#templates-root-container .wok-vm-gallery .item-hidden.column-type, +#templates-root-container .wok-vm-gallery .item-hidden.column-version, +#templates-root-container .wok-vm-gallery .item-hidden.column-processors { padding-bottom: 11px; } @@ -1996,7 +2004,8 @@ white-space: nowrap; } -.storage-modal .filter-select.popable .popover ul li:hover, .storage-modal .filter-select.popable .popover ul li:focus, +.storage-modal .filter-select.popable .popover ul li:hover, +.storage-modal .filter-select.popable .popover ul li:focus, .storage-modal .storage-target-input .popover ul li:hover, .storage-modal .storage-target-input .popover ul li:focus, .storage-modal .storage-add-input .popover ul li:hover, diff --git a/ui/css/src/modules/_guests.scss b/ui/css/src/modules/_guests.scss index d302fdc..9250bcb 100644 --- a/ui/css/src/modules/_guests.scss +++ b/ui/css/src/modules/_guests.scss @@ -312,6 +312,10 @@ > span.item-hidden { display: none !important; } + .btn .guest-pending { + margin-left: -38px; + margin-right: -45px; + } } .wok-guest-list .distro-icon { background-color: transparent; @@ -348,4 +352,4 @@ .wok-guest-list .wok-guest-list-body.inactive { color: $disabled-color !important; } -} +} \ No newline at end of file diff --git a/ui/js/src/kimchi.guest_livemigration.js b/ui/js/src/kimchi.guest_livemigration.js new file mode 100644 index 0000000..94c793b --- /dev/null +++ b/ui/js/src/kimchi.guest_livemigration.js @@ -0,0 +1,136 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013-2016 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +kimchi.guest_livemigration_main = function() { + kimchi.setupLiveMigrationFormEvent(); + kimchi.initLiveMigrationDialog(); +}; + + +kimchi.getOngoingMigration = function(guestName, toDelete) { + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri=' + encodeURIComponent('^/plugins/kimchi/vms/' + guestName + '/migrate'), function(tasks) { + for (var i = 0; i < tasks.length; i++) { + var guestUri = tasks[i].target_uri; + var guestName = guestUri.split('/')[4]; + guests[guestName] = tasks[i]; + + if (kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) { + continue; + } + + kimchi.trackTask(tasks[i].id, function(guests) { + if (toDelete) { + kimchi.deleteVM(guestName, function(result) { + return; + }); + } + kimchi.listVmsAuto(); + }, function(guests) { + return; + }, function(guests) { + return; + }); + } + }, null, true); + return guests; +}; + +kimchi.initLiveMigrationDialog = function(okCallback) { + $("#migrateFormOk").on("click", function() { + $("#migrateFormOk").prop("disabled", true); + $("#remote_host").prop("readonly", "readonly"); + $("#user").prop("readonly", "readonly"); + $("#password").prop("readonly", "readonly"); + $("#deleteVM").prop("readonly", "readonly"); + wok.window.close(); + kimchi.initLiveMigrationProccess(); + }); +}; + +kimchi.initLiveMigrationProccess = function() { + var obj = kimchi.getLiveMigrationInputValues(); + var toDelete = obj[kimchi.selectedGuest].toDelete; + kimchi.migrateGuest(kimchi.selectedGuest, obj[kimchi.selectedGuest].values, function() { + kimchi.listVmsAuto(); + kimchi.getOngoingMigration(kimchi.selectedGuest, toDelete); + }, function(err) { + wok.message.error(err.responseJSON.reason); + }); +} + +kimchi.getLiveMigrationInputValues = function() { + var host = $("#remote_host").val(); + var username = $("#user").val(); + var password = $("#password").val(); + var toDelete = $("#deleteVM").prop('checked'); + var data = {}; + data[kimchi.selectedGuest] = { + values: { + remote_host: host + }, + toDelete: toDelete + + }; + if (username && password) { + data[kimchi.selectedGuest].values.user = username; + data[kimchi.selectedGuest].values.password = password; + } + return data; +}; + +kimchi.setupLiveMigrationFormEvent = function() { + $("#migrateFormOk").prop("disabled", true); + $("#remote_host").on("change keyup", function(event) { + if (!this.value) { + $(this).parent().addClass('has-error'); + } else { + $(this).parent().removeClass('has-error'); + } + kimchi.updateLiveMigrationButton(); + }); + $("#user").on("change keyup", function(event) { + if (this.value && !$("#password").val()) { + $("#user").parent().removeClass('has-warning'); + $("#password").parent().addClass('has-warning'); + } else { + $("#user").parent().removeClass('has-warning'); + $("#password").parent().removeClass('has-warning'); + } + kimchi.updateLiveMigrationButton(); + }); + $("#password").on("change keyup", function(event) { + if (this.value && !$("#user").val()) { + $("#user").parent().addClass('has-warning'); + } else { + $("#user").parent().removeClass('has-warning'); + $("#password").parent().removeClass('has-warning'); + kimchi.updateLiveMigrationButton(); + } + }); +}; + +kimchi.updateLiveMigrationButton = function() { + if ($("#remote_host").val()) { + if ($("input[type='text']").parent().hasClass("has-error") || $("input[type='text']").parent().hasClass("has-warning")) { + $("#migrateFormOk").prop("disabled", true); + } else { + $("#migrateFormOk").prop("disabled", false); + } + }; +}; \ No newline at end of file diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index 2d3ccf3..cabb84c 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -233,6 +233,14 @@ kimchi.vmedit = function(event) { }); }; +kimchi.vmmigrate = function(event) { + var button = event.target; + var vm = $(button).closest('li[name=guest]'); + var vm_id = $(vm).attr("id"); + kimchi.selectedGuest = vm_id; + wok.window.open('plugins/kimchi/guest-migration.html'); +}; + kimchi.openVmConsole = function(event) { var button = event.target; var vm = $(button).closest('li[name=guest]'); @@ -275,7 +283,7 @@ kimchi.initGuestFilter = function() { }; kimchi.resetGuestFilter = function() { - if(guestFilterList){ + if (guestFilterList) { $('#search_input').val(); listFiltered = false; } @@ -326,23 +334,34 @@ kimchi.listVmsAuto = function() { }, null, true); return guests; }; - var getMigratingGuests = function(){ - var guests = []; - kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/.+/migrate'), function(tasks) { - for(var i=0;i<tasks.length;i++){ - var guestUri = tasks[i].target_uri; - var guestName = guestUri.split('/')[4] - guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isMigrating: true})); - if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) - kimchi.trackTask(tasks[i].id, null, function(err){ - wok.message.error(err.message); - }, null); - } - }, null, true); - return guests; - }; + + var getMigratingGuests = function() { + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri=' + encodeURIComponent('^/plugins/kimchi/vms/.+/migrate'), function(tasks) { + for (var i = 0; i < tasks.length; i++) { + var guestUri = tasks[i].target_uri; + var guestName = guestUri.split('/')[4] + guests.push($.extend({}, kimchi.sampleGuestObject, { + name: guestName, + isMigrating: true + })); + if (kimchi.trackingTasks.indexOf(tasks[i].id) == -1) + kimchi.trackTask(tasks[i].id, null, function(err) { + wok.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; + kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus == "success") { + var migrated = getMigratingGuests(); + for (i = migrated.length - 1; i >= 0; i--) { + for (j = result.length - 1; j >= 0; j--) { + if (result[j].name == migrated[i].name) result.splice(j, 1); + } + } result = getMigratingGuests().concat(result); result = getCloningGuests().concat(result); result = getCreatingGuests().concat(result); @@ -400,27 +419,6 @@ kimchi.listVmsAuto = function() { kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { var result = kimchi.guestElem.clone(); - - var initializeMigratePanel = function() { - $("#migrateFormOk").on("click", function() { - //TODO: Get values from UI for remote_host, user, password - var data = { - "remote_host" : "ltc-hab1.aus.stglabs.ibm.com", - "user" : "root", - "password" : "passw0rd" - }; - //TODO: Need to get guest to be passed in here - kimchi.migrateGuest(guest, data, function(){ - kimchi.listVmsAuto(); - wok.window.close(); - }, function(err) { - wok.message.error(err.responseJSON.reason); - }); - }); - } - - initializeMigratePanel(); - //Setup the VM list entry var currentState = result.find('.guest-state'); var vmRunningBool = (vmObject.state == "running"); @@ -440,7 +438,6 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Add the OS Type and Icon var osType = result.find('.distro-icon'); - console.log(vmObject); if (vmObject.icon == 'plugins/kimchi/images/icon-fedora.png') { osType.addClass('icon-fedora'); osType.attr('val', 'Fedora'); @@ -475,7 +472,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM console thumbnail display var curImg = vmObject.icon; if (vmObject.screenshot) { - curImg = vmObject.screenshot.replace(/^\//,''); + curImg = vmObject.screenshot.replace(/^\//, ''); } var load_src = curImg || 'plugins/kimchi/images/icon-vm.png'; var tile_src = prevScreenImage || vmObject['load-src']; @@ -493,7 +490,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, and the paused tile to resume - if (!(vmObject.isCloning || vmObject.isCreating)) { + if (!(vmObject.isCloning || vmObject.isCreating || vmObject.isMigrating)) { if (vmPoweredOffBool) { liveTile.off("click", function(event) { event.preventDefault(); @@ -666,7 +663,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } //Setup action event handlers - if(!(vmObject.isCloning || vmObject.isCreating || vmObject.isMigrating)){ + if (!(vmObject.isCloning || vmObject.isCreating || vmObject.isMigrating)) { guestActions.find("[name=vm-start]").on("click", function(event) { event.preventDefault(); @@ -725,18 +722,19 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { }); }, null); }); - guestActions.find("[name=vm-migrate]").click(function(){ - var guest = $(this).closest('li[name=guest]').attr("id"); - wok.window.open('plugins/kimchi/guest-migration.html'); + guestActions.find("[name=vm-migrate]").on('click', function(event) { + event.preventDefault(); + kimchi.vmmigrate(event); }); } else { guestActions.find('.btn').attr('disabled', true); - result.find('.guest-pending').removeClass('hide-content'); + result.find('.guest-done').addClass('hidden'); + result.find('.guest-pending').removeClass('hidden'); pendingText = result.find('.guest-pending .text') if (vmObject.isCloning) pendingText.text(i18n['KCHAPI6009M']); - else if(vmObject.isMigrating) - pendingText.text("Migrating"); + else if (vmObject.isMigrating) + pendingText.text(i18n['KCHAPI6012M']); else pendingText.text(i18n['KCHAPI6008M']); } @@ -770,4 +768,4 @@ kimchi.editTemplate = function(guestTemplate, oldPopStat) { return guestTemplate.replace("vm-action", "vm-action open"); } return guestTemplate; -}; +}; \ No newline at end of file diff --git a/ui/pages/guest-migration.html.tmpl b/ui/pages/guest-migration.html.tmpl index 278d22c..7065605 100644 --- a/ui/pages/guest-migration.html.tmpl +++ b/ui/pages/guest-migration.html.tmpl @@ -21,45 +21,47 @@ #silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang, fallback=True) #silent _ = t.gettext #silent _t = t.gettext -<div id="migrate-guest-window" class="window modal-content"> - <div class="modal-header"> - <h4 class="modal-title" id="migrateModalLabel">$_("Migrate a Guest")</h4> - </div> - <div id="migrateInfo" class="modal-body"> - <div class="alert alert-warning" role="alert">Disclaimer: This process cannot be stopped after started, - can take a long time to complete and will turn off the VM on this Hypervisor when it is successfully - migrated to the remote destination. - </div> - <div class="form-group"> - <label for="remoteHostName">$_("Remote Server")</label> - <input type="text" class="form-control" id="remoteHostName" /> - <p class="help-block"> - <i class="fa fa-info-circle"></i> $_("IP Address or Hostname")</p> - </div> - <div class="alert alert-info" role="alert">The following fields are optional. Fill them if you want Kimchi to - setup a password-less ssh session between the localhost and the remote host. The setup process will only - be successful if the user has 'SUDO ALL' permission in the remote machine. +<!DOCTYPE html> +<html> +<body> + <div id="migrate-guest-window" class="window modal-content"> + <div class="modal-header"> + <h4 class="modal-title" id="migrateModalLabel">$_("Migrate a Guest")</h4> </div> - <div class="form-group"> - <label for="user">$_("User")</label> - <input type="text" class="form-control" id="user" /> - <p class="help-block"> - <i class="fa fa-info-circle"></i> $_("Username of the remote host")</p> + <div id="migrateInfo" class="modal-body"> + <span id="alert-modal-container"></span> + <div class="alert alert-warning" role="alert">$_("Disclaimer: This process cannot be stopped after started, can take a long time to complete and will turn off the VM on this Hypervisor when it is successfully migrated to the remote destination.")</div> + <div class="form-group"> + <label for="remote_host">$_("Remote Server")</label> + <input type="text" class="form-control" id="remote_host" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("IP Address or Hostname")</p> + </div> + <div class="alert alert-info" role="alert">$_("The following fields are optional. Fill them if you want Kimchi to setup a password-less ssh session between the localhost and the remote host. The setup process will only be successful if the user has 'SUDO ALL' permission in the remote machine.")</div> + <div class="form-group"> + <label for="user">$_("User")</label> + <input type="text" class="form-control" id="user" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("Username of the remote host")</p> + </div> + <div class="form-group"> + <label for="password">$_("Password")</label> + <input type="password" class="form-control" id="password" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("Password of the user in the remote host")</p> + </div> + <div class="form-group"> + <input id="deleteVM" class="wok-checkbox" type="checkbox" value="" /> + <label for="deleteVM" id="labelDeleteVM">$_("Delete this VM when the migration is completed") </label> + </div> </div> - <div class="form-group"> - <label for="password">$_("Password")</label> - <input type="password" class="form-control" id="password" /> - <p class="help-block"> - <i class="fa fa-info-circle"></i> $_("Password of the user in the remote host")</p> + <div class="modal-footer"> + <button type="submit" id="migrateFormOk" class="btn btn-default">$_("Start")</button> + <button type="button" id="migrateFormCancel" data-dismiss="modal" class="btn btn-default">$_("Cancel")</button> </div> - <div class="form-group"> - <input id="deleteVM" class="wok-checkbox" type="checkbox" value="" /> - <label for="deleteVM" id="labelDeleteVM">$_("Delete this VM when the migration is completed") </label> - </div> - </div> - <div class="modal-footer"> - <button type="submit" id="migrateFormOk" class="btn btn-default">$_("Start")</button> - <button type="button" id="migrateFormCancel" data-dismiss="modal" class="btn btn-default">$_("Cancel")</button> </div> -</div> - + <script> + kimchi.guest_livemigration_main(); + </script> +</body> +</html> diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 080d918..6d189ef 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -34,7 +34,14 @@ --><span class='column-action pull-right'> <span class="pull-right"> <div class="dropdown menu-flat guest-actions" name="guest-actions" style="margin-top: 6px"> - <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false"><span class="edit-alt"></span>$_("Actions")<span class="caret"></span> + <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false"> + <span class="guest-done"> + <span class="edit-alt"></span>$_("Actions")<span class="caret"></span> + </span> + <span class="guest-pending hidden"> + <span class="wok-loading-icon"></span> + <span class="text"></span> + </span> </button> <ul class="dropdown-menu" role="menu"> <li role="presentation"><a nwAct="connect-vnc" class='shutoff-disabled' name="vm-console" href="#"><i class="fa fa-list-alt"></i>$_("Connect VNC")</a></li> diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl index c1979c6..f940ee5 100644 --- a/ui/pages/i18n.json.tmpl +++ b/ui/pages/i18n.json.tmpl @@ -44,6 +44,8 @@ "KCHAPI6008M": "$_("Creating...")", "KCHAPI6009M": "$_("Cloning...")", "KCHAPI6010M": "$_("Saving...")", + "KCHAPI6011M": "$_("Start")", + "KCHAPI6012M": "$_("Migrating...")", "KCHTMPL6001W": "$_("No ISO found")", diff --git a/ui/pages/tabs/guests.html.tmpl b/ui/pages/tabs/guests.html.tmpl index 21d1f90..5409dfb 100644 --- a/ui/pages/tabs/guests.html.tmpl +++ b/ui/pages/tabs/guests.html.tmpl @@ -44,7 +44,7 @@ <span class="icon-bar"></span> <span class="icon-bar"></span> </button> - </div> + </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="toolbar"> <ul class="nav navbar-nav navbar-right tools" display="none"> @@ -60,7 +60,7 @@ <label for="search_input" class="sr-only">$_("Filter"):</label> <input type="text" class="filter form-control search" id="search_input" placeholder="$_("Filter")"> </div> - </div> + </div> <div id="alert-container"></div> <div id="guestListField" style="display: none"> <ul class="wok-guest-list"> -- 1.8.3.1

I approve this patch set. I believe that locking the origin VM in the UI during the migration process was a good design decision, given that after it completes the VM would be shut down anyway (or deleted). Aline, there were minors whitespaces and make check-local errors when applying the patch set. Feel free to decide if that warrants a v7 or you'll fix them when pushing to master. Daniel On 01/21/2016 02:20 PM, sguimaraes943@gmail.com wrote:
From: Samuel Guimarães <sguimaraes943@gmail.com>
v5 - Rebased initial checkin for live migration to new-ui; - Added form validation to optional fields (e.g. disable submit button if username is filled but password is empty, added warn classes to form fields); - Fixed error messages in modal window; - Delete VM once Live Migration is finished checkbox is working;
v6 - wok.window.close() destroys modal content when called. Before sending the necessary parameters to perform a migration to Kimchi API, all the input values including the "Delete..." checkbox had to be stored in an array including the VM name and then called a function to keep track of the ongoing tasks and verify if the VM should be deleted based on the checkbox value. - Rewrote most of the JS in kimchi.guest_livemigration.js - Fixed asynchronous live migration proccess. - Fixed some minor issues with Guests main page when Cloning, Creating and Migrating VMs (this wasn't listed as a bug nor enhancement on Github but it had to be fixed in order to show "Migrating..." text on the Action button for List and Gallery views) - Small change in SCSS and CSS to fix Action button. - Now when a VM is migrating it is removed from the list and attached to the top just like when it is cloning or creating a new VM. In order to keep the migrating guest in the same position it would require other js libraries or more prototype functions just to handle the arrays in this screen. - Had to change a line in /models/vms.py because getTasksByFilter() wasn't working when trying to get all the migrating guests by "/plugins/kimchi/vms/.+/migrate"
Socorro Stoppler (1): Initial checkin for live migration UI support
samhenri (1): Live Migration front-end
model/vms.py | 2 +- ui/css/kimchi.css | 21 +++-- ui/css/src/modules/_guests.scss | 6 +- ui/js/src/kimchi.api.js | 3 +- ui/js/src/kimchi.guest_livemigration.js | 136 ++++++++++++++++++++++++++++++++ ui/js/src/kimchi.guest_main.js | 58 +++++++++++--- ui/pages/guest-migration.html.tmpl | 67 ++++++++++++++++ ui/pages/guest.html.tmpl | 16 ++-- ui/pages/i18n.json.tmpl | 2 + ui/pages/tabs/guests.html.tmpl | 4 +- 10 files changed, 290 insertions(+), 25 deletions(-) create mode 100644 ui/js/src/kimchi.guest_livemigration.js create mode 100644 ui/pages/guest-migration.html.tmpl
participants (3)
-
Aline Manera
-
Daniel Henrique Barboza
-
sguimaraes943@gmail.com