]>
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, | |
28 | allowedNodes: undefined | |
29 | } | |
30 | ||
31 | }, | |
32 | ||
33 | formulas: { | |
34 | setMigrationMode: function(get) { | |
35 | if (get('running')){ | |
36 | if (get('vmtype') === 'qemu') { | |
37 | return gettext('Online'); | |
38 | } else { | |
39 | return gettext('Restart Mode'); | |
40 | } | |
41 | } else { | |
42 | return gettext('Offline'); | |
43 | } | |
44 | }, | |
45 | setStorageselectorHidden: function(get) { | |
46 | if (get('migration.with-local-disks') && get('running')) { | |
47 | return false; | |
48 | } else { | |
49 | return true; | |
50 | } | |
51 | } | |
52 | } | |
2df7ea9f | 53 | }, |
2df7ea9f EK |
54 | |
55 | controller: { | |
56 | xclass: 'Ext.app.ViewController', | |
57 | control: { | |
58 | 'panel[reference=formPanel]': { | |
59 | validityChange: function(panel, isValid) { | |
9706707d TM |
60 | this.getViewModel().set('migration.possible', isValid); |
61 | this.checkMigratePreconditions(); | |
2df7ea9f | 62 | } |
9706707d TM |
63 | } |
64 | }, | |
65 | ||
66 | init: function(view) { | |
67 | var me = this, | |
68 | vm = view.getViewModel(); | |
69 | ||
70 | if (!view.nodename) { | |
71 | throw "missing custom view config: nodename"; | |
72 | } | |
73 | vm.set('nodename', view.nodename); | |
74 | ||
75 | if (!view.vmid) { | |
76 | throw "missing custom view config: vmid"; | |
77 | } | |
78 | vm.set('vmid', view.vmid); | |
79 | ||
80 | if (!view.vmtype) { | |
81 | throw "missing custom view config: vmtype"; | |
82 | } | |
83 | vm.set('vmtype', view.vmtype); | |
84 | ||
85 | ||
86 | view.setTitle( | |
87 | Ext.String.format('{0} {1}{2}', gettext('Migrate'), vm.get(view.vmtype).commonName, view.vmid) | |
88 | ); | |
89 | me.lookup('proxmoxHelpButton').setHelpConfig({ | |
90 | onlineHelp: vm.get(view.vmtype).onlineHelp | |
91 | }); | |
92 | me.checkMigratePreconditions(); | |
93 | me.lookup('formPanel').isValid(); | |
94 | ||
95 | }, | |
96 | ||
97 | onTargetChange: function (nodeSelector) { | |
98 | //Always display the storages of the currently seleceted migration target | |
99 | this.lookup('pveDiskStorageSelector').setNodename(nodeSelector.value); | |
100 | this.checkMigratePreconditions(); | |
101 | }, | |
102 | ||
103 | startMigration: function() { | |
104 | var me = this, | |
105 | view = me.getView(), | |
106 | vm = me.getViewModel(); | |
107 | ||
108 | var values = me.lookup('formPanel').getValues(); | |
109 | var params = { | |
110 | target: values.target | |
111 | }; | |
112 | ||
113 | if (vm.get('migration.mode')) { | |
114 | params[vm.get('migration.mode')] = 1; | |
115 | } | |
116 | if (vm.get('migration.with-local-disks')) { | |
117 | params['with-local-disks'] = 1; | |
118 | } | |
119 | //only submit targetstorage if vm is running, storage migration to different storage is only possible online | |
120 | if (vm.get('migration.with-local-disks') && vm.get('running')) { | |
121 | params.targetstorage = values.targetstorage; | |
122 | } | |
123 | ||
124 | Proxmox.Utils.API2Request({ | |
125 | params: params, | |
126 | url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate', | |
127 | waitMsgTarget: view, | |
128 | method: 'POST', | |
129 | failure: function(response, opts) { | |
130 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
131 | }, | |
132 | success: function(response, options) { | |
133 | var upid = response.result.data; | |
134 | var extraTitle = Ext.String.format(' ({0} ---> {1})', vm.get('nodename'), params.target); | |
135 | ||
136 | Ext.create('Proxmox.window.TaskViewer', { | |
137 | upid: upid, | |
138 | extraTitle: extraTitle | |
139 | }).show(); | |
140 | ||
141 | view.close(); | |
142 | } | |
143 | }); | |
144 | ||
145 | }, | |
146 | ||
147 | checkMigratePreconditions: function() { | |
148 | var me = this, | |
149 | vm = me.getViewModel(); | |
150 | ||
151 | ||
152 | var vmrec = PVE.data.ResourceStore.findRecord('vmid', vm.get('vmid'), | |
153 | 0, false, false, true); | |
154 | if (vmrec && vmrec.data && vmrec.data.running) { | |
155 | vm.set('running', true); | |
156 | } | |
157 | ||
158 | if (vm.get('vmtype') === 'qemu') { | |
159 | me.checkQemuPreconditions(); | |
160 | } else { | |
161 | me.checkLxcPreconditions(); | |
162 | } | |
163 | me.lookup('pveNodeSelector').disallowedNodes = [vm.get('nodename')]; | |
164 | ||
165 | // Only allow nodes where the local storage is available in case of offline migration | |
166 | // where storage migration is not possible | |
167 | me.lookup('pveNodeSelector').allowedNodes = vm.get('migration.allowedNodes'); | |
168 | ||
169 | me.lookup('formPanel').isValid(); | |
170 | ||
171 | }, | |
172 | ||
173 | checkQemuPreconditions: function() { | |
174 | var me = this, | |
175 | vm = me.getViewModel(), | |
176 | migrateStats; | |
177 | ||
178 | if (vm.get('running')) { | |
179 | vm.set('migration.mode', 'online'); | |
180 | } | |
181 | ||
182 | Proxmox.Utils.API2Request({ | |
183 | url: '/nodes/' + vm.get('nodename') + '/' + vm.get('vmtype') + '/' + vm.get('vmid') + '/migrate', | |
184 | method: 'GET', | |
185 | failure: function(response, opts) { | |
186 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
187 | }, | |
188 | success: function(response, options) { | |
189 | migrateStats = response.result.data; | |
190 | if (migrateStats.running) { | |
191 | vm.set('running', true); | |
2df7ea9f | 192 | } |
9706707d TM |
193 | // Get migration object from viewmodel to prevent |
194 | // to many bind callbacks | |
195 | var migration = vm.get('migration'); | |
196 | migration.preconditions = []; | |
197 | ||
198 | if (migrateStats.allowed_nodes) { | |
199 | migration.allowedNodes = migrateStats.allowed_nodes; | |
2df7ea9f | 200 | |
9706707d TM |
201 | if (!migrateStats.allowed_nodes.includes(me.lookup('pveNodeSelector').value)) { |
202 | migration.possible = false; | |
203 | migration.preconditions.push({ | |
204 | text: 'Local storage not available on selected Node, start VM to use live storage migration or select other target node', | |
205 | icon: '<i class="fa fa-times critical"></i>' | |
206 | }); | |
2df7ea9f | 207 | } |
9706707d TM |
208 | } |
209 | ||
210 | if (migrateStats.local_resources.length) { | |
211 | migration.possible = false; | |
212 | migration.preconditions.push({ | |
213 | text: 'Can\'t migrate VM with local resources: '+ migrateStats.local_resources.join(', '), | |
214 | icon: '<i class="fa fa-times critical"></i>' | |
215 | }); | |
216 | } | |
217 | ||
218 | if (migrateStats.local_disks.length) { | |
219 | ||
220 | migrateStats.local_disks.forEach(function (disk) { | |
221 | if (disk.cdrom && disk.cdrom === 1) { | |
222 | migration.possible = false; | |
223 | migration.preconditions.push({ | |
224 | text:'Can\'t migrate VM with local CD/DVD', | |
225 | icon: '<i class="fa fa-times critical"></i>' | |
226 | }); | |
227 | ||
228 | } else if (!disk.referenced_in_config) { | |
229 | migration.possible = false; | |
230 | migration.preconditions.push({ | |
231 | text: 'Found not referenced/unused disk via storage: '+ disk.volid, | |
232 | icon: '<i class="fa fa-times critical"></i>' | |
233 | }); | |
234 | } else { | |
235 | migration['with-local-disks'] = 1; | |
236 | migration.preconditions.push({ | |
237 | text:'Migration with local disk might take long: '+ disk.volid, | |
238 | icon: '<i class="fa fa-exclamation-triangle warning"></i>' | |
239 | }); | |
240 | } | |
241 | }); | |
242 | ||
243 | } | |
244 | ||
245 | vm.set('migration', migration); | |
246 | ||
2df7ea9f | 247 | } |
9706707d TM |
248 | }); |
249 | }, | |
250 | checkLxcPreconditions: function() { | |
251 | var me = this, | |
252 | vm = me.getViewModel(); | |
253 | if (vm.get('running')) { | |
254 | vm.set('migration.mode', 'restart'); | |
04c32bed | 255 | } |
04c32bed | 256 | } |
9706707d TM |
257 | |
258 | ||
2df7ea9f | 259 | }, |
04c32bed | 260 | |
3188b0b7 | 261 | width: 600, |
2df7ea9f | 262 | modal: true, |
9706707d TM |
263 | layout: { |
264 | type: 'vbox', | |
265 | align: 'stretch' | |
266 | }, | |
2df7ea9f | 267 | border: false, |
2df7ea9f EK |
268 | items: [ |
269 | { | |
270 | xtype: 'form', | |
271 | reference: 'formPanel', | |
04c32bed DM |
272 | bodyPadding: 10, |
273 | border: false, | |
9706707d TM |
274 | layout: { |
275 | type: 'column' | |
04c32bed DM |
276 | }, |
277 | items: [ | |
278 | { | |
9706707d TM |
279 | xtype: 'container', |
280 | columnWidth: 0.5, | |
281 | items: [{ | |
282 | xtype: 'pveNodeSelector', | |
283 | reference: 'pveNodeSelector', | |
284 | name: 'target', | |
285 | fieldLabel: gettext('Target node'), | |
286 | allowBlank: false, | |
287 | disallowedNodes: undefined, | |
288 | onlineValidator: true, | |
289 | listeners: { | |
290 | change: 'onTargetChange' | |
291 | } | |
292 | }, | |
293 | { | |
294 | xtype: 'displayfield', | |
295 | reference: 'migrationMode', | |
296 | fieldLabel: gettext('Mode'), | |
297 | bind: { | |
298 | value: '{setMigrationMode}' | |
299 | } | |
300 | } | |
301 | ] | |
04c32bed DM |
302 | }, |
303 | { | |
9706707d TM |
304 | xtype: 'container', |
305 | columnWidth: 0.5, | |
306 | items: [ | |
307 | { | |
308 | xtype: 'pveStorageSelector', | |
309 | reference: 'pveDiskStorageSelector', | |
310 | name: 'targetstorage', | |
311 | fieldLabel: gettext('Target Storage'), | |
312 | storageContent: 'images', | |
313 | bind: { | |
314 | hidden: '{setStorageselectorHidden}' | |
315 | } | |
316 | } | |
317 | ] | |
318 | } | |
319 | ] | |
320 | }, | |
321 | { | |
322 | xtype: 'gridpanel', | |
323 | reference: 'preconditionGrid', | |
324 | flex: 1, | |
325 | columns: [ | |
326 | {text: 'Severity', dataIndex: 'icon', width: 80}, | |
327 | {text: 'Info', dataIndex: 'text', flex: 1} | |
328 | ], | |
329 | bind: { | |
330 | hidden: '{!migration.preconditions.length}', | |
331 | store: { | |
332 | fields: ['icon','text'], | |
333 | data: '{migration.preconditions}' | |
04c32bed | 334 | } |
9706707d | 335 | } |
2df7ea9f | 336 | } |
9706707d | 337 | |
2df7ea9f EK |
338 | ], |
339 | buttons: [ | |
340 | { | |
672a6270 TL |
341 | xtype: 'proxmoxHelpButton', |
342 | reference: 'proxmoxHelpButton', | |
2df7ea9f EK |
343 | onlineHelp: 'pct_migration', |
344 | listenToGlobalEvent: false, | |
345 | hidden: false | |
346 | }, | |
347 | '->', | |
348 | { | |
349 | xtype: 'button', | |
350 | reference: 'submitButton', | |
9706707d TM |
351 | text: gettext('Migrate'), |
352 | handler: 'startMigration', | |
353 | bind: { | |
354 | disabled: '{!migration.possible}' | |
1c62fde2 EK |
355 | } |
356 | } | |
9706707d | 357 | ] |
3188b0b7 | 358 | }); |