This is an automated email from the ASF dual-hosted git repository. abudnik pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mesos.git
commit 1122674a5c03894e4552d46cfa26dca0557a8f68 Author: Andrei Budnik <abud...@apache.org> AuthorDate: Fri Sep 6 13:25:35 2019 +0200 Implemented an integration test for /containerizer/debug endpoint. This test starts an agent with the MockIsolator to intercept calls to its `prepare` method, then it launches a task, which gets stuck. We check that the /containerizer/debug endpoint returns a non-empty list of pending futures including `MockIsolator::prepare`. After setting the promise for the `prepare`, the task successfully starts and we expect for the /containerizer/debug endpoint to return an empty list of pending operations. Review: https://reviews.apache.org/r/71455 --- src/tests/slave_tests.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/tests/slave_tests.cpp b/src/tests/slave_tests.cpp index c147bfc..fd4fd6b 100644 --- a/src/tests/slave_tests.cpp +++ b/src/tests/slave_tests.cpp @@ -63,6 +63,7 @@ #endif // USE_SSL_SOCKET #include "common/build.hpp" +#include "common/future_tracker.hpp" #include "common/http.hpp" #include "common/protobuf_utils.hpp" @@ -83,6 +84,8 @@ #include "slave/containerizer/fetcher_process.hpp" #include "slave/containerizer/mesos/containerizer.hpp" +#include "slave/containerizer/mesos/isolator_tracker.hpp" +#include "slave/containerizer/mesos/launcher.hpp" #include "tests/active_user_test_helper.hpp" #include "tests/containerizer.hpp" @@ -94,6 +97,7 @@ #include "tests/resources_utils.hpp" #include "tests/utils.hpp" +#include "tests/containerizer/isolator.hpp" #include "tests/containerizer/mock_containerizer.hpp" using namespace mesos::internal::slave; @@ -112,7 +116,9 @@ using mesos::master::detector::StandaloneMasterDetector; using mesos::v1::resource_provider::Event; using mesos::slave::ContainerConfig; +using mesos::slave::ContainerLaunchInfo; using mesos::slave::ContainerTermination; +using mesos::slave::Isolator; using mesos::v1::scheduler::Call; using mesos::v1::scheduler::Mesos; @@ -2787,6 +2793,158 @@ TEST_F(SlaveTest, ContainersEndpoint) } +// This test ensures that `/containerizer/debug` endpoint returns a non-empty +// list of pending futures when an isolator becomes unresponsive during +// container launch. +TEST_F(SlaveTest, ROOT_ContainerizerDebugEndpoint) +{ + Try<Owned<cluster::Master>> master = StartMaster(); + ASSERT_SOME(master); + + slave::Flags flags = CreateSlaveFlags(); + + Try<Launcher*> _launcher = SubprocessLauncher::create(flags); + ASSERT_SOME(_launcher); + + Owned<Launcher> launcher(_launcher.get()); + + MockIsolator* mockIsolator = new MockIsolator(); + + Future<Nothing> prepare; + Promise<Option<ContainerLaunchInfo>> promise; + + EXPECT_CALL(*mockIsolator, recover(_, _)) + .WillOnce(Return(Nothing())); + + // Simulate a long prepare from the isolator. + EXPECT_CALL(*mockIsolator, prepare(_, _)) + .WillOnce(DoAll(FutureSatisfy(&prepare), + Return(promise.future()))); + + EXPECT_CALL(*mockIsolator, update(_, _)) + .WillOnce(Return(Nothing())); + + // Wrap `mockIsolator` in `PendingFutureTracker`. + Try<PendingFutureTracker*> _futureTracker = PendingFutureTracker::create(); + ASSERT_SOME(_futureTracker); + + Owned<PendingFutureTracker> futureTracker(_futureTracker.get()); + + Owned<Isolator> isolator = Owned<Isolator>(new IsolatorTracker( + Owned<Isolator>(mockIsolator), "MockIsolator", futureTracker.get())); + + Fetcher fetcher(flags); + + Try<Owned<Provisioner>> provisioner = Provisioner::create(flags); + ASSERT_SOME(provisioner); + + Try<MesosContainerizer*> create = MesosContainerizer::create( + flags, + true, + &fetcher, + nullptr, + launcher, + provisioner->share(), + {isolator}); + + ASSERT_SOME(create); + + Owned<MesosContainerizer> containerizer(create.get()); + + Owned<MasterDetector> detector = master.get()->createDetector(); + + Try<Owned<cluster::Slave>> slave = + StartSlave(SlaveOptions(detector.get()) + .withContainerizer(containerizer.get()) + .withFutureTracker(futureTracker.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()); + + const Offer& offer = offers.get()[0]; + + // Launch a task and wait until it is in RUNNING status. + TaskInfo task = createTask( + offer.slave_id(), + Resources::parse("cpus:1;mem:32").get(), + SLEEP_COMMAND(1000)); + + Future<TaskStatus> statusStarting; + Future<TaskStatus> statusRunning; + EXPECT_CALL(sched, statusUpdate(&driver, _)) + .WillOnce(FutureArg<1>(&statusStarting)) + .WillOnce(FutureArg<1>(&statusRunning)); + + driver.launchTasks(offer.id(), {task}); + + AWAIT_READY(prepare); + + { + Future<Response> response = process::http::get( + slave.get()->pid, + "containerizer/debug", + None(), + createBasicAuthHeaders(DEFAULT_CREDENTIAL)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response); + + Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body); + ASSERT_SOME(parse); + + ASSERT_FALSE(parse->at<JSON::Array>("pending")->values.empty()); + ASSERT_TRUE(strings::contains(response->body, "MockIsolator::prepare")); + } + + // Once the future returned by the `prepare` method becomes ready, + // the task should start successfully and no pending futures should be + // contained in the output returned by `/containerizer/debug` endpoint. + promise.set(Option<ContainerLaunchInfo>(ContainerLaunchInfo())); + + 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()); + + { + Future<Response> response = process::http::get( + slave.get()->pid, + "containerizer/debug", + None(), + createBasicAuthHeaders(DEFAULT_CREDENTIAL)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response); + + Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body); + ASSERT_SOME(parse); + + ASSERT_TRUE(parse->at<JSON::Array>("pending")->values.empty()); + } + + driver.stop(); + driver.join(); +} + + // This test ensures that when a slave is shutting down, it will not // try to reregister with the master. //