Repository: mesos Updated Branches: refs/heads/master e4bd79489 -> b8888f1e3
Introduced Mesos modules abstractions. Adding a first class primitive, abstraction and process for dynamic library writing and loading can make it easier to extend inner workings of Mesos. Making it possible to have dynamic loadable resource allocators, isolators, containerizes, authenticators and much more. Review: https://reviews.apache.org/r/25848 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/b8888f1e Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/b8888f1e Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/b8888f1e Branch: refs/heads/master Commit: b8888f1e343c4745420b8b77e7c9213dc6515375 Parents: e4bd794 Author: Kapil Arya <[email protected]> Authored: Wed Oct 8 14:12:44 2014 -0700 Committer: Benjamin Hindman <[email protected]> Committed: Wed Oct 8 15:38:25 2014 -0700 ---------------------------------------------------------------------- include/mesos/module.hpp | 96 +++++++++++ src/Makefile.am | 13 +- src/examples/example_module_impl.cpp | 66 ++++++++ src/examples/test_module.hpp | 70 ++++++++ src/messages/messages.proto | 16 ++ src/module/manager.cpp | 208 ++++++++++++++++++++++++ src/module/manager.hpp | 117 +++++++++++++ src/tests/master_tests.cpp | 2 + src/tests/module_tests.cpp | 262 ++++++++++++++++++++++++++++++ 9 files changed, 849 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/include/mesos/module.hpp ---------------------------------------------------------------------- diff --git a/include/mesos/module.hpp b/include/mesos/module.hpp new file mode 100644 index 0000000..a172a34 --- /dev/null +++ b/include/mesos/module.hpp @@ -0,0 +1,96 @@ +/** + * 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. + */ + +#ifndef __MODULE_HPP__ +#define __MODULE_HPP__ + +// Mesos Module API (MESOS-1384). +// +// A Mesos module is an instance of a module 'kind' known to Mesos, +// implemented in a dynamically loaded library. A module library can +// hold multiple modules. When a Mesos master or slave is started, it +// takes a list of libraries with contained modules as command line +// argument (in JSON format or a path to a JSON file). +// +// JSON := [library, ...] +// library := {"path": <library path>, "modules" : [<module name>, ...]} +// +// How to write a module library: +// 1. Define a create() function that returns a pointer to an object +// of 'kind' type. +// 2. If you want to indicate backwards compatibility for a module, +// create: +// 'bool compatible() { return true; } +// (You can replace compatible with any other name). +// If you want custom compatibility checks, replace the return +// statement above with them. +// 3. Define a variable of Module<KIND> struct type and populate the +// fields (including pointers to 'create' and 'compatible'). The +// variable name thus becomes the module name. + +#include <mesos/version.hpp> + +#define MESOS_MODULE_API_VERSION "1" + +// Internal utilities, not part of the module API: + +// Declare a loadable module library. +// This also provides handshakes with Mesos for version checking. +struct ModuleBase +{ + ModuleBase( + const char* _moduleApiVersion, + const char* _mesosVersion, + const char* _kind, + const char* _authorName, + const char* _authorEmail, + const char* _description, + bool (*_compatible)()) + : moduleApiVersion(_moduleApiVersion), + mesosVersion(_mesosVersion), + kind(_kind), + authorName(_authorName), + authorEmail(_authorEmail), + description(_description), + compatible(_compatible) + { } + + const char* moduleApiVersion; + const char* mesosVersion; + // String representation of module 'kind' returned from 'create'. + const char* kind; + const char* authorName; + const char* authorEmail; + const char* description; + + // Callback invoked to check version compatibility. If this field + // is NULL, backwards compatibility is disabled and the module + // version must match the Mesos release version exactly. If the + // macro is used, Mesos first checks backwards compatibility against + // its own internal table maintained by Mesos developers, then your + // implementation that follows the macro gets a say. If you return + // true, the module is admitted. + bool (*compatible)(); +}; + + +// This declaration is neeed only for later specializations. +template<class T> +struct Module; + +#endif // __MODULE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index cff38ce..fb12b3e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -275,6 +275,7 @@ libmesos_no_3rdparty_la_SOURCES = \ master/registry.proto \ master/registrar.cpp \ master/repairer.cpp \ + module/manager.cpp \ sasl/authenticatee.hpp \ sasl/authenticator.hpp \ sasl/auxprop.hpp \ @@ -309,6 +310,7 @@ libmesos_no_3rdparty_la_SOURCES = \ pkginclude_HEADERS = \ $(top_srcdir)/include/mesos/executor.hpp \ $(top_srcdir)/include/mesos/mesos.hpp \ + $(top_srcdir)/include/mesos/module.hpp \ $(top_srcdir)/include/mesos/resources.hpp \ $(top_srcdir)/include/mesos/scheduler.hpp \ $(top_srcdir)/include/mesos/values.hpp \ @@ -398,6 +400,7 @@ libmesos_no_3rdparty_la_SOURCES += \ common/thread.hpp \ credentials/credentials.hpp \ examples/utils.hpp \ + examples/test_module.hpp \ files/files.hpp \ hdfs/hdfs.hpp \ linux/cgroups.hpp \ @@ -419,6 +422,7 @@ libmesos_no_3rdparty_la_SOURCES += \ master/registrar.hpp \ master/sorter.hpp \ messages/messages.hpp \ + module/manager.hpp \ slave/constants.hpp \ slave/flags.hpp \ slave/gc.hpp \ @@ -1130,6 +1134,12 @@ load_generator_framework_LDADD = libmesos.la check_PROGRAMS += mesos-tests +# Library containing an example module. +lib_LTLIBRARIES += libexamplemodule.la +libexamplemodule_la_SOURCES = examples/example_module_impl.cpp +libexamplemodule_la_CPPFLAGS = $(MESOS_CPPFLAGS) +libexamplemodule_la_LDFLAGS = -release $(PACKAGE_VERSION) -shared + mesos_tests_SOURCES = \ tests/allocator_tests.cpp \ tests/attributes_tests.cpp \ @@ -1158,6 +1168,7 @@ mesos_tests_SOURCES = \ tests/master_contender_detector_tests.cpp \ tests/master_tests.cpp \ tests/mesos.cpp \ + tests/module_tests.cpp \ tests/monitor_tests.cpp \ tests/partition_tests.cpp \ tests/paths_tests.cpp \ @@ -1188,7 +1199,7 @@ mesos_tests_CPPFLAGS += -DBUILD_DIR=\"$(abs_top_builddir)\" mesos_tests_CPPFLAGS += -I../$(GTEST)/include mesos_tests_CPPFLAGS += -I../$(GMOCK)/include -mesos_tests_LDADD = ../$(LIBPROCESS)/3rdparty/libgmock.la libmesos.la +mesos_tests_LDADD = ../$(LIBPROCESS)/3rdparty/libgmock.la libmesos.la -ldl mesos_tests_DEPENDENCIES = # Initialized to allow += below. http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/examples/example_module_impl.cpp ---------------------------------------------------------------------- diff --git a/src/examples/example_module_impl.cpp b/src/examples/example_module_impl.cpp new file mode 100644 index 0000000..dcba355 --- /dev/null +++ b/src/examples/example_module_impl.cpp @@ -0,0 +1,66 @@ +/** + * 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 "test_module.hpp" + +// Mesos core receives an object of type TestModuleImpl when +// instantiating the module. The object is then used to make calls +// to foo() and bar() which are declared as part of the TestModule +// interface. +class TestModuleImpl : public TestModule +{ +public: + virtual int foo(char a, long b) + { + return a + b; + } + + virtual int bar(float a, double b) + { + return a * b; + } +}; + + +static bool compatible() +{ + return true; +} + + +static TestModule* create() +{ + return new TestModuleImpl(); +} + + +// Declares a module named 'example' of 'TestModule' kind. +// compatible() hook is provided by the module for compatibility +// checks. +// create() hook returns an object of type 'TestModule'. +// Mesos core binds the module instance pointer as needed. +Module<TestModule> exampleModule( + MESOS_MODULE_API_VERSION, + MESOS_VERSION, + "author", + "[email protected]", + "This is a test module.", + compatible, + create); http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/examples/test_module.hpp ---------------------------------------------------------------------- diff --git a/src/examples/test_module.hpp b/src/examples/test_module.hpp new file mode 100644 index 0000000..4760988 --- /dev/null +++ b/src/examples/test_module.hpp @@ -0,0 +1,70 @@ +/** + * 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. + */ + +#ifndef __TEST_MODULE_HPP__ +#define __TEST_MODULE_HPP__ + +#include <mesos/module.hpp> + +// Each module "kind" has a base class associated with it that is +// inherited by the module instances. Mesos core uses the base +// class interface to bind with the module instances. +// TestModule is a base class for the "TestModule" kind. +class TestModule +{ +public: + TestModule() {} + + // Mesos core will use the base class pointer to delete the module + // instance (which is a derived object). The virtual destructor + // here ensures that the derived destructor is called for any + // cleanup that may be required for the derived object. + virtual ~TestModule() {} + + virtual int foo(char a, long b) = 0; + + virtual int bar(float a, double b) = 0; +}; + + +template<> +struct Module<TestModule> : ModuleBase +{ + Module( + const char* _moduleApiVersion, + const char* _mesosVersion, + const char* _authorName, + const char* _authorEmail, + const char* _description, + bool (*_compatible)(), + TestModule* (*_create)()) + : ModuleBase( + _moduleApiVersion, + _mesosVersion, + "TestModule", + _authorName, + _authorEmail, + _description, + _compatible), + create(_create) + { } + + TestModule* (*create)(); +}; + +#endif // __TEST_MODULE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/messages/messages.proto ---------------------------------------------------------------------- diff --git a/src/messages/messages.proto b/src/messages/messages.proto index b8039ef..edf1e4e 100644 --- a/src/messages/messages.proto +++ b/src/messages/messages.proto @@ -406,3 +406,19 @@ message TaskHealthStatus { // This will not be populated if task is healthy. optional int32 consecutive_failures = 4; } + + +// Collection of Modules. +message Modules { + message Library { + // TODO(karya): Add a "name" field that allows specifying library + // name instead of an absolute path. + //optional string name = 1; + + optional string path = 1; + + repeated string modules = 2; + } + + repeated Library libraries = 1; +} http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/module/manager.cpp ---------------------------------------------------------------------- diff --git a/src/module/manager.cpp b/src/module/manager.cpp new file mode 100644 index 0000000..e178242 --- /dev/null +++ b/src/module/manager.cpp @@ -0,0 +1,208 @@ +/** + * 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 <vector> + +#include <stout/json.hpp> +#include <stout/numify.hpp> +#include <stout/os.hpp> +#include <stout/strings.hpp> +#include <stout/stringify.hpp> +#include <stout/version.hpp> + +#include "mesos/module.hpp" + +#include "manager.hpp" + +using std::list; +using std::string; +using std::vector; +using process::Owned; + +namespace mesos { +namespace internal { + +pthread_mutex_t ModuleManager::mutex = PTHREAD_MUTEX_INITIALIZER; +hashmap<const string, string> ModuleManager::kindToVersion; +hashmap<const string, ModuleBase*> ModuleManager::moduleBases; +list<Owned<DynamicLibrary> > ModuleManager::dynamicLibraries; + + +void ModuleManager::initialize() +{ +// ATTENTION: Every time a Mesos developer breaks compatibility with a +// module kind type, this table needs to be updated. Specifically, +// the version value in the entry corresponding to the kind needs to +// be set to the Mesos version that affects the current change. +// Typically that should be the version currently under development. + + kindToVersion["TestModule"] = MESOS_VERSION; + +// What happens then when Mesos is built with a certain version, +// 'kindToVersion' states a certain other minimum version, and a +// module library is built against "module.hpp" belonging to yet +// another Mesos version? +// +// Mesos can admit modules built against earlier versions of itself +// by stating so explicitly in 'kindToVersion'. If a modules is built +// with a Mesos version greater than or equal to the one stated in +// 'kindToVersion', it passes this verification step. Otherwise it is +// rejected when attempting to load it. +// +// Here are some examples: +// +// Mesos kindToVersion library modules loadable? +// 0.18.0 0.18.0 0.18.0 YES +// 0.29.0 0.18.0 0.18.0 YES +// 0.29.0 0.18.0 0.21.0 YES +// 0.18.0 0.18.0 0.29.0 NO +// 0.29.0 0.21.0 0.18.0 NO +// 0.29.0 0.29.0 0.18.0 NO + +// ATTENTION: This mechanism only protects the interfaces of modules, +// not how they maintain functional compatibility with Mesos and among +// each other. This is covered by their own "isCompatible" call. +} + + +// For testing only. Unload all dlopen()'d module libraries and +// clear the list of module manifests. +void ModuleManager::unloadAll() +{ + kindToVersion.clear(); + moduleBases.clear(); + dynamicLibraries.clear(); +} + + +// TODO(karya): Show library author info for failed library/module. +Try<Nothing> ModuleManager::verifyModule( + const string& moduleName, const ModuleBase* moduleBase) +{ + CHECK_NOTNULL(moduleBase); + if (moduleBase->mesosVersion == NULL || + moduleBase->moduleApiVersion == NULL || + moduleBase->authorName == NULL || + moduleBase->authorEmail == NULL || + moduleBase->description == NULL || + moduleBase->kind == NULL) { + return Error("Error loading module '" + moduleName + "'; missing fields"); + } + + // Verify module api version. + if (stringify(moduleBase->moduleApiVersion) != MESOS_MODULE_API_VERSION) { + return Error( + "Module API version mismatch. Mesos has: " MESOS_MODULE_API_VERSION ", " + "library requires: " + stringify(moduleBase->moduleApiVersion)); + } + + if (!kindToVersion.contains(moduleBase->kind)) { + return Error("Unknown module kind: " + stringify(moduleBase->kind)); + } + + Try<Version> mesosVersion = Version::parse(MESOS_VERSION); + CHECK(!mesosVersion.isError()); + + Try<Version> minimumVersion = Version::parse(kindToVersion[moduleBase->kind]); + CHECK(!minimumVersion.isError()); + + Try<Version> moduleMesosVersion = Version::parse(moduleBase->mesosVersion); + if (moduleMesosVersion.isError()) { + return Error(moduleMesosVersion.error()); + } + + if (moduleMesosVersion.get() < minimumVersion.get()) { + return Error( + "Minimum supported mesos version for '" + stringify(moduleBase->kind) + + "' is " + stringify(minimumVersion.get()) + ", but module is compiled " + "with version " + stringify(moduleMesosVersion.get())); + } + + if (moduleBase->compatible == NULL) { + if (moduleMesosVersion.get() != mesosVersion.get()) { + return Error( + "Mesos has version " + stringify(mesosVersion.get()) + + ", but module is compiled with version " + + stringify(moduleMesosVersion.get())); + } + return Nothing(); + } + + if (moduleMesosVersion.get() > mesosVersion.get()) { + return Error( + "Mesos has version " + stringify(mesosVersion.get()) + + ", but module is compiled with version " + + stringify(moduleMesosVersion.get())); + } + + bool result = moduleBase->compatible(); + if (!result) { + return Error("Module " + moduleName + "has determined to be incompatible"); + } + + return Nothing(); +} + + +void ModuleManager::load(const Modules& modules) +{ + Lock lock(&mutex); + initialize(); + + foreach (const Modules::Library& library, modules.libraries()) { + const string& path = library.path(); + if (path.empty()) { + LOG(WARNING) << "Library path not provided"; + continue; + } + Owned<DynamicLibrary> lib = Owned<DynamicLibrary>(new DynamicLibrary()); + Try<Nothing> result = lib.get()->open(path); + if (!result.isSome()) { + LOG(WARNING) << "Error opening library: " << path; + continue; + } + // Currently we never delete the DynamicLibrary instance nor do we + // expose a way to delete it so for now we just put it in a list. + // TODO(karya): If we add the functionality to "unload" a module + // library, we should make this pointer addressable by something + // like the module name. + dynamicLibraries.push_back(lib); + + // Load module manifests. + foreach (const string& moduleName, library.modules()) { + Try<void*> symbol = lib->loadSymbol(moduleName); + if (symbol.isError()) { + LOG(WARNING) << "Error loading module '" << moduleName << "': " + << symbol.error(); + continue; + } + ModuleBase* moduleBase = (ModuleBase*) symbol.get(); + Try<Nothing> result = verifyModule(moduleName, moduleBase); + if (result.isError()) { + LOG(WARNING) << "Error verifying module '" << moduleName << "': " + << result.error(); + continue; + } + moduleBases[moduleName] = (ModuleBase*) symbol.get(); + } + } +} + +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/module/manager.hpp ---------------------------------------------------------------------- diff --git a/src/module/manager.hpp b/src/module/manager.hpp new file mode 100644 index 0000000..093c139 --- /dev/null +++ b/src/module/manager.hpp @@ -0,0 +1,117 @@ +/** + * 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. + */ + +#ifndef __MODULE_MANAGER_HPP__ +#define __MODULE_MANAGER_HPP__ + +#include <pthread.h> + +#include <list> +#include <string> +#include <vector> + +#include <glog/logging.h> + +#include <messages/messages.hpp> + +#include <process/owned.hpp> + +#include <stout/check.hpp> +#include <stout/dynamiclibrary.hpp> +#include <stout/hashmap.hpp> + +#include "common/lock.hpp" + +#include "mesos/mesos.hpp" + +namespace mesos { +namespace internal { + +// Mesos module loading. +// +// Phases: +// 1. Load dynamic libraries that contain modules. The command-line +// flag "--modules" is used to declare all available library-module +// tuples. +// 2. Verify versions and compatibilities. +// a) Library compatibility. (Module API version check) +// b) Module compatibility. (Module Kind version check) +// 3. Instantiate singleton per module. (happens in the library) +// 4. Bind reference to use case. (happens in Mesos) +class ModuleManager +{ +public: + // Loads dynamic libraries, and verifies the compatibility of the + // modules in them. + static void load(const Modules& modules); + + // create() should be called only after load(). + template<typename Kind> + static Try<Kind*> create(const std::string& moduleName) + { + Lock lock(&mutex); + if (!moduleBases.contains(moduleName)) { + return Error( + "Module '" + moduleName + "' not specified with --module flag"); + } + + Module<Kind>* module = (Module<Kind>*) moduleBases[moduleName]; + if (module->create == NULL) { + return Error( + "Error creating Module instance for '" + moduleName + "': " + "create() method not found"); + } + Kind* singleton = module->create(); + if (singleton == NULL) { + return Error("Error creating Module instance for '" + moduleName + "'"); + } + return singleton; + } + + static void unloadAll(); + +private: + typedef hashmap<const std::string, std::vector<std::string> > + LibraryModuleMap; + + static void initialize(); + + static Try<LibraryModuleMap> parseFlag(const std::string& flag); + + static Try<Nothing> verifyModule( + const std::string& moduleName, const ModuleBase* moduleBase); + + // TODO(karya): Replace pthread_mutex_t with std::mutex in + // common/lock.hpp and other places that refer to it. + static pthread_mutex_t mutex; + static hashmap<const std::string, std::string> kindToVersion; + // Mapping from "module name" to the actual ModuleBase. If two + // modules from different libraries have the same name then the last + // one specified in the protobuf Modules will be picked. + static hashmap<const std::string, ModuleBase*> moduleBases; + // A list of dynamic libraries to keep the object from getting + // destructed. + // TODO(karya): Make it addressable only when we decide to implement + // something that lets remove the module library. + static std::list<process::Owned<DynamicLibrary> > dynamicLibraries; +}; + +} // namespace internal { +} // namespace mesos { + +#endif // __MODULE_MANAGER_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/tests/master_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/master_tests.cpp b/src/tests/master_tests.cpp index d9dc40c..75435f0 100644 --- a/src/tests/master_tests.cpp +++ b/src/tests/master_tests.cpp @@ -26,6 +26,8 @@ #include <mesos/executor.hpp> #include <mesos/scheduler.hpp> +#include <messages/messages.hpp> + #include <process/clock.hpp> #include <process/future.hpp> #include <process/gmock.hpp> http://git-wip-us.apache.org/repos/asf/mesos/blob/b8888f1e/src/tests/module_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/module_tests.cpp b/src/tests/module_tests.cpp new file mode 100644 index 0000000..a651bef --- /dev/null +++ b/src/tests/module_tests.cpp @@ -0,0 +1,262 @@ +/** + * 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 <stout/dynamiclibrary.hpp> + +#include <examples/test_module.hpp> + +#include <mesos/module.hpp> + +#include <module/manager.hpp> + +#include "tests/flags.hpp" +#include "tests/mesos.hpp" + +using std::string; + +using namespace mesos; +using namespace mesos::internal; +using namespace mesos::internal::tests; + +class ModuleTest : public MesosTest {}; + +static const string getLibraryPath(string libraryName) +{ + return path::join( + tests::flags.build_dir, "src", ".libs", "lib" + libraryName + +#ifdef __linux__ + ".so" +#else + ".dylib" +#endif + ); +} + + +static Modules getModules(const string& libraryName, const string& moduleName) +{ + Modules modules; + Modules::Library* library = modules.add_libraries(); + library->set_path(getLibraryPath(libraryName)); + library->add_modules(moduleName); + return modules; +} + + +// Test that a module library gets loaded, and its contents +// version-verified. The provided test library matches the current +// Mesos version exactly. Parse the library and module request from a +// JSON string. +TEST_F(ModuleTest, ExampleModuleParseStringTest) +{ + const string libraryName = "examplemodule"; + const string moduleName = "exampleModule"; + Modules modules = getModules(libraryName, moduleName); + ModuleManager::load(modules); + + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_SOME(module); + + // The TestModuleImpl module's implementation of foo() returns + // the sum of the passed arguments, whereas bar() returns the + // product. + EXPECT_EQ(module.get()->foo('A', 1024), 1089); + EXPECT_EQ(module.get()->bar(0.5, 10.8), 5); + ModuleManager::unloadAll(); +} + + +// Test for correct author name, author email and library description. +TEST_F(ModuleTest, AuthorInfoTest) +{ + const string libraryName = "examplemodule"; + const string moduleName = "exampleModule"; + DynamicLibrary library; + Try<Nothing> result = library.open(getLibraryPath(libraryName)); + EXPECT_SOME(result); + // Check the return values against the values defined in + // test_module_impl.cpp. + Try<void*> symbol = library.loadSymbol(moduleName); + EXPECT_SOME(symbol); + ModuleBase* moduleBase = (ModuleBase*) symbol.get(); + EXPECT_EQ(stringify(moduleBase->authorName), "author"); + EXPECT_EQ(stringify(moduleBase->authorEmail), "[email protected]"); + EXPECT_EQ(stringify(moduleBase->description), "This is a test module."); + ModuleManager::unloadAll(); +} + + +// Test that module library loading fails when given an unknown path. +TEST_F(ModuleTest, UnknownLibraryTest) +{ + const string libraryName = "unknown"; + const string moduleName = "exampleModule"; + Modules modules = getModules(libraryName, moduleName); + ModuleManager::load(modules); + + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +// Test that module loading fails when given an unknown module name on +// the commandline. +TEST_F(ModuleTest, UnknownModuleTest) +{ + const string libraryName = "examplemodule"; + const string moduleName = "unknown"; + Modules modules = getModules(libraryName, moduleName); + ModuleManager::load(modules); + + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +// Test that module instantiation fails when given an unknown module +// name. +TEST_F(ModuleTest, UnknownModuleInstantiationTest) +{ + const string libraryName = "examplemodule"; + const string moduleName = "exampleModule"; + Modules modules = getModules(libraryName, moduleName); + ModuleManager::load(modules); + + Try<TestModule*> module = ModuleManager::create<TestModule>("unknown"); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +// Test that loading a non-module library fails. +TEST_F(ModuleTest, NonModuleLibrary) +{ + const string libraryName = "mesos"; + const string moduleName = "exampleModule"; + Modules modules = getModules(libraryName, moduleName); + ModuleManager::load(modules); + + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +static void updateModuleLibraryVersion( + DynamicLibrary* library, const string& moduleName, Try<const char*> version) +{ + EXPECT_SOME(version); + Try<void*> symbol = library->loadSymbol(moduleName); + EXPECT_SOME(symbol); + ModuleBase* moduleBase = (ModuleBase*) symbol.get(); + + moduleBase->mesosVersion = version.get(); +} + + +static void updateModuleApiVersion( + DynamicLibrary* library, const string& moduleName, Try<const char*> version) +{ + EXPECT_SOME(version); + Try<void*> symbol = library->loadSymbol(moduleName); + EXPECT_SOME(symbol); + ModuleBase* moduleBase = (ModuleBase*) symbol.get(); + moduleBase->moduleApiVersion = version.get(); +} + + +// Test that loading a module library with a different API version +// fails +TEST_F(ModuleTest, DifferentApiVersion) +{ + const string libraryName = "examplemodule"; + const string moduleName = "exampleModule"; + DynamicLibrary library; + Try<Nothing> result = library.open(getLibraryPath(libraryName)); + EXPECT_SOME(result); + + Modules modules = getModules(libraryName, moduleName); + + // Make the API version '0'. + updateModuleApiVersion(&library, moduleName, "0"); + ModuleManager::load(modules); + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); + + // Make the API version arbitrarily high. + updateModuleApiVersion(&library, moduleName, "1000"); + ModuleManager::load(modules); + module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); + + // Make the API version some random string. + updateModuleApiVersion(&library, moduleName, "ThisIsNotAnAPIVersion!"); + ModuleManager::load(modules); + module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +// Test that loading a module library compiled with a newer Mesos +// fails. +TEST_F(ModuleTest, NewerModuleLibrary) +{ + const string libraryName = "examplemodule"; + const string moduleName = "exampleModule"; + DynamicLibrary library; + Try<Nothing> result = library.open(getLibraryPath(libraryName)); + EXPECT_SOME(result); + + Modules modules = getModules(libraryName, moduleName); + + // Make the library version arbitrarily high. + updateModuleLibraryVersion(&library, moduleName, "100.1.0"); + ModuleManager::load(modules); + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +// Test that loading a module library compiled with a really old +// Mesos fails. +TEST_F(ModuleTest, OlderModuleLibrary) +{ + const string libraryName = "examplemodule"; + const string moduleName = "exampleModule"; + DynamicLibrary library; + Try<Nothing> result = library.open(getLibraryPath(libraryName)); + EXPECT_SOME(result); + + Modules modules = getModules(libraryName, moduleName); + + // Make the library version arbitrarily low. + updateModuleLibraryVersion(&library, moduleName, "0.1.0"); + ModuleManager::load(modules); + Try<TestModule*> module = ModuleManager::create<TestModule>(moduleName); + EXPECT_ERROR(module); + ModuleManager::unloadAll(); +} + + +// TODO(bernd): Add MESOS_MODULE_IS_COMPATIBILE() tests.
