]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/Migrate.js
ui: Utils: use render functions from widget-toolkit
[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
95
96 view.setTitle(
f6710aac 97 Ext.String.format('{0} {1} {2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid),
9706707d
TM
98 );
99 me.lookup('proxmoxHelpButton').setHelpConfig({
f6710aac 100 onlineHelp: vm.get(view.vmtype).onlineHelp,
9706707d
TM
101 });
102 me.checkMigratePreconditions();
103 me.lookup('formPanel').isValid();
9706707d
TM
104 },
105
8058410f 106 onTargetChange: function(nodeSelector) {
9706707d
TM
107 //Always display the storages of the currently seleceted migration target
108 this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value);
109 this.checkMigratePreconditions();
110 },
111
112 startMigration: function() {
113 var me = this,
114 view = me.getView(),
115 vm = me.getViewModel();
116
117 var values = me.lookup('formPanel').getValues();
118 var params = {
f6710aac 119 target: values.target,
9706707d
TM
120 };
121
122 if (vm.get('migration.mode')) {
123 params[vm.get('migration.mode')] = 1;
124 }
125 if (vm.get('migration.with-local-disks')) {
126 params['with-local-disks'] = 1;
127 }
11c17a01
FE
128 //offline migration to a different storage currently might fail at a late stage
129 //(i.e. after some disks have been moved), so don't expose it yet in the GUI
130 if (vm.get('migration.with-local-disks') && vm.get('running') && values.targetstorage) {
9706707d
TM
131 params.targetstorage = values.targetstorage;
132 }
133
4ddaf290 134 if (vm.get('migration.overwriteLocalResourceCheck')) {
399ffa76 135 params.force = 1;
4ddaf290
TM
136 }
137
9706707d
TM
138 Proxmox.Utils.API2Request({
139 params: params,
140 url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate',
141 waitMsgTarget: view,
142 method: 'POST',
143 failure: function(response, opts) {
144 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
145 },
146 success: function(response, options) {
147 var upid = response.result.data;
148 var extraTitle = Ext.String.format(' ({0} ---> {1})', vm.get('nodename'), params.target);
149
150 Ext.create('Proxmox.window.TaskViewer', {
151 upid: upid,
f6710aac 152 extraTitle: extraTitle,
9706707d
TM
153 }).show();
154
155 view.close();
f6710aac 156 },
9706707d 157 });
9706707d
TM
158 },
159
4ddaf290 160 checkMigratePreconditions: function(resetMigrationPossible) {
9706707d
TM
161 var me = this,
162 vm = me.getViewModel();
163
9706707d
TM
164 var vmrec = PVE.data.ResourceStore.findRecord('vmid', vm.get('vmid'),
165 0, false, false, true);
166 if (vmrec && vmrec.data && vmrec.data.running) {
167 vm.set('running', true);
168 }
169
170 if (vm.get('vmtype') === 'qemu') {
4ddaf290 171 me.checkQemuPreconditions(resetMigrationPossible);
9706707d 172 } else {
4ddaf290 173 me.checkLxcPreconditions(resetMigrationPossible);
9706707d
TM
174 }
175 me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')];
176
177 // Only allow nodes where the local storage is available in case of offline migration
178 // where storage migration is not possible
179 me.lookup('pveNodeSelector').allowedNodes = vm.get('migration.allowedNodes');
180
181 me.lookup('formPanel').isValid();
9706707d
TM
182 },
183
4ddaf290 184 checkQemuPreconditions: function(resetMigrationPossible) {
9706707d
TM
185 var me = this,
186 vm = me.getViewModel(),
187 migrateStats;
188
189 if (vm.get('running')) {
190 vm.set('migration.mode', 'online');
191 }
192
193 Proxmox.Utils.API2Request({
194 url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate',
195 method: 'GET',
196 failure: function(response, opts) {
197 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
198 },
199 success: function(response, options) {
200 migrateStats = response.result.data;
201 if (migrateStats.running) {
202 vm.set('running', true);
2df7ea9f 203 }
9706707d
TM
204 // Get migration object from viewmodel to prevent
205 // to many bind callbacks
206 var migration = vm.get('migration');
4ddaf290 207 if (resetMigrationPossible) migration.possible = true;
9706707d
TM
208 migration.preconditions = [];
209
210 if (migrateStats.allowed_nodes) {
211 migration.allowedNodes = migrateStats.allowed_nodes;
c54f9a75
TM
212 var target = me.lookup('pveNodeSelector').value;
213 if (target.length && !migrateStats.allowed_nodes.includes(target)) {
214 let disallowed = migrateStats.not_allowed_nodes[target];
215 let missing_storages = disallowed.unavailable_storages.join(', ');
2df7ea9f 216
9706707d
TM
217 migration.possible = false;
218 migration.preconditions.push({
c54f9a75
TM
219 text: 'Storage (' + missing_storages + ') not available on selected target. ' +
220 'Start VM to use live storage migration or select other target node',
f6710aac 221 severity: 'error',
9706707d 222 });
2df7ea9f 223 }
9706707d
TM
224 }
225
226 if (migrateStats.local_resources.length) {
4ddaf290 227 migration.hasLocalResources = true;
8058410f 228 if (!migration.overwriteLocalResourceCheck || vm.get('running')) {
4ddaf290
TM
229 migration.possible = false;
230 migration.preconditions.push({
231 text: Ext.String.format('Can\'t migrate VM with local resources: {0}',
232 migrateStats.local_resources.join(', ')),
f6710aac 233 severity: 'error',
4ddaf290
TM
234 });
235 } else {
236 migration.preconditions.push({
237 text: Ext.String.format('Migrate VM with local resources: {0}. ' +
238 'This might fail if resources aren\'t available on the target node.',
239 migrateStats.local_resources.join(', ')),
f6710aac 240 severity: 'warning',
4ddaf290
TM
241 });
242 }
9706707d
TM
243 }
244
245 if (migrateStats.local_disks.length) {
8058410f 246 migrateStats.local_disks.forEach(function(disk) {
9706707d 247 if (disk.cdrom && disk.cdrom === 1) {
cea7382b
ML
248 if (disk.volid.includes('vm-'+vm.get('vmid')+'-cloudinit')) {
249 if (migrateStats.running) {
250 migration.possible = false;
251 migration.preconditions.push({
252 text: "Can't live migrate VM with local cloudinit disk, use shared storage instead",
f6710aac 253 severity: 'error',
cea7382b
ML
254 });
255 } else {
256 return;
257 }
258 } else {
259 migration.possible = false;
260 migration.preconditions.push({
261 text: "Can't migrate VM with local CD/DVD",
f6710aac 262 severity: 'error',
cea7382b
ML
263 });
264 }
9706707d 265 } else {
1bd7bcdb 266 var size_string = disk.size ? '(' + Proxmox.Utils.render_size(disk.size) + ')' : '';
9706707d
TM
267 migration['with-local-disks'] = 1;
268 migration.preconditions.push({
0411c2c0
FE
269 text: Ext.String.format('Migration with local disk might take long: {0} {1}',
270 disk.volid, size_string),
f6710aac 271 severity: 'warning',
9706707d
TM
272 });
273 }
274 });
9706707d
TM
275 }
276
277 vm.set('migration', migration);
f6710aac 278 },
9706707d
TM
279 });
280 },
4ddaf290 281 checkLxcPreconditions: function(resetMigrationPossible) {
9706707d
TM
282 var me = this,
283 vm = me.getViewModel();
284 if (vm.get('running')) {
285 vm.set('migration.mode', 'restart');
04c32bed 286 }
f6710aac 287 },
9706707d
TM
288
289
2df7ea9f 290 },
04c32bed 291
3188b0b7 292 width: 600,
2df7ea9f 293 modal: true,
9706707d
TM
294 layout: {
295 type: 'vbox',
f6710aac 296 align: 'stretch',
9706707d 297 },
2df7ea9f 298 border: false,
2df7ea9f
EK
299 items: [
300 {
301 xtype: 'form',
302 reference: 'formPanel',
04c32bed
DM
303 bodyPadding: 10,
304 border: false,
d27a44a6 305 layout: 'hbox',
04c32bed
DM
306 items: [
307 {
9706707d 308 xtype: 'container',
d27a44a6 309 flex: 1,
9706707d 310 items: [{
090dff2f
TL
311 xtype: 'displayfield',
312 name: 'source',
313 fieldLabel: gettext('Source node'),
314 bind: {
f6710aac
TL
315 value: '{nodename}',
316 },
9706707d
TM
317 },
318 {
319 xtype: 'displayfield',
320 reference: 'migrationMode',
321 fieldLabel: gettext('Mode'),
322 bind: {
f6710aac
TL
323 value: '{setMigrationMode}',
324 },
325 }],
04c32bed
DM
326 },
327 {
9706707d 328 xtype: 'container',
d27a44a6 329 flex: 1,
090dff2f
TL
330 items: [{
331 xtype: 'pveNodeSelector',
332 reference: 'pveNodeSelector',
333 name: 'target',
334 fieldLabel: gettext('Target node'),
335 allowBlank: false,
336 disallowedNodes: undefined,
337 onlineValidator: true,
338 listeners: {
f6710aac
TL
339 change: 'onTargetChange',
340 },
090dff2f
TL
341 },
342 {
343 xtype: 'pveStorageSelector',
344 reference: 'pveDiskStorageSelector',
345 name: 'targetstorage',
346 fieldLabel: gettext('Target storage'),
347 storageContent: 'images',
11c17a01
FE
348 allowBlank: true,
349 autoSelect: false,
cd338fe1 350 emptyText: gettext('Current layout'),
090dff2f 351 bind: {
f6710aac
TL
352 hidden: '{setStorageselectorHidden}',
353 },
4ddaf290
TM
354 },
355 {
356 xtype: 'proxmoxcheckbox',
357 name: 'overwriteLocalResourceCheck',
358 fieldLabel: gettext('Force'),
359 autoEl: {
360 tag: 'div',
f6710aac 361 'data-qtip': 'Overwrite local resources unavailable check',
4ddaf290
TM
362 },
363 bind: {
364 hidden: '{setLocalResourceCheckboxHidden}',
f6710aac 365 value: '{migration.overwriteLocalResourceCheck}',
4ddaf290
TM
366 },
367 listeners: {
8058410f 368 change: { fn: 'checkMigratePreconditions', extraArg: true },
f6710aac
TL
369 },
370 }],
371 },
372 ],
9706707d
TM
373 },
374 {
375 xtype: 'gridpanel',
376 reference: 'preconditionGrid',
b4533390 377 selectable: false,
9706707d 378 flex: 1,
b4533390
TL
379 columns: [{
380 text: '',
381 dataIndex: 'severity',
382 renderer: function(v) {
383 switch (v) {
384 case 'warning':
385 return '<i class="fa fa-exclamation-triangle warning"></i> ';
386 case 'error':
387 return '<i class="fa fa-times critical"></i>';
388 default:
389 return v;
390 }
391 },
f6710aac 392 width: 35,
b4533390
TL
393 },
394 {
395 text: 'Info',
396 dataIndex: 'text',
397 cellWrap: true,
f6710aac 398 flex: 1,
b4533390 399 }],
9706707d
TM
400 bind: {
401 hidden: '{!migration.preconditions.length}',
402 store: {
cd338fe1
TL
403 fields: ['severity', 'text'],
404 data: '{migration.preconditions}',
405 sorters: 'text',
f6710aac
TL
406 },
407 },
408 },
9706707d 409
2df7ea9f
EK
410 ],
411 buttons: [
412 {
672a6270
TL
413 xtype: 'proxmoxHelpButton',
414 reference: 'proxmoxHelpButton',
2df7ea9f
EK
415 onlineHelp: 'pct_migration',
416 listenToGlobalEvent: false,
f6710aac 417 hidden: false,
2df7ea9f
EK
418 },
419 '->',
420 {
421 xtype: 'button',
422 reference: 'submitButton',
9706707d
TM
423 text: gettext('Migrate'),
424 handler: 'startMigration',
425 bind: {
f6710aac
TL
426 disabled: '{!migration.possible}',
427 },
428 },
429 ],
3188b0b7 430});