Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / models.js @ 25b225a5

History | View | Annotate | Download (52.2 kB)

1
;(function(root){
2
    
3
    // root
4
    var root = root;
5
    
6
    // setup namepsaces
7
    var snf = root.synnefo = root.synnefo || {};
8
    var models = snf.models = snf.models || {}
9
    var storage = snf.storage = snf.storage || {};
10
    var util = snf.util = snf.util || {};
11

    
12
    // shortcuts
13
    var bb = root.Backbone;
14
    var slice = Array.prototype.slice
15

    
16
    // logging
17
    var logger = new snf.logging.logger("SNF-MODELS");
18
    var debug = _.bind(logger.debug, logger);
19
    
20
    // get url helper
21
    var getUrl = function() {
22
        return snf.config.api_url + "/" + this.path;
23
    }
24
    
25
    // i18n
26
    BUILDING_MESSAGES = window.BUILDING_MESSAGES || {'INIT': 'init', 'COPY': '{0}, {1}, {2}', 'FINAL': 'final'};
27

    
28
    // Base object for all our models
29
    models.Model = bb.Model.extend({
30
        sync: snf.api.sync,
31
        api: snf.api,
32
        has_status: false,
33

    
34
        initialize: function() {
35
            if (this.has_status) {
36
                this.bind("change:status", this.handle_remove);
37
                this.handle_remove();
38
            }
39
            models.Model.__super__.initialize.apply(this, arguments)
40
        },
41

    
42
        handle_remove: function() {
43
            if (this.get("status") == 'DELETED') {
44
                if (this.collection) {
45
                    try { this.clear_pending_action();} catch (err) {};
46
                    try { this.reset_pending_actions();} catch (err) {};
47
                    try { this.stop_stats_update();} catch (err) {};
48
                    this.collection.remove(this.id);
49
                }
50
            }
51
        },
52
        
53
        // custom set method to allow submodels to use
54
        // set_<attr> methods for handling the value of each
55
        // attribute and overriding the default set method
56
        // for specific parameters
57
        set: function(params, options) {
58
            _.each(params, _.bind(function(value, key){
59
                if (this["set_" + key]) {
60
                    params[key] = this["set_" + key](value);
61
                }
62
            }, this))
63
            var ret = bb.Model.prototype.set.call(this, params, options);
64
            return ret;
65
        },
66

    
67
        url: function(options) {
68
            return getUrl.call(this) + "/" + this.id;
69
        },
70

    
71
        api_path: function(options) {
72
            return this.path + "/" + this.id;
73
        },
74

    
75
        parse: function(resp, xhr) {
76
            return resp.server;
77
        },
78

    
79
        remove: function() {
80
            this.api.call(this.api_path(), "delete");
81
        },
82

    
83
        changedKeys: function() {
84
            return _.keys(this.changedAttributes() || {});
85
        },
86

    
87
        hasOnlyChange: function(keys) {
88
            var ret = false;
89
            _.each(keys, _.bind(function(key) {
90
                if (this.changedKeys().length == 1 && this.changedKeys().indexOf(key) > -1) { ret = true};
91
            }, this));
92
            return ret;
93
        }
94

    
95
    })
96
    
97
    // Base object for all our model collections
98
    models.Collection = bb.Collection.extend({
99
        sync: snf.api.sync,
100
        api: snf.api,
101

    
102
        url: function(options) {
103
            return getUrl.call(this) + (options.details || this.details ? '/detail' : '');
104
        },
105

    
106
        fetch: function(options) {
107
            // default to update
108
            if (!this.noUpdate) {
109
                if (!options) { options = {} };
110
                if (options.update === undefined) { options.update = true };
111
                if (!options.removeMissing && options.refresh) { options.removeMissing = true };
112
            }
113
            // custom event foreach fetch
114
            return bb.Collection.prototype.fetch.call(this, options)
115
        },
116

    
117
        get_fetcher: function(timeout, fast, limit, initial, params) {
118
            var fetch_params = params || {};
119
            fetch_params.skips_timeouts = true;
120

    
121
            var timeout = parseInt(timeout);
122
            var fast = fast || 1000;
123
            var limit = limit;
124
            var initial_call = initial || true;
125
            
126
            var last_ajax = undefined;
127
            var cb = _.bind(function() {
128
                // clone to avoid referenced objects
129
                var params = _.clone(fetch_params);
130
                updater._ajax = last_ajax;
131
                if (last_ajax) {
132
                    last_ajax.abort();
133
                }
134
                last_ajax = this.fetch(params);
135
            }, this);
136
            var updater = new snf.api.updateHandler({'callback': cb, timeout:timeout, 
137
                                                    fast:fast, limit:limit, 
138
                                                    call_on_start:initial_call});
139

    
140
            snf.api.bind("call", _.throttle(_.bind(function(){ updater.faster(true)}, this)), 1000);
141
            return updater;
142
        }
143
    });
144
    
145
    // Image model
146
    models.Image = models.Model.extend({
147
        path: 'images',
148

    
149
        get_size: function() {
150
            return parseInt(this.get('metadata') ? this.get('metadata').values.size : -1)
151
        },
152

    
153
        get_os: function() {
154
            return this.get("OS");
155
        },
156

    
157
        get_sort_order: function() {
158
            return parseInt(this.get('metadata') ? this.get('metadata').values.sortorder : -1)
159
        }
160
    });
161

    
162
    // Flavor model
163
    models.Flavor = models.Model.extend({
164
        path: 'flavors',
165

    
166
        details_string: function() {
167
            return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk'));
168
        },
169

    
170
        get_disk_size: function() {
171
            return parseInt(this.get("disk") * 1000)
172
        }
173

    
174
    });
175
    
176
    //network vms list helper
177
    var NetworkVMSList = function() {
178
        this.initialize = function() {
179
            this.vms = [];
180
            this.pending = [];
181
            this.pending_for_removal = [];
182
        }
183
        
184
        this.add_pending_for_remove = function(vm_id) {
185
            if (this.pending_for_removal.indexOf(vm_id) == -1) {
186
                this.pending_for_removal.push(vm_id);
187
            }
188

    
189
            if (this.pending_for_removal.length) {
190
                this.trigger("pending:remove:add");
191
            }
192
        },
193

    
194
        this.add_pending = function(vm_id) {
195
            if (this.pending.indexOf(vm_id) == -1) {
196
                this.pending[this.pending.length] = vm_id;
197
            }
198

    
199
            if (this.pending.length) {
200
                this.trigger("pending:add");
201
            }
202
        }
203

    
204
        this.check_pending = function() {
205
            var len = this.pending.length;
206
            var args = [this.pending];
207
            this.pending = _.difference(this.pending, this.vms);
208
            if (len != this.pending.length) {
209
                if (this.pending.length == 0) {
210
                    this.trigger("pending:clear");
211
                }
212
            }
213

    
214
            var len = this.pending_for_removal.length;
215
            this.pending_for_removal = _.intersection(this.pending_for_removal, this.vms);
216
            if (this.pending_for_removal.length == 0) {
217
                this.trigger("pending:remove:clear");
218
            }
219

    
220
        }
221

    
222

    
223
        this.add = function(vm_id) {
224
            if (this.vms.indexOf(vm_id) == -1) {
225
                this.vms[this.vms.length] = vm_id;
226
                this.trigger("network:connect", vm_id);
227
                this.check_pending();
228
                return true;
229
            }
230
        }
231

    
232
        this.remove = function(vm_id) {
233
            if (this.vms.indexOf(vm_id) > -1) {
234
                this.vms = _.without(this.vms, vm_id);
235
                this.trigger("network:disconnect", vm_id);
236
                this.check_pending();
237
                return true;
238
            }
239
        }
240

    
241
        this.get = function() {
242
            return this.vms;
243
        }
244

    
245
        this.list = function() {
246
            return storage.vms.filter(_.bind(function(vm){
247
                return this.vms.indexOf(vm.id) > -1;
248
            }, this))
249
        }
250

    
251
        this.initialize();
252
    };
253
    _.extend(NetworkVMSList.prototype, bb.Events);
254
    
255
    // vm networks list helper
256
    var VMNetworksList = function() {
257
        this.initialize = function() {
258
            this.networks = {};
259
            this.network_ids = [];
260
        }
261

    
262
        this.add = function(net_id, data) {
263
            if (!this.networks[net_id]) {
264
                this.networks[net_id] = data || {};
265
                this.network_ids[this.network_ids.length] = net_id;
266
                this.trigger("network:connect", net_id);
267
                return true;
268
            }
269
        }
270

    
271
        this.remove = function(net_id) {
272
            if (this.networks[net_id]) {
273
                delete this.networks[net_id];
274
                this.network_ids = _.without(this.network_ids, net_id);
275
                this.trigger("network:disconnect", net_id);
276
                return true;
277
            }
278
            return false;
279
        }
280

    
281
        this.get = function() {
282
            return this.networks;
283
        }
284

    
285
        this.list = function() {
286
            return storage.networks.filter(_.bind(function(net){
287
                return this.network_ids.indexOf(net.id) > -1;
288
            }, this))
289
        }
290

    
291
        this.initialize();
292
    };
293
    _.extend(VMNetworksList.prototype, bb.Events);
294

    
295
    // Image model
296
    models.Network = models.Model.extend({
297
        path: 'networks',
298
        has_status: true,
299
        
300
        initialize: function() {
301
            this.vms = new NetworkVMSList();
302
            this.vms.bind("pending:add", _.bind(this.handle_pending_connections, this, "add"));
303
            this.vms.bind("pending:clear", _.bind(this.handle_pending_connections, this, "clear"));
304
            this.vms.bind("pending:remove:add", _.bind(this.handle_pending_connections, this, "add"));
305
            this.vms.bind("pending:remove:clear", _.bind(this.handle_pending_connections, this, "clear"));
306

    
307
            ret = models.Network.__super__.initialize.apply(this, arguments);
308

    
309
            storage.vms.bind("change:linked_to_nets", _.bind(this.update_connections, this, "vm:change"));
310
            storage.vms.bind("add", _.bind(this.update_connections, this, "add"));
311
            storage.vms.bind("remove", _.bind(this.update_connections, this, "remove"));
312
            storage.vms.bind("reset", _.bind(this.update_connections, this, "reset"));
313

    
314
            this.bind("change:linked_to", _.bind(this.update_connections, this, "net:change"));
315
            this.update_connections();
316
            this.update_state();
317

    
318
            return ret;
319
        },
320

    
321
        update_state: function() {
322
            if (this.vms.pending.length) {
323
                this.set({state: "CONNECTING"});
324
                return
325
            }
326

    
327
            if (this.vms.pending_for_removal.length) {
328
                this.set({state: "DISCONNECTING"});
329
                return
330
            }   
331
            
332
            var firewalling = false;
333
            _.each(this.vms.get(), _.bind(function(vm_id){
334
                var vm = storage.vms.get(vm_id);
335
                if (!vm) { return };
336
                if (!_.isEmpty(vm.pending_firewalls)) {
337
                    this.set({state:"FIREWALLING"});
338
                    firewalling = true;
339
                    return false;
340
                }
341
            },this));
342
            if (firewalling) { return };
343

    
344
            this.set({state:"NORMAL"});
345
        },
346

    
347
        handle_pending_connections: function(action) {
348
            this.update_state();
349
        },
350

    
351
        // handle vm/network connections
352
        update_connections: function(action, model) {
353
            
354
            // vm removed disconnect vm from network
355
            if (action == "remove") {
356
                var removed_from_net = this.vms.remove(model.id);
357
                var removed_from_vm = model.networks.remove(this.id);
358
                if (removed_from_net) {this.trigger("vm:disconnect", model, this); this.change()};
359
                if (removed_from_vm) {model.trigger("network:disconnect", this, model); this.change()};
360
                return;
361
            }
362
            
363
            // update links for all vms
364
            var links = this.get("linked_to");
365
            storage.vms.each(_.bind(function(vm) {
366
                var vm_links = vm.get("linked_to") || [];
367
                if (vm_links.indexOf(this.id) > -1) {
368
                    // vm has connection to current network
369
                    if (links.indexOf(vm.id) > -1) {
370
                        // and network has connection to vm, so try
371
                        // to append it
372
                        var add_to_net = this.vms.add(vm.id);
373
                        var index = _.indexOf(vm_links, this.id);
374
                        var add_to_vm = vm.networks.add(this.id, vm.get("linked_to_nets")[index]);
375
                        
376
                        // call only if connection did not existed
377
                        if (add_to_net) {this.trigger("vm:connect", vm, this); this.change()};
378
                        if (add_to_vm) {vm.trigger("network:connect", this, vm); vm.change()};
379
                    } else {
380
                        // no connection, try to remove it
381
                        var removed_from_net = this.vms.remove(vm.id);
382
                        var removed_from_vm = vm.networks.remove(this.id);
383
                        if (removed_from_net) {this.trigger("vm:disconnect", vm, this); this.change()};
384
                        if (removed_from_vm) {vm.trigger("network:disconnect", this, vm); vm.change()};
385
                    }
386
                } else {
387
                    // vm has no connection to current network, try to remove it
388
                    var removed_from_net = this.vms.remove(vm.id);
389
                    var removed_from_vm = vm.networks.remove(this.id);
390
                    if (removed_from_net) {this.trigger("vm:disconnect", vm, this); this.change()};
391
                    if (removed_from_vm) {vm.trigger("network:disconnect", this, vm); vm.change()};
392
                }
393
            },this));
394
        },
395

    
396
        is_public: function() {
397
            return this.id == "public";
398
        },
399

    
400
        contains_vm: function(vm) {
401
            var net_vm_exists = this.vms.get().indexOf(vm.id) > -1;
402
            var vm_net_exists = vm.is_connected_to(this);
403
            return net_vm_exists && vm_net_exists;
404
        },
405

    
406
        add_vm: function (vm, callback, error, options) {
407
            var payload = {add:{serverRef:"" + vm.id}};
408
            payload._options = options || {};
409
            return this.api.call(this.api_path() + "/action", "create", 
410
                                 payload,
411
                                 _.bind(function(){
412
                                     this.vms.add_pending(vm.id);
413
                                     if (callback) {callback()}
414
                                 },this), error);
415
        },
416

    
417
        remove_vm: function (vm, callback, error, options) {
418
            var payload = {remove:{serverRef:"" + vm.id}};
419
            payload._options = options || {};
420
            return this.api.call(this.api_path() + "/action", "create", 
421
                                 {remove:{serverRef:"" + vm.id}},
422
                                 _.bind(function(){
423
                                     this.vms.add_pending_for_remove(vm.id);
424
                                     if (callback) {callback()}
425
                                 },this), error);
426
        },
427

    
428
        rename: function(name, callback) {
429
            return this.api.call(this.api_path(), "update", {
430
                network:{name:name}, 
431
                _options:{
432
                    critical: false, 
433
                    error_params:{
434
                        title: "Network action failed",
435
                        ns: "Networks",
436
                        extra_details: {"Network id": this.id},
437
                    }
438
                }}, callback);
439
        },
440

    
441
        get_connectable_vms: function() {
442
            var servers = this.vms.list();
443
            return storage.vms.filter(function(vm){
444
                return servers.indexOf(vm) == -1 && !vm.in_error_state();
445
            })
446
        },
447

    
448
        state_message: function() {
449
            if (this.get("state") == "NORMAL" && this.is_public()) {
450
                return "Public network";
451
            }
452

    
453
            return models.Network.STATES[this.get("state")];
454
        },
455

    
456
        in_progress: function() {
457
            return models.Network.STATES_TRANSITIONS[this.get("state")] != undefined;
458
        }
459
    });
460
    
461
    models.Network.STATES = {
462
        'NORMAL': 'Private network',
463
        'CONNECTING': 'Connecting...',
464
        'DISCONNECTING': 'Disconnecting...',
465
        'FIREWALLING': 'Firewall update...',
466
        'DESTROY': 'Destroying...'
467
    }
468

    
469
    models.Network.STATES_TRANSITIONS = {
470
        'CONNECTING': ['NORMAL'],
471
        'DISCONNECTING': ['NORMAL'],
472
        'FIREWALLING': ['NORMAL']
473
    }
474

    
475
    // Virtualmachine model
476
    models.VM = models.Model.extend({
477

    
478
        path: 'servers',
479
        has_status: true,
480
        initialize: function(params) {
481
            this.networks = new VMNetworksList();
482
            
483
            this.pending_firewalls = {};
484
            
485
            models.VM.__super__.initialize.apply(this, arguments);
486

    
487
            this.set({state: params.status || "ERROR"});
488
            this.log = new snf.logging.logger("VM " + this.id);
489
            this.pending_action = undefined;
490
            
491
            // init stats parameter
492
            this.set({'stats': undefined}, {silent: true});
493
            // defaults to not update the stats
494
            // each view should handle this vm attribute 
495
            // depending on if it displays stat images or not
496
            this.do_update_stats = false;
497
            
498
            // interval time
499
            // this will dynamicaly change if the server responds that
500
            // images get refreshed on different intervals
501
            this.stats_update_interval = synnefo.config.STATS_INTERVAL || 5000;
502
            this.stats_available = false;
503

    
504
            // initialize interval
505
            this.init_stats_intervals(this.stats_update_interval);
506
            
507
            this.bind("change:progress", _.bind(this.update_building_progress, this));
508
            this.update_building_progress();
509

    
510
            this.bind("change:firewalls", _.bind(this.handle_firewall_change, this));
511
            
512
            // default values
513
            this.set({linked_to_nets:this.get("linked_to_nets") || []});
514
            this.set({firewalls:this.get("firewalls") || []});
515

    
516
            this.bind("change:state", _.bind(function(){if (this.state() == "DESTROY") { this.handle_destroy() }}, this))
517
        },
518

    
519
        handle_firewall_change: function() {
520

    
521
        },
522
        
523
        set_linked_to_nets: function(data) {
524
            this.set({"linked_to":_.map(data, function(n){ return n.id})});
525
            return data;
526
        },
527

    
528
        is_connected_to: function(net) {
529
            return _.filter(this.networks.list(), function(n){return n.id == net.id}).length > 0;
530
        },
531
        
532
        status: function(st) {
533
            if (!st) { return this.get("status")}
534
            return this.set({status:st});
535
        },
536

    
537
        set_status: function(st) {
538
            var new_state = this.state_for_api_status(st);
539
            var transition = false;
540

    
541
            if (this.state() != new_state) {
542
                if (models.VM.STATES_TRANSITIONS[this.state()]) {
543
                    transition = this.state();
544
                }
545
            }
546
            
547
            // call it silently to avoid double change trigger
548
            this.set({'state': this.state_for_api_status(st)}, {silent: true});
549
            
550
            // trigger transition
551
            if (transition && models.VM.TRANSITION_STATES.indexOf(new_state) == -1) { 
552
                this.trigger("transition", {from:transition, to:new_state}) 
553
            };
554
            return st;
555
        },
556

    
557
        update_building_progress: function() {
558
            if (this.is_building()) {
559
                var progress = this.get("progress");
560
                if (progress == 0) {
561
                    this.state("BUILD_INIT");
562
                    this.set({progress_message: BUILDING_MESSAGES['INIT']});
563
                }
564
                if (progress > 0 && progress < 99) {
565
                    this.state("BUILD_COPY");
566
                    var params = this.get_copy_details(true);
567
                    this.set({progress_message: BUILDING_MESSAGES['COPY'].format(params.copy, 
568
                                                                                 params.size, 
569
                                                                                 params.progress)});
570
                }
571
                if (progress == 100) {
572
                    this.state("BUILD_FINAL");
573
                    this.set({progress_message: BUILDING_MESSAGES['FINAL']});
574
                }
575
            } else {
576
            }
577
        },
578

    
579
        get_copy_details: function(human, image) {
580
            var human = human || false;
581
            var image = image || this.get_image();
582

    
583
            var progress = this.get('progress');
584
            var size = image.get_size();
585
            var size_copied = (size * progress / 100).toFixed(2);
586
            
587
            if (human) {
588
                size = util.readablizeBytes(size*1024*1024);
589
                size_copied = util.readablizeBytes(size_copied*1024*1024);
590
            }
591
            return {'progress': progress, 'size': size, 'copy': size_copied};
592
        },
593

    
594
        start_stats_update: function(force_if_empty) {
595
            var prev_state = this.do_update_stats;
596

    
597
            this.do_update_stats = true;
598
            
599
            // fetcher initialized ??
600
            if (!this.stats_fetcher) {
601
                this.init_stats_intervals();
602
            }
603

    
604

    
605
            // fetcher running ???
606
            if (!this.stats_fetcher.running || !prev_state) {
607
                this.stats_fetcher.start();
608
            }
609

    
610
            if (force_if_empty && this.get("stats") == undefined) {
611
                this.update_stats(true);
612
            }
613
        },
614

    
615
        stop_stats_update: function(stop_calls) {
616
            this.do_update_stats = false;
617

    
618
            if (stop_calls) {
619
                this.stats_fetcher.stop();
620
            }
621
        },
622

    
623
        // clear and reinitialize update interval
624
        init_stats_intervals: function (interval) {
625
            this.stats_fetcher = this.get_stats_fetcher(this.stats_update_interval);
626
            this.stats_fetcher.start();
627
        },
628
        
629
        get_stats_fetcher: function(timeout) {
630
            var cb = _.bind(function(data){
631
                this.update_stats();
632
            }, this);
633
            var fetcher = new snf.api.updateHandler({'callback': cb, timeout:timeout});
634
            return fetcher;
635
        },
636

    
637
        // do the api call
638
        update_stats: function(force) {
639
            // do not update stats if flag not set
640
            if ((!this.do_update_stats && !force) || this.updating_stats) {
641
                return;
642
            }
643

    
644
            // make the api call, execute handle_stats_update on sucess
645
            // TODO: onError handler ???
646
            stats_url = this.url() + "/stats";
647
            this.updating_stats = true;
648
            this.sync("GET", this, {
649
                handles_error:true, 
650
                url: stats_url, 
651
                refresh:true, 
652
                success: _.bind(this.handle_stats_update, this),
653
                error: _.bind(this.handle_stats_error, this),
654
                complete: _.bind(function(){this.updating_stats = false;}, this),
655
                critical: false,
656
                display: false,
657
                log_error: false
658
            });
659
        },
660

    
661
        get_stats_image: function(stat, type) {
662
        },
663
        
664
        _set_stats: function(stats) {
665
            var silent = silent === undefined ? false : silent;
666
            // unavailable stats while building
667
            if (this.get("status") == "BUILD") { 
668
                this.stats_available = false;
669
            } else { this.stats_available = true; }
670

    
671
            if (this.get("status") == "DESTROY") { this.stats_available = false; }
672
            
673
            this.set({stats: stats}, {silent:true});
674
            this.trigger("stats:update", stats);
675
        },
676

    
677
        unbind: function() {
678
            models.VM.__super__.unbind.apply(this, arguments);
679
        },
680

    
681
        handle_stats_error: function() {
682
            stats = {};
683
            _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
684
                stats[k] = false;
685
            });
686

    
687
            this.set({'stats': stats});
688
        },
689

    
690
        // this method gets executed after a successful vm stats api call
691
        handle_stats_update: function(data) {
692
            var self = this;
693
            // avoid browser caching
694
            
695
            if (data.stats && _.size(data.stats) > 0) {
696
                var ts = $.now();
697
                var stats = data.stats;
698
                var images_loaded = 0;
699
                var images = {};
700

    
701
                function check_images_loaded() {
702
                    images_loaded++;
703

    
704
                    if (images_loaded == 4) {
705
                        self._set_stats(images);
706
                    }
707
                }
708
                _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
709
                    
710
                    stats[k] = stats[k] + "?_=" + ts;
711
                    
712
                    var stat = k.slice(0,3);
713
                    var type = k.slice(3,6) == "Bar" ? "bar" : "time";
714
                    var img = $("<img />");
715
                    var val = stats[k];
716
                    
717
                    // load stat image to a temporary dom element
718
                    // update model stats on image load/error events
719
                    img.load(function() {
720
                        images[k] = val;
721
                        check_images_loaded();
722
                    });
723

    
724
                    img.error(function() {
725
                        images[stat + type] = false;
726
                        check_images_loaded();
727
                    });
728

    
729
                    img.attr({'src': stats[k]});
730
                })
731
                data.stats = stats;
732
            }
733

    
734
            // do we need to change the interval ??
735
            if (data.stats.refresh * 1000 != this.stats_update_interval) {
736
                this.stats_update_interval = data.stats.refresh * 1000;
737
                this.stats_fetcher.timeout = this.stats_update_interval;
738
                this.stats_fetcher.stop();
739
                this.stats_fetcher.start(false);
740
            }
741
        },
742

    
743
        // helper method that sets the do_update_stats
744
        // in the future this method could also make an api call
745
        // immediaetly if needed
746
        enable_stats_update: function() {
747
            this.do_update_stats = true;
748
        },
749
        
750
        handle_destroy: function() {
751
            this.stats_fetcher.stop();
752
        },
753

    
754
        require_reboot: function() {
755
            if (this.is_active()) {
756
                this.set({'reboot_required': true});
757
            }
758
        },
759
        
760
        set_pending_action: function(data) {
761
            this.pending_action = data;
762
            return data;
763
        },
764

    
765
        // machine has pending action
766
        update_pending_action: function(action, force) {
767
            this.set({pending_action: action});
768
        },
769

    
770
        clear_pending_action: function() {
771
            this.set({pending_action: undefined});
772
        },
773

    
774
        has_pending_action: function() {
775
            return this.get("pending_action") ? this.get("pending_action") : false;
776
        },
777
        
778
        // machine is active
779
        is_active: function() {
780
            return models.VM.ACTIVE_STATES.indexOf(this.state()) > -1;
781
        },
782
        
783
        // machine is building 
784
        is_building: function() {
785
            return models.VM.BUILDING_STATES.indexOf(this.state()) > -1;
786
        },
787
        
788
        in_error_state: function() {
789
            return this.state() === "ERROR"
790
        },
791

    
792
        // user can connect to machine
793
        is_connectable: function() {
794
            // check if ips exist
795
            if (!this.get_addresses().ip4 && !this.get_addresses().ip6) {
796
                return false;
797
            }
798
            return models.VM.CONNECT_STATES.indexOf(this.state()) > -1;
799
        },
800
        
801
        set_firewalls: function(data) {
802
            _.each(data, _.bind(function(val, key){
803
                if (this.pending_firewalls && this.pending_firewalls[key] && this.pending_firewalls[key] == val) {
804
                        this.require_reboot();
805
                        this.remove_pending_firewall(key, val);
806
                }
807
            }, this));
808
            return data;
809
        },
810

    
811
        remove_pending_firewall: function(net_id, value) {
812
            if (this.pending_firewalls[net_id] == value) {
813
                delete this.pending_firewalls[net_id];
814
                storage.networks.get(net_id).update_state();
815
            }
816
        },
817
            
818
        remove_meta: function(key, complete, error) {
819
            var url = this.api_path() + "/meta/" + key;
820
            this.api.call(url, "delete", undefined, complete, error);
821
        },
822

    
823
        save_meta: function(meta, complete, error) {
824
            var url = this.api_path() + "/meta/" + meta.key;
825
            var payload = {meta:{}};
826
            payload.meta[meta.key] = meta.value;
827
            payload._options = {
828
                critical:false, 
829
                error_params: {
830
                    title: "Machine metadata error",
831
                    extra_details: {"Machine id": this.id}
832
            }};
833

    
834
            this.api.call(url, "update", payload, complete, error);
835
        },
836

    
837
        set_firewall: function(net_id, value, callback, error, options) {
838
            if (this.get("firewalls") && this.get("firewalls")[net_id] == value) { return }
839

    
840
            this.pending_firewalls[net_id] = value;
841
            this.trigger("change", this, this);
842
            var payload = {"firewallProfile":{"profile":value}};
843
            payload._options = _.extend({critical: false}, options);
844
            
845
            // reset firewall state on error
846
            var error_cb = _.bind(function() {
847
                thi
848
            }, this);
849

    
850
            this.api.call(this.api_path() + "/action", "create", payload, callback, error);
851
            storage.networks.get(net_id).update_state();
852
        },
853

    
854
        firewall_pending: function(net_id) {
855
            return this.pending_firewalls[net_id] != undefined;
856
        },
857
        
858
        // update/get the state of the machine
859
        state: function() {
860
            var args = slice.call(arguments);
861
                
862
            // TODO: it might not be a good idea to set the state in set_state method
863
            if (args.length > 0 && models.VM.STATES.indexOf(args[0]) > -1) {
864
                this.set({'state': args[0]});
865
            }
866

    
867
            return this.get('state');
868
        },
869
        
870
        // get the state that the api status corresponds to
871
        state_for_api_status: function(status) {
872
            return this.state_transition(this.state(), status);
873
        },
874
        
875
        // vm state equals vm api status
876
        state_is_status: function(state) {
877
            return models.VM.STATUSES.indexOf(state) != -1;
878
        },
879
        
880
        // get transition state for the corresponging api status
881
        state_transition: function(state, new_status) {
882
            var statuses = models.VM.STATES_TRANSITIONS[state];
883
            if (statuses) {
884
                if (statuses.indexOf(new_status) > -1) {
885
                    return new_status;
886
                } else {
887
                    return state;
888
                }
889
            } else {
890
                return new_status;
891
            }
892
        },
893
        
894
        // the current vm state is a transition state
895
        in_transition: function() {
896
            return models.VM.TRANSITION_STATES.indexOf(this.state()) > -1 || 
897
                models.VM.TRANSITION_STATES.indexOf(this.get('status')) > -1;
898
        },
899
        
900
        // get image object
901
        // TODO: update images synchronously if image not found
902
        get_image: function() {
903
            var image = storage.images.get(this.get('imageRef'));
904
            if (!image) {
905
                storage.images.update_unknown_id(this.get('imageRef'));
906
                image = storage.flavors.get(this.get('imageRef'));
907
            }
908
            return image;
909
        },
910
        
911
        // get flavor object
912
        get_flavor: function() {
913
            var flv = storage.flavors.get(this.get('flavorRef'));
914
            if (!flv) {
915
                storage.flavors.update_unknown_id(this.get('flavorRef'));
916
                flv = storage.flavors.get(this.get('flavorRef'));
917
            }
918
            return flv;
919
        },
920

    
921
        // retrieve the metadata object
922
        get_meta: function() {
923
            //return {
924
                //'OS': 'debian',
925
                //'username': 'vinilios',
926
                //'group': 'webservers',
927
                //'meta2': 'meta value',
928
                //'looooooooooooooooong meta': 'short value',
929
                //'short meta': 'loooooooooooooooooooooooooooooooooong value',
930
                //'21421': 'fdsfds fds',
931
                //'21421': 'fdsfds fds',
932
                //'1fds 21421': 'fdsfds fds',
933
                //'fds 21421': 'fdsfds fds',
934
                //'fge 21421': 'fdsfds fds',
935
                //'21421 rew rew': 'fdsfds fds'
936
            //}
937
            try {
938
                return this.get('metadata').values
939
            } catch (err) {
940
                return {};
941
            }
942
        },
943
        
944
        // get metadata OS value
945
        get_os: function() {
946
            return this.get_meta().OS || (this.get_image() ? this.get_image().get_os() || "okeanos" : "okeanos");
947
        },
948
        
949
        // get public ip addresses
950
        // TODO: public network is always the 0 index ???
951
        get_addresses: function(net_id) {
952
            var net_id = net_id || "public";
953
            
954
            var info = this.get_network_info(net_id);
955
            if (!info) { return {} };
956
            addrs = {};
957
            _.each(info.values, function(addr) {
958
                addrs["ip" + addr.version] = addr.addr;
959
            });
960
            return addrs
961
        },
962

    
963
        get_network_info: function(net_id) {
964
            var net_id = net_id || "public";
965
            
966
            if (!this.networks.network_ids.length) { return {} };
967

    
968
            var addresses = this.networks.get();
969
            try {
970
                return _.select(addresses, function(net, key){return key == net_id })[0];
971
            } catch (err) {
972
                //this.log.debug("Cannot find network {0}".format(net_id))
973
            }
974
        },
975

    
976
        firewall_profile: function(net_id) {
977
            var net_id = net_id || "public";
978
            var firewalls = this.get("firewalls");
979
            return firewalls[net_id];
980
        },
981

    
982
        has_firewall: function(net_id) {
983
            var net_id = net_id || "public";
984
            return ["ENABLED","PROTECTED"].indexOf(this.firewall_profile()) > -1;
985
        },
986
    
987
        // get actions that the user can execute
988
        // depending on the vm state/status
989
        get_available_actions: function() {
990
            return models.VM.AVAILABLE_ACTIONS[this.state()];
991
        },
992

    
993
        set_profile: function(profile, net_id) {
994
        },
995
        
996
        // call rename api
997
        rename: function(new_name) {
998
            //this.set({'name': new_name});
999
            this.sync("update", this, {
1000
                critical: true,
1001
                data: {
1002
                    'server': {
1003
                        'name': new_name
1004
                    }
1005
                }, 
1006
                // do the rename after the method succeeds
1007
                success: _.bind(function(){
1008
                    //this.set({name: new_name});
1009
                    snf.api.trigger("call");
1010
                }, this)
1011
            });
1012
        },
1013
        
1014
        get_console_url: function(data) {
1015
            var url_params = {
1016
                machine: this.get("name"),
1017
                host_ip: this.get_addresses().ip4,
1018
                host_ip_v6: this.get_addresses().ip6,
1019
                host: data.host,
1020
                port: data.port,
1021
                password: data.password
1022
            }
1023
            return '/machines/console?' + $.param(url_params);
1024
        },
1025

    
1026
        // action helper
1027
        call: function(action_name, success, error) {
1028
            var id_param = [this.id];
1029

    
1030
            success = success || function() {};
1031
            error = error || function() {};
1032

    
1033
            var self = this;
1034

    
1035
            switch(action_name) {
1036
                case 'start':
1037
                    this.__make_api_call(this.get_action_url(), // vm actions url
1038
                                         "create", // create so that sync later uses POST to make the call
1039
                                         {start:{}}, // payload
1040
                                         function() {
1041
                                             // set state after successful call
1042
                                             self.state("START"); 
1043
                                             success.apply(this, arguments);
1044
                                             snf.api.trigger("call");
1045
                                         },  
1046
                                         error, 'start');
1047
                    break;
1048
                case 'reboot':
1049
                    this.__make_api_call(this.get_action_url(), // vm actions url
1050
                                         "create", // create so that sync later uses POST to make the call
1051
                                         {reboot:{type:"HARD"}}, // payload
1052
                                         function() {
1053
                                             // set state after successful call
1054
                                             self.state("REBOOT"); 
1055
                                             success.apply(this, arguments)
1056
                                             snf.api.trigger("call");
1057
                                             self.set({'reboot_required': false});
1058
                                         },
1059
                                         error, 'reboot');
1060
                    break;
1061
                case 'shutdown':
1062
                    this.__make_api_call(this.get_action_url(), // vm actions url
1063
                                         "create", // create so that sync later uses POST to make the call
1064
                                         {shutdown:{}}, // payload
1065
                                         function() {
1066
                                             // set state after successful call
1067
                                             self.state("SHUTDOWN"); 
1068
                                             success.apply(this, arguments)
1069
                                             snf.api.trigger("call");
1070
                                         },  
1071
                                         error, 'shutdown');
1072
                    break;
1073
                case 'console':
1074
                    this.__make_api_call(this.url() + "/action", "create", {'console': {'type':'vnc'}}, function(data) {
1075
                        var cons_data = data.console;
1076
                        success.apply(this, [cons_data]);
1077
                    }, undefined, 'console')
1078
                    break;
1079
                case 'destroy':
1080
                    this.__make_api_call(this.url(), // vm actions url
1081
                                         "delete", // create so that sync later uses POST to make the call
1082
                                         undefined, // payload
1083
                                         function() {
1084
                                             // set state after successful call
1085
                                             self.state('DESTROY');
1086
                                             success.apply(this, arguments)
1087
                                         },  
1088
                                         error, 'destroy');
1089
                    break;
1090
                default:
1091
                    throw "Invalid VM action ("+action_name+")";
1092
            }
1093
        },
1094
        
1095
        __make_api_call: function(url, method, data, success, error, action) {
1096
            var self = this;
1097
            error = error || function(){};
1098
            success = success || function(){};
1099

    
1100
            var params = {
1101
                url: url,
1102
                data: data,
1103
                success: function(){ self.handle_action_succeed.apply(self, arguments); success.apply(this, arguments)},
1104
                error: function(){ self.handle_action_fail.apply(self, arguments); error.apply(this, arguments)},
1105
                error_params: { ns: "Machines actions", 
1106
                                title: "'" + this.get("name") + "'" + " " + action + " failed", 
1107
                                extra_details: { 'Machine ID': this.id, 'URL': url, 'Action': action || "undefined" },
1108
                                allow_reload: false
1109
                              },
1110
                display: false,
1111
                critical: false
1112
            }
1113
            this.sync(method, this, params);
1114
        },
1115

    
1116
        handle_action_succeed: function() {
1117
            this.trigger("action:success", arguments);
1118
        },
1119
        
1120
        reset_action_error: function() {
1121
            this.action_error = false;
1122
            this.trigger("action:fail:reset", this.action_error);
1123
        },
1124

    
1125
        handle_action_fail: function() {
1126
            this.action_error = arguments;
1127
            this.trigger("action:fail", arguments);
1128
        },
1129

    
1130
        get_action_url: function(name) {
1131
            return this.url() + "/action";
1132
        },
1133

    
1134
        get_connection_info: function(host_os, success, error) {
1135
            var url = "/machines/connect";
1136
            params = {
1137
                ip_address: this.get_addresses().ip4,
1138
                os: this.get_os(),
1139
                host_os: host_os,
1140
                srv: this.id
1141
            }
1142

    
1143
            url = url + "?" + $.param(params);
1144

    
1145
            var ajax = snf.api.sync("read", undefined, { url: url, 
1146
                                                         error:error, 
1147
                                                         success:success, 
1148
                                                         handles_error:1});
1149
        }
1150
    })
1151
    
1152
    models.VM.ACTIONS = [
1153
        'start',
1154
        'shutdown',
1155
        'reboot',
1156
        'console',
1157
        'destroy'
1158
    ]
1159

    
1160
    models.VM.AVAILABLE_ACTIONS = {
1161
        'UNKNWON'       : ['destroy'],
1162
        'BUILD'         : ['destroy'],
1163
        'REBOOT'        : ['shutdown', 'destroy', 'console'],
1164
        'STOPPED'       : ['start', 'destroy'],
1165
        'ACTIVE'        : ['shutdown', 'destroy', 'reboot', 'console'],
1166
        'ERROR'         : ['destroy'],
1167
        'DELETED'        : [],
1168
        'DESTROY'       : [],
1169
        'BUILD_INIT'    : ['destroy'],
1170
        'BUILD_COPY'    : ['destroy'],
1171
        'BUILD_FINAL'   : ['destroy'],
1172
        'SHUTDOWN'      : ['destroy'],
1173
        'START'         : [],
1174
        'CONNECT'       : [],
1175
        'DISCONNECT'    : []
1176
    }
1177

    
1178
    // api status values
1179
    models.VM.STATUSES = [
1180
        'UNKNWON',
1181
        'BUILD',
1182
        'REBOOT',
1183
        'STOPPED',
1184
        'ACTIVE',
1185
        'ERROR',
1186
        'DELETED'
1187
    ]
1188

    
1189
    // api status values
1190
    models.VM.CONNECT_STATES = [
1191
        'ACTIVE',
1192
        'REBOOT',
1193
        'SHUTDOWN'
1194
    ]
1195

    
1196
    // vm states
1197
    models.VM.STATES = models.VM.STATUSES.concat([
1198
        'DESTROY',
1199
        'BUILD_INIT',
1200
        'BUILD_COPY',
1201
        'BUILD_FINAL',
1202
        'SHUTDOWN',
1203
        'START',
1204
        'CONNECT',
1205
        'DISCONNECT',
1206
        'FIREWALL'
1207
    ]);
1208
    
1209
    models.VM.STATES_TRANSITIONS = {
1210
        'DESTROY' : ['DELETED'],
1211
        'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'],
1212
        'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY'],
1213
        'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY'],
1214
        'START': ['ERROR', 'ACTIVE', 'DESTROY'],
1215
        'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'],
1216
        'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'],
1217
        'BUILD_COPY': ['ERROR', 'ACTIVE', 'BUILD_FINAL', 'DESTROY'],
1218
        'BUILD_FINAL': ['ERROR', 'ACTIVE', 'DESTROY'],
1219
        'BUILD_INIT': ['ERROR', 'ACTIVE', 'BUILD_COPY', 'BUILD_FINAL', 'DESTROY']
1220
    }
1221

    
1222
    models.VM.TRANSITION_STATES = [
1223
        'DESTROY',
1224
        'SHUTDOWN',
1225
        'START',
1226
        'REBOOT',
1227
        'BUILD'
1228
    ]
1229

    
1230
    models.VM.ACTIVE_STATES = [
1231
        'BUILD', 'REBOOT', 'ACTIVE',
1232
        'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL',
1233
        'SHUTDOWN', 'CONNECT', 'DISCONNECT'
1234
    ]
1235

    
1236
    models.VM.BUILDING_STATES = [
1237
        'BUILD', 'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL'
1238
    ]
1239

    
1240
    models.Networks = models.Collection.extend({
1241
        model: models.Network,
1242
        path: 'networks',
1243
        details: true,
1244
        //noUpdate: true,
1245
        defaults: {'linked_to':[]},
1246

    
1247
        parse: function (resp, xhr) {
1248
            // FIXME: depricated global var
1249
            if (!resp) { return []};
1250
               
1251
            var data = _.map(resp.networks.values, _.bind(this.parse_net_api_data, this));
1252
            return data;
1253
        },
1254

    
1255
        parse_net_api_data: function(data) {
1256
            if (data.servers && data.servers.values) {
1257
                data['linked_to'] = data.servers.values;
1258
            }
1259
            return data;
1260
        },
1261

    
1262
        create: function (name, callback) {
1263
            return this.api.call(this.path, "create", {network:{name:name}}, callback);
1264
        }
1265
    })
1266

    
1267
    models.Images = models.Collection.extend({
1268
        model: models.Image,
1269
        path: 'images',
1270
        details: true,
1271
        noUpdate: true,
1272
        
1273
        meta_keys_as_attrs: ["OS", "description", "kernel", "size", "GUI"],
1274

    
1275
        // update collection model with id passed
1276
        // making a direct call to the flavor
1277
        // api url
1278
        update_unknown_id: function(id) {
1279
            var url = getUrl.call(this) + "/" + id;
1280
            this.api.call(this.path + "/" + id, "read", {_options:{async:false}}, undefined, 
1281
            _.bind(function() {
1282
                this.add({id:id, name:"Unknown image", size:-1, progress:100, status:"DELETED"})
1283
            }, this), _.bind(function(image) {
1284
                this.add(image.image);
1285
            }, this));
1286
        },
1287

    
1288
        parse: function (resp, xhr) {
1289
            // FIXME: depricated global var
1290
            var data = _.map(resp.images.values, _.bind(this.parse_meta, this));
1291
            return resp.images.values;
1292
        },
1293

    
1294
        get_meta_key: function(img, key) {
1295
            if (img.metadata && img.metadata.values && img.metadata.values[key]) {
1296
                return img.metadata.values[key];
1297
            }
1298
            return undefined;
1299
        },
1300

    
1301
        comparator: function(img) {
1302
            return -img.get_sort_order("sortorder") || 1000 * img.id;
1303
        },
1304

    
1305
        parse_meta: function(img) {
1306
            _.each(this.meta_keys_as_attrs, _.bind(function(key){
1307
                img[key] = this.get_meta_key(img, key);
1308
            }, this));
1309
            return img;
1310
        },
1311

    
1312
        active: function() {
1313
            return this.filter(function(img){return img.get('status') != "DELETED"});
1314
        }
1315
    })
1316

    
1317
    models.Flavors = models.Collection.extend({
1318
        model: models.Flavor,
1319
        path: 'flavors',
1320
        details: true,
1321
        noUpdate: true,
1322
        
1323
        // update collection model with id passed
1324
        // making a direct call to the flavor
1325
        // api url
1326
        update_unknown_id: function(id) {
1327
            var url = getUrl.call(this) + "/" + id;
1328
            this.api.call(this.path + "/" + id, "read", {_options:{async:false}}, undefined, 
1329
            _.bind(function() {
1330
                this.add({id:id, cpu:"", ram:"", disk:"", name: "", status:"DELETED"})
1331
            }, this), _.bind(function(flv) {
1332
                if (!flv.flavor.status) { flv.flavor.status = "DELETED" };
1333
                this.add(flv.flavor);
1334
            }, this));
1335
        },
1336

    
1337
        parse: function (resp, xhr) {
1338
            // FIXME: depricated global var
1339
            return resp.flavors.values;
1340
        },
1341

    
1342
        comparator: function(flv) {
1343
            return flv.get("disk") * flv.get("cpu") * flv.get("ram");
1344
        },
1345

    
1346
        unavailable_values_for_image: function(img, flavors) {
1347
            var flavors = flavors || this.active();
1348
            var size = img.get_size();
1349
            
1350
            var index = {cpu:[], disk:[], ram:[]};
1351

    
1352
            _.each(this.active(), function(el) {
1353
                var img_size = size;
1354
                var flv_size = el.get_disk_size();
1355
                if (flv_size < img_size) {
1356
                    if (index.disk.indexOf(flv_size) == -1) {
1357
                        index.disk.push(flv_size);
1358
                    }
1359
                };
1360
            });
1361
            
1362
            return index;
1363
        },
1364

    
1365
        get_flavor: function(cpu, mem, disk, filter_list) {
1366
            if (!filter_list) { filter_list = this.models };
1367

    
1368
            return this.select(function(flv){
1369
                if (flv.get("cpu") == cpu + "" &&
1370
                   flv.get("ram") == mem + "" &&
1371
                   flv.get("disk") == disk + "" &&
1372
                   filter_list.indexOf(flv) > -1) { return true; }
1373
            })[0];
1374
        },
1375
        
1376
        get_data: function(lst) {
1377
            var data = {'cpu': [], 'mem':[], 'disk':[]};
1378

    
1379
            _.each(lst, function(flv) {
1380
                if (data.cpu.indexOf(flv.get("cpu")) == -1) {
1381
                    data.cpu.push(flv.get("cpu"));
1382
                }
1383
                if (data.mem.indexOf(flv.get("ram")) == -1) {
1384
                    data.mem.push(flv.get("ram"));
1385
                }
1386
                if (data.disk.indexOf(flv.get("disk")) == -1) {
1387
                    data.disk.push(flv.get("disk"));
1388
                }
1389
            })
1390
            
1391
            return data;
1392
        },
1393

    
1394
        active: function() {
1395
            return this.filter(function(flv){return flv.get('status') != "DELETED"});
1396
        }
1397
            
1398
    })
1399

    
1400
    models.VMS = models.Collection.extend({
1401
        model: models.VM,
1402
        path: 'servers',
1403
        details: true,
1404
        copy_image_meta: true,
1405
        
1406
        parse: function (resp, xhr) {
1407
            // FIXME: depricated after refactoring
1408
            var data = resp;
1409
            if (!resp) { return [] };
1410
            data = _.filter(_.map(resp.servers.values, _.bind(this.parse_vm_api_data, this)), function(v){return v});
1411
            return data;
1412
        },
1413
        
1414
        get_reboot_required: function() {
1415
            return this.filter(function(vm){return vm.get("reboot_required") == true})
1416
        },
1417

    
1418
        has_pending_actions: function() {
1419
            return this.filter(function(vm){return vm.pending_action}).length > 0;
1420
        },
1421

    
1422
        reset_pending_actions: function() {
1423
            this.each(function(vm) {
1424
                vm.clear_pending_action();
1425
            })
1426
        },
1427
        
1428
        stop_stats_update: function(exclude) {
1429
            var exclude = exclude || [];
1430
            this.each(function(vm) {
1431
                if (exclude.indexOf(vm) > -1) {
1432
                    return;
1433
                }
1434
                vm.stop_stats_update();
1435
            })
1436
        },
1437
        
1438
        has_meta: function(vm_data) {
1439
            return vm_data.metadata && vm_data.metadata.values
1440
        },
1441

    
1442
        has_addresses: function(vm_data) {
1443
            return vm_data.metadata && vm_data.metadata.values
1444
        },
1445

    
1446
        parse_vm_api_data: function(data) {
1447
            // do not add non existing DELETED entries
1448
            if (data.status && data.status == "DELETED") {
1449
                if (!this.get(data.id)) {
1450
                    console.error("non exising deleted vm", data)
1451
                    return false;
1452
                }
1453
            }
1454

    
1455
            // OS attribute
1456
            if (this.has_meta(data)) {
1457
                data['OS'] = data.metadata.values.OS || "okeanos";
1458
            }
1459
            
1460
            data['firewalls'] = {};
1461
            if (data['addresses'] && data['addresses'].values) {
1462
                data['linked_to_nets'] = data['addresses'].values;
1463
                _.each(data['addresses'].values, function(f){
1464
                    if (f['firewallProfile']) {
1465
                        data['firewalls'][f['id']] = f['firewallProfile']
1466
                    }
1467
                });
1468
            }
1469
            
1470
            // if vm has no metadata, no metadata object
1471
            // is in json response, reset it to force
1472
            // value update
1473
            if (!data['metadata']) {
1474
                data['metadata'] = {values:{}};
1475
            }
1476

    
1477
            return data;
1478
        },
1479

    
1480
        create: function (name, image, flavor, meta, extra, callback) {
1481
            if (this.copy_image_meta) {
1482
                meta['OS'] = image.get("OS");
1483
           }
1484
            
1485
            opts = {name: name, imageRef: image.id, flavorRef: flavor.id, metadata:meta}
1486
            opts = _.extend(opts, extra);
1487

    
1488
            this.api.call(this.path, "create", {'server': opts}, undefined, undefined, callback, {critical: false});
1489
        }
1490

    
1491
    })
1492
    
1493

    
1494
    // storage initialization
1495
    snf.storage.images = new models.Images();
1496
    snf.storage.flavors = new models.Flavors();
1497
    snf.storage.networks = new models.Networks();
1498
    snf.storage.vms = new models.VMS();
1499

    
1500
    //snf.storage.vms.fetch({update:true});
1501
    //snf.storage.images.fetch({update:true});
1502
    //snf.storage.flavors.fetch({update:true});
1503

    
1504
})(this);