// This test confirms that rlimits are set for nested containers. TEST_F(PosixRLimitsIsolatorTest, NestedContainers) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "posix/rlimits"; #ifndef USE_SSL_SOCKET // Disable operator API authentication for the default executor. // Executor authentication currently has SSL as a dependency, so we // cannot require executors to authenticate with the agent operator // API if Mesos was not built with SSL support. flags.authenticate_http_readwrite = false; #endif // USE_SSL_SOCKET Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); 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)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(frameworkId); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Future<TaskStatus> taskStatuses[4]; { // This variable doesn't have to be used explicitly. testing::InSequence inSequence; foreach (Future<TaskStatus>& taskStatus, taskStatuses) { EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&taskStatus)); } EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillRepeatedly(Return()); // Ignore subsequent updates. }
TEST_F(ResourceOffersTest, ResourcesGetReofferedWhenUnused) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get()); ASSERT_SOME(slave); MockScheduler sched1; MesosSchedulerDriver driver1( &sched1, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched1, registered(&driver1, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched1, resourceOffers(&driver1, _)) .WillOnce(FutureArg<1>(&offers)); driver1.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); vector<TaskInfo> tasks; // Use nothing! driver1.launchTasks(offers.get()[0].id(), tasks); MockScheduler sched2; MesosSchedulerDriver driver2( &sched2, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched2, registered(&driver2, _, _)); EXPECT_CALL(sched2, resourceOffers(&driver2, _)) .WillOnce(FutureArg<1>(&offers)); driver2.start(); AWAIT_READY(offers); // Stop first framework before second so no offers are sent. driver1.stop(); driver1.join(); driver2.stop(); driver2.join(); }
TEST_F_TEMP_DISABLED_ON_WINDOWS( ResourceOffersTest, ResourceOfferWithMultipleSlaves) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); vector<Owned<cluster::Slave>> slaves; // Start 10 slaves. for (int i = 0; i < 10; i++) { slave::Flags flags = CreateSlaveFlags(); flags.launcher = "posix"; flags.resources = Option<std::string>("cpus:2;mem:1024"); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); slaves.push_back(slave.get()); } 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()); // All 10 slaves might not be in first offer. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); EXPECT_GE(10u, offers->size()); Resources resources(offers.get()[0].resources()); EXPECT_EQ(2, resources.get<Value::Scalar>("cpus")->value()); EXPECT_EQ(1024, resources.get<Value::Scalar>("mem")->value()); driver.stop(); driver.join(); }
TEST_F(ResourceOffersTest, ResourcesGetReofferedAfterFrameworkStops) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get()); ASSERT_SOME(slave); MockScheduler sched1; MesosSchedulerDriver driver1( &sched1, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched1, registered(&driver1, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched1, resourceOffers(&driver1, _)) .WillOnce(FutureArg<1>(&offers)); driver1.start(); AWAIT_READY(offers); EXPECT_FALSE(offers->empty()); driver1.stop(); driver1.join(); MockScheduler sched2; MesosSchedulerDriver driver2( &sched2, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched2, registered(&driver2, _, _)); EXPECT_CALL(sched2, resourceOffers(&driver2, _)) .WillOnce(FutureArg<1>(&offers)); driver2.start(); AWAIT_READY(offers); driver2.stop(); driver2.join(); }
TEST_F(ResourceOffersTest, Request) { TestAllocator<master::allocator::HierarchicalDRFAllocator> allocator; EXPECT_CALL(allocator, initialize(_, _, _, _, _, _)); Try<Owned<cluster::Master>> master = StartMaster(&allocator); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(allocator, addFramework(_, _, _, _, _)); Future<Nothing> registered; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureSatisfy(®istered)); driver.start(); AWAIT_READY(registered); vector<Request> sent; Request request; request.mutable_slave_id()->set_value("test"); sent.push_back(request); Future<vector<Request>> received; EXPECT_CALL(allocator, requestResources(_, _)) .WillOnce(FutureArg<1>(&received)); driver.requestResources(sent); AWAIT_READY(received); EXPECT_EQ(sent.size(), received->size()); EXPECT_FALSE(received->empty()); EXPECT_EQ(request.slave_id(), received.get()[0].slave_id()); driver.stop(); driver.join(); }
// This test verifies that the container will not be killed if // disk_enforce_quota flag is false (even if the disk usage exceeds // its quota). TEST_F(DiskQuotaTest, NoQuotaEnforcement) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "posix/cpu,posix/mem,disk/du"; // NOTE: We can't pause the clock because we need the reaper to reap // the 'du' subprocess. flags.container_disk_watch_interval = Milliseconds(1); flags.enforce_container_disk_quota = false; Fetcher fetcher(flags); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; // Create a task that uses 2MB disk. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1048576 count=2 && sleep 1000"); Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offer.id(), {task}); AWAIT_READY(status); EXPECT_EQ(task.task_id(), status->task_id()); EXPECT_EQ(TASK_RUNNING, status->state()); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); const ContainerID& containerId = *(containers->begin()); // Wait until disk usage can be retrieved and the usage actually // exceeds the limit. If the container is killed due to quota // enforcement (which shouldn't happen), the 'usage' call will // return a failed future, leading to a failed test. Duration elapsed = Duration::zero(); while (true) { Future<ResourceStatistics> usage = containerizer->usage(containerId); AWAIT_READY(usage); ASSERT_TRUE(usage->has_disk_limit_bytes()); EXPECT_EQ(Megabytes(1), Bytes(usage->disk_limit_bytes())); if (usage->has_disk_used_bytes() && usage->disk_used_bytes() > usage->disk_limit_bytes()) { break; } ASSERT_LT(elapsed, Seconds(5)); os::sleep(Milliseconds(1)); elapsed += Milliseconds(1); } driver.stop(); driver.join(); }
TEST_F(DiskQuotaTest, ResourceStatistics) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "posix/cpu,posix/mem,disk/du"; flags.resources = strings::format("disk(%s):10", DEFAULT_TEST_ROLE).get(); // NOTE: We can't pause the clock because we need the reaper to reap // the 'du' subprocess. flags.container_disk_watch_interval = Milliseconds(1); Fetcher fetcher(flags); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_role(DEFAULT_TEST_ROLE); MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; Resource volume = createPersistentVolume( Megabytes(4), DEFAULT_TEST_ROLE, "id1", "path1", None(), None(), DEFAULT_CREDENTIAL.principal()); Resources taskResources = Resources::parse("cpus:1;mem:128").get(); taskResources += createDiskResource( "3", DEFAULT_TEST_ROLE, None(), None()); taskResources += volume; // Create a task that uses 2MB disk. TaskInfo task = createTask( offer.slave_id(), taskResources, "dd if=/dev/zero of=file bs=1048576 count=2 && " "dd if=/dev/zero of=path1/file bs=1048576 count=2 && " "sleep 1000"); Future<TaskStatus> status1; Future<TaskStatus> status2; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status1)) .WillOnce(FutureArg<1>(&status2)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.acceptOffers( {offer.id()}, {CREATE(volume), LAUNCH({task})}); AWAIT_READY(status1); EXPECT_EQ(task.task_id(), status1->task_id()); EXPECT_EQ(TASK_RUNNING, status1->state()); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); const ContainerID& containerId = *(containers->begin()); // Wait until disk usage can be retrieved. Duration elapsed = Duration::zero(); while (true) { Future<ResourceStatistics> usage = containerizer->usage(containerId); AWAIT_READY(usage); ASSERT_TRUE(usage->has_disk_limit_bytes()); EXPECT_EQ(Megabytes(3), Bytes(usage->disk_limit_bytes())); if (usage->has_disk_used_bytes()) { EXPECT_LE(usage->disk_used_bytes(), usage->disk_limit_bytes()); } ASSERT_EQ(2u, usage->disk_statistics().size()); bool done = true; foreach (const DiskStatistics& statistics, usage->disk_statistics()) { ASSERT_TRUE(statistics.has_limit_bytes()); EXPECT_EQ( statistics.has_persistence() ? Megabytes(4) : Megabytes(3), statistics.limit_bytes()); if (!statistics.has_used_bytes()) { done = false; } else { EXPECT_GT( statistics.has_persistence() ? Megabytes(4) : Megabytes(3), statistics.used_bytes()); } } if (done) { break; } ASSERT_LT(elapsed, Seconds(5)); os::sleep(Milliseconds(1)); elapsed += Milliseconds(1); } driver.killTask(task.task_id()); AWAIT_READY(status2); EXPECT_EQ(task.task_id(), status2->task_id()); EXPECT_EQ(TASK_KILLED, status2->state()); driver.stop(); driver.join(); }
// This test verifies that the container will be killed if the disk // usage exceeds its quota. TEST_F(DiskQuotaTest, DiskUsageExceedsQuota) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "posix/cpu,posix/mem,disk/du"; // NOTE: We can't pause the clock because we need the reaper to reap // the 'du' subprocess. flags.container_disk_watch_interval = Milliseconds(1); flags.enforce_container_disk_quota = true; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; // Create a task which requests 1MB disk, but actually uses more // than 2MB disk. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1048576 count=2 && sleep 1000"); Future<TaskStatus> status1; Future<TaskStatus> status2; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status1)) .WillOnce(FutureArg<1>(&status2)); driver.launchTasks(offer.id(), {task}); AWAIT_READY(status1); EXPECT_EQ(task.task_id(), status1->task_id()); EXPECT_EQ(TASK_RUNNING, status1->state()); AWAIT_READY(status2); EXPECT_EQ(task.task_id(), status2->task_id()); EXPECT_EQ(TASK_FAILED, status2->state()); driver.stop(); driver.join(); }
// This test verifies that the container will be killed if the volume // usage exceeds its quota. TEST_F(DiskQuotaTest, VolumeUsageExceedsQuota) { FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_role("role1"); master::Flags masterFlags = CreateMasterFlags(); Try<Owned<cluster::Master>> master = StartMaster(masterFlags); ASSERT_SOME(master); slave::Flags slaveFlags = CreateSlaveFlags(); slaveFlags.isolation = "posix/cpu,posix/mem,disk/du"; // NOTE: We can't pause the clock because we need the reaper to reap // the 'du' subprocess. slaveFlags.container_disk_watch_interval = Milliseconds(1); slaveFlags.enforce_container_disk_quota = true; slaveFlags.resources = "cpus:2;mem:128;disk(role1):128"; Try<Resources> initialResources = Resources::parse(slaveFlags.resources.get()); ASSERT_SOME(initialResources); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); 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); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; // Create a task that requests a 1 MB persistent volume but attempts // to use 2MB. Resources volume = createPersistentVolume( Megabytes(1), "role1", "id1", "volume_path", None(), None(), frameworkInfo.principal()); // We intentionally request a sandbox that is much bugger (16MB) than // the file the task writes (2MB) to the persistent volume (1MB). This // makes sure that the quota is indeed enforced on the persistent volume. Resources taskResources = Resources::parse("cpus:1;mem:64;disk(role1):16").get() + volume; TaskInfo task = createTask( offer.slave_id(), taskResources, "dd if=/dev/zero of=volume_path/file bs=1048576 count=2 && sleep 1000"); Future<TaskStatus> status1; Future<TaskStatus> status2; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status1)) .WillOnce(FutureArg<1>(&status2)); // Create the volume and launch the task. driver.acceptOffers( {offer.id()}, {CREATE(volume), LAUNCH({task})}); AWAIT_READY(status1); EXPECT_EQ(task.task_id(), status1->task_id()); EXPECT_EQ(TASK_RUNNING, status1->state()); AWAIT_READY(status2); EXPECT_EQ(task.task_id(), status1->task_id()); EXPECT_EQ(TASK_FAILED, status2->state()); driver.stop(); driver.join(); }
TEST_F(ResourceOffersTest, ResourcesGetReofferedAfterTaskInfoError) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get()); ASSERT_SOME(slave); MockScheduler sched1; MesosSchedulerDriver driver1( &sched1, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched1, registered(&driver1, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched1, resourceOffers(&driver1, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver1.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); TaskInfo task; task.set_name(""); task.mutable_task_id()->set_value("1"); task.mutable_slave_id()->MergeFrom(offers.get()[0].slave_id()); task.mutable_executor()->MergeFrom(DEFAULT_EXECUTOR_INFO); Resource* cpus = task.add_resources(); cpus->set_name("cpus"); cpus->set_type(Value::SCALAR); cpus->mutable_scalar()->set_value(-1); Resource* mem = task.add_resources(); mem->set_name("mem"); mem->set_type(Value::SCALAR); mem->mutable_scalar()->set_value(static_cast<double>(Gigabytes(1).bytes())); vector<TaskInfo> tasks; tasks.push_back(task); Future<TaskStatus> status; EXPECT_CALL(sched1, statusUpdate(&driver1, _)) .WillOnce(FutureArg<1>(&status)); driver1.launchTasks(offers.get()[0].id(), tasks); AWAIT_READY(status); EXPECT_EQ(task.task_id(), status->task_id()); EXPECT_EQ(TASK_ERROR, status->state()); EXPECT_EQ(TaskStatus::REASON_TASK_INVALID, status->reason()); EXPECT_TRUE(status->has_message()); EXPECT_TRUE(strings::contains(status->message(), "Invalid scalar resource")) << status->message(); MockScheduler sched2; MesosSchedulerDriver driver2( &sched2, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched2, registered(&driver2, _, _)); EXPECT_CALL(sched2, resourceOffers(&driver2, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver2.start(); AWAIT_READY(offers); driver1.stop(); driver1.join(); driver2.stop(); driver2.join(); }
// This test verifies that the framework can launch a command task // that specifies both container image and persistent volumes. TEST_F(LinuxFilesystemIsolatorMesosTest, ROOT_ChangeRootFilesystemCommandExecutorPersistentVolume) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); string registry = path::join(sandbox.get(), "registry"); AWAIT_READY(DockerArchive::create(registry, "test_image")); slave::Flags flags = CreateSlaveFlags(); flags.resources = "cpus:2;mem:1024;disk(role1):1024"; flags.isolation = "filesystem/linux,docker/runtime"; flags.docker_registry = registry; flags.docker_store_dir = path::join(sandbox.get(), "store"); flags.image_providers = "docker"; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); MockScheduler sched; FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_roles(0, "role1"); MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); 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); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; string dir1 = path::join(sandbox.get(), "dir1"); ASSERT_SOME(os::mkdir(dir1)); Resource persistentVolume = createPersistentVolume( Megabytes(64), "role1", "id1", "path1", None(), None(), frameworkInfo.principal()); // We use the filter explicitly here so that the resources will not // be filtered for 5 seconds (the default). Filters filters; filters.set_refuse_seconds(0); TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:512").get() + persistentVolume, "echo abc > path1/file"); task.mutable_container()->CopyFrom(createContainerInfo( "test_image", {createVolumeHostPath("/tmp", dir1, Volume::RW)})); // Create the persistent volumes and launch task via `acceptOffers`. driver.acceptOffers( {offer.id()}, {CREATE(persistentVolume), LAUNCH({task})}, filters); Future<TaskStatus> statusStarting; Future<TaskStatus> statusRunning; Future<TaskStatus> statusFinished; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusStarting)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusFinished)); AWAIT_READY(statusStarting); EXPECT_EQ(TASK_STARTING, statusStarting->state()); AWAIT_READY(statusRunning); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); AWAIT_READY(statusFinished); EXPECT_EQ(TASK_FINISHED, statusFinished->state()); // NOTE: The command executor's id is the same as the task id. ExecutorID executorId; executorId.set_value(task.task_id().value()); string directory = slave::paths::getExecutorLatestRunPath( flags.work_dir, offer.slave_id(), frameworkId.get(), executorId); EXPECT_FALSE(os::exists(path::join(directory, "path1"))); string volumePath = slave::paths::getPersistentVolumePath( flags.work_dir, "role1", "id1"); EXPECT_SOME_EQ("abc\n", os::read(path::join(volumePath, "file"))); driver.stop(); driver.join(); }
// In this test, the framework is not checkpointed. This ensures that when we // stop the slave, the executor is killed and we will need to recover the // working directories without getting any checkpointed recovery state. TEST_F(ROOT_XFS_QuotaTest, NoCheckpointRecovery) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); Fetcher fetcher(flags); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave( detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1048576 count=1; sleep 1000"); Future<TaskStatus> runningStatus; Future<TaskStatus> startingStatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningStatus)) .WillOnce(Return()); driver.launchTasks(offer.id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(runningStatus); EXPECT_EQ(task.task_id(), runningStatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningStatus->state()); Future<ResourceUsage> usage1 = process::dispatch(slave.get()->pid, &Slave::usage); AWAIT_READY(usage1); // We should have 1 executor using resources. ASSERT_EQ(1, usage1->executors().size()); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); ContainerID containerId = *containers->begin(); // Restart the slave. slave.get()->terminate(); Future<SlaveReregisteredMessage> slaveReregisteredMessage = FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); containerizer.reset(_containerizer.get()); slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); // Wait until slave recovery is complete. Future<Nothing> _recover = FUTURE_DISPATCH(_, &Slave::_recover); AWAIT_READY_FOR(_recover, Seconds(60)); // Wait until the orphan containers are cleaned up. AWAIT_READY_FOR(containerizer.get()->wait(containerId), Seconds(60)); AWAIT_READY(slaveReregisteredMessage); Future<ResourceUsage> usage2 = process::dispatch(slave.get()->pid, &Slave::usage); AWAIT_READY(usage2); // We should have no executors left because we didn't checkpoint. ASSERT_TRUE(usage2->executors().empty()); Try<std::list<string>> sandboxes = os::glob(path::join( slave::paths::getSandboxRootDir(mountPoint.get()), "*", "frameworks", "*", "executors", "*", "runs", "*")); ASSERT_SOME(sandboxes); // One sandbox and one symlink. ASSERT_EQ(2u, sandboxes->size()); // Scan the remaining sandboxes and make sure that no projects are assigned. foreach (const string& sandbox, sandboxes.get()) { // Skip the "latest" symlink. if (os::stat::islink(sandbox)) { continue; } EXPECT_NONE(xfs::getProjectId(sandbox)); } driver.stop(); driver.join(); }
// This test verifies that the volume usage accounting for sandboxes // with bind-mounted volumes (while linux filesystem isolator is used) // works correctly by creating a file within the volume the size of // which exceeds the sandbox quota. TEST_F(LinuxFilesystemIsolatorMesosTest, ROOT_VolumeUsageExceedsSandboxQuota) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); string registry = path::join(sandbox.get(), "registry"); AWAIT_READY(DockerArchive::create(registry, "test_image")); slave::Flags flags = CreateSlaveFlags(); flags.resources = "cpus:2;mem:128;disk(role1):128"; flags.isolation = "disk/du,filesystem/linux,docker/runtime"; flags.docker_registry = registry; flags.docker_store_dir = path::join(sandbox.get(), "store"); flags.image_providers = "docker"; // NOTE: We can't pause the clock because we need the reaper to reap // the 'du' subprocess. flags.container_disk_watch_interval = Milliseconds(1); flags.enforce_container_disk_quota = true; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); MockScheduler sched; FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_roles(0, "role1"); MesosSchedulerDriver driver( &sched, frameworkInfo, 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); // We request a sandbox (1MB) that is smaller than the persistent // volume (4MB) and attempt to create a file in that volume that is // twice the size of the sanbox (2MB). Resources volume = createPersistentVolume( Megabytes(4), "role1", "id1", "volume_path", None(), None(), frameworkInfo.principal()); Resources taskResources = Resources::parse("cpus:1;mem:64;disk(role1):1").get() + volume; // We sleep to give quota enforcement (du) a chance to kick in. TaskInfo task = createTask( offers.get()[0].slave_id(), taskResources, "dd if=/dev/zero of=volume_path/file bs=1048576 count=2 && sleep 1"); Future<TaskStatus> statusStarting; Future<TaskStatus> statusRunning; Future<TaskStatus> statusFinished; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusStarting)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusFinished)); driver.acceptOffers( {offers.get()[0].id()}, {CREATE(volume), LAUNCH({task})}); AWAIT_READY(statusStarting); EXPECT_EQ(task.task_id(), statusStarting->task_id()); EXPECT_EQ(TASK_STARTING, statusStarting->state()); AWAIT_READY(statusRunning); EXPECT_EQ(task.task_id(), statusRunning->task_id()); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); AWAIT_READY(statusFinished); EXPECT_EQ(task.task_id(), statusFinished->task_id()); EXPECT_EQ(TASK_FINISHED, statusFinished->state()); driver.stop(); driver.join(); }
// Verify that we can get accurate resource statistics from the XFS // disk isolator. TEST_F(ROOT_XFS_QuotaTest, ResourceStatistics) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); Fetcher fetcher(flags); Owned<MasterDetector> detector = master.get()->createDetector(); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; // Create a task that uses 4 of 3MB disk but doesn't fail. We will verify // that the allocated disk is filled. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:3").get(), "dd if=/dev/zero of=file bs=1048576 count=4 || sleep 1000"); Future<TaskStatus> startingStatus; Future<TaskStatus> runningStatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningStatus)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(runningStatus); EXPECT_EQ(task.task_id(), runningStatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningStatus->state()); Future<hashset<ContainerID>> containers = containerizer.get()->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); ContainerID containerId = *(containers->begin()); Timeout timeout = Timeout::in(Seconds(5)); while (true) { Future<ResourceStatistics> usage = containerizer.get()->usage(containerId); AWAIT_READY(usage); ASSERT_TRUE(usage->has_disk_limit_bytes()); EXPECT_EQ(Megabytes(3), Bytes(usage->disk_limit_bytes())); if (usage->has_disk_used_bytes()) { // Usage must always be <= the limit. EXPECT_LE(usage->disk_used_bytes(), usage->disk_limit_bytes()); // Usage might not be equal to the limit, but it must hit // and not exceed the limit. if (usage->disk_used_bytes() >= usage->disk_limit_bytes()) { EXPECT_EQ( usage->disk_used_bytes(), usage->disk_limit_bytes()); EXPECT_EQ(Megabytes(3), Bytes(usage->disk_used_bytes())); break; } } ASSERT_FALSE(timeout.expired()); os::sleep(Milliseconds(100)); } driver.stop(); driver.join(); }
// This is the same logic as ResourceStatistics, except the task should // be allowed to exceed the disk quota, and usage statistics should report // that the quota was exceeded. TEST_F(ROOT_XFS_QuotaTest, ResourceStatisticsNoEnforce) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.enforce_container_disk_quota = false; Fetcher fetcher(flags); Owned<MasterDetector> detector = master.get()->createDetector(); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; // Create a task that uses 4MB of 3MB disk and fails if it can't // write the full amount. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:3").get(), "dd if=/dev/zero of=file bs=1048576 count=4 && sleep 1000"); Future<TaskStatus> startingStatus; Future<TaskStatus> runningStatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningStatus)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(runningStatus); EXPECT_EQ(task.task_id(), runningStatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningStatus->state()); Future<hashset<ContainerID>> containers = containerizer.get()->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); ContainerID containerId = *(containers->begin()); Duration diskTimeout = Seconds(5); Timeout timeout = Timeout::in(diskTimeout); while (true) { Future<ResourceStatistics> usage = containerizer.get()->usage(containerId); AWAIT_READY(usage); ASSERT_TRUE(usage->has_disk_limit_bytes()); EXPECT_EQ(Megabytes(3), Bytes(usage->disk_limit_bytes())); if (usage->has_disk_used_bytes()) { if (usage->disk_used_bytes() >= Megabytes(4).bytes()) { break; } } // The stopping condition for this test is that the isolator is // able to report that we wrote the full amount of data without // being constrained by the task disk limit. EXPECT_LE(usage->disk_used_bytes(), Megabytes(4).bytes()); ASSERT_FALSE(timeout.expired()) << "Used " << Bytes(usage->disk_used_bytes()) << " of expected " << Megabytes(4) << " within the " << diskTimeout << " timeout"; os::sleep(Milliseconds(100)); } driver.stop(); driver.join(); }
// This is the same logic as DiskUsageExceedsQuota except we turn off disk quota // enforcement, so exceeding the quota should be allowed. TEST_F(ROOT_XFS_QuotaTest, DiskUsageExceedsQuotaNoEnforce) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); slave::Flags flags = CreateSlaveFlags(); flags.enforce_container_disk_quota = false; Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; // Create a task which requests 1MB disk, but actually uses more // than 2MB disk. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1048576 count=2"); Future<TaskStatus> startingStatus; Future<TaskStatus> runningStatus; Future<TaskStatus> finishedStatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningStatus)) .WillOnce(FutureArg<1>(&finishedStatus)); driver.launchTasks(offer.id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(runningStatus); EXPECT_EQ(task.task_id(), runningStatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningStatus->state()); // We expect the task to succeed even though it exceeded // the disk quota. AWAIT_READY(finishedStatus); EXPECT_EQ(task.task_id(), finishedStatus->task_id()); EXPECT_EQ(TASK_FINISHED, finishedStatus->state()); driver.stop(); driver.join(); }
// Verify that a task that tries to consume more space than it has requested // is only allowed to consume exactly the assigned resources. We tell dd // to write 2MB but only give it 1MB of resources and (roughly) verify that // it exits with a failure (that should be a write error). TEST_F(ROOT_XFS_QuotaTest, DiskUsageExceedsQuota) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), CreateSlaveFlags()); ASSERT_SOME(slave); 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; // Create a task which requests 1MB disk, but actually uses more // than 2MB disk. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1048576 count=2"); Future<TaskStatus> startingStatus; Future<TaskStatus> runningStatus; Future<TaskStatus> failedStatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningStatus)) .WillOnce(FutureArg<1>(&failedStatus)); driver.launchTasks(offer.id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(runningStatus); EXPECT_EQ(task.task_id(), runningStatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningStatus->state()); AWAIT_READY(failedStatus); EXPECT_EQ(task.task_id(), failedStatus->task_id()); EXPECT_EQ(TASK_FAILED, failedStatus->state()); // Unlike the 'disk/du' isolator, the reason for task failure // should be that dd got an IO error. EXPECT_EQ(TaskStatus::SOURCE_EXECUTOR, failedStatus->source()); EXPECT_EQ("Command exited with status 1", failedStatus->message()); driver.stop(); driver.join(); }
// This test verifies that persistent volumes are unmounted properly // after a checkpointed framework disappears and the slave restarts. // // TODO(jieyu): Even though the command task specifies a new // filesystem root, the executor (command executor) itself does not // change filesystem root (uses the host filesystem). We need to add a // test to test the scenario that the executor itself changes rootfs. TEST_F(LinuxFilesystemIsolatorMesosTest, ROOT_RecoverOrphanedPersistentVolume) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); string registry = path::join(sandbox.get(), "registry"); AWAIT_READY(DockerArchive::create(registry, "test_image")); slave::Flags flags = CreateSlaveFlags(); flags.resources = "cpus:2;mem:1024;disk(role1):1024"; flags.isolation = "filesystem/linux,docker/runtime"; flags.docker_registry = registry; flags.docker_store_dir = path::join(sandbox.get(), "store"); flags.image_providers = "docker"; Fetcher fetcher(flags); Try<MesosContainerizer*> create = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(create); Owned<Containerizer> containerizer(create.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave( detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_roles(0, "role1"); frameworkInfo.set_checkpoint(true); MesosSchedulerDriver driver( &sched, frameworkInfo, 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; string dir1 = path::join(sandbox.get(), "dir1"); ASSERT_SOME(os::mkdir(dir1)); Resource persistentVolume = createPersistentVolume( Megabytes(64), "role1", "id1", "path1", None(), None(), frameworkInfo.principal()); // Create a task that does nothing for a long time. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:512").get() + persistentVolume, "sleep 1000"); task.mutable_container()->CopyFrom(createContainerInfo( "test_image", {createVolumeHostPath("/tmp", dir1, Volume::RW)})); Future<TaskStatus> statusStarting; Future<TaskStatus> statusRunning; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusStarting)) .WillOnce(FutureArg<1>(&statusRunning)) .WillRepeatedly(DoDefault()); Future<Nothing> ack = FUTURE_DISPATCH(_, &Slave::_statusUpdateAcknowledgement); // Create the persistent volumes and launch task via `acceptOffers`. driver.acceptOffers( {offer.id()}, {CREATE(persistentVolume), LAUNCH({task})}); AWAIT_READY(statusStarting); EXPECT_EQ(TASK_STARTING, statusStarting->state()); AWAIT_READY(statusRunning); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); // Wait for the ACK to be checkpointed. AWAIT_READY(ack); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); ContainerID containerId = *containers->begin(); // Restart the slave. slave.get()->terminate(); // Wipe the slave meta directory so that the slave will treat the // above running task as an orphan. ASSERT_SOME(os::rmdir(slave::paths::getMetaRootDir(flags.work_dir))); Future<Nothing> _recover = FUTURE_DISPATCH(_, &Slave::_recover); // Recreate the containerizer using the same helper as above. containerizer.reset(); create = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(create); containerizer.reset(create.get()); slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); // Wait until slave recovery is complete. AWAIT_READY(_recover); // Wait until the orphan containers are cleaned up. AWAIT_READY(containerizer->wait(containerId)); Try<fs::MountInfoTable> table = fs::MountInfoTable::read(); ASSERT_SOME(table); // All mount targets should be under this directory. string directory = slave::paths::getSandboxRootDir(flags.work_dir); // Verify that the orphaned container's persistent volume and // the rootfs are unmounted. foreach (const fs::MountInfoTable::Entry& entry, table->entries) { EXPECT_FALSE(strings::contains(entry.target, directory)) << "Target was not unmounted: " << entry.target; } driver.stop(); driver.join(); }
// This test verifies that disk quota isolator recovers properly after // the slave restarts. TEST_F(DiskQuotaTest, SlaveRecovery) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "posix/cpu,posix/mem,disk/du"; flags.container_disk_watch_interval = Milliseconds(1); Fetcher fetcher(flags); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; // Enable checkpointing for the framework. FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_checkpoint(true); MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; // Create a task that uses 2MB disk. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:3").get(), "dd if=/dev/zero of=file bs=1048576 count=2 && sleep 1000"); Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offer.id(), {task}); AWAIT_READY(status); EXPECT_EQ(task.task_id(), status->task_id()); EXPECT_EQ(TASK_RUNNING, status->state()); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); const ContainerID& containerId = *(containers->begin()); // Stop the slave. slave.get()->terminate(); Future<ReregisterExecutorMessage> reregisterExecutorMessage = FUTURE_PROTOBUF(ReregisterExecutorMessage(), _, _); Future<Nothing> _recover = FUTURE_DISPATCH(_, &Slave::_recover); _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); containerizer.reset(_containerizer.get()); detector = master.get()->createDetector(); slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); Clock::pause(); AWAIT_READY(_recover); // Wait for slave to schedule reregister timeout. Clock::settle(); // Ensure the executor re-registers before completing recovery. AWAIT_READY(reregisterExecutorMessage); // Ensure the slave considers itself recovered. Clock::advance(flags.executor_reregistration_timeout); // NOTE: We resume the clock because we need the reaper to reap the // 'du' subprocess. Clock::resume(); // Wait until disk usage can be retrieved. Duration elapsed = Duration::zero(); while (true) { Future<ResourceStatistics> usage = containerizer->usage(containerId); AWAIT_READY(usage); ASSERT_TRUE(usage->has_disk_limit_bytes()); EXPECT_EQ(Megabytes(3), Bytes(usage->disk_limit_bytes())); if (usage->has_disk_used_bytes()) { EXPECT_LE(usage->disk_used_bytes(), usage->disk_limit_bytes()); // NOTE: This is to capture the regression in MESOS-2452. The data // stored in the executor meta directory should be less than 64K. if (usage->disk_used_bytes() > Kilobytes(64).bytes()) { break; } } ASSERT_LT(elapsed, Seconds(15)); os::sleep(Milliseconds(1)); elapsed += Milliseconds(1); } driver.stop(); driver.join(); }
// Tests that the task fails when it attempts to write to a persistent volume // mounted as read-only. Note that although we use a shared persistent volume, // the behavior is the same for non-shared persistent volumes. TEST_F(LinuxFilesystemIsolatorMesosTest, ROOT_WriteAccessSharedPersistentVolumeReadOnlyMode) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); string registry = path::join(sandbox.get(), "registry"); AWAIT_READY(DockerArchive::create(registry, "test_image")); slave::Flags flags = CreateSlaveFlags(); flags.resources = "cpus:2;mem:128;disk(role1):128"; flags.isolation = "filesystem/linux,docker/runtime"; flags.docker_registry = registry; flags.docker_store_dir = path::join(sandbox.get(), "store"); flags.image_providers = "docker"; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); MockScheduler sched; FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_roles(0, "role1"); frameworkInfo.add_capabilities()->set_type( FrameworkInfo::Capability::SHARED_RESOURCES); MesosSchedulerDriver driver( &sched, frameworkInfo, 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); // We create a shared volume which shall be used by the task to // write to that volume. Resource volume = createPersistentVolume( Megabytes(4), "role1", "id1", "volume_path", None(), None(), frameworkInfo.principal(), true); // Shared volume. // The task uses the shared volume as read-only. Resource roVolume = volume; roVolume.mutable_disk()->mutable_volume()->set_mode(Volume::RO); Resources taskResources = Resources::parse("cpus:1;mem:64;disk(role1):1").get() + roVolume; TaskInfo task = createTask( offers.get()[0].slave_id(), taskResources, "echo hello > volume_path/file"); // The task fails to write to the volume since the task's resources // intends to use the volume as read-only. Future<TaskStatus> statusStarting; Future<TaskStatus> statusRunning; Future<TaskStatus> statusFailed; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusStarting)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusFailed)); driver.acceptOffers( {offers.get()[0].id()}, {CREATE(volume), LAUNCH({task})}); AWAIT_READY(statusStarting); EXPECT_EQ(task.task_id(), statusStarting->task_id()); EXPECT_EQ(TASK_STARTING, statusStarting->state()); AWAIT_READY(statusRunning); EXPECT_EQ(task.task_id(), statusRunning->task_id()); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); AWAIT_READY(statusFailed); EXPECT_EQ(task.task_id(), statusFailed->task_id()); EXPECT_EQ(TASK_FAILED, statusFailed->state()); driver.stop(); driver.join(); }
TEST_P(CpuIsolatorTest, ROOT_UserCpuUsage) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = GetParam(); Fetcher fetcher(flags); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave( detector.get(), containerizer.get()); ASSERT_SOME(slave); 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); // Max out a single core in userspace. This will run for at most one // second. TaskInfo task = createTask( offers.get()[0], "while true ; do true ; done & sleep 60"); Future<TaskStatus> statusRunning; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusRunning)); driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(statusRunning); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); ContainerID containerId = *(containers->begin()); // Wait up to 1 second for the child process to induce 1/8 of a // second of user cpu time. ResourceStatistics statistics; Duration waited = Duration::zero(); do { Future<ResourceStatistics> usage = containerizer->usage(containerId); AWAIT_READY(usage); statistics = usage.get(); // If we meet our usage expectations, we're done! if (statistics.cpus_user_time_secs() >= 0.125) { break; } os::sleep(Milliseconds(200)); waited += Milliseconds(200); } while (waited < Seconds(1)); EXPECT_LE(0.125, statistics.cpus_user_time_secs()); driver.stop(); driver.join(); }
// This test confirms that if a task exceeds configured resource // limits it is forcibly terminated. TEST_F(PosixRLimitsIsolatorTest, TaskExceedingLimit) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "posix/rlimits"; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); // The task attempts to use an infinite amount of CPU time. TaskInfo task = createTask( offers.get()[0].slave_id(), offers.get()[0].resources(), "while true; do true; done"); ContainerInfo* container = task.mutable_container(); container->set_type(ContainerInfo::MESOS); // Limit the process to use maximally 1 second of CPU time. RLimitInfo rlimitInfo; RLimitInfo::RLimit* cpuLimit = rlimitInfo.add_rlimits(); cpuLimit->set_type(RLimitInfo::RLimit::RLMT_CPU); cpuLimit->set_soft(1); cpuLimit->set_hard(1); container->mutable_rlimit_info()->CopyFrom(rlimitInfo); Future<TaskStatus> statusRunning; Future<TaskStatus> statusFailed; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusFailed)); driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(statusRunning); EXPECT_EQ(task.task_id(), statusRunning->task_id()); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); AWAIT_READY(statusFailed); EXPECT_EQ(task.task_id(), statusFailed->task_id()); EXPECT_EQ(TASK_FAILED, statusFailed->state()); driver.stop(); driver.join(); }
// This test verifies that a task group is launched on the agent if the executor // provides a valid authentication token specifying its own ContainerID. TEST_F(ExecutorAuthorizationTest, RunTaskGroup) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Start an agent with permissive ACLs so that a task can be launched. ACLs acls; acls.set_permissive(true); slave::Flags flags = CreateSlaveFlags(); flags.acls = acls; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); 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); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:0.5;mem:32").get(), "sleep 1000"); Future<TaskStatus> status; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status)); Resources executorResources = allocatedResources(Resources::parse("cpus:0.1;mem:32;disk:32").get(), "*"); ExecutorInfo executor; executor.mutable_executor_id()->set_value("default"); executor.set_type(ExecutorInfo::DEFAULT); executor.mutable_framework_id()->CopyFrom(frameworkId.get()); executor.mutable_resources()->CopyFrom(executorResources); TaskGroupInfo taskGroup; taskGroup.add_tasks()->CopyFrom(task); driver.acceptOffers({offer.id()}, {LAUNCH_GROUP(executor, taskGroup)}); AWAIT_READY(status); ASSERT_EQ(task.task_id(), status->task_id()); EXPECT_EQ(TASK_STARTING, status->state()); driver.stop(); driver.join(); }
// This test verifies that a task is launched on the agent if the task // user is authorized based on `run_tasks` ACL configured on the agent // to only allow whitelisted users to run tasks on the agent. TYPED_TEST(SlaveAuthorizerTest, AuthorizeRunTaskOnAgent) { // Get the current user. Result<string> user = os::user(); ASSERT_SOME(user) << "Failed to get the current user name" << (user.isError() ? ": " + user.error() : ""); Try<Owned<cluster::Master>> master = this->StartMaster(); ASSERT_SOME(master); // Start a slave with `bar` and the current user being the only authorized // users to launch tasks on the agent. ACLs acls; acls.set_permissive(false); // Restrictive. mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->set_type(ACL::Entity::ANY); acl->mutable_users()->add_values("bar"); acl->mutable_users()->add_values(user.get()); slave::Flags slaveFlags = this->CreateSlaveFlags(); slaveFlags.acls = acls; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = this->StartSlave( detector.get(), slaveFlags); ASSERT_SOME(slave); // Create a framework with user `foo`. FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_user("foo"); MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(&driver, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); // Framework is registered since the master admits frameworks of any user. AWAIT_READY(frameworkId); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; // Launch the first task with no user, so it defaults to the // framework user `foo`. TaskInfo task1 = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:32").get(), "sleep 1000"); // Launch the second task as the current user. TaskInfo task2 = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:32").get(), "sleep 1000"); task2.mutable_command()->set_user(user.get()); // The first task should fail since the task user `foo` is not an // authorized user that can launch a task. However, the second task // should succeed. Future<TaskStatus> status0; Future<TaskStatus> status1; Future<TaskStatus> status2; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status0)) .WillOnce(FutureArg<1>(&status1)) .WillOnce(FutureArg<1>(&status2)); driver.acceptOffers( {offer.id()}, {LAUNCH({task1, task2})}); // Wait for TASK_ERROR for 1st task, and TASK_STARTING followed by // TASK_RUNNING for 2nd task. AWAIT_READY(status0); AWAIT_READY(status1); AWAIT_READY(status2); // Validate both the statuses. Note that the order of receiving the // status updates for the 2 tasks is not deterministic, but we know // that task2's TASK_RUNNING arrives after TASK_STARTING. hashmap<TaskID, TaskStatus> statuses; statuses[status0->task_id()] = status0.get(); statuses[status1->task_id()] = status1.get(); statuses[status2->task_id()] = status2.get(); ASSERT_TRUE(statuses.contains(task1.task_id())); EXPECT_EQ(TASK_ERROR, statuses.at(task1.task_id()).state()); EXPECT_EQ(TaskStatus::SOURCE_SLAVE, statuses.at(task1.task_id()).source()); EXPECT_EQ(TaskStatus::REASON_TASK_UNAUTHORIZED, statuses.at(task1.task_id()).reason()); ASSERT_TRUE(statuses.contains(task2.task_id())); EXPECT_EQ(TASK_RUNNING, statuses.at(task2.task_id()).state()); driver.stop(); driver.join(); }
// In this test, the framework is checkpointed so we expect the executor to // persist across the slave restart and to have the same resource usage before // and after. TEST_F(ROOT_XFS_QuotaTest, CheckpointRecovery) { slave::Flags flags = CreateSlaveFlags(); Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), CreateSlaveFlags()); ASSERT_SOME(slave); FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_checkpoint(true); MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1048576 count=1; sleep 1000"); Future<TaskStatus> startingStatus; Future<TaskStatus> runningStatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningStatus)); driver.launchTasks(offer.id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), runningStatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningStatus->state()); Future<ResourceUsage> usage1 = process::dispatch(slave.get()->pid, &Slave::usage); AWAIT_READY(usage1); // We should have 1 executor using resources. ASSERT_EQ(1, usage1->executors().size()); // Restart the slave. slave.get()->terminate(); Future<SlaveReregisteredMessage> slaveReregisteredMessage = FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); // Wait for the slave to re-register. AWAIT_READY(slaveReregisteredMessage); Future<ResourceUsage> usage2 = process::dispatch(slave.get()->pid, &Slave::usage); AWAIT_READY(usage2); // We should have still have 1 executor using resources. ASSERT_EQ(1, usage1->executors().size()); Try<std::list<string>> sandboxes = os::glob(path::join( slave::paths::getSandboxRootDir(mountPoint.get()), "*", "frameworks", "*", "executors", "*", "runs", "*")); ASSERT_SOME(sandboxes); // One sandbox and one symlink. ASSERT_EQ(2u, sandboxes->size()); // Scan the remaining sandboxes. We ought to still have project IDs // assigned to them all. foreach (const string& sandbox, sandboxes.get()) { // Skip the "latest" symlink. if (os::stat::islink(sandbox)) { continue; } EXPECT_SOME(xfs::getProjectId(sandbox)); } driver.stop(); driver.join(); }
// This test verifies that the framework can launch a command task // that specifies a container image. TEST_F(LinuxFilesystemIsolatorMesosTest, ROOT_ChangeRootFilesystemCommandExecutor) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); string registry = path::join(sandbox.get(), "registry"); AWAIT_READY(DockerArchive::create(registry, "test_image")); slave::Flags flags = CreateSlaveFlags(); flags.isolation = "filesystem/linux,docker/runtime"; flags.docker_registry = registry; flags.docker_store_dir = path::join(sandbox.get(), "store"); flags.image_providers = "docker"; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); 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(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); const Offer& offer = offers.get()[0]; TaskInfo task = createTask( offer.slave_id(), offer.resources(), "test -d " + flags.sandbox_directory); task.mutable_container()->CopyFrom(createContainerInfo("test_image")); driver.launchTasks(offer.id(), {task}); Future<TaskStatus> statusStarting; Future<TaskStatus> statusRunning; Future<TaskStatus> statusFinished; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusStarting)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusFinished)); AWAIT_READY(statusStarting); EXPECT_EQ(TASK_STARTING, statusStarting->state()); AWAIT_READY(statusRunning); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); AWAIT_READY(statusFinished); EXPECT_EQ(TASK_FINISHED, statusFinished->state()); driver.stop(); driver.join(); }
// Tests that the logrotate container logger only closes FDs when it // is supposed to and does not interfere with other FDs on the agent. TEST_F(ContainerLoggerTest, LOGROTATE_ModuleFDOwnership) { // Create a master, agent, and framework. Try<Owned<cluster::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(flags); // We use an actual containerizer + executor since we want something to run. Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, false, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); AWAIT_READY(slaveRegisteredMessage); 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)); // 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); ASSERT_FALSE(offers->empty()); // Start a task that will keep running until the end of the test. TaskInfo task = createTask(offers.get()[0], "sleep 100"); Future<TaskStatus> statusStarting; Future<TaskStatus> statusRunning; Future<TaskStatus> statusKilled; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&statusStarting)) .WillOnce(FutureArg<1>(&statusRunning)) .WillOnce(FutureArg<1>(&statusKilled)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offers.get()[0].id(), {task}); AWAIT_READY(statusStarting); EXPECT_EQ(TASK_STARTING, statusStarting->state()); AWAIT_READY(statusRunning); EXPECT_EQ(TASK_RUNNING, statusRunning->state()); // Open multiple files, so that we're fairly certain we've opened // the same FDs (integers) opened by the container logger. vector<int> fds; for (int i = 0; i < 50; i++) { Try<int> fd = os::open(os::DEV_NULL, O_RDONLY); ASSERT_SOME(fd); fds.push_back(fd.get()); } // Kill the task, which also kills the executor. driver.killTask(statusRunning->task_id()); AWAIT_READY(statusKilled); EXPECT_EQ(TASK_KILLED, statusKilled->state()); Future<Nothing> executorTerminated = FUTURE_DISPATCH(_, &Slave::executorTerminated); AWAIT_READY(executorTerminated); // Close all the FDs we opened. Every `close` should succeed. foreach (int fd, fds) { ASSERT_SOME(os::close(fd)); }
// In this test, the agent initially doesn't enable disk isolation // but then restarts with XFS disk isolation enabled. We verify that // the old container launched before the agent restart is // successfully recovered. TEST_F(ROOT_XFS_QuotaTest, RecoverOldContainers) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Owned<MasterDetector> detector = master.get()->createDetector(); slave::Flags flags = CreateSlaveFlags(); // `CreateSlaveFlags()` enables `disk/xfs` so here we reset // `isolation` to empty. flags.isolation.clear(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); ASSERT_SOME(slave); FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_checkpoint(true); MockScheduler sched; MesosSchedulerDriver driver( &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:128;disk:1").get(), "dd if=/dev/zero of=file bs=1024 count=1; sleep 1000"); Future<TaskStatus> startingStatus; Future<TaskStatus> runningstatus; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&startingStatus)) .WillOnce(FutureArg<1>(&runningstatus)); driver.launchTasks(offer.id(), {task}); AWAIT_READY(startingStatus); EXPECT_EQ(task.task_id(), startingStatus->task_id()); EXPECT_EQ(TASK_STARTING, startingStatus->state()); AWAIT_READY(runningstatus); EXPECT_EQ(task.task_id(), runningstatus->task_id()); EXPECT_EQ(TASK_RUNNING, runningstatus->state()); { Future<ResourceUsage> usage = process::dispatch(slave.get()->pid, &Slave::usage); AWAIT_READY(usage); // We should have 1 executor using resources but it doesn't have // disk limit enabled. ASSERT_EQ(1, usage->executors().size()); const ResourceUsage_Executor& executor = usage->executors().Get(0); ASSERT_TRUE(executor.has_statistics()); ASSERT_FALSE(executor.statistics().has_disk_limit_bytes()); } // Restart the slave. slave.get()->terminate(); Future<SlaveReregisteredMessage> slaveReregisteredMessage = FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _); // This time use the agent flags that include XFS disk isolation. slave = StartSlave(detector.get(), CreateSlaveFlags()); ASSERT_SOME(slave); // Wait for the slave to re-register. AWAIT_READY(slaveReregisteredMessage); { Future<ResourceUsage> usage = process::dispatch(slave.get()->pid, &Slave::usage); AWAIT_READY(usage); // We should still have 1 executor using resources but it doesn't // have disk limit enabled. ASSERT_EQ(1, usage->executors().size()); const ResourceUsage_Executor& executor = usage->executors().Get(0); ASSERT_TRUE(executor.has_statistics()); ASSERT_FALSE(executor.statistics().has_disk_limit_bytes()); } driver.stop(); driver.join(); }
TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_Statistics) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); slave::Flags flags = CreateSlaveFlags(); // We only care about memory cgroup for this test. flags.isolation = "cgroups/mem"; Fetcher fetcher(flags); Try<MesosContainerizer*> _containerizer = MesosContainerizer::create(flags, true, &fetcher); ASSERT_SOME(_containerizer); Owned<MesosContainerizer> containerizer(_containerizer.get()); Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), containerizer.get(), flags); ASSERT_SOME(slave); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(sched, registered(_, _, _)); Future<vector<Offer>> offers; EXPECT_CALL(sched, resourceOffers(_, _)) .WillOnce(FutureArg<1>(&offers)) .WillRepeatedly(Return()); // Ignore subsequent offers. driver.start(); AWAIT_READY(offers); ASSERT_FALSE(offers->empty()); Offer offer = offers.get()[0]; // Run a task that triggers memory pressure event. We request 1G // disk because we are going to write a 512 MB file repeatedly. TaskInfo task = createTask( offer.slave_id(), Resources::parse("cpus:1;mem:256;disk:1024").get(), "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done"); Future<TaskStatus> starting; Future<TaskStatus> running; Future<TaskStatus> killed; EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&starting)) .WillOnce(FutureArg<1>(&running)) .WillOnce(FutureArg<1>(&killed)) .WillRepeatedly(Return()); // Ignore subsequent updates. driver.launchTasks(offer.id(), {task}); AWAIT_READY(starting); EXPECT_EQ(task.task_id(), starting->task_id()); EXPECT_EQ(TASK_STARTING, starting->state()); AWAIT_READY(running); EXPECT_EQ(task.task_id(), running->task_id()); EXPECT_EQ(TASK_RUNNING, running->state()); Future<hashset<ContainerID>> containers = containerizer->containers(); AWAIT_READY(containers); ASSERT_EQ(1u, containers->size()); ContainerID containerId = *(containers->begin()); // Wait a while for some memory pressure events to occur. Duration waited = Duration::zero(); do { Future<ResourceStatistics> usage = containerizer->usage(containerId); AWAIT_READY(usage); if (usage->mem_low_pressure_counter() > 0) { // We will check the correctness of the memory pressure counters // later, because the memory-hammering task is still active // and potentially incrementing these counters. break; } os::sleep(Milliseconds(100)); waited += Milliseconds(100); } while (waited < Seconds(5)); EXPECT_LE(waited, Seconds(5)); // Pause the clock to ensure that the reaper doesn't reap the exited // command executor and inform the containerizer/slave. Clock::pause(); Clock::settle(); // Stop the memory-hammering task. driver.killTask(task.task_id()); AWAIT_READY_FOR(killed, Seconds(120)); EXPECT_EQ(task.task_id(), killed->task_id()); EXPECT_EQ(TASK_KILLED, killed->state()); // Now check the correctness of the memory pressure counters. Future<ResourceStatistics> usage = containerizer->usage(containerId); AWAIT_READY(usage); EXPECT_GE(usage->mem_low_pressure_counter(), usage->mem_medium_pressure_counter()); EXPECT_GE(usage->mem_medium_pressure_counter(), usage->mem_critical_pressure_counter()); Clock::resume(); driver.stop(); driver.join(); }
// This test verifies that authorization based endpoint filtering // works correctly on the /state endpoint. // Both default users are allowed to view high level frameworks, but only // one is allowed to view the tasks. // After launching a single task per each framework, one for role "superhero" // and the other for role "muggle", this test verifies that each of two // default users can view resource allocations and resource reservations for // corresponding allowed roles only. TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint) { ACLs acls; const string roleSuperhero = "superhero"; const string roleMuggle = "muggle"; { // Default principal can see all frameworks. mesos::ACL::ViewFramework* acl = acls.add_view_frameworks(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_users()->set_type(ACL::Entity::ANY); } { // Second default principal can see all frameworks. mesos::ACL::ViewFramework* acl = acls.add_view_frameworks(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal()); acl->mutable_users()->set_type(ACL::Entity::ANY); } { // No other principal can see frameworks running under any user. ACL::ViewFramework* acl = acls.add_view_frameworks(); acl->mutable_principals()->set_type(ACL::Entity::ANY); acl->mutable_users()->set_type(ACL::Entity::NONE); } { // Default principal can see all executors. mesos::ACL::ViewExecutor* acl = acls.add_view_executors(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_users()->set_type(ACL::Entity::ANY); } { // No other principal can see executors running under any user. ACL::ViewExecutor* acl = acls.add_view_executors(); acl->mutable_principals()->set_type(ACL::Entity::ANY); acl->mutable_users()->set_type(ACL::Entity::NONE); } { // Default principal can see all tasks. mesos::ACL::ViewTask* acl = acls.add_view_tasks(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_users()->set_type(ACL::Entity::ANY); } { // No other principal can see tasks running under any user. ACL::ViewTask* acl = acls.add_view_tasks(); acl->mutable_principals()->set_type(ACL::Entity::ANY); acl->mutable_users()->set_type(ACL::Entity::NONE); } { // Default principal can view "superhero" role only. ACL::ViewRole* acl = acls.add_view_roles(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_roles()->add_values(roleSuperhero); acl = acls.add_view_roles(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE); } { // Second default principal can view "muggle" role only. ACL::ViewRole* acl = acls.add_view_roles(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal()); acl->mutable_roles()->add_values(roleMuggle); acl = acls.add_view_roles(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal()); acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE); } // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(parameterize(acls)); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); Try<Owned<cluster::Master>> master = this->StartMaster(authorizer.get()); ASSERT_SOME(master); // Register framework with user "bar" and role "superhero". FrameworkInfo frameworkSuperhero = DEFAULT_FRAMEWORK_INFO; frameworkSuperhero.set_name("framework-" + roleSuperhero); frameworkSuperhero.set_roles(0, roleSuperhero); frameworkSuperhero.set_user("bar"); // Create an executor with user "bar". ExecutorInfo executorSuperhero = createExecutorInfo("test-executor-" + roleSuperhero, "sleep 2"); executorSuperhero.mutable_command()->set_user("bar"); MockExecutor execSuperhero(executorSuperhero.executor_id()); // Register framework with user "foo" and role "muggle". FrameworkInfo frameworkMuggle = DEFAULT_FRAMEWORK_INFO; frameworkMuggle.set_name("framework-" + roleMuggle); frameworkMuggle.set_principal(DEFAULT_CREDENTIAL_2.principal()); frameworkMuggle.set_roles(0, roleMuggle); frameworkMuggle.set_user("foo"); // Create an executor with user "foo". ExecutorInfo executorMuggle = createExecutorInfo("test-executor-" + roleMuggle, "sleep 2"); executorMuggle.mutable_command()->set_user("foo"); MockExecutor execMuggle(executorMuggle.executor_id()); TestContainerizer containerizer( {{executorSuperhero.executor_id(), &execSuperhero}, {executorMuggle.executor_id(), &execMuggle}}); slave::Flags flags = this->CreateSlaveFlags(); // Statically reserve resources for each role. flags.resources = "cpus(" + roleSuperhero + "):2;" + "cpus(" + roleMuggle + "):3;mem(" + roleSuperhero + "):512;" + "mem(" + roleMuggle + "):1024;"; Owned<MasterDetector> detector = master.get()->createDetector(); Try<Owned<cluster::Slave>> slave = this->StartSlave( detector.get(), &containerizer, authorizer.get(), flags); ASSERT_SOME(slave); MockScheduler schedSuperhero; MesosSchedulerDriver driverSuperhero( &schedSuperhero, frameworkSuperhero, master.get()->pid, DEFAULT_CREDENTIAL); EXPECT_CALL(execSuperhero, registered(_, _, _, _)) .Times(AtMost(1)); Future<FrameworkID> frameworkIdSuperhero; EXPECT_CALL(schedSuperhero, registered(&driverSuperhero, _, _)) .WillOnce(FutureArg<1>(&frameworkIdSuperhero)); Future<vector<Offer>> offersSuperhero; EXPECT_CALL(schedSuperhero, resourceOffers(&driverSuperhero, _)) .WillOnce(FutureArg<1>(&offersSuperhero)) .WillRepeatedly(Return()); // Ignore subsequent offers. driverSuperhero.start(); AWAIT_READY(frameworkIdSuperhero); AWAIT_READY(offersSuperhero); ASSERT_FALSE(offersSuperhero->empty()); // Define a task which will run on executorSuperhero of frameworkSuperhero. TaskInfo taskSuperhero; taskSuperhero.set_name("test-" + roleSuperhero); taskSuperhero.mutable_task_id()->set_value("1"); taskSuperhero.mutable_slave_id()->MergeFrom( offersSuperhero.get()[0].slave_id()); taskSuperhero.mutable_resources()->MergeFrom( offersSuperhero.get()[0].resources()); taskSuperhero.mutable_executor()->MergeFrom(executorSuperhero); EXPECT_CALL(execSuperhero, launchTask(_, _)) .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING)) .WillRepeatedly(Return()); Future<TaskStatus> statusSuperhero; EXPECT_CALL(schedSuperhero, statusUpdate(&driverSuperhero, _)) .WillOnce(FutureArg<1>(&statusSuperhero)); driverSuperhero.launchTasks(offersSuperhero.get()[0].id(), {taskSuperhero}); AWAIT_READY(statusSuperhero); EXPECT_EQ(TASK_RUNNING, statusSuperhero->state()); MockScheduler schedMuggle; MesosSchedulerDriver driverMuggle( &schedMuggle, frameworkMuggle, master.get()->pid, DEFAULT_CREDENTIAL_2); EXPECT_CALL(execMuggle, registered(_, _, _, _)) .Times(AtMost(1)); Future<FrameworkID> frameworkIdMuggle; EXPECT_CALL(schedMuggle, registered(&driverMuggle, _, _)) .WillOnce(FutureArg<1>(&frameworkIdMuggle)); Future<vector<Offer>> offersMuggle; EXPECT_CALL(schedMuggle, resourceOffers(&driverMuggle, _)) .WillOnce(FutureArg<1>(&offersMuggle)) .WillRepeatedly(Return()); // Ignore subsequent offers. driverMuggle.start(); AWAIT_READY(frameworkIdMuggle); AWAIT_READY(offersMuggle); ASSERT_FALSE(offersMuggle->empty()); // Define a task which will run on executorMuggle of frameworkMuggle. TaskInfo taskMuggle; taskMuggle.set_name("test-" + roleMuggle); taskMuggle.mutable_task_id()->set_value("2"); taskMuggle.mutable_slave_id()->MergeFrom( offersMuggle.get()[0].slave_id()); taskMuggle.mutable_resources()->MergeFrom( offersMuggle.get()[0].resources()); taskMuggle.mutable_executor()->MergeFrom(executorMuggle); EXPECT_CALL(execMuggle, launchTask(_, _)) .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING)) .WillRepeatedly(Return()); Future<TaskStatus> statusMuggle; EXPECT_CALL(schedMuggle, statusUpdate(&driverMuggle, _)) .WillOnce(FutureArg<1>(&statusMuggle)); driverMuggle.launchTasks(offersMuggle.get()[0].id(), {taskMuggle}); AWAIT_READY(statusMuggle); ASSERT_EQ(TASK_RUNNING, statusMuggle->state()); // Retrieve endpoint with the user allowed to view the frameworks. // The default user allowed to view role "superhero" only. { Future<Response> response = http::get( slave.get()->pid, "state", None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body); ASSERT_SOME(parse); JSON::Object state = parse.get(); ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>()); JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>(); EXPECT_EQ(2u, frameworks.values.size()); foreach (const JSON::Value& value, frameworks.values) { JSON::Object framework = value.as<JSON::Object>(); EXPECT_FALSE(framework.values.empty()); ASSERT_TRUE(framework.values["executors"].is<JSON::Array>()); JSON::Array executors = framework.values["executors"].as<JSON::Array>(); EXPECT_EQ(1u, executors.values.size()); JSON::Object executor = executors.values.front().as<JSON::Object>(); EXPECT_EQ(1u, executor.values["tasks"].as<JSON::Array>().values.size()); } ASSERT_TRUE(state.values["reserved_resources"].is<JSON::Object>()); JSON::Object reserved_resources = state.values["reserved_resources"].as<JSON::Object>(); EXPECT_TRUE(reserved_resources.values[roleSuperhero].is<JSON::Object>()); EXPECT_FALSE(reserved_resources.values[roleMuggle].is<JSON::Object>()); ASSERT_TRUE( state.values["reserved_resources_allocated"].is<JSON::Object>()); JSON::Object reserved_resources_allocated = state.values["reserved_resources_allocated"].as<JSON::Object>(); EXPECT_TRUE( reserved_resources_allocated.values[roleSuperhero].is<JSON::Object>()); EXPECT_FALSE( reserved_resources_allocated.values[roleMuggle].is<JSON::Object>()); ASSERT_TRUE(state.values["reserved_resources_full"].is<JSON::Object>()); JSON::Object reserved_resources_full = state.values["reserved_resources_full"].as<JSON::Object>(); EXPECT_TRUE( reserved_resources_full.values[roleSuperhero].is<JSON::Array>()); EXPECT_FALSE( reserved_resources_full.values[roleMuggle].is<JSON::Array>()); } // Retrieve endpoint with the user allowed to view the frameworks, // but not the executors. // The second default user allowed to view role "muggle" only. { Future<Response> response = http::get( slave.get()->pid, "state", None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL_2)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body); ASSERT_SOME(parse); JSON::Object state = parse.get(); ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>()); JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>(); EXPECT_EQ(2u, frameworks.values.size()); foreach (const JSON::Value& value, frameworks.values) { JSON::Object framework = value.as<JSON::Object>(); EXPECT_FALSE(framework.values.empty()); EXPECT_TRUE( framework.values["executors"].as<JSON::Array>().values.empty()); } ASSERT_TRUE(state.values["reserved_resources"].is<JSON::Object>()); JSON::Object reserved_resources = state.values["reserved_resources"].as<JSON::Object>(); EXPECT_TRUE(reserved_resources.values[roleMuggle].is<JSON::Object>()); EXPECT_FALSE(reserved_resources.values[roleSuperhero].is<JSON::Object>()); ASSERT_TRUE( state.values["reserved_resources_allocated"].is<JSON::Object>()); JSON::Object reserved_resources_allocated = state.values["reserved_resources_allocated"].as<JSON::Object>(); EXPECT_TRUE( reserved_resources_allocated.values[roleMuggle].is<JSON::Object>()); EXPECT_FALSE( reserved_resources_allocated.values[roleSuperhero].is<JSON::Object>()); ASSERT_TRUE(state.values["reserved_resources_full"].is<JSON::Object>()); JSON::Object reserved_resources_full = state.values["reserved_resources_full"].as<JSON::Object>(); EXPECT_TRUE( reserved_resources_full.values[roleMuggle].is<JSON::Array>()); EXPECT_FALSE( reserved_resources_full.values[roleSuperhero].is<JSON::Array>()); } EXPECT_CALL(execSuperhero, shutdown(_)) .Times(AtMost(1)); EXPECT_CALL(execMuggle, shutdown(_)) .Times(AtMost(1)); driverSuperhero.stop(); driverSuperhero.join(); driverMuggle.stop(); driverMuggle.join(); }