Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (17.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
    });
26
    
27
    // Neutron base collection, common neutron collection params are shared
28
    models.NetworkCollection = snfmodels.Collection.extend({
29
      api_type: 'network',
30
      details: true,
31
      noUpdate: true,
32
      updateEntries: true,
33
      add_on_create: true
34
    });
35
  
36
    // Subnet model
37
    models.Subnet = models.NetworkModel.extend();
38
    
39
    // Subnet collection
40
    models.Subnets = models.NetworkCollection.extend({
41
      model: models.Subnet,
42
      details: false,
43
      path: 'subnets',
44
      parse: function(resp) {
45
        return resp.subnets
46
      }
47
    });
48
    
49
    // Network 
50
    models.Network = models.NetworkModel.extend({
51
      path: 'networks',
52

    
53
      parse: function(obj) {
54
        return obj.network;
55
      },
56

    
57
      // Available network actions.
58
      // connect: 
59
      model_actions: {
60
        'connect': [['status', 'is_public'], function() {
61
          //TODO: Also check network status
62
          return !this.is_public() && _.contains(['ACTIVE'], this.get('status'));
63
        }],
64
        'remove': [['status', 'is_public', 'ports'], function() {
65
          if (this.ports && this.ports.length) {
66
            return false
67
          }
68
          return !this.is_public() && _.contains(['ACTIVE'], this.get('status'));
69
        }]
70
      },
71
      
72
      do_remove: function(succ, err) {
73
        this.actions.reset_pending();
74
        this.destroy({
75
          success: _.bind(function() {
76
            this.set({status: 'REMOVING'});
77
            this.set({ext_status: 'REMOVING'});
78
            // force status display update
79
            this.set({cidr: 'REMOVING'});
80
          }, this),
81
          silent: true
82
        });
83
      },
84

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

    
136
      // call rename api
137
      rename: function(new_name, cb) {
138
          this.sync("update", this, {
139
              critical: true,
140
              data: {
141
                  'network': {
142
                      'name': new_name
143
                  }
144
              }, 
145
              // do the rename after the method succeeds
146
              success: _.bind(function(){
147
                  //this.set({name: new_name});
148
                  snf.api.trigger("call");
149
              }, this),
150
              complete: cb || function() {}
151
          });
152
      },
153

    
154
      pending_connections: 0,
155
      pending_disconnects: 0,
156

    
157
      initialize: function() {
158
        var self = this;
159
        this.subnets = new Backbone.FilteredCollection(undefined, {
160
          collection: synnefo.storage.subnets,
161
          collectionFilter: function(m) {
162
            return self.id == m.get('network_id')
163
        }});
164
        this.ports = new Backbone.FilteredCollection(undefined, {
165
          collection: synnefo.storage.ports,
166
          collectionFilter: function(m) {
167
            return self.id == m.get('network_id')
168
          }
169
        });
170
        this.ports.network = this;
171
        this.ports.bind("reset", function() {
172
          this.pending_connections = 0;
173
          this.pending_disconnects = 0;
174
          this.update_connecting_status();
175
          this.update_actions();
176
        }, this);
177
        this.ports.bind("add", function() {
178
          this.pending_connections--;
179
          this.update_connecting_status();
180
          this.update_actions();
181
        }, this);
182
        this.ports.bind("remove", function() {
183
          this.pending_disconnects--;
184
          this.update_connecting_status();
185
          this.update_actions();
186
        }, this);
187
        this.set({ports: this.ports});
188

    
189
        this.connectable_vms = new Backbone.FilteredCollection(undefined, {
190
          collection: synnefo.storage.vms,
191
          collectionFilter: function(m) {
192
            return m.can_connect();
193
          }
194
        });
195
        models.Network.__super__.initialize.apply(this, arguments);
196
        this.update_actions();
197
      },
198
      
199
      update_actions: function() {
200
        if (this.ports.length) {
201
          this.set({can_remove: false})
202
        } else {
203
          this.set({can_remove: true})
204
        }
205
      },
206

    
207
      update_connecting_status: function() {
208
        if (this.pending_connections <= 0) {
209
          this.pending_connections = 0;
210
        }
211
        if (this.pending_disconnects <= 0) {
212
          this.pending_disconnects = 0;
213
        }
214
        this.trigger('change:status', this.get('status'));
215
      },
216

    
217
      get_nics: function() {
218
        return this.nics.models
219
      },
220

    
221
      is_public: function() {
222
        return this.get('router:external')
223
      },
224

    
225
      connect_vm: function(vm, cb) {
226
        var self = this;
227
        var data = {
228
          port: {
229
            network_id: this.id,
230
            device_id: vm.id
231
          }
232
        }
233

    
234
        this.pending_connections++;
235
        this.update_connecting_status();
236
        synnefo.storage.ports.create(data, {complete: cb});
237
      }
238
    });
239

    
240
    models.CombinedPublicNetwork = models.Network.extend({
241
      defaults: {
242
        'admin_state_up': true,
243
        'id': 'snf-combined-public-network',
244
        'name': 'Internet',
245
        'status': 'ACTIVE',
246
        'router:external': true,
247
        'shared': false,
248
        'rename_disabled': true,
249
        'subnets': []
250
      },
251
      
252
      initialize: function() {
253
        var self = this;
254
        this.ports = new Backbone.FilteredCollection(undefined, {
255
          collection: synnefo.storage.ports,
256
          collectionFilter: function(m) {
257
            return m.get('network') && m.get('network').get('is_public');
258
          }
259
        });
260
        this.set({ports: this.ports});
261
        this.floating_ips = synnefo.storage.floating_ips;
262
        this.set({floating_ips: this.floating_ips});
263

    
264
        this.available_floating_ips = new Backbone.FilteredCollection(undefined, {
265
          collection: synnefo.storage.floating_ips,
266
          collectionFilter: function(m) {
267
            return !m.get('port_id');
268
          }
269
        });
270
        this.set({available_floating_ips: this.available_floating_ips});
271
        models.Network.__super__.initialize.apply(this, arguments);
272
      },
273

    
274
    })
275

    
276
    models.Networks = models.NetworkCollection.extend({
277
      model: models.Network,
278
      path: 'networks',
279
      details: true,
280
      parse: function(resp) {
281
        var data = _.map(resp.networks, function(net) {
282
          if (!net.name) {
283
            net.name = '(no name set)';
284
          }
285
          return net
286
        })
287
        return resp.networks
288
      },
289

    
290
      get_floating_ips_network: function() {
291
        return this.filter(function(n) { return n.get('is_public')})[1]
292
      },
293

    
294
      create: function (name, type, cidr, dhcp, callback) {
295
        var quota = synnefo.storage.quotas;
296
        var params = {network:{name:name}};
297
        var subnet_params = {subnet:{network_id:undefined}};
298
        if (!type) { throw "Network type cannot be empty"; }
299

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

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

    
375
      model_actions: {
376
        'disconnect': [['status', 'network', 'vm'], function() {
377
          var network = this.get('network');
378
          if ((!network || network.get('is_public')) && (network && !network.get('is_floating'))) {
379
            return false
380
          }
381
          var vm_active = this.get('vm') && this.get('vm').is_active();
382
          if (!synnefo.config.hotplug_enabled && this.get('vm') && vm_active) {
383
            return false;
384
          }
385
          var status_ok = _.contains(['DOWN', 'ACTIVE', 'CONNECTED'], 
386
                                     this.get('status'));
387
          var vm_status_ok = this.get('vm') && this.get('vm').can_connect();
388
          var vm_status = this.get('vm') && this.get('vm').get('status');
389
          return status_ok && vm_status_ok
390
        }]
391
      },
392

    
393
      storage_attrs: {
394
        'device_id': ['vms', 'vm'],
395
        'network_id': ['networks', 'network']
396
      },
397

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

    
440
      disconnect: function(cb) {
441
        var network = this.get('network');
442
        var vm = this.get('vm');
443
        network.pending_disconnects++;
444
        network.update_connecting_status();
445
        var success = _.bind(function() {
446
          if (vm) {
447
            vm.set({'status': 'DISCONNECTING'});
448
          }
449
          this.set({'status': 'DISCONNECTING'});
450
          cb && cb();
451
        }, this);
452
        this.destroy({success: success, complete: cb, silent: true});
453
      }
454
    });
455

    
456
    models.Ports = models.NetworkCollection.extend({
457
      model: models.Port,
458
      path: 'ports',
459
      details: true,
460
      noUpdate: true,
461
      updateEntries: true,
462

    
463
      parse: function(data) {
464
        return data.ports;
465
      },
466

    
467
      comparator: function(m) {
468
          return parseInt(m.id);
469
      }
470
    });
471

    
472
    models.FloatingIP = models.NetworkModel.extend({
473
      path: 'floatingips',
474

    
475
      parse: function(obj) {
476
        return obj.floatingip;
477
      },
478

    
479
      storage_attrs: {
480
        'port_id': ['ports', 'port'],
481
        'floating_network_id': ['networks', 'network'],
482
      },
483

    
484
      model_actions: {
485
        'remove': [['status'], function() {
486
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
487
          return status_ok
488
        }],
489
        'connect': [['status'], function() {
490
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
491
          return status_ok
492
        }],
493
        'disconnect': [['status', 'port_id', 'port'], function() {
494
          var port = this.get('port');
495
          if (!port) { return false }
496
          return port.get('can_disconnect');
497
        }]
498
      },
499
      
500
      do_remove: function(succ, err) { return this.do_destroy(succ, err) },
501
      do_destroy: function(succ, err) {
502
        this.actions.reset_pending();
503
        this.destroy({
504
          success: _.bind(function() {
505
            this.set({status: 'REMOVING'});
506
            succ && succ();
507
          }, this),
508
          error: err || function() {},
509
          silent: true
510
        });
511
      },
512

    
513
      do_disconnect: function(succ, err) {
514
        this.actions.reset_pending();
515
        this.get('port').disconnect(succ);
516
      },
517

    
518
      proxy_attrs: {
519
        'ip': [
520
          ['floating_ip_adress'], function() {
521
            return this.get('floating_ip_address'); 
522
        }],
523

    
524
        'in_progress': [
525
          ['status'], function() {
526
            return _.contains(['CONNECTING', 'DISCONNECTING', 'REMOVING'], 
527
                              this.get('status'))
528
          }  
529
        ],
530

    
531
        'status': [
532
          ['port_id', 'port'], function() {
533
            var port_id = this.get('port_id');
534
            if (!port_id) {
535
              return 'DISCONNECTED'
536
            } else {
537
              var port = this.get('port');
538
              if (port) {
539
                var port_status = port.get('ext_status');
540
                if (port_status == "DISCONNECTING") {
541
                  return port_status
542
                }
543
                if (port_status == "CONNECTING") {
544
                  return port_status
545
                }
546
                return 'CONNECTED'
547
              }
548
              return 'CONNECTING'  
549
            }
550
          }
551
        ]
552
      }
553
    });
554

    
555
    models.FloatingIPs = models.NetworkCollection.extend({
556
      model: models.FloatingIP,
557
      details: false,
558
      path: 'floatingips',
559
      parse: function(resp) {
560
        return resp.floatingips;
561
      }
562
    });
563

    
564
    models.Router = models.NetworkModel.extend({
565
    });
566

    
567
    models.Routers = models.NetworkCollection.extend({
568
      model: models.Router,
569
      path: 'routers',
570
      parse: function(resp) {
571
        return resp.routers
572
      }
573
    });
574

    
575
    snf.storage.floating_ips = new models.FloatingIPs();
576
    snf.storage.routers = new models.Routers();
577
    snf.storage.networks = new models.Networks();
578
    snf.storage.ports = new models.Ports();
579
    snf.storage.subnets = new models.Subnets();
580

    
581
})(this);