Hey Niklas. One of the hook tests is continously failing on our internal CI. I've filed https://issues.apache.org/jira/browse/MESOS-2226. Can you take a look?
Also, https://issues.apache.org/jira/browse/MESOS-2227 On Wed, Jan 14, 2015 at 3:41 PM, <[email protected]> wrote: > Repository: mesos > Updated Branches: > refs/heads/master 60007b9e7 -> 4e110d5d0 > > > Added example hook module. > > This module provides hooks for master label decorator, slave executor > environment decorator and slave remove executor. A couple of test > cases are also provided to verify the hooks from this module. > > Review: https://reviews.apache.org/r/29496 > > > Project: http://git-wip-us.apache.org/repos/asf/mesos/repo > Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/4e110d5d > Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/4e110d5d > Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/4e110d5d > > Branch: refs/heads/master > Commit: 4e110d5d0f0c6cd65b8b6e5edff30bb4c589a278 > Parents: 0773763 > Author: Kapil Arya <[email protected]> > Authored: Wed Jan 14 13:49:06 2015 -0800 > Committer: Niklas Q. Nielsen <[email protected]> > Committed: Wed Jan 14 15:11:37 2015 -0800 > > ---------------------------------------------------------------------- > src/Makefile.am | 7 + > src/examples/test_hook_module.cpp | 138 +++++++++++++++++ > src/tests/hook_tests.cpp | 272 +++++++++++++++++++++++++++++++++ > src/tests/module.cpp | 21 +++ > src/tests/module.hpp | 3 +- > 5 files changed, 440 insertions(+), 1 deletion(-) > ---------------------------------------------------------------------- > > > http://git-wip-us.apache.org/repos/asf/mesos/blob/4e110d5d/src/Makefile.am > ---------------------------------------------------------------------- > diff --git a/src/Makefile.am b/src/Makefile.am > index 100db39..256384e 100644 > --- a/src/Makefile.am > +++ b/src/Makefile.am > @@ -1237,6 +1237,12 @@ libtestauthentication_la_SOURCES = > examples/test_authentication_modules.cpp > libtestauthentication_la_CPPFLAGS = $(MESOS_CPPFLAGS) > libtestauthentication_la_LDFLAGS = -release $(PACKAGE_VERSION) -shared > > +# Library containing test Hook module. > +lib_LTLIBRARIES += libtesthook.la > +libtesthook_la_SOURCES = examples/test_hook_module.cpp > +libtesthook_la_CPPFLAGS = $(MESOS_CPPFLAGS) > +libtesthook_la_LDFLAGS = -release $(PACKAGE_VERSION) -shared > + > mesos_tests_SOURCES = \ > tests/attributes_tests.cpp \ > tests/authentication_tests.cpp \ > @@ -1260,6 +1266,7 @@ mesos_tests_SOURCES = \ > tests/flags.cpp \ > tests/gc_tests.cpp \ > tests/hierarchical_allocator_tests.cpp \ > + tests/hook_tests.cpp \ > tests/isolator_tests.cpp \ > tests/log_tests.cpp \ > tests/logging_tests.cpp \ > > > http://git-wip-us.apache.org/repos/asf/mesos/blob/4e110d5d/src/examples/test_hook_module.cpp > ---------------------------------------------------------------------- > diff --git a/src/examples/test_hook_module.cpp > b/src/examples/test_hook_module.cpp > new file mode 100644 > index 0000000..04fd43e > --- /dev/null > +++ b/src/examples/test_hook_module.cpp > @@ -0,0 +1,138 @@ > +/** > + * Licensed to the Apache Software Foundation (ASF) under one > + * or more contributor license agreements. See the NOTICE file > + * distributed with this work for additional information > + * regarding copyright ownership. The ASF licenses this file > + * to you under the Apache License, Version 2.0 (the > + * "License"); you may not use this file except in compliance > + * with the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or > implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > + > +#include <string> > + > +#include <mesos/mesos.hpp> > +#include <mesos/module.hpp> > + > +#include <stout/foreach.hpp> > +#include <stout/os.hpp> > +#include <stout/try.hpp> > + > +#include "hook/hook.hpp" > +#include "master/master.hpp" > +#include "module/hook.hpp" > +#include "slave/slave.hpp" > + > +using std::string; > + > +using namespace mesos; > +using namespace mesos::internal; > + > +// Must be kept in sync with variables of the same name in > +// tests/hook_tests.cpp. > +const char* testLabelKey = "MESOS_Test_Label"; > +const char* testLabelValue = "ApacheMesos"; > +const char* testEnvironmentVariableName = > "MESOS_TEST_ENVIRONMENT_VARIABLE"; > + > +class TestHook : public Hook > +{ > +public: > + virtual Result<Labels> masterLaunchTaskLabelDecorator( > + const TaskInfo& taskInfo, > + const FrameworkInfo& frameworkInfo, > + const SlaveInfo& slaveInfo) > + { > + LOG(INFO) << "Executing 'masterLaunchTaskLabelDecorator' hook"; > + > + Labels labels; > + Label *label = labels.add_labels(); > + label->set_key(testLabelKey); > + label->set_value(testLabelValue); > + > + return labels; > + } > + > + > + // In this hook, we create a temporary file and add its path to an > + // environment variable. Later on, this environment variable is > + // looked up by the removeExecutorHook to locate and delete this > + // file. > + virtual Result<Environment> slaveLaunchExecutorEnvironmentDecorator( > + const ExecutorInfo& executorInfo, > + const TaskInfo& taskInfo) > + { > + LOG(INFO) << "Executing 'slaveLaunchExecutorEnvironmentDecorator' > hook"; > + > + // Find the label value for the label that was created in the > + // label decorator hook above. > + Option<string> labelValue; > + foreach (const Label& label, taskInfo.labels().labels()) { > + if (label.key() == testLabelKey) { > + labelValue = label.value(); > + CHECK_EQ(labelValue.get(), testLabelValue); > + } > + } > + CHECK_SOME(labelValue); > + > + // Create a temporary file. > + Try<string> file = os::mktemp(); > + CHECK_SOME(file); > + CHECK_SOME(os::write(file.get(), labelValue.get())); > + > + // Inject file path into command environment. > + Environment environment; > + Environment::Variable* variable = environment.add_variables(); > + variable->set_name(testEnvironmentVariableName); > + variable->set_value(file.get()); > + > + return environment; > + } > + > + > + // This hook locates the file created by environment decorator hook > + // and deletes it. > + virtual Try<Nothing> slaveRemoveExecutorHook( > + const FrameworkInfo& frameworkInfo, > + const ExecutorInfo& executorInfo) > + { > + LOG(INFO) << "Executing 'slaveRemoveExecutorHook'"; > + > + foreach (const Environment::Variable& variable, > + executorInfo.command().environment().variables()) { > + if (variable.name() == testEnvironmentVariableName) { > + string path = variable.value(); > + // The removeExecutor hook may be called multiple times; thus > + // we ignore the subsequent calls. > + if (os::isfile(path)) { > + CHECK_SOME(os::rm(path)); > + } > + break; > + } > + } > + return Nothing(); > + } > +}; > + > + > +static Hook* createHook(const Parameters& parameters) > +{ > + return new TestHook(); > +} > + > + > +// Declares a Hook module named 'TestHook'. > +mesos::modules::Module<Hook> org_apache_mesos_TestHook( > + MESOS_MODULE_API_VERSION, > + MESOS_VERSION, > + "Apache Mesos", > + "[email protected]", > + "Test Hook module.", > + NULL, > + createHook); > > > http://git-wip-us.apache.org/repos/asf/mesos/blob/4e110d5d/src/tests/hook_tests.cpp > ---------------------------------------------------------------------- > diff --git a/src/tests/hook_tests.cpp b/src/tests/hook_tests.cpp > new file mode 100644 > index 0000000..4e9a3a7 > --- /dev/null > +++ b/src/tests/hook_tests.cpp > @@ -0,0 +1,272 @@ > +/** > + * Licensed to the Apache Software Foundation (ASF) under one > + * or more contributor license agreements. See the NOTICE file > + * distributed with this work for additional information > + * regarding copyright ownership. The ASF licenses this file > + * to you under the Apache License, Version 2.0 (the > + * "License"); you may not use this file except in compliance > + * with the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or > implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > + > +#include <mesos/module.hpp> > + > +#include <process/future.hpp> > +#include <process/gmock.hpp> > +#include <process/pid.hpp> > + > +#include <stout/option.hpp> > +#include <stout/os.hpp> > +#include <stout/try.hpp> > + > +#include "hook/manager.hpp" > +#include "module/manager.hpp" > +#include "master/flags.hpp" > +#include "master/master.hpp" > +#include "slave/flags.hpp" > +#include "slave/slave.hpp" > + > +#include "tests/containerizer.hpp" > +#include "tests/flags.hpp" > +#include "tests/mesos.hpp" > + > +using std::string; > + > +using namespace mesos; > +using namespace mesos::internal; > +using namespace mesos::internal::tests; > +using namespace mesos::modules; > + > +using mesos::internal::master::Master; > +using mesos::internal::slave::Slave; > + > +using process::Future; > +using process::PID; > + > +using std::string; > +using std::vector; > + > +using testing::_; > +using testing::Return; > + > +const char* HOOK_MODULE_LIBRARY_NAME = "testhook"; > +const char* HOOK_MODULE_NAME = "org_apache_mesos_TestHook"; > + > +// Must be kept in sync with variables of the same name in > +// examples/test_hook_module.cpp. > +const char* testLabelKey = "MESOS_Test_Label"; > +const char* testLabelValue = "ApacheMesos"; > +const char* testEnvironmentVariableName = > "MESOS_TEST_ENVIRONMENT_VARIABLE"; > + > +class HookTest : public MesosTest > +{ > +protected: > + // TODO(karya): Replace constructor/destructor with SetUp/TearDown. > + // Currently, using SetUp/TearDown causes VerifySlave* test to > + // fail with a duplicate slave id message. However, everything > + // seems normal when using this construction/destructor combo. > + HookTest() > + { > + // Install hooks. > + EXPECT_SOME(HookManager::initialize(HOOK_MODULE_NAME)); > + } > + > + ~HookTest() > + { > + // Unload the hooks so a subsequent install may succeed. > + EXPECT_SOME(HookManager::unload(HOOK_MODULE_NAME)); > + } > +}; > + > + > +// Test varioud hook install/uninstall mechanisms. > +TEST_F(HookTest, HookLoading) > +{ > + // Installing unknown hooks should fail. > + EXPECT_ERROR(HookManager::initialize("Unknown Hook")); > + > + // Uninstalling an unknown hook should fail. > + EXPECT_ERROR(HookManager::unload("Unknown Hook")); > + > + // Installing an already installed hook should fail. > + EXPECT_ERROR(HookManager::initialize(HOOK_MODULE_NAME)); > + > + // Uninstalling a hook should succeed. > + EXPECT_SOME(HookManager::unload(HOOK_MODULE_NAME)); > + > + // Uninstalling an already uninstalled hook should fail. > + EXPECT_ERROR(HookManager::unload(HOOK_MODULE_NAME)); > + // This is needed to allow the tear-down to succeed. > + EXPECT_SOME(HookManager::initialize(HOOK_MODULE_NAME)); > +} > + > + > +// Test that the label decorator hook hangs a new label off the > +// taskinfo message during master launch task. > +TEST_F(HookTest, VerifyMasterLaunchTaskHook) > +{ > + Try<PID<Master> > master = StartMaster(CreateMasterFlags()); > + ASSERT_SOME(master); > + > + TestContainerizer containerizer; > + > + StandaloneMasterDetector detector(master.get()); > + > + // Start a mock slave since we aren't testing the slave hooks yet. > + MockSlave slave(CreateSlaveFlags(), &detector, &containerizer); > + process::spawn(slave); > + > + MockScheduler sched; > + MesosSchedulerDriver driver( > + &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); > + > + EXPECT_CALL(sched, registered(&driver, _, _)) > + .Times(1); > + > + Future<vector<Offer> > offers; > + EXPECT_CALL(sched, resourceOffers(&driver, _)) > + .WillOnce(FutureArg<1>(&offers)) > + .WillRepeatedly(Return()); // Ignore subsequent offers. > + > + driver.start(); > + > + AWAIT_READY(offers); > + EXPECT_NE(0u, offers.get().size()); > + > + CommandInfo command; > + command.set_value("sleep 10"); > + > + // Launch a task with the command executor. > + TaskInfo task; > + task.set_name(""); > + task.mutable_task_id()->set_value("1"); > + task.mutable_slave_id()->MergeFrom(offers.get()[0].slave_id()); > + task.mutable_resources()->MergeFrom(offers.get()[0].resources()); > + task.mutable_command()->MergeFrom(command); > + > + vector<TaskInfo> tasks; > + tasks.push_back(task); > + > + Future<TaskInfo> taskInfo; > + EXPECT_CALL(slave, runTask(_, _, _, _, _)) > + .Times(1) > + .WillOnce(FutureArg<4>(&taskInfo)); > + > + driver.launchTasks(offers.get()[0].id(), tasks); > + > + AWAIT_READY(taskInfo); > + > + // At launchTasks, the label decorator hook inside should have been > + // executed and we should see the labels now. > + Option<string> labelValue; > + foreach (const Label& label, taskInfo.get().labels().labels()) { > + if (label.key() == testLabelKey) { > + labelValue = label.value(); > + } > + } > + EXPECT_SOME_EQ(testLabelValue, labelValue); > + > + driver.stop(); > + driver.join(); > + > + process::terminate(slave); > + process::wait(slave); > + > + Shutdown(); // Must shutdown before 'containerizer' gets deallocated. > +} > + > + > +// Test executor environment decorator hook and remove executor hook > +// for slave. We expect the environment-decorator hook to create a > +// temporary file and the remove-executor hook to delete that file. > +TEST_F(HookTest, VerifySlaveLaunchExecutorHook) > +{ > + master::Flags masterFlags = CreateMasterFlags(); > + > + Try<PID<Master> > master = StartMaster(masterFlags); > + ASSERT_SOME(master); > + > + slave::Flags slaveFlags = CreateSlaveFlags(); > + > + MockExecutor exec(DEFAULT_EXECUTOR_ID); > + > + TestContainerizer containerizer(&exec); > + > + Try<PID<Slave> > slave = StartSlave(&containerizer); > + ASSERT_SOME(slave); > + > + MockScheduler sched; > + MesosSchedulerDriver driver( > + &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); > + > + EXPECT_CALL(sched, registered(&driver, _, _)) > + .Times(1); > + > + Future<vector<Offer> > offers; > + EXPECT_CALL(sched, resourceOffers(&driver, _)) > + .WillOnce(FutureArg<1>(&offers)) > + .WillRepeatedly(Return()); // Ignore subsequent offers. > + > + driver.start(); > + > + AWAIT_READY(offers); > + EXPECT_NE(0u, offers.get().size()); > + > + // Launch a task with the command executor. > + TaskInfo task; > + task.set_name(""); > + task.mutable_task_id()->set_value("1"); > + task.mutable_slave_id()->MergeFrom(offers.get()[0].slave_id()); > + task.mutable_resources()->MergeFrom(offers.get()[0].resources()); > + task.mutable_executor()->MergeFrom(DEFAULT_EXECUTOR_INFO); > + > + vector<TaskInfo> tasks; > + tasks.push_back(task); > + > + EXPECT_CALL(exec, launchTask(_, _)); > + > + Future<ExecutorInfo> executorInfo; > + EXPECT_CALL(exec, registered(_, _, _, _)) > + .Times(1) > + .WillOnce(FutureArg<1>(&executorInfo)); > + > + driver.launchTasks(offers.get()[0].id(), tasks); > + > + AWAIT_READY(executorInfo); > + > + // At launchTasks, the label decorator hook inside should have been > + // executed and we should see the labels now. > + // Further, the environment decorator hook should also have been > + // executed. In that hook, we create a temp file and set the path > + // as the value of the environment variable. > + // Here we verify that the environment variable is present and the > + // file is present on the disk. > + Option<string> path; > + foreach (const Environment::Variable& variable, > + executorInfo.get().command().environment().variables()) { > + if (variable.name() == testEnvironmentVariableName) { > + path = variable.value(); > + break; > + } > + } > + > + EXPECT_SOME(path); > + // The file must have been create by the environment decorator hook. > + EXPECT_TRUE(os::isfile(path.get())); > + > + driver.stop(); > + driver.join(); > + > + Shutdown(); // Must shutdown before 'containerizer' gets deallocated. > + > + // The removeExecutor hook in the test module deletes the temp file. > + // Verify that the file is not present. > + EXPECT_FALSE(os::isfile(path.get())); > +} > > > http://git-wip-us.apache.org/repos/asf/mesos/blob/4e110d5d/src/tests/module.cpp > ---------------------------------------------------------------------- > diff --git a/src/tests/module.cpp b/src/tests/module.cpp > index 6cec1cb..e6dbf94 100644 > --- a/src/tests/module.cpp > +++ b/src/tests/module.cpp > @@ -93,6 +93,24 @@ static void addAuthenticationModules(Modules& modules) > } > > > +static void addHookModules(Modules& modules) > +{ > + const string libraryPath = path::join( > + tests::flags.build_dir, > + "src", > + ".libs", > + os::libraries::expandName("testhook")); > + > + // Now add our test hook module. > + Modules::Library* library = modules.add_libraries(); > + library->set_file(libraryPath); > + > + // To add a new module from this library, create a new ModuleID enum > + // and tie it with a module name. > + addModule(library, TestHook, "org_apache_mesos_TestHook"); > +} > + > + > Try<Nothing> tests::initModules(const Option<Modules>& modules) > { > // First get the user provided modules. > @@ -107,6 +125,9 @@ Try<Nothing> tests::initModules(const Option<Modules>& > modules) > // Add authentication modules from testauthentication library. > addAuthenticationModules(mergedModules); > > + // Add hook modules from testhook library. > + addHookModules(mergedModules); > + > return ModuleManager::load(mergedModules); > } > > > > http://git-wip-us.apache.org/repos/asf/mesos/blob/4e110d5d/src/tests/module.hpp > ---------------------------------------------------------------------- > diff --git a/src/tests/module.hpp b/src/tests/module.hpp > index bc1a37d..21e8fc4 100644 > --- a/src/tests/module.hpp > +++ b/src/tests/module.hpp > @@ -42,7 +42,8 @@ enum ModuleID > TestMemIsolator, > TestCpuIsolator, > TestCRAMMD5Authenticatee, > - TestCRAMMD5Authenticator > + TestCRAMMD5Authenticator, > + TestHook > }; > > > >
