blob: 8a47fd0d53582c381aceb736778b067c3ec30b51 [file]
#ifndef PRODUCTION_BORG_MGMT_NODE_PROXY_SAFEPOWER_SAFEPOWER_AGENT_ACTION_CONTEXT_H_
#define PRODUCTION_BORG_MGMT_NODE_PROXY_SAFEPOWER_SAFEPOWER_AGENT_ACTION_CONTEXT_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "action_hash.h"
#include "condition.h"
#include "flight_record.h"
#include "action.pb.h"
#include "safepower_agent.pb.h"
#include "state_persistence.pb.h"
#include "state_updater.h"
#include "utils.pb.h"
#include "absl/base/nullability.h"
#include "absl/base/thread_annotations.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "absl/hash/hash.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "google/protobuf/repeated_field.h"
#include "google/protobuf/repeated_ptr_field.h"
namespace safepower_agent {
class ActionContext;
// An active listener that does nothing on state updates, but serves purely
// to keep the local StateUpdater active (non-idle) during action execution.
// This prevents background state monitors from going idle
// and pausing while an action is in flight.
class KeepAliveListener
: public StateUpdater<safepower_agent_proto::SystemState>::Listener {
public:
KeepAliveListener()
: StateUpdater<safepower_agent_proto::SystemState>::Listener(
/*is_passive_listener=*/false) {}
void UpdateState(const safepower_agent_proto::SystemState&,
const safepower_agent_proto::SystemState&) override {}
protected:
void Done() override {}
};
// Used to start and manage actions. Handles retrieving persistent state and
// scheduling new actions.
class ActionContextManager {
public:
using FlightRecordRequest =
::borg_mgmt::node_proxy::safepower::utils::FlightRecordRequest;
// Action is a function that is called when the action is ready to be
// executed.
// The first argument is the proto representation of the action.
// The second argument is a callback function that is called when the action
// is finished. The callback takes a single argument: the status of the
// action.
// It is worth noting that actions are non-blocking. To allow for this, the
// callback function is passed by value. This allows the caller to invoke the
// callback at a later time.
using Action =
absl::AnyInvocable<void(const safepower_agent_proto::Action&,
absl::AnyInvocable<void(absl::Status) &&>)>;
explicit ActionContextManager(
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>
state_updater,
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>
global_state_updater,
bool read_only_mode = false)
: state_updater_(std::move(state_updater)),
global_state_updater_(std::move(global_state_updater)),
read_only_mode_(read_only_mode),
forwarder_(global_state_updater_) {
forwarder_.Listen(state_updater_);
}
~ActionContextManager() { forwarder_.StopListening(); }
ActionContextManager(ActionContextManager&& other) = delete;
ActionContextManager& operator=(ActionContextManager&& other) = delete;
absl::Status RegisterAction(const safepower_agent_proto::Action& action,
Action action_impl)
ABSL_LOCKS_EXCLUDED(actions_mutex_);
absl::Status LoadSavedActions();
absl::StatusOr<ActionContext* > StartAction(
safepower_agent_proto::StartActionRequest request)
ABSL_LOCKS_EXCLUDED(actions_mutex_);
void FinishAction(const safepower_agent_proto::Action& action,
Action action_impl) ABSL_LOCKS_EXCLUDED(actions_mutex_);
std::vector<FlightRecordWrapper> ListRunningActions() const
ABSL_LOCKS_EXCLUDED(actions_mutex_);
// Dumps debug information about the running actions to the provided stream.
void DumpDebugInfo(std::ostream& os) const
ABSL_LOCKS_EXCLUDED(actions_mutex_);
ActionContext* GetActionContext(
const FlightRecordRequest& flight_record)
ABSL_LOCKS_EXCLUDED(actions_mutex_);
bool IsActionReloading(const FlightRecordRequest& flight_record) const
ABSL_LOCKS_EXCLUDED(actions_mutex_);
void GetSupportedActions(
google::protobuf::RepeatedPtrField<safepower_agent_proto::Action>& actions) const
ABSL_LOCKS_EXCLUDED(actions_mutex_);
const
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>&
system_state_updater() const {
return state_updater_;
}
bool read_only_mode() const { return read_only_mode_; }
const
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>&
global_system_state_updater() const {
return global_state_updater_;
}
// This is just for use by mutex annotations. Since it return const, it
// can't be used to acquire the mutex.
const absl::Mutex& actions_mutex() const { return actions_mutex_; }
absl::Status validateFlightRecord(const FlightRecordRequest& record);
private:
absl::Status ValidateRequest(
const safepower_agent_proto::StartActionRequest& request,
absl::Time start_time);
absl::StatusOr<Action> ReserveAction(
const safepower_agent_proto::Action& action)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(actions_mutex_);
absl::StatusOr< std::unique_ptr<ActionContext>>
ReinitActionContext(
safepower_agent_persistence_proto::SavedAction saved_action)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(actions_mutex_);
absl::Status ReloadAndInsertAction(
safepower_agent_persistence_proto::SavedActionRecord saved_action_record,
std::unique_ptr<ActionContext>& context_to_cleanup)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(actions_mutex_);
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>
state_updater_;
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>
global_state_updater_;
bool read_only_mode_;
// Forwards updates from `state_updater_` to `global_state_updater_`.
class Forwarder
: public StateUpdater<safepower_agent_proto::SystemState>::Listener {
public:
explicit Forwarder(
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>
global_state_updater)
: StateUpdater<safepower_agent_proto::SystemState>::Listener(
/*is_passive_listener=*/true),
global_state_updater_(std::move(global_state_updater)) {}
void UpdateState(
const safepower_agent_proto::SystemState& /*previous_state*/,
const safepower_agent_proto::SystemState& update) override {
global_state_updater_->UpdateState(update);
}
protected:
void Done() override {}
private:
std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>>
global_state_updater_;
};
Forwarder forwarder_;
mutable absl::Mutex actions_mutex_;
absl::flat_hash_map<safepower_agent_proto::Action, Action, ActionHash,
ActionEq>
actions_ ABSL_GUARDED_BY(actions_mutex_);
// ActionContext owns the key.
absl::flat_hash_map<FlightRecordWrapper, std::unique_ptr<ActionContext>>
running_actions_ ABSL_GUARDED_BY(actions_mutex_);
absl::flat_hash_set<FlightRecordWrapper> reloading_actions_
ABSL_GUARDED_BY(actions_mutex_);
};
// Represents a single action execution.
class ActionContext {
constexpr static int kMaxStateChangeRetryCount = 4;
public:
// Only allow ActionContext to be created by ActionContextManager.
struct CreationToken {
private:
CreationToken() = default;
friend class ActionContextManager;
};
using Action = ActionContextManager::Action;
explicit ActionContext(
CreationToken token, ActionContextManager& manager,
safepower_agent_proto::StartActionRequest request,
Action action_impl = {},
safepower_agent_proto::ActionStateLog initial_state = NewInitialState());
~ActionContext();
ActionContext(const ActionContext& other) = delete;
ActionContext& operator=(const ActionContext& other) = delete;
// Dumps debug information about this action to the provided stream.
void DumpDebugInfo(std::ostream& os) const ABSL_LOCKS_EXCLUDED(mutex_);
const FlightRecordWrapper& flight_record() const { return flight_record_; }
const safepower_agent_proto::StartActionRequest& request() const {
return request_;
}
std::shared_ptr<StateUpdater<safepower_agent_proto::ActionStateLog>>
action_state_updater() const {
return action_state_updater_;
}
absl::Status Activate();
static bool IsFinalState(safepower_agent_proto::ActionState state) {
return state == safepower_agent_proto::ACTION_STATE_SUCCESS ||
state == safepower_agent_proto::ACTION_STATE_ERROR ||
state == safepower_agent_proto::ACTION_STATE_ABORTED;
}
private:
std::optional<absl::Status> CheckValidation(
const safepower_agent_proto::SystemState& state);
absl::Status EnterStateInit() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
absl::Status EnterStateRunningAction() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
absl::Status EnterStateCheckingPrecondition()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
absl::Status EnterStateValidatingFinalState()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void NextStateInit() ABSL_LOCKS_EXCLUDED(mutex_);
void NextStatePreconditionMatched(absl::Status status,
Condition::MatchList matches)
ABSL_LOCKS_EXCLUDED(mutex_);
void NextStateActionRan(absl::Status status) ABSL_LOCKS_EXCLUDED(mutex_);
void NextStateValidationCompleted(absl::Status status,
Condition::MatchList matches)
ABSL_LOCKS_EXCLUDED(mutex_);
void RunAction() ABSL_LOCKS_EXCLUDED(mutex_);
absl::Status StartCheckingCondition(
Condition& condition, absl::string_view condition_id,
absl::AnyInvocable<void(absl::Status, Condition::MatchList)> callback)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
std::string execution_task_name() const {
// The execution task name must be unique for each action.
// As the flight name and step can be any characters, we add a hash to
// ensure that the name is unique.
uint64_t hash = absl::HashOf(flight_record());
return absl::StrCat(flight_record().flight_name(), "-",
flight_record().step_id(), "-", hash, ".execution");
}
std::string init_task_name() const {
return absl::StrCat(execution_task_name(), ".init");
}
std::string response_task_name() const {
return absl::StrCat(execution_task_name(), ".response");
}
std::string final_state_task_name() const {
return absl::StrCat(execution_task_name(), ".final");
}
void SetState(safepower_agent_proto::ActionState new_state,
safepower_agent_proto::ActionStateChange change_info = {})
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void Finish(Action action_impl);
mutable absl::Mutex mutex_ ABSL_ACQUIRED_AFTER(manager_.actions_mutex());
ActionContextManager& manager_;
const safepower_agent_proto::StartActionRequest request_;
const FlightRecordWrapper flight_record_;
Action action_impl_ ABSL_GUARDED_BY(mutex_);
std::optional<Condition> precondition_;
std::optional<Condition> validation_;
std::unique_ptr<KeepAliveListener> keep_alive_listener_
ABSL_GUARDED_BY(mutex_);
std::shared_ptr<StateUpdater<safepower_agent_proto::ActionStateLog>>
action_state_updater_;
static safepower_agent_proto::ActionStateLog NewInitialState();
};
} // namespace safepower_agent
#endif // PRODUCTION_BORG_MGMT_NODE_PROXY_SAFEPOWER_SAFEPOWER_AGENT_ACTION_CONTEXT_H_