// This test ensures that destroy can be called at the end of the // launch loop. The composing containerizer still calls the // underlying containerizer's destroy (because it's not sure // if the containerizer can handle the type of container being // launched). If the launch is not supported by any containerizers // both the launch and destroy futures should be false. TEST_F(ComposingContainerizerTest, DestroyAfterLaunchLoop) { vector<Containerizer*> containerizers; MockContainerizer* mockContainerizer1 = new MockContainerizer(); containerizers.push_back(mockContainerizer1); ComposingContainerizer containerizer(containerizers); ContainerID containerId; containerId.set_value("container"); TaskInfo taskInfo; ExecutorInfo executorInfo; SlaveID slaveId; std::map<std::string, std::string> environment; Promise<bool> launchPromise; EXPECT_CALL(*mockContainerizer1, launch(_, _, _, _, _, _, _, _)) .WillOnce(Return(launchPromise.future())); Future<Nothing> destroy; Promise<bool> destroyPromise; EXPECT_CALL(*mockContainerizer1, destroy(_)) .WillOnce(DoAll(FutureSatisfy(&destroy), Return(destroyPromise.future()))); Future<bool> launched = containerizer.launch( containerId, taskInfo, executorInfo, "dir", "user", slaveId, environment, false); Resources resources = Resources::parse("cpus:1;mem:256").get(); EXPECT_TRUE(launched.isPending()); Future<bool> destroyed = containerizer.destroy(containerId); // We make sure the destroy is being called on the containerizer. AWAIT_READY(destroy); launchPromise.set(false); destroyPromise.set(false); // `launch` should return false and `destroyed` should return false // because none of the containerizers support the launch. AWAIT_EXPECT_EQ(false, launched); AWAIT_EXPECT_EQ(false, destroyed); }
TEST(FutureTest, Future) { Promise<bool> promise; promise.set(true); ASSERT_TRUE(promise.future().isReady()); EXPECT_TRUE(promise.future().get()); }
static void dataCompletion(int ret, const char* value, int value_len, const Stat* stat, const void* data) { const tuple<Promise<int>*, string*, Stat*>* args = reinterpret_cast<const tuple<Promise<int>*, string*, Stat*>*>(data); Promise<int>* promise = (*args).get<0>(); string* result = (*args).get<1>(); Stat* stat_result = (*args).get<2>(); if (ret == 0) { if (result != NULL) { result->assign(value, value_len); } if (stat_result != NULL) { *stat_result = *stat; } } promise->set(ret); delete promise; delete args; }
// This test checks if destroy is called while container is being // launched, the composing containerizer still calls the underlying // containerizer's destroy and skip calling the rest of the // containerizers. TEST_F(ComposingContainerizerTest, DestroyWhileLaunching) { vector<Containerizer*> containerizers; MockContainerizer* mockContainerizer = new MockContainerizer(); MockContainerizer* mockContainerizer2 = new MockContainerizer(); containerizers.push_back(mockContainerizer); containerizers.push_back(mockContainerizer2); ComposingContainerizer containerizer(containerizers); ContainerID containerId; containerId.set_value("container"); TaskInfo taskInfo; ExecutorInfo executorInfo; SlaveID slaveId; std::map<std::string, std::string> environment; Promise<bool> launchPromise; EXPECT_CALL(*mockContainerizer, launch(_, _, _, _, _, _, _, _)) .WillOnce(Return(launchPromise.future())); Future<Nothing> destroy; EXPECT_CALL(*mockContainerizer, destroy(_)) .WillOnce(FutureSatisfy(&destroy)); Future<bool> launch = containerizer.launch( containerId, taskInfo, executorInfo, "dir", "user", slaveId, environment, false); Resources resources = Resources::parse("cpus:1;mem:256").get(); EXPECT_TRUE(launch.isPending()); containerizer.destroy(containerId); EXPECT_CALL(*mockContainerizer2, launch(_, _, _, _, _, _, _, _)) .Times(0); // We make sure the destroy is being called on the first containerizer. // The second containerizer shouldn't be called as well since the // container is already destroyed. AWAIT_READY(destroy); launchPromise.set(false); AWAIT_FAILED(launch); }
static void voidCompletion(int ret, const void *data) { const tuple<Promise<int>*>* args = reinterpret_cast<const tuple<Promise<int>*>*>(data); Promise<int>* promise = (*args).get<0>(); promise->set(ret); delete promise; delete args; }
TEST(FutureTest, UndiscardableFuture) { Promise<int> promise; Future<int> f = undiscardable(promise.future()); f.discard(); EXPECT_TRUE(f.hasDiscard()); EXPECT_FALSE(promise.future().hasDiscard()); promise.set(42); AWAIT_ASSERT_EQ(42, f); }
TEST(FutureTest, CallableOnce) { Promise<Nothing> promise; promise.set(Nothing()); Future<int> future = promise.future() .then(lambda::partial( [](std::unique_ptr<int>&& o) { return *o; }, std::unique_ptr<int>(new int(42)))); ASSERT_TRUE(future.isReady()); EXPECT_EQ(42, future.get()); int n = 0; future = promise.future() .onReady(lambda::partial( [&n](std::unique_ptr<int> o) { n += *o; }, std::unique_ptr<int>(new int(1)))) .onAny(lambda::partial( [&n](std::unique_ptr<int>&& o) { n += *o; }, std::unique_ptr<int>(new int(10)))) .onFailed(lambda::partial( [&n](const std::unique_ptr<int>& o) { n += *o; }, std::unique_ptr<int>(new int(100)))) .onDiscard(lambda::partial( [&n](std::unique_ptr<int>&& o) { n += *o; }, std::unique_ptr<int>(new int(1000)))) .onDiscarded(lambda::partial( [&n](std::unique_ptr<int>&& o) { n += *o; }, std::unique_ptr<int>(new int(10000)))) .then([&n]() { return n; }); ASSERT_TRUE(future.isReady()); EXPECT_EQ(11, future.get()); }
// This test ensures that a framework that is removed while // authorization for registration is in progress is properly handled. TEST_F(MasterAuthorizationTest, FrameworkRemovedBeforeRegistration) { MockAuthorizer authorizer; Try<PID<Master> > master = StartMaster(&authorizer); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::ReceiveOffers&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); driver.start(); // Wait until authorization is in progress. AWAIT_READY(future); // Stop the framework. // At this point the framework is disconnected but the master does // not take any action because the framework is not in its map yet. driver.stop(); driver.join(); // Settle the clock here to ensure master handles the framework // 'exited' event. Clock::pause(); Clock::settle(); Clock::resume(); Future<Nothing> frameworkRemoved = FUTURE_DISPATCH(_, &AllocatorProcess::frameworkRemoved); // Now complete authorization. promise.set(true); // When the master tries to link to a non-existent framework PID // it should realize the framework is gone and remove it. AWAIT_READY(frameworkRemoved); Shutdown(); }
static void stringCompletion(int ret, const char* value, const void* data) { const tuple<Promise<int>*, string*> *args = reinterpret_cast<const tuple<Promise<int>*, string*>*>(data); Promise<int>* promise = (*args).get<0>(); string* result = (*args).get<1>(); if (ret == 0) { if (result != NULL) { result->assign(value); } } promise->set(ret); delete promise; delete args; }
static void statCompletion(int ret, const Stat* stat, const void* data) { const tuple<Promise<int>*, Stat*>* args = reinterpret_cast<const tuple<Promise<int>*, Stat*>*>(data); Promise<int>* promise = (*args).get<0>(); Stat *stat_result = (*args).get<1>(); if (ret == 0) { if (stat_result != NULL) { *stat_result = *stat; } } promise->set(ret); delete promise; delete args; }
TEST(FutureTest, Then) { Promise<int*> promise; int i = 42; promise.set(&i); Future<string> future = promise.future() .then(lambda::bind(&itoa1, lambda::_1)); ASSERT_TRUE(future.isReady()); EXPECT_EQ("42", future.get()); future = promise.future() .then(lambda::bind(&itoa2, lambda::_1)); ASSERT_TRUE(future.isReady()); EXPECT_EQ("42", future.get()); }
TEST(Process, then) { Promise<int*> promise; int i = 42; promise.set(&i); Future<std::string> future = promise.future() .then(std::tr1::bind(&itoa1, std::tr1::placeholders::_1)); ASSERT_TRUE(future.isReady()); EXPECT_EQ("42", future.get()); future = promise.future() .then(std::tr1::bind(&itoa2, std::tr1::placeholders::_1)); ASSERT_TRUE(future.isReady()); EXPECT_EQ("42", future.get()); }
TEST(FutureTest, UndiscardableLambda) { Promise<int> promise; Future<int> f = Future<int>(2) .then(undiscardable([&](int multiplier) { return promise.future() .then([=](int i) { return i * multiplier; }); })); f.discard(); EXPECT_TRUE(f.hasDiscard()); EXPECT_FALSE(promise.future().hasDiscard()); promise.set(42); AWAIT_ASSERT_EQ(84, f); }
static void stringsCompletion(int ret, const String_vector* values, const void* data) { const tuple<Promise<int>*, vector<string>*>* args = reinterpret_cast<const tuple<Promise<int>*, vector<string>*>*>(data); Promise<int>* promise = (*args).get<0>(); vector<string>* results = (*args).get<1>(); if (ret == 0) { if (results != NULL) { for (int i = 0; i < values->count; i++) { results->push_back(values->data[i]); } } } promise->set(ret); delete promise; delete args; }
TEST(FutureTest, FromTryFuture) { Try<Future<int>> t = 1; Future<int> future = t; ASSERT_TRUE(future.isReady()); EXPECT_EQ(1, future.get()); Promise<int> p; t = p.future(); future = t; ASSERT_TRUE(future.isPending()); p.set(1); ASSERT_TRUE(future.isReady()); EXPECT_EQ(1, future.get()); t = Error("error"); future = t; ASSERT_TRUE(future.isFailed()); EXPECT_EQ(t.error(), future.failure()); }
// Checks that completing a promise will keep the 'after' callback // from executing. TEST(FutureTest, After2) { Clock::pause(); std::atomic_bool executed(false); Promise<Nothing> promise; Future<Nothing> future = promise.future() .after(Hours(42), lambda::bind(&after, &executed, lambda::_1)); EXPECT_TRUE(future.isPending()); // Only advanced halfway, future should remain pending. Clock::advance(Hours(21)); EXPECT_TRUE(future.isPending()); // Even doing a discard on the future should keep it pending. future.discard(); EXPECT_TRUE(future.isPending()); // Now set the promise, the 'after' timer should be cancelled and // the pending future should be completed. promise.set(Nothing()); AWAIT_READY(future); // Advancing time the rest of the way should not cause the 'after' // callback to execute. Clock::advance(Hours(21)); EXPECT_FALSE(executed.load()); Clock::resume(); }
// 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. }
TEST(FutureTest, Stringify) { Future<bool> future; EXPECT_EQ("Abandoned", stringify(future)); { Owned<Promise<bool>> promise(new Promise<bool>()); future = promise->future(); promise.reset(); EXPECT_EQ("Abandoned", stringify(future)); } { Owned<Promise<bool>> promise(new Promise<bool>()); future = promise->future(); promise->future().discard(); promise.reset(); EXPECT_EQ("Abandoned (with discard)", stringify(future)); } { Promise<bool> promise; future = promise.future(); EXPECT_EQ("Pending", stringify(future)); promise.future().discard(); EXPECT_EQ("Pending (with discard)", stringify(future)); } { Promise<bool> promise; future = promise.future(); promise.set(true); EXPECT_EQ("Ready", stringify(future)); } { Promise<bool> promise; future = promise.future(); promise.future().discard(); promise.set(true); EXPECT_EQ("Ready (with discard)", stringify(future)); } { Promise<bool> promise; future = promise.future(); promise.fail("Failure"); EXPECT_EQ("Failed: Failure", stringify(future)); } { Promise<bool> promise; future = promise.future(); promise.future().discard(); promise.fail("Failure"); EXPECT_EQ("Failed (with discard): Failure", stringify(future)); } { Promise<bool> promise; future = promise.future(); promise.discard(); EXPECT_EQ("Discarded", stringify(future)); } { Promise<bool> promise; future = promise.future(); promise.future().discard(); promise.discard(); EXPECT_EQ("Discarded (with discard)", stringify(future)); } }
// This test verifies that two tasks each launched on a different // slave with same executor id but different executor info are // allowed even when the first task is pending due to authorization. TEST_F(MasterAuthorizationTest, PendingExecutorInfoDiffersOnDifferentSlaves) { MockAuthorizer authorizer; Try<PID<Master> > master = StartMaster(&authorizer); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); Future<Nothing> registered; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureSatisfy(®istered)); driver.start(); AWAIT_READY(registered); Future<vector<Offer> > offers1; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers1)); // Start the first slave. MockExecutor exec1(DEFAULT_EXECUTOR_ID); Try<PID<Slave> > slave1 = StartSlave(&exec1); ASSERT_SOME(slave1); AWAIT_READY(offers1); EXPECT_NE(0u, offers1.get().size()); // Launch the first task with the default executor id. ExecutorInfo executor1; executor1 = DEFAULT_EXECUTOR_INFO; executor1.mutable_command()->set_value("exit 1"); TaskInfo task1 = createTask( offers1.get()[0], executor1.command().value(), executor1.executor_id()); vector<TaskInfo> tasks1; tasks1.push_back(task1); // 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(offers1.get()[0].id(), tasks1); // Wait until authorization is in progress. AWAIT_READY(future); Future<vector<Offer> > offers2; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers2)) .WillRepeatedly(Return()); // Ignore subsequent offers. // Now start the second slave. MockExecutor exec2(DEFAULT_EXECUTOR_ID); Try<PID<Slave> > slave2 = StartSlave(&exec2); ASSERT_SOME(slave2); AWAIT_READY(offers2); EXPECT_NE(0u, offers2.get().size()); // Now launch the second task with the same executor id but // a different executor command. ExecutorInfo executor2; executor2 = executor1; executor2.mutable_command()->set_value("exit 2"); TaskInfo task2 = createTask( offers2.get()[0], executor2.command().value(), executor2.executor_id()); vector<TaskInfo> tasks2; tasks2.push_back(task2); EXPECT_CALL(exec2, registered(_, _, _, _)) .Times(1); EXPECT_CALL(exec2, launchTask(_, _)) .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING)); Future<TaskStatus> status2; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status2)); EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) .WillOnce(Return(true)); driver.launchTasks(offers2.get()[0].id(), tasks2); AWAIT_READY(status2); ASSERT_EQ(TASK_RUNNING, status2.get().state()); EXPECT_CALL(exec1, registered(_, _, _, _)) .Times(1); EXPECT_CALL(exec1, launchTask(_, _)) .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING)); Future<TaskStatus> status1; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status1)); // Complete authorization of 'task1'. promise.set(true); AWAIT_READY(status1); ASSERT_EQ(TASK_RUNNING, status1.get().state()); EXPECT_CALL(exec1, shutdown(_)) .Times(AtMost(1)); EXPECT_CALL(exec2, shutdown(_)) .Times(AtMost(1)); driver.stop(); driver.join(); Shutdown(); }
// This test ensures that destroy can be called while in the // launch loop. The composing containerizer still calls the // underlying containerizer's destroy (because it's not sure // if the containerizer can handle the type of container being // launched). If the launch is not supported by the 1st containerizer, // the composing containerizer should stop the launch loop and // set the value of destroy future to true. TEST_F(ComposingContainerizerTest, DestroyDuringUnsupportedLaunchLoop) { vector<Containerizer*> containerizers; MockContainerizer* mockContainerizer1 = new MockContainerizer(); MockContainerizer* mockContainerizer2 = new MockContainerizer(); containerizers.push_back(mockContainerizer1); containerizers.push_back(mockContainerizer2); ComposingContainerizer containerizer(containerizers); ContainerID containerId; containerId.set_value("container"); TaskInfo taskInfo; ExecutorInfo executorInfo; SlaveID slaveId; std::map<std::string, std::string> environment; Promise<bool> launchPromise; EXPECT_CALL(*mockContainerizer1, launch(_, _, _, _, _, _, _, _)) .WillOnce(Return(launchPromise.future())); Future<Nothing> destroy; Promise<bool> destroyPromise; EXPECT_CALL(*mockContainerizer1, destroy(_)) .WillOnce(DoAll(FutureSatisfy(&destroy), Return(destroyPromise.future()))); Future<bool> launched = containerizer.launch( containerId, taskInfo, executorInfo, "dir", "user", slaveId, environment, false); Resources resources = Resources::parse("cpus:1;mem:256").get(); EXPECT_TRUE(launched.isPending()); Future<bool> destroyed = containerizer.destroy(containerId); EXPECT_CALL(*mockContainerizer2, launch(_, _, _, _, _, _, _, _)) .Times(0); // We make sure the destroy is being called on the first containerizer. // The second containerizer shouldn't be called as well since the // container is already destroyed. AWAIT_READY(destroy); launchPromise.set(false); destroyPromise.set(false); // `launched` should be a failure and `destroyed` should be true // because the launch was stopped from being tried on the 2nd // containerizer because of the destroy. AWAIT_FAILED(launched); AWAIT_EXPECT_EQ(true, destroyed); }