def get(self, rse): """ Get RSE usage information. .. :quickref: UsageHistory; Get RSE usage history. :param rse: the RSE name. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid Auth Token. :status 404: RSE Not Found. :status 406: Not Acceptable. :returns: Line separated list of dictionary with RSE usage information. """ try: def generate(issuer, source, vo): for usage in list_rse_usage_history(rse=rse, issuer=issuer, source=source, vo=vo): yield render_json(**usage) + '\n' return try_stream( generate(issuer=request.environ.get('issuer'), source=request.args.get('source'), vo=request.environ.get('vo'))) except RSENotFound as error: return generate_http_error_flask(404, error)
def get(self, account=None, name=None): """ Retrieve a subscription. .. :quickref: Subscription; Get subscriptions. :param account: The account name. :param name: The subscription name. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid Auth Token. :status 404: Subscription Not Found. :status 406: Not Acceptable. :returns: Line separated list of dictionaries with subscription information. """ try: def generate(vo): for subscription in list_subscriptions(name=name, account=account, vo=vo): yield render_json(**subscription) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except SubscriptionNotFound as error: return generate_http_error_flask(404, error)
def get(self, rse, scheme): """ List all references of the provided RSE for the given protocol. .. :quickref: Protocol; List RSE protocol. :param rse: The RSE name. :param scheme: The protocol identifier. :resheader Content-Type: application/json :status 200: OK. :status 401: Invalid Auth Token. :status 404: RSE Not Found. :status 404: RSE Protocol Not Supported. :status 404: RSE Protocol Domain Not Supported. :status 406: Not Acceptable. :returns: A list with detailed protocol information. """ try: p_list = get_rse_protocols(rse, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except (RSENotFound, RSEProtocolNotSupported, RSEProtocolDomainNotSupported) as error: return generate_http_error_flask(404, error) return jsonify(p_list)
def delete(self, rse, scheme, hostname=None, port=None): """ Deletes a protocol entry for the provided RSE. .. :quickref: Protocol; Delete an RSE protocol. :param rse: The RSE name. :param scheme: The protocol identifier. :param hostname: The hostname defined for the scheme, used if more than one scheme is registered with the same identifier. :param port: The port registered for the hostname, ued if more than one scheme is registered with the same identifier and hostname. :status 200: OK. :status 401: Invalid Auth Token. :status 404: RSE not found. :status 404: RSE Protocol Not Supported. """ try: del_protocols(rse, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'), scheme=scheme, hostname=hostname, port=port) except (RSEProtocolNotSupported, RSENotFound) as error: return generate_http_error_flask(404, error) return '', 200
def get(self): """ --- summary: Return all rules for a given account tags: - Rule responses: 200: description: OK content: application/json: schema: type: string 401: description: Invalid Auth Token 404: description: No rule found for the given id 406: description: Not Acceptable """ try: def generate(filters, vo): for rule in list_replication_rules(filters=filters, vo=vo): yield dumps(rule, cls=APIEncoder) + '\n' return try_stream(generate(filters=dict(request.args.items(multi=False)), vo=request.environ.get('vo'))) except RuleNotFound as error: return generate_http_error_flask(404, error)
def get(self): """ Validate a Rucio Auth Token. .. :quickref: Validate; Validate a Rucio Auth Token. :reqheader Rucio-Auth-Token: as a variable-length string. :status 406: Not Acceptable. :returns: Tuple(account name, token lifetime). """ headers = self.get_headers() headers['Content-Type'] = 'application/octet-stream' headers[ 'Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' headers.add('Cache-Control', 'post-check=0, pre-check=0') headers['Pragma'] = 'no-cache' token = request.headers.get('X-Rucio-Auth-Token', default=None) result = validate_auth_token(token) if not result: return generate_http_error_flask( status_code=401, exc=CannotAuthenticate.__name__, exc_msg='Cannot authenticate with given credentials', headers=headers) return str(result), 200, headers
def get(self, account): """ Return all rules of a given account. .. :quickref: Rules; Get rules for account. :param account: The account name. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid auth token. :status 404: Rule not found. :status 406: Not Acceptable. :returns: Line separated list of rules. """ filters = {'account': account} filters.update(request.args) try: def generate(vo): for rule in list_replication_rules(filters=filters, vo=vo): yield dumps(rule, cls=APIEncoder) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except RuleNotFound as error: return generate_http_error_flask(404, error)
def get(self, account, rse_expression=None): """ get the current global limits for an account on a specific RSE expression .. :quickref: GlobalAccountLimits; Get global account limits. :param account: The account name. :param rse_expression: The rse expression. :resheader Content-Type: application/json :status 200: OK. :status 401: Invalid auth token. :status 404: RSE not found. :status 406: Not Acceptable. :returns: JSON dict containing informations about the requested user. """ try: if rse_expression: limits = get_global_account_limit( account=account, rse_expression=rse_expression, vo=request.environ.get('vo')) else: limits = get_global_account_limits( account=account, vo=request.environ.get('vo')) except RSENotFound as error: return generate_http_error_flask(404, error) return Response(render_json(**limits), content_type="application/json")
def get(self, section): """ List configuration of a section .. :quickref: Section; List config section. :param section: The section name. :resheader Content-Type: application/json :status 200: OK. :status 401: Invalid Auth Token. :status 404: Config not found. :status 406: Not Acceptable. """ res = {} for item in config.items(section, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')): res[item[0]] = item[1] if res == {}: return generate_http_error_flask( status_code=404, exc=ConfigNotFound.__name__, exc_msg=f"No configuration found for section '{section}'") return jsonify(res), 200
def post(self): """ List the DIDs associated to a list of replicas. .. :quickref: ReplicasDIDs; List DIDs for replicas. :<json string pfns: The list of PFNs. :<json string rse: The RSE name. :resheader Content-Type: application/x-json-string :status 200: OK. :status 400: Cannot decode json parameter list. :status 406: Not Acceptable. :returns: A list of dictionaries containing the mapping PFNs to DIDs. """ parameters = json_parameters() pfns = param_get(parameters, 'pfns', default=[]) rse = param_get(parameters, 'rse') try: def generate(vo): for pfn in get_did_from_pfns(pfns, rse, vo=vo): yield dumps(pfn) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except AccessDenied as error: return generate_http_error_flask(401, error)
def post(self): """ Declare a list of suspicious replicas. .. :quickref: SuspiciousReplicas; Declare suspicious replicas. :<json string pfns: The list of PFNs. :<json string reason: The reason of the loss. :resheader Content-Type: application/json :status 201: Created. :status 400: Cannot decode json parameter list. :status 401: Invalid auth token. :status 404: Replica not found. :returns: A list of not successfully declared files. """ parameters = json_parameters(parse_response) pfns = param_get(parameters, 'pfns', default=[]) reason = param_get(parameters, 'reason', default=None) try: not_declared_files = declare_suspicious_file_replicas( pfns=pfns, reason=reason, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) return not_declared_files, 201 except AccessDenied as error: return generate_http_error_flask(401, error)
def get(self, scope_name): """ get locks for a given scope, name. :param scope_name: data identifier (scope)/(name). :query did_type: The type used to filter, e.g., DATASET, CONTAINER. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid Auth Token. :status 406: Not Acceptable. :returns: Line separated list of dictionary with lock information. """ did_type = request.args.get('did_type', default=None) if did_type != 'dataset': return 'Wrong did_type specified', 500 try: scope, name = parse_scope_name(scope_name, request.environ.get('vo')) def generate(vo): for lock in get_dataset_locks(scope, name, vo=vo): yield render_json(**lock) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except ValueError as error: return generate_http_error_flask(400, error)
def put(self): """ Update a file replicas state at a given RSE. .. :quickref: Replicas; update replicas state. :<json string rse: The RSE name. :<json list files: list of dicts with 'scope', 'name' and 'state'. :status 201: Replica successfully updated. :status 400: Cannot decode json parameter list. :status 401: Invalid auth token. """ parameters = json_parameters(parse_response) rse = param_get(parameters, 'rse') files = param_get(parameters, 'files') try: update_replicas_states(rse=rse, files=files, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except AccessDenied as error: return generate_http_error_flask(401, error) return '', 200
def get(self, rse): """ Get RSE usage information. .. :quickref: Usage; Get RSE usage. :param rse: the RSE name. :query source: The information source, e.g., srm. :query per_account: Boolean whether the usage should be also calculated per account or not. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid Auth Token. :status 404: RSE Not Found. :status 406: Not Acceptable. :returns: A list of dictionaries with the usage information. """ per_account = request.args.get('per_account') == 'True' try: def generate(issuer, source, per_account, vo): for usage in get_rse_usage(rse, issuer=issuer, source=source, per_account=per_account, vo=vo): yield render_json(**usage) + '\n' return try_stream( generate( issuer=request.environ.get('issuer'), source=request.args.get('source'), per_account=per_account, vo=request.environ.get('vo'), ) ) except RSENotFound as error: return generate_http_error_flask(404, error)
def post(self): """ get locks for a given scope, name. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 400: Wrong DID type. :returns: Line separated list of dictionary with lock information. """ data = json_parse(types=(dict, )) try: dids = data["dids"] except KeyError: return 'Can not find the list of DIDs in the data. Use "dids" keyword.', 400 vo = request.environ.get('vo') try: locks = get_dataset_locks_bulk(dids, vo) # removes duplicates def generate(locks): for lock in locks: lock["scope"] = str(lock["scope"]) yield render_json(**lock) + '\n' return try_stream(generate(locks)) except ValueError as error: return generate_http_error_flask(400, error)
def get(self): """ List all RSEs. .. :quickref: RSEs; List all RSEs. :query expression: The returned list only contains RSE matching this expression. :resheader Content-Type: application/x-json-stream :status 200: DIDs found. :status 400: Invalid RSE Expression. :status 401: Invalid Auth Token. :status 406: Not Acceptable. :returns: A list containing all RSEs. """ expression = request.args.get('expression', default=None) if expression: try: def generate(vo): for rse in parse_rse_expression(expression, vo=vo): yield render_json(rse=rse) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except (InvalidRSEExpression, InvalidObject) as error: return generate_http_error_flask(400, error) else: def generate(vo): for rse in list_rses(vo=vo): yield render_json(**rse) + '\n' return try_stream(generate(vo=request.environ.get('vo')))
def get(self, account, name): """ Return all rules of a given subscription id. .. :quickref: Rules; Get subscription rules. :param account: The account name. :param name: The subscription name. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid Auth Token. :status 404: Rule Not Found. :status 404: Subscription Not Found. :status 406: Not Acceptable. :returns: Line separated list of dictionaries with rule information. """ state = request.args.get('state', default=None) try: subscriptions = [subscription['id'] for subscription in list_subscriptions(name=name, account=account, vo=request.environ.get('vo'))] def generate(vo): if len(subscriptions) > 0: if state: for rule in list_replication_rules({'subscription_id': subscriptions[0], 'state': state}, vo=vo): yield render_json(**rule) + '\n' else: for rule in list_replication_rules({'subscription_id': subscriptions[0]}, vo=vo): yield render_json(**rule) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except (RuleNotFound, SubscriptionNotFound) as error: return generate_http_error_flask(404, error)
def get(self, scope_name): """ List dataset replicas using the Virtual Placement service. NOTICE: This is an RnD function and might change or go away at any time. .. :quickref: DatasetReplicas; List dataset replicas with VP. :param scope_name: data identifier (scope)/(name). :query deep: Flag to ennable lookup at the file level. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid auth token. :status 406: Not Acceptable. :returns: If VP exists a list of dicts of sites, otherwise nothing """ try: scope, name = parse_scope_name(scope_name, request.environ.get('vo')) def generate(_deep, vo): for row in list_dataset_replicas_vp(scope=scope, name=name, deep=_deep, vo=vo): yield dumps(row, cls=APIEncoder) + '\n' deep = request.args.get('deep', default=False) return try_stream( generate(_deep=deep, vo=request.environ.get('vo'))) except ValueError as error: return generate_http_error_flask(400, error)
def get(self, scope_name): """ List dataset replicas. .. :quickref: DatasetReplicas; List dataset replicas. :param scope_name: data identifier (scope)/(name). :query deep: Flag to ennable lookup at the file level. :resheader Content-Type: application/x-json-stream :status 200: OK. :status 401: Invalid auth token. :status 406: Not Acceptable. :returns: A dictionary containing all replicas information. """ try: scope, name = parse_scope_name(scope_name, request.environ.get('vo')) def generate(_deep, vo): for row in list_dataset_replicas(scope=scope, name=name, deep=_deep, vo=vo): yield dumps(row, cls=APIEncoder) + '\n' deep = request.args.get('deep', default=False) return try_stream( generate(_deep=deep, vo=request.environ.get('vo'))) except ValueError as error: return generate_http_error_flask(400, error)
def put(self, account): """ --- summary: Update description: Update a parameter for an account. tags: - Account parameters: - name: account in: path description: The account identifier. schema: type: string style: simple requestBody: content: 'application/json': schema: description: Json object with key-value pairs corresponding to the new values of the parameters. type: object responses: 200: description: OK 401: description: Invalid Auth Token 404: description: No account found. 400: description: Unknown status """ parameters = json_parameters() for key, value in parameters.items(): try: update_account(account, key=key, value=value, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except ValueError: return generate_http_error_flask(400, ValueError.__name__, f'Unknown value {value}') except AccessDenied as error: return generate_http_error_flask(401, error) except AccountNotFound as error: return generate_http_error_flask(404, error) return '', 200
def post(self, scope_name, key): """ Add metadata to a data identifier. .. :quickref: SingleMeta; Add DID metadata. HTTP Success: 201 Created HTTP Error: 400 Bad Request 401 Unauthorized 404 Not Found 409 Conflict 500 Internal Error :param scope_name: data identifier (scope)/(name). :param key: the key. """ try: scope, name = parse_scope_name(scope_name, request.environ.get('vo')) except ValueError as error: return generate_http_error_flask(400, error) parameters = json_parameters() value = param_get(parameters, 'value') try: set_metadata( scope=scope, name=name, key=key, value=value, issuer=request.environ.get('issuer'), recursive=param_get(parameters, 'recursive', default=False), vo=request.environ.get('vo'), ) except DataIdentifierNotFound as error: return generate_http_error_flask(404, error) except Duplicate as error: return generate_http_error_flask(409, error) except (KeyNotFound, InvalidMetadata, InvalidValueForKey) as error: return generate_http_error_flask(400, error) return 'Created', 201
def get(self, scope_name): """ Retrieve a single data identifier. .. :quickref: DIDs; Retrieve a single DID. **Example request**: .. sourcecode:: http GET /dids/scope1/dataset1?dynamic HTTP/1.1 Host: rucio.cern.ch **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: application/json {"scope": "scope1", "did_type": "DATASET", "name": "dataset1", "bytes": 234, "length": 3, "account": "jdoe", "open": True, "monotonic": False, "expired_at": null} :query dynamic: Flag to dynamically calculate size for open DIDs :resheader Content-Type: application/json :status 200: DID found :status 401: Invalid Auth Token :status 404: Scope not found :status 406: Not Acceptable. :returns: Dictionary with DID metadata """ try: scope, name = parse_scope_name(scope_name, request.environ.get('vo')) dynamic = 'dynamic' in request.args did = get_did(scope=scope, name=name, dynamic=dynamic, vo=request.environ.get('vo')) return Response(render_json(**did), content_type='application/json') except ValueError as error: return generate_http_error_flask(400, error) except (ScopeNotFound, DataIdentifierNotFound) as error: return generate_http_error_flask(404, error)
def get(self, scope_name): """ Returns the contents of a data identifier. .. :quickref: Attachement; Get DID contents. **Example request**: .. sourcecode:: http GET /dids/scope1/dataset1?dynamic HTTP/1.1 Host: rucio.cern.ch **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: application/json {"scope": "scope1", "did_type": "DATASET", "name": "dataset1", "bytes": 234, "length": 3, "account": "jdoe", "open": True, "monotonic": False, "expired_at": null} :query dynamic: Flag to dynamically calculate size for open DIDs :resheader Content-Type: application/x-json-stream :status 200: DID found :status 401: Invalid Auth Token :status 404: Scope not found :status 406: Not Acceptable :returns: Dictionary with DID metadata """ try: scope, name = parse_scope_name(scope_name, request.environ.get('vo')) def generate(vo): for did in list_content(scope=scope, name=name, vo=vo): yield render_json(**did) + '\n' return try_stream(generate(vo=request.environ.get('vo'))) except ValueError as error: return generate_http_error_flask(400, error) except DataIdentifierNotFound as error: return generate_http_error_flask(404, error)
def post(self, account, scope): """ --- summary: Add Scope description: Adds a new scope. tags: - Scopes parameters: - name: account in: path description: The account associated with the scope. schema: type: string style: simple - name: scope in: path description: The name of the scope. schema: type: string style: simple responses: 201: description: OK content: application/json: schema: type: string enum: ["Created"] 401: description: Invalid Auth Token 404: description: Account not found 409: description: Scope already exists """ try: add_scope(scope, account, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except Duplicate as error: return generate_http_error_flask(409, error) except AccountNotFound as error: return generate_http_error_flask(404, error) return 'Created', 201
def post(self): """ --- summary: Create description: Create or set the configuration option in the requested section. tags: - Config requestBody: content: 'application/json': schema: description: "The request body is expected to contain a json {'section': {'option': 'value'}}." type: object responses: 201: description: OK content: application/json: schema: type: string enum: ['Created'] 401: description: Invalid Auth Token 400: description: The input data was incomplete or invalid 500: description: Configuration error """ parameters = json_parameters() for section, section_config in parameters.items(): if not isinstance(section_config, dict): return generate_http_error_flask(400, ValueError.__name__, '') for option, value in section_config.items(): try: config.set(section=section, option=option, value=value, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except ConfigurationError: return generate_http_error_flask( 400, 'ConfigurationError', f"Could not set value '{value}' for section '{section}' option '{option}'" ) return 'Created', 201
def post(self): """ Declare a list of bad replicas by DID. .. :quickref: BadDIDs; Declare bad replicas by DID. :<json string pfns: The list of PFNs. :<json string reason: The reason of the loss. :<json string state: The state is eiher BAD, SUSPICIOUS or TEMPORARY_UNAVAILABLE. :<json string expires_at: The expiration date. Only apply to TEMPORARY_UNAVAILABLE. :resheader Content-Type: application/x-json-string :status 201: Created. :status 400: Cannot decode json parameter list. :status 401: Invalid auth token. :status 404: Replica not found. :returns: A list of not successfully declared files. """ parameters = json_parameters(parse_response) expires_at = param_get(parameters, 'expires_at', default=None) if expires_at: expires_at = datetime.strptime(expires_at, "%Y-%m-%dT%H:%M:%S.%f") try: not_declared_files = add_bad_dids( dids=param_get(parameters, 'dids', default=[]), rse=param_get(parameters, 'rse', default=None), issuer=request.environ.get('issuer'), state=BadFilesStatus.BAD, reason=param_get(parameters, 'reason', default=None), expires_at=expires_at, vo=request.environ.get('vo'), ) except (ValueError, InvalidType) as error: return generate_http_error_flask(400, ValueError.__name__, error.args[0]) except AccessDenied as error: return generate_http_error_flask(401, error) except ReplicaNotFound as error: return generate_http_error_flask(404, error) except Duplicate as error: return generate_http_error_flask(409, error) return Response(dumps(not_declared_files), status=201, content_type='application/json')
def get(self): """ .. :quickref: OIDC; :status 200: OK :status 401: Unauthorized :resheader X-Rucio-Auth-Token: The authentication token :resheader X-Rucio-Auth-Token-Expires: The time when the token expires """ headers = self.get_headers() headers.set('Content-Type', 'application/octet-stream') headers.set('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate') headers.add('Cache-Control', 'post-check=0, pre-check=0') headers.set('Pragma', 'no-cache') query_string = request.query_string.decode(encoding='utf-8') ip = request.headers.get('X-Forwarded-For', default=request.remote_addr) try: result = get_token_oidc(query_string, ip) except AccessDenied: return generate_http_error_flask(401, CannotAuthorize.__name__, 'Cannot authorize token request.', headers=headers) if not result: return generate_http_error_flask(401, CannotAuthorize.__name__, 'Cannot authorize token request.', headers=headers) if 'token' in result and 'webhome' not in result: headers.set('X-Rucio-Auth-Token', result['token'].token) headers.set('X-Rucio-Auth-Token-Expires', date_to_str(result['token'].expired_at)) return '', 200, headers elif 'webhome' in result: webhome = result['webhome'] if webhome is None: headers.extend(error_headers(CannotAuthenticate.__name__, 'Cannot find your OIDC identity linked to any Rucio account')) headers.set('Content-Type', 'text/html') return render_template('auth_crash.html', crashtype='unknown_identity'), 401, headers # domain setting is necessary so that the token gets distributed also to the webui server domain = '.'.join(urlparse(webhome).netloc.split('.')[1:]) response = redirect(webhome, code=303) response.headers.extend(headers) response.set_cookie('x-rucio-auth-token', value=result['token'].token, domain=domain, path='/') response.set_cookie('rucio-auth-token-created-at', value=str(time.time()), domain=domain, path='/') return response else: return '', 400, headers
def delete(self, rse): """ Disable RSE with given RSE name. .. :quickref: RSE; disable RSE. :param rse: the RSE name. :status 200: OK. :status 401: Invalid Auth Token. :status 404: RSE not found. """ try: del_rse(rse=rse, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except (RSENotFound, RSEOperationNotSupported, CounterNotFound) as error: return generate_http_error_flask(404, error) except AccessDenied as error: return generate_http_error_flask(401, error) return '', 200
def delete(self, account): """ disable account with given account name. .. :quickref: AccountParameter; Delete account information. :param account: The account identifier. :status 200: OK. :status 401: Invalid auth token. :status 404: Account not found. """ try: del_account(account, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')) except AccessDenied as error: return generate_http_error_flask(401, error) except AccountNotFound as error: return generate_http_error_flask(404, error) return '', 200
def post(self): """ Create file replicas at a given RSE. .. :quickref: Replicas; create replicas at RSE :<json string rse: The RSE name. :<json list files: list of dicts with 'scope', 'name', 'bytes', 'meta' and 'adler32'. :<json bool ignore_availability: Flag to ignore the RSE blacklisting. :status 201: Replica Successfully created. :status 400: Invalid Path. :status 401: Invalid auth token. :status 404: RSE not found. :status 404: Scope not found. :status 409: Replica already exists. :status 409: DID already exists. :status 503: Resource Temporary Unavailable. """ parameters = json_parameters(parse_response) rse = param_get(parameters, 'rse') files = param_get(parameters, 'files') try: add_replicas( rse=rse, files=files, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'), ignore_availability=param_get(parameters, 'ignore_availability', default=False), ) except InvalidPath as error: return generate_http_error_flask(400, error) except AccessDenied as error: return generate_http_error_flask(401, error) except (Duplicate, DataIdentifierAlreadyExists) as error: return generate_http_error_flask(409, error) except (RSENotFound, ScopeNotFound) as error: return generate_http_error_flask(404, error) except ResourceTemporaryUnavailable as error: return generate_http_error_flask(503, error) return 'Created', 201