Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (76 kB)

1
// Copyright 2011 GRNET S.A. All rights reserved.
2
// 
3
// Redistribution and use in source and binary forms, with or
4
// without modification, are permitted provided that the following
5
// conditions are met:
6
// 
7
//   1. Redistributions of source code must retain the above
8
//      copyright notice, this list of conditions and the following
9
//      disclaimer.
10
// 
11
//   2. Redistributions in binary form must reproduce the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer in the documentation and/or other materials
14
//      provided with the distribution.
15
// 
16
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
// POSSIBILITY OF SUCH DAMAGE.
28
// 
29
// The views and conclusions contained in the software and
30
// documentation are those of the authors and should not be
31
// interpreted as representing official policies, either expressed
32
// or implied, of GRNET S.A.
33
// 
34

    
35
;(function(root){
36
    
37
    // root
38
    var root = root;
39
    
40
    // setup namepsaces
41
    var snf = root.synnefo = root.synnefo || {};
42
    var models = snf.models = snf.models || {}
43
    var storage = snf.storage = snf.storage || {};
44
    var util = snf.util = snf.util || {};
45

    
46
    // shortcuts
47
    var bb = root.Backbone;
48
    var slice = Array.prototype.slice
49

    
50
    // logging
51
    var logger = new snf.logging.logger("SNF-MODELS");
52
    var debug = _.bind(logger.debug, logger);
53
    
54
    // get url helper
55
    var getUrl = function(baseurl) {
56
        var baseurl = baseurl || snf.config.api_urls[this.api_type];
57
        return baseurl + "/" + this.path;
58
    }
59

    
60
    var NIC_REGEX = /^nic-([0-9]+)-([0-9]+)$/
61
    
62
    // i18n
63
    BUILDING_MESSAGES = window.BUILDING_MESSAGES || {'INIT': 'init', 'COPY': '{0}, {1}, {2}', 'FINAL': 'final'};
64

    
65
    // Base object for all our models
66
    models.Model = bb.Model.extend({
67
        sync: snf.api.sync,
68
        api: snf.api,
69
        api_type: 'compute',
70
        has_status: false,
71

    
72
        initialize: function() {
73
            if (this.has_status) {
74
                this.bind("change:status", this.handle_remove);
75
                this.handle_remove();
76
            }
77
            
78
            this.api_call = _.bind(this.api.call, this);
79
            models.Model.__super__.initialize.apply(this, arguments);
80
        },
81

    
82
        handle_remove: function() {
83
            if (this.get("status") == 'DELETED') {
84
                if (this.collection) {
85
                    try { this.clear_pending_action();} catch (err) {};
86
                    try { this.reset_pending_actions();} catch (err) {};
87
                    try { this.stop_stats_update();} catch (err) {};
88
                    this.collection.remove(this.id);
89
                }
90
            }
91
        },
92
        
93
        // custom set method to allow submodels to use
94
        // set_<attr> methods for handling the value of each
95
        // attribute and overriding the default set method
96
        // for specific parameters
97
        set: function(params, options) {
98
            _.each(params, _.bind(function(value, key){
99
                if (this["set_" + key]) {
100
                    params[key] = this["set_" + key](value);
101
                }
102
            }, this))
103
            var ret = bb.Model.prototype.set.call(this, params, options);
104
            return ret;
105
        },
106

    
107
        url: function(options) {
108
            return getUrl.call(this, this.base_url) + "/" + this.id;
109
        },
110

    
111
        api_path: function(options) {
112
            return this.path + "/" + this.id;
113
        },
114

    
115
        parse: function(resp, xhr) {
116
            return resp.server;
117
        },
118

    
119
        remove: function() {
120
            this.api_call(this.api_path(), "delete");
121
        },
122

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
265
        display_owner: function() {
266
            var owner = this.get_owner();
267
            if (_.include(_.keys(synnefo.config.system_images_owners), owner)) {
268
                return synnefo.config.system_images_owners[owner];
269
            } else {
270
                return owner;
271
            }
272
        },
273
    
274
        get_readable_size: function() {
275
            if (this.is_deleted()) {
276
                return synnefo.config.image_deleted_size_title || '(none)';
277
            }
278
            return this.get_size() > 0 ? util.readablizeBytes(this.get_size() * 1024 * 1024) : '(none)';
279
        },
280

    
281
        get_os: function() {
282
            return this.get_meta('OS');
283
        },
284

    
285
        get_gui: function() {
286
            return this.get_meta('GUI');
287
        },
288

    
289
        get_created_user: function() {
290
            return synnefo.config.os_created_users[this.get_os()] || "root";
291
        },
292

    
293
        get_sort_order: function() {
294
            return parseInt(this.get('metadata') ? this.get('metadata').values.sortorder : -1)
295
        },
296

    
297
        get_vm: function() {
298
            var vm_id = this.get("serverRef");
299
            var vm = undefined;
300
            vm = storage.vms.get(vm_id);
301
            return vm;
302
        },
303

    
304
        is_public: function() {
305
            return this.get('is_public') || true;
306
        },
307

    
308
        is_deleted: function() {
309
            return this.get('status') == "DELETED"
310
        },
311
        
312
        ssh_keys_path: function() {
313
            prepend = '';
314
            if (this.get_created_user() != 'root') {
315
                prepend = '/home'
316
            }
317
            return '{1}/{0}/.ssh/authorized_keys'.format(this.get_created_user(), prepend);
318
        },
319

    
320
        _supports_ssh: function() {
321
            if (synnefo.config.support_ssh_os_list.indexOf(this.get_os()) > -1) {
322
                return true;
323
            }
324
            if (this.get_meta('osfamily') == 'linux') {
325
              return true;
326
            }
327
            return false;
328
        },
329

    
330
        supports: function(feature) {
331
            if (feature == "ssh") {
332
                return this._supports_ssh()
333
            }
334
            return false;
335
        },
336

    
337
        personality_data_for_keys: function(keys) {
338
            contents = '';
339
            _.each(keys, function(key){
340
                contents = contents + key.get("content") + "\n"
341
            });
342
            contents = $.base64.encode(contents);
343

    
344
            return {
345
                path: this.ssh_keys_path(),
346
                contents: contents
347
            }
348
        }
349
    });
350

    
351
    // Flavor model
352
    models.Flavor = models.Model.extend({
353
        path: 'flavors',
354

    
355
        details_string: function() {
356
            return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk'));
357
        },
358

    
359
        get_disk_size: function() {
360
            return parseInt(this.get("disk") * 1000)
361
        },
362

    
363
        get_disk_template_info: function() {
364
            var info = snf.config.flavors_disk_templates_info[this.get("disk_template")];
365
            if (!info) {
366
                info = { name: this.get("disk_template"), description:'' };
367
            }
368
            return info
369
        }
370

    
371
    });
372
    
373
    models.ParamsList = function(){this.initialize.apply(this, arguments)};
374
    _.extend(models.ParamsList.prototype, bb.Events, {
375

    
376
        initialize: function(parent, param_name) {
377
            this.parent = parent;
378
            this.actions = {};
379
            this.param_name = param_name;
380
            this.length = 0;
381
        },
382
        
383
        has_action: function(action) {
384
            return this.actions[action] ? true : false;
385
        },
386
            
387
        _parse_params: function(arguments) {
388
            if (arguments.length <= 1) {
389
                return [];
390
            }
391

    
392
            var args = _.toArray(arguments);
393
            return args.splice(1);
394
        },
395

    
396
        contains: function(action, params) {
397
            params = this._parse_params(arguments);
398
            var has_action = this.has_action(action);
399
            if (!has_action) { return false };
400

    
401
            var paramsEqual = false;
402
            _.each(this.actions[action], function(action_params) {
403
                if (_.isEqual(action_params, params)) {
404
                    paramsEqual = true;
405
                }
406
            });
407
                
408
            return paramsEqual;
409
        },
410
        
411
        is_empty: function() {
412
            return _.isEmpty(this.actions);
413
        },
414

    
415
        add: function(action, params) {
416
            params = this._parse_params(arguments);
417
            if (this.contains.apply(this, arguments)) { return this };
418
            var isnew = false
419
            if (!this.has_action(action)) {
420
                this.actions[action] = [];
421
                isnew = true;
422
            };
423

    
424
            this.actions[action].push(params);
425
            this.parent.trigger("change:" + this.param_name, this.parent, this);
426
            if (isnew) {
427
                this.trigger("add", action, params);
428
            } else {
429
                this.trigger("change", action, params);
430
            }
431
            return this;
432
        },
433
        
434
        remove_all: function(action) {
435
            if (this.has_action(action)) {
436
                delete this.actions[action];
437
                this.parent.trigger("change:" + this.param_name, this.parent, this);
438
                this.trigger("remove", action);
439
            }
440
            return this;
441
        },
442

    
443
        reset: function() {
444
            this.actions = {};
445
            this.parent.trigger("change:" + this.param_name, this.parent, this);
446
            this.trigger("reset");
447
            this.trigger("remove");
448
        },
449

    
450
        remove: function(action, params) {
451
            params = this._parse_params(arguments);
452
            if (!this.has_action(action)) { return this };
453
            var index = -1;
454
            _.each(this.actions[action], _.bind(function(action_params) {
455
                if (_.isEqual(action_params, params)) {
456
                    index = this.actions[action].indexOf(action_params);
457
                }
458
            }, this));
459
            
460
            if (index > -1) {
461
                this.actions[action].splice(index, 1);
462
                if (_.isEmpty(this.actions[action])) {
463
                    delete this.actions[action];
464
                }
465
                this.parent.trigger("change:" + this.param_name, this.parent, this);
466
                this.trigger("remove", action, params);
467
            }
468
        }
469

    
470
    });
471

    
472
    // Image model
473
    models.Network = models.Model.extend({
474
        path: 'networks',
475
        has_status: true,
476
        defaults: {'connecting':0},
477
        
478
        initialize: function() {
479
            var ret = models.Network.__super__.initialize.apply(this, arguments);
480
            this.set({"actions": new models.ParamsList(this, "actions")});
481
            this.update_state();
482
            this.bind("change:nics", _.bind(synnefo.storage.nics.update_net_nics, synnefo.storage.nics));
483
            this.bind("change:status", _.bind(this.update_state, this));
484
            return ret;
485
        },
486
        
487
        is_deleted: function() {
488
          return this.get('status') == 'DELETED';
489
        },
490

    
491
        toJSON: function() {
492
            var attrs = _.clone(this.attributes);
493
            attrs.actions = _.clone(this.get("actions").actions);
494
            return attrs;
495
        },
496
        
497
        set_state: function(val) {
498
            if (val == "PENDING" && this.get("state") == "DESTORY") {
499
                return "DESTROY";
500
            }
501
            return val;
502
        },
503

    
504
        update_state: function() {
505
            if (this.get("connecting") > 0) {
506
                this.set({state: "CONNECTING"});
507
                return
508
            }
509
            
510
            if (this.get_nics(function(nic){ return nic.get("removing") == 1}).length > 0) {
511
                this.set({state: "DISCONNECTING"});
512
                return
513
            }   
514
            
515
            if (this.contains_firewalling_nics() > 0) {
516
                this.set({state: "FIREWALLING"});
517
                return
518
            }   
519
            
520
            if (this.get("state") == "DESTROY") { 
521
                this.set({"destroyed":1});
522
            }
523
            
524
            this.set({state:this.get('status')});
525
        },
526

    
527
        is_public: function() {
528
            return this.get("public");
529
        },
530

    
531
        decrease_connecting: function() {
532
            var conn = this.get("connecting");
533
            if (!conn) { conn = 0 };
534
            if (conn > 0) {
535
                conn--;
536
            }
537
            this.set({"connecting": conn});
538
            this.update_state();
539
        },
540

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

    
549
        connected_to: function(vm) {
550
            return this.get('linked_to').indexOf(""+vm.id) > -1;
551
        },
552

    
553
        connected_with_nic_id: function(nic_id) {
554
            return _.keys(this.get('nics')).indexOf(nic_id) > -1;
555
        },
556

    
557
        get_nics: function(filter) {
558
            var nics = synnefo.storage.nics.filter(function(nic) {
559
                return nic.get('network_id') == this.id;
560
            }, this);
561

    
562
            if (filter) {
563
                return _.filter(nics, filter);
564
            }
565
            return nics;
566
        },
567

    
568
        contains_firewalling_nics: function() {
569
            return this.get_nics(function(n){return n.get('pending_firewall')}).length
570
        },
571

    
572
        call: function(action, params, success, error) {
573
            if (action == "destroy") {
574
                this.set({state:"DESTROY"});
575
                this.get("actions").remove("destroy", params);
576
                this.remove(_.bind(function(){
577
                    success();
578
                }, this), error);
579
            }
580
            
581
            if (action == "disconnect") {
582
                if (this.get("state") == "DESTROY") {
583
                    return;
584
                }
585

    
586
                _.each(params, _.bind(function(nic_id) {
587
                    var nic = snf.storage.nics.get(nic_id);
588
                    this.get("actions").remove("disconnect", nic_id);
589
                    if (nic) {
590
                        this.remove_nic(nic, success, error);
591
                    }
592
                }, this));
593
            }
594
        },
595

    
596
        add_vm: function (vm, callback, error, options) {
597
            var payload = {add:{serverRef:"" + vm.id}};
598
            payload._options = options || {};
599
            return this.api_call(this.api_path() + "/action", "create", 
600
                                 payload,
601
                                 undefined,
602
                                 error,
603
                                 _.bind(function(){
604
                                     //this.vms.add_pending(vm.id);
605
                                     this.increase_connecting();
606
                                     if (callback) {callback()}
607
                                 },this), error);
608
        },
609

    
610
        remove_nic: function (nic, callback, error, options) {
611
            var payload = {remove:{attachment:"" + nic.get("attachment_id")}};
612
            payload._options = options || {};
613
            return this.api_call(this.api_path() + "/action", "create", 
614
                                 payload,
615
                                 undefined,
616
                                 error,
617
                                 _.bind(function(){
618
                                     nic.set({"removing": 1});
619
                                     nic.get_network().update_state();
620
                                     //this.vms.add_pending_for_remove(vm.id);
621
                                     if (callback) {callback()}
622
                                 },this), error);
623
        },
624

    
625
        rename: function(name, callback) {
626
            return this.api_call(this.api_path(), "update", {
627
                network:{name:name}, 
628
                _options:{
629
                    critical: false, 
630
                    error_params:{
631
                        title: "Network action failed",
632
                        ns: "Networks",
633
                        extra_details: {"Network id": this.id}
634
                    }
635
                }}, callback);
636
        },
637

    
638
        get_connectable_vms: function() {
639
            return storage.vms.filter(function(vm){
640
                return !vm.in_error_state() && !vm.is_building();
641
            })
642
        },
643

    
644
        state_message: function() {
645
            if (this.get("state") == "ACTIVE" && !this.is_public()) {
646
                if (this.get("cidr") && this.get("dhcp") == true) {
647
                    return this.get("cidr");
648
                } else {
649
                    return "Private network";
650
                }
651
            }
652
            if (this.get("state") == "ACTIVE" && this.is_public()) {
653
                  return "Public network";
654
            }
655

    
656
            return models.Network.STATES[this.get("state")];
657
        },
658

    
659
        in_progress: function() {
660
            return models.Network.STATES_TRANSITIONS[this.get("state")] != undefined;
661
        },
662

    
663
        do_all_pending_actions: function(success, error) {
664
            var destroy = this.get("actions").has_action("destroy");
665
            _.each(this.get("actions").actions, _.bind(function(params, action) {
666
                _.each(params, _.bind(function(with_params) {
667
                    this.call(action, with_params, success, error);
668
                }, this));
669
            }, this));
670
            this.get("actions").reset();
671
        }
672
    });
673
    
674
    models.Network.STATES = {
675
        'ACTIVE': 'Private network',
676
        'CONNECTING': 'Connecting...',
677
        'DISCONNECTING': 'Disconnecting...',
678
        'FIREWALLING': 'Firewall update...',
679
        'DESTROY': 'Destroying...',
680
        'PENDING': 'Pending...',
681
        'ERROR': 'Error'
682
    }
683

    
684
    models.Network.STATES_TRANSITIONS = {
685
        'CONNECTING': ['ACTIVE'],
686
        'DISCONNECTING': ['ACTIVE'],
687
        'PENDING': ['ACTIVE'],
688
        'FIREWALLING': ['ACTIVE']
689
    }
690

    
691
    // Virtualmachine model
692
    models.VM = models.Model.extend({
693

    
694
        path: 'servers',
695
        has_status: true,
696
        initialize: function(params) {
697
            
698
            this.pending_firewalls = {};
699
            
700
            models.VM.__super__.initialize.apply(this, arguments);
701

    
702
            this.set({state: params.status || "ERROR"});
703
            this.log = new snf.logging.logger("VM " + this.id);
704
            this.pending_action = undefined;
705
            
706
            // init stats parameter
707
            this.set({'stats': undefined}, {silent: true});
708
            // defaults to not update the stats
709
            // each view should handle this vm attribute 
710
            // depending on if it displays stat images or not
711
            this.do_update_stats = false;
712
            
713
            // interval time
714
            // this will dynamicaly change if the server responds that
715
            // images get refreshed on different intervals
716
            this.stats_update_interval = synnefo.config.STATS_INTERVAL || 5000;
717
            this.stats_available = false;
718

    
719
            // initialize interval
720
            this.init_stats_intervals(this.stats_update_interval);
721
            
722
            // handle progress message on instance change
723
            this.bind("change", _.bind(this.update_status_message, this));
724
            // force update of progress message
725
            this.update_status_message(true);
726
            
727
            // default values
728
            this.bind("change:state", _.bind(function(){
729
                if (this.state() == "DESTROY") { 
730
                    this.handle_destroy() 
731
                }
732
            }, this));
733

    
734
            this.bind("change:nics", _.bind(synnefo.storage.nics.update_vm_nics, synnefo.storage.nics));
735
        },
736

    
737
        status: function(st) {
738
            if (!st) { return this.get("status")}
739
            return this.set({status:st});
740
        },
741

    
742
        set_status: function(st) {
743
            var new_state = this.state_for_api_status(st);
744
            var transition = false;
745

    
746
            if (this.state() != new_state) {
747
                if (models.VM.STATES_TRANSITIONS[this.state()]) {
748
                    transition = this.state();
749
                }
750
            }
751
            
752
            // call it silently to avoid double change trigger
753
            this.set({'state': this.state_for_api_status(st)}, {silent: true});
754
            
755
            // trigger transition
756
            if (transition && models.VM.TRANSITION_STATES.indexOf(new_state) == -1) { 
757
                this.trigger("transition", {from:transition, to:new_state}) 
758
            };
759
            return st;
760
        },
761
            
762
        get_diagnostics: function(success) {
763
            this.__make_api_call(this.get_diagnostics_url(),
764
                                 "read", // create so that sync later uses POST to make the call
765
                                 null, // payload
766
                                 function(data) {
767
                                     success(data);
768
                                 },  
769
                                 null, 'diagnostics');
770
        },
771

    
772
        has_diagnostics: function() {
773
            return this.get("diagnostics") && this.get("diagnostics").length;
774
        },
775

    
776
        get_progress_info: function() {
777
            // details about progress message
778
            // contains a list of diagnostic messages
779
            return this.get("status_messages");
780
        },
781

    
782
        get_status_message: function() {
783
            return this.get('status_message');
784
        },
785
        
786
        // extract status message from diagnostics
787
        status_message_from_diagnostics: function(diagnostics) {
788
            var valid_sources_map = synnefo.config.diagnostics_status_messages_map;
789
            var valid_sources = valid_sources_map[this.get('status')];
790
            if (!valid_sources) { return null };
791
            
792
            // filter messsages based on diagnostic source
793
            var messages = _.filter(diagnostics, function(diag) {
794
                return valid_sources.indexOf(diag.source) > -1;
795
            });
796

    
797
            var msg = messages[0];
798
            if (msg) {
799
              var message = msg.message;
800
              var message_tpl = snf.config.diagnostic_messages_tpls[msg.source];
801

    
802
              if (message_tpl) {
803
                  message = message_tpl.replace('MESSAGE', msg.message);
804
              }
805
              return message;
806
            }
807
            
808
            // no message to display, but vm in build state, display
809
            // finalizing message.
810
            if (this.is_building() == 'BUILD') {
811
                return synnefo.config.BUILDING_MESSAGES['FINAL'];
812
            }
813
            return null;
814
        },
815

    
816
        update_status_message: function(force) {
817
            // update only if one of the specified attributes has changed
818
            if (
819
              !this.keysChanged(['diagnostics', 'progress', 'status', 'state'])
820
                && !force
821
            ) { return };
822
            
823
            // if user requested to destroy the vm set the appropriate 
824
            // message.
825
            if (this.get('state') == "DESTROY") { 
826
                message = "Terminating..."
827
                this.set({status_message: message})
828
                return;
829
            }
830
            
831
            // set error message, if vm has diagnostic message display it as
832
            // progress message
833
            if (this.in_error_state()) {
834
                var d = this.get('diagnostics');
835
                if (d && d.length) {
836
                    var message = this.status_message_from_diagnostics(d);
837
                    this.set({status_message: message});
838
                } else {
839
                    this.set({status_message: null});
840
                }
841
                return;
842
            }
843
            
844
            // identify building status message
845
            if (this.is_building()) {
846
                var self = this;
847
                var success = function(msg) {
848
                    self.set({status_message: msg});
849
                }
850
                this.get_building_status_message(success);
851
                return;
852
            }
853

    
854
            this.set({status_message:null});
855
        },
856
            
857
        // get building status message. Asynchronous function since it requires
858
        // access to vm image.
859
        get_building_status_message: function(callback) {
860
            // no progress is set, vm is in initial build status
861
            var progress = this.get("progress");
862
            if (progress == 0 || !progress) {
863
                return callback(BUILDING_MESSAGES['INIT']);
864
            }
865
            
866
            // vm has copy progress, display copy percentage
867
            if (progress > 0 && progress <= 99) {
868
                this.get_copy_details(true, undefined, _.bind(
869
                    function(details){
870
                        callback(BUILDING_MESSAGES['COPY'].format(details.copy, 
871
                                                           details.size, 
872
                                                           details.progress));
873
                }, this));
874
                return;
875
            }
876

    
877
            // copy finished display FINAL message or identify status message
878
            // from diagnostics.
879
            if (progress >= 100) {
880
                if (!this.has_diagnostics()) {
881
                        callback(BUILDING_MESSAGES['FINAL']);
882
                } else {
883
                        var d = this.get("diagnostics");
884
                        var msg = this.status_message_from_diagnostics(d);
885
                        if (msg) {
886
                              callback(msg);
887
                        }
888
                }
889
            }
890
        },
891

    
892
        get_copy_details: function(human, image, callback) {
893
            var human = human || false;
894
            var image = image || this.get_image(_.bind(function(image){
895
                var progress = this.get('progress');
896
                var size = image.get_size();
897
                var size_copied = (size * progress / 100).toFixed(2);
898
                
899
                if (human) {
900
                    size = util.readablizeBytes(size*1024*1024);
901
                    size_copied = util.readablizeBytes(size_copied*1024*1024);
902
                }
903

    
904
                callback({'progress': progress, 'size': size, 'copy': size_copied})
905
            }, this));
906
        },
907

    
908
        start_stats_update: function(force_if_empty) {
909
            var prev_state = this.do_update_stats;
910

    
911
            this.do_update_stats = true;
912
            
913
            // fetcher initialized ??
914
            if (!this.stats_fetcher) {
915
                this.init_stats_intervals();
916
            }
917

    
918

    
919
            // fetcher running ???
920
            if (!this.stats_fetcher.running || !prev_state) {
921
                this.stats_fetcher.start();
922
            }
923

    
924
            if (force_if_empty && this.get("stats") == undefined) {
925
                this.update_stats(true);
926
            }
927
        },
928

    
929
        stop_stats_update: function(stop_calls) {
930
            this.do_update_stats = false;
931

    
932
            if (stop_calls) {
933
                this.stats_fetcher.stop();
934
            }
935
        },
936

    
937
        // clear and reinitialize update interval
938
        init_stats_intervals: function (interval) {
939
            this.stats_fetcher = this.get_stats_fetcher(this.stats_update_interval);
940
            this.stats_fetcher.start();
941
        },
942
        
943
        get_stats_fetcher: function(timeout) {
944
            var cb = _.bind(function(data){
945
                this.update_stats();
946
            }, this);
947
            var fetcher = new snf.api.updateHandler({'callback': cb, interval: timeout, id:'stats'});
948
            return fetcher;
949
        },
950

    
951
        // do the api call
952
        update_stats: function(force) {
953
            // do not update stats if flag not set
954
            if ((!this.do_update_stats && !force) || this.updating_stats) {
955
                return;
956
            }
957

    
958
            // make the api call, execute handle_stats_update on sucess
959
            // TODO: onError handler ???
960
            stats_url = this.url() + "/stats";
961
            this.updating_stats = true;
962
            this.sync("read", this, {
963
                handles_error:true, 
964
                url: stats_url, 
965
                refresh:true, 
966
                success: _.bind(this.handle_stats_update, this),
967
                error: _.bind(this.handle_stats_error, this),
968
                complete: _.bind(function(){this.updating_stats = false;}, this),
969
                critical: false,
970
                log_error: false,
971
                skips_timeouts: true
972
            });
973
        },
974

    
975
        get_stats_image: function(stat, type) {
976
        },
977
        
978
        _set_stats: function(stats) {
979
            var silent = silent === undefined ? false : silent;
980
            // unavailable stats while building
981
            if (this.get("status") == "BUILD") { 
982
                this.stats_available = false;
983
            } else { this.stats_available = true; }
984

    
985
            if (this.get("status") == "DESTROY") { this.stats_available = false; }
986
            
987
            this.set({stats: stats}, {silent:true});
988
            this.trigger("stats:update", stats);
989
        },
990

    
991
        unbind: function() {
992
            models.VM.__super__.unbind.apply(this, arguments);
993
        },
994

    
995
        handle_stats_error: function() {
996
            stats = {};
997
            _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
998
                stats[k] = false;
999
            });
1000

    
1001
            this.set({'stats': stats});
1002
        },
1003

    
1004
        // this method gets executed after a successful vm stats api call
1005
        handle_stats_update: function(data) {
1006
            var self = this;
1007
            // avoid browser caching
1008
            
1009
            if (data.stats && _.size(data.stats) > 0) {
1010
                var ts = $.now();
1011
                var stats = data.stats;
1012
                var images_loaded = 0;
1013
                var images = {};
1014

    
1015
                function check_images_loaded() {
1016
                    images_loaded++;
1017

    
1018
                    if (images_loaded == 4) {
1019
                        self._set_stats(images);
1020
                    }
1021
                }
1022
                _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
1023
                    
1024
                    stats[k] = stats[k] + "?_=" + ts;
1025
                    
1026
                    var stat = k.slice(0,3);
1027
                    var type = k.slice(3,6) == "Bar" ? "bar" : "time";
1028
                    var img = $("<img />");
1029
                    var val = stats[k];
1030
                    
1031
                    // load stat image to a temporary dom element
1032
                    // update model stats on image load/error events
1033
                    img.load(function() {
1034
                        images[k] = val;
1035
                        check_images_loaded();
1036
                    });
1037

    
1038
                    img.error(function() {
1039
                        images[stat + type] = false;
1040
                        check_images_loaded();
1041
                    });
1042

    
1043
                    img.attr({'src': stats[k]});
1044
                })
1045
                data.stats = stats;
1046
            }
1047

    
1048
            // do we need to change the interval ??
1049
            if (data.stats.refresh * 1000 != this.stats_update_interval) {
1050
                this.stats_update_interval = data.stats.refresh * 1000;
1051
                this.stats_fetcher.interval = this.stats_update_interval;
1052
                this.stats_fetcher.maximum_interval = this.stats_update_interval;
1053
                this.stats_fetcher.stop();
1054
                this.stats_fetcher.start(false);
1055
            }
1056
        },
1057

    
1058
        // helper method that sets the do_update_stats
1059
        // in the future this method could also make an api call
1060
        // immediaetly if needed
1061
        enable_stats_update: function() {
1062
            this.do_update_stats = true;
1063
        },
1064
        
1065
        handle_destroy: function() {
1066
            this.stats_fetcher.stop();
1067
        },
1068

    
1069
        require_reboot: function() {
1070
            if (this.is_active()) {
1071
                this.set({'reboot_required': true});
1072
            }
1073
        },
1074
        
1075
        set_pending_action: function(data) {
1076
            this.pending_action = data;
1077
            return data;
1078
        },
1079

    
1080
        // machine has pending action
1081
        update_pending_action: function(action, force) {
1082
            this.set({pending_action: action});
1083
        },
1084

    
1085
        clear_pending_action: function() {
1086
            this.set({pending_action: undefined});
1087
        },
1088

    
1089
        has_pending_action: function() {
1090
            return this.get("pending_action") ? this.get("pending_action") : false;
1091
        },
1092
        
1093
        // machine is active
1094
        is_active: function() {
1095
            return models.VM.ACTIVE_STATES.indexOf(this.state()) > -1;
1096
        },
1097
        
1098
        // machine is building 
1099
        is_building: function() {
1100
            return models.VM.BUILDING_STATES.indexOf(this.state()) > -1;
1101
        },
1102
        
1103
        in_error_state: function() {
1104
            return this.state() === "ERROR"
1105
        },
1106

    
1107
        // user can connect to machine
1108
        is_connectable: function() {
1109
            // check if ips exist
1110
            if (!this.get_addresses().ip4 && !this.get_addresses().ip6) {
1111
                return false;
1112
            }
1113
            return models.VM.CONNECT_STATES.indexOf(this.state()) > -1;
1114
        },
1115
        
1116
        remove_meta: function(key, complete, error) {
1117
            var url = this.api_path() + "/meta/" + key;
1118
            this.api_call(url, "delete", undefined, complete, error);
1119
        },
1120

    
1121
        save_meta: function(meta, complete, error) {
1122
            var url = this.api_path() + "/meta/" + meta.key;
1123
            var payload = {meta:{}};
1124
            payload.meta[meta.key] = meta.value;
1125
            payload._options = {
1126
                critical:false, 
1127
                error_params: {
1128
                    title: "Machine metadata error",
1129
                    extra_details: {"Machine id": this.id}
1130
            }};
1131

    
1132
            this.api_call(url, "update", payload, complete, error);
1133
        },
1134

    
1135

    
1136
        // update/get the state of the machine
1137
        state: function() {
1138
            var args = slice.call(arguments);
1139
                
1140
            // TODO: it might not be a good idea to set the state in set_state method
1141
            if (args.length > 0 && models.VM.STATES.indexOf(args[0]) > -1) {
1142
                this.set({'state': args[0]});
1143
            }
1144

    
1145
            return this.get('state');
1146
        },
1147
        
1148
        // get the state that the api status corresponds to
1149
        state_for_api_status: function(status) {
1150
            return this.state_transition(this.state(), status);
1151
        },
1152
        
1153
        // vm state equals vm api status
1154
        state_is_status: function(state) {
1155
            return models.VM.STATUSES.indexOf(state) != -1;
1156
        },
1157
        
1158
        // get transition state for the corresponging api status
1159
        state_transition: function(state, new_status) {
1160
            var statuses = models.VM.STATES_TRANSITIONS[state];
1161
            if (statuses) {
1162
                if (statuses.indexOf(new_status) > -1) {
1163
                    return new_status;
1164
                } else {
1165
                    return state;
1166
                }
1167
            } else {
1168
                return new_status;
1169
            }
1170
        },
1171
        
1172
        // the current vm state is a transition state
1173
        in_transition: function() {
1174
            return models.VM.TRANSITION_STATES.indexOf(this.state()) > -1 || 
1175
                models.VM.TRANSITION_STATES.indexOf(this.get('status')) > -1;
1176
        },
1177
        
1178
        // get image object
1179
        get_image: function(callback) {
1180
            if (callback == undefined) { callback = function(){} }
1181
            var image = storage.images.get(this.get('imageRef'));
1182
            if (!image) {
1183
                storage.images.update_unknown_id(this.get('imageRef'), callback);
1184
                return;
1185
            }
1186
            callback(image);
1187
            return image;
1188
        },
1189
        
1190
        // get flavor object
1191
        get_flavor: function() {
1192
            var flv = storage.flavors.get(this.get('flavorRef'));
1193
            if (!flv) {
1194
                storage.flavors.update_unknown_id(this.get('flavorRef'));
1195
                flv = storage.flavors.get(this.get('flavorRef'));
1196
            }
1197
            return flv;
1198
        },
1199

    
1200
        get_meta: function(key, deflt) {
1201
            if (this.get('metadata') && this.get('metadata').values) {
1202
                if (!this.get('metadata').values[key]) { return deflt }
1203
                return _.escape(this.get('metadata').values[key]);
1204
            } else {
1205
                return deflt;
1206
            }
1207
        },
1208

    
1209
        get_meta_keys: function() {
1210
            if (this.get('metadata') && this.get('metadata').values) {
1211
                return _.keys(this.get('metadata').values);
1212
            } else {
1213
                return [];
1214
            }
1215
        },
1216
        
1217
        // get metadata OS value
1218
        get_os: function() {
1219
            var image = this.get_image();
1220
            return this.get_meta('OS') || (image ? 
1221
                                            image.get_os() || "okeanos" : "okeanos");
1222
        },
1223

    
1224
        get_gui: function() {
1225
            return this.get_meta('GUI');
1226
        },
1227
        
1228
        connected_to: function(net) {
1229
            return this.get('linked_to').indexOf(net.id) > -1;
1230
        },
1231

    
1232
        connected_with_nic_id: function(nic_id) {
1233
            return _.keys(this.get('nics')).indexOf(nic_id) > -1;
1234
        },
1235

    
1236
        get_nics: function(filter) {
1237
            ret = synnefo.storage.nics.filter(function(nic) {
1238
                return parseInt(nic.get('vm_id')) == this.id;
1239
            }, this);
1240

    
1241
            if (filter) {
1242
                return _.filter(ret, filter);
1243
            }
1244

    
1245
            return ret;
1246
        },
1247

    
1248
        get_net_nics: function(net_id) {
1249
            return this.get_nics(function(n){return n.get('network_id') == net_id});
1250
        },
1251

    
1252
        get_public_nic: function() {
1253
            return this.get_nics(function(n){ return n.get_network().is_public() === true })[0];
1254
        },
1255

    
1256
        get_hostname: function() {
1257
          var hostname = this.get_meta('hostname');
1258
          if (!hostname) {
1259
            hostname = synnefo.config.vm_hostname_format.format(this.id);
1260
          }
1261
          return hostname;
1262
        },
1263

    
1264
        get_nic: function(net_id) {
1265
        },
1266

    
1267
        has_firewall: function() {
1268
            var nic = this.get_public_nic();
1269
            if (nic) {
1270
                var profile = nic.get('firewallProfile'); 
1271
                return ['ENABLED', 'PROTECTED'].indexOf(profile) > -1;
1272
            }
1273
            return false;
1274
        },
1275

    
1276
        get_firewall_profile: function() {
1277
            var nic = this.get_public_nic();
1278
            if (nic) {
1279
                return nic.get('firewallProfile');
1280
            }
1281
            return null;
1282
        },
1283

    
1284
        get_addresses: function() {
1285
            var pnic = this.get_public_nic();
1286
            if (!pnic) { return {'ip4': undefined, 'ip6': undefined }};
1287
            return {'ip4': pnic.get('ipv4'), 'ip6': pnic.get('ipv6')};
1288
        },
1289
    
1290
        // get actions that the user can execute
1291
        // depending on the vm state/status
1292
        get_available_actions: function() {
1293
            return models.VM.AVAILABLE_ACTIONS[this.state()];
1294
        },
1295

    
1296
        set_profile: function(profile, net_id) {
1297
        },
1298
        
1299
        // call rename api
1300
        rename: function(new_name) {
1301
            //this.set({'name': new_name});
1302
            this.sync("update", this, {
1303
                critical: true,
1304
                data: {
1305
                    'server': {
1306
                        'name': new_name
1307
                    }
1308
                }, 
1309
                // do the rename after the method succeeds
1310
                success: _.bind(function(){
1311
                    //this.set({name: new_name});
1312
                    snf.api.trigger("call");
1313
                }, this)
1314
            });
1315
        },
1316
        
1317
        get_console_url: function(data) {
1318
            var url_params = {
1319
                machine: this.get("name"),
1320
                host_ip: this.get_addresses().ip4,
1321
                host_ip_v6: this.get_addresses().ip6,
1322
                host: data.host,
1323
                port: data.port,
1324
                password: data.password
1325
            }
1326
            return '/machines/console?' + $.param(url_params);
1327
        },
1328

    
1329
        // action helper
1330
        call: function(action_name, success, error, params) {
1331
            var id_param = [this.id];
1332
            
1333
            params = params || {};
1334
            success = success || function() {};
1335
            error = error || function() {};
1336

    
1337
            var self = this;
1338

    
1339
            switch(action_name) {
1340
                case 'start':
1341
                    this.__make_api_call(this.get_action_url(), // vm actions url
1342
                                         "create", // create so that sync later uses POST to make the call
1343
                                         {start:{}}, // payload
1344
                                         function() {
1345
                                             // set state after successful call
1346
                                             self.state("START"); 
1347
                                             success.apply(this, arguments);
1348
                                             snf.api.trigger("call");
1349
                                         },  
1350
                                         error, 'start', params);
1351
                    break;
1352
                case 'reboot':
1353
                    this.__make_api_call(this.get_action_url(), // vm actions url
1354
                                         "create", // create so that sync later uses POST to make the call
1355
                                         {reboot:{type:"HARD"}}, // payload
1356
                                         function() {
1357
                                             // set state after successful call
1358
                                             self.state("REBOOT"); 
1359
                                             success.apply(this, arguments)
1360
                                             snf.api.trigger("call");
1361
                                             self.set({'reboot_required': false});
1362
                                         },
1363
                                         error, 'reboot', params);
1364
                    break;
1365
                case 'shutdown':
1366
                    this.__make_api_call(this.get_action_url(), // vm actions url
1367
                                         "create", // create so that sync later uses POST to make the call
1368
                                         {shutdown:{}}, // payload
1369
                                         function() {
1370
                                             // set state after successful call
1371
                                             self.state("SHUTDOWN"); 
1372
                                             success.apply(this, arguments)
1373
                                             snf.api.trigger("call");
1374
                                         },  
1375
                                         error, 'shutdown', params);
1376
                    break;
1377
                case 'console':
1378
                    this.__make_api_call(this.url() + "/action", "create", {'console': {'type':'vnc'}}, function(data) {
1379
                        var cons_data = data.console;
1380
                        success.apply(this, [cons_data]);
1381
                    }, undefined, 'console', params)
1382
                    break;
1383
                case 'destroy':
1384
                    this.__make_api_call(this.url(), // vm actions url
1385
                                         "delete", // create so that sync later uses POST to make the call
1386
                                         undefined, // payload
1387
                                         function() {
1388
                                             // set state after successful call
1389
                                             self.state('DESTROY');
1390
                                             success.apply(this, arguments)
1391
                                         },  
1392
                                         error, 'destroy', params);
1393
                    break;
1394
                default:
1395
                    throw "Invalid VM action ("+action_name+")";
1396
            }
1397
        },
1398
        
1399
        __make_api_call: function(url, method, data, success, error, action, extra_params) {
1400
            var self = this;
1401
            error = error || function(){};
1402
            success = success || function(){};
1403

    
1404
            var params = {
1405
                url: url,
1406
                data: data,
1407
                success: function(){ self.handle_action_succeed.apply(self, arguments); success.apply(this, arguments)},
1408
                error: function(){ self.handle_action_fail.apply(self, arguments); error.apply(this, arguments)},
1409
                error_params: { ns: "Machines actions", 
1410
                                title: "'" + this.get("name") + "'" + " " + action + " failed", 
1411
                                extra_details: { 'Machine ID': this.id, 'URL': url, 'Action': action || "undefined" },
1412
                                allow_reload: false
1413
                              },
1414
                display: false,
1415
                critical: false
1416
            }
1417
            _.extend(params, extra_params)
1418
            this.sync(method, this, params);
1419
        },
1420

    
1421
        handle_action_succeed: function() {
1422
            this.trigger("action:success", arguments);
1423
        },
1424
        
1425
        reset_action_error: function() {
1426
            this.action_error = false;
1427
            this.trigger("action:fail:reset", this.action_error);
1428
        },
1429

    
1430
        handle_action_fail: function() {
1431
            this.action_error = arguments;
1432
            this.trigger("action:fail", arguments);
1433
        },
1434

    
1435
        get_action_url: function(name) {
1436
            return this.url() + "/action";
1437
        },
1438

    
1439
        get_diagnostics_url: function() {
1440
            return this.url() + "/diagnostics";
1441
        },
1442

    
1443
        get_connection_info: function(host_os, success, error) {
1444
            var url = "/machines/connect";
1445
            params = {
1446
                ip_address: this.get_public_nic().get('ipv4'),
1447
                hostname: this.get_hostname(),
1448
                os: this.get_os(),
1449
                host_os: host_os,
1450
                srv: this.id
1451
            }
1452

    
1453
            url = url + "?" + $.param(params);
1454

    
1455
            var ajax = snf.api.sync("read", undefined, { url: url, 
1456
                                                         error:error, 
1457
                                                         success:success, 
1458
                                                         handles_error:1});
1459
        }
1460
    })
1461
    
1462
    models.VM.ACTIONS = [
1463
        'start',
1464
        'shutdown',
1465
        'reboot',
1466
        'console',
1467
        'destroy'
1468
    ]
1469

    
1470
    models.VM.AVAILABLE_ACTIONS = {
1471
        'UNKNWON'       : ['destroy'],
1472
        'BUILD'         : ['destroy'],
1473
        'REBOOT'        : ['shutdown', 'destroy', 'console'],
1474
        'STOPPED'       : ['start', 'destroy'],
1475
        'ACTIVE'        : ['shutdown', 'destroy', 'reboot', 'console'],
1476
        'ERROR'         : ['destroy'],
1477
        'DELETED'        : [],
1478
        'DESTROY'       : [],
1479
        'BUILD_INIT'    : ['destroy'],
1480
        'BUILD_COPY'    : ['destroy'],
1481
        'BUILD_FINAL'   : ['destroy'],
1482
        'SHUTDOWN'      : ['destroy'],
1483
        'START'         : [],
1484
        'CONNECT'       : [],
1485
        'DISCONNECT'    : []
1486
    }
1487

    
1488
    // api status values
1489
    models.VM.STATUSES = [
1490
        'UNKNWON',
1491
        'BUILD',
1492
        'REBOOT',
1493
        'STOPPED',
1494
        'ACTIVE',
1495
        'ERROR',
1496
        'DELETED'
1497
    ]
1498

    
1499
    // api status values
1500
    models.VM.CONNECT_STATES = [
1501
        'ACTIVE',
1502
        'REBOOT',
1503
        'SHUTDOWN'
1504
    ]
1505

    
1506
    // vm states
1507
    models.VM.STATES = models.VM.STATUSES.concat([
1508
        'DESTROY',
1509
        'BUILD_INIT',
1510
        'BUILD_COPY',
1511
        'BUILD_FINAL',
1512
        'SHUTDOWN',
1513
        'START',
1514
        'CONNECT',
1515
        'DISCONNECT',
1516
        'FIREWALL'
1517
    ]);
1518
    
1519
    models.VM.STATES_TRANSITIONS = {
1520
        'DESTROY' : ['DELETED'],
1521
        'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'],
1522
        'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY'],
1523
        'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY'],
1524
        'START': ['ERROR', 'ACTIVE', 'DESTROY'],
1525
        'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'],
1526
        'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'],
1527
        'BUILD_COPY': ['ERROR', 'ACTIVE', 'BUILD_FINAL', 'DESTROY'],
1528
        'BUILD_FINAL': ['ERROR', 'ACTIVE', 'DESTROY'],
1529
        'BUILD_INIT': ['ERROR', 'ACTIVE', 'BUILD_COPY', 'BUILD_FINAL', 'DESTROY']
1530
    }
1531

    
1532
    models.VM.TRANSITION_STATES = [
1533
        'DESTROY',
1534
        'SHUTDOWN',
1535
        'START',
1536
        'REBOOT',
1537
        'BUILD'
1538
    ]
1539

    
1540
    models.VM.ACTIVE_STATES = [
1541
        'BUILD', 'REBOOT', 'ACTIVE',
1542
        'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL',
1543
        'SHUTDOWN', 'CONNECT', 'DISCONNECT'
1544
    ]
1545

    
1546
    models.VM.BUILDING_STATES = [
1547
        'BUILD', 'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL'
1548
    ]
1549

    
1550
    models.Networks = models.Collection.extend({
1551
        model: models.Network,
1552
        path: 'networks',
1553
        details: true,
1554
        //noUpdate: true,
1555
        defaults: {'nics':[],'linked_to':[]},
1556
        
1557
        parse: function (resp, xhr) {
1558
            // FIXME: depricated global var
1559
            if (!resp) { return []};
1560
            var data = _.map(resp.networks.values, _.bind(this.parse_net_api_data, this));
1561
            return data;
1562
        },
1563

    
1564
        add: function() {
1565
            ret = models.Networks.__super__.add.apply(this, arguments);
1566
            // update nics after each network addition
1567
            ret.each(function(r){
1568
                synnefo.storage.nics.update_net_nics(r);
1569
            });
1570
        },
1571

    
1572
        reset_pending_actions: function() {
1573
            this.each(function(net) {
1574
                net.get("actions").reset();
1575
            });
1576
        },
1577

    
1578
        do_all_pending_actions: function() {
1579
            this.each(function(net) {
1580
                net.do_all_pending_actions();
1581
            })
1582
        },
1583

    
1584
        parse_net_api_data: function(data) {
1585
            // append nic metadata
1586
            // net.get('nics') contains a list of vm/index objects 
1587
            // e.g. {'vm_id':12231, 'index':1}
1588
            // net.get('linked_to') contains a list of vms the network is 
1589
            // connected to e.g. [1001, 1002]
1590
            if (data.attachments && data.attachments.values) {
1591
                data['nics'] = {};
1592
                data['linked_to'] = [];
1593
                _.each(data.attachments.values, function(nic_id){
1594
                  
1595
                  var vm_id = NIC_REGEX.exec(nic_id)[1];
1596
                  var nic_index = parseInt(NIC_REGEX.exec(nic_id)[2]);
1597

    
1598
                  if (vm_id !== undefined && nic_index !== undefined) {
1599
                      data['nics'][nic_id] = {
1600
                          'vm_id': vm_id, 
1601
                          'index': nic_index, 
1602
                          'id': nic_id
1603
                      };
1604
                      if (data['linked_to'].indexOf(vm_id) == -1) {
1605
                        data['linked_to'].push(vm_id);
1606
                      }
1607
                  }
1608
                });
1609
            }
1610
            return data;
1611
        },
1612

    
1613
        create: function (name, type, cidr, dhcp, callback) {
1614
            var params = {
1615
                network:{
1616
                    name:name
1617
                }
1618
            };
1619

    
1620
            if (type) {
1621
                params.network.type = type;
1622
            }
1623
            if (cidr) {
1624
                params.network.cidr = cidr;
1625
            }
1626
            if (dhcp) {
1627
                params.network.dhcp = dhcp;
1628
            }
1629

    
1630
            if (dhcp === false) {
1631
                params.network.dhcp = false;
1632
            }
1633
            
1634
            return this.api_call(this.path, "create", params, callback);
1635
        },
1636

    
1637
        get_public: function(){
1638
          return this.filter(function(n){return n.get('public')});
1639
        }
1640
    })
1641

    
1642
    models.Images = models.Collection.extend({
1643
        model: models.Image,
1644
        path: 'images',
1645
        details: true,
1646
        noUpdate: true,
1647
        supportIncUpdates: false,
1648
        meta_keys_as_attrs: ["OS", "description", "kernel", "size", "GUI"],
1649
        meta_labels: {},
1650
        read_method: 'read',
1651

    
1652
        // update collection model with id passed
1653
        // making a direct call to the image
1654
        // api url
1655
        update_unknown_id: function(id, callback) {
1656
            var url = getUrl.call(this) + "/" + id;
1657
            this.api_call(this.path + "/" + id, this.read_method, {
1658
              _options:{
1659
                async:true, 
1660
                skip_api_error:true}
1661
              }, undefined, 
1662
            _.bind(function() {
1663
                if (!this.get(id)) {
1664
                            if (this.fallback_service) {
1665
                        // if current service has fallback_service attribute set
1666
                        // use this service to retrieve the missing image model
1667
                        var tmpservice = new this.fallback_service();
1668
                        tmpservice.update_unknown_id(id, _.bind(function(img){
1669
                            img.attributes.status = "DELETED";
1670
                            this.add(img.attributes);
1671
                            callback(this.get(id));
1672
                        }, this));
1673
                    } else {
1674
                        var title = synnefo.config.image_deleted_title || 'Deleted';
1675
                        // else add a dummy DELETED state image entry
1676
                        this.add({id:id, name:title, size:-1, 
1677
                                  progress:100, status:"DELETED"});
1678
                        callback(this.get(id));
1679
                    }   
1680
                } else {
1681
                    callback(this.get(id));
1682
                }
1683
            }, this), _.bind(function(image, msg, xhr) {
1684
                if (!image) {
1685
                    var title = synnefo.config.image_deleted_title || 'Deleted';
1686
                    this.add({id:id, name:title, size:-1, 
1687
                              progress:100, status:"DELETED"});
1688
                    callback(this.get(id));
1689
                    return;
1690
                }
1691
                var img_data = this._read_image_from_request(image, msg, xhr);
1692
                this.add(img_data);
1693
                callback(this.get(id));
1694
            }, this));
1695
        },
1696

    
1697
        _read_image_from_request: function(image, msg, xhr) {
1698
            return image.image;
1699
        },
1700

    
1701
        parse: function (resp, xhr) {
1702
            var data = _.map(resp.images.values, _.bind(this.parse_meta, this));
1703
            return resp.images.values;
1704
        },
1705

    
1706
        get_meta_key: function(img, key) {
1707
            if (img.metadata && img.metadata.values && img.metadata.values[key]) {
1708
                return _.escape(img.metadata.values[key]);
1709
            }
1710
            return undefined;
1711
        },
1712

    
1713
        comparator: function(img) {
1714
            return -img.get_sort_order("sortorder") || 1000 * img.id;
1715
        },
1716

    
1717
        parse_meta: function(img) {
1718
            _.each(this.meta_keys_as_attrs, _.bind(function(key){
1719
                if (img[key]) { return };
1720
                img[key] = this.get_meta_key(img, key) || "";
1721
            }, this));
1722
            return img;
1723
        },
1724

    
1725
        active: function() {
1726
            return this.filter(function(img){return img.get('status') != "DELETED"});
1727
        },
1728

    
1729
        predefined: function() {
1730
            return _.filter(this.active(), function(i) { return !i.get("serverRef")});
1731
        },
1732
        
1733
        fetch_for_type: function(type, complete, error) {
1734
            this.fetch({update:true, 
1735
                        success: complete, 
1736
                        error: error, 
1737
                        skip_api_error: true });
1738
        },
1739
        
1740
        get_images_for_type: function(type) {
1741
            if (this['get_{0}_images'.format(type)]) {
1742
                return this['get_{0}_images'.format(type)]();
1743
            }
1744

    
1745
            return this.active();
1746
        },
1747

    
1748
        update_images_for_type: function(type, onStart, onComplete, onError, force_load) {
1749
            var load = false;
1750
            error = onError || function() {};
1751
            function complete(collection) { 
1752
                onComplete(collection.get_images_for_type(type)); 
1753
            }
1754
            
1755
            // do we need to fetch/update current collection entries
1756
            if (load) {
1757
                onStart();
1758
                this.fetch_for_type(type, complete, error);
1759
            } else {
1760
                // fallback to complete
1761
                complete(this);
1762
            }
1763
        }
1764
    })
1765

    
1766
    models.Flavors = models.Collection.extend({
1767
        model: models.Flavor,
1768
        path: 'flavors',
1769
        details: true,
1770
        noUpdate: true,
1771
        supportIncUpdates: false,
1772
        // update collection model with id passed
1773
        // making a direct call to the flavor
1774
        // api url
1775
        update_unknown_id: function(id, callback) {
1776
            var url = getUrl.call(this) + "/" + id;
1777
            this.api_call(this.path + "/" + id, "read", {_options:{async:false, skip_api_error:true}}, undefined, 
1778
            _.bind(function() {
1779
                this.add({id:id, cpu:"Unknown", ram:"Unknown", disk:"Unknown", name: "Unknown", status:"DELETED"})
1780
            }, this), _.bind(function(flv) {
1781
                if (!flv.flavor.status) { flv.flavor.status = "DELETED" };
1782
                this.add(flv.flavor);
1783
            }, this));
1784
        },
1785

    
1786
        parse: function (resp, xhr) {
1787
            return _.map(resp.flavors.values, function(o) { o.disk_template = o['SNF:disk_template']; return o});
1788
        },
1789

    
1790
        comparator: function(flv) {
1791
            return flv.get("disk") * flv.get("cpu") * flv.get("ram");
1792
        },
1793

    
1794
        unavailable_values_for_image: function(img, flavors) {
1795
            var flavors = flavors || this.active();
1796
            var size = img.get_size();
1797
            
1798
            var index = {cpu:[], disk:[], ram:[]};
1799

    
1800
            _.each(this.active(), function(el) {
1801
                var img_size = size;
1802
                var flv_size = el.get_disk_size();
1803
                if (flv_size < img_size) {
1804
                    if (index.disk.indexOf(flv_size) == -1) {
1805
                        index.disk.push(flv_size);
1806
                    }
1807
                };
1808
            });
1809
            
1810
            return index;
1811
        },
1812

    
1813
        get_flavor: function(cpu, mem, disk, disk_template, filter_list) {
1814
            if (!filter_list) { filter_list = this.models };
1815
            
1816
            return this.select(function(flv){
1817
                if (flv.get("cpu") == cpu + "" &&
1818
                   flv.get("ram") == mem + "" &&
1819
                   flv.get("disk") == disk + "" &&
1820
                   flv.get("disk_template") == disk_template &&
1821
                   filter_list.indexOf(flv) > -1) { return true; }
1822
            })[0];
1823
        },
1824
        
1825
        get_data: function(lst) {
1826
            var data = {'cpu': [], 'mem':[], 'disk':[]};
1827

    
1828
            _.each(lst, function(flv) {
1829
                if (data.cpu.indexOf(flv.get("cpu")) == -1) {
1830
                    data.cpu.push(flv.get("cpu"));
1831
                }
1832
                if (data.mem.indexOf(flv.get("ram")) == -1) {
1833
                    data.mem.push(flv.get("ram"));
1834
                }
1835
                if (data.disk.indexOf(flv.get("disk")) == -1) {
1836
                    data.disk.push(flv.get("disk"));
1837
                }
1838
            })
1839
            
1840
            return data;
1841
        },
1842

    
1843
        active: function() {
1844
            return this.filter(function(flv){return flv.get('status') != "DELETED"});
1845
        }
1846
            
1847
    })
1848

    
1849
    models.VMS = models.Collection.extend({
1850
        model: models.VM,
1851
        path: 'servers',
1852
        details: true,
1853
        copy_image_meta: true,
1854

    
1855
        parse: function (resp, xhr) {
1856
            var data = resp;
1857
            if (!resp) { return [] };
1858
            data = _.filter(_.map(resp.servers.values, _.bind(this.parse_vm_api_data, this)), function(v){return v});
1859
            return data;
1860
        },
1861

    
1862
        parse_vm_api_data: function(data) {
1863
            // do not add non existing DELETED entries
1864
            if (data.status && data.status == "DELETED") {
1865
                if (!this.get(data.id)) {
1866
                    return false;
1867
                }
1868
            }
1869

    
1870
            // OS attribute
1871
            if (this.has_meta(data)) {
1872
                data['OS'] = data.metadata.values.OS || "okeanos";
1873
            }
1874
            
1875
            if (!data.diagnostics) {
1876
                data.diagnostics = [];
1877
            }
1878

    
1879
            // network metadata
1880
            data['firewalls'] = {};
1881
            data['nics'] = {};
1882
            data['linked_to'] = [];
1883

    
1884
            if (data['attachments'] && data['attachments'].values) {
1885
                var nics = data['attachments'].values;
1886
                _.each(nics, function(nic) {
1887
                    var net_id = nic.network_id;
1888
                    var index = parseInt(NIC_REGEX.exec(nic.id)[2]);
1889
                    if (data['linked_to'].indexOf(net_id) == -1) {
1890
                        data['linked_to'].push(net_id);
1891
                    }
1892

    
1893
                    data['nics'][nic.id] = nic;
1894
                })
1895
            }
1896
            
1897
            // if vm has no metadata, no metadata object
1898
            // is in json response, reset it to force
1899
            // value update
1900
            if (!data['metadata']) {
1901
                data['metadata'] = {values:{}};
1902
            }
1903

    
1904
            return data;
1905
        },
1906

    
1907
        add: function() {
1908
            ret = models.VMS.__super__.add.apply(this, arguments);
1909
            ret.each(function(r){
1910
                synnefo.storage.nics.update_vm_nics(r);
1911
            });
1912
        },
1913
        
1914
        get_reboot_required: function() {
1915
            return this.filter(function(vm){return vm.get("reboot_required") == true})
1916
        },
1917

    
1918
        has_pending_actions: function() {
1919
            return this.filter(function(vm){return vm.pending_action}).length > 0;
1920
        },
1921

    
1922
        reset_pending_actions: function() {
1923
            this.each(function(vm) {
1924
                vm.clear_pending_action();
1925
            })
1926
        },
1927

    
1928
        do_all_pending_actions: function(success, error) {
1929
            this.each(function(vm) {
1930
                if (vm.has_pending_action()) {
1931
                    vm.call(vm.pending_action, success, error);
1932
                    vm.clear_pending_action();
1933
                }
1934
            })
1935
        },
1936
        
1937
        do_all_reboots: function(success, error) {
1938
            this.each(function(vm) {
1939
                if (vm.get("reboot_required")) {
1940
                    vm.call("reboot", success, error);
1941
                }
1942
            });
1943
        },
1944

    
1945
        reset_reboot_required: function() {
1946
            this.each(function(vm) {
1947
                vm.set({'reboot_required': undefined});
1948
            })
1949
        },
1950
        
1951
        stop_stats_update: function(exclude) {
1952
            var exclude = exclude || [];
1953
            this.each(function(vm) {
1954
                if (exclude.indexOf(vm) > -1) {
1955
                    return;
1956
                }
1957
                vm.stop_stats_update();
1958
            })
1959
        },
1960
        
1961
        has_meta: function(vm_data) {
1962
            return vm_data.metadata && vm_data.metadata.values
1963
        },
1964

    
1965
        has_addresses: function(vm_data) {
1966
            return vm_data.metadata && vm_data.metadata.values
1967
        },
1968

    
1969
        create: function (name, image, flavor, meta, extra, callback) {
1970

    
1971
            if (this.copy_image_meta) {
1972
                if (synnefo.config.vm_image_common_metadata) {
1973
                    _.each(synnefo.config.vm_image_common_metadata, 
1974
                        function(key){
1975
                            if (image.get_meta(key)) {
1976
                                meta[key] = image.get_meta(key);
1977
                            }
1978
                    });
1979
                }
1980

    
1981
                if (image.get("OS")) {
1982
                    meta['OS'] = image.get("OS");
1983
                }
1984
            }
1985
            
1986
            opts = {name: name, imageRef: image.id, flavorRef: flavor.id, metadata:meta}
1987
            opts = _.extend(opts, extra);
1988

    
1989
            this.api_call(this.path, "create", {'server': opts}, undefined, undefined, callback, {critical: true});
1990
        }
1991

    
1992
    })
1993
    
1994
    models.NIC = models.Model.extend({
1995
        
1996
        initialize: function() {
1997
            models.NIC.__super__.initialize.apply(this, arguments);
1998
            this.pending_for_firewall = false;
1999
            this.bind("change:firewallProfile", _.bind(this.check_firewall, this));
2000
            this.bind("change:pending_firewall", function(nic) {
2001
                nic.get_network().update_state();
2002
            });
2003
            this.get_vm().bind("remove", function(){
2004
                try {
2005
                    this.collection.remove(this);
2006
                } catch (err) {};
2007
            }, this);
2008
            this.get_network().bind("remove", function(){
2009
                try {
2010
                    this.collection.remove(this);
2011
                } catch (err) {};
2012
            }, this);
2013

    
2014
        },
2015

    
2016
        get_vm: function() {
2017
            return synnefo.storage.vms.get(parseInt(this.get('vm_id')));
2018
        },
2019

    
2020
        get_network: function() {
2021
            return synnefo.storage.networks.get(this.get('network_id'));
2022
        },
2023

    
2024
        get_v6_address: function() {
2025
            return this.get("ipv6");
2026
        },
2027

    
2028
        get_v4_address: function() {
2029
            return this.get("ipv4");
2030
        },
2031

    
2032
        set_firewall: function(value, callback, error, options) {
2033
            var net_id = this.get('network_id');
2034
            var self = this;
2035

    
2036
            // api call data
2037
            var payload = {"firewallProfile":{"profile":value}};
2038
            payload._options = _.extend({critical: false}, options);
2039
            
2040
            this.set({'pending_firewall': value});
2041
            this.set({'pending_firewall_sending': true});
2042
            this.set({'pending_firewall_from': this.get('firewallProfile')});
2043

    
2044
            var success_cb = function() {
2045
                if (callback) {
2046
                    callback();
2047
                }
2048
                self.set({'pending_firewall_sending': false});
2049
            };
2050

    
2051
            var error_cb = function() {
2052
                self.reset_pending_firewall();
2053
            }
2054
            
2055
            this.get_vm().api_call(this.get_vm().api_path() + "/action", "create", payload, success_cb, error_cb);
2056
        },
2057

    
2058
        reset_pending_firewall: function() {
2059
            this.set({'pending_firewall': false});
2060
            this.set({'pending_firewall': false});
2061
        },
2062

    
2063
        check_firewall: function() {
2064
            var firewall = this.get('firewallProfile');
2065
            var pending = this.get('pending_firewall');
2066
            var previous = this.get('pending_firewall_from');
2067
            if (previous != firewall) { this.get_vm().require_reboot() };
2068
            this.reset_pending_firewall();
2069
        }
2070
        
2071
    });
2072

    
2073
    models.NICs = models.Collection.extend({
2074
        model: models.NIC,
2075
        
2076
        add_or_update: function(nic_id, data, vm) {
2077
            var params = _.clone(data);
2078
            var vm;
2079
            params.attachment_id = params.id;
2080
            params.id = params.id + '-' + params.network_id;
2081
            params.vm_id = parseInt(NIC_REGEX.exec(nic_id)[1]);
2082

    
2083
            if (!this.get(params.id)) {
2084
                this.add(params);
2085
                var nic = this.get(params.id);
2086
                vm = nic.get_vm();
2087
                nic.get_network().decrease_connecting();
2088
                nic.bind("remove", function() {
2089
                    nic.set({"removing": 0});
2090
                    if (this.get_network()) {
2091
                        // network might got removed before nic
2092
                        nic.get_network().update_state();
2093
                    }
2094
                });
2095

    
2096
            } else {
2097
                this.get(params.id).set(params);
2098
                vm = this.get(params.id).get_vm();
2099
            }
2100
            
2101
            // vm nics changed, trigger vm update
2102
            if (vm) { vm.trigger("change", vm)};
2103
        },
2104
        
2105
        reset_nics: function(nics, filter_attr, filter_val) {
2106
            var nics_to_check = this.filter(function(nic) {
2107
                return nic.get(filter_attr) == filter_val;
2108
            });
2109
            
2110
            _.each(nics_to_check, function(nic) {
2111
                if (nics.indexOf(nic.get('id')) == -1) {
2112
                    this.remove(nic);
2113
                } else {
2114
                }
2115
            }, this);
2116
        },
2117

    
2118
        update_vm_nics: function(vm) {
2119
            var nics = vm.get('nics');
2120
            this.reset_nics(_.map(nics, function(nic, key){
2121
                return key + "-" + nic.network_id;
2122
            }), 'vm_id', vm.id);
2123

    
2124
            _.each(nics, function(val, key) {
2125
                var net = synnefo.storage.networks.get(val.network_id);
2126
                if (net && net.connected_with_nic_id(key) && vm.connected_with_nic_id(key)) {
2127
                    this.add_or_update(key, vm.get('nics')[key], vm);
2128
                }
2129
            }, this);
2130
        },
2131

    
2132
        update_net_nics: function(net) {
2133
            var nics = net.get('nics');
2134
            this.reset_nics(_.map(nics, function(nic, key){
2135
                return key + "-" + net.get('id');
2136
            }), 'network_id', net.id);
2137

    
2138
            _.each(nics, function(val, key) {
2139
                var vm = synnefo.storage.vms.get(val.vm_id);
2140
                if (vm && net.connected_with_nic_id(key) && vm.connected_with_nic_id(key)) {
2141
                    this.add_or_update(key, vm.get('nics')[key], vm);
2142
                }
2143
            }, this);
2144
        }
2145
    });
2146

    
2147
    models.PublicKey = models.Model.extend({
2148
        path: 'keys',
2149
        base_url: '/ui/userdata',
2150
        details: false,
2151
        noUpdate: true,
2152

    
2153

    
2154
        get_public_key: function() {
2155
            return cryptico.publicKeyFromString(this.get("content"));
2156
        },
2157

    
2158
        get_filename: function() {
2159
            return "{0}.pub".format(this.get("name"));
2160
        },
2161

    
2162
        identify_type: function() {
2163
            try {
2164
                var cont = snf.util.validatePublicKey(this.get("content"));
2165
                var type = cont.split(" ")[0];
2166
                return synnefo.util.publicKeyTypesMap[type];
2167
            } catch (err) { return false };
2168
        }
2169

    
2170
    })
2171
    
2172
    models.PublicKeys = models.Collection.extend({
2173
        model: models.PublicKey,
2174
        details: false,
2175
        path: 'keys',
2176
        base_url: '/ui/userdata',
2177
        noUpdate: true,
2178

    
2179
        generate_new: function(success, error) {
2180
            snf.api.sync('create', undefined, {
2181
                url: getUrl.call(this, this.base_url) + "/generate", 
2182
                success: success, 
2183
                error: error,
2184
                skip_api_error: true
2185
            });
2186
        },
2187

    
2188
        add_crypto_key: function(key, success, error, options) {
2189
            var options = options || {};
2190
            var m = new models.PublicKey();
2191

    
2192
            // guess a name
2193
            var name_tpl = "my generated public key";
2194
            var name = name_tpl;
2195
            var name_count = 1;
2196
            
2197
            while(this.filter(function(m){ return m.get("name") == name }).length > 0) {
2198
                name = name_tpl + " " + name_count;
2199
                name_count++;
2200
            }
2201
            
2202
            m.set({name: name});
2203
            m.set({content: key});
2204
            
2205
            options.success = function () { return success(m) };
2206
            options.errror = error;
2207
            options.skip_api_error = true;
2208
            
2209
            this.create(m.attributes, options);
2210
        }
2211
    })
2212
    
2213
    // storage initialization
2214
    snf.storage.images = new models.Images();
2215
    snf.storage.flavors = new models.Flavors();
2216
    snf.storage.networks = new models.Networks();
2217
    snf.storage.vms = new models.VMS();
2218
    snf.storage.keys = new models.PublicKeys();
2219
    snf.storage.nics = new models.NICs();
2220

    
2221
    //snf.storage.vms.fetch({update:true});
2222
    //snf.storage.images.fetch({update:true});
2223
    //snf.storage.flavors.fetch({update:true});
2224

    
2225
})(this);