blob: 3e44b636c76c01f88b00f5f8d2db39cc5fe00c1c [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 <list>
#include <string>
#include <vector>
#include <mesos/mesos.hpp>
#include <mesos/resources.hpp>
#include <mesos/roles.hpp>
#include <mesos/type_utils.hpp>
#include <stout/check.hpp>
#include <stout/fs.hpp>
#include <stout/nothing.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/strings.hpp>
#include <stout/try.hpp>
#include <stout/unreachable.hpp>
#include <glog/logging.h>
#include "common/validation.hpp"
#include "csi/paths.hpp"
#include "messages/messages.hpp"
#include "slave/paths.hpp"
#include "slave/validation.hpp"
using std::list;
using std::string;
using std::vector;
namespace mesos {
namespace internal {
namespace slave {
namespace paths {
// File names.
const char BOOT_ID_FILE[] = "boot_id";
const char SLAVE_INFO_FILE[] = "slave.info";
const char DRAIN_CONFIG_FILE[] = "drain.config";
const char FRAMEWORK_PID_FILE[] = "framework.pid";
const char FRAMEWORK_INFO_FILE[] = "framework.info";
const char LIBPROCESS_PID_FILE[] = "libprocess.pid";
const char EXECUTOR_INFO_FILE[] = "executor.info";
const char EXECUTOR_SENTINEL_FILE[] = "executor.sentinel";
const char HTTP_MARKER_FILE[] = "http.marker";
const char FORKED_PID_FILE[] = "forked.pid";
const char TASK_INFO_FILE[] = "task.info";
const char TASK_UPDATES_FILE[] = "task.updates";
const char RESOURCE_STATE_FILE[] = "resources_and_operations.state";
const char RESOURCE_STATE_TARGET_FILE[] = "resources_and_operations.target";
const char RESOURCES_INFO_FILE[] = "resources.info";
const char RESOURCES_TARGET_FILE[] = "resources.target";
const char RESOURCE_PROVIDER_STATE_FILE[] = "resource_provider.state";
const char OPERATION_UPDATES_FILE[] = "operation.updates";
const char VOLUME_GIDS_FILE[] = "volume_gids";
const char CONTAINERS_DIR[] = "containers";
const char CSI_DIR[] = "csi";
const char SLAVES_DIR[] = "slaves";
const char FRAMEWORKS_DIR[] = "frameworks";
const char EXECUTORS_DIR[] = "executors";
const char EXECUTOR_RUNS_DIR[] = "runs";
const char RESOURCE_PROVIDER_REGISTRY[] = "resource_provider_registry";
const char RESOURCE_PROVIDERS_DIR[] = "resource_providers";
const char OPERATIONS_DIR[] = "operations";
Try<ExecutorRunPath> parseExecutorRunPath(
const string& _rootDir,
const string& dir)
{
// TODO(josephw): Consider using `<regex>` here, which requires GCC 4.9+.
// Make sure there's a separator at the end of the `rootdir` so that
// we don't accidentally slice off part of a directory.
const string rootDir = path::join(_rootDir, "");
if (!strings::startsWith(dir, rootDir)) {
return Error(
"Directory '" + dir + "' does not fall under "
"the root directory: " + rootDir);
}
vector<string> tokens = strings::tokenize(
dir.substr(rootDir.size()), stringify(os::PATH_SEPARATOR));
// A complete executor run path consists of at least 8 tokens, which
// includes the four named directories and the four IDs.
if (tokens.size() < 8) {
return Error(
"Path after root directory is not long enough to be an "
"executor run path: " + path::join(tokens));
}
// All four named directories much match.
if (tokens[0] == SLAVES_DIR &&
tokens[2] == FRAMEWORKS_DIR &&
tokens[4] == EXECUTORS_DIR &&
tokens[6] == EXECUTOR_RUNS_DIR) {
ExecutorRunPath path;
path.slaveId.set_value(tokens[1]);
path.frameworkId.set_value(tokens[3]);
path.executorId.set_value(tokens[5]);
path.containerId.set_value(tokens[7]);
return path;
}
return Error("Could not parse executor run path from directory: " + dir);
}
string getMetaRootDir(const string& rootDir)
{
return path::join(rootDir, "meta");
}
string getSandboxRootDir(const string& rootDir)
{
return path::join(rootDir, SLAVES_DIR);
}
string getProvisionerDir(const string& rootDir)
{
return path::join(rootDir, "provisioner");
}
string getCsiRootDir(const string& workDir)
{
return path::join(workDir, CSI_DIR);
}
string getBootIdPath(const string& rootDir)
{
return path::join(rootDir, BOOT_ID_FILE);
}
string getLatestSlavePath(const string& rootDir)
{
return path::join(rootDir, SLAVES_DIR, LATEST_SYMLINK);
}
string getSlavePath(
const string& rootDir,
const SlaveID& slaveId)
{
return path::join(rootDir, SLAVES_DIR, stringify(slaveId));
}
Try<list<string>> getContainerPaths(
const string& rootDir)
{
return fs::list(path::join(rootDir, CONTAINERS_DIR, "*"));
}
string getContainerPath(
const string& rootDir,
const ContainerID& containerId)
{
return path::join(rootDir, CONTAINERS_DIR, stringify(containerId));
}
string getSlaveInfoPath(
const string& rootDir,
const SlaveID& slaveId)
{
return path::join(getSlavePath(rootDir, slaveId), SLAVE_INFO_FILE);
}
Try<list<string>> getFrameworkPaths(
const string& rootDir,
const SlaveID& slaveId)
{
return fs::list(
path::join(getSlavePath(rootDir, slaveId), FRAMEWORKS_DIR, "*"));
}
string getFrameworkPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId)
{
return path::join(
getSlavePath(rootDir, slaveId), FRAMEWORKS_DIR, stringify(frameworkId));
}
string getFrameworkPidPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId)
{
return path::join(
getFrameworkPath(rootDir, slaveId, frameworkId), FRAMEWORK_PID_FILE);
}
string getFrameworkInfoPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId)
{
return path::join(
getFrameworkPath(rootDir, slaveId, frameworkId), FRAMEWORK_INFO_FILE);
}
Try<list<string>> getExecutorPaths(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId)
{
return fs::list(path::join(
getFrameworkPath(rootDir, slaveId, frameworkId),
EXECUTORS_DIR,
"*"));
}
string getExecutorPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId)
{
return path::join(
getFrameworkPath(rootDir, slaveId, frameworkId),
EXECUTORS_DIR,
stringify(executorId));
}
string getExecutorInfoPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId)
{
return path::join(
getExecutorPath(rootDir, slaveId, frameworkId, executorId),
EXECUTOR_INFO_FILE);
}
Try<list<string>> getExecutorRunPaths(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId)
{
return fs::list(path::join(
getExecutorPath(rootDir, slaveId, frameworkId, executorId),
EXECUTOR_RUNS_DIR,
"*"));
}
string getExecutorRunPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId)
{
return path::join(
getExecutorPath(rootDir, slaveId, frameworkId, executorId),
EXECUTOR_RUNS_DIR,
stringify(containerId));
}
string getExecutorGeneratedForCommandTaskPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId)
{
constexpr char EXECUTOR_GENERATED_FOR_COMMAND_TASK_PATH[] =
"executor_generated_for_command_task";
return path::join(
getExecutorPath(rootDir, slaveId, frameworkId, executorId),
EXECUTOR_GENERATED_FOR_COMMAND_TASK_PATH);
}
string getExecutorHttpMarkerPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId)
{
return path::join(
getExecutorRunPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId),
HTTP_MARKER_FILE);
}
string getExecutorSentinelPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId)
{
return path::join(
getExecutorRunPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId),
EXECUTOR_SENTINEL_FILE);
}
string getExecutorVirtualPath(
const FrameworkID& frameworkId,
const ExecutorID& executorId)
{
return path::join(
stringify(os::PATH_SEPARATOR) + FRAMEWORKS_DIR,
stringify(frameworkId),
EXECUTORS_DIR,
stringify(executorId),
EXECUTOR_RUNS_DIR,
LATEST_SYMLINK);
}
string getExecutorLatestRunPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId)
{
return path::join(
getExecutorPath(rootDir, slaveId, frameworkId, executorId),
EXECUTOR_RUNS_DIR,
LATEST_SYMLINK);
}
string getLibprocessPidPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId)
{
return path::join(
getExecutorRunPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId),
"pids",
LIBPROCESS_PID_FILE);
}
string getForkedPidPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId)
{
return path::join(
getExecutorRunPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId),
"pids",
FORKED_PID_FILE);
}
Try<list<string>> getTaskPaths(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId)
{
return fs::list(path::join(
getExecutorRunPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId),
"tasks",
"*"));
}
string getTaskPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId,
const TaskID& taskId)
{
return path::join(
getExecutorRunPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId),
"tasks",
stringify(taskId));
}
string getTaskInfoPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId,
const TaskID& taskId)
{
return path::join(
getTaskPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId,
taskId),
TASK_INFO_FILE);
}
string getTaskUpdatesPath(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId,
const TaskID& taskId)
{
return path::join(
getTaskPath(
rootDir,
slaveId,
frameworkId,
executorId,
containerId,
taskId),
TASK_UPDATES_FILE);
}
string getResourceProviderRegistryPath(
const string& rootDir,
const SlaveID& slaveId)
{
return path::join(
getSlavePath(getMetaRootDir(rootDir), slaveId),
RESOURCE_PROVIDER_REGISTRY);
}
Try<list<string>> getResourceProviderPaths(
const string& metaDir,
const SlaveID& slaveId)
{
return fs::list(path::join(
getSlavePath(metaDir, slaveId),
RESOURCE_PROVIDERS_DIR,
"*", // Resource provider type.
"*", // Resource provider name.
"*"));
}
string getResourceProviderPath(
const string& metaDir,
const SlaveID& slaveId,
const string& resourceProviderType,
const string& resourceProviderName,
const ResourceProviderID& resourceProviderId)
{
return path::join(
getSlavePath(metaDir, slaveId),
RESOURCE_PROVIDERS_DIR,
resourceProviderType,
resourceProviderName,
stringify(resourceProviderId));
}
string getResourceProviderStatePath(
const string& metaDir,
const SlaveID& slaveId,
const string& resourceProviderType,
const string& resourceProviderName,
const ResourceProviderID& resourceProviderId)
{
return path::join(
getResourceProviderPath(
metaDir,
slaveId,
resourceProviderType,
resourceProviderName,
resourceProviderId),
RESOURCE_PROVIDER_STATE_FILE);
}
string getLatestResourceProviderPath(
const string& metaDir,
const SlaveID& slaveId,
const string& resourceProviderType,
const string& resourceProviderName)
{
return path::join(
getSlavePath(metaDir, slaveId),
RESOURCE_PROVIDERS_DIR,
resourceProviderType,
resourceProviderName,
LATEST_SYMLINK);
}
Try<list<string>> getOperationPaths(
const string& rootDir)
{
return fs::list(path::join(rootDir, OPERATIONS_DIR, "*"));
}
Try<list<string>> getSlaveOperationPaths(
const string& metaDir,
const SlaveID& slaveId)
{
return getOperationPaths(getSlavePath(metaDir, slaveId));
}
string getOperationPath(
const string& rootDir,
const id::UUID& operationUuid)
{
return path::join(rootDir, OPERATIONS_DIR, operationUuid.toString());
}
string getSlaveOperationPath(
const string& metaDir,
const SlaveID& slaveId,
const id::UUID& operationUuid)
{
return getOperationPath(getSlavePath(metaDir, slaveId), operationUuid);
}
Try<id::UUID> parseOperationPath(
const string& rootDir,
const string& dir)
{
// TODO(chhsiao): Consider using `<regex>`, which requires GCC 4.9+.
// Make sure there's a separator at the end of the prefix so that we
// don't accidently slice off part of a directory.
const string prefix = path::join(rootDir, OPERATIONS_DIR, "");
if (!strings::startsWith(dir, prefix)) {
return Error(
"Directory '" + dir + "' does not fall under operations directory '" +
prefix + "'");
}
Try<id::UUID> operationUuid = id::UUID::fromString(Path(dir).basename());
if (operationUuid.isError()) {
return Error(
"Could not decode operation UUID from string '" +
Path(dir).basename() + "': " + operationUuid.error());
}
return operationUuid.get();
}
Try<id::UUID> parseSlaveOperationPath(
const string& metaDir,
const SlaveID& slaveId,
const string& dir)
{
return parseOperationPath(getSlavePath(metaDir, slaveId), dir);
}
string getOperationUpdatesPath(
const string& rootDir,
const id::UUID& operationUuid)
{
return path::join(
getOperationPath(rootDir, operationUuid),
OPERATION_UPDATES_FILE);
}
string getSlaveOperationUpdatesPath(
const string& metaDir,
const SlaveID& slaveId,
const id::UUID& operationUuid)
{
return getOperationUpdatesPath(getSlavePath(metaDir, slaveId), operationUuid);
}
string getResourceStatePath(const string& rootDir)
{
return path::join(rootDir, "resources", RESOURCE_STATE_FILE);
}
string getResourceStateTargetPath(const string& rootDir)
{
return path::join(rootDir, "resources", RESOURCE_STATE_TARGET_FILE);
}
string getResourcesInfoPath(
const string& rootDir)
{
return path::join(rootDir, "resources", RESOURCES_INFO_FILE);
}
string getResourcesTargetPath(
const string& rootDir)
{
return path::join(rootDir, "resources", RESOURCES_TARGET_FILE);
}
string getDrainConfigPath(
const string& metaDir,
const SlaveID& slaveId)
{
return path::join(getSlavePath(metaDir, slaveId), DRAIN_CONFIG_FILE);
}
Try<list<string>> getPersistentVolumePaths(
const std::string& workDir)
{
return fs::list(path::join(workDir, "volumes", "roles", "*", "*"));
}
string getPersistentVolumePath(
const string& workDir,
const string& role,
const string& persistenceId)
{
// Role names might contain literal `/` if the role is part of a
// role hierarchy. Since `/` is not allowed in a directory name
// under Linux, we could either represent such sub-roles with
// sub-directories, or encode the `/` with some other identifier.
// To clearly distinguish artifacts in a volume from subroles we
// choose to encode `/` in role names as ` ` (literal space) as
// opposed to using subdirectories. Whitespace is not allowed as
// part of a role name. Also, practically all modern filesystems can
// use ` ` in filenames. There are some limitations in auxilary
// tooling which are not relevant here, e.g., many shell constructs
// require quotes around filesnames containing ` `; containers using
// persistent volumes would not see the ` ` as the role-related part
// of the path would not be part of a mapping into the container
// sandbox.
string serializableRole = strings::replace(role, "/", " ");
return path::join(
workDir, "volumes", "roles", serializableRole, persistenceId);
}
string getPersistentVolumePath(
const string& workDir,
const Resource& volume)
{
CHECK_GT(volume.reservations_size(), 0);
CHECK(volume.has_disk());
CHECK(volume.disk().has_persistence());
const string& role = Resources::reservationRole(volume);
// Additionally check that the role and the persistent ID are valid
// before using them to construct a directory path.
CHECK_NONE(roles::validate(role));
CHECK_NONE(common::validation::validateID(volume.disk().persistence().id()));
// If no `source` is provided in `DiskInfo` volumes are mapped into
// the `workDir`.
if (!volume.disk().has_source()) {
return getPersistentVolumePath(
workDir,
role,
volume.disk().persistence().id());
}
// If a `source` was provided for the volume, we map it according
// to the `type` of disk. Currently only the `PATH` and 'MOUNT'
// types are supported.
switch (volume.disk().source().type()) {
case Resource::DiskInfo::Source::PATH: {
// For `PATH` we mount a directory inside the `root`.
CHECK(volume.disk().source().has_path());
CHECK(volume.disk().source().path().has_root());
string root = volume.disk().source().path().root();
if (!path::is_absolute(root)) {
// A relative path in `root` is relative to agent work dir.
root = path::join(workDir, root);
}
if (volume.disk().source().has_id()) {
// For a CSI volume the mount point is derived from `root` and `id`.
root =
csi::paths::getMountTargetPath(root, volume.disk().source().id());
}
return getPersistentVolumePath(
root,
role,
volume.disk().persistence().id());
}
case Resource::DiskInfo::Source::MOUNT: {
// For `MOUNT` we map straight onto the root of the mount.
CHECK(volume.disk().source().has_mount());
CHECK(volume.disk().source().mount().has_root());
string root = volume.disk().source().mount().root();
if (!path::is_absolute(root)) {
// A relative path in `root` is relative to agent work dir.
root = path::join(workDir, root);
}
if (volume.disk().source().has_id()) {
// For a CSI volume the mount point is derived from `root` and `id`.
root =
csi::paths::getMountTargetPath(root, volume.disk().source().id());
}
return root;
}
case Resource::DiskInfo::Source::BLOCK:
case Resource::DiskInfo::Source::RAW:
case Resource::DiskInfo::Source::UNKNOWN:
LOG(FATAL) << "Unsupported DiskInfo.Source.type";
break;
}
UNREACHABLE();
}
string getVolumeGidsPath(const string& rootDir)
{
return path::join(rootDir, "volume_gid_manager", VOLUME_GIDS_FILE);
}
Try<string> createExecutorDirectory(
const string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId,
const Option<string>& user)
{
// These IDs should be valid as they are either assigned by the
// master/agent or validated by the master but we do a sanity check
// here before using them to create a directory.
CHECK_NONE(common::validation::validateSlaveID(slaveId));
CHECK_NONE(common::validation::validateFrameworkID(frameworkId));
CHECK_NONE(common::validation::validateExecutorID(executorId));
CHECK_NONE(slave::validation::container::validateContainerId(containerId));
const string directory =
getExecutorRunPath(rootDir, slaveId, frameworkId, executorId, containerId);
if (user.isSome()) {
LOG(INFO) << "Creating sandbox '" << directory << "'"
<< " for user '" << user.get() << "'";
} else {
LOG(INFO) << "Creating sandbox '" << directory << "'";
}
Try<Nothing> mkdir = createSandboxDirectory(directory, user);
if (mkdir.isError()) {
return Error(
"Failed to create executor directory '" + directory + "': " +
mkdir.error());
}
// Remove the previous "latest" symlink.
const string latest =
getExecutorLatestRunPath(rootDir, slaveId, frameworkId, executorId);
if (os::exists(latest)) {
CHECK_SOME(os::rm(latest))
<< "Failed to remove latest symlink '" << latest << "'";
}
// Symlink the new executor directory to "latest".
Try<Nothing> symlink = ::fs::symlink(directory, latest);
if (symlink.isError()) {
return Error(
"Failed to symlink '" + directory + "' to '" + latest + "': " +
symlink.error());
}
return directory;
}
// Given a directory path and an optional user, create a directory
// suitable for use as a task sandbox. A task sandbox must be owned
// by the task user (if present) and have restricted permissions.
Try<Nothing> createSandboxDirectory(
const string& directory,
const Option<string>& user)
{
Try<Nothing> mkdir = os::mkdir(directory);
if (mkdir.isError()) {
return Error("Failed to create directory: " + mkdir.error());
}
#ifndef __WINDOWS__
// Since this is a sandbox directory containing private task data,
// we want to ensure that it is not accessible to "others".
Try<Nothing> chmod = os::chmod(directory, 0750);
if (chmod.isError()) {
return Error("Failed to chmod directory: " + chmod.error());
}
if (user.isSome()) {
Try<Nothing> chown = os::chown(user.get(), directory);
if (chown.isError()) {
// Attempt to clean up, but since we've already failed to chown,
// we don't check the return value here.
os::rmdir(directory);
return Error(
"Failed to chown directory to '" +
user.get() + "': " + chown.error());
}
}
#endif // __WINDOWS__
return Nothing();
}
string createSlaveDirectory(
const string& rootDir,
const SlaveID& slaveId)
{
// `slaveId` should be valid because it's assigned by the master but
// we do a sanity check here before using it to create a directory.
CHECK_NONE(common::validation::validateSlaveID(slaveId));
const string directory = getSlavePath(rootDir, slaveId);
Try<Nothing> mkdir = os::mkdir(directory);
CHECK_SOME(mkdir)
<< "Failed to create agent directory '" << directory << "'";
// Remove the previous "latest" symlink.
const string latest = getLatestSlavePath(rootDir);
if (os::exists(latest)) {
CHECK_SOME(os::rm(latest))
<< "Failed to remove latest symlink '" << latest << "'";
}
// Symlink the new slave directory to "latest".
Try<Nothing> symlink = ::fs::symlink(directory, latest);
CHECK_SOME(symlink)
<< "Failed to symlink directory '" << directory
<< "' to '" << latest << "'";
return directory;
}
string createResourceProviderDirectory(
const string& rootDir,
const SlaveID& slaveId,
const string& resourceProviderType,
const string& resourceProviderName,
const ResourceProviderID& resourceProviderId)
{
const string directory = getResourceProviderPath(
rootDir,
slaveId,
resourceProviderType,
resourceProviderName,
resourceProviderId);
Try<Nothing> mkdir = os::mkdir(directory);
CHECK_SOME(mkdir)
<< "Failed to create resource provider directory '" << directory << "'";
// Remove the previous "latest" symlink.
const string latest = getLatestResourceProviderPath(
rootDir,
slaveId,
resourceProviderType,
resourceProviderName);
if (os::exists(latest)) {
CHECK_SOME(os::rm(latest))
<< "Failed to remove latest symlink '" << latest << "'";
}
// Symlink the new resource provider directory to "latest".
Try<Nothing> symlink = ::fs::symlink(directory, latest);
CHECK_SOME(symlink)
<< "Failed to symlink directory '" << directory
<< "' to '" << latest << "'";
return directory;
}
} // namespace paths {
} // namespace slave {
} // namespace internal {
} // namespace mesos {