Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (18.4 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
      group_by: 'name',
263
      group_networks: [],
264

    
265
      initialize: function(attributes) {
266
        this.groupKey = attributes.name;
267
        var self = this;
268
        this.ports = new Backbone.FilteredCollection(undefined, {
269
          collection: synnefo.storage.ports,
270
          collectionFilter: function(m) {
271
            return m.get('network') && 
272
                   m.get('network').get(self.group_by) == self.groupKey;
273
          }
274
        });
275
        this.set({ports: this.ports});
276
        this.floating_ips = synnefo.storage.floating_ips;
277
        this.set({floating_ips: this.floating_ips});
278

    
279
        this.available_floating_ips = new Backbone.FilteredCollection(undefined, {
280
          collection: synnefo.storage.floating_ips,
281
          collectionFilter: function(m) {
282
            return !m.get('port_id');
283
          }
284
        });
285
        this.set({available_floating_ips: this.available_floating_ips});
286
        this.set({name: attributes.name || 'Internet'});
287
        models.Network.__super__.initialize.call(this, attributes);
288
      }
289
    });
290

    
291
    models.Networks = models.NetworkCollection.extend({
292
      model: models.Network,
293
      path: 'networks',
294
      details: true,
295

    
296
      parse: function(resp) {
297
        var data = _.map(resp.networks, function(net) {
298
          if (!net.name) {
299
            net.name = '(no name set)';
300
          }
301
          return net
302
        })
303
        return resp.networks
304
      },
305

    
306
      get_floating_ips_network: function() {
307
        return this.filter(function(n) { return n.get('is_public')})[1]
308
      },
309
      
310
      create_subnet: function(subnet_params, complete, error) {
311
        synnefo.storage.subnets.create(subnet_params, {
312
          complete: function () { complete && complete() },
313
          error: function() { error && error() }
314
        });
315
      },
316

    
317
      create: function (name, type, cidr, dhcp, callback) {
318
        var quota = synnefo.storage.quotas;
319
        var params = {network:{name:name}};
320
        var subnet_params = {subnet:{network_id:undefined}};
321
        if (!type) { throw "Network type cannot be empty"; }
322

    
323
        params.network.type = type;
324
        if (cidr) { subnet_params.subnet.cidr = cidr; }
325
        if (dhcp) { subnet_params.subnet.dhcp_enabled = dhcp; }
326
        if (dhcp === false) { subnet_params.subnet.dhcp_enabled = false; }
327
        
328
        var cb = function() {
329
          callback && callback();
330
        }
331
        
332
        var complete = function() {
333
          if (!create_subnet) { cb && cb() }
334
        };
335
        var error = function() { cb() };
336
        var create_subnet = !!cidr;
337
        
338
        // on network create success, try to create the requested network 
339
        // subnet.
340
        var self = this;
341
        var success = function(resp) {
342
          var network = resp.network;
343
          if (create_subnet) {
344
            subnet_params.subnet.network_id = network.id;
345
            self.create_subnet(subnet_params, cb, function() {
346
              // rollback network creation
347
              var created_network = new synnefo.models.networks.Network({
348
                id: network.id
349
              });
350
              created_network.destroy({no_skip: true});
351
            });
352
          }
353
          quota.get('cyclades.network.private').increase();
354
        }
355
        return this.api_call(this.path, "create", params, complete, error, success);
356
      }
357
    });
358
    
359
    // dummy model/collection
360
    models.FixedIP = models.NetworkModel.extend({
361
      storage_attrs: {
362
        'subnet_id': ['subnets', 'subnet']
363
      }
364
    });
365
    models.FixedIPs = models.NetworkCollection.extend({
366
      model: models.FixedIP
367
    });
368

    
369
    models.Port = models.NetworkModel.extend({
370
      path: 'ports',
371
      parse: function(obj) {
372
        return obj.port;
373
      },
374
      initialize: function() {
375
        models.Port.__super__.initialize.apply(this, arguments);
376
        var ips = new models.FixedIPs();
377
        this.set({'ips': ips});
378
        this.bind('change:fixed_ips', function() {
379
          var ips = this.get('ips');
380
          //var ips = _.map(ips, function(ip) { ip.id = ip.a})
381
          this.update_ips()
382
        }, this);
383
        this.update_ips();
384
        this.set({'pending_firewall': null});
385
      },
386
      
387
      update_ips: function() {
388
        var self = this;
389
        var ips = _.map(this.get('fixed_ips'), function(ip_obj) {
390
          var ip = _.clone(ip_obj);
391
          var type = "v4";
392
          if (ip.ip_address.indexOf(":") >= 0) {
393
            type = "v6";
394
          }
395
          ip.id = ip.ip_address;
396
          ip.type = type;
397
          ip.subnet_id = ip.subnet;
398
          ip.port_id = self.id;
399
          delete ip.subnet;
400
          return ip;
401
        });
402
        this.get('ips').update(ips, {removeMissing: true});
403
      },
404

    
405
      model_actions: {
406
        'disconnect': [['status', 'network', 'vm'], function() {
407
          var network = this.get('network');
408
          if ((!network || network.get('is_public')) && (network && !network.get('is_floating'))) {
409
            return false
410
          }
411
          var vm_active = this.get('vm') && this.get('vm').is_active();
412
          if (!synnefo.config.hotplug_enabled && this.get('vm') && vm_active) {
413
            return false;
414
          }
415
          if (this.get('device_id'))
416
          var status_ok = _.contains(['DOWN', 'ACTIVE', 'CONNECTED'], 
417
                                     this.get('status'));
418
          var vm_status_ok = this.get('vm') && this.get('vm').can_connect();
419
          var vm_status = this.get('vm') && this.get('vm').get('status');
420
          return status_ok && vm_status_ok
421
        }]
422
      },
423

    
424
      storage_attrs: {
425
        'device_id': ['vms', 'vm'],
426
        'network_id': ['networks', 'network']
427
      },
428

    
429
      proxy_attrs: {
430
        'firewall_status': [
431
          ['vm'], function(vm) {
432
            var attachment = vm && vm.get_attachment(this.id);
433
            if (!attachment) { return "DISABLED" }
434
            return attachment.firewallProfile
435
          } 
436
        ],
437
        'ext_status': [
438
          ['status'], function() {
439
            if (_.contains(["DISCONNECTING"], this.get('ext_status'))) {
440
              return this.get("ext_status")
441
            }
442
            return this.get("status")
443
          }
444
        ],
445
        'in_progress': [
446
          ['ext_status', 'vm'], function() {
447
            var vm_progress = this.get('vm') && this.get('vm').get('in_progress');
448
            if (vm_progress) { return true }
449
            return _.contains(["BUILD", "DISCONNECTING", "CONNECTING"], this.get("ext_status"))
450
          }
451
        ],
452
        // check progress of port instance only
453
        'in_progress_no_vm': [
454
          ['ext_status'], function() {
455
            return _.contains(["BUILD", "DISCONNECTING", "CONNECTING"], this.get("ext_status"))
456
          }
457
        ],
458
        'firewall_running': [
459
          ['firewall_status', 'pending_firewall'], function(status, pending) {
460
              var pending = this.get('pending_firewall');
461
              var status = this.get('firewall_status');
462
              if (!pending) { return false }
463
              if (status == pending) {
464
                this.set({'pending_firewall': null});
465
              }
466
              return status != pending;
467
          }
468
        ],
469
      },
470

    
471
      disconnect: function(cb) {
472
        var network = this.get('network');
473
        var vm = this.get('vm');
474
        network.pending_disconnects++;
475
        network.update_connecting_status();
476
        var success = _.bind(function() {
477
          if (vm) {
478
            vm.set({'status': 'DISCONNECTING'});
479
          }
480
          this.set({'status': 'DISCONNECTING'});
481
          cb && cb();
482
        }, this);
483
        this.destroy({success: success, complete: cb, silent: true});
484
      }
485
    });
486

    
487
    models.Ports = models.NetworkCollection.extend({
488
      model: models.Port,
489
      path: 'ports',
490
      details: true,
491
      noUpdate: true,
492
      updateEntries: true,
493

    
494
      parse: function(data) {
495
        return data.ports;
496
      },
497

    
498
      comparator: function(m) {
499
          return parseInt(m.id);
500
      }
501
    });
502

    
503
    models.FloatingIP = models.NetworkModel.extend({
504
      path: 'floatingips',
505

    
506
      parse: function(obj) {
507
        return obj.floatingip;
508
      },
509

    
510
      storage_attrs: {
511
        'port_id': ['ports', 'port'],
512
        'floating_network_id': ['networks', 'network'],
513
      },
514

    
515
      model_actions: {
516
        'remove': [['status'], function() {
517
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'));
518
          return status_ok
519
        }],
520
        'connect': [['status'], function() {
521
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
522
          return status_ok
523
        }],
524
        'disconnect': [['status', 'port_id', 'port'], function() {
525
          var port = this.get('port');
526
          if (!port) { return false }
527

    
528
          // not connected to a device
529
          if (port && !port.get('device_id')) { return true }
530
          return port.get('can_disconnect');
531
        }]
532
      },
533
      
534
      do_remove: function(succ, err) { return this.do_destroy(succ, err) },
535
      do_destroy: function(succ, err) {
536
        this.actions.reset_pending();
537
        this.destroy({
538
          success: _.bind(function() {
539
            this.set({status: 'REMOVING'});
540
            succ && succ();
541
          }, this),
542
          error: err || function() {},
543
          silent: true
544
        });
545
      },
546

    
547
      do_disconnect: function(succ, err) {
548
        this.actions.reset_pending();
549
        this.get('port').disconnect(succ);
550
      },
551

    
552
      proxy_attrs: {
553
        'ip': [
554
          ['floating_ip_adress'], function() {
555
            return this.get('floating_ip_address'); 
556
        }],
557

    
558
        'in_progress': [
559
          ['status'], function() {
560
            return _.contains(['CONNECTING', 'DISCONNECTING', 'REMOVING'], 
561
                              this.get('status'))
562
          }  
563
        ],
564

    
565
        'status': [
566
          ['port_id', 'port'], function() {
567
            var port_id = this.get('port_id');
568
            if (!port_id) {
569
              return 'DISCONNECTED'
570
            } else {
571
              var port = this.get('port');
572
              if (port) {
573
                var port_status = port.get('ext_status');
574
                if (port_status == "DISCONNECTING") {
575
                  return port_status
576
                }
577
                if (port_status == "CONNECTING") {
578
                  return port_status
579
                }
580
                return 'CONNECTED'
581
              }
582
              return 'CONNECTING'  
583
            }
584
          }
585
        ]
586
      }
587
    });
588

    
589
    models.FloatingIPs = models.NetworkCollection.extend({
590
      model: models.FloatingIP,
591
      details: false,
592
      path: 'floatingips',
593
      parse: function(resp) {
594
        return resp.floatingips;
595
      }
596
    });
597

    
598
    models.Router = models.NetworkModel.extend({
599
    });
600

    
601
    models.Routers = models.NetworkCollection.extend({
602
      model: models.Router,
603
      path: 'routers',
604
      parse: function(resp) {
605
        return resp.routers
606
      }
607
    });
608

    
609
    snf.storage.floating_ips = new models.FloatingIPs();
610
    snf.storage.routers = new models.Routers();
611
    snf.storage.networks = new models.Networks();
612
    snf.storage.ports = new models.Ports();
613
    snf.storage.subnets = new models.Subnets();
614

    
615
})(this);