TEST_F(SubprocessTest, Environment) { Clock::pause(); // Simple value. map<string, string> environment; environment["MESSAGE"] = "hello"; Try<Subprocess> s = subprocess( "echo $MESSAGE", Subprocess::PIPE(), Subprocess::PIPE(), Subprocess::PIPE(), environment); ASSERT_SOME(s); ASSERT_SOME(s.get().out()); AWAIT_EXPECT_EQ("hello\n", io::read(s.get().out().get())); // Advance time until the internal reaper reaps the subprocess. while (s.get().status().isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } AWAIT_ASSERT_READY(s.get().status()); ASSERT_SOME(s.get().status().get()); int status = s.get().status().get().get(); EXPECT_TRUE(WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); // Multiple key-value pairs. environment.clear(); environment["MESSAGE0"] = "hello"; environment["MESSAGE1"] = "world"; s = subprocess( "echo $MESSAGE0 $MESSAGE1", Subprocess::PIPE(), Subprocess::PIPE(), Subprocess::PIPE(), environment); ASSERT_SOME(s); ASSERT_SOME(s.get().out()); AWAIT_EXPECT_EQ("hello world\n", io::read(s.get().out().get())); // Advance time until the internal reaper reaps the subprocess. while (s.get().status().isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } AWAIT_ASSERT_READY(s.get().status()); ASSERT_SOME(s.get().status().get()); status = s.get().status().get().get(); EXPECT_TRUE(WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); Clock::resume(); }
int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; CommandInfo commandInfo; // Construct URIs from the encoded environment string. const std::string& uris = os::getenv("MESOS_EXECUTOR_URIS"); foreach (const std::string& token, strings::tokenize(uris, " ")) { // Delimiter between URI, execute permission and extract options // Expected format: {URI}+[01][XN] // {URI} - The actual URI for the asset to fetch // [01] - 1 if the execute permission should be set else 0 // [XN] - X if we should extract the URI (if it's compressed) else N size_t pos = token.rfind("+"); CHECK(pos != std::string::npos) << "Invalid executor uri token in env " << token; CommandInfo::URI uri; uri.set_value(token.substr(0, pos)); uri.set_executable(token.substr(pos + 1, 1) == "1"); uri.set_extract(token.substr(pos + 2, 1) == "X"); commandInfo.add_uris()->MergeFrom(uri); } CHECK(os::hasenv("MESOS_WORK_DIRECTORY")) << "Missing MESOS_WORK_DIRECTORY environment variable"; std::string directory = os::getenv("MESOS_WORK_DIRECTORY"); // We cannot use Some in the ternary expression because the compiler needs to // be able to infer the type, thus the explicit Option<string>. // TODO(idownes): Add an os::hasenv that returns an Option<string>. Option<std::string> user = os::hasenv("MESOS_USER") ? Option<std::string>(os::getenv("MESOS_USER")) // Explicit so it compiles. : None(); // Fetch each URI to a local file, chmod, then chown if a user is provided. foreach (const CommandInfo::URI& uri, commandInfo.uris()) { // Fetch the URI to a local file. Try<string> fetched = fetch(uri.value(), directory); if (fetched.isError()) { EXIT(1) << "Failed to fetch: " << uri.value(); } // Chmod the fetched URI if it's executable, else assume it's an archive // that should be extracted. if (uri.executable()) { Try<Nothing> chmod = os::chmod( fetched.get(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); if (chmod.isError()) { EXIT(1) << "Failed to chmod " << fetched.get() << ": " << chmod.error(); } } else if (uri.extract()) { //TODO(idownes): Consider removing the archive once extracted. // Try to extract the file if it's recognized as an archive. Try<bool> extracted = extract(fetched.get(), directory); if (extracted.isError()) { EXIT(1) << "Failed to extract " << fetched.get() << ":" << extracted.error(); } } else { LOG(INFO) << "Skipped extracting path '" << fetched.get() << "'"; } // Recursively chown the directory if a user is provided. if (user.isSome()) { Try<Nothing> chowned = os::chown(user.get(), directory); if (chowned.isError()) { EXIT(1) << "Failed to chown " << directory << ": " << chowned.error(); } } } return 0; }
Result(const Try<T>& _t) : data(_t.isSome() ? Try<Option<T>>(Some(_t.get())) : Try<Option<T>>(Error(_t.error()))) {}
// Checks that the DRF allocator implements the DRF algorithm // correctly. The test accomplishes this by adding frameworks and // slaves one at a time to the allocator, making sure that each time // a new slave is added all of its resources are offered to whichever // framework currently has the smallest share. Checking for proper DRF // logic when resources are returned, frameworks exit, etc. is handled // by SorterTest.DRFSorter. TEST_F(DRFAllocatorTest, DRFAllocatorProcess) { MockAllocatorProcess<HierarchicalDRFAllocatorProcess> allocator; EXPECT_CALL(allocator, initialize(_, _, _)); master::Flags masterFlags = CreateMasterFlags(); masterFlags.roles = Option<string>("role1,role2"); Try<PID<Master> > master = StartMaster(&allocator, masterFlags); ASSERT_SOME(master); slave::Flags flags1 = CreateSlaveFlags(); flags1.resources = Option<string>("cpus:2;mem:1024;disk:0"); EXPECT_CALL(allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave1 = StartSlave(flags1); ASSERT_SOME(slave1); // Total cluster resources now cpus=2, mem=1024. FrameworkInfo frameworkInfo1; frameworkInfo1.set_name("framework1"); frameworkInfo1.set_user("user1"); frameworkInfo1.set_role("role1"); MockScheduler sched1; MesosSchedulerDriver driver1(&sched1, frameworkInfo1, master.get()); EXPECT_CALL(allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched1, registered(_, _, _)); Future<vector<Offer> > offers1; EXPECT_CALL(sched1, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers1)); driver1.start(); AWAIT_READY(offers1); // framework1 will be offered all of slave1's resources since it is // the only framework running so far. EXPECT_THAT(offers1.get(), OfferEq(2, 1024)); // user1 share = 1 (cpus=2, mem=1024) // framework1 share = 1 FrameworkInfo frameworkInfo2; frameworkInfo2.set_name("framework2"); frameworkInfo2.set_user("user2"); frameworkInfo2.set_role("role2"); MockScheduler sched2; MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get()); Future<Nothing> frameworkAdded2; EXPECT_CALL(allocator, frameworkAdded(_, _, _)) .WillOnce(DoAll(InvokeFrameworkAdded(&allocator), FutureSatisfy(&frameworkAdded2))); EXPECT_CALL(sched2, registered(_, _, _)); driver2.start(); AWAIT_READY(frameworkAdded2); slave::Flags flags2 = CreateSlaveFlags(); flags2.resources = Option<string>("cpus:1;mem:512;disk:0"); EXPECT_CALL(allocator, slaveAdded(_, _, _)); Future<vector<Offer> > offers2; EXPECT_CALL(sched2, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers2)); Try<PID<Slave> > slave2 = StartSlave(flags2); ASSERT_SOME(slave2); // Total cluster resources now cpus=3, mem=1536. // user1 share = 0.66 (cpus=2, mem=1024) // framework1 share = 1 // user2 share = 0 // framework2 share = 0 AWAIT_READY(offers2); // framework2 will be offered all of slave2's resources since user2 // has the lowest user share, and framework2 is its only framework. EXPECT_THAT(offers2.get(), OfferEq(1, 512)); // user1 share = 0.67 (cpus=2, mem=1024) // framework1 share = 1 // user2 share = 0.33 (cpus=1, mem=512) // framework2 share = 1 slave::Flags flags3 = CreateSlaveFlags(); flags3.resources = Option<string>("cpus:3;mem:2048;disk:0"); EXPECT_CALL(allocator, slaveAdded(_, _, _)); Future<vector<Offer> > offers3; EXPECT_CALL(sched2, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers3)); Try<PID<Slave> > slave3 = StartSlave(flags3); ASSERT_SOME(slave3); // Total cluster resources now cpus=6, mem=3584. // user1 share = 0.33 (cpus=2, mem=1024) // framework1 share = 1 // user2 share = 0.16 (cpus=1, mem=512) // framework2 share = 1 AWAIT_READY(offers3); // framework2 will be offered all of slave3's resources since user2 // has the lowest share. EXPECT_THAT(offers3.get(), OfferEq(3, 2048)); // user1 share = 0.33 (cpus=2, mem=1024) // framework1 share = 1 // user2 share = 0.71 (cpus=4, mem=2560) // framework2 share = 1 FrameworkInfo frameworkInfo3; frameworkInfo3.set_name("framework3"); frameworkInfo3.set_user("user3"); frameworkInfo3.set_role("role1"); MockScheduler sched3; MesosSchedulerDriver driver3(&sched3, frameworkInfo3, master.get()); Future<Nothing> frameworkAdded3; EXPECT_CALL(allocator, frameworkAdded(_, _, _)) .WillOnce(DoAll(InvokeFrameworkAdded(&allocator), FutureSatisfy(&frameworkAdded3))); EXPECT_CALL(sched3, registered(_, _, _)); driver3.start(); AWAIT_READY(frameworkAdded3); slave::Flags flags4 = CreateSlaveFlags(); flags4.resources = Option<string>("cpus:4;mem:4096;disk:0"); EXPECT_CALL(allocator, slaveAdded(_, _, _)); Future<vector<Offer> > offers4; EXPECT_CALL(sched3, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers4)); Try<PID<Slave> > slave4 = StartSlave(flags4); ASSERT_SOME(slave4); // Total cluster resources now cpus=10, mem=7680. // user1 share = 0.2 (cpus=2, mem=1024) // framework1 share = 1 // framework3 share = 0 // user2 share = 0.4 (cpus=4, mem=2560) // framework2 share = 1 AWAIT_READY(offers4); // framework3 will be offered all of slave4's resources since user1 // has the lowest user share, and framework3 has the lowest share of // user1's frameworks. EXPECT_THAT(offers4.get(), OfferEq(4, 4096)); // user1 share = 0.67 (cpus=6, mem=5120) // framework1 share = 0.33 (cpus=2, mem=1024) // framework3 share = 0.8 (cpus=4, mem=4096) // user2 share = 0.4 (cpus=4, mem=2560) // framework2 share = 1 FrameworkInfo frameworkInfo4; frameworkInfo4.set_name("framework4"); frameworkInfo4.set_user("user1"); frameworkInfo4.set_role("role1"); MockScheduler sched4; MesosSchedulerDriver driver4(&sched4, frameworkInfo4, master.get()); Future<Nothing> frameworkAdded4; EXPECT_CALL(allocator, frameworkAdded(_, _, _)) .WillOnce(DoAll(InvokeFrameworkAdded(&allocator), FutureSatisfy(&frameworkAdded4))); EXPECT_CALL(sched4, registered(_, _, _)); driver4.start(); AWAIT_READY(frameworkAdded4); slave::Flags flags5 = CreateSlaveFlags(); flags5.resources = Option<string>("cpus:1;mem:512;disk:0"); EXPECT_CALL(allocator, slaveAdded(_, _, _)); Future<vector<Offer> > offers5; EXPECT_CALL(sched2, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers5)); Try<PID<Slave> > slave5 = StartSlave(flags5); ASSERT_SOME(slave5); // Total cluster resources now cpus=11, mem=8192 // user1 share = 0.63 (cpus=6, mem=5120) // framework1 share = 0.33 (cpus=2, mem=1024) // framework3 share = 0.8 (cpus=4, mem=4096) // framework4 share = 0 // user2 share = 0.36 (cpus=4, mem=2560) // framework2 share = 1 AWAIT_READY(offers5); // Even though framework4 doesn't have any resources, user2 has a // lower share than user1, so framework2 receives slave4's resources EXPECT_THAT(offers5.get(), OfferEq(1, 512)); // Shut everything down. EXPECT_CALL(allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(allocator, frameworkDeactivated(_)) .Times(AtMost(4)); EXPECT_CALL(allocator, frameworkRemoved(_)) .Times(AtMost(4)); driver1.stop(); driver1.join(); driver2.stop(); driver2.join(); driver3.stop(); driver3.join(); driver4.stop(); driver4.join(); EXPECT_CALL(allocator, slaveRemoved(_)) .Times(AtMost(5)); Shutdown(); }
// Checks that if a framework launches a task and then the slave the // task was running on gets killed, the task's resources are properly // recovered and, along with the rest of the resources from the killed // slave, never offered again. TYPED_TEST(AllocatorTest, SlaveLost) { EXPECT_CALL(this->allocator, initialize(_, _, _)); Try<PID<Master> > master = this->StartMaster(&this->allocator); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); slave::Flags flags1 = this->CreateSlaveFlags(); flags1.resources = Option<string>("cpus:2;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave1 = this->StartSlave(&exec, flags1); ASSERT_SOME(slave1); MockScheduler sched; MesosSchedulerDriver driver(&sched, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched, registered(_, _, _)); // Initially, all of slave1's resources are available. EXPECT_CALL(sched, resourceOffers(_, OfferEq(2, 1024))) .WillOnce(LaunchTasks(1, 2, 512)); EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)); EXPECT_CALL(exec, registered(_, _, _, _)); Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(DoAll(SendStatusUpdateFromTask(TASK_RUNNING), FutureSatisfy(&launchTask))); EXPECT_CALL(sched, statusUpdate(_, _)) .WillRepeatedly(DoDefault()); driver.start(); // Ensures the task is completely launched before we // kill the slave, to test that the task's resources // are recovered correctly (i.e. never reallocated // since the slave is killed) AWAIT_READY(launchTask); EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)); Future<Nothing> slaveRemoved; EXPECT_CALL(this->allocator, slaveRemoved(_)) .WillOnce(DoAll(InvokeSlaveRemoved(&this->allocator), FutureSatisfy(&slaveRemoved))); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); EXPECT_CALL(sched, slaveLost(_, _)); this->ShutdownSlaves(); AWAIT_READY(slaveRemoved); slave::Flags flags2 = this->CreateSlaveFlags(); flags2.resources = Option<string>("cpus:3;mem:256"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); // Eventually after slave2 is launched, we should get // an offer that contains all of slave2's resources // and none of slave1's resources. Future<Nothing> resourceOffers; EXPECT_CALL(sched, resourceOffers(_, OfferEq(3, 256))) .WillOnce(FutureSatisfy(&resourceOffers)); Try<PID<Slave> > slave2 = this->StartSlave(flags2); ASSERT_SOME(slave2); AWAIT_READY(resourceOffers); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(1)); driver.stop(); driver.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); }
// Checks that a framework attempting to register with an invalid role // will receive an error message and that roles can be added through the // master's command line flags. TYPED_TEST(AllocatorTest, RoleTest) { EXPECT_CALL(this->allocator, initialize(_, _, _)); master::Flags masterFlags = this->CreateMasterFlags(); masterFlags.roles = Option<string>("role2"); Try<PID<Master> > master = StartMaster(&this->allocator, masterFlags); ASSERT_SOME(master); // Launch a framework with a role that doesn't exist to see that it // receives an error message. FrameworkInfo frameworkInfo1; frameworkInfo1.set_name("framework1"); frameworkInfo1.set_user("user1"); frameworkInfo1.set_role("role1"); MockScheduler sched1; MesosSchedulerDriver driver1(&sched1, frameworkInfo1, master.get()); Future<FrameworkErrorMessage> errorMessage = FUTURE_PROTOBUF(FrameworkErrorMessage(), _, _); EXPECT_CALL(sched1, error(_, _)); driver1.start(); AWAIT_READY(errorMessage); // Launch a framework under an existing role to see that it registers. FrameworkInfo frameworkInfo2; frameworkInfo2.set_name("framework2"); frameworkInfo2.set_user("user2"); frameworkInfo2.set_role("role2"); MockScheduler sched2; MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get()); Future<Nothing> registered2; EXPECT_CALL(sched2, registered(_, _, _)) .WillOnce(FutureSatisfy(®istered2)); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); driver2.start(); AWAIT_READY(registered2); // Shut everything down. EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(1)); driver2.stop(); driver2.join(); driver1.stop(); driver1.join(); this->Shutdown(); }
// Tests the situation where a frameworkRemoved call is dispatched // while we're doing an allocation to that framework, so that // resourcesRecovered is called for an already removed framework. TYPED_TEST(AllocatorTest, OutOfOrderDispatch) { EXPECT_CALL(this->allocator, initialize(_, _, _)); Try<PID<Master> > master = this->StartMaster(&this->allocator); ASSERT_SOME(master); slave::Flags flags1 = this->CreateSlaveFlags(); flags1.resources = Option<string>("cpus:2;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave1 = this->StartSlave(flags1); ASSERT_SOME(slave1); FrameworkInfo frameworkInfo1; frameworkInfo1.set_user("user1"); frameworkInfo1.set_name("framework1"); MockScheduler sched1; MesosSchedulerDriver driver1(&sched1, frameworkInfo1, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, Eq(frameworkInfo1), _)) .WillOnce(InvokeFrameworkAdded(&this->allocator)); FrameworkID frameworkId1; EXPECT_CALL(sched1, registered(_, _, _)) .WillOnce(SaveArg<1>(&frameworkId1)); // All of the slave's resources should be offered to start. Future<Nothing> resourceOffers; EXPECT_CALL(sched1, resourceOffers(_, OfferEq(2, 1024))) .WillOnce(FutureSatisfy(&resourceOffers)); driver1.start(); AWAIT_READY(resourceOffers); // TODO(benh): I don't see why we want to "catch" (i.e., block) this // resourcesRecovered call. It seems like we want this one to // properly be executed and later we want to _inject_ a // resourcesRecovered to simulate the code in Master::offer after a // framework has terminated or is inactive. FrameworkID frameworkId; SlaveID slaveId; Resources savedResources; EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) // "Catches" the resourcesRecovered call from the master, so // that it doesn't get processed until we redispatch it after // the frameworkRemoved trigger. .WillOnce(DoAll(SaveArg<0>(&frameworkId), SaveArg<1>(&slaveId), SaveArg<2>(&savedResources))); EXPECT_CALL(this->allocator, frameworkDeactivated(_)); Future<Nothing> frameworkRemoved; EXPECT_CALL(this->allocator, frameworkRemoved(Eq(frameworkId1))) .WillOnce(DoAll(InvokeFrameworkRemoved(&this->allocator), FutureSatisfy(&frameworkRemoved))); driver1.stop(); driver1.join(); AWAIT_READY(frameworkRemoved); EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillOnce(DoDefault()); // Re-dispatch the resourcesRecovered call which we "caught" // earlier now that the framework has been removed, to test // that recovering resources from a removed framework works. this->a->resourcesRecovered(frameworkId, slaveId, savedResources); // TODO(benh): Seems like we should wait for the above // resourcesRecovered to be executed. FrameworkInfo frameworkInfo2; frameworkInfo2.set_user("user2"); frameworkInfo2.set_name("framework2"); MockScheduler sched2; MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, Eq(frameworkInfo2), _)) .WillOnce(InvokeFrameworkAdded(&this->allocator)); FrameworkID frameworkId2; EXPECT_CALL(sched2, registered(_, _, _)) .WillOnce(SaveArg<1>(&frameworkId2)); // All of the slave's resources should be offered since no other // frameworks should be running. EXPECT_CALL(sched2, resourceOffers(_, OfferEq(2, 1024))) .WillOnce(FutureSatisfy(&resourceOffers)); driver2.start(); AWAIT_READY(resourceOffers); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(Eq(frameworkId2))) .Times(AtMost(1)); driver2.stop(); driver2.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); }
static Try<Nothing> increasePageCache(const vector<string>& tokens) { const Bytes UNIT = Megabytes(1); if (tokens.size() < 2) { return Error("Expect at least one argument"); } Try<Bytes> size = Bytes::parse(tokens[1]); if (size.isError()) { return Error("The first argument '" + tokens[1] + "' is not a byte size"); } // TODO(chzhcn): Currently, we assume the current working directory // is a temporary directory and will be cleaned up when the test // finishes. Since the child process will inherit the current // working directory from the parent process, that means the test // that uses this helper probably needs to inherit from // TemporaryDirectoryTest. Consider relaxing this constraint. Try<string> path = os::mktemp(path::join(os::getcwd(), "XXXXXX")); if (path.isError()) { return Error("Failed to create a temporary file: " + path.error()); } Try<int> fd = os::open(path.get(), O_WRONLY); if (fd.isError()) { return Error("Failed to open file: " + fd.error()); } // NOTE: We are doing round-down here to calculate the number of // writes to do. for (uint64_t i = 0; i < size.get().bytes() / UNIT.bytes(); i++) { // Write UNIT size to disk at a time. The content isn't important. Try<Nothing> write = os::write(fd.get(), string(UNIT.bytes(), 'a')); if (write.isError()) { os::close(fd.get()); return Error("Failed to write file: " + write.error()); } // Use fsync to make sure data is written to disk. if (fsync(fd.get()) == -1) { // Save the error message because os::close below might // overwrite the errno. const string message = os::strerror(errno); os::close(fd.get()); return Error("Failed to fsync: " + message); } } os::close(fd.get()); return Nothing(); }
int main(int argc, char** argv) { if (argc != 3) { std::cerr << "Usage: " << argv[0] << " <master> <balloon limit in MB>" << std::endl; return -1; } // Verify the balloon limit. Try<size_t> limit = numify<size_t>(argv[2]); if (limit.isError()) { std::cerr << "Balloon limit is not a valid number" << std::endl; return -1; } if (limit.get() < EXECUTOR_MEMORY_MB) { std::cerr << "Please use a balloon limit bigger than " << EXECUTOR_MEMORY_MB << " MB" << std::endl; } // Find this executable's directory to locate executor. string uri; Option<string> value = os::getenv("MESOS_BUILD_DIR"); if (value.isSome()) { uri = path::join(value.get(), "src", "balloon-executor"); } else { uri = path::join( os::realpath(Path(argv[0]).dirname()).get(), "balloon-executor"); } ExecutorInfo executor; executor.mutable_executor_id()->set_value("default"); executor.mutable_command()->set_value(uri); executor.set_name("Balloon Executor"); executor.set_source("balloon_test"); Resource* mem = executor.add_resources(); mem->set_name("mem"); mem->set_type(Value::SCALAR); mem->mutable_scalar()->set_value(EXECUTOR_MEMORY_MB); BalloonScheduler scheduler(executor, limit.get()); FrameworkInfo framework; framework.set_user(""); // Have Mesos fill in the current user. framework.set_name("Balloon Framework (C++)"); value = os::getenv("MESOS_CHECKPOINT"); if (value.isSome()) { framework.set_checkpoint( numify<bool>(value.get()).get()); } MesosSchedulerDriver* driver; value = os::getenv("MESOS_AUTHENTICATE"); if (value.isSome()) { cout << "Enabling authentication for the framework" << endl; value = os::getenv("DEFAULT_PRINCIPAL"); if (value.isNone()) { EXIT(EXIT_FAILURE) << "Expecting authentication principal in the environment"; } Credential credential; credential.set_principal(value.get()); framework.set_principal(value.get()); value = os::getenv("DEFAULT_SECRET"); if (value.isNone()) { EXIT(EXIT_FAILURE) << "Expecting authentication secret in the environment"; } credential.set_secret(value.get()); driver = new MesosSchedulerDriver( &scheduler, framework, argv[1], credential); } else { framework.set_principal("balloon-framework-cpp"); driver = new MesosSchedulerDriver( &scheduler, framework, argv[1]); } int status = driver->run() == DRIVER_STOPPED ? 0 : 1; // Ensure that the driver process terminates. driver->stop(); delete driver; return status; }
void launchTask(ExecutorDriver* driver, const TaskInfo& task) { if (run.isSome()) { // TODO(alexr): Use `protobuf::createTaskStatus()` // instead of manually setting fields. TaskStatus status; status.mutable_task_id()->CopyFrom(task.task_id()); status.set_state(TASK_FAILED); status.set_message( "Attempted to run multiple tasks using a \"docker\" executor"); driver->sendStatusUpdate(status); return; } // Capture the TaskID. taskId = task.task_id(); // Capture the kill policy. if (task.has_kill_policy()) { killPolicy = task.kill_policy(); } LOG(INFO) << "Starting task " << taskId.get(); CHECK(task.has_container()); CHECK(task.has_command()); CHECK(task.container().type() == ContainerInfo::DOCKER); Try<Docker::RunOptions> runOptions = Docker::RunOptions::create( task.container(), task.command(), containerName, sandboxDirectory, mappedDirectory, task.resources() + task.executor().resources(), cgroupsEnableCfs, taskEnvironment, None(), // No extra devices. defaultContainerDNS ); if (runOptions.isError()) { // TODO(alexr): Use `protobuf::createTaskStatus()` // instead of manually setting fields. TaskStatus status; status.mutable_task_id()->CopyFrom(task.task_id()); status.set_state(TASK_FAILED); status.set_message( "Failed to create docker run options: " + runOptions.error()); driver->sendStatusUpdate(status); _stop(); return; } // We're adding task and executor resources to launch docker since // the DockerContainerizer updates the container cgroup limits // directly and it expects it to be the sum of both task and // executor resources. This does leave to a bit of unaccounted // resources for running this executor, but we are assuming // this is just a very small amount of overcommit. run = docker->run( runOptions.get(), Subprocess::FD(STDOUT_FILENO), Subprocess::FD(STDERR_FILENO)); run->onAny(defer(self(), &Self::reaped, lambda::_1)); // Delay sending TASK_RUNNING status update until we receive // inspect output. Note that we store a future that completes // after the sending of the running update. This allows us to // ensure that the terminal update is sent after the running // update (see `reaped()`). inspect = docker->inspect(containerName, DOCKER_INSPECT_DELAY) .then(defer(self(), [=](const Docker::Container& container) { if (!killed) { containerPid = container.pid; // TODO(alexr): Use `protobuf::createTaskStatus()` // instead of manually setting fields. TaskStatus status; status.mutable_task_id()->CopyFrom(taskId.get()); status.set_state(TASK_RUNNING); status.set_data(container.output); if (container.ipAddress.isSome()) { // TODO(karya): Deprecated -- Remove after 0.25.0 has shipped. Label* label = status.mutable_labels()->add_labels(); label->set_key("Docker.NetworkSettings.IPAddress"); label->set_value(container.ipAddress.get()); NetworkInfo* networkInfo = status.mutable_container_status()->add_network_infos(); // Copy the NetworkInfo if it is specified in the // ContainerInfo. A Docker container has at most one // NetworkInfo, which is validated in containerizer. if (task.container().network_infos().size() > 0) { networkInfo->CopyFrom(task.container().network_infos(0)); networkInfo->clear_ip_addresses(); } NetworkInfo::IPAddress* ipAddress = networkInfo->add_ip_addresses(); ipAddress->set_ip_address(container.ipAddress.get()); containerNetworkInfo = *networkInfo; } driver->sendStatusUpdate(status); } return Nothing(); })); inspect.onFailed(defer(self(), [=](const string& failure) { LOG(ERROR) << "Failed to inspect container '" << containerName << "'" << ": " << failure; // TODO(bmahler): This is fatal, try to shut down cleanly. // Since we don't have a container id, we can only discard // the run future. })); inspect.onReady(defer(self(), &Self::launchCheck, task)); inspect.onReady( defer(self(), &Self::launchHealthCheck, containerName, task)); }
Try<X509*> generate_x509( EVP_PKEY* subject_key, EVP_PKEY* sign_key, const Option<X509*>& parent_certificate, int serial, int days, Option<std::string> hostname) { Option<X509_NAME*> issuer_name = None(); if (parent_certificate.isNone()) { // If there is no parent certificate, then the subject and // signing key must be the same. if (subject_key != sign_key) { return Error("Subject vs signing key mismatch"); } } else { // If there is a parent certificate, then set the issuer name to // be that of the parent. issuer_name = X509_get_subject_name(parent_certificate.get()); if (issuer_name.get() == nullptr) { return Error("Failed to get subject name of parent certificate: " "X509_get_subject_name"); } } // Allocate the in-memory structure for the certificate. X509* x509 = X509_new(); if (x509 == nullptr) { return Error("Failed to allocate certification: X509_new"); } // Set the version to V3. if (X509_set_version(x509, 2) != 1) { X509_free(x509); return Error("Failed to set version: X509_set_version"); } // Set the serial number. if (ASN1_INTEGER_set(X509_get_serialNumber(x509), serial) != 1) { X509_free(x509); return Error("Failed to set serial number: ASN1_INTEGER_set"); } // Make this certificate valid for 'days' number of days from now. if (X509_gmtime_adj(X509_get_notBefore(x509), 0) == nullptr || X509_gmtime_adj(X509_get_notAfter(x509), 60L * 60L * 24L * days) == nullptr) { X509_free(x509); return Error("Failed to set valid days of certificate: X509_gmtime_adj"); } // Set the public key for our certificate based on the subject key. if (X509_set_pubkey(x509, subject_key) != 1) { X509_free(x509); return Error("Failed to set public key: X509_set_pubkey"); } // Figure out our hostname if one was not provided. if (hostname.isNone()) { const Try<std::string> _hostname = net::hostname(); if (_hostname.isError()) { X509_free(x509); return Error("Failed to determine hostname"); } hostname = _hostname.get(); } // Grab the subject name of the new certificate. X509_NAME* name = X509_get_subject_name(x509); if (name == nullptr) { X509_free(x509); return Error("Failed to get subject name: X509_get_subject_name"); } // Set the country code, organization, and common name. if (X509_NAME_add_entry_by_txt( name, "C", MBSTRING_ASC, reinterpret_cast<const unsigned char*>("US"), -1, -1, 0) != 1) { X509_free(x509); return Error("Failed to set country code: X509_NAME_add_entry_by_txt"); } if (X509_NAME_add_entry_by_txt( name, "O", MBSTRING_ASC, reinterpret_cast<const unsigned char*>("Test"), -1, -1, 0) != 1) { X509_free(x509); return Error("Failed to set organization name: X509_NAME_add_entry_by_txt"); } if (X509_NAME_add_entry_by_txt( name, "CN", MBSTRING_ASC, reinterpret_cast<const unsigned char*>(hostname.get().c_str()), -1, -1, 0) != 1) { X509_free(x509); return Error("Failed to set common name: X509_NAME_add_entry_by_txt"); } // Set the issuer name to be the same as the subject if it is not // already set (this is a self-signed certificate). if (issuer_name.isNone()) { issuer_name = name; } CHECK_SOME(issuer_name); if (X509_set_issuer_name(x509, issuer_name.get()) != 1) { X509_free(x509); return Error("Failed to set issuer name: X509_set_issuer_name"); } // Sign the certificate with the sign key. if (X509_sign(x509, sign_key, EVP_sha1()) == 0) { X509_free(x509); return Error("Failed to sign certificate: X509_sign"); } return x509; }
// This test verifies that the slave run task label decorator can add // and remove labels from a task during the launch sequence. A task // with two labels ("foo":"bar" and "bar":"baz") is launched and will // get modified by the slave hook to strip the "foo":"bar" pair and // add a new "baz":"qux" pair. TEST_F(HookTest, VerifySlaveRunTaskHook) { Try<PID<Master>> master = StartMaster(); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); TestContainerizer containerizer(&exec); Try<PID<Slave>> slave = StartSlave(&containerizer); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get(), 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(); AWAIT_READY(offers); ASSERT_EQ(1u, offers.get().size()); TaskInfo task; task.set_name(""); task.mutable_task_id()->set_value("1"); task.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id()); task.mutable_resources()->CopyFrom(offers.get()[0].resources()); task.mutable_executor()->CopyFrom(DEFAULT_EXECUTOR_INFO); // Add two labels: (1) will be removed by the hook to ensure that // runTaskHook can remove labels (2) will be preserved to ensure // that the framework can add labels to the task and have those be // available by the end of the launch task sequence when hooks are // used (to protect against hooks removing labels completely). Labels* labels = task.mutable_labels(); Label* label1 = labels->add_labels(); label1->set_key("foo"); label1->set_value("bar"); Label* label2 = labels->add_labels(); label2->set_key("bar"); label2->set_value("baz"); vector<TaskInfo> tasks; tasks.push_back(task); EXPECT_CALL(exec, registered(_, _, _, _)); Future<TaskInfo> taskInfo; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(DoAll( FutureArg<1>(&taskInfo), SendStatusUpdateFromTask(TASK_RUNNING))); driver.launchTasks(offers.get()[0].id(), tasks); AWAIT_READY(taskInfo); // The master hook will hang an extra label off. const Labels& labels_ = taskInfo.get().labels(); ASSERT_EQ(3, labels_.labels_size()); // The slave run task hook will prepend a new "baz":"qux" label. EXPECT_EQ(labels_.labels(0).key(), "baz"); EXPECT_EQ(labels_.labels(0).value(), "qux"); // Master launch task hook will still hang off test label. EXPECT_EQ(labels_.labels(1).key(), testLabelKey); EXPECT_EQ(labels_.labels(1).value(), testLabelValue); // And lastly, we only expect the "foo":"bar" pair to be stripped by // the module. The last pair should be the original "bar":"baz" // pair set by the test. EXPECT_EQ(labels_.labels(2).key(), "bar"); EXPECT_EQ(labels_.labels(2).value(), "baz"); driver.stop(); driver.join(); Shutdown(); // Must shutdown before 'containerizer' gets deallocated. }
// Test executor environment decorator hook and remove executor hook // for slave. We expect the environment-decorator hook to create a // temporary file and the remove-executor hook to delete that file. TEST_F(HookTest, DISABLED_VerifySlaveLaunchExecutorHook) { master::Flags masterFlags = CreateMasterFlags(); Try<PID<Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); slave::Flags slaveFlags = CreateSlaveFlags(); MockExecutor exec(DEFAULT_EXECUTOR_ID); TestContainerizer containerizer(&exec); Try<PID<Slave>> slave = StartSlave(&containerizer); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get(), 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(); AWAIT_READY(offers); EXPECT_NE(0u, offers.get().size()); // Launch a task with the command executor. TaskInfo task; task.set_name(""); task.mutable_task_id()->set_value("1"); task.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id()); task.mutable_resources()->CopyFrom(offers.get()[0].resources()); task.mutable_executor()->CopyFrom(DEFAULT_EXECUTOR_INFO); vector<TaskInfo> tasks; tasks.push_back(task); EXPECT_CALL(exec, launchTask(_, _)); Future<ExecutorInfo> executorInfo; EXPECT_CALL(exec, registered(_, _, _, _)) .WillOnce(FutureArg<1>(&executorInfo)); // On successful completion of the "slaveLaunchExecutorHook", the // test hook will send a HookExecuted message to itself. We wait // until that message is intercepted by the testing infrastructure. Future<HookExecuted> hookFuture = FUTURE_PROTOBUF(HookExecuted(), _, _); driver.launchTasks(offers.get()[0].id(), tasks); AWAIT_READY(executorInfo); driver.stop(); driver.join(); Shutdown(); // Must shutdown before 'containerizer' gets deallocated. // Now wait for the hook to finish execution. AWAIT_READY(hookFuture); }
// Test that the label decorator hook hangs a new label off the // taskinfo message during master launch task. TEST_F(HookTest, VerifyMasterLaunchTaskHook) { Try<PID<Master>> master = StartMaster(CreateMasterFlags()); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); TestContainerizer containerizer(&exec); // Start a mock slave since we aren't testing the slave hooks yet. Try<PID<Slave>> slave = StartSlave(&containerizer); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get(), 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(); AWAIT_READY(offers); EXPECT_NE(0u, offers.get().size()); TaskInfo task; task.set_name(""); task.mutable_task_id()->set_value("1"); task.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id()); task.mutable_resources()->CopyFrom(offers.get()[0].resources()); task.mutable_executor()->CopyFrom(DEFAULT_EXECUTOR_INFO); // Add label which will be removed by the hook. Labels* labels = task.mutable_labels(); Label* label = labels->add_labels(); label->set_key(testRemoveLabelKey); label->set_value(testRemoveLabelValue); vector<TaskInfo> tasks; tasks.push_back(task); Future<RunTaskMessage> runTaskMessage = FUTURE_PROTOBUF(RunTaskMessage(), _, _); EXPECT_CALL(exec, registered(_, _, _, _)); EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING)); Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)) .WillRepeatedly(Return()); driver.launchTasks(offers.get()[0].id(), tasks); AWAIT_READY(runTaskMessage); AWAIT_READY(status); // At launchTasks, the label decorator hook inside should have been // executed and we should see the labels now. Also, verify that the // hook module has stripped the first 'testRemoveLabelKey' label. // We do this by ensuring that only one label is present and that it // is the new 'testLabelKey' label. const Labels &labels_ = runTaskMessage.get().task().labels(); ASSERT_EQ(1, labels_.labels_size()); EXPECT_EQ(labels_.labels().Get(0).key(), testLabelKey); EXPECT_EQ(labels_.labels().Get(0).value(), testLabelValue); driver.stop(); driver.join(); Shutdown(); // Must shutdown before 'containerizer' gets deallocated. }
// Checks that if a task is launched and then finishes normally, its // resources are recovered and reoffered correctly. TYPED_TEST(AllocatorTest, TaskFinished) { EXPECT_CALL(this->allocator, initialize(_, _, _)); master::Flags masterFlags = this->CreateMasterFlags(); masterFlags.allocation_interval = Milliseconds(50); Try<PID<Master> > master = this->StartMaster(&this->allocator, masterFlags); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); slave::Flags flags = this->CreateSlaveFlags(); flags.resources = Option<string>("cpus:3;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave = this->StartSlave(&exec, flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver(&sched, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched, registered(_, _, _)); // We decline offers that we aren't expecting so that the resources // get aggregated. Note that we need to do this _first_ and // _separate_ from the expectation below so that this expectation is // checked last and matches all possible offers. EXPECT_CALL(sched, resourceOffers(_, _)) .WillRepeatedly(DeclineOffers()); // Initially, all of the slave's resources. EXPECT_CALL(sched, resourceOffers(_, OfferEq(3, 1024))) .WillOnce(LaunchTasks(2, 1, 256)); // Some resources will be unused and we need to make sure that we // don't send the TASK_FINISHED status update below until after the // allocator knows about the unused resources so that it can // aggregate them with the resources from the finished task. Future<Nothing> resourcesUnused; EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)) .WillRepeatedly(DoAll(InvokeResourcesUnused(&this->allocator), FutureSatisfy(&resourcesUnused))); EXPECT_CALL(exec, registered(_, _, _, _)); ExecutorDriver* execDriver; TaskInfo taskInfo; Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(DoAll(SaveArg<0>(&execDriver), SaveArg<1>(&taskInfo), SendStatusUpdateFromTask(TASK_RUNNING), FutureSatisfy(&launchTask))) .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING)); EXPECT_CALL(sched, statusUpdate(_, _)) .WillRepeatedly(DoDefault()); driver.start(); AWAIT_READY(launchTask); AWAIT_READY(resourcesUnused); TaskStatus status; status.mutable_task_id()->MergeFrom(taskInfo.task_id()); status.set_state(TASK_FINISHED); EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)); // After the first task gets killed. Future<Nothing> resourceOffers; EXPECT_CALL(sched, resourceOffers(_, OfferEq(2, 768))) .WillOnce(FutureSatisfy(&resourceOffers)); execDriver->sendStatusUpdate(status); AWAIT_READY(resourceOffers); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(1)); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); driver.stop(); driver.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); }
// This test ensures we don't break the API when it comes to JSON // representation of tasks. TEST(HTTPTest, ModelTask) { TaskID taskId; taskId.set_value("t"); SlaveID slaveId; slaveId.set_value("s"); ExecutorID executorId; executorId.set_value("t"); FrameworkID frameworkId; frameworkId.set_value("f"); TaskState state = TASK_RUNNING; vector<TaskStatus> statuses; TaskStatus status; status.mutable_task_id()->CopyFrom(taskId); status.set_state(state); status.mutable_slave_id()->CopyFrom(slaveId); status.mutable_executor_id()->CopyFrom(executorId); status.set_timestamp(0.0); statuses.push_back(status); Labels labels; labels.add_labels()->CopyFrom(createLabel("ACTION", "port:7987 DENY")); Ports ports; Port* port = ports.add_ports(); port->set_number(80); port->mutable_labels()->CopyFrom(labels); DiscoveryInfo discovery; discovery.set_visibility(DiscoveryInfo::CLUSTER); discovery.set_name("discover"); discovery.mutable_ports()->CopyFrom(ports); TaskInfo taskInfo; taskInfo.set_name("task"); taskInfo.mutable_task_id()->CopyFrom(taskId); taskInfo.mutable_slave_id()->CopyFrom(slaveId); taskInfo.mutable_command()->set_value("echo hello"); taskInfo.mutable_discovery()->CopyFrom(discovery); Task task = createTask(taskInfo, state, frameworkId); task.add_statuses()->CopyFrom(statuses[0]); JSON::Value object = model(task); Try<JSON::Value> expected = JSON::parse( "{" " \"executor_id\":\"\"," " \"framework_id\":\"f\"," " \"id\":\"t\"," " \"name\":\"task\"," " \"resources\":" " {" " \"cpus\":0," " \"disk\":0," " \"gpus\":0," " \"mem\":0" " }," " \"slave_id\":\"s\"," " \"state\":\"TASK_RUNNING\"," " \"statuses\":" " [" " {" " \"state\":\"TASK_RUNNING\"," " \"timestamp\":0" " }" " ]," " \"discovery\":" " {" " \"name\":\"discover\"," " \"ports\":" " {" " \"ports\":" " [" " {" " \"number\":80," " \"labels\":" " {" " \"labels\":" " [" " {" " \"key\":\"ACTION\"," " \"value\":\"port:7987 DENY\"" " }" " ]" " }" " }" " ]" " }," " \"visibility\":\"CLUSTER\"" " }" "}"); ASSERT_SOME(expected); EXPECT_EQ(expected.get(), object); }
// Checks that a slave that is not whitelisted will not have its // resources get offered, and that if the whitelist is updated so // that it is whitelisted, its resources will then be offered. TYPED_TEST(AllocatorTest, WhitelistSlave) { // Create a dummy whitelist, so that no resources will get allocated. string hosts = "dummy-slave"; string path = "whitelist.txt"; ASSERT_SOME(os::write(path, hosts)) << "Error writing whitelist"; master::Flags masterFlags = this->CreateMasterFlags(); masterFlags.whitelist = "file://" + path; // TODO(benh): Put in /tmp. EXPECT_CALL(this->allocator, initialize(_, _, _)); Future<Nothing> updateWhitelist1; EXPECT_CALL(this->allocator, updateWhitelist(_)) .WillOnce(DoAll(InvokeUpdateWhitelist(&this->allocator), FutureSatisfy(&updateWhitelist1))); Try<PID<Master> > master = this->StartMaster(&this->allocator, masterFlags); ASSERT_SOME(master); slave::Flags flags = this->CreateSlaveFlags(); flags.resources = Option<string>("cpus:2;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave = this->StartSlave(flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver(&sched, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched, registered(_, _, _)); // Once the slave gets whitelisted, all of its resources should be // offered to the one framework running. Future<Nothing> resourceOffers; EXPECT_CALL(sched, resourceOffers(_, OfferEq(2, 1024))) .WillOnce(FutureSatisfy(&resourceOffers)); // Make sure the allocator has been given the original, empty // whitelist. AWAIT_READY(updateWhitelist1); driver.start(); // Give the allocator some time to confirm that it doesn't // make an allocation. Clock::pause(); Clock::advance(Seconds(1)); Clock::settle(); EXPECT_FALSE(resourceOffers.isReady()); // Update the whitelist to include the slave, so that // the allocator will start making allocations. Try<string> hostname = os::hostname(); ASSERT_SOME(hostname); hosts = hostname.get() + "\n" + "dummy-slave"; EXPECT_CALL(this->allocator, updateWhitelist(_)); ASSERT_SOME(os::write(path, hosts)) << "Error writing whitelist"; // Give the WhitelistWatcher some time to notice that // the whitelist has changed. while (resourceOffers.isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } Clock::resume(); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(1)); driver.stop(); driver.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); os::rm(path); }
// This tests the authorization of ACLs used for unreserve // operations on dynamically reserved resources. TYPED_TEST(AuthorizationTest, Unreserve) { ACLs acls; // "foo" principal can unreserve its own resources. mesos::ACL::UnreserveResources* acl1 = acls.add_unreserve_resources(); acl1->mutable_principals()->add_values("foo"); acl1->mutable_reserver_principals()->add_values("foo"); // "bar" principal cannot unreserve anyone's resources. mesos::ACL::UnreserveResources* acl2 = acls.add_unreserve_resources(); acl2->mutable_principals()->add_values("bar"); acl2->mutable_reserver_principals()->set_type(mesos::ACL::Entity::NONE); // "ops" principal can unreserve anyone's resources. mesos::ACL::UnreserveResources* acl3 = acls.add_unreserve_resources(); acl3->mutable_principals()->add_values("ops"); acl3->mutable_reserver_principals()->set_type(mesos::ACL::Entity::ANY); // No other principals can unreserve resources. mesos::ACL::UnreserveResources* acl4 = acls.add_unreserve_resources(); acl4->mutable_principals()->set_type(mesos::ACL::Entity::ANY); acl4->mutable_reserver_principals()->set_type(mesos::ACL::Entity::NONE); // Create an Authorizer with the ACLs. Try<Authorizer*> create = TypeParam::create(); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); Try<Nothing> initialized = authorizer.get()->initialize(acls); ASSERT_SOME(initialized); // Principal "foo" can unreserve its own resources. mesos::ACL::UnreserveResources request1; request1.mutable_principals()->add_values("foo"); request1.mutable_reserver_principals()->add_values("foo"); AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request1)); // Principal "bar" cannot unreserve anyone's // resources, so requests 2 and 3 will fail. mesos::ACL::UnreserveResources request2; request2.mutable_principals()->add_values("bar"); request2.mutable_reserver_principals()->add_values("foo"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request2)); mesos::ACL::UnreserveResources request3; request3.mutable_principals()->add_values("bar"); request3.mutable_reserver_principals()->add_values("bar"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request3)); // Principal "ops" can unreserve anyone's resources, // so requests 4 and 5 will succeed. mesos::ACL::UnreserveResources request4; request4.mutable_principals()->add_values("ops"); request4.mutable_reserver_principals()->add_values("foo"); AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request4)); mesos::ACL::UnreserveResources request5; request5.mutable_principals()->add_values("ops"); request5.mutable_reserver_principals()->add_values("foo"); request5.mutable_reserver_principals()->add_values("bar"); request5.mutable_reserver_principals()->add_values("ops"); AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request5)); // Principal "zelda" is not mentioned in the ACLs of the Authorizer, so it // will be caught by the final ACL, which provides a default case that denies // access for all other principals. This case will fail. mesos::ACL::UnreserveResources request6; request6.mutable_principals()->add_values("zelda"); request6.mutable_reserver_principals()->add_values("foo"); AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request6)); }
// Checks that when a task is launched with fewer resources than what // the offer was for, the resources that are returned unused are // reoffered appropriately. TYPED_TEST(AllocatorTest, ResourcesUnused) { EXPECT_CALL(this->allocator, initialize(_, _, _)); Try<PID<Master> > master = this->StartMaster(&this->allocator); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); slave::Flags flags1 = this->CreateSlaveFlags(); flags1.resources = Option<string>("cpus:2;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave1 = this->StartSlave(&exec, flags1); ASSERT_SOME(slave1); MockScheduler sched1; MesosSchedulerDriver driver1(&sched1, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched1, registered(_, _, _)); // We decline offers that we aren't expecting so that the resources // get aggregated. Note that we need to do this _first_ and // _separate_ from the expectation below so that this expectation is // checked last and matches all possible offers. EXPECT_CALL(sched1, resourceOffers(_, _)) .WillRepeatedly(DeclineOffers()); // The first offer will contain all of the slave's resources, since // this is the only framework running so far. Launch a task that // uses less than that to leave some resources unused. EXPECT_CALL(sched1, resourceOffers(_, OfferEq(2, 1024))) .WillOnce(LaunchTasks(1, 1, 512)); Future<Nothing> resourcesUnused; EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)) .WillOnce(DoAll(InvokeResourcesUnused(&this->allocator), FutureSatisfy(&resourcesUnused))); EXPECT_CALL(exec, registered(_, _, _, _)); Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(FutureSatisfy(&launchTask)); driver1.start(); AWAIT_READY(launchTask); // We need to wait until the allocator knows about the unused // resources to start the second framework so that we get the // expected offer. AWAIT_READY(resourcesUnused); FrameworkInfo frameworkInfo2; frameworkInfo2.set_user("user2"); frameworkInfo2.set_name("framework2"); MockScheduler sched2; MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched2, registered(_, _, _)); // We should expect that framework2 gets offered all of the // resources on the slave not being used by the launched task. Future<Nothing> resourceOffers; EXPECT_CALL(sched2, resourceOffers(_, OfferEq(1, 512))) .WillOnce(FutureSatisfy(&resourceOffers)); driver2.start(); AWAIT_READY(resourceOffers); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(2)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(2)); Future<Nothing> shutdown; EXPECT_CALL(exec, shutdown(_)) .WillOnce(FutureSatisfy(&shutdown)); driver1.stop(); driver1.join(); driver2.stop(); driver2.join(); AWAIT_READY(shutdown); // Ensures MockExecutor can be deallocated. EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); }
// This method will be called when a container running as non-root user tries // to use a shared persistent volume or a PARENT type SANDBOX_PATH volume, the // parameter `path` will be the source path of the volume. Future<gid_t> allocate(const string& path, VolumeGidInfo::Type type) { gid_t gid; // If a gid has already been allocated for the specified path, // just return the gid. if (infos.contains(path)) { gid = infos[path].gid(); LOG(INFO) << "Use the allocated gid " << gid << " of the volume path '" << path << "'"; // If we are already setting ownership for the specified path, skip the // additional setting. if (setting.contains(path)) { return setting[path]->future(); } } else { struct stat s; if (::stat(path.c_str(), &s) < 0) { return Failure("Failed to stat '" + path + "': " + os::strerror(errno)); } // If the gid of the specified path is in the total gid range, just // return the gid. This could happen in the case that nested container // uses persistent volume, in which case we did a workaround in the // default executor to set up a volume mapping (i.e., map the persistent // volume to a PARENT type SANDBOX_PATH volume for the nested container) // so that the nested container can access the persistent volume. // // Please note that in the case of shared persistent volume, operator // should NOT restart agent with a different total gid range, otherwise // the gid of the shared persistent volume may be overwritten if a nested // container tries to use the shared persistent volume after the restart. if (totalGids.contains(s.st_gid)) { gid = s.st_gid; LOG(INFO) << "Use the gid " << gid << " for the volume path '" << path << "' which should be the mount point of another volume " << "which is actually allocated with the gid"; } else { // Allocate a free gid to the specified path and then set the // ownership for it. if (freeGids.empty()) { return Failure( "Failed to allocate gid to the volume path '" + path + "' because the free gid range is exhausted"); } gid = freeGids.begin()->lower(); LOG(INFO) << "Allocating gid " << gid << " to the volume path '" << path << "'"; freeGids -= gid; --metrics.volume_gids_free; VolumeGidInfo info; info.set_type(type); info.set_path(path); info.set_gid(gid); infos.put(path, info); Try<Nothing> status = persist(); if (status.isError()) { return Failure( "Failed to save state of volume gid infos: " + status.error()); } Owned<Promise<gid_t>> promise(new Promise<gid_t>()); Future<gid_t> future = async(&setVolumeOwnership, path, gid, true) .then([path, gid](const Try<Nothing>& result) -> Future<gid_t> { if (result.isError()) { return Failure( "Failed to set the owner group of the volume path '" + path + "' to " + stringify(gid) + ": " + result.error()); } return gid; }) .onAny(defer(self(), [=](const Future<gid_t>&) { setting.erase(path); })); promise->associate(future); setting[path] = promise; return promise->future(); } } return gid; }
// Checks that if a framework launches a task and then fails over to a // new scheduler, the task's resources are not reoffered as long as it // is running. TYPED_TEST(AllocatorTest, SchedulerFailover) { EXPECT_CALL(this->allocator, initialize(_, _, _)); Try<PID<Master> > master = this->StartMaster(&this->allocator); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); slave::Flags flags = this->CreateSlaveFlags(); flags.resources = Option<string>("cpus:3;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave = this->StartSlave(&exec, flags); ASSERT_SOME(slave); FrameworkInfo frameworkInfo1; frameworkInfo1.set_name("framework1"); frameworkInfo1.set_user("user1"); frameworkInfo1.set_failover_timeout(.1); // Launch the first (i.e., failing) scheduler. MockScheduler sched1; MesosSchedulerDriver driver1(&sched1, frameworkInfo1, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); FrameworkID frameworkId; EXPECT_CALL(sched1, registered(&driver1, _, _)) .WillOnce(SaveArg<1>(&frameworkId)); // We decline offers that we aren't expecting so that the resources // get aggregated. Note that we need to do this _first_ and // _separate_ from the expectation below so that this expectation is // checked last and matches all possible offers. EXPECT_CALL(sched1, resourceOffers(_, _)) .WillRepeatedly(DeclineOffers()); // Initially, all of slave1's resources are avaliable. EXPECT_CALL(sched1, resourceOffers(_, OfferEq(3, 1024))) .WillOnce(LaunchTasks(1, 1, 256)); // We don't filter the unused resources to make sure that // they get offered to the framework as soon as it fails over. EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)) .WillOnce(InvokeUnusedWithFilters(&this->allocator, 0)); EXPECT_CALL(exec, registered(_, _, _, _)); Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(FutureSatisfy(&launchTask)); driver1.start(); // Ensures that the task has been completely launched // before we have the framework fail over. AWAIT_READY(launchTask); // When we shut down the first framework, we don't want it to tell // the master it's shutting down so that the master will wait to see // if it fails over. DROP_PROTOBUFS(UnregisterFrameworkMessage(), _, _); Future<Nothing> frameworkDeactivated; EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .WillOnce(DoAll(InvokeFrameworkDeactivated(&this->allocator), FutureSatisfy(&frameworkDeactivated))); driver1.stop(); AWAIT_READY(frameworkDeactivated); FrameworkInfo frameworkInfo2; // Bug in gcc 4.1.*, must assign on next line. frameworkInfo2 = DEFAULT_FRAMEWORK_INFO; frameworkInfo2.mutable_id()->MergeFrom(frameworkId); // Now launch the second (i.e., failover) scheduler using the // framework id recorded from the first scheduler. MockScheduler sched2; MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get()); EXPECT_CALL(this->allocator, frameworkActivated(_, _)); EXPECT_CALL(sched2, registered(_, frameworkId, _)); // Even though the scheduler failed over, the 1 cpu, 512 mem // task that it launched earlier should still be running, so // only 2 cpus and 768 mem are available. Future<Nothing> resourceOffers; EXPECT_CALL(sched2, resourceOffers(_, OfferEq(2, 768))) .WillOnce(FutureSatisfy(&resourceOffers)); driver2.start(); AWAIT_READY(resourceOffers); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(1)); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); driver2.stop(); driver2.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); }
// This method will be called in two cases: // 1. When a shared persistent volume is destroyed by agent, the parameter // `path` will be the shared persistent volume's path. // 2. When a container is destroyed by containerizer, the parameter `path` // will be the container's sandbox path. // We search if the given path is contained in `infos` (for the case 1) or is // the parent directory of any volume paths in `infos` (for the case 2, i.e., // the PARENT type SANDBOX_PATH volume must be a subdirectory in the parent // container's sandbox) and then free the allocated gid for the found path(s). Future<Nothing> deallocate(const string& path) { vector<string> sandboxPathVolumes; bool changed = false; for (auto it = infos.begin(); it != infos.end(); ) { const VolumeGidInfo& info = it->second; const string& volumePath = info.path(); if (strings::startsWith(volumePath, path)) { if (volumePath != path) { // This is the case of the PARENT type SANDBOX_PATH volume. sandboxPathVolumes.push_back(volumePath); } gid_t gid = info.gid(); LOG(INFO) << "Deallocated gid " << gid << " for the volume path '" << volumePath << "'"; // Only return the gid to the free range if it is in the total // range. The gid may not be in the total range in the case that // Mesos agent is restarted with a different total range and we // deallocate gid for a previous volume path from the old range. if (totalGids.contains(gid)) { freeGids += gid; ++metrics.volume_gids_free; } it = infos.erase(it); changed = true; } else { ++it; } } // For the PARENT type SANDBOX_PATH volume, it will exist for a while // (depending on GC policy) after the container is destroyed. So to // avoid leaking it to other containers in the case that its gid is // allocated to another volume, we need to change its owner group back // to the original one (i.e., the primary group of its owner). vector<Future<Try<Nothing>>> futures; vector<pair<string, gid_t>> volumeGids; foreach (const string& volume, sandboxPathVolumes) { // Get the uid of the volume's owner. struct stat s; if (::stat(volume.c_str(), &s) < 0) { LOG(WARNING) << "Failed to stat '" << volume << "': " << os::strerror(errno); continue; } Result<string> user = os::user(s.st_uid); if (!user.isSome()) { LOG(WARNING) << "Failed to get username for the uid " << s.st_uid << ": " << (user.isError() ? user.error() : "not found"); continue; } // Get the primary group ID of the user. Result<gid_t> gid = os::getgid(user.get()); if (!gid.isSome()) { LOG(WARNING) << "Failed to get gid for the user '" << user.get() << "': " << (gid.isError() ? gid.error() : "not found"); continue; } futures.push_back(async(&setVolumeOwnership, volume, gid.get(), false)); volumeGids.push_back({volume, gid.get()}); } return await(futures) .then(defer( self(), [=](const vector<Future<Try<Nothing>>>& results) -> Future<Nothing> { for (size_t i = 0; i < results.size(); ++i) { const Future<Try<Nothing>>& result = results[i]; const string& path = volumeGids[i].first; const gid_t gid = volumeGids[i].second; if (!result.isReady()) { LOG(WARNING) << "Failed to set the owner group of the volume " << "path '" << path << "' back to " << gid << ": " << (result.isFailed() ? result.failure() : "discarded"); } else if (result->isError()) { LOG(WARNING) << "Failed to set the owner group of the volume " << "path '" << path << "' back to " << gid << ": " << result->error(); } } if (changed) { Try<Nothing> status = persist(); if (status.isError()) { return Failure( "Failed to save state of volume gid infos: " + status.error()); } } return Nothing(); })); }
// Checks that if a framework launches a task and then the framework // is killed, the tasks resources are returned and reoffered correctly. TYPED_TEST(AllocatorTest, FrameworkExited) { EXPECT_CALL(this->allocator, initialize(_, _, _)); master::Flags masterFlags = this->CreateMasterFlags(); masterFlags.allocation_interval = Milliseconds(50); Try<PID<Master> > master = this->StartMaster(&this->allocator, masterFlags); ASSERT_SOME(master); // TODO(benh): We use this executor for two frameworks in this test // which works but is brittle and harder to reason about. MockExecutor exec(DEFAULT_EXECUTOR_ID); slave::Flags flags = this->CreateSlaveFlags(); flags.resources = Option<string>("cpus:3;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave = this->StartSlave(&exec, flags); ASSERT_SOME(slave); MockScheduler sched1; MesosSchedulerDriver driver1(&sched1, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched1, registered(_, _, _)); // We decline offers that we aren't expecting so that the resources // get aggregated. Note that we need to do this _first_ and // _separate_ from the expectation below so that this expectation is // checked last and matches all possible offers. EXPECT_CALL(sched1, resourceOffers(_, _)) .WillRepeatedly(DeclineOffers()); // The first time the framework is offered resources, all of the // cluster's resources should be avaliable. EXPECT_CALL(sched1, resourceOffers(_, OfferEq(3, 1024))) .WillOnce(LaunchTasks(1, 2, 512)); // The framework does not use all the resources. Future<Nothing> resourcesUnused; EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)) .WillOnce(DoAll(InvokeResourcesUnused(&this->allocator), FutureSatisfy(&resourcesUnused))); EXPECT_CALL(exec, registered(_, _, _, _)); Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(FutureSatisfy(&launchTask)); driver1.start(); // Ensures that framework 1's task is completely launched // before we kill the framework to test if its resources // are recovered correctly. AWAIT_READY(launchTask); // We need to wait until the allocator knows about the unused // resources to start the second framework so that we get the // expected offer. AWAIT_READY(resourcesUnused); MockScheduler sched2; MesosSchedulerDriver driver2(&sched2, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched2, registered(_, _, _)); // We decline offers that we aren't expecting so that the resources // get aggregated. Note that we need to do this _first_ and // _separate_ from the expectation below so that this expectation is // checked last and matches all possible offers. EXPECT_CALL(sched2, resourceOffers(_, _)) .WillRepeatedly(DeclineOffers()); // The first time sched2 gets an offer, framework 1 has a task // running with 2 cpus and 512 mem, leaving 1 cpu and 512 mem. EXPECT_CALL(sched2, resourceOffers(_, OfferEq(1, 512))) .WillOnce(LaunchTasks(1, 1, 256)); EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)); EXPECT_CALL(exec, registered(_, _, _, _)); EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(FutureSatisfy(&launchTask)); driver2.start(); AWAIT_READY(launchTask); // Shut everything down but check that framework 2 gets the // resources from framework 1 after it is shutdown. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(2)); // Once for each framework. EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(2)); // Once for each framework. // After we stop framework 1, all of it's resources should // have been returned, but framework 2 should still have a // task with 1 cpu and 256 mem, leaving 2 cpus and 768 mem. Future<Nothing> resourceOffers; EXPECT_CALL(sched2, resourceOffers(_, OfferEq(2, 768))) .WillOnce(FutureSatisfy(&resourceOffers)); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); driver1.stop(); driver1.join(); AWAIT_READY(resourceOffers); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); driver2.stop(); driver2.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(1)); this->Shutdown(); }
TEST_F(MesosContainerizerLaunchTest, ROOT_ChangeRootfs) { Try<Owned<Rootfs>> rootfs = LinuxRootfs::create(path::join(os::getcwd(), "rootfs")); ASSERT_SOME(rootfs); // Add /usr/bin/stat into the rootfs. ASSERT_SOME(rootfs.get()->add("/usr/bin/stat")); Clock::pause(); Try<Subprocess> s = run( "/usr/bin/stat -c %i / >" + path::join("/", "stat.output"), rootfs.get()->root); ASSERT_SOME(s); // Advance time until the internal reaper reaps the subprocess. while (s.get().status().isPending()) { Clock::advance(process::MAX_REAP_INTERVAL()); Clock::settle(); } AWAIT_ASSERT_READY(s.get().status()); ASSERT_SOME(s.get().status().get()); int status = s.get().status().get().get(); ASSERT_TRUE(WIFEXITED(status)); ASSERT_EQ(0, WEXITSTATUS(status)); // Check the rootfs has a different root by comparing the inodes. Try<ino_t> self = os::stat::inode("/"); ASSERT_SOME(self); Try<string> read = os::read(path::join(rootfs.get()->root, "stat.output")); ASSERT_SOME(read); Try<ino_t> other = numify<ino_t>(strings::trim(read.get())); ASSERT_SOME(other); EXPECT_NE(self.get(), other.get()); }
// Checks that if a slave is added after some allocations have already // occurred, its resources are added to the available pool of // resources and offered appropriately. TYPED_TEST(AllocatorTest, SlaveAdded) { EXPECT_CALL(this->allocator, initialize(_, _, _)); master::Flags masterFlags = this->CreateMasterFlags(); masterFlags.allocation_interval = Milliseconds(50); Try<PID<Master> > master = this->StartMaster(&this->allocator, masterFlags); ASSERT_SOME(master); MockExecutor exec(DEFAULT_EXECUTOR_ID); slave::Flags flags1 = this->CreateSlaveFlags(); flags1.resources = Option<string>("cpus:3;mem:1024"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); Try<PID<Slave> > slave1 = this->StartSlave(&exec, flags1); ASSERT_SOME(slave1); MockScheduler sched; MesosSchedulerDriver driver(&sched, DEFAULT_FRAMEWORK_INFO, master.get()); EXPECT_CALL(this->allocator, frameworkAdded(_, _, _)); EXPECT_CALL(sched, registered(_, _, _)); // We decline offers that we aren't expecting so that the resources // get aggregated. Note that we need to do this _first_ and // _separate_ from the expectation below so that this expectation is // checked last and matches all possible offers. EXPECT_CALL(sched, resourceOffers(_, _)) .WillRepeatedly(DeclineOffers()); // Initially, all of slave1's resources are avaliable. EXPECT_CALL(sched, resourceOffers(_, OfferEq(3, 1024))) .WillOnce(LaunchTasks(1, 2, 512)); // We filter the first time so that the unused resources // on slave1 from the task launch won't get reoffered // immediately and will get combined with slave2's // resources for a single offer. EXPECT_CALL(this->allocator, resourcesUnused(_, _, _, _)) .WillOnce(InvokeUnusedWithFilters(&this->allocator, 0.1)) .WillRepeatedly(InvokeUnusedWithFilters(&this->allocator, 0)); EXPECT_CALL(exec, registered(_, _, _, _)); Future<Nothing> launchTask; EXPECT_CALL(exec, launchTask(_, _)) .WillOnce(DoAll(SendStatusUpdateFromTask(TASK_RUNNING), FutureSatisfy(&launchTask))); EXPECT_CALL(sched, statusUpdate(_, _)) .WillRepeatedly(DoDefault()); driver.start(); AWAIT_READY(launchTask); slave::Flags flags2 = this->CreateSlaveFlags(); flags2.resources = Option<string>("cpus:4;mem:2048"); EXPECT_CALL(this->allocator, slaveAdded(_, _, _)); // After slave2 launches, all of its resources are combined with the // resources on slave1 that the task isn't using. Future<Nothing> resourceOffers; EXPECT_CALL(sched, resourceOffers(_, OfferEq(5, 2560))) .WillOnce(FutureSatisfy(&resourceOffers)); Try<PID<Slave> > slave2 = this->StartSlave(flags2); ASSERT_SOME(slave2); AWAIT_READY(resourceOffers); // Shut everything down. EXPECT_CALL(this->allocator, resourcesRecovered(_, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(this->allocator, frameworkDeactivated(_)) .Times(AtMost(1)); EXPECT_CALL(this->allocator, frameworkRemoved(_)) .Times(AtMost(1)); EXPECT_CALL(exec, shutdown(_)) .Times(AtMost(1)); driver.stop(); driver.join(); EXPECT_CALL(this->allocator, slaveRemoved(_)) .Times(AtMost(2)); this->Shutdown(); }
// Tests that the packaged logrotate container logger writes files into the // sandbox and keeps them at a reasonable size. TEST_F(ContainerLoggerTest, LOGROTATE_RotateInSandbox) { // 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(); // Use the non-default container logger that rotates logs. flags.container_logger = LOGROTATE_CONTAINER_LOGGER_NAME; 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()); // Start a task that spams stdout with 11 MB of (mostly blank) output. // The logrotate container logger module is loaded with parameters that limit // the log size to five files of 2 MB each. After the task completes, there // should be five files with a total size of 9 MB. The first 2 MB file // should have been deleted. The "stdout" file should be 1 MB large. TaskInfo task = createTask( offers.get()[0], "i=0; while [ $i -lt 11264 ]; " "do printf '%-1024d\\n' $i; i=$((i+1)); done"); Future<TaskStatus> statusRunning; Future<TaskStatus> statusFinished; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusFinished)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(statusRunning); EXPECT_EQ(TASK_RUNNING, statusRunning.get().state()); AWAIT_READY(statusFinished); EXPECT_EQ(TASK_FINISHED, statusFinished.get().state()); driver.stop(); driver.join(); Shutdown(); // The `LogrotateContainerLogger` spawns some `mesos-logrotate-logger` // processes above, which continue running briefly after the container exits. // Once they finish reading the container's pipe, they should exit. Try<os::ProcessTree> pstrees = os::pstree(0); ASSERT_SOME(pstrees); foreach (const os::ProcessTree& pstree, pstrees.get().children) { ASSERT_EQ(pstree.process.pid, waitpid(pstree.process.pid, NULL, 0)); } // Check for the expected log rotation. string sandboxDirectory = path::join( slave::paths::getExecutorPath( flags.work_dir, slaveId, frameworkId.get(), statusRunning->executor_id()), "runs", "latest"); ASSERT_TRUE(os::exists(sandboxDirectory)); // The leading log file should be about half full (1 MB). string stdoutPath = path::join(sandboxDirectory, "stdout"); ASSERT_TRUE(os::exists(stdoutPath)); // NOTE: We don't expect the size of the leading log file to be precisely // one MB since there is also the executor's output besides the task's stdout. Try<Bytes> stdoutSize = os::stat::size(stdoutPath); ASSERT_SOME(stdoutSize); EXPECT_LE(1024, stdoutSize->kilobytes()); EXPECT_GE(1050, stdoutSize->kilobytes()); // We should only have files up to "stdout.4". stdoutPath = path::join(sandboxDirectory, "stdout.5"); EXPECT_FALSE(os::exists(stdoutPath)); // The next four rotated log files (2 MB each) should be present. for (int i = 1; i < 5; i++) { stdoutPath = path::join(sandboxDirectory, "stdout." + stringify(i)); ASSERT_TRUE(os::exists(stdoutPath)); // NOTE: The rotated files are written in contiguous blocks, meaning that // each file may be less than the maximum allowed size. stdoutSize = os::stat::size(stdoutPath); EXPECT_LE(2040, stdoutSize->kilobytes()); EXPECT_GE(2048, stdoutSize->kilobytes()); } }
// Fetch URI into directory. Try<string> fetch( const string& uri, const string& directory) { LOG(INFO) << "Fetching URI '" << uri << "'"; // Some checks to make sure using the URI value in shell commands // is safe. TODO(benh): These should be pushed into the scheduler // driver and reported to the user. if (uri.find_first_of('\\') != string::npos || uri.find_first_of('\'') != string::npos || uri.find_first_of('\0') != string::npos) { LOG(ERROR) << "URI contains illegal characters, refusing to fetch"; return Error("Illegal characters in URI"); } // Grab the resource using the hadoop client if it's one of the known schemes // TODO(tarnfeld): This isn't very scalable with hadoop's pluggable // filesystem implementations. // TODO(matei): Enforce some size limits on files we get from HDFS if (strings::startsWith(uri, "hdfs://") || strings::startsWith(uri, "hftp://") || strings::startsWith(uri, "s3://") || strings::startsWith(uri, "s3n://")) { Try<string> base = os::basename(uri); if (base.isError()) { LOG(ERROR) << "Invalid basename for URI: " << base.error(); return Error("Invalid basename for URI"); } string path = path::join(directory, base.get()); HDFS hdfs; LOG(INFO) << "Downloading resource from '" << uri << "' to '" << path << "'"; Try<Nothing> result = hdfs.copyToLocal(uri, path); if (result.isError()) { LOG(ERROR) << "HDFS copyToLocal failed: " << result.error(); return Error(result.error()); } return path; } else if (strings::startsWith(uri, "http://") || strings::startsWith(uri, "https://") || strings::startsWith(uri, "ftp://") || strings::startsWith(uri, "ftps://")) { string path = uri.substr(uri.find("://") + 3); if (path.find("/") == string::npos || path.size() <= path.find("/") + 1) { LOG(ERROR) << "Malformed URL (missing path)"; return Error("Malformed URI"); } path = path::join(directory, path.substr(path.find_last_of("/") + 1)); LOG(INFO) << "Downloading '" << uri << "' to '" << path << "'"; Try<int> code = net::download(uri, path); if (code.isError()) { LOG(ERROR) << "Error downloading resource: " << code.error().c_str(); return Error("Fetch of URI failed (" + code.error() + ")"); } else if (code.get() != 200) { LOG(ERROR) << "Error downloading resource, received HTTP/FTP return code " << code.get(); return Error("HTTP/FTP error (" + stringify(code.get()) + ")"); } return path; } else { // Copy the local resource. string local = uri; bool fileUri = false; if (strings::startsWith(local, string(FILE_URI_LOCALHOST))) { local = local.substr(sizeof(FILE_URI_LOCALHOST) - 1); fileUri = true; } else if (strings::startsWith(local, string(FILE_URI_PREFIX))) { local = local.substr(sizeof(FILE_URI_PREFIX) - 1); fileUri = true; } if(fileUri && !strings::startsWith(local, "/")) { return Error("File URI only supports absolute paths"); } if (local.find_first_of("/") != 0) { // We got a non-Hadoop and non-absolute path. if (os::hasenv("MESOS_FRAMEWORKS_HOME")) { local = path::join(os::getenv("MESOS_FRAMEWORKS_HOME"), local); LOG(INFO) << "Prepended environment variable " << "MESOS_FRAMEWORKS_HOME to relative path, " << "making it: '" << local << "'"; } else { LOG(ERROR) << "A relative path was passed for the resource but the " << "environment variable MESOS_FRAMEWORKS_HOME is not set. " << "Please either specify this config option " << "or avoid using a relative path"; return Error("Could not resolve relative URI"); } } Try<string> base = os::basename(local); if (base.isError()) { LOG(ERROR) << base.error(); return Error("Fetch of URI failed"); } // Copy the resource to the directory. string path = path::join(directory, base.get()); std::ostringstream command; command << "cp '" << local << "' '" << path << "'"; LOG(INFO) << "Copying resource from '" << local << "' to '" << directory << "'"; int status = os::system(command.str()); if (status != 0) { LOG(ERROR) << "Failed to copy '" << local << "' : Exit status " << status; return Error("Local copy failed"); } return path; } }
static Future<string> launch( const string& path, const vector<string>& argv) { Try<Subprocess> s = subprocess( path, argv, Subprocess::PATH("/dev/null"), Subprocess::PIPE(), Subprocess::PIPE()); string command = strings::join( ", ", path, strings::join(", ", argv)); if (s.isError()) { return Failure( "Failed to execute the subprocess '" + command + "': " + s.error()); } return await( s.get().status(), process::io::read(s.get().out().get()), process::io::read(s.get().err().get())) .then([command](const tuple< Future<Option<int>>, Future<string>, Future<string>>& t) -> Future<string> { Future<Option<int>> status = std::get<0>(t); if (!status.isReady()) { return Failure( "Failed to get the exit status of the subprocess: " + (status.isFailed() ? status.failure() : "discarded")); } if (status->isNone()) { return Failure("Failed to reap the subprocess"); } if (status->get() != 0) { Future<string> error = std::get<2>(t); if (!error.isReady()) { return Failure( "Unexpected result from the subprocess: " + WSTRINGIFY(status->get()) + ", stderr='" + error.get() + "'"); } return Failure("Subprocess '" + command + "' failed: " + error.get()); } Future<string> output = std::get<1>(t); if (!output.isReady()) { return Failure( "Failed to read stdout from '" + command + "': " + (output.isFailed() ? output.failure() : "discarded")); } return output; }); }
// 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(); }
TEST_F(SubprocessTest, Flags) { Clock::pause(); Flags flags; flags.b = true; flags.i = 42; flags.s = "hello"; flags.s2 = "we're"; flags.s3 = "\"geek\""; flags.d = Seconds(10); flags.y = Bytes(100); JSON::Object object; object.values["strings"] = "string"; object.values["integer1"] = 1; object.values["integer2"] = -1; object.values["double1"] = 1; object.values["double2"] = -1; object.values["double3"] = -1.42; JSON::Object nested; nested.values["string"] = "string"; object.values["nested"] = nested; JSON::Array array; array.values.push_back(nested); object.values["array"] = array; flags.j = object; string out = path::join(os::getcwd(), "stdout"); Try<Subprocess> s = subprocess( "/bin/echo", vector<string>(1, "echo"), Subprocess::PIPE(), Subprocess::PATH(out), Subprocess::PIPE(), flags); ASSERT_SOME(s); // Advance time until the internal reaper reaps the subprocess. while (s.get().status().isPending()) { Clock::advance(Seconds(1)); Clock::settle(); } AWAIT_ASSERT_READY(s.get().status()); ASSERT_SOME(s.get().status().get()); int status = s.get().status().get().get(); EXPECT_TRUE(WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); // Parse the output and make sure that it matches the flags we // specified in the beginning. Try<string> read = os::read(out); ASSERT_SOME(read); // TODO(jieyu): Consider testing the case where escaped spaces exist // in the arguments. vector<string> split = strings::split(read.get(), " "); int argc = split.size() + 1; char** argv = new char*[argc]; argv[0] = (char*) "command"; for (int i = 1; i < argc; i++) { argv[i] = ::strdup(split[i - 1].c_str()); } Flags flags2; Try<Nothing> load = flags2.load(None(), argc, argv); ASSERT_SOME(load); EXPECT_EQ(flags.b, flags2.b); EXPECT_EQ(flags.i, flags2.i); EXPECT_EQ(flags.s, flags2.s); EXPECT_EQ(flags.s2, flags2.s2); EXPECT_EQ(flags.s3, flags2.s3); EXPECT_EQ(flags.d, flags2.d); EXPECT_EQ(flags.y, flags2.y); EXPECT_EQ(flags.j, flags2.j); for (int i = 1; i < argc; i++) { ::free(argv[i]); } delete argv; Clock::resume(); }