blob: 862b71fa9d1e303e98de3767d69c9805e7b26dcc [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 <mutex>
#include <string>
#include <vector>
#include <mesos/module.hpp>
#include <mesos/module/anonymous.hpp>
#include <mesos/module/module.hpp>
#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 "messages/messages.hpp"
#include "module/manager.hpp"
using std::list;
using std::string;
using std::vector;
using process::Owned;
using namespace mesos;
using namespace mesos::internal;
using namespace mesos::modules;
std::mutex ModuleManager::mutex;
hashmap<const string, string> ModuleManager::kindToVersion;
hashmap<const string, ModuleBase*> ModuleManager::moduleBases;
hashmap<const string, Owned<DynamicLibrary>> ModuleManager::dynamicLibraries;
hashmap<const std::string, Parameters> ModuleManager::moduleParameters;
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["Allocator"] = MESOS_VERSION;
kindToVersion["Anonymous"] = MESOS_VERSION;
kindToVersion["Authenticatee"] = MESOS_VERSION;
kindToVersion["Authenticator"] = MESOS_VERSION;
kindToVersion["Authorizer"] = MESOS_VERSION;
kindToVersion["Hook"] = MESOS_VERSION;
kindToVersion["Isolator"] = MESOS_VERSION;
kindToVersion["QoSController"] = MESOS_VERSION;
kindToVersion["ResourceEstimator"] = MESOS_VERSION;
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 a given module and remove it from the list
// of ModuleBases.
Try<Nothing> ModuleManager::unload(const string& moduleName)
{
synchronized (mutex) {
if (!moduleBases.contains(moduleName)) {
return Error(
"Error unloading module '" + moduleName + "': module not loaded");
}
// Do not remove the dynamiclibrary as it could result in
// unloading the library from the process memory.
moduleBases.erase(moduleName);
}
return Nothing();
}
// 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_SOME(mesosVersion);
Try<Version> minimumVersion = Version::parse(kindToVersion[moduleBase->kind]);
CHECK_SOME(minimumVersion);
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();
}
Try<Nothing> ModuleManager::load(const Modules& modules)
{
synchronized (mutex) {
initialize();
foreach (const Modules::Library& library, modules.libraries()) {
string libraryName;
if (library.has_file()) {
libraryName = library.file();
} else if (library.has_name()) {
libraryName = os::libraries::expandName(library.name());
} else {
return Error("Library name or path not provided");
}
if (!dynamicLibraries.contains(libraryName)) {
Owned<DynamicLibrary> dynamicLibrary(new DynamicLibrary());
Try<Nothing> result = dynamicLibrary->open(libraryName);
if (!result.isSome()) {
return Error(
"Error opening library: '" + libraryName +
"': " + result.error());
}
dynamicLibraries[libraryName] = dynamicLibrary;
}
// Load module manifests.
foreach (const Modules::Library::Module& module, library.modules()) {
if (!module.has_name()) {
return Error(
"Error: module name not provided with library '" + libraryName +
"'");
}
// Check for possible duplicate module names.
const std::string moduleName = module.name();
if (moduleBases.contains(moduleName)) {
return Error("Error loading duplicate module '" + moduleName + "'");
}
// Load ModuleBase.
Try<void*> symbol =
dynamicLibraries[libraryName]->loadSymbol(moduleName);
if (symbol.isError()) {
return Error(
"Error loading module '" + moduleName + "': " + symbol.error());
}
ModuleBase* moduleBase = (ModuleBase*) symbol.get();
// Verify module compatibility including version, etc.
Try<Nothing> result = verifyModule(moduleName, moduleBase);
if (result.isError()) {
return Error(
"Error verifying module '" + moduleName + "': " + result.error());
}
moduleBases[moduleName] = (ModuleBase*) symbol.get();
// Now copy the supplied module-specific parameters.
moduleParameters[moduleName].mutable_parameter()->CopyFrom(
module.parameters());
}
}
}
return Nothing();
}