]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/Migrate.js
ui: clarify that compression selector is for backup only
[pve-manager.git] / www / manager6 / window / Migrate.js
CommitLineData
04c32bed
DM
1Ext.define('PVE.window.Migrate', {
2 extend: 'Ext.window.Window',
3
9706707d
TM
4 vmtype: undefined,
5 nodename: undefined,
6 vmid: undefined,
8b4987e5 7 maxHeight: 450,
9706707d
TM
8
9 viewModel: {
10 data: {
11 vmid: undefined,
12 nodename: undefined,
13 vmtype: undefined,
14 running: false,
15 qemu: {
16 onlineHelp: 'qm_migration',
f6710aac 17 commonName: 'VM',
9706707d
TM
18 },
19 lxc: {
20 onlineHelp: 'pct_migration',
f6710aac 21 commonName: 'CT',
9706707d
TM
22 },
23 migration: {
24 possible: true,
25 preconditions: [],
26 'with-local-disks': 0,
27 mode: undefined,
4ddaf290
TM
28 allowedNodes: undefined,
29 overwriteLocalResourceCheck: false,
f6710aac
TL
30 hasLocalResources: false,
31 },
9706707d
TM
32
33 },
34
35 formulas: {
36 setMigrationMode: function(get) {
8058410f 37 if (get('running')) {
9706707d
TM
38 if (get('vmtype') === 'qemu') {
39 return gettext('Online');
40 } else {
41 return gettext('Restart Mode');
42 }
43 } else {
44 return gettext('Offline');
45 }
46 },
47 setStorageselectorHidden: function(get) {
48 if (get('migration.with-local-disks') && get('running')) {
49 return false;
50 } else {
51 return true;
52 }
4ddaf290
TM
53 },
54 setLocalResourceCheckboxHidden: function(get) {
55 if (get('running') || !get('migration.hasLocalResources') ||
56 Proxmox.UserName !== 'root@pam') {
57 return true;
58 } else {
59 return false;
60 }
f6710aac
TL
61 },
62 },
2df7ea9f 63 },
2df7ea9f
EK
64
65 controller: {
66 xclass: 'Ext.app.ViewController',
67 control: {
68 'panel[reference=formPanel]': {
69 validityChange: function(panel, isValid) {
9706707d
TM
70 this.getViewModel().set('migration.possible', isValid);
71 this.checkMigratePreconditions();
f6710aac
TL
72 },
73 },
9706707d
TM
74 },
75
76 init: function(view) {
77 var me = this,
78 vm = view.getViewModel();
79
80 if (!view.nodename) {
81 throw "missing custom view config: nodename";
82 }
83 vm.set('nodename', view.nodename);
84
85 if (!view.vmid) {
86 throw "missing custom view config: vmid";
87 }
88 vm.set('vmid', view.vmid);
89
90 if (!view.vmtype) {
91 throw "missing custom view config: vmtype";
92 }
93 vm.set('vmtype', view.vmtype);
94
9706707d 95 view.setTitle(
f6710aac 96 Ext.String.format('{0} {1} {2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid),
9706707d
TM
97 );
98 me.lookup('proxmoxHelpButton').setHelpConfig({
f6710aac 99 onlineHelp: vm.get(view.vmtype).onlineHelp,
9706707d 100 });
9706707d 101 me.lookup('formPanel').isValid();
9706707d
TM
102 },
103
8058410f 104 onTargetChange: function(nodeSelector) {
d8d4c14b 105 // Always display the storages of the currently seleceted migration target
9706707d
TM
106 this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value);
107 this.checkMigratePreconditions();
108 },
109
110 startMigration: function() {
111 var me = this,
112 view = me.getView(),
113 vm = me.getViewModel();
114
115 var values = me.lookup('formPanel').getValues();
116 var params = {
f6710aac 117 target: values.target,
9706707d
TM
118 };
119
120 if (vm.get('migration.mode')) {
121 params[vm.get('migration.mode')] = 1;
122 }
123 if (vm.get('migration.with-local-disks')) {
124 params['with-local-disks'] = 1;
125 }
11c17a01
FE
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) {
9706707d
TM
129 params.targetstorage = values.targetstorage;
130 }
131
4ddaf290 132 if (vm.get('migration.overwriteLocalResourceCheck')) {
399ffa76 133 params.force = 1;
4ddaf290
TM
134 }
135
9706707d
TM
136 Proxmox.Utils.API2Request({
137 params: params,
138 url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate',
139 waitMsgTarget: view,
140 method: 'POST',
141 failure: function(response, opts) {
142 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
143 },
144 success: function(response, options) {
145 var upid = response.result.data;
146 var extraTitle = Ext.String.format(' ({0} ---> {1})', vm.get('nodename'), params.target);
147
148 Ext.create('Proxmox.window.TaskViewer', {
149 upid: upid,
f6710aac 150 extraTitle: extraTitle,
9706707d
TM
151 }).show();
152
153 view.close();
f6710aac 154 },
9706707d 155 });
9706707d
TM
156 },
157
6d727f81 158 checkMigratePreconditions: async function(resetMigrationPossible) {
9706707d
TM
159 var me = this,
160 vm = me.getViewModel();
161
9706707d
TM
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);
166 }
167
6d727f81
DC
168 me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')];
169
9706707d 170 if (vm.get('vmtype') === 'qemu') {
6d727f81 171 await me.checkQemuPreconditions(resetMigrationPossible);
9706707d 172 } else {
4ddaf290 173 me.checkLxcPreconditions(resetMigrationPossible);
9706707d 174 }
9706707d
TM
175
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');
179
180 me.lookup('formPanel').isValid();
9706707d
TM
181 },
182
d8d4c14b
TL
183 checkQemuPreconditions: async function(resetMigrationPossible) {
184 let me = this,
9706707d
TM
185 vm = me.getViewModel(),
186 migrateStats;
187
188 if (vm.get('running')) {
189 vm.set('migration.mode', 'online');
190 }
191
d8d4c14b 192 try {
685b52f5
TL
193 if (me.fetchingNodeMigrateInfo && me.fetchingNodeMigrateInfo === vm.get('nodename')) {
194 return;
195 }
196 me.fetchingNodeMigrateInfo = vm.get('nodename');
d8d4c14b
TL
197 let { result } = await Proxmox.Async.api2({
198 url: `/nodes/${vm.get('nodename')}/${vm.get('vmtype')}/${vm.get('vmid')}/migrate`,
199 method: 'GET',
200 });
201 migrateStats = result.data;
685b52f5 202 me.fetchingNodeMigrateInfo = false;
d8d4c14b
TL
203 } catch (error) {
204 Ext.Msg.alert(gettext('Error'), error.htmlStatus);
205 return;
206 }
2df7ea9f 207
d8d4c14b
TL
208 if (migrateStats.running) {
209 vm.set('running', true);
210 }
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;
215 }
216 migration.preconditions = [];
217
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)) {
6d727f81 222 let disallowed = migrateStats.not_allowed_nodes[target] ?? {};
02f14aa8
DC
223 if (disallowed.unavailable_storages !== undefined) {
224 let missingStorages = disallowed.unavailable_storages.join(', ');
d8d4c14b 225
02f14aa8
DC
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',
230 severity: 'error',
231 });
232 }
233
234 if (disallowed['unavailable-resources'] !== undefined) {
235 let unavailableResources = disallowed['unavailable-resources'].join(', ');
236
237 migration.possible = false;
238 migration.preconditions.push({
239 text: 'Mapped Resources (' + unavailableResources + ') not available on selected target. ',
240 severity: 'error',
241 });
242 }
d8d4c14b
TL
243 }
244 }
245
02f14aa8
DC
246 let blockingResources = [];
247 let mappedResources = migrateStats['mapped-resources'] ?? [];
248
249 for (const res of migrateStats.local_resources) {
250 if (mappedResources.indexOf(res) === -1) {
251 blockingResources.push(res);
252 }
253 }
254
255 if (blockingResources.length) {
d8d4c14b
TL
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}',
02f14aa8 261 blockingResources.join(', ')),
d8d4c14b
TL
262 severity: 'error',
263 });
264 } else {
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.',
02f14aa8 268 blockingResources.join(', ')),
d8d4c14b
TL
269 severity: 'warning',
270 });
271 }
272 }
9706707d 273
02f14aa8
DC
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(', ')),
280 severity: 'error',
281 });
282 }
283 }
284
d8d4c14b
TL
285 if (migrateStats.local_disks.length) {
286 migrateStats.local_disks.forEach(function(disk) {
287 if (disk.cdrom && disk.cdrom === 1) {
4d66fc7f 288 if (!disk.volid.includes('vm-' + vm.get('vmid') + '-cloudinit')) {
4ddaf290
TM
289 migration.possible = false;
290 migration.preconditions.push({
d8d4c14b 291 text: "Can't migrate VM with local CD/DVD",
f6710aac 292 severity: 'error',
4ddaf290 293 });
4ddaf290 294 }
d8d4c14b
TL
295 } else {
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),
300 severity: 'warning',
9706707d 301 });
9706707d 302 }
d8d4c14b
TL
303 });
304 }
9706707d 305
d8d4c14b 306 vm.set('migration', migration);
9706707d 307 },
4ddaf290 308 checkLxcPreconditions: function(resetMigrationPossible) {
d8d4c14b 309 let vm = this.getViewModel();
9706707d
TM
310 if (vm.get('running')) {
311 vm.set('migration.mode', 'restart');
04c32bed 312 }
f6710aac 313 },
2df7ea9f 314 },
04c32bed 315
3188b0b7 316 width: 600,
2df7ea9f 317 modal: true,
9706707d
TM
318 layout: {
319 type: 'vbox',
f6710aac 320 align: 'stretch',
9706707d 321 },
2df7ea9f 322 border: false,
2df7ea9f
EK
323 items: [
324 {
325 xtype: 'form',
326 reference: 'formPanel',
04c32bed
DM
327 bodyPadding: 10,
328 border: false,
d27a44a6 329 layout: 'hbox',
04c32bed
DM
330 items: [
331 {
9706707d 332 xtype: 'container',
d27a44a6 333 flex: 1,
9706707d 334 items: [{
090dff2f
TL
335 xtype: 'displayfield',
336 name: 'source',
337 fieldLabel: gettext('Source node'),
338 bind: {
f6710aac
TL
339 value: '{nodename}',
340 },
9706707d
TM
341 },
342 {
343 xtype: 'displayfield',
344 reference: 'migrationMode',
345 fieldLabel: gettext('Mode'),
346 bind: {
f6710aac
TL
347 value: '{setMigrationMode}',
348 },
349 }],
04c32bed
DM
350 },
351 {
9706707d 352 xtype: 'container',
d27a44a6 353 flex: 1,
090dff2f
TL
354 items: [{
355 xtype: 'pveNodeSelector',
356 reference: 'pveNodeSelector',
357 name: 'target',
358 fieldLabel: gettext('Target node'),
359 allowBlank: false,
360 disallowedNodes: undefined,
361 onlineValidator: true,
362 listeners: {
f6710aac
TL
363 change: 'onTargetChange',
364 },
090dff2f
TL
365 },
366 {
367 xtype: 'pveStorageSelector',
368 reference: 'pveDiskStorageSelector',
369 name: 'targetstorage',
370 fieldLabel: gettext('Target storage'),
371 storageContent: 'images',
11c17a01
FE
372 allowBlank: true,
373 autoSelect: false,
cd338fe1 374 emptyText: gettext('Current layout'),
090dff2f 375 bind: {
f6710aac
TL
376 hidden: '{setStorageselectorHidden}',
377 },
4ddaf290
TM
378 },
379 {
380 xtype: 'proxmoxcheckbox',
381 name: 'overwriteLocalResourceCheck',
382 fieldLabel: gettext('Force'),
383 autoEl: {
384 tag: 'div',
f6710aac 385 'data-qtip': 'Overwrite local resources unavailable check',
4ddaf290
TM
386 },
387 bind: {
388 hidden: '{setLocalResourceCheckboxHidden}',
f6710aac 389 value: '{migration.overwriteLocalResourceCheck}',
4ddaf290
TM
390 },
391 listeners: {
d8d4c14b
TL
392 change: {
393 fn: 'checkMigratePreconditions',
394 extraArg: true,
395 },
f6710aac
TL
396 },
397 }],
398 },
399 ],
9706707d
TM
400 },
401 {
402 xtype: 'gridpanel',
403 reference: 'preconditionGrid',
b4533390 404 selectable: false,
9706707d 405 flex: 1,
b4533390
TL
406 columns: [{
407 text: '',
408 dataIndex: 'severity',
409 renderer: function(v) {
410 switch (v) {
411 case 'warning':
412 return '<i class="fa fa-exclamation-triangle warning"></i> ';
413 case 'error':
414 return '<i class="fa fa-times critical"></i>';
415 default:
416 return v;
417 }
418 },
f6710aac 419 width: 35,
b4533390
TL
420 },
421 {
422 text: 'Info',
423 dataIndex: 'text',
424 cellWrap: true,
f6710aac 425 flex: 1,
b4533390 426 }],
9706707d
TM
427 bind: {
428 hidden: '{!migration.preconditions.length}',
429 store: {
cd338fe1
TL
430 fields: ['severity', 'text'],
431 data: '{migration.preconditions}',
432 sorters: 'text',
f6710aac
TL
433 },
434 },
435 },
9706707d 436
2df7ea9f
EK
437 ],
438 buttons: [
439 {
672a6270
TL
440 xtype: 'proxmoxHelpButton',
441 reference: 'proxmoxHelpButton',
2df7ea9f
EK
442 onlineHelp: 'pct_migration',
443 listenToGlobalEvent: false,
f6710aac 444 hidden: false,
2df7ea9f
EK
445 },
446 '->',
447 {
448 xtype: 'button',
449 reference: 'submitButton',
9706707d
TM
450 text: gettext('Migrate'),
451 handler: 'startMigration',
452 bind: {
f6710aac
TL
453 disabled: '{!migration.possible}',
454 },
455 },
456 ],
3188b0b7 457});