Set lock_path correctly.
[openstack-build/neutron-build.git] / neutron / quota / __init__.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 """Quotas for instances, volumes, and floating ips."""
16
17 import sys
18
19 from oslo_config import cfg
20 from oslo_log import log as logging
21 from oslo_log import versionutils
22 from oslo_utils import importutils
23 import six
24 import webob
25
26 from neutron._i18n import _, _LI, _LW
27 from neutron.common import exceptions
28 from neutron.db.quota import api as quota_api
29 from neutron.quota import resource_registry
30
31
32 LOG = logging.getLogger(__name__)
33 QUOTA_DB_MODULE = 'neutron.db.quota.driver'
34 QUOTA_DB_DRIVER = '%s.DbQuotaDriver' % QUOTA_DB_MODULE
35 QUOTA_CONF_DRIVER = 'neutron.quota.ConfDriver'
36 default_quota_items = ['network', 'subnet', 'port']
37
38
39 quota_opts = [
40     cfg.ListOpt('quota_items',
41                 default=default_quota_items,
42                 deprecated_for_removal=True,
43                 help=_('Resource name(s) that are supported in quota '
44                        'features. This option is now deprecated for '
45                        'removal.')),
46     cfg.IntOpt('default_quota',
47                default=-1,
48                help=_('Default number of resource allowed per tenant. '
49                       'A negative value means unlimited.')),
50     cfg.IntOpt('quota_network',
51                default=10,
52                help=_('Number of networks allowed per tenant. '
53                       'A negative value means unlimited.')),
54     cfg.IntOpt('quota_subnet',
55                default=10,
56                help=_('Number of subnets allowed per tenant, '
57                       'A negative value means unlimited.')),
58     cfg.IntOpt('quota_port',
59                default=50,
60                help=_('Number of ports allowed per tenant. '
61                       'A negative value means unlimited.')),
62     cfg.StrOpt('quota_driver',
63                default=QUOTA_DB_DRIVER,
64                help=_('Default driver to use for quota checks')),
65     cfg.BoolOpt('track_quota_usage',
66                 default=True,
67                 help=_('Keep in track in the database of current resource'
68                        'quota usage. Plugins which do not leverage the '
69                        'neutron database should set this flag to False')),
70 ]
71 # Register the configuration options
72 cfg.CONF.register_opts(quota_opts, 'QUOTAS')
73
74
75 class ConfDriver(object):
76     """Configuration driver.
77
78     Driver to perform necessary checks to enforce quotas and obtain
79     quota information. The default driver utilizes the default values
80     in neutron.conf.
81     """
82
83     def _get_quotas(self, context, resources):
84         """Get quotas.
85
86         A helper method which retrieves the quotas for the specific
87         resources identified by keys, and which apply to the current
88         context.
89
90         :param context: The request context, for access checks.
91         :param resources: A dictionary of the registered resources.
92         """
93
94         quotas = {}
95         for resource in resources.values():
96             quotas[resource.name] = resource.default
97         return quotas
98
99     def limit_check(self, context, tenant_id,
100                     resources, values):
101         """Check simple quota limits.
102
103         For limits--those quotas for which there is no usage
104         synchronization function--this method checks that a set of
105         proposed values are permitted by the limit restriction.
106
107         If any of the proposed values is over the defined quota, an
108         OverQuota exception will be raised with the sorted list of the
109         resources which are too high.  Otherwise, the method returns
110         nothing.
111
112         :param context: The request context, for access checks.
113         :param tenant_id: The tenant_id to check quota.
114         :param resources: A dictionary of the registered resources.
115         :param values: A dictionary of the values to check against the
116                        quota.
117         """
118         # Ensure no value is less than zero
119         unders = [key for key, val in values.items() if val < 0]
120         if unders:
121             raise exceptions.InvalidQuotaValue(unders=sorted(unders))
122
123         # Get the applicable quotas
124         quotas = self._get_quotas(context, resources)
125
126         # Check the quotas and construct a list of the resources that
127         # would be put over limit by the desired values
128         overs = [key for key, val in values.items()
129                  if quotas[key] >= 0 and quotas[key] < val]
130         if overs:
131             raise exceptions.OverQuota(overs=sorted(overs), quotas=quotas,
132                                        usages={})
133
134     @staticmethod
135     def get_tenant_quotas(context, resources, tenant_id):
136         quotas = {}
137         sub_resources = dict((k, v) for k, v in resources.items())
138         for resource in sub_resources.values():
139             quotas[resource.name] = resource.default
140         return quotas
141
142     @staticmethod
143     def get_all_quotas(context, resources):
144         return []
145
146     @staticmethod
147     def delete_tenant_quota(context, tenant_id):
148         msg = _('Access to this resource was denied.')
149         raise webob.exc.HTTPForbidden(msg)
150
151     @staticmethod
152     def update_quota_limit(context, tenant_id, resource, limit):
153         msg = _('Access to this resource was denied.')
154         raise webob.exc.HTTPForbidden(msg)
155
156     def make_reservation(self, context, tenant_id, resources, deltas, plugin):
157         """This driver does not support reservations.
158
159         This routine is provided for backward compatibility purposes with
160         the API controllers which have now been adapted to make reservations
161         rather than counting resources and checking limits - as this
162         routine ultimately does.
163         """
164         for resource in deltas.keys():
165             count = QUOTAS.count(context, resource, plugin, tenant_id)
166             total_use = deltas.get(resource, 0) + count
167             deltas[resource] = total_use
168
169         self.limit_check(
170             context,
171             tenant_id,
172             resource_registry.get_all_resources(),
173             deltas)
174         # return a fake reservation - the REST controller expects it
175         return quota_api.ReservationInfo('fake', None, None, None)
176
177     def commit_reservation(self, context, reservation_id):
178         """This is a noop as this driver does not support reservations."""
179
180     def cancel_reservation(self, context, reservation_id):
181         """This is a noop as this driver does not support reservations."""
182
183
184 class QuotaEngine(object):
185     """Represent the set of recognized quotas."""
186
187     _instance = None
188
189     @classmethod
190     def get_instance(cls):
191         if not cls._instance:
192             cls._instance = cls()
193         return cls._instance
194
195     def __init__(self, quota_driver_class=None):
196         """Initialize a Quota object."""
197         self._driver = None
198         self._driver_class = quota_driver_class
199
200     def get_driver(self):
201         if self._driver is None:
202             _driver_class = (self._driver_class or
203                              cfg.CONF.QUOTAS.quota_driver)
204             if (_driver_class == QUOTA_DB_DRIVER and
205                     QUOTA_DB_MODULE not in sys.modules):
206                 # If quotas table is not loaded, force config quota driver.
207                 _driver_class = QUOTA_CONF_DRIVER
208                 LOG.info(_LI("ConfDriver is used as quota_driver because the "
209                              "loaded plugin does not support 'quotas' table."))
210             if isinstance(_driver_class, six.string_types):
211                 _driver_class = importutils.import_object(_driver_class)
212             if isinstance(_driver_class, ConfDriver):
213                 versionutils.report_deprecated_feature(
214                     LOG, _LW("The quota driver neutron.quota.ConfDriver is "
215                              "deprecated as of Liberty. "
216                              "neutron.db.quota.driver.DbQuotaDriver should "
217                              "be used in its place"))
218             self._driver = _driver_class
219             LOG.info(_LI('Loaded quota_driver: %s.'), _driver_class)
220         return self._driver
221
222     def count(self, context, resource_name, *args, **kwargs):
223         """Count a resource.
224
225         For countable resources, invokes the count() function and
226         returns its result.  Arguments following the context and
227         resource are passed directly to the count function declared by
228         the resource.
229
230         :param context: The request context, for access checks.
231         :param resource_name: The name of the resource, as a string.
232         """
233
234         # Get the resource
235         res = resource_registry.get_resource(resource_name)
236         if not res or not hasattr(res, 'count'):
237             raise exceptions.QuotaResourceUnknown(unknown=[resource_name])
238
239         return res.count(context, *args, **kwargs)
240
241     def make_reservation(self, context, tenant_id, deltas, plugin):
242         # Verify that resources are managed by the quota engine
243         # Ensure no value is less than zero
244         unders = [key for key, val in deltas.items() if val < 0]
245         if unders:
246             raise exceptions.InvalidQuotaValue(unders=sorted(unders))
247
248         requested_resources = set(deltas.keys())
249         all_resources = resource_registry.get_all_resources()
250         managed_resources = set([res for res in all_resources.keys()
251                                  if res in requested_resources])
252         # Make sure we accounted for all of them...
253         unknown_resources = requested_resources - managed_resources
254
255         if unknown_resources:
256             raise exceptions.QuotaResourceUnknown(
257                 unknown=sorted(unknown_resources))
258         # FIXME(salv-orlando): There should be no reason for sending all the
259         # resource in the registry to the quota driver, but as other driver
260         # APIs request them, this will be sorted out with a different patch.
261         return self.get_driver().make_reservation(
262             context,
263             tenant_id,
264             all_resources,
265             deltas,
266             plugin)
267
268     def commit_reservation(self, context, reservation_id):
269         self.get_driver().commit_reservation(context, reservation_id)
270
271     def cancel_reservation(self, context, reservation_id):
272         self.get_driver().cancel_reservation(context, reservation_id)
273
274     def limit_check(self, context, tenant_id, **values):
275         """Check simple quota limits.
276
277         For limits--those quotas for which there is no usage
278         synchronization function--this method checks that a set of
279         proposed values are permitted by the limit restriction.  The
280         values to check are given as keyword arguments, where the key
281         identifies the specific quota limit to check, and the value is
282         the proposed value.
283
284         This method will raise a QuotaResourceUnknown exception if a
285         given resource is unknown or if it is not a countable resource.
286
287         If any of the proposed values exceeds the respective quota defined
288         for the tenant, an OverQuota exception will be raised.
289         The exception will include a sorted list with the resources
290         which exceed the quota limit. Otherwise, the method returns nothing.
291
292         :param context: Request context
293         :param tenant_id: Tenant for which the quota limit is being checked
294         :param values: Dict specifying requested deltas for each resource
295         """
296         # TODO(salv-orlando): Deprecate calls to this API
297         # Verify that resources are managed by the quota engine
298         requested_resources = set(values.keys())
299         managed_resources = set([res for res in
300                                  resource_registry.get_all_resources()
301                                  if res in requested_resources])
302
303         # Make sure we accounted for all of them...
304         unknown_resources = requested_resources - managed_resources
305         if unknown_resources:
306             raise exceptions.QuotaResourceUnknown(
307                 unknown=sorted(unknown_resources))
308
309         return self.get_driver().limit_check(
310             context, tenant_id, resource_registry.get_all_resources(), values)
311
312
313 QUOTAS = QuotaEngine.get_instance()
314
315
316 def register_resources_from_config():
317     # This operation is now deprecated. All the neutron core and extended
318     # resource for which  quota limits are enforced explicitly register
319     # themselves with the quota engine.
320     for resource_item in (set(cfg.CONF.QUOTAS.quota_items) -
321                           set(default_quota_items)):
322         resource_registry.register_resource_by_name(resource_item)
323
324
325 register_resources_from_config()