Set lock_path correctly.
[openstack-build/neutron-build.git] / neutron / db / quota / api.py
1 # Copyright (c) 2015 OpenStack Foundation.  All rights reserved.
2 #
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
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
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
13 #    under the License.
14
15 import collections
16 import datetime
17
18 import sqlalchemy as sa
19 from sqlalchemy.orm import exc as orm_exc
20 from sqlalchemy import sql
21
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
25
26
27 # Wrapper for utcnow - needed for mocking it in unit tests
28 def utcnow():
29     return datetime.datetime.utcnow()
30
31
32 class QuotaUsageInfo(collections.namedtuple(
33     'QuotaUsageInfo', ['resource', 'tenant_id', 'used', 'dirty'])):
34     """Information about resource quota usage."""
35
36
37 class ReservationInfo(collections.namedtuple(
38     'ReservationInfo', ['reservation_id', 'tenant_id',
39                         'expiration', 'deltas'])):
40     """Information about a resource reservation."""
41
42
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.
46
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
52     """
53
54     query = common_db_api.model_query(context, quota_models.QuotaUsage)
55     query = query.filter_by(resource=resource, tenant_id=tenant_id)
56
57     if lock_for_update:
58         query = query.with_lockmode('update')
59
60     result = query.first()
61     if not result:
62         return
63     return QuotaUsageInfo(result.resource,
64                           result.tenant_id,
65                           result.in_use,
66                           result.dirty)
67
68
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,
73                            item.tenant_id,
74                            item.in_use,
75                            item.dirty) for item in query]
76
77
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,
82                            item.tenant_id,
83                            item.in_use,
84                            item.dirty) for item in query]
85
86
87 def set_quota_usage(context, resource, tenant_id,
88                     in_use=None, delta=False):
89     """Set resource quota usage.
90
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
94                       being set
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)
99     """
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(
103             tenant_id=tenant_id)
104         usage_data = query.first()
105         if not usage_data:
106             # Must create entry
107             usage_data = quota_models.QuotaUsage(
108                 resource=resource,
109                 tenant_id=tenant_id)
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:
113             if delta:
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,
120                           usage_data.in_use,
121                           usage_data.dirty)
122
123
124 def set_quota_usage_dirty(context, resource, tenant_id, dirty=True):
125     """Set quota usage dirty bit for a given resource and tenant.
126
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.
131     """
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})
135
136
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.
139
140     :param resources: list of resource for which the dirty bit is going
141                       to be set
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.
145     """
146     query = common_db_api.model_query(context, quota_models.QuotaUsage)
147     query = query.filter_by(tenant_id=tenant_id)
148     if resources:
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)
152
153
154 def set_all_quota_usage_dirty(context, resource, dirty=True):
155     """Set the dirty bit on quota usage for all tenants.
156
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
159               actually updated
160     """
161     query = common_db_api.model_query(context, quota_models.QuotaUsage)
162     query = query.filter_by(resource=resource)
163     return query.update({'dirty': dirty})
164
165
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():
175             context.session.add(
176                 quota_models.ResourceDelta(resource=resource,
177                                            amount=delta,
178                                            reservation=resv))
179     return ReservationInfo(resv['id'],
180                            resv['tenant_id'],
181                            resv['expiration'],
182                            dict((delta.resource, delta.amount)
183                                 for delta in resv.resource_deltas))
184
185
186 def get_reservation(context, reservation_id):
187     query = context.session.query(quota_models.Reservation).filter_by(
188         id=reservation_id)
189     resv = query.first()
190     if not resv:
191         return
192     return ReservationInfo(resv['id'],
193                            resv['tenant_id'],
194                            resv['expiration'],
195                            dict((delta.resource, delta.amount)
196                                 for delta in resv.resource_deltas))
197
198
199 def remove_reservation(context, reservation_id, set_dirty=False):
200     delete_query = context.session.query(quota_models.Reservation).filter_by(
201         id=reservation_id)
202     # Not handling MultipleResultsFound as the query is filtering by primary
203     # key
204     try:
205         reservation = delete_query.one()
206     except orm_exc.NoResultFound:
207         # TODO(salv-orlando): Raise here and then handle the exception?
208         return
209     tenant_id = reservation.tenant_id
210     resources = [delta.resource for delta in reservation.resource_deltas]
211     num_deleted = delete_query.delete()
212     if set_dirty:
213         # quota_usage for all resource involved in this reservation must
214         # be marked as dirty
215         set_resources_quota_usage_dirty(context, resources, tenant_id)
216     return num_deleted
217
218
219 def get_reservations_for_resources(context, tenant_id, resources,
220                                    expired=False):
221     """Retrieve total amount of reservations for specified resources.
222
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
229     """
230     if not resources:
231         # Do not waste time
232         return
233     now = utcnow()
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)
239     if expired:
240         exp_expr = (quota_models.Reservation.expiration < now)
241     else:
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),
246         exp_expr)).group_by(
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)
251
252
253 def remove_expired_reservations(context, tenant_id=None):
254     now = utcnow()
255     resv_query = context.session.query(quota_models.Reservation)
256     if tenant_id:
257         tenant_expr = (quota_models.Reservation.tenant_id == tenant_id)
258     else:
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()