1 Ext
.define('PVE.window.Migrate', {
2 extend
: 'Ext.window.Window',
16 onlineHelp
: 'qm_migration',
20 onlineHelp
: 'pct_migration',
26 'with-local-disks': 0,
28 allowedNodes
: undefined,
29 overwriteLocalResourceCheck
: false,
30 hasLocalResources
: false,
36 setMigrationMode: function(get) {
38 if (get('vmtype') === 'qemu') {
39 return gettext('Online');
41 return gettext('Restart Mode');
44 return gettext('Offline');
47 setStorageselectorHidden: function(get) {
48 if (get('migration.with-local-disks') && get('running')) {
54 setLocalResourceCheckboxHidden: function(get) {
55 if (get('running') || !get('migration.hasLocalResources') ||
56 Proxmox
.UserName
!== 'root@pam') {
66 xclass
: 'Ext.app.ViewController',
68 'panel[reference=formPanel]': {
69 validityChange: function(panel
, isValid
) {
70 this.getViewModel().set('migration.possible', isValid
);
71 this.checkMigratePreconditions();
76 init: function(view
) {
78 vm
= view
.getViewModel();
81 throw "missing custom view config: nodename";
83 vm
.set('nodename', view
.nodename
);
86 throw "missing custom view config: vmid";
88 vm
.set('vmid', view
.vmid
);
91 throw "missing custom view config: vmtype";
93 vm
.set('vmtype', view
.vmtype
);
96 Ext
.String
.format('{0} {1} {2}', gettext('Migrate'), vm
.get(view
.vmtype
).commonName
, view
.vmid
),
98 me
.lookup('proxmoxHelpButton').setHelpConfig({
99 onlineHelp
: vm
.get(view
.vmtype
).onlineHelp
,
101 me
.lookup('formPanel').isValid();
104 onTargetChange: function(nodeSelector
) {
105 // Always display the storages of the currently seleceted migration target
106 this.lookup('pveDiskStorageSelector').setNodename(nodeSelector
.value
);
107 this.checkMigratePreconditions();
110 startMigration: function() {
113 vm
= me
.getViewModel();
115 var values
= me
.lookup('formPanel').getValues();
117 target
: values
.target
,
120 if (vm
.get('migration.mode')) {
121 params
[vm
.get('migration.mode')] = 1;
123 if (vm
.get('migration.with-local-disks')) {
124 params
['with-local-disks'] = 1;
126 //offline migration to a different storage currently might fail at a late stage
127 //(i.e. after some disks have been moved), so don't expose it yet in the GUI
128 if (vm
.get('migration.with-local-disks') && vm
.get('running') && values
.targetstorage
) {
129 params
.targetstorage
= values
.targetstorage
;
132 if (vm
.get('migration.overwriteLocalResourceCheck')) {
136 Proxmox
.Utils
.API2Request({
138 url
: '/nodes/' + vm
.get('nodename') + '/' + vm
.get('vmtype') + '/' + vm
.get('vmid') + '/migrate',
141 failure: function(response
, opts
) {
142 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
144 success: function(response
, options
) {
145 var upid
= response
.result
.data
;
146 var extraTitle
= Ext
.String
.format(' ({0} ---> {1})', vm
.get('nodename'), params
.target
);
148 Ext
.create('Proxmox.window.TaskViewer', {
150 extraTitle
: extraTitle
,
158 checkMigratePreconditions
: async
function(resetMigrationPossible
) {
160 vm
= me
.getViewModel();
162 var vmrec
= PVE
.data
.ResourceStore
.findRecord('vmid', vm
.get('vmid'),
163 0, false, false, true);
164 if (vmrec
&& vmrec
.data
&& vmrec
.data
.running
) {
165 vm
.set('running', true);
168 me
.lookup('pveNodeSelector').disallowedNodes
= [vm
.get('nodename')];
170 if (vm
.get('vmtype') === 'qemu') {
171 await me
.checkQemuPreconditions(resetMigrationPossible
);
173 me
.checkLxcPreconditions(resetMigrationPossible
);
176 // Only allow nodes where the local storage is available in case of offline migration
177 // where storage migration is not possible
178 me
.lookup('pveNodeSelector').allowedNodes
= vm
.get('migration.allowedNodes');
180 me
.lookup('formPanel').isValid();
183 checkQemuPreconditions
: async
function(resetMigrationPossible
) {
185 vm
= me
.getViewModel(),
188 if (vm
.get('running')) {
189 vm
.set('migration.mode', 'online');
193 if (me
.fetchingNodeMigrateInfo
&& me
.fetchingNodeMigrateInfo
=== vm
.get('nodename')) {
196 me
.fetchingNodeMigrateInfo
= vm
.get('nodename');
197 let { result
} = await Proxmox
.Async
.api2({
198 url
: `/nodes/${vm.get('nodename')}/${vm.get('vmtype')}/${vm.get('vmid')}/migrate`,
201 migrateStats
= result
.data
;
202 me
.fetchingNodeMigrateInfo
= false;
204 Ext
.Msg
.alert(gettext('Error'), error
.htmlStatus
);
208 if (migrateStats
.running
) {
209 vm
.set('running', true);
211 // Get migration object from viewmodel to prevent to many bind callbacks
212 let migration
= vm
.get('migration');
213 if (resetMigrationPossible
) {
214 migration
.possible
= true;
216 migration
.preconditions
= [];
218 if (migrateStats
.allowed_nodes
) {
219 migration
.allowedNodes
= migrateStats
.allowed_nodes
;
220 let target
= me
.lookup('pveNodeSelector').value
;
221 if (target
.length
&& !migrateStats
.allowed_nodes
.includes(target
)) {
222 let disallowed
= migrateStats
.not_allowed_nodes
[target
] ?? {};
223 if (disallowed
.unavailable_storages
!== undefined) {
224 let missingStorages
= disallowed
.unavailable_storages
.join(', ');
226 migration
.possible
= false;
227 migration
.preconditions
.push({
228 text
: 'Storage (' + missingStorages
+ ') not available on selected target. ' +
229 'Start VM to use live storage migration or select other target node',
234 if (disallowed
['unavailable-resources'] !== undefined) {
235 let unavailableResources
= disallowed
['unavailable-resources'].join(', ');
237 migration
.possible
= false;
238 migration
.preconditions
.push({
239 text
: 'Mapped Resources (' + unavailableResources
+ ') not available on selected target. ',
246 let blockingResources
= [];
247 let mappedResources
= migrateStats
['mapped-resources'] ?? [];
249 for (const res
of migrateStats
.local_resources
) {
250 if (mappedResources
.indexOf(res
) === -1) {
251 blockingResources
.push(res
);
255 if (blockingResources
.length
) {
256 migration
.hasLocalResources
= true;
257 if (!migration
.overwriteLocalResourceCheck
|| vm
.get('running')) {
258 migration
.possible
= false;
259 migration
.preconditions
.push({
260 text
: Ext
.String
.format('Can\'t migrate VM with local resources: {0}',
261 blockingResources
.join(', ')),
265 migration
.preconditions
.push({
266 text
: Ext
.String
.format('Migrate VM with local resources: {0}. ' +
267 'This might fail if resources aren\'t available on the target node.',
268 blockingResources
.join(', ')),
274 if (mappedResources
&& mappedResources
.length
) {
275 if (vm
.get('running')) {
276 migration
.possible
= false;
277 migration
.preconditions
.push({
278 text
: Ext
.String
.format('Can\'t migrate running VM with mapped resources: {0}',
279 mappedResources
.join(', ')),
285 if (migrateStats
.local_disks
.length
) {
286 migrateStats
.local_disks
.forEach(function(disk
) {
287 if (disk
.cdrom
&& disk
.cdrom
=== 1) {
288 if (!disk
.volid
.includes('vm-' + vm
.get('vmid') + '-cloudinit')) {
289 migration
.possible
= false;
290 migration
.preconditions
.push({
291 text
: "Can't migrate VM with local CD/DVD",
296 let size
= disk
.size
? '(' + Proxmox
.Utils
.render_size(disk
.size
) + ')' : '';
297 migration
['with-local-disks'] = 1;
298 migration
.preconditions
.push({
299 text
: Ext
.String
.format('Migration with local disk might take long: {0} {1}', disk
.volid
, size
),
306 vm
.set('migration', migration
);
308 checkLxcPreconditions: function(resetMigrationPossible
) {
309 let vm
= this.getViewModel();
310 if (vm
.get('running')) {
311 vm
.set('migration.mode', 'restart');
326 reference
: 'formPanel',
335 xtype
: 'displayfield',
337 fieldLabel
: gettext('Source node'),
343 xtype
: 'displayfield',
344 reference
: 'migrationMode',
345 fieldLabel
: gettext('Mode'),
347 value
: '{setMigrationMode}',
355 xtype
: 'pveNodeSelector',
356 reference
: 'pveNodeSelector',
358 fieldLabel
: gettext('Target node'),
360 disallowedNodes
: undefined,
361 onlineValidator
: true,
363 change
: 'onTargetChange',
367 xtype
: 'pveStorageSelector',
368 reference
: 'pveDiskStorageSelector',
369 name
: 'targetstorage',
370 fieldLabel
: gettext('Target storage'),
371 storageContent
: 'images',
374 emptyText
: gettext('Current layout'),
376 hidden
: '{setStorageselectorHidden}',
380 xtype
: 'proxmoxcheckbox',
381 name
: 'overwriteLocalResourceCheck',
382 fieldLabel
: gettext('Force'),
385 'data-qtip': 'Overwrite local resources unavailable check',
388 hidden
: '{setLocalResourceCheckboxHidden}',
389 value
: '{migration.overwriteLocalResourceCheck}',
393 fn
: 'checkMigratePreconditions',
403 reference
: 'preconditionGrid',
408 dataIndex
: 'severity',
409 renderer: function(v
) {
412 return '<i class="fa fa-exclamation-triangle warning"></i> ';
414 return '<i class="fa fa-times critical"></i>';
428 hidden
: '{!migration.preconditions.length}',
430 fields
: ['severity', 'text'],
431 data
: '{migration.preconditions}',
440 xtype
: 'proxmoxHelpButton',
441 reference
: 'proxmoxHelpButton',
442 onlineHelp
: 'pct_migration',
443 listenToGlobalEvent
: false,
449 reference
: 'submitButton',
450 text
: gettext('Migrate'),
451 handler
: 'startMigration',
453 disabled
: '{!migration.possible}',