Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / neutron.js @ ab38ac31

History | View | Annotate | Download (17.7 kB)

1
;(function(root){
2
    // Neutron api models, collections, helpers
3
  
4
    // root
5
    var root = root;
6
    
7
    // setup namepsaces
8
    var snf = root.synnefo = root.synnefo || {};
9
    var snfmodels = snf.models = snf.models || {}
10
    var models = snfmodels.networks = snfmodels.networks || {};
11
    var storage = snf.storage = snf.storage || {};
12
    var util = snf.util = snf.util || {};
13

    
14
    // shortcuts
15
    var bb = root.Backbone;
16
    var slice = Array.prototype.slice
17

    
18
    // logging
19
    var logger = new snf.logging.logger("SNF-MODELS");
20
    var debug = _.bind(logger.debug, logger);
21
    
22
    // Neutron base model, extending existing synnefo model
23
    models.NetworkModel = snfmodels.Model.extend({
24
      api_type: 'network',
25
      toJSON: function() {
26
        var res = {};
27
        _.each(this.attributes, function(attr, key) {
28
          if (attr instanceof bb.Collection) {
29
            attr = "[Collection object]";
30
          }
31
          res[key] = attr;
32
        });
33
        return res;
34
      }
35
    });
36
    
37
    // Neutron base collection, common neutron collection params are shared
38
    models.NetworkCollection = snfmodels.Collection.extend({
39
      api_type: 'network',
40
      details: true,
41
      noUpdate: true,
42
      updateEntries: true,
43
      add_on_create: true
44
    });
45
  
46
    // Subnet model
47
    models.Subnet = models.NetworkModel.extend();
48
    
49
    // Subnet collection
50
    models.Subnets = models.NetworkCollection.extend({
51
      model: models.Subnet,
52
      details: false,
53
      path: 'subnets',
54
      parse: function(resp) {
55
        return resp.subnets
56
      }
57
    });
58
    
59
    // Network 
60
    models.Network = models.NetworkModel.extend({
61
      path: 'networks',
62

    
63
      parse: function(obj) {
64
        return obj.network;
65
      },
66

    
67
      // Available network actions.
68
      // connect: 
69
      model_actions: {
70
        'connect': [['status', 'is_public'], function() {
71
          //TODO: Also check network status
72
          return !this.is_public() && _.contains(['ACTIVE'], this.get('status'));
73
        }],
74
        'remove': [['status', 'is_public', 'ports'], function() {
75
          if (this.ports && this.ports.length) {
76
            return false
77
          }
78
          return !this.is_public() && _.contains(['ACTIVE'], this.get('status'));
79
        }]
80
      },
81
      
82
      do_remove: function(succ, err) {
83
        this.actions.reset_pending();
84
        this.destroy({
85
          success: _.bind(function() {
86
            this.set({status: 'REMOVING'});
87
            this.set({ext_status: 'REMOVING'});
88
            // force status display update
89
            this.set({cidr: 'REMOVING'});
90
          }, this),
91
          silent: true
92
        });
93
      },
94

    
95
      proxy_attrs: {
96
        'is_public': [
97
          ['router:external', 'public'], function() {
98
            return this.get('router:external') || this.get('public')
99
          } 
100
        ],
101
        'is_floating': [
102
          ['SNF:floating_ip_pool'], function() {
103
            return this.get('SNF:floating_ip_pool')
104
          }
105
        ],
106
        'cidr': [
107
          ['subnet'], function() {
108
            var subnet = this.get('subnet');
109
            if (subnet && subnet.get('cidr')) {
110
              return subnet.get('cidr')
111
            } else {
112
              return undefined
113
            }
114
          }
115
        ],
116
        'ext_status': [
117
          ['status', 'cidr'], function(st) {
118
            if (this.get('ext_status') == 'REMOVING') {
119
              return 'REMOVING'
120
            }
121
            if (this.pending_connections) {
122
              return 'CONNECTING'
123
            } else if (this.pending_disconnects) {
124
              return 'DISCONNECTING'
125
            } else {
126
              return this.get('status')
127
            }
128
        }],
129
        'in_progress': [
130
          ['ext_status'], function() {
131
            return _.contains(['CONNECTING', 
132
                               'DISCONNECTING', 
133
                               'REMOVING'], 
134
                               this.get('ext_status'))
135
          }  
136
        ]
137
      },
138
      
139
      storage_attrs: {
140
        'subnets': ['subnets', 'subnet', function(model, attr) {
141
          var subnets = model.get(attr);
142
          if (subnets && subnets.length) { return subnets[0] }
143
        }]
144
      },
145

    
146
      // call rename api
147
      rename: function(new_name, cb) {
148
          this.sync("update", this, {
149
              critical: true,
150
              data: {
151
                  'network': {
152
                      'name': new_name
153
                  }
154
              }, 
155
              // do the rename after the method succeeds
156
              success: _.bind(function(){
157
                  //this.set({name: new_name});
158
                  snf.api.trigger("call");
159
              }, this),
160
              complete: cb || function() {}
161
          });
162
      },
163

    
164
      pending_connections: 0,
165
      pending_disconnects: 0,
166

    
167
      initialize: function() {
168
        var self = this;
169
        this.subnets = new Backbone.FilteredCollection(undefined, {
170
          collection: synnefo.storage.subnets,
171
          collectionFilter: function(m) {
172
            return self.id == m.get('network_id')
173
        }});
174
        this.ports = new Backbone.FilteredCollection(undefined, {
175
          collection: synnefo.storage.ports,
176
          collectionFilter: function(m) {
177
            return self.id == m.get('network_id')
178
          }
179
        });
180
        this.ports.network = this;
181
        this.ports.bind("reset", function() {
182
          this.pending_connections = 0;
183
          this.pending_disconnects = 0;
184
          this.update_connecting_status();
185
          this.update_actions();
186
        }, this);
187
        this.ports.bind("add", function() {
188
          this.pending_connections--;
189
          this.update_connecting_status();
190
          this.update_actions();
191
        }, this);
192
        this.ports.bind("remove", function() {
193
          this.pending_disconnects--;
194
          this.update_connecting_status();
195
          this.update_actions();
196
        }, this);
197
        this.set({ports: this.ports});
198

    
199
        this.connectable_vms = new Backbone.FilteredCollection(undefined, {
200
          collection: synnefo.storage.vms,
201
          collectionFilter: function(m) {
202
            return m.can_connect();
203
          }
204
        });
205
        models.Network.__super__.initialize.apply(this, arguments);
206
        this.update_actions();
207
      },
208
      
209
      update_actions: function() {
210
        if (this.ports.length) {
211
          this.set({can_remove: false})
212
        } else {
213
          this.set({can_remove: true})
214
        }
215
      },
216

    
217
      update_connecting_status: function() {
218
        if (this.pending_connections <= 0) {
219
          this.pending_connections = 0;
220
        }
221
        if (this.pending_disconnects <= 0) {
222
          this.pending_disconnects = 0;
223
        }
224
        this.trigger('change:status', this.get('status'));
225
      },
226

    
227
      get_nics: function() {
228
        return this.nics.models
229
      },
230

    
231
      is_public: function() {
232
        return this.get('router:external')
233
      },
234

    
235
      connect_vm: function(vm, cb) {
236
        var self = this;
237
        var data = {
238
          port: {
239
            network_id: this.id,
240
            device_id: vm.id
241
          }
242
        }
243

    
244
        this.pending_connections++;
245
        this.update_connecting_status();
246
        synnefo.storage.ports.create(data, {complete: cb});
247
      }
248
    });
249

    
250
    models.CombinedPublicNetwork = models.Network.extend({
251
      defaults: {
252
        'admin_state_up': true,
253
        'id': 'snf-combined-public-network',
254
        'name': 'Internet',
255
        'status': 'ACTIVE',
256
        'router:external': true,
257
        'shared': false,
258
        'rename_disabled': true,
259
        'subnets': []
260
      },
261
      
262
      initialize: function() {
263
        var self = this;
264
        this.ports = new Backbone.FilteredCollection(undefined, {
265
          collection: synnefo.storage.ports,
266
          collectionFilter: function(m) {
267
            return m.get('network') && m.get('network').get('is_public');
268
          }
269
        });
270
        this.set({ports: this.ports});
271
        this.floating_ips = synnefo.storage.floating_ips;
272
        this.set({floating_ips: this.floating_ips});
273

    
274
        this.available_floating_ips = new Backbone.FilteredCollection(undefined, {
275
          collection: synnefo.storage.floating_ips,
276
          collectionFilter: function(m) {
277
            return !m.get('port_id');
278
          }
279
        });
280
        this.set({available_floating_ips: this.available_floating_ips});
281
        models.Network.__super__.initialize.apply(this, arguments);
282
      },
283

    
284
    })
285

    
286
    models.Networks = models.NetworkCollection.extend({
287
      model: models.Network,
288
      path: 'networks',
289
      details: true,
290
      parse: function(resp) {
291
        var data = _.map(resp.networks, function(net) {
292
          if (!net.name) {
293
            net.name = '(no name set)';
294
          }
295
          return net
296
        })
297
        return resp.networks
298
      },
299

    
300
      get_floating_ips_network: function() {
301
        return this.filter(function(n) { return n.get('is_public')})[1]
302
      },
303

    
304
      create: function (name, type, cidr, dhcp, callback) {
305
        var quota = synnefo.storage.quotas;
306
        var params = {network:{name:name}};
307
        var subnet_params = {subnet:{network_id:undefined}};
308
        if (!type) { throw "Network type cannot be empty"; }
309

    
310
        params.network.type = type;
311
        if (cidr) { subnet_params.subnet.cidr = cidr; }
312
        if (dhcp) { subnet_params.subnet.dhcp_enabled = dhcp; }
313
        if (dhcp === false) { subnet_params.subnet.dhcp_enabled = false; }
314
        
315
        var cb = function() {
316
          callback && callback();
317
        }
318
        
319
        var complete = function() {};
320
        var error = function() { cb() };
321
        // on network create success, try to create the requested 
322
        // network subnet
323
        var success = function(resp) {
324
          var network = resp.network;
325
          subnet_params.subnet.network_id = network.id;
326
          synnefo.storage.subnets.create(subnet_params, {
327
            complete: function () { cb && cb() },
328
            error: function() {
329
              var created_network = new synnefo.models.networks.Network({id: network.id});
330
              created_network.destroy({no_skip: true});
331
            }
332
          });
333
          quota.get('cyclades.network.private').increase();
334
        }
335
        return this.api_call(this.path, "create", params, complete, error, success);
336
      }
337
    });
338
    
339
    // dummy model/collection
340
    models.FixedIP = models.NetworkModel.extend({
341
      storage_attrs: {
342
        'subnet_id': ['subnets', 'subnet']
343
      }
344
    });
345
    models.FixedIPs = models.NetworkCollection.extend({
346
      model: models.FixedIP
347
    });
348

    
349
    models.Port = models.NetworkModel.extend({
350
      path: 'ports',
351
      parse: function(obj) {
352
        return obj.port;
353
      },
354
      initialize: function() {
355
        models.Port.__super__.initialize.apply(this, arguments);
356
        var ips = new models.FixedIPs();
357
        this.set({'ips': ips});
358
        this.bind('change:fixed_ips', function() {
359
          var ips = this.get('ips');
360
          //var ips = _.map(ips, function(ip) { ip.id = ip.a})
361
          this.update_ips()
362
        }, this);
363
        this.update_ips();
364
        this.set({'pending_firewall': null});
365
      },
366
      
367
      update_ips: function() {
368
        var self = this;
369
        var ips = _.map(this.get('fixed_ips'), function(ip_obj) {
370
          var ip = _.clone(ip_obj);
371
          var type = "v4";
372
          if (ip.ip_address.indexOf(":") >= 0) {
373
            type = "v6";
374
          }
375
          ip.id = ip.ip_address;
376
          ip.type = type;
377
          ip.subnet_id = ip.subnet;
378
          ip.port_id = self.id;
379
          delete ip.subnet;
380
          return ip;
381
        });
382
        this.get('ips').update(ips, {removeMissing: true});
383
      },
384

    
385
      model_actions: {
386
        'disconnect': [['status', 'network', 'vm'], function() {
387
          var network = this.get('network');
388
          if ((!network || network.get('is_public')) && (network && !network.get('is_floating'))) {
389
            return false
390
          }
391
          var vm_active = this.get('vm') && this.get('vm').is_active();
392
          if (!synnefo.config.hotplug_enabled && this.get('vm') && vm_active) {
393
            return false;
394
          }
395
          var status_ok = _.contains(['DOWN', 'ACTIVE', 'CONNECTED'], 
396
                                     this.get('status'));
397
          var vm_status_ok = this.get('vm') && this.get('vm').can_connect();
398
          var vm_status = this.get('vm') && this.get('vm').get('status');
399
          return status_ok && vm_status_ok
400
        }]
401
      },
402

    
403
      storage_attrs: {
404
        'device_id': ['vms', 'vm'],
405
        'network_id': ['networks', 'network']
406
      },
407

    
408
      proxy_attrs: {
409
        'firewall_status': [
410
          ['vm'], function(vm) {
411
            var attachment = vm && vm.get_attachment(this.id);
412
            if (!attachment) { return "DISABLED" }
413
            return attachment.firewallProfile
414
          } 
415
        ],
416
        'ext_status': [
417
          ['status'], function() {
418
            if (_.contains(["DISCONNECTING"], this.get('ext_status'))) {
419
              return this.get("ext_status")
420
            }
421
            return this.get("status")
422
          }
423
        ],
424
        'in_progress': [
425
          ['ext_status', 'vm'], function() {
426
            var vm_progress = this.get('vm') && this.get('vm').get('in_progress');
427
            if (vm_progress) { return true }
428
            return _.contains(["BUILD", "DISCONNECTING", "CONNECTING"], this.get("ext_status"))
429
          }
430
        ],
431
        // check progress of port instance only
432
        'in_progress_no_vm': [
433
          ['ext_status'], function() {
434
            return _.contains(["BUILD", "DISCONNECTING", "CONNECTING"], this.get("ext_status"))
435
          }
436
        ],
437
        'firewall_running': [
438
          ['firewall_status', 'pending_firewall'], function(status, pending) {
439
              var pending = this.get('pending_firewall');
440
              var status = this.get('firewall_status');
441
              if (!pending) { return false }
442
              if (status == pending) {
443
                this.set({'pending_firewall': null});
444
              }
445
              return status != pending;
446
          }
447
        ],
448
      },
449

    
450
      disconnect: function(cb) {
451
        var network = this.get('network');
452
        var vm = this.get('vm');
453
        network.pending_disconnects++;
454
        network.update_connecting_status();
455
        var success = _.bind(function() {
456
          if (vm) {
457
            vm.set({'status': 'DISCONNECTING'});
458
          }
459
          this.set({'status': 'DISCONNECTING'});
460
          cb && cb();
461
        }, this);
462
        this.destroy({success: success, complete: cb, silent: true});
463
      }
464
    });
465

    
466
    models.Ports = models.NetworkCollection.extend({
467
      model: models.Port,
468
      path: 'ports',
469
      details: true,
470
      noUpdate: true,
471
      updateEntries: true,
472

    
473
      parse: function(data) {
474
        return data.ports;
475
      },
476

    
477
      comparator: function(m) {
478
          return parseInt(m.id);
479
      }
480
    });
481

    
482
    models.FloatingIP = models.NetworkModel.extend({
483
      path: 'floatingips',
484

    
485
      parse: function(obj) {
486
        return obj.floatingip;
487
      },
488

    
489
      storage_attrs: {
490
        'port_id': ['ports', 'port'],
491
        'floating_network_id': ['networks', 'network'],
492
      },
493

    
494
      model_actions: {
495
        'remove': [['status'], function() {
496
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
497
          return status_ok
498
        }],
499
        'connect': [['status'], function() {
500
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
501
          return status_ok
502
        }],
503
        'disconnect': [['status', 'port_id', 'port'], function() {
504
          var port = this.get('port');
505
          if (!port) { return false }
506
          return port.get('can_disconnect');
507
        }]
508
      },
509
      
510
      do_remove: function(succ, err) { return this.do_destroy(succ, err) },
511
      do_destroy: function(succ, err) {
512
        this.actions.reset_pending();
513
        this.destroy({
514
          success: _.bind(function() {
515
            this.set({status: 'REMOVING'});
516
            succ && succ();
517
          }, this),
518
          error: err || function() {},
519
          silent: true
520
        });
521
      },
522

    
523
      do_disconnect: function(succ, err) {
524
        this.actions.reset_pending();
525
        this.get('port').disconnect(succ);
526
      },
527

    
528
      proxy_attrs: {
529
        'ip': [
530
          ['floating_ip_adress'], function() {
531
            return this.get('floating_ip_address'); 
532
        }],
533

    
534
        'in_progress': [
535
          ['status'], function() {
536
            return _.contains(['CONNECTING', 'DISCONNECTING', 'REMOVING'], 
537
                              this.get('status'))
538
          }  
539
        ],
540

    
541
        'status': [
542
          ['port_id', 'port'], function() {
543
            var port_id = this.get('port_id');
544
            if (!port_id) {
545
              return 'DISCONNECTED'
546
            } else {
547
              var port = this.get('port');
548
              if (port) {
549
                var port_status = port.get('ext_status');
550
                if (port_status == "DISCONNECTING") {
551
                  return port_status
552
                }
553
                if (port_status == "CONNECTING") {
554
                  return port_status
555
                }
556
                return 'CONNECTED'
557
              }
558
              return 'CONNECTING'  
559
            }
560
          }
561
        ]
562
      }
563
    });
564

    
565
    models.FloatingIPs = models.NetworkCollection.extend({
566
      model: models.FloatingIP,
567
      details: false,
568
      path: 'floatingips',
569
      parse: function(resp) {
570
        return resp.floatingips;
571
      }
572
    });
573

    
574
    models.Router = models.NetworkModel.extend({
575
    });
576

    
577
    models.Routers = models.NetworkCollection.extend({
578
      model: models.Router,
579
      path: 'routers',
580
      parse: function(resp) {
581
        return resp.routers
582
      }
583
    });
584

    
585
    snf.storage.floating_ips = new models.FloatingIPs();
586
    snf.storage.routers = new models.Routers();
587
    snf.storage.networks = new models.Networks();
588
    snf.storage.ports = new models.Ports();
589
    snf.storage.subnets = new models.Subnets();
590

    
591
})(this);