Replace "tenant_id" with "project_id" in Quota engine

This is part of the remaining technical debt of the specs
https://specs.openstack.org/openstack/neutron-specs/specs/newton/moving-to-keystone-v3.html

Blueprint: https://blueprints.launchpad.net/neutron/+spec/keystone-v3

Change-Id: I1faf520d3cdafe2de873525c8ebe1fa2114bdcd7
This commit is contained in:
Rodolfo Alonso Hernandez 2021-08-24 13:10:35 +00:00
parent 8791947a6b
commit 7dcddeb0bd
18 changed files with 579 additions and 539 deletions

View File

@ -245,7 +245,7 @@ The process of making a reservation is fairly straightforward:
on every requested resource, and then retrieving the amount of reserved
resources.
* Fetch current quota limits for requested resources, by invoking the
_get_tenant_quotas method.
_get_project_quotas method.
* Fetch expired reservations for selected resources. This amount will be
subtracted from resource usage. As in most cases there won't be any
expired reservation, this approach actually requires less DB operations than

View File

@ -401,8 +401,10 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
def create_network_db(self, context, network):
# single request processing
n = network['network']
# TODO(ralonsoh): "tenant_id" reference should be removed.
project_id = n.get('project_id') or n['tenant_id']
with db_api.CONTEXT_WRITER.using(context):
args = {'tenant_id': n['tenant_id'],
args = {'tenant_id': project_id,
'id': n.get('id') or uuidutils.generate_uuid(),
'name': n['name'],
'mtu': n.get('mtu', constants.DEFAULT_NETWORK_MTU),
@ -1426,12 +1428,14 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
p = port['port']
port_id = p.get('id') or uuidutils.generate_uuid()
network_id = p['network_id']
# TODO(ralonsoh): "tenant_id" reference should be removed.
project_id = p.get('project_id') or p['tenant_id']
if p.get('device_owner'):
self._enforce_device_owner_not_router_intf_or_device_id(
context, p.get('device_owner'), p.get('device_id'),
p['tenant_id'])
project_id)
port_data = dict(tenant_id=p['tenant_id'],
port_data = dict(tenant_id=project_id,
name=p['name'],
id=port_id,
network_id=network_id,

View File

@ -31,28 +31,28 @@ def utcnow():
class QuotaUsageInfo(collections.namedtuple(
'QuotaUsageInfo', ['resource', 'tenant_id', 'used', 'dirty'])):
'QuotaUsageInfo', ['resource', 'project_id', 'used', 'dirty'])):
"""Information about resource quota usage."""
class ReservationInfo(collections.namedtuple(
'ReservationInfo', ['reservation_id', 'tenant_id',
'ReservationInfo', ['reservation_id', 'project_id',
'expiration', 'deltas'])):
"""Information about a resource reservation."""
@db_api.retry_if_session_inactive()
def get_quota_usage_by_resource_and_tenant(context, resource, tenant_id):
"""Return usage info for a given resource and tenant.
def get_quota_usage_by_resource_and_project(context, resource, project_id):
"""Return usage info for a given resource and project.
:param context: Request context
:param resource: Name of the resource
:param tenant_id: Tenant identifier
:param project_id: Project identifier
:returns: a QuotaUsageInfo instance
"""
result = quota_obj.QuotaUsage.get_object_dirty_protected(
context, resource=resource, project_id=tenant_id)
context, resource=resource, project_id=project_id)
if not result:
return
return QuotaUsageInfo(result.resource, result.project_id, result.in_use,
@ -69,23 +69,22 @@ def get_quota_usage_by_resource(context, resource):
@db_api.retry_if_session_inactive()
def get_quota_usage_by_tenant_id(context, tenant_id):
objs = quota_obj.QuotaUsage.get_objects(context, project_id=tenant_id)
def get_quota_usage_by_project_id(context, project_id):
objs = quota_obj.QuotaUsage.get_objects(context, project_id=project_id)
return [QuotaUsageInfo(item.resource,
tenant_id,
project_id,
item.in_use,
item.dirty) for item in objs]
@db_api.retry_if_session_inactive()
def set_quota_usage(context, resource, tenant_id,
in_use=None, delta=False):
def set_quota_usage(context, resource, project_id, in_use=None, delta=False):
"""Set resource quota usage.
:param context: instance of neutron context with db session
:param resource: name of the resource for which usage is being set
:param tenant_id: identifier of the tenant for which quota usage is
being set
:param project_id: identifier of the project for which quota usage is
being set
:param in_use: integer specifying the new quantity of used resources,
or a delta to apply to current used resource
:param delta: Specifies whether in_use is an absolute number
@ -93,11 +92,11 @@ def set_quota_usage(context, resource, tenant_id,
"""
with db_api.CONTEXT_WRITER.using(context):
usage_data = quota_obj.QuotaUsage.get_object(
context, resource=resource, project_id=tenant_id)
context, resource=resource, project_id=project_id)
if not usage_data:
# Must create entry
usage_data = quota_obj.QuotaUsage(
context, resource=resource, project_id=tenant_id)
context, resource=resource, project_id=project_id)
usage_data.create()
# Perform explicit comparison with None as 0 is a valid value
if in_use is not None:
@ -113,16 +112,16 @@ def set_quota_usage(context, resource, tenant_id,
@db_api.retry_if_session_inactive()
@db_api.CONTEXT_WRITER
def set_quota_usage_dirty(context, resource, tenant_id, dirty=True):
"""Set quota usage dirty bit for a given resource and tenant.
def set_quota_usage_dirty(context, resource, project_id, dirty=True):
"""Set quota usage dirty bit for a given resource and project.
:param resource: a resource for which quota usage if tracked
:param tenant_id: tenant identifier
:param project_id: project identifier
:param dirty: the desired value for the dirty bit (defaults to True)
:returns: 1 if the quota usage data were updated, 0 otherwise.
"""
obj = quota_obj.QuotaUsage.get_object(
context, resource=resource, project_id=tenant_id)
context, resource=resource, project_id=project_id)
if obj:
obj.dirty = dirty
obj.update()
@ -132,16 +131,17 @@ def set_quota_usage_dirty(context, resource, tenant_id, dirty=True):
@db_api.retry_if_session_inactive()
@db_api.CONTEXT_WRITER
def set_resources_quota_usage_dirty(context, resources, tenant_id, dirty=True):
"""Set quota usage dirty bit for a given tenant and multiple resources.
def set_resources_quota_usage_dirty(context, resources, project_id,
dirty=True):
"""Set quota usage dirty bit for a given project and multiple resources.
:param resources: list of resource for which the dirty bit is going
to be set
:param tenant_id: tenant identifier
:param project_id: project identifier
:param dirty: the desired value for the dirty bit (defaults to True)
:returns: the number of records for which the bit was actually set.
"""
filters = {'project_id': tenant_id}
filters = {'project_id': project_id}
if resources:
filters['resource'] = resources
objs = quota_obj.QuotaUsage.get_objects(context, **filters)
@ -154,10 +154,10 @@ def set_resources_quota_usage_dirty(context, resources, tenant_id, dirty=True):
@db_api.retry_if_session_inactive()
@db_api.CONTEXT_WRITER
def set_all_quota_usage_dirty(context, resource, dirty=True):
"""Set the dirty bit on quota usage for all tenants.
"""Set the dirty bit on quota usage for all projects.
:param resource: the resource for which the dirty bit should be set
:returns: the number of tenants for which the dirty bit was
:returns: the number of projects for which the dirty bit was
actually updated
"""
# TODO(manjeets) consider squashing this method with
@ -170,7 +170,7 @@ def set_all_quota_usage_dirty(context, resource, dirty=True):
@db_api.retry_if_session_inactive()
def create_reservation(context, tenant_id, deltas, expiration=None):
def create_reservation(context, project_id, deltas, expiration=None):
# This method is usually called from within another transaction.
# Consider using begin_nested
expiration = expiration or (utcnow() + datetime.timedelta(0, 120))
@ -179,7 +179,7 @@ def create_reservation(context, tenant_id, deltas, expiration=None):
delta_objs.append(quota_obj.ResourceDelta(
context, resource=resource, amount=delta))
reserv_obj = quota_obj.Reservation(
context, project_id=tenant_id, expiration=expiration,
context, project_id=project_id, expiration=expiration,
resource_deltas=delta_objs)
reserv_obj.create()
return ReservationInfo(reserv_obj['id'],
@ -223,12 +223,12 @@ def remove_reservation(context, reservation_id, set_dirty=False):
@db_api.retry_if_session_inactive()
@db_api.CONTEXT_READER
def get_reservations_for_resources(context, tenant_id, resources,
def get_reservations_for_resources(context, project_id, resources,
expired=False):
"""Retrieve total amount of reservations for specified resources.
:param context: Neutron context with db session
:param tenant_id: Tenant identifier
:param project_id: Project identifier
:param resources: Resources for which reserved amounts should be fetched
:param expired: False to fetch active reservations, True to fetch expired
reservations (defaults to False)
@ -238,17 +238,17 @@ def get_reservations_for_resources(context, tenant_id, resources,
# can be mocked easily where as datetime is built in type
# mock.path does not allow mocking built in types.
return quota_obj.Reservation.get_total_reservations_map(
context, utcnow(), tenant_id, resources, expired)
context, utcnow(), project_id, resources, expired)
@db_api.retry_if_session_inactive()
@db_api.CONTEXT_WRITER
def remove_expired_reservations(context, tenant_id=None, timeout=None):
def remove_expired_reservations(context, project_id=None, timeout=None):
expiring_time = utcnow()
if timeout:
expiring_time -= datetime.timedelta(seconds=timeout)
return quota_obj.Reservation.delete_expired(context, expiring_time,
tenant_id)
project_id)
class QuotaDriverAPI(object, metaclass=abc.ABCMeta):
@ -257,7 +257,7 @@ class QuotaDriverAPI(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def get_default_quotas(context, resources, project_id):
"""Given a list of resources, retrieve the default quotas set for
a tenant.
a project.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resource keys.
@ -267,7 +267,7 @@ class QuotaDriverAPI(object, metaclass=abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def get_tenant_quotas(context, resources, project_id):
def get_project_quotas(context, resources, project_id):
"""Retrieve the quotas for the given list of resources and project
:param context: The request context, for access checks.
@ -278,7 +278,7 @@ class QuotaDriverAPI(object, metaclass=abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def get_detailed_tenant_quotas(context, resources, project_id):
def get_detailed_project_quotas(context, resources, project_id):
"""Retrieve detailed quotas for the given list of resources and project
:param context: The request context, for access checks.
@ -291,11 +291,11 @@ class QuotaDriverAPI(object, metaclass=abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def delete_tenant_quota(context, project_id):
def delete_project_quota(context, project_id):
"""Delete the quota entries for a given project_id.
After deletion, this tenant will use default quota values in conf.
Raise a "not found" error if the quota for the given tenant was
After deletion, this project will use default quota values in conf.
Raise a "not found" error if the quota for the given project was
never defined.
:param context: The request context, for access checks.
@ -382,15 +382,15 @@ class NullQuotaDriver(QuotaDriverAPI):
pass
@staticmethod
def get_tenant_quotas(context, resources, project_id):
def get_project_quotas(context, resources, project_id):
pass
@staticmethod
def get_detailed_tenant_quotas(context, resources, project_id):
def get_detailed_project_quotas(context, resources, project_id):
pass
@staticmethod
def delete_tenant_quota(context, project_id):
def delete_project_quota(context, project_id):
pass
@staticmethod

View File

@ -35,139 +35,142 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
"""
@staticmethod
def get_default_quotas(context, resources, tenant_id):
def get_default_quotas(context, resources, project_id):
"""Given a list of resources, retrieve the default quotas set for
a tenant.
a project.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resource keys.
:param tenant_id: The ID of the tenant to return default quotas for.
:param project_id: The ID of the project to return default quotas for.
:return: dict from resource name to dict of name and limit
"""
# Currently the tenant_id parameter is unused, since all tenants
# Currently the project_id parameter is unused, since all projects
# share the same default values. This may change in the future so
# we include tenant-id to remain backwards compatible.
# we include project ID to remain backwards compatible.
return dict((key, resource.default)
for key, resource in resources.items())
@staticmethod
@db_api.retry_if_session_inactive()
def get_tenant_quotas(context, resources, tenant_id):
def get_project_quotas(context, resources, project_id):
"""Given a list of resources, retrieve the quotas for the given
tenant. If no limits are found for the specified tenant, the operation
returns the default limits.
project. If no limits are found for the specified project, the
operation returns the default limits.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resource keys.
:param tenant_id: The ID of the tenant to return quotas for.
:param project_id: The ID of the project to return quotas for.
:return: dict from resource name to dict of name and limit
"""
# init with defaults
tenant_quota = dict((key, resource.default)
project_quota = dict((key, resource.default)
for key, resource in resources.items())
# update with tenant specific limits
quota_objs = quota_obj.Quota.get_objects(context, project_id=tenant_id)
# update with project specific limits
quota_objs = quota_obj.Quota.get_objects(context,
project_id=project_id)
for item in quota_objs:
tenant_quota[item['resource']] = item['limit']
project_quota[item['resource']] = item['limit']
return tenant_quota
return project_quota
@staticmethod
@db_api.retry_if_session_inactive()
def get_detailed_tenant_quotas(context, resources, tenant_id):
"""Given a list of resources and a sepecific tenant, retrieve
def get_detailed_project_quotas(context, resources, project_id):
"""Given a list of resources and a specific project, retrieve
the detailed quotas (limit, used, reserved).
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resource keys.
:param project_id: The ID of the project to return quotas for.
:return dict: mapping resource name in dict to its corresponding limit
used and reserved. Reserved currently returns default value of 0
"""
res_reserve_info = quota_api.get_reservations_for_resources(
context, tenant_id, resources.keys())
tenant_quota_ext = {}
context, project_id, resources.keys())
project_quota_ext = {}
for key, resource in resources.items():
if isinstance(resource, res.TrackedResource):
used = resource.count_used(context, tenant_id,
used = resource.count_used(context, project_id,
resync_usage=False)
else:
# NOTE(ihrachys) .count won't use the plugin we pass, but we
# pass it regardless to keep the quota driver API intact
plugins = directory.get_plugins()
plugin = plugins.get(key, plugins[constants.CORE])
used = resource.count(context, plugin, tenant_id)
used = resource.count(context, plugin, project_id)
tenant_quota_ext[key] = {
project_quota_ext[key] = {
'limit': resource.default,
'used': used,
'reserved': res_reserve_info.get(key, 0),
}
# update with specific tenant limits
quota_objs = quota_obj.Quota.get_objects(context, project_id=tenant_id)
# update with specific project limits
quota_objs = quota_obj.Quota.get_objects(context,
project_id=project_id)
for item in quota_objs:
tenant_quota_ext[item['resource']]['limit'] = item['limit']
return tenant_quota_ext
project_quota_ext[item['resource']]['limit'] = item['limit']
return project_quota_ext
@staticmethod
@db_api.retry_if_session_inactive()
def delete_tenant_quota(context, tenant_id):
"""Delete the quota entries for a given tenant_id.
def delete_project_quota(context, project_id):
"""Delete the quota entries for a given project_id.
After deletion, this tenant will use default quota values in conf.
Raise a "not found" error if the quota for the given tenant was
After deletion, this project will use default quota values in conf.
Raise a "not found" error if the quota for the given project was
never defined.
"""
if quota_obj.Quota.delete_objects(context, project_id=tenant_id) < 1:
if quota_obj.Quota.delete_objects(context, project_id=project_id) < 1:
# No record deleted means the quota was not found
raise exceptions.TenantQuotaNotFound(tenant_id=tenant_id)
raise exceptions.TenantQuotaNotFound(tenant_id=project_id)
@staticmethod
@db_api.retry_if_session_inactive()
def get_all_quotas(context, resources):
"""Given a list of resources, retrieve the quotas for the all tenants.
"""Given a list of resources, retrieve the quotas for the all projects.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resource keys.
:return: quotas list of dict of tenant_id:, resourcekey1:
:return: quotas list of dict of project_id:, resourcekey1:
resourcekey2: ...
"""
tenant_default = dict((key, resource.default)
project_default = dict((key, resource.default)
for key, resource in resources.items())
all_tenant_quotas = {}
all_project_quotas = {}
for quota in quota_obj.Quota.get_objects(context):
tenant_id = quota['project_id']
project_id = quota['project_id']
# avoid setdefault() because only want to copy when actually
# required
tenant_quota = all_tenant_quotas.get(tenant_id)
if tenant_quota is None:
tenant_quota = tenant_default.copy()
tenant_quota['tenant_id'] = tenant_id
attributes.populate_project_info(tenant_quota)
all_tenant_quotas[tenant_id] = tenant_quota
project_quota = all_project_quotas.get(project_id)
if project_quota is None:
project_quota = project_default.copy()
project_quota['project_id'] = project_id
attributes.populate_project_info(project_quota)
all_project_quotas[project_id] = project_quota
tenant_quota[quota['resource']] = quota['limit']
project_quota[quota['resource']] = quota['limit']
# Convert values to a list to as caller expect an indexable iterable,
# where python3's dict_values does not support indexing
return list(all_tenant_quotas.values())
return list(all_project_quotas.values())
@staticmethod
@db_api.retry_if_session_inactive()
def update_quota_limit(context, tenant_id, resource, limit):
tenant_quotas = quota_obj.Quota.get_objects(
context, project_id=tenant_id, resource=resource)
if tenant_quotas:
tenant_quotas[0].limit = limit
tenant_quotas[0].update()
def update_quota_limit(context, project_id, resource, limit):
project_quotas = quota_obj.Quota.get_objects(
context, project_id=project_id, resource=resource)
if project_quotas:
project_quotas[0].limit = limit
project_quotas[0].update()
else:
quota_obj.Quota(context, project_id=tenant_id, resource=resource,
quota_obj.Quota(context, project_id=project_id, resource=resource,
limit=limit).create()
def _get_quotas(self, context, tenant_id, resources):
def _get_quotas(self, context, project_id, resources):
"""Retrieves the quotas for specific resources.
A helper method which retrieves the quotas for the specific
@ -175,24 +178,23 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
context.
:param context: The request context, for access checks.
:param tenant_id: the tenant_id to check quota.
:param project_id: the project ID to check quota.
:param resources: A dictionary of the registered resources.
"""
# Grab and return the quotas (without usages)
quotas = DbQuotaDriver.get_tenant_quotas(
context, resources, tenant_id)
quotas = DbQuotaDriver.get_project_quotas(
context, resources, project_id)
return dict((k, v) for k, v in quotas.items())
def _handle_expired_reservations(self, context, tenant_id):
LOG.debug("Deleting expired reservations for tenant:%s", tenant_id)
def _handle_expired_reservations(self, context, project_id):
LOG.debug("Deleting expired reservations for project: %s", project_id)
# Delete expired reservations (we don't want them to accrue
# in the database)
quota_api.remove_expired_reservations(
context, tenant_id=tenant_id)
quota_api.remove_expired_reservations(context, project_id=project_id)
@db_api.retry_if_session_inactive()
def make_reservation(self, context, tenant_id, resources, deltas, plugin):
def make_reservation(self, context, project_id, resources, deltas, plugin):
# Lock current reservation table
# NOTE(salv-orlando): This routine uses DB write locks.
# These locks are acquired by the count() method invoked on resources.
@ -207,11 +209,11 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
# to a single node will be available.
requested_resources = deltas.keys()
with db_api.CONTEXT_WRITER.using(context):
# get_tenant_quotes needs in input a dictionary mapping resource
# "get_project_quotas" needs in input a dictionary mapping resource
# name to BaseResosurce instances so that the default quota can be
# retrieved
current_limits = self.get_tenant_quotas(
context, resources, tenant_id)
current_limits = self.get_project_quotas(
context, resources, project_id)
unlimited_resources = set([resource for (resource, limit) in
current_limits.items() if limit < 0])
# Do not even bother counting resources and calculating headroom
@ -230,13 +232,13 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
# instances
current_usages = dict(
(resource, resources[resource].count(
context, plugin, tenant_id, resync_usage=False)) for
context, plugin, project_id, resync_usage=False)) for
resource in requested_resources)
# Adjust for expired reservations. Apparently it is cheaper than
# querying every time for active reservations and counting overall
# quantity of resources reserved
expired_deltas = quota_api.get_reservations_for_resources(
context, tenant_id, requested_resources, expired=True)
context, project_id, requested_resources, expired=True)
# Verify that the request can be accepted with current limits
resources_over_limit = []
for resource in requested_resources:
@ -254,14 +256,14 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
if res_headroom < deltas[resource]:
resources_over_limit.append(resource)
if expired_reservations:
self._handle_expired_reservations(context, tenant_id)
self._handle_expired_reservations(context, project_id)
if resources_over_limit:
raise exceptions.OverQuota(overs=sorted(resources_over_limit))
# Success, store the reservation
# TODO(salv-orlando): Make expiration time configurable
return quota_api.create_reservation(
context, tenant_id, deltas)
context, project_id, deltas)
def commit_reservation(self, context, reservation_id):
# Do not mark resource usage as dirty. If a reservation is committed,
@ -276,7 +278,7 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
quota_api.remove_reservation(context, reservation_id,
set_dirty=True)
def limit_check(self, context, tenant_id, resources, values):
def limit_check(self, context, project_id, resources, values):
"""Check simple quota limits.
For limits--those quotas for which there is no usage
@ -289,7 +291,7 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
nothing.
:param context: The request context, for access checks.
:param tenant_id: The tenant_id to check the quota.
:param project_id: The project ID to check the quota.
:param resources: A dictionary of the registered resources.
:param values: A dictionary of the values to check against the
quota.
@ -301,7 +303,7 @@ class DbQuotaDriver(quota_api.QuotaDriverAPI):
raise exceptions.InvalidQuotaValue(unders=sorted(unders))
# Get the applicable quotas
quotas = self._get_quotas(context, tenant_id, resources)
quotas = self._get_quotas(context, project_id, resources)
# Check the quotas and construct a list of the resources that
# would be put over limit by the desired values

View File

@ -45,7 +45,7 @@ class DbQuotaNoLockDriver(quota_driver.DbQuotaDriver):
resources_over_limit = []
with db_api.CONTEXT_WRITER.using(context):
# Filter out unlimited resources.
limits = self.get_tenant_quotas(context, resources, project_id)
limits = self.get_project_quotas(context, resources, project_id)
unlimited_resources = set([resource for (resource, limit) in
limits.items() if limit < 0])
requested_resources = (set(deltas.keys()) - unlimited_resources)
@ -54,7 +54,7 @@ class DbQuotaNoLockDriver(quota_driver.DbQuotaDriver):
# operation is fast and by calling it before making any
# reservation, we ensure the freshness of the reservations.
quota_api.remove_expired_reservations(
context, tenant_id=project_id,
context, project_id=project_id,
timeout=quota_api.RESERVATION_EXPIRATION_TIMEOUT)
# Count the number of (1) used and (2) reserved resources for this

View File

@ -924,10 +924,10 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
@registry.receives(resources.NETWORK, [events.BEFORE_CREATE])
def _ensure_default_security_group_handler(self, resource, event, trigger,
payload):
if event == events.BEFORE_UPDATE:
project_id = payload.states[0]['tenant_id']
else:
project_id = payload.latest_state['tenant_id']
_state = (payload.states[0] if event == events.BEFORE_UPDATE else
payload.latest_state)
# TODO(ralonsoh): "tenant_id" reference should be removed.
project_id = _state.get('project_id') or _state['tenant_id']
if project_id:
self._ensure_default_security_group(payload.context, project_id)
@ -993,7 +993,8 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
return
port_sg = port.get(ext_sg.SECURITYGROUPS)
if port_sg is None or not validators.is_attr_set(port_sg):
port_project = port.get('tenant_id')
# TODO(ralonsoh): "tenant_id" reference should be removed.
port_project = port.get('project_id') or port.get('tenant_id')
default_sg = self._ensure_default_security_group(context,
port_project)
if default_sg:

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import warnings
from neutron_lib.api import converters
from neutron_lib.api import extensions as api_extensions
from neutron_lib.api import faults
@ -74,20 +76,20 @@ class QuotaSetsController(wsgi.Controller):
'is_visible': True}
self._update_extended_attributes = False
def _get_quotas(self, request, tenant_id):
return self._driver.get_tenant_quotas(
def _get_quotas(self, request, project_id):
return self._driver.get_project_quotas(
request.context,
resource_registry.get_all_resources(),
tenant_id)
project_id)
def default(self, request, id):
context = request.context
if id != context.tenant_id:
if id != context.project_id:
validate_policy(context, "get_quota")
return {self._resource_name: self._driver.get_default_quotas(
context=context,
resources=resource_registry.get_all_resources(),
tenant_id=id)}
project_id=id)}
def create(self, request, body=None):
msg = _('POST requests are not supported on this resource.')
@ -101,20 +103,31 @@ class QuotaSetsController(wsgi.Controller):
context, resource_registry.get_all_resources())}
def tenant(self, request):
"""Retrieve the tenant info in context."""
"""Retrieve the project info in context."""
warnings.warn(
'"tenant" Quota API method is deprecated, use "project" instead')
return self._project(request, 'tenant')
def project(self, request):
"""Retrieve the project info in context."""
return self._project(request, 'project')
@staticmethod
def _project(request, key):
"""Retrieve the project info in context."""
context = request.context
if not context.tenant_id:
if not context.project_id:
raise exceptions.QuotaMissingTenant()
return {'tenant': {'tenant_id': context.tenant_id}}
return {key: {key + '_id': context.project_id}}
def show(self, request, id):
if id != request.context.tenant_id:
if id != request.context.project_id:
validate_policy(request.context, "get_quota")
return {self._resource_name: self._get_quotas(request, id)}
def delete(self, request, id):
validate_policy(request.context, "delete_quota")
self._driver.delete_tenant_quota(request.context, id)
self._driver.delete_project_quota(request.context, id)
def update(self, request, id, body=None):
validate_policy(request.context, "update_quota")
@ -152,7 +165,7 @@ class Quotasv2(api_extensions.ExtensionDescriptor):
def get_description(cls):
description = 'Expose functions for quotas management'
if cfg.CONF.QUOTAS.quota_driver == DB_QUOTA_DRIVER:
description += ' per tenant'
description += ' per project'
return description
@classmethod
@ -169,7 +182,8 @@ class Quotasv2(api_extensions.ExtensionDescriptor):
Quotasv2.get_alias(),
controller,
member_actions={DEFAULT_QUOTAS_ACTION: 'GET'},
collection_actions={'tenant': 'GET'})]
collection_actions={'tenant': 'GET',
'project': 'GET'})]
def get_extended_resources(self, version):
if version == "2.0":

View File

@ -46,17 +46,17 @@ EXTENDED_ATTRIBUTES_2_0 = {
class DetailQuotaSetsController(quotasv2.QuotaSetsController):
def _get_detailed_quotas(self, request, tenant_id):
return self._driver.get_detailed_tenant_quotas(
def _get_detailed_quotas(self, request, project_id):
return self._driver.get_detailed_project_quotas(
request.context,
resource_registry.get_all_resources(), tenant_id)
resource_registry.get_all_resources(), project_id)
def details(self, request, id):
if id != request.context.project_id:
# Check if admin
if not request.context.is_admin:
reason = _("Only admin is authorized to access quotas for"
" another tenant")
reason = _("Only admin is authorized to access quotas for "
"another project")
raise n_exc.AdminRequired(reason=reason)
return {self._resource_name:
self._get_detailed_quotas(request, id)}
@ -95,7 +95,8 @@ class Quotasv2_detail(api_extensions.ExtensionDescriptor):
RESOURCE_COLLECTION,
controller,
member_actions={'details': 'GET'},
collection_actions={'tenant': 'GET'})]
collection_actions={'tenant': 'GET',
'project': 'GET'})]
def get_extended_resources(self, version):
return EXTENDED_ATTRIBUTES_2_0 if version == "2.0" else {}

View File

@ -1051,7 +1051,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
def _create_network_db(self, context, network):
net_data = network[net_def.RESOURCE_NAME]
tenant_id = net_data['tenant_id']
# TODO(ralonsoh): "tenant_id" reference should be removed.
tenant_id = net_data.get('project_id') or net_data['tenant_id']
with db_api.CONTEXT_WRITER.using(context):
net_db = self.create_network_db(context, network)
net_data['id'] = net_db.id

View File

@ -30,7 +30,7 @@ from neutron.db.quota import api as quota_api
LOG = log.getLogger(__name__)
def _count_resource(context, collection_name, tenant_id):
def _count_resource(context, collection_name, project_id):
count_getter_name = "get_%s_count" % collection_name
getter_name = "get_%s" % collection_name
@ -45,12 +45,12 @@ def _count_resource(context, collection_name, tenant_id):
try:
obj_count_getter = getattr(plugins[pname], count_getter_name)
return obj_count_getter(
context, filters={'tenant_id': [tenant_id]})
context, filters={'project_id': [project_id]})
except (NotImplementedError, AttributeError):
try:
obj_getter = getattr(plugins[pname], getter_name)
obj_list = obj_getter(
context, filters={'tenant_id': [tenant_id]})
context, filters={'project_id': [project_id]})
return len(obj_list) if obj_list else 0
except (NotImplementedError, AttributeError):
pass
@ -172,7 +172,7 @@ class TrackedResource(BaseResource):
usage data are employed when performing quota checks.
This class operates under the assumption that the model class
describing the resource has a tenant identifier attribute.
describing the resource has a project identifier attribute.
:param name: The name of the resource, i.e., "networks".
:param model_class: The sqlalchemy model class of the resource for
@ -191,11 +191,11 @@ class TrackedResource(BaseResource):
super(TrackedResource, self).__init__(
name, flag=flag, plural_name=plural_name)
# Register events for addition/removal of records in the model class
# As tenant_id is immutable for all Neutron objects there is no need
# As project_id is immutable for all Neutron objects there is no need
# to register a listener for update events
self._model_class = model_class
self._dirty_tenants = set()
self._out_of_sync_tenants = set()
self._dirty_projects = set()
self._out_of_sync_projects = set()
# NOTE(ralonsoh): "DbQuotaNoLockDriver" driver does not need to track
# the DB events or resync the resource quota usage.
if cfg.CONF.QUOTAS.quota_driver == quota_conf.QUOTA_DB_DRIVER:
@ -207,78 +207,78 @@ class TrackedResource(BaseResource):
def dirty(self):
if not self._track_resource_events:
return
return self._dirty_tenants
return self._dirty_projects
def mark_dirty(self, context):
if not self._dirty_tenants or not self._track_resource_events:
if not self._dirty_projects or not self._track_resource_events:
return
with db_api.CONTEXT_WRITER.using(context):
# It is not necessary to protect this operation with a lock.
# Indeed when this method is called the request has been processed
# and therefore all resources created or deleted.
# dirty_tenants will contain all the tenants for which the
# resource count is changed. The list might contain also tenants
# dirty_projects will contain all the projects for which the
# resource count is changed. The list might contain also projects
# for which resource count was altered in other requests, but this
# won't be harmful.
dirty_tenants_snap = self._dirty_tenants.copy()
for tenant_id in dirty_tenants_snap:
quota_api.set_quota_usage_dirty(context, self.name, tenant_id)
self._out_of_sync_tenants |= dirty_tenants_snap
self._dirty_tenants -= dirty_tenants_snap
dirty_projects_snap = self._dirty_projects.copy()
for project_id in dirty_projects_snap:
quota_api.set_quota_usage_dirty(context, self.name, project_id)
self._out_of_sync_projects |= dirty_projects_snap
self._dirty_projects -= dirty_projects_snap
def _db_event_handler(self, mapper, _conn, target):
try:
tenant_id = target['tenant_id']
project_id = target['project_id']
except AttributeError:
with excutils.save_and_reraise_exception():
LOG.error("Model class %s does not have a tenant_id "
LOG.error("Model class %s does not have a project_id "
"attribute", target)
self._dirty_tenants.add(tenant_id)
self._dirty_projects.add(project_id)
# Retry the operation if a duplicate entry exception is raised. This
# can happen is two or more workers are trying to create a resource of a
# give kind for the same tenant concurrently. Retrying the operation will
# give kind for the same project concurrently. Retrying the operation will
# ensure that an UPDATE statement is emitted rather than an INSERT one
@db_api.retry_if_session_inactive()
def _set_quota_usage(self, context, tenant_id, in_use):
def _set_quota_usage(self, context, project_id, in_use):
return quota_api.set_quota_usage(
context, self.name, tenant_id, in_use=in_use)
context, self.name, project_id, in_use=in_use)
def _resync(self, context, tenant_id, in_use):
def _resync(self, context, project_id, in_use):
# Update quota usage
usage_info = self._set_quota_usage(context, tenant_id, in_use)
usage_info = self._set_quota_usage(context, project_id, in_use)
self._dirty_tenants.discard(tenant_id)
self._out_of_sync_tenants.discard(tenant_id)
LOG.debug(("Unset dirty status for tenant:%(tenant_id)s on "
self._dirty_projects.discard(project_id)
self._out_of_sync_projects.discard(project_id)
LOG.debug(("Unset dirty status for project:%(project_id)s on "
"resource:%(resource)s"),
{'tenant_id': tenant_id, 'resource': self.name})
{'project_id': project_id, 'resource': self.name})
return usage_info
def resync(self, context, tenant_id):
if (tenant_id not in self._out_of_sync_tenants or
def resync(self, context, project_id):
if (project_id not in self._out_of_sync_projects or
not self._track_resource_events):
return
LOG.debug(("Synchronizing usage tracker for tenant:%(tenant_id)s on "
LOG.debug(("Synchronizing usage tracker for project:%(project_id)s on "
"resource:%(resource)s"),
{'tenant_id': tenant_id, 'resource': self.name})
{'project_id': project_id, 'resource': self.name})
in_use = context.session.query(
self._model_class.tenant_id).filter_by(
tenant_id=tenant_id).count()
self._model_class.project_id).filter_by(
project_id=project_id).count()
# Update quota usage
return self._resync(context, tenant_id, in_use)
return self._resync(context, project_id, in_use)
def count_used(self, context, tenant_id, resync_usage=True):
def count_used(self, context, project_id, resync_usage=True):
"""Returns the current usage count for the resource.
:param context: The request context.
:param tenant_id: The ID of the tenant
:param project_id: The ID of the project
:param resync_usage: Default value is set to True. Syncs
with in_use usage.
"""
# Load current usage data, setting a row-level lock on the DB
usage_info = quota_api.get_quota_usage_by_resource_and_tenant(
context, self.name, tenant_id)
usage_info = quota_api.get_quota_usage_by_resource_and_project(
context, self.name, project_id)
# If dirty or missing, calculate actual resource usage querying
# the database and set/create usage info data
@ -286,27 +286,28 @@ class TrackedResource(BaseResource):
# assumption is generally valid, but if the database is tampered with,
# or if data migrations do not take care of usage counters, the
# assumption will not hold anymore
if (tenant_id in self._dirty_tenants or
if (project_id in self._dirty_projects or
not usage_info or usage_info.dirty):
LOG.debug(("Usage tracker for resource:%(resource)s and tenant:"
"%(tenant_id)s is out of sync, need to count used "
LOG.debug(("Usage tracker for resource:%(resource)s and project:"
"%(project_id)s is out of sync, need to count used "
"quota"), {'resource': self.name,
'tenant_id': tenant_id})
'project_id': project_id})
in_use = context.session.query(
self._model_class.tenant_id).filter_by(
tenant_id=tenant_id).count()
self._model_class.project_id).filter_by(
project_id=project_id).count()
# Update quota usage, if requested (by default do not do that, as
# typically one counts before adding a record, and that would mark
# the usage counter as dirty again)
if resync_usage:
usage_info = self._resync(context, tenant_id, in_use)
usage_info = self._resync(context, project_id, in_use)
else:
resource = usage_info.resource if usage_info else self.name
tenant_id = usage_info.tenant_id if usage_info else tenant_id
project_id = (usage_info.project_id if usage_info else
project_id)
dirty = usage_info.dirty if usage_info else True
usage_info = quota_api.QuotaUsageInfo(
resource, tenant_id, in_use, dirty)
resource, project_id, in_use, dirty)
LOG.debug(("Quota usage for %(resource)s was recalculated. "
"Used quota:%(used)d."),
@ -314,16 +315,16 @@ class TrackedResource(BaseResource):
'used': usage_info.used})
return usage_info.used
def count_reserved(self, context, tenant_id):
def count_reserved(self, context, project_id):
"""Return the current reservation count for the resource."""
# NOTE(princenana) Current implementation of reservations
# is ephemeral and returns the default value
reservations = quota_api.get_reservations_for_resources(
context, tenant_id, [self.name])
context, project_id, [self.name])
reserved = reservations.get(self.name, 0)
return reserved
def count(self, context, _plugin, tenant_id, resync_usage=True,
def count(self, context, _plugin, project_id, resync_usage=True,
count_db_registers=False):
"""Return the count of the resource.
@ -332,11 +333,11 @@ class TrackedResource(BaseResource):
CountableResource instances.
"""
if count_db_registers:
count = self._count_db_registers(context, tenant_id)
count = self._count_db_registers(context, project_id)
else:
count = self.count_used(context, tenant_id, resync_usage)
count = self.count_used(context, project_id, resync_usage)
return count + self.count_reserved(context, tenant_id)
return count + self.count_reserved(context, project_id)
def _count_db_registers(self, context, project_id):
"""Return the existing resources (self._model_class) in a project.

View File

@ -192,7 +192,7 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
def test_index_admin(self):
# NOTE(salv-orlando): The quota controller has an hardcoded check for
# admin-ness for this operation, which is supposed to return quotas for
# all tenants. Such check is "vestigial" from the home-grown WSGI and
# all projects. Such check is "vestigial" from the home-grown WSGI and
# shall be removed
response = self.app.get('%s.json' % self.base_url,
headers={'X-Project-Id': 'admin',
@ -213,7 +213,7 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
self._verify_default_limits(json_body)
def test_get(self):
# It is not ok to access another tenant's limits
# It is not ok to access another project's limits
url = '%s/foo.json' % self.base_url
response = self.app.get(url, expect_errors=True)
self.assertEqual(403, response.status_int)
@ -269,7 +269,7 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
json_body = jsonutils.loads(response.body)
found = False
for qs in json_body['quotas']:
if qs['tenant_id'] == 'foo':
if qs['project_id'] == 'foo':
found = True
self.assertTrue(found)
@ -283,7 +283,7 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
self.assertEqual(200, response.status_int)
json_body = jsonutils.loads(response.body)
for qs in json_body['quotas']:
self.assertNotEqual('foo', qs['tenant_id'])
self.assertNotEqual('foo', qs['project_id'])
def test_quotas_get_defaults(self):
response = self.app.get('%s/foo/default.json' % self.base_url,
@ -294,13 +294,14 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
json_body = jsonutils.loads(response.body)
self._verify_default_limits(json_body)
def test_get_tenant_info(self):
response = self.app.get('%s/tenant.json' % self.base_url,
headers={'X-Project-Id': 'admin',
'X-Roles': 'admin'})
self.assertEqual(200, response.status_int)
json_body = jsonutils.loads(response.body)
self.assertEqual('admin', json_body['tenant']['tenant_id'])
def test_get_project_info(self):
for key in ('project', 'tenant'):
response = self.app.get('%s/%s.json' % (self.base_url, key),
headers={'X-Project-Id': 'admin',
'X-Roles': 'admin'})
self.assertEqual(200, response.status_int)
json_body = jsonutils.loads(response.body)
self.assertEqual('admin', json_body[key][key + '_id'])
class TestResourceController(TestRootController):
@ -318,11 +319,11 @@ class TestResourceController(TestRootController):
def _gen_port(self):
network_id = self.plugin.create_network(context.get_admin_context(), {
'network':
{'name': 'pecannet', 'tenant_id': 'tenid', 'shared': False,
{'name': 'pecannet', 'project_id': 'tenid', 'shared': False,
'admin_state_up': True, 'status': 'ACTIVE'}})['id']
self.port = self.plugin.create_port(context.get_admin_context(), {
'port':
{'tenant_id': 'tenid', 'network_id': network_id,
{'project_id': 'tenid', 'network_id': network_id,
'fixed_ips': n_const.ATTR_NOT_SPECIFIED,
'mac_address': '00:11:22:33:44:55',
'admin_state_up': True, 'device_id': 'FF',
@ -364,7 +365,7 @@ class TestResourceController(TestRootController):
self._test_get_collection_with_fields_selector(fields=[])
def test_project_id_in_mandatory_fields(self):
# ports only specifies that tenant_id is mandatory, but project_id
# ports only specifies that project_id is mandatory, but project_id
# should still be passed to the plugin.
mock_get = mock.patch.object(self.plugin, 'get_ports',
return_value=[]).start()
@ -384,10 +385,10 @@ class TestResourceController(TestRootController):
# Explicitly require an attribute which is also 'required_by_policy'.
# The attribute should not be stripped while generating the response
item_resp = self.app.get(
'/v2.0/ports/%s.json?fields=id&fields=tenant_id' % self.port['id'],
headers={'X-Project-Id': 'tenid'})
'/v2.0/ports/%s.json?fields=id&fields=project_id' %
self.port['id'], headers={'X-Project-Id': 'tenid'})
self.assertEqual(200, item_resp.status_int)
self._check_item(['id', 'tenant_id'],
self._check_item(['id', 'project_id'],
jsonutils.loads(item_resp.body)['port'])
def test_duped_and_empty_fields_stripped(self):
@ -406,7 +407,7 @@ class TestResourceController(TestRootController):
'/v2.0/ports.json',
params={'port': {'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'}},
'project_id': 'tenid'}},
headers={'X-Project-Id': 'tenid'})
self.assertEqual(response.status_int, 201)
@ -426,7 +427,7 @@ class TestResourceController(TestRootController):
'/v2.0/ports.json',
params={'port': {'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'}},
'project_id': 'tenid'}},
headers={'X-Project-Id': 'tenid'})
self.assertEqual(201, response.status_int)
@ -439,7 +440,7 @@ class TestResourceController(TestRootController):
self.assertEqual(1, len(json_body))
self.assertIn('port', json_body)
self.assertEqual('test', json_body['port']['name'])
self.assertEqual('tenid', json_body['port']['tenant_id'])
self.assertEqual('tenid', json_body['port']['project_id'])
def test_delete(self):
response = self.app.delete('/v2.0/ports/%s.json' % self.port['id'],
@ -487,10 +488,10 @@ class TestResourceController(TestRootController):
'/v2.0/ports.json',
params={'ports': [{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'},
'project_id': 'tenid'},
{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'}]
'project_id': 'tenid'}]
},
headers={'X-Project-Id': 'tenid'})
self.assertEqual(201, response.status_int)
@ -518,11 +519,11 @@ class TestResourceController(TestRootController):
params={'ports': [{'network_id': self.port['network_id'],
'admin_state_up': True,
'security_groups': [sg_id],
'tenant_id': 'tenid'},
'project_id': 'tenid'},
{'network_id': self.port['network_id'],
'admin_state_up': True,
'security_groups': [sg_id],
'tenant_id': 'tenid'}]
'project_id': 'tenid'}]
},
headers={'X-Project-Id': 'tenid'})
self.assertEqual(201, port_response.status_int)
@ -539,10 +540,10 @@ class TestResourceController(TestRootController):
'/v2.0/ports.json',
params={'ports': [{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'},
'project_id': 'tenid'},
{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'}]
'project_id': 'tenid'}]
},
headers={'X-Project-Id': 'tenid'})
self.assertEqual(response.status_int, 201)
@ -556,13 +557,13 @@ class TestResourceController(TestRootController):
'/v2.0/ports.json',
params={'ports': [{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'},
'project_id': 'tenid'},
{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'},
'project_id': 'tenid'},
{'network_id': 'bad_net_id',
'admin_state_up': True,
'tenant_id': 'tenid'}]
'project_id': 'tenid'}]
},
headers={'X-Project-Id': 'tenid'},
expect_errors=True)
@ -579,7 +580,7 @@ class TestResourceController(TestRootController):
'/v2.0/ports.json',
params={'ports': [{'network_id': self.port['network_id'],
'admin_state_up': True,
'tenant_id': 'tenid'}]
'project_id': 'tenid'}]
},
headers={'X-Project-Id': 'tenid'})
self.assertEqual(response.status_int, 201)
@ -598,13 +599,15 @@ class TestPaginationAndSorting(test_functional.PecanFunctionalTest):
self.addCleanup(policy.reset)
self.plugin = directory.get_plugin()
self.ctx = context.get_admin_context()
self._project_id = 'project_id1'
self._create_networks(self.RESOURCE_COUNT)
self.networks = self._get_collection()['networks']
def _create_networks(self, count=1):
network_ids = []
for index in range(count):
network = {'name': 'pecannet-%d' % index, 'tenant_id': 'tenid',
network = {'name': 'pecannet-%d' % index,
'project_id': self._project_id,
'shared': False, 'admin_state_up': True,
'status': 'ACTIVE'}
network_id = self.plugin.create_network(
@ -632,7 +635,8 @@ class TestPaginationAndSorting(test_functional.PecanFunctionalTest):
url = '/v2.0/%s.json' % collection
if query_params:
url = '%s?%s' % (url, '&'.join(query_params))
list_resp = self.app.get(url, headers={'X-Project-Id': 'tenid'})
list_resp = self.app.get(url,
headers={'X-Project-Id': self._project_id})
self.assertEqual(200, list_resp.status_int)
return list_resp.json
@ -735,9 +739,9 @@ class TestRequestProcessing(TestRootController):
def test_context_set_in_request(self):
self.app.get('/v2.0/ports.json',
headers={'X-Project-Id': 'tenant_id'})
self.assertEqual('tenant_id',
self.captured_context['neutron_context'].tenant_id)
headers={'X-Project-Id': 'project_id'})
self.assertEqual('project_id',
self.captured_context['neutron_context'].project_id)
def test_core_resource_identified(self):
self.app.get('/v2.0/ports.json')
@ -1169,4 +1173,3 @@ class TestExcludeAttributePolicy(test_functional.PecanFunctionalTest):
json_body = jsonutils.loads(response.body)
self.assertEqual(response.status_int, 200)
self.assertEqual('tenid', json_body['network']['project_id'])
self.assertEqual('tenid', json_body['network']['tenant_id'])

View File

@ -31,26 +31,26 @@ DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
def _set_context(self):
self.tenant_id = 'Higuain'
self.context = context.Context('Gonzalo', self.tenant_id,
self.project_id = 'Higuain'
self.context = context.Context('Gonzalo', self.project_id,
is_admin=False, is_advsvc=False)
def _create_reservation(self, resource_deltas,
tenant_id=None, expiration=None):
tenant_id = tenant_id or self.tenant_id
project_id=None, expiration=None):
project_id = project_id or self.project_id
return quota_api.create_reservation(
self.context, tenant_id, resource_deltas, expiration)
self.context, project_id, resource_deltas, expiration)
def _create_quota_usage(self, resource, used, tenant_id=None):
tenant_id = tenant_id or self.tenant_id
def _create_quota_usage(self, resource, used, project_id=None):
project_id = project_id or self.project_id
return quota_api.set_quota_usage(context.get_admin_context(),
resource, tenant_id, in_use=used)
resource, project_id, in_use=used)
def _verify_quota_usage(self, usage_info,
expected_resource=None,
expected_used=None,
expected_dirty=None):
self.assertEqual(self.tenant_id, usage_info.tenant_id)
self.assertEqual(self.project_id, usage_info.project_id)
if expected_resource:
self.assertEqual(expected_resource, usage_info.resource)
if expected_dirty is not None:
@ -75,12 +75,12 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
self._create_quota_usage('goals', 26)
# Higuain scores a double
usage_info_1 = quota_api.set_quota_usage(
self.context, 'goals', self.tenant_id,
self.context, 'goals', self.project_id,
in_use=28)
self._verify_quota_usage(usage_info_1,
expected_used=28)
usage_info_2 = quota_api.set_quota_usage(
self.context, 'goals', self.tenant_id,
self.context, 'goals', self.project_id,
in_use=24)
self._verify_quota_usage(usage_info_2,
expected_used=24)
@ -89,7 +89,7 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
self._create_quota_usage('goals', 26)
# Higuain scores a double
usage_info_1 = quota_api.set_quota_usage(
self.context, 'goals', self.tenant_id,
self.context, 'goals', self.project_id,
in_use=2, delta=True)
self._verify_quota_usage(usage_info_1,
expected_used=28)
@ -98,35 +98,36 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
self._create_quota_usage('goals', 26)
# Higuain needs a shower after the match
self.assertEqual(1, quota_api.set_quota_usage_dirty(
self.context, 'goals', self.tenant_id))
usage_info = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id)
self.context, 'goals', self.project_id))
usage_info = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id)
self._verify_quota_usage(usage_info,
expected_dirty=True)
# Higuain is clean now
self.assertEqual(1, quota_api.set_quota_usage_dirty(
self.context, 'goals', self.tenant_id, dirty=False))
usage_info = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id)
self.context, 'goals', self.project_id, dirty=False))
usage_info = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id)
self._verify_quota_usage(usage_info,
expected_dirty=False)
def test_set_dirty_non_existing_quota_usage(self):
self.assertEqual(0, quota_api.set_quota_usage_dirty(
self.context, 'meh', self.tenant_id))
self.context, 'meh', self.project_id))
def test_set_resources_quota_usage_dirty(self):
self._create_quota_usage('goals', 26)
self._create_quota_usage('assists', 11)
self._create_quota_usage('bookings', 3)
self.assertEqual(2, quota_api.set_resources_quota_usage_dirty(
self.context, ['goals', 'bookings'], self.tenant_id))
usage_info_goals = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id)
usage_info_assists = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'assists', self.tenant_id)
usage_info_bookings = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'bookings', self.tenant_id)
self.context, ['goals', 'bookings'], self.project_id))
usage_info_goals = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id)
usage_info_assists = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'assists', self.project_id)
usage_info_bookings = (
quota_api.get_quota_usage_by_resource_and_project(
self.context, 'bookings', self.project_id))
self._verify_quota_usage(usage_info_goals, expected_dirty=True)
self._verify_quota_usage(usage_info_assists, expected_dirty=False)
self._verify_quota_usage(usage_info_bookings, expected_dirty=True)
@ -135,30 +136,31 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
self._create_quota_usage('goals', 26)
self._create_quota_usage('assists', 11)
self._create_quota_usage('bookings', 3)
# Expect all the resources for the tenant to be set dirty
# Expect all the resources for the project to be set dirty
self.assertEqual(3, quota_api.set_resources_quota_usage_dirty(
self.context, [], self.tenant_id))
usage_info_goals = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id)
usage_info_assists = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'assists', self.tenant_id)
usage_info_bookings = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'bookings', self.tenant_id)
self.context, [], self.project_id))
usage_info_goals = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id)
usage_info_assists = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'assists', self.project_id)
usage_info_bookings = (
quota_api.get_quota_usage_by_resource_and_project(
self.context, 'bookings', self.project_id))
self._verify_quota_usage(usage_info_goals, expected_dirty=True)
self._verify_quota_usage(usage_info_assists, expected_dirty=True)
self._verify_quota_usage(usage_info_bookings, expected_dirty=True)
# Higuain is clean now
self.assertEqual(1, quota_api.set_quota_usage_dirty(
self.context, 'goals', self.tenant_id, dirty=False))
usage_info = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id)
self.context, 'goals', self.project_id, dirty=False))
usage_info = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id)
self._verify_quota_usage(usage_info,
expected_dirty=False)
def _test_set_all_quota_usage_dirty(self, expected):
self._create_quota_usage('goals', 26)
self._create_quota_usage('goals', 12, tenant_id='Callejon')
self._create_quota_usage('goals', 12, project_id='Callejon')
self.assertEqual(expected, quota_api.set_all_quota_usage_dirty(
self.context, 'goals'))
@ -167,13 +169,13 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
# admin context we can clean only one
self._test_set_all_quota_usage_dirty(expected=1)
def test_get_quota_usage_by_tenant(self):
def test_get_quota_usage_by_project(self):
self._create_quota_usage('goals', 26)
self._create_quota_usage('assists', 11)
# Create a resource for a different tenant
self._create_quota_usage('mehs', 99, tenant_id='buffon')
usage_infos = quota_api.get_quota_usage_by_tenant_id(
self.context, self.tenant_id)
# Create a resource for a different project
self._create_quota_usage('mehs', 99, project_id='buffon')
usage_infos = quota_api.get_quota_usage_by_project_id(
self.context, self.project_id)
self.assertEqual(2, len(usage_infos))
resources = [info.resource for info in usage_infos]
@ -183,26 +185,26 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
def test_get_quota_usage_by_resource(self):
self._create_quota_usage('goals', 26)
self._create_quota_usage('assists', 11)
self._create_quota_usage('goals', 12, tenant_id='Callejon')
self._create_quota_usage('goals', 12, project_id='Callejon')
usage_infos = quota_api.get_quota_usage_by_resource(
self.context, 'goals')
# Only 1 result expected in tenant context
# Only 1 result expected in project context
self.assertEqual(1, len(usage_infos))
self._verify_quota_usage(usage_infos[0],
expected_resource='goals',
expected_used=26)
def test_get_quota_usage_by_tenant_and_resource(self):
def test_get_quota_usage_by_project_and_resource(self):
self._create_quota_usage('goals', 26)
usage_info = quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id)
usage_info = quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id)
self._verify_quota_usage(usage_info,
expected_resource='goals',
expected_used=26)
def test_get_non_existing_quota_usage_returns_none(self):
self.assertIsNone(quota_api.get_quota_usage_by_resource_and_tenant(
self.context, 'goals', self.tenant_id))
self.assertIsNone(quota_api.get_quota_usage_by_resource_and_project(
self.context, 'goals', self.project_id))
def _verify_reserved_resources(self, expected, actual):
for (resource, delta) in actual.items():
@ -214,14 +216,14 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
def test_create_reservation(self):
resources = {'goals': 2, 'assists': 1}
resv = self._create_reservation(resources)
self.assertEqual(self.tenant_id, resv.tenant_id)
self.assertEqual(self.project_id, resv.project_id)
self._verify_reserved_resources(resources, resv.deltas)
def test_create_reservation_with_expiration(self):
resources = {'goals': 2, 'assists': 1}
exp_date = datetime.datetime(2016, 3, 31, 14, 30)
resv = self._create_reservation(resources, expiration=exp_date)
self.assertEqual(self.tenant_id, resv.tenant_id)
self.assertEqual(self.project_id, resv.project_id)
self.assertEqual(exp_date, resv.expiration)
self._verify_reserved_resources(resources, resv.deltas)
@ -245,7 +247,8 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
mock_utcnow.return_value = datetime.datetime(
2015, 5, 20, 0, 0)
deltas = quota_api.get_reservations_for_resources(
self.context, self.tenant_id, ['goals', 'assists', 'bookings'])
self.context, self.project_id,
['goals', 'assists', 'bookings'])
self.assertIn('goals', deltas)
self.assertEqual(5, deltas['goals'])
self.assertIn('assists', deltas)
@ -260,7 +263,7 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
2015, 5, 20, 0, 0)
self._get_reservations_for_resource_helper()
deltas = quota_api.get_reservations_for_resources(
self.context, self.tenant_id,
self.context, self.project_id,
['goals', 'assists', 'bookings'],
expired=True)
self.assertIn('assists', deltas)
@ -271,7 +274,7 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
def test_get_reservation_for_resources_with_empty_list(self):
self.assertIsNone(quota_api.get_reservations_for_resources(
self.context, self.tenant_id, []))
self.context, self.project_id, []))
def test_remove_expired_reservations(self):
with mock.patch('neutron.db.quota.api.utcnow') as mock_utcnow:
@ -283,13 +286,13 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
exp_date_2 = datetime.datetime(2015, 3, 31, 14, 30)
resv_2 = self._create_reservation(resources, expiration=exp_date_2)
self.assertEqual(1, quota_api.remove_expired_reservations(
self.context, self.tenant_id))
self.context, self.project_id))
self.assertIsNone(quota_api.get_reservation(
self.context, resv_2.reservation_id))
self.assertIsNotNone(quota_api.get_reservation(
self.context, resv_1.reservation_id))
def test_remove_expired_reservations_no_tenant(self):
def test_remove_expired_reservations_no_project(self):
with mock.patch('neutron.db.quota.api.utcnow') as mock_utcnow:
mock_utcnow.return_value = datetime.datetime(
2015, 5, 20, 0, 0)
@ -299,7 +302,7 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
exp_date_2 = datetime.datetime(2015, 3, 31, 14, 30)
resv_2 = self._create_reservation(resources,
expiration=exp_date_2,
tenant_id='Callejon')
project_id='Callejon')
self.assertEqual(2, quota_api.remove_expired_reservations(
context.get_admin_context()))
self.assertIsNone(quota_api.get_reservation(
@ -311,14 +314,14 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
class TestQuotaDbApiAdminContext(TestQuotaDbApi):
def _set_context(self):
self.tenant_id = 'Higuain'
self.context = context.Context('Gonzalo', self.tenant_id,
self.project_id = 'Higuain'
self.context = context.Context('Gonzalo', self.project_id,
is_admin=True, is_advsvc=True)
def test_get_quota_usage_by_resource(self):
self._create_quota_usage('goals', 26)
self._create_quota_usage('assists', 11)
self._create_quota_usage('goals', 12, tenant_id='Callejon')
self._create_quota_usage('goals', 12, project_id='Callejon')
usage_infos = quota_api.get_quota_usage_by_resource(
self.context, 'goals')
# 2 results expected in admin context

View File

@ -28,7 +28,7 @@ from neutron.tests.unit import testlib_api
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
def _count_resource(context, resource, tenant_id):
def _count_resource(context, resource, project_id):
"""A fake counting function to determine current used counts"""
if resource[-1] == 's':
resource = resource[:-1]
@ -97,7 +97,8 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
defaults = {RESOURCE: TestResource(RESOURCE, 4)}
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 2)
quotas = self.plugin.get_tenant_quotas(self.context, defaults, PROJECT)
quotas = self.plugin.get_project_quotas(self.context, defaults,
PROJECT)
self.assertEqual(2, quotas[RESOURCE])
def test_update_quota_limit(self):
@ -105,15 +106,17 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 2)
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 3)
quotas = self.plugin.get_tenant_quotas(self.context, defaults, PROJECT)
quotas = self.plugin.get_project_quotas(self.context, defaults,
PROJECT)
self.assertEqual(3, quotas[RESOURCE])
def test_delete_tenant_quota_restores_default_limit(self):
def test_delete_project_quota_restores_default_limit(self):
defaults = {RESOURCE: TestResource(RESOURCE, 4)}
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 2)
self.plugin.delete_tenant_quota(self.context, PROJECT)
quotas = self.plugin.get_tenant_quotas(self.context, defaults, PROJECT)
self.plugin.delete_project_quota(self.context, PROJECT)
quotas = self.plugin.get_project_quotas(self.context, defaults,
PROJECT)
self.assertEqual(4, quotas[RESOURCE])
def test_get_default_quotas(self):
@ -123,20 +126,20 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
quotas = self.plugin.get_default_quotas(user_ctx, defaults, PROJECT)
self.assertEqual(4, quotas[RESOURCE])
def test_get_tenant_quotas(self):
def test_get_project_quotas(self):
user_ctx = context.Context(user_id=PROJECT, tenant_id=PROJECT)
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 2)
quotas = self.plugin.get_tenant_quotas(user_ctx, {}, PROJECT)
quotas = self.plugin.get_project_quotas(user_ctx, {}, PROJECT)
self.assertEqual(2, quotas[RESOURCE])
def test_get_tenant_quotas_different_tenant(self):
def test_get_project_quotas_different_project(self):
user_ctx = context.Context(user_id=PROJECT,
tenant_id='another_project')
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 2)
# It is appropriate to use assertFalse here as the expected return
# value is an empty dict (the defaults passed in the statement below
# after the request context)
self.assertFalse(self.plugin.get_tenant_quotas(user_ctx, {}, PROJECT))
self.assertFalse(self.plugin.get_project_quotas(user_ctx, {}, PROJECT))
def test_get_all_quotas(self):
project_1 = 'prj_test_1'
@ -151,14 +154,14 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
self.plugin.update_quota_limit(self.context, project_2, resource_2, 9)
quotas = self.plugin.get_all_quotas(self.context, resources)
# Expect two tenants' quotas
# Expect two projects' quotas
self.assertEqual(2, len(quotas))
# But not quotas for the same tenant twice
self.assertNotEqual(quotas[0]['tenant_id'], quotas[1]['tenant_id'])
# But not quotas for the same project twice
self.assertNotEqual(quotas[0]['project_id'], quotas[1]['project_id'])
# Check the expected limits. The quotas can be in any order.
for quota in quotas:
project = quota['tenant_id']
project = quota['project_id']
self.assertIn(project, (project_1, project_2))
if project == project_1:
expected_limit_r1 = 7
@ -208,15 +211,15 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
self.plugin.update_quota_limit(self.context, PROJECT, resource_name, 2)
reservation = quota_driver.make_reservation(
self.context,
self.context.tenant_id,
self.context.project_id,
resources,
deltas,
self.plugin)
self.assertIn(resource_name, reservation.deltas)
self.assertEqual(deltas[resource_name],
reservation.deltas[resource_name])
self.assertEqual(self.context.tenant_id,
reservation.tenant_id)
self.assertEqual(self.context.project_id,
reservation.project_id)
def test_make_reservation_single_resource(self):
quota_driver = driver.DbQuotaDriver()
@ -237,7 +240,7 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
self.plugin.update_quota_limit(self.context, PROJECT, ALT_RESOURCE, 2)
reservation = quota_driver.make_reservation(
self.context,
self.context.tenant_id,
self.context.project_id,
resources,
deltas,
self.plugin)
@ -245,8 +248,8 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
self.assertIn(ALT_RESOURCE, reservation.deltas)
self.assertEqual(1, reservation.deltas[RESOURCE])
self.assertEqual(2, reservation.deltas[ALT_RESOURCE])
self.assertEqual(self.context.tenant_id,
reservation.tenant_id)
self.assertEqual(self.context.project_id,
reservation.project_id)
def test_make_reservation_over_quota_fails(self):
quota_driver = driver.DbQuotaDriver()
@ -257,12 +260,12 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
self.assertRaises(exceptions.OverQuota,
quota_driver.make_reservation,
self.context,
self.context.tenant_id,
self.context.project_id,
resources,
deltas,
self.plugin)
def test_get_detailed_tenant_quotas_resource(self):
def test_get_detailed_project_quotas_resource(self):
res = {RESOURCE: TestTrackedResource(RESOURCE, test_quota.MehModel)}
self.plugin.update_quota_limit(self.context, PROJECT, RESOURCE, 6)
@ -270,13 +273,13 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
quota_driver.make_reservation(self.context, PROJECT, res,
{RESOURCE: 1}, self.plugin)
quota_api.set_quota_usage(self.context, RESOURCE, PROJECT, 2)
detailed_quota = self.plugin.get_detailed_tenant_quotas(self.context,
res, PROJECT)
detailed_quota = self.plugin.get_detailed_project_quotas(
self.context, res, PROJECT)
self.assertEqual(6, detailed_quota[RESOURCE]['limit'])
self.assertEqual(2, detailed_quota[RESOURCE]['used'])
self.assertEqual(1, detailed_quota[RESOURCE]['reserved'])
def test_get_detailed_tenant_quotas_multiple_resource(self):
def test_get_detailed_project_quotas_multiple_resource(self):
project_1 = 'prj_test_1'
resource_1 = 'res_test_1'
resource_2 = 'res_test_2'
@ -295,9 +298,8 @@ class TestDbQuotaDriver(testlib_api.SqlTestCase,
quota_api.set_quota_usage(self.context, resource_1, project_1, 2)
quota_api.set_quota_usage(self.context, resource_2, project_1, 3)
detailed_quota = self.plugin.get_detailed_tenant_quotas(self.context,
resources,
project_1)
detailed_quota = self.plugin.get_detailed_project_quotas(
self.context, resources, project_1)
self.assertEqual(6, detailed_quota[resource_1]['limit'])
self.assertEqual(1, detailed_quota[resource_1]['reserved'])

View File

@ -73,9 +73,9 @@ class QuotaExtensionTestCase(testlib_api.WebTestCase):
router.APIRouter()
def _test_quota_default_values(self, expected_values):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id)}
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env)
quota = self.deserialize(res)
for resource, expected_value in expected_values.items():
@ -118,10 +118,10 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
'extra1': qconf.DEFAULT_QUOTA})
def test_show_default_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
res = self.api.get(_get_path('quotas', id=tenant_id,
res = self.api.get(_get_path('quotas', id=project_id,
action=DEFAULT_QUOTAS_ACTION,
fmt=self.fmt),
extra_environ=env)
@ -134,11 +134,11 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
self.assertEqual(
qconf.DEFAULT_QUOTA_PORT, quota['quota']['port'])
def test_show_default_quotas_with_owner_tenant(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
def test_show_default_quotas_with_owner_project(self):
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id,
res = self.api.get(_get_path('quotas', id=project_id,
action=DEFAULT_QUOTAS_ACTION,
fmt=self.fmt),
extra_environ=env)
@ -152,20 +152,20 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
qconf.DEFAULT_QUOTA_PORT, quota['quota']['port'])
def test_show_default_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id,
res = self.api.get(_get_path('quotas', id=project_id,
action=DEFAULT_QUOTAS_ACTION,
fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)
def test_show_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
@ -177,18 +177,18 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
qconf.DEFAULT_QUOTA_PORT, quota['quota']['port'])
def test_show_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)
def test_show_quotas_with_owner_tenant(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
def test_show_quotas_with_owner_project(self):
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
@ -200,8 +200,8 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
qconf.DEFAULT_QUOTA_PORT, quota['quota']['port'])
def test_list_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
res = self.api.get(_get_path('quotas', fmt=self.fmt),
extra_environ=env)
@ -210,93 +210,93 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
self.assertEqual([], quota['quotas'])
def test_list_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=False)}
res = self.api.get(_get_path('quotas', fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)
def test_update_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=False)}
quotas = {'quota': {'network': 100}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(403, res.status_int)
def test_update_quotas_with_non_integer_returns_400(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'network': 'abc'}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(400, res.status_int)
def test_update_quotas_with_negative_integer_returns_400(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'network': -2}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(400, res.status_int)
def test_update_quotas_with_out_of_range_integer_returns_400(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'network': constants.DB_INTEGER_MAX_VALUE + 1}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(exc.HTTPBadRequest.code, res.status_int)
def test_update_quotas_to_unlimited(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'network': -1}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=False)
self.assertEqual(200, res.status_int)
def test_update_quotas_exceeding_current_limit(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'network': 120}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=False)
self.assertEqual(200, res.status_int)
def test_update_quotas_with_non_support_resource_returns_400(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'abc': 100}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(400, res.status_int)
def test_update_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
quotas = {'quota': {'network': 100}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env)
self.assertEqual(200, res.status_int)
env2 = {'neutron.context': context.Context('', tenant_id)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
env2 = {'neutron.context': context.Context('', project_id)}
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env2)
quota = self.deserialize(res)
self.assertEqual(100, quota['quota']['network'])
@ -304,44 +304,44 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
self.assertEqual(qconf.DEFAULT_QUOTA_PORT, quota['quota']['port'])
def test_update_attributes(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
quotas = {'quota': {'extra1': 100}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env)
self.assertEqual(200, res.status_int)
env2 = {'neutron.context': context.Context('', tenant_id)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
env2 = {'neutron.context': context.Context('', project_id)}
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env2)
quota = self.deserialize(res)
self.assertEqual(100, quota['quota']['extra1'])
def test_delete_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
# Create a quota to ensure we have something to delete
quotas = {'quota': {'network': 100}}
self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env)
res = self.api.delete(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.delete(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env)
self.assertEqual(204, res.status_int)
def test_delete_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=False)}
res = self.api.delete(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.delete(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)
def test_delete_quota_with_unknown_tenant_returns_404(self):
tenant_id = 'idnotexist'
env = {'neutron.context': context.Context('', tenant_id + '2',
def test_delete_quota_with_unknown_project_returns_404(self):
project_id = 'idnotexist'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
res = self.api.delete(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.delete(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(exc.HTTPNotFound.code, res.status_int)
@ -353,44 +353,48 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
pass
def test_quotas_limit_check(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
quotas = {'quota': {'network': 5}}
res = self.api.put(_get_path('quotas', id=tenant_id,
res = self.api.put(_get_path('quotas', id=project_id,
fmt=self.fmt),
self.serialize(quotas), extra_environ=env)
self.assertEqual(200, res.status_int)
quota.QUOTAS.limit_check(context.Context('', tenant_id),
tenant_id,
quota.QUOTAS.limit_check(context.Context('', project_id),
project_id,
network=4)
def test_quotas_limit_check_with_invalid_quota_value(self):
tenant_id = 'tenant_id1'
project_id = 'project_id1'
with testtools.ExpectedException(exceptions.InvalidQuotaValue):
quota.QUOTAS.limit_check(context.Context('', tenant_id),
tenant_id,
quota.QUOTAS.limit_check(context.Context('', project_id),
project_id,
network=-2)
def test_quotas_limit_check_with_not_registered_resource_fails(self):
tenant_id = 'tenant_id1'
project_id = 'project_id1'
self.assertRaises(exceptions.QuotaResourceUnknown,
quota.QUOTAS.limit_check,
context.get_admin_context(),
tenant_id,
project_id,
foobar=1)
def test_quotas_get_tenant_from_request_context(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
def test_quotas_get_project_from_request_context(self):
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
res = self.api.get(_get_path('quotas/tenant', fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
self.assertEqual(quota['tenant']['tenant_id'], tenant_id)
# NOTE(ralonsoh): the Quota API keeps "tenant" and "project" methods
# for compatibility. "tenant" is already deprecated and will be
# removed.
for key in ('project', 'tenant'):
res = self.api.get(_get_path('quotas/' + key, fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
self.assertEqual(quota[key][key + '_id'], project_id)
def test_quotas_get_tenant_from_empty_request_context_returns_400(self):
def test_quotas_get_project_from_empty_request_context_returns_400(self):
env = {'neutron.context': context.Context('', '',
is_admin=True)}
res = self.api.get(_get_path('quotas/tenant', fmt=self.fmt),
@ -398,20 +402,20 @@ class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
self.assertEqual(400, res.status_int)
def test_make_reservation_resource_unknown_raises(self):
tenant_id = 'tenant_id1'
project_id = 'project_id1'
self.assertRaises(exceptions.QuotaResourceUnknown,
quota.QUOTAS.make_reservation,
context.get_admin_context(),
tenant_id,
project_id,
{'foobar': 1},
plugin=None)
def test_make_reservation_negative_delta_raises(self):
tenant_id = 'tenant_id1'
project_id = 'project_id1'
self.assertRaises(exceptions.InvalidQuotaValue,
quota.QUOTAS.make_reservation,
context.get_admin_context(),
tenant_id,
project_id,
{'network': -1},
plugin=None)
@ -441,34 +445,34 @@ class QuotaExtensionCfgTestCase(QuotaExtensionTestCase):
'extra1': qconf.DEFAULT_QUOTA})
def test_show_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
def test_show_quotas_without_admin_forbidden(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.get(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)
def test_update_quotas_forbidden(self):
tenant_id = 'tenant_id1'
project_id = 'project_id1'
quotas = {'quota': {'network': 100}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.put(_get_path('quotas', id=project_id, fmt=self.fmt),
self.serialize(quotas),
expect_errors=True)
self.assertEqual(200, res.status_int)
def test_delete_quotas_forbidden(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=False)}
res = self.api.delete(_get_path('quotas', id=tenant_id, fmt=self.fmt),
res = self.api.delete(_get_path('quotas', id=project_id, fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)
@ -476,7 +480,7 @@ class QuotaExtensionCfgTestCase(QuotaExtensionTestCase):
class TestDbQuotaDriver(base.BaseTestCase):
"""Test for neutron.db.quota.driver.DbQuotaDriver."""
def test_get_tenant_quotas_arg(self):
def test_get_project_quotas_arg(self):
"""Call neutron.db.quota.driver.DbQuotaDriver._get_quotas."""
quota_driver = driver.DbQuotaDriver()
@ -484,20 +488,20 @@ class TestDbQuotaDriver(base.BaseTestCase):
foo_quotas = {'network': 5}
default_quotas = {'network': 10}
target_tenant = 'foo'
target_project = 'foo'
with mock.patch.object(driver.DbQuotaDriver,
'get_tenant_quotas',
return_value=foo_quotas) as get_tenant_quotas:
'get_project_quotas',
return_value=foo_quotas) as get_project_quotas:
quotas = quota_driver._get_quotas(ctx,
target_tenant,
target_project,
default_quotas)
self.assertEqual(quotas, foo_quotas)
get_tenant_quotas.assert_called_once_with(ctx,
get_project_quotas.assert_called_once_with(ctx,
default_quotas,
target_tenant)
target_project)
class TestQuotaDriverLoad(base.BaseTestCase):

View File

@ -67,9 +67,9 @@ class DetailQuotaExtensionDbTestCase(DetailQuotaExtensionTestCase):
fmt = 'json'
def test_show_detail_quotas(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id)}
res = self.api.get(_get_path('quotas', id=tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id)}
res = self.api.get(_get_path('quotas', id=project_id,
fmt=self.fmt,
endpoint=DEFAULT_QUOTAS_ACTION),
extra_environ=env)
@ -95,10 +95,10 @@ class DetailQuotaExtensionDbTestCase(DetailQuotaExtensionTestCase):
'quota_network', -10, group='QUOTAS')
cfg.CONF.set_override(
'quota_subnet', -50, group='QUOTAS')
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id,
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id,
is_admin=True)}
res = self.api.get(_get_path('quotas', id=tenant_id,
res = self.api.get(_get_path('quotas', id=project_id,
fmt=self.fmt,
endpoint=DEFAULT_QUOTAS_ACTION),
extra_environ=env)
@ -118,10 +118,10 @@ class DetailQuotaExtensionDbTestCase(DetailQuotaExtensionTestCase):
quota['quota']['port']['limit'])
def test_show_detail_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=True)}
res = self.api.get(_get_path('quotas', id=tenant_id,
res = self.api.get(_get_path('quotas', id=project_id,
fmt=self.fmt,
endpoint=DEFAULT_QUOTAS_ACTION),
extra_environ=env)
@ -141,10 +141,10 @@ class DetailQuotaExtensionDbTestCase(DetailQuotaExtensionTestCase):
quota['quota']['port']['limit'])
def test_detail_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'neutron.context': context.Context('', tenant_id + '2',
project_id = 'project_id1'
env = {'neutron.context': context.Context('', project_id + '2',
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id,
res = self.api.get(_get_path('quotas', id=project_id,
fmt=self.fmt,
endpoint=DEFAULT_QUOTAS_ACTION),
extra_environ=env, expect_errors=True)

View File

@ -45,7 +45,9 @@ class BaseTestTrackedResources(test_plugin.Ml2PluginV2TestCase,
test_db_base_plugin_v2.NeutronDbPluginV2TestCase.quota_db_driver = (
'neutron.db.quota.driver.DbQuotaDriver')
super(BaseTestTrackedResources, self).setUp()
self._tenant_id = uuidutils.generate_uuid()
self._project_id = uuidutils.generate_uuid()
# TODO(ralonsoh): "tenant_id" reference should be removed.
self._tenant_id = self._project_id
@staticmethod
def _cleanup():
@ -54,7 +56,7 @@ class BaseTestTrackedResources(test_plugin.Ml2PluginV2TestCase,
def _test_init(self, resource_name):
quota_db_api.set_quota_usage(
self.ctx, resource_name, self._tenant_id)
self.ctx, resource_name, self._project_id)
class BaseTestEventHandler(object):
@ -88,7 +90,7 @@ class BaseTestEventHandler(object):
if item:
model = self.handler_mock.call_args_list[call_idx][0][-1]
self.assertEqual(model['id'], item['id'])
self.assertEqual(model['tenant_id'], item['tenant_id'])
self.assertEqual(model['project_id'], item['project_id'])
call_idx = call_idx - 1
@ -140,7 +142,7 @@ class TestTrackedResourcesEventHandler(BaseTestEventHandler,
self._test_init('subnetpool')
pool = self._make_subnetpool('json', ['10.0.0.0/8'],
name='meh',
tenant_id=self._tenant_id)['subnetpool']
tenant_id=self._project_id)['subnetpool']
self._verify_event_handler_calls(pool)
self._delete('subnetpools', pool['id'])
self._verify_event_handler_calls(pool, expected_call_count=2)
@ -148,7 +150,8 @@ class TestTrackedResourcesEventHandler(BaseTestEventHandler,
def test_create_delete_securitygroup_triggers_event(self):
self._test_init('security_group')
sec_group = self._make_security_group(
'json', 'meh', 'meh', tenant_id=self._tenant_id)['security_group']
'json', 'meh', 'meh',
project_id=self._project_id)['security_group']
# When a security group is created it also creates 2 rules, therefore
# there will be three calls and we need to verify the first
self._verify_event_handler_calls([None, None, sec_group],
@ -161,9 +164,10 @@ class TestTrackedResourcesEventHandler(BaseTestEventHandler,
def test_create_delete_securitygrouprule_triggers_event(self):
self._test_init('security_group_rule')
sec_group = self._make_security_group(
'json', 'meh', 'meh', tenant_id=self._tenant_id)['security_group']
'json', 'meh', 'meh',
project_id=self._project_id)['security_group']
rule_req = self._build_security_group_rule(
sec_group['id'], 'ingress', 'TCP', tenant_id=self._tenant_id)
sec_group['id'], 'ingress', 'TCP', tenant_id=self._project_id)
sec_group_rule = self._make_security_group_rule(
'json', rule_req)['security_group_rule']
# When a security group is created it also creates 2 rules, therefore
@ -213,8 +217,8 @@ class TestL3ResourcesEventHandler(BaseTestEventHandler,
class TestTrackedResources(BaseTestTrackedResources):
def _verify_dirty_bit(self, resource_name, expected_value=True):
usage = quota_db_api.get_quota_usage_by_resource_and_tenant(
self.ctx, resource_name, self._tenant_id)
usage = quota_db_api.get_quota_usage_by_resource_and_project(
self.ctx, resource_name, self._project_id)
self.assertEqual(expected_value, usage.dirty)
def test_create_delete_network_marks_dirty(self):
@ -223,14 +227,14 @@ class TestTrackedResources(BaseTestTrackedResources):
self._verify_dirty_bit('network')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'network', self._tenant_id, dirty=False)
self.ctx, 'network', self._project_id, dirty=False)
self._delete('networks', net['id'])
self._verify_dirty_bit('network')
def test_list_networks_clears_dirty(self):
self._test_init('network')
net = self._make_network('json', 'meh', True)['network']
self.ctx.tenant_id = net['tenant_id']
self.ctx.project_id = net['project_id']
self._list('networks', neutron_context=self.ctx)
self._verify_dirty_bit('network', expected_value=False)
@ -241,7 +245,7 @@ class TestTrackedResources(BaseTestTrackedResources):
self._verify_dirty_bit('port')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'port', self._tenant_id, dirty=False)
self.ctx, 'port', self._project_id, dirty=False)
self._delete('ports', port['id'])
self._verify_dirty_bit('port')
@ -249,7 +253,7 @@ class TestTrackedResources(BaseTestTrackedResources):
self._test_init('port')
net = self._make_network('json', 'meh', True)['network']
port = self._make_port('json', net['id'])['port']
self.ctx.tenant_id = port['tenant_id']
self.ctx.project_id = port['project_id']
self._list('ports', neutron_context=self.ctx)
self._verify_dirty_bit('port', expected_value=False)
@ -261,7 +265,7 @@ class TestTrackedResources(BaseTestTrackedResources):
self._verify_dirty_bit('subnet')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'subnet', self._tenant_id, dirty=False)
self.ctx, 'subnet', self._project_id, dirty=False)
self._delete('subnets', subnet['id'])
self._verify_dirty_bit('subnet')
@ -274,7 +278,7 @@ class TestTrackedResources(BaseTestTrackedResources):
self._verify_dirty_bit('subnet')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'subnet', self._tenant_id, dirty=False)
self.ctx, 'subnet', self._project_id, dirty=False)
self._delete('networks', net['network']['id'])
self._verify_dirty_bit('network')
self._verify_dirty_bit('subnet')
@ -284,7 +288,7 @@ class TestTrackedResources(BaseTestTrackedResources):
net = self._make_network('json', 'meh', True)
subnet = self._make_subnet('json', net, '10.0.0.1',
'10.0.0.0/24')['subnet']
self.ctx.tenant_id = subnet['tenant_id']
self.ctx.project_id = subnet['project_id']
self._list('subnets', neutron_context=self.ctx)
self._verify_dirty_bit('subnet', expected_value=False)
@ -292,11 +296,11 @@ class TestTrackedResources(BaseTestTrackedResources):
self._test_init('subnetpool')
pool = self._make_subnetpool('json', ['10.0.0.0/8'],
name='meh',
tenant_id=self._tenant_id)['subnetpool']
tenant_id=self._project_id)['subnetpool']
self._verify_dirty_bit('subnetpool')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'subnetpool', self._tenant_id, dirty=False)
self.ctx, 'subnetpool', self._project_id, dirty=False)
self._delete('subnetpools', pool['id'])
self._verify_dirty_bit('subnetpool')
@ -304,51 +308,51 @@ class TestTrackedResources(BaseTestTrackedResources):
self._test_init('subnetpool')
pool = self._make_subnetpool('json', ['10.0.0.0/8'],
name='meh',
tenant_id=self._tenant_id)['subnetpool']
self.ctx.tenant_id = pool['tenant_id']
tenant_id=self._project_id)['subnetpool']
self.ctx.project_id = pool['project_id']
self._list('subnetpools', neutron_context=self.ctx)
self._verify_dirty_bit('subnetpool', expected_value=False)
def test_create_delete_securitygroup_marks_dirty(self):
self._test_init('security_group')
sec_group = self._make_security_group(
'json', 'meh', 'meh', tenant_id=self._tenant_id)['security_group']
'json', 'meh', 'meh', tenant_id=self._project_id)['security_group']
self._verify_dirty_bit('security_group')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'security_group', self._tenant_id, dirty=False)
self.ctx, 'security_group', self._project_id, dirty=False)
self._delete('security-groups', sec_group['id'])
self._verify_dirty_bit('security_group')
def test_list_securitygroups_clears_dirty(self):
self._test_init('security_group')
self._make_security_group(
'json', 'meh', 'meh', tenant_id=self._tenant_id)['security_group']
self.ctx.tenant_id = self._tenant_id
'json', 'meh', 'meh', tenant_id=self._project_id)['security_group']
self.ctx.project_id = self._project_id
self._list('security-groups', neutron_context=self.ctx)
self._verify_dirty_bit('security_group', expected_value=False)
def test_create_delete_securitygrouprule_marks_dirty(self):
self._test_init('security_group_rule')
sec_group = self._make_security_group(
'json', 'meh', 'meh', tenant_id=self._tenant_id)['security_group']
'json', 'meh', 'meh', tenant_id=self._project_id)['security_group']
rule_req = self._build_security_group_rule(
sec_group['id'], 'ingress', 'TCP', tenant_id=self._tenant_id)
sec_group['id'], 'ingress', 'TCP', tenant_id=self._project_id)
sec_group_rule = self._make_security_group_rule(
'json', rule_req)['security_group_rule']
self._verify_dirty_bit('security_group_rule')
# Clear the dirty bit
quota_db_api.set_quota_usage_dirty(
self.ctx, 'security_group_rule', self._tenant_id, dirty=False)
self.ctx, 'security_group_rule', self._project_id, dirty=False)
self._delete('security-group-rules', sec_group_rule['id'])
self._verify_dirty_bit('security_group_rule')
def test_list_securitygrouprules_clears_dirty(self):
self._test_init('security_group_rule')
self._make_security_group(
'json', 'meh', 'meh', tenant_id=self._tenant_id)['security_group']
'json', 'meh', 'meh', tenant_id=self._project_id)['security_group']
# As the security group create operation also creates 2 security group
# rules there is no need to explicitly create any rule
self.ctx.tenant_id = self._tenant_id
self.ctx.project_id = self._project_id
self._list('security-group-rules', neutron_context=self.ctx)
self._verify_dirty_bit('security_group_rule', expected_value=False)

View File

@ -75,22 +75,22 @@ class TestResource(base.DietTestCase):
class TestTrackedResource(testlib_api.SqlTestCase):
def _add_data(self, tenant_id=None):
def _add_data(self, project_id=None):
session = db_api.get_writer_session()
with session.begin():
tenant_id = tenant_id or self.tenant_id
project_id = project_id or self.project_id
session.add(test_quota.MehModel(
meh='meh_%s' % uuidutils.generate_uuid(),
tenant_id=tenant_id))
project_id=project_id))
session.add(test_quota.MehModel(
meh='meh_%s' % uuidutils.generate_uuid(),
tenant_id=tenant_id))
project_id=project_id))
def _delete_data(self):
session = db_api.get_writer_session()
with session.begin():
query = session.query(test_quota.MehModel).filter_by(
tenant_id=self.tenant_id)
project_id=self.project_id)
for item in query:
session.delete(item)
@ -98,7 +98,7 @@ class TestTrackedResource(testlib_api.SqlTestCase):
session = db_api.get_writer_session()
with session.begin():
query = session.query(test_quota.MehModel).filter_by(
tenant_id=self.tenant_id)
project_id=self.project_id)
for item in query:
item['meh'] = 'meh-%s' % item['meh']
session.add(item)
@ -109,9 +109,9 @@ class TestTrackedResource(testlib_api.SqlTestCase):
self.setup_coreplugin(DB_PLUGIN_KLASS)
self.resource = 'meh'
self.other_resource = 'othermeh'
self.tenant_id = 'meh'
self.project_id = 'meh'
self.context = context.Context(
user_id='', tenant_id=self.tenant_id, is_admin=False)
user_id='', project_id=self.project_id, is_admin=False)
def _create_resource(self):
res = resource.TrackedResource(
@ -133,7 +133,7 @@ class TestTrackedResource(testlib_api.SqlTestCase):
def test_count_first_call_with_dirty_false(self):
quota_api.set_quota_usage(
self.context, self.resource, self.tenant_id, in_use=1)
self.context, self.resource, self.project_id, in_use=1)
res = self._create_resource()
self._add_data()
# explicitly set dirty flag to False
@ -141,17 +141,17 @@ class TestTrackedResource(testlib_api.SqlTestCase):
self.context, self.resource, dirty=False)
# Expect correct count to be returned anyway since the first call to
# count() always resyncs with the db
self.assertEqual(2, res.count(self.context, None, self.tenant_id))
self.assertEqual(2, res.count(self.context, None, self.project_id))
def test_count_reserved(self):
res = self._create_resource()
quota_api.create_reservation(self.context, self.tenant_id,
quota_api.create_reservation(self.context, self.project_id,
{res.name: 1})
self.assertEqual(1, res.count_reserved(self.context, self.tenant_id))
self.assertEqual(1, res.count_reserved(self.context, self.project_id))
def test_count_used_first_call_with_dirty_false(self):
quota_api.set_quota_usage(
self.context, self.resource, self.tenant_id, in_use=1)
self.context, self.resource, self.project_id, in_use=1)
res = self._create_resource()
self._add_data()
# explicitly set dirty flag to False
@ -160,18 +160,18 @@ class TestTrackedResource(testlib_api.SqlTestCase):
# Expect correct count_used to be returned
# anyway since the first call to
# count_used() always resyncs with the db
self.assertEqual(2, res.count_used(self.context, self.tenant_id))
self.assertEqual(2, res.count_used(self.context, self.project_id))
def _test_count(self):
res = self._create_resource()
quota_api.set_quota_usage(
self.context, res.name, self.tenant_id, in_use=0)
self.context, res.name, self.project_id, in_use=0)
self._add_data()
return res
def test_count_with_dirty_false(self):
res = self._test_count()
res.count(self.context, None, self.tenant_id)
res.count(self.context, None, self.project_id)
# At this stage count has been invoked, and the dirty flag should be
# false. Another invocation of count should not query the model class
set_quota = 'neutron.db.quota.api.set_quota_usage'
@ -179,11 +179,11 @@ class TestTrackedResource(testlib_api.SqlTestCase):
self.assertEqual(0, mock_set_quota.call_count)
self.assertEqual(2, res.count(self.context,
None,
self.tenant_id))
self.project_id))
def test_count_used_with_dirty_false(self):
res = self._test_count()
res.count_used(self.context, self.tenant_id)
res.count_used(self.context, self.project_id)
# At this stage count_used has been invoked,
# and the dirty flag should be false. Another invocation
# of count_used should not query the model class
@ -191,7 +191,7 @@ class TestTrackedResource(testlib_api.SqlTestCase):
with mock.patch(set_quota) as mock_set_quota:
self.assertEqual(0, mock_set_quota.call_count)
self.assertEqual(2, res.count_used(self.context,
self.tenant_id))
self.project_id))
def test_count_with_dirty_true_resync(self):
res = self._test_count()
@ -199,7 +199,7 @@ class TestTrackedResource(testlib_api.SqlTestCase):
# set_quota_usage has been invoked with the correct parameters
self.assertEqual(2, res.count(self.context,
None,
self.tenant_id,
self.project_id,
resync_usage=True))
def test_count_used_with_dirty_true_resync(self):
@ -207,7 +207,7 @@ class TestTrackedResource(testlib_api.SqlTestCase):
# Expect correct count_used to be returned, which also implies
# set_quota_usage has been invoked with the correct parameters
self.assertEqual(2, res.count_used(self.context,
self.tenant_id,
self.project_id,
resync_usage=True))
def test_count_with_dirty_true_resync_calls_set_quota_usage(self):
@ -216,11 +216,11 @@ class TestTrackedResource(testlib_api.SqlTestCase):
with mock.patch(set_quota_usage) as mock_set_quota_usage:
quota_api.set_quota_usage_dirty(self.context,
self.resource,
self.tenant_id)
res.count(self.context, None, self.tenant_id,
self.project_id)
res.count(self.context, None, self.project_id,
resync_usage=True)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)
self.context, self.resource, self.project_id, in_use=2)
def test_count_used_with_dirty_true_resync_calls_set_quota_usage(self):
res = self._test_count()
@ -228,25 +228,25 @@ class TestTrackedResource(testlib_api.SqlTestCase):
with mock.patch(set_quota_usage) as mock_set_quota_usage:
quota_api.set_quota_usage_dirty(self.context,
self.resource,
self.tenant_id)
res.count_used(self.context, self.tenant_id,
self.project_id)
res.count_used(self.context, self.project_id,
resync_usage=True)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)
self.context, self.resource, self.project_id, in_use=2)
def test_count_with_dirty_true_no_usage_info(self):
res = self._create_resource()
self._add_data()
# Invoke count without having usage info in DB - Expect correct
# count to be returned
self.assertEqual(2, res.count(self.context, None, self.tenant_id))
self.assertEqual(2, res.count(self.context, None, self.project_id))
def test_count_used_with_dirty_true_no_usage_info(self):
res = self._create_resource()
self._add_data()
# Invoke count_used without having usage info in DB - Expect correct
# count_used to be returned
self.assertEqual(2, res.count_used(self.context, self.tenant_id))
self.assertEqual(2, res.count_used(self.context, self.project_id))
def test_count_with_dirty_true_no_usage_info_calls_set_quota_usage(self):
res = self._create_resource()
@ -255,10 +255,10 @@ class TestTrackedResource(testlib_api.SqlTestCase):
with mock.patch(set_quota_usage) as mock_set_quota_usage:
quota_api.set_quota_usage_dirty(self.context,
self.resource,
self.tenant_id)
res.count(self.context, None, self.tenant_id, resync_usage=True)
self.project_id)
res.count(self.context, None, self.project_id, resync_usage=True)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)
self.context, self.resource, self.project_id, in_use=2)
def test_count_used_with_dirty_true_no_usage_info_calls_set_quota_usage(
self):
@ -268,44 +268,44 @@ class TestTrackedResource(testlib_api.SqlTestCase):
with mock.patch(set_quota_usage) as mock_set_quota_usage:
quota_api.set_quota_usage_dirty(self.context,
self.resource,
self.tenant_id)
res.count_used(self.context, self.tenant_id, resync_usage=True)
self.project_id)
res.count_used(self.context, self.project_id, resync_usage=True)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)
self.context, self.resource, self.project_id, in_use=2)
def test_add_delete_data_triggers_event(self):
res = self._create_resource()
other_res = self._create_other_resource()
# Validate dirty tenants since mock does not work well with SQLAlchemy
# Validate dirty projects since mock does not work well with SQLAlchemy
# event handlers.
self._add_data()
self._add_data('someone_else')
self.assertEqual(2, len(res._dirty_tenants))
self.assertEqual(2, len(res._dirty_projects))
# Also, the dirty flag should not be set for other resources
self.assertEqual(0, len(other_res._dirty_tenants))
self.assertIn(self.tenant_id, res._dirty_tenants)
self.assertIn('someone_else', res._dirty_tenants)
self.assertEqual(0, len(other_res._dirty_projects))
self.assertIn(self.project_id, res._dirty_projects)
self.assertIn('someone_else', res._dirty_projects)
def test_delete_data_triggers_event(self):
res = self._create_resource()
self._add_data()
self._add_data('someone_else')
# Artificially clear _dirty_tenants
res._dirty_tenants.clear()
# Artificially clear _dirty_projects
res._dirty_projects.clear()
self._delete_data()
# We did not delete "someone_else", so expect only a single dirty
# tenant
self.assertEqual(1, len(res._dirty_tenants))
self.assertIn(self.tenant_id, res._dirty_tenants)
# project
self.assertEqual(1, len(res._dirty_projects))
self.assertIn(self.project_id, res._dirty_projects)
def test_update_does_not_trigger_event(self):
res = self._create_resource()
self._add_data()
self._add_data('someone_else')
# Artificially clear _dirty_tenants
res._dirty_tenants.clear()
# Artificially clear _dirty_projects
res._dirty_projects.clear()
self._update_data()
self.assertEqual(0, len(res._dirty_tenants))
self.assertEqual(0, len(res._dirty_projects))
def test_mark_dirty(self):
res = self._create_resource()
@ -316,11 +316,11 @@ class TestTrackedResource(testlib_api.SqlTestCase):
res.mark_dirty(self.context)
self.assertEqual(2, mock_set_quota_usage.call_count)
mock_set_quota_usage.assert_any_call(
self.context, self.resource, self.tenant_id)
self.context, self.resource, self.project_id)
mock_set_quota_usage.assert_any_call(
self.context, self.resource, 'someone_else')
def test_mark_dirty_no_dirty_tenant(self):
def test_mark_dirty_no_dirty_project(self):
res = self._create_resource()
set_quota_usage = 'neutron.db.quota.api.set_quota_usage_dirty'
with mock.patch(set_quota_usage) as mock_set_quota_usage:
@ -331,14 +331,14 @@ class TestTrackedResource(testlib_api.SqlTestCase):
res = self._create_resource()
self._add_data()
res.mark_dirty(self.context)
# self.tenant_id now is out of sync
# self.project_id now is out of sync
set_quota_usage = 'neutron.db.quota.api.set_quota_usage'
with mock.patch(set_quota_usage) as mock_set_quota_usage:
res.resync(self.context, self.tenant_id)
res.resync(self.context, self.project_id)
# and now it should be in sync
self.assertNotIn(self.tenant_id, res._out_of_sync_tenants)
self.assertNotIn(self.project_id, res._out_of_sync_projects)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)
self.context, self.resource, self.project_id, in_use=2)
class Test_CountResource(base.BaseTestCase):
@ -355,15 +355,15 @@ class Test_CountResource(base.BaseTestCase):
context = mock.Mock()
collection_name = 'floatingips'
tenant_id = 'fakeid'
project_id = 'fakeid'
self.assertRaises(
NotImplementedError,
resource._count_resource, context, collection_name, tenant_id)
resource._count_resource, context, collection_name, project_id)
for plugin in plugins.values():
for func in (plugin.get_floatingips_count, plugin.get_floatingips):
func.assert_called_with(
context, filters={'tenant_id': [tenant_id]})
context, filters={'project_id': [project_id]})
def test_core_plugin_checked_first(self):
plugin1 = mock.Mock()
@ -378,6 +378,6 @@ class Test_CountResource(base.BaseTestCase):
context = mock.Mock()
collection_name = 'floatingips'
tenant_id = 'fakeid'
project_id = 'fakeid'
self.assertEqual(
10, resource._count_resource(context, collection_name, tenant_id))
10, resource._count_resource(context, collection_name, project_id))

View File

@ -101,7 +101,7 @@ class TestAuxiliaryFunctions(base.DietTestCase):
'TrackedResource.resync') as mock_resync:
self.registry.set_tracked_resource('meh', test_quota.MehModel)
self.registry.register_resource_by_name('meh')
resource_registry.resync_resource(mock.ANY, 'meh', 'tenant_id')
resource_registry.resync_resource(mock.ANY, 'meh', 'project_id')
self.assertEqual(0, mock_resync.call_count)
def test_resync_tracked_resource(self):
@ -109,14 +109,14 @@ class TestAuxiliaryFunctions(base.DietTestCase):
'TrackedResource.resync') as mock_resync:
self.registry.set_tracked_resource('meh', test_quota.MehModel)
self.registry.register_resource_by_name('meh')
resource_registry.resync_resource(mock.ANY, 'meh', 'tenant_id')
mock_resync.assert_called_once_with(mock.ANY, 'tenant_id')
resource_registry.resync_resource(mock.ANY, 'meh', 'project_id')
mock_resync.assert_called_once_with(mock.ANY, 'project_id')
def test_resync_non_tracked_resource(self):
with mock.patch('neutron.quota.resource.'
'TrackedResource.resync') as mock_resync:
self.registry.register_resource_by_name('meh')
resource_registry.resync_resource(mock.ANY, 'meh', 'tenant_id')
resource_registry.resync_resource(mock.ANY, 'meh', 'project_id')
self.assertEqual(0, mock_resync.call_count)
def test_set_resources_dirty_invoked_with_tracking_disabled(self):
@ -132,7 +132,7 @@ class TestAuxiliaryFunctions(base.DietTestCase):
self.assertEqual(0, mock_mark_dirty.call_count)
def test_set_resources_dirty_no_dirty_resource(self):
ctx = context.Context('user_id', 'tenant_id',
ctx = context.Context('user_id', 'project_id',
is_admin=False, is_advsvc=False)
with mock.patch('neutron.quota.resource.'
'TrackedResource.mark_dirty') as mock_mark_dirty:
@ -140,12 +140,12 @@ class TestAuxiliaryFunctions(base.DietTestCase):
self.registry.register_resource_by_name('meh')
res = self.registry.get_resource('meh')
# This ensures dirty is false
res._dirty_tenants.clear()
res._dirty_projects.clear()
resource_registry.set_resources_dirty(ctx)
self.assertEqual(0, mock_mark_dirty.call_count)
def test_set_resources_dirty_no_tracked_resource(self):
ctx = context.Context('user_id', 'tenant_id',
ctx = context.Context('user_id', 'project_id',
is_admin=False, is_advsvc=False)
with mock.patch('neutron.quota.resource.'
'TrackedResource.mark_dirty') as mock_mark_dirty:
@ -154,7 +154,7 @@ class TestAuxiliaryFunctions(base.DietTestCase):
self.assertEqual(0, mock_mark_dirty.call_count)
def test_set_resources_dirty(self):
ctx = context.Context('user_id', 'tenant_id',
ctx = context.Context('user_id', 'project_id',
is_admin=False, is_advsvc=False)
with mock.patch('neutron.quota.resource.'
'TrackedResource.mark_dirty') as mock_mark_dirty:
@ -163,6 +163,6 @@ class TestAuxiliaryFunctions(base.DietTestCase):
self.registry.resources['meh']._track_resource_events = True
res = self.registry.get_resource('meh')
# This ensures dirty is true
res._dirty_tenants.add('tenant_id')
res._dirty_projects.add('project_id')
resource_registry.set_resources_dirty(ctx)
mock_mark_dirty.assert_called_once_with(ctx)