def test_optional_parameters(self): callback = self._get_usage_for_project enforcer = limit.Enforcer(self.claim, callback=callback, verify=True) self.assertEqual(self.claim, enforcer.claim) self.assertEqual(self._get_usage_for_project, enforcer.callback) self.assertTrue(enforcer.verify)
def enforce_api_limit(entity_type: str, count: int) -> None: """Check if the values given are over the limit for that key. This is generally used for limiting the size of certain API requests that eventually get stored in the database. """ if not nova_limit_utils.use_unified_limits(): return if entity_type not in API_LIMITS: fmt = "%s is not a valid API limit: %s" raise ValueError(fmt % (entity_type, API_LIMITS)) try: enforcer = limit.Enforcer(always_zero_usage) except limit_exceptions.SessionInitError as e: msg = ( "Failed to connect to keystone while enforcing %s quota limit." % entity_type) LOG.error(msg + " Error: " + str(e)) raise exception.KeystoneConnectionFailed(msg) try: enforcer.enforce(None, {entity_type: count}) except limit_exceptions.ProjectOverLimit as e: # Copy the exception message to a OverQuota to propagate to the # API layer. raise EXCEPTIONS.get(entity_type, exception.OverQuota)(str(e))
def _get_enforcer(context: 'nova.context.RequestContext', project_id: str) -> limit.Enforcer: # NOTE(johngarbutt) should we move context arg into oslo.limit? def callback(project_id, resource_names): return _get_usage(context, project_id, resource_names) return limit.Enforcer(callback)
def test_enforce(self, mock_enforce): enforcer = limit.Enforcer(self._get_usage_for_project) project_id = uuid.uuid4().hex deltas = {"a": 1} enforcer.enforce(project_id, deltas) mock_enforce.assert_called_once_with(project_id, deltas)
def test_deltas_must_be_a_dictionary(self): project_id = uuid.uuid4().hex invalid_delta_types = [uuid.uuid4().hex, 5, 5.1, True, [], None, {}] enforcer = limit.Enforcer(self._get_usage_for_project) for invalid_delta in invalid_delta_types: self.assertRaises(ValueError, enforcer.enforce, project_id, invalid_delta)
def test_get_model_impl(self): json = mock.MagicMock() limit._SDK_CONNECTION.get.return_value = json json.json.return_value = {"model": {"name": "flat"}} enforcer = limit.Enforcer(self._get_usage_for_project) flat_impl = enforcer._get_model_impl(self._get_usage_for_project) self.assertIsInstance(flat_impl, limit._FlatEnforcer) json.json.return_value = {"model": {"name": "strict-two-level"}} flat_impl = enforcer._get_model_impl(self._get_usage_for_project) self.assertIsInstance(flat_impl, limit._StrictTwoLevelEnforcer) json.json.return_value = {"model": {"name": "foo"}} e = self.assertRaises(ValueError, enforcer._get_model_impl, self._get_usage_for_project) self.assertEqual("enforcement model foo is not supported", str(e))
def enforce_db_limit(context: 'nova.context.RequestContext', entity_type: str, entity_scope: ty.Any, delta: int) -> None: """Check provided delta does not put resource over limit. Firstly we count the current usage given the specified scope. We then add that count to the specified delta to see if we are over the limit for that kind of entity. Note previously we used to recheck these limits. However these are really soft DDoS protections, not hard resource limits, so we don't do the recheck for these. The scope is specific to the limit type: * key_pairs scope is context.user_id * server_groups scope is context.project_id * server_group_members scope is server_group_uuid """ if not nova_limit_utils.use_unified_limits(): return if entity_type not in DB_COUNT_FUNCTION.keys(): fmt = "%s does not have a DB count function defined: %s" raise ValueError(fmt % (entity_type, DB_COUNT_FUNCTION.keys())) if delta < 0: raise ValueError("delta must be a positive integer") count_function = DB_COUNT_FUNCTION[entity_type] try: enforcer = limit.Enforcer( functools.partial(count_function, context, entity_scope)) except limit_exceptions.SessionInitError as e: msg = ( "Failed to connect to keystone while enforcing %s quota limit." % entity_type) LOG.error(msg + " Error: " + str(e)) raise exception.KeystoneConnectionFailed(msg) try: enforcer.enforce(None, {entity_type: delta}) except limit_exceptions.ProjectOverLimit as e: # Copy the exception message to a OverQuota to propagate to the # API layer. raise EXCEPTIONS.get(entity_type, exception.OverQuota)(str(e))
def _enforce_some(context, project_id, quota_value_fns, deltas): """Helper method to enforce a set of quota values. :param context: The RequestContext :param project_id: The project_id of the tenant being checked :param get_value_fns: A mapping of quota names to functions that will be called with no arguments to return the numerical value representing current usage. :param deltas: A mapping of quota names to the amount of resource being requested for each (to be added to the current usage before determining if over-quota). :raises: exception.LimitExceeded if the current usage is over the defined limit. :returns: None if the tenant is not currently over their quota. """ if not CONF.use_keystone_limits: return def callback(project_id, resource_names): return {name: quota_value_fns[name]() for name in resource_names} enforcer = limit.Enforcer(callback) try: enforcer.enforce( project_id, { quota_name: deltas.get(quota_name, 0) for quota_name in quota_value_fns }) except ol_exc.ProjectOverLimit as e: raise exception.LimitExceeded(body=str(e)) except ol_exc.SessionInitError as e: LOG.error( _LE('Failed to initialize oslo_limit, likely due to ' 'incorrect or insufficient configuration: %(err)s'), {'err': str(e)}) # We could just raise LimitExceeded here, but a 500 is # appropriate for incorrect server-side configuration, so we # re-raise here after the above error message to make sure we # are noticed. raise
def do_POST(self): data = self.rfile.read(int(self.headers['Content-Length'])) try: admissionRequest = json.loads(data) except json.decoder.JSONDecodeError: self.send_error(400, "Expected JSON") return try: uid = admissionRequest['request']['uid'] namespace = admissionRequest['request']['namespace'] except KeyError: self.send_error(400, "Invalid AdmissionReview object") return kind = admissionRequest['request']['kind']['kind'] if kind == "Pod": requested_pods = 1 else: self.send_error(400, "Only works for pods") return print("Incoming request: Kind: %s, Pod Count: %d\n" % (kind, requested_pods)) self._setup_openstack_connection() sc = self.openstack_conn.config.get_service_catalog() endpoint = sc.endpoint_data_for('kubernetes').endpoint_id limit.CONF.oslo_limit.endpoint_id = endpoint enforcer = limit.Enforcer(self._usage_callback) allowed = True domain = self._get_parent(namespace) if domain: try: domain = self.openstack_conn.get_domain( name_or_id=domain)['id'] except openstack.exceptions.ResourceNotFound: print("denying request: no matching domain for namespace " "parent") allowed = False project = self.openstack_conn.get_project(namespace, domain_id=domain) if project: print("found matching project: %s" % project['id']) else: print("denying request: no matching project or domain") allowed = False else: try: project = self.openstack_conn.get_domain(name_or_id=namespace) print("found matching domain: %s" % project['id']) except openstack.exceptions.ResourceNotFound: print("denying request: no matching project or domain") allowed = False deltas = {'pods': requested_pods} if allowed: try: enforcer.enforce(project['id'], deltas) print("Successfully claimed %d pods" % requested_pods) except oslo_limit.exception.ProjectOverLimit: allowed = False print("Could not claim %d pods" % requested_pods) admissionResponse = { 'apiVersion': 'admission.k8s.io/v1', 'kind': 'AdmissionReview', 'response': { 'uid': uid, 'allowed': allowed } } httpResponse = json.dumps(admissionResponse) self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(bytes(httpResponse, 'utf-8'))
def get_legacy_default_limits() -> ty.Dict[str, int]: # TODO(johngarbutt): need oslo.limit API for this, it should do caching enforcer = limit.Enforcer(lambda: None) new_limits = enforcer.get_registered_limits(LEGACY_LIMITS.keys()) return _convert_keys_to_legacy_name(dict(new_limits))
def test_required_parameters(self): enforcer = limit.Enforcer(self.claim) self.assertEqual(self.claim, enforcer.claim) self.assertIsNone(enforcer.callback) self.assertTrue(enforcer.verify)
def test_set_model_impl(self): enforcer = limit.Enforcer(self._get_usage_for_project) self.assertIsInstance(enforcer.model, limit._FlatEnforcer)
def test_project_id_must_be_a_string(self): enforcer = limit.Enforcer(self._get_usage_for_project) invalid_delta_types = [{}, 5, 5.1, True, False, [], None, ""] for invalid_project_id in invalid_delta_types: self.assertRaises(ValueError, enforcer.enforce, invalid_project_id, {})
def get_legacy_project_limits(project_id): enforcer = limit.Enforcer(lambda: None) new_limits = enforcer.get_project_limits(project_id, LEGACY_LIMITS.keys()) return _convert_keys_to_legacy_name(dict(new_limits))
def get_legacy_default_limits(): enforcer = limit.Enforcer(lambda: None) new_limits = enforcer.get_registered_limits(LEGACY_LIMITS.keys()) return _convert_keys_to_legacy_name(dict(new_limits))