blob: 5930d8a1752c10e390b5d83d7f08e0ba86d9220d [file] [log] [blame]
#include <iostream>
#include <map>
#include <string>
#include <systemd/sd-bus.h>
#include <sdbusplus/server.hpp>
#include <phosphor-logging/log.hpp>
#include "host_state_manager.hpp"
namespace phosphor
{
namespace state
{
namespace manager
{
// When you see server:: you know we're referencing our base class
namespace server = sdbusplus::xyz::openbmc_project::State::server;
using namespace phosphor::logging;
constexpr auto HOST_STATE_POWEROFF_TGT = "obmc-host-stop@0.target";
constexpr auto HOST_STATE_POWERON_TGT = "obmc-host-start@0.target";
constexpr auto HOST_STATE_QUIESCE_TGT = "obmc-host-quiesce@0.target";
constexpr auto ACTIVE_STATE = "active";
constexpr auto ACTIVATING_STATE = "activating";
/* Map a transition to it's systemd target */
const std::map<server::Host::Transition,std::string> SYSTEMD_TARGET_TABLE =
{
{server::Host::Transition::Off, HOST_STATE_POWEROFF_TGT},
{server::Host::Transition::On, HOST_STATE_POWERON_TGT}
};
constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
constexpr auto SYSTEM_SERVICE = "org.openbmc.managers.System";
constexpr auto SYSTEM_OBJ_PATH = "/org/openbmc/managers/System";
constexpr auto SYSTEM_INTERFACE = SYSTEM_SERVICE;
constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
constexpr auto REBOOTCOUNTER_SERVICE("org.openbmc.Sensors");
constexpr auto REBOOTCOUNTER_PATH("/org/openbmc/sensors/host/BootCount");
constexpr auto REBOOTCOUNTER_INTERFACE("org.openbmc.SensorValue");
constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit";
constexpr auto DEFAULT_BOOTCOUNT = 2;
/* Map a system state to the HostState */
const std::map<std::string, server::Host::HostState> SYS_HOST_STATE_TABLE = {
{"HOST_BOOTING", server::Host::HostState::Running},
{"HOST_POWERED_OFF", server::Host::HostState::Off},
{"HOST_QUIESCED", server::Host::HostState::Quiesced}
};
void Host::subscribeToSystemdSignals()
{
auto method = this->bus.new_method_call(SYSTEMD_SERVICE,
SYSTEMD_OBJ_PATH,
SYSTEMD_INTERFACE,
"Subscribe");
this->bus.call_noreply(method);
return;
}
// TODO - Will be rewritten once sdbusplus client bindings are in place
// and persistent storage design is in place
void Host::determineInitialState()
{
std::string sysState;
auto method = this->bus.new_method_call(SYSTEM_SERVICE,
SYSTEM_OBJ_PATH,
SYSTEM_INTERFACE,
"getSystemState");
auto reply = this->bus.call(method);
reply.read(sysState);
if(sysState == "HOST_BOOTED")
{
log<level::INFO>("Initial Host State will be Running",
entry("CURRENT_HOST_STATE=%s",
convertForMessage(HostState::Running).c_str()));
server::Host::currentHostState(HostState::Running);
server::Host::requestedHostTransition(Transition::On);
}
else
{
log<level::INFO>("Initial Host State will be Off",
entry("CURRENT_HOST_STATE=%s",
convertForMessage(HostState::Off).c_str()));
server::Host::currentHostState(HostState::Off);
server::Host::requestedHostTransition(Transition::Off);
}
// Set transition initially to Off
// TODO - Eventually need to restore this from persistent storage
server::Host::requestedHostTransition(Transition::Off);
return;
}
void Host::executeTransition(Transition tranReq)
{
auto sysdUnit = SYSTEMD_TARGET_TABLE.find(tranReq)->second;
auto method = this->bus.new_method_call(SYSTEMD_SERVICE,
SYSTEMD_OBJ_PATH,
SYSTEMD_INTERFACE,
"StartUnit");
method.append(sysdUnit);
method.append("replace");
this->bus.call_noreply(method);
return;
}
bool Host::stateActive(const std::string& target)
{
sdbusplus::message::variant<std::string> currentState;
sdbusplus::message::object_path unitTargetPath;
auto method = this->bus.new_method_call(SYSTEMD_SERVICE,
SYSTEMD_OBJ_PATH,
SYSTEMD_INTERFACE,
"GetUnit");
method.append(target);
auto result = this->bus.call(method);
//Check that the bus call didn't result in an error
if(result.is_method_error())
{
log<level::ERR>("Error in bus call - could not resolve GetUnit for:",
entry(" %s", SYSTEMD_INTERFACE));
return false;
}
result.read(unitTargetPath);
method = this->bus.new_method_call(SYSTEMD_SERVICE,
static_cast<const std::string&>
(unitTargetPath).c_str(),
SYSTEMD_PROPERTY_IFACE,
"Get");
method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState");
result = this->bus.call(method);
//Check that the bus call didn't result in an error
if(result.is_method_error())
{
log<level::ERR>("Error in bus call - could not resolve Get for:",
entry(" %s", SYSTEMD_PROPERTY_IFACE));
return false;
}
result.read(currentState);
if(currentState != ACTIVE_STATE && currentState != ACTIVATING_STATE)
{
//False - not active
return false;
}
//True - active
return true;
}
void Host::setHostbootCount(int bootCount)
{
auto method = this->bus.new_method_call(REBOOTCOUNTER_SERVICE,
REBOOTCOUNTER_PATH,
REBOOTCOUNTER_INTERFACE,
"setValue");
sdbusplus::message::variant<int> newParam = bootCount;
method.append(newParam);
this->bus.call_noreply(method);
}
bool Host::isAutoReboot()
{
sdbusplus::message::variant<std::string> autoRebootParam;
std::string strParam;
std::string HOST_PATH("/org/openbmc/settings/host0");
std::string HOST_INTERFACE("org.openbmc.settings.Host");
auto mapper = this->bus.new_method_call(MAPPER_BUSNAME,
MAPPER_PATH,
MAPPER_INTERFACE,
"GetObject");
mapper.append(HOST_PATH, std::vector<std::string>({HOST_INTERFACE}));
auto mapperResponseMsg = this->bus.call(mapper);
if (mapperResponseMsg.is_method_error())
{
log<level::ERR>("Error in mapper call");
return false;
}
std::map<std::string, std::vector<std::string>> mapperResponse;
mapperResponseMsg.read(mapperResponse);
if (mapperResponse.empty())
{
log<level::ERR>("Error reading mapper response");
return false;
}
const auto& host = mapperResponse.begin()->first;
auto method = this->bus.new_method_call(host.c_str(),
HOST_PATH.c_str(),
"org.freedesktop.DBus.Properties",
"Get");
method.append(HOST_INTERFACE.c_str(), "auto_reboot");
auto reply = this->bus.call(method);
if (reply.is_method_error())
{
log<level::ERR>("Error in auto_reboot Get");
return false;
}
reply.read(autoRebootParam);
strParam =
sdbusplus::message::variant_ns::get<std::string>(autoRebootParam);
if (strParam.empty())
{
log<level::ERR>("Error reading auto_reboot response");
return false;
}
sdbusplus::message::variant<int> rebootCounterParam = 0;
method = this->bus.new_method_call(REBOOTCOUNTER_SERVICE,
REBOOTCOUNTER_PATH,
REBOOTCOUNTER_INTERFACE,
"getValue");
reply = this->bus.call(method);
if (reply.is_method_error())
{
log<level::ERR>("Error in BOOTCOUNT getValue");
return false;
}
reply.read(rebootCounterParam);
if (strParam == "yes")
{
if( rebootCounterParam > 0)
{
// Reduce BOOTCOUNT by 1
log<level::INFO>("Auto reboot enabled. "
"Reducing HOST BOOTCOUNT by 1.");
Host::setHostbootCount((sdbusplus::message::variant_ns::
get<int>(rebootCounterParam)) - 1);
return true;
}
else if(rebootCounterParam == 0)
{
// Reset reboot counter and go to quiesce state
log<level::INFO>("Auto reboot enabled. "
"HOST BOOTCOUNT already set to 0.");
Host::setHostbootCount(DEFAULT_BOOTCOUNT);
return false;
}
else
{
log<level::INFO>("Auto reboot enabled. "
"HOST BOOTCOUNT has an invalid value.");
return false;
}
}
else
{
log<level::INFO>("Auto reboot disabled.");
return false;
}
}
void Host::sysStateChange(sdbusplus::message::message& msg)
{
uint32_t newStateID {};
sdbusplus::message::object_path newStateObjPath;
std::string newStateUnit{};
std::string newStateResult{};
//Read the msg and populate each variable
msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
if((newStateUnit == HOST_STATE_POWEROFF_TGT) &&
(newStateResult == "done") &&
(!stateActive(HOST_STATE_POWERON_TGT)))
{
log<level::INFO>("Received signal that host is off");
this->currentHostState(server::Host::HostState::Off);
// Check if we need to start a new transition (i.e. a Reboot)
if(this->server::Host::requestedHostTransition() ==
Transition::Reboot)
{
log<level::DEBUG>("Reached intermediate state, going to next");
this->executeTransition(server::Host::Transition::On);
}
}
else if((newStateUnit == HOST_STATE_POWERON_TGT) &&
(newStateResult == "done") &&
(stateActive(HOST_STATE_POWERON_TGT)))
{
log<level::INFO>("Received signal that host is running");
this->currentHostState(server::Host::HostState::Running);
}
else if((newStateUnit == HOST_STATE_QUIESCE_TGT) &&
(newStateResult == "done"))
{
if (Host::isAutoReboot())
{
log<level::INFO>("Beginning reboot...");
Host::requestedHostTransition(server::Host::Transition::Reboot);
}
else
{
log<level::INFO>("Maintaining quiesce");
this->currentHostState(server::Host::HostState::Quiesced);
}
}
}
Host::Transition Host::requestedHostTransition(Transition value)
{
log<level::INFO>(
"Host State transaction request",
entry("REQUESTED_HOST_TRANSITION=%s",
convertForMessage(value).c_str()));
Transition tranReq = value;
if(value == server::Host::Transition::Reboot)
{
// On reboot requests we just need to do a off if we're on and
// vice versa. The handleSysStateChange() code above handles the
// second part of the reboot
if(this->server::Host::currentHostState() ==
server::Host::HostState::Off)
{
tranReq = server::Host::Transition::On;
}
else
{
tranReq = server::Host::Transition::Off;
}
}
executeTransition(tranReq);
return server::Host::requestedHostTransition(value);
}
Host::HostState Host::currentHostState(HostState value)
{
log<level::INFO>("Change to Host State",
entry("CURRENT_HOST_STATE=%s",
convertForMessage(value).c_str()));
return server::Host::currentHostState(value);
}
} // namespace manager
} // namespace state
} // namepsace phosphor