Coverage for nova/quota.py: 92%
424 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
17"""Quotas for resources per project."""
19import copy
21from oslo_log import log as logging
22from oslo_utils import importutils
23from sqlalchemy import sql
25import nova.conf
26from nova import context as nova_context
27from nova.db.api import api as api_db_api
28from nova.db.api import models as api_models
29from nova.db.main import api as main_db_api
30from nova import exception
31from nova.limit import local as local_limit
32from nova.limit import placement as placement_limit
33from nova import objects
34from nova.scheduler.client import report
35from nova import utils
37LOG = logging.getLogger(__name__)
38CONF = nova.conf.CONF
39# Lazy-loaded on first access.
40# Avoid constructing the KSA adapter and provider tree on every access.
41PLACEMENT_CLIENT = None
42# If user_id and queued_for_delete are populated for a project, cache the
43# result to avoid doing unnecessary EXISTS database queries.
44UID_QFD_POPULATED_CACHE_BY_PROJECT = set()
45# For the server group members check, we do not scope to a project, so if all
46# user_id and queued_for_delete are populated for all projects, cache the
47# result to avoid doing unnecessary EXISTS database queries.
48UID_QFD_POPULATED_CACHE_ALL = False
51class DbQuotaDriver(object):
52 """Driver to perform necessary checks to enforce quotas and obtain
53 quota information. The default driver utilizes the local
54 database.
55 """
56 UNLIMITED_VALUE = -1
58 def get_reserved(self):
59 # Since we stopped reserving the DB, we just return 0
60 return 0
62 def get_defaults(self, context, resources):
63 """Given a list of resources, retrieve the default quotas.
64 Use the class quotas named `_DEFAULT_QUOTA_NAME` as default quotas,
65 if it exists.
67 :param context: The request context, for access checks.
68 :param resources: A dictionary of the registered resources.
69 """
71 quotas = {}
72 default_quotas = objects.Quotas.get_default_class(context)
73 for resource in resources.values():
74 # resource.default returns the config options. So if there's not
75 # an entry for the resource in the default class, it uses the
76 # config option.
77 quotas[resource.name] = default_quotas.get(resource.name,
78 resource.default)
80 return quotas
82 def get_class_quotas(self, context, resources, quota_class):
83 """Given a list of resources, retrieve the quotas for the given
84 quota class.
86 :param context: The request context, for access checks.
87 :param resources: A dictionary of the registered resources.
88 :param quota_class: The name of the quota class to return
89 quotas for.
90 """
92 quotas = {}
93 class_quotas = objects.Quotas.get_all_class_by_name(context,
94 quota_class)
95 for resource in resources.values():
96 quotas[resource.name] = class_quotas.get(resource.name,
97 resource.default)
99 return quotas
101 def _process_quotas(self, context, resources, project_id, quotas,
102 quota_class=None, usages=None,
103 remains=False):
104 modified_quotas = {}
105 # Get the quotas for the appropriate class. If the project ID
106 # matches the one in the context, we use the quota_class from
107 # the context, otherwise, we use the provided quota_class (if
108 # any)
109 if project_id == context.project_id:
110 quota_class = context.quota_class
111 if quota_class:
112 class_quotas = objects.Quotas.get_all_class_by_name(context,
113 quota_class)
114 else:
115 class_quotas = {}
117 default_quotas = self.get_defaults(context, resources)
119 for resource in resources.values():
120 limit = quotas.get(resource.name, class_quotas.get(
121 resource.name, default_quotas[resource.name]))
122 modified_quotas[resource.name] = dict(limit=limit)
124 # Include usages if desired. This is optional because one
125 # internal consumer of this interface wants to access the
126 # usages directly from inside a transaction.
127 if usages:
128 usage = usages.get(resource.name, {})
129 modified_quotas[resource.name].update(
130 in_use=usage.get('in_use', 0),
131 )
133 # Initialize remains quotas with the default limits.
134 if remains:
135 modified_quotas[resource.name].update(remains=limit)
137 if remains:
138 # Get all user quotas for a project and subtract their limits
139 # from the class limits to get the remains. For example, if the
140 # class/default is 20 and there are two users each with quota of 5,
141 # then there is quota of 10 left to give out.
142 all_quotas = objects.Quotas.get_all(context, project_id)
143 for quota in all_quotas:
144 if quota.resource in modified_quotas: 144 ↛ 143line 144 didn't jump to line 143 because the condition on line 144 was always true
145 modified_quotas[quota.resource]['remains'] -= \
146 quota.hard_limit
148 return modified_quotas
150 def _get_usages(self, context, resources, project_id, user_id=None):
151 """Get usages of specified resources.
153 This function is called to get resource usages for validating quota
154 limit creates or updates in the os-quota-sets API and for displaying
155 resource usages in the os-used-limits API. This function is not used
156 for checking resource usage against quota limits.
158 :param context: The request context for access checks
159 :param resources: The dict of Resources for which to get usages
160 :param project_id: The project_id for scoping the usage count
161 :param user_id: Optional user_id for scoping the usage count
162 :returns: A dict containing resources and their usage information,
163 for example:
164 {'project_id': 'project-uuid',
165 'user_id': 'user-uuid',
166 'instances': {'in_use': 5}}
167 """
168 usages = {}
169 for resource in resources.values():
170 # NOTE(melwitt): We should skip resources that are not countable,
171 # such as AbsoluteResources.
172 if not isinstance(resource, CountableResource):
173 continue
174 if resource.name in usages:
175 # This is needed because for any of the resources:
176 # ('instances', 'cores', 'ram'), they are counted at the same
177 # time for efficiency (query the instances table once instead
178 # of multiple times). So, a count of any one of them contains
179 # counts for the others and we can avoid re-counting things.
180 continue
181 if resource.name in ('key_pairs', 'server_group_members'):
182 # These per user resources are special cases whose usages
183 # are not considered when validating limit create/update or
184 # displaying used limits. They are always zero.
185 usages[resource.name] = {'in_use': 0}
186 else:
187 if ( 187 ↛ 191line 187 didn't jump to line 191 because the condition on line 187 was never true
188 resource.name in
189 main_db_api.quota_get_per_project_resources()
190 ):
191 count = resource.count_as_dict(context, project_id)
192 key = 'project'
193 else:
194 # NOTE(melwitt): This assumes a specific signature for
195 # count_as_dict(). Usages used to be records in the
196 # database but now we are counting resources. The
197 # count_as_dict() function signature needs to match this
198 # call, else it should get a conditional in this function.
199 count = resource.count_as_dict(context, project_id,
200 user_id=user_id)
201 key = 'user' if user_id else 'project'
202 # Example count_as_dict() return value:
203 # {'project': {'instances': 5},
204 # 'user': {'instances': 2}}
205 counted_resources = count[key].keys()
206 for res in counted_resources:
207 count_value = count[key][res]
208 usages[res] = {'in_use': count_value}
209 return usages
211 def get_user_quotas(self, context, resources, project_id, user_id,
212 quota_class=None,
213 usages=True, project_quotas=None,
214 user_quotas=None):
215 """Given a list of resources, retrieve the quotas for the given
216 user and project.
218 :param context: The request context, for access checks.
219 :param resources: A dictionary of the registered resources.
220 :param project_id: The ID of the project to return quotas for.
221 :param user_id: The ID of the user to return quotas for.
222 :param quota_class: If project_id != context.project_id, the
223 quota class cannot be determined. This
224 parameter allows it to be specified. It
225 will be ignored if project_id ==
226 context.project_id.
227 :param usages: If True, the current counts will also be returned.
228 :param project_quotas: Quotas dictionary for the specified project.
229 :param user_quotas: Quotas dictionary for the specified project
230 and user.
231 """
232 if user_quotas:
233 user_quotas = user_quotas.copy()
234 else:
235 user_quotas = objects.Quotas.get_all_by_project_and_user(
236 context, project_id, user_id)
237 # Use the project quota for default user quota.
238 proj_quotas = project_quotas or objects.Quotas.get_all_by_project(
239 context, project_id)
240 for key, value in proj_quotas.items():
241 if key not in user_quotas.keys():
242 user_quotas[key] = value
243 user_usages = {}
244 if usages:
245 user_usages = self._get_usages(context, resources, project_id,
246 user_id=user_id)
247 return self._process_quotas(context, resources, project_id,
248 user_quotas, quota_class,
249 usages=user_usages)
251 def get_project_quotas(self, context, resources, project_id,
252 quota_class=None,
253 usages=True, remains=False, project_quotas=None):
254 """Given a list of resources, retrieve the quotas for the given
255 project.
257 :param context: The request context, for access checks.
258 :param resources: A dictionary of the registered resources.
259 :param project_id: The ID of the project to return quotas for.
260 :param quota_class: If project_id != context.project_id, the
261 quota class cannot be determined. This
262 parameter allows it to be specified. It
263 will be ignored if project_id ==
264 context.project_id.
265 :param usages: If True, the current counts will also be returned.
266 :param remains: If True, the current remains of the project will
267 will be returned.
268 :param project_quotas: Quotas dictionary for the specified project.
269 """
270 project_quotas = project_quotas or objects.Quotas.get_all_by_project(
271 context, project_id)
272 project_usages = {}
273 if usages:
274 project_usages = self._get_usages(context, resources, project_id)
275 return self._process_quotas(context, resources, project_id,
276 project_quotas, quota_class,
277 usages=project_usages,
278 remains=remains)
280 def _is_unlimited_value(self, v):
281 """A helper method to check for unlimited value.
282 """
284 return v <= self.UNLIMITED_VALUE
286 def _sum_quota_values(self, v1, v2):
287 """A helper method that handles unlimited values when performing
288 sum operation.
289 """
291 if self._is_unlimited_value(v1) or self._is_unlimited_value(v2):
292 return self.UNLIMITED_VALUE
293 return v1 + v2
295 def _sub_quota_values(self, v1, v2):
296 """A helper method that handles unlimited values when performing
297 subtraction operation.
298 """
300 if self._is_unlimited_value(v1) or self._is_unlimited_value(v2):
301 return self.UNLIMITED_VALUE
302 return v1 - v2
304 def get_settable_quotas(self, context, resources, project_id,
305 user_id=None):
306 """Given a list of resources, retrieve the range of settable quotas for
307 the given user or project.
309 :param context: The request context, for access checks.
310 :param resources: A dictionary of the registered resources.
311 :param project_id: The ID of the project to return quotas for.
312 :param user_id: The ID of the user to return quotas for.
313 """
315 settable_quotas = {}
316 db_proj_quotas = objects.Quotas.get_all_by_project(context, project_id)
317 project_quotas = self.get_project_quotas(context, resources,
318 project_id, remains=True,
319 project_quotas=db_proj_quotas)
320 if user_id:
321 setted_quotas = objects.Quotas.get_all_by_project_and_user(
322 context, project_id, user_id)
323 user_quotas = self.get_user_quotas(context, resources,
324 project_id, user_id,
325 project_quotas=db_proj_quotas,
326 user_quotas=setted_quotas)
327 for key, value in user_quotas.items():
328 # Maximum is the remaining quota for a project (class/default
329 # minus the sum of all user quotas in the project), plus the
330 # given user's quota. So if the class/default is 20 and there
331 # are two users each with quota of 5, then there is quota of
332 # 10 remaining. The given user currently has quota of 5, so
333 # the maximum you could update their quota to would be 15.
334 # Class/default 20 - currently used in project 10 + current
335 # user 5 = 15.
336 maximum = \
337 self._sum_quota_values(project_quotas[key]['remains'],
338 setted_quotas.get(key, 0))
339 # This function is called for the quota_sets api and the
340 # corresponding nova-manage command. The idea is when someone
341 # attempts to update a quota, the value chosen must be at least
342 # as much as the current usage and less than or equal to the
343 # project limit less the sum of existing per user limits.
344 minimum = value['in_use']
345 settable_quotas[key] = {'minimum': minimum, 'maximum': maximum}
346 else:
347 for key, value in project_quotas.items():
348 minimum = \
349 max(int(self._sub_quota_values(value['limit'],
350 value['remains'])),
351 int(value['in_use']))
352 settable_quotas[key] = {'minimum': minimum, 'maximum': -1}
353 return settable_quotas
355 def _get_quotas(self, context, resources, keys, project_id=None,
356 user_id=None, project_quotas=None):
357 """A helper method which retrieves the quotas for the specific
358 resources identified by keys, and which apply to the current
359 context.
361 :param context: The request context, for access checks.
362 :param resources: A dictionary of the registered resources.
363 :param keys: A list of the desired quotas to retrieve.
364 :param project_id: Specify the project_id if current context
365 is admin and admin wants to impact on
366 common user's tenant.
367 :param user_id: Specify the user_id if current context
368 is admin and admin wants to impact on
369 common user.
370 :param project_quotas: Quotas dictionary for the specified project.
371 """
373 # Filter resources
374 desired = set(keys)
375 sub_resources = {k: v for k, v in resources.items() if k in desired}
377 # Make sure we accounted for all of them...
378 if len(keys) != len(sub_resources):
379 unknown = desired - set(sub_resources.keys())
380 raise exception.QuotaResourceUnknown(unknown=sorted(unknown))
382 if user_id:
383 LOG.debug('Getting quotas for user %(user_id)s and project '
384 '%(project_id)s. Resources: %(keys)s',
385 {'user_id': user_id, 'project_id': project_id,
386 'keys': keys})
387 # Grab and return the quotas (without usages)
388 quotas = self.get_user_quotas(context, sub_resources,
389 project_id, user_id,
390 context.quota_class, usages=False,
391 project_quotas=project_quotas)
392 else:
393 LOG.debug('Getting quotas for project %(project_id)s. Resources: '
394 '%(keys)s', {'project_id': project_id, 'keys': keys})
395 # Grab and return the quotas (without usages)
396 quotas = self.get_project_quotas(context, sub_resources,
397 project_id,
398 context.quota_class,
399 usages=False,
400 project_quotas=project_quotas)
402 return {k: v['limit'] for k, v in quotas.items()}
404 def limit_check(self, context, resources, values, project_id=None,
405 user_id=None):
406 """Check simple quota limits.
408 For limits--those quotas for which there is no usage
409 synchronization function--this method checks that a set of
410 proposed values are permitted by the limit restriction.
412 This method will raise a QuotaResourceUnknown exception if a
413 given resource is unknown or if it is not a simple limit
414 resource.
416 If any of the proposed values is over the defined quota, an
417 OverQuota exception will be raised with the sorted list of the
418 resources which are too high. Otherwise, the method returns
419 nothing.
421 :param context: The request context, for access checks.
422 :param resources: A dictionary of the registered resources.
423 :param values: A dictionary of the values to check against the
424 quota.
425 :param project_id: Specify the project_id if current context
426 is admin and admin wants to impact on
427 common user's tenant.
428 :param user_id: Specify the user_id if current context
429 is admin and admin wants to impact on
430 common user.
431 """
432 _valid_method_call_check_resources(values, 'check', resources)
434 # Ensure no value is less than zero
435 unders = [key for key, val in values.items() if val < 0]
436 if unders:
437 raise exception.InvalidQuotaValue(unders=sorted(unders))
439 # If project_id is None, then we use the project_id in context
440 if project_id is None: 440 ↛ 443line 440 didn't jump to line 443 because the condition on line 440 was always true
441 project_id = context.project_id
442 # If user id is None, then we use the user_id in context
443 if user_id is None: 443 ↛ 447line 443 didn't jump to line 447 because the condition on line 443 was always true
444 user_id = context.user_id
446 # Get the applicable quotas
447 project_quotas = objects.Quotas.get_all_by_project(context, project_id)
448 quotas = self._get_quotas(context, resources, values.keys(),
449 project_id=project_id,
450 project_quotas=project_quotas)
451 user_quotas = self._get_quotas(context, resources, values.keys(),
452 project_id=project_id,
453 user_id=user_id,
454 project_quotas=project_quotas)
456 # Check the quotas and construct a list of the resources that
457 # would be put over limit by the desired values
458 overs = [key for key, val in values.items()
459 if quotas[key] >= 0 and quotas[key] < val or
460 (user_quotas[key] >= 0 and user_quotas[key] < val)]
461 if overs:
462 headroom = {}
463 for key in overs:
464 headroom[key] = min(
465 val for val in (quotas.get(key), project_quotas.get(key))
466 if val is not None
467 )
468 raise exception.OverQuota(overs=sorted(overs), quotas=quotas,
469 usages={}, headroom=headroom)
471 def limit_check_project_and_user(self, context, resources,
472 project_values=None, user_values=None,
473 project_id=None, user_id=None):
474 """Check values (usage + desired delta) against quota limits.
476 For limits--this method checks that a set of
477 proposed values are permitted by the limit restriction.
479 This method will raise a QuotaResourceUnknown exception if a
480 given resource is unknown or if it is not a simple limit
481 resource.
483 If any of the proposed values is over the defined quota, an
484 OverQuota exception will be raised with the sorted list of the
485 resources which are too high. Otherwise, the method returns
486 nothing.
488 :param context: The request context, for access checks
489 :param resources: A dictionary of the registered resources
490 :param project_values: Optional dict containing the resource values to
491 check against project quota,
492 e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
493 :param user_values: Optional dict containing the resource values to
494 check against user quota,
495 e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
496 :param project_id: Optional project_id for scoping the limit check to a
497 different project than in the context
498 :param user_id: Optional user_id for scoping the limit check to a
499 different user than in the context
500 """
501 if project_values is None:
502 project_values = {}
503 if user_values is None:
504 user_values = {}
506 _valid_method_call_check_resources(project_values, 'check', resources)
507 _valid_method_call_check_resources(user_values, 'check', resources)
509 if not any([project_values, user_values]):
510 raise exception.Invalid(
511 'Must specify at least one of project_values or user_values '
512 'for the limit check.')
514 # Ensure no value is less than zero
515 for vals in (project_values, user_values):
516 unders = [key for key, val in vals.items() if val < 0]
517 if unders:
518 raise exception.InvalidQuotaValue(unders=sorted(unders))
520 # Get a set of all keys for calling _get_quotas() so we get all of the
521 # resource limits we need.
522 all_keys = set(project_values).union(user_values)
524 # Keys that are in both project_values and user_values need to be
525 # checked against project quota and user quota, respectively.
526 # Keys that are not in both only need to be checked against project
527 # quota or user quota, if it is defined. Separate the keys that don't
528 # need to be checked against both quotas, merge them into one dict,
529 # and remove them from project_values and user_values.
530 keys_to_merge = set(project_values).symmetric_difference(user_values)
531 merged_values = {}
532 for key in keys_to_merge:
533 # The key will be either in project_values or user_values based on
534 # the earlier symmetric_difference. Default to 0 in case the found
535 # value is 0 and won't take precedence over a None default.
536 merged_values[key] = (project_values.get(key, 0) or
537 user_values.get(key, 0))
538 project_values.pop(key, None)
539 user_values.pop(key, None)
541 # If project_id is None, then we use the project_id in context
542 if project_id is None:
543 project_id = context.project_id
544 # If user id is None, then we use the user_id in context
545 if user_id is None:
546 user_id = context.user_id
548 # Get the applicable quotas. They will be merged together (taking the
549 # min limit) if project_values and user_values were not specified
550 # together.
552 # per project quota limits (quotas that have no concept of
553 # user-scoping: <none>)
554 project_quotas = objects.Quotas.get_all_by_project(context, project_id)
555 # per user quotas, project quota limits (for quotas that have
556 # user-scoping, limits for the project)
557 quotas = self._get_quotas(context, resources, all_keys,
558 project_id=project_id,
559 project_quotas=project_quotas)
560 # per user quotas, user quota limits (for quotas that have
561 # user-scoping, the limits for the user)
562 user_quotas = self._get_quotas(context, resources, all_keys,
563 project_id=project_id,
564 user_id=user_id,
565 project_quotas=project_quotas)
567 if merged_values:
568 # This is for resources that are not counted across a project and
569 # must pass both the quota for the project and the quota for the
570 # user.
571 # Combine per user project quotas and user_quotas for use in the
572 # checks, taking the minimum limit between the two.
573 merged_quotas = copy.deepcopy(quotas)
574 for k, v in user_quotas.items():
575 if k in merged_quotas: 575 ↛ 578line 575 didn't jump to line 578 because the condition on line 575 was always true
576 merged_quotas[k] = min(merged_quotas[k], v)
577 else:
578 merged_quotas[k] = v
580 # Check the quotas and construct a list of the resources that
581 # would be put over limit by the desired values
582 overs = [key for key, val in merged_values.items()
583 if merged_quotas[key] >= 0 and merged_quotas[key] < val]
584 if overs:
585 headroom = {}
586 for key in overs:
587 headroom[key] = merged_quotas[key]
588 raise exception.OverQuota(overs=sorted(overs),
589 quotas=merged_quotas, usages={},
590 headroom=headroom)
592 # This is for resources that are counted across a project and
593 # across a user (instances, cores, ram, server_groups). The
594 # project_values must pass the quota for the project and the
595 # user_values must pass the quota for the user.
596 over_user_quota = False
597 overs = []
598 for key in user_values.keys():
599 # project_values and user_values should contain the same keys or
600 # be empty after the keys in the symmetric_difference were removed
601 # from both dicts.
602 if quotas[key] >= 0 and quotas[key] < project_values[key]:
603 overs.append(key)
604 elif (user_quotas[key] >= 0 and
605 user_quotas[key] < user_values[key]):
606 overs.append(key)
607 over_user_quota = True
608 if overs:
609 quotas_exceeded = user_quotas if over_user_quota else quotas
610 headroom = {}
611 for key in overs:
612 headroom[key] = quotas_exceeded[key]
613 raise exception.OverQuota(overs=sorted(overs),
614 quotas=quotas_exceeded, usages={},
615 headroom=headroom)
618class NoopQuotaDriver(object):
619 """Driver that turns quotas calls into no-ops and pretends that quotas
620 for all resources are unlimited. This can be used if you do not
621 wish to have any quota checking.
622 """
624 def get_reserved(self):
625 # Noop has always returned -1 for reserved
626 return -1
628 def get_defaults(self, context, resources):
629 """Given a list of resources, retrieve the default quotas.
631 :param context: The request context, for access checks.
632 :param resources: A dictionary of the registered resources.
633 """
634 quotas = {}
635 for resource in resources.values():
636 quotas[resource.name] = -1
637 return quotas
639 def get_class_quotas(self, context, resources, quota_class):
640 """Given a list of resources, retrieve the quotas for the given
641 quota class.
643 :param context: The request context, for access checks.
644 :param resources: A dictionary of the registered resources.
645 :param quota_class: The name of the quota class to return
646 quotas for.
647 """
648 quotas = {}
649 for resource in resources.values():
650 quotas[resource.name] = -1
651 return quotas
653 def _get_noop_quotas(self, resources, usages=None, remains=False):
654 quotas = {}
655 for resource in resources.values():
656 quotas[resource.name] = {}
657 quotas[resource.name]['limit'] = -1
658 if usages:
659 quotas[resource.name]['in_use'] = -1
660 if remains: 660 ↛ 661line 660 didn't jump to line 661 because the condition on line 660 was never true
661 quotas[resource.name]['remains'] = -1
662 return quotas
664 def get_user_quotas(self, context, resources, project_id, user_id,
665 quota_class=None,
666 usages=True):
667 """Given a list of resources, retrieve the quotas for the given
668 user and project.
670 :param context: The request context, for access checks.
671 :param resources: A dictionary of the registered resources.
672 :param project_id: The ID of the project to return quotas for.
673 :param user_id: The ID of the user to return quotas for.
674 :param quota_class: If project_id != context.project_id, the
675 quota class cannot be determined. This
676 parameter allows it to be specified. It
677 will be ignored if project_id ==
678 context.project_id.
679 :param usages: If True, the current counts will also be returned.
680 """
681 return self._get_noop_quotas(resources, usages=usages)
683 def get_project_quotas(self, context, resources, project_id,
684 quota_class=None,
685 usages=True, remains=False):
686 """Given a list of resources, retrieve the quotas for the given
687 project.
689 :param context: The request context, for access checks.
690 :param resources: A dictionary of the registered resources.
691 :param project_id: The ID of the project to return quotas for.
692 :param quota_class: If project_id != context.project_id, the
693 quota class cannot be determined. This
694 parameter allows it to be specified. It
695 will be ignored if project_id ==
696 context.project_id.
697 :param usages: If True, the current counts will also be returned.
698 :param remains: If True, the current remains of the project will
699 will be returned.
700 """
701 return self._get_noop_quotas(resources, usages=usages, remains=remains)
703 def get_settable_quotas(self, context, resources, project_id,
704 user_id=None):
705 """Given a list of resources, retrieve the range of settable quotas for
706 the given user or project.
708 :param context: The request context, for access checks.
709 :param resources: A dictionary of the registered resources.
710 :param project_id: The ID of the project to return quotas for.
711 :param user_id: The ID of the user to return quotas for.
712 """
713 quotas = {}
714 for resource in resources.values():
715 quotas[resource.name] = {'minimum': 0, 'maximum': -1}
716 return quotas
718 def limit_check(self, context, resources, values, project_id=None,
719 user_id=None):
720 """Check simple quota limits.
722 For limits--those quotas for which there is no usage
723 synchronization function--this method checks that a set of
724 proposed values are permitted by the limit restriction.
726 This method will raise a QuotaResourceUnknown exception if a
727 given resource is unknown or if it is not a simple limit
728 resource.
730 If any of the proposed values is over the defined quota, an
731 OverQuota exception will be raised with the sorted list of the
732 resources which are too high. Otherwise, the method returns
733 nothing.
735 :param context: The request context, for access checks.
736 :param resources: A dictionary of the registered resources.
737 :param values: A dictionary of the values to check against the
738 quota.
739 :param project_id: Specify the project_id if current context
740 is admin and admin wants to impact on
741 common user's tenant.
742 :param user_id: Specify the user_id if current context
743 is admin and admin wants to impact on
744 common user.
745 """
746 pass
748 def limit_check_project_and_user(self, context, resources,
749 project_values=None, user_values=None,
750 project_id=None, user_id=None):
751 """Check values against quota limits.
753 For limits--this method checks that a set of
754 proposed values are permitted by the limit restriction.
756 This method will raise a QuotaResourceUnknown exception if a
757 given resource is unknown or if it is not a simple limit
758 resource.
760 If any of the proposed values is over the defined quota, an
761 OverQuota exception will be raised with the sorted list of the
762 resources which are too high. Otherwise, the method returns
763 nothing.
765 :param context: The request context, for access checks
766 :param resources: A dictionary of the registered resources
767 :param project_values: Optional dict containing the resource values to
768 check against project quota,
769 e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
770 :param user_values: Optional dict containing the resource values to
771 check against user quota,
772 e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
773 :param project_id: Optional project_id for scoping the limit check to a
774 different project than in the context
775 :param user_id: Optional user_id for scoping the limit check to a
776 different user than in the context
777 """
778 pass
781class UnifiedLimitsDriver(NoopQuotaDriver):
782 """Ease migration to new unified limits code.
784 Help ease migration to unified limits by ensuring the old code
785 paths still work with unified limits. Eventually the expectation is
786 all this legacy quota code will go away, leaving the new simpler code
787 """
789 def __init__(self):
790 LOG.warning("The Unified Limits Quota Driver is experimental and "
791 "is under active development. Do not use this driver.")
793 def get_reserved(self):
794 # To make unified limits APIs the same as the DB driver, return 0
795 return 0
797 def get_class_quotas(self, context, resources, quota_class):
798 """Given a list of resources, retrieve the quotas for the given
799 quota class.
801 :param context: The request context, for access checks.
802 :param resources: A dictionary of the registered resources.
803 :param quota_class: Placeholder, we always assume default quota class.
804 """
805 # NOTE(johngarbutt): ignoring quota_class, as ignored in noop driver
806 return self.get_defaults(context, resources)
808 def get_defaults(self, context, resources):
809 local_limits = local_limit.get_legacy_default_limits()
810 # Note we get 0 if there is no registered limit,
811 # to mirror oslo_limit behaviour when there is no registered limit
812 placement_limits = placement_limit.get_legacy_default_limits()
813 quotas = {}
814 for resource in resources.values():
815 if resource.name in placement_limits:
816 quotas[resource.name] = placement_limits[resource.name]
817 else:
818 # return -1 for things like security_group_rules
819 # that are neither a keystone limit or a local limit
820 quotas[resource.name] = local_limits.get(resource.name, -1)
822 return quotas
824 def get_project_quotas(self, context, resources, project_id,
825 quota_class=None,
826 usages=True, remains=False):
827 if quota_class is not None: 827 ↛ 828line 827 didn't jump to line 828 because the condition on line 827 was never true
828 raise NotImplementedError("quota_class")
830 if remains: 830 ↛ 831line 830 didn't jump to line 831 because the condition on line 830 was never true
831 raise NotImplementedError("remains")
833 local_limits = local_limit.get_legacy_default_limits()
834 # keystone limits always returns core, ram and instances
835 # if nothing set in keystone, we get back 0, i.e. don't allow
836 placement_limits = placement_limit.get_legacy_project_limits(
837 project_id)
839 project_quotas = {}
840 for resource in resources.values():
841 if resource.name in placement_limits:
842 limit = placement_limits[resource.name]
843 else:
844 # return -1 for things like security_group_rules
845 # that are neither a keystone limit or a local limit
846 limit = local_limits.get(resource.name, -1)
847 project_quotas[resource.name] = {"limit": limit}
849 if usages:
850 local_in_use = local_limit.get_in_use(context, project_id)
851 p_in_use = placement_limit.get_legacy_counts(context, project_id)
853 for resource in resources.values():
854 # default to 0 for resources that are deprecated,
855 # i.e. not in keystone or local limits, such that we
856 # are API compatible with what was returned with
857 # the db driver, even though noop driver returned -1
858 usage_count = 0
859 if resource.name in local_in_use:
860 usage_count = local_in_use[resource.name]
861 if resource.name in p_in_use:
862 usage_count = p_in_use[resource.name]
863 project_quotas[resource.name]["in_use"] = usage_count
865 return project_quotas
867 def get_user_quotas(self, context, resources, project_id, user_id,
868 quota_class=None, usages=True):
869 return self.get_project_quotas(context, resources, project_id,
870 quota_class, usages)
873class BaseResource(object):
874 """Describe a single resource for quota checking."""
876 def __init__(self, name, flag=None):
877 """Initializes a Resource.
879 :param name: The name of the resource, i.e., "instances".
880 :param flag: The name of the flag or configuration option
881 which specifies the default value of the quota
882 for this resource.
883 """
885 self.name = name
886 self.flag = flag
888 @property
889 def default(self):
890 """Return the default value of the quota."""
891 return CONF.quota[self.flag] if self.flag else -1
894class AbsoluteResource(BaseResource):
895 """Describe a resource that does not correspond to database objects."""
896 valid_method = 'check'
899class CountableResource(AbsoluteResource):
900 """Describe a resource where the counts aren't based solely on the
901 project ID.
902 """
904 def __init__(self, name, count_as_dict, flag=None):
905 """Initializes a CountableResource.
907 Countable resources are those resources which directly
908 correspond to objects in the database, but for which a count
909 by project ID is inappropriate e.g. keypairs
910 A CountableResource must be constructed with a counting
911 function, which will be called to determine the current counts
912 of the resource.
914 The counting function will be passed the context, along with
915 the extra positional and keyword arguments that are passed to
916 Quota.count_as_dict(). It should return a dict specifying the
917 count scoped to a project and/or a user.
919 Example count of instances, cores, or ram returned as a rollup
920 of all the resources since we only want to query the instances
921 table once, not multiple times, for each resource.
922 Instances, cores, and ram are counted across a project and
923 across a user:
925 {'project': {'instances': 5, 'cores': 8, 'ram': 4096},
926 'user': {'instances': 1, 'cores': 2, 'ram': 512}}
928 Example count of server groups keeping a consistent format.
929 Server groups are counted across a project and across a user:
931 {'project': {'server_groups': 7},
932 'user': {'server_groups': 2}}
934 Example count of key pairs keeping a consistent format.
935 Key pairs are counted across a user only:
937 {'user': {'key_pairs': 5}}
939 Note that this counting is not performed in a transaction-safe
940 manner. This resource class is a temporary measure to provide
941 required functionality, until a better approach to solving
942 this problem can be evolved.
944 :param name: The name of the resource, i.e., "instances".
945 :param count_as_dict: A callable which returns the count of the
946 resource as a dict. The arguments passed are as
947 described above.
948 :param flag: The name of the flag or configuration option
949 which specifies the default value of the quota
950 for this resource.
951 """
953 super(CountableResource, self).__init__(name, flag=flag)
954 self.count_as_dict = count_as_dict
957class QuotaEngine(object):
958 """Represent the set of recognized quotas."""
960 def __init__(self, quota_driver=None, resources=None):
961 """Initialize a Quota object.
963 :param quota_driver: a QuotaDriver object (only used in testing. if
964 None (default), instantiates a driver from the
965 CONF.quota.driver option)
966 :param resources: iterable of Resource objects
967 """
968 resources = resources or []
969 self._resources = {
970 resource.name: resource for resource in resources
971 }
972 # NOTE(mriedem): quota_driver is ever only supplied in tests with a
973 # fake driver.
974 self.__driver_override = quota_driver
975 self.__driver = None
976 self.__driver_name = None
978 @property
979 def _driver(self):
980 if self.__driver_override:
981 return self.__driver_override
983 # NOTE(johngarbutt) to allow unit tests to change the driver by
984 # simply overriding config, double check if we have the correct
985 # driver cached before we return the currently cached driver
986 driver_name_in_config = CONF.quota.driver
987 if self.__driver_name != driver_name_in_config:
988 self.__driver = importutils.import_object(driver_name_in_config)
989 self.__driver_name = driver_name_in_config
991 return self.__driver
993 def get_defaults(self, context):
994 """Retrieve the default quotas.
996 :param context: The request context, for access checks.
997 """
999 return self._driver.get_defaults(context, self._resources)
1001 def get_class_quotas(self, context, quota_class):
1002 """Retrieve the quotas for the given quota class.
1004 :param context: The request context, for access checks.
1005 :param quota_class: The name of the quota class to return
1006 quotas for.
1007 """
1009 return self._driver.get_class_quotas(context, self._resources,
1010 quota_class)
1012 def get_user_quotas(self, context, project_id, user_id, quota_class=None,
1013 usages=True):
1014 """Retrieve the quotas for the given user and project.
1016 :param context: The request context, for access checks.
1017 :param project_id: The ID of the project to return quotas for.
1018 :param user_id: The ID of the user to return quotas for.
1019 :param quota_class: If project_id != context.project_id, the
1020 quota class cannot be determined. This
1021 parameter allows it to be specified.
1022 :param usages: If True, the current counts will also be returned.
1023 """
1025 return self._driver.get_user_quotas(context, self._resources,
1026 project_id, user_id,
1027 quota_class=quota_class,
1028 usages=usages)
1030 def get_project_quotas(self, context, project_id, quota_class=None,
1031 usages=True, remains=False):
1032 """Retrieve the quotas for the given project.
1034 :param context: The request context, for access checks.
1035 :param project_id: The ID of the project to return quotas for.
1036 :param quota_class: If project_id != context.project_id, the
1037 quota class cannot be determined. This
1038 parameter allows it to be specified.
1039 :param usages: If True, the current counts will also be returned.
1040 :param remains: If True, the current remains of the project will
1041 will be returned.
1042 """
1044 return self._driver.get_project_quotas(context, self._resources,
1045 project_id,
1046 quota_class=quota_class,
1047 usages=usages,
1048 remains=remains)
1050 def get_settable_quotas(self, context, project_id, user_id=None):
1051 """Given a list of resources, retrieve the range of settable quotas for
1052 the given user or project.
1054 :param context: The request context, for access checks.
1055 :param project_id: The ID of the project to return quotas for.
1056 :param user_id: The ID of the user to return quotas for.
1057 """
1059 return self._driver.get_settable_quotas(context, self._resources,
1060 project_id,
1061 user_id=user_id)
1063 def count_as_dict(self, context, resource, *args, **kwargs):
1064 """Count a resource and return a dict.
1066 For countable resources, invokes the count_as_dict() function and
1067 returns its result. Arguments following the context and
1068 resource are passed directly to the count function declared by
1069 the resource.
1071 :param context: The request context, for access checks.
1072 :param resource: The name of the resource, as a string.
1073 :returns: A dict containing the count(s) for the resource, for example:
1074 {'project': {'instances': 2, 'cores': 4, 'ram': 1024},
1075 'user': {'instances': 1, 'cores': 2, 'ram': 512}}
1077 another example:
1078 {'user': {'key_pairs': 5}}
1079 """
1081 # Get the resource
1082 res = self._resources.get(resource)
1083 if not res or not hasattr(res, 'count_as_dict'):
1084 raise exception.QuotaResourceUnknown(unknown=[resource])
1086 return res.count_as_dict(context, *args, **kwargs)
1088 # TODO(melwitt): This can be removed once no old code can call
1089 # limit_check(). It will be replaced with limit_check_project_and_user().
1090 def limit_check(self, context, project_id=None, user_id=None, **values):
1091 """Check simple quota limits.
1093 For limits--those quotas for which there is no usage
1094 synchronization function--this method checks that a set of
1095 proposed values are permitted by the limit restriction. The
1096 values to check are given as keyword arguments, where the key
1097 identifies the specific quota limit to check, and the value is
1098 the proposed value.
1100 This method will raise a QuotaResourceUnknown exception if a
1101 given resource is unknown or if it is not a simple limit
1102 resource.
1104 If any of the proposed values is over the defined quota, an
1105 OverQuota exception will be raised with the sorted list of the
1106 resources which are too high. Otherwise, the method returns
1107 nothing.
1109 :param context: The request context, for access checks.
1110 :param project_id: Specify the project_id if current context
1111 is admin and admin wants to impact on
1112 common user's tenant.
1113 :param user_id: Specify the user_id if current context
1114 is admin and admin wants to impact on
1115 common user.
1116 """
1118 return self._driver.limit_check(context, self._resources, values,
1119 project_id=project_id, user_id=user_id)
1121 def limit_check_project_and_user(self, context, project_values=None,
1122 user_values=None, project_id=None,
1123 user_id=None):
1124 """Check values against quota limits.
1126 For limits--this method checks that a set of
1127 proposed values are permitted by the limit restriction.
1129 This method will raise a QuotaResourceUnknown exception if a
1130 given resource is unknown or if it is not a simple limit
1131 resource.
1133 If any of the proposed values is over the defined quota, an
1134 OverQuota exception will be raised with the sorted list of the
1135 resources which are too high. Otherwise, the method returns
1136 nothing.
1138 :param context: The request context, for access checks
1139 :param project_values: Optional dict containing the resource values to
1140 check against project quota,
1141 e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
1142 :param user_values: Optional dict containing the resource values to
1143 check against user quota,
1144 e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
1145 :param project_id: Optional project_id for scoping the limit check to a
1146 different project than in the context
1147 :param user_id: Optional user_id for scoping the limit check to a
1148 different user than in the context
1149 """
1150 return self._driver.limit_check_project_and_user(
1151 context, self._resources, project_values=project_values,
1152 user_values=user_values, project_id=project_id, user_id=user_id)
1154 @property
1155 def resources(self):
1156 return sorted(self._resources.keys())
1158 def get_reserved(self):
1159 return self._driver.get_reserved()
1162@api_db_api.context_manager.reader
1163def _user_id_queued_for_delete_populated(context, project_id=None):
1164 """Determine whether user_id and queued_for_delete are set.
1166 This will be used to determine whether we need to fall back on
1167 the legacy quota counting method (if we cannot rely on counting
1168 instance mappings for the instance count). If any records with user_id=None
1169 and queued_for_delete=False are found, we need to fall back to the legacy
1170 counting method. If any records with queued_for_delete=None are found, we
1171 need to fall back to the legacy counting method.
1173 Note that this check specifies queued_for_deleted=False, which excludes
1174 deleted and SOFT_DELETED instances. The 'populate_user_id' data migration
1175 migrates SOFT_DELETED instances because they could be restored at any time
1176 in the future. However, for this quota-check-time method, it is acceptable
1177 to ignore SOFT_DELETED instances, since we just want to know if it is safe
1178 to use instance mappings to count instances at this point in time (and
1179 SOFT_DELETED instances do not count against quota limits).
1181 We also want to fall back to the legacy counting method if we detect any
1182 records that have not yet populated the queued_for_delete field. We do this
1183 instead of counting queued_for_delete=None records since that might not
1184 accurately reflect the project or project user's quota usage.
1186 :param project_id: The project to check
1187 :returns: True if user_id is set for all non-deleted instances and
1188 queued_for_delete is set for all instances, else False
1189 """
1190 user_id_not_populated = sql.and_(
1191 api_models.InstanceMapping.user_id == sql.null(),
1192 api_models.InstanceMapping.queued_for_delete == sql.false())
1193 # If either queued_for_delete or user_id are unmigrated, we will return
1194 # False.
1195 unmigrated_filter = sql.or_(
1196 api_models.InstanceMapping.queued_for_delete == sql.null(),
1197 user_id_not_populated)
1198 query = context.session.query(api_models.InstanceMapping).filter(
1199 unmigrated_filter)
1200 if project_id: 1200 ↛ 1201line 1200 didn't jump to line 1201 because the condition on line 1200 was never true
1201 query = query.filter_by(project_id=project_id)
1202 return not context.session.query(query.exists()).scalar()
1205def _keypair_get_count_by_user(context, user_id):
1206 count = objects.KeyPairList.get_count_by_user(context, user_id)
1207 return {'user': {'key_pairs': count}}
1210def _server_group_count_members_by_user_legacy(context, group, user_id):
1211 # NOTE(melwitt): This is mostly duplicated from
1212 # InstanceGroup.count_members_by_user() to query across multiple cells.
1213 # We need to be able to pass the correct cell context to
1214 # InstanceList.get_by_filters().
1215 # NOTE(melwitt): Counting across cells for instances means we will miss
1216 # counting resources if a cell is down.
1217 cell_mappings = objects.CellMappingList.get_all(context)
1218 greenthreads = []
1219 filters = {'deleted': False, 'user_id': user_id, 'uuid': group.members}
1220 for cell_mapping in cell_mappings:
1221 with nova_context.target_cell(context, cell_mapping) as cctxt:
1222 greenthreads.append(utils.spawn(
1223 objects.InstanceList.get_by_filters, cctxt, filters,
1224 expected_attrs=[]))
1225 instances = objects.InstanceList(objects=[])
1226 for greenthread in greenthreads:
1227 found = greenthread.wait()
1228 instances = instances + found
1229 # Count build requests using the same filters to catch group members
1230 # that are not yet created in a cell.
1231 # NOTE(mriedem): BuildRequestList.get_by_filters is not very efficient for
1232 # what we need and we can optimize this with a new query method.
1233 build_requests = objects.BuildRequestList.get_by_filters(context, filters)
1234 # Ignore any duplicates since build requests and instances can co-exist
1235 # for a short window of time after the instance is created in a cell but
1236 # before the build request is deleted.
1237 instance_uuids = [inst.uuid for inst in instances]
1238 count = len(instances)
1239 for build_request in build_requests:
1240 if build_request.instance_uuid not in instance_uuids:
1241 count += 1
1242 return {'user': {'server_group_members': count}}
1245def is_qfd_populated(context):
1246 """Check if user_id and queued_for_delete fields are populated.
1248 This method is related to counting quota usage from placement. It is not
1249 yet possible to count instances from placement, so in the meantime we can
1250 use instance mappings for counting. This method is used to determine
1251 whether the user_id and queued_for_delete columns are populated in the API
1252 database's instance_mappings table. Instance mapping records are not
1253 deleted from the database until the database is archived, so
1254 queued_for_delete tells us whether or not we should count them for instance
1255 quota usage. The user_id field enables us to scope instance quota usage to
1256 a user (legacy quota).
1258 Scoping instance quota to a user is only possible
1259 when counting quota usage from placement is configured and unified limits
1260 is not configured. When unified limits is configured, quotas are scoped
1261 only to projects.
1263 In the future when it is possible to count instance usage from placement,
1264 this method will no longer be needed.
1265 """
1266 global UID_QFD_POPULATED_CACHE_ALL
1267 if not UID_QFD_POPULATED_CACHE_ALL:
1268 LOG.debug('Checking whether user_id and queued_for_delete are '
1269 'populated for all projects')
1270 UID_QFD_POPULATED_CACHE_ALL = _user_id_queued_for_delete_populated(
1271 context)
1273 return UID_QFD_POPULATED_CACHE_ALL
1276def _server_group_count_members_by_user(context, group, user_id):
1277 """Get the count of server group members for a group by user.
1279 :param context: The request context for database access
1280 :param group: The InstanceGroup object with members to count
1281 :param user_id: The user_id to count across
1282 :returns: A dict containing the user-scoped count. For example:
1284 {'user': 'server_group_members': <count across user>}}
1285 """
1286 # Because server group members quota counting is not scoped to a project,
1287 # but scoped to a particular InstanceGroup and user, we have no reasonable
1288 # way of pruning down our migration check to only a subset of all instance
1289 # mapping records.
1290 # So, we check whether user_id/queued_for_delete is populated for all
1291 # records and cache the result to prevent unnecessary checking once the
1292 # data migration has been completed.
1293 if is_qfd_populated(context): 1293 ↛ 1298line 1293 didn't jump to line 1298 because the condition on line 1293 was always true
1294 count = objects.InstanceMappingList.get_count_by_uuids_and_user(
1295 context, group.members, user_id)
1296 return {'user': {'server_group_members': count}}
1298 LOG.warning('Falling back to legacy quota counting method for server '
1299 'group members')
1300 return _server_group_count_members_by_user_legacy(context, group,
1301 user_id)
1304def _instances_cores_ram_count_legacy(context, project_id, user_id=None):
1305 """Get the counts of instances, cores, and ram in cell databases.
1307 :param context: The request context for database access
1308 :param project_id: The project_id to count across
1309 :param user_id: The user_id to count across
1310 :returns: A dict containing the project-scoped counts and user-scoped
1311 counts if user_id is specified. For example:
1313 {'project': {'instances': <count across project>,
1314 'cores': <count across project>,
1315 'ram': <count across project>},
1316 'user': {'instances': <count across user>,
1317 'cores': <count across user>,
1318 'ram': <count across user>}}
1319 """
1320 # NOTE(melwitt): Counting across cells for instances, cores, and ram means
1321 # we will miss counting resources if a cell is down.
1322 # NOTE(tssurya): We only go into those cells in which the tenant has
1323 # instances. We could optimize this to avoid the CellMappingList query
1324 # for single-cell deployments by checking the cell cache and only doing
1325 # this filtering if there is more than one non-cell0 cell.
1326 # TODO(tssurya): Consider adding a scatter_gather_cells_for_project
1327 # variant that makes this native to nova.context.
1328 if CONF.api.instance_list_per_project_cells: 1328 ↛ 1329line 1328 didn't jump to line 1329 because the condition on line 1328 was never true
1329 cell_mappings = objects.CellMappingList.get_by_project_id(
1330 context, project_id)
1331 else:
1332 nova_context.load_cells()
1333 cell_mappings = nova_context.CELLS
1334 results = nova_context.scatter_gather_cells(
1335 context, cell_mappings, nova_context.CELL_TIMEOUT,
1336 objects.InstanceList.get_counts, project_id, user_id=user_id)
1337 total_counts = {'project': {'instances': 0, 'cores': 0, 'ram': 0}}
1338 if user_id:
1339 total_counts['user'] = {'instances': 0, 'cores': 0, 'ram': 0}
1340 for result in results.values():
1341 if not nova_context.is_cell_failure_sentinel(result): 1341 ↛ 1340line 1341 didn't jump to line 1340 because the condition on line 1341 was always true
1342 for resource, count in result['project'].items():
1343 total_counts['project'][resource] += count
1344 if user_id:
1345 for resource, count in result['user'].items():
1346 total_counts['user'][resource] += count
1347 return total_counts
1350def _cores_ram_count_placement(context, project_id, user_id=None):
1351 return report.report_client_singleton().get_usages_counts_for_quota(
1352 context, project_id, user_id=user_id)
1355def _instances_cores_ram_count_api_db_placement(context, project_id,
1356 user_id=None):
1357 # Will return a dict with format: {'project': {'instances': M},
1358 # 'user': {'instances': N}}
1359 # where the 'user' key is optional.
1360 total_counts = objects.InstanceMappingList.get_counts(context,
1361 project_id,
1362 user_id=user_id)
1363 cores_ram_counts = _cores_ram_count_placement(context, project_id,
1364 user_id=user_id)
1365 total_counts['project'].update(cores_ram_counts['project'])
1366 if 'user' in total_counts: 1366 ↛ 1368line 1366 didn't jump to line 1368 because the condition on line 1366 was always true
1367 total_counts['user'].update(cores_ram_counts['user'])
1368 return total_counts
1371def _instances_cores_ram_count(context, project_id, user_id=None):
1372 """Get the counts of instances, cores, and ram.
1374 :param context: The request context for database access
1375 :param project_id: The project_id to count across
1376 :param user_id: The user_id to count across
1377 :returns: A dict containing the project-scoped counts and user-scoped
1378 counts if user_id is specified. For example:
1380 {'project': {'instances': <count across project>,
1381 'cores': <count across project>,
1382 'ram': <count across project>},
1383 'user': {'instances': <count across user>,
1384 'cores': <count across user>,
1385 'ram': <count across user>}}
1386 """
1387 global UID_QFD_POPULATED_CACHE_BY_PROJECT
1388 if CONF.quota.count_usage_from_placement:
1389 # If a project has all user_id and queued_for_delete data populated,
1390 # cache the result to avoid needless database checking in the future.
1391 if (not UID_QFD_POPULATED_CACHE_ALL and
1392 project_id not in UID_QFD_POPULATED_CACHE_BY_PROJECT):
1393 LOG.debug('Checking whether user_id and queued_for_delete are '
1394 'populated for project_id %s', project_id)
1395 uid_qfd_populated = _user_id_queued_for_delete_populated(
1396 context, project_id)
1397 if uid_qfd_populated:
1398 UID_QFD_POPULATED_CACHE_BY_PROJECT.add(project_id)
1399 else:
1400 uid_qfd_populated = True
1401 if uid_qfd_populated:
1402 return _instances_cores_ram_count_api_db_placement(context,
1403 project_id,
1404 user_id=user_id)
1405 LOG.warning('Falling back to legacy quota counting method for '
1406 'instances, cores, and ram')
1407 return _instances_cores_ram_count_legacy(context, project_id,
1408 user_id=user_id)
1411def _server_group_count(context, project_id, user_id=None):
1412 """Get the counts of server groups in the database.
1414 :param context: The request context for database access
1415 :param project_id: The project_id to count across
1416 :param user_id: The user_id to count across
1417 :returns: A dict containing the project-scoped counts and user-scoped
1418 counts if user_id is specified. For example:
1420 {'project': {'server_groups': <count across project>},
1421 'user': {'server_groups': <count across user>}}
1422 """
1423 return objects.InstanceGroupList.get_counts(context, project_id,
1424 user_id=user_id)
1427QUOTAS = QuotaEngine(
1428 resources=[
1429 CountableResource(
1430 'instances', _instances_cores_ram_count, 'instances'),
1431 CountableResource(
1432 'cores', _instances_cores_ram_count, 'cores'),
1433 CountableResource(
1434 'ram', _instances_cores_ram_count, 'ram'),
1435 AbsoluteResource(
1436 'metadata_items', 'metadata_items'),
1437 AbsoluteResource(
1438 'injected_files', 'injected_files'),
1439 AbsoluteResource(
1440 'injected_file_content_bytes', 'injected_file_content_bytes'),
1441 AbsoluteResource(
1442 'injected_file_path_bytes', 'injected_file_path_length'),
1443 CountableResource(
1444 'key_pairs', _keypair_get_count_by_user, 'key_pairs'),
1445 CountableResource(
1446 'server_groups', _server_group_count, 'server_groups'),
1447 CountableResource(
1448 'server_group_members', _server_group_count_members_by_user,
1449 'server_group_members'),
1450 # Deprecated nova-network quotas, retained to avoid changing API
1451 # responses
1452 AbsoluteResource('fixed_ips'),
1453 AbsoluteResource('floating_ips'),
1454 AbsoluteResource('security_groups'),
1455 AbsoluteResource('security_group_rules'),
1456 ],
1457)
1460def _valid_method_call_check_resource(name, method, resources):
1461 if name not in resources:
1462 raise exception.InvalidQuotaMethodUsage(method=method, res=name)
1463 res = resources[name]
1465 if res.valid_method != method:
1466 raise exception.InvalidQuotaMethodUsage(method=method, res=name)
1469def _valid_method_call_check_resources(resource_values, method, resources):
1470 """A method to check whether the resource can use the quota method.
1472 :param resource_values: Dict containing the resource names and values
1473 :param method: The quota method to check
1474 :param resources: Dict containing Resource objects to validate against
1475 """
1477 for name in resource_values.keys():
1478 _valid_method_call_check_resource(name, method, resources)