1 # Copyright (c) 2015 OpenStack Foundation. All rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
18 import sqlalchemy as sa
19 from sqlalchemy.orm import exc as orm_exc
20 from sqlalchemy import sql
22 from neutron.db import api as db_api
23 from neutron.db import common_db_mixin as common_db_api
24 from neutron.db.quota import models as quota_models
27 # Wrapper for utcnow - needed for mocking it in unit tests
29 return datetime.datetime.utcnow()
32 class QuotaUsageInfo(collections.namedtuple(
33 'QuotaUsageInfo', ['resource', 'tenant_id', 'used', 'dirty'])):
34 """Information about resource quota usage."""
37 class ReservationInfo(collections.namedtuple(
38 'ReservationInfo', ['reservation_id', 'tenant_id',
39 'expiration', 'deltas'])):
40 """Information about a resource reservation."""
43 def get_quota_usage_by_resource_and_tenant(context, resource, tenant_id,
44 lock_for_update=False):
45 """Return usage info for a given resource and tenant.
47 :param context: Request context
48 :param resource: Name of the resource
49 :param tenant_id: Tenant identifier
50 :param lock_for_update: if True sets a write-intent lock on the query
51 :returns: a QuotaUsageInfo instance
54 query = common_db_api.model_query(context, quota_models.QuotaUsage)
55 query = query.filter_by(resource=resource, tenant_id=tenant_id)
58 query = query.with_lockmode('update')
60 result = query.first()
63 return QuotaUsageInfo(result.resource,
69 def get_quota_usage_by_resource(context, resource):
70 query = common_db_api.model_query(context, quota_models.QuotaUsage)
71 query = query.filter_by(resource=resource)
72 return [QuotaUsageInfo(item.resource,
75 item.dirty) for item in query]
78 def get_quota_usage_by_tenant_id(context, tenant_id):
79 query = common_db_api.model_query(context, quota_models.QuotaUsage)
80 query = query.filter_by(tenant_id=tenant_id)
81 return [QuotaUsageInfo(item.resource,
84 item.dirty) for item in query]
87 def set_quota_usage(context, resource, tenant_id,
88 in_use=None, delta=False):
89 """Set resource quota usage.
91 :param context: instance of neutron context with db session
92 :param resource: name of the resource for which usage is being set
93 :param tenant_id: identifier of the tenant for which quota usage is
95 :param in_use: integer specifying the new quantity of used resources,
96 or a delta to apply to current used resource
97 :param delta: Specifies whether in_use is an absolute number
98 or a delta (default to False)
100 with db_api.autonested_transaction(context.session):
101 query = common_db_api.model_query(context, quota_models.QuotaUsage)
102 query = query.filter_by(resource=resource).filter_by(
104 usage_data = query.first()
107 usage_data = quota_models.QuotaUsage(
110 context.session.add(usage_data)
111 # Perform explicit comparison with None as 0 is a valid value
112 if in_use is not None:
114 in_use = usage_data.in_use + in_use
115 usage_data.in_use = in_use
116 # After an explicit update the dirty bit should always be reset
117 usage_data.dirty = False
118 return QuotaUsageInfo(usage_data.resource,
119 usage_data.tenant_id,
124 def set_quota_usage_dirty(context, resource, tenant_id, dirty=True):
125 """Set quota usage dirty bit for a given resource and tenant.
127 :param resource: a resource for which quota usage if tracked
128 :param tenant_id: tenant identifier
129 :param dirty: the desired value for the dirty bit (defaults to True)
130 :returns: 1 if the quota usage data were updated, 0 otherwise.
132 query = common_db_api.model_query(context, quota_models.QuotaUsage)
133 query = query.filter_by(resource=resource).filter_by(tenant_id=tenant_id)
134 return query.update({'dirty': dirty})
137 def set_resources_quota_usage_dirty(context, resources, tenant_id, dirty=True):
138 """Set quota usage dirty bit for a given tenant and multiple resources.
140 :param resources: list of resource for which the dirty bit is going
142 :param tenant_id: tenant identifier
143 :param dirty: the desired value for the dirty bit (defaults to True)
144 :returns: the number of records for which the bit was actually set.
146 query = common_db_api.model_query(context, quota_models.QuotaUsage)
147 query = query.filter_by(tenant_id=tenant_id)
149 query = query.filter(quota_models.QuotaUsage.resource.in_(resources))
150 # synchronize_session=False needed because of the IN condition
151 return query.update({'dirty': dirty}, synchronize_session=False)
154 def set_all_quota_usage_dirty(context, resource, dirty=True):
155 """Set the dirty bit on quota usage for all tenants.
157 :param resource: the resource for which the dirty bit should be set
158 :returns: the number of tenants for which the dirty bit was
161 query = common_db_api.model_query(context, quota_models.QuotaUsage)
162 query = query.filter_by(resource=resource)
163 return query.update({'dirty': dirty})
166 def create_reservation(context, tenant_id, deltas, expiration=None):
167 # This method is usually called from within another transaction.
168 # Consider using begin_nested
169 with context.session.begin(subtransactions=True):
170 expiration = expiration or (utcnow() + datetime.timedelta(0, 120))
171 resv = quota_models.Reservation(tenant_id=tenant_id,
172 expiration=expiration)
173 context.session.add(resv)
174 for (resource, delta) in deltas.items():
176 quota_models.ResourceDelta(resource=resource,
179 return ReservationInfo(resv['id'],
182 dict((delta.resource, delta.amount)
183 for delta in resv.resource_deltas))
186 def get_reservation(context, reservation_id):
187 query = context.session.query(quota_models.Reservation).filter_by(
192 return ReservationInfo(resv['id'],
195 dict((delta.resource, delta.amount)
196 for delta in resv.resource_deltas))
199 def remove_reservation(context, reservation_id, set_dirty=False):
200 delete_query = context.session.query(quota_models.Reservation).filter_by(
202 # Not handling MultipleResultsFound as the query is filtering by primary
205 reservation = delete_query.one()
206 except orm_exc.NoResultFound:
207 # TODO(salv-orlando): Raise here and then handle the exception?
209 tenant_id = reservation.tenant_id
210 resources = [delta.resource for delta in reservation.resource_deltas]
211 num_deleted = delete_query.delete()
213 # quota_usage for all resource involved in this reservation must
215 set_resources_quota_usage_dirty(context, resources, tenant_id)
219 def get_reservations_for_resources(context, tenant_id, resources,
221 """Retrieve total amount of reservations for specified resources.
223 :param context: Neutron context with db session
224 :param tenant_id: Tenant identifier
225 :param resources: Resources for which reserved amounts should be fetched
226 :param expired: False to fetch active reservations, True to fetch expired
227 reservations (defaults to False)
228 :returns: a dictionary mapping resources with corresponding deltas
234 resv_query = context.session.query(
235 quota_models.ResourceDelta.resource,
236 quota_models.Reservation.expiration,
237 sql.func.sum(quota_models.ResourceDelta.amount)).join(
238 quota_models.Reservation)
240 exp_expr = (quota_models.Reservation.expiration < now)
242 exp_expr = (quota_models.Reservation.expiration >= now)
243 resv_query = resv_query.filter(sa.and_(
244 quota_models.Reservation.tenant_id == tenant_id,
245 quota_models.ResourceDelta.resource.in_(resources),
247 quota_models.ResourceDelta.resource,
248 quota_models.Reservation.expiration)
249 return dict((resource, total_reserved)
250 for (resource, exp, total_reserved) in resv_query)
253 def remove_expired_reservations(context, tenant_id=None):
255 resv_query = context.session.query(quota_models.Reservation)
257 tenant_expr = (quota_models.Reservation.tenant_id == tenant_id)
259 tenant_expr = sql.true()
260 resv_query = resv_query.filter(sa.and_(
261 tenant_expr, quota_models.Reservation.expiration < now))
262 return resv_query.delete()