]> git.proxmox.com Git - pve-manager.git/commitdiff
ui: migrate: refactor migrate window & add migration with local disks
authorTim Marx <t.marx@proxmox.com>
Fri, 14 Jun 2019 12:35:36 +0000 (14:35 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Sat, 15 Jun 2019 09:15:30 +0000 (11:15 +0200)
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.marx@proxmox.com>
www/manager6/window/Migrate.js

index 9395a97fb0bbfd7ba4e1bbcfab1871b2c9eb47c0..3a305f32504280889897f45cf929f1ec98426176 100644 (file)
+/*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