blob: ce7b95bb2f2b8ca77ae29014d5c2c8bc5dd8efec [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.
#ifndef __SLAVE_STATE_HPP__
#define __SLAVE_STATE_HPP__
#ifndef __WINDOWS__
#include <unistd.h>
#endif // __WINDOWS__
#include <vector>
#include <mesos/resources.hpp>
#include <mesos/type_utils.hpp>
#include <process/pid.hpp>
#include <stout/hashmap.hpp>
#include <stout/hashset.hpp>
#include <stout/path.hpp>
#include <stout/protobuf.hpp>
#include <stout/try.hpp>
#include <stout/utils.hpp>
#include <stout/uuid.hpp>
#include <stout/os/mkdir.hpp>
#include <stout/os/mktemp.hpp>
#include <stout/os/rename.hpp>
#include <stout/os/rm.hpp>
#include <stout/os/write.hpp>
#include "common/resources_utils.hpp"
#include "messages/messages.hpp"
namespace mesos {
namespace internal {
namespace slave {
namespace state {
// Forward declarations.
struct State;
struct SlaveState;
struct ResourcesState;
struct FrameworkState;
struct ExecutorState;
struct RunState;
struct TaskState;
// This function performs recovery from the state stored at 'rootDir'.
// If the 'strict' flag is set, any errors encountered while
// recovering a state are considered fatal and hence the recovery is
// short-circuited and returns an error. There might be orphaned
// executors that need to be manually cleaned up. If the 'strict' flag
// is not set, any errors encountered are considered non-fatal and the
// recovery continues by recovering as much of the state as possible,
// while increasing the 'errors' count. Note that 'errors' on a struct
// includes the 'errors' encountered recursively. In other words,
// 'State.errors' is the sum total of all recovery errors.
Try<State> recover(const std::string& rootDir, bool strict);
// Reads the protobuf message(s) from the given path.
// `T` may be either a single protobuf message or a sequence of messages
// if `T` is a specialization of `google::protobuf::RepeatedPtrField`.
template <typename T>
Result<T> read(const std::string& path)
{
Result<T> result = ::protobuf::read<T>(path);
if (result.isSome()) {
upgradeResources(&result.get());
}
return result;
}
// While we return a `Result<string>` here in order to keep the return
// type of `state::read` consistent, the `None` case does not arise here.
// That is, an empty file will result in an empty string, rather than
// the `Result` ending up in a `None` state.
template <>
inline Result<std::string> read<std::string>(const std::string& path)
{
return os::read(path);
}
template <>
inline Result<Resources> read<Resources>(const std::string& path)
{
Result<google::protobuf::RepeatedPtrField<Resource>> resources =
read<google::protobuf::RepeatedPtrField<Resource>>(path);
if (resources.isError()) {
return Error(resources.error());
}
if (resources.isNone()) {
return None();
}
return std::move(resources.get());
}
namespace internal {
inline Try<Nothing> checkpoint(
const std::string& path,
const std::string& message,
bool sync)
{
return ::os::write(path, message, sync);
}
template <
typename T,
typename std::enable_if<
std::is_convertible<T*, google::protobuf::Message*>::value,
int>::type = 0>
inline Try<Nothing> checkpoint(const std::string& path, T message, bool sync)
{
// If the `Try` from `downgradeResources` returns an `Error`, we currently
// continue to checkpoint the resources in a partially downgraded state.
// This implies that an agent with refined reservations cannot be downgraded
// to versions before reservation refinement support, which was introduced
// in 1.4.0.
//
// TODO(mpark): Do something smarter with the result once
// something like an agent recovery capability is introduced.
downgradeResources(&message);
return ::protobuf::write(path, message, sync);
}
inline Try<Nothing> checkpoint(
const std::string& path,
google::protobuf::RepeatedPtrField<Resource> resources,
bool sync)
{
// If the `Try` from `downgradeResources` returns an `Error`, we currently
// continue to checkpoint the resources in a partially downgraded state.
// This implies that an agent with refined reservations cannot be downgraded
// to versions before reservation refinement support, which was introduced
// in 1.4.0.
//
// TODO(mpark): Do something smarter with the result once
// something like an agent recovery capability is introduced.
downgradeResources(&resources);
return ::protobuf::write(path, resources, sync);
}
inline Try<Nothing> checkpoint(
const std::string& path,
const Resources& resources,
bool sync)
{
const google::protobuf::RepeatedPtrField<Resource>& messages = resources;
return checkpoint(path, messages, sync);
}
} // namespace internal {
// Thin wrapper to checkpoint data to disk and perform the necessary
// error checking. It checkpoints an instance of T at the given path.
// We can checkpoint anything as long as T is supported by
// internal::checkpoint. Currently the list of supported Ts are:
// - std::string
// - google::protobuf::Message
// - google::protobuf::RepeatedPtrField<T>
// - mesos::Resources
//
// NOTE: We provide atomic (all-or-nothing) semantics here by always
// writing to a temporary file first then using os::rename to atomically
// move it to the desired path. If `sync` is set to true, this call succeeds
// only if `fsync` is supported and successfully commits the changes to the
// filesystem for the checkpoint file and each created directory.
//
// TODO(chhsiao): Consider enabling syncing by default after evaluating its
// performance impact.
template <typename T>
Try<Nothing> checkpoint(const std::string& path, const T& t, bool sync = false)
{
// Create the base directory.
std::string base = Path(path).dirname();
Try<Nothing> mkdir = os::mkdir(base, true, sync);
if (mkdir.isError()) {
return Error("Failed to create directory '" + base + "': " + mkdir.error());
}
// NOTE: We create the temporary file at 'base/XXXXXX' to make sure
// rename below does not cross devices (MESOS-2319).
//
// TODO(jieyu): It's possible that the temporary file becomes
// dangling if slave crashes or restarts while checkpointing.
// Consider adding a way to garbage collect them.
Try<std::string> temp = os::mktemp(path::join(base, "XXXXXX"));
if (temp.isError()) {
return Error("Failed to create temporary file: " + temp.error());
}
// Now checkpoint the instance of T to the temporary file.
Try<Nothing> checkpoint = internal::checkpoint(temp.get(), t, sync);
if (checkpoint.isError()) {
// Try removing the temporary file on error.
os::rm(temp.get());
return Error("Failed to write temporary file '" + temp.get() +
"': " + checkpoint.error());
}
// Rename the temporary file to the path.
Try<Nothing> rename = os::rename(temp.get(), path, sync);
if (rename.isError()) {
// Try removing the temporary file on error.
os::rm(temp.get());
return Error("Failed to rename '" + temp.get() + "' to '" +
path + "': " + rename.error());
}
return Nothing();
}
// NOTE: The *State structs (e.g., TaskState, RunState, etc) are
// defined in reverse dependency order because many of them have
// Option<*State> dependencies which means we need them declared in
// their entirety in order to compile because things like
// Option<*State> need to know the final size of the types.
struct TaskState
{
TaskState() : errors(0) {}
static Try<TaskState> recover(
const std::string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId,
const TaskID& taskId,
bool strict);
TaskID id;
Option<Task> info;
std::vector<StatusUpdate> updates;
hashset<id::UUID> acks;
unsigned int errors;
};
struct RunState
{
RunState() : completed(false), errors(0) {}
static Try<RunState> recover(
const std::string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
const ContainerID& containerId,
bool strict,
bool rebooted);
Option<ContainerID> id;
hashmap<TaskID, TaskState> tasks;
Option<pid_t> forkedPid;
Option<process::UPID> libprocessPid;
// This represents if the executor is connected via HTTP. It can be None()
// when the connection type is unknown.
Option<bool> http;
// Executor terminated and all its updates acknowledged.
bool completed;
unsigned int errors;
};
struct ExecutorState
{
ExecutorState() : errors(0) {}
static Try<ExecutorState> recover(
const std::string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
const ExecutorID& executorId,
bool strict,
bool rebooted);
ExecutorID id;
Option<ExecutorInfo> info;
Option<ContainerID> latest;
hashmap<ContainerID, RunState> runs;
unsigned int errors;
bool generatedForCommandTask = false;
};
struct FrameworkState
{
FrameworkState() : errors(0) {}
static Try<FrameworkState> recover(
const std::string& rootDir,
const SlaveID& slaveId,
const FrameworkID& frameworkId,
bool strict,
bool rebooted);
FrameworkID id;
Option<FrameworkInfo> info;
// Note that HTTP frameworks (supported in 0.24.0) do not have a
// PID, in which case 'pid' is Some(UPID()) rather than None().
Option<process::UPID> pid;
hashmap<ExecutorID, ExecutorState> executors;
unsigned int errors;
};
struct ResourcesState
{
ResourcesState() : errors(0) {}
static Try<ResourcesState> recover(
const std::string& rootDir,
bool strict);
Resources resources;
Option<Resources> target;
unsigned int errors;
};
struct SlaveState
{
SlaveState() : errors(0) {}
static Try<SlaveState> recover(
const std::string& rootDir,
const SlaveID& slaveId,
bool strict,
bool rebooted);
SlaveID id;
Option<SlaveInfo> info;
hashmap<FrameworkID, FrameworkState> frameworks;
unsigned int errors;
};
// The top level state. The members are child nodes in the tree. Each
// child node (recursively) recovers the checkpointed state.
struct State
{
State() : errors(0) {}
Option<ResourcesState> resources;
Option<SlaveState> slave;
bool rebooted = false;
// TODO(jieyu): Consider using a vector of Option<Error> here so
// that we can print all the errors. This also applies to all the
// State structs above.
unsigned int errors;
};
} // namespace state {
} // namespace slave {
} // namespace internal {
} // namespace mesos {
#endif // __SLAVE_STATE_HPP__