root / snf-cyclades-app / synnefo / ui / static / snf / js / models.js @ 3796f345
History | View | Annotate | Download (75.9 kB)
1 |
// Copyright 2011 GRNET S.A. All rights reserved.
|
---|---|
2 |
//
|
3 |
// Redistribution and use in source and binary forms, with or
|
4 |
// without modification, are permitted provided that the following
|
5 |
// conditions are met:
|
6 |
//
|
7 |
// 1. Redistributions of source code must retain the above
|
8 |
// copyright notice, this list of conditions and the following
|
9 |
// disclaimer.
|
10 |
//
|
11 |
// 2. Redistributions in binary form must reproduce the above
|
12 |
// copyright notice, this list of conditions and the following
|
13 |
// disclaimer in the documentation and/or other materials
|
14 |
// provided with the distribution.
|
15 |
//
|
16 |
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
// POSSIBILITY OF SUCH DAMAGE.
|
28 |
//
|
29 |
// The views and conclusions contained in the software and
|
30 |
// documentation are those of the authors and should not be
|
31 |
// interpreted as representing official policies, either expressed
|
32 |
// or implied, of GRNET S.A.
|
33 |
//
|
34 |
|
35 |
;(function(root){
|
36 |
|
37 |
// root
|
38 |
var root = root;
|
39 |
|
40 |
// setup namepsaces
|
41 |
var snf = root.synnefo = root.synnefo || {};
|
42 |
var models = snf.models = snf.models || {}
|
43 |
var storage = snf.storage = snf.storage || {};
|
44 |
var util = snf.util = snf.util || {};
|
45 |
|
46 |
// shortcuts
|
47 |
var bb = root.Backbone;
|
48 |
var slice = Array.prototype.slice
|
49 |
|
50 |
// logging
|
51 |
var logger = new snf.logging.logger("SNF-MODELS"); |
52 |
var debug = _.bind(logger.debug, logger);
|
53 |
|
54 |
// get url helper
|
55 |
var getUrl = function(baseurl) { |
56 |
var baseurl = baseurl || snf.config.api_urls[this.api_type]; |
57 |
return baseurl + "/" + this.path; |
58 |
} |
59 |
|
60 |
var NIC_REGEX = /^nic-([0-9]+)-([0-9]+)$/ |
61 |
|
62 |
// i18n
|
63 |
BUILDING_MESSAGES = window.BUILDING_MESSAGES || {'INIT': 'init', 'COPY': '{0}, {1}, {2}', 'FINAL': 'final'}; |
64 |
|
65 |
// Base object for all our models
|
66 |
models.Model = bb.Model.extend({ |
67 |
sync: snf.api.sync,
|
68 |
api: snf.api,
|
69 |
api_type: 'compute', |
70 |
has_status: false, |
71 |
|
72 |
initialize: function() { |
73 |
if (this.has_status) { |
74 |
this.bind("change:status", this.handle_remove); |
75 |
this.handle_remove();
|
76 |
} |
77 |
|
78 |
this.api_call = _.bind(this.api.call, this); |
79 |
models.Model.__super__.initialize.apply(this, arguments); |
80 |
}, |
81 |
|
82 |
handle_remove: function() { |
83 |
if (this.get("status") == 'DELETED') { |
84 |
if (this.collection) { |
85 |
try { this.clear_pending_action();} catch (err) {}; |
86 |
try { this.reset_pending_actions();} catch (err) {}; |
87 |
try { this.stop_stats_update();} catch (err) {}; |
88 |
this.collection.remove(this.id); |
89 |
} |
90 |
} |
91 |
}, |
92 |
|
93 |
// custom set method to allow submodels to use
|
94 |
// set_<attr> methods for handling the value of each
|
95 |
// attribute and overriding the default set method
|
96 |
// for specific parameters
|
97 |
set: function(params, options) { |
98 |
_.each(params, _.bind(function(value, key){
|
99 |
if (this["set_" + key]) { |
100 |
params[key] = this["set_" + key](value); |
101 |
} |
102 |
}, this))
|
103 |
var ret = bb.Model.prototype.set.call(this, params, options); |
104 |
return ret;
|
105 |
}, |
106 |
|
107 |
url: function(options) { |
108 |
return getUrl.call(this, this.base_url) + "/" + this.id; |
109 |
}, |
110 |
|
111 |
api_path: function(options) { |
112 |
return this.path + "/" + this.id; |
113 |
}, |
114 |
|
115 |
parse: function(resp, xhr) { |
116 |
return resp.server;
|
117 |
}, |
118 |
|
119 |
remove: function() { |
120 |
this.api_call(this.api_path(), "delete"); |
121 |
}, |
122 |
|
123 |
changedKeys: function() { |
124 |
return _.keys(this.changedAttributes() || {}); |
125 |
}, |
126 |
|
127 |
// return list of changed attributes that included in passed list
|
128 |
// argument
|
129 |
getKeysChanged: function(keys) { |
130 |
return _.intersection(keys, this.changedKeys()); |
131 |
}, |
132 |
|
133 |
// boolean check of keys changed
|
134 |
keysChanged: function(keys) { |
135 |
return this.getKeysChanged(keys).length > 0; |
136 |
}, |
137 |
|
138 |
// check if any of the passed attribues has changed
|
139 |
hasOnlyChange: function(keys) { |
140 |
var ret = false; |
141 |
_.each(keys, _.bind(function(key) {
|
142 |
if (this.changedKeys().length == 1 && this.changedKeys().indexOf(key) > -1) { ret = true}; |
143 |
}, this));
|
144 |
return ret;
|
145 |
} |
146 |
|
147 |
}) |
148 |
|
149 |
// Base object for all our model collections
|
150 |
models.Collection = bb.Collection.extend({ |
151 |
sync: snf.api.sync,
|
152 |
api: snf.api,
|
153 |
api_type: 'compute', |
154 |
supportIncUpdates: true, |
155 |
|
156 |
initialize: function() { |
157 |
models.Collection.__super__.initialize.apply(this, arguments); |
158 |
this.api_call = _.bind(this.api.call, this); |
159 |
}, |
160 |
|
161 |
url: function(options, method) { |
162 |
return getUrl.call(this, this.base_url) + ( |
163 |
options.details || this.details && method != 'create' ? '/detail' : ''); |
164 |
}, |
165 |
|
166 |
fetch: function(options) { |
167 |
if (!options) { options = {} };
|
168 |
// default to update
|
169 |
if (!this.noUpdate) { |
170 |
if (options.update === undefined) { options.update = true }; |
171 |
if (!options.removeMissing && options.refresh) { options.removeMissing = true }; |
172 |
} else {
|
173 |
if (options.refresh === undefined) { |
174 |
options.refresh = true;
|
175 |
} |
176 |
} |
177 |
// custom event foreach fetch
|
178 |
return bb.Collection.prototype.fetch.call(this, options) |
179 |
}, |
180 |
|
181 |
create: function(model, options) { |
182 |
var coll = this; |
183 |
options || (options = {}); |
184 |
model = this._prepareModel(model, options);
|
185 |
if (!model) return false; |
186 |
var success = options.success;
|
187 |
options.success = function(nextModel, resp, xhr) { |
188 |
if (success) success(nextModel, resp, xhr);
|
189 |
}; |
190 |
model.save(null, options);
|
191 |
return model;
|
192 |
}, |
193 |
|
194 |
get_fetcher: function(interval, increase, fast, increase_after_calls, max, initial_call, params) { |
195 |
var fetch_params = params || {};
|
196 |
var handler_options = {};
|
197 |
|
198 |
fetch_params.skips_timeouts = true;
|
199 |
handler_options.interval = interval; |
200 |
handler_options.increase = increase; |
201 |
handler_options.fast = fast; |
202 |
handler_options.increase_after_calls = increase_after_calls; |
203 |
handler_options.max= max; |
204 |
handler_options.id = "collection id";
|
205 |
|
206 |
var last_ajax = undefined; |
207 |
var callback = _.bind(function() { |
208 |
// clone to avoid referenced objects
|
209 |
var params = _.clone(fetch_params);
|
210 |
updater._ajax = last_ajax; |
211 |
|
212 |
// wait for previous request to finish
|
213 |
if (last_ajax && last_ajax.readyState < 4 && last_ajax.statusText != "timeout") { |
214 |
// opera readystate for 304 responses is 0
|
215 |
if (!($.browser.opera && last_ajax.readyState == 0 && last_ajax.status == 304)) { |
216 |
return;
|
217 |
} |
218 |
} |
219 |
|
220 |
last_ajax = this.fetch(params);
|
221 |
}, this);
|
222 |
handler_options.callback = callback; |
223 |
|
224 |
var updater = new snf.api.updateHandler(_.clone(_.extend(handler_options, fetch_params))); |
225 |
snf.api.bind("call", _.throttle(_.bind(function(){ updater.faster(true)}, this)), 1000); |
226 |
return updater;
|
227 |
} |
228 |
}); |
229 |
|
230 |
// Image model
|
231 |
models.Image = models.Model.extend({ |
232 |
path: 'images', |
233 |
|
234 |
get_size: function() { |
235 |
return parseInt(this.get('metadata') ? this.get('metadata').values.size : -1) |
236 |
}, |
237 |
|
238 |
get_description: function(escape) { |
239 |
if (escape == undefined) { escape = true }; |
240 |
if (escape) { return this.escape('description') || "No description available"} |
241 |
return this.get('description') || "No description available." |
242 |
}, |
243 |
|
244 |
get_meta: function(key) { |
245 |
if (this.get('metadata') && this.get('metadata').values) { |
246 |
if (!this.get('metadata').values[key]) { return null } |
247 |
return _.escape(this.get('metadata').values[key]); |
248 |
} else {
|
249 |
return null; |
250 |
} |
251 |
}, |
252 |
|
253 |
get_meta_keys: function() { |
254 |
if (this.get('metadata') && this.get('metadata').values) { |
255 |
return _.keys(this.get('metadata').values); |
256 |
} else {
|
257 |
return [];
|
258 |
} |
259 |
}, |
260 |
|
261 |
get_owner: function() { |
262 |
return this.get('owner') || _.keys(synnefo.config.system_images_owners)[0]; |
263 |
}, |
264 |
|
265 |
display_owner: function() { |
266 |
var owner = this.get_owner(); |
267 |
if (_.include(_.keys(synnefo.config.system_images_owners), owner)) {
|
268 |
return synnefo.config.system_images_owners[owner];
|
269 |
} else {
|
270 |
return owner;
|
271 |
} |
272 |
}, |
273 |
|
274 |
get_readable_size: function() { |
275 |
if (this.is_deleted()) { |
276 |
return synnefo.config.image_deleted_size_title || '(none)'; |
277 |
} |
278 |
return this.get_size() > 0 ? util.readablizeBytes(this.get_size() * 1024 * 1024) : '(none)'; |
279 |
}, |
280 |
|
281 |
get_os: function() { |
282 |
return this.get_meta('OS'); |
283 |
}, |
284 |
|
285 |
get_gui: function() { |
286 |
return this.get_meta('GUI'); |
287 |
}, |
288 |
|
289 |
get_created_user: function() { |
290 |
return synnefo.config.os_created_users[this.get_os()] || "root"; |
291 |
}, |
292 |
|
293 |
get_sort_order: function() { |
294 |
return parseInt(this.get('metadata') ? this.get('metadata').values.sortorder : -1) |
295 |
}, |
296 |
|
297 |
get_vm: function() { |
298 |
var vm_id = this.get("serverRef"); |
299 |
var vm = undefined; |
300 |
vm = storage.vms.get(vm_id); |
301 |
return vm;
|
302 |
}, |
303 |
|
304 |
is_public: function() { |
305 |
return this.get('is_public') || true; |
306 |
}, |
307 |
|
308 |
is_deleted: function() { |
309 |
return this.get('status') == "DELETED" |
310 |
}, |
311 |
|
312 |
ssh_keys_path: function() { |
313 |
prepend = '';
|
314 |
if (this.get_created_user() != 'root') { |
315 |
prepend = '/home'
|
316 |
} |
317 |
return '{1}/{0}/.ssh/authorized_keys'.format(this.get_created_user(), prepend); |
318 |
}, |
319 |
|
320 |
_supports_ssh: function() { |
321 |
if (synnefo.config.support_ssh_os_list.indexOf(this.get_os()) > -1) { |
322 |
return true; |
323 |
} |
324 |
if (this.get_meta('osfamily') == 'linux') { |
325 |
return true; |
326 |
} |
327 |
return false; |
328 |
}, |
329 |
|
330 |
supports: function(feature) { |
331 |
if (feature == "ssh") { |
332 |
return this._supports_ssh() |
333 |
} |
334 |
return false; |
335 |
}, |
336 |
|
337 |
personality_data_for_keys: function(keys) { |
338 |
contents = '';
|
339 |
_.each(keys, function(key){
|
340 |
contents = contents + key.get("content") + "\n" |
341 |
}); |
342 |
contents = $.base64.encode(contents);
|
343 |
|
344 |
return {
|
345 |
path: this.ssh_keys_path(), |
346 |
contents: contents
|
347 |
} |
348 |
} |
349 |
}); |
350 |
|
351 |
// Flavor model
|
352 |
models.Flavor = models.Model.extend({ |
353 |
path: 'flavors', |
354 |
|
355 |
details_string: function() { |
356 |
return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk')); |
357 |
}, |
358 |
|
359 |
get_disk_size: function() { |
360 |
return parseInt(this.get("disk") * 1000) |
361 |
}, |
362 |
|
363 |
get_disk_template_info: function() { |
364 |
var info = snf.config.flavors_disk_templates_info[this.get("disk_template")]; |
365 |
if (!info) {
|
366 |
info = { name: this.get("disk_template"), description:'' }; |
367 |
} |
368 |
return info
|
369 |
} |
370 |
|
371 |
}); |
372 |
|
373 |
models.ParamsList = function(){this.initialize.apply(this, arguments)}; |
374 |
_.extend(models.ParamsList.prototype, bb.Events, { |
375 |
|
376 |
initialize: function(parent, param_name) { |
377 |
this.parent = parent;
|
378 |
this.actions = {};
|
379 |
this.param_name = param_name;
|
380 |
this.length = 0; |
381 |
}, |
382 |
|
383 |
has_action: function(action) { |
384 |
return this.actions[action] ? true : false; |
385 |
}, |
386 |
|
387 |
_parse_params: function(arguments) { |
388 |
if (arguments.length <= 1) { |
389 |
return [];
|
390 |
} |
391 |
|
392 |
var args = _.toArray(arguments); |
393 |
return args.splice(1); |
394 |
}, |
395 |
|
396 |
contains: function(action, params) { |
397 |
params = this._parse_params(arguments); |
398 |
var has_action = this.has_action(action); |
399 |
if (!has_action) { return false }; |
400 |
|
401 |
var paramsEqual = false; |
402 |
_.each(this.actions[action], function(action_params) { |
403 |
if (_.isEqual(action_params, params)) {
|
404 |
paramsEqual = true;
|
405 |
} |
406 |
}); |
407 |
|
408 |
return paramsEqual;
|
409 |
}, |
410 |
|
411 |
is_empty: function() { |
412 |
return _.isEmpty(this.actions); |
413 |
}, |
414 |
|
415 |
add: function(action, params) { |
416 |
params = this._parse_params(arguments); |
417 |
if (this.contains.apply(this, arguments)) { return this }; |
418 |
var isnew = false |
419 |
if (!this.has_action(action)) { |
420 |
this.actions[action] = [];
|
421 |
isnew = true;
|
422 |
}; |
423 |
|
424 |
this.actions[action].push(params);
|
425 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
426 |
if (isnew) {
|
427 |
this.trigger("add", action, params); |
428 |
} else {
|
429 |
this.trigger("change", action, params); |
430 |
} |
431 |
return this; |
432 |
}, |
433 |
|
434 |
remove_all: function(action) { |
435 |
if (this.has_action(action)) { |
436 |
delete this.actions[action]; |
437 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
438 |
this.trigger("remove", action); |
439 |
} |
440 |
return this; |
441 |
}, |
442 |
|
443 |
reset: function() { |
444 |
this.actions = {};
|
445 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
446 |
this.trigger("reset"); |
447 |
this.trigger("remove"); |
448 |
}, |
449 |
|
450 |
remove: function(action, params) { |
451 |
params = this._parse_params(arguments); |
452 |
if (!this.has_action(action)) { return this }; |
453 |
var index = -1; |
454 |
_.each(this.actions[action], _.bind(function(action_params) { |
455 |
if (_.isEqual(action_params, params)) {
|
456 |
index = this.actions[action].indexOf(action_params);
|
457 |
} |
458 |
}, this));
|
459 |
|
460 |
if (index > -1) { |
461 |
this.actions[action].splice(index, 1); |
462 |
if (_.isEmpty(this.actions[action])) { |
463 |
delete this.actions[action]; |
464 |
} |
465 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
466 |
this.trigger("remove", action, params); |
467 |
} |
468 |
} |
469 |
|
470 |
}); |
471 |
|
472 |
// Image model
|
473 |
models.Network = models.Model.extend({ |
474 |
path: 'networks', |
475 |
has_status: true, |
476 |
defaults: {'connecting':0}, |
477 |
|
478 |
initialize: function() { |
479 |
var ret = models.Network.__super__.initialize.apply(this, arguments); |
480 |
this.set({"actions": new models.ParamsList(this, "actions")}); |
481 |
this.update_state();
|
482 |
this.bind("change:nics", _.bind(synnefo.storage.nics.update_net_nics, synnefo.storage.nics)); |
483 |
this.bind("change:status", _.bind(this.update_state, this)); |
484 |
return ret;
|
485 |
}, |
486 |
|
487 |
toJSON: function() { |
488 |
var attrs = _.clone(this.attributes); |
489 |
attrs.actions = _.clone(this.get("actions").actions); |
490 |
return attrs;
|
491 |
}, |
492 |
|
493 |
set_state: function(val) { |
494 |
if (val == "PENDING" && this.get("state") == "DESTORY") { |
495 |
return "DESTROY"; |
496 |
} |
497 |
return val;
|
498 |
}, |
499 |
|
500 |
update_state: function() { |
501 |
if (this.get("connecting") > 0) { |
502 |
this.set({state: "CONNECTING"}); |
503 |
return
|
504 |
} |
505 |
|
506 |
if (this.get_nics(function(nic){ return nic.get("removing") == 1}).length > 0) { |
507 |
this.set({state: "DISCONNECTING"}); |
508 |
return
|
509 |
} |
510 |
|
511 |
if (this.contains_firewalling_nics() > 0) { |
512 |
this.set({state: "FIREWALLING"}); |
513 |
return
|
514 |
} |
515 |
|
516 |
if (this.get("state") == "DESTROY") { |
517 |
this.set({"destroyed":1}); |
518 |
} |
519 |
|
520 |
this.set({state:this.get('status')}); |
521 |
}, |
522 |
|
523 |
is_public: function() { |
524 |
return this.get("public"); |
525 |
}, |
526 |
|
527 |
decrease_connecting: function() { |
528 |
var conn = this.get("connecting"); |
529 |
if (!conn) { conn = 0 }; |
530 |
if (conn > 0) { |
531 |
conn--; |
532 |
} |
533 |
this.set({"connecting": conn}); |
534 |
this.update_state();
|
535 |
}, |
536 |
|
537 |
increase_connecting: function() { |
538 |
var conn = this.get("connecting"); |
539 |
if (!conn) { conn = 0 }; |
540 |
conn++; |
541 |
this.set({"connecting": conn}); |
542 |
this.update_state();
|
543 |
}, |
544 |
|
545 |
connected_to: function(vm) { |
546 |
return this.get('linked_to').indexOf(""+vm.id) > -1; |
547 |
}, |
548 |
|
549 |
connected_with_nic_id: function(nic_id) { |
550 |
return _.keys(this.get('nics')).indexOf(nic_id) > -1; |
551 |
}, |
552 |
|
553 |
get_nics: function(filter) { |
554 |
var nics = synnefo.storage.nics.filter(function(nic) { |
555 |
return nic.get('network_id') == this.id; |
556 |
}, this);
|
557 |
|
558 |
if (filter) {
|
559 |
return _.filter(nics, filter);
|
560 |
} |
561 |
return nics;
|
562 |
}, |
563 |
|
564 |
contains_firewalling_nics: function() { |
565 |
return this.get_nics(function(n){return n.get('pending_firewall')}).length |
566 |
}, |
567 |
|
568 |
call: function(action, params, success, error) { |
569 |
if (action == "destroy") { |
570 |
this.set({state:"DESTROY"}); |
571 |
this.get("actions").remove("destroy", params); |
572 |
this.remove(_.bind(function(){ |
573 |
success(); |
574 |
}, this), error);
|
575 |
} |
576 |
|
577 |
if (action == "disconnect") { |
578 |
if (this.get("state") == "DESTROY") { |
579 |
return;
|
580 |
} |
581 |
|
582 |
_.each(params, _.bind(function(nic_id) {
|
583 |
var nic = snf.storage.nics.get(nic_id);
|
584 |
this.get("actions").remove("disconnect", nic_id); |
585 |
if (nic) {
|
586 |
this.remove_nic(nic, success, error);
|
587 |
} |
588 |
}, this));
|
589 |
} |
590 |
}, |
591 |
|
592 |
add_vm: function (vm, callback, error, options) { |
593 |
var payload = {add:{serverRef:"" + vm.id}}; |
594 |
payload._options = options || {}; |
595 |
return this.api_call(this.api_path() + "/action", "create", |
596 |
payload, |
597 |
undefined,
|
598 |
error, |
599 |
_.bind(function(){
|
600 |
//this.vms.add_pending(vm.id);
|
601 |
this.increase_connecting();
|
602 |
if (callback) {callback()}
|
603 |
},this), error);
|
604 |
}, |
605 |
|
606 |
remove_nic: function (nic, callback, error, options) { |
607 |
var payload = {remove:{attachment:"" + nic.get("attachment_id")}}; |
608 |
payload._options = options || {}; |
609 |
return this.api_call(this.api_path() + "/action", "create", |
610 |
payload, |
611 |
undefined,
|
612 |
error, |
613 |
_.bind(function(){
|
614 |
nic.set({"removing": 1}); |
615 |
nic.get_network().update_state(); |
616 |
//this.vms.add_pending_for_remove(vm.id);
|
617 |
if (callback) {callback()}
|
618 |
},this), error);
|
619 |
}, |
620 |
|
621 |
rename: function(name, callback) { |
622 |
return this.api_call(this.api_path(), "update", { |
623 |
network:{name:name}, |
624 |
_options:{
|
625 |
critical: false, |
626 |
error_params:{
|
627 |
title: "Network action failed", |
628 |
ns: "Networks", |
629 |
extra_details: {"Network id": this.id} |
630 |
} |
631 |
}}, callback); |
632 |
}, |
633 |
|
634 |
get_connectable_vms: function() { |
635 |
return storage.vms.filter(function(vm){ |
636 |
return !vm.in_error_state() && !vm.is_building();
|
637 |
}) |
638 |
}, |
639 |
|
640 |
state_message: function() { |
641 |
if (this.get("state") == "ACTIVE" && !this.is_public()) { |
642 |
if (this.get("cidr") && this.get("dhcp") == true) { |
643 |
return this.get("cidr"); |
644 |
} else {
|
645 |
return "Private network"; |
646 |
} |
647 |
} |
648 |
if (this.get("state") == "ACTIVE" && this.is_public()) { |
649 |
return "Public network"; |
650 |
} |
651 |
|
652 |
return models.Network.STATES[this.get("state")]; |
653 |
}, |
654 |
|
655 |
in_progress: function() { |
656 |
return models.Network.STATES_TRANSITIONS[this.get("state")] != undefined; |
657 |
}, |
658 |
|
659 |
do_all_pending_actions: function(success, error) { |
660 |
var destroy = this.get("actions").has_action("destroy"); |
661 |
_.each(this.get("actions").actions, _.bind(function(params, action) { |
662 |
_.each(params, _.bind(function(with_params) {
|
663 |
this.call(action, with_params, success, error);
|
664 |
}, this));
|
665 |
}, this));
|
666 |
this.get("actions").reset(); |
667 |
} |
668 |
}); |
669 |
|
670 |
models.Network.STATES = { |
671 |
'ACTIVE': 'Private network', |
672 |
'CONNECTING': 'Connecting...', |
673 |
'DISCONNECTING': 'Disconnecting...', |
674 |
'FIREWALLING': 'Firewall update...', |
675 |
'DESTROY': 'Destroying...', |
676 |
'PENDING': 'Pending...', |
677 |
'ERROR': 'Error' |
678 |
} |
679 |
|
680 |
models.Network.STATES_TRANSITIONS = { |
681 |
'CONNECTING': ['ACTIVE'], |
682 |
'DISCONNECTING': ['ACTIVE'], |
683 |
'PENDING': ['ACTIVE'], |
684 |
'FIREWALLING': ['ACTIVE'] |
685 |
} |
686 |
|
687 |
// Virtualmachine model
|
688 |
models.VM = models.Model.extend({ |
689 |
|
690 |
path: 'servers', |
691 |
has_status: true, |
692 |
initialize: function(params) { |
693 |
|
694 |
this.pending_firewalls = {};
|
695 |
|
696 |
models.VM.__super__.initialize.apply(this, arguments); |
697 |
|
698 |
this.set({state: params.status || "ERROR"}); |
699 |
this.log = new snf.logging.logger("VM " + this.id); |
700 |
this.pending_action = undefined; |
701 |
|
702 |
// init stats parameter
|
703 |
this.set({'stats': undefined}, {silent: true}); |
704 |
// defaults to not update the stats
|
705 |
// each view should handle this vm attribute
|
706 |
// depending on if it displays stat images or not
|
707 |
this.do_update_stats = false; |
708 |
|
709 |
// interval time
|
710 |
// this will dynamicaly change if the server responds that
|
711 |
// images get refreshed on different intervals
|
712 |
this.stats_update_interval = synnefo.config.STATS_INTERVAL || 5000; |
713 |
this.stats_available = false; |
714 |
|
715 |
// initialize interval
|
716 |
this.init_stats_intervals(this.stats_update_interval); |
717 |
|
718 |
// handle progress message on instance change
|
719 |
this.bind("change", _.bind(this.update_status_message, this)); |
720 |
// force update of progress message
|
721 |
this.update_status_message(true); |
722 |
|
723 |
// default values
|
724 |
this.bind("change:state", _.bind(function(){ |
725 |
if (this.state() == "DESTROY") { |
726 |
this.handle_destroy()
|
727 |
} |
728 |
}, this));
|
729 |
|
730 |
this.bind("change:nics", _.bind(synnefo.storage.nics.update_vm_nics, synnefo.storage.nics)); |
731 |
}, |
732 |
|
733 |
status: function(st) { |
734 |
if (!st) { return this.get("status")} |
735 |
return this.set({status:st}); |
736 |
}, |
737 |
|
738 |
set_status: function(st) { |
739 |
var new_state = this.state_for_api_status(st); |
740 |
var transition = false; |
741 |
|
742 |
if (this.state() != new_state) { |
743 |
if (models.VM.STATES_TRANSITIONS[this.state()]) { |
744 |
transition = this.state();
|
745 |
} |
746 |
} |
747 |
|
748 |
// call it silently to avoid double change trigger
|
749 |
this.set({'state': this.state_for_api_status(st)}, {silent: true}); |
750 |
|
751 |
// trigger transition
|
752 |
if (transition && models.VM.TRANSITION_STATES.indexOf(new_state) == -1) { |
753 |
this.trigger("transition", {from:transition, to:new_state}) |
754 |
}; |
755 |
return st;
|
756 |
}, |
757 |
|
758 |
get_diagnostics: function(success) { |
759 |
this.__make_api_call(this.get_diagnostics_url(), |
760 |
"read", // create so that sync later uses POST to make the call |
761 |
null, // payload |
762 |
function(data) {
|
763 |
success(data); |
764 |
}, |
765 |
null, 'diagnostics'); |
766 |
}, |
767 |
|
768 |
has_diagnostics: function() { |
769 |
return this.get("diagnostics") && this.get("diagnostics").length; |
770 |
}, |
771 |
|
772 |
get_progress_info: function() { |
773 |
// details about progress message
|
774 |
// contains a list of diagnostic messages
|
775 |
return this.get("status_messages"); |
776 |
}, |
777 |
|
778 |
get_status_message: function() { |
779 |
return this.get('status_message'); |
780 |
}, |
781 |
|
782 |
// extract status message from diagnostics
|
783 |
status_message_from_diagnostics: function(diagnostics) { |
784 |
var valid_sources_map = synnefo.config.diagnostics_status_messages_map;
|
785 |
var valid_sources = valid_sources_map[this.get('status')]; |
786 |
if (!valid_sources) { return null }; |
787 |
|
788 |
// filter messsages based on diagnostic source
|
789 |
var messages = _.filter(diagnostics, function(diag) { |
790 |
return valid_sources.indexOf(diag.source) > -1; |
791 |
}); |
792 |
|
793 |
var msg = messages[0]; |
794 |
if (msg) {
|
795 |
var message = msg.message;
|
796 |
var message_tpl = snf.config.diagnostic_messages_tpls[msg.source];
|
797 |
|
798 |
if (message_tpl) {
|
799 |
message = message_tpl.replace('MESSAGE', msg.message);
|
800 |
} |
801 |
return message;
|
802 |
} |
803 |
|
804 |
// no message to display, but vm in build state, display
|
805 |
// finalizing message.
|
806 |
if (this.is_building() == 'BUILD') { |
807 |
return synnefo.config.BUILDING_MESSAGES['FINAL']; |
808 |
} |
809 |
return null; |
810 |
}, |
811 |
|
812 |
update_status_message: function(force) { |
813 |
// update only if one of the specified attributes has changed
|
814 |
if (
|
815 |
!this.keysChanged(['diagnostics', 'progress', 'status', 'state']) |
816 |
&& !force |
817 |
) { return };
|
818 |
|
819 |
// if user requested to destroy the vm set the appropriate
|
820 |
// message.
|
821 |
if (this.get('state') == "DESTROY") { |
822 |
message = "Terminating..."
|
823 |
this.set({status_message: message}) |
824 |
return;
|
825 |
} |
826 |
|
827 |
// set error message, if vm has diagnostic message display it as
|
828 |
// progress message
|
829 |
if (this.in_error_state()) { |
830 |
var d = this.get('diagnostics'); |
831 |
if (d && d.length) {
|
832 |
var message = this.status_message_from_diagnostics(d); |
833 |
this.set({status_message: message}); |
834 |
} else {
|
835 |
this.set({status_message: null}); |
836 |
} |
837 |
return;
|
838 |
} |
839 |
|
840 |
// identify building status message
|
841 |
if (this.is_building()) { |
842 |
var self = this; |
843 |
var success = function(msg) { |
844 |
self.set({status_message: msg});
|
845 |
} |
846 |
this.get_building_status_message(success);
|
847 |
return;
|
848 |
} |
849 |
|
850 |
this.set({status_message:null}); |
851 |
}, |
852 |
|
853 |
// get building status message. Asynchronous function since it requires
|
854 |
// access to vm image.
|
855 |
get_building_status_message: function(callback) { |
856 |
// no progress is set, vm is in initial build status
|
857 |
var progress = this.get("progress"); |
858 |
if (progress == 0 || !progress) { |
859 |
return callback(BUILDING_MESSAGES['INIT']); |
860 |
} |
861 |
|
862 |
// vm has copy progress, display copy percentage
|
863 |
if (progress > 0 && progress <= 99) { |
864 |
this.get_copy_details(true, undefined, _.bind( |
865 |
function(details){
|
866 |
callback(BUILDING_MESSAGES['COPY'].format(details.copy,
|
867 |
details.size, |
868 |
details.progress)); |
869 |
}, this));
|
870 |
return;
|
871 |
} |
872 |
|
873 |
// copy finished display FINAL message or identify status message
|
874 |
// from diagnostics.
|
875 |
if (progress >= 100) { |
876 |
if (!this.has_diagnostics()) { |
877 |
callback(BUILDING_MESSAGES['FINAL']);
|
878 |
} else {
|
879 |
var d = this.get("diagnostics"); |
880 |
var msg = this.status_message_from_diagnostics(d); |
881 |
if (msg) {
|
882 |
callback(msg); |
883 |
} |
884 |
} |
885 |
} |
886 |
}, |
887 |
|
888 |
get_copy_details: function(human, image, callback) { |
889 |
var human = human || false; |
890 |
var image = image || this.get_image(_.bind(function(image){ |
891 |
var progress = this.get('progress'); |
892 |
var size = image.get_size();
|
893 |
var size_copied = (size * progress / 100).toFixed(2); |
894 |
|
895 |
if (human) {
|
896 |
size = util.readablizeBytes(size*1024*1024); |
897 |
size_copied = util.readablizeBytes(size_copied*1024*1024); |
898 |
} |
899 |
|
900 |
callback({'progress': progress, 'size': size, 'copy': size_copied}) |
901 |
}, this));
|
902 |
}, |
903 |
|
904 |
start_stats_update: function(force_if_empty) { |
905 |
var prev_state = this.do_update_stats; |
906 |
|
907 |
this.do_update_stats = true; |
908 |
|
909 |
// fetcher initialized ??
|
910 |
if (!this.stats_fetcher) { |
911 |
this.init_stats_intervals();
|
912 |
} |
913 |
|
914 |
|
915 |
// fetcher running ???
|
916 |
if (!this.stats_fetcher.running || !prev_state) { |
917 |
this.stats_fetcher.start();
|
918 |
} |
919 |
|
920 |
if (force_if_empty && this.get("stats") == undefined) { |
921 |
this.update_stats(true); |
922 |
} |
923 |
}, |
924 |
|
925 |
stop_stats_update: function(stop_calls) { |
926 |
this.do_update_stats = false; |
927 |
|
928 |
if (stop_calls) {
|
929 |
this.stats_fetcher.stop();
|
930 |
} |
931 |
}, |
932 |
|
933 |
// clear and reinitialize update interval
|
934 |
init_stats_intervals: function (interval) { |
935 |
this.stats_fetcher = this.get_stats_fetcher(this.stats_update_interval); |
936 |
this.stats_fetcher.start();
|
937 |
}, |
938 |
|
939 |
get_stats_fetcher: function(timeout) { |
940 |
var cb = _.bind(function(data){ |
941 |
this.update_stats();
|
942 |
}, this);
|
943 |
var fetcher = new snf.api.updateHandler({'callback': cb, interval: timeout, id:'stats'}); |
944 |
return fetcher;
|
945 |
}, |
946 |
|
947 |
// do the api call
|
948 |
update_stats: function(force) { |
949 |
// do not update stats if flag not set
|
950 |
if ((!this.do_update_stats && !force) || this.updating_stats) { |
951 |
return;
|
952 |
} |
953 |
|
954 |
// make the api call, execute handle_stats_update on sucess
|
955 |
// TODO: onError handler ???
|
956 |
stats_url = this.url() + "/stats"; |
957 |
this.updating_stats = true; |
958 |
this.sync("read", this, { |
959 |
handles_error:true, |
960 |
url: stats_url,
|
961 |
refresh:true, |
962 |
success: _.bind(this.handle_stats_update, this), |
963 |
error: _.bind(this.handle_stats_error, this), |
964 |
complete: _.bind(function(){this.updating_stats = false;}, this), |
965 |
critical: false, |
966 |
log_error: false, |
967 |
skips_timeouts: true |
968 |
}); |
969 |
}, |
970 |
|
971 |
get_stats_image: function(stat, type) { |
972 |
}, |
973 |
|
974 |
_set_stats: function(stats) { |
975 |
var silent = silent === undefined ? false : silent; |
976 |
// unavailable stats while building
|
977 |
if (this.get("status") == "BUILD") { |
978 |
this.stats_available = false; |
979 |
} else { this.stats_available = true; } |
980 |
|
981 |
if (this.get("status") == "DESTROY") { this.stats_available = false; } |
982 |
|
983 |
this.set({stats: stats}, {silent:true}); |
984 |
this.trigger("stats:update", stats); |
985 |
}, |
986 |
|
987 |
unbind: function() { |
988 |
models.VM.__super__.unbind.apply(this, arguments); |
989 |
}, |
990 |
|
991 |
handle_stats_error: function() { |
992 |
stats = {}; |
993 |
_.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) { |
994 |
stats[k] = false;
|
995 |
}); |
996 |
|
997 |
this.set({'stats': stats}); |
998 |
}, |
999 |
|
1000 |
// this method gets executed after a successful vm stats api call
|
1001 |
handle_stats_update: function(data) { |
1002 |
var self = this; |
1003 |
// avoid browser caching
|
1004 |
|
1005 |
if (data.stats && _.size(data.stats) > 0) { |
1006 |
var ts = $.now(); |
1007 |
var stats = data.stats;
|
1008 |
var images_loaded = 0; |
1009 |
var images = {};
|
1010 |
|
1011 |
function check_images_loaded() { |
1012 |
images_loaded++; |
1013 |
|
1014 |
if (images_loaded == 4) { |
1015 |
self._set_stats(images); |
1016 |
} |
1017 |
} |
1018 |
_.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) { |
1019 |
|
1020 |
stats[k] = stats[k] + "?_=" + ts;
|
1021 |
|
1022 |
var stat = k.slice(0,3); |
1023 |
var type = k.slice(3,6) == "Bar" ? "bar" : "time"; |
1024 |
var img = $("<img />"); |
1025 |
var val = stats[k];
|
1026 |
|
1027 |
// load stat image to a temporary dom element
|
1028 |
// update model stats on image load/error events
|
1029 |
img.load(function() {
|
1030 |
images[k] = val; |
1031 |
check_images_loaded(); |
1032 |
}); |
1033 |
|
1034 |
img.error(function() {
|
1035 |
images[stat + type] = false;
|
1036 |
check_images_loaded(); |
1037 |
}); |
1038 |
|
1039 |
img.attr({'src': stats[k]});
|
1040 |
}) |
1041 |
data.stats = stats; |
1042 |
} |
1043 |
|
1044 |
// do we need to change the interval ??
|
1045 |
if (data.stats.refresh * 1000 != this.stats_update_interval) { |
1046 |
this.stats_update_interval = data.stats.refresh * 1000; |
1047 |
this.stats_fetcher.interval = this.stats_update_interval; |
1048 |
this.stats_fetcher.maximum_interval = this.stats_update_interval; |
1049 |
this.stats_fetcher.stop();
|
1050 |
this.stats_fetcher.start(false); |
1051 |
} |
1052 |
}, |
1053 |
|
1054 |
// helper method that sets the do_update_stats
|
1055 |
// in the future this method could also make an api call
|
1056 |
// immediaetly if needed
|
1057 |
enable_stats_update: function() { |
1058 |
this.do_update_stats = true; |
1059 |
}, |
1060 |
|
1061 |
handle_destroy: function() { |
1062 |
this.stats_fetcher.stop();
|
1063 |
}, |
1064 |
|
1065 |
require_reboot: function() { |
1066 |
if (this.is_active()) { |
1067 |
this.set({'reboot_required': true}); |
1068 |
} |
1069 |
}, |
1070 |
|
1071 |
set_pending_action: function(data) { |
1072 |
this.pending_action = data;
|
1073 |
return data;
|
1074 |
}, |
1075 |
|
1076 |
// machine has pending action
|
1077 |
update_pending_action: function(action, force) { |
1078 |
this.set({pending_action: action}); |
1079 |
}, |
1080 |
|
1081 |
clear_pending_action: function() { |
1082 |
this.set({pending_action: undefined}); |
1083 |
}, |
1084 |
|
1085 |
has_pending_action: function() { |
1086 |
return this.get("pending_action") ? this.get("pending_action") : false; |
1087 |
}, |
1088 |
|
1089 |
// machine is active
|
1090 |
is_active: function() { |
1091 |
return models.VM.ACTIVE_STATES.indexOf(this.state()) > -1; |
1092 |
}, |
1093 |
|
1094 |
// machine is building
|
1095 |
is_building: function() { |
1096 |
return models.VM.BUILDING_STATES.indexOf(this.state()) > -1; |
1097 |
}, |
1098 |
|
1099 |
in_error_state: function() { |
1100 |
return this.state() === "ERROR" |
1101 |
}, |
1102 |
|
1103 |
// user can connect to machine
|
1104 |
is_connectable: function() { |
1105 |
// check if ips exist
|
1106 |
if (!this.get_addresses().ip4 && !this.get_addresses().ip6) { |
1107 |
return false; |
1108 |
} |
1109 |
return models.VM.CONNECT_STATES.indexOf(this.state()) > -1; |
1110 |
}, |
1111 |
|
1112 |
remove_meta: function(key, complete, error) { |
1113 |
var url = this.api_path() + "/meta/" + key; |
1114 |
this.api_call(url, "delete", undefined, complete, error); |
1115 |
}, |
1116 |
|
1117 |
save_meta: function(meta, complete, error) { |
1118 |
var url = this.api_path() + "/meta/" + meta.key; |
1119 |
var payload = {meta:{}}; |
1120 |
payload.meta[meta.key] = meta.value; |
1121 |
payload._options = { |
1122 |
critical:false, |
1123 |
error_params: {
|
1124 |
title: "Machine metadata error", |
1125 |
extra_details: {"Machine id": this.id} |
1126 |
}}; |
1127 |
|
1128 |
this.api_call(url, "update", payload, complete, error); |
1129 |
}, |
1130 |
|
1131 |
|
1132 |
// update/get the state of the machine
|
1133 |
state: function() { |
1134 |
var args = slice.call(arguments); |
1135 |
|
1136 |
// TODO: it might not be a good idea to set the state in set_state method
|
1137 |
if (args.length > 0 && models.VM.STATES.indexOf(args[0]) > -1) { |
1138 |
this.set({'state': args[0]}); |
1139 |
} |
1140 |
|
1141 |
return this.get('state'); |
1142 |
}, |
1143 |
|
1144 |
// get the state that the api status corresponds to
|
1145 |
state_for_api_status: function(status) { |
1146 |
return this.state_transition(this.state(), status); |
1147 |
}, |
1148 |
|
1149 |
// vm state equals vm api status
|
1150 |
state_is_status: function(state) { |
1151 |
return models.VM.STATUSES.indexOf(state) != -1; |
1152 |
}, |
1153 |
|
1154 |
// get transition state for the corresponging api status
|
1155 |
state_transition: function(state, new_status) { |
1156 |
var statuses = models.VM.STATES_TRANSITIONS[state];
|
1157 |
if (statuses) {
|
1158 |
if (statuses.indexOf(new_status) > -1) { |
1159 |
return new_status;
|
1160 |
} else {
|
1161 |
return state;
|
1162 |
} |
1163 |
} else {
|
1164 |
return new_status;
|
1165 |
} |
1166 |
}, |
1167 |
|
1168 |
// the current vm state is a transition state
|
1169 |
in_transition: function() { |
1170 |
return models.VM.TRANSITION_STATES.indexOf(this.state()) > -1 || |
1171 |
models.VM.TRANSITION_STATES.indexOf(this.get('status')) > -1; |
1172 |
}, |
1173 |
|
1174 |
// get image object
|
1175 |
get_image: function(callback) { |
1176 |
if (callback == undefined) { callback = function(){} } |
1177 |
var image = storage.images.get(this.get('imageRef')); |
1178 |
if (!image) {
|
1179 |
storage.images.update_unknown_id(this.get('imageRef'), callback); |
1180 |
return;
|
1181 |
} |
1182 |
callback(image); |
1183 |
return image;
|
1184 |
}, |
1185 |
|
1186 |
// get flavor object
|
1187 |
get_flavor: function() { |
1188 |
var flv = storage.flavors.get(this.get('flavorRef')); |
1189 |
if (!flv) {
|
1190 |
storage.flavors.update_unknown_id(this.get('flavorRef')); |
1191 |
flv = storage.flavors.get(this.get('flavorRef')); |
1192 |
} |
1193 |
return flv;
|
1194 |
}, |
1195 |
|
1196 |
get_meta: function(key, deflt) { |
1197 |
if (this.get('metadata') && this.get('metadata').values) { |
1198 |
if (!this.get('metadata').values[key]) { return deflt } |
1199 |
return _.escape(this.get('metadata').values[key]); |
1200 |
} else {
|
1201 |
return deflt;
|
1202 |
} |
1203 |
}, |
1204 |
|
1205 |
get_meta_keys: function() { |
1206 |
if (this.get('metadata') && this.get('metadata').values) { |
1207 |
return _.keys(this.get('metadata').values); |
1208 |
} else {
|
1209 |
return [];
|
1210 |
} |
1211 |
}, |
1212 |
|
1213 |
// get metadata OS value
|
1214 |
get_os: function() { |
1215 |
var image = this.get_image(); |
1216 |
return this.get_meta('OS') || (image ? |
1217 |
image.get_os() || "okeanos" : "okeanos"); |
1218 |
}, |
1219 |
|
1220 |
get_gui: function() { |
1221 |
return this.get_meta('GUI'); |
1222 |
}, |
1223 |
|
1224 |
connected_to: function(net) { |
1225 |
return this.get('linked_to').indexOf(net.id) > -1; |
1226 |
}, |
1227 |
|
1228 |
connected_with_nic_id: function(nic_id) { |
1229 |
return _.keys(this.get('nics')).indexOf(nic_id) > -1; |
1230 |
}, |
1231 |
|
1232 |
get_nics: function(filter) { |
1233 |
ret = synnefo.storage.nics.filter(function(nic) {
|
1234 |
return parseInt(nic.get('vm_id')) == this.id; |
1235 |
}, this);
|
1236 |
|
1237 |
if (filter) {
|
1238 |
return _.filter(ret, filter);
|
1239 |
} |
1240 |
|
1241 |
return ret;
|
1242 |
}, |
1243 |
|
1244 |
get_net_nics: function(net_id) { |
1245 |
return this.get_nics(function(n){return n.get('network_id') == net_id}); |
1246 |
}, |
1247 |
|
1248 |
get_public_nic: function() { |
1249 |
return this.get_nics(function(n){ return n.get_network().is_public() === true })[0]; |
1250 |
}, |
1251 |
|
1252 |
get_hostname: function() { |
1253 |
var hostname = this.get_meta('hostname'); |
1254 |
if (!hostname) {
|
1255 |
hostname = synnefo.config.vm_hostname_format.format(this.id);
|
1256 |
} |
1257 |
return hostname;
|
1258 |
}, |
1259 |
|
1260 |
get_nic: function(net_id) { |
1261 |
}, |
1262 |
|
1263 |
has_firewall: function() { |
1264 |
var nic = this.get_public_nic(); |
1265 |
if (nic) {
|
1266 |
var profile = nic.get('firewallProfile'); |
1267 |
return ['ENABLED', 'PROTECTED'].indexOf(profile) > -1; |
1268 |
} |
1269 |
return false; |
1270 |
}, |
1271 |
|
1272 |
get_firewall_profile: function() { |
1273 |
var nic = this.get_public_nic(); |
1274 |
if (nic) {
|
1275 |
return nic.get('firewallProfile'); |
1276 |
} |
1277 |
return null; |
1278 |
}, |
1279 |
|
1280 |
get_addresses: function() { |
1281 |
var pnic = this.get_public_nic(); |
1282 |
if (!pnic) { return {'ip4': undefined, 'ip6': undefined }}; |
1283 |
return {'ip4': pnic.get('ipv4'), 'ip6': pnic.get('ipv6')}; |
1284 |
}, |
1285 |
|
1286 |
// get actions that the user can execute
|
1287 |
// depending on the vm state/status
|
1288 |
get_available_actions: function() { |
1289 |
return models.VM.AVAILABLE_ACTIONS[this.state()]; |
1290 |
}, |
1291 |
|
1292 |
set_profile: function(profile, net_id) { |
1293 |
}, |
1294 |
|
1295 |
// call rename api
|
1296 |
rename: function(new_name) { |
1297 |
//this.set({'name': new_name});
|
1298 |
this.sync("update", this, { |
1299 |
critical: true, |
1300 |
data: {
|
1301 |
'server': {
|
1302 |
'name': new_name
|
1303 |
} |
1304 |
}, |
1305 |
// do the rename after the method succeeds
|
1306 |
success: _.bind(function(){ |
1307 |
//this.set({name: new_name});
|
1308 |
snf.api.trigger("call");
|
1309 |
}, this)
|
1310 |
}); |
1311 |
}, |
1312 |
|
1313 |
get_console_url: function(data) { |
1314 |
var url_params = {
|
1315 |
machine: this.get("name"), |
1316 |
host_ip: this.get_addresses().ip4, |
1317 |
host_ip_v6: this.get_addresses().ip6, |
1318 |
host: data.host,
|
1319 |
port: data.port,
|
1320 |
password: data.password
|
1321 |
} |
1322 |
return '/machines/console?' + $.param(url_params); |
1323 |
}, |
1324 |
|
1325 |
// action helper
|
1326 |
call: function(action_name, success, error, params) { |
1327 |
var id_param = [this.id]; |
1328 |
|
1329 |
params = params || {}; |
1330 |
success = success || function() {};
|
1331 |
error = error || function() {};
|
1332 |
|
1333 |
var self = this; |
1334 |
|
1335 |
switch(action_name) {
|
1336 |
case 'start': |
1337 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1338 |
"create", // create so that sync later uses POST to make the call |
1339 |
{start:{}}, // payload |
1340 |
function() {
|
1341 |
// set state after successful call
|
1342 |
self.state("START");
|
1343 |
success.apply(this, arguments); |
1344 |
snf.api.trigger("call");
|
1345 |
}, |
1346 |
error, 'start', params);
|
1347 |
break;
|
1348 |
case 'reboot': |
1349 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1350 |
"create", // create so that sync later uses POST to make the call |
1351 |
{reboot:{type:"HARD"}}, // payload |
1352 |
function() {
|
1353 |
// set state after successful call
|
1354 |
self.state("REBOOT");
|
1355 |
success.apply(this, arguments) |
1356 |
snf.api.trigger("call");
|
1357 |
self.set({'reboot_required': false}); |
1358 |
}, |
1359 |
error, 'reboot', params);
|
1360 |
break;
|
1361 |
case 'shutdown': |
1362 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1363 |
"create", // create so that sync later uses POST to make the call |
1364 |
{shutdown:{}}, // payload |
1365 |
function() {
|
1366 |
// set state after successful call
|
1367 |
self.state("SHUTDOWN");
|
1368 |
success.apply(this, arguments) |
1369 |
snf.api.trigger("call");
|
1370 |
}, |
1371 |
error, 'shutdown', params);
|
1372 |
break;
|
1373 |
case 'console': |
1374 |
this.__make_api_call(this.url() + "/action", "create", {'console': {'type':'vnc'}}, function(data) { |
1375 |
var cons_data = data.console;
|
1376 |
success.apply(this, [cons_data]);
|
1377 |
}, undefined, 'console', params) |
1378 |
break;
|
1379 |
case 'destroy': |
1380 |
this.__make_api_call(this.url(), // vm actions url |
1381 |
"delete", // create so that sync later uses POST to make the call |
1382 |
undefined, // payload |
1383 |
function() {
|
1384 |
// set state after successful call
|
1385 |
self.state('DESTROY');
|
1386 |
success.apply(this, arguments) |
1387 |
}, |
1388 |
error, 'destroy', params);
|
1389 |
break;
|
1390 |
default:
|
1391 |
throw "Invalid VM action ("+action_name+")"; |
1392 |
} |
1393 |
}, |
1394 |
|
1395 |
__make_api_call: function(url, method, data, success, error, action, extra_params) { |
1396 |
var self = this; |
1397 |
error = error || function(){};
|
1398 |
success = success || function(){};
|
1399 |
|
1400 |
var params = {
|
1401 |
url: url,
|
1402 |
data: data,
|
1403 |
success: function(){ self.handle_action_succeed.apply(self, arguments); success.apply(this, arguments)}, |
1404 |
error: function(){ self.handle_action_fail.apply(self, arguments); error.apply(this, arguments)}, |
1405 |
error_params: { ns: "Machines actions", |
1406 |
title: "'" + this.get("name") + "'" + " " + action + " failed", |
1407 |
extra_details: { 'Machine ID': this.id, 'URL': url, 'Action': action || "undefined" }, |
1408 |
allow_reload: false |
1409 |
}, |
1410 |
display: false, |
1411 |
critical: false |
1412 |
} |
1413 |
_.extend(params, extra_params) |
1414 |
this.sync(method, this, params); |
1415 |
}, |
1416 |
|
1417 |
handle_action_succeed: function() { |
1418 |
this.trigger("action:success", arguments); |
1419 |
}, |
1420 |
|
1421 |
reset_action_error: function() { |
1422 |
this.action_error = false; |
1423 |
this.trigger("action:fail:reset", this.action_error); |
1424 |
}, |
1425 |
|
1426 |
handle_action_fail: function() { |
1427 |
this.action_error = arguments; |
1428 |
this.trigger("action:fail", arguments); |
1429 |
}, |
1430 |
|
1431 |
get_action_url: function(name) { |
1432 |
return this.url() + "/action"; |
1433 |
}, |
1434 |
|
1435 |
get_diagnostics_url: function() { |
1436 |
return this.url() + "/diagnostics"; |
1437 |
}, |
1438 |
|
1439 |
get_connection_info: function(host_os, success, error) { |
1440 |
var url = "/machines/connect"; |
1441 |
params = { |
1442 |
ip_address: this.get_public_nic().get('ipv4'), |
1443 |
hostname: this.get_hostname(), |
1444 |
os: this.get_os(), |
1445 |
host_os: host_os,
|
1446 |
srv: this.id |
1447 |
} |
1448 |
|
1449 |
url = url + "?" + $.param(params); |
1450 |
|
1451 |
var ajax = snf.api.sync("read", undefined, { url: url, |
1452 |
error:error,
|
1453 |
success:success,
|
1454 |
handles_error:1}); |
1455 |
} |
1456 |
}) |
1457 |
|
1458 |
models.VM.ACTIONS = [ |
1459 |
'start',
|
1460 |
'shutdown',
|
1461 |
'reboot',
|
1462 |
'console',
|
1463 |
'destroy'
|
1464 |
] |
1465 |
|
1466 |
models.VM.AVAILABLE_ACTIONS = { |
1467 |
'UNKNWON' : ['destroy'], |
1468 |
'BUILD' : ['destroy'], |
1469 |
'REBOOT' : ['shutdown', 'destroy', 'console'], |
1470 |
'STOPPED' : ['start', 'destroy'], |
1471 |
'ACTIVE' : ['shutdown', 'destroy', 'reboot', 'console'], |
1472 |
'ERROR' : ['destroy'], |
1473 |
'DELETED' : [],
|
1474 |
'DESTROY' : [],
|
1475 |
'BUILD_INIT' : ['destroy'], |
1476 |
'BUILD_COPY' : ['destroy'], |
1477 |
'BUILD_FINAL' : ['destroy'], |
1478 |
'SHUTDOWN' : ['destroy'], |
1479 |
'START' : [],
|
1480 |
'CONNECT' : [],
|
1481 |
'DISCONNECT' : []
|
1482 |
} |
1483 |
|
1484 |
// api status values
|
1485 |
models.VM.STATUSES = [ |
1486 |
'UNKNWON',
|
1487 |
'BUILD',
|
1488 |
'REBOOT',
|
1489 |
'STOPPED',
|
1490 |
'ACTIVE',
|
1491 |
'ERROR',
|
1492 |
'DELETED'
|
1493 |
] |
1494 |
|
1495 |
// api status values
|
1496 |
models.VM.CONNECT_STATES = [ |
1497 |
'ACTIVE',
|
1498 |
'REBOOT',
|
1499 |
'SHUTDOWN'
|
1500 |
] |
1501 |
|
1502 |
// vm states
|
1503 |
models.VM.STATES = models.VM.STATUSES.concat([ |
1504 |
'DESTROY',
|
1505 |
'BUILD_INIT',
|
1506 |
'BUILD_COPY',
|
1507 |
'BUILD_FINAL',
|
1508 |
'SHUTDOWN',
|
1509 |
'START',
|
1510 |
'CONNECT',
|
1511 |
'DISCONNECT',
|
1512 |
'FIREWALL'
|
1513 |
]); |
1514 |
|
1515 |
models.VM.STATES_TRANSITIONS = { |
1516 |
'DESTROY' : ['DELETED'], |
1517 |
'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'], |
1518 |
'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY'], |
1519 |
'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY'], |
1520 |
'START': ['ERROR', 'ACTIVE', 'DESTROY'], |
1521 |
'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'], |
1522 |
'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'], |
1523 |
'BUILD_COPY': ['ERROR', 'ACTIVE', 'BUILD_FINAL', 'DESTROY'], |
1524 |
'BUILD_FINAL': ['ERROR', 'ACTIVE', 'DESTROY'], |
1525 |
'BUILD_INIT': ['ERROR', 'ACTIVE', 'BUILD_COPY', 'BUILD_FINAL', 'DESTROY'] |
1526 |
} |
1527 |
|
1528 |
models.VM.TRANSITION_STATES = [ |
1529 |
'DESTROY',
|
1530 |
'SHUTDOWN',
|
1531 |
'START',
|
1532 |
'REBOOT',
|
1533 |
'BUILD'
|
1534 |
] |
1535 |
|
1536 |
models.VM.ACTIVE_STATES = [ |
1537 |
'BUILD', 'REBOOT', 'ACTIVE', |
1538 |
'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL', |
1539 |
'SHUTDOWN', 'CONNECT', 'DISCONNECT' |
1540 |
] |
1541 |
|
1542 |
models.VM.BUILDING_STATES = [ |
1543 |
'BUILD', 'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL' |
1544 |
] |
1545 |
|
1546 |
models.Networks = models.Collection.extend({ |
1547 |
model: models.Network,
|
1548 |
path: 'networks', |
1549 |
details: true, |
1550 |
//noUpdate: true,
|
1551 |
defaults: {'nics':[],'linked_to':[]}, |
1552 |
|
1553 |
parse: function (resp, xhr) { |
1554 |
// FIXME: depricated global var
|
1555 |
if (!resp) { return []}; |
1556 |
var data = _.map(resp.networks.values, _.bind(this.parse_net_api_data, this)); |
1557 |
return data;
|
1558 |
}, |
1559 |
|
1560 |
add: function() { |
1561 |
ret = models.Networks.__super__.add.apply(this, arguments); |
1562 |
// update nics after each network addition
|
1563 |
ret.each(function(r){
|
1564 |
synnefo.storage.nics.update_net_nics(r); |
1565 |
}); |
1566 |
}, |
1567 |
|
1568 |
reset_pending_actions: function() { |
1569 |
this.each(function(net) { |
1570 |
net.get("actions").reset();
|
1571 |
}); |
1572 |
}, |
1573 |
|
1574 |
do_all_pending_actions: function() { |
1575 |
this.each(function(net) { |
1576 |
net.do_all_pending_actions(); |
1577 |
}) |
1578 |
}, |
1579 |
|
1580 |
parse_net_api_data: function(data) { |
1581 |
// append nic metadata
|
1582 |
// net.get('nics') contains a list of vm/index objects
|
1583 |
// e.g. {'vm_id':12231, 'index':1}
|
1584 |
// net.get('linked_to') contains a list of vms the network is
|
1585 |
// connected to e.g. [1001, 1002]
|
1586 |
if (data.attachments && data.attachments.values) {
|
1587 |
data['nics'] = {};
|
1588 |
data['linked_to'] = [];
|
1589 |
_.each(data.attachments.values, function(nic_id){
|
1590 |
|
1591 |
var vm_id = NIC_REGEX.exec(nic_id)[1]; |
1592 |
var nic_index = parseInt(NIC_REGEX.exec(nic_id)[2]); |
1593 |
|
1594 |
if (vm_id !== undefined && nic_index !== undefined) { |
1595 |
data['nics'][nic_id] = {
|
1596 |
'vm_id': vm_id,
|
1597 |
'index': nic_index,
|
1598 |
'id': nic_id
|
1599 |
}; |
1600 |
if (data['linked_to'].indexOf(vm_id) == -1) { |
1601 |
data['linked_to'].push(vm_id);
|
1602 |
} |
1603 |
} |
1604 |
}); |
1605 |
} |
1606 |
return data;
|
1607 |
}, |
1608 |
|
1609 |
create: function (name, type, cidr, dhcp, callback) { |
1610 |
var params = {
|
1611 |
network:{
|
1612 |
name:name
|
1613 |
} |
1614 |
}; |
1615 |
|
1616 |
if (type) {
|
1617 |
params.network.type = type; |
1618 |
} |
1619 |
if (cidr) {
|
1620 |
params.network.cidr = cidr; |
1621 |
} |
1622 |
if (dhcp) {
|
1623 |
params.network.dhcp = dhcp; |
1624 |
} |
1625 |
|
1626 |
if (dhcp === false) { |
1627 |
params.network.dhcp = false;
|
1628 |
} |
1629 |
|
1630 |
return this.api_call(this.path, "create", params, callback); |
1631 |
}, |
1632 |
|
1633 |
get_public: function(){ |
1634 |
return this.filter(function(n){return n.get('public')}); |
1635 |
} |
1636 |
}) |
1637 |
|
1638 |
models.Images = models.Collection.extend({ |
1639 |
model: models.Image,
|
1640 |
path: 'images', |
1641 |
details: true, |
1642 |
noUpdate: true, |
1643 |
supportIncUpdates: false, |
1644 |
meta_keys_as_attrs: ["OS", "description", "kernel", "size", "GUI"], |
1645 |
meta_labels: {},
|
1646 |
read_method: 'read', |
1647 |
|
1648 |
// update collection model with id passed
|
1649 |
// making a direct call to the image
|
1650 |
// api url
|
1651 |
update_unknown_id: function(id, callback) { |
1652 |
var url = getUrl.call(this) + "/" + id; |
1653 |
this.api_call(this.path + "/" + id, this.read_method, { |
1654 |
_options:{
|
1655 |
async:true, |
1656 |
skip_api_error:true} |
1657 |
}, undefined,
|
1658 |
_.bind(function() {
|
1659 |
if (!this.get(id)) { |
1660 |
if (this.fallback_service) { |
1661 |
// if current service has fallback_service attribute set
|
1662 |
// use this service to retrieve the missing image model
|
1663 |
var tmpservice = new this.fallback_service(); |
1664 |
tmpservice.update_unknown_id(id, _.bind(function(img){
|
1665 |
img.attributes.status = "DELETED";
|
1666 |
this.add(img.attributes);
|
1667 |
callback(this.get(id));
|
1668 |
}, this));
|
1669 |
} else {
|
1670 |
var title = synnefo.config.image_deleted_title || 'Deleted'; |
1671 |
// else add a dummy DELETED state image entry
|
1672 |
this.add({id:id, name:title, size:-1, |
1673 |
progress:100, status:"DELETED"}); |
1674 |
callback(this.get(id));
|
1675 |
} |
1676 |
} else {
|
1677 |
callback(this.get(id));
|
1678 |
} |
1679 |
}, this), _.bind(function(image, msg, xhr) { |
1680 |
if (!image) {
|
1681 |
var title = synnefo.config.image_deleted_title || 'Deleted'; |
1682 |
this.add({id:id, name:title, size:-1, |
1683 |
progress:100, status:"DELETED"}); |
1684 |
callback(this.get(id));
|
1685 |
return;
|
1686 |
} |
1687 |
var img_data = this._read_image_from_request(image, msg, xhr); |
1688 |
this.add(img_data);
|
1689 |
callback(this.get(id));
|
1690 |
}, this));
|
1691 |
}, |
1692 |
|
1693 |
_read_image_from_request: function(image, msg, xhr) { |
1694 |
return image.image;
|
1695 |
}, |
1696 |
|
1697 |
parse: function (resp, xhr) { |
1698 |
var data = _.map(resp.images.values, _.bind(this.parse_meta, this)); |
1699 |
return resp.images.values;
|
1700 |
}, |
1701 |
|
1702 |
get_meta_key: function(img, key) { |
1703 |
if (img.metadata && img.metadata.values && img.metadata.values[key]) {
|
1704 |
return _.escape(img.metadata.values[key]);
|
1705 |
} |
1706 |
return undefined; |
1707 |
}, |
1708 |
|
1709 |
comparator: function(img) { |
1710 |
return -img.get_sort_order("sortorder") || 1000 * img.id; |
1711 |
}, |
1712 |
|
1713 |
parse_meta: function(img) { |
1714 |
_.each(this.meta_keys_as_attrs, _.bind(function(key){ |
1715 |
if (img[key]) { return }; |
1716 |
img[key] = this.get_meta_key(img, key) || ""; |
1717 |
}, this));
|
1718 |
return img;
|
1719 |
}, |
1720 |
|
1721 |
active: function() { |
1722 |
return this.filter(function(img){return img.get('status') != "DELETED"}); |
1723 |
}, |
1724 |
|
1725 |
predefined: function() { |
1726 |
return _.filter(this.active(), function(i) { return !i.get("serverRef")}); |
1727 |
}, |
1728 |
|
1729 |
fetch_for_type: function(type, complete, error) { |
1730 |
this.fetch({update:true, |
1731 |
success: complete,
|
1732 |
error: error,
|
1733 |
skip_api_error: true }); |
1734 |
}, |
1735 |
|
1736 |
get_images_for_type: function(type) { |
1737 |
if (this['get_{0}_images'.format(type)]) { |
1738 |
return this['get_{0}_images'.format(type)](); |
1739 |
} |
1740 |
|
1741 |
return this.active(); |
1742 |
}, |
1743 |
|
1744 |
update_images_for_type: function(type, onStart, onComplete, onError, force_load) { |
1745 |
var load = false; |
1746 |
error = onError || function() {};
|
1747 |
function complete(collection) { |
1748 |
onComplete(collection.get_images_for_type(type)); |
1749 |
} |
1750 |
|
1751 |
// do we need to fetch/update current collection entries
|
1752 |
if (load) {
|
1753 |
onStart(); |
1754 |
this.fetch_for_type(type, complete, error);
|
1755 |
} else {
|
1756 |
// fallback to complete
|
1757 |
complete(this);
|
1758 |
} |
1759 |
} |
1760 |
}) |
1761 |
|
1762 |
models.Flavors = models.Collection.extend({ |
1763 |
model: models.Flavor,
|
1764 |
path: 'flavors', |
1765 |
details: true, |
1766 |
noUpdate: true, |
1767 |
supportIncUpdates: false, |
1768 |
// update collection model with id passed
|
1769 |
// making a direct call to the flavor
|
1770 |
// api url
|
1771 |
update_unknown_id: function(id, callback) { |
1772 |
var url = getUrl.call(this) + "/" + id; |
1773 |
this.api_call(this.path + "/" + id, "read", {_options:{async:false, skip_api_error:true}}, undefined, |
1774 |
_.bind(function() {
|
1775 |
this.add({id:id, cpu:"Unknown", ram:"Unknown", disk:"Unknown", name: "Unknown", status:"DELETED"}) |
1776 |
}, this), _.bind(function(flv) { |
1777 |
if (!flv.flavor.status) { flv.flavor.status = "DELETED" }; |
1778 |
this.add(flv.flavor);
|
1779 |
}, this));
|
1780 |
}, |
1781 |
|
1782 |
parse: function (resp, xhr) { |
1783 |
return _.map(resp.flavors.values, function(o) { o.disk_template = o['SNF:disk_template']; return o}); |
1784 |
}, |
1785 |
|
1786 |
comparator: function(flv) { |
1787 |
return flv.get("disk") * flv.get("cpu") * flv.get("ram"); |
1788 |
}, |
1789 |
|
1790 |
unavailable_values_for_image: function(img, flavors) { |
1791 |
var flavors = flavors || this.active(); |
1792 |
var size = img.get_size();
|
1793 |
|
1794 |
var index = {cpu:[], disk:[], ram:[]}; |
1795 |
|
1796 |
_.each(this.active(), function(el) { |
1797 |
var img_size = size;
|
1798 |
var flv_size = el.get_disk_size();
|
1799 |
if (flv_size < img_size) {
|
1800 |
if (index.disk.indexOf(flv_size) == -1) { |
1801 |
index.disk.push(flv_size); |
1802 |
} |
1803 |
}; |
1804 |
}); |
1805 |
|
1806 |
return index;
|
1807 |
}, |
1808 |
|
1809 |
get_flavor: function(cpu, mem, disk, disk_template, filter_list) { |
1810 |
if (!filter_list) { filter_list = this.models }; |
1811 |
|
1812 |
return this.select(function(flv){ |
1813 |
if (flv.get("cpu") == cpu + "" && |
1814 |
flv.get("ram") == mem + "" && |
1815 |
flv.get("disk") == disk + "" && |
1816 |
flv.get("disk_template") == disk_template &&
|
1817 |
filter_list.indexOf(flv) > -1) { return true; } |
1818 |
})[0];
|
1819 |
}, |
1820 |
|
1821 |
get_data: function(lst) { |
1822 |
var data = {'cpu': [], 'mem':[], 'disk':[]}; |
1823 |
|
1824 |
_.each(lst, function(flv) {
|
1825 |
if (data.cpu.indexOf(flv.get("cpu")) == -1) { |
1826 |
data.cpu.push(flv.get("cpu"));
|
1827 |
} |
1828 |
if (data.mem.indexOf(flv.get("ram")) == -1) { |
1829 |
data.mem.push(flv.get("ram"));
|
1830 |
} |
1831 |
if (data.disk.indexOf(flv.get("disk")) == -1) { |
1832 |
data.disk.push(flv.get("disk"));
|
1833 |
} |
1834 |
}) |
1835 |
|
1836 |
return data;
|
1837 |
}, |
1838 |
|
1839 |
active: function() { |
1840 |
return this.filter(function(flv){return flv.get('status') != "DELETED"}); |
1841 |
} |
1842 |
|
1843 |
}) |
1844 |
|
1845 |
models.VMS = models.Collection.extend({ |
1846 |
model: models.VM,
|
1847 |
path: 'servers', |
1848 |
details: true, |
1849 |
copy_image_meta: true, |
1850 |
|
1851 |
parse: function (resp, xhr) { |
1852 |
var data = resp;
|
1853 |
if (!resp) { return [] }; |
1854 |
data = _.filter(_.map(resp.servers.values, _.bind(this.parse_vm_api_data, this)), function(v){return v}); |
1855 |
return data;
|
1856 |
}, |
1857 |
|
1858 |
parse_vm_api_data: function(data) { |
1859 |
// do not add non existing DELETED entries
|
1860 |
if (data.status && data.status == "DELETED") { |
1861 |
if (!this.get(data.id)) { |
1862 |
return false; |
1863 |
} |
1864 |
} |
1865 |
|
1866 |
// OS attribute
|
1867 |
if (this.has_meta(data)) { |
1868 |
data['OS'] = data.metadata.values.OS || "okeanos"; |
1869 |
} |
1870 |
|
1871 |
if (!data.diagnostics) {
|
1872 |
data.diagnostics = []; |
1873 |
} |
1874 |
|
1875 |
// network metadata
|
1876 |
data['firewalls'] = {};
|
1877 |
data['nics'] = {};
|
1878 |
data['linked_to'] = [];
|
1879 |
|
1880 |
if (data['attachments'] && data['attachments'].values) { |
1881 |
var nics = data['attachments'].values; |
1882 |
_.each(nics, function(nic) {
|
1883 |
var net_id = nic.network_id;
|
1884 |
var index = parseInt(NIC_REGEX.exec(nic.id)[2]); |
1885 |
if (data['linked_to'].indexOf(net_id) == -1) { |
1886 |
data['linked_to'].push(net_id);
|
1887 |
} |
1888 |
|
1889 |
data['nics'][nic.id] = nic;
|
1890 |
}) |
1891 |
} |
1892 |
|
1893 |
// if vm has no metadata, no metadata object
|
1894 |
// is in json response, reset it to force
|
1895 |
// value update
|
1896 |
if (!data['metadata']) { |
1897 |
data['metadata'] = {values:{}}; |
1898 |
} |
1899 |
|
1900 |
return data;
|
1901 |
}, |
1902 |
|
1903 |
add: function() { |
1904 |
ret = models.VMS.__super__.add.apply(this, arguments); |
1905 |
ret.each(function(r){
|
1906 |
synnefo.storage.nics.update_vm_nics(r); |
1907 |
}); |
1908 |
}, |
1909 |
|
1910 |
get_reboot_required: function() { |
1911 |
return this.filter(function(vm){return vm.get("reboot_required") == true}) |
1912 |
}, |
1913 |
|
1914 |
has_pending_actions: function() { |
1915 |
return this.filter(function(vm){return vm.pending_action}).length > 0; |
1916 |
}, |
1917 |
|
1918 |
reset_pending_actions: function() { |
1919 |
this.each(function(vm) { |
1920 |
vm.clear_pending_action(); |
1921 |
}) |
1922 |
}, |
1923 |
|
1924 |
do_all_pending_actions: function(success, error) { |
1925 |
this.each(function(vm) { |
1926 |
if (vm.has_pending_action()) {
|
1927 |
vm.call(vm.pending_action, success, error); |
1928 |
vm.clear_pending_action(); |
1929 |
} |
1930 |
}) |
1931 |
}, |
1932 |
|
1933 |
do_all_reboots: function(success, error) { |
1934 |
this.each(function(vm) { |
1935 |
if (vm.get("reboot_required")) { |
1936 |
vm.call("reboot", success, error);
|
1937 |
} |
1938 |
}); |
1939 |
}, |
1940 |
|
1941 |
reset_reboot_required: function() { |
1942 |
this.each(function(vm) { |
1943 |
vm.set({'reboot_required': undefined}); |
1944 |
}) |
1945 |
}, |
1946 |
|
1947 |
stop_stats_update: function(exclude) { |
1948 |
var exclude = exclude || [];
|
1949 |
this.each(function(vm) { |
1950 |
if (exclude.indexOf(vm) > -1) { |
1951 |
return;
|
1952 |
} |
1953 |
vm.stop_stats_update(); |
1954 |
}) |
1955 |
}, |
1956 |
|
1957 |
has_meta: function(vm_data) { |
1958 |
return vm_data.metadata && vm_data.metadata.values
|
1959 |
}, |
1960 |
|
1961 |
has_addresses: function(vm_data) { |
1962 |
return vm_data.metadata && vm_data.metadata.values
|
1963 |
}, |
1964 |
|
1965 |
create: function (name, image, flavor, meta, extra, callback) { |
1966 |
|
1967 |
if (this.copy_image_meta) { |
1968 |
if (synnefo.config.vm_image_common_metadata) {
|
1969 |
_.each(synnefo.config.vm_image_common_metadata, |
1970 |
function(key){
|
1971 |
if (image.get_meta(key)) {
|
1972 |
meta[key] = image.get_meta(key); |
1973 |
} |
1974 |
}); |
1975 |
} |
1976 |
|
1977 |
if (image.get("OS")) { |
1978 |
meta['OS'] = image.get("OS"); |
1979 |
} |
1980 |
} |
1981 |
|
1982 |
opts = {name: name, imageRef: image.id, flavorRef: flavor.id, metadata:meta} |
1983 |
opts = _.extend(opts, extra); |
1984 |
|
1985 |
this.api_call(this.path, "create", {'server': opts}, undefined, undefined, callback, {critical: true}); |
1986 |
} |
1987 |
|
1988 |
}) |
1989 |
|
1990 |
models.NIC = models.Model.extend({ |
1991 |
|
1992 |
initialize: function() { |
1993 |
models.NIC.__super__.initialize.apply(this, arguments); |
1994 |
this.pending_for_firewall = false; |
1995 |
this.bind("change:firewallProfile", _.bind(this.check_firewall, this)); |
1996 |
this.bind("change:pending_firewall", function(nic) { |
1997 |
nic.get_network().update_state(); |
1998 |
}); |
1999 |
this.get_vm().bind("remove", function(){ |
2000 |
try {
|
2001 |
this.collection.remove(this); |
2002 |
} catch (err) {};
|
2003 |
}, this);
|
2004 |
this.get_network().bind("remove", function(){ |
2005 |
try {
|
2006 |
this.collection.remove(this); |
2007 |
} catch (err) {};
|
2008 |
}, this);
|
2009 |
|
2010 |
}, |
2011 |
|
2012 |
get_vm: function() { |
2013 |
return synnefo.storage.vms.get(parseInt(this.get('vm_id'))); |
2014 |
}, |
2015 |
|
2016 |
get_network: function() { |
2017 |
return synnefo.storage.networks.get(this.get('network_id')); |
2018 |
}, |
2019 |
|
2020 |
get_v6_address: function() { |
2021 |
return this.get("ipv6"); |
2022 |
}, |
2023 |
|
2024 |
get_v4_address: function() { |
2025 |
return this.get("ipv4"); |
2026 |
}, |
2027 |
|
2028 |
set_firewall: function(value, callback, error, options) { |
2029 |
var net_id = this.get('network_id'); |
2030 |
var self = this; |
2031 |
|
2032 |
// api call data
|
2033 |
var payload = {"firewallProfile":{"profile":value}}; |
2034 |
payload._options = _.extend({critical: false}, options); |
2035 |
|
2036 |
this.set({'pending_firewall': value}); |
2037 |
this.set({'pending_firewall_sending': true}); |
2038 |
this.set({'pending_firewall_from': this.get('firewallProfile')}); |
2039 |
|
2040 |
var success_cb = function() { |
2041 |
if (callback) {
|
2042 |
callback(); |
2043 |
} |
2044 |
self.set({'pending_firewall_sending': false}); |
2045 |
}; |
2046 |
|
2047 |
var error_cb = function() { |
2048 |
self.reset_pending_firewall(); |
2049 |
} |
2050 |
|
2051 |
this.get_vm().api_call(this.get_vm().api_path() + "/action", "create", payload, success_cb, error_cb); |
2052 |
}, |
2053 |
|
2054 |
reset_pending_firewall: function() { |
2055 |
this.set({'pending_firewall': false}); |
2056 |
this.set({'pending_firewall': false}); |
2057 |
}, |
2058 |
|
2059 |
check_firewall: function() { |
2060 |
var firewall = this.get('firewallProfile'); |
2061 |
var pending = this.get('pending_firewall'); |
2062 |
var previous = this.get('pending_firewall_from'); |
2063 |
if (previous != firewall) { this.get_vm().require_reboot() }; |
2064 |
this.reset_pending_firewall();
|
2065 |
} |
2066 |
|
2067 |
}); |
2068 |
|
2069 |
models.NICs = models.Collection.extend({ |
2070 |
model: models.NIC,
|
2071 |
|
2072 |
add_or_update: function(nic_id, data, vm) { |
2073 |
var params = _.clone(data);
|
2074 |
var vm;
|
2075 |
params.attachment_id = params.id; |
2076 |
params.id = params.id + '-' + params.network_id;
|
2077 |
params.vm_id = parseInt(NIC_REGEX.exec(nic_id)[1]);
|
2078 |
|
2079 |
if (!this.get(params.id)) { |
2080 |
this.add(params);
|
2081 |
var nic = this.get(params.id); |
2082 |
vm = nic.get_vm(); |
2083 |
nic.get_network().decrease_connecting(); |
2084 |
nic.bind("remove", function() { |
2085 |
nic.set({"removing": 0}); |
2086 |
if (this.get_network()) { |
2087 |
// network might got removed before nic
|
2088 |
nic.get_network().update_state(); |
2089 |
} |
2090 |
}); |
2091 |
|
2092 |
} else {
|
2093 |
this.get(params.id).set(params);
|
2094 |
vm = this.get(params.id).get_vm();
|
2095 |
} |
2096 |
|
2097 |
// vm nics changed, trigger vm update
|
2098 |
if (vm) { vm.trigger("change", vm)}; |
2099 |
}, |
2100 |
|
2101 |
reset_nics: function(nics, filter_attr, filter_val) { |
2102 |
var nics_to_check = this.filter(function(nic) { |
2103 |
return nic.get(filter_attr) == filter_val;
|
2104 |
}); |
2105 |
|
2106 |
_.each(nics_to_check, function(nic) {
|
2107 |
if (nics.indexOf(nic.get('id')) == -1) { |
2108 |
this.remove(nic);
|
2109 |
} else {
|
2110 |
} |
2111 |
}, this);
|
2112 |
}, |
2113 |
|
2114 |
update_vm_nics: function(vm) { |
2115 |
var nics = vm.get('nics'); |
2116 |
this.reset_nics(_.map(nics, function(nic, key){ |
2117 |
return key + "-" + nic.network_id; |
2118 |
}), 'vm_id', vm.id);
|
2119 |
|
2120 |
_.each(nics, function(val, key) {
|
2121 |
var net = synnefo.storage.networks.get(val.network_id);
|
2122 |
if (net && net.connected_with_nic_id(key) && vm.connected_with_nic_id(key)) {
|
2123 |
this.add_or_update(key, vm.get('nics')[key], vm); |
2124 |
} |
2125 |
}, this);
|
2126 |
}, |
2127 |
|
2128 |
update_net_nics: function(net) { |
2129 |
var nics = net.get('nics'); |
2130 |
this.reset_nics(_.map(nics, function(nic, key){ |
2131 |
return key + "-" + net.get('id'); |
2132 |
}), 'network_id', net.id);
|
2133 |
|
2134 |
_.each(nics, function(val, key) {
|
2135 |
var vm = synnefo.storage.vms.get(val.vm_id);
|
2136 |
if (vm && net.connected_with_nic_id(key) && vm.connected_with_nic_id(key)) {
|
2137 |
this.add_or_update(key, vm.get('nics')[key], vm); |
2138 |
} |
2139 |
}, this);
|
2140 |
} |
2141 |
}); |
2142 |
|
2143 |
models.PublicKey = models.Model.extend({ |
2144 |
path: 'keys', |
2145 |
base_url: '/ui/userdata', |
2146 |
details: false, |
2147 |
noUpdate: true, |
2148 |
|
2149 |
|
2150 |
get_public_key: function() { |
2151 |
return cryptico.publicKeyFromString(this.get("content")); |
2152 |
}, |
2153 |
|
2154 |
get_filename: function() { |
2155 |
return "{0}.pub".format(this.get("name")); |
2156 |
}, |
2157 |
|
2158 |
identify_type: function() { |
2159 |
try {
|
2160 |
var cont = snf.util.validatePublicKey(this.get("content")); |
2161 |
var type = cont.split(" ")[0]; |
2162 |
return synnefo.util.publicKeyTypesMap[type];
|
2163 |
} catch (err) { return false }; |
2164 |
} |
2165 |
|
2166 |
}) |
2167 |
|
2168 |
models.PublicKeys = models.Collection.extend({ |
2169 |
model: models.PublicKey,
|
2170 |
details: false, |
2171 |
path: 'keys', |
2172 |
base_url: '/ui/userdata', |
2173 |
noUpdate: true, |
2174 |
|
2175 |
generate_new: function(success, error) { |
2176 |
snf.api.sync('create', undefined, { |
2177 |
url: getUrl.call(this, this.base_url) + "/generate", |
2178 |
success: success,
|
2179 |
error: error,
|
2180 |
skip_api_error: true |
2181 |
}); |
2182 |
}, |
2183 |
|
2184 |
add_crypto_key: function(key, success, error, options) { |
2185 |
var options = options || {};
|
2186 |
var m = new models.PublicKey(); |
2187 |
|
2188 |
// guess a name
|
2189 |
var name_tpl = "my generated public key"; |
2190 |
var name = name_tpl;
|
2191 |
var name_count = 1; |
2192 |
|
2193 |
while(this.filter(function(m){ return m.get("name") == name }).length > 0) { |
2194 |
name = name_tpl + " " + name_count;
|
2195 |
name_count++; |
2196 |
} |
2197 |
|
2198 |
m.set({name: name});
|
2199 |
m.set({content: key});
|
2200 |
|
2201 |
options.success = function () { return success(m) }; |
2202 |
options.errror = error; |
2203 |
options.skip_api_error = true;
|
2204 |
|
2205 |
this.create(m.attributes, options);
|
2206 |
} |
2207 |
}) |
2208 |
|
2209 |
// storage initialization
|
2210 |
snf.storage.images = new models.Images();
|
2211 |
snf.storage.flavors = new models.Flavors();
|
2212 |
snf.storage.networks = new models.Networks();
|
2213 |
snf.storage.vms = new models.VMS();
|
2214 |
snf.storage.keys = new models.PublicKeys();
|
2215 |
snf.storage.nics = new models.NICs();
|
2216 |
|
2217 |
//snf.storage.vms.fetch({update:true});
|
2218 |
//snf.storage.images.fetch({update:true});
|
2219 |
//snf.storage.flavors.fetch({update:true});
|
2220 |
|
2221 |
})(this);
|