Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / models.js @ 1f7ea2f5

History | View | Annotate | Download (51.3 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()}, this)), 2000);
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

    
158
    // Flavor model
159
    models.Flavor = models.Model.extend({
160
        path: 'flavors',
161

    
162
        details_string: function() {
163
            return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk'));
164
        },
165

    
166
        get_disk_size: function() {
167
            return parseInt(this.get("disk") * 1000)
168
        },
169

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

    
185
            if (this.pending_for_removal.length) {
186
                this.trigger("pending:remove:add");
187
            }
188
        },
189

    
190
        this.add_pending = function(vm_id) {
191
            if (this.pending.indexOf(vm_id) == -1) {
192
                this.pending[this.pending.length] = vm_id;
193
            }
194

    
195
            if (this.pending.length) {
196
                this.trigger("pending:add");
197
            }
198
        }
199

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

    
210
            var len = this.pending_for_removal.length;
211
            this.pending_for_removal = _.intersection(this.pending_for_removal, this.vms);
212
            if (this.pending_for_removal.length == 0) {
213
                this.trigger("pending:remove:clear");
214
            }
215

    
216
        }
217

    
218

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

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

    
237
        this.get = function() {
238
            return this.vms;
239
        }
240

    
241
        this.list = function() {
242
            return storage.vms.filter(_.bind(function(vm){
243
                return this.vms.indexOf(vm.id) > -1;
244
            }, this))
245
        }
246

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

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

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

    
277
        this.get = function() {
278
            return this.networks;
279
        }
280

    
281
        this.list = function() {
282
            return storage.networks.filter(_.bind(function(net){
283
                return this.network_ids.indexOf(net.id) > -1;
284
            }, this))
285
        }
286

    
287
        this.initialize();
288
    };
289
    _.extend(VMNetworksList.prototype, bb.Events);
290

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

    
303
            ret = models.Network.__super__.initialize.apply(this, arguments);
304

    
305
            storage.vms.bind("change:linked_to_nets", _.bind(this.update_connections, this, "vm:change"));
306
            storage.vms.bind("add", _.bind(this.update_connections, this, "add"));
307
            storage.vms.bind("remove", _.bind(this.update_connections, this, "remove"));
308
            storage.vms.bind("reset", _.bind(this.update_connections, this, "reset"));
309
            this.bind("change:linked_to", _.bind(this.update_connections, this, "net:change"));
310
            this.update_connections();
311
            this.update_state();
312
            return ret;
313
        },
314

    
315
        update_state: function() {
316
            if (this.vms.pending.length) {
317
                this.set({state: "CONNECTING"});
318
                return
319
            }
320
            if (this.vms.pending_for_removal.length) {
321
                this.set({state: "DISCONNECTING"});
322
                return
323
            }   
324
            
325
            var firewalling = false;
326
            _.each(this.vms.get(), _.bind(function(vm_id){
327
                var vm = storage.vms.get(vm_id);
328
                if (!vm) { return };
329
                if (!_.isEmpty(vm.pending_firewalls)) {
330
                    this.set({state:"FIREWALLING"});
331
                    firewalling = true;
332
                    return false;
333
                }
334
            },this));
335
            if (firewalling) { return };
336

    
337
            this.set({state:"NORMAL"});
338
        },
339

    
340
        handle_pending_connections: function(action) {
341
            this.update_state();
342
        },
343

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

    
389
        is_public: function() {
390
            return this.id == "public";
391
        },
392

    
393
        contains_vm: function(vm) {
394
            var net_vm_exists = this.vms.get().indexOf(vm.id) > -1;
395
            var vm_net_exists = vm.is_connected_to(this);
396
            return net_vm_exists && vm_net_exists;
397
        },
398

    
399
        add_vm: function (vm, callback, error, options) {
400
            var payload = {add:{serverRef:"" + vm.id}};
401
            payload._options = options || {};
402
            return this.api.call(this.api_path() + "/action", "create", 
403
                                 payload,
404
                                 _.bind(function(){
405
                                     this.vms.add_pending(vm.id);
406
                                     if (callback) {callback()}
407
                                 },this), error);
408
        },
409

    
410
        remove_vm: function (vm, callback, error, options) {
411
            var payload = {remove:{serverRef:"" + vm.id}};
412
            payload._options = options || {};
413
            return this.api.call(this.api_path() + "/action", "create", 
414
                                 {remove:{serverRef:"" + vm.id}},
415
                                 _.bind(function(){
416
                                     this.vms.add_pending_for_remove(vm.id);
417
                                     if (callback) {callback()}
418
                                 },this), error);
419
        },
420

    
421
        rename: function(name, callback) {
422
            return this.api.call(this.api_path(), "update", {network:{name:name}}, callback);
423
        },
424

    
425
        get_connectable_vms: function() {
426
            var servers = this.vms.list();
427
            return storage.vms.filter(function(vm){return servers.indexOf(vm) == -1})
428
        },
429

    
430
        state_message: function() {
431
            if (this.get("state") == "NORMAL" && this.is_public()) {
432
                return "Public network";
433
            }
434

    
435
            return models.Network.STATES[this.get("state")];
436
        },
437

    
438
        in_progress: function() {
439
            return models.Network.STATES_TRANSITIONS[this.get("state")] != undefined;
440
        }
441
    });
442
    
443
    models.Network.STATES = {
444
        'NORMAL': 'Private network',
445
        'CONNECTING': 'Connecting...',
446
        'DISCONNECTING': 'Disconnecting...',
447
        'FIREWALLING': 'Firewall update...'
448
    }
449

    
450
    models.Network.STATES_TRANSITIONS = {
451
        'CONNECTING': ['NORMAL'],
452
        'DISCONNECTING': ['NORMAL'],
453
        'FIREWALLING': ['NORMAL']
454
    }
455

    
456
    // Virtualmachine model
457
    models.VM = models.Model.extend({
458

    
459
        path: 'servers',
460
        has_status: true,
461
        initialize: function(params) {
462
            this.networks = new VMNetworksList();
463
            
464
            this.pending_firewalls = {};
465
            
466
            models.VM.__super__.initialize.apply(this, arguments);
467

    
468
            this.set({state: params.status || "ERROR"});
469
            this.log = new snf.logging.logger("VM " + this.id);
470
            this.pending_action = undefined;
471
            
472
            // init stats parameter
473
            this.set({'stats': undefined}, {silent: true});
474
            // defaults to not update the stats
475
            // each view should handle this vm attribute 
476
            // depending on if it displays stat images or not
477
            this.do_update_stats = false;
478
            
479
            // interval time
480
            // this will dynamicaly change if the server responds that
481
            // images get refreshed on different intervals
482
            this.stats_update_interval = synnefo.config.STATS_INTERVAL || 5000;
483
            this.stats_available = false;
484

    
485
            // initialize interval
486
            this.init_stats_intervals(this.stats_update_interval);
487
            
488
            this.bind("change:progress", _.bind(this.update_building_progress, this));
489
            this.update_building_progress();
490

    
491
            this.bind("change:firewalls", _.bind(this.handle_firewall_change, this));
492
            
493
            // default values
494
            this.set({linked_to_nets:this.get("linked_to_nets") || []});
495
            this.set({firewalls:this.get("firewalls") || []});
496

    
497
            this.bind("change:state", _.bind(function(){if (this.state() == "DESTROY") { this.handle_destroy() }}, this))
498
        },
499

    
500
        handle_firewall_change: function() {
501

    
502
        },
503
        
504
        set_linked_to_nets: function(data) {
505
            this.set({"linked_to":_.map(data, function(n){ return n.id})});
506
            return data;
507
        },
508

    
509
        is_connected_to: function(net) {
510
            return _.filter(this.networks.list(), function(n){return n.id == net.id}).length > 0;
511
        },
512
        
513
        status: function(st) {
514
            if (!st) { return this.get("status")}
515
            return this.set({status:st});
516
        },
517

    
518
        set_status: function(st) {
519
            var new_state = this.state_for_api_status(st);
520
            var transition = false;
521

    
522
            if (this.state() != new_state) {
523
                if (models.VM.STATES_TRANSITIONS[this.state()]) {
524
                    transition = this.state();
525
                }
526
            }
527
            
528
            // call it silently to avoid double change trigger
529
            this.set({'state': this.state_for_api_status(st)}, {silent: true});
530
            
531
            // trigger transition
532
            if (transition && models.VM.TRANSITION_STATES.indexOf(new_state) == -1) { 
533
                this.trigger("transition", {from:transition, to:new_state}) 
534
            };
535
            return st;
536
        },
537

    
538
        update_building_progress: function() {
539
            if (this.is_building()) {
540
                var progress = this.get("progress");
541
                if (progress == 0) {
542
                    this.state("BUILD_INIT");
543
                    this.set({progress_message: BUILDING_MESSAGES['INIT']});
544
                }
545
                if (progress > 0 && progress < 99) {
546
                    this.state("BUILD_COPY");
547
                    var params = this.get_copy_details(true);
548
                    this.set({progress_message: BUILDING_MESSAGES['COPY'].format(params.copy, 
549
                                                                                 params.size, 
550
                                                                                 params.progress)});
551
                }
552
                if (progress == 100) {
553
                    this.state("BUILD_FINAL");
554
                    this.set({progress_message: BUILDING_MESSAGES['FINAL']});
555
                }
556
            } else {
557
            }
558
        },
559

    
560
        get_copy_details: function(human, image) {
561
            var human = human || false;
562
            var image = image || this.get_image();
563

    
564
            var progress = this.get('progress');
565
            var size = image.get_size();
566
            var size_copied = (size * progress / 100).toFixed(2);
567
            
568
            if (human) {
569
                size = util.readablizeBytes(size*1024*1024);
570
                size_copied = util.readablizeBytes(size_copied*1024*1024);
571
            }
572
            return {'progress': progress, 'size': size, 'copy': size_copied};
573
        },
574

    
575
        start_stats_update: function(force_if_empty) {
576
            var prev_state = this.do_update_stats;
577

    
578
            this.do_update_stats = true;
579
            
580
            // fetcher initialized ??
581
            if (!this.stats_fetcher) {
582
                this.init_stats_intervals();
583
            }
584

    
585

    
586
            // fetcher running ???
587
            if (!this.stats_fetcher.running || !prev_state) {
588
                this.stats_fetcher.start();
589
            }
590

    
591
            if (force_if_empty && this.get("stats") == undefined) {
592
                this.update_stats(true);
593
            }
594
        },
595

    
596
        stop_stats_update: function(stop_calls) {
597
            this.do_update_stats = false;
598

    
599
            if (stop_calls) {
600
                this.stats_fetcher.stop();
601
            }
602
        },
603

    
604
        // clear and reinitialize update interval
605
        init_stats_intervals: function (interval) {
606
            this.stats_fetcher = this.get_stats_fetcher(this.stats_update_interval);
607
            this.stats_fetcher.start();
608
        },
609
        
610
        get_stats_fetcher: function(timeout) {
611
            var cb = _.bind(function(data){
612
                this.update_stats();
613
            }, this);
614
            var fetcher = new snf.api.updateHandler({'callback': cb, timeout:timeout});
615
            return fetcher;
616
        },
617

    
618
        // do the api call
619
        update_stats: function(force) {
620
            // do not update stats if flag not set
621
            if ((!this.do_update_stats && !force) || this.updating_stats) {
622
                return;
623
            }
624

    
625
            // make the api call, execute handle_stats_update on sucess
626
            // TODO: onError handler ???
627
            stats_url = this.url() + "/stats";
628
            this.updating_stats = true;
629
            this.sync("GET", this, {
630
                handles_error:true, 
631
                url: stats_url, 
632
                refresh:true, 
633
                success: _.bind(this.handle_stats_update, this),
634
                error: _.bind(this.handle_stats_error, this),
635
                complete: _.bind(function(){this.updating_stats = false;}, this),
636
                critical: false,
637
                display: false,
638
                log_error: false
639
            });
640
        },
641

    
642
        get_stats_image: function(stat, type) {
643
        },
644
        
645
        _set_stats: function(stats) {
646
            var silent = silent === undefined ? false : silent;
647
            // unavailable stats while building
648
            if (this.get("status") == "BUILD") { 
649
                this.stats_available = false;
650
            } else { this.stats_available = true; }
651

    
652
            if (this.get("status") == "DESTROY") { this.stats_available = false; }
653
            
654
            this.set({stats: stats}, {silent:true});
655
            this.trigger("stats:update", stats);
656
        },
657

    
658
        unbind: function() {
659
            models.VM.__super__.unbind.apply(this, arguments);
660
        },
661

    
662
        handle_stats_error: function() {
663
            stats = {};
664
            _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
665
                stats[k] = false;
666
            });
667

    
668
            this.set({'stats': stats});
669
        },
670

    
671
        // this method gets executed after a successful vm stats api call
672
        handle_stats_update: function(data) {
673
            var self = this;
674
            // avoid browser caching
675
            
676
            if (data.stats && _.size(data.stats) > 0) {
677
                var ts = $.now();
678
                var stats = data.stats;
679
                var images_loaded = 0;
680
                var images = {};
681

    
682
                function check_images_loaded() {
683
                    images_loaded++;
684

    
685
                    if (images_loaded == 4) {
686
                        self._set_stats(images);
687
                    }
688
                }
689
                _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
690
                    
691
                    stats[k] = stats[k] + "?_=" + ts;
692
                    
693
                    var stat = k.slice(0,3);
694
                    var type = k.slice(3,6) == "Bar" ? "bar" : "time";
695
                    var img = $("<img />");
696
                    var val = stats[k];
697
                    
698
                    // load stat image to a temporary dom element
699
                    // update model stats on image load/error events
700
                    img.load(function() {
701
                        images[k] = val;
702
                        check_images_loaded();
703
                    });
704

    
705
                    img.error(function() {
706
                        images[stat + type] = false;
707
                        check_images_loaded();
708
                    });
709

    
710
                    img.attr({'src': stats[k]});
711
                })
712
                data.stats = stats;
713
            }
714

    
715
            // do we need to change the interval ??
716
            if (data.stats.refresh * 1000 != this.stats_update_interval) {
717
                this.stats_update_interval = data.stats.refresh * 1000;
718
                this.stats_fetcher.timeout = this.stats_update_interval;
719
                this.stats_fetcher.stop();
720
                this.stats_fetcher.start(false);
721
            }
722
        },
723

    
724
        // helper method that sets the do_update_stats
725
        // in the future this method could also make an api call
726
        // immediaetly if needed
727
        enable_stats_update: function() {
728
            this.do_update_stats = true;
729
        },
730
        
731
        handle_destroy: function() {
732
            this.stats_fetcher.stop();
733
        },
734

    
735
        require_reboot: function() {
736
            if (this.is_active()) {
737
                this.set({'reboot_required': true});
738
            }
739
        },
740
        
741
        set_pending_action: function(data) {
742
            this.pending_action = data;
743
            return data;
744
        },
745

    
746
        // machine has pending action
747
        update_pending_action: function(action, force) {
748
            this.set({pending_action: action});
749
        },
750

    
751
        clear_pending_action: function() {
752
            this.set({pending_action: undefined});
753
        },
754

    
755
        has_pending_action: function() {
756
            return this.get("pending_action") ? this.get("pending_action") : false;
757
        },
758
        
759
        // machine is active
760
        is_active: function() {
761
            return models.VM.ACTIVE_STATES.indexOf(this.state()) > -1;
762
        },
763
        
764
        // machine is building 
765
        is_building: function() {
766
            return models.VM.BUILDING_STATES.indexOf(this.state()) > -1;
767
        },
768

    
769
        // user can connect to machine
770
        is_connectable: function() {
771
            // check if ips exist
772
            if (!this.get_addresses().ip4 && !this.get_addresses().ip6) {
773
                return false;
774
            }
775
            return models.VM.CONNECT_STATES.indexOf(this.state()) > -1;
776
        },
777
        
778
        set_firewalls: function(data) {
779
            _.each(data, _.bind(function(val, key){
780
                if (this.pending_firewalls && this.pending_firewalls[key] && this.pending_firewalls[key] == val) {
781
                        this.require_reboot();
782
                        this.remove_pending_firewall(key, val);
783
                }
784
            }, this));
785
            return data;
786
        },
787

    
788
        remove_pending_firewall: function(net_id, value) {
789
            if (this.pending_firewalls[net_id] == value) {
790
                delete this.pending_firewalls[net_id];
791
                storage.networks.get(net_id).update_state();
792
            }
793
        },
794
            
795
        remove_meta: function(key, complete, error) {
796
            var url = this.api_path() + "/meta/" + key;
797
            this.api.call(url, "delete", undefined, complete, error);
798
        },
799

    
800
        save_meta: function(meta, complete, error) {
801
            var url = this.api_path() + "/meta/" + meta.key;
802
            var payload = {meta:{}};
803
            payload.meta[meta.key] = meta.value;
804

    
805
            // inject error settings
806
            payload._options = {critical: false};
807

    
808
            this.api.call(url, "update", payload, complete, error)
809
        },
810

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

    
814
            this.pending_firewalls[net_id] = value;
815
            this.trigger("change", this, this);
816
            var payload = {"firewallProfile":{"profile":value}};
817
            payload._options = _.extend({critical: false}, options);
818
            
819
            // reset firewall state on error
820
            var error_cb = _.bind(function() {
821
                thi
822
            }, this);
823

    
824
            this.api.call(this.api_path() + "/action", "create", payload, callback, error);
825
            storage.networks.get(net_id).update_state();
826
        },
827

    
828
        firewall_pending: function(net_id) {
829
            return this.pending_firewalls[net_id] != undefined;
830
        },
831
        
832
        // update/get the state of the machine
833
        state: function() {
834
            var args = slice.call(arguments);
835
                
836
            // TODO: it might not be a good idea to set the state in set_state method
837
            if (args.length > 0 && models.VM.STATES.indexOf(args[0]) > -1) {
838
                this.set({'state': args[0]});
839
            }
840

    
841
            return this.get('state');
842
        },
843
        
844
        // get the state that the api status corresponds to
845
        state_for_api_status: function(status) {
846
            return this.state_transition(this.state(), status);
847
        },
848
        
849
        // vm state equals vm api status
850
        state_is_status: function(state) {
851
            return models.VM.STATUSES.indexOf(state) != -1;
852
        },
853
        
854
        // get transition state for the corresponging api status
855
        state_transition: function(state, new_status) {
856
            var statuses = models.VM.STATES_TRANSITIONS[state];
857
            if (statuses) {
858
                if (statuses.indexOf(new_status) > -1) {
859
                    return new_status;
860
                } else {
861
                    return state;
862
                }
863
            } else {
864
                return new_status;
865
            }
866
        },
867
        
868
        // the current vm state is a transition state
869
        in_transition: function() {
870
            return models.VM.TRANSITION_STATES.indexOf(this.state()) > -1 || 
871
                models.VM.TRANSITION_STATES.indexOf(this.get('status')) > -1;
872
        },
873
        
874
        // get image object
875
        // TODO: update images synchronously if image not found
876
        get_image: function() {
877
            var image = storage.images.get(this.get('imageRef'));
878
            if (!image) {
879
                storage.images.update_unknown_id(this.get('imageRef'));
880
                image = storage.flavors.get(this.get('imageRef'));
881
            }
882
            return image;
883
        },
884
        
885
        // get flavor object
886
        // TODO: update flavors synchronously if image not found
887
        get_flavor: function() {
888
            var flv = storage.flavors.get(this.get('flavorRef'));
889
            if (!flv) {
890
                storage.flavors.update_unknown_id(this.get('flavorRef'));
891
                flv = storage.flavors.get(this.get('flavorRef'));
892
            }
893
            return flv;
894
        },
895

    
896
        // retrieve the metadata object
897
        get_meta: function() {
898
            //return {
899
                //'OS': 'debian',
900
                //'username': 'vinilios',
901
                //'group': 'webservers',
902
                //'meta2': 'meta value',
903
                //'looooooooooooooooong meta': 'short value',
904
                //'short meta': 'loooooooooooooooooooooooooooooooooong value',
905
                //'21421': 'fdsfds fds',
906
                //'21421': 'fdsfds fds',
907
                //'1fds 21421': 'fdsfds fds',
908
                //'fds 21421': 'fdsfds fds',
909
                //'fge 21421': 'fdsfds fds',
910
                //'21421 rew rew': 'fdsfds fds'
911
            //}
912
            try {
913
                return this.get('metadata').values
914
            } catch (err) {
915
                return {};
916
            }
917
        },
918
        
919
        // get metadata OS value
920
        get_os: function() {
921
            return this.get_meta().OS || (this.get_image() ? this.get_image().get_os() || "okeanos" : "okeanos");
922
        },
923
        
924
        // get public ip addresses
925
        // TODO: public network is always the 0 index ???
926
        get_addresses: function(net_id) {
927
            var net_id = net_id || "public";
928
            
929
            var info = this.get_network_info(net_id);
930
            if (!info) { return {} };
931
            addrs = {};
932
            _.each(info.values, function(addr) {
933
                addrs["ip" + addr.version] = addr.addr;
934
            });
935
            return addrs
936
        },
937

    
938
        get_network_info: function(net_id) {
939
            var net_id = net_id || "public";
940
            
941
            if (!this.networks.network_ids.length) { return {} };
942

    
943
            var addresses = this.networks.get();
944
            try {
945
                return _.select(addresses, function(net, key){return key == net_id })[0];
946
            } catch (err) {
947
                //this.log.debug("Cannot find network {0}".format(net_id))
948
            }
949
        },
950

    
951
        firewall_profile: function(net_id) {
952
            var net_id = net_id || "public";
953
            var firewalls = this.get("firewalls");
954
            return firewalls[net_id];
955
        },
956

    
957
        has_firewall: function(net_id) {
958
            var net_id = net_id || "public";
959
            return ["ENABLED","PROTECTED"].indexOf(this.firewall_profile()) > -1;
960
        },
961
    
962
        // get actions that the user can execute
963
        // depending on the vm state/status
964
        get_available_actions: function() {
965
            return models.VM.AVAILABLE_ACTIONS[this.state()];
966
        },
967

    
968
        set_profile: function(profile, net_id) {
969
        },
970
        
971
        // call rename api
972
        rename: function(new_name) {
973
            //this.set({'name': new_name});
974
            this.sync("update", this, {
975
                data: {
976
                    'server': {
977
                        'name': new_name
978
                    }
979
                }, 
980
                // do the rename after the method succeeds
981
                success: _.bind(function(){
982
                    //this.set({name: new_name});
983
                    snf.api.trigger("call");
984
                }, this)
985
            });
986
        },
987
        
988
        get_console_url: function(data) {
989
            var url_params = {
990
                machine: this.get("name"),
991
                host_ip: this.get_addresses().ip4,
992
                host_ip_v6: this.get_addresses().ip6,
993
                host: data.host,
994
                port: data.port,
995
                password: data.password
996
            }
997
            return '/machines/console?' + $.param(url_params);
998
        },
999

    
1000
        // action helper
1001
        call: function(action_name, success, error) {
1002
            var id_param = [this.id];
1003

    
1004
            success = success || function() {};
1005
            error = error || function() {};
1006

    
1007
            var self = this;
1008

    
1009
            switch(action_name) {
1010
                case 'start':
1011
                    this.__make_api_call(this.get_action_url(), // vm actions url
1012
                                         "create", // create so that sync later uses POST to make the call
1013
                                         {start:{}}, // payload
1014
                                         function() {
1015
                                             // set state after successful call
1016
                                             self.state("START"); 
1017
                                             success.apply(this, arguments);
1018
                                             snf.api.trigger("call");
1019
                                         },  
1020
                                         error, 'start');
1021
                    break;
1022
                case 'reboot':
1023
                    this.__make_api_call(this.get_action_url(), // vm actions url
1024
                                         "create", // create so that sync later uses POST to make the call
1025
                                         {reboot:{type:"HARD"}}, // payload
1026
                                         function() {
1027
                                             // set state after successful call
1028
                                             self.state("REBOOT"); 
1029
                                             success.apply(this, arguments)
1030
                                             snf.api.trigger("call");
1031
                                             self.set({'reboot_required': false});
1032
                                         },
1033
                                         error, 'reboot');
1034
                    break;
1035
                case 'shutdown':
1036
                    this.__make_api_call(this.get_action_url(), // vm actions url
1037
                                         "create", // create so that sync later uses POST to make the call
1038
                                         {shutdown:{}}, // payload
1039
                                         function() {
1040
                                             // set state after successful call
1041
                                             self.state("SHUTDOWN"); 
1042
                                             success.apply(this, arguments)
1043
                                             snf.api.trigger("call");
1044
                                         },  
1045
                                         error, 'shutdown');
1046
                    break;
1047
                case 'console':
1048
                    this.__make_api_call(this.url() + "/action", "create", {'console': {'type':'vnc'}}, function(data) {
1049
                        var cons_data = data.console;
1050
                        success.apply(this, [cons_data]);
1051
                    }, undefined, 'console')
1052
                    break;
1053
                case 'destroy':
1054
                    this.__make_api_call(this.url(), // vm actions url
1055
                                         "delete", // create so that sync later uses POST to make the call
1056
                                         undefined, // payload
1057
                                         function() {
1058
                                             // set state after successful call
1059
                                             self.state('DESTROY');
1060
                                             success.apply(this, arguments)
1061
                                         },  
1062
                                         error, 'destroy');
1063
                    break;
1064
                default:
1065
                    throw "Invalid VM action ("+action_name+")";
1066
            }
1067
        },
1068
        
1069
        __make_api_call: function(url, method, data, success, error, action) {
1070
            var self = this;
1071
            error = error || function(){};
1072
            success = success || function(){};
1073

    
1074
            var params = {
1075
                url: url,
1076
                data: data,
1077
                success: function(){ self.handle_action_succeed.apply(self, arguments); success.apply(this, arguments)},
1078
                error: function(){ self.handle_action_fail.apply(self, arguments); error.apply(this, arguments)},
1079
                error_params: { ns: "Machines actions", 
1080
                                message: "'" + this.get("name") + "'" + " action failed", 
1081
                                extra_details: { 'Machine ID': this.id, 'URL': url, 'Action': action || "undefined" },
1082
                                allow_reload: false
1083
                              },
1084
                display: false,
1085
                critical: false
1086
            }
1087
            this.sync(method, this, params);
1088
        },
1089

    
1090
        handle_action_succeed: function() {
1091
            this.trigger("action:success", arguments);
1092
        },
1093
        
1094
        reset_action_error: function() {
1095
            this.action_error = false;
1096
            this.trigger("action:fail:reset", this.action_error);
1097
        },
1098

    
1099
        handle_action_fail: function() {
1100
            this.action_error = arguments;
1101
            this.trigger("action:fail", arguments);
1102
        },
1103

    
1104
        get_action_url: function(name) {
1105
            return this.url() + "/action";
1106
        },
1107

    
1108
        get_connection_info: function(host_os, success, error) {
1109
            var url = "/machines/connect";
1110
            params = {
1111
                ip_address: this.get_addresses().ip4,
1112
                os: this.get_os(),
1113
                host_os: host_os,
1114
                srv: this.id
1115
            }
1116

    
1117
            url = url + "?" + $.param(params);
1118

    
1119
            var ajax = snf.api.sync("read", undefined, { url: url, 
1120
                                                         error:error, 
1121
                                                         success:success, 
1122
                                                         handles_error:1});
1123
        }
1124
    })
1125
    
1126
    models.VM.ACTIONS = [
1127
        'start',
1128
        'shutdown',
1129
        'reboot',
1130
        'console',
1131
        'destroy'
1132
    ]
1133

    
1134
    models.VM.AVAILABLE_ACTIONS = {
1135
        'UNKNWON'       : ['destroy'],
1136
        'BUILD'         : ['destroy'],
1137
        'REBOOT'        : ['shutdown', 'destroy', 'console'],
1138
        'STOPPED'       : ['start', 'destroy'],
1139
        'ACTIVE'        : ['shutdown', 'destroy', 'reboot', 'console'],
1140
        'ERROR'         : ['destroy'],
1141
        'DELETED'        : [],
1142
        'DESTROY'       : [],
1143
        'BUILD_INIT'    : ['destroy'],
1144
        'BUILD_COPY'    : ['destroy'],
1145
        'BUILD_FINAL'   : ['destroy'],
1146
        'SHUTDOWN'      : ['destroy'],
1147
        'START'         : [],
1148
        'CONNECT'       : [],
1149
        'DISCONNECT'    : []
1150
    }
1151

    
1152
    // api status values
1153
    models.VM.STATUSES = [
1154
        'UNKNWON',
1155
        'BUILD',
1156
        'REBOOT',
1157
        'STOPPED',
1158
        'ACTIVE',
1159
        'ERROR',
1160
        'DELETED'
1161
    ]
1162

    
1163
    // api status values
1164
    models.VM.CONNECT_STATES = [
1165
        'ACTIVE',
1166
        'REBOOT',
1167
        'SHUTDOWN'
1168
    ]
1169

    
1170
    // vm states
1171
    models.VM.STATES = models.VM.STATUSES.concat([
1172
        'DESTROY',
1173
        'BUILD_INIT',
1174
        'BUILD_COPY',
1175
        'BUILD_FINAL',
1176
        'SHUTDOWN',
1177
        'START',
1178
        'CONNECT',
1179
        'DISCONNECT',
1180
        'FIREWALL'
1181
    ]);
1182
    
1183
    models.VM.STATES_TRANSITIONS = {
1184
        'DESTROY' : ['DELETED'],
1185
        'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'],
1186
        'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY'],
1187
        'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY'],
1188
        'START': ['ERROR', 'ACTIVE', 'DESTROY'],
1189
        'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'],
1190
        'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'],
1191
        'BUILD_COPY': ['ERROR', 'ACTIVE', 'BUILD_FINAL', 'DESTROY'],
1192
        'BUILD_FINAL': ['ERROR', 'ACTIVE', 'DESTROY'],
1193
        'BUILD_INIT': ['ERROR', 'ACTIVE', 'BUILD_COPY', 'BUILD_FINAL', 'DESTROY']
1194
    }
1195

    
1196
    models.VM.TRANSITION_STATES = [
1197
        'DESTROY',
1198
        'SHUTDOWN',
1199
        'START',
1200
        'REBOOT',
1201
        'BUILD'
1202
    ]
1203

    
1204
    models.VM.ACTIVE_STATES = [
1205
        'BUILD', 'REBOOT', 'ACTIVE',
1206
        'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL',
1207
        'SHUTDOWN', 'CONNECT', 'DISCONNECT'
1208
    ]
1209

    
1210
    models.VM.BUILDING_STATES = [
1211
        'BUILD', 'BUILD_INIT', 'BUILD_COPY', 'BUILD_FINAL'
1212
    ]
1213

    
1214
    models.Networks = models.Collection.extend({
1215
        model: models.Network,
1216
        path: 'networks',
1217
        details: true,
1218
        //noUpdate: true,
1219
        defaults: {'linked_to':[]},
1220

    
1221
        parse: function (resp, xhr) {
1222
            // FIXME: depricated global var
1223
            if (!resp) { return []};
1224
               
1225
            var data = _.map(resp.networks.values, _.bind(this.parse_net_api_data, this));
1226
            return data;
1227
        },
1228

    
1229
        parse_net_api_data: function(data) {
1230
            if (data.servers && data.servers.values) {
1231
                data['linked_to'] = data.servers.values;
1232
            }
1233
            return data;
1234
        },
1235

    
1236
        create: function (name, callback) {
1237
            return this.api.call(this.path, "create", {network:{name:name}}, callback);
1238
        }
1239
    })
1240

    
1241
    models.Images = models.Collection.extend({
1242
        model: models.Image,
1243
        path: 'images',
1244
        details: true,
1245
        noUpdate: true,
1246
        
1247
        meta_keys_as_attrs: ["OS", "description", "kernel", "size", "GUI"],
1248

    
1249
        // update collection model with id passed
1250
        // making a direct call to the flavor
1251
        // api url
1252
        update_unknown_id: function(id) {
1253
            var url = getUrl.call(this) + "/" + id;
1254
            this.api.call(this.path + "/" + id, "read", {_options:{async:false}}, undefined, 
1255
            _.bind(function() {
1256
                this.add({id:id, name:"Unknown image", size:-1, progress:100, status:"DELETED"})
1257
            }, this), _.bind(function(image) {
1258
                this.add(image.image);
1259
            }, this));
1260
        },
1261

    
1262
        parse: function (resp, xhr) {
1263
            // FIXME: depricated global var
1264
            var data = _.map(resp.images.values, _.bind(this.parse_meta, this));
1265
            return resp.images.values;
1266
        },
1267

    
1268
        get_meta_key: function(img, key) {
1269
            if (img.metadata && img.metadata.values && img.metadata.values[key]) {
1270
                return img.metadata.values[key];
1271
            }
1272
            return undefined;
1273
        },
1274

    
1275
        parse_meta: function(img) {
1276
            _.each(this.meta_keys_as_attrs, _.bind(function(key){
1277
                img[key] = this.get_meta_key(img, key);
1278
            }, this));
1279
            return img;
1280
        },
1281

    
1282
        active: function() {
1283
            return this.filter(function(img){return img.get('status') != "DELETED"});
1284
        }
1285
    })
1286

    
1287
    models.Flavors = models.Collection.extend({
1288
        model: models.Flavor,
1289
        path: 'flavors',
1290
        details: true,
1291
        noUpdate: true,
1292
        
1293
        // update collection model with id passed
1294
        // making a direct call to the flavor
1295
        // api url
1296
        update_unknown_id: function(id) {
1297
            var url = getUrl.call(this) + "/" + id;
1298
            this.api.call(this.path + "/" + id, "read", {_options:{async:false}}, undefined, 
1299
            _.bind(function() {
1300
                this.add({id:id, cpu:"", ram:"", disk:"", name: "", status:"DELETED"})
1301
            }, this), _.bind(function(flv) {
1302
                if (!flv.flavor.status) { flv.flavor.status = "DELETED" };
1303
                this.add(flv.flavor);
1304
            }, this));
1305
        },
1306

    
1307
        parse: function (resp, xhr) {
1308
            // FIXME: depricated global var
1309
            return resp.flavors.values;
1310
        },
1311

    
1312
        unavailable_values_for_image: function(img, flavors) {
1313
            var flavors = flavors || this.active();
1314
            var size = img.get_size();
1315
            
1316
            var index = {cpu:[], disk:[], ram:[]};
1317

    
1318
            _.each(this.active(), function(el) {
1319
                var img_size = size;
1320
                var flv_size = el.get_disk_size();
1321
                if (flv_size < img_size) {
1322
                    if (index.disk.indexOf(flv_size) == -1) {
1323
                        index.disk.push(flv_size);
1324
                    }
1325
                };
1326
            });
1327
            
1328
            return index;
1329
        },
1330

    
1331
        get_flavor: function(cpu, mem, disk, filter_list) {
1332
            if (!filter_list) { filter_list = this.models };
1333

    
1334
            return this.select(function(flv){
1335
                if (flv.get("cpu") == cpu + "" &&
1336
                   flv.get("ram") == mem + "" &&
1337
                   flv.get("disk") == disk + "" &&
1338
                   filter_list.indexOf(flv) > -1) { return true; }
1339
            })[0];
1340
        },
1341
        
1342
        get_data: function(lst) {
1343
            var data = {'cpu': [], 'mem':[], 'disk':[]};
1344

    
1345
            _.each(lst, function(flv) {
1346
                if (data.cpu.indexOf(flv.get("cpu")) == -1) {
1347
                    data.cpu.push(flv.get("cpu"));
1348
                }
1349
                if (data.mem.indexOf(flv.get("ram")) == -1) {
1350
                    data.mem.push(flv.get("ram"));
1351
                }
1352
                if (data.disk.indexOf(flv.get("disk")) == -1) {
1353
                    data.disk.push(flv.get("disk"));
1354
                }
1355
            })
1356
            
1357
            return data;
1358
        },
1359

    
1360
        active: function() {
1361
            return this.filter(function(flv){return flv.get('status') != "DELETED"});
1362
        }
1363
            
1364
    })
1365

    
1366
    models.VMS = models.Collection.extend({
1367
        model: models.VM,
1368
        path: 'servers',
1369
        details: true,
1370
        copy_image_meta: true,
1371
        
1372
        parse: function (resp, xhr) {
1373
            // FIXME: depricated after refactoring
1374
            var data = resp;
1375
            if (!resp) { return [] };
1376
            data = _.filter(_.map(resp.servers.values, _.bind(this.parse_vm_api_data, this)), function(v){return v});
1377
            return data;
1378
        },
1379
        
1380
        get_reboot_required: function() {
1381
            return this.filter(function(vm){return vm.get("reboot_required") == true})
1382
        },
1383

    
1384
        has_pending_actions: function() {
1385
            return this.filter(function(vm){return vm.pending_action}).length > 0;
1386
        },
1387

    
1388
        reset_pending_actions: function() {
1389
            this.each(function(vm) {
1390
                vm.clear_pending_action();
1391
            })
1392
        },
1393
        
1394
        stop_stats_update: function(exclude) {
1395
            var exclude = exclude || [];
1396
            this.each(function(vm) {
1397
                if (exclude.indexOf(vm) > -1) {
1398
                    return;
1399
                }
1400
                vm.stop_stats_update();
1401
            })
1402
        },
1403
        
1404
        has_meta: function(vm_data) {
1405
            return vm_data.metadata && vm_data.metadata.values
1406
        },
1407

    
1408
        has_addresses: function(vm_data) {
1409
            return vm_data.metadata && vm_data.metadata.values
1410
        },
1411

    
1412
        parse_vm_api_data: function(data) {
1413
            // do not add non existing DELETED entries
1414
            if (data.status && data.status == "DELETED") {
1415
                if (!this.get(data.id)) {
1416
                    console.error("non exising deleted vm", data)
1417
                    return false;
1418
                }
1419
            }
1420

    
1421
            // OS attribute
1422
            if (this.has_meta(data)) {
1423
                data['OS'] = data.metadata.values.OS || "okeanos";
1424
            }
1425
            
1426
            data['firewalls'] = {};
1427
            if (data['addresses'] && data['addresses'].values) {
1428
                data['linked_to_nets'] = data['addresses'].values;
1429
                _.each(data['addresses'].values, function(f){
1430
                    if (f['firewallProfile']) {
1431
                        data['firewalls'][f['id']] = f['firewallProfile']
1432
                    }
1433
                });
1434
            }
1435
            
1436
            // if vm has no metadata, no metadata object
1437
            // is in json response, reset it to force
1438
            // value update
1439
            if (!data['metadata']) {
1440
                data['metadata'] = {values:{}};
1441
            }
1442

    
1443
            return data;
1444
        },
1445

    
1446
        create: function (name, image, flavor, meta, extra, callback) {
1447
            if (this.copy_image_meta) {
1448
                meta['OS'] = image.get("OS");
1449
           }
1450
            
1451
            opts = {name: name, imageRef: image.id, flavorRef: flavor.id, metadata:meta}
1452
            opts = _.extend(opts, extra);
1453

    
1454
            this.api.call(this.path, "create", {'server': opts}, undefined, undefined, callback, {critical: false});
1455
        }
1456

    
1457
    })
1458
    
1459

    
1460
    // storage initialization
1461
    snf.storage.images = new models.Images();
1462
    snf.storage.flavors = new models.Flavors();
1463
    snf.storage.networks = new models.Networks();
1464
    snf.storage.vms = new models.VMS();
1465

    
1466
    //snf.storage.vms.fetch({update:true});
1467
    //snf.storage.images.fetch({update:true});
1468
    //snf.storage.flavors.fetch({update:true});
1469

    
1470
})(this);