Skip to content
Permalink
Browse files Browse the repository at this point in the history
Implement quotas for security groups.
Fixes LP Bug #969545 for Diablo.

Change-Id: Ibc02256b6debd29c56307320acc48e9cfae85ba9
  • Loading branch information
dprince committed Apr 19, 2012
1 parent 947a25b commit 8c8735a
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 6 deletions.
12 changes: 12 additions & 0 deletions nova/api/ec2/cloud.py
Expand Up @@ -42,6 +42,7 @@
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
Expand Down Expand Up @@ -856,6 +857,13 @@ def authorize_security_group_ingress(self, context, group_name=None,
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,
Expand Down Expand Up @@ -908,6 +916,10 @@ def create_security_group(self, context, group_name, group_description):
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,
Expand Down
5 changes: 4 additions & 1 deletion nova/api/openstack/contrib/quotas.py
Expand Up @@ -40,6 +40,8 @@ def _format_quota_set(self, project_id, quota_set):
'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):
Expand All @@ -56,7 +58,8 @@ def update(self, req, id, body):
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])
Expand Down
12 changes: 12 additions & 0 deletions nova/api/openstack/contrib/security_groups.py
Expand Up @@ -26,6 +26,7 @@
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
Expand Down Expand Up @@ -136,6 +137,10 @@ def create(self, req, body):
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):
Expand Down Expand Up @@ -219,6 +224,13 @@ def create(self, req, body):
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,
Expand Down
10 changes: 10 additions & 0 deletions nova/db/api.py
Expand Up @@ -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)


####################


Expand Down Expand Up @@ -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)


###################


Expand Down
19 changes: 19 additions & 0 deletions nova/db/sqlalchemy/api.py
Expand Up @@ -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()


###################


Expand Down Expand Up @@ -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()


###################


Expand Down
32 changes: 32 additions & 0 deletions nova/quota.py
Expand Up @@ -44,6 +44,10 @@
'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():
Expand All @@ -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():
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 12 additions & 4 deletions nova/tests/api/openstack/contrib/test_quotas.py
Expand Up @@ -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():
Expand Down Expand Up @@ -60,7 +61,9 @@ def test_format_quota_set(self):
'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)
Expand Down Expand Up @@ -95,7 +98,9 @@ def test_quotas_defaults(self):
'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)

Expand Down Expand Up @@ -123,7 +128,10 @@ def test_quotas_update_as_admin(self):
'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'
Expand Down
44 changes: 44 additions & 0 deletions nova/tests/api/openstack/contrib/test_security_groups.py
Expand Up @@ -22,10 +22,13 @@
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')
Expand Down Expand Up @@ -257,6 +260,19 @@ def test_create_security_group_non_string_description_json(self):
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"
Expand Down Expand Up @@ -918,6 +934,34 @@ def test_create_rule_with_same_group_parent_id_json(self):
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'])
Expand Down
25 changes: 25 additions & 0 deletions nova/tests/test_cloud.py
Expand Up @@ -256,6 +256,31 @@ def test_delete_security_group_no_params(self):
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)
Expand Down
32 changes: 31 additions & 1 deletion nova/tests/test_quota.py
Expand Up @@ -43,7 +43,9 @@ def setUp(self):
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'
Expand Down Expand Up @@ -185,6 +187,34 @@ def test_unlimited_floating_ips(self):
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)
Expand Down

0 comments on commit 8c8735a

Please sign in to comment.