You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by vi...@apache.org on 2014/06/11 01:33:51 UTC
[6/7] git commit: Added MockAuthorizer and more authorization tests.
Added MockAuthorizer and more authorization tests.
Review: https://reviews.apache.org/r/22190
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/c4084554
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/c4084554
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/c4084554
Branch: refs/heads/vinod/authorize_tasks
Commit: c4084554e8245397c168d44d0e54a1c2491ca3a8
Parents: a6c4ee7
Author: Vinod Kone <vi...@twitter.com>
Authored: Mon Jun 2 22:20:11 2014 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Tue Jun 10 16:33:36 2014 -0700
----------------------------------------------------------------------
src/tests/cluster.hpp | 44 ++-
src/tests/master_authorization_tests.cpp | 396 +++++++++++++++++++++++++-
src/tests/mesos.cpp | 24 ++
src/tests/mesos.hpp | 46 +++
4 files changed, 502 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/c4084554/src/tests/cluster.hpp
----------------------------------------------------------------------
diff --git a/src/tests/cluster.hpp b/src/tests/cluster.hpp
index 449165c..1c96ee7 100644
--- a/src/tests/cluster.hpp
+++ b/src/tests/cluster.hpp
@@ -98,12 +98,25 @@ public:
Try<process::PID<master::Master> > start(
const master::Flags& flags = master::Flags());
+ // Start and manage a new master using the specified allocator
+ // process.
+ Try<process::PID<master::Master> > start(
+ master::allocator::AllocatorProcess* allocatorProcess,
+ const master::Flags& flags = master::Flags());
+
+ // Start and manage a new master using the specified authorizer.
+ Try<process::PID<master::Master> > start(
+ Authorizer* authorizer,
+ const master::Flags& flags = master::Flags());
+
// Start and manage a new master using the specified flags.
- // An allocator process may be specified in which case it will outlive
- // the launched master. If no allocator process is specified then
- // the default allocator will be instantiated.
+ // An allocator process or authorizer may be specified in which
+ // case it will outlive the launched master. If either allocator
+ // process or authorizer is not specified then the default
+ // allocator or authorizer will be used.
Try<process::PID<master::Master> > start(
const Option<master::allocator::AllocatorProcess*>& allocatorProcess,
+ const Option<Authorizer*>& authorizer,
const master::Flags& flags = master::Flags());
// Stops and cleans up a master at the specified PID.
@@ -272,12 +285,29 @@ inline void Cluster::Masters::shutdown()
inline Try<process::PID<master::Master> > Cluster::Masters::start(
const master::Flags& flags)
{
- return start(None(), flags);
+ return start(None(), None(), flags);
+}
+
+
+inline Try<process::PID<master::Master> > Cluster::Masters::start(
+ master::allocator::AllocatorProcess* allocator,
+ const master::Flags& flags)
+{
+ return start(allocator, None(), flags);
+}
+
+
+inline Try<process::PID<master::Master> > Cluster::Masters::start(
+ Authorizer* authorizer,
+ const master::Flags& flags)
+{
+ return start(None(), authorizer, flags);
}
inline Try<process::PID<master::Master> > Cluster::Masters::start(
const Option<master::allocator::AllocatorProcess*>& allocatorProcess,
+ const Option<Authorizer*>& authorizer,
const master::Flags& flags)
{
// Disallow multiple masters when not using ZooKeeper.
@@ -354,7 +384,9 @@ inline Try<process::PID<master::Master> > Cluster::Masters::start(
master.detector = new StandaloneMasterDetector();
}
- if (flags.acls.isSome()) {
+ if (authorizer.isSome()) {
+ CHECK_NOTNULL(authorizer.get());
+ } else if (flags.acls.isSome()) {
Try<process::Owned<Authorizer> > authorizer_ =
Authorizer::create(flags.acls.get());
if (authorizer_.isError()) {
@@ -372,7 +404,7 @@ inline Try<process::PID<master::Master> > Cluster::Masters::start(
&cluster->files,
master.contender,
master.detector,
- master.authorizer,
+ authorizer.isSome() ? authorizer : master.authorizer,
flags);
if (url.isNone()) {
http://git-wip-us.apache.org/repos/asf/mesos/blob/c4084554/src/tests/master_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_authorization_tests.cpp b/src/tests/master_authorization_tests.cpp
index 17debaf..3a28ca2 100644
--- a/src/tests/master_authorization_tests.cpp
+++ b/src/tests/master_authorization_tests.cpp
@@ -31,6 +31,7 @@
#include <stout/gtest.hpp>
#include <stout/try.hpp>
+#include "master/allocator.hpp"
#include "master/master.hpp"
#include "messages/messages.hpp"
@@ -45,16 +46,21 @@ using namespace mesos::internal::tests;
using mesos::internal::master::Master;
+using mesos::internal::master::allocator::AllocatorProcess;
+
using mesos::internal::slave::Slave;
using process::Clock;
using process::Future;
using process::PID;
+using process::Promise;
using std::vector;
using testing::_;
+using testing::An;
using testing::AtMost;
+using testing::DoAll;
using testing::Return;
@@ -64,9 +70,8 @@ class MasterAuthorizationTest : public MesosTest {};
// This test verifies that an authorized task launch is successful.
TEST_F(MasterAuthorizationTest, AuthorizedTask)
{
- // Setup ACLs so that the framework can only launch tasks as "foo".
+ // Setup ACLs so that the framework can launch tasks as "foo".
ACLs acls;
- acls.set_permissive(false);
mesos::ACL::RunTasks* acl = acls.add_run_tasks();
acl->mutable_principals()->add_values(DEFAULT_FRAMEWORK_INFO.principal());
acl->mutable_users()->add_values("foo");
@@ -207,3 +212,390 @@ TEST_F(MasterAuthorizationTest, UnauthorizedTask)
Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
}
+
+
+// This test verifies that a 'killTask()' that comes before
+// '_launchTasks()' is called results in TASK_KILLED.
+TEST_F(MasterAuthorizationTest, KillTask)
+{
+ MockAuthorizer authorizer;
+ Try<PID<Master> > master = StartMaster(&authorizer);
+ ASSERT_SOME(master);
+
+ MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+ Try<PID<Slave> > slave = StartSlave(&exec);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .Times(1);
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task = createTask(offers.get()[0], "", DEFAULT_EXECUTOR_ID);
+ vector<TaskInfo> tasks;
+ tasks.push_back(task);
+
+ // Return a pending future from authorizer.
+ Future<Nothing> future;
+ Promise<bool> promise;
+ EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>()))
+ .WillOnce(DoAll(FutureSatisfy(&future),
+ Return(promise.future())));
+
+ driver.launchTasks(offers.get()[0].id(), tasks);
+
+ // Wait until authorization is in progress.
+ AWAIT_READY(future);
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status));
+
+ // Now kill the task.
+ driver.killTask(task.task_id());
+
+ // Framework should get a TASK_KILLED right away.
+ AWAIT_READY(status);
+ EXPECT_EQ(TASK_KILLED, status.get().state());
+
+ Future<Nothing> resourcesUnused =
+ FUTURE_DISPATCH(_, &AllocatorProcess::resourcesUnused);
+
+ // Now complete authorization.
+ promise.set(true);
+
+ // No task launch should happen resulting in all resources being
+ // returned to the allocator.
+ AWAIT_READY(resourcesUnused);
+
+ driver.stop();
+ driver.join();
+
+ Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
+
+
+// This test verifies that a slave removal that comes before
+// '_launchTasks()' is called results in TASK_LOST.
+TEST_F(MasterAuthorizationTest, SlaveRemoved)
+{
+ MockAuthorizer authorizer;
+ Try<PID<Master> > master = StartMaster(&authorizer);
+ ASSERT_SOME(master);
+
+ MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+ Try<PID<Slave> > slave = StartSlave(&exec);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .Times(1);
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task = createTask(offers.get()[0], "", DEFAULT_EXECUTOR_ID);
+ vector<TaskInfo> tasks;
+ tasks.push_back(task);
+
+ // Return a pending future from authorizer.
+ Future<Nothing> future;
+ Promise<bool> promise;
+ EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>()))
+ .WillOnce(DoAll(FutureSatisfy(&future),
+ Return(promise.future())));
+
+ driver.launchTasks(offers.get()[0].id(), tasks);
+
+ // Wait until authorization is in progress.
+ AWAIT_READY(future);
+
+ Future<Nothing> slaveLost;
+ EXPECT_CALL(sched, slaveLost(&driver, _))
+ .WillOnce(FutureSatisfy(&slaveLost));
+
+ // Now stop the slave.
+ Stop(slave.get());
+
+ AWAIT_READY(slaveLost);
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status));
+
+ Future<Nothing> resourcesRecovered =
+ FUTURE_DISPATCH(_, &AllocatorProcess::resourcesRecovered);
+
+ // Now complete authorization.
+ promise.set(true);
+
+ // Framework should get a TASK_LOST.
+ AWAIT_READY(status);
+ EXPECT_EQ(TASK_LOST, status.get().state());
+
+ // No task launch should happen resulting in all resources being
+ // returned to the allocator.
+ AWAIT_READY(resourcesRecovered);
+
+ driver.stop();
+ driver.join();
+
+ Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
+
+
+// This test verifies that a slave disconnection that comes before
+// '_launchTasks()' is called results in TASK_LOST.
+TEST_F(MasterAuthorizationTest, SlaveDisconnected)
+{
+ MockAuthorizer authorizer;
+ Try<PID<Master> > master = StartMaster(&authorizer);
+ ASSERT_SOME(master);
+
+ MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+ // Create a checkpointing slave so that a disconnected slave is not
+ // immediately removed.
+ slave::Flags flags = CreateSlaveFlags();
+ flags.checkpoint = true;
+ Try<PID<Slave> > slave = StartSlave(&exec, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .Times(1);
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task = createTask(offers.get()[0], "", DEFAULT_EXECUTOR_ID);
+ vector<TaskInfo> tasks;
+ tasks.push_back(task);
+
+ // Return a pending future from authorizer.
+ Future<Nothing> future;
+ Promise<bool> promise;
+ EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>()))
+ .WillOnce(DoAll(FutureSatisfy(&future),
+ Return(promise.future())));
+
+ driver.launchTasks(offers.get()[0].id(), tasks);
+
+ // Wait until authorization is in progress.
+ AWAIT_READY(future);
+
+ EXPECT_CALL(sched, slaveLost(&driver, _))
+ .Times(AtMost(1));
+
+ Future<Nothing> slaveDisconnected =
+ FUTURE_DISPATCH(_, &AllocatorProcess::slaveDisconnected);
+
+ // Now stop the slave.
+ Stop(slave.get());
+
+ AWAIT_READY(slaveDisconnected);
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status));
+
+ Future<Nothing> resourcesRecovered =
+ FUTURE_DISPATCH(_, &AllocatorProcess::resourcesRecovered);
+
+ // Now complete authorization.
+ promise.set(true);
+
+ // Framework should get a TASK_LOST.
+ AWAIT_READY(status);
+ EXPECT_EQ(TASK_LOST, status.get().state());
+
+ // No task launch should happen resulting in all resources being
+ // returned to the allocator.
+ AWAIT_READY(resourcesRecovered);
+
+ driver.stop();
+ driver.join();
+
+ Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
+
+
+// This test verifies that a framework removal that comes before
+// '_launchTasks()' is called results in recovery of resources.
+TEST_F(MasterAuthorizationTest, FrameworkRemoved)
+{
+ MockAuthorizer authorizer;
+ Try<PID<Master> > master = StartMaster(&authorizer);
+ ASSERT_SOME(master);
+
+ MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+ Try<PID<Slave> > slave = StartSlave(&exec);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .Times(1);
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task = createTask(offers.get()[0], "", DEFAULT_EXECUTOR_ID);
+ vector<TaskInfo> tasks;
+ tasks.push_back(task);
+
+ // Return a pending future from authorizer.
+ Future<Nothing> future;
+ Promise<bool> promise;
+ EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>()))
+ .WillOnce(DoAll(FutureSatisfy(&future),
+ Return(promise.future())));
+
+ driver.launchTasks(offers.get()[0].id(), tasks);
+
+ // Wait until authorization is in progress.
+ AWAIT_READY(future);
+
+ Future<Nothing> frameworkRemoved =
+ FUTURE_DISPATCH(_, &AllocatorProcess::frameworkRemoved);
+
+ // Now stop the framework.
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(frameworkRemoved);
+
+ Future<Nothing> resourcesRecovered =
+ FUTURE_DISPATCH(_, &AllocatorProcess::resourcesRecovered);
+
+ // Now complete authorization.
+ promise.set(true);
+
+ // No task launch should happen resulting in all resources being
+ // returned to the allocator.
+ AWAIT_READY(resourcesRecovered);
+
+ Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
+
+
+// This test verifies that a reconciliation request that comes before
+// '_launchTasks()' is ignored.
+TEST_F(MasterAuthorizationTest, ReconcileTask)
+{
+ MockAuthorizer authorizer;
+ Try<PID<Master> > master = StartMaster(&authorizer);
+ ASSERT_SOME(master);
+
+ MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+ Try<PID<Slave> > slave = StartSlave(&exec);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .Times(1);
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task = createTask(offers.get()[0], "", DEFAULT_EXECUTOR_ID);
+ vector<TaskInfo> tasks;
+ tasks.push_back(task);
+
+ // Return a pending future from authorizer.
+ Future<Nothing> future;
+ Promise<bool> promise;
+ EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>()))
+ .WillOnce(DoAll(FutureSatisfy(&future),
+ Return(promise.future())));
+
+ driver.launchTasks(offers.get()[0].id(), tasks);
+
+ // Wait until authorization is in progress.
+ AWAIT_READY(future);
+
+ // Scheduler shouldn't get an update from reconciliation.
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .Times(0);
+
+ Future<ReconcileTasksMessage> reconcileTasksMessage =
+ FUTURE_PROTOBUF(ReconcileTasksMessage(), _, _);
+
+ vector<TaskStatus> statuses;
+
+ TaskStatus status;
+ status.mutable_task_id()->CopyFrom(task.task_id());
+ status.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id());
+ status.set_state(TASK_STAGING);
+
+ statuses.push_back(status);
+
+ driver.reconcileTasks(statuses);
+
+ AWAIT_READY(reconcileTasksMessage);
+
+ // Make sure the framework doesn't receive any update.
+ Clock::pause();
+ Clock::settle();
+
+ // Now stop the framework.
+ driver.stop();
+ driver.join();
+
+ Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
http://git-wip-us.apache.org/repos/asf/mesos/blob/c4084554/src/tests/mesos.cpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.cpp b/src/tests/mesos.cpp
index ea6a1c0..e6d807c 100644
--- a/src/tests/mesos.cpp
+++ b/src/tests/mesos.cpp
@@ -27,6 +27,8 @@
#include <stout/stringify.hpp>
#include <stout/uuid.hpp>
+#include "authorizer/authorizer.hpp"
+
#ifdef __linux__
#include "linux/cgroups.hpp"
#endif
@@ -199,6 +201,28 @@ Try<process::PID<master::Master> > MesosTest::StartMaster(
}
+Try<process::PID<master::Master> > MesosTest::StartMaster(
+ Authorizer* authorizer,
+ const Option<master::Flags>& flags,
+ bool wait)
+{
+ Future<Nothing> detected = FUTURE_DISPATCH(_, &master::Master::detected);
+
+ Try<process::PID<master::Master> > master = cluster.masters.start(
+ authorizer, flags.isNone() ? CreateMasterFlags() : flags.get());
+
+ // Wait until the leader is detected because otherwise this master
+ // may reject authentication requests because it doesn't know it's
+ // the leader yet [MESOS-881].
+ if (wait && master.isSome() && !detected.await(Seconds(10))) {
+ return Error("Failed to wait " + stringify(Seconds(10)) +
+ " for master to detect the leader");
+ }
+
+ return master;
+}
+
+
Try<process::PID<slave::Slave> > MesosTest::StartSlave(
const Option<slave::Flags>& flags)
{
http://git-wip-us.apache.org/repos/asf/mesos/blob/c4084554/src/tests/mesos.hpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.hpp b/src/tests/mesos.hpp
index 2e01e5e..0b9b2f9 100644
--- a/src/tests/mesos.hpp
+++ b/src/tests/mesos.hpp
@@ -41,6 +41,8 @@
#include <stout/try.hpp>
#include <stout/uuid.hpp>
+#include "authorizer/authorizer.hpp"
+
#include "messages/messages.hpp" // For google::protobuf::Message.
#include "master/allocator.hpp"
@@ -101,6 +103,14 @@ protected:
const Option<master::Flags>& flags = None(),
bool wait = true);
+ // Starts a master with the specified authorizer and flags.
+ // Waits for the master to detect a leader (could be itself) before
+ // returning if 'wait' is set to true.
+ virtual Try<process::PID<master::Master> > StartMaster(
+ Authorizer* authorizer,
+ const Option<master::Flags>& flags = None(),
+ bool wait = true);
+
// Starts a slave with the specified flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
const Option<slave::Flags>& flags = None());
@@ -469,6 +479,42 @@ public:
};
+// Definition of a MockAuthozier that can be used in tests with gmock.
+class MockAuthorizer : public Authorizer
+{
+public:
+ MockAuthorizer()
+ {
+ using ::testing::An;
+ using ::testing::Return;
+
+ // NOTE: We use 'EXPECT_CALL' and 'WillRepeatedly' here instead of
+ // 'ON_CALL' and 'WillByDefault'. See 'TestContainerizer::SetUp()'
+ // for more details.
+ EXPECT_CALL(*this, authorize(An<const mesos::ACL::RunTasks&>()))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*this, authorize(An<const mesos::ACL::ReceiveOffers&>()))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*this, authorize(An<const mesos::ACL::HTTPGet&>()))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*this, authorize(An<const mesos::ACL::HTTPPut&>()))
+ .WillRepeatedly(Return(true));
+ }
+
+ MOCK_METHOD1(
+ authorize, process::Future<bool>(const ACL::RunTasks& request));
+ MOCK_METHOD1(
+ authorize, process::Future<bool>(const ACL::ReceiveOffers& request));
+ MOCK_METHOD1(
+ authorize, process::Future<bool>(const ACL::HTTPGet& request));
+ MOCK_METHOD1(
+ authorize, process::Future<bool>(const ACL::HTTPPut& request));
+};
+
+
template <typename T = master::allocator::AllocatorProcess>
class MockAllocatorProcess : public master::allocator::AllocatorProcess
{