]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/window/Migrate.js
ui: migrate: add storage and size information to preconditions
[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
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 }
53 },
54
55 controller: {
56 xclass: 'Ext.app.ViewController',
57 control: {
58 'panel[reference=formPanel]': {
59 validityChange: function(panel, isValid) {
60 this.getViewModel().set('migration.possible', isValid);
61 this.checkMigratePreconditions();
62 }
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);
192 }
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;
200 var target = me.lookup('pveNodeSelector').value;
201 if (target.length && !migrateStats.allowed_nodes.includes(target)) {
202 let disallowed = migrateStats.not_allowed_nodes[target];
203 let missing_storages = disallowed.unavailable_storages.join(', ');
204
205 migration.possible = false;
206 migration.preconditions.push({
207 text: 'Storage (' + missing_storages + ') not available on selected target. ' +
208 'Start VM to use live storage migration or select other target node',
209 severity: 'error'
210 });
211 }
212 }
213
214 if (migrateStats.local_resources.length) {
215 migration.possible = false;
216 migration.preconditions.push({
217 text: 'Can\'t migrate VM with local resources: '+ migrateStats.local_resources.join(', '),
218 severity: 'error'
219 });
220 }
221
222 if (migrateStats.local_disks.length) {
223
224 migrateStats.local_disks.forEach(function (disk) {
225 if (disk.cdrom && disk.cdrom === 1) {
226 migration.possible = false;
227 migration.preconditions.push({
228 text: "Can't migrate VM with local CD/DVD",
229 severity: 'error'
230 });
231
232 } else if (!disk.referenced_in_config) {
233 migration.possible = false;
234 migration.preconditions.push({
235 text: 'Found not referenced/unused disk via storage: '+ disk.volid,
236 severity: 'error'
237 });
238 } else {
239 migration['with-local-disks'] = 1;
240 migration.preconditions.push({
241 text:'Migration with local disk might take long: ' + disk.volid
242 +' (' + PVE.Utils.render_size(disk.size) + ')',
243 severity: 'warning'
244 });
245 }
246 });
247
248 }
249
250 vm.set('migration', migration);
251
252 }
253 });
254 },
255 checkLxcPreconditions: function() {
256 var me = this,
257 vm = me.getViewModel();
258 if (vm.get('running')) {
259 vm.set('migration.mode', 'restart');
260 }
261 }
262
263
264 },
265
266 width: 600,
267 modal: true,
268 layout: {
269 type: 'vbox',
270 align: 'stretch'
271 },
272 border: false,
273 items: [
274 {
275 xtype: 'form',
276 reference: 'formPanel',
277 bodyPadding: 10,
278 border: false,
279 layout: {
280 type: 'column'
281 },
282 items: [
283 {
284 xtype: 'container',
285 columnWidth: 0.5,
286 items: [{
287 xtype: 'displayfield',
288 name: 'source',
289 fieldLabel: gettext('Source node'),
290 bind: {
291 value: '{nodename}'
292 }
293 },
294 {
295 xtype: 'displayfield',
296 reference: 'migrationMode',
297 fieldLabel: gettext('Mode'),
298 bind: {
299 value: '{setMigrationMode}'
300 }
301 }]
302 },
303 {
304 xtype: 'container',
305 columnWidth: 0.5,
306 items: [{
307 xtype: 'pveNodeSelector',
308 reference: 'pveNodeSelector',
309 name: 'target',
310 fieldLabel: gettext('Target node'),
311 allowBlank: false,
312 disallowedNodes: undefined,
313 onlineValidator: true,
314 listeners: {
315 change: 'onTargetChange'
316 }
317 },
318 {
319 xtype: 'pveStorageSelector',
320 reference: 'pveDiskStorageSelector',
321 name: 'targetstorage',
322 fieldLabel: gettext('Target storage'),
323 storageContent: 'images',
324 bind: {
325 hidden: '{setStorageselectorHidden}'
326 }
327 }]
328 }
329 ]
330 },
331 {
332 xtype: 'gridpanel',
333 reference: 'preconditionGrid',
334 selectable: false,
335 flex: 1,
336 columns: [{
337 text: '',
338 dataIndex: 'severity',
339 renderer: function(v) {
340 switch (v) {
341 case 'warning':
342 return '<i class="fa fa-exclamation-triangle warning"></i> ';
343 case 'error':
344 return '<i class="fa fa-times critical"></i>';
345 default:
346 return v;
347 }
348 },
349 width: 35
350 },
351 {
352 text: 'Info',
353 dataIndex: 'text',
354 cellWrap: true,
355 flex: 1
356 }],
357 bind: {
358 hidden: '{!migration.preconditions.length}',
359 store: {
360 fields: ['severity','text'],
361 data: '{migration.preconditions}'
362 }
363 }
364 }
365
366 ],
367 buttons: [
368 {
369 xtype: 'proxmoxHelpButton',
370 reference: 'proxmoxHelpButton',
371 onlineHelp: 'pct_migration',
372 listenToGlobalEvent: false,
373 hidden: false
374 },
375 '->',
376 {
377 xtype: 'button',
378 reference: 'submitButton',
379 text: gettext('Migrate'),
380 handler: 'startMigration',
381 bind: {
382 disabled: '{!migration.possible}'
383 }
384 }
385 ]
386 });