Revision 198b546d snf-cyclades-app/synnefo/ui/static/snf/js/models.js

b/snf-cyclades-app/synnefo/ui/static/snf/js/models.js
1
// Copyright 2011 GRNET S.A. All rights reserved.
1
// Copyright 2014 GRNET S.A. All rights reserved.
2 2
// 
3 3
// Redistribution and use in source and binary forms, with or
4 4
// without modification, are permitted provided that the following
......
334 334
        api: snf.api,
335 335
        api_type: 'compute',
336 336
        supportIncUpdates: true,
337
        
338
        mapAttrs: function() {
339
          var params = _.toArray(arguments);
340
          return this.map(function(i) { 
341
            return _.map(params, function(attr) { 
342
              return i.get(attr);
343
            });
344
          })
345
        },
346

  
347
        mapAttr: function(attr) {
348
          return this.map(function(i) { return i.get(attr)})
349
        },
350

  
351
        filterAttr: function(attr, eq) {
352
          return this.filter(function(i) { return i.get(attr) === eq})
353
        },
337 354

  
338 355
        initialize: function() {
339 356
            models.Collection.__super__.initialize.apply(this, arguments);
......
353 370
            return getUrl.call(this, this.base_url) + (
354 371
                    options.details || this.details && method != 'create' ? '/detail' : '');
355 372
        },
373
        
374
        delay_fetch: function(delay, options) {
375
          window.setTimeout(_.bind(function() {
376
            this.fetch(options)
377
          }, this), delay);
378
        },
356 379

  
357 380
        fetch: function(options) {
358 381
            if (!options) { options = {} };
......
392 415
                if (coll.add_on_create) {
393 416
                  coll.add(nextModel, options);
394 417
                }
418
                synnefo.api.trigger("quota:update");
395 419
                if (success) success(nextModel, resp, xhr);
396 420
            };
397 421
            model.save(null, options);
......
618 642
            return parseInt(this.get("ram")) * 1024 * 1024;
619 643
        },
620 644

  
645
        get_readable: function(key) {
646
          var parser = function(v) { return v }
647
          var getter = this.get;
648
          if (key == 'ram') {
649
            parser = synnefo.util.readablizeBytes
650
            getter = this.ram_to_bytes
651
          }
652
          if (key == 'disk') {
653
            parser = synnefo.util.readablizeBytes
654
            getter = this.ram_to_bytes
655
          }
656
          if (key == 'cpu') {
657
            parser = function(v) { return v + 'x' }
658
          }
659

  
660
          var val = getter.call(this, key);
661
          return parser(val);
662
        },
663

  
664
        quotas: function() {
665
          return {
666
            'cyclades.vm': 1,
667
            'cyclades.disk': this.disk_to_bytes(), 
668
            'cyclades.ram': this.ram_to_bytes(),
669
            'cyclades.cpu': this.get('cpu')
670
          }
671
        }
672

  
621 673
    });
622 674
    
623 675
    models.ParamsList = function(){this.initialize.apply(this, arguments)};
......
737 789
          ]
738 790
        },
739 791

  
792
        storage_attrs: {
793
          'tenant_id': ['projects', 'project']
794
        },
795

  
740 796
        initialize: function(params) {
741 797
            var self = this;
742 798
            this.ports = new Backbone.FilteredCollection(undefined, {
......
1104 1160
        },
1105 1161
        
1106 1162
        can_start: function(flv, count_current) {
1163
          var self = this;
1107 1164
          var get_quota = function(key) {
1108
            return synnefo.storage.quotas.get(key).get('available');
1165
            if (!self.get('project')) { return false }
1166
            return self.get('project').quotas.get(key).get('available');
1109 1167
          }
1110 1168
          var flavor = flv || this.get_flavor();
1111 1169
          var vm_ram_current = 0, vm_cpu_current = 0;
......
1136 1194
          return this.get('status') == 'STOPPED';
1137 1195
        },
1138 1196

  
1197
        can_reassign: function() {
1198
          return true;
1199
        },
1200

  
1139 1201
        handle_stats_error: function() {
1140 1202
            stats = {};
1141 1203
            _.each(['cpuBar', 'cpuTimeSeries', 'netBar', 'netTimeSeries'], function(k) {
......
1399 1461
        // call rename api
1400 1462
        rename: function(new_name) {
1401 1463
            //this.set({'name': new_name});
1464
            var self = this;
1402 1465
            this.sync("update", this, {
1403 1466
                critical: true,
1404 1467
                data: {
......
1408 1471
                }, 
1409 1472
                success: _.bind(function(){
1410 1473
                    snf.api.trigger("call");
1474
                    this.set({'name': new_name});
1411 1475
                }, this)
1412 1476
            });
1413 1477
        },
......
1515 1579
                                             // set state after successful call
1516 1580
                                             self.state('DESTROY');
1517 1581
                                             success.apply(this, arguments);
1518

  
1582
                                             synnefo.api.trigger("quotas:call", 20);
1519 1583
                                         },  
1520 1584
                                         error, 'destroy', params);
1521 1585
                    break;
1586
                case 'reassign':
1587
                    this.__make_api_call(this.get_action_url(), // vm actions url
1588
                                         "create", // create so that sync later uses POST to make the call
1589
                                         {reassign: {project:params.project_id}}, // payload
1590
                                         function() {
1591
                                             self.state('reassign');
1592
                                             self.set({'tenant_id': params.project_id});
1593
                                             success.apply(this, arguments);
1594
                                             snf.api.trigger("call");
1595
                                         },  
1596
                                         error, 'reassign', params);
1597
                    break;
1522 1598
                case 'resize':
1523 1599
                    this.__make_api_call(this.get_action_url(), // vm actions url
1524 1600
                                         "create", // create so that sync later uses POST to make the call
......
1660 1736
        'console',
1661 1737
        'destroy',
1662 1738
        'resize',
1739
        'reassign',
1663 1740
        'snapshot'
1664 1741
    ]
1665 1742

  
......
1669 1746
      'STOPPING': 'SHUTDOWN',
1670 1747
      'STARTING': 'START',
1671 1748
      'RESIZING': 'RESIZE',
1749
      'REASSIGNING': 'REASSIGN',
1672 1750
      'CONNECTING': 'CONNECT',
1673 1751
      'DISCONNECTING': 'DISCONNECT',
1674 1752
      'DESTROYING': 'DESTROY'
......
1678 1756
        'UNKNWON'       : ['destroy'],
1679 1757
        'BUILD'         : ['destroy'],
1680 1758
        'REBOOT'        : ['destroy'],
1681
        'STOPPED'       : ['start', 'destroy', 'resize', 'snapshot'],
1682
        'ACTIVE'        : ['shutdown', 'destroy', 'reboot', 'console', 'resize', 'snapshot'],
1759
        'STOPPED'       : ['start', 'destroy', 'reassign', 'resize', 'snapshot'],
1760
        'ACTIVE'        : ['shutdown', 'destroy', 'reboot', 'console', 'reassign', 'resize', 'snapshot'],
1683 1761
        'ERROR'         : ['destroy'],
1684 1762
        'DELETED'       : ['destroy'],
1685 1763
        'DESTROY'       : ['destroy'],
......
1687 1765
        'START'         : ['destroy'],
1688 1766
        'CONNECT'       : ['destroy'],
1689 1767
        'DISCONNECT'    : ['destroy'],
1690
        'RESIZE'        : ['destroy']
1768
        'RESIZE'        : ['destroy'],
1769
        'REASSIGN'      : ['destroy']
1691 1770
    }
1692 1771
    
1693 1772
    models.VM.AVAILABLE_ACTIONS_INACTIVE = {}
......
1701 1780
        'ACTIVE',
1702 1781
        'ERROR',
1703 1782
        'DELETED',
1783
        'REASSIGN',
1704 1784
        'RESIZE'
1705 1785
    ]
1706 1786

  
......
1719 1799
        'CONNECT',
1720 1800
        'DISCONNECT',
1721 1801
        'FIREWALL',
1802
        'REASSIGN',
1722 1803
        'RESIZE'
1723 1804
    ]);
1724 1805
    
1725 1806
    models.VM.STATES_TRANSITIONS = {
1726 1807
        'DESTROY' : ['DELETED'],
1727 1808
        'SHUTDOWN': ['ERROR', 'STOPPED', 'DESTROY'],
1728
        'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY'],
1729
        'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY'],
1809
        'STOPPED': ['ERROR', 'ACTIVE', 'DESTROY', 'RESIZE', 'REASSIGN'],
1810
        'ACTIVE': ['ERROR', 'STOPPED', 'REBOOT', 'SHUTDOWN', 'DESTROY', 'REASSIGN'],
1730 1811
        'START': ['ERROR', 'ACTIVE', 'DESTROY'],
1731 1812
        'REBOOT': ['ERROR', 'ACTIVE', 'STOPPED', 'DESTROY'],
1732 1813
        'BUILD': ['ERROR', 'ACTIVE', 'DESTROY'],
1733
        'RESIZE': ['ERROR', 'STOPPED']
1814
        'RESIZE': ['ERROR', 'STOPPED'],
1815
        'REASSIGN': ['ERROR', 'STOPPED', 'ACTIVE']
1734 1816
    }
1735 1817

  
1736 1818
    models.VM.TRANSITION_STATES = [
......
1740 1822
        'REBOOT',
1741 1823
        'BUILD',
1742 1824
        'RESIZE',
1825
        'REASSIGN',
1743 1826
        'DISCONNECT',
1744 1827
        'CONNECT'
1745 1828
    ]
......
2165 2248
            return vm_data.metadata && vm_data.metadata
2166 2249
        },
2167 2250

  
2168
        create: function (name, image, flavor, meta, extra, callback) {
2251
        create: function (name, image, flavor, meta, project, extra, callback) {
2169 2252

  
2170 2253
            if (this.copy_image_meta) {
2171 2254
                if (synnefo.config.vm_image_common_metadata) {
......
2183 2266
            }
2184 2267
            
2185 2268
            opts = {name: name, imageRef: image.id, flavorRef: flavor.id, 
2186
                    metadata:meta}
2269
                    metadata:meta, project: project.id}
2187 2270
            opts = _.extend(opts, extra);
2188 2271
            
2189 2272
            var cb = function(data) {
2190
              synnefo.storage.quotas.get('cyclades.vm').increase();
2273
              synnefo.api.trigger("quota:update");
2191 2274
              callback(data);
2192 2275
            }
2193 2276

  
......
2260 2343

  
2261 2344
        rename: function(new_name) {
2262 2345
          //this.set({'name': new_name});
2346
          var self = this;
2263 2347
          this.sync("update", this, {
2264 2348
            critical: true,
2265 2349
            data: {'name': new_name}, 
2266 2350
            success: _.bind(function(){
2267 2351
              snf.api.trigger("call");
2352
              this.set({'name': new_name});
2268 2353
            }, this)
2269 2354
          });
2270 2355
        },
......
2400 2485

  
2401 2486
  
2402 2487
    models.Quota = models.Model.extend({
2488
        storage_attrs: {
2489
          'project_id': ['projects', 'project']
2490
        },
2403 2491

  
2404 2492
        initialize: function() {
2405 2493
            models.Quota.__super__.initialize.apply(this, arguments);
......
2476 2564
            return snf.util.readablizeBytes(value);
2477 2565
        }
2478 2566
    });
2479

  
2567
    
2480 2568
    models.Quotas = models.Collection.extend({
2481 2569
        model: models.Quota,
2482 2570
        api_type: 'accounts',
2483 2571
        path: 'quotas',
2484 2572
        supportIncUpdates: false,
2573

  
2574
        required_quota: {
2575
          'vm': {
2576
            'cyclades.vm': 1,
2577
            'cyclades.ram': 1,
2578
            'cyclades.cpu': 1,
2579
            'cyclades.disk': 1
2580
          },
2581
          'network': {
2582
            'cyclades.network.private': 1
2583
          },
2584
          'ip': {
2585
            'cyclades.floating_ip': 1
2586
          }
2587
        },
2588

  
2485 2589
        parse: function(resp) {
2486
            filtered = _.map(resp.system, function(value, key) {
2487
                var available = (value.limit - value.usage) || 0;
2590
            var parsed = [];
2591
            _.each(resp, function(resources, uuid) {
2592
              parsed = _.union(parsed, _.map(resources, function(value, key) {
2593
                var quota_available = value.limit - value.usage || 0;
2594
                var total_available = quota_available;
2595
                var project_available = value.project_limit - value.project_usage || 0;
2596
                var limit = value.limit;
2597
                var usage = value.usage;
2598
                
2599
                var available = quota_available;
2600
                var total_available = available;
2601

  
2602
                // priority to project limits
2603
                if (project_available < available ) {
2604
                  available = project_available;
2605
                  limit = value.project_limit;
2606
                  usage = value.project_usage;
2607
                  total_available = available;
2608
                }
2609

  
2488 2610
                var available_active = available;
2611

  
2612
                // corresponding total quota
2489 2613
                var keysplit = key.split(".");
2490
                var limit_active = value.limit;
2491
                var usage_active = value.usage;
2492
                keysplit[keysplit.length-1] = "total_" + keysplit[keysplit.length-1];
2493
                var activekey = keysplit.join(".");
2494
                var exists = resp.system[activekey];
2495
                if (exists) {
2496
                    available_active = exists.limit - exists.usage;
2497
                    limit_active = exists.limit;
2498
                    usage_active = exists.usage;
2614
                var last_part = keysplit.pop();
2615
                var activekey = keysplit.join(".") + "." + "total_" + last_part;
2616
                var total = resp[uuid][activekey];
2617
                if (total) {
2618
                  total_available = total.limit - total.usage;
2619
                  var total_project_available = total.project_limit - total.project_usage;
2620
                  var total_limit = total.limit;
2621
                  var total_usage = total.usage;
2622
                  if (total_project_available < total_available) {
2623
                    total_available = total_project_available;
2624
                    total_limit = total.project_limit;
2625
                    total_usage = total.project_usage;
2626
                  }
2627
                  if (total_available < available) {
2628
                    available = total_available;
2629
                    limit = total_limit;
2630
                    usage = total_usage;
2631
                  }
2499 2632
                }
2500
                return _.extend(value, {'name': key, 'id': key, 
2633

  
2634
                var limit_active = limit;
2635
                var usage_active = usage;
2636

  
2637
                var id = uuid + ":" + key;
2638
                return _.extend(value, {
2639
                          'name': key, 
2640
                          'id': id, 
2501 2641
                          'available': available,
2502 2642
                          'available_active': available_active,
2643
                          'total_available': total_available,
2503 2644
                          'limit_active': limit_active,
2645
                          'project_id': uuid,
2504 2646
                          'usage_active': usage_active,
2505
                          'resource': snf.storage.resources.get(key)});
2647
                          'resource': snf.storage.resources.get(key)
2648
                });
2649
              }));
2506 2650
            });
2507
            return filtered;
2651
            return parsed;
2508 2652
        },
2509 2653
        
2654
        project_key: function(project, key) {
2655
          return project + ":" + key;
2656
        },
2657

  
2510 2658
        get_by_id: function(k) {
2511 2659
          return this.filter(function(q) { return q.get('name') == k})[0]
2512 2660
        },
2513 2661

  
2514 2662
        get_available_for_vm: function(options) {
2515
          var quotas = synnefo.storage.quotas;
2663
          var quotas = this;
2516 2664
          var key = 'available';
2517 2665
          var available_quota = {};
2518 2666
          _.each(['cyclades.ram', 'cyclades.cpu', 'cyclades.disk'], 
......
2521 2669
              available_quota[key.replace('cyclades.', '')] = value;
2522 2670
          });
2523 2671
          return available_quota;
2672
        },
2673
        
2674
        can_create: function(type) {
2675
          return this.get_available_projects(this.required_quota[type]).length > 0;
2676
        },
2677

  
2678
        get_available_projects: function(quotas) {
2679
          return synnefo.storage.projects.filter(function(project) {
2680
            return project.quotas.can_fit(quotas);
2681
          });
2682
        },
2683

  
2684
        can_fit: function(quotas, total, _issues) {
2685
          var issues = [];
2686
          if (total === undefined) { total = false }
2687
          _.each(quotas, function(value, key) {
2688
            var q = this.get(key);
2689
            if (!q) { issues.push(key); return }
2690
            var quota = q.get('available_active');
2691
            if (total) {
2692
              quota = q.get('available');
2693
            }
2694
            if (quota < value) {
2695
              issues.push(key);
2696
            }
2697
          }, this);
2698
          if (_issues) { return issues }
2699
          return issues.length === 0;
2524 2700
        }
2525 2701
    })
2526 2702

  
......
2533 2709
        api_type: 'accounts',
2534 2710
        path: 'resources',
2535 2711
        model: models.Network,
2712
        display_name_map: {
2713
          'cyclades.vm': 'Machines',
2714
          'cyclades.ram': 'Memory size',
2715
          'cyclades.total_ram': 'Memory size (total)',
2716
          'cyclades.cpu': 'CPUs',
2717
          'cyclades.total_cpu': 'CPUs (total)',
2718
          'cyclades.floating_ip': 'IP Addresses',
2719
          'pithos.diskpace': 'Storage space',
2720
          'cyclades.disk': 'Disk size',
2721
          'cyclades.network.private': 'Private networks'
2722
        },
2536 2723

  
2537 2724
        parse: function(resp) {
2538 2725
            return _.map(resp, function(value, key) {
2539
                return _.extend(value, {'name': key, 'id': key});
2540
            })
2726
                var display_name = this.display_name_map[key] || key;
2727
                return _.extend(value, {
2728
                  'name': key, 
2729
                  'id': key,
2730
                  'display_name': display_name
2731
                });
2732
            }, this);
2733
        }
2734
    });
2735
    
2736
    models.ProjectQuotas = models.Quotas.extend({})
2737
    _.extend(models.ProjectQuotas.prototype, Backbone.FilteredCollection.prototype);
2738
    models.ProjectQuotas.prototype.get = function(key) {
2739
      key = this.project_id + ":" + key;
2740
      return models.ProjectQuotas.__super__.get.call(this, key);
2741
    }
2742

  
2743
    models.Project = models.Model.extend({
2744
        api_type: 'accounts',
2745
        path: 'projects',
2746

  
2747
        initialize: function() {
2748
          var self = this;
2749
          this.quotas = new models.ProjectQuotas(undefined, {
2750
            collection: synnefo.storage.quotas,
2751
            collectionFilter: function(m) {
2752
              return self.id == m.get('project_id')
2753
          }});
2754
          this.quotas.bind('change', function() {
2755
            self.trigger('change:_quotas');
2756
          });
2757
          this.quotas.project_id = this.id;
2758
          models.Project.__super__.initialize.apply(this, arguments);
2759
        }
2760
    });
2761

  
2762
    models.Projects = models.Collection.extend({
2763
        api_type: 'accounts',
2764
        path: 'projects',
2765
        model: models.Project,
2766
        supportIncUpdates: false,
2767
        user_project_uuid: null,
2768
        
2769
        url: function() {
2770
          var args = Array.prototype.splice.call(arguments, 0);
2771
          var url = models.Projects.__super__.url.apply(this, args);
2772
          return url + "?mode=member";
2773
        },
2774

  
2775
        parse: function(resp) {
2776
          _.each(resp, function(project){
2777
            if (project.base_project) {
2778
              this.user_project_uuid = project.id;
2779
            }
2780
            if (project.id == synnefo.user.get_username()) {
2781
              project.name = "User project"
2782
            }
2783
          }, this);
2784
          return resp;
2785
        },
2786

  
2787
        get_user_project: function() {
2788
          return this.get(synnefo.user.current_username);
2789
        },
2790

  
2791
        comparator: function(project) {
2792
            if (project.get('base_project')) { return -100 }
2793
            return project.get('name');
2541 2794
        }
2542 2795
    });
2543 2796
    
......
2548 2801
    snf.storage.keys = new models.PublicKeys();
2549 2802
    snf.storage.resources = new models.Resources();
2550 2803
    snf.storage.quotas = new models.Quotas();
2804
    snf.storage.projects = new models.Projects();
2551 2805
    snf.storage.public_pools = new models.PublicPools();
2552 2806

  
2553 2807
})(this);

Also available in: Unified diff