Sophie

Sophie

distrib > Fedora > 16 > i386 > by-pkgid > 5c655bb31b7eacedb96e8b5da992c6ce > files > 10

openstack-nova-2011.3.1-11.fc16.src.rpm

From 8c8735a73afb16d5856f0aa6088e9ae406c52beb Mon Sep 17 00:00:00 2001
From: Dan Prince <dprince@redhat.com>
Date: Wed, 11 Apr 2012 16:21:29 -0400
Subject: [PATCH] Implement quotas for security groups.

Fixes LP Bug #969545 for Diablo.

Change-Id: Ibc02256b6debd29c56307320acc48e9cfae85ba9
---
 nova/api/ec2/cloud.py                              |   12 +++++
 nova/api/openstack/contrib/quotas.py               |    5 ++-
 nova/api/openstack/contrib/security_groups.py      |   12 +++++
 nova/db/api.py                                     |   10 ++++
 nova/db/sqlalchemy/api.py                          |   19 ++++++++
 nova/quota.py                                      |   32 ++++++++++++++
 nova/tests/api/openstack/contrib/test_quotas.py    |   16 +++++--
 .../api/openstack/contrib/test_security_groups.py  |   44 ++++++++++++++++++++
 nova/tests/test_cloud.py                           |   25 +++++++++++
 nova/tests/test_quota.py                           |   32 ++++++++++++++-
 10 files changed, 201 insertions(+), 6 deletions(-)

diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 64fce0c..cd41921 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -42,6 +42,7 @@ from nova import ipv6
 from nova import log as logging
 from nova import network
 from nova import rpc
+from nova import quota
 from nova import utils
 from nova import volume
 from nova.api.ec2 import ec2utils
@@ -856,6 +857,13 @@ class CloudController(object):
                     raise exception.ApiError(_(err) % values_for_rule)
                 postvalues.append(values_for_rule)
 
+        allowed = quota.allowed_security_group_rules(context,
+                                                   security_group['id'],
+                                                   1)
+        if allowed < 1:
+            msg = _("Quota exceeded, too many security group rules.")
+            raise exception.ApiError(msg)
+
         for values_for_rule in postvalues:
             security_group_rule = db.security_group_rule_create(
                     context,
@@ -908,6 +916,10 @@ class CloudController(object):
         if db.security_group_exists(context, context.project_id, group_name):
             raise exception.ApiError(_('group %s already exists') % group_name)
 
+        if quota.allowed_security_groups(context, 1) < 1:
+            msg = _("Quota exceeded, too many security groups.")
+            raise exception.ApiError(msg)
+
         group = {'user_id': context.user_id,
                  'project_id': context.project_id,
                  'name': group_name,
diff --git a/nova/api/openstack/contrib/quotas.py b/nova/api/openstack/contrib/quotas.py
index 459b71d..1688326 100644
--- a/nova/api/openstack/contrib/quotas.py
+++ b/nova/api/openstack/contrib/quotas.py
@@ -40,6 +40,8 @@ class QuotaSetsController(object):
             'instances': quota_set['instances'],
             'injected_files': quota_set['injected_files'],
             'cores': quota_set['cores'],
+            'security_groups': quota_set['security_groups'],
+            'security_group_rules': quota_set['security_group_rules'],
         }}
 
     def show(self, req, id):
@@ -56,7 +58,8 @@ class QuotaSetsController(object):
         project_id = id
         resources = ['metadata_items', 'injected_file_content_bytes',
                 'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances',
-                'injected_files', 'cores']
+                'injected_files', 'cores', 'security_groups',
+                'security_group_rules']
         for key in body['quota_set'].keys():
             if key in resources:
                 value = int(body['quota_set'][key])
diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py
index e8f1f2c..78d4881 100644
--- a/nova/api/openstack/contrib/security_groups.py
+++ b/nova/api/openstack/contrib/security_groups.py
@@ -26,6 +26,7 @@ from nova import flags
 from nova import log as logging
 from nova import rpc
 from nova import utils
+from nova import quota
 from nova.api.openstack import common
 from nova.api.openstack import extensions
 from nova.api.openstack import wsgi
@@ -136,6 +137,10 @@ class SecurityGroupController(object):
         group_name = group_name.strip()
         group_description = group_description.strip()
 
+        if quota.allowed_security_groups(context, 1) < 1:
+            msg = _("Quota exceeded, too many security groups.")
+            raise exc.HTTPBadRequest(explanation=msg)
+
         LOG.audit(_("Create Security Group %s"), group_name, context=context)
         self.compute_api.ensure_default_security_group(context)
         if db.security_group_exists(context, context.project_id, group_name):
@@ -219,6 +224,13 @@ class SecurityGroupRulesController(SecurityGroupController):
             msg = _('This rule already exists in group %s') % parent_group_id
             raise exc.HTTPBadRequest(explanation=msg)
 
+        allowed = quota.allowed_security_group_rules(context,
+                                                   parent_group_id,
+                                                   1)
+        if allowed < 1:
+            msg = _("Quota exceeded, too many security group rules.")
+            raise exc.HTTPBadRequest(explanation=msg)
+
         security_group_rule = db.security_group_rule_create(context, values)
 
         self.compute_api.trigger_security_group_rules_refresh(context,
diff --git a/nova/db/api.py b/nova/db/api.py
index c0e44d2..7d241e5 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1098,6 +1098,11 @@ def security_group_destroy_all(context):
     return IMPL.security_group_destroy_all(context)
 
 
+def security_group_count_by_project(context, project_id):
+    """Count number of security groups in a project."""
+    return IMPL.security_group_count_by_project(context, project_id)
+
+
 ####################
 
 
@@ -1129,6 +1134,11 @@ def security_group_rule_get(context, security_group_rule_id):
     return IMPL.security_group_rule_get(context, security_group_rule_id)
 
 
+def security_group_rule_count_by_group(context, security_group_id):
+    """Count rules in a given security group."""
+    return IMPL.security_group_rule_count_by_group(context, security_group_id)
+
+
 ###################
 
 
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 6108585..b060926 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -2803,6 +2803,16 @@ def security_group_destroy_all(context, session=None):
                         'updated_at': literal_column('updated_at')})
 
 
+@require_context
+def security_group_count_by_project(context, project_id):
+    authorize_project_context(context, project_id)
+    session = get_session()
+    return session.query(models.SecurityGroup).\
+                         filter_by(deleted=False).\
+                         filter_by(project_id=project_id).\
+                         count()
+
+
 ###################
 
 
@@ -2884,6 +2894,15 @@ def security_group_rule_destroy(context, security_group_rule_id):
         security_group_rule.delete(session=session)
 
 
+@require_context
+def security_group_rule_count_by_group(context, security_group_id):
+    session = get_session()
+    return session.query(models.SecurityGroupIngressRule).\
+                         filter_by(deleted=False).\
+                         filter_by(parent_group_id=security_group_id).\
+                         count()
+
+
 ###################
 
 
diff --git a/nova/quota.py b/nova/quota.py
index 7714777..d491f4a 100644
--- a/nova/quota.py
+++ b/nova/quota.py
@@ -44,6 +44,10 @@ flags.DEFINE_integer('quota_max_injected_file_content_bytes', 10 * 1024,
                      'number of bytes allowed per injected file')
 flags.DEFINE_integer('quota_max_injected_file_path_bytes', 255,
                      'number of bytes allowed per injected file path')
+flags.DEFINE_integer('quota_security_groups', 10,
+                     'number of security groups per project')
+flags.DEFINE_integer('quota_security_group_rules', 20,
+                     'number of security rules per security group')
 
 
 def _get_default_quotas():
@@ -58,6 +62,8 @@ def _get_default_quotas():
         'injected_files': FLAGS.quota_max_injected_files,
         'injected_file_content_bytes':
             FLAGS.quota_max_injected_file_content_bytes,
+        'security_groups': FLAGS.quota_security_groups,
+        'security_group_rules': FLAGS.quota_security_group_rules,
     }
     # -1 in the quota flags means unlimited
     for key in defaults.keys():
@@ -134,6 +140,32 @@ def allowed_floating_ips(context, requested_floating_ips):
     return min(requested_floating_ips, allowed_floating_ips)
 
 
+def allowed_security_groups(context, requested_security_groups):
+    """Check quota and return min(requested, allowed) security groups."""
+    project_id = context.project_id
+    context = context.elevated()
+    used_sec_groups = db.security_group_count_by_project(context, project_id)
+    quota = get_project_quotas(context, project_id)
+    allowed_sec_groups = _get_request_allotment(requested_security_groups,
+                                                  used_sec_groups,
+                                                  quota['security_groups'])
+    return min(requested_security_groups, allowed_sec_groups)
+
+
+def allowed_security_group_rules(context, security_group_id,
+        requested_rules):
+    """Check quota and return min(requested, allowed) sec group rules."""
+    project_id = context.project_id
+    context = context.elevated()
+    used_rules = db.security_group_rule_count_by_group(context,
+                                                            security_group_id)
+    quota = get_project_quotas(context, project_id)
+    allowed_rules = _get_request_allotment(requested_rules,
+                                              used_rules,
+                                              quota['security_group_rules'])
+    return min(requested_rules, allowed_rules)
+
+
 def _calculate_simple_quota(context, resource, requested):
     """Check quota for resource; return min(requested, allowed)."""
     quota = get_project_quotas(context, context.project_id)
diff --git a/nova/tests/api/openstack/contrib/test_quotas.py b/nova/tests/api/openstack/contrib/test_quotas.py
index e391e5f..fe0a165 100644
--- a/nova/tests/api/openstack/contrib/test_quotas.py
+++ b/nova/tests/api/openstack/contrib/test_quotas.py
@@ -29,7 +29,8 @@ def quota_set(id):
     return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10,
             'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10,
             'instances': 10, 'injected_files': 5, 'cores': 20,
-            'injected_file_content_bytes': 10240}}
+            'injected_file_content_bytes': 10240,
+            'security_groups': 10, 'security_group_rules': 20}}
 
 
 def quota_set_list():
@@ -60,7 +61,9 @@ class QuotaSetsTest(test.TestCase):
             'metadata_items': 128,
             'gigabytes': 1000,
             'injected_files': 5,
-            'injected_file_content_bytes': 10240}
+            'injected_file_content_bytes': 10240,
+            'security_groups': 10,
+            'security_group_rules': 20}
 
         quota_set = QuotaSetsController()._format_quota_set('1234',
                                                             raw_quota_set)
@@ -95,7 +98,9 @@ class QuotaSetsTest(test.TestCase):
                     'floating_ips': 10,
                     'metadata_items': 128,
                     'injected_files': 5,
-                    'injected_file_content_bytes': 10240}}
+                    'injected_file_content_bytes': 10240,
+                    'security_groups': 10,
+                    'security_group_rules': 20}}
 
         self.assertEqual(json.loads(res.body), expected)
 
@@ -123,7 +128,10 @@ class QuotaSetsTest(test.TestCase):
                              'cores': 50, 'ram': 51200, 'volumes': 10,
                              'gigabytes': 1000, 'floating_ips': 10,
                              'metadata_items': 128, 'injected_files': 5,
-                             'injected_file_content_bytes': 10240}}
+                             'injected_file_content_bytes': 10240,
+                             'security_groups': 40,
+                             'security_group_rules': 80
+                             }}
 
         req = webob.Request.blank('/v1.1/fake/os-quota-sets/update_me')
         req.method = 'PUT'
diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py
index d0b25e0..839f442 100644
--- a/nova/tests/api/openstack/contrib/test_security_groups.py
+++ b/nova/tests/api/openstack/contrib/test_security_groups.py
@@ -22,10 +22,13 @@ import webob
 from xml.dom import minidom
 
 from nova import exception
+from nova import flags
 from nova import test
 from nova.api.openstack.contrib import security_groups
 from nova.tests.api.openstack import fakes
 
+FLAGS = flags.FLAGS
+
 
 def _get_create_request_json(body_dict):
     req = webob.Request.blank('/v1.1/fake/os-security-groups')
@@ -257,6 +260,19 @@ class TestSecurityGroups(test.TestCase):
         response = _create_security_group_json(security_group)
         self.assertEquals(response.status_int, 400)
 
+    def test_create_security_group_quota_limit(self):
+        security_group = {}
+        for num in range(1, FLAGS.quota_security_groups):
+            security_group['name'] = "test%i" % num
+            security_group['description'] = "test%i" % num
+            response = _create_security_group_json(security_group)
+            self.assertEquals(response.status_int, 200)
+
+        security_group['name'] = "test_to_many"
+        security_group['description'] = "test_to_many"
+        response = _create_security_group_json(security_group)
+        self.assertEquals(response.status_int, 400)
+
     def test_get_security_group_list(self):
         security_group = {}
         security_group['name'] = "test"
@@ -918,6 +934,34 @@ class TestSecurityGroupRules(test.TestCase):
         response = self._create_security_group_rule_json(rules)
         self.assertEquals(response.status_int, 400)
 
+    def test_create_rule_quota_limit(self):
+        #NOTE: subtract 1 because we create 1 rule in setup
+        for num in range(100, (100 + FLAGS.quota_security_group_rules) - 1):
+            rule = {
+                      "security_group_rule": {
+                            "ip_protocol": "tcp",
+                            "from_port": num,
+                            "to_port": num,
+                            "parent_group_id": "%s"
+                                       % self.parent_security_group['id'],
+                         }
+                      }
+            response = self._create_security_group_rule_json(rule)
+            print response.body
+            self.assertEquals(response.status_int, 200)
+
+        rule = {
+                  "security_group_rule": {
+                        "ip_protocol": "tcp",
+                        "from_port": "121",
+                        "to_port": "121",
+                        "parent_group_id": "%s"
+                                   % self.parent_security_group['id'],
+                     }
+                  }
+        response = self._create_security_group_rule_json(rule)
+        self.assertEquals(response.status_int, 400)
+
     def test_delete(self):
         response = self._delete_security_group_rule(
                                   self.security_group_rule['id'])
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index fa45974..c6fe41b 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -256,6 +256,31 @@ class CloudTestCase(test.TestCase):
         delete = self.cloud.delete_security_group
         self.assertRaises(exception.ApiError, delete, self.context)
 
+    def test_security_group_ingress_quota_limit(self):
+        self.flags(quota_security_group_rules=20)
+        kwargs = {'project_id': self.context.project_id, 'name': 'test'}
+        sec_group = db.security_group_create(self.context, kwargs)
+        authz = self.cloud.authorize_security_group_ingress
+        for i in range(100, 120):
+            kwargs = {'to_port': i, 'from_port': i, 'ip_protocol': 'tcp'}
+            authz(self.context, group_id=sec_group['id'], **kwargs)
+
+        kwargs = {'to_port': 121, 'from_port': 121, 'ip_protocol': 'tcp'}
+        self.assertRaises(exception.ApiError, authz, self.context,
+                              group_id=sec_group['id'], **kwargs)
+
+    def test_security_group_quota_limit(self):
+        self.flags(quota_security_groups=10)
+        for i in range(1, 10):
+            name = 'test name %i' % i
+            descript = 'test description %i' % i
+            create = self.cloud.create_security_group
+            result = create(self.context, name, descript)
+
+        # 11'th group should fail
+        self.assertRaises(exception.ApiError,
+                          create, self.context, 'foo', 'bar')
+
     def test_authorize_security_group_ingress(self):
         kwargs = {'project_id': self.context.project_id, 'name': 'test'}
         sec = db.security_group_create(self.context, kwargs)
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index f4b481e..9541a13 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -43,7 +43,9 @@ class QuotaTestCase(test.TestCase):
                    quota_cores=4,
                    quota_volumes=2,
                    quota_gigabytes=20,
-                   quota_floating_ips=1)
+                   quota_floating_ips=1,
+                   quota_security_groups=10,
+                   quota_security_group_rules=20)
 
         self.network = self.network = self.start_service('network')
         self.user_id = 'admin'
@@ -185,6 +187,34 @@ class QuotaTestCase(test.TestCase):
         floating_ips = quota.allowed_floating_ips(self.context, 101)
         self.assertEqual(floating_ips, 101)
 
+    def test_unlimited_security_groups(self):
+        self.flags(quota_security_groups=10)
+        security_groups = quota.allowed_security_groups(self.context, 100)
+        self.assertEqual(security_groups, 10)
+        db.quota_create(self.context, self.project_id, 'security_groups', None)
+        security_groups = quota.allowed_security_groups(self.context, 100)
+        self.assertEqual(security_groups, 100)
+        security_groups = quota.allowed_security_groups(self.context, 101)
+        self.assertEqual(security_groups, 101)
+
+    def test_unlimited_security_group_rules(self):
+
+        def fake_security_group_rule_count_by_group(context, sec_group_id):
+            return 0
+
+        self.stubs.Set(db, 'security_group_rule_count_by_group',
+                       fake_security_group_rule_count_by_group)
+
+        self.flags(quota_security_group_rules=20)
+        rules = quota.allowed_security_group_rules(self.context, 1234, 100)
+        self.assertEqual(rules, 20)
+        db.quota_create(self.context, self.project_id, 'security_group_rules',
+                        None)
+        rules = quota.allowed_security_group_rules(self.context, 1234, 100)
+        self.assertEqual(rules, 100)
+        rules = quota.allowed_security_group_rules(self.context, 1234, 101)
+        self.assertEqual(rules, 101)
+
     def test_unlimited_metadata_items(self):
         self.flags(quota_metadata_items=10)
         items = quota.allowed_metadata_items(self.context, 100)