Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (18.2 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_subnet: function(subnet_params, complete, error) {
305
        synnefo.storage.subnets.create(subnet_params, {
306
          complete: function () { complete && complete() },
307
          error: function() { error && error() }
308
        });
309
      },
310

    
311
      create: function (name, type, cidr, dhcp, callback) {
312
        var quota = synnefo.storage.quotas;
313
        var params = {network:{name:name}};
314
        var subnet_params = {subnet:{network_id:undefined}};
315
        if (!type) { throw "Network type cannot be empty"; }
316

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

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

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

    
418
      storage_attrs: {
419
        'device_id': ['vms', 'vm'],
420
        'network_id': ['networks', 'network']
421
      },
422

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

    
465
      disconnect: function(cb) {
466
        var network = this.get('network');
467
        var vm = this.get('vm');
468
        network.pending_disconnects++;
469
        network.update_connecting_status();
470
        var success = _.bind(function() {
471
          if (vm) {
472
            vm.set({'status': 'DISCONNECTING'});
473
          }
474
          this.set({'status': 'DISCONNECTING'});
475
          cb && cb();
476
        }, this);
477
        this.destroy({success: success, complete: cb, silent: true});
478
      }
479
    });
480

    
481
    models.Ports = models.NetworkCollection.extend({
482
      model: models.Port,
483
      path: 'ports',
484
      details: true,
485
      noUpdate: true,
486
      updateEntries: true,
487

    
488
      parse: function(data) {
489
        return data.ports;
490
      },
491

    
492
      comparator: function(m) {
493
          return parseInt(m.id);
494
      }
495
    });
496

    
497
    models.FloatingIP = models.NetworkModel.extend({
498
      path: 'floatingips',
499

    
500
      parse: function(obj) {
501
        return obj.floatingip;
502
      },
503

    
504
      storage_attrs: {
505
        'port_id': ['ports', 'port'],
506
        'floating_network_id': ['networks', 'network'],
507
      },
508

    
509
      model_actions: {
510
        'remove': [['status'], function() {
511
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'));
512
          return status_ok
513
        }],
514
        'connect': [['status'], function() {
515
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
516
          return status_ok
517
        }],
518
        'disconnect': [['status', 'port_id', 'port'], function() {
519
          var port = this.get('port');
520
          if (!port) { return false }
521

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

    
541
      do_disconnect: function(succ, err) {
542
        this.actions.reset_pending();
543
        this.get('port').disconnect(succ);
544
      },
545

    
546
      proxy_attrs: {
547
        'ip': [
548
          ['floating_ip_adress'], function() {
549
            return this.get('floating_ip_address'); 
550
        }],
551

    
552
        'in_progress': [
553
          ['status'], function() {
554
            return _.contains(['CONNECTING', 'DISCONNECTING', 'REMOVING'], 
555
                              this.get('status'))
556
          }  
557
        ],
558

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

    
583
    models.FloatingIPs = models.NetworkCollection.extend({
584
      model: models.FloatingIP,
585
      details: false,
586
      path: 'floatingips',
587
      parse: function(resp) {
588
        return resp.floatingips;
589
      }
590
    });
591

    
592
    models.Router = models.NetworkModel.extend({
593
    });
594

    
595
    models.Routers = models.NetworkCollection.extend({
596
      model: models.Router,
597
      path: 'routers',
598
      parse: function(resp) {
599
        return resp.routers
600
      }
601
    });
602

    
603
    snf.storage.floating_ips = new models.FloatingIPs();
604
    snf.storage.routers = new models.Routers();
605
    snf.storage.networks = new models.Networks();
606
    snf.storage.ports = new models.Ports();
607
    snf.storage.subnets = new models.Subnets();
608

    
609
})(this);