root / snf-cyclades-app / synnefo / ui / static / snf / js / models.js @ 198b546d
History | View | Annotate | Download (95.8 kB)
1 |
// Copyright 2014 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 |
var append = "/"; |
58 |
if (baseurl.split("").reverse()[0] == "/") { |
59 |
append = "";
|
60 |
} |
61 |
return baseurl + append + this.path; |
62 |
} |
63 |
|
64 |
// i18n
|
65 |
BUILDING_MESSAGES = window.BUILDING_MESSAGES || {'INIT': 'init', 'COPY': '{0}, {1}, {2}', 'FINAL': 'final'}; |
66 |
|
67 |
// Base object for all our models
|
68 |
models.Model = bb.Model.extend({ |
69 |
sync: snf.api.sync,
|
70 |
api: snf.api,
|
71 |
api_type: 'compute', |
72 |
has_status: false, |
73 |
auto_bind: [],
|
74 |
|
75 |
|
76 |
initialize: function() { |
77 |
var self = this; |
78 |
|
79 |
this._proxy_model_cache = {};
|
80 |
_.each(this.auto_bind, function(fname) { |
81 |
self[fname] = _.bind(self[fname], self); |
82 |
}); |
83 |
|
84 |
if (this.has_status) { |
85 |
this.bind("change:status", this.handle_remove); |
86 |
this.handle_remove();
|
87 |
} |
88 |
|
89 |
this.api_call = _.bind(this.api.call, this); |
90 |
|
91 |
if (this.proxy_attrs) { |
92 |
this.init_proxy_attrs();
|
93 |
} |
94 |
|
95 |
if (this.storage_attrs) { |
96 |
this.init_storage_attrs();
|
97 |
} |
98 |
|
99 |
if (this.model_actions) { |
100 |
this.init_model_actions();
|
101 |
} |
102 |
|
103 |
models.Model.__super__.initialize.apply(this, arguments); |
104 |
|
105 |
}, |
106 |
|
107 |
// Initialize model actions object
|
108 |
// For each entry in model's model_action object register the relevant
|
109 |
// model proxy `can_<actionname>` attributes.
|
110 |
init_model_actions: function() { |
111 |
var actions = _.keys(this.model_actions); |
112 |
this.set({
|
113 |
"actions": new models._ActionsModel({}, { |
114 |
actions: actions,
|
115 |
model: this |
116 |
}) |
117 |
}); |
118 |
this.actions = this.get("actions"); |
119 |
this.actions.bind("set-pending", function(action) { |
120 |
this.trigger("action:set-pending", action, this.actions, this); |
121 |
}, this);
|
122 |
this.actions.bind("unset-pending", function(action) { |
123 |
this.trigger("action:unset-pending", action, this.actions, this); |
124 |
}, this);
|
125 |
this.actions.bind("reset-pending", function() { |
126 |
this.trigger("action:reset-pending", this.actions, this); |
127 |
}, this);
|
128 |
|
129 |
_.each(this.model_actions, function(params, key){ |
130 |
var attr = 'can_' + key; |
131 |
if (params.length == 0) { return } |
132 |
var deps = params[0]; |
133 |
var cb = _.bind(params[1], this); |
134 |
_.each(deps, function(dep) {
|
135 |
this._set_proxy_attr(attr, dep, cb);
|
136 |
}, this);
|
137 |
}, this);
|
138 |
}, |
139 |
|
140 |
// Initialize proxy storage model attributes. These attribues allows
|
141 |
// us to automatically access cross collection associated objects.
|
142 |
init_storage_attrs: function() { |
143 |
_.each(this.storage_attrs, function(params, attr) { |
144 |
var store, key, attr_name;
|
145 |
store = synnefo.storage[params[0]];
|
146 |
key = params[1];
|
147 |
attr_resolver = params[2];
|
148 |
if (!attr_resolver) {
|
149 |
attr_resolver = function(model, attr) { |
150 |
return model.get(attr);
|
151 |
} |
152 |
} |
153 |
attr_name = attr; |
154 |
|
155 |
var resolve_related_instance = function(storage, attr_name, val) { |
156 |
var data = {};
|
157 |
|
158 |
if (!val) {
|
159 |
// update with undefined and return
|
160 |
data[key] = undefined;
|
161 |
this.set(data);
|
162 |
return;
|
163 |
}; |
164 |
|
165 |
// retrieve related object (check if its a Model??)
|
166 |
var obj = store.get(val);
|
167 |
|
168 |
if (obj) {
|
169 |
// set related object
|
170 |
data[attr_name] = obj; |
171 |
this.set(data, {silent:true}) |
172 |
this.trigger("change:" + attr_name, obj); |
173 |
} else {
|
174 |
var self = this; |
175 |
var retry_to_resolve = function(store, val, key) { |
176 |
var retries = 0; |
177 |
var retry = window.setInterval(function(){ |
178 |
retries++; |
179 |
if (retries > 200) { |
180 |
clearInterval(retry); |
181 |
} |
182 |
var obj = store.get(val);
|
183 |
if (obj) {
|
184 |
data[key] = obj; |
185 |
self.set(data, {silent:false}); |
186 |
clearInterval(retry); |
187 |
} |
188 |
}, 500);
|
189 |
return retry
|
190 |
} |
191 |
retry_to_resolve(store, val, key); |
192 |
} |
193 |
} |
194 |
|
195 |
var self = this; |
196 |
function init_bindings(instance, store, key, attr, attr_resolver) { |
197 |
instance.bind('change:' + attr, function(model) { |
198 |
resolve_related_instance.call(model, store, key, attr_resolver(model, attr)); |
199 |
}, this);
|
200 |
|
201 |
instance.bind('add', function(model) { |
202 |
resolve_related_instance.call(model, store, key, attr_resolver(model, attr)); |
203 |
}, this);
|
204 |
} |
205 |
|
206 |
init_bindings(this, store, key, attr, attr_resolver);
|
207 |
resolve_related_instance.call(this, store, key, attr_resolver(this, attr)); |
208 |
}, this);
|
209 |
}, |
210 |
|
211 |
_proxy_model_cache: {},
|
212 |
|
213 |
_bind_model: function(model, attr, check_attr, cb) { |
214 |
var proxy_cache_key = attr + '_' + check_attr; |
215 |
if (this._proxy_model_cache[proxy_cache_key]) { |
216 |
var proxy = this._proxy_model_cache[proxy_cache_key]; |
217 |
proxy[0].unbind('change', proxy[1]); |
218 |
} |
219 |
var data = {};
|
220 |
var changebind = _.bind(function() { |
221 |
data[attr] = cb.call(this, this.get(check_attr)); |
222 |
this.set(data);
|
223 |
}, this);
|
224 |
model.bind('change', changebind);
|
225 |
this._proxy_model_cache[proxy_cache_key] = [model, changebind];
|
226 |
}, |
227 |
|
228 |
_bind_attr: function(attr, check_attr, cb) { |
229 |
this.bind('change:' + check_attr, function() { |
230 |
if (this.get(check_attr) instanceof models.Model) { |
231 |
var model = this.get(check_attr); |
232 |
this._bind_model(model, attr, check_attr, cb);
|
233 |
} |
234 |
var val = cb.call(this, this.get(check_attr)); |
235 |
var data = {};
|
236 |
if (this.get(attr) !== val) { |
237 |
data[attr] = val; |
238 |
this.set(data);
|
239 |
} |
240 |
}, this);
|
241 |
}, |
242 |
|
243 |
_set_proxy_attr: function(attr, check_attr, cb) { |
244 |
// initial set
|
245 |
var data = {};
|
246 |
data[attr] = cb.call(this, this.get(check_attr)); |
247 |
if (data[attr] !== undefined) { |
248 |
this.set(data, {silent:true}); |
249 |
} |
250 |
if(this.get(check_attr) instanceof models.Model) { |
251 |
this._bind_model(this.get(check_attr), attr, check_attr, cb); |
252 |
} |
253 |
this._bind_attr(attr, check_attr, cb);
|
254 |
}, |
255 |
|
256 |
init_proxy_attrs: function() { |
257 |
_.each(this.proxy_attrs, function(opts, attr){ |
258 |
var cb = opts[1]; |
259 |
_.each(opts[0], function(check_attr){ |
260 |
this._set_proxy_attr(attr, check_attr, cb)
|
261 |
}, this);
|
262 |
}, this);
|
263 |
}, |
264 |
|
265 |
handle_remove: function() { |
266 |
if (this.get("status") == 'DELETED') { |
267 |
if (this.collection) { |
268 |
try { this.clear_pending_action();} catch (err) {}; |
269 |
try { this.reset_pending_actions();} catch (err) {}; |
270 |
try { this.stop_stats_update();} catch (err) {}; |
271 |
this.collection.remove(this.id); |
272 |
} |
273 |
} |
274 |
}, |
275 |
|
276 |
// custom set method to allow submodels to use
|
277 |
// set_<attr> methods for handling the value of each
|
278 |
// attribute and overriding the default set method
|
279 |
// for specific parameters
|
280 |
set: function(params, options) { |
281 |
_.each(params, _.bind(function(value, key){
|
282 |
if (this["set_" + key]) { |
283 |
params[key] = this["set_" + key](value); |
284 |
} |
285 |
}, this))
|
286 |
var ret = bb.Model.prototype.set.call(this, params, options); |
287 |
return ret;
|
288 |
}, |
289 |
|
290 |
url: function(options) { |
291 |
return getUrl.call(this, this.base_url) + "/" + this.id; |
292 |
}, |
293 |
|
294 |
api_path: function(options) { |
295 |
return this.path + "/" + this.id; |
296 |
}, |
297 |
|
298 |
parse: function(resp, xhr) { |
299 |
}, |
300 |
|
301 |
remove: function(complete, error, success) { |
302 |
this.api_call(this.api_path(), "delete", undefined, complete, error, success); |
303 |
}, |
304 |
|
305 |
changedKeys: function() { |
306 |
return _.keys(this.changedAttributes() || {}); |
307 |
}, |
308 |
|
309 |
// return list of changed attributes that included in passed list
|
310 |
// argument
|
311 |
getKeysChanged: function(keys) { |
312 |
return _.intersection(keys, this.changedKeys()); |
313 |
}, |
314 |
|
315 |
// boolean check of keys changed
|
316 |
keysChanged: function(keys) { |
317 |
return this.getKeysChanged(keys).length > 0; |
318 |
}, |
319 |
|
320 |
// check if any of the passed attribues has changed
|
321 |
hasOnlyChange: function(keys) { |
322 |
var ret = false; |
323 |
_.each(keys, _.bind(function(key) {
|
324 |
if (this.changedKeys().length == 1 && this.changedKeys().indexOf(key) > -1) { ret = true}; |
325 |
}, this));
|
326 |
return ret;
|
327 |
} |
328 |
|
329 |
}) |
330 |
|
331 |
// Base object for all our model collections
|
332 |
models.Collection = bb.Collection.extend({ |
333 |
sync: snf.api.sync,
|
334 |
api: snf.api,
|
335 |
api_type: 'compute', |
336 |
supportIncUpdates: true, |
337 |
|
338 |
mapAttrs: function() { |
339 |
var params = _.toArray(arguments); |
340 |
return this.map(function(i) { |
341 |
return _.map(params, function(attr) { |
342 |
return i.get(attr);
|
343 |
}); |
344 |
}) |
345 |
}, |
346 |
|
347 |
mapAttr: function(attr) { |
348 |
return this.map(function(i) { return i.get(attr)}) |
349 |
}, |
350 |
|
351 |
filterAttr: function(attr, eq) { |
352 |
return this.filter(function(i) { return i.get(attr) === eq}) |
353 |
}, |
354 |
|
355 |
initialize: function() { |
356 |
models.Collection.__super__.initialize.apply(this, arguments); |
357 |
this.api_call = _.bind(this.api.call, this); |
358 |
if (this.sortFields) { |
359 |
_.each(this.sortFields, function(f) { |
360 |
this.bind("change:" + f, _.bind(this.resort, this)); |
361 |
}, this);
|
362 |
} |
363 |
}, |
364 |
|
365 |
resort: function() { |
366 |
this.sort();
|
367 |
}, |
368 |
|
369 |
url: function(options, method) { |
370 |
return getUrl.call(this, this.base_url) + ( |
371 |
options.details || this.details && method != 'create' ? '/detail' : ''); |
372 |
}, |
373 |
|
374 |
delay_fetch: function(delay, options) { |
375 |
window.setTimeout(_.bind(function() {
|
376 |
this.fetch(options)
|
377 |
}, this), delay);
|
378 |
}, |
379 |
|
380 |
fetch: function(options) { |
381 |
if (!options) { options = {} };
|
382 |
// default to update
|
383 |
if (!this.noUpdate) { |
384 |
if (options.update === undefined) { options.update = true }; |
385 |
if (!options.removeMissing && options.refresh) {
|
386 |
options.removeMissing = true;
|
387 |
}; |
388 |
// for collections which associated models don't support
|
389 |
// deleted state identification through attributes, resolve
|
390 |
// deleted entries by checking for missing objects in fetch
|
391 |
// responses.
|
392 |
if (this.updateEntries && options.removeMissing === undefined) { |
393 |
options.removeMissing = true;
|
394 |
} |
395 |
} else {
|
396 |
if (options.refresh === undefined) { |
397 |
options.refresh = true;
|
398 |
if (this.updateEntries) { |
399 |
options.update = true;
|
400 |
options.removeMissing = true;
|
401 |
} |
402 |
} |
403 |
} |
404 |
// custom event foreach fetch
|
405 |
return bb.Collection.prototype.fetch.call(this, options) |
406 |
}, |
407 |
|
408 |
create: function(model, options) { |
409 |
var coll = this; |
410 |
options || (options = {}); |
411 |
model = this._prepareModel(model, options);
|
412 |
if (!model) return false; |
413 |
var success = options.success;
|
414 |
options.success = function(nextModel, resp, xhr) { |
415 |
if (coll.add_on_create) {
|
416 |
coll.add(nextModel, options); |
417 |
} |
418 |
synnefo.api.trigger("quota:update");
|
419 |
if (success) success(nextModel, resp, xhr);
|
420 |
}; |
421 |
model.save(null, options);
|
422 |
return model;
|
423 |
}, |
424 |
|
425 |
get_fetcher: function(interval, increase, fast, increase_after_calls, |
426 |
max, initial_call, params, fetcher_id) { |
427 |
var fetch_params = params || {};
|
428 |
var handler_options = {};
|
429 |
|
430 |
fetch_params.skips_timeouts = true;
|
431 |
handler_options.interval = interval; |
432 |
handler_options.increase = increase; |
433 |
handler_options.fast = fast; |
434 |
handler_options.increase_after_calls = increase_after_calls; |
435 |
handler_options.max= max; |
436 |
handler_options.id = fetcher_id || "collection";
|
437 |
|
438 |
var last_ajax = undefined; |
439 |
var callback = _.bind(function() { |
440 |
// clone to avoid referenced objects
|
441 |
var params = _.clone(fetch_params);
|
442 |
updater._ajax = last_ajax; |
443 |
|
444 |
// wait for previous request to finish
|
445 |
if (last_ajax && last_ajax.readyState < 4 && last_ajax.statusText != "timeout") { |
446 |
// opera readystate for 304 responses is 0
|
447 |
if (!($.browser.opera && last_ajax.readyState == 0 && last_ajax.status == 304)) { |
448 |
return;
|
449 |
} |
450 |
} |
451 |
last_ajax = this.fetch(params);
|
452 |
}, this);
|
453 |
handler_options.callback = callback; |
454 |
|
455 |
var updater = new snf.api.updateHandler(_.clone(_.extend(handler_options, fetch_params))); |
456 |
snf.api.bind("call", _.throttle(_.bind(function(){ updater.faster(true)}, this)), 1000); |
457 |
return updater;
|
458 |
} |
459 |
}); |
460 |
|
461 |
// Image model
|
462 |
models.Image = models.Model.extend({ |
463 |
path: 'images', |
464 |
|
465 |
get_size: function() { |
466 |
return parseInt(this.get('metadata') ? this.get('metadata').size : -1) |
467 |
}, |
468 |
|
469 |
get_description: function(escape) { |
470 |
if (escape == undefined) { escape = true }; |
471 |
if (escape) { return this.escape('description') || "No description available"} |
472 |
return this.get('description') || "No description available." |
473 |
}, |
474 |
|
475 |
get_meta: function(key) { |
476 |
if (this.get('metadata') && this.get('metadata')) { |
477 |
if (!this.get('metadata')[key]) { return null } |
478 |
return _.escape(this.get('metadata')[key]); |
479 |
} else {
|
480 |
return null; |
481 |
} |
482 |
}, |
483 |
|
484 |
get_meta_keys: function() { |
485 |
if (this.get('metadata') && this.get('metadata')) { |
486 |
return _.keys(this.get('metadata')); |
487 |
} else {
|
488 |
return [];
|
489 |
} |
490 |
}, |
491 |
|
492 |
get_owner: function() { |
493 |
return this.get('owner') || _.keys(synnefo.config.system_images_owners)[0]; |
494 |
}, |
495 |
|
496 |
get_owner_uuid: function() { |
497 |
return this.get('owner_uuid'); |
498 |
}, |
499 |
|
500 |
is_system_image: function() { |
501 |
var owner = this.get_owner(); |
502 |
return _.include(_.keys(synnefo.config.system_images_owners), owner)
|
503 |
}, |
504 |
|
505 |
owned_by: function(user) { |
506 |
if (!user) { user = synnefo.user }
|
507 |
return user.get_username() == this.get('owner_uuid'); |
508 |
}, |
509 |
|
510 |
display_owner: function() { |
511 |
var owner = this.get_owner(); |
512 |
if (_.include(_.keys(synnefo.config.system_images_owners), owner)) {
|
513 |
return synnefo.config.system_images_owners[owner];
|
514 |
} else {
|
515 |
return owner;
|
516 |
} |
517 |
}, |
518 |
|
519 |
get_readable_size: function() { |
520 |
if (this.is_deleted()) { |
521 |
return synnefo.config.image_deleted_size_title || '(none)'; |
522 |
} |
523 |
return this.get_size() > 0 ? util.readablizeBytes(this.get_size() * 1024 * 1024) : '(none)'; |
524 |
}, |
525 |
|
526 |
get_os: function() { |
527 |
return this.get_meta('OS'); |
528 |
}, |
529 |
|
530 |
get_gui: function() { |
531 |
return this.get_meta('GUI'); |
532 |
}, |
533 |
|
534 |
get_created_users: function() { |
535 |
try {
|
536 |
var users = this.get_meta('users').split(" "); |
537 |
} catch (err) { users = null } |
538 |
if (!users) {
|
539 |
var osfamily = this.get_meta('osfamily'); |
540 |
if (osfamily == 'windows') { |
541 |
users = ['Administrator'];
|
542 |
} else {
|
543 |
users = ['root'];
|
544 |
} |
545 |
} |
546 |
return users;
|
547 |
}, |
548 |
|
549 |
get_sort_order: function() { |
550 |
return parseInt(this.get('metadata') ? this.get('metadata').sortorder : -1) |
551 |
}, |
552 |
|
553 |
get_vm: function() { |
554 |
var vm_id = this.get("serverRef"); |
555 |
var vm = undefined; |
556 |
vm = storage.vms.get(vm_id); |
557 |
return vm;
|
558 |
}, |
559 |
|
560 |
is_public: function() { |
561 |
return this.get('is_public') == undefined ? true : this.get('is_public'); |
562 |
}, |
563 |
|
564 |
is_deleted: function() { |
565 |
return this.get('status') == "DELETED" |
566 |
}, |
567 |
|
568 |
ssh_keys_paths: function() { |
569 |
return _.map(this.get_created_users(), function(username) { |
570 |
prepend = '';
|
571 |
if (username != 'root') { |
572 |
prepend = '/home'
|
573 |
} |
574 |
return {'user': username, 'path': '{1}/{0}/.ssh/authorized_keys'.format(username, |
575 |
prepend)}; |
576 |
}); |
577 |
}, |
578 |
|
579 |
_supports_ssh: function() { |
580 |
var exclude_list = synnefo.config.ssh_support_osfamily_exclude_list || [];
|
581 |
var os = this.get_os(); |
582 |
if (exclude_list.indexOf(os) > -1) { |
583 |
return false; |
584 |
} |
585 |
return true; |
586 |
}, |
587 |
|
588 |
supports: function(feature) { |
589 |
if (feature == "ssh") { |
590 |
return this._supports_ssh() |
591 |
} |
592 |
return false; |
593 |
}, |
594 |
|
595 |
personality_data_for_keys: function(keys) { |
596 |
return _.map(this.ssh_keys_paths(), function(pathinfo) { |
597 |
var contents = ''; |
598 |
_.each(keys, function(key){
|
599 |
contents = contents + key.get("content") + "\n" |
600 |
}); |
601 |
contents = $.base64.encode(contents);
|
602 |
|
603 |
return {
|
604 |
path: pathinfo.path,
|
605 |
contents: contents,
|
606 |
mode: 0600, |
607 |
owner: pathinfo.user
|
608 |
} |
609 |
}); |
610 |
} |
611 |
}); |
612 |
|
613 |
// Flavor model
|
614 |
models.Flavor = models.Model.extend({ |
615 |
path: 'flavors', |
616 |
|
617 |
details_string: function() { |
618 |
return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk')); |
619 |
}, |
620 |
|
621 |
get_disk_size: function() { |
622 |
return parseInt(this.get("disk") * 1024) |
623 |
}, |
624 |
|
625 |
get_ram_size: function() { |
626 |
return parseInt(this.get("ram")) |
627 |
}, |
628 |
|
629 |
get_disk_template_info: function() { |
630 |
var info = snf.config.flavors_disk_templates_info[this.get("disk_template")]; |
631 |
if (!info) {
|
632 |
info = { name: this.get("disk_template"), description:'' }; |
633 |
} |
634 |
return info
|
635 |
}, |
636 |
|
637 |
disk_to_bytes: function() { |
638 |
return parseInt(this.get("disk")) * 1024 * 1024 * 1024; |
639 |
}, |
640 |
|
641 |
ram_to_bytes: function() { |
642 |
return parseInt(this.get("ram")) * 1024 * 1024; |
643 |
}, |
644 |
|
645 |
get_readable: function(key) { |
646 |
var parser = function(v) { return v } |
647 |
var getter = this.get; |
648 |
if (key == 'ram') { |
649 |
parser = synnefo.util.readablizeBytes |
650 |
getter = this.ram_to_bytes
|
651 |
} |
652 |
if (key == 'disk') { |
653 |
parser = synnefo.util.readablizeBytes |
654 |
getter = this.ram_to_bytes
|
655 |
} |
656 |
if (key == 'cpu') { |
657 |
parser = function(v) { return v + 'x' } |
658 |
} |
659 |
|
660 |
var val = getter.call(this, key); |
661 |
return parser(val);
|
662 |
}, |
663 |
|
664 |
quotas: function() { |
665 |
return {
|
666 |
'cyclades.vm': 1, |
667 |
'cyclades.disk': this.disk_to_bytes(), |
668 |
'cyclades.ram': this.ram_to_bytes(), |
669 |
'cyclades.cpu': this.get('cpu') |
670 |
} |
671 |
} |
672 |
|
673 |
}); |
674 |
|
675 |
models.ParamsList = function(){this.initialize.apply(this, arguments)}; |
676 |
_.extend(models.ParamsList.prototype, bb.Events, { |
677 |
|
678 |
initialize: function(parent, param_name) { |
679 |
this.parent = parent;
|
680 |
this.actions = {};
|
681 |
this.param_name = param_name;
|
682 |
this.length = 0; |
683 |
}, |
684 |
|
685 |
has_action: function(action) { |
686 |
return this.actions[action] ? true : false; |
687 |
}, |
688 |
|
689 |
_parse_params: function(arguments) { |
690 |
if (arguments.length <= 1) { |
691 |
return [];
|
692 |
} |
693 |
|
694 |
var args = _.toArray(arguments); |
695 |
return args.splice(1); |
696 |
}, |
697 |
|
698 |
contains: function(action, params) { |
699 |
params = this._parse_params(arguments); |
700 |
var has_action = this.has_action(action); |
701 |
if (!has_action) { return false }; |
702 |
|
703 |
var paramsEqual = false; |
704 |
_.each(this.actions[action], function(action_params) { |
705 |
if (_.isEqual(action_params, params)) {
|
706 |
paramsEqual = true;
|
707 |
} |
708 |
}); |
709 |
|
710 |
return paramsEqual;
|
711 |
}, |
712 |
|
713 |
is_empty: function() { |
714 |
return _.isEmpty(this.actions); |
715 |
}, |
716 |
|
717 |
add: function(action, params) { |
718 |
params = this._parse_params(arguments); |
719 |
if (this.contains.apply(this, arguments)) { return this }; |
720 |
var isnew = false |
721 |
if (!this.has_action(action)) { |
722 |
this.actions[action] = [];
|
723 |
isnew = true;
|
724 |
}; |
725 |
|
726 |
this.actions[action].push(params);
|
727 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
728 |
if (isnew) {
|
729 |
this.trigger("add", action, params); |
730 |
} else {
|
731 |
this.trigger("change", action, params); |
732 |
} |
733 |
return this; |
734 |
}, |
735 |
|
736 |
remove_all: function(action) { |
737 |
if (this.has_action(action)) { |
738 |
delete this.actions[action]; |
739 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
740 |
this.trigger("remove", action); |
741 |
} |
742 |
return this; |
743 |
}, |
744 |
|
745 |
reset: function() { |
746 |
this.actions = {};
|
747 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
748 |
this.trigger("reset"); |
749 |
this.trigger("remove"); |
750 |
}, |
751 |
|
752 |
remove: function(action, params) { |
753 |
params = this._parse_params(arguments); |
754 |
if (!this.has_action(action)) { return this }; |
755 |
var index = -1; |
756 |
_.each(this.actions[action], _.bind(function(action_params) { |
757 |
if (_.isEqual(action_params, params)) {
|
758 |
index = this.actions[action].indexOf(action_params);
|
759 |
} |
760 |
}, this));
|
761 |
|
762 |
if (index > -1) { |
763 |
this.actions[action].splice(index, 1); |
764 |
if (_.isEmpty(this.actions[action])) { |
765 |
delete this.actions[action]; |
766 |
} |
767 |
this.parent.trigger("change:" + this.param_name, this.parent, this); |
768 |
this.trigger("remove", action, params); |
769 |
} |
770 |
} |
771 |
|
772 |
}); |
773 |
|
774 |
// Virtualmachine model
|
775 |
models.VM = models.Model.extend({ |
776 |
|
777 |
path: 'servers', |
778 |
has_status: true, |
779 |
proxy_attrs: {
|
780 |
'busy': [
|
781 |
['status', 'state'], function() { |
782 |
return !_.contains(['ACTIVE', 'STOPPED'], this.get('status')); |
783 |
} |
784 |
], |
785 |
'in_progress': [
|
786 |
['status', 'state'], function() { |
787 |
return this.in_transition(); |
788 |
} |
789 |
] |
790 |
}, |
791 |
|
792 |
storage_attrs: {
|
793 |
'tenant_id': ['projects', 'project'] |
794 |
}, |
795 |
|
796 |
initialize: function(params) { |
797 |
var self = this; |
798 |
this.ports = new Backbone.FilteredCollection(undefined, { |
799 |
collection: synnefo.storage.ports,
|
800 |
collectionFilter: function(m) { |
801 |
return self.id == m.get('device_id') |
802 |
}}); |
803 |
|
804 |
this.pending_firewalls = {};
|
805 |
|
806 |
models.VM.__super__.initialize.apply(this, arguments); |
807 |
|
808 |
|
809 |
this.set({state: params.status || "ERROR"}); |
810 |
this.log = new snf.logging.logger("VM " + this.id); |
811 |
this.pending_action = undefined; |
812 |
|
813 |
// init stats parameter
|
814 |
this.set({'stats': undefined}, {silent: true}); |
815 |
// defaults to not update the stats
|
816 |
// each view should handle this vm attribute
|
817 |
// depending on if it displays stat images or not
|
818 |
this.do_update_stats = false; |
819 |
|
820 |
// interval time
|
821 |
// this will dynamicaly change if the server responds that
|
822 |
// images get refreshed on different intervals
|
823 |
this.stats_update_interval = synnefo.config.STATS_INTERVAL || 5000; |
824 |
this.stats_available = false; |
825 |
|
826 |
// initialize interval
|
827 |
this.init_stats_intervals(this.stats_update_interval); |
828 |
|
829 |
// handle progress message on instance change
|
830 |
this.bind("change", _.bind(this.update_status_message, this)); |
831 |
this.bind("change:task_state", _.bind(this.update_status, this)); |
832 |
// force update of progress message
|
833 |
this.update_status_message(true); |
834 |
|
835 |
// default values
|
836 |
this.bind("change:state", _.bind(function(){ |
837 |
if (this.state() == "DESTROY") { |
838 |
this.handle_destroy()
|
839 |
} |
840 |
}, this));
|
841 |
|
842 |
}, |
843 |
|
844 |
get_public_ips: function() { |
845 |
var ips = [];
|
846 |
this.ports.filter(function(port) { |
847 |
if (port.get('network') && !port.get('network').get('is_public')) { return } |
848 |
if (!port.get("ips")) { return } |
849 |
port.get("ips").each(function(ip) { |
850 |
ips.push(ip); |
851 |
}); |
852 |
}); |
853 |
return ips;
|
854 |
}, |
855 |
|
856 |
has_public_ip: function() { |
857 |
return this.ports.filter(function(port) { |
858 |
return port.get("network") && |
859 |
port.get("network").get("is_public") && |
860 |
port.get("ips").length > 0; |
861 |
}).length > 0;
|
862 |
}, |
863 |
|
864 |
has_public_ipv6: function() { |
865 |
return this.has_ip_version("v6", true); |
866 |
}, |
867 |
|
868 |
has_public_ipv4: function() { |
869 |
return this.has_ip_version("v4", true); |
870 |
}, |
871 |
|
872 |
has_ip_version: function(ver, public) { |
873 |
var found = false; |
874 |
this.ports.each(function(port) { |
875 |
if (found) { return } |
876 |
if (public !== undefined) { |
877 |
if (port.get("network") && |
878 |
port.get("network").get("is_public") != public) { |
879 |
return
|
880 |
} |
881 |
} |
882 |
port.get('ips').each(function(ip) { |
883 |
if (found) { return } |
884 |
if (ip.get("type") == ver) { |
885 |
found = true
|
886 |
} |
887 |
}) |
888 |
}, this)
|
889 |
return found;
|
890 |
}, |
891 |
|
892 |
status: function(st) { |
893 |
if (!st) { return this.get("status")} |
894 |
return this.set({status:st}); |
895 |
}, |
896 |
|
897 |
update_status: function() { |
898 |
this.set_status(this.get('status')); |
899 |
}, |
900 |
|
901 |
set_status: function(st) { |
902 |
var new_state = this.state_for_api_status(st); |
903 |
var transition = false; |
904 |
|
905 |
if (this.state() != new_state) { |
906 |
if (models.VM.STATES_TRANSITIONS[this.state()]) { |
907 |
transition = this.state();
|
908 |
} |
909 |
} |
910 |
|
911 |
// call it silently to avoid double change trigger
|
912 |
var state = this.state_for_api_status(st); |
913 |
this.set({'state': state}, {silent: true}); |
914 |
|
915 |
// trigger transition
|
916 |
if (transition && models.VM.TRANSITION_STATES.indexOf(new_state) == -1) { |
917 |
this.trigger("transition", {from:transition, to:new_state}) |
918 |
}; |
919 |
return st;
|
920 |
}, |
921 |
|
922 |
get_diagnostics: function(success) { |
923 |
this.__make_api_call(this.get_diagnostics_url(), |
924 |
"read", // create so that sync later uses POST to make the call |
925 |
null, // payload |
926 |
function(data) {
|
927 |
success(data); |
928 |
}, |
929 |
null, 'diagnostics'); |
930 |
}, |
931 |
|
932 |
has_diagnostics: function() { |
933 |
return this.get("diagnostics") && this.get("diagnostics").length; |
934 |
}, |
935 |
|
936 |
get_progress_info: function() { |
937 |
// details about progress message
|
938 |
// contains a list of diagnostic messages
|
939 |
return this.get("status_messages"); |
940 |
}, |
941 |
|
942 |
get_status_message: function() { |
943 |
return this.get('status_message'); |
944 |
}, |
945 |
|
946 |
// extract status message from diagnostics
|
947 |
status_message_from_diagnostics: function(diagnostics) { |
948 |
var valid_sources_map = synnefo.config.diagnostics_status_messages_map;
|
949 |
var valid_sources = valid_sources_map[this.get('status')]; |
950 |
if (!valid_sources) { return null }; |
951 |
|
952 |
// filter messsages based on diagnostic source
|
953 |
var messages = _.filter(diagnostics, function(diag) { |
954 |
return valid_sources.indexOf(diag.source) > -1; |
955 |
}); |
956 |
|
957 |
var msg = messages[0]; |
958 |
if (msg) {
|
959 |
var message = msg.message;
|
960 |
var message_tpl = snf.config.diagnostic_messages_tpls[msg.source];
|
961 |
|
962 |
if (message_tpl) {
|
963 |
message = message_tpl.replace('MESSAGE', msg.message);
|
964 |
} |
965 |
return message;
|
966 |
} |
967 |
|
968 |
// no message to display, but vm in build state, display
|
969 |
// finalizing message.
|
970 |
if (this.is_building() == 'BUILD') { |
971 |
return synnefo.config.BUILDING_MESSAGES['FINAL']; |
972 |
} |
973 |
return null; |
974 |
}, |
975 |
|
976 |
update_status_message: function(force) { |
977 |
// update only if one of the specified attributes has changed
|
978 |
if (
|
979 |
!this.keysChanged(['diagnostics', 'progress', 'status', 'state']) |
980 |
&& !force |
981 |
) { return };
|
982 |
|
983 |
// if user requested to destroy the vm set the appropriate
|
984 |
// message.
|
985 |
if (this.get('state') == "DESTROY") { |
986 |
message = "Terminating..."
|
987 |
this.set({status_message: message}) |
988 |
return;
|
989 |
} |
990 |
|
991 |
// set error message, if vm has diagnostic message display it as
|
992 |
// progress message
|
993 |
if (this.in_error_state()) { |
994 |
var d = this.get('diagnostics'); |
995 |
if (d && d.length) {
|
996 |
var message = this.status_message_from_diagnostics(d); |
997 |
this.set({status_message: message}); |
998 |
} else {
|
999 |
this.set({status_message: null}); |
1000 |
} |
1001 |
return;
|
1002 |
} |
1003 |
|
1004 |
// identify building status message
|
1005 |
if (this.is_building()) { |
1006 |
var self = this; |
1007 |
var success = function(msg) { |
1008 |
self.set({status_message: msg});
|
1009 |
} |
1010 |
this.get_building_status_message(success);
|
1011 |
return;
|
1012 |
} |
1013 |
|
1014 |
this.set({status_message:null}); |
1015 |
}, |
1016 |
|
1017 |
// get building status message. Asynchronous function since it requires
|
1018 |
// access to vm image.
|
1019 |
get_building_status_message: function(callback) { |
1020 |
// no progress is set, vm is in initial build status
|
1021 |
var progress = this.get("progress"); |
1022 |
if (progress == 0 || !progress) { |
1023 |
return callback(BUILDING_MESSAGES['INIT']); |
1024 |
} |
1025 |
|
1026 |
// vm has copy progress, display copy percentage
|
1027 |
if (progress > 0 && progress <= 99) { |
1028 |
this.get_copy_details(true, undefined, _.bind( |
1029 |
function(details){
|
1030 |
callback(BUILDING_MESSAGES['COPY'].format(details.copy,
|
1031 |
details.size, |
1032 |
details.progress)); |
1033 |
}, this));
|
1034 |
return;
|
1035 |
} |
1036 |
|
1037 |
// copy finished display FINAL message or identify status message
|
1038 |
// from diagnostics.
|
1039 |
if (progress >= 100) { |
1040 |
if (!this.has_diagnostics()) { |
1041 |
callback(BUILDING_MESSAGES['FINAL']);
|
1042 |
} else {
|
1043 |
var d = this.get("diagnostics"); |
1044 |
var msg = this.status_message_from_diagnostics(d); |
1045 |
if (msg) {
|
1046 |
callback(msg); |
1047 |
} |
1048 |
} |
1049 |
} |
1050 |
}, |
1051 |
|
1052 |
get_copy_details: function(human, image, callback) { |
1053 |
var human = human || false; |
1054 |
var image = image || this.get_image(_.bind(function(image){ |
1055 |
var progress = this.get('progress'); |
1056 |
var size = image.get_size();
|
1057 |
var size_copied = (size * progress / 100).toFixed(2); |
1058 |
|
1059 |
if (human) {
|
1060 |
size = util.readablizeBytes(size*1024*1024); |
1061 |
size_copied = util.readablizeBytes(size_copied*1024*1024); |
1062 |
} |
1063 |
|
1064 |
callback({'progress': progress, 'size': size, 'copy': size_copied}) |
1065 |
}, this));
|
1066 |
}, |
1067 |
|
1068 |
start_stats_update: function(force_if_empty) { |
1069 |
var prev_state = this.do_update_stats; |
1070 |
|
1071 |
this.do_update_stats = true; |
1072 |
|
1073 |
// fetcher initialized ??
|
1074 |
if (!this.stats_fetcher) { |
1075 |
this.init_stats_intervals();
|
1076 |
} |
1077 |
|
1078 |
|
1079 |
// fetcher running ???
|
1080 |
if (!this.stats_fetcher.running || !prev_state) { |
1081 |
this.stats_fetcher.start();
|
1082 |
} |
1083 |
|
1084 |
if (force_if_empty && this.get("stats") == undefined) { |
1085 |
this.update_stats(true); |
1086 |
} |
1087 |
}, |
1088 |
|
1089 |
stop_stats_update: function(stop_calls) { |
1090 |
this.do_update_stats = false; |
1091 |
|
1092 |
if (stop_calls) {
|
1093 |
this.stats_fetcher.stop();
|
1094 |
} |
1095 |
}, |
1096 |
|
1097 |
// clear and reinitialize update interval
|
1098 |
init_stats_intervals: function (interval) { |
1099 |
this.stats_fetcher = this.get_stats_fetcher(this.stats_update_interval); |
1100 |
this.stats_fetcher.start();
|
1101 |
}, |
1102 |
|
1103 |
get_stats_fetcher: function(timeout) { |
1104 |
var cb = _.bind(function(data){ |
1105 |
this.update_stats();
|
1106 |
}, this);
|
1107 |
var fetcher = new snf.api.updateHandler({'callback': cb, interval: timeout, id:'stats'}); |
1108 |
return fetcher;
|
1109 |
}, |
1110 |
|
1111 |
// do the api call
|
1112 |
update_stats: function(force) { |
1113 |
// do not update stats if flag not set
|
1114 |
if ((!this.do_update_stats && !force) || this.updating_stats) { |
1115 |
return;
|
1116 |
} |
1117 |
|
1118 |
// make the api call, execute handle_stats_update on sucess
|
1119 |
// TODO: onError handler ???
|
1120 |
stats_url = this.url() + "/stats"; |
1121 |
this.updating_stats = true; |
1122 |
this.sync("read", this, { |
1123 |
handles_error:true, |
1124 |
url: stats_url,
|
1125 |
refresh:true, |
1126 |
success: _.bind(this.handle_stats_update, this), |
1127 |
error: _.bind(this.handle_stats_error, this), |
1128 |
complete: _.bind(function(){this.updating_stats = false;}, this), |
1129 |
critical: false, |
1130 |
log_error: false, |
1131 |
skips_timeouts: true |
1132 |
}); |
1133 |
}, |
1134 |
|
1135 |
get_attachment: function(id) { |
1136 |
var attachment = undefined; |
1137 |
_.each(this.get("attachments"), function(a) { |
1138 |
if (a.id == id) {
|
1139 |
attachment = a; |
1140 |
} |
1141 |
}); |
1142 |
return attachment
|
1143 |
}, |
1144 |
|
1145 |
_set_stats: function(stats) { |
1146 |
var silent = silent === undefined ? false : silent; |
1147 |
// unavailable stats while building
|
1148 |
if (this.get("status") == "BUILD") { |
1149 |
this.stats_available = false; |
1150 |
} else { this.stats_available = true; } |
1151 |
|
1152 |
if (this.get("status") == "DESTROY") { this.stats_available = false; } |
1153 |
|
1154 |
this.set({stats: stats}, {silent:true}); |
1155 |
this.trigger("stats:update", stats); |
1156 |
}, |
1157 |
|
1158 |
unbind: function() { |
1159 |
models.VM.__super__.unbind.apply(this, arguments); |
1160 |
}, |
1161 |
|
1162 |
can_start: function(flv, count_current) { |
1163 |
var self = this; |
1164 |
var get_quota = function(key) { |
1165 |
if (!self.get('project')) { return false } |
1166 |
return self.get('project').quotas.get(key).get('available'); |
1167 |
} |
1168 |
var flavor = flv || this.get_flavor(); |
1169 |
var vm_ram_current = 0, vm_cpu_current = 0; |
1170 |
if (flv && this.is_active() || flv && count_current) { |
1171 |
var current = this.get_flavor(); |
1172 |
vm_ram_current = current.ram_to_bytes(); |
1173 |
vm_cpu_current = parseInt(current.get('cpu'));
|
1174 |
} |
1175 |
var vm_ram = flavor.ram_to_bytes();
|
1176 |
var vm_cpu = parseInt(flavor.get('cpu')); |
1177 |
var available_cpu = get_quota('cyclades.cpu') + vm_cpu_current; |
1178 |
var available_ram = get_quota('cyclades.ram') + vm_ram_current; |
1179 |
if (vm_ram > available_ram || vm_cpu > available_cpu) { return false } |
1180 |
return true |
1181 |
}, |
1182 |
|
1183 |
can_connect: function() { |
1184 |
if (!synnefo.config.hotplug_enabled && this.is_active()) { return false } |
1185 |
return _.contains(["ACTIVE", "STOPPED"], this.get("status")) && |
1186 |
!this.get('suspended') |
1187 |
}, |
1188 |
|
1189 |
can_disconnect: function() { |
1190 |
return _.contains(["ACTIVE", "STOPPED"], this.get("status")) |
1191 |
}, |
1192 |
|
1193 |
can_resize: function() { |
1194 |
return this.get('status') == 'STOPPED'; |
1195 |
}, |
1196 |
|
1197 |
can_reassign: function() { |
1198 |
return true; |
1199 |
}, |
1200 |
|
1201 |
handle_stats_error: function() { |
1202 |
stats = {}; |
1203 |
_.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) { |
1204 |
stats[k] = false;
|
1205 |
}); |
1206 |
|
1207 |
this.set({'stats': stats}); |
1208 |
}, |
1209 |
|
1210 |
// this method gets executed after a successful vm stats api call
|
1211 |
handle_stats_update: function(data) { |
1212 |
var self = this; |
1213 |
// avoid browser caching
|
1214 |
|
1215 |
if (data.stats && _.size(data.stats) > 0) { |
1216 |
var ts = $.now(); |
1217 |
var stats = data.stats;
|
1218 |
var images_loaded = 0; |
1219 |
var images = {};
|
1220 |
|
1221 |
function check_images_loaded() { |
1222 |
images_loaded++; |
1223 |
|
1224 |
if (images_loaded == 4) { |
1225 |
self._set_stats(images); |
1226 |
} |
1227 |
} |
1228 |
_.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) { |
1229 |
|
1230 |
stats[k] = stats[k] + "?_=" + ts;
|
1231 |
|
1232 |
var stat = k.slice(0,3); |
1233 |
var type = k.slice(3,6) == "Bar" ? "bar" : "time"; |
1234 |
var img = $("<img />"); |
1235 |
var val = stats[k];
|
1236 |
|
1237 |
// load stat image to a temporary dom element
|
1238 |
// update model stats on image load/error events
|
1239 |
img.load(function() {
|
1240 |
images[k] = val; |
1241 |
check_images_loaded(); |
1242 |
}); |
1243 |
|
1244 |
img.error(function() {
|
1245 |
images[stat + type] = false;
|
1246 |
check_images_loaded(); |
1247 |
}); |
1248 |
|
1249 |
img.attr({'src': stats[k]});
|
1250 |
}) |
1251 |
data.stats = stats; |
1252 |
} |
1253 |
|
1254 |
// do we need to change the interval ??
|
1255 |
if (data.stats.refresh * 1000 != this.stats_update_interval) { |
1256 |
this.stats_update_interval = data.stats.refresh * 1000; |
1257 |
this.stats_fetcher.interval = this.stats_update_interval; |
1258 |
this.stats_fetcher.maximum_interval = this.stats_update_interval; |
1259 |
this.stats_fetcher.stop();
|
1260 |
this.stats_fetcher.start(false); |
1261 |
} |
1262 |
}, |
1263 |
|
1264 |
// helper method that sets the do_update_stats
|
1265 |
// in the future this method could also make an api call
|
1266 |
// immediaetly if needed
|
1267 |
enable_stats_update: function() { |
1268 |
this.do_update_stats = true; |
1269 |
}, |
1270 |
|
1271 |
handle_destroy: function() { |
1272 |
this.stats_fetcher.stop();
|
1273 |
}, |
1274 |
|
1275 |
require_reboot: function() { |
1276 |
if (this.is_active()) { |
1277 |
this.set({'reboot_required': true}); |
1278 |
} |
1279 |
}, |
1280 |
|
1281 |
set_pending_action: function(data) { |
1282 |
this.pending_action = data;
|
1283 |
return data;
|
1284 |
}, |
1285 |
|
1286 |
// machine has pending action
|
1287 |
update_pending_action: function(action, force) { |
1288 |
this.set({pending_action: action}); |
1289 |
}, |
1290 |
|
1291 |
clear_pending_action: function() { |
1292 |
this.set({pending_action: undefined}); |
1293 |
}, |
1294 |
|
1295 |
has_pending_action: function() { |
1296 |
return this.get("pending_action") ? this.get("pending_action") : false; |
1297 |
}, |
1298 |
|
1299 |
// machine is active
|
1300 |
is_active: function() { |
1301 |
return models.VM.ACTIVE_STATES.indexOf(this.state()) > -1; |
1302 |
}, |
1303 |
|
1304 |
// machine is building
|
1305 |
is_building: function() { |
1306 |
return models.VM.BUILDING_STATES.indexOf(this.state()) > -1; |
1307 |
}, |
1308 |
|
1309 |
is_rebooting: function() { |
1310 |
return this.state() == 'REBOOT'; |
1311 |
}, |
1312 |
|
1313 |
in_error_state: function() { |
1314 |
return this.state() === "ERROR" |
1315 |
}, |
1316 |
|
1317 |
// user can connect to machine
|
1318 |
is_connectable: function() { |
1319 |
return models.VM.CONNECT_STATES.indexOf(this.state()) > -1; |
1320 |
}, |
1321 |
|
1322 |
remove_meta: function(key, complete, error) { |
1323 |
var url = this.api_path() + "/metadata/" + key; |
1324 |
this.api_call(url, "delete", undefined, complete, error); |
1325 |
}, |
1326 |
|
1327 |
save_meta: function(meta, complete, error) { |
1328 |
var url = this.api_path() + "/metadata/" + meta.key; |
1329 |
var payload = {meta:{}}; |
1330 |
payload.meta[meta.key] = meta.value; |
1331 |
payload._options = { |
1332 |
critical:false, |
1333 |
error_params: {
|
1334 |
title: "Machine metadata error", |
1335 |
extra_details: {"Machine id": this.id} |
1336 |
}}; |
1337 |
|
1338 |
this.api_call(url, "update", payload, complete, error); |
1339 |
}, |
1340 |
|
1341 |
|
1342 |
// update/get the state of the machine
|
1343 |
state: function() { |
1344 |
var args = slice.call(arguments); |
1345 |
|
1346 |
if (args.length > 0 && models.VM.STATES.indexOf(args[0]) > -1) { |
1347 |
this.set({'state': args[0]}); |
1348 |
} |
1349 |
|
1350 |
return this.get('state'); |
1351 |
}, |
1352 |
|
1353 |
// get the state that the api status corresponds to
|
1354 |
state_for_api_status: function(status) { |
1355 |
return this.state_transition(this.state(), status); |
1356 |
}, |
1357 |
|
1358 |
// get transition state for the corresponging api status
|
1359 |
state_transition: function(state, new_status) { |
1360 |
var statuses = models.VM.STATES_TRANSITIONS[state];
|
1361 |
if (statuses) {
|
1362 |
if (statuses.indexOf(new_status) > -1) { |
1363 |
return new_status;
|
1364 |
} else {
|
1365 |
return state;
|
1366 |
} |
1367 |
} else {
|
1368 |
return new_status;
|
1369 |
} |
1370 |
}, |
1371 |
|
1372 |
// the current vm state is a transition state
|
1373 |
in_transition: function() { |
1374 |
return models.VM.TRANSITION_STATES.indexOf(this.state()) > -1 || |
1375 |
models.VM.TRANSITION_STATES.indexOf(this.get('status')) > -1; |
1376 |
}, |
1377 |
|
1378 |
// get image object
|
1379 |
get_image: function(callback) { |
1380 |
if (callback == undefined) { callback = function(){} } |
1381 |
var image = storage.images.get(this.get('image')); |
1382 |
if (!image) {
|
1383 |
storage.images.update_unknown_id(this.get('image'), callback); |
1384 |
return;
|
1385 |
} |
1386 |
callback(image); |
1387 |
return image;
|
1388 |
}, |
1389 |
|
1390 |
// get flavor object
|
1391 |
get_flavor: function() { |
1392 |
var flv = storage.flavors.get(this.get('flavor')); |
1393 |
if (!flv) {
|
1394 |
storage.flavors.update_unknown_id(this.get('flavor')); |
1395 |
flv = storage.flavors.get(this.get('flavor')); |
1396 |
} |
1397 |
return flv;
|
1398 |
}, |
1399 |
|
1400 |
get_resize_flavors: function() { |
1401 |
var vm_flavor = this.get_flavor(); |
1402 |
var flavors = synnefo.storage.flavors.filter(function(f){ |
1403 |
return f.get('disk_template') == |
1404 |
vm_flavor.get('disk_template') && f.get('disk') == |
1405 |
vm_flavor.get('disk');
|
1406 |
}); |
1407 |
return flavors;
|
1408 |
}, |
1409 |
|
1410 |
get_flavor_quotas: function() { |
1411 |
var flavor = this.get_flavor(); |
1412 |
return {
|
1413 |
cpu: flavor.get('cpu'), |
1414 |
ram: flavor.get_ram_size(),
|
1415 |
disk:flavor.get_disk_size()
|
1416 |
} |
1417 |
}, |
1418 |
|
1419 |
get_meta: function(key, deflt) { |
1420 |
if (this.get('metadata') && this.get('metadata')) { |
1421 |
if (!this.get('metadata')[key]) { return deflt } |
1422 |
return _.escape(this.get('metadata')[key]); |
1423 |
} else {
|
1424 |
return deflt;
|
1425 |
} |
1426 |
}, |
1427 |
|
1428 |
get_meta_keys: function() { |
1429 |
if (this.get('metadata') && this.get('metadata')) { |
1430 |
return _.keys(this.get('metadata')); |
1431 |
} else {
|
1432 |
return [];
|
1433 |
} |
1434 |
}, |
1435 |
|
1436 |
// get metadata OS value
|
1437 |
get_os: function() { |
1438 |
var image = this.get_image(); |
1439 |
return this.get_meta('OS') || (image ? |
1440 |
image.get_os() || "okeanos" : "okeanos"); |
1441 |
}, |
1442 |
|
1443 |
get_gui: function() { |
1444 |
return this.get_meta('GUI'); |
1445 |
}, |
1446 |
|
1447 |
get_hostname: function() { |
1448 |
return this.get_meta('hostname') || this.get('fqdn') || synnefo.config.no_fqdn_message; |
1449 |
}, |
1450 |
|
1451 |
// get actions that the user can execute
|
1452 |
// depending on the vm state/status
|
1453 |
get_available_actions: function() { |
1454 |
if (this.get('suspended')) { return [] } |
1455 |
return models.VM.AVAILABLE_ACTIONS[this.state()]; |
1456 |
}, |
1457 |
|
1458 |
set_profile: function(profile, net_id) { |
1459 |
}, |
1460 |
|
1461 |
// call rename api
|
1462 |
rename: function(new_name) { |
1463 |
//this.set({'name': new_name});
|
1464 |
var self = this; |
1465 |
this.sync("update", this, { |
1466 |
critical: true, |
1467 |
data: {
|
1468 |
'server': {
|
1469 |
'name': new_name
|
1470 |
} |
1471 |
}, |
1472 |
success: _.bind(function(){ |
1473 |
snf.api.trigger("call");
|
1474 |
this.set({'name': new_name}); |
1475 |
}, this)
|
1476 |
}); |
1477 |
}, |
1478 |
|
1479 |
get_console_url: function(data) { |
1480 |
var url_params = {
|
1481 |
machine: this.get("name"), |
1482 |
host_ip: this.get_hostname(), |
1483 |
host_ip_v6: this.get_hostname(), |
1484 |
host: data.host,
|
1485 |
port: data.port,
|
1486 |
password: data.password
|
1487 |
} |
1488 |
return synnefo.config.ui_console_url + '?' + $.param(url_params); |
1489 |
}, |
1490 |
|
1491 |
set_firewall: function(nic, value, success_cb, error_cb) { |
1492 |
var self = this; |
1493 |
var success = function() { self.require_reboot(); success_cb() } |
1494 |
var error = function() { error_cb() } |
1495 |
var data = {'nic': nic.id, 'profile': value, 'display': true}; |
1496 |
var url = this.url() + "/action"; |
1497 |
//var params = {skip_api_error: false, display: true};
|
1498 |
this.call('firewallProfile', success, error, data); |
1499 |
}, |
1500 |
|
1501 |
connect_floating_ip: function(ip, cb, error) { |
1502 |
var self = this; |
1503 |
var from_status = this.get('status'); |
1504 |
this.set({'status': 'CONNECTING'}); |
1505 |
synnefo.storage.ports.create({ |
1506 |
port: {
|
1507 |
network_id: ip.get('floating_network_id'), |
1508 |
device_id: this.id, |
1509 |
fixed_ips: [{'ip_address': ip.get('floating_ip_address')}] |
1510 |
} |
1511 |
}, { |
1512 |
success: cb,
|
1513 |
error: function() { error && error() }, |
1514 |
skip_api_error: false |
1515 |
}); |
1516 |
}, |
1517 |
|
1518 |
// action helper
|
1519 |
call: function(action_name, success, error, params) { |
1520 |
var id_param = [this.id]; |
1521 |
|
1522 |
params = params || {}; |
1523 |
success = success || function() {};
|
1524 |
error = error || function() {};
|
1525 |
|
1526 |
var self = this; |
1527 |
|
1528 |
switch(action_name) {
|
1529 |
case 'start': |
1530 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1531 |
"create", // create so that sync later uses POST to make the call |
1532 |
{start:{}}, // payload |
1533 |
function() {
|
1534 |
// set state after successful call
|
1535 |
self.state("START");
|
1536 |
success.apply(this, arguments); |
1537 |
snf.api.trigger("call");
|
1538 |
}, |
1539 |
error, 'start', params);
|
1540 |
break;
|
1541 |
case 'reboot': |
1542 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1543 |
"create", // create so that sync later uses POST to make the call |
1544 |
{reboot:{}}, // payload |
1545 |
function() {
|
1546 |
// set state after successful call
|
1547 |
self.state("REBOOT");
|
1548 |
success.apply(this, arguments) |
1549 |
snf.api.trigger("call");
|
1550 |
self.set({'reboot_required': false}); |
1551 |
}, |
1552 |
error, 'reboot', params);
|
1553 |
break;
|
1554 |
case 'shutdown': |
1555 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1556 |
"create", // create so that sync later uses POST to make the call |
1557 |
{shutdown:{}}, // payload |
1558 |
function() {
|
1559 |
// set state after successful call
|
1560 |
self.state("SHUTDOWN");
|
1561 |
success.apply(this, arguments) |
1562 |
snf.api.trigger("call");
|
1563 |
}, |
1564 |
error, 'shutdown', params);
|
1565 |
break;
|
1566 |
case 'console': |
1567 |
this.__make_api_call(this.url() + "/action", "create", |
1568 |
{'console': {'type':'vnc'}}, |
1569 |
function(data) {
|
1570 |
var cons_data = data.console;
|
1571 |
success.apply(this, [cons_data]);
|
1572 |
}, undefined, 'console', params) |
1573 |
break;
|
1574 |
case 'destroy': |
1575 |
this.__make_api_call(this.url(), // vm actions url |
1576 |
"delete", // create so that sync later uses POST to make the call |
1577 |
undefined, // payload |
1578 |
function() {
|
1579 |
// set state after successful call
|
1580 |
self.state('DESTROY');
|
1581 |
success.apply(this, arguments); |
1582 |
synnefo.api.trigger("quotas:call", 20); |
1583 |
}, |
1584 |
error, 'destroy', params);
|
1585 |
break;
|
1586 |
case 'reassign': |
1587 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1588 |
"create", // create so that sync later uses POST to make the call |
1589 |
{reassign: {project:params.project_id}}, // payload |
1590 |
function() {
|
1591 |
self.state('reassign');
|
1592 |
self.set({'tenant_id': params.project_id});
|
1593 |
success.apply(this, arguments); |
1594 |
snf.api.trigger("call");
|
1595 |
}, |
1596 |
error, 'reassign', params);
|
1597 |
break;
|
1598 |
case 'resize': |
1599 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1600 |
"create", // create so that sync later uses POST to make the call |
1601 |
{resize: {flavorRef:params.flavor}}, // payload |
1602 |
function() {
|
1603 |
self.state('RESIZE');
|
1604 |
success.apply(this, arguments); |
1605 |
snf.api.trigger("call");
|
1606 |
}, |
1607 |
error, 'resize', params);
|
1608 |
break;
|
1609 |
case 'destroy': |
1610 |
this.__make_api_call(this.url(), // vm actions url |
1611 |
"delete", // create so that sync later uses POST to make the call |
1612 |
undefined, // payload |
1613 |
function() {
|
1614 |
// set state after successful call
|
1615 |
self.state('DESTROY');
|
1616 |
success.apply(this, arguments); |
1617 |
synnefo.storage.quotas.get('cyclades.vm').decrease();
|
1618 |
|
1619 |
}, |
1620 |
error, 'destroy', params);
|
1621 |
break;
|
1622 |
case 'firewallProfile': |
1623 |
this.__make_api_call(this.get_action_url(), // vm actions url |
1624 |
"create",
|
1625 |
{firewallProfile:{nic:params.nic, profile:params.profile}}, // payload |
1626 |
function() {
|
1627 |
success.apply(this, arguments); |
1628 |
snf.api.trigger("call");
|
1629 |
}, |
1630 |
error, 'start', params);
|
1631 |
break;
|
1632 |
default:
|
1633 |
throw "Invalid VM action ("+action_name+")"; |
1634 |
} |
1635 |
}, |
1636 |
|
1637 |
__make_api_call: function(url, method, data, success, error, action, |
1638 |
extra_params) { |
1639 |
var self = this; |
1640 |
error = error || function(){};
|
1641 |
success = success || function(){};
|
1642 |
|
1643 |
var params = {
|
1644 |
url: url,
|
1645 |
data: data,
|
1646 |
success: function() { |
1647 |
self.handle_action_succeed.apply(self, arguments);
|
1648 |
success.apply(this, arguments) |
1649 |
}, |
1650 |
error: function() { |
1651 |
self.handle_action_fail.apply(self, arguments);
|
1652 |
error.apply(this, arguments) |
1653 |
}, |
1654 |
error_params: { ns: "Machines actions", |
1655 |
title: "'" + this.get("name") + "'" + " " + action + " failed", |
1656 |
extra_details: {
|
1657 |
'Machine ID': this.id, |
1658 |
'URL': url,
|
1659 |
'Action': action || "undefined" }, |
1660 |
allow_reload: false |
1661 |
}, |
1662 |
display: false, |
1663 |
critical: false |
1664 |
} |
1665 |
_.extend(params, extra_params); |
1666 |
this.sync(method, this, params); |
1667 |
}, |
1668 |
|
1669 |
handle_action_succeed: function() { |
1670 |
this.trigger("action:success", arguments); |
1671 |
}, |
1672 |
|
1673 |
reset_action_error: function() { |
1674 |
this.action_error = false; |
1675 |
this.trigger("action:fail:reset", this.action_error); |
1676 |
}, |
1677 |
|
1678 |
handle_action_fail: function() { |
1679 |
this.action_error = arguments; |
1680 |
this.trigger("action:fail", arguments); |
1681 |
}, |
1682 |
|
1683 |
get_action_url: function(name) { |
1684 |
return this.url() + "/action"; |
1685 |
}, |
1686 |
|
1687 |
get_diagnostics_url: function() { |
1688 |
return this.url() + "/diagnostics"; |
1689 |
}, |
1690 |
|
1691 |
get_users: function() { |
1692 |
var image;
|
1693 |
var users = [];
|
1694 |
try {
|
1695 |
var users = this.get_meta('users').split(" "); |
1696 |
} catch (err) { users = null } |
1697 |
if (!users) {
|
1698 |
image = this.get_image();
|
1699 |
if (image) {
|
1700 |
users = image.get_created_users(); |
1701 |
} |
1702 |
} |
1703 |
return users;
|
1704 |
}, |
1705 |
|
1706 |
get_connection_info: function(host_os, success, error) { |
1707 |
var url = synnefo.config.ui_connect_url;
|
1708 |
var users = this.get_users(); |
1709 |
|
1710 |
params = { |
1711 |
ip_address: this.get_hostname(), |
1712 |
hostname: this.get_hostname(), |
1713 |
os: this.get_os(), |
1714 |
host_os: host_os,
|
1715 |
ports: JSON.stringify(this.get('SNF:port_forwarding') || {}), |
1716 |
srv: this.id |
1717 |
} |
1718 |
|
1719 |
if (users.length) {
|
1720 |
params['username'] = _.last(users)
|
1721 |
} |
1722 |
|
1723 |
url = url + "?" + $.param(params); |
1724 |
|
1725 |
var ajax = snf.api.sync("read", undefined, { url: url, |
1726 |
error:error,
|
1727 |
success:success,
|
1728 |
handles_error:1}); |
1729 |
} |
1730 |
}); |
1731 |
|
1732 |
models.VM.ACTIONS = [ |
1733 |
'start',
|
1734 |
'shutdown',
|
1735 |
'reboot',
|
1736 |
'console',
|
1737 |
'destroy',
|
1738 |
'resize',
|
1739 |
'reassign',
|
1740 |
'snapshot'
|
1741 |
] |
1742 |
|
1743 |
models.VM.TASK_STATE_STATUS_MAP = { |
1744 |
'BUILDING': 'BUILD', |
1745 |
'REBOOTING': 'REBOOT', |
1746 |
'STOPPING': 'SHUTDOWN', |
1747 |
'STARTING': 'START', |
1748 |
'RESIZING': 'RESIZE', |
1749 |
'REASSIGNING': 'REASSIGN', |
1750 |
'CONNECTING': 'CONNECT', |
1751 |
'DISCONNECTING': 'DISCONNECT', |
1752 |
'DESTROYING': 'DESTROY' |
1753 |
} |
1754 |
|
1755 |
models.VM.AVAILABLE_ACTIONS = { |
1756 |
'UNKNWON' : ['destroy'], |
1757 |
'BUILD' : ['destroy'], |
1758 |
'REBOOT' : ['destroy'], |
1759 |
'STOPPED' : ['start', 'destroy', 'reassign', 'resize', 'snapshot'], |
1760 |
'ACTIVE' : ['shutdown', 'destroy', 'reboot', 'console', 'reassign', 'resize', 'snapshot'], |
1761 |
'ERROR' : ['destroy'], |
1762 |
'DELETED' : ['destroy'], |
1763 |
'DESTROY' : ['destroy'], |
1764 |
'SHUTDOWN' : ['destroy'], |
1765 |
'START' : ['destroy'], |
1766 |
'CONNECT' : ['destroy'], |
1767 |
'DISCONNECT' : ['destroy'], |
1768 |
'RESIZE' : ['destroy'], |
1769 |
'REASSIGN' : ['destroy'] |
1770 |
} |
1771 |
|
1772 |
models.VM.AVAILABLE_ACTIONS_INACTIVE = {} |
1773 |
|
1774 |
// api status values
|
1775 |
models.VM.STATUSES = [ |
1776 |
'UNKNWON',
|
1777 |
'BUILD',
|
1778 |
'REBOOT',
|
1779 |
'STOPPED',
|
1780 |
'ACTIVE',
|
1781 |
'ERROR',
|
1782 |
'DELETED',
|
1783 |
'REASSIGN',
|
1784 |
'RESIZE'
|
1785 |
] |
1786 |
|
1787 |
// api status values
|
1788 |
models.VM.CONNECT_STATES = [ |
1789 |
'ACTIVE',
|
1790 |
'REBOOT',
|
1791 |
'SHUTDOWN'
|
1792 |
] |
1793 |
|
1794 |
// vm states
|
1795 |
models.VM.STATES = models.VM.STATUSES.concat([ |
1796 |
'DESTROY',
|
1797 |
'SHUTDOWN',
|
1798 |
'START',
|
1799 |
'CONNECT',
|
1800 |
'DISCONNECT',
|
1801 |
'FIREWALL',
|
1802 |
'REASSIGN',
|
1803 |
'RESIZE'
|
1804 |
]); |
1805 |
|
1806 |
models.VM.STATES_TRANSITIONS = { |
1807 |
'DESTROY' : ['DELETED'], |
1808 |
'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'], |
1809 |
'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY', 'RESIZE', 'REASSIGN'], |
1810 |
'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY', 'REASSIGN'], |
1811 |
'START': ['ERROR', 'ACTIVE', 'DESTROY'], |
1812 |
'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'], |
1813 |
'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'], |
1814 |
'RESIZE': ['ERROR', 'STOPPED'], |
1815 |
'REASSIGN': ['ERROR', 'STOPPED', 'ACTIVE'] |
1816 |
} |
1817 |
|
1818 |
models.VM.TRANSITION_STATES = [ |
1819 |
'DESTROY',
|
1820 |
'SHUTDOWN',
|
1821 |
'START',
|
1822 |
'REBOOT',
|
1823 |
'BUILD',
|
1824 |
'RESIZE',
|
1825 |
'REASSIGN',
|
1826 |
'DISCONNECT',
|
1827 |
'CONNECT'
|
1828 |
] |
1829 |
|
1830 |
models.VM.ACTIVE_STATES = [ |
1831 |
'BUILD', 'REBOOT', 'ACTIVE', |
1832 |
'SHUTDOWN', 'CONNECT', 'DISCONNECT' |
1833 |
] |
1834 |
|
1835 |
models.VM.BUILDING_STATES = [ |
1836 |
'BUILD'
|
1837 |
] |
1838 |
|
1839 |
models.Images = models.Collection.extend({ |
1840 |
model: models.Image,
|
1841 |
path: 'images', |
1842 |
details: true, |
1843 |
noUpdate: true, |
1844 |
supportIncUpdates: false, |
1845 |
meta_keys_as_attrs: ["OS", "description", "kernel", "size", "GUI"], |
1846 |
meta_labels: {},
|
1847 |
read_method: 'read', |
1848 |
|
1849 |
// update collection model with id passed
|
1850 |
// making a direct call to the image
|
1851 |
// api url
|
1852 |
update_unknown_id: function(id, callback) { |
1853 |
var url = getUrl.call(this) + "/" + id; |
1854 |
this.api_call(this.path + "/" + id, this.read_method, { |
1855 |
_options:{
|
1856 |
async:true, |
1857 |
skip_api_error:true} |
1858 |
}, undefined,
|
1859 |
_.bind(function() {
|
1860 |
if (!this.get(id)) { |
1861 |
if (this.fallback_service) { |
1862 |
// if current service has fallback_service attribute set
|
1863 |
// use this service to retrieve the missing image model
|
1864 |
var tmpservice = new this.fallback_service(); |
1865 |
tmpservice.update_unknown_id(id, _.bind(function(img){
|
1866 |
img.attributes.status = "DELETED";
|
1867 |
this.add(img.attributes);
|
1868 |
callback(this.get(id));
|
1869 |
}, this));
|
1870 |
} else {
|
1871 |
var title = synnefo.config.image_deleted_title || 'Deleted'; |
1872 |
// else add a dummy DELETED state image entry
|
1873 |
this.add({id:id, name:title, size:-1, |
1874 |
progress:100, status:"DELETED"}); |
1875 |
callback(this.get(id));
|
1876 |
} |
1877 |
} else {
|
1878 |
callback(this.get(id));
|
1879 |
} |
1880 |
}, this), _.bind(function(image, msg, xhr) { |
1881 |
if (!image) {
|
1882 |
var title = synnefo.config.image_deleted_title || 'Deleted'; |
1883 |
this.add({id:id, name:title, size:-1, |
1884 |
progress:100, status:"DELETED"}); |
1885 |
callback(this.get(id));
|
1886 |
return;
|
1887 |
} |
1888 |
var img_data = this._read_image_from_request(image, msg, xhr); |
1889 |
this.add(img_data);
|
1890 |
callback(this.get(id));
|
1891 |
}, this));
|
1892 |
}, |
1893 |
|
1894 |
_read_image_from_request: function(image, msg, xhr) { |
1895 |
return image.image;
|
1896 |
}, |
1897 |
|
1898 |
parse: function (resp, xhr) { |
1899 |
var parsed = _.map(resp.images, _.bind(this.parse_meta, this)); |
1900 |
parsed = this.fill_owners(parsed);
|
1901 |
return parsed;
|
1902 |
}, |
1903 |
|
1904 |
fill_owners: function(images) { |
1905 |
// do translate uuid->displayname if needed
|
1906 |
// store display name in owner attribute for compatibility
|
1907 |
var uuids = [];
|
1908 |
|
1909 |
var images = _.map(images, function(img, index) { |
1910 |
if (synnefo.config.translate_uuids) {
|
1911 |
uuids.push(img['owner']);
|
1912 |
} |
1913 |
img['owner_uuid'] = img['owner']; |
1914 |
return img;
|
1915 |
}); |
1916 |
|
1917 |
if (uuids.length > 0) { |
1918 |
var handle_results = function(data) { |
1919 |
_.each(images, function (img) {
|
1920 |
img['owner'] = data.uuid_catalog[img['owner_uuid']]; |
1921 |
}); |
1922 |
} |
1923 |
// notice the async false
|
1924 |
var uuid_map = this.translate_uuids(uuids, false, |
1925 |
handle_results) |
1926 |
} |
1927 |
return images;
|
1928 |
}, |
1929 |
|
1930 |
translate_uuids: function(uuids, async, cb) { |
1931 |
var url = synnefo.config.user_catalog_url;
|
1932 |
var data = JSON.stringify({'uuids': uuids}); |
1933 |
|
1934 |
// post to user_catalogs api
|
1935 |
snf.api.sync('create', undefined, { |
1936 |
url: url,
|
1937 |
data: data,
|
1938 |
async: async,
|
1939 |
success: cb
|
1940 |
}); |
1941 |
}, |
1942 |
|
1943 |
get_meta_key: function(img, key) { |
1944 |
if (img.metadata && img.metadata && img.metadata[key]) {
|
1945 |
return _.escape(img.metadata[key]);
|
1946 |
} |
1947 |
return undefined; |
1948 |
}, |
1949 |
|
1950 |
comparator: function(img) { |
1951 |
return -img.get_sort_order("sortorder") || 0; |
1952 |
}, |
1953 |
|
1954 |
parse_meta: function(img) { |
1955 |
_.each(this.meta_keys_as_attrs, _.bind(function(key){ |
1956 |
if (img[key]) { return }; |
1957 |
img[key] = this.get_meta_key(img, key) || ""; |
1958 |
}, this));
|
1959 |
return img;
|
1960 |
}, |
1961 |
|
1962 |
active: function() { |
1963 |
return this.filter(function(img){return img.get('status') != "DELETED"}); |
1964 |
}, |
1965 |
|
1966 |
predefined: function() { |
1967 |
return _.filter(this.active(), function(i) { return !i.get("serverRef")}); |
1968 |
}, |
1969 |
|
1970 |
fetch_for_type: function(type, complete, error) { |
1971 |
this.fetch({update:true, |
1972 |
success: complete,
|
1973 |
error: error,
|
1974 |
skip_api_error: true }); |
1975 |
}, |
1976 |
|
1977 |
get_images_for_type: function(type) { |
1978 |
if (this['get_{0}_images'.format(type)]) { |
1979 |
return this['get_{0}_images'.format(type)](); |
1980 |
} |
1981 |
|
1982 |
return this.active(); |
1983 |
}, |
1984 |
|
1985 |
update_images_for_type: function(type, onStart, onComplete, onError, force_load) { |
1986 |
var load = false; |
1987 |
error = onError || function() {};
|
1988 |
function complete(collection) { |
1989 |
onComplete(collection.get_images_for_type(type)); |
1990 |
} |
1991 |
|
1992 |
// do we need to fetch/update current collection entries
|
1993 |
if (load) {
|
1994 |
onStart(); |
1995 |
this.fetch_for_type(type, complete, error);
|
1996 |
} else {
|
1997 |
// fallback to complete
|
1998 |
complete(this);
|
1999 |
} |
2000 |
} |
2001 |
}) |
2002 |
|
2003 |
models.Flavors = models.Collection.extend({ |
2004 |
model: models.Flavor,
|
2005 |
path: 'flavors', |
2006 |
details: true, |
2007 |
noUpdate: true, |
2008 |
supportIncUpdates: false, |
2009 |
// update collection model with id passed
|
2010 |
// making a direct call to the flavor
|
2011 |
// api url
|
2012 |
update_unknown_id: function(id, callback) { |
2013 |
var url = getUrl.call(this) + "/" + id; |
2014 |
this.api_call(this.path + "/" + id, "read", {_options:{async:false, skip_api_error:true}}, undefined, |
2015 |
_.bind(function() {
|
2016 |
this.add({id:id, cpu:"Unknown", ram:"Unknown", disk:"Unknown", name: "Unknown", status:"DELETED"}) |
2017 |
}, this), _.bind(function(flv) { |
2018 |
if (!flv.flavor.status) { flv.flavor.status = "DELETED" }; |
2019 |
this.add(flv.flavor);
|
2020 |
}, this));
|
2021 |
}, |
2022 |
|
2023 |
parse: function (resp, xhr) { |
2024 |
return _.map(resp.flavors, function(o) { |
2025 |
o.cpu = o['vcpus'];
|
2026 |
o.disk_template = o['SNF:disk_template'];
|
2027 |
return o
|
2028 |
}); |
2029 |
}, |
2030 |
|
2031 |
comparator: function(flv) { |
2032 |
return flv.get("disk") * flv.get("cpu") * flv.get("ram"); |
2033 |
}, |
2034 |
|
2035 |
unavailable_values_for_quotas: function(quotas, flavors, extra) { |
2036 |
var flavors = flavors || this.active(); |
2037 |
var index = {cpu:[], disk:[], ram:[]}; |
2038 |
var extra = extra == undefined ? {cpu:0, disk:0, ram:0} : extra; |
2039 |
|
2040 |
_.each(flavors, function(el) {
|
2041 |
|
2042 |
var disk_available = quotas['disk'] + extra.disk; |
2043 |
var disk_size = el.get_disk_size();
|
2044 |
if (index.disk.indexOf(disk_size) == -1) { |
2045 |
var disk = el.disk_to_bytes();
|
2046 |
if (disk > disk_available) {
|
2047 |
index.disk.push(el.get('disk'));
|
2048 |
} |
2049 |
} |
2050 |
|
2051 |
var ram_available = quotas['ram'] + extra.ram * 1024 * 1024; |
2052 |
var ram_size = el.get_ram_size();
|
2053 |
if (index.ram.indexOf(ram_size) == -1) { |
2054 |
var ram = el.ram_to_bytes();
|
2055 |
if (ram > ram_available) {
|
2056 |
index.ram.push(el.get('ram'))
|
2057 |
} |
2058 |
} |
2059 |
|
2060 |
var cpu = el.get('cpu'); |
2061 |
var cpu_available = quotas['cpu'] + extra.cpu; |
2062 |
if (index.cpu.indexOf(cpu) == -1) { |
2063 |
if (cpu > cpu_available) {
|
2064 |
index.cpu.push(el.get('cpu'))
|
2065 |
} |
2066 |
} |
2067 |
}); |
2068 |
return index;
|
2069 |
}, |
2070 |
|
2071 |
unavailable_values_for_image: function(img, flavors) { |
2072 |
var flavors = flavors || this.active(); |
2073 |
var size = img.get_size();
|
2074 |
|
2075 |
var index = {cpu:[], disk:[], ram:[]}; |
2076 |
|
2077 |
_.each(this.active(), function(el) { |
2078 |
var img_size = size;
|
2079 |
var flv_size = el.get_disk_size();
|
2080 |
if (flv_size < img_size) {
|
2081 |
if (index.disk.indexOf(el.get("disk")) == -1) { |
2082 |
index.disk.push(el.get("disk"));
|
2083 |
} |
2084 |
}; |
2085 |
}); |
2086 |
|
2087 |
return index;
|
2088 |
}, |
2089 |
|
2090 |
get_flavor: function(cpu, mem, disk, disk_template, filter_list) { |
2091 |
if (!filter_list) { filter_list = this.models }; |
2092 |
|
2093 |
return this.select(function(flv){ |
2094 |
if (flv.get("cpu") == cpu + "" && |
2095 |
flv.get("ram") == mem + "" && |
2096 |
flv.get("disk") == disk + "" && |
2097 |
flv.get("disk_template") == disk_template &&
|
2098 |
filter_list.indexOf(flv) > -1) { return true; } |
2099 |
})[0];
|
2100 |
}, |
2101 |
|
2102 |
get_data: function(lst) { |
2103 |
var data = {'cpu': [], 'mem':[], 'disk':[], 'disk_template':[]}; |
2104 |
|
2105 |
_.each(lst, function(flv) {
|
2106 |
if (data.cpu.indexOf(flv.get("cpu")) == -1) { |
2107 |
data.cpu.push(flv.get("cpu"));
|
2108 |
} |
2109 |
if (data.mem.indexOf(flv.get("ram")) == -1) { |
2110 |
data.mem.push(flv.get("ram"));
|
2111 |
} |
2112 |
if (data.disk.indexOf(flv.get("disk")) == -1) { |
2113 |
data.disk.push(flv.get("disk"));
|
2114 |
} |
2115 |
if (data.disk_template.indexOf(flv.get("disk_template")) == -1) { |
2116 |
data.disk_template.push(flv.get("disk_template"));
|
2117 |
} |
2118 |
}) |
2119 |
|
2120 |
return data;
|
2121 |
}, |
2122 |
|
2123 |
active: function() { |
2124 |
return this.filter(function(flv){return flv.get('status') != "DELETED"}); |
2125 |
} |
2126 |
|
2127 |
}) |
2128 |
|
2129 |
models.VMS = models.Collection.extend({ |
2130 |
model: models.VM,
|
2131 |
path: 'servers', |
2132 |
details: true, |
2133 |
copy_image_meta: true, |
2134 |
|
2135 |
parse: function (resp, xhr) { |
2136 |
var data = resp;
|
2137 |
if (!resp) { return [] }; |
2138 |
data = _.filter(_.map(resp.servers, |
2139 |
_.bind(this.parse_vm_api_data, this)), |
2140 |
function(v){return v}); |
2141 |
return data;
|
2142 |
}, |
2143 |
|
2144 |
parse_vm_api_data: function(data) { |
2145 |
var status;
|
2146 |
// do not add non existing DELETED entries
|
2147 |
if (data.status && data.status == "DELETED") { |
2148 |
if (!this.get(data.id)) { |
2149 |
return false; |
2150 |
} |
2151 |
} |
2152 |
|
2153 |
if ('SNF:task_state' in data) { |
2154 |
data['task_state'] = data['SNF:task_state']; |
2155 |
// Update machine state based on task_state value
|
2156 |
// Do not apply task_state logic when machine is in ERROR state.
|
2157 |
// In that case only update from task_state only if equals to
|
2158 |
// DESTROY
|
2159 |
if (data['task_state']) { |
2160 |
if (data['status'] != 'ERROR' && data['task_state'] != 'DESTROY') { |
2161 |
status = models.VM.TASK_STATE_STATUS_MAP[data['task_state']];
|
2162 |
if (status) { data['status'] = status } |
2163 |
} |
2164 |
} |
2165 |
} |
2166 |
|
2167 |
// OS attribute
|
2168 |
if (this.has_meta(data)) { |
2169 |
data['OS'] = data.metadata.OS || snf.config.unknown_os;
|
2170 |
} |
2171 |
|
2172 |
if (!data.diagnostics) {
|
2173 |
data.diagnostics = []; |
2174 |
} |
2175 |
|
2176 |
// network metadata
|
2177 |
data['firewalls'] = {};
|
2178 |
data['fqdn'] = data['SNF:fqdn']; |
2179 |
|
2180 |
// if vm has no metadata, no metadata object
|
2181 |
// is in json response, reset it to force
|
2182 |
// value update
|
2183 |
if (!data['metadata']) { |
2184 |
data['metadata'] = {};
|
2185 |
} |
2186 |
|
2187 |
// v2.0 API returns objects
|
2188 |
data.image_obj = data.image; |
2189 |
data.image = data.image_obj.id; |
2190 |
data.flavor_obj = data.flavor; |
2191 |
data.flavor = data.flavor_obj.id; |
2192 |
|
2193 |
return data;
|
2194 |
}, |
2195 |
|
2196 |
get_reboot_required: function() { |
2197 |
return this.filter(function(vm){return vm.get("reboot_required") == true}) |
2198 |
}, |
2199 |
|
2200 |
has_pending_actions: function() { |
2201 |
return this.filter(function(vm){return vm.pending_action}).length > 0; |
2202 |
}, |
2203 |
|
2204 |
reset_pending_actions: function() { |
2205 |
this.each(function(vm) { |
2206 |
vm.clear_pending_action(); |
2207 |
}) |
2208 |
}, |
2209 |
|
2210 |
do_all_pending_actions: function(success, error) { |
2211 |
this.each(function(vm) { |
2212 |
if (vm.has_pending_action()) {
|
2213 |
vm.call(vm.pending_action, success, error); |
2214 |
vm.clear_pending_action(); |
2215 |
} |
2216 |
}) |
2217 |
}, |
2218 |
|
2219 |
do_all_reboots: function(success, error) { |
2220 |
this.each(function(vm) { |
2221 |
if (vm.get("reboot_required")) { |
2222 |
vm.call("reboot", success, error);
|
2223 |
} |
2224 |
}); |
2225 |
}, |
2226 |
|
2227 |
reset_reboot_required: function() { |
2228 |
this.each(function(vm) { |
2229 |
vm.set({'reboot_required': undefined}); |
2230 |
}) |
2231 |
}, |
2232 |
|
2233 |
stop_stats_update: function(exclude) { |
2234 |
var exclude = exclude || [];
|
2235 |
this.each(function(vm) { |
2236 |
if (exclude.indexOf(vm) > -1) { |
2237 |
return;
|
2238 |
} |
2239 |
vm.stop_stats_update(); |
2240 |
}) |
2241 |
}, |
2242 |
|
2243 |
has_meta: function(vm_data) { |
2244 |
return vm_data.metadata && vm_data.metadata
|
2245 |
}, |
2246 |
|
2247 |
has_addresses: function(vm_data) { |
2248 |
return vm_data.metadata && vm_data.metadata
|
2249 |
}, |
2250 |
|
2251 |
create: function (name, image, flavor, meta, project, extra, callback) { |
2252 |
|
2253 |
if (this.copy_image_meta) { |
2254 |
if (synnefo.config.vm_image_common_metadata) {
|
2255 |
_.each(synnefo.config.vm_image_common_metadata, |
2256 |
function(key){
|
2257 |
if (image.get_meta(key)) {
|
2258 |
meta[key] = image.get_meta(key); |
2259 |
} |
2260 |
}); |
2261 |
} |
2262 |
|
2263 |
if (image.get("OS")) { |
2264 |
meta['OS'] = image.get("OS"); |
2265 |
} |
2266 |
} |
2267 |
|
2268 |
opts = {name: name, imageRef: image.id, flavorRef: flavor.id, |
2269 |
metadata:meta, project: project.id} |
2270 |
opts = _.extend(opts, extra); |
2271 |
|
2272 |
var cb = function(data) { |
2273 |
synnefo.api.trigger("quota:update");
|
2274 |
callback(data); |
2275 |
} |
2276 |
|
2277 |
this.api_call(this.path, "create", {'server': opts}, undefined, |
2278 |
undefined, cb, {critical: true}); |
2279 |
}, |
2280 |
|
2281 |
load_missing_images: function(callback) { |
2282 |
var missing_ids = [];
|
2283 |
var resolved = 0; |
2284 |
|
2285 |
// fill missing_ids
|
2286 |
this.each(function(el) { |
2287 |
var imgid = el.get("image"); |
2288 |
var existing = synnefo.storage.images.get(imgid);
|
2289 |
if (!existing && missing_ids.indexOf(imgid) == -1) { |
2290 |
missing_ids.push(imgid); |
2291 |
} |
2292 |
}); |
2293 |
var check = function() { |
2294 |
// once all missing ids where resolved continue calling the
|
2295 |
// callback
|
2296 |
resolved++; |
2297 |
if (resolved == missing_ids.length) {
|
2298 |
callback(missing_ids) |
2299 |
} |
2300 |
} |
2301 |
if (missing_ids.length == 0) { |
2302 |
callback(missing_ids); |
2303 |
return;
|
2304 |
} |
2305 |
// start resolving missing image ids
|
2306 |
_(missing_ids).each(function(imgid){
|
2307 |
synnefo.storage.images.update_unknown_id(imgid, check); |
2308 |
}); |
2309 |
}, |
2310 |
|
2311 |
get_connectable: function() { |
2312 |
return storage.vms.filter(function(vm){ |
2313 |
return !vm.in_error_state() && !vm.is_building();
|
2314 |
}); |
2315 |
} |
2316 |
}) |
2317 |
|
2318 |
models.PublicKey = models.Model.extend({ |
2319 |
path: 'keys', |
2320 |
api_type: 'userdata', |
2321 |
detail: false, |
2322 |
model_actions: {
|
2323 |
'remove': [['name'], function() { |
2324 |
return true; |
2325 |
}] |
2326 |
}, |
2327 |
|
2328 |
get_public_key: function() { |
2329 |
return cryptico.publicKeyFromString(this.get("content")); |
2330 |
}, |
2331 |
|
2332 |
get_filename: function() { |
2333 |
return "{0}.pub".format(this.get("name")); |
2334 |
}, |
2335 |
|
2336 |
identify_type: function() { |
2337 |
try {
|
2338 |
var cont = snf.util.validatePublicKey(this.get("content")); |
2339 |
var type = cont.split(" ")[0]; |
2340 |
return synnefo.util.publicKeyTypesMap[type];
|
2341 |
} catch (err) { return false }; |
2342 |
}, |
2343 |
|
2344 |
rename: function(new_name) { |
2345 |
//this.set({'name': new_name});
|
2346 |
var self = this; |
2347 |
this.sync("update", this, { |
2348 |
critical: true, |
2349 |
data: {'name': new_name}, |
2350 |
success: _.bind(function(){ |
2351 |
snf.api.trigger("call");
|
2352 |
this.set({'name': new_name}); |
2353 |
}, this)
|
2354 |
}); |
2355 |
}, |
2356 |
|
2357 |
do_remove: function() { |
2358 |
this.actions.reset_pending();
|
2359 |
this.remove(function() { |
2360 |
synnefo.storage.keys.fetch(); |
2361 |
}); |
2362 |
} |
2363 |
}) |
2364 |
|
2365 |
models._ActionsModel = models.Model.extend({ |
2366 |
defaults: { pending: null }, |
2367 |
actions: [],
|
2368 |
status: {
|
2369 |
INACTIVE: 0, |
2370 |
PENDING: 1, |
2371 |
CALLED: 2 |
2372 |
}, |
2373 |
|
2374 |
initialize: function(attrs, opts) { |
2375 |
models._ActionsModel.__super__.initialize.call(this, attrs);
|
2376 |
this.actions = opts.actions;
|
2377 |
this.model = opts.model;
|
2378 |
this.bind("change", function() { |
2379 |
this.set({'pending': this.get_pending()}); |
2380 |
}, this);
|
2381 |
this.clear();
|
2382 |
}, |
2383 |
|
2384 |
_in_status: function(st) { |
2385 |
var actions = null; |
2386 |
_.each(this.attributes, function(status, action){ |
2387 |
if (status == st) {
|
2388 |
if (!actions) {
|
2389 |
actions = [] |
2390 |
} |
2391 |
actions.push(action); |
2392 |
} |
2393 |
}); |
2394 |
return actions;
|
2395 |
}, |
2396 |
|
2397 |
get_pending: function() { |
2398 |
return this._in_status(this.status.PENDING); |
2399 |
}, |
2400 |
|
2401 |
unset_pending_action: function(action) { |
2402 |
var data = {};
|
2403 |
data[action] = this.status.INACTIVE;
|
2404 |
this.set(data);
|
2405 |
this.trigger("unset-pending", action); |
2406 |
}, |
2407 |
|
2408 |
set_pending_action: function(action, reset_pending) { |
2409 |
reset_pending = reset_pending === undefined ? true : reset_pending; |
2410 |
var data = {};
|
2411 |
data[action] = this.status.PENDING;
|
2412 |
if (reset_pending) {
|
2413 |
this.reset_pending();
|
2414 |
} |
2415 |
this.set(data);
|
2416 |
this.trigger("set-pending", action); |
2417 |
}, |
2418 |
|
2419 |
reset_pending: function() { |
2420 |
var data = {};
|
2421 |
_.each(this.actions, function(action) { |
2422 |
data[action] = this.status.INACTIVE;
|
2423 |
}, this);
|
2424 |
this.set(data);
|
2425 |
this.trigger("reset-pending"); |
2426 |
} |
2427 |
}); |
2428 |
|
2429 |
models.PublicPool = models.Model.extend({}); |
2430 |
models.PublicPools = models.Collection.extend({ |
2431 |
model: models.PublicPool,
|
2432 |
path: 'os-floating-ip-pools', |
2433 |
api_type: 'compute', |
2434 |
noUpdate: true, |
2435 |
|
2436 |
parse: function(data) { |
2437 |
return _.map(data.floating_ip_pools, function(pool) { |
2438 |
pool.id = pool.name; |
2439 |
return pool;
|
2440 |
}); |
2441 |
} |
2442 |
}); |
2443 |
|
2444 |
models.PublicKeys = models.Collection.extend({ |
2445 |
model: models.PublicKey,
|
2446 |
details: false, |
2447 |
path: 'keys', |
2448 |
api_type: 'userdata', |
2449 |
noUpdate: true, |
2450 |
updateEntries: true, |
2451 |
|
2452 |
generate_new: function(success, error) { |
2453 |
snf.api.sync('create', undefined, { |
2454 |
url: getUrl.call(this, this.base_url) + "/generate", |
2455 |
success: success,
|
2456 |
error: error,
|
2457 |
skip_api_error: true |
2458 |
}); |
2459 |
}, |
2460 |
|
2461 |
add_crypto_key: function(key, success, error, options) { |
2462 |
var options = options || {};
|
2463 |
var m = new models.PublicKey(); |
2464 |
|
2465 |
// guess a name
|
2466 |
var name_tpl = "my generated public key"; |
2467 |
var name = name_tpl;
|
2468 |
var name_count = 1; |
2469 |
|
2470 |
while(this.filter(function(m){ return m.get("name") == name }).length > 0) { |
2471 |
name = name_tpl + " " + name_count;
|
2472 |
name_count++; |
2473 |
} |
2474 |
|
2475 |
m.set({name: name});
|
2476 |
m.set({content: key});
|
2477 |
|
2478 |
options.success = function () { return success(m) }; |
2479 |
options.errror = error; |
2480 |
options.skip_api_error = true;
|
2481 |
|
2482 |
this.create(m.attributes, options);
|
2483 |
} |
2484 |
}); |
2485 |
|
2486 |
|
2487 |
models.Quota = models.Model.extend({ |
2488 |
storage_attrs: {
|
2489 |
'project_id': ['projects', 'project'] |
2490 |
}, |
2491 |
|
2492 |
initialize: function() { |
2493 |
models.Quota.__super__.initialize.apply(this, arguments); |
2494 |
this.bind("change", this.check, this); |
2495 |
this.check();
|
2496 |
}, |
2497 |
|
2498 |
check: function() { |
2499 |
var usage, limit;
|
2500 |
usage = this.get('usage'); |
2501 |
limit = this.get('limit'); |
2502 |
if (usage >= limit) {
|
2503 |
this.trigger("available"); |
2504 |
} else {
|
2505 |
this.trigger("unavailable"); |
2506 |
} |
2507 |
}, |
2508 |
|
2509 |
increase: function(val) { |
2510 |
if (val === undefined) { val = 1}; |
2511 |
this.set({'usage': this.get('usage') + val}) |
2512 |
}, |
2513 |
|
2514 |
decrease: function(val) { |
2515 |
if (val === undefined) { val = 1}; |
2516 |
this.set({'usage': this.get('usage') - val}) |
2517 |
}, |
2518 |
|
2519 |
can_consume: function() { |
2520 |
var usage, limit;
|
2521 |
usage = this.get('usage'); |
2522 |
limit = this.get('limit'); |
2523 |
if (usage >= limit) {
|
2524 |
return false |
2525 |
} else {
|
2526 |
return true |
2527 |
} |
2528 |
}, |
2529 |
|
2530 |
is_bytes: function() { |
2531 |
return this.get('resource').get('unit') == 'bytes'; |
2532 |
}, |
2533 |
|
2534 |
get_available: function(active) { |
2535 |
suffix = '';
|
2536 |
if (active) { suffix = '_active'} |
2537 |
var value = this.get('limit'+suffix) - this.get('usage'+suffix); |
2538 |
if (active) {
|
2539 |
if (this.get('available') <= value) { |
2540 |
value = this.get('available'); |
2541 |
} |
2542 |
} |
2543 |
if (value < 0) { return value } |
2544 |
return value
|
2545 |
}, |
2546 |
|
2547 |
get_readable: function(key, active) { |
2548 |
var value;
|
2549 |
if (key == 'available') { |
2550 |
value = this.get_available(active);
|
2551 |
} else {
|
2552 |
value = this.get(key)
|
2553 |
} |
2554 |
if (value <= 0) { value = 0 } |
2555 |
// greater than max js int (assume infinite quota)
|
2556 |
if (value > Math.pow(2, 53)) { |
2557 |
return "Infinite" |
2558 |
} |
2559 |
|
2560 |
if (!this.is_bytes()) { |
2561 |
return value + ""; |
2562 |
} |
2563 |
|
2564 |
return snf.util.readablizeBytes(value);
|
2565 |
} |
2566 |
}); |
2567 |
|
2568 |
models.Quotas = models.Collection.extend({ |
2569 |
model: models.Quota,
|
2570 |
api_type: 'accounts', |
2571 |
path: 'quotas', |
2572 |
supportIncUpdates: false, |
2573 |
|
2574 |
required_quota: {
|
2575 |
'vm': {
|
2576 |
'cyclades.vm': 1, |
2577 |
'cyclades.ram': 1, |
2578 |
'cyclades.cpu': 1, |
2579 |
'cyclades.disk': 1 |
2580 |
}, |
2581 |
'network': {
|
2582 |
'cyclades.network.private': 1 |
2583 |
}, |
2584 |
'ip': {
|
2585 |
'cyclades.floating_ip': 1 |
2586 |
} |
2587 |
}, |
2588 |
|
2589 |
parse: function(resp) { |
2590 |
var parsed = [];
|
2591 |
_.each(resp, function(resources, uuid) {
|
2592 |
parsed = _.union(parsed, _.map(resources, function(value, key) {
|
2593 |
var quota_available = value.limit - value.usage || 0; |
2594 |
var total_available = quota_available;
|
2595 |
var project_available = value.project_limit - value.project_usage || 0; |
2596 |
var limit = value.limit;
|
2597 |
var usage = value.usage;
|
2598 |
|
2599 |
var available = quota_available;
|
2600 |
var total_available = available;
|
2601 |
|
2602 |
// priority to project limits
|
2603 |
if (project_available < available ) {
|
2604 |
available = project_available; |
2605 |
limit = value.project_limit; |
2606 |
usage = value.project_usage; |
2607 |
total_available = available; |
2608 |
} |
2609 |
|
2610 |
var available_active = available;
|
2611 |
|
2612 |
// corresponding total quota
|
2613 |
var keysplit = key.split("."); |
2614 |
var last_part = keysplit.pop();
|
2615 |
var activekey = keysplit.join(".") + "." + "total_" + last_part; |
2616 |
var total = resp[uuid][activekey];
|
2617 |
if (total) {
|
2618 |
total_available = total.limit - total.usage; |
2619 |
var total_project_available = total.project_limit - total.project_usage;
|
2620 |
var total_limit = total.limit;
|
2621 |
var total_usage = total.usage;
|
2622 |
if (total_project_available < total_available) {
|
2623 |
total_available = total_project_available; |
2624 |
total_limit = total.project_limit; |
2625 |
total_usage = total.project_usage; |
2626 |
} |
2627 |
if (total_available < available) {
|
2628 |
available = total_available; |
2629 |
limit = total_limit; |
2630 |
usage = total_usage; |
2631 |
} |
2632 |
} |
2633 |
|
2634 |
var limit_active = limit;
|
2635 |
var usage_active = usage;
|
2636 |
|
2637 |
var id = uuid + ":" + key; |
2638 |
return _.extend(value, {
|
2639 |
'name': key,
|
2640 |
'id': id,
|
2641 |
'available': available,
|
2642 |
'available_active': available_active,
|
2643 |
'total_available': total_available,
|
2644 |
'limit_active': limit_active,
|
2645 |
'project_id': uuid,
|
2646 |
'usage_active': usage_active,
|
2647 |
'resource': snf.storage.resources.get(key)
|
2648 |
}); |
2649 |
})); |
2650 |
}); |
2651 |
return parsed;
|
2652 |
}, |
2653 |
|
2654 |
project_key: function(project, key) { |
2655 |
return project + ":" + key; |
2656 |
}, |
2657 |
|
2658 |
get_by_id: function(k) { |
2659 |
return this.filter(function(q) { return q.get('name') == k})[0] |
2660 |
}, |
2661 |
|
2662 |
get_available_for_vm: function(options) { |
2663 |
var quotas = this; |
2664 |
var key = 'available'; |
2665 |
var available_quota = {};
|
2666 |
_.each(['cyclades.ram', 'cyclades.cpu', 'cyclades.disk'], |
2667 |
function (key) {
|
2668 |
var value = quotas.get(key).get_available(true); |
2669 |
available_quota[key.replace('cyclades.', '')] = value; |
2670 |
}); |
2671 |
return available_quota;
|
2672 |
}, |
2673 |
|
2674 |
can_create: function(type) { |
2675 |
return this.get_available_projects(this.required_quota[type]).length > 0; |
2676 |
}, |
2677 |
|
2678 |
get_available_projects: function(quotas) { |
2679 |
return synnefo.storage.projects.filter(function(project) { |
2680 |
return project.quotas.can_fit(quotas);
|
2681 |
}); |
2682 |
}, |
2683 |
|
2684 |
can_fit: function(quotas, total, _issues) { |
2685 |
var issues = [];
|
2686 |
if (total === undefined) { total = false } |
2687 |
_.each(quotas, function(value, key) {
|
2688 |
var q = this.get(key); |
2689 |
if (!q) { issues.push(key); return } |
2690 |
var quota = q.get('available_active'); |
2691 |
if (total) {
|
2692 |
quota = q.get('available');
|
2693 |
} |
2694 |
if (quota < value) {
|
2695 |
issues.push(key); |
2696 |
} |
2697 |
}, this);
|
2698 |
if (_issues) { return issues } |
2699 |
return issues.length === 0; |
2700 |
} |
2701 |
}) |
2702 |
|
2703 |
models.Resource = models.Model.extend({ |
2704 |
api_type: 'accounts', |
2705 |
path: 'resources' |
2706 |
}); |
2707 |
|
2708 |
models.Resources = models.Collection.extend({ |
2709 |
api_type: 'accounts', |
2710 |
path: 'resources', |
2711 |
model: models.Network,
|
2712 |
display_name_map: {
|
2713 |
'cyclades.vm': 'Machines', |
2714 |
'cyclades.ram': 'Memory size', |
2715 |
'cyclades.total_ram': 'Memory size (total)', |
2716 |
'cyclades.cpu': 'CPUs', |
2717 |
'cyclades.total_cpu': 'CPUs (total)', |
2718 |
'cyclades.floating_ip': 'IP Addresses', |
2719 |
'pithos.diskpace': 'Storage space', |
2720 |
'cyclades.disk': 'Disk size', |
2721 |
'cyclades.network.private': 'Private networks' |
2722 |
}, |
2723 |
|
2724 |
parse: function(resp) { |
2725 |
return _.map(resp, function(value, key) { |
2726 |
var display_name = this.display_name_map[key] || key; |
2727 |
return _.extend(value, {
|
2728 |
'name': key,
|
2729 |
'id': key,
|
2730 |
'display_name': display_name
|
2731 |
}); |
2732 |
}, this);
|
2733 |
} |
2734 |
}); |
2735 |
|
2736 |
models.ProjectQuotas = models.Quotas.extend({}) |
2737 |
_.extend(models.ProjectQuotas.prototype, Backbone.FilteredCollection.prototype); |
2738 |
models.ProjectQuotas.prototype.get = function(key) { |
2739 |
key = this.project_id + ":" + key; |
2740 |
return models.ProjectQuotas.__super__.get.call(this, key); |
2741 |
} |
2742 |
|
2743 |
models.Project = models.Model.extend({ |
2744 |
api_type: 'accounts', |
2745 |
path: 'projects', |
2746 |
|
2747 |
initialize: function() { |
2748 |
var self = this; |
2749 |
this.quotas = new models.ProjectQuotas(undefined, { |
2750 |
collection: synnefo.storage.quotas,
|
2751 |
collectionFilter: function(m) { |
2752 |
return self.id == m.get('project_id') |
2753 |
}}); |
2754 |
this.quotas.bind('change', function() { |
2755 |
self.trigger('change:_quotas');
|
2756 |
}); |
2757 |
this.quotas.project_id = this.id; |
2758 |
models.Project.__super__.initialize.apply(this, arguments); |
2759 |
} |
2760 |
}); |
2761 |
|
2762 |
models.Projects = models.Collection.extend({ |
2763 |
api_type: 'accounts', |
2764 |
path: 'projects', |
2765 |
model: models.Project,
|
2766 |
supportIncUpdates: false, |
2767 |
user_project_uuid: null, |
2768 |
|
2769 |
url: function() { |
2770 |
var args = Array.prototype.splice.call(arguments, 0); |
2771 |
var url = models.Projects.__super__.url.apply(this, args); |
2772 |
return url + "?mode=member"; |
2773 |
}, |
2774 |
|
2775 |
parse: function(resp) { |
2776 |
_.each(resp, function(project){
|
2777 |
if (project.base_project) {
|
2778 |
this.user_project_uuid = project.id;
|
2779 |
} |
2780 |
if (project.id == synnefo.user.get_username()) {
|
2781 |
project.name = "User project"
|
2782 |
} |
2783 |
}, this);
|
2784 |
return resp;
|
2785 |
}, |
2786 |
|
2787 |
get_user_project: function() { |
2788 |
return this.get(synnefo.user.current_username); |
2789 |
}, |
2790 |
|
2791 |
comparator: function(project) { |
2792 |
if (project.get('base_project')) { return -100 } |
2793 |
return project.get('name'); |
2794 |
} |
2795 |
}); |
2796 |
|
2797 |
// storage initialization
|
2798 |
snf.storage.images = new models.Images();
|
2799 |
snf.storage.flavors = new models.Flavors();
|
2800 |
snf.storage.vms = new models.VMS();
|
2801 |
snf.storage.keys = new models.PublicKeys();
|
2802 |
snf.storage.resources = new models.Resources();
|
2803 |
snf.storage.quotas = new models.Quotas();
|
2804 |
snf.storage.projects = new models.Projects();
|
2805 |
snf.storage.public_pools = new models.PublicPools();
|
2806 |
|
2807 |
})(this);
|