Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / models.js @ fc6f43c3

History | View | Annotate | Download (76.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
        },
117

    
118
        remove: function(complete, error, success) {
119
            this.api_call(this.api_path(), "delete", undefined, complete, error, success);
120
        },
121

    
122
        changedKeys: function() {
123
            return _.keys(this.changedAttributes() || {});
124
        },
125
            
126
        // return list of changed attributes that included in passed list
127
        // argument
128
        getKeysChanged: function(keys) {
129
            return _.intersection(keys, this.changedKeys());
130
        },
131
        
132
        // boolean check of keys changed
133
        keysChanged: function(keys) {
134
            return this.getKeysChanged(keys).length > 0;
135
        },
136

    
137
        // check if any of the passed attribues has changed
138
        hasOnlyChange: function(keys) {
139
            var ret = false;
140
            _.each(keys, _.bind(function(key) {
141
                if (this.changedKeys().length == 1 && this.changedKeys().indexOf(key) > -1) { ret = true};
142
            }, this));
143
            return ret;
144
        }
145

    
146
    })
147
    
148
    // Base object for all our model collections
149
    models.Collection = bb.Collection.extend({
150
        sync: snf.api.sync,
151
        api: snf.api,
152
        api_type: 'compute',
153
        supportIncUpdates: true,
154

    
155
        initialize: function() {
156
            models.Collection.__super__.initialize.apply(this, arguments);
157
            this.api_call = _.bind(this.api.call, this);
158
        },
159

    
160
        url: function(options, method) {
161
            return getUrl.call(this, this.base_url) + (
162
                    options.details || this.details && method != 'create' ? '/detail' : '');
163
        },
164

    
165
        fetch: function(options) {
166
            if (!options) { options = {} };
167
            // default to update
168
            if (!this.noUpdate) {
169
                if (options.update === undefined) { options.update = true };
170
                if (!options.removeMissing && options.refresh) { options.removeMissing = true };
171
            } else {
172
                if (options.refresh === undefined) {
173
                    options.refresh = true;
174
                }
175
            }
176
            // custom event foreach fetch
177
            return bb.Collection.prototype.fetch.call(this, options)
178
        },
179

    
180
        create: function(model, options) {
181
            var coll = this;
182
            options || (options = {});
183
            model = this._prepareModel(model, options);
184
            if (!model) return false;
185
            var success = options.success;
186
            options.success = function(nextModel, resp, xhr) {
187
                if (success) success(nextModel, resp, xhr);
188
            };
189
            model.save(null, options);
190
            return model;
191
        },
192

    
193
        get_fetcher: function(interval, increase, fast, increase_after_calls, max, initial_call, params) {
194
            var fetch_params = params || {};
195
            var handler_options = {};
196

    
197
            fetch_params.skips_timeouts = true;
198
            handler_options.interval = interval;
199
            handler_options.increase = increase;
200
            handler_options.fast = fast;
201
            handler_options.increase_after_calls = increase_after_calls;
202
            handler_options.max= max;
203
            handler_options.id = "collection id";
204

    
205
            var last_ajax = undefined;
206
            var callback = _.bind(function() {
207
                // clone to avoid referenced objects
208
                var params = _.clone(fetch_params);
209
                updater._ajax = last_ajax;
210
                
211
                // wait for previous request to finish
212
                if (last_ajax && last_ajax.readyState < 4 && last_ajax.statusText != "timeout") {
213
                    // opera readystate for 304 responses is 0
214
                    if (!($.browser.opera && last_ajax.readyState == 0 && last_ajax.status == 304)) {
215
                        return;
216
                    }
217
                }
218
                
219
                last_ajax = this.fetch(params);
220
            }, this);
221
            handler_options.callback = callback;
222

    
223
            var updater = new snf.api.updateHandler(_.clone(_.extend(handler_options, fetch_params)));
224
            snf.api.bind("call", _.throttle(_.bind(function(){ updater.faster(true)}, this)), 1000);
225
            return updater;
226
        }
227
    });
228
    
229
    // Image model
230
    models.Image = models.Model.extend({
231
        path: 'images',
232

    
233
        get_size: function() {
234
            return parseInt(this.get('metadata') ? this.get('metadata').values.size : -1)
235
        },
236

    
237
        get_description: function(escape) {
238
            if (escape == undefined) { escape = true };
239
            if (escape) { return this.escape('description') || "No description available"}
240
            return this.get('description') || "No description available."
241
        },
242

    
243
        get_meta: function(key) {
244
            if (this.get('metadata') && this.get('metadata').values) {
245
                if (!this.get('metadata').values[key]) { return null }
246
                return _.escape(this.get('metadata').values[key]);
247
            } else {
248
                return null;
249
            }
250
        },
251

    
252
        get_meta_keys: function() {
253
            if (this.get('metadata') && this.get('metadata').values) {
254
                return _.keys(this.get('metadata').values);
255
            } else {
256
                return [];
257
            }
258
        },
259

    
260
        get_owner: function() {
261
            return this.get('owner') || _.keys(synnefo.config.system_images_owners)[0];
262
        },
263

    
264
        is_system_image: function() {
265
          var owner = this.get_owner();
266
          return _.include(_.keys(synnefo.config.system_images_owners), owner)
267
        },
268

    
269
        owned_by: function(user) {
270
          if (!user) { user = synnefo.user }
271
          return user.username == this.get_owner();
272
        },
273

    
274
        display_owner: function() {
275
            var owner = this.get_owner();
276
            if (_.include(_.keys(synnefo.config.system_images_owners), owner)) {
277
                return synnefo.config.system_images_owners[owner];
278
            } else {
279
                return owner;
280
            }
281
        },
282
    
283
        get_readable_size: function() {
284
            if (this.is_deleted()) {
285
                return synnefo.config.image_deleted_size_title || '(none)';
286
            }
287
            return this.get_size() > 0 ? util.readablizeBytes(this.get_size() * 1024 * 1024) : '(none)';
288
        },
289

    
290
        get_os: function() {
291
            return this.get_meta('OS');
292
        },
293

    
294
        get_gui: function() {
295
            return this.get_meta('GUI');
296
        },
297

    
298
        get_created_user: function() {
299
            return synnefo.config.os_created_users[this.get_os()] || "root";
300
        },
301

    
302
        get_sort_order: function() {
303
            return parseInt(this.get('metadata') ? this.get('metadata').values.sortorder : -1)
304
        },
305

    
306
        get_vm: function() {
307
            var vm_id = this.get("serverRef");
308
            var vm = undefined;
309
            vm = storage.vms.get(vm_id);
310
            return vm;
311
        },
312

    
313
        is_public: function() {
314
            return this.get('is_public') || true;
315
        },
316

    
317
        is_deleted: function() {
318
            return this.get('status') == "DELETED"
319
        },
320
        
321
        ssh_keys_path: function() {
322
            prepend = '';
323
            if (this.get_created_user() != 'root') {
324
                prepend = '/home'
325
            }
326
            return '{1}/{0}/.ssh/authorized_keys'.format(this.get_created_user(), prepend);
327
        },
328

    
329
        _supports_ssh: function() {
330
            if (synnefo.config.support_ssh_os_list.indexOf(this.get_os()) > -1) {
331
                return true;
332
            }
333
            if (this.get_meta('osfamily') == 'linux') {
334
              return true;
335
            }
336
            return false;
337
        },
338

    
339
        supports: function(feature) {
340
            if (feature == "ssh") {
341
                return this._supports_ssh()
342
            }
343
            return false;
344
        },
345

    
346
        personality_data_for_keys: function(keys) {
347
            contents = '';
348
            _.each(keys, function(key){
349
                contents = contents + key.get("content") + "\n"
350
            });
351
            contents = $.base64.encode(contents);
352

    
353
            return {
354
                path: this.ssh_keys_path(),
355
                contents: contents
356
            }
357
        }
358
    });
359

    
360
    // Flavor model
361
    models.Flavor = models.Model.extend({
362
        path: 'flavors',
363

    
364
        details_string: function() {
365
            return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk'));
366
        },
367

    
368
        get_disk_size: function() {
369
            return parseInt(this.get("disk") * 1000)
370
        },
371

    
372
        get_disk_template_info: function() {
373
            var info = snf.config.flavors_disk_templates_info[this.get("disk_template")];
374
            if (!info) {
375
                info = { name: this.get("disk_template"), description:'' };
376
            }
377
            return info
378
        }
379

    
380
    });
381
    
382
    models.ParamsList = function(){this.initialize.apply(this, arguments)};
383
    _.extend(models.ParamsList.prototype, bb.Events, {
384

    
385
        initialize: function(parent, param_name) {
386
            this.parent = parent;
387
            this.actions = {};
388
            this.param_name = param_name;
389
            this.length = 0;
390
        },
391
        
392
        has_action: function(action) {
393
            return this.actions[action] ? true : false;
394
        },
395
            
396
        _parse_params: function(arguments) {
397
            if (arguments.length <= 1) {
398
                return [];
399
            }
400

    
401
            var args = _.toArray(arguments);
402
            return args.splice(1);
403
        },
404

    
405
        contains: function(action, params) {
406
            params = this._parse_params(arguments);
407
            var has_action = this.has_action(action);
408
            if (!has_action) { return false };
409

    
410
            var paramsEqual = false;
411
            _.each(this.actions[action], function(action_params) {
412
                if (_.isEqual(action_params, params)) {
413
                    paramsEqual = true;
414
                }
415
            });
416
                
417
            return paramsEqual;
418
        },
419
        
420
        is_empty: function() {
421
            return _.isEmpty(this.actions);
422
        },
423

    
424
        add: function(action, params) {
425
            params = this._parse_params(arguments);
426
            if (this.contains.apply(this, arguments)) { return this };
427
            var isnew = false
428
            if (!this.has_action(action)) {
429
                this.actions[action] = [];
430
                isnew = true;
431
            };
432

    
433
            this.actions[action].push(params);
434
            this.parent.trigger("change:" + this.param_name, this.parent, this);
435
            if (isnew) {
436
                this.trigger("add", action, params);
437
            } else {
438
                this.trigger("change", action, params);
439
            }
440
            return this;
441
        },
442
        
443
        remove_all: function(action) {
444
            if (this.has_action(action)) {
445
                delete this.actions[action];
446
                this.parent.trigger("change:" + this.param_name, this.parent, this);
447
                this.trigger("remove", action);
448
            }
449
            return this;
450
        },
451

    
452
        reset: function() {
453
            this.actions = {};
454
            this.parent.trigger("change:" + this.param_name, this.parent, this);
455
            this.trigger("reset");
456
            this.trigger("remove");
457
        },
458

    
459
        remove: function(action, params) {
460
            params = this._parse_params(arguments);
461
            if (!this.has_action(action)) { return this };
462
            var index = -1;
463
            _.each(this.actions[action], _.bind(function(action_params) {
464
                if (_.isEqual(action_params, params)) {
465
                    index = this.actions[action].indexOf(action_params);
466
                }
467
            }, this));
468
            
469
            if (index > -1) {
470
                this.actions[action].splice(index, 1);
471
                if (_.isEmpty(this.actions[action])) {
472
                    delete this.actions[action];
473
                }
474
                this.parent.trigger("change:" + this.param_name, this.parent, this);
475
                this.trigger("remove", action, params);
476
            }
477
        }
478

    
479
    });
480

    
481
    // Image model
482
    models.Network = models.Model.extend({
483
        path: 'networks',
484
        has_status: true,
485
        defaults: {'connecting':0},
486
        
487
        initialize: function() {
488
            var ret = models.Network.__super__.initialize.apply(this, arguments);
489
            this.set({"actions": new models.ParamsList(this, "actions")});
490
            this.update_state();
491
            this.bind("change:nics", _.bind(synnefo.storage.nics.update_net_nics, synnefo.storage.nics));
492
            this.bind("change:status", _.bind(this.update_state, this));
493
            return ret;
494
        },
495
        
496
        is_deleted: function() {
497
          return this.get('status') == 'DELETED';
498
        },
499

    
500
        toJSON: function() {
501
            var attrs = _.clone(this.attributes);
502
            attrs.actions = _.clone(this.get("actions").actions);
503
            return attrs;
504
        },
505
        
506
        set_state: function(val) {
507
            if (val == "PENDING" && this.get("state") == "DESTORY") {
508
                return "DESTROY";
509
            }
510
            return val;
511
        },
512

    
513
        update_state: function() {
514
            if (this.get("connecting") > 0) {
515
                this.set({state: "CONNECTING"});
516
                return
517
            }
518
            
519
            if (this.get_nics(function(nic){ return nic.get("removing") == 1}).length > 0) {
520
                this.set({state: "DISCONNECTING"});
521
                return
522
            }   
523
            
524
            if (this.contains_firewalling_nics() > 0) {
525
                this.set({state: "FIREWALLING"});
526
                return
527
            }   
528
            
529
            if (this.get("state") == "DESTROY") { 
530
                this.set({"destroyed":1});
531
            }
532
            
533
            this.set({state:this.get('status')});
534
        },
535

    
536
        is_public: function() {
537
            return this.get("public");
538
        },
539

    
540
        decrease_connecting: function() {
541
            var conn = this.get("connecting");
542
            if (!conn) { conn = 0 };
543
            if (conn > 0) {
544
                conn--;
545
            }
546
            this.set({"connecting": conn});
547
            this.update_state();
548
        },
549

    
550
        increase_connecting: function() {
551
            var conn = this.get("connecting");
552
            if (!conn) { conn = 0 };
553
            conn++;
554
            this.set({"connecting": conn});
555
            this.update_state();
556
        },
557

    
558
        connected_to: function(vm) {
559
            return this.get('linked_to').indexOf(""+vm.id) > -1;
560
        },
561

    
562
        connected_with_nic_id: function(nic_id) {
563
            return _.keys(this.get('nics')).indexOf(nic_id) > -1;
564
        },
565

    
566
        get_nics: function(filter) {
567
            var nics = synnefo.storage.nics.filter(function(nic) {
568
                return nic.get('network_id') == this.id;
569
            }, this);
570

    
571
            if (filter) {
572
                return _.filter(nics, filter);
573
            }
574
            return nics;
575
        },
576

    
577
        contains_firewalling_nics: function() {
578
            return this.get_nics(function(n){return n.get('pending_firewall')}).length
579
        },
580

    
581
        call: function(action, params, success, error) {
582
            if (action == "destroy") {
583
                var previous_state = this.get('state');
584
                var previous_status = this.get('status');
585

    
586
                this.set({state:"DESTROY"});
587

    
588
                var _success = _.bind(function() {
589
                    if (success) { success() };
590
                }, this);
591
                var _error = _.bind(function() {
592
                    this.set({state: previous_state, status: previous_status})
593
                    if (error) { error() };
594
                }, this);
595

    
596
                this.remove(undefined, _error, _success);
597
            }
598
            
599
            if (action == "disconnect") {
600
                if (this.get("state") == "DESTROY") {
601
                    return;
602
                }
603
                
604
                _.each(params, _.bind(function(nic_id) {
605
                    var nic = snf.storage.nics.get(nic_id);
606
                    this.get("actions").remove("disconnect", nic_id);
607
                    if (nic) {
608
                        this.remove_nic(nic, success, error);
609
                    }
610
                }, this));
611
            }
612
        },
613

    
614
        add_vm: function (vm, callback, error, options) {
615
            var payload = {add:{serverRef:"" + vm.id}};
616
            payload._options = options || {};
617
            return this.api_call(this.api_path() + "/action", "create", 
618
                                 payload,
619
                                 undefined,
620
                                 error,
621
                                 _.bind(function(){
622
                                     //this.vms.add_pending(vm.id);
623
                                     this.increase_connecting();
624
                                     if (callback) {callback()}
625
                                 },this), error);
626
        },
627

    
628
        remove_nic: function (nic, callback, error, options) {
629
            var payload = {remove:{attachment:"" + nic.get("attachment_id")}};
630
            payload._options = options || {};
631
            return this.api_call(this.api_path() + "/action", "create", 
632
                                 payload,
633
                                 undefined,
634
                                 error,
635
                                 _.bind(function(){
636
                                     nic.set({"removing": 1});
637
                                     nic.get_network().update_state();
638
                                     //this.vms.add_pending_for_remove(vm.id);
639
                                     if (callback) {callback()}
640
                                 },this), error);
641
        },
642

    
643
        rename: function(name, callback) {
644
            return this.api_call(this.api_path(), "update", {
645
                network:{name:name}, 
646
                _options:{
647
                    critical: false, 
648
                    error_params:{
649
                        title: "Network action failed",
650
                        ns: "Networks",
651
                        extra_details: {"Network id": this.id}
652
                    }
653
                }}, callback);
654
        },
655

    
656
        get_connectable_vms: function() {
657
            return storage.vms.filter(function(vm){
658
                return !vm.in_error_state() && !vm.is_building();
659
            })
660
        },
661

    
662
        state_message: function() {
663
            if (this.get("state") == "ACTIVE" && !this.is_public()) {
664
                if (this.get("cidr") && this.get("dhcp") == true) {
665
                    return this.get("cidr");
666
                } else {
667
                    return "Private network";
668
                }
669
            }
670
            if (this.get("state") == "ACTIVE" && this.is_public()) {
671
                  return "Public network";
672
            }
673

    
674
            return models.Network.STATES[this.get("state")];
675
        },
676

    
677
        in_progress: function() {
678
            return models.Network.STATES_TRANSITIONS[this.get("state")] != undefined;
679
        },
680

    
681
        do_all_pending_actions: function(success, error) {
682
          var params, actions, action_params;
683
          actions = _.clone(this.get("actions").actions);
684
            _.each(actions, _.bind(function(params, action) {
685
                action_params = _.map(actions[action], function(a){ return _.clone(a)});
686
                _.each(action_params, _.bind(function(params) {
687
                    this.call(action, params, success, error);
688
                }, this));
689
            }, this));
690
            this.get("actions").reset();
691
        }
692
    });
693
    
694
    models.Network.STATES = {
695
        'ACTIVE': 'Private network',
696
        'CONNECTING': 'Connecting...',
697
        'DISCONNECTING': 'Disconnecting...',
698
        'FIREWALLING': 'Firewall update...',
699
        'DESTROY': 'Destroying...',
700
        'PENDING': 'Pending...',
701
        'ERROR': 'Error'
702
    }
703

    
704
    models.Network.STATES_TRANSITIONS = {
705
        'CONNECTING': ['ACTIVE'],
706
        'DISCONNECTING': ['ACTIVE'],
707
        'PENDING': ['ACTIVE'],
708
        'FIREWALLING': ['ACTIVE']
709
    }
710

    
711
    // Virtualmachine model
712
    models.VM = models.Model.extend({
713

    
714
        path: 'servers',
715
        has_status: true,
716
        initialize: function(params) {
717
            
718
            this.pending_firewalls = {};
719
            
720
            models.VM.__super__.initialize.apply(this, arguments);
721

    
722
            this.set({state: params.status || "ERROR"});
723
            this.log = new snf.logging.logger("VM " + this.id);
724
            this.pending_action = undefined;
725
            
726
            // init stats parameter
727
            this.set({'stats': undefined}, {silent: true});
728
            // defaults to not update the stats
729
            // each view should handle this vm attribute 
730
            // depending on if it displays stat images or not
731
            this.do_update_stats = false;
732
            
733
            // interval time
734
            // this will dynamicaly change if the server responds that
735
            // images get refreshed on different intervals
736
            this.stats_update_interval = synnefo.config.STATS_INTERVAL || 5000;
737
            this.stats_available = false;
738

    
739
            // initialize interval
740
            this.init_stats_intervals(this.stats_update_interval);
741
            
742
            // handle progress message on instance change
743
            this.bind("change", _.bind(this.update_status_message, this));
744
            // force update of progress message
745
            this.update_status_message(true);
746
            
747
            // default values
748
            this.bind("change:state", _.bind(function(){
749
                if (this.state() == "DESTROY") { 
750
                    this.handle_destroy() 
751
                }
752
            }, this));
753

    
754
            this.bind("change:nics", _.bind(synnefo.storage.nics.update_vm_nics, synnefo.storage.nics));
755
        },
756

    
757
        status: function(st) {
758
            if (!st) { return this.get("status")}
759
            return this.set({status:st});
760
        },
761

    
762
        set_status: function(st) {
763
            var new_state = this.state_for_api_status(st);
764
            var transition = false;
765

    
766
            if (this.state() != new_state) {
767
                if (models.VM.STATES_TRANSITIONS[this.state()]) {
768
                    transition = this.state();
769
                }
770
            }
771
            
772
            // call it silently to avoid double change trigger
773
            this.set({'state': this.state_for_api_status(st)}, {silent: true});
774
            
775
            // trigger transition
776
            if (transition && models.VM.TRANSITION_STATES.indexOf(new_state) == -1) { 
777
                this.trigger("transition", {from:transition, to:new_state}) 
778
            };
779
            return st;
780
        },
781
            
782
        get_diagnostics: function(success) {
783
            this.__make_api_call(this.get_diagnostics_url(),
784
                                 "read", // create so that sync later uses POST to make the call
785
                                 null, // payload
786
                                 function(data) {
787
                                     success(data);
788
                                 },  
789
                                 null, 'diagnostics');
790
        },
791

    
792
        has_diagnostics: function() {
793
            return this.get("diagnostics") && this.get("diagnostics").length;
794
        },
795

    
796
        get_progress_info: function() {
797
            // details about progress message
798
            // contains a list of diagnostic messages
799
            return this.get("status_messages");
800
        },
801

    
802
        get_status_message: function() {
803
            return this.get('status_message');
804
        },
805
        
806
        // extract status message from diagnostics
807
        status_message_from_diagnostics: function(diagnostics) {
808
            var valid_sources_map = synnefo.config.diagnostics_status_messages_map;
809
            var valid_sources = valid_sources_map[this.get('status')];
810
            if (!valid_sources) { return null };
811
            
812
            // filter messsages based on diagnostic source
813
            var messages = _.filter(diagnostics, function(diag) {
814
                return valid_sources.indexOf(diag.source) > -1;
815
            });
816

    
817
            var msg = messages[0];
818
            if (msg) {
819
              var message = msg.message;
820
              var message_tpl = snf.config.diagnostic_messages_tpls[msg.source];
821

    
822
              if (message_tpl) {
823
                  message = message_tpl.replace('MESSAGE', msg.message);
824
              }
825
              return message;
826
            }
827
            
828
            // no message to display, but vm in build state, display
829
            // finalizing message.
830
            if (this.is_building() == 'BUILD') {
831
                return synnefo.config.BUILDING_MESSAGES['FINAL'];
832
            }
833
            return null;
834
        },
835

    
836
        update_status_message: function(force) {
837
            // update only if one of the specified attributes has changed
838
            if (
839
              !this.keysChanged(['diagnostics', 'progress', 'status', 'state'])
840
                && !force
841
            ) { return };
842
            
843
            // if user requested to destroy the vm set the appropriate 
844
            // message.
845
            if (this.get('state') == "DESTROY") { 
846
                message = "Terminating..."
847
                this.set({status_message: message})
848
                return;
849
            }
850
            
851
            // set error message, if vm has diagnostic message display it as
852
            // progress message
853
            if (this.in_error_state()) {
854
                var d = this.get('diagnostics');
855
                if (d && d.length) {
856
                    var message = this.status_message_from_diagnostics(d);
857
                    this.set({status_message: message});
858
                } else {
859
                    this.set({status_message: null});
860
                }
861
                return;
862
            }
863
            
864
            // identify building status message
865
            if (this.is_building()) {
866
                var self = this;
867
                var success = function(msg) {
868
                    self.set({status_message: msg});
869
                }
870
                this.get_building_status_message(success);
871
                return;
872
            }
873

    
874
            this.set({status_message:null});
875
        },
876
            
877
        // get building status message. Asynchronous function since it requires
878
        // access to vm image.
879
        get_building_status_message: function(callback) {
880
            // no progress is set, vm is in initial build status
881
            var progress = this.get("progress");
882
            if (progress == 0 || !progress) {
883
                return callback(BUILDING_MESSAGES['INIT']);
884
            }
885
            
886
            // vm has copy progress, display copy percentage
887
            if (progress > 0 && progress <= 99) {
888
                this.get_copy_details(true, undefined, _.bind(
889
                    function(details){
890
                        callback(BUILDING_MESSAGES['COPY'].format(details.copy, 
891
                                                           details.size, 
892
                                                           details.progress));
893
                }, this));
894
                return;
895
            }
896

    
897
            // copy finished display FINAL message or identify status message
898
            // from diagnostics.
899
            if (progress >= 100) {
900
                if (!this.has_diagnostics()) {
901
                        callback(BUILDING_MESSAGES['FINAL']);
902
                } else {
903
                        var d = this.get("diagnostics");
904
                        var msg = this.status_message_from_diagnostics(d);
905
                        if (msg) {
906
                              callback(msg);
907
                        }
908
                }
909
            }
910
        },
911

    
912
        get_copy_details: function(human, image, callback) {
913
            var human = human || false;
914
            var image = image || this.get_image(_.bind(function(image){
915
                var progress = this.get('progress');
916
                var size = image.get_size();
917
                var size_copied = (size * progress / 100).toFixed(2);
918
                
919
                if (human) {
920
                    size = util.readablizeBytes(size*1024*1024);
921
                    size_copied = util.readablizeBytes(size_copied*1024*1024);
922
                }
923

    
924
                callback({'progress': progress, 'size': size, 'copy': size_copied})
925
            }, this));
926
        },
927

    
928
        start_stats_update: function(force_if_empty) {
929
            var prev_state = this.do_update_stats;
930

    
931
            this.do_update_stats = true;
932
            
933
            // fetcher initialized ??
934
            if (!this.stats_fetcher) {
935
                this.init_stats_intervals();
936
            }
937

    
938

    
939
            // fetcher running ???
940
            if (!this.stats_fetcher.running || !prev_state) {
941
                this.stats_fetcher.start();
942
            }
943

    
944
            if (force_if_empty && this.get("stats") == undefined) {
945
                this.update_stats(true);
946
            }
947
        },
948

    
949
        stop_stats_update: function(stop_calls) {
950
            this.do_update_stats = false;
951

    
952
            if (stop_calls) {
953
                this.stats_fetcher.stop();
954
            }
955
        },
956

    
957
        // clear and reinitialize update interval
958
        init_stats_intervals: function (interval) {
959
            this.stats_fetcher = this.get_stats_fetcher(this.stats_update_interval);
960
            this.stats_fetcher.start();
961
        },
962
        
963
        get_stats_fetcher: function(timeout) {
964
            var cb = _.bind(function(data){
965
                this.update_stats();
966
            }, this);
967
            var fetcher = new snf.api.updateHandler({'callback': cb, interval: timeout, id:'stats'});
968
            return fetcher;
969
        },
970

    
971
        // do the api call
972
        update_stats: function(force) {
973
            // do not update stats if flag not set
974
            if ((!this.do_update_stats && !force) || this.updating_stats) {
975
                return;
976
            }
977

    
978
            // make the api call, execute handle_stats_update on sucess
979
            // TODO: onError handler ???
980
            stats_url = this.url() + "/stats";
981
            this.updating_stats = true;
982
            this.sync("read", this, {
983
                handles_error:true, 
984
                url: stats_url, 
985
                refresh:true, 
986
                success: _.bind(this.handle_stats_update, this),
987
                error: _.bind(this.handle_stats_error, this),
988
                complete: _.bind(function(){this.updating_stats = false;}, this),
989
                critical: false,
990
                log_error: false,
991
                skips_timeouts: true
992
            });
993
        },
994

    
995
        get_stats_image: function(stat, type) {
996
        },
997
        
998
        _set_stats: function(stats) {
999
            var silent = silent === undefined ? false : silent;
1000
            // unavailable stats while building
1001
            if (this.get("status") == "BUILD") { 
1002
                this.stats_available = false;
1003
            } else { this.stats_available = true; }
1004

    
1005
            if (this.get("status") == "DESTROY") { this.stats_available = false; }
1006
            
1007
            this.set({stats: stats}, {silent:true});
1008
            this.trigger("stats:update", stats);
1009
        },
1010

    
1011
        unbind: function() {
1012
            models.VM.__super__.unbind.apply(this, arguments);
1013
        },
1014

    
1015
        handle_stats_error: function() {
1016
            stats = {};
1017
            _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
1018
                stats[k] = false;
1019
            });
1020

    
1021
            this.set({'stats': stats});
1022
        },
1023

    
1024
        // this method gets executed after a successful vm stats api call
1025
        handle_stats_update: function(data) {
1026
            var self = this;
1027
            // avoid browser caching
1028
            
1029
            if (data.stats && _.size(data.stats) > 0) {
1030
                var ts = $.now();
1031
                var stats = data.stats;
1032
                var images_loaded = 0;
1033
                var images = {};
1034

    
1035
                function check_images_loaded() {
1036
                    images_loaded++;
1037

    
1038
                    if (images_loaded == 4) {
1039
                        self._set_stats(images);
1040
                    }
1041
                }
1042
                _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
1043
                    
1044
                    stats[k] = stats[k] + "?_=" + ts;
1045
                    
1046
                    var stat = k.slice(0,3);
1047
                    var type = k.slice(3,6) == "Bar" ? "bar" : "time";
1048
                    var img = $("<img />");
1049
                    var val = stats[k];
1050
                    
1051
                    // load stat image to a temporary dom element
1052
                    // update model stats on image load/error events
1053
                    img.load(function() {
1054
                        images[k] = val;
1055
                        check_images_loaded();
1056
                    });
1057

    
1058
                    img.error(function() {
1059
                        images[stat + type] = false;
1060
                        check_images_loaded();
1061
                    });
1062

    
1063
                    img.attr({'src': stats[k]});
1064
                })
1065
                data.stats = stats;
1066
            }
1067

    
1068
            // do we need to change the interval ??
1069
            if (data.stats.refresh * 1000 != this.stats_update_interval) {
1070
                this.stats_update_interval = data.stats.refresh * 1000;
1071
                this.stats_fetcher.interval = this.stats_update_interval;
1072
                this.stats_fetcher.maximum_interval = this.stats_update_interval;
1073
                this.stats_fetcher.stop();
1074
                this.stats_fetcher.start(false);
1075
            }
1076
        },
1077

    
1078
        // helper method that sets the do_update_stats
1079
        // in the future this method could also make an api call
1080
        // immediaetly if needed
1081
        enable_stats_update: function() {
1082
            this.do_update_stats = true;
1083
        },
1084
        
1085
        handle_destroy: function() {
1086
            this.stats_fetcher.stop();
1087
        },
1088

    
1089
        require_reboot: function() {
1090
            if (this.is_active()) {
1091
                this.set({'reboot_required': true});
1092
            }
1093
        },
1094
        
1095
        set_pending_action: function(data) {
1096
            this.pending_action = data;
1097
            return data;
1098
        },
1099

    
1100
        // machine has pending action
1101
        update_pending_action: function(action, force) {
1102
            this.set({pending_action: action});
1103
        },
1104

    
1105
        clear_pending_action: function() {
1106
            this.set({pending_action: undefined});
1107
        },
1108

    
1109
        has_pending_action: function() {
1110
            return this.get("pending_action") ? this.get("pending_action") : false;
1111
        },
1112
        
1113
        // machine is active
1114
        is_active: function() {
1115
            return models.VM.ACTIVE_STATES.indexOf(this.state()) > -1;
1116
        },
1117
        
1118
        // machine is building 
1119
        is_building: function() {
1120
            return models.VM.BUILDING_STATES.indexOf(this.state()) > -1;
1121
        },
1122
        
1123
        in_error_state: function() {
1124
            return this.state() === "ERROR"
1125
        },
1126

    
1127
        // user can connect to machine
1128
        is_connectable: function() {
1129
            // check if ips exist
1130
            if (!this.get_addresses().ip4 && !this.get_addresses().ip6) {
1131
                return false;
1132
            }
1133
            return models.VM.CONNECT_STATES.indexOf(this.state()) > -1;
1134
        },
1135
        
1136
        remove_meta: function(key, complete, error) {
1137
            var url = this.api_path() + "/meta/" + key;
1138
            this.api_call(url, "delete", undefined, complete, error);
1139
        },
1140

    
1141
        save_meta: function(meta, complete, error) {
1142
            var url = this.api_path() + "/meta/" + meta.key;
1143
            var payload = {meta:{}};
1144
            payload.meta[meta.key] = meta.value;
1145
            payload._options = {
1146
                critical:false, 
1147
                error_params: {
1148
                    title: "Machine metadata error",
1149
                    extra_details: {"Machine id": this.id}
1150
            }};
1151

    
1152
            this.api_call(url, "update", payload, complete, error);
1153
        },
1154

    
1155

    
1156
        // update/get the state of the machine
1157
        state: function() {
1158
            var args = slice.call(arguments);
1159
                
1160
            // TODO: it might not be a good idea to set the state in set_state method
1161
            if (args.length > 0 && models.VM.STATES.indexOf(args[0]) > -1) {
1162
                this.set({'state': args[0]});
1163
            }
1164

    
1165
            return this.get('state');
1166
        },
1167
        
1168
        // get the state that the api status corresponds to
1169
        state_for_api_status: function(status) {
1170
            return this.state_transition(this.state(), status);
1171
        },
1172
        
1173
        // vm state equals vm api status
1174
        state_is_status: function(state) {
1175
            return models.VM.STATUSES.indexOf(state) != -1;
1176
        },
1177
        
1178
        // get transition state for the corresponging api status
1179
        state_transition: function(state, new_status) {
1180
            var statuses = models.VM.STATES_TRANSITIONS[state];
1181
            if (statuses) {
1182
                if (statuses.indexOf(new_status) > -1) {
1183
                    return new_status;
1184
                } else {
1185
                    return state;
1186
                }
1187
            } else {
1188
                return new_status;
1189
            }
1190
        },
1191
        
1192
        // the current vm state is a transition state
1193
        in_transition: function() {
1194
            return models.VM.TRANSITION_STATES.indexOf(this.state()) > -1 || 
1195
                models.VM.TRANSITION_STATES.indexOf(this.get('status')) > -1;
1196
        },
1197
        
1198
        // get image object
1199
        get_image: function(callback) {
1200
            if (callback == undefined) { callback = function(){} }
1201
            var image = storage.images.get(this.get('imageRef'));
1202
            if (!image) {
1203
                storage.images.update_unknown_id(this.get('imageRef'), callback);
1204
                return;
1205
            }
1206
            callback(image);
1207
            return image;
1208
        },
1209
        
1210
        // get flavor object
1211
        get_flavor: function() {
1212
            var flv = storage.flavors.get(this.get('flavorRef'));
1213
            if (!flv) {
1214
                storage.flavors.update_unknown_id(this.get('flavorRef'));
1215
                flv = storage.flavors.get(this.get('flavorRef'));
1216
            }
1217
            return flv;
1218
        },
1219

    
1220
        get_meta: function(key, deflt) {
1221
            if (this.get('metadata') && this.get('metadata').values) {
1222
                if (!this.get('metadata').values[key]) { return deflt }
1223
                return _.escape(this.get('metadata').values[key]);
1224
            } else {
1225
                return deflt;
1226
            }
1227
        },
1228

    
1229
        get_meta_keys: function() {
1230
            if (this.get('metadata') && this.get('metadata').values) {
1231
                return _.keys(this.get('metadata').values);
1232
            } else {
1233
                return [];
1234
            }
1235
        },
1236
        
1237
        // get metadata OS value
1238
        get_os: function() {
1239
            var image = this.get_image();
1240
            return this.get_meta('OS') || (image ? 
1241
                                            image.get_os() || "okeanos" : "okeanos");
1242
        },
1243

    
1244
        get_gui: function() {
1245
            return this.get_meta('GUI');
1246
        },
1247
        
1248
        connected_to: function(net) {
1249
            return this.get('linked_to').indexOf(net.id) > -1;
1250
        },
1251

    
1252
        connected_with_nic_id: function(nic_id) {
1253
            return _.keys(this.get('nics')).indexOf(nic_id) > -1;
1254
        },
1255

    
1256
        get_nics: function(filter) {
1257
            ret = synnefo.storage.nics.filter(function(nic) {
1258
                return parseInt(nic.get('vm_id')) == this.id;
1259
            }, this);
1260

    
1261
            if (filter) {
1262
                return _.filter(ret, filter);
1263
            }
1264

    
1265
            return ret;
1266
        },
1267

    
1268
        get_net_nics: function(net_id) {
1269
            return this.get_nics(function(n){return n.get('network_id') == net_id});
1270
        },
1271

    
1272
        get_public_nic: function() {
1273
            return this.get_nics(function(n){ return n.get_network().is_public() === true })[0];
1274
        },
1275

    
1276
        get_hostname: function() {
1277
          var hostname = this.get_meta('hostname');
1278
          if (!hostname) {
1279
            hostname = synnefo.config.vm_hostname_format.format(this.id);
1280
          }
1281
          return hostname;
1282
        },
1283

    
1284
        get_nic: function(net_id) {
1285
        },
1286

    
1287
        has_firewall: function() {
1288
            var nic = this.get_public_nic();
1289
            if (nic) {
1290
                var profile = nic.get('firewallProfile'); 
1291
                return ['ENABLED', 'PROTECTED'].indexOf(profile) > -1;
1292
            }
1293
            return false;
1294
        },
1295

    
1296
        get_firewall_profile: function() {
1297
            var nic = this.get_public_nic();
1298
            if (nic) {
1299
                return nic.get('firewallProfile');
1300
            }
1301
            return null;
1302
        },
1303

    
1304
        get_addresses: function() {
1305
            var pnic = this.get_public_nic();
1306
            if (!pnic) { return {'ip4': undefined, 'ip6': undefined }};
1307
            return {'ip4': pnic.get('ipv4'), 'ip6': pnic.get('ipv6')};
1308
        },
1309
    
1310
        // get actions that the user can execute
1311
        // depending on the vm state/status
1312
        get_available_actions: function() {
1313
            return models.VM.AVAILABLE_ACTIONS[this.state()];
1314
        },
1315

    
1316
        set_profile: function(profile, net_id) {
1317
        },
1318
        
1319
        // call rename api
1320
        rename: function(new_name) {
1321
            //this.set({'name': new_name});
1322
            this.sync("update", this, {
1323
                critical: true,
1324
                data: {
1325
                    'server': {
1326
                        'name': new_name
1327
                    }
1328
                }, 
1329
                // do the rename after the method succeeds
1330
                success: _.bind(function(){
1331
                    //this.set({name: new_name});
1332
                    snf.api.trigger("call");
1333
                }, this)
1334
            });
1335
        },
1336
        
1337
        get_console_url: function(data) {
1338
            var url_params = {
1339
                machine: this.get("name"),
1340
                host_ip: this.get_addresses().ip4,
1341
                host_ip_v6: this.get_addresses().ip6,
1342
                host: data.host,
1343
                port: data.port,
1344
                password: data.password
1345
            }
1346
            return '/machines/console?' + $.param(url_params);
1347
        },
1348

    
1349
        // action helper
1350
        call: function(action_name, success, error, params) {
1351
            var id_param = [this.id];
1352
            
1353
            params = params || {};
1354
            success = success || function() {};
1355
            error = error || function() {};
1356

    
1357
            var self = this;
1358

    
1359
            switch(action_name) {
1360
                case 'start':
1361
                    this.__make_api_call(this.get_action_url(), // vm actions url
1362
                                         "create", // create so that sync later uses POST to make the call
1363
                                         {start:{}}, // payload
1364
                                         function() {
1365
                                             // set state after successful call
1366
                                             self.state("START"); 
1367
                                             success.apply(this, arguments);
1368
                                             snf.api.trigger("call");
1369
                                         },  
1370
                                         error, 'start', params);
1371
                    break;
1372
                case 'reboot':
1373
                    this.__make_api_call(this.get_action_url(), // vm actions url
1374
                                         "create", // create so that sync later uses POST to make the call
1375
                                         {reboot:{type:"HARD"}}, // payload
1376
                                         function() {
1377
                                             // set state after successful call
1378
                                             self.state("REBOOT"); 
1379
                                             success.apply(this, arguments)
1380
                                             snf.api.trigger("call");
1381
                                             self.set({'reboot_required': false});
1382
                                         },
1383
                                         error, 'reboot', params);
1384
                    break;
1385
                case 'shutdown':
1386
                    this.__make_api_call(this.get_action_url(), // vm actions url
1387
                                         "create", // create so that sync later uses POST to make the call
1388
                                         {shutdown:{}}, // payload
1389
                                         function() {
1390
                                             // set state after successful call
1391
                                             self.state("SHUTDOWN"); 
1392
                                             success.apply(this, arguments)
1393
                                             snf.api.trigger("call");
1394
                                         },  
1395
                                         error, 'shutdown', params);
1396
                    break;
1397
                case 'console':
1398
                    this.__make_api_call(this.url() + "/action", "create", {'console': {'type':'vnc'}}, function(data) {
1399
                        var cons_data = data.console;
1400
                        success.apply(this, [cons_data]);
1401
                    }, undefined, 'console', params)
1402
                    break;
1403
                case 'destroy':
1404
                    this.__make_api_call(this.url(), // vm actions url
1405
                                         "delete", // create so that sync later uses POST to make the call
1406
                                         undefined, // payload
1407
                                         function() {
1408
                                             // set state after successful call
1409
                                             self.state('DESTROY');
1410
                                             success.apply(this, arguments)
1411
                                         },  
1412
                                         error, 'destroy', params);
1413
                    break;
1414
                default:
1415
                    throw "Invalid VM action ("+action_name+")";
1416
            }
1417
        },
1418
        
1419
        __make_api_call: function(url, method, data, success, error, action, extra_params) {
1420
            var self = this;
1421
            error = error || function(){};
1422
            success = success || function(){};
1423

    
1424
            var params = {
1425
                url: url,
1426
                data: data,
1427
                success: function(){ self.handle_action_succeed.apply(self, arguments); success.apply(this, arguments)},
1428
                error: function(){ self.handle_action_fail.apply(self, arguments); error.apply(this, arguments)},
1429
                error_params: { ns: "Machines actions", 
1430
                                title: "'" + this.get("name") + "'" + " " + action + " failed", 
1431
                                extra_details: { 'Machine ID': this.id, 'URL': url, 'Action': action || "undefined" },
1432
                                allow_reload: false
1433
                              },
1434
                display: false,
1435
                critical: false
1436
            }
1437
            _.extend(params, extra_params)
1438
            this.sync(method, this, params);
1439
        },
1440

    
1441
        handle_action_succeed: function() {
1442
            this.trigger("action:success", arguments);
1443
        },
1444
        
1445
        reset_action_error: function() {
1446
            this.action_error = false;
1447
            this.trigger("action:fail:reset", this.action_error);
1448
        },
1449

    
1450
        handle_action_fail: function() {
1451
            this.action_error = arguments;
1452
            this.trigger("action:fail", arguments);
1453
        },
1454

    
1455
        get_action_url: function(name) {
1456
            return this.url() + "/action";
1457
        },
1458

    
1459
        get_diagnostics_url: function() {
1460
            return this.url() + "/diagnostics";
1461
        },
1462

    
1463
        get_connection_info: function(host_os, success, error) {
1464
            var url = "/machines/connect";
1465
            params = {
1466
                ip_address: this.get_public_nic().get('ipv4'),
1467
                hostname: this.get_hostname(),
1468
                os: this.get_os(),
1469
                host_os: host_os,
1470
                srv: this.id
1471
            }
1472

    
1473
            url = url + "?" + $.param(params);
1474

    
1475
            var ajax = snf.api.sync("read", undefined, { url: url, 
1476
                                                         error:error, 
1477
                                                         success:success, 
1478
                                                         handles_error:1});
1479
        }
1480
    })
1481
    
1482
    models.VM.ACTIONS = [
1483
        'start',
1484
        'shutdown',
1485
        'reboot',
1486
        'console',
1487
        'destroy'
1488
    ]
1489

    
1490
    models.VM.AVAILABLE_ACTIONS = {
1491
        'UNKNWON'       : ['destroy'],
1492
        'BUILD'         : ['destroy'],
1493
        'REBOOT'        : ['shutdown', 'destroy', 'console'],
1494
        'STOPPED'       : ['start', 'destroy'],
1495
        'ACTIVE'        : ['shutdown', 'destroy', 'reboot', 'console'],
1496
        'ERROR'         : ['destroy'],
1497
        'DELETED'        : [],
1498
        'DESTROY'       : [],
1499
        'BUILD_INIT'    : ['destroy'],
1500
        'BUILD_COPY'    : ['destroy'],
1501
        'BUILD_FINAL'   : ['destroy'],
1502
        'SHUTDOWN'      : ['destroy'],
1503
        'START'         : [],
1504
        'CONNECT'       : [],
1505
        'DISCONNECT'    : []
1506
    }
1507

    
1508
    // api status values
1509
    models.VM.STATUSES = [
1510
        'UNKNWON',
1511
        'BUILD',
1512
        'REBOOT',
1513
        'STOPPED',
1514
        'ACTIVE',
1515
        'ERROR',
1516
        'DELETED'
1517
    ]
1518

    
1519
    // api status values
1520
    models.VM.CONNECT_STATES = [
1521
        'ACTIVE',
1522
        'REBOOT',
1523
        'SHUTDOWN'
1524
    ]
1525

    
1526
    // vm states
1527
    models.VM.STATES = models.VM.STATUSES.concat([
1528
        'DESTROY',
1529
        'BUILD_INIT',
1530
        'BUILD_COPY',
1531
        'BUILD_FINAL',
1532
        'SHUTDOWN',
1533
        'START',
1534
        'CONNECT',
1535
        'DISCONNECT',
1536
        'FIREWALL'
1537
    ]);
1538
    
1539
    models.VM.STATES_TRANSITIONS = {
1540
        'DESTROY' : ['DELETED'],
1541
        'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'],
1542
        'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY'],
1543
        'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY'],
1544
        'START': ['ERROR', 'ACTIVE', 'DESTROY'],
1545
        'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'],
1546
        'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'],
1547
        'BUILD_COPY': ['ERROR', 'ACTIVE', 'BUILD_FINAL', 'DESTROY'],
1548
        'BUILD_FINAL': ['ERROR', 'ACTIVE', 'DESTROY'],
1549
        'BUILD_INIT': ['ERROR', 'ACTIVE', 'BUILD_COPY', 'BUILD_FINAL', 'DESTROY']
1550
    }
1551

    
1552
    models.VM.TRANSITION_STATES = [
1553
        'DESTROY',
1554
        'SHUTDOWN',
1555
        'START',
1556
        'REBOOT',
1557
        'BUILD'
1558
    ]
1559

    
1560
    models.VM.ACTIVE_STATES = [
1561
        'BUILD', 'REBOOT', 'ACTIVE',
1562
        'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL',
1563
        'SHUTDOWN', 'CONNECT', 'DISCONNECT'
1564
    ]
1565

    
1566
    models.VM.BUILDING_STATES = [
1567
        'BUILD', 'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL'
1568
    ]
1569

    
1570
    models.Networks = models.Collection.extend({
1571
        model: models.Network,
1572
        path: 'networks',
1573
        details: true,
1574
        //noUpdate: true,
1575
        defaults: {'nics':[],'linked_to':[]},
1576
        
1577
        parse: function (resp, xhr) {
1578
            // FIXME: depricated global var
1579
            if (!resp) { return []};
1580
            var data = _.filter(_.map(resp.networks.values, _.bind(this.parse_net_api_data, this)),
1581
                               function(e){ return e });
1582
            return data;
1583
        },
1584

    
1585
        add: function() {
1586
            ret = models.Networks.__super__.add.apply(this, arguments);
1587
            // update nics after each network addition
1588
            ret.each(function(r){
1589
                synnefo.storage.nics.update_net_nics(r);
1590
            });
1591
        },
1592

    
1593
        reset_pending_actions: function() {
1594
            this.each(function(net) {
1595
                net.get("actions").reset();
1596
            });
1597
        },
1598

    
1599
        do_all_pending_actions: function() {
1600
            this.each(function(net) {
1601
                net.do_all_pending_actions();
1602
            })
1603
        },
1604

    
1605
        parse_net_api_data: function(data) {
1606
            // append nic metadata
1607
            // net.get('nics') contains a list of vm/index objects 
1608
            // e.g. {'vm_id':12231, 'index':1}
1609
            // net.get('linked_to') contains a list of vms the network is 
1610
            // connected to e.g. [1001, 1002]
1611
            if (data.attachments && data.attachments.values) {
1612
                data['nics'] = {};
1613
                data['linked_to'] = [];
1614
                _.each(data.attachments.values, function(nic_id){
1615
                  
1616
                  var vm_id = NIC_REGEX.exec(nic_id)[1];
1617
                  var nic_index = parseInt(NIC_REGEX.exec(nic_id)[2]);
1618

    
1619
                  if (vm_id !== undefined && nic_index !== undefined) {
1620
                      data['nics'][nic_id] = {
1621
                          'vm_id': vm_id, 
1622
                          'index': nic_index, 
1623
                          'id': nic_id
1624
                      };
1625
                      if (data['linked_to'].indexOf(vm_id) == -1) {
1626
                        data['linked_to'].push(vm_id);
1627
                      }
1628
                  }
1629
                });
1630
            }
1631

    
1632
            if (data.status == "DELETED" && !this.get(parseInt(data.id))) {
1633
              return false;
1634
            }
1635
            return data;
1636
        },
1637

    
1638
        create: function (name, type, cidr, dhcp, callback) {
1639
            var params = {
1640
                network:{
1641
                    name:name
1642
                }
1643
            };
1644

    
1645
            if (type) {
1646
                params.network.type = type;
1647
            }
1648
            if (cidr) {
1649
                params.network.cidr = cidr;
1650
            }
1651
            if (dhcp) {
1652
                params.network.dhcp = dhcp;
1653
            }
1654

    
1655
            if (dhcp === false) {
1656
                params.network.dhcp = false;
1657
            }
1658
            
1659
            return this.api_call(this.path, "create", params, callback);
1660
        },
1661

    
1662
        get_public: function(){
1663
          return this.filter(function(n){return n.get('public')});
1664
        }
1665
    })
1666

    
1667
    models.Images = models.Collection.extend({
1668
        model: models.Image,
1669
        path: 'images',
1670
        details: true,
1671
        noUpdate: true,
1672
        supportIncUpdates: false,
1673
        meta_keys_as_attrs: ["OS", "description", "kernel", "size", "GUI"],
1674
        meta_labels: {},
1675
        read_method: 'read',
1676

    
1677
        // update collection model with id passed
1678
        // making a direct call to the image
1679
        // api url
1680
        update_unknown_id: function(id, callback) {
1681
            var url = getUrl.call(this) + "/" + id;
1682
            this.api_call(this.path + "/" + id, this.read_method, {
1683
              _options:{
1684
                async:true, 
1685
                skip_api_error:true}
1686
              }, undefined, 
1687
            _.bind(function() {
1688
                if (!this.get(id)) {
1689
                            if (this.fallback_service) {
1690
                        // if current service has fallback_service attribute set
1691
                        // use this service to retrieve the missing image model
1692
                        var tmpservice = new this.fallback_service();
1693
                        tmpservice.update_unknown_id(id, _.bind(function(img){
1694
                            img.attributes.status = "DELETED";
1695
                            this.add(img.attributes);
1696
                            callback(this.get(id));
1697
                        }, this));
1698
                    } else {
1699
                        var title = synnefo.config.image_deleted_title || 'Deleted';
1700
                        // else add a dummy DELETED state image entry
1701
                        this.add({id:id, name:title, size:-1, 
1702
                                  progress:100, status:"DELETED"});
1703
                        callback(this.get(id));
1704
                    }   
1705
                } else {
1706
                    callback(this.get(id));
1707
                }
1708
            }, this), _.bind(function(image, msg, xhr) {
1709
                if (!image) {
1710
                    var title = synnefo.config.image_deleted_title || 'Deleted';
1711
                    this.add({id:id, name:title, size:-1, 
1712
                              progress:100, status:"DELETED"});
1713
                    callback(this.get(id));
1714
                    return;
1715
                }
1716
                var img_data = this._read_image_from_request(image, msg, xhr);
1717
                this.add(img_data);
1718
                callback(this.get(id));
1719
            }, this));
1720
        },
1721

    
1722
        _read_image_from_request: function(image, msg, xhr) {
1723
            return image.image;
1724
        },
1725

    
1726
        parse: function (resp, xhr) {
1727
            var data = _.map(resp.images.values, _.bind(this.parse_meta, this));
1728
            return resp.images.values;
1729
        },
1730

    
1731
        get_meta_key: function(img, key) {
1732
            if (img.metadata && img.metadata.values && img.metadata.values[key]) {
1733
                return _.escape(img.metadata.values[key]);
1734
            }
1735
            return undefined;
1736
        },
1737

    
1738
        comparator: function(img) {
1739
            return -img.get_sort_order("sortorder") || 1000 * img.id;
1740
        },
1741

    
1742
        parse_meta: function(img) {
1743
            _.each(this.meta_keys_as_attrs, _.bind(function(key){
1744
                if (img[key]) { return };
1745
                img[key] = this.get_meta_key(img, key) || "";
1746
            }, this));
1747
            return img;
1748
        },
1749

    
1750
        active: function() {
1751
            return this.filter(function(img){return img.get('status') != "DELETED"});
1752
        },
1753

    
1754
        predefined: function() {
1755
            return _.filter(this.active(), function(i) { return !i.get("serverRef")});
1756
        },
1757
        
1758
        fetch_for_type: function(type, complete, error) {
1759
            this.fetch({update:true, 
1760
                        success: complete, 
1761
                        error: error, 
1762
                        skip_api_error: true });
1763
        },
1764
        
1765
        get_images_for_type: function(type) {
1766
            if (this['get_{0}_images'.format(type)]) {
1767
                return this['get_{0}_images'.format(type)]();
1768
            }
1769

    
1770
            return this.active();
1771
        },
1772

    
1773
        update_images_for_type: function(type, onStart, onComplete, onError, force_load) {
1774
            var load = false;
1775
            error = onError || function() {};
1776
            function complete(collection) { 
1777
                onComplete(collection.get_images_for_type(type)); 
1778
            }
1779
            
1780
            // do we need to fetch/update current collection entries
1781
            if (load) {
1782
                onStart();
1783
                this.fetch_for_type(type, complete, error);
1784
            } else {
1785
                // fallback to complete
1786
                complete(this);
1787
            }
1788
        }
1789
    })
1790

    
1791
    models.Flavors = models.Collection.extend({
1792
        model: models.Flavor,
1793
        path: 'flavors',
1794
        details: true,
1795
        noUpdate: true,
1796
        supportIncUpdates: false,
1797
        // update collection model with id passed
1798
        // making a direct call to the flavor
1799
        // api url
1800
        update_unknown_id: function(id, callback) {
1801
            var url = getUrl.call(this) + "/" + id;
1802
            this.api_call(this.path + "/" + id, "read", {_options:{async:false, skip_api_error:true}}, undefined, 
1803
            _.bind(function() {
1804
                this.add({id:id, cpu:"Unknown", ram:"Unknown", disk:"Unknown", name: "Unknown", status:"DELETED"})
1805
            }, this), _.bind(function(flv) {
1806
                if (!flv.flavor.status) { flv.flavor.status = "DELETED" };
1807
                this.add(flv.flavor);
1808
            }, this));
1809
        },
1810

    
1811
        parse: function (resp, xhr) {
1812
            return _.map(resp.flavors.values, function(o) { o.disk_template = o['SNF:disk_template']; return o});
1813
        },
1814

    
1815
        comparator: function(flv) {
1816
            return flv.get("disk") * flv.get("cpu") * flv.get("ram");
1817
        },
1818

    
1819
        unavailable_values_for_image: function(img, flavors) {
1820
            var flavors = flavors || this.active();
1821
            var size = img.get_size();
1822
            
1823
            var index = {cpu:[], disk:[], ram:[]};
1824

    
1825
            _.each(this.active(), function(el) {
1826
                var img_size = size;
1827
                var flv_size = el.get_disk_size();
1828
                if (flv_size < img_size) {
1829
                    if (index.disk.indexOf(flv_size) == -1) {
1830
                        index.disk.push(flv_size);
1831
                    }
1832
                };
1833
            });
1834
            
1835
            return index;
1836
        },
1837

    
1838
        get_flavor: function(cpu, mem, disk, disk_template, filter_list) {
1839
            if (!filter_list) { filter_list = this.models };
1840
            
1841
            return this.select(function(flv){
1842
                if (flv.get("cpu") == cpu + "" &&
1843
                   flv.get("ram") == mem + "" &&
1844
                   flv.get("disk") == disk + "" &&
1845
                   flv.get("disk_template") == disk_template &&
1846
                   filter_list.indexOf(flv) > -1) { return true; }
1847
            })[0];
1848
        },
1849
        
1850
        get_data: function(lst) {
1851
            var data = {'cpu': [], 'mem':[], 'disk':[]};
1852

    
1853
            _.each(lst, function(flv) {
1854
                if (data.cpu.indexOf(flv.get("cpu")) == -1) {
1855
                    data.cpu.push(flv.get("cpu"));
1856
                }
1857
                if (data.mem.indexOf(flv.get("ram")) == -1) {
1858
                    data.mem.push(flv.get("ram"));
1859
                }
1860
                if (data.disk.indexOf(flv.get("disk")) == -1) {
1861
                    data.disk.push(flv.get("disk"));
1862
                }
1863
            })
1864
            
1865
            return data;
1866
        },
1867

    
1868
        active: function() {
1869
            return this.filter(function(flv){return flv.get('status') != "DELETED"});
1870
        }
1871
            
1872
    })
1873

    
1874
    models.VMS = models.Collection.extend({
1875
        model: models.VM,
1876
        path: 'servers',
1877
        details: true,
1878
        copy_image_meta: true,
1879

    
1880
        parse: function (resp, xhr) {
1881
            var data = resp;
1882
            if (!resp) { return [] };
1883
            data = _.filter(_.map(resp.servers.values, _.bind(this.parse_vm_api_data, this)), function(v){return v});
1884
            return data;
1885
        },
1886

    
1887
        parse_vm_api_data: function(data) {
1888
            // do not add non existing DELETED entries
1889
            if (data.status && data.status == "DELETED") {
1890
                if (!this.get(data.id)) {
1891
                    return false;
1892
                }
1893
            }
1894

    
1895
            // OS attribute
1896
            if (this.has_meta(data)) {
1897
                data['OS'] = data.metadata.values.OS || "okeanos";
1898
            }
1899
            
1900
            if (!data.diagnostics) {
1901
                data.diagnostics = [];
1902
            }
1903

    
1904
            // network metadata
1905
            data['firewalls'] = {};
1906
            data['nics'] = {};
1907
            data['linked_to'] = [];
1908

    
1909
            if (data['attachments'] && data['attachments'].values) {
1910
                var nics = data['attachments'].values;
1911
                _.each(nics, function(nic) {
1912
                    var net_id = nic.network_id;
1913
                    var index = parseInt(NIC_REGEX.exec(nic.id)[2]);
1914
                    if (data['linked_to'].indexOf(net_id) == -1) {
1915
                        data['linked_to'].push(net_id);
1916
                    }
1917

    
1918
                    data['nics'][nic.id] = nic;
1919
                })
1920
            }
1921
            
1922
            // if vm has no metadata, no metadata object
1923
            // is in json response, reset it to force
1924
            // value update
1925
            if (!data['metadata']) {
1926
                data['metadata'] = {values:{}};
1927
            }
1928

    
1929
            return data;
1930
        },
1931

    
1932
        add: function() {
1933
            ret = models.VMS.__super__.add.apply(this, arguments);
1934
            ret.each(function(r){
1935
                synnefo.storage.nics.update_vm_nics(r);
1936
            });
1937
        },
1938
        
1939
        get_reboot_required: function() {
1940
            return this.filter(function(vm){return vm.get("reboot_required") == true})
1941
        },
1942

    
1943
        has_pending_actions: function() {
1944
            return this.filter(function(vm){return vm.pending_action}).length > 0;
1945
        },
1946

    
1947
        reset_pending_actions: function() {
1948
            this.each(function(vm) {
1949
                vm.clear_pending_action();
1950
            })
1951
        },
1952

    
1953
        do_all_pending_actions: function(success, error) {
1954
            this.each(function(vm) {
1955
                if (vm.has_pending_action()) {
1956
                    vm.call(vm.pending_action, success, error);
1957
                    vm.clear_pending_action();
1958
                }
1959
            })
1960
        },
1961
        
1962
        do_all_reboots: function(success, error) {
1963
            this.each(function(vm) {
1964
                if (vm.get("reboot_required")) {
1965
                    vm.call("reboot", success, error);
1966
                }
1967
            });
1968
        },
1969

    
1970
        reset_reboot_required: function() {
1971
            this.each(function(vm) {
1972
                vm.set({'reboot_required': undefined});
1973
            })
1974
        },
1975
        
1976
        stop_stats_update: function(exclude) {
1977
            var exclude = exclude || [];
1978
            this.each(function(vm) {
1979
                if (exclude.indexOf(vm) > -1) {
1980
                    return;
1981
                }
1982
                vm.stop_stats_update();
1983
            })
1984
        },
1985
        
1986
        has_meta: function(vm_data) {
1987
            return vm_data.metadata && vm_data.metadata.values
1988
        },
1989

    
1990
        has_addresses: function(vm_data) {
1991
            return vm_data.metadata && vm_data.metadata.values
1992
        },
1993

    
1994
        create: function (name, image, flavor, meta, extra, callback) {
1995

    
1996
            if (this.copy_image_meta) {
1997
                if (synnefo.config.vm_image_common_metadata) {
1998
                    _.each(synnefo.config.vm_image_common_metadata, 
1999
                        function(key){
2000
                            if (image.get_meta(key)) {
2001
                                meta[key] = image.get_meta(key);
2002
                            }
2003
                    });
2004
                }
2005

    
2006
                if (image.get("OS")) {
2007
                    meta['OS'] = image.get("OS");
2008
                }
2009
            }
2010
            
2011
            opts = {name: name, imageRef: image.id, flavorRef: flavor.id, metadata:meta}
2012
            opts = _.extend(opts, extra);
2013

    
2014
            this.api_call(this.path, "create", {'server': opts}, undefined, undefined, callback, {critical: true});
2015
        }
2016

    
2017
    })
2018
    
2019
    models.NIC = models.Model.extend({
2020
        
2021
        initialize: function() {
2022
            models.NIC.__super__.initialize.apply(this, arguments);
2023
            this.pending_for_firewall = false;
2024
            this.bind("change:firewallProfile", _.bind(this.check_firewall, this));
2025
            this.bind("change:pending_firewall", function(nic) {
2026
                nic.get_network().update_state();
2027
            });
2028
            this.get_vm().bind("remove", function(){
2029
                try {
2030
                    this.collection.remove(this);
2031
                } catch (err) {};
2032
            }, this);
2033
            this.get_network().bind("remove", function(){
2034
                try {
2035
                    this.collection.remove(this);
2036
                } catch (err) {};
2037
            }, this);
2038

    
2039
        },
2040

    
2041
        get_vm: function() {
2042
            return synnefo.storage.vms.get(parseInt(this.get('vm_id')));
2043
        },
2044

    
2045
        get_network: function() {
2046
            return synnefo.storage.networks.get(this.get('network_id'));
2047
        },
2048

    
2049
        get_v6_address: function() {
2050
            return this.get("ipv6");
2051
        },
2052

    
2053
        get_v4_address: function() {
2054
            return this.get("ipv4");
2055
        },
2056

    
2057
        set_firewall: function(value, callback, error, options) {
2058
            var net_id = this.get('network_id');
2059
            var self = this;
2060

    
2061
            // api call data
2062
            var payload = {"firewallProfile":{"profile":value}};
2063
            payload._options = _.extend({critical: false}, options);
2064
            
2065
            this.set({'pending_firewall': value});
2066
            this.set({'pending_firewall_sending': true});
2067
            this.set({'pending_firewall_from': this.get('firewallProfile')});
2068

    
2069
            var success_cb = function() {
2070
                if (callback) {
2071
                    callback();
2072
                }
2073
                self.set({'pending_firewall_sending': false});
2074
            };
2075

    
2076
            var error_cb = function() {
2077
                self.reset_pending_firewall();
2078
            }
2079
            
2080
            this.get_vm().api_call(this.get_vm().api_path() + "/action", "create", payload, success_cb, error_cb);
2081
        },
2082

    
2083
        reset_pending_firewall: function() {
2084
            this.set({'pending_firewall': false});
2085
            this.set({'pending_firewall': false});
2086
        },
2087

    
2088
        check_firewall: function() {
2089
            var firewall = this.get('firewallProfile');
2090
            var pending = this.get('pending_firewall');
2091
            var previous = this.get('pending_firewall_from');
2092
            if (previous != firewall) { this.get_vm().require_reboot() };
2093
            this.reset_pending_firewall();
2094
        }
2095
        
2096
    });
2097

    
2098
    models.NICs = models.Collection.extend({
2099
        model: models.NIC,
2100
        
2101
        add_or_update: function(nic_id, data, vm) {
2102
            var params = _.clone(data);
2103
            var vm;
2104
            params.attachment_id = params.id;
2105
            params.id = params.id + '-' + params.network_id;
2106
            params.vm_id = parseInt(NIC_REGEX.exec(nic_id)[1]);
2107

    
2108
            if (!this.get(params.id)) {
2109
                this.add(params);
2110
                var nic = this.get(params.id);
2111
                vm = nic.get_vm();
2112
                nic.get_network().decrease_connecting();
2113
                nic.bind("remove", function() {
2114
                    nic.set({"removing": 0});
2115
                    if (this.get_network()) {
2116
                        // network might got removed before nic
2117
                        nic.get_network().update_state();
2118
                    }
2119
                });
2120

    
2121
            } else {
2122
                this.get(params.id).set(params);
2123
                vm = this.get(params.id).get_vm();
2124
            }
2125
            
2126
            // vm nics changed, trigger vm update
2127
            if (vm) { vm.trigger("change", vm)};
2128
        },
2129
        
2130
        reset_nics: function(nics, filter_attr, filter_val) {
2131
            var nics_to_check = this.filter(function(nic) {
2132
                return nic.get(filter_attr) == filter_val;
2133
            });
2134
            
2135
            _.each(nics_to_check, function(nic) {
2136
                if (nics.indexOf(nic.get('id')) == -1) {
2137
                    this.remove(nic);
2138
                } else {
2139
                }
2140
            }, this);
2141
        },
2142

    
2143
        update_vm_nics: function(vm) {
2144
            var nics = vm.get('nics');
2145
            this.reset_nics(_.map(nics, function(nic, key){
2146
                return key + "-" + nic.network_id;
2147
            }), 'vm_id', vm.id);
2148

    
2149
            _.each(nics, function(val, key) {
2150
                var net = synnefo.storage.networks.get(val.network_id);
2151
                if (net && net.connected_with_nic_id(key) && vm.connected_with_nic_id(key)) {
2152
                    this.add_or_update(key, vm.get('nics')[key], vm);
2153
                }
2154
            }, this);
2155
        },
2156

    
2157
        update_net_nics: function(net) {
2158
            var nics = net.get('nics');
2159
            this.reset_nics(_.map(nics, function(nic, key){
2160
                return key + "-" + net.get('id');
2161
            }), 'network_id', net.id);
2162

    
2163
            _.each(nics, function(val, key) {
2164
                var vm = synnefo.storage.vms.get(val.vm_id);
2165
                if (vm && net.connected_with_nic_id(key) && vm.connected_with_nic_id(key)) {
2166
                    this.add_or_update(key, vm.get('nics')[key], vm);
2167
                }
2168
            }, this);
2169
        }
2170
    });
2171

    
2172
    models.PublicKey = models.Model.extend({
2173
        path: 'keys',
2174
        base_url: '/ui/userdata',
2175
        details: false,
2176
        noUpdate: true,
2177

    
2178

    
2179
        get_public_key: function() {
2180
            return cryptico.publicKeyFromString(this.get("content"));
2181
        },
2182

    
2183
        get_filename: function() {
2184
            return "{0}.pub".format(this.get("name"));
2185
        },
2186

    
2187
        identify_type: function() {
2188
            try {
2189
                var cont = snf.util.validatePublicKey(this.get("content"));
2190
                var type = cont.split(" ")[0];
2191
                return synnefo.util.publicKeyTypesMap[type];
2192
            } catch (err) { return false };
2193
        }
2194

    
2195
    })
2196
    
2197
    models.PublicKeys = models.Collection.extend({
2198
        model: models.PublicKey,
2199
        details: false,
2200
        path: 'keys',
2201
        base_url: '/ui/userdata',
2202
        noUpdate: true,
2203

    
2204
        generate_new: function(success, error) {
2205
            snf.api.sync('create', undefined, {
2206
                url: getUrl.call(this, this.base_url) + "/generate", 
2207
                success: success, 
2208
                error: error,
2209
                skip_api_error: true
2210
            });
2211
        },
2212

    
2213
        add_crypto_key: function(key, success, error, options) {
2214
            var options = options || {};
2215
            var m = new models.PublicKey();
2216

    
2217
            // guess a name
2218
            var name_tpl = "my generated public key";
2219
            var name = name_tpl;
2220
            var name_count = 1;
2221
            
2222
            while(this.filter(function(m){ return m.get("name") == name }).length > 0) {
2223
                name = name_tpl + " " + name_count;
2224
                name_count++;
2225
            }
2226
            
2227
            m.set({name: name});
2228
            m.set({content: key});
2229
            
2230
            options.success = function () { return success(m) };
2231
            options.errror = error;
2232
            options.skip_api_error = true;
2233
            
2234
            this.create(m.attributes, options);
2235
        }
2236
    })
2237
    
2238
    // storage initialization
2239
    snf.storage.images = new models.Images();
2240
    snf.storage.flavors = new models.Flavors();
2241
    snf.storage.networks = new models.Networks();
2242
    snf.storage.vms = new models.VMS();
2243
    snf.storage.keys = new models.PublicKeys();
2244
    snf.storage.nics = new models.NICs();
2245

    
2246
    //snf.storage.vms.fetch({update:true});
2247
    //snf.storage.images.fetch({update:true});
2248
    //snf.storage.flavors.fetch({update:true});
2249

    
2250
})(this);