[pve-devel] [PATCH manager] ui: migrate: refactor migrate window & add migration with local disks

Tim Marx t.marx at proxmox.com
Wed May 22 15:25:33 CEST 2019


This patch depends on the patches in qemu in this series:
f5677b949dfc3d8ffc317357ff31a2e7f9c86bc7
20bccadf52b34877a5e772111b243e5adc26b4a3

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 at proxmox.com>
---
 www/manager6/window/Migrate.js | 383 ++++++++++++++++++++++++++++++-----------
 1 file changed, 280 insertions(+), 103 deletions(-)

diff --git a/www/manager6/window/Migrate.js b/www/manager6/window/Migrate.js
index 9395a97f..30789212 100644
--- a/www/manager6/window/Migrate.js
+++ b/www/manager6/window/Migrate.js
@@ -1,76 +1,267 @@
+/*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);
+	},
+
+	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;
+		    }
+
+		    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);

-		    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();
-			}
-		    });
 		}
+	    });
+	},
+	checkLxcPreconditions: function() {
+	    var me = this,
+		vm = me.getViewModel();
+	    if (vm.get('running')) {
+		vm.set('migration.mode', 'restart');
 	    }
 	}
+
+
     },

-    width: 350,
+    width: 500,
     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%'
-	    },
 	    items: [
 		{
 		    xtype: 'pveNodeSelector',
@@ -79,16 +270,49 @@ Ext.define('PVE.window.Migrate', {
 		    fieldLabel: gettext('Target node'),
 		    allowBlank: false,
 		    disallowedNodes: undefined,
-		    onlineValidator: true
+		    onlineValidator: true,
+		    listeners: {
+			change: 'onTargetChange'
+		    }
 		},
 		{
 		    xtype: 'displayfield',
 		    reference: 'migrationMode',
 		    fieldLabel: gettext('Mode'),
-		    value: gettext('Offline')
+		    bind: {
+			value: '{setMigrationMode}'
+		    }
+		},
+		{
+		    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 +326,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




More information about the pve-devel mailing list