]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/window/Migrate.js
ui: guest import: move live import checkbox into panel
[pve-manager.git] / www / manager6 / window / Migrate.js
index e022bee7faf7746b473d041bc005b8de22257de0..5473821b9416b70accefa9e4adba0883979f62be 100644 (file)
@@ -1,4 +1,3 @@
-/*jslint confusion: true*/
 Ext.define('PVE.window.Migrate', {
     extend: 'Ext.window.Window',
 
@@ -15,11 +14,11 @@ Ext.define('PVE.window.Migrate', {
            running: false,
            qemu: {
                onlineHelp: 'qm_migration',
-               commonName: 'VM'
+               commonName: 'VM',
            },
            lxc: {
                onlineHelp: 'pct_migration',
-               commonName: 'CT'
+               commonName: 'CT',
            },
            migration: {
                possible: true,
@@ -28,14 +27,14 @@ Ext.define('PVE.window.Migrate', {
                mode: undefined,
                allowedNodes: undefined,
                overwriteLocalResourceCheck: false,
-               hasLocalResources: false
-           }
+               hasLocalResources: false,
+           },
 
        },
 
        formulas: {
            setMigrationMode: function(get) {
-               if (get('running')){
+               if (get('running')) {
                    if (get('vmtype') === 'qemu') {
                        return gettext('Online');
                    } else {
@@ -59,8 +58,8 @@ Ext.define('PVE.window.Migrate', {
                } else {
                    return false;
                }
-           }
-       }
+           },
+       },
     },
 
     controller: {
@@ -70,8 +69,8 @@ Ext.define('PVE.window.Migrate', {
                validityChange: function(panel, isValid) {
                    this.getViewModel().set('migration.possible', isValid);
                    this.checkMigratePreconditions();
-               }
-           }
+               },
+           },
        },
 
        init: function(view) {
@@ -93,20 +92,17 @@ Ext.define('PVE.window.Migrate', {
            }
            vm.set('vmtype', view.vmtype);
 
-
            view.setTitle(
-               Ext.String.format('{0} {1} {2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid)
+               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
+               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
+       onTargetChange: function(nodeSelector) {
+           // Always display the storages of the currently seleceted migration target
            this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value);
            this.checkMigratePreconditions();
        },
@@ -118,7 +114,7 @@ Ext.define('PVE.window.Migrate', {
 
            var values = me.lookup('formPanel').getValues();
            var params = {
-               target: values.target
+               target: values.target,
            };
 
            if (vm.get('migration.mode')) {
@@ -127,13 +123,14 @@ Ext.define('PVE.window.Migrate', {
            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')) {
+           //offline migration to a different storage currently might fail at a late stage
+           //(i.e. after some disks have been moved), so don't expose it yet in the GUI
+           if (vm.get('migration.with-local-disks') && vm.get('running') && values.targetstorage) {
                params.targetstorage = values.targetstorage;
            }
 
            if (vm.get('migration.overwriteLocalResourceCheck')) {
-               params['force'] = 1;
+               params.force = 1;
            }
 
            Proxmox.Utils.API2Request({
@@ -150,16 +147,15 @@ Ext.define('PVE.window.Migrate', {
 
                    Ext.create('Proxmox.window.TaskViewer', {
                        upid: upid,
-                       extraTitle: extraTitle
+                       extraTitle: extraTitle,
                    }).show();
 
                    view.close();
-               }
+               },
            });
-
        },
 
-       checkMigratePreconditions: function(resetMigrationPossible) {
+       checkMigratePreconditions: async function(resetMigrationPossible) {
            var me = this,
                vm = me.getViewModel();
 
@@ -169,23 +165,23 @@ Ext.define('PVE.window.Migrate', {
                vm.set('running', true);
            }
 
+           me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')];
+
            if (vm.get('vmtype') === 'qemu') {
-               me.checkQemuPreconditions(resetMigrationPossible);
+               await me.checkQemuPreconditions(resetMigrationPossible);
            } else {
                me.checkLxcPreconditions(resetMigrationPossible);
            }
-           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(resetMigrationPossible) {
-           var me = this,
+       checkQemuPreconditions: async function(resetMigrationPossible) {
+           let me = this,
                vm = me.getViewModel(),
                migrateStats;
 
@@ -193,112 +189,135 @@ Ext.define('PVE.window.Migrate', {
                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);
+           try {
+               if (me.fetchingNodeMigrateInfo && me.fetchingNodeMigrateInfo === vm.get('nodename')) {
+                   return;
+               }
+               me.fetchingNodeMigrateInfo = vm.get('nodename');
+               let { result } = await Proxmox.Async.api2({
+                   url: `/nodes/${vm.get('nodename')}/${vm.get('vmtype')}/${vm.get('vmid')}/migrate`,
+                   method: 'GET',
+               });
+               migrateStats = result.data;
+               me.fetchingNodeMigrateInfo = false;
+           } catch (error) {
+               Ext.Msg.alert(gettext('Error'), error.htmlStatus);
+               return;
+           }
+
+           if (migrateStats.running) {
+               vm.set('running', true);
+           }
+           // Get migration object from viewmodel to prevent to many bind callbacks
+           let migration = vm.get('migration');
+           if (resetMigrationPossible) {
+               migration.possible = true;
+           }
+           migration.preconditions = [];
+
+           if (migrateStats.allowed_nodes) {
+               migration.allowedNodes = migrateStats.allowed_nodes;
+               let target = me.lookup('pveNodeSelector').value;
+               if (target.length && !migrateStats.allowed_nodes.includes(target)) {
+                   let disallowed = migrateStats.not_allowed_nodes[target] ?? {};
+                   if (disallowed.unavailable_storages !== undefined) {
+                       let missingStorages = disallowed.unavailable_storages.join(', ');
+
+                       migration.possible = false;
+                       migration.preconditions.push({
+                           text: 'Storage (' + missingStorages + ') not available on selected target. ' +
+                             'Start VM to use live storage migration or select other target node',
+                           severity: 'error',
+                       });
                    }
-                   // Get migration object from viewmodel to prevent
-                   // to many bind callbacks
-                   var migration = vm.get('migration');
-                   if (resetMigrationPossible) migration.possible = true;
-                   migration.preconditions = [];
-
-                   if (migrateStats.allowed_nodes) {
-                       migration.allowedNodes = migrateStats.allowed_nodes;
-                       var target = me.lookup('pveNodeSelector').value;
-                       if (target.length && !migrateStats.allowed_nodes.includes(target)) {
-                           let disallowed = migrateStats.not_allowed_nodes[target];
-                           let missing_storages = disallowed.unavailable_storages.join(', ');
 
-                           migration.possible = false;
-                           migration.preconditions.push({
-                               text: 'Storage (' + missing_storages + ') not available on selected target. ' +
-                                 'Start VM to use live storage migration or select other target node',
-                               severity: 'error'
-                           });
-                       }
+                   if (disallowed['unavailable-resources'] !== undefined) {
+                       let unavailableResources = disallowed['unavailable-resources'].join(', ');
+
+                       migration.possible = false;
+                       migration.preconditions.push({
+                           text: 'Mapped Resources (' + unavailableResources + ') not available on selected target. ',
+                           severity: 'error',
+                       });
                    }
+               }
+           }
+
+           let blockingResources = [];
+           let mappedResources = migrateStats['mapped-resources'] ?? [];
+
+           for (const res of migrateStats.local_resources) {
+               if (mappedResources.indexOf(res) === -1) {
+                   blockingResources.push(res);
+               }
+           }
+
+           if (blockingResources.length) {
+               migration.hasLocalResources = true;
+               if (!migration.overwriteLocalResourceCheck || vm.get('running')) {
+                   migration.possible = false;
+                   migration.preconditions.push({
+                       text: Ext.String.format('Can\'t migrate VM with local resources: {0}',
+                       blockingResources.join(', ')),
+                       severity: 'error',
+                   });
+               } else {
+                   migration.preconditions.push({
+                       text: Ext.String.format('Migrate VM with local resources: {0}. ' +
+                       'This might fail if resources aren\'t available on the target node.',
+                       blockingResources.join(', ')),
+                       severity: 'warning',
+                   });
+               }
+           }
 
-                   if (migrateStats.local_resources.length) {
-                       migration.hasLocalResources = true;
-                       if(!migration.overwriteLocalResourceCheck || vm.get('running')){
+           if (mappedResources && mappedResources.length) {
+               if (vm.get('running')) {
+                   migration.possible = false;
+                   migration.preconditions.push({
+                       text: Ext.String.format('Can\'t migrate running VM with mapped resources: {0}',
+                       mappedResources.join(', ')),
+                       severity: 'error',
+                   });
+               }
+           }
+
+           if (migrateStats.local_disks.length) {
+               migrateStats.local_disks.forEach(function(disk) {
+                   if (disk.cdrom && disk.cdrom === 1) {
+                       if (!disk.volid.includes('vm-' + vm.get('vmid') + '-cloudinit')) {
                            migration.possible = false;
                            migration.preconditions.push({
-                               text: Ext.String.format('Can\'t migrate VM with local resources: {0}',
-                               migrateStats.local_resources.join(', ')),
-                               severity: 'error'
-                           });
-                       } else {
-                           migration.preconditions.push({
-                               text: Ext.String.format('Migrate VM with local resources: {0}. ' +
-                               'This might fail if resources aren\'t available on the target node.',
-                               migrateStats.local_resources.join(', ')),
-                               severity: 'warning'
+                               text: "Can't migrate VM with local CD/DVD",
+                               severity: 'error',
                            });
                        }
-                   }
-
-                   if (migrateStats.local_disks.length) {
-
-                       migrateStats.local_disks.forEach(function (disk) {
-                           if (disk.cdrom && disk.cdrom === 1) {
-                               if (disk.volid.includes('vm-'+vm.get('vmid')+'-cloudinit')) {
-                                   if (migrateStats.running) {
-                                       migration.possible = false;
-                                       migration.preconditions.push({
-                                            text: "Can't live migrate VM with local cloudinit disk, use shared storage instead",
-                                            severity: 'error'
-                                       });
-                                   } else {
-                                       return;
-                                   }
-                               } else {
-                                   migration.possible = false;
-                                   migration.preconditions.push({
-                                       text: "Can't migrate VM with local CD/DVD",
-                                       severity: 'error'
-                                   });
-                               }
-                           } else {
-                               migration['with-local-disks'] = 1;
-                               migration.preconditions.push({
-                                   text:'Migration with local disk might take long: ' + disk.volid
-                                       +' (' + PVE.Utils.render_size(disk.size) + ')',
-                                   severity: 'warning'
-                               });
-                           }
+                   } else {
+                       let size = disk.size ? '(' + Proxmox.Utils.render_size(disk.size) + ')' : '';
+                       migration['with-local-disks'] = 1;
+                       migration.preconditions.push({
+                           text: Ext.String.format('Migration with local disk might take long: {0} {1}', disk.volid, size),
+                           severity: 'warning',
                        });
-
                    }
+               });
+           }
 
-                   vm.set('migration', migration);
-
-               }
-           });
+           vm.set('migration', migration);
        },
        checkLxcPreconditions: function(resetMigrationPossible) {
-           var me = this,
-               vm = me.getViewModel();
+           let vm = this.getViewModel();
            if (vm.get('running')) {
                vm.set('migration.mode', 'restart');
            }
-       }
-
-
+       },
     },
 
     width: 600,
     modal: true,
     layout: {
        type: 'vbox',
-       align: 'stretch'
+       align: 'stretch',
     },
     border: false,
     items: [
@@ -307,33 +326,31 @@ Ext.define('PVE.window.Migrate', {
            reference: 'formPanel',
            bodyPadding: 10,
            border: false,
-           layout: {
-               type: 'column'
-           },
+           layout: 'hbox',
            items: [
                {
                    xtype: 'container',
-                   columnWidth: 0.5,
+                   flex: 1,
                    items: [{
                        xtype: 'displayfield',
                        name: 'source',
                        fieldLabel: gettext('Source node'),
                        bind: {
-                           value: '{nodename}'
-                       }
+                           value: '{nodename}',
+                       },
                    },
                    {
                        xtype: 'displayfield',
                        reference: 'migrationMode',
                        fieldLabel: gettext('Mode'),
                        bind: {
-                           value: '{setMigrationMode}'
-                       }
-                   }]
+                           value: '{setMigrationMode}',
+                       },
+                   }],
                },
                {
                    xtype: 'container',
-                   columnWidth: 0.5,
+                   flex: 1,
                    items: [{
                        xtype: 'pveNodeSelector',
                        reference: 'pveNodeSelector',
@@ -343,8 +360,8 @@ Ext.define('PVE.window.Migrate', {
                        disallowedNodes: undefined,
                        onlineValidator: true,
                        listeners: {
-                           change: 'onTargetChange'
-                       }
+                           change: 'onTargetChange',
+                       },
                    },
                    {
                            xtype: 'pveStorageSelector',
@@ -352,9 +369,12 @@ Ext.define('PVE.window.Migrate', {
                            name: 'targetstorage',
                            fieldLabel: gettext('Target storage'),
                            storageContent: 'images',
+                           allowBlank: true,
+                           autoSelect: false,
+                           emptyText: gettext('Current layout'),
                            bind: {
-                               hidden: '{setStorageselectorHidden}'
-                           }
+                               hidden: '{setStorageselectorHidden}',
+                           },
                    },
                    {
                        xtype: 'proxmoxcheckbox',
@@ -362,18 +382,21 @@ Ext.define('PVE.window.Migrate', {
                        fieldLabel: gettext('Force'),
                        autoEl: {
                            tag: 'div',
-                           'data-qtip': 'Overwrite local resources unavailable check'
+                           'data-qtip': 'Overwrite local resources unavailable check',
                        },
                        bind: {
                            hidden: '{setLocalResourceCheckboxHidden}',
-                           value: '{migration.overwriteLocalResourceCheck}'
+                           value: '{migration.overwriteLocalResourceCheck}',
                        },
                        listeners: {
-                           change: {fn: 'checkMigratePreconditions', extraArg: true}
-                       }
-               }]
-               }
-           ]
+                           change: {
+                               fn: 'checkMigratePreconditions',
+                               extraArg: true,
+                           },
+                       },
+               }],
+               },
+           ],
        },
        {
            xtype: 'gridpanel',
@@ -393,22 +416,23 @@ Ext.define('PVE.window.Migrate', {
                            return v;
                    }
                },
-               width: 35
+               width: 35,
            },
            {
                text: 'Info',
                dataIndex: 'text',
                cellWrap: true,
-               flex: 1
+               flex: 1,
            }],
            bind: {
                hidden: '{!migration.preconditions.length}',
                store: {
-                   fields: ['severity','text'],
-                   data: '{migration.preconditions}'
-               }
-           }
-       }
+                   fields: ['severity', 'text'],
+                   data: '{migration.preconditions}',
+                   sorters: 'text',
+               },
+           },
+       },
 
     ],
     buttons: [
@@ -417,7 +441,7 @@ Ext.define('PVE.window.Migrate', {
            reference: 'proxmoxHelpButton',
            onlineHelp: 'pct_migration',
            listenToGlobalEvent: false,
-           hidden: false
+           hidden: false,
        },
        '->',
        {
@@ -426,8 +450,8 @@ Ext.define('PVE.window.Migrate', {
            text: gettext('Migrate'),
            handler: 'startMigration',
            bind: {
-               disabled: '{!migration.possible}'
-           }
-       }
-    ]
+               disabled: '{!migration.possible}',
+           },
+       },
+    ],
 });