This patch depends on:
qemu-server: e1f0fbf4448b374eb9a19502aee565adb5be7ec0

This patch refactors the migrate ui to incoperate the viewmodel approach
which should help if we need to add functionality in future iterations.
Additionally it is now possible to migrate with local disks.

Signed-off-by: Tim Marx <t.m...@proxmox.com>
---
changes since v1:
* added error message why seleceted node is not allowed
* layout change 2 columns

 www/manager6/window/Migrate.js | 418 ++++++++++++++++++++++++++++++-----------
 1 file changed, 309 insertions(+), 109 deletions(-)

diff --git a/www/manager6/window/Migrate.js b/www/manager6/window/Migrate.js
index 9395a97f..3a305f32 100644
--- a/www/manager6/window/Migrate.js
+++ b/www/manager6/window/Migrate.js
@@ -1,94 +1,341 @@
+/*jslint confusion: true*/
 Ext.define('PVE.window.Migrate', {
     extend: 'Ext.window.Window',

-    config: {
-       vmtype: undefined,
-       nodename: undefined,
-       vmid: undefined
+
+    vmtype: undefined,
+    nodename: undefined,
+    vmid: undefined,
+
+    viewModel: {
+       data: {
+           vmid: undefined,
+           nodename: undefined,
+           vmtype: undefined,
+           running: false,
+           qemu: {
+               onlineHelp: 'qm_migration',
+               commonName: 'VM'
+           },
+           lxc: {
+               onlineHelp: 'pct_migration',
+               commonName: 'CT'
+           },
+           migration: {
+               possible: true,
+               preconditions: [],
+               'with-local-disks': 0,
+               mode: undefined,
+               allowedNodes: undefined
+           }
+
+       },
+
+       formulas: {
+           setMigrationMode: function(get) {
+               if (get('running')){
+                   if (get('vmtype') === 'qemu') {
+                       return gettext('Online');
+                   } else {
+                       return gettext('Restart Mode');
+                   }
+               } else {
+                   return gettext('Offline');
+               }
+           },
+           setStorageselectorHidden: function(get) {
+                   if (get('migration.with-local-disks') && get('running')) {
+                       return false;
+                   } else {
+                       return true;
+                   }
+           }
+       }
     },
- // private, used to store the migration mode after checking if the guest runs
-    liveMode: undefined,

     controller: {
        xclass: 'Ext.app.ViewController',
        control: {
            'panel[reference=formPanel]': {
                validityChange: function(panel, isValid) {
-                   this.lookup('submitButton').setDisabled(!isValid);
+                   this.getViewModel().set('migration.possible', isValid);
+                   this.checkMigratePreconditions();
                }
-           },
-           'button[reference=submitButton]': {
-               click: function() {
-                   var me = this;
-                   var view = me.getView();
-
-                   var values = me.lookup('formPanel').getValues();
-                   var params = {
-                       target: values.target
-                   };
-
-                   if (view.liveMode) {
-                       params[view.liveMode] = 1;
+           }
+       },
+
+       init: function(view) {
+           var me = this,
+               vm = view.getViewModel();
+
+           if (!view.nodename) {
+               throw "missing custom view config: nodename";
+           }
+           vm.set('nodename', view.nodename);
+
+           if (!view.vmid) {
+               throw "missing custom view config: vmid";
+           }
+           vm.set('vmid', view.vmid);
+
+           if (!view.vmtype) {
+               throw "missing custom view config: vmtype";
+           }
+           vm.set('vmtype', view.vmtype);
+
+
+           view.setTitle(
+               Ext.String.format('{0} {1}{2}', gettext('Migrate'), 
vm.get(view.vmtype).commonName, view.vmid)
+           );
+           me.lookup('proxmoxHelpButton').setHelpConfig({
+               onlineHelp: vm.get(view.vmtype).onlineHelp
+           });
+           me.checkMigratePreconditions();
+           me.lookup('formPanel').isValid();
+
+       },
+
+       onTargetChange: function (nodeSelector) {
+           //Always display the storages of the currently seleceted migration 
target
+           
this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value);
+           this.checkMigratePreconditions();
+       },
+
+       startMigration: function() {
+           var me = this,
+               view = me.getView(),
+               vm = me.getViewModel();
+
+           var values = me.lookup('formPanel').getValues();
+           var params = {
+               target: values.target
+           };
+
+           if (vm.get('migration.mode')) {
+               params[vm.get('migration.mode')] = 1;
+           }
+           if (vm.get('migration.with-local-disks')) {
+               params['with-local-disks'] = 1;
+           }
+           //only submit targetstorage if vm is running, storage migration to 
different storage is only possible online
+           if (vm.get('migration.with-local-disks') && vm.get('running')) {
+               params.targetstorage = values.targetstorage;
+           }
+
+           Proxmox.Utils.API2Request({
+               params: params,
+               url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + 
'/' + vm.get('vmid') + '/migrate',
+               waitMsgTarget: view,
+               method: 'POST',
+               failure: function(response, opts) {
+                   Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+               },
+               success: function(response, options) {
+                   var upid = response.result.data;
+                   var extraTitle = Ext.String.format(' ({0} ---> {1})', 
vm.get('nodename'), params.target);
+
+                   Ext.create('Proxmox.window.TaskViewer', {
+                       upid: upid,
+                       extraTitle: extraTitle
+                   }).show();
+
+                   view.close();
+               }
+           });
+
+       },
+
+       checkMigratePreconditions: function() {
+           var me = this,
+               vm = me.getViewModel();
+
+
+           var vmrec = PVE.data.ResourceStore.findRecord('vmid', 
vm.get('vmid'),
+                       0, false, false, true);
+           if (vmrec && vmrec.data && vmrec.data.running) {
+               vm.set('running', true);
+           }
+
+           if (vm.get('vmtype') === 'qemu') {
+               me.checkQemuPreconditions();
+           } else {
+               me.checkLxcPreconditions();
+           }
+           me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')];
+
+           // Only allow nodes where the local storage is available in case of 
offline migration
+           // where storage migration is not possible
+           me.lookup('pveNodeSelector').allowedNodes = 
vm.get('migration.allowedNodes');
+
+           me.lookup('formPanel').isValid();
+
+       },
+
+       checkQemuPreconditions: function() {
+           var me = this,
+               vm = me.getViewModel(),
+               migrateStats;
+
+           if (vm.get('running')) {
+               vm.set('migration.mode', 'online');
+           }
+
+           Proxmox.Utils.API2Request({
+               url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + 
'/' + vm.get('vmid') + '/migrate',
+               method: 'GET',
+               failure: function(response, opts) {
+                   Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+               },
+               success: function(response, options) {
+                   migrateStats = response.result.data;
+                   if (migrateStats.running) {
+                       vm.set('running', true);
                    }
+                   // Get migration object from viewmodel to prevent
+                   // to many bind callbacks
+                   var migration = vm.get('migration');
+                   migration.preconditions = [];
+
+                   if (migrateStats.allowed_nodes) {
+                       migration.allowedNodes = migrateStats.allowed_nodes;

-                   Proxmox.Utils.API2Request({
-                       params: params,
-                       url: '/nodes/' + view.nodename + '/' + view.vmtype + 
'/' + view.vmid + '/migrate',
-                       waitMsgTarget: view,
-                       method: 'POST',
-                       failure: function(response, opts) {
-                           Ext.Msg.alert(gettext('Error'), 
response.htmlStatus);
-                       },
-                       success: function(response, options) {
-                           var upid = response.result.data;
-                           var extraTitle = Ext.String.format(' ({0} ---> 
{1})', view.nodename, params.target);
-
-                           Ext.create('Proxmox.window.TaskViewer', {
-                               upid: upid,
-                               extraTitle: extraTitle
-                           }).show();
-
-                           view.close();
+                       if 
(!migrateStats.allowed_nodes.includes(me.lookup('pveNodeSelector').value)) {
+                           migration.possible = false;
+                           migration.preconditions.push({
+                               text: 'Local storage not available on selected 
Node, start VM to use live storage migration or select other target node',
+                               icon: '<i class="fa fa-times critical"></i>'
+                           });
                        }
-                   });
+                   }
+
+                   if (migrateStats.local_resources.length) {
+                       migration.possible = false;
+                       migration.preconditions.push({
+                           text: 'Can\'t migrate VM with local resources: '+ 
migrateStats.local_resources.join(', '),
+                           icon: '<i class="fa fa-times critical"></i>'
+                       });
+                   }
+
+                   if (migrateStats.local_disks.length) {
+
+                       migrateStats.local_disks.forEach(function (disk) {
+                           if (disk.cdrom && disk.cdrom === 1) {
+                               migration.possible = false;
+                               migration.preconditions.push({
+                                   text:'Can\'t migrate VM with local CD/DVD',
+                                   icon: '<i class="fa fa-times critical"></i>'
+                               });
+
+                           } else if (!disk.referenced_in_config) {
+                               migration.possible = false;
+                               migration.preconditions.push({
+                                   text: 'Found not referenced/unused disk via 
storage: '+ disk.volid,
+                                   icon: '<i class="fa fa-times critical"></i>'
+                               });
+                           } else {
+                               migration['with-local-disks'] = 1;
+                               migration.preconditions.push({
+                                   text:'Migration with local disk might take 
long: '+ disk.volid,
+                                   icon: '<i class="fa fa-exclamation-triangle 
warning"></i>'
+                               });
+                           }
+                       });
+
+                   }
+
+                   vm.set('migration', migration);
+
                }
+           });
+       },
+       checkLxcPreconditions: function() {
+           var me = this,
+               vm = me.getViewModel();
+           if (vm.get('running')) {
+               vm.set('migration.mode', 'restart');
            }
        }
+
+
     },

-    width: 350,
+    width: 700,
     modal: true,
-    layout: 'auto',
+    layout: {
+       type: 'vbox',
+       align: 'stretch'
+    },
     border: false,
-    resizable: false,
     items: [
        {
            xtype: 'form',
            reference: 'formPanel',
            bodyPadding: 10,
            border: false,
-           fieldDefaults: {
-               labelWidth: 100,
-               anchor: '100%'
+           layout: {
+               type: 'column'
            },
            items: [
                {
-                   xtype: 'pveNodeSelector',
-                   reference: 'pveNodeSelector',
-                   name: 'target',
-                   fieldLabel: gettext('Target node'),
-                   allowBlank: false,
-                   disallowedNodes: undefined,
-                   onlineValidator: true
+                   xtype: 'container',
+                   columnWidth: 0.5,
+                   items: [{
+                       xtype: 'pveNodeSelector',
+                       reference: 'pveNodeSelector',
+                       name: 'target',
+                       fieldLabel: gettext('Target node'),
+                       allowBlank: false,
+                       disallowedNodes: undefined,
+                       onlineValidator: true,
+                       listeners: {
+                           change: 'onTargetChange'
+                       }
+                   },
+                   {
+                       xtype: 'displayfield',
+                       reference: 'migrationMode',
+                       fieldLabel: gettext('Mode'),
+                       bind: {
+                           value: '{setMigrationMode}'
+                       }
+                   }
+                   ]
                },
                {
-                   xtype: 'displayfield',
-                   reference: 'migrationMode',
-                   fieldLabel: gettext('Mode'),
-                   value: gettext('Offline')
+                   xtype: 'container',
+                   columnWidth: 0.5,
+                   items: [
+                       {
+                               xtype: 'pveStorageSelector',
+                               reference: 'pveDiskStorageSelector',
+                               name: 'targetstorage',
+                               fieldLabel: gettext('Target Storage'),
+                               storageContent: 'images',
+                               bind: {
+                                   hidden: '{setStorageselectorHidden}'
+                               }
+                       }
+                   ]
+               }
+           ]
+       },
+       {
+           xtype: 'gridpanel',
+           reference: 'preconditionGrid',
+           flex: 1,
+           columns: [
+               {text: 'Severity', dataIndex: 'icon', width: 80},
+               {text: 'Info',  dataIndex: 'text', flex: 1}
+           ],
+           bind: {
+               hidden: '{!migration.preconditions.length}',
+               store: {
+                   fields: ['icon','text'],
+                   data: '{migration.preconditions}'
                }
-               ]
+           }
        }
+
     ],
     buttons: [
        {
@@ -102,58 +349,11 @@ Ext.define('PVE.window.Migrate', {
        {
            xtype: 'button',
            reference: 'submitButton',
-           text: gettext('Migrate')
-       }
-    ],
-
-    initComponent : function() {
-       var me = this;
-
-       if (!me.nodename) {
-           throw "no node name specified";
-       }
-
-       if (!me.vmid) {
-           throw "no VM ID specified";
-       }
-
-       if (!me.vmtype) {
-           throw "no VM type specified";
-       }
-
-       me.callParent();
-
-       var title = gettext('Migrate') + (' CT ') + me.vmid;
-       me.liveMode = 'restart';
-
-       if (me.vmtype === 'qemu') {
-           me.lookup('proxmoxHelpButton').setHelpConfig({
-               onlineHelp: 'qm_migration'
-           });
-           title = gettext('Migrate') + (' VM ') + me.vmid;
-           me.liveMode = 'online';
-       }
-
-       var running = false;
-       var vmrec = PVE.data.ResourceStore.findRecord('vmid', me.vmid,
-           0, false, false, true);
-       if (vmrec && vmrec.data && vmrec.data.running) {
-           running = true;
-       }
-
-       if (running) {
-           var displayField = me.lookup('migrationMode');
-           if (me.vmtype === 'qemu') {
-               displayField.setValue(gettext('Online'));
-               me.liveMode = 'online';
-           } else {
-               displayField.setValue(gettext('Restart Mode'));
-               me.liveMode = 'restart';
+           text: gettext('Migrate'),
+           handler: 'startMigration',
+           bind: {
+               disabled: '{!migration.possible}'
            }
        }
-
-       me.setTitle(title);
-       me.lookup('pveNodeSelector').disallowedNodes = [me.nodename];
-       me.lookup('formPanel').isValid();
-    }
+    ]
 });
\ No newline at end of file
--
2.11.0

_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to