blob: a1bd02199c6de2b2870fb27afab7b65668b34e9a [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 "csi/paths.hpp"
#include <mesos/type_utils.hpp>
#include <process/address.hpp>
#include <process/http.hpp>
#include <stout/check.hpp>
#include <stout/fs.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.hpp>
#include <stout/os/realpath.hpp>
namespace http = process::http;
using std::list;
using std::string;
using std::vector;
namespace mesos {
namespace csi {
namespace paths {
// File names.
constexpr char CONTAINER_INFO_FILE[] = "container.info";
constexpr char ENDPOINT_SOCKET_FILE[] = "endpoint.sock";
constexpr char VOLUME_STATE_FILE[] = "volume.state";
constexpr char CONTAINERS_DIR[] = "containers";
constexpr char VOLUMES_DIR[] = "volumes";
constexpr char MOUNTS_DIR[] = "mounts";
constexpr char STAGING_DIR[] = "staging";
constexpr char TARGET_DIR[] = "target";
constexpr char ENDPOINT_DIR_SYMLINK[] = "endpoint";
constexpr char ENDPOINT_DIR[] = "mesos-csi-XXXXXX";
Try<list<string>> getContainerPaths(
const string& rootDir,
const string& type,
const string& name)
{
return fs::list(path::join(rootDir, type, name, CONTAINERS_DIR, "*"));
}
string getContainerPath(
const string& rootDir,
const string& type,
const string& name,
const ContainerID& containerId)
{
return path::join(
rootDir,
type,
name,
CONTAINERS_DIR,
stringify(containerId));
}
Try<ContainerPath> parseContainerPath(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 `rootDir` so that
// we don't accidentally slice off part of a directory.
const string prefix = path::join(rootDir, "");
if (!strings::startsWith(dir, prefix)) {
return Error(
"Directory '" + dir + "' does not fall under the root directory '" +
rootDir + "'");
}
vector<string> tokens = strings::tokenize(
dir.substr(prefix.size()),
stringify(os::PATH_SEPARATOR));
// A complete container path consists of 4 tokens:
// <type>/<name>/containers/<volume_id>
if (tokens.size() != 4 || tokens[2] != CONTAINERS_DIR) {
return Error(
"Path '" + path::join(tokens) + "' does not match the structure of a "
"container path");
}
ContainerPath path;
path.type = tokens[0];
path.name = tokens[1];
path.containerId.set_value(tokens[3]);
return path;
}
string getContainerInfoPath(
const string& rootDir,
const string& type,
const string& name,
const ContainerID& containerId)
{
return path::join(
getContainerPath(rootDir, type, name, containerId),
CONTAINER_INFO_FILE);
}
string getEndpointDirSymlinkPath(
const string& rootDir,
const string& type,
const string& name,
const ContainerID& containerId)
{
return path::join(
getContainerPath(rootDir, type, name, containerId),
ENDPOINT_DIR_SYMLINK);
}
Try<string> getEndpointSocketPath(
const string& rootDir,
const string& type,
const string& name,
const ContainerID& containerId)
{
#ifdef __WINDOWS__
return Error("CSI is not supported on Windows");
#else
const string symlinkPath =
getEndpointDirSymlinkPath(rootDir, type, name, containerId);
Try<Nothing> mkdir = os::mkdir(Path(symlinkPath).dirname());
if(mkdir.isError()) {
return Error(
"Failed to create directory '" + Path(symlinkPath).dirname() + "': " +
mkdir.error());
}
Result<string> endpointDir = os::realpath(symlinkPath);
if (endpointDir.isSome()) {
return path::join(endpointDir.get(), ENDPOINT_SOCKET_FILE);
}
if (os::exists(symlinkPath)) {
Try<Nothing> rm = os::rm(symlinkPath);
if (rm.isError()) {
return Error(
"Failed to remove endpoint symlink '" + symlinkPath + "': " +
rm.error());
}
}
Try<string> mkdtemp = os::mkdtemp(path::join(os::temp(), ENDPOINT_DIR));
if (mkdtemp.isError()) {
return Error(
"Failed to create endpoint directory in '" + os::temp() + "': " +
mkdtemp.error());
}
Try<Nothing> symlink = fs::symlink(mkdtemp.get(), symlinkPath);
if (symlink.isError()) {
return Error(
"Failed to symlink directory '" + mkdtemp.get() + "' to '" +
symlinkPath + "': " + symlink.error());
}
const string socketPath = path::join(mkdtemp.get(), ENDPOINT_SOCKET_FILE);
// Check if the socket path is too long.
Try<process::network::unix::Address> address =
process::network::unix::Address::create(socketPath);
if (address.isError()) {
return Error(
"Failed to create address from '" + socketPath + "': " +
address.error());
}
return socketPath;
#endif // __WINDOWS__
}
Try<list<string>> getVolumePaths(
const string& rootDir,
const string& type,
const string& name)
{
return fs::list(path::join(rootDir, type, name, VOLUMES_DIR, "*"));
}
string getVolumePath(
const string& rootDir,
const string& type,
const string& name,
const string& volumeId)
{
// Volume ID is percent-encoded to avoid invalid characters in the path.
return path::join(rootDir, type, name, VOLUMES_DIR, http::encode(volumeId));
}
Try<VolumePath> parseVolumePath(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 `rootDir` so that
// we don't accidentally slice off part of a directory.
const string prefix = path::join(rootDir, "");
if (!strings::startsWith(dir, prefix)) {
return Error(
"Directory '" + dir + "' does not fall under the root directory '" +
rootDir + "'");
}
vector<string> tokens = strings::tokenize(
dir.substr(prefix.size()),
stringify(os::PATH_SEPARATOR));
// A complete volume path consists of 4 tokens:
// <type>/<name>/volumes/<volume_id>
if (tokens.size() != 4 || tokens[2] != VOLUMES_DIR) {
return Error(
"Path '" + path::join(tokens) + "' does not match the structure of a "
"volume path");
}
// Volume ID is percent-encoded to avoid invalid characters in the path.
Try<string> volumeId = http::decode(tokens[3]);
if (volumeId.isError()) {
return Error(
"Could not decode volume ID from string '" + tokens[3] + "': " +
volumeId.error());
}
return VolumePath{tokens[0], tokens[1], volumeId.get()};
}
string getVolumeStatePath(
const string& rootDir,
const string& type,
const string& name,
const string& volumeId)
{
return path::join(
getVolumePath(rootDir, type, name, volumeId),
VOLUME_STATE_FILE);
}
string getMountRootDir(
const string& rootDir,
const string& type,
const string& name)
{
return path::join(rootDir, type, name, MOUNTS_DIR);
}
Try<list<string>> getMountPaths(const string& mountRootDir)
{
return fs::list(path::join(mountRootDir, "*"));
}
string getMountPath(const string& mountRootDir, const string& volumeId)
{
// Volume ID is percent-encoded to avoid invalid characters in the path.
return path::join(mountRootDir, http::encode(volumeId));
}
Try<string> parseMountPath(const string& mountRootDir, const string& dir)
{
// TODO(chhsiao): Consider using `<regex>`, which requires GCC 4.9+.
// Make sure there's a separator at the end of the `mountRootDir` so that we
// don't accidentally slice off part of a directory.
const string prefix = path::join(mountRootDir, "");
if (!strings::startsWith(dir, prefix)) {
return Error(
"Directory '" + dir + "' does not fall under the mount root directory"
" '" + mountRootDir + "'");
}
// Volume ID is percent-encoded to avoid invalid characters in the path.
Try<string> volumeId = http::decode(Path(dir).basename());
if (volumeId.isError()) {
return Error(
"Could not decode volume ID from string '" + Path(dir).basename() +
"': " + volumeId.error());
}
return volumeId.get();
}
string getMountStagingPath(
const string& mountRootDir,
const string& volumeId)
{
return path::join(getMountPath(mountRootDir, volumeId), STAGING_DIR);
}
string getMountTargetPath(
const string& mountRootDir,
const string& volumeId)
{
return path::join(getMountPath(mountRootDir, volumeId), TARGET_DIR);
}
} // namespace paths {
} // namespace csi {
} // namespace mesos {