You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ar...@apache.org on 2017/09/04 15:53:07 UTC
[1/2] mesos git commit: Enabled filtering of resource allocations and
reservations in agent.
Repository: mesos
Updated Branches:
refs/heads/master dfe4fc84e -> b9aebbbca
Enabled filtering of resource allocations and reservations in agent.
Adds support of the `VIEW_ROLE` ACL to the results generated by the
`/state` endpoint in the agent for fields `reserved_resources_full`,
`reserved_resources` and `reserved_resources_allocated`.
Review: https://reviews.apache.org/r/60915/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/a931bfb1
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/a931bfb1
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/a931bfb1
Branch: refs/heads/master
Commit: a931bfb1bf06bb990664fabe78aae06d373db82e
Parents: dfe4fc8
Author: Andrei Budnik <ab...@mesosphere.com>
Authored: Mon Sep 4 17:09:32 2017 +0200
Committer: Alexander Rojas <al...@mesosphere.io>
Committed: Mon Sep 4 17:52:37 2017 +0200
----------------------------------------------------------------------
src/slave/http.cpp | 53 +++++++++++++++++++++++++++++++++++++++----------
1 file changed, 42 insertions(+), 11 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/a931bfb1/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 7fb0948..3ea7829 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -1231,6 +1231,7 @@ Future<Response> Http::state(
Future<Owned<ObjectApprover>> tasksApprover;
Future<Owned<ObjectApprover>> executorsApprover;
Future<Owned<ObjectApprover>> flagsApprover;
+ Future<Owned<ObjectApprover>> rolesApprover;
if (slave->authorizer.isSome()) {
Option<authorization::Subject> subject = createSubject(principal);
@@ -1246,23 +1247,29 @@ Future<Response> Http::state(
flagsApprover = slave->authorizer.get()->getObjectApprover(
subject, authorization::VIEW_FLAGS);
+
+ rolesApprover = slave->authorizer.get()->getObjectApprover(
+ subject, authorization::VIEW_ROLE);
} else {
frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
flagsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ rolesApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
}
return collect(
frameworksApprover,
tasksApprover,
executorsApprover,
- flagsApprover)
+ flagsApprover,
+ rolesApprover)
.then(defer(
slave->self(),
[this, request](const tuple<Owned<ObjectApprover>,
Owned<ObjectApprover>,
Owned<ObjectApprover>,
+ Owned<ObjectApprover>,
Owned<ObjectApprover>>& approvers)
-> Response {
// This lambda is consumed before the outer lambda
@@ -1273,10 +1280,12 @@ Future<Response> Http::state(
Owned<ObjectApprover> tasksApprover;
Owned<ObjectApprover> executorsApprover;
Owned<ObjectApprover> flagsApprover;
+ Owned<ObjectApprover> rolesApprover;
tie(frameworksApprover,
tasksApprover,
executorsApprover,
- flagsApprover) = approvers;
+ flagsApprover,
+ rolesApprover) = approvers;
writer->field("version", MESOS_VERSION);
@@ -1309,21 +1318,34 @@ Future<Response> Http::state(
const Resources& totalResources = slave->totalResources;
writer->field("resources", totalResources);
- writer->field("reserved_resources", totalResources.reservations());
+ writer->field(
+ "reserved_resources",
+ [&totalResources, &rolesApprover](JSON::ObjectWriter* writer) {
+ foreachpair (const string& role,
+ const Resources& resources,
+ totalResources.reservations()) {
+ if (approveViewRole(rolesApprover, role)) {
+ writer->field(role, resources);
+ }
+ }
+ });
+
writer->field("unreserved_resources", totalResources.unreserved());
writer->field(
"reserved_resources_full",
- [&totalResources](JSON::ObjectWriter* writer) {
+ [&totalResources, &rolesApprover](JSON::ObjectWriter* writer) {
foreachpair (const string& role,
const Resources& resources,
totalResources.reservations()) {
- writer->field(role, [&resources](JSON::ArrayWriter* writer) {
- foreach (Resource resource, resources) {
- convertResourceFormat(&resource, ENDPOINT);
- writer->element(JSON::Protobuf(resource));
- }
- });
+ if (approveViewRole(rolesApprover, role)) {
+ writer->field(role, [&resources](JSON::ArrayWriter* writer) {
+ foreach (Resource resource, resources) {
+ convertResourceFormat(&resource, ENDPOINT);
+ writer->element(JSON::Protobuf(resource));
+ }
+ });
+ }
}
});
@@ -1345,7 +1367,16 @@ Future<Response> Http::state(
}
writer->field(
- "reserved_resources_allocated", allocatedResources.reservations());
+ "reserved_resources_allocated",
+ [&allocatedResources, &rolesApprover](JSON::ObjectWriter* writer) {
+ foreachpair (const string& role,
+ const Resources& resources,
+ allocatedResources.reservations()) {
+ if (approveViewRole(rolesApprover, role)) {
+ writer->field(role, resources);
+ }
+ }
+ });
writer->field(
"unreserved_resources_allocated", allocatedResources.unreserved());
[2/2] mesos git commit: Added test to verify filtering of resource
reservations & allocations.
Posted by ar...@apache.org.
Added test to verify filtering of resource reservations & allocations.
This patch extends `SlaveAuthorizerTest.FilterStateEndpoint` test to
check that agent's `/state` endpoint shows resource reservations and
allocations based on the `VIEW_ROLE` permissions of the principal
doing the request.
Review: https://reviews.apache.org/r/61666/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/b9aebbbc
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/b9aebbbc
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/b9aebbbc
Branch: refs/heads/master
Commit: b9aebbbca755eafae0480193c172dc67e90cc547
Parents: a931bfb
Author: Andrei Budnik <ab...@mesosphere.com>
Authored: Mon Sep 4 17:09:51 2017 +0200
Committer: Alexander Rojas <al...@mesosphere.io>
Committed: Mon Sep 4 17:52:52 2017 +0200
----------------------------------------------------------------------
src/tests/slave_authorization_tests.cpp | 282 +++++++++++++++++++++------
1 file changed, 227 insertions(+), 55 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/b9aebbbc/src/tests/slave_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_authorization_tests.cpp b/src/tests/slave_authorization_tests.cpp
index 536a8ef..30eceae 100644
--- a/src/tests/slave_authorization_tests.cpp
+++ b/src/tests/slave_authorization_tests.cpp
@@ -101,12 +101,19 @@ TYPED_TEST_CASE(SlaveAuthorizerTest, AuthorizerTypes);
// This test verifies that authorization based endpoint filtering
// works correctly on the /state endpoint.
-// Both default users are allowed to to view high level frameworks, but only
+// Both default users are allowed to view high level frameworks, but only
// one is allowed to view the tasks.
+// After launching a single task per each framework, one for role "superhero"
+// and the other for role "muggle", this test verifies that each of two
+// default users can view resource allocations and resource reservations for
+// corresponding allowed roles only.
TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
{
ACLs acls;
+ const string roleSuperhero = "superhero";
+ const string roleMuggle = "muggle";
+
{
// Default principal can see all frameworks.
mesos::ACL::ViewFramework* acl = acls.add_view_frameworks();
@@ -156,6 +163,28 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
acl->mutable_users()->set_type(ACL::Entity::NONE);
}
+ {
+ // Default principal can view "superhero" role only.
+ ACL::ViewRole* acl = acls.add_view_roles();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+ acl->mutable_roles()->add_values(roleSuperhero);
+
+ acl = acls.add_view_roles();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+ acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE);
+ }
+
+ {
+ // Second default principal can view "muggle" role only.
+ ACL::ViewRole* acl = acls.add_view_roles();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_roles()->add_values(roleMuggle);
+
+ acl = acls.add_view_roles();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE);
+ }
+
// Create an `Authorizer` with the ACLs.
Try<Authorizer*> create = TypeParam::create(parameterize(acls));
ASSERT_SOME(create);
@@ -164,69 +193,146 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
Try<Owned<cluster::Master>> master = this->StartMaster(authorizer.get());
ASSERT_SOME(master);
- // Register framework with user "bar".
- FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
- frameworkInfo.set_role("role");
- frameworkInfo.set_user("bar");
+ // Register framework with user "bar" and role "superhero".
+ FrameworkInfo frameworkSuperhero = DEFAULT_FRAMEWORK_INFO;
+ frameworkSuperhero.set_name("framework-" + roleSuperhero);
+ frameworkSuperhero.set_role(roleSuperhero);
+ frameworkSuperhero.set_user("bar");
// Create an executor with user "bar".
- ExecutorInfo executor = createExecutorInfo("test-executor", "sleep 2");
- executor.mutable_command()->set_user("bar");
-
- MockExecutor exec(executor.executor_id());
- TestContainerizer containerizer(&exec);
+ ExecutorInfo executorSuperhero =
+ createExecutorInfo("test-executor-" + roleSuperhero, "sleep 2");
+ executorSuperhero.mutable_command()->set_user("bar");
+ MockExecutor execSuperhero(executorSuperhero.executor_id());
+
+ // Register framework with user "foo" and role "muggle".
+ FrameworkInfo frameworkMuggle = DEFAULT_FRAMEWORK_INFO;
+ frameworkMuggle.set_name("framework-" + roleMuggle);
+ frameworkMuggle.set_principal(DEFAULT_CREDENTIAL_2.principal());
+ frameworkMuggle.set_role(roleMuggle);
+ frameworkMuggle.set_user("foo");
+
+ // Create an executor with user "foo".
+ ExecutorInfo executorMuggle =
+ createExecutorInfo("test-executor-" + roleMuggle, "sleep 2");
+ executorMuggle.mutable_command()->set_user("foo");
+ MockExecutor execMuggle(executorMuggle.executor_id());
+
+ TestContainerizer containerizer(
+ {{executorSuperhero.executor_id(), &execSuperhero},
+ {executorMuggle.executor_id(), &execMuggle}});
+
+ slave::Flags flags = this->CreateSlaveFlags();
+ // Statically reserve resources for each role.
+ flags.resources = "cpus(" + roleSuperhero + "):2;" + "cpus(" + roleMuggle +
+ "):3;mem(" + roleSuperhero + "):512;" + "mem(" + roleMuggle + "):1024;";
Owned<MasterDetector> detector = master.get()->createDetector();
- Try<Owned<cluster::Slave>> slave =
- this->StartSlave(detector.get(), &containerizer, authorizer.get());
+ Try<Owned<cluster::Slave>> slave = this->StartSlave(
+ detector.get(), &containerizer, authorizer.get(), flags);
ASSERT_SOME(slave);
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL);
+ MockScheduler schedSuperhero;
+ MesosSchedulerDriver driverSuperhero(
+ &schedSuperhero,
+ frameworkSuperhero,
+ master.get()->pid,
+ DEFAULT_CREDENTIAL);
- EXPECT_CALL(exec, registered(_, _, _, _))
+ EXPECT_CALL(execSuperhero, registered(_, _, _, _))
.Times(AtMost(1));
- Future<Nothing> registered;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureSatisfy(®istered))
+ Future<FrameworkID> frameworkIdSuperhero;
+ EXPECT_CALL(schedSuperhero, registered(&driverSuperhero, _, _))
+ .WillOnce(FutureArg<1>(&frameworkIdSuperhero));
+
+ Future<vector<Offer>> offersSuperhero;
+ EXPECT_CALL(schedSuperhero, resourceOffers(&driverSuperhero, _))
+ .WillOnce(FutureArg<1>(&offersSuperhero))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driverSuperhero.start();
+
+ AWAIT_READY(frameworkIdSuperhero);
+
+ AWAIT_READY(offersSuperhero);
+ EXPECT_FALSE(offersSuperhero->empty());
+
+ // Define a task which will run on executorSuperhero of frameworkSuperhero.
+ TaskInfo taskSuperhero;
+ taskSuperhero.set_name("test-" + roleSuperhero);
+ taskSuperhero.mutable_task_id()->set_value("1");
+ taskSuperhero.mutable_slave_id()->MergeFrom(
+ offersSuperhero.get()[0].slave_id());
+ taskSuperhero.mutable_resources()->MergeFrom(
+ offersSuperhero.get()[0].resources());
+ taskSuperhero.mutable_executor()->MergeFrom(executorSuperhero);
+
+ EXPECT_CALL(execSuperhero, launchTask(_, _))
+ .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING))
.WillRepeatedly(Return());
- Future<vector<Offer>> offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
+ Future<TaskStatus> statusSuperhero;
+ EXPECT_CALL(schedSuperhero, statusUpdate(&driverSuperhero, _))
+ .WillOnce(FutureArg<1>(&statusSuperhero));
+
+ driverSuperhero.launchTasks(offersSuperhero.get()[0].id(), {taskSuperhero});
+
+ AWAIT_READY(statusSuperhero);
+ EXPECT_EQ(TASK_RUNNING, statusSuperhero->state());
+
+ MockScheduler schedMuggle;
+ MesosSchedulerDriver driverMuggle(
+ &schedMuggle,
+ frameworkMuggle,
+ master.get()->pid,
+ DEFAULT_CREDENTIAL_2);
+
+ EXPECT_CALL(execMuggle, registered(_, _, _, _))
+ .Times(AtMost(1));
+
+ Future<FrameworkID> frameworkIdMuggle;
+ EXPECT_CALL(schedMuggle, registered(&driverMuggle, _, _))
+ .WillOnce(FutureArg<1>(&frameworkIdMuggle));
+
+ Future<vector<Offer>> offersMuggle;
+ EXPECT_CALL(schedMuggle, resourceOffers(&driverMuggle, _))
+ .WillOnce(FutureArg<1>(&offersMuggle))
.WillRepeatedly(Return()); // Ignore subsequent offers.
- driver.start();
+ driverMuggle.start();
- AWAIT_READY(registered);
+ AWAIT_READY(frameworkIdMuggle);
- AWAIT_READY(offers);
- EXPECT_FALSE(offers->empty());
+ AWAIT_READY(offersMuggle);
+ EXPECT_FALSE(offersMuggle->empty());
- TaskInfo task;
- task.set_name("test");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->MergeFrom(offers.get()[0].slave_id());
- task.mutable_resources()->MergeFrom(offers.get()[0].resources());
- task.mutable_executor()->MergeFrom(executor);
+ // Define a task which will run on executorMuggle of frameworkMuggle.
+ TaskInfo taskMuggle;
+ taskMuggle.set_name("test-" + roleMuggle);
+ taskMuggle.mutable_task_id()->set_value("2");
+ taskMuggle.mutable_slave_id()->MergeFrom(
+ offersMuggle.get()[0].slave_id());
+ taskMuggle.mutable_resources()->MergeFrom(
+ offersMuggle.get()[0].resources());
+ taskMuggle.mutable_executor()->MergeFrom(executorMuggle);
- EXPECT_CALL(exec, launchTask(_, _))
+ EXPECT_CALL(execMuggle, launchTask(_, _))
.WillOnce(SendStatusUpdateFromTask(TASK_RUNNING))
.WillRepeatedly(Return());
- Future<TaskStatus> status;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status));
+ Future<TaskStatus> statusMuggle;
+ EXPECT_CALL(schedMuggle, statusUpdate(&driverMuggle, _))
+ .WillOnce(FutureArg<1>(&statusMuggle));
- driver.launchTasks(offers.get()[0].id(), {task});
+ driverMuggle.launchTasks(offersMuggle.get()[0].id(), {taskMuggle});
- AWAIT_READY(status);
- EXPECT_EQ(TASK_RUNNING, status->state());
+ AWAIT_READY(statusMuggle);
+ ASSERT_EQ(TASK_RUNNING, statusMuggle->state());
- // Retrieve endpoint with the user allowed to view the framework.
+ // Retrieve endpoint with the user allowed to view the frameworks.
+ // The default user allowed to view role "superhero" only.
{
Future<Response> response = http::get(
slave.get()->pid,
@@ -245,20 +351,50 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>());
JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>();
- EXPECT_EQ(1u, frameworks.values.size());
+ EXPECT_EQ(2u, frameworks.values.size());
+
+ foreach (const JSON::Value& value, frameworks.values) {
+ JSON::Object framework = value.as<JSON::Object>();
+ EXPECT_FALSE(framework.values.empty());
+ ASSERT_TRUE(framework.values["executors"].is<JSON::Array>());
- JSON::Object framework = frameworks.values.front().as<JSON::Object>();
- ASSERT_TRUE(framework.values["executors"].is<JSON::Array>());
+ JSON::Array executors = framework.values["executors"].as<JSON::Array>();
+ EXPECT_EQ(1u, executors.values.size());
+
+ JSON::Object executor = executors.values.front().as<JSON::Object>();
+ EXPECT_EQ(1u, executor.values["tasks"].as<JSON::Array>().values.size());
+ }
- JSON::Array executors = framework.values["executors"].as<JSON::Array>();
- EXPECT_EQ(1u, executors.values.size());
+ ASSERT_TRUE(state.values["reserved_resources"].is<JSON::Object>());
- JSON::Object executor = executors.values.front().as<JSON::Object>();
- EXPECT_EQ(1u, executor.values["tasks"].as<JSON::Array>().values.size());
+ JSON::Object reserved_resources =
+ state.values["reserved_resources"].as<JSON::Object>();
+ EXPECT_TRUE(reserved_resources.values[roleSuperhero].is<JSON::Object>());
+ EXPECT_FALSE(reserved_resources.values[roleMuggle].is<JSON::Object>());
+
+ ASSERT_TRUE(
+ state.values["reserved_resources_allocated"].is<JSON::Object>());
+
+ JSON::Object reserved_resources_allocated =
+ state.values["reserved_resources_allocated"].as<JSON::Object>();
+ EXPECT_TRUE(
+ reserved_resources_allocated.values[roleSuperhero].is<JSON::Object>());
+ EXPECT_FALSE(
+ reserved_resources_allocated.values[roleMuggle].is<JSON::Object>());
+
+ ASSERT_TRUE(state.values["reserved_resources_full"].is<JSON::Object>());
+
+ JSON::Object reserved_resources_full =
+ state.values["reserved_resources_full"].as<JSON::Object>();
+ EXPECT_TRUE(
+ reserved_resources_full.values[roleSuperhero].is<JSON::Array>());
+ EXPECT_FALSE(
+ reserved_resources_full.values[roleMuggle].is<JSON::Array>());
}
- // Retrieve endpoint with the user allowed to view the framework,
- // but not the executor.
+ // Retrieve endpoint with the user allowed to view the frameworks,
+ // but not the executors.
+ // The second default user allowed to view role "muggle" only.
{
Future<Response> response = http::get(
slave.get()->pid,
@@ -276,17 +412,53 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>());
JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>();
- EXPECT_EQ(1u, frameworks.values.size());
+ EXPECT_EQ(2u, frameworks.values.size());
- JSON::Object framework = frameworks.values.front().as<JSON::Object>();
- EXPECT_TRUE(framework.values["executors"].as<JSON::Array>().values.empty());
+ foreach (const JSON::Value& value, frameworks.values) {
+ JSON::Object framework = value.as<JSON::Object>();
+ EXPECT_FALSE(framework.values.empty());
+ EXPECT_TRUE(
+ framework.values["executors"].as<JSON::Array>().values.empty());
}
- EXPECT_CALL(exec, shutdown(_))
+ ASSERT_TRUE(state.values["reserved_resources"].is<JSON::Object>());
+
+ JSON::Object reserved_resources =
+ state.values["reserved_resources"].as<JSON::Object>();
+ EXPECT_TRUE(reserved_resources.values[roleMuggle].is<JSON::Object>());
+ EXPECT_FALSE(reserved_resources.values[roleSuperhero].is<JSON::Object>());
+
+ ASSERT_TRUE(
+ state.values["reserved_resources_allocated"].is<JSON::Object>());
+
+ JSON::Object reserved_resources_allocated =
+ state.values["reserved_resources_allocated"].as<JSON::Object>();
+ EXPECT_TRUE(
+ reserved_resources_allocated.values[roleMuggle].is<JSON::Object>());
+ EXPECT_FALSE(
+ reserved_resources_allocated.values[roleSuperhero].is<JSON::Object>());
+
+ ASSERT_TRUE(state.values["reserved_resources_full"].is<JSON::Object>());
+
+ JSON::Object reserved_resources_full =
+ state.values["reserved_resources_full"].as<JSON::Object>();
+ EXPECT_TRUE(
+ reserved_resources_full.values[roleMuggle].is<JSON::Array>());
+ EXPECT_FALSE(
+ reserved_resources_full.values[roleSuperhero].is<JSON::Array>());
+ }
+
+ EXPECT_CALL(execSuperhero, shutdown(_))
.Times(AtMost(1));
- driver.stop();
- driver.join();
+ EXPECT_CALL(execMuggle, shutdown(_))
+ .Times(AtMost(1));
+
+ driverSuperhero.stop();
+ driverSuperhero.join();
+
+ driverMuggle.stop();
+ driverMuggle.join();
}