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
15 """Quotas for instances, volumes, and floating ips."""
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
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
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']
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 '
46 cfg.IntOpt('default_quota',
48 help=_('Default number of resource allowed per tenant. '
49 'A negative value means unlimited.')),
50 cfg.IntOpt('quota_network',
52 help=_('Number of networks allowed per tenant. '
53 'A negative value means unlimited.')),
54 cfg.IntOpt('quota_subnet',
56 help=_('Number of subnets allowed per tenant, '
57 'A negative value means unlimited.')),
58 cfg.IntOpt('quota_port',
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',
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')),
71 # Register the configuration options
72 cfg.CONF.register_opts(quota_opts, 'QUOTAS')
75 class ConfDriver(object):
76 """Configuration driver.
78 Driver to perform necessary checks to enforce quotas and obtain
79 quota information. The default driver utilizes the default values
83 def _get_quotas(self, context, resources):
86 A helper method which retrieves the quotas for the specific
87 resources identified by keys, and which apply to the current
90 :param context: The request context, for access checks.
91 :param resources: A dictionary of the registered resources.
95 for resource in resources.values():
96 quotas[resource.name] = resource.default
99 def limit_check(self, context, tenant_id,
101 """Check simple quota limits.
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.
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
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
118 # Ensure no value is less than zero
119 unders = [key for key, val in values.items() if val < 0]
121 raise exceptions.InvalidQuotaValue(unders=sorted(unders))
123 # Get the applicable quotas
124 quotas = self._get_quotas(context, resources)
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]
131 raise exceptions.OverQuota(overs=sorted(overs), quotas=quotas,
135 def get_tenant_quotas(context, resources, tenant_id):
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
143 def get_all_quotas(context, resources):
147 def delete_tenant_quota(context, tenant_id):
148 msg = _('Access to this resource was denied.')
149 raise webob.exc.HTTPForbidden(msg)
152 def update_quota_limit(context, tenant_id, resource, limit):
153 msg = _('Access to this resource was denied.')
154 raise webob.exc.HTTPForbidden(msg)
156 def make_reservation(self, context, tenant_id, resources, deltas, plugin):
157 """This driver does not support reservations.
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.
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
172 resource_registry.get_all_resources(),
174 # return a fake reservation - the REST controller expects it
175 return quota_api.ReservationInfo('fake', None, None, None)
177 def commit_reservation(self, context, reservation_id):
178 """This is a noop as this driver does not support reservations."""
180 def cancel_reservation(self, context, reservation_id):
181 """This is a noop as this driver does not support reservations."""
184 class QuotaEngine(object):
185 """Represent the set of recognized quotas."""
190 def get_instance(cls):
191 if not cls._instance:
192 cls._instance = cls()
195 def __init__(self, quota_driver_class=None):
196 """Initialize a Quota object."""
198 self._driver_class = quota_driver_class
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)
222 def count(self, context, resource_name, *args, **kwargs):
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
230 :param context: The request context, for access checks.
231 :param resource_name: The name of the resource, as a string.
235 res = resource_registry.get_resource(resource_name)
236 if not res or not hasattr(res, 'count'):
237 raise exceptions.QuotaResourceUnknown(unknown=[resource_name])
239 return res.count(context, *args, **kwargs)
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]
246 raise exceptions.InvalidQuotaValue(unders=sorted(unders))
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
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(
268 def commit_reservation(self, context, reservation_id):
269 self.get_driver().commit_reservation(context, reservation_id)
271 def cancel_reservation(self, context, reservation_id):
272 self.get_driver().cancel_reservation(context, reservation_id)
274 def limit_check(self, context, tenant_id, **values):
275 """Check simple quota limits.
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
284 This method will raise a QuotaResourceUnknown exception if a
285 given resource is unknown or if it is not a countable resource.
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.
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
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])
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))
309 return self.get_driver().limit_check(
310 context, tenant_id, resource_registry.get_all_resources(), values)
313 QUOTAS = QuotaEngine.get_instance()
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)
325 register_resources_from_config()