Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (18.1 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
          var status_ok = _.contains(['DOWN', 'ACTIVE', 'CONNECTED'], 
410
                                     this.get('status'));
411
          var vm_status_ok = this.get('vm') && this.get('vm').can_connect();
412
          var vm_status = this.get('vm') && this.get('vm').get('status');
413
          return status_ok && vm_status_ok
414
        }]
415
      },
416

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

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

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

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

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

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

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

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

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

    
508
      model_actions: {
509
        'remove': [['status'], function() {
510
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
511
          return status_ok
512
        }],
513
        'connect': [['status'], function() {
514
          var status_ok = _.contains(['DISCONNECTED'], this.get('status'))
515
          return status_ok
516
        }],
517
        'disconnect': [['status', 'port_id', 'port'], function() {
518
          var port = this.get('port');
519
          if (!port) { return false }
520
          return port.get('can_disconnect');
521
        }]
522
      },
523
      
524
      do_remove: function(succ, err) { return this.do_destroy(succ, err) },
525
      do_destroy: function(succ, err) {
526
        this.actions.reset_pending();
527
        this.destroy({
528
          success: _.bind(function() {
529
            this.set({status: 'REMOVING'});
530
            succ && succ();
531
          }, this),
532
          error: err || function() {},
533
          silent: true
534
        });
535
      },
536

    
537
      do_disconnect: function(succ, err) {
538
        this.actions.reset_pending();
539
        this.get('port').disconnect(succ);
540
      },
541

    
542
      proxy_attrs: {
543
        'ip': [
544
          ['floating_ip_adress'], function() {
545
            return this.get('floating_ip_address'); 
546
        }],
547

    
548
        'in_progress': [
549
          ['status'], function() {
550
            return _.contains(['CONNECTING', 'DISCONNECTING', 'REMOVING'], 
551
                              this.get('status'))
552
          }  
553
        ],
554

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

    
579
    models.FloatingIPs = models.NetworkCollection.extend({
580
      model: models.FloatingIP,
581
      details: false,
582
      path: 'floatingips',
583
      parse: function(resp) {
584
        return resp.floatingips;
585
      }
586
    });
587

    
588
    models.Router = models.NetworkModel.extend({
589
    });
590

    
591
    models.Routers = models.NetworkCollection.extend({
592
      model: models.Router,
593
      path: 'routers',
594
      parse: function(resp) {
595
        return resp.routers
596
      }
597
    });
598

    
599
    snf.storage.floating_ips = new models.FloatingIPs();
600
    snf.storage.routers = new models.Routers();
601
    snf.storage.networks = new models.Networks();
602
    snf.storage.ports = new models.Ports();
603
    snf.storage.subnets = new models.Subnets();
604

    
605
})(this);