Add quotas for Server Groups (V2 API compatibility & V2.1 support)
Server groups can be used to control the affinity and anti-affinity scheduling policy for a group of servers (instances). Whilst this is a useful mechanism for users such scheduling decisions need to be balanced by a deployers requirements to make effective use of the available capacity. This change adds quota values to constrain the number and size of server groups a user can create. Two new quota values are be introduced to limit the number of server groups and the number of servers in a server group. These will follow the existing pattern for quotas in that: * They are defined by config values, which also include the default value * They can be defined per project or per user within a project * A value of -1 for either quota will be treated as unlimited * Defaults can be set via the quota groups API * Values may be changed at any time but will only take effect at the next server group or server create. Reducing the quota will not affect any existing groups, but new servers will not be allowed into group that have become over quota. This is part one of a linked sequences of changes that implement the new quotas - split to make the reviews easier. This part adds the definition of the new quota values, but leaves the V2 API unchanged. The V2.1 API is updated as it shows all quota values. The second part adds the new V2 API extension to make the new quota values visible and changeable. At this point a Tempest change is required to get a clean run as it checks for a specific set of values. The third part implements the quota checks themselves. Thanks to Cyril Roelandt for supplying some of the unit tests. Co-authored-by: Cyril Roelandt <cyril.roelandt@enovance.com> Implements: blueprint server-group-quotas DocImpact Change-Id: Ib281e43eabfbd176454bde7f0622d46fb04fcb79
This commit is contained in:
parent
989f054a05
commit
597c46fde1
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 45
|
||||
"security_groups": 45,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,14 @@
|
||||
"maxTotalInstances": 10,
|
||||
"maxTotalKeypairs": 100,
|
||||
"maxTotalRAMSize": 51200,
|
||||
"maxServerGroups": 10,
|
||||
"maxServerGroupMembers": 10,
|
||||
"totalCoresUsed": 0,
|
||||
"totalInstancesUsed": 0,
|
||||
"totalRAMUsed": 0,
|
||||
"totalSecurityGroupsUsed": 0,
|
||||
"totalFloatingIpsUsed": 0
|
||||
"totalFloatingIpsUsed": 0,
|
||||
"totalServerGroupsUsed": 0
|
||||
},
|
||||
"rate": []
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ from nova import utils
|
||||
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
# Quotas that are only enabled by specific extensions
|
||||
EXTENDED_QUOTAS = {'server_groups': 'os-server-group-quotas',
|
||||
'server_group_members': 'os-server-group-quotas'}
|
||||
|
||||
|
||||
authorize = extensions.extension_authorizer('compute', 'quota_classes')
|
||||
@ -39,21 +42,35 @@ class QuotaClassTemplate(xmlutil.TemplateBuilder):
|
||||
root.set('id')
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
elem = xmlutil.SubTemplateElement(root, resource)
|
||||
elem.text = resource
|
||||
if resource not in EXTENDED_QUOTAS:
|
||||
elem = xmlutil.SubTemplateElement(root, resource)
|
||||
elem.text = resource
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class QuotaClassSetsController(wsgi.Controller):
|
||||
|
||||
supported_quotas = []
|
||||
|
||||
def __init__(self, ext_mgr):
|
||||
self.ext_mgr = ext_mgr
|
||||
self.supported_quotas = QUOTAS.resources
|
||||
for resource, extension in EXTENDED_QUOTAS.items():
|
||||
if not self.ext_mgr.is_loaded(extension):
|
||||
self.supported_quotas.remove(resource)
|
||||
|
||||
def _format_quota_set(self, quota_class, quota_set):
|
||||
"""Convert the quota object to a result dict."""
|
||||
|
||||
result = dict(id=str(quota_class))
|
||||
if quota_class:
|
||||
result = dict(id=str(quota_class))
|
||||
else:
|
||||
result = {}
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
result[resource] = quota_set[resource]
|
||||
for resource in self.supported_quotas:
|
||||
if resource in quota_set:
|
||||
result[resource] = quota_set[resource]
|
||||
|
||||
return dict(quota_class_set=result)
|
||||
|
||||
@ -63,8 +80,8 @@ class QuotaClassSetsController(wsgi.Controller):
|
||||
authorize(context)
|
||||
try:
|
||||
nova.context.authorize_quota_class_context(context, id)
|
||||
return self._format_quota_set(id,
|
||||
QUOTAS.get_class_quotas(context, id))
|
||||
values = QUOTAS.get_class_quotas(context, id)
|
||||
return self._format_quota_set(id, values)
|
||||
except exception.Forbidden:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
@ -73,27 +90,39 @@ class QuotaClassSetsController(wsgi.Controller):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
quota_class = id
|
||||
bad_keys = []
|
||||
|
||||
if not self.is_valid_body(body, 'quota_class_set'):
|
||||
msg = _("quota_class_set not specified")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
quota_class_set = body['quota_class_set']
|
||||
for key in quota_class_set.keys():
|
||||
if key in QUOTAS:
|
||||
try:
|
||||
value = utils.validate_integer(
|
||||
if key not in self.supported_quotas:
|
||||
bad_keys.append(key)
|
||||
continue
|
||||
try:
|
||||
value = utils.validate_integer(
|
||||
body['quota_class_set'][key], key)
|
||||
except exception.InvalidInput as e:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=e.format_message())
|
||||
try:
|
||||
db.quota_class_update(context, quota_class, key, value)
|
||||
except exception.QuotaClassNotFound:
|
||||
db.quota_class_create(context, quota_class, key, value)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
return {'quota_class_set': QUOTAS.get_class_quotas(context,
|
||||
quota_class)}
|
||||
except exception.InvalidInput as e:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=e.format_message())
|
||||
|
||||
if bad_keys:
|
||||
msg = _("Bad key(s) %s in quota_set") % ",".join(bad_keys)
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
for key in quota_class_set.keys():
|
||||
value = utils.validate_integer(
|
||||
body['quota_class_set'][key], key)
|
||||
try:
|
||||
db.quota_class_update(context, quota_class, key, value)
|
||||
except exception.QuotaClassNotFound:
|
||||
db.quota_class_create(context, quota_class, key, value)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
values = QUOTAS.get_class_quotas(context, quota_class)
|
||||
return self._format_quota_set(None, values)
|
||||
|
||||
|
||||
class Quota_classes(extensions.ExtensionDescriptor):
|
||||
@ -109,7 +138,7 @@ class Quota_classes(extensions.ExtensionDescriptor):
|
||||
resources = []
|
||||
|
||||
res = extensions.ResourceExtension('os-quota-class-sets',
|
||||
QuotaClassSetsController())
|
||||
QuotaClassSetsController(self.ext_mgr))
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
||||
|
@ -30,9 +30,14 @@ from nova import utils
|
||||
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
LOG = logging.getLogger(__name__)
|
||||
NON_QUOTA_KEYS = ['tenant_id', 'id', 'force']
|
||||
|
||||
# Quotas that are only enabled by specific extensions
|
||||
EXTENDED_QUOTAS = {'server_groups': 'os-server-group-quotas',
|
||||
'server_group_members': 'os-server-group-quotas'}
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
authorize_update = extensions.extension_authorizer('compute', 'quotas:update')
|
||||
authorize_show = extensions.extension_authorizer('compute', 'quotas:show')
|
||||
@ -45,24 +50,35 @@ class QuotaTemplate(xmlutil.TemplateBuilder):
|
||||
root.set('id')
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
elem = xmlutil.SubTemplateElement(root, resource)
|
||||
elem.text = resource
|
||||
if resource not in EXTENDED_QUOTAS:
|
||||
elem = xmlutil.SubTemplateElement(root, resource)
|
||||
elem.text = resource
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class QuotaSetsController(wsgi.Controller):
|
||||
|
||||
supported_quotas = []
|
||||
|
||||
def __init__(self, ext_mgr):
|
||||
self.ext_mgr = ext_mgr
|
||||
self.supported_quotas = QUOTAS.resources
|
||||
for resource, extension in EXTENDED_QUOTAS.items():
|
||||
if not self.ext_mgr.is_loaded(extension):
|
||||
self.supported_quotas.remove(resource)
|
||||
|
||||
def _format_quota_set(self, project_id, quota_set):
|
||||
"""Convert the quota object to a result dict."""
|
||||
|
||||
result = dict(id=str(project_id))
|
||||
if project_id:
|
||||
result = dict(id=str(project_id))
|
||||
else:
|
||||
result = {}
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
result[resource] = quota_set[resource]
|
||||
for resource in self.supported_quotas:
|
||||
if resource in quota_set:
|
||||
result[resource] = quota_set[resource]
|
||||
|
||||
return dict(quota_set=result)
|
||||
|
||||
@ -140,9 +156,10 @@ class QuotaSetsController(wsgi.Controller):
|
||||
msg = _("quota_set not specified")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
quota_set = body['quota_set']
|
||||
|
||||
for key, value in quota_set.items():
|
||||
if (key not in QUOTAS and
|
||||
key not in NON_QUOTA_KEYS):
|
||||
if (key not in self.supported_quotas
|
||||
and key not in NON_QUOTA_KEYS):
|
||||
bad_keys.append(key)
|
||||
continue
|
||||
if key == 'force' and extended_loaded:
|
||||
@ -158,7 +175,7 @@ class QuotaSetsController(wsgi.Controller):
|
||||
|
||||
LOG.debug("force update quotas: %s", force_update)
|
||||
|
||||
if len(bad_keys) > 0:
|
||||
if bad_keys:
|
||||
msg = _("Bad key(s) %s in quota_set") % ",".join(bad_keys)
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
@ -203,13 +220,15 @@ class QuotaSetsController(wsgi.Controller):
|
||||
key, value, user_id=user_id)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
return {'quota_set': self._get_quotas(context, id, user_id=user_id)}
|
||||
values = self._get_quotas(context, id, user_id=user_id)
|
||||
return self._format_quota_set(None, values)
|
||||
|
||||
@wsgi.serializers(xml=QuotaTemplate)
|
||||
def defaults(self, req, id):
|
||||
context = req.environ['nova.context']
|
||||
authorize_show(context)
|
||||
return self._format_quota_set(id, QUOTAS.get_defaults(context))
|
||||
values = QUOTAS.get_defaults(context)
|
||||
return self._format_quota_set(id, values)
|
||||
|
||||
def delete(self, req, id):
|
||||
if self.ext_mgr.is_loaded('os-extended-quotas'):
|
||||
|
@ -60,6 +60,9 @@ class UsedLimitsController(wsgi.Controller):
|
||||
'totalFloatingIpsUsed': 'floating_ips',
|
||||
'totalSecurityGroupsUsed': 'security_groups',
|
||||
}
|
||||
if self.ext_mgr.is_loaded('os-server-group-quotas'):
|
||||
quota_map['totalServerGroupsUsed'] = 'server_groups'
|
||||
|
||||
used_limits = {}
|
||||
for display_name, key in quota_map.iteritems():
|
||||
if key in quotas:
|
||||
|
@ -40,7 +40,7 @@ class LimitsController(object):
|
||||
return builder.build(rate_limits, abs_limits)
|
||||
|
||||
def _get_view_builder(self, req):
|
||||
return limits_views.ViewBuilder()
|
||||
return limits_views.ViewBuilderV3()
|
||||
|
||||
|
||||
class Limits(extensions.V3APIExtensionBase):
|
||||
|
@ -45,6 +45,7 @@ class UsedLimitsController(wsgi.Controller):
|
||||
'totalInstancesUsed': 'instances',
|
||||
'totalFloatingIpsUsed': 'floating_ips',
|
||||
'totalSecurityGroupsUsed': 'security_groups',
|
||||
'totalServerGroupsUsed': 'server_groups',
|
||||
}
|
||||
used_limits = {}
|
||||
for display_name, key in quota_map.iteritems():
|
||||
@ -81,4 +82,4 @@ class UsedLimits(extensions.V3APIExtensionBase):
|
||||
return [limits_ext]
|
||||
|
||||
def get_resources(self):
|
||||
return []
|
||||
return []
|
||||
|
@ -39,6 +39,8 @@ update = {
|
||||
'injected_files': common_quota,
|
||||
'injected_file_content_bytes': common_quota,
|
||||
'injected_file_path_bytes': common_quota,
|
||||
'server_groups': common_quota,
|
||||
'server_group_members': common_quota,
|
||||
'force': parameter_types.boolean,
|
||||
},
|
||||
'additionalProperties': False,
|
||||
|
@ -21,6 +21,22 @@ from nova.openstack.common import timeutils
|
||||
class ViewBuilder(object):
|
||||
"""OpenStack API base limits view builder."""
|
||||
|
||||
limit_names = {}
|
||||
|
||||
def __init__(self):
|
||||
self.limit_names = {
|
||||
"ram": ["maxTotalRAMSize"],
|
||||
"instances": ["maxTotalInstances"],
|
||||
"cores": ["maxTotalCores"],
|
||||
"key_pairs": ["maxTotalKeypairs"],
|
||||
"floating_ips": ["maxTotalFloatingIps"],
|
||||
"metadata_items": ["maxServerMeta", "maxImageMeta"],
|
||||
"injected_files": ["maxPersonality"],
|
||||
"injected_file_content_bytes": ["maxPersonalitySize"],
|
||||
"security_groups": ["maxSecurityGroups"],
|
||||
"security_group_rules": ["maxSecurityGroupRules"],
|
||||
}
|
||||
|
||||
def build(self, rate_limits, absolute_limits):
|
||||
rate_limits = self._build_rate_limits(rate_limits)
|
||||
absolute_limits = self._build_absolute_limits(absolute_limits)
|
||||
@ -41,22 +57,10 @@ class ViewBuilder(object):
|
||||
For example: {"ram": 512, "gigabytes": 1024}.
|
||||
|
||||
"""
|
||||
limit_names = {
|
||||
"ram": ["maxTotalRAMSize"],
|
||||
"instances": ["maxTotalInstances"],
|
||||
"cores": ["maxTotalCores"],
|
||||
"key_pairs": ["maxTotalKeypairs"],
|
||||
"floating_ips": ["maxTotalFloatingIps"],
|
||||
"metadata_items": ["maxServerMeta", "maxImageMeta"],
|
||||
"injected_files": ["maxPersonality"],
|
||||
"injected_file_content_bytes": ["maxPersonalitySize"],
|
||||
"security_groups": ["maxSecurityGroups"],
|
||||
"security_group_rules": ["maxSecurityGroupRules"],
|
||||
}
|
||||
limits = {}
|
||||
for name, value in absolute_limits.iteritems():
|
||||
if name in limit_names and value is not None:
|
||||
for name in limit_names[name]:
|
||||
if name in self.limit_names and value is not None:
|
||||
for name in self.limit_names[name]:
|
||||
limits[name] = value
|
||||
return limits
|
||||
|
||||
@ -100,6 +104,8 @@ class ViewBuilder(object):
|
||||
|
||||
class ViewBuilderV3(ViewBuilder):
|
||||
|
||||
def build(self, rate_limits):
|
||||
rate_limits = self._build_rate_limits(rate_limits)
|
||||
return {"limits": {"rate": rate_limits}}
|
||||
def __init__(self):
|
||||
super(ViewBuilderV3, self).__init__()
|
||||
# NOTE In v2.0 these are added by a specific extension
|
||||
self.limit_names["server_groups"] = ["maxServerGroups"]
|
||||
self.limit_names["server_group_members"] = ["maxServerGroupMembers"]
|
||||
|
@ -333,11 +333,17 @@ def _sync_security_groups(context, project_id, user_id, session):
|
||||
return dict(security_groups=_security_group_count_by_project_and_user(
|
||||
context, project_id, user_id, session))
|
||||
|
||||
|
||||
def _sync_server_groups(context, project_id, user_id, session):
|
||||
return dict(server_groups=_instance_group_count_by_project_and_user(
|
||||
context, project_id, user_id, session))
|
||||
|
||||
QUOTA_SYNC_FUNCTIONS = {
|
||||
'_sync_instances': _sync_instances,
|
||||
'_sync_floating_ips': _sync_floating_ips,
|
||||
'_sync_fixed_ips': _sync_fixed_ips,
|
||||
'_sync_security_groups': _sync_security_groups,
|
||||
'_sync_server_groups': _sync_server_groups,
|
||||
}
|
||||
|
||||
###################
|
||||
@ -5882,6 +5888,15 @@ def instance_group_get_all_by_project_id(context, project_id):
|
||||
all()
|
||||
|
||||
|
||||
def _instance_group_count_by_project_and_user(context, project_id,
|
||||
user_id, session=None):
|
||||
return model_query(context, models.InstanceGroup, read_deleted="no",
|
||||
session=session).\
|
||||
filter_by(project_id=project_id).\
|
||||
filter_by(user_id=user_id).\
|
||||
count()
|
||||
|
||||
|
||||
def _instance_group_model_get_query(context, model_class, group_id,
|
||||
session=None, read_deleted='no'):
|
||||
return model_query(context,
|
||||
|
@ -30,7 +30,8 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject):
|
||||
# Version 1.5: Add get_hosts()
|
||||
# Version 1.6: Add get_by_name()
|
||||
# Version 1.7: Deprecate metadetails
|
||||
VERSION = '1.7'
|
||||
# Version 1.8: Add count_members_by_user()
|
||||
VERSION = '1.8'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
@ -160,6 +161,15 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject):
|
||||
return list(set([instance.host for instance in instances
|
||||
if instance.host]))
|
||||
|
||||
@base.remotable
|
||||
def count_members_by_user(self, context, user_id):
|
||||
"""Count the number of instances in a group belonging to a user."""
|
||||
filter_uuids = self.members
|
||||
filters = {'uuid': filter_uuids, 'user_id': user_id, 'deleted': False}
|
||||
instances = objects.InstanceList.get_by_filters(context,
|
||||
filters=filters)
|
||||
return len(instances)
|
||||
|
||||
|
||||
class InstanceGroupList(base.ObjectListBase, base.NovaObject):
|
||||
# Version 1.0: Initial version
|
||||
@ -168,7 +178,8 @@ class InstanceGroupList(base.ObjectListBase, base.NovaObject):
|
||||
# Version 1.2: InstanceGroup <= version 1.5
|
||||
# Version 1.3: InstanceGroup <= version 1.6
|
||||
# Version 1.4: InstanceGroup <= version 1.7
|
||||
VERSION = '1.2'
|
||||
# Version 1.5: InstanceGroup <= version 1.8
|
||||
VERSION = '1.5'
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('InstanceGroup'),
|
||||
@ -180,6 +191,7 @@ class InstanceGroupList(base.ObjectListBase, base.NovaObject):
|
||||
'1.2': '1.5',
|
||||
'1.3': '1.6',
|
||||
'1.4': '1.7',
|
||||
'1.5': '1.8',
|
||||
}
|
||||
|
||||
@base.remotable_classmethod
|
||||
|
@ -39,6 +39,13 @@ def ids_from_security_group(context, security_group):
|
||||
return ids_from_instance(context, security_group)
|
||||
|
||||
|
||||
# TODO(PhilD): This method needs to be cleaned up once the
|
||||
# ids_from_instance helper method is renamed or some common
|
||||
# method is added for objects.quotas.
|
||||
def ids_from_server_group(context, server_group):
|
||||
return ids_from_instance(context, server_group)
|
||||
|
||||
|
||||
class Quotas(base.NovaObject):
|
||||
# Version 1.0: initial version
|
||||
# Version 1.1: Added create_limit() and update_limit()
|
||||
|
@ -72,6 +72,12 @@ quota_opts = [
|
||||
cfg.IntOpt('quota_key_pairs',
|
||||
default=100,
|
||||
help='Number of key pairs per user'),
|
||||
cfg.IntOpt('quota_server_groups',
|
||||
default=10,
|
||||
help='Number of server groups per project'),
|
||||
cfg.IntOpt('quota_server_group_members',
|
||||
default=10,
|
||||
help='Number of servers per server group'),
|
||||
cfg.IntOpt('reservation_expire',
|
||||
default=86400,
|
||||
help='Number of seconds until a reservation expires'),
|
||||
@ -1416,6 +1422,11 @@ def _keypair_get_count_by_user(*args, **kwargs):
|
||||
return objects.KeyPairList.get_count_by_user(*args, **kwargs)
|
||||
|
||||
|
||||
def _server_group_count_members_by_user(*args, **kwargs):
|
||||
"""Helper method to avoid referencing objects.InstanceGroup on import."""
|
||||
return objects.InstanceGroup.count_members_by_user(*args, **kwargs)
|
||||
|
||||
|
||||
QUOTAS = QuotaEngine()
|
||||
|
||||
|
||||
@ -1439,6 +1450,11 @@ resources = [
|
||||
'quota_security_group_rules'),
|
||||
CountableResource('key_pairs', _keypair_get_count_by_user,
|
||||
'quota_key_pairs'),
|
||||
ReservableResource('server_groups', '_sync_server_groups',
|
||||
'quota_server_groups'),
|
||||
CountableResource('server_group_members',
|
||||
_server_group_count_members_by_user,
|
||||
'quota_server_group_members'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -17,6 +17,7 @@ from lxml import etree
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.compute.contrib import quota_classes
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
@ -37,7 +38,9 @@ class QuotaClassSetsTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaClassSetsTest, self).setUp()
|
||||
self.controller = quota_classes.QuotaClassSetsController()
|
||||
self.ext_mgr = extensions.ExtensionManager()
|
||||
self.ext_mgr.extensions = {}
|
||||
self.controller = quota_classes.QuotaClassSetsController(self.ext_mgr)
|
||||
|
||||
def test_format_quota_set(self):
|
||||
raw_quota_set = {
|
||||
|
@ -30,13 +30,17 @@ from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
|
||||
|
||||
def quota_set(id):
|
||||
return {'quota_set': {'id': id, 'metadata_items': 128,
|
||||
'ram': 51200, 'floating_ips': 10, 'fixed_ips': -1,
|
||||
'instances': 10, 'injected_files': 5, 'cores': 20,
|
||||
'injected_file_content_bytes': 10240,
|
||||
'security_groups': 10, 'security_group_rules': 20,
|
||||
'key_pairs': 100, 'injected_file_path_bytes': 255}}
|
||||
def quota_set(id, include_server_group_quotas=True):
|
||||
res = {'quota_set': {'id': id, 'metadata_items': 128,
|
||||
'ram': 51200, 'floating_ips': 10, 'fixed_ips': -1,
|
||||
'instances': 10, 'injected_files': 5, 'cores': 20,
|
||||
'injected_file_content_bytes': 10240,
|
||||
'security_groups': 10, 'security_group_rules': 20,
|
||||
'key_pairs': 100, 'injected_file_path_bytes': 255}}
|
||||
if include_server_group_quotas:
|
||||
res['quota_set']['server_groups'] = 10
|
||||
res['quota_set']['server_group_members'] = 10
|
||||
return res
|
||||
|
||||
|
||||
class BaseQuotaSetsTest(test.TestCase):
|
||||
@ -81,11 +85,11 @@ class BaseQuotaSetsTest(test.TestCase):
|
||||
class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
plugin = quotas_v21
|
||||
validation_error = exception.ValidationError
|
||||
include_server_group_quotas = True
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaSetsTestV21, self).setUp()
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
self._setup_controller()
|
||||
self.default_quotas = {
|
||||
'instances': 10,
|
||||
'cores': 20,
|
||||
@ -98,8 +102,15 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
'injected_file_content_bytes': 10240,
|
||||
'security_groups': 10,
|
||||
'security_group_rules': 20,
|
||||
'key_pairs': 100
|
||||
'key_pairs': 100,
|
||||
}
|
||||
if self.include_server_group_quotas:
|
||||
self.default_quotas['server_groups'] = 10
|
||||
self.default_quotas['server_group_members'] = 10
|
||||
|
||||
def _setup_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
|
||||
def test_format_quota_set(self):
|
||||
quota_set = self.controller._format_quota_set('1234',
|
||||
@ -119,6 +130,9 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
self.assertEqual(qs['security_groups'], 10)
|
||||
self.assertEqual(qs['security_group_rules'], 20)
|
||||
self.assertEqual(qs['key_pairs'], 100)
|
||||
if self.include_server_group_quotas:
|
||||
self.assertEqual(qs['server_groups'], 10)
|
||||
self.assertEqual(qs['server_group_members'], 10)
|
||||
|
||||
def test_quotas_defaults(self):
|
||||
uri = '/v2/fake_tenant/os-quota-sets/fake_tenant/defaults'
|
||||
@ -136,7 +150,8 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
use_admin_context=True)
|
||||
res_dict = self.controller.show(req, 1234)
|
||||
|
||||
self.assertEqual(res_dict, quota_set('1234'))
|
||||
ref_quota_set = quota_set('1234', self.include_server_group_quotas)
|
||||
self.assertEqual(res_dict, ref_quota_set)
|
||||
|
||||
def test_quotas_show_as_unauthorized_user(self):
|
||||
self.setup_mock_for_show()
|
||||
@ -169,6 +184,9 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
'security_groups': 0,
|
||||
'security_group_rules': 0,
|
||||
'key_pairs': 100, 'fixed_ips': -1}}
|
||||
if self.include_server_group_quotas:
|
||||
body['quota_set']['server_groups'] = 10
|
||||
body['quota_set']['server_group_members'] = 10
|
||||
expected_body = self.get_update_expected_response(body)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me',
|
||||
@ -330,8 +348,7 @@ class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
|
||||
def setUp(self):
|
||||
super(ExtendedQuotasTestV21, self).setUp()
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
self._setup_controller()
|
||||
self.setup_mock_for_update()
|
||||
|
||||
fake_quotas = {'ram': {'limit': 51200,
|
||||
@ -344,6 +361,10 @@ class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
'in_use': 0,
|
||||
'reserved': 0}}
|
||||
|
||||
def _setup_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
|
||||
def fake_get_quotas(self, context, id, user_id=None, usages=False):
|
||||
if usages:
|
||||
return self.fake_quotas
|
||||
@ -384,9 +405,13 @@ class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
|
||||
class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
plugin = quotas_v21
|
||||
include_server_group_quotas = True
|
||||
|
||||
def setUp(self):
|
||||
super(UserQuotasTestV21, self).setUp()
|
||||
self._setup_controller()
|
||||
|
||||
def _setup_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
|
||||
@ -395,8 +420,8 @@ class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/1234?user_id=1',
|
||||
use_admin_context=True)
|
||||
res_dict = self.controller.show(req, 1234)
|
||||
|
||||
self.assertEqual(res_dict, quota_set('1234'))
|
||||
ref_quota_set = quota_set('1234', self.include_server_group_quotas)
|
||||
self.assertEqual(res_dict, ref_quota_set)
|
||||
|
||||
def test_user_quotas_show_as_unauthorized_user(self):
|
||||
self.setup_mock_for_show()
|
||||
@ -415,6 +440,10 @@ class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
'security_groups': 10,
|
||||
'security_group_rules': 20,
|
||||
'key_pairs': 100}}
|
||||
if self.include_server_group_quotas:
|
||||
body['quota_set']['server_groups'] = 10
|
||||
body['quota_set']['server_group_members'] = 10
|
||||
|
||||
expected_body = self.get_update_expected_response(body)
|
||||
|
||||
url = '/v2/fake4/os-quota-sets/update_me?user_id=1'
|
||||
@ -432,7 +461,9 @@ class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
'injected_file_content_bytes': 10240,
|
||||
'security_groups': 10,
|
||||
'security_group_rules': 20,
|
||||
'key_pairs': 100}}
|
||||
'key_pairs': 100,
|
||||
'server_groups': 10,
|
||||
'server_group_members': 10}}
|
||||
|
||||
url = '/v2/fake4/os-quota-sets/update_me?user_id=1'
|
||||
req = fakes.HTTPRequest.blank(url)
|
||||
@ -474,6 +505,15 @@ class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
class QuotaSetsTestV2(QuotaSetsTestV21):
|
||||
plugin = quotas_v2
|
||||
validation_error = webob.exc.HTTPBadRequest
|
||||
include_server_group_quotas = False
|
||||
|
||||
def _setup_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').MultipleTimes().\
|
||||
AndReturn(self.include_server_group_quotas)
|
||||
self.mox.ReplayAll()
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
self.mox.ResetAll()
|
||||
|
||||
# NOTE: The following tests are tricky and v2.1 API does not allow
|
||||
# this kind of input by strong input validation. Just for test coverage,
|
||||
@ -528,10 +568,63 @@ class QuotaSetsTestV2(QuotaSetsTestV21):
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
|
||||
req, 1234)
|
||||
|
||||
# NOTE: os-server-group-quotas is only for v2.0. On v2.1 this feature
|
||||
# is always enabled, so this test is only needed for v2.0
|
||||
def test_quotas_update_without_server_group_quotas_extenstion(self):
|
||||
self.setup_mock_for_update()
|
||||
self.default_quotas.update({
|
||||
'server_groups': 50,
|
||||
'sever_group_members': 50
|
||||
})
|
||||
body = {'quota_set': self.default_quotas}
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me',
|
||||
use_admin_context=True)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
||||
req, 'update_me', body=body)
|
||||
|
||||
|
||||
class ExtendedQuotasTestV2(ExtendedQuotasTestV21):
|
||||
plugin = quotas_v2
|
||||
|
||||
def _setup_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').MultipleTimes().\
|
||||
AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
self.mox.ResetAll()
|
||||
|
||||
|
||||
class UserQuotasTestV2(UserQuotasTestV21):
|
||||
plugin = quotas_v2
|
||||
include_server_group_quotas = False
|
||||
|
||||
def _setup_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').MultipleTimes().\
|
||||
AndReturn(self.include_server_group_quotas)
|
||||
self.mox.ReplayAll()
|
||||
self.controller = self.plugin.QuotaSetsController(self.ext_mgr)
|
||||
self.mox.ResetAll()
|
||||
|
||||
# NOTE: os-server-group-quotas is only for v2.0. On v2.1 this feature
|
||||
# is always enabled, so this test is only needed for v2.0
|
||||
def test_user_quotas_update_as_admin_without_sg_quota_extension(self):
|
||||
self.setup_mock_for_update()
|
||||
body = {'quota_set': {'instances': 10, 'cores': 20,
|
||||
'ram': 51200, 'floating_ips': 10,
|
||||
'fixed_ips': -1, 'metadata_items': 128,
|
||||
'injected_files': 5,
|
||||
'injected_file_content_bytes': 10240,
|
||||
'injected_file_path_bytes': 255,
|
||||
'security_groups': 10,
|
||||
'security_group_rules': 20,
|
||||
'key_pairs': 100,
|
||||
'server_groups': 100,
|
||||
'server_group_members': 200}}
|
||||
|
||||
url = '/v2/fake4/os-quota-sets/update_me?user_id=1'
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
||||
req, 'update_me', body=body)
|
||||
|
@ -34,6 +34,7 @@ class FakeRequest(object):
|
||||
|
||||
class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
used_limit_extension = "compute_extension:v3:os-used-limits:used_limits"
|
||||
include_server_group_quotas = True
|
||||
|
||||
def setUp(self):
|
||||
"""Run before each test."""
|
||||
@ -62,12 +63,17 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
'totalInstancesUsed': 'instances',
|
||||
'totalFloatingIpsUsed': 'floating_ips',
|
||||
'totalSecurityGroupsUsed': 'security_groups',
|
||||
'totalServerGroupsUsed': 'server_groups',
|
||||
}
|
||||
limits = {}
|
||||
expected_abs_limits = []
|
||||
for display_name, q in quota_map.iteritems():
|
||||
limits[q] = {'limit': len(display_name),
|
||||
'in_use': len(display_name) / 2,
|
||||
'reserved': len(display_name) / 3}
|
||||
if (self.include_server_group_quotas or
|
||||
display_name != 'totalServerGroupsUsed'):
|
||||
expected_abs_limits.append(display_name)
|
||||
|
||||
def stub_get_project_quotas(context, project_id, usages=True):
|
||||
return limits
|
||||
@ -76,14 +82,17 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
stub_get_project_quotas)
|
||||
if self.ext_mgr is not None:
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(
|
||||
self.include_server_group_quotas)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.controller.index(fake_req, res)
|
||||
abs_limits = res.obj['limits']['absolute']
|
||||
for used_limit, value in abs_limits.iteritems():
|
||||
r = limits[quota_map[used_limit]]['reserved'] if reserved else 0
|
||||
for limit in expected_abs_limits:
|
||||
value = abs_limits[limit]
|
||||
r = limits[quota_map[limit]]['reserved'] if reserved else 0
|
||||
self.assertEqual(value,
|
||||
limits[quota_map[used_limit]]['in_use'] + r)
|
||||
limits[quota_map[limit]]['in_use'] + r)
|
||||
|
||||
def test_used_limits_basic(self):
|
||||
self._do_test_used_limits(False)
|
||||
@ -111,6 +120,8 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
fake_req.GET = {'tenant_id': tenant_id}
|
||||
if self.ext_mgr is not None:
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(
|
||||
self.include_server_group_quotas)
|
||||
self.authorize(self.fake_context, target=target)
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas')
|
||||
quota.QUOTAS.get_project_quotas(self.fake_context, '%s' % tenant_id,
|
||||
@ -134,6 +145,8 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
fake_req.GET = {}
|
||||
if self.ext_mgr is not None:
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(
|
||||
self.include_server_group_quotas)
|
||||
self.mox.StubOutWithMock(extensions, 'extension_authorizer')
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas')
|
||||
quota.QUOTAS.get_project_quotas(self.fake_context, '%s' % project_id,
|
||||
@ -182,6 +195,8 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
if self.ext_mgr is not None:
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(
|
||||
self.include_server_group_quotas)
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas')
|
||||
quota.QUOTAS.get_project_quotas(self.fake_context, project_id,
|
||||
usages=True).AndReturn({})
|
||||
@ -206,6 +221,8 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
|
||||
if self.ext_mgr is not None:
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(
|
||||
self.include_server_group_quotas)
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.mox.ReplayAll()
|
||||
@ -230,6 +247,8 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
|
||||
if self.ext_mgr is not None:
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(
|
||||
self.include_server_group_quotas)
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.mox.ReplayAll()
|
||||
@ -241,6 +260,7 @@ class UsedLimitsTestCaseV21(test.NoDBTestCase):
|
||||
|
||||
class UsedLimitsTestCaseV2(UsedLimitsTestCaseV21):
|
||||
used_limit_extension = "compute_extension:used_limits_for_admin"
|
||||
include_server_group_quotas = False
|
||||
|
||||
def _set_up_controller(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
@ -274,6 +294,7 @@ class UsedLimitsTestCaseXml(test.NoDBTestCase):
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.ext_mgr.is_loaded('os-server-group-quotas').AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.controller.index(fake_req, res)
|
||||
|
@ -5463,6 +5463,11 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
||||
for i in range(3):
|
||||
db.security_group_create(self.ctxt, {'project_id': 'project1'})
|
||||
|
||||
usages['server_groups'] = 4
|
||||
for i in range(4):
|
||||
db.instance_group_create(self.ctxt, {'uuid': str(i),
|
||||
'project_id': 'project1'})
|
||||
|
||||
reservations_uuids = db.quota_reserve(self.ctxt, reservable_resources,
|
||||
quotas, quotas, deltas, None,
|
||||
None, None, 'project1')
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10,
|
||||
"id": "fake_tenant"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 45
|
||||
"security_groups": 45,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
"metadata_items": 128,
|
||||
"ram": 51200,
|
||||
"security_group_rules": 20,
|
||||
"security_groups": 10
|
||||
"security_groups": 10,
|
||||
"server_groups": 10,
|
||||
"server_group_members": 10
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,14 @@
|
||||
"maxTotalInstances": 10,
|
||||
"maxTotalKeypairs": 100,
|
||||
"maxTotalRAMSize": 51200,
|
||||
"maxServerGroups": 10,
|
||||
"maxServerGroupMembers": 10,
|
||||
"totalCoresUsed": 0,
|
||||
"totalInstancesUsed": 0,
|
||||
"totalRAMUsed": 0,
|
||||
"totalSecurityGroupsUsed": 0,
|
||||
"totalFloatingIpsUsed": 0
|
||||
"totalFloatingIpsUsed": 0,
|
||||
"totalServerGroupsUsed": 0
|
||||
},
|
||||
"rate": []
|
||||
}
|
||||
|
@ -277,6 +277,35 @@ class _TestInstanceGroupObjects(test.TestCase):
|
||||
group.obj_make_compatible(group_primitive, '1.6')
|
||||
self.assertEqual({}, group_primitive['metadetails'])
|
||||
|
||||
def test_count_members_by_user(self):
|
||||
instance1 = tests_utils.get_test_instance(self.context,
|
||||
flavor=flavors.get_default_flavor(), obj=True)
|
||||
instance1.user_id = 'user1'
|
||||
instance1.save()
|
||||
instance2 = tests_utils.get_test_instance(self.context,
|
||||
flavor=flavors.get_default_flavor(), obj=True)
|
||||
instance2.user_id = 'user2'
|
||||
instance2.save()
|
||||
instance3 = tests_utils.get_test_instance(self.context,
|
||||
flavor=flavors.get_default_flavor(), obj=True)
|
||||
instance3.user_id = 'user2'
|
||||
instance3.save()
|
||||
|
||||
instance_ids = [instance1.uuid, instance2.uuid, instance3.uuid]
|
||||
values = self._get_default_values()
|
||||
group = self._create_instance_group(self.context, values)
|
||||
instance_group.InstanceGroup.add_members(self.context, group.uuid,
|
||||
instance_ids)
|
||||
|
||||
group = instance_group.InstanceGroup.get_by_uuid(self.context,
|
||||
group.uuid)
|
||||
count_user1 = group.count_members_by_user(self.context, 'user1')
|
||||
count_user2 = group.count_members_by_user(self.context, 'user2')
|
||||
count_user3 = group.count_members_by_user(self.context, 'user3')
|
||||
self.assertEqual(1, count_user1)
|
||||
self.assertEqual(2, count_user2)
|
||||
self.assertEqual(0, count_user3)
|
||||
|
||||
|
||||
class TestInstanceGroupObject(test_objects._LocalTest,
|
||||
_TestInstanceGroupObjects):
|
||||
|
@ -958,8 +958,8 @@ object_data = {
|
||||
'InstanceExternalEvent': '1.0-f1134523654407a875fd59b80f759ee7',
|
||||
'InstanceFault': '1.2-313438e37e9d358f3566c85f6ddb2d3e',
|
||||
'InstanceFaultList': '1.1-aeb598ffd0cd6aa61fca7adf0f5e900d',
|
||||
'InstanceGroup': '1.7-b31ea31fdb452ab7810adbe789244f91',
|
||||
'InstanceGroupList': '1.2-a474822eebc3e090012e581adcc1fa09',
|
||||
'InstanceGroup': '1.8-9f3ef6ee21e424f817f76a63d35eb803',
|
||||
'InstanceGroupList': '1.5-b507229896d60fad117cb3223dbaa0cc',
|
||||
'InstanceInfoCache': '1.5-ef64b604498bfa505a8c93747a9d8b2f',
|
||||
'InstanceList': '1.8-16db4c93fe5b80564413b9a4f547e0d1',
|
||||
'InstanceNUMACell': '1.0-17e6ee0a24cb6651d1b084efa3027bda',
|
||||
|
@ -810,6 +810,8 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
quota_injected_file_path_length=255,
|
||||
quota_security_groups=10,
|
||||
quota_security_group_rules=20,
|
||||
quota_server_groups=10,
|
||||
quota_server_group_members=10,
|
||||
reservation_expire=86400,
|
||||
until_refresh=0,
|
||||
max_age=0,
|
||||
@ -839,6 +841,8 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
security_groups=10,
|
||||
security_group_rules=20,
|
||||
key_pairs=100,
|
||||
server_groups=10,
|
||||
server_group_members=10,
|
||||
))
|
||||
|
||||
def _stub_quota_class_get_default(self):
|
||||
@ -885,6 +889,8 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
security_groups=10,
|
||||
security_group_rules=20,
|
||||
key_pairs=100,
|
||||
server_groups=10,
|
||||
server_group_members=10,
|
||||
))
|
||||
|
||||
def test_get_class_quotas_no_defaults(self):
|
||||
@ -1016,6 +1022,16 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
))
|
||||
|
||||
def _stub_get_by_project_and_user_specific(self):
|
||||
@ -1144,6 +1160,16 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
))
|
||||
|
||||
def test_get_user_quotas_alt_context_no_class(self):
|
||||
@ -1219,6 +1245,16 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
))
|
||||
|
||||
def test_get_project_quotas_alt_context_no_class(self):
|
||||
@ -1294,6 +1330,16 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
))
|
||||
|
||||
def test_get_user_quotas_alt_context_with_class(self):
|
||||
@ -1371,6 +1417,16 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
))
|
||||
|
||||
def test_get_project_quotas_alt_context_with_class(self):
|
||||
@ -1447,6 +1503,16 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
in_use=0,
|
||||
reserved=0,
|
||||
),
|
||||
))
|
||||
|
||||
def test_get_user_quotas_no_defaults(self):
|
||||
@ -1558,6 +1624,12 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
key_pairs=dict(
|
||||
limit=100,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
),
|
||||
))
|
||||
|
||||
def test_get_project_quotas_no_usages(self):
|
||||
@ -1608,6 +1680,12 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
key_pairs=dict(
|
||||
limit=100,
|
||||
),
|
||||
server_groups=dict(
|
||||
limit=10,
|
||||
),
|
||||
server_group_members=dict(
|
||||
limit=10,
|
||||
),
|
||||
))
|
||||
|
||||
def _stub_get_settable_quotas(self):
|
||||
@ -1724,6 +1802,14 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
'minimum': 0,
|
||||
'maximum': 100,
|
||||
},
|
||||
'server_groups': {
|
||||
'minimum': 0,
|
||||
'maximum': 10,
|
||||
},
|
||||
'server_group_members': {
|
||||
'minimum': 0,
|
||||
'maximum': 10,
|
||||
},
|
||||
})
|
||||
|
||||
def test_get_settable_quotas_without_user(self):
|
||||
@ -1784,6 +1870,14 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
'minimum': 0,
|
||||
'maximum': -1,
|
||||
},
|
||||
'server_groups': {
|
||||
'minimum': 0,
|
||||
'maximum': -1,
|
||||
},
|
||||
'server_group_members': {
|
||||
'minimum': 0,
|
||||
'maximum': -1,
|
||||
},
|
||||
})
|
||||
|
||||
def test_get_settable_quotas_by_user_with_unlimited_value(self):
|
||||
@ -1846,6 +1940,14 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
'minimum': 0,
|
||||
'maximum': 100,
|
||||
},
|
||||
'server_groups': {
|
||||
'minimum': 0,
|
||||
'maximum': 10,
|
||||
},
|
||||
'server_group_members': {
|
||||
'minimum': 0,
|
||||
'maximum': 10,
|
||||
},
|
||||
})
|
||||
|
||||
def _stub_get_project_quotas(self):
|
||||
@ -1898,7 +2000,8 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
['instances', 'cores', 'ram',
|
||||
'floating_ips', 'security_groups'],
|
||||
'floating_ips', 'security_groups',
|
||||
'server_groups'],
|
||||
True,
|
||||
project_id='test_project')
|
||||
|
||||
@ -1909,6 +2012,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
ram=50 * 1024,
|
||||
floating_ips=10,
|
||||
security_groups=10,
|
||||
server_groups=10,
|
||||
))
|
||||
|
||||
def test_get_quotas_no_sync(self):
|
||||
@ -1919,7 +2023,8 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
['metadata_items', 'injected_files',
|
||||
'injected_file_content_bytes',
|
||||
'injected_file_path_bytes',
|
||||
'security_group_rules'], False,
|
||||
'security_group_rules',
|
||||
'server_group_members'], False,
|
||||
project_id='test_project')
|
||||
|
||||
self.assertEqual(self.calls, ['get_project_quotas'])
|
||||
@ -1929,6 +2034,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
injected_file_content_bytes=10 * 1024,
|
||||
injected_file_path_bytes=255,
|
||||
security_group_rules=20,
|
||||
server_group_members=10,
|
||||
))
|
||||
|
||||
def test_limit_check_under(self):
|
||||
|
Loading…
Reference in New Issue
Block a user