def test__remove_expired_reservations(self): for project, resource in itertools.product(self.projects, self.resources): deltas = {resource: 1} with db_api.CONTEXT_WRITER.using(self.context): quota_api.create_reservation(self.context, project, deltas) # Initial check: the reservations are correctly created. for project in self.projects: for res in quota_obj.Reservation.get_objects(self.context, project_id=project): self.assertEqual(1, len(res.resource_deltas)) delta = res.resource_deltas[0] self.assertEqual(1, delta.amount) self.assertIn(delta.resource, self.resources) # Delete the expired reservations and check. # NOTE(ralonsoh): the timeout is set to -121 to force the deletion # of all created reservations, including those ones created in this # test. The value of 121 overcomes the 120 seconds of default # expiration time a reservation has. timeout = quota_api.RESERVATION_EXPIRATION_TIMEOUT quota_api.RESERVATION_EXPIRATION_TIMEOUT = -(timeout + 1) self.addCleanup(self._cleanup_timeout, timeout) self.quota_driver._remove_expired_reservations() res = quota_obj.Reservation.get_objects(self.context) self.assertEqual([], res)
def make_reservation(self, context, project_id, resources, deltas, plugin): resources_over_limit = [] with db_api.CONTEXT_WRITER.using(context): # Filter out unlimited resources. limits = self.get_tenant_quotas(context, resources, project_id) unlimited_resources = set([ resource for (resource, limit) in limits.items() if limit < 0 ]) requested_resources = (set(deltas.keys()) - unlimited_resources) # Delete expired reservations before counting valid ones. This # operation is fast and by calling it before making any # reservation, we ensure the freshness of the reservations. quota_api.remove_expired_reservations(context, tenant_id=project_id) # Count the number of (1) used and (2) reserved resources for this # project_id. If any resource limit is exceeded, raise exception. for resource_name in requested_resources: tracked_resource = resources.get(resource_name) if not tracked_resource: continue used_and_reserved = tracked_resource.count( context, None, project_id, count_db_registers=True) resource_num = deltas[resource_name] if limits[resource_name] < (used_and_reserved + resource_num): resources_over_limit.append(resource_name) if resources_over_limit: raise exceptions.OverQuota(overs=sorted(resources_over_limit)) return quota_api.create_reservation(context, project_id, deltas)
def _create_reservation(self, resource_deltas, project_id=None, expiration=None): project_id = project_id or self.project_id return quota_api.create_reservation(self.context, project_id, resource_deltas, expiration)
def _create_reservation(self, resource_deltas, tenant_id=None, expiration=None): tenant_id = tenant_id or self.tenant_id return quota_api.create_reservation(self.context, tenant_id, resource_deltas, expiration)
def make_reservation(self, context, project_id, resources, deltas, plugin): resources_over_limit = [] with db_api.CONTEXT_WRITER.using(context): # Filter out unlimited resources. limits = self.get_project_quotas(context, resources, project_id) unlimited_resources = set([ resource for (resource, limit) in limits.items() if limit < 0 ]) requested_resources = (set(deltas.keys()) - unlimited_resources) # Count the number of (1) used and (2) reserved resources for this # project_id. If any resource limit is exceeded, raise exception. for resource_name in requested_resources: used_and_reserved = self.get_resource_usage( context, project_id, resources, resource_name) resource_num = deltas[resource_name] if limits[resource_name] < (used_and_reserved + resource_num): resources_over_limit.append(resource_name) if resources_over_limit: raise exceptions.OverQuota(overs=sorted(resources_over_limit)) return quota_api.create_reservation(context, project_id, deltas)
def make_reservation(self, context, tenant_id, resources, deltas, plugin): # Lock current reservation table # NOTE(salv-orlando): This routine uses DB write locks. # These locks are acquired by the count() method invoked on resources. # Please put your shotguns aside. # A non locking algorithm for handling reservation is feasible, however # it will require two database writes even in cases when there are not # concurrent reservations. # For this reason it might be advisable to handle contention using # this kind of locks and paying the cost of a write set certification # failure when a MySQL Galera cluster is employed. Also, this class of # locks should be ok to use when support for sending "hotspot" writes # to a single node will be available. requested_resources = deltas.keys() with db_api.autonested_transaction(context.session): # get_tenant_quotes needs in input a dictionary mapping resource # name to BaseResosurce instances so that the default quota can be # retrieved current_limits = self.get_tenant_quotas( context, resources, tenant_id) unlimited_resources = set([resource for (resource, limit) in current_limits.items() if limit < 0]) # Do not even bother counting resources and calculating headroom # for resources with unlimited quota LOG.debug(("Resources %s have unlimited quota limit. It is not " "required to calculated headroom "), ",".join(unlimited_resources)) requested_resources = (set(requested_resources) - unlimited_resources) # Gather current usage information # TODO(salv-orlando): calling count() for every resource triggers # multiple queries on quota usage. This should be improved, however # this is not an urgent matter as the REST API currently only # allows allocation of a resource at a time # NOTE: pass plugin too for compatibility with CountableResource # instances current_usages = dict( (resource, resources[resource].count( context, plugin, tenant_id, resync_usage=False)) for resource in requested_resources) # Adjust for expired reservations. Apparently it is cheaper than # querying every time for active reservations and counting overall # quantity of resources reserved expired_deltas = quota_api.get_reservations_for_resources( context, tenant_id, requested_resources, expired=True) # Verify that the request can be accepted with current limits resources_over_limit = [] for resource in requested_resources: expired_reservations = expired_deltas.get(resource, 0) total_usage = current_usages[resource] - expired_reservations res_headroom = current_limits[resource] - total_usage LOG.debug(("Attempting to reserve %(delta)d items for " "resource %(resource)s. Total usage: %(total)d; " "quota limit: %(limit)d; headroom:%(headroom)d"), {'resource': resource, 'delta': deltas[resource], 'total': total_usage, 'limit': current_limits[resource], 'headroom': res_headroom}) if res_headroom < deltas[resource]: resources_over_limit.append(resource) if expired_reservations: self._handle_expired_reservations(context, tenant_id) if resources_over_limit: raise exceptions.OverQuota(overs=sorted(resources_over_limit)) # Success, store the reservation # TODO(salv-orlando): Make expiration time configurable return quota_api.create_reservation( context, tenant_id, deltas)
def make_reservation(self, context, tenant_id, resources, deltas, plugin): # Lock current reservation table # NOTE(salv-orlando): This routine uses DB write locks. # These locks are acquired by the count() method invoked on resources. # Please put your shotguns aside. # A non locking algorithm for handling reservation is feasible, however # it will require two database writes even in cases when there are not # concurrent reservations. # For this reason it might be advisable to handle contention using # this kind of locks and paying the cost of a write set certification # failure when a MySQL Galera cluster is employed. Also, this class of # locks should be ok to use when support for sending "hotspot" writes # to a single node will be available. requested_resources = deltas.keys() with db_api.context_manager.writer.using(context): # get_tenant_quotes needs in input a dictionary mapping resource # name to BaseResosurce instances so that the default quota can be # retrieved current_limits = self.get_tenant_quotas(context, resources, tenant_id) unlimited_resources = set([ resource for (resource, limit) in current_limits.items() if limit < 0 ]) # Do not even bother counting resources and calculating headroom # for resources with unlimited quota LOG.debug( "Resources %s have unlimited quota limit. It is not " "required to calculate headroom ", ",".join(unlimited_resources)) requested_resources = (set(requested_resources) - unlimited_resources) # Gather current usage information # TODO(salv-orlando): calling count() for every resource triggers # multiple queries on quota usage. This should be improved, however # this is not an urgent matter as the REST API currently only # allows allocation of a resource at a time # NOTE: pass plugin too for compatibility with CountableResource # instances current_usages = dict((resource, resources[resource].count( context, plugin, tenant_id, resync_usage=False)) for resource in requested_resources) # Adjust for expired reservations. Apparently it is cheaper than # querying every time for active reservations and counting overall # quantity of resources reserved expired_deltas = quota_api.get_reservations_for_resources( context, tenant_id, requested_resources, expired=True) # Verify that the request can be accepted with current limits resources_over_limit = [] for resource in requested_resources: expired_reservations = expired_deltas.get(resource, 0) total_usage = current_usages[resource] - expired_reservations res_headroom = current_limits[resource] - total_usage LOG.debug( ("Attempting to reserve %(delta)d items for " "resource %(resource)s. Total usage: %(total)d; " "quota limit: %(limit)d; headroom:%(headroom)d"), { 'resource': resource, 'delta': deltas[resource], 'total': total_usage, 'limit': current_limits[resource], 'headroom': res_headroom }) if res_headroom < deltas[resource]: resources_over_limit.append(resource) if expired_reservations: self._handle_expired_reservations(context, tenant_id) if resources_over_limit: raise exceptions.OverQuota(overs=sorted(resources_over_limit)) # Success, store the reservation # TODO(salv-orlando): Make expiration time configurable return quota_api.create_reservation(context, tenant_id, deltas)
def test_count_reserved(self): res = self._create_resource() quota_api.create_reservation(self.context, self.tenant_id, {res.name: 1}) self.assertEqual(1, res.count_reserved(self.context, self.tenant_id))
def _create_reservation(self, resource_deltas, tenant_id=None, expiration=None): tenant_id = tenant_id or self.tenant_id return quota_api.create_reservation( self.context, tenant_id, resource_deltas, expiration)