blob: 4d66e767de1f877cb66b37826ba7c9d00639a7c0 [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 <map>
#include <vector>
#include <process/dispatch.hpp>
#include <process/owned.hpp>
#include <stout/fs.hpp>
#include <stout/hashmap.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.hpp>
#include <stout/uuid.hpp>
#include "hook/manager.hpp"
#include "slave/flags.hpp"
#include "slave/slave.hpp"
#ifdef __linux__
#include "slave/containerizer/linux_launcher.hpp"
#endif // __linux__
#include "slave/containerizer/composing.hpp"
#include "slave/containerizer/containerizer.hpp"
#include "slave/containerizer/docker.hpp"
#include "slave/containerizer/launcher.hpp"
#include "slave/containerizer/external_containerizer.hpp"
#include "slave/containerizer/mesos/containerizer.hpp"
using std::map;
using std::string;
using std::vector;
using namespace process;
namespace mesos {
namespace internal {
namespace slave {
// TODO(idownes): Move this to the Containerizer interface to complete
// the delegation of containerization, i.e., external containerizers should be
// able to report the resources they can isolate.
Try<Resources> Containerizer::resources(const Flags& flags)
{
Try<Resources> parsed = Resources::parse(
flags.resources.get(""), flags.default_role);
if (parsed.isError()) {
return Error(parsed.error());
}
Resources resources = parsed.get();
// NOTE: We need to check for the "cpus" string within the flag
// because once Resources are parsed, we cannot distinguish between
// (1) "cpus:0", and
// (2) no cpus specified.
// We only auto-detect cpus in case (2).
// The same logic applies for the other resources!
if (!strings::contains(flags.resources.get(""), "cpus")) {
// No CPU specified so probe OS or resort to DEFAULT_CPUS.
double cpus;
Try<long> cpus_ = os::cpus();
if (!cpus_.isSome()) {
LOG(WARNING) << "Failed to auto-detect the number of cpus to use: '"
<< cpus_.error()
<< "'; defaulting to " << DEFAULT_CPUS;
cpus = DEFAULT_CPUS;
} else {
cpus = cpus_.get();
}
resources += Resources::parse(
"cpus",
stringify(cpus),
flags.default_role).get();
}
// Memory resource.
if (!strings::contains(flags.resources.get(""), "mem")) {
// No memory specified so probe OS or resort to DEFAULT_MEM.
Bytes mem;
Try<os::Memory> mem_ = os::memory();
if (mem_.isError()) {
LOG(WARNING) << "Failed to auto-detect the size of main memory: '"
<< mem_.error()
<< "' ; defaulting to DEFAULT_MEM";
mem = DEFAULT_MEM;
} else {
Bytes total = mem_.get().total;
if (total >= Gigabytes(2)) {
mem = total - Gigabytes(1); // Leave 1GB free.
} else {
mem = Bytes(total.bytes() / 2); // Use 50% of the memory.
}
}
resources += Resources::parse(
"mem",
stringify(mem.megabytes()),
flags.default_role).get();
}
// Disk resource.
if (!strings::contains(flags.resources.get(""), "disk")) {
// No disk specified so probe OS or resort to DEFAULT_DISK.
Bytes disk;
// NOTE: We calculate disk size of the file system on
// which the slave work directory is mounted.
Try<Bytes> disk_ = fs::size(flags.work_dir);
if (!disk_.isSome()) {
LOG(WARNING) << "Failed to auto-detect the disk space: '"
<< disk_.error()
<< "' ; defaulting to " << DEFAULT_DISK;
disk = DEFAULT_DISK;
} else {
Bytes total = disk_.get();
if (total >= Gigabytes(10)) {
disk = total - Gigabytes(5); // Leave 5GB free.
} else {
disk = Bytes(total.bytes() / 2); // Use 50% of the disk.
}
}
resources += Resources::parse(
"disk",
stringify(disk.megabytes()),
flags.default_role).get();
}
// Network resource.
if (!strings::contains(flags.resources.get(""), "ports")) {
// No ports specified so resort to DEFAULT_PORTS.
resources += Resources::parse(
"ports",
stringify(DEFAULT_PORTS),
flags.default_role).get();
}
return resources;
}
Try<Containerizer*> Containerizer::create(
const Flags& flags,
bool local,
Fetcher* fetcher)
{
if (flags.isolation == "external") {
LOG(WARNING) << "The 'external' isolation flag is deprecated, "
<< "please update your flags to"
<< " '--containerizers=external'.";
Try<ExternalContainerizer*> containerizer =
ExternalContainerizer::create(flags);
if (containerizer.isError()) {
return Error("Could not create ExternalContainerizer: " +
containerizer.error());
}
return containerizer.get();
}
// TODO(benh): We need to store which containerizer or
// containerizers were being used. See MESOS-1663.
// Create containerizer(s).
vector<Containerizer*> containerizers;
foreach (const string& type, strings::split(flags.containerizers, ",")) {
if (type == "mesos") {
Try<MesosContainerizer*> containerizer =
MesosContainerizer::create(flags, local, fetcher);
if (containerizer.isError()) {
return Error("Could not create MesosContainerizer: " +
containerizer.error());
} else {
containerizers.push_back(containerizer.get());
}
} else if (type == "docker") {
Try<DockerContainerizer*> containerizer =
DockerContainerizer::create(flags, fetcher);
if (containerizer.isError()) {
return Error("Could not create DockerContainerizer: " +
containerizer.error());
} else {
containerizers.push_back(containerizer.get());
}
} else if (type == "external") {
Try<ExternalContainerizer*> containerizer =
ExternalContainerizer::create(flags);
if (containerizer.isError()) {
return Error("Could not create ExternalContainerizer: " +
containerizer.error());
} else {
containerizers.push_back(containerizer.get());
}
} else {
return Error("Unknown or unsupported containerizer: " + type);
}
}
if (containerizers.size() == 1) {
return containerizers.front();
}
Try<ComposingContainerizer*> containerizer =
ComposingContainerizer::create(containerizers);
if (containerizer.isError()) {
return Error(containerizer.error());
}
return containerizer.get();
}
map<string, string> executorEnvironment(
const ExecutorInfo& executorInfo,
const string& directory,
const SlaveID& slaveId,
const PID<Slave>& slavePid,
bool checkpoint,
const Duration& recoveryTimeout)
{
map<string, string> env;
// Set LIBPROCESS_PORT so that we bind to a random free port (since
// this might have been set via --port option). We do this before
// the environment variables below in case it is included.
env["LIBPROCESS_PORT"] = "0";
// Also add MESOS_NATIVE_JAVA_LIBRARY if it's not already present (and
// like above, we do this before the environment variables below in
// case the framework wants to override).
// TODO(tillt): Adapt library towards JNI specific name once libmesos
// has been split.
if (!os::hasenv("MESOS_NATIVE_JAVA_LIBRARY")) {
string path =
#ifdef __APPLE__
LIBDIR "/libmesos-" VERSION ".dylib";
#else
LIBDIR "/libmesos-" VERSION ".so";
#endif
if (os::exists(path)) {
env["MESOS_NATIVE_JAVA_LIBRARY"] = path;
}
}
// Also add MESOS_NATIVE_LIBRARY if it's not already present.
// This environment variable is kept for offering non JVM-based
// frameworks a more compact and JNI independent library.
if (!os::hasenv("MESOS_NATIVE_LIBRARY")) {
string path =
#ifdef __APPLE__
LIBDIR "/libmesos-" VERSION ".dylib";
#else
LIBDIR "/libmesos-" VERSION ".so";
#endif
if (os::exists(path)) {
env["MESOS_NATIVE_LIBRARY"] = path;
}
}
env["MESOS_FRAMEWORK_ID"] = executorInfo.framework_id().value();
env["MESOS_EXECUTOR_ID"] = executorInfo.executor_id().value();
env["MESOS_DIRECTORY"] = directory;
env["MESOS_SLAVE_ID"] = slaveId.value();
env["MESOS_SLAVE_PID"] = stringify(slavePid);
env["MESOS_CHECKPOINT"] = checkpoint ? "1" : "0";
if (checkpoint) {
env["MESOS_RECOVERY_TIMEOUT"] = stringify(recoveryTimeout);
}
// Include any environment variables from Hooks.
// TODO(karya): Call environment decorator hook _after_ putting all
// variables from executorInfo into 'env'. This would prevent the
// ones provided by hooks from being overwritten by the ones in
// executorInfo in case of a conflict. The overwriting takes places
// at the callsites of executorEnvironment (e.g., ___launch function
// in src/slave/containerizer/docker.cpp)
// TODO(karya): Provide a mechanism to pass the new environment
// variables created above (MESOS_*) on to the hook modules.
const Environment& hooksEnvironment =
HookManager::slaveExecutorEnvironmentDecorator(executorInfo);
foreach (const Environment::Variable& variable,
hooksEnvironment.variables()) {
env[variable.name()] = variable.value();
}
return env;
}
} // namespace slave {
} // namespace internal {
} // namespace mesos {