blob: 9e44e5e0a41e48121f147778295a07b10b03bcf2 [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 <set>
#include <vector>
#include <mesos/secret/resolver.hpp>
#include <process/dispatch.hpp>
#include <process/owned.hpp>
#include <stout/fs.hpp>
#include <stout/hashmap.hpp>
#include <stout/numify.hpp>
#include <stout/os.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.hpp>
#include <stout/uuid.hpp>
#include "hook/manager.hpp"
#include "slave/flags.hpp"
#include "slave/gc.hpp"
#include "slave/slave.hpp"
#include "slave/containerizer/composing.hpp"
#include "slave/containerizer/containerizer.hpp"
#include "slave/containerizer/docker.hpp"
#include "slave/containerizer/mesos/containerizer.hpp"
#include "slave/containerizer/mesos/launcher.hpp"
#ifdef __linux__
#include "slave/containerizer/mesos/linux_launcher.hpp"
#endif // __linux__
#include "slave/containerizer/mesos/isolators/gpu/nvidia.hpp"
using std::map;
using std::set;
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.
Try<Resources> Containerizer::resources(const Flags& flags)
{
Try<Resources> parsed = Resources::parse(
flags.resources.getOrElse(""), flags.default_role);
if (parsed.isError()) {
return Error(parsed.error());
}
Resources resources = parsed.get();
// NOTE: We need to check for the "cpus" resource within the flags
// because once the `Resources` object is created, we cannot distinguish
// between
// (1) "cpus:0", and
// (2) no cpus specified.
// due to `Resources:add` discarding empty resources.
// We only auto-detect cpus in case (2).
// The same logic applies for the other resources!
// `Resources::fromString().get()` is safe because `Resources::parse()` above
// is valid.
vector<Resource> resourceList = Resources::fromString(
flags.resources.getOrElse(""), flags.default_role).get();
bool hasCpus = false;
bool hasMem = false;
bool hasDisk = false;
bool hasPorts = false;
foreach (const Resource& resource, resourceList) {
if (resource.name() == "cpus") {
hasCpus = true;
} else if (resource.name() == "mem") {
hasMem = true;
} else if (resource.name() == "disk") {
hasDisk = true;
} else if (resource.name() == "ports") {
hasPorts = true;
}
}
if (!hasCpus) {
// 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();
}
#ifdef __linux__
// GPU resource.
Try<Resources> gpus = NvidiaGpuAllocator::resources(flags);
if (gpus.isError()) {
return Error("Failed to obtain GPU resources: " + gpus.error());
}
// When adding in the GPU resources, make sure that we filter out
// the existing GPU resources (if any) so that we do not double
// allocate GPUs.
resources = gpus.get() + resources.filter(
[](const Resource& resource) {
return resource.name() != "gpus";
});
#endif
// Memory resource.
if (!hasMem) {
// 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_->total;
if (total >= Gigabytes(2)) {
mem = total - Gigabytes(1); // Leave 1GB free.
} else {
mem = Bytes(total.bytes() / 2); // Use 50% of the memory.
}
}
// NOTE: The size is truncated here to preserve the existing
// behavior for backward compatibility.
resources += Resources::parse(
"mem",
stringify(mem.bytes() / Bytes::MEGABYTES),
flags.default_role).get();
}
// Disk resource.
if (!hasDisk) {
// 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.
}
}
// NOTE: The size is truncated here to preserve the existing
// behavior for backward compatibility.
resources += Resources::parse(
"disk",
stringify(disk.bytes() / Bytes::MEGABYTES),
flags.default_role).get();
}
// Network resource.
if (!hasPorts) {
// No ports specified so resort to DEFAULT_PORTS.
resources += Resources::parse(
"ports",
stringify(DEFAULT_PORTS),
flags.default_role).get();
}
Option<Error> error = Resources::validate(resources);
if (error.isSome()) {
return error.get();
}
return resources;
}
Try<Containerizer*> Containerizer::create(
const Flags& flags,
bool local,
Fetcher* fetcher,
GarbageCollector* gc,
SecretResolver* secretResolver,
VolumeGidManager* volumeGidManager,
PendingFutureTracker* futureTracker)
{
// Get the set of containerizer types.
const vector<string> _types = strings::split(flags.containerizers, ",");
const set<string> containerizerTypes(_types.begin(), _types.end());
if (containerizerTypes.size() != _types.size()) {
return Error("Duplicate entries found in --containerizer flag"
" '" + flags.containerizers + "'");
}
// Optionally create the Nvidia components.
Option<NvidiaComponents> nvidia;
#ifdef __linux__
if (nvml::isAvailable()) {
// If we are using the docker containerizer (either alone or in
// conjunction with the mesos containerizer), unconditionally
// create the Nvidia components and pass them through. If we are
// using the mesos containerizer alone, make sure we also have the
// `gpu/nvidia` isolator flag set before creating these components.
bool shouldCreate = false;
if (containerizerTypes.count("docker") > 0) {
shouldCreate = true;
} else if (containerizerTypes.count("mesos") > 0) {
const vector<string> _isolators = strings::tokenize(flags.isolation, ",");
const set<string> isolators(_isolators.begin(), _isolators.end());
if (isolators.count("gpu/nvidia") > 0) {
shouldCreate = true;
}
}
if (shouldCreate) {
Try<Resources> gpus = NvidiaGpuAllocator::resources(flags);
if (gpus.isError()) {
return Error("Failed call to NvidiaGpuAllocator::resources: " +
gpus.error());
}
Try<NvidiaGpuAllocator> allocator =
NvidiaGpuAllocator::create(flags, gpus.get());
if (allocator.isError()) {
return Error("Failed to NvidiaGpuAllocator::create: " +
allocator.error());
}
Try<NvidiaVolume> volume = NvidiaVolume::create();
if (volume.isError()) {
return Error("Failed to NvidiaVolume::create: " + volume.error());
}
nvidia = NvidiaComponents(allocator.get(), volume.get());
}
}
#endif
// 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, containerizerTypes) {
if (type == "mesos") {
Try<MesosContainerizer*> containerizer = MesosContainerizer::create(
flags,
local,
fetcher,
gc,
secretResolver,
nvidia,
volumeGidManager,
futureTracker);
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, nvidia);
if (containerizer.isError()) {
return Error("Could not create DockerContainerizer: " +
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();
}
} // namespace slave {
} // namespace internal {
} // namespace mesos {