// Using JSON base file for authentication without // protobuf tools assistance. TEST_F(CredentialsTest, AuthenticatedSlaveJSON) { string path = path::join(os::getcwd(), "credentials"); Try<int> fd = os::open( path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP); CHECK_SOME(fd); // This unit tests our capacity to process JSON credentials without // using our protobuf tools. JSON::Object credential; credential.values["principal"] = DEFAULT_CREDENTIAL.principal(); credential.values["secret"] = DEFAULT_CREDENTIAL.secret(); JSON::Array array; array.values.push_back(credential); JSON::Object credentials; credentials.values["credentials"] = array; CHECK_SOME(os::write(fd.get(), stringify(credentials))) << "Failed to write credentials to '" << path << "'"; CHECK_SOME(os::close(fd.get())); map<string, Option<string>> values{{"credentials", Some("file://" + path)}}; master::Flags masterFlags = CreateMasterFlags(); masterFlags.load(values, true); Try<PID<Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); slave::Flags slaveFlags = CreateSlaveFlags(); slaveFlags.load(values, true); Try<PID<Slave>> slave = StartSlave(slaveFlags); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); ASSERT_NE("", slaveRegisteredMessage.get().slave_id().value()); Shutdown(); }
// This test verifies that an authenticated slave is // granted registration by the master. TEST_F(CredentialsTest, AuthenticatedSlave) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get()); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); ASSERT_NE("", slaveRegisteredMessage->slave_id().value()); }
// This test verifies that an authenticated slave is // granted registration by the master. TEST_F(CredentialsTest, AuthenticatedSlave) { Try<PID<Master>> master = StartMaster(); ASSERT_SOME(master); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); Try<PID<Slave>> slave = StartSlave(); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); ASSERT_NE("", slaveRegisteredMessage.get().slave_id().value()); Shutdown(); }
// This test verifies that if master --> slave socket closes and the // slave is not aware of it (i.e., one way network partition), slave // will re-register with the master. TEST_F(PartitionTest, OneWayPartitionMasterToSlave) { // Start a master. master::Flags masterFlags = CreateMasterFlags(); Try<Owned<cluster::Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); Future<Message> slaveRegisteredMessage = FUTURE_MESSAGE(Eq(SlaveRegisteredMessage().GetTypeName()), _, _); // Ensure a ping reaches the slave. Future<Message> ping = FUTURE_MESSAGE( Eq(PingSlaveMessage().GetTypeName()), _, _); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get()); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); AWAIT_READY(ping); Future<Nothing> deactivateSlave = FUTURE_DISPATCH(_, &MesosAllocatorProcess::deactivateSlave); // Inject a slave exited event at the master causing the master // to mark the slave as disconnected. The slave should not notice // it until the next ping is received. process::inject::exited(slaveRegisteredMessage.get().to, master.get()->pid); // Wait until master deactivates the slave. AWAIT_READY(deactivateSlave); Future<SlaveReregisteredMessage> slaveReregisteredMessage = FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); // Ensure the slave observer marked the slave as deactivated. Clock::pause(); Clock::settle(); // Let the slave observer send the next ping. Clock::advance(masterFlags.slave_ping_timeout); // Slave should re-register. AWAIT_READY(slaveReregisteredMessage); }
// This test verifies that if master --> slave socket closes and the // slave is not aware of it (i.e., one way network partition), slave // will re-register with the master. TEST_F(PartitionTest, OneWayPartitionMasterToSlave) { // Start a master. Try<PID<Master> > master = StartMaster(); ASSERT_SOME(master); Future<Message> slaveRegisteredMessage = FUTURE_MESSAGE(Eq(SlaveRegisteredMessage().GetTypeName()), _, _); // Ensure a ping reaches the slave. Future<Message> ping = FUTURE_MESSAGE(Eq("PING"), _, _); Try<PID<Slave>> slave = StartSlave(); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); AWAIT_READY(ping); Future<Nothing> deactivateSlave = FUTURE_DISPATCH(_, &MesosAllocatorProcess::deactivateSlave); // Inject a slave exited event at the master causing the master // to mark the slave as disconnected. The slave should not notice // it until the next ping is received. process::inject::exited(slaveRegisteredMessage.get().to, master.get()); // Wait until master deactivates the slave. AWAIT_READY(deactivateSlave); Future<SlaveReregisteredMessage> slaveReregisteredMessage = FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); // Ensure the slave observer marked the slave as deactivated. Clock::pause(); Clock::settle(); // Let the slave observer send the next ping. Clock::advance(slave::MASTER_PING_TIMEOUT()); // Slave should re-register. AWAIT_READY(slaveReregisteredMessage); }
// Test verifing well executed credential authentication // using text formatted credentials so as to test // backwards compatibility. TEST_F(CredentialsTest, AuthenticatedSlaveText) { string path = path::join(os::getcwd(), "credentials"); Try<int> fd = os::open( path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP); CHECK_SOME(fd); std::string credentials = DEFAULT_CREDENTIAL.principal() + " " + DEFAULT_CREDENTIAL.secret(); CHECK_SOME(os::write(fd.get(), credentials)) << "Failed to write credentials to '" << path << "'"; CHECK_SOME(os::close(fd.get())); map<string, Option<string>> values{{"credentials", Some("file://" + path)}}; master::Flags masterFlags = CreateMasterFlags(); masterFlags.load(values, true); Try<PID<Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); slave::Flags slaveFlags = CreateSlaveFlags(); slaveFlags.load(values, true); Try<PID<Slave>> slave = StartSlave(slaveFlags); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); ASSERT_NE("", slaveRegisteredMessage.get().slave_id().value()); Shutdown(); }
// Test verifing well executed credential authentication // using text formatted credentials so as to test // backwards compatibility. TEST_F(CredentialsTest, AuthenticatedSlaveText) { string path = path::join(os::getcwd(), "credentials"); Try<int_fd> fd = os::open( path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP); ASSERT_SOME(fd); string credentials = DEFAULT_CREDENTIAL.principal() + " " + DEFAULT_CREDENTIAL.secret(); ASSERT_SOME(os::write(fd.get(), credentials)) << "Failed to write credentials to '" << path << "'"; ASSERT_SOME(os::close(fd.get())); map<string, Option<string>> values{ {"credentials", Some(uri::from_path(path))}}; master::Flags masterFlags = CreateMasterFlags(); masterFlags.load(values, true); Try<Owned<cluster::Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); slave::Flags slaveFlags = CreateSlaveFlags(); slaveFlags.load(values, true); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); ASSERT_NE("", slaveRegisteredMessage->slave_id().value()); }
// The purpose of this test is to ensure that when slaves are removed // from the master, and then attempt to send status updates, we send // a ShutdownMessage to the slave. Why? Because during a network // partition, the master will remove a partitioned slave, thus sending // its tasks to LOST. At this point, when the partition is removed, // the slave may attempt to send updates if it was unaware that the // master removed it. We've already notified frameworks that these // tasks were LOST, so we have to have the slave shut down. TEST_F(PartitionTest, PartitionedSlaveStatusUpdates) { master::Flags masterFlags = CreateMasterFlags(); Try<Owned<cluster::Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); // Allow the master to PING the slave, but drop all PONG messages // from the slave. Note that we don't match on the master / slave // PIDs because it's actually the SlaveObserver Process that sends // the pings. Future<Message> ping = FUTURE_MESSAGE( Eq(PingSlaveMessage().GetTypeName()), _, _); DROP_PROTOBUFS(PongSlaveMessage(), _, _); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); MockExecutor exec(DEFAULT_EXECUTOR_ID); TestContainerizer containerizer(&exec); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), &containerizer); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); SlaveID slaveId = slaveRegisteredMessage.get().slave_id(); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillRepeatedly(Return()); driver.start(); AWAIT_READY(frameworkId); // Drop the first shutdown message from the master (simulated // partition), allow the second shutdown message to pass when // the slave sends an update. Future<ShutdownMessage> shutdownMessage = DROP_PROTOBUF(ShutdownMessage(), _, slave.get()->pid); EXPECT_CALL(sched, offerRescinded(&driver, _)) .WillRepeatedly(Return()); Future<Nothing> slaveLost; EXPECT_CALL(sched, slaveLost(&driver, _)) .WillOnce(FutureSatisfy(&slaveLost)); Clock::pause(); // Now, induce a partition of the slave by having the master // timeout the slave. size_t pings = 0; while (true) { AWAIT_READY(ping); pings++; if (pings == masterFlags.max_slave_ping_timeouts) { break; } ping = FUTURE_MESSAGE(Eq(PingSlaveMessage().GetTypeName()), _, _); Clock::advance(masterFlags.slave_ping_timeout); Clock::settle(); } Clock::advance(masterFlags.slave_ping_timeout); Clock::settle(); // Wait for the master to attempt to shut down the slave. AWAIT_READY(shutdownMessage); // The master will notify the framework that the slave was lost. AWAIT_READY(slaveLost); shutdownMessage = FUTURE_PROTOBUF(ShutdownMessage(), _, slave.get()->pid); // At this point, the slave still thinks it's registered, so we // simulate a status update coming from the slave. TaskID taskId; taskId.set_value("task_id"); const StatusUpdate& update = protobuf::createStatusUpdate( frameworkId.get(), slaveId, taskId, TASK_RUNNING, TaskStatus::SOURCE_SLAVE, UUID::random()); StatusUpdateMessage message; message.mutable_update()->CopyFrom(update); message.set_pid(stringify(slave.get()->pid)); process::post(master.get()->pid, message); // The master should shutdown the slave upon receiving the update. AWAIT_READY(shutdownMessage); Clock::resume(); driver.stop(); driver.join(); }
// This test verifies that the master reconciles tasks that are // missing from a re-registering slave. In this case, we trigger // a race between the slave re-registration message and the launch // message. There should be no TASK_LOST. // This was motivated by MESOS-1696. TEST_F(MasterSlaveReconciliationTest, ReconcileRace) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); TestContainerizer containerizer(&exec); StandaloneMasterDetector detector(master.get()->pid); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), master.get()->pid, _); Try<Owned<cluster::Slave>> slave = StartSlave(&detector, &containerizer); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(&driver, _, _)); Future<vector<Offer> > offers; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); // Since the agent may have retried registration, we want to // ensure that any duplicate registrations are flushed before // we appoint the master again. Otherwise, the agent may // receive a stale registration message. Clock::pause(); Clock::settle(); Clock::resume(); // Trigger a re-registration of the slave and capture the message // so that we can spoof a race with a launch task message. DROP_PROTOBUFS(ReregisterSlaveMessage(), slave.get()->pid, master.get()->pid); Future<ReregisterSlaveMessage> reregisterSlaveMessage = DROP_PROTOBUF( ReregisterSlaveMessage(), slave.get()->pid, master.get()->pid); detector.appoint(master.get()->pid); AWAIT_READY(reregisterSlaveMessage); AWAIT_READY(offers); EXPECT_NE(0u, offers.get().size()); TaskInfo task; task.set_name("test task"); 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(DEFAULT_EXECUTOR_INFO); ExecutorDriver* executorDriver; EXPECT_CALL(exec, registered(_, _, _, _)) .WillOnce(SaveArg<0>(&executorDriver)); // Leave the task in TASK_STAGING. Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(FutureSatisfy(&launchTask)); EXPECT_CALL(sched, statusUpdate(&driver, _)) .Times(0); driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(launchTask); // Send the stale re-registration message, which does not contain // the task we just launched. This will trigger a reconciliation // by the master. Future<SlaveReregisteredMessage> slaveReregisteredMessage = FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); // Prevent this from being dropped per the DROP_PROTOBUFS above. FUTURE_PROTOBUF( ReregisterSlaveMessage(), slave.get()->pid, master.get()->pid); process::post( slave.get()->pid, master.get()->pid, reregisterSlaveMessage.get()); AWAIT_READY(slaveReregisteredMessage); // Neither the master nor the slave should send a TASK_LOST // as part of the reconciliation. We check this by calling // Clock::settle() to flush all pending events. Clock::pause(); Clock::settle(); Clock::resume(); // Now send TASK_FINISHED and make sure it's the only message // received by the scheduler. Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)); TaskStatus taskStatus; taskStatus.mutable_task_id()->CopyFrom(task.task_id()); taskStatus.set_state(TASK_FINISHED); executorDriver->sendStatusUpdate(taskStatus); AWAIT_READY(status); ASSERT_EQ(TASK_FINISHED, status.get().state()); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); driver.stop(); driver.join(); }
// Tests that the default container logger writes files into the sandbox. TEST_F(ContainerLoggerTest, DefaultToSandbox) { // Create a master, agent, and framework. Try<PID<Master>> master = StartMaster(); ASSERT_SOME(master); Future<SlaveRegisteredMessage> slaveRegisteredMessage = FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _); // We'll need access to these flags later. slave::Flags flags = CreateSlaveFlags(); Fetcher fetcher; // We use an actual containerizer + executor since we want something to run. Try<MesosContainerizer*> containerizer = MesosContainerizer::create(flags, false, &fetcher); CHECK_SOME(containerizer); Try<PID<Slave>> slave = StartSlave(containerizer.get(), flags); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); SlaveID slaveId = slaveRegisteredMessage.get().slave_id(); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); // Wait for an offer, and start a task. Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(frameworkId); AWAIT_READY(offers); EXPECT_NE(0u, offers.get().size()); // We'll start a task that outputs to stdout. TaskInfo task = createTask(offers.get()[0], "echo 'Hello World!'"); Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(status); EXPECT_EQ(TASK_RUNNING, status.get().state()); // Check that the sandbox was written to. string sandboxDirectory = path::join( slave::paths::getExecutorPath( flags.work_dir, slaveId, frameworkId.get(), status->executor_id()), "runs", "latest"); ASSERT_TRUE(os::exists(sandboxDirectory)); string stdoutPath = path::join(sandboxDirectory, "stdout"); ASSERT_TRUE(os::exists(stdoutPath)); Result<string> stdout = os::read(stdoutPath); ASSERT_SOME(stdout); EXPECT_TRUE(strings::contains(stdout.get(), "Hello World!")); driver.stop(); driver.join(); Shutdown(); }