blob: 60497aac3200ab9a679a81a593b5bf0d02fd4b50 [file] [log] [blame]
/**
* 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 <mesos/module/isolator.hpp>
#include <mesos/module/module.hpp>
#include <mesos/slave/isolator.hpp>
#include <stout/dynamiclibrary.hpp>
#include <stout/os.hpp>
#include "common/parse.hpp"
#include "examples/test_module.hpp"
#include "module/manager.hpp"
#include "tests/flags.hpp"
#include "tests/mesos.hpp"
using std::string;
using namespace mesos::internal::slave;
using namespace mesos::modules;
using mesos::slave::Isolator;
namespace mesos {
namespace internal {
namespace tests {
const char* DEFAULT_MODULE_LIBRARY_NAME = "examplemodule";
const char* DEFAULT_MODULE_NAME = "org_apache_mesos_TestModule";
class ModuleTest : public MesosTest
{
protected:
// During the one-time setup of the test cases, we do the
// following:
// 1. set LD_LIBRARY_PATH to also point to the src/.libs directory.
// The original LD_LIBRARY_PATH is restored at the end of all
// tests.
// 2. dlopen() examplemodule library and retrieve the pointer to
// ModuleBase for the test module. This pointer is later used to
// reset the Mesos and module API versions during per-test
// teardown.
static void SetUpTestCase()
{
libraryDirectory = path::join(tests::flags.build_dir, "src", ".libs");
// Get the current value of LD_LIBRARY_PATH.
originalLdLibraryPath = os::libraries::paths();
// Append our library path to LD_LIBRARY_PATH so that dlopen can
// search the library directory for module libraries.
os::libraries::appendPaths(libraryDirectory);
EXPECT_SOME(dynamicLibrary.open(
os::libraries::expandName(DEFAULT_MODULE_LIBRARY_NAME)));
Try<void*> symbol = dynamicLibrary.loadSymbol(DEFAULT_MODULE_NAME);
EXPECT_SOME(symbol);
moduleBase = (ModuleBase*) symbol.get();
}
static void TearDownTestCase()
{
// Close the module library.
dynamicLibrary.close();
// Restore LD_LIBRARY_PATH environment variable.
os::libraries::setPaths(originalLdLibraryPath);
}
ModuleTest()
: module(None())
{
Modules::Library* library = defaultModules.add_libraries();
library->set_file(path::join(
libraryDirectory,
os::libraries::expandName(DEFAULT_MODULE_LIBRARY_NAME)));
library->add_modules()->set_name(DEFAULT_MODULE_NAME);
}
// During the per-test tear-down, we unload the module to allow
// later loads to succeed.
~ModuleTest()
{
// The TestModule instance is created by calling new. Let's
// delete it to avoid memory leaks.
if (module.isSome()) {
delete module.get();
}
// Reset module API version and Mesos version in case the test
// changed them.
moduleBase->kind = "TestModule";
moduleBase->moduleApiVersion = MESOS_MODULE_API_VERSION;
moduleBase->mesosVersion = MESOS_VERSION;
// Unload the module so a subsequent loading may succeed.
ModuleManager::unload(DEFAULT_MODULE_NAME);
}
Modules defaultModules;
Result<TestModule*> module;
static DynamicLibrary dynamicLibrary;
static ModuleBase* moduleBase;
static string originalLdLibraryPath;
static string libraryDirectory;
};
DynamicLibrary ModuleTest::dynamicLibrary;
ModuleBase* ModuleTest::moduleBase = NULL;
string ModuleTest::originalLdLibraryPath;
string ModuleTest::libraryDirectory;
static Modules getModules(const string& libraryName, const string& moduleName)
{
Modules modules;
Modules::Library* library = modules.add_libraries();
library->set_file(os::libraries::expandName(libraryName));
Modules::Library::Module* module = library->add_modules();
module->set_name(moduleName);
return modules;
}
static Modules getModules(
const string& libraryName,
const string& moduleName,
const string& parameterKey,
const string& parameterValue)
{
Modules modules = getModules(libraryName, moduleName);
Modules::Library* library = modules.mutable_libraries(0);
Modules::Library::Module* module = library->mutable_modules(0);
Parameter* parameter = module->add_parameters();
parameter->set_key(parameterKey);
parameter->set_value(parameterValue);
return modules;
}
static Try<Modules> getModulesFromJson(
const string& libraryName,
const string& moduleName,
const string& parameterKey,
const string& parameterValue)
{
string jsonString =
"{\n"
" \"libraries\": [\n"
" {\n"
" \"file\": \"" + os::libraries::expandName(libraryName) + "\",\n"
" \"modules\": [\n"
" {\n"
" \"name\": \"" + moduleName + "\",\n"
" \"parameters\": [\n"
" {\n"
" \"key\": \"" + parameterKey + "\",\n"
" \"value\": \"" + parameterValue + "\"\n"
" }\n"
" ]\n"
" }\n"
" ]\n"
" }\n"
" ]\n"
"}";
return flags::parse<Modules>(jsonString);
}
// Test that a module library gets loaded, and its contents
// version-verified. The provided test library matches the current
// Mesos version exactly.
TEST_F(ModuleTest, ExampleModuleLoadTest)
{
EXPECT_SOME(ModuleManager::load(defaultModules));
EXPECT_TRUE(ModuleManager::contains<TestModule>(DEFAULT_MODULE_NAME));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
// The TestModuleImpl module's implementation of foo() returns
// the sum of the passed arguments, whereas bar() returns the
// product. baz() returns '-1' if "sum" is not specified as the
// "operation" in the command line parameters.
EXPECT_EQ(module.get()->foo('A', 1024), 1089);
EXPECT_EQ(module.get()->bar(0.5, 10.8), 5);
EXPECT_EQ(module.get()->baz(5, 10), -1);
}
// Test passing parameter without value.
TEST_F(ModuleTest, ParameterWithoutValue)
{
Modules modules = getModules(
DEFAULT_MODULE_LIBRARY_NAME,
DEFAULT_MODULE_NAME,
"operation",
"");
EXPECT_SOME(ModuleManager::load(modules));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_ERROR(module);
}
// Test passing parameter with invalid value.
TEST_F(ModuleTest, ParameterWithInvalidValue)
{
Modules modules = getModules(
DEFAULT_MODULE_LIBRARY_NAME,
DEFAULT_MODULE_NAME,
"operation",
"X");
EXPECT_SOME(ModuleManager::load(modules));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_ERROR(module);
}
// Test passing parameter without key.
TEST_F(ModuleTest, ParameterWithoutKey)
{
Modules modules =
getModules(DEFAULT_MODULE_LIBRARY_NAME, DEFAULT_MODULE_NAME, "", "sum");
EXPECT_SOME(ModuleManager::load(modules));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
// Since there was no valid key, baz() should return -1.
EXPECT_EQ(module.get()->baz(5, 10), -1);
}
// Test passing parameter with invalid key.
TEST_F(ModuleTest, ParameterWithInvalidKey)
{
Modules modules =
getModules(DEFAULT_MODULE_LIBRARY_NAME, DEFAULT_MODULE_NAME, "X", "sum");
EXPECT_SOME(ModuleManager::load(modules));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
// Since there was no valid key, baz() should return -1.
EXPECT_EQ(module.get()->baz(5, 10), -1);
}
// Test passing parameter with valid key and value.
TEST_F(ModuleTest, ValidParameters)
{
Modules modules = getModules(
DEFAULT_MODULE_LIBRARY_NAME,
DEFAULT_MODULE_NAME,
"operation",
"sum");
EXPECT_SOME(ModuleManager::load(modules));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
EXPECT_EQ(module.get()->baz(5, 10), 15);
}
// Test Json parsing to generate Modules protobuf.
TEST_F(ModuleTest, JsonParseTest)
{
Try<Modules> modules = getModulesFromJson(
DEFAULT_MODULE_LIBRARY_NAME,
DEFAULT_MODULE_NAME,
"operation",
"sum");
EXPECT_SOME(modules);
EXPECT_SOME(ModuleManager::load(modules.get()));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
EXPECT_EQ(module.get()->baz(5, 10), 15);
}
// Test that unloading a module succeeds if it has not been unloaded
// already. Unloading unknown modules should fail as well.
TEST_F(ModuleTest, ExampleModuleUnloadTest)
{
EXPECT_SOME(ModuleManager::load(defaultModules));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
// Unloading the module should succeed the first time.
EXPECT_SOME(ModuleManager::unload(DEFAULT_MODULE_NAME));
// Unloading the same module a second time should fail.
EXPECT_ERROR(ModuleManager::unload(DEFAULT_MODULE_NAME));
// Unloading an unknown module should fail.
EXPECT_ERROR(ModuleManager::unload("unknown"));
}
// Verify that loading a module of an invalid kind fails.
TEST_F(ModuleTest, InvalidModuleKind)
{
moduleBase->kind = "NotTestModule";
EXPECT_ERROR(ModuleManager::load(defaultModules));
}
// Verify that loading a module of different kind fails.
TEST_F(ModuleTest, ModuleKindMismatch)
{
EXPECT_SOME(ModuleManager::load(defaultModules));
EXPECT_TRUE(ModuleManager::contains<TestModule>(DEFAULT_MODULE_NAME));
EXPECT_FALSE(ModuleManager::contains<Isolator>(DEFAULT_MODULE_NAME));
module = ModuleManager::create<TestModule>(DEFAULT_MODULE_NAME);
EXPECT_SOME(module);
EXPECT_ERROR(ModuleManager::create<Isolator>(DEFAULT_MODULE_NAME));
}
// Test for correct author name, author email and library description.
TEST_F(ModuleTest, AuthorInfoTest)
{
EXPECT_STREQ(moduleBase->authorName, "Apache Mesos");
EXPECT_STREQ(moduleBase->authorEmail, "modules@mesos.apache.org");
EXPECT_STREQ(moduleBase->description, "This is a test module.");
}
// Test that a module library gets loaded when provided with a
// library name without any extension and without the "lib" prefix.
TEST_F(ModuleTest, LibraryNameWithoutExtension)
{
Modules modules;
Modules::Library* library = modules.add_libraries();
library->set_name(DEFAULT_MODULE_LIBRARY_NAME);
Modules::Library::Module* module = library->add_modules();
module->set_name(DEFAULT_MODULE_NAME);
EXPECT_SOME(ModuleManager::load(modules));
}
// Test that a module library gets loaded with just library name if
// found in LD_LIBRARY_PATH.
TEST_F(ModuleTest, LibraryNameWithExtension)
{
Modules modules;
Modules::Library* library = modules.add_libraries();
library->set_file(os::libraries::expandName(DEFAULT_MODULE_LIBRARY_NAME));
Modules::Library::Module* module = library->add_modules();
module->set_name(DEFAULT_MODULE_NAME);
EXPECT_SOME(ModuleManager::load(modules));
}
// Test that module library loading fails when filename is empty.
TEST_F(ModuleTest, EmptyLibraryFilename)
{
Modules modules = getModules("", "org_apache_mesos_TestModule");
EXPECT_ERROR(ModuleManager::load(modules));
}
// Test that module library loading fails when module name is empty.
TEST_F(ModuleTest, EmptyModuleName)
{
Modules modules = getModules("examplemodule", "");
EXPECT_ERROR(ModuleManager::load(modules));
}
// Test that module library loading fails when given an unknown path.
TEST_F(ModuleTest, UnknownLibraryTest)
{
Modules modules = getModules("unknown", "org_apache_mesos_TestModule");
EXPECT_ERROR(ModuleManager::load(modules));
}
// Test that module loading fails when given an unknown module name on
// the commandline.
TEST_F(ModuleTest, UnknownModuleTest)
{
Modules modules = getModules("examplemodule", "unknown");
EXPECT_ERROR(ModuleManager::load(modules));
}
// Test that module instantiation fails when given an unknown module
// name.
TEST_F(ModuleTest, UnknownModuleInstantiationTest)
{
EXPECT_SOME(ModuleManager::load(defaultModules));
EXPECT_ERROR(ModuleManager::create<TestModule>("unknown"));
}
// Test that loading a non-module library fails.
TEST_F(ModuleTest, NonModuleLibrary)
{
// Trying to load libmesos.so (libmesos.dylib on OS X) as a module
// library should fail.
Modules modules = getModules("mesos", DEFAULT_MODULE_NAME);
EXPECT_ERROR(ModuleManager::load(modules));
}
// Test that loading a duplicate module fails.
TEST_F(ModuleTest, DuplicateModule)
{
// Add duplicate module.
Modules::Library* library = defaultModules.add_libraries();
library->set_name(DEFAULT_MODULE_LIBRARY_NAME);
Modules::Library::Module* module = library->add_modules();
module->set_name(DEFAULT_MODULE_NAME);
EXPECT_ERROR(ModuleManager::load(defaultModules));
}
// Test that loading a module library with a different API version
// fails
TEST_F(ModuleTest, DifferentApiVersion)
{
// Make the API version '0'.
moduleBase->moduleApiVersion = "0";
EXPECT_ERROR(ModuleManager::load(defaultModules));
// Make the API version arbitrarily high.
moduleBase->moduleApiVersion = "1000";
EXPECT_ERROR(ModuleManager::load(defaultModules));
// Make the API version some random string.
moduleBase->moduleApiVersion = "ThisIsNotAnAPIVersion!";
EXPECT_ERROR(ModuleManager::load(defaultModules));
}
// Test that loading a module library compiled with a newer Mesos
// fails.
TEST_F(ModuleTest, NewerModuleLibrary)
{
// Make the library version arbitrarily high.
moduleBase->mesosVersion = "100.1.0";
EXPECT_ERROR(ModuleManager::load(defaultModules));
}
// Test that loading a module library compiled with a really old
// Mesos fails.
TEST_F(ModuleTest, OlderModuleLibrary)
{
// Make the library version arbitrarily low.
moduleBase->mesosVersion = "0.1.0";
EXPECT_ERROR(ModuleManager::load(defaultModules));
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {