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