
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