Future<http::Response> Master::QuotaHandler::remove( const http::Request& request, const Option<string>& principal) const { VLOG(1) << "Removing quota for request path: '" << request.url.path << "'"; // Check that the request type is DELETE which is guaranteed by the master. CHECK_EQ("DELETE", request.method); // Extract role from url. vector<string> tokens = strings::tokenize(request.url.path, "/"); // Check that there are exactly 3 parts: {master,quota,'role'}. if (tokens.size() != 3u) { return BadRequest( "Failed to parse request path '" + request.url.path + "': 3 tokens ('master', 'quota', 'role') required, found " + stringify(tokens.size()) + " token(s)"); } // Check that "quota" is the second to last token. if (tokens.end()[-2] != "quota") { return BadRequest( "Failed to parse request path '" + request.url.path + "': Missing 'quota' endpoint"); } const string& role = tokens.back(); // Check that the role is on the role whitelist, if it exists. if (!master->isWhitelistedRole(role)) { return BadRequest( "Failed to validate remove quota request for path '" + request.url.path +"': Unknown role '" + role + "'"); } // Check that we are removing an existing quota. if (!master->quotas.contains(role)) { return BadRequest( "Failed to remove quota for path '" + request.url.path + "': Role '" + role + "' has no quota set"); } Option<string> quota_principal = master->quotas[role].info.has_principal() ? master->quotas[role].info.principal() : Option<string>::none(); return authorizeRemoveQuota(principal, quota_principal) .then(defer(master->self(), [=](bool authorized) -> Future<http::Response> { if (!authorized) { return Forbidden(); } return _remove(role); })); }
Future<http::Response> Master::QuotaHandler::set( const http::Request& request, const Option<string>& principal) const { VLOG(1) << "Setting quota from request: '" << request.body << "'"; // Check that the request type is POST which is guaranteed by the master. CHECK_EQ("POST", request.method); // Validate request and extract JSON. // TODO(alexr): Create a type (e.g. a protobuf) for the request JSON. If we // move the `force` field out of the request JSON, we can reuse `QuotaInfo`. Try<JSON::Object> parse = JSON::parse<JSON::Object>(request.body); if (parse.isError()) { return BadRequest( "Failed to parse set quota request JSON '" + request.body + "': " + parse.error()); } // Extract role from the request JSON. Result<JSON::String> roleJSON = parse.get().find<JSON::String>("role"); if (roleJSON.isError()) { // An `Error` usually indicates that a search string is malformed // (which is not the case here), however it may also indicate that // the `role` field is not a string. return BadRequest( "Failed to extract 'role' from set quota request JSON '" + request.body + "': " + roleJSON.error()); } if (roleJSON.isNone()) { return BadRequest( "Failed to extract 'role' from set quota request JSON '" + request.body + "': Field is missing"); } string role = roleJSON.get().value; // Extract resources from the request JSON. Result<JSON::Array> resourcesJSON = parse.get().find<JSON::Array>("resources"); if (resourcesJSON.isError()) { // An `Error` usually indicates that a search string is malformed // (which is not the case here), however it may also indicate that // the `resources` field is not an array. return BadRequest( "Failed to extract 'resources' from set quota request JSON '" + request.body + "': " + resourcesJSON.error()); } if (resourcesJSON.isNone()) { return BadRequest( "Failed to extract 'resources' from set quota request JSON '" + request.body + "': Field is missing"); } // Create protobuf representation of resources. Try<RepeatedPtrField<Resource>> resources = ::protobuf::parse<RepeatedPtrField<Resource>>(resourcesJSON.get()); if (resources.isError()) { return BadRequest( "Failed to parse 'resources' from set quota request JSON '" + request.body + "': " + resources.error()); } // Create the `QuotaInfo` protobuf message from the request JSON. Try<QuotaInfo> create = createQuotaInfo(role, resources.get()); if (create.isError()) { return BadRequest( "Failed to create 'QuotaInfo' from set quota request JSON '" + request.body + "': " + create.error()); } QuotaInfo quotaInfo = create.get(); // Check that the `QuotaInfo` is a valid quota request. Option<Error> validateError = quota::validation::quotaInfo(quotaInfo); if (validateError.isSome()) { return BadRequest( "Failed to validate set quota request JSON '" + request.body + "': " + validateError.get().message); } // Check that the role is on the role whitelist, if it exists. if (!master->isWhitelistedRole(quotaInfo.role())) { return BadRequest( "Failed to validate set quota request JSON '" + request.body + "': Unknown role '" + quotaInfo.role() + "'"); } // Check that we are not updating an existing quota. // TODO(joerg84): Update error message once quota update is in place. if (master->quotas.contains(quotaInfo.role())) { return BadRequest( "Failed to validate set quota request JSON '" + request.body + "': Can not set quota for a role that already has quota"); } // The force flag can be used to overwrite the `capacityHeuristic` check. Result<JSON::Boolean> force = parse.get().find<JSON::Boolean>("force"); if (force.isError()) { // An `Error` usually indicates that a search string is malformed // (which is not the case here), however it may also indicate that // the `force` field is not a boolean. return BadRequest( "Failed to extract 'force' from set quota request JSON '" + request.body + "': " + force.error()); } const bool forced = force.isSome() ? force.get().value : false; if (principal.isSome()) { quotaInfo.set_principal(principal.get()); } return authorizeSetQuota(principal, quotaInfo.role()) .then(defer(master->self(), [=](bool authorized) -> Future<http::Response> { if (!authorized) { return Forbidden(); } return _set(quotaInfo, forced); })); }