]>
Commit | Line | Data |
---|---|---|
9706707d | 1 | /*jslint confusion: true*/ |
04c32bed DM |
2 | Ext.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 | }); |