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