srvcfg: Updated for dynamic service/socket mgmt
Fixed srvcfg crash manager issue, by doing the service &
socket unit file queries dynamically and exposing the objects
to be controlled. This takes care of instanced service &
socket files like phosphor-ipmi-net@eth0 or
phosphor-ipmi-net@eth1 dynamically.
Tested:
1. Verified as per the base service name, all the instanced
service & socket names are dynamically queried and exposed
2. Made sure to list the disabled services thorugh this method
3. Made sure new services listed after fw-update are also
exposed as objects
4. Verfied phosphor-ipmi-net@eth0 port change using ipmitool
-p <newport> option
5. Verified permanent disable of the unit.
6. Verified run time enable / disable of the service.
7. Verified phosphor-ipmi-kcs service permanent & run time
enable / disable
Change-Id: Ib933a2fbff73eae4348a5940d350ae7972db03fb
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
diff --git a/src/srvcfg_manager.cpp b/src/srvcfg_manager.cpp
index 333c001..69217d7 100644
--- a/src/srvcfg_manager.cpp
+++ b/src/srvcfg_manager.cpp
@@ -15,11 +15,13 @@
*/
#include <fstream>
#include <regex>
+#include <boost/asio/spawn.hpp>
#include "srvcfg_manager.hpp"
extern std::shared_ptr<boost::asio::deadline_timer> timer;
extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
srvMgrObjects;
+static bool updateInProgress = false;
namespace phosphor
{
@@ -34,207 +36,231 @@
static constexpr const char *systemdOverrideUnitBasePath =
"/etc/systemd/system/";
-void ServiceConfig::syncWithSystemD1Properties()
+void ServiceConfig::updateSocketProperties(
+ const boost::container::flat_map<std::string, VariantType> &propertyMap)
{
- // Read systemd1 socket/service property and load.
+ auto listenIt = propertyMap.find("Listen");
+ if (listenIt != propertyMap.end())
+ {
+ auto listenVal =
+ std::get<std::vector<std::tuple<std::string, std::string>>>(
+ listenIt->second);
+ if (listenVal.size())
+ {
+ protocol = std::get<0>(listenVal[0]);
+ std::string port = std::get<1>(listenVal[0]);
+ auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1),
+ nullptr, 10);
+ if (tmp > std::numeric_limits<uint16_t>::max())
+ {
+ throw std::out_of_range("Out of range");
+ }
+ portNum = tmp;
+ if (iface && iface->is_initialized())
+ {
+ internalSet = true;
+ iface->set_property(srvCfgPropPort, portNum);
+ internalSet = false;
+ }
+ }
+ }
+}
+
+void ServiceConfig::updateServiceProperties(
+ const boost::container::flat_map<std::string, VariantType> &propertyMap)
+{
+ auto stateIt = propertyMap.find("UnitFileState");
+ if (stateIt != propertyMap.end())
+ {
+ stateValue = std::get<std::string>(stateIt->second);
+ unitEnabledState = unitMaskedState = false;
+ if (stateValue == stateMasked)
+ {
+ unitMaskedState = true;
+ }
+ else if (stateValue == stateEnabled)
+ {
+ unitEnabledState = true;
+ }
+ if (iface && iface->is_initialized())
+ {
+ internalSet = true;
+ iface->set_property(srvCfgPropMasked, unitMaskedState);
+ iface->set_property(srvCfgPropEnabled, unitEnabledState);
+ internalSet = false;
+ }
+ }
+ auto subStateIt = propertyMap.find("SubState");
+ if (subStateIt != propertyMap.end())
+ {
+ subStateValue = std::get<std::string>(subStateIt->second);
+ if (subStateValue == subStateRunning)
+ {
+ unitRunningState = true;
+ }
+ if (iface && iface->is_initialized())
+ {
+ internalSet = true;
+ iface->set_property(srvCfgPropRunning, unitRunningState);
+ internalSet = false;
+ }
+ }
+}
+
+void ServiceConfig::queryAndUpdateProperties()
+{
conn->async_method_call(
[this](boost::system::error_code ec,
- const sdbusplus::message::variant<
- std::vector<std::tuple<std::string, std::string>>> &value) {
+ const boost::container::flat_map<std::string, VariantType>
+ &propertyMap) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
- "async_method_call error: Failed to get property");
+ "async_method_call error: Failed to service unit "
+ "properties");
return;
}
-
try
{
- auto listenVal = sdbusplus::message::variant_ns::get<
- std::vector<std::tuple<std::string, std::string>>>(value);
- protocol = std::get<0>(listenVal[0]);
- std::string port = std::get<1>(listenVal[0]);
- auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1),
- nullptr, 10);
- if (tmp > std::numeric_limits<uint16_t>::max())
+ updateServiceProperties(propertyMap);
+ if (!socketObjectPath.empty())
{
- throw std::out_of_range("Out of range");
+ conn->async_method_call(
+ [this](boost::system::error_code ec,
+ const boost::container::flat_map<
+ std::string, VariantType> &propertyMap) {
+ if (ec)
+ {
+ phosphor::logging::log<
+ phosphor::logging::level::ERR>(
+ "async_method_call error: Failed to get "
+ "all property");
+ return;
+ }
+ try
+ {
+ updateSocketProperties(propertyMap);
+ if (!iface)
+ {
+ registerProperties();
+ }
+ }
+ catch (const std::exception &e)
+ {
+ phosphor::logging::log<
+ phosphor::logging::level::ERR>(
+ "Exception in getting socket properties",
+ phosphor::logging::entry("WHAT=%s",
+ e.what()));
+ return;
+ }
+ },
+ sysdService, socketObjectPath, dBusPropIntf,
+ dBusGetAllMethod, sysdSocketIntf);
}
- portNum = tmp;
+ else if (!iface)
+ {
+ registerProperties();
+ }
}
catch (const std::exception &e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
- "Exception for port number",
+ "Exception in getting socket properties",
phosphor::logging::entry("WHAT=%s", e.what()));
return;
}
- conn->async_method_call(
- [](boost::system::error_code ec) {
- if (ec)
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "async_method_call error: Failed to set property");
- return;
- }
- },
- serviceConfigSrvName, objPath.c_str(),
- "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
- "Port", sdbusplus::message::variant<uint16_t>(portNum));
},
- "org.freedesktop.systemd1", sysDSockObjPath.c_str(),
- "org.freedesktop.DBus.Properties", "Get",
- "org.freedesktop.systemd1.Socket", "Listen");
-
- conn->async_method_call(
- [this](boost::system::error_code ec,
- const sdbusplus::message::variant<std::string> &pValue) {
- if (ec)
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "async_method_call error: Failed to get property");
- return;
- }
-
- channelList.clear();
- std::istringstream stm(
- sdbusplus::message::variant_ns::get<std::string>(pValue));
- std::string token;
- while (std::getline(stm, token, ','))
- {
- channelList.push_back(token);
- }
- conn->async_method_call(
- [](boost::system::error_code ec) {
- if (ec)
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "async_method_call error: Failed to set property");
- return;
- }
- },
- serviceConfigSrvName, objPath.c_str(),
- "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
- "Channel",
- sdbusplus::message::variant<std::vector<std::string>>(
- channelList));
- },
- "org.freedesktop.systemd1", sysDSockObjPath.c_str(),
- "org.freedesktop.DBus.Properties", "Get",
- "org.freedesktop.systemd1.Socket", "BindToDevice");
-
- std::string srvUnitName(sysDUnitName);
- if (srvUnitName == "dropbear")
- {
- // Dropbear service expects template arguments.
- srvUnitName.append("@");
- }
- srvUnitName.append(".service");
- conn->async_method_call(
- [this](boost::system::error_code ec, const std::string &pValue) {
- if (ec)
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "async_method_call error: Failed to get property");
- return;
- }
- if ((pValue == "enabled") || (pValue == "static") ||
- (pValue == "unmasked"))
- {
- stateValue = "enabled";
- }
- else if ((pValue == "disabled") || (pValue == "masked"))
- {
- stateValue = "disabled";
- }
- conn->async_method_call(
- [](boost::system::error_code ec) {
- if (ec)
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "async_method_call error: Failed to set property");
- return;
- }
- },
- serviceConfigSrvName, objPath.c_str(),
- "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
- "State", sdbusplus::message::variant<std::string>(stateValue));
- },
- "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager", "GetUnitFileState", srvUnitName);
-
+ sysdService, serviceObjectPath, dBusPropIntf, dBusGetAllMethod,
+ sysdUnitIntf);
return;
}
+void ServiceConfig::createSocketOverrideConf()
+{
+ if (!socketObjectPath.empty())
+ {
+ std::string socketUnitName(instantiatedUnitName + ".socket");
+ /// Check override socket directory exist, if not create it.
+ std::experimental::filesystem::path ovrUnitFileDir(
+ systemdOverrideUnitBasePath);
+ ovrUnitFileDir += socketUnitName;
+ ovrUnitFileDir += ".d";
+ if (!std::experimental::filesystem::exists(ovrUnitFileDir))
+ {
+ if (!std::experimental::filesystem::create_directories(
+ ovrUnitFileDir))
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Unable to create the directory.",
+ phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
+ phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
+ Common::Error::InternalFailure>();
+ }
+ }
+ overrideConfDir = std::string(ovrUnitFileDir);
+ }
+}
+
ServiceConfig::ServiceConfig(
sdbusplus::asio::object_server &srv_,
- std::shared_ptr<sdbusplus::asio::connection> &conn_, std::string objPath_,
- std::string unitName) :
+ std::shared_ptr<sdbusplus::asio::connection> &conn_,
+ const std::string &objPath_, const std::string &baseUnitName_,
+ const std::string &instanceName_, const std::string &serviceObjPath_,
+ const std::string &socketObjPath_) :
server(srv_),
- conn(conn_), objPath(objPath_), sysDUnitName(unitName)
+ conn(conn_), objPath(objPath_), baseUnitName(baseUnitName_),
+ instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
+ socketObjectPath(socketObjPath_)
{
- std::string socketUnitName(sysDUnitName + ".socket");
- // .socket systemd service files are handled.
- // Regular .service only files are ignored.
- if (!checkSystemdUnitExist(socketUnitName))
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "Unit doesn't exist.",
- phosphor::logging::entry("UNITNAME=%s", socketUnitName.c_str()));
- phosphor::logging::elog<
- sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
- }
-
- /// Check override socket directory exist, if not create it.
- std::experimental::filesystem::path ovrUnitFileDir(
- systemdOverrideUnitBasePath);
- ovrUnitFileDir += socketUnitName;
- ovrUnitFileDir += ".d";
- if (!std::experimental::filesystem::exists(ovrUnitFileDir))
- {
- if (!std::experimental::filesystem::create_directories(ovrUnitFileDir))
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "Unable to create the directory.",
- phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
- phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
- Error::InternalFailure>();
- }
- }
-
- /* Store require info locally */
- unitSocketFilePath = std::string(ovrUnitFileDir);
-
- sysDSockObjPath = systemd1UnitBasePath;
- sysDSockObjPath.append(
- std::regex_replace(sysDUnitName, std::regex("-"), "_2d"));
- sysDSockObjPath.append("_2esocket");
-
- // Adds interface, object and Properties....
- registerProperties();
-
- syncWithSystemD1Properties();
-
+ instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
updatedFlag = 0;
+ queryAndUpdateProperties();
return;
}
-void ServiceConfig::applySystemDServiceConfig()
+std::string ServiceConfig::getSocketUnitName()
{
- if (updatedFlag)
+ return instantiatedUnitName + ".socket";
+}
+
+std::string ServiceConfig::getServiceUnitName()
+{
+ return instantiatedUnitName + ".service";
+}
+
+bool ServiceConfig::isMaskedOut()
+{
+ // return true if state is masked & no request to update the maskedState
+ return (
+ stateValue == "masked" &&
+ !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
+}
+
+void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
+{
+ if (!updatedFlag || isMaskedOut())
{
- // No updates. Just return.
+ // No updates / masked - Just return.
return;
}
-
phosphor::logging::log<phosphor::logging::level::INFO>(
"Applying new settings.",
phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
- if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::channel)) |
- (1 << static_cast<uint8_t>(UpdatedProp::port))))
+ if (subStateValue == "running")
{
+ if (!socketObjectPath.empty())
+ {
+ systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
+ }
+ systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
+ }
+
+ if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
+ {
+ createSocketOverrideConf();
// Create override config file and write data.
- std::string ovrCfgFile{unitSocketFilePath + "/" + overrideConfFileName};
+ std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
std::string tmpFile{ovrCfgFile + "_tmp"};
std::ofstream cfgFile(tmpFile, std::ios::out);
if (!cfgFile.good())
@@ -251,21 +277,6 @@
cfgFile << "Listen" << protocol << "="
<< "\n";
cfgFile << "Listen" << protocol << "=" << portNum << "\n";
- // BindToDevice
- bool firstElement = true;
- cfgFile << "BindToDevice=";
- for (const auto &it : channelList)
- {
- if (firstElement)
- {
- cfgFile << it;
- firstElement = false;
- }
- else
- {
- cfgFile << "," << it;
- }
- }
cfgFile.close();
if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
@@ -278,55 +289,49 @@
}
}
- std::string socketUnitName(sysDUnitName + ".socket");
- std::string srvUnitName(sysDUnitName);
- if (srvUnitName == "dropbear")
+ if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
+ (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
{
- // Dropbear service expects template arguments.
- // Todo: Unit action for service, fails with error
- // "missing the instance name". Needs to implement
- // getting all running instances and use it. This
- // impact runtime but works fine during reboot.
- srvUnitName.append("@");
+ std::vector<std::string> unitFiles;
+ if (socketObjectPath.empty())
+ {
+ unitFiles = {getServiceUnitName()};
+ }
+ else
+ {
+ unitFiles = {getSocketUnitName(), getServiceUnitName()};
+ }
+ systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
+ unitMaskedState, unitEnabledState);
}
- srvUnitName.append(".service");
- // Stop the running service in below scenarios.
- // 1. State changed from "enabled" to "disabled"
- // 2. No change in state and existing stateValue is
- // "enabled" and there is change in other properties.
- if (((stateValue == "disabled") &&
- (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))) ||
- (!(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state))) &&
- (stateValue == "enabled") && (updatedFlag)))
+ return;
+}
+void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
+{
+ if (!updatedFlag || isMaskedOut())
{
- systemdUnitAction(conn, socketUnitName, "StopUnit");
- systemdUnitAction(conn, srvUnitName, "StopUnit");
+ // No updates. Just return.
+ return;
}
- if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))
+ if (unitRunningState)
{
- std::vector<std::string> unitFiles = {socketUnitName, srvUnitName};
- systemdUnitFilesStateChange(conn, unitFiles, stateValue);
- }
-
- // Perform daemon reload to read new settings
- systemdDaemonReload(conn);
-
- if (stateValue == "enabled")
- {
- // Restart the socket
- systemdUnitAction(conn, socketUnitName, "StartUnit");
+ if (!socketObjectPath.empty())
+ {
+ systemdUnitAction(conn, yield, getSocketUnitName(),
+ sysdRestartUnit);
+ }
+ systemdUnitAction(conn, yield, getServiceUnitName(), sysdRestartUnit);
}
// Reset the flag
updatedFlag = 0;
- // All done. Lets reload the properties which are applied on systemd1.
- // TODO: We need to capture the service restart signal and reload data
- // inside the signal handler. So that we can update the service
- // properties modified, outside of this service as well.
- syncWithSystemD1Properties();
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Applied new settings",
+ phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
+ queryAndUpdateProperties();
return;
}
@@ -345,67 +350,144 @@
"async wait error.");
return;
}
- for (auto &srvMgrObj : srvMgrObjects)
- {
- auto &srvObj = srvMgrObj.second;
- if (srvObj->updatedFlag)
- {
- srvObj->applySystemDServiceConfig();
- }
- }
+ updateInProgress = true;
+ boost::asio::spawn(conn->get_io_context(),
+ [this](boost::asio::yield_context yield) {
+ // Stop and apply configuration for all objects
+ for (auto &srvMgrObj : srvMgrObjects)
+ {
+ auto &srvObj = srvMgrObj.second;
+ if (srvObj->updatedFlag)
+ {
+ srvObj->stopAndApplyUnitConfig(yield);
+ }
+ }
+ // Do system reload
+ systemdDaemonReload(conn, yield);
+ // restart unit config.
+ for (auto &srvMgrObj : srvMgrObjects)
+ {
+ auto &srvObj = srvMgrObj.second;
+ if (srvObj->updatedFlag)
+ {
+ srvObj->restartUnitConfig(yield);
+ }
+ }
+ updateInProgress = false;
+ });
});
}
void ServiceConfig::registerProperties()
{
- std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
- server.add_interface(objPath, serviceConfigIntfName);
+ iface = server.add_interface(objPath, serviceConfigIntfName);
+
+ if (!socketObjectPath.empty())
+ {
+ iface->register_property(
+ srvCfgPropPort, portNum,
+ [this](const uint16_t &req, uint16_t &res) {
+ if (!internalSet)
+ {
+ if (req == res)
+ {
+ return 1;
+ }
+ if (updateInProgress)
+ {
+ return 0;
+ }
+ portNum = req;
+ updatedFlag |=
+ (1 << static_cast<uint8_t>(UpdatedProp::port));
+ startServiceRestartTimer();
+ }
+ res = req;
+ return 1;
+ });
+ }
iface->register_property(
- "Port", portNum, [this](const uint16_t &req, uint16_t &res) {
- if (req == res)
+ srvCfgPropMasked, unitMaskedState, [this](const bool &req, bool &res) {
+ if (!internalSet)
{
- return 1;
+ if (req == res)
+ {
+ return 1;
+ }
+ if (updateInProgress)
+ {
+ return 0;
+ }
+ unitMaskedState = req;
+ unitEnabledState = !unitMaskedState;
+ unitRunningState = !unitMaskedState;
+ updatedFlag |=
+ (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
+ (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
+ (1 << static_cast<uint8_t>(UpdatedProp::runningState));
+ internalSet = true;
+ iface->set_property(srvCfgPropEnabled, unitEnabledState);
+ iface->set_property(srvCfgPropRunning, unitRunningState);
+ internalSet = false;
+ startServiceRestartTimer();
}
- portNum = req;
- updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::port));
- startServiceRestartTimer();
res = req;
return 1;
});
iface->register_property(
- "Channel", channelList,
- [this](const std::vector<std::string> &req,
- std::vector<std::string> &res) {
- if (req == res)
+ srvCfgPropEnabled, unitEnabledState,
+ [this](const bool &req, bool &res) {
+ if (!internalSet)
{
- return 1;
+ if (req == res)
+ {
+ return 1;
+ }
+ if (updateInProgress)
+ {
+ return 0;
+ }
+ if (unitMaskedState)
+ { // block updating if masked
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Invalid value specified");
+ return -EINVAL;
+ }
+ unitEnabledState = req;
+ updatedFlag |=
+ (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
+ startServiceRestartTimer();
}
- channelList.clear();
- std::copy(req.begin(), req.end(), back_inserter(channelList));
-
- updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::channel));
- startServiceRestartTimer();
res = req;
return 1;
});
iface->register_property(
- "State", stateValue, [this](const std::string &req, std::string &res) {
- if (req == res)
+ srvCfgPropRunning, unitRunningState,
+ [this](const bool &req, bool &res) {
+ if (!internalSet)
{
- return 1;
+ if (req == res)
+ {
+ return 1;
+ }
+ if (updateInProgress)
+ {
+ return 0;
+ }
+ if (unitMaskedState)
+ { // block updating if masked
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Invalid value specified");
+ return -EINVAL;
+ }
+ unitRunningState = req;
+ updatedFlag |=
+ (1 << static_cast<uint8_t>(UpdatedProp::runningState));
+ startServiceRestartTimer();
}
- if ((req != "enabled") && (req != "disabled"))
- {
- phosphor::logging::log<phosphor::logging::level::ERR>(
- "Invalid value specified");
- return -EINVAL;
- }
- stateValue = req;
- updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::state));
- startServiceRestartTimer();
res = req;
return 1;
});