You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by mz...@apache.org on 2019/07/23 22:04:44 UTC
[mesos] 03/06: Added limits validation for `UPDATE_QUOTA` call.
This is an automated email from the ASF dual-hosted git repository.
mzhu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git
commit 2462cfbc831b1fdb9905904eeab586be570cae6d
Author: Meng Zhu <mz...@mesosphere.io>
AuthorDate: Thu Jul 11 16:45:08 2019 -0700
Added limits validation for `UPDATE_QUOTA` call.
The validation checks the newly requested limits against
its current consumption. If a role's consumption is already
more than the limits, the update request will be denied
(unless force flag is set to true).
Also added a test.
Review: https://reviews.apache.org/r/71061
---
src/master/quota_handler.cpp | 25 +++++++++-
src/tests/master_quota_tests.cpp | 100 +++++++++++++++++++++++++++++++++++++++
2 files changed, 123 insertions(+), 2 deletions(-)
diff --git a/src/master/quota_handler.cpp b/src/master/quota_handler.cpp
index 2a3ca56..4388fe6 100644
--- a/src/master/quota_handler.cpp
+++ b/src/master/quota_handler.cpp
@@ -466,9 +466,30 @@ Future<http::Response> Master::QuotaHandler::update(
}
}
- // TODO(mzhu): Validate a role's limit is below its current consumption
+ // Validate a role's requested limit is below its current consumption
// (otherwise a `force` flag is needed).
- //
+ foreach (const auto& config, configs) {
+ ResourceLimits limits{config.limits()};
+ ResourceQuantities consumedQuota =
+ RoleResourceBreakdown(master, config.role()).consumedQuota();
+
+ if (!limits.contains(consumedQuota)) {
+ if (call.update_quota().force()) {
+ LOG(INFO) << "Updating '" << config.role() << "' quota limit to"
+ << " '" + stringify(limits) + "';"
+ << " this is below its current quota consumption"
+ << " '" + stringify(consumedQuota) + "';"
+ << " Ignored violation since the force flag is provided.";
+ } else {
+ return BadRequest("Invalid QuotaConfig: Role '" + config.role() + "'"
+ " is already consuming '" + stringify(consumedQuota) + "';"
+ " this is more than the requested limits"
+ " '" + stringify(limits) + "'"
+ " (use 'force' flag to bypass this check)");
+ }
+ }
+ }
+
// TODO(mzhu): Pull out these validation in a function that can be shared
// between this and the old handlers.
diff --git a/src/tests/master_quota_tests.cpp b/src/tests/master_quota_tests.cpp
index 8105765..58ecc50 100644
--- a/src/tests/master_quota_tests.cpp
+++ b/src/tests/master_quota_tests.cpp
@@ -1401,6 +1401,106 @@ TEST_F(MasterQuotaTest, AvailableResourcesMultipleAgents)
}
+// This test ensures that quota limits update request is validated
+// against the roles current quota consumption. A role's current
+// consumption must not exceed the requested limits otherwise
+// the update will not be granted. A force flag can override the check.
+TEST_F(MasterQuotaTest, ValidateLimitAgainstConsumed)
+{
+ TestAllocator<> allocator;
+ EXPECT_CALL(allocator, initialize(_, _, _));
+
+ Try<Owned<cluster::Master>> master = StartMaster(&allocator);
+ ASSERT_SOME(master);
+
+ // Start an agent and allocate all its resources to a task.
+ slave::Flags flags = CreateSlaveFlags();
+ flags.resources = "cpus:2;mem:1024";
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+ frameworkInfo.set_roles(0, ROLE1);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ ExecutorInfo executorInfo = createExecutorInfo("dummy", "sleep 3600");
+
+ Future<Nothing> taskLaunched;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(DoAll(
+ LaunchTasks(executorInfo, 1, 2, 1024, ROLE1),
+ FutureSatisfy(&taskLaunched)));
+
+ EXPECT_CALL(sched, statusUpdate(&driver, _)).WillRepeatedly(Return());
+
+ driver.start();
+
+ AWAIT_READY(taskLaunched);
+
+ // Now we have:
+ // - Allocated resources: cpus:2;mem:1024
+
+ // Request a limit of 1 cpu which will be rejected since `ROLE1`
+ // is already consuming 2 cpus.
+ {
+ process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Content-Type"] = "application/json";
+
+ Future<Response> response = process::http::post(
+ master.get()->pid,
+ "/api/v1",
+ headers,
+ createUpdateQuotaRequestBody(createQuotaConfig(ROLE1, "", "cpus:1")));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response);
+
+ EXPECT_EQ(
+ "Invalid QuotaConfig: Role 'role1' is already consuming"
+ " 'cpus:2; mem:1024'; this is more than the requested limits"
+ " 'cpus:1' (use 'force' flag to bypass this check)",
+ response->body);
+ }
+
+ // Request a limit of 2 cpus is fine.
+ {
+ process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Content-Type"] = "application/json";
+
+ Future<Response> response = process::http::post(
+ master.get()->pid,
+ "/api/v1",
+ headers,
+ createUpdateQuotaRequestBody(createQuotaConfig(ROLE1, "", "cpus:2")));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+ }
+
+ // Force request a limit of 1 cpu. This will be granted.
+ {
+ process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Content-Type"] = "application/json";
+
+ Future<Response> response = process::http::post(
+ master.get()->pid,
+ "/api/v1",
+ headers,
+ createUpdateQuotaRequestBody(
+ createQuotaConfig(ROLE1, "", "cpus:1"), true));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+ }
+}
+
+
// Checks that a quota request succeeds if there are sufficient total
// resources in the cluster, even though they are blocked in outstanding
// offers, i.e. quota request rescinds offers.