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/main.cpp b/src/main.cpp
index abbb0b3..cca4899 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -14,35 +14,287 @@
// limitations under the License.
*/
#include "srvcfg_manager.hpp"
+#include <boost/algorithm/string/replace.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <filesystem>
+#include <cereal/archives/json.hpp>
+#include <cereal/types/tuple.hpp>
+#include <cereal/types/unordered_map.hpp>
+#include <fstream>
std::shared_ptr<boost::asio::deadline_timer> timer = nullptr;
std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
srvMgrObjects;
-static std::map<std::string, std::string> serviceList = {
- {"netipmid", "phosphor-ipmi-net"}, {"web", "bmcweb"}, {"ssh", "dropbear"}};
+static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
+
+// Base service name list. All instance of these services and
+// units(service/socket) will be managed by this daemon.
+static std::vector<std::string> serviceNames = {
+ "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", "start-ipkvm"};
+
+using ListUnitsType =
+ std::tuple<std::string, std::string, std::string, std::string, std::string,
+ std::string, sdbusplus::message::object_path, uint32_t,
+ std::string, sdbusplus::message::object_path>;
+
+enum class ListUnitElements
+{
+ name,
+ descriptionString,
+ loadState,
+ activeState,
+ subState,
+ followedUnit,
+ objectPath,
+ queuedJobType,
+ jobType,
+ jobObject
+};
+
+enum class UnitType
+{
+ service,
+ socket,
+ target,
+ device,
+ invalid
+};
+
+using MonitorListMap =
+ std::unordered_map<std::string, std::tuple<std::string, std::string,
+ std::string, std::string>>;
+MonitorListMap unitsToMonitor;
+
+enum class monitorElement
+{
+ unitName,
+ instanceName,
+ serviceObjPath,
+ socketObjPath
+};
+
+std::tuple<std::string, UnitType, std::string>
+ getUnitNameTypeAndInstance(const std::string& fullUnitName)
+{
+ UnitType type = UnitType::invalid;
+ std::string instanceName;
+ std::string unitName;
+ // get service type
+ auto typePos = fullUnitName.rfind(".");
+ if (typePos != std::string::npos)
+ {
+ const auto& typeStr = fullUnitName.substr(typePos + 1);
+ // Ignore types other than service and socket
+ if (typeStr == "service")
+ {
+ type = UnitType::service;
+ }
+ else if (typeStr == "socket")
+ {
+ type = UnitType::socket;
+ }
+ // get instance name if available
+ auto instancePos = fullUnitName.rfind("@");
+ if (instancePos != std::string::npos)
+ {
+ instanceName =
+ fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
+ unitName = fullUnitName.substr(0, instancePos);
+ }
+ else
+ {
+ unitName = fullUnitName.substr(0, typePos);
+ }
+ }
+ return std::make_tuple(unitName, type, instanceName);
+}
+
+static inline void
+ handleListUnitsResponse(sdbusplus::asio::object_server& server,
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ boost::system::error_code ec,
+ const std::vector<ListUnitsType>& listUnits)
+{
+ // Loop through all units, and mark all units, which has to be
+ // managed, irrespective of instance name.
+ for (const auto& unit : listUnits)
+ {
+ const auto& fullUnitName =
+ std::get<static_cast<int>(ListUnitElements::name)>(unit);
+ auto [unitName, type, instanceName] =
+ getUnitNameTypeAndInstance(fullUnitName);
+ if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=
+ serviceNames.end())
+ {
+ std::string instantiatedUnitName =
+ unitName + addInstanceName(instanceName, "_40");
+ boost::replace_all(instantiatedUnitName, "-", "_2d");
+ const sdbusplus::message::object_path& objectPath =
+ std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
+ // Group the service & socket units togther.. Same services
+ // are managed together.
+ auto it = unitsToMonitor.find(instantiatedUnitName);
+ if (it != unitsToMonitor.end())
+ {
+ auto& value = it->second;
+ if (type == UnitType::service)
+ {
+ std::get<static_cast<int>(monitorElement::unitName)>(
+ value) = unitName;
+ std::get<static_cast<int>(monitorElement::instanceName)>(
+ value) = instanceName;
+ std::get<static_cast<int>(monitorElement::serviceObjPath)>(
+ value) = objectPath;
+ }
+ else if (type == UnitType::socket)
+ {
+ std::get<static_cast<int>(monitorElement::socketObjPath)>(
+ value) = objectPath;
+ }
+ }
+ if (type == UnitType::service)
+ {
+ unitsToMonitor.emplace(instantiatedUnitName,
+ std::make_tuple(unitName, instanceName,
+ objectPath.str, ""));
+ }
+ else if (type == UnitType::socket)
+ {
+ unitsToMonitor.emplace(
+ instantiatedUnitName,
+ std::make_tuple("", "", "", objectPath.str));
+ }
+ }
+ }
+
+ bool updateRequired = false;
+ bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
+ if (jsonExist)
+ {
+ std::ifstream file(srvCfgMgrFile);
+ cereal::JSONInputArchive archive(file);
+ MonitorListMap savedMonitorList;
+ archive(savedMonitorList);
+
+ // compare the unit list read from systemd1 and the save list.
+ MonitorListMap diffMap;
+ std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
+ begin(savedMonitorList), end(savedMonitorList),
+ std::inserter(diffMap, begin(diffMap)));
+ for (auto& unitIt : diffMap)
+ {
+ auto it = savedMonitorList.find(unitIt.first);
+ if (it == savedMonitorList.end())
+ {
+ savedMonitorList.insert(unitIt);
+ updateRequired = true;
+ }
+ }
+ unitsToMonitor = savedMonitorList;
+ }
+ if (!jsonExist || updateRequired)
+ {
+ std::ofstream file(srvCfgMgrFile);
+ cereal::JSONOutputArchive archive(file);
+ archive(CEREAL_NVP(unitsToMonitor));
+ }
+
+ // create objects for needed services
+ for (auto& it : unitsToMonitor)
+ {
+ std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
+ "/" + it.first);
+ std::string instanciatedUnitName =
+ std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
+ addInstanceName(
+ std::get<static_cast<int>(monitorElement::instanceName)>(
+ it.second),
+ "@");
+ auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
+ server, conn, objPath,
+ std::get<static_cast<int>(monitorElement::unitName)>(it.second),
+ std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
+ std::get<static_cast<int>(monitorElement::serviceObjPath)>(
+ it.second),
+ std::get<static_cast<int>(monitorElement::socketObjPath)>(
+ it.second));
+ srvMgrObjects.emplace(
+ std::make_pair(std::move(objPath), std::move(srvCfgObj)));
+ }
+}
+
+void init(sdbusplus::asio::object_server& server,
+ std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+ // Go through all systemd units, and dynamically detect and manage
+ // the service daemons
+ conn->async_method_call(
+ [&server, &conn](boost::system::error_code ec,
+ const std::vector<ListUnitsType>& listUnits) {
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "async_method_call error: ListUnits failed");
+ return;
+ }
+ handleListUnitsResponse(server, conn, ec, listUnits);
+ },
+ sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
+}
+
+void checkAndInit(sdbusplus::asio::object_server& server,
+ std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+ // Check whether systemd completed all the loading before initializing
+ conn->async_method_call(
+ [&server, &conn](boost::system::error_code ec,
+ const std::variant<uint64_t>& value) {
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "async_method_call error: ListUnits failed");
+ return;
+ }
+ if (std::get<uint64_t>(value))
+ {
+ if (!srvMgrObjects.size())
+ {
+ init(server, conn);
+ }
+ }
+ },
+ sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
+ "FinishTimestamp");
+}
int main()
{
- // setup connection to dbus
boost::asio::io_service io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
timer = std::make_shared<boost::asio::deadline_timer>(io);
conn->request_name(phosphor::service::serviceConfigSrvName);
auto server = sdbusplus::asio::object_server(conn, true);
auto mgrInterface =
- server.add_interface("/xyz/openbmc_project/control/service", "");
+ server.add_interface(phosphor::service::srcCfgMgrBasePath, "");
mgrInterface->initialize();
- server.add_manager("/xyz/openbmc_project/control/service");
- for (const auto &service : serviceList)
- {
- std::string objPath("/xyz/openbmc_project/control/service/" +
- service.first);
- auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
- server, conn, objPath, service.second);
- srvMgrObjects.emplace(
- std::make_pair(std::move(service.first), std::move(srvCfgObj)));
- }
+ server.add_manager(phosphor::service::srcCfgMgrBasePath);
+ // Initialize the objects after systemd indicated startup finished.
+ auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
+ static_cast<sdbusplus::bus::bus&>(*conn),
+ "type='signal',"
+ "member='StartupFinished',path='/org/freedesktop/systemd1',"
+ "interface='org.freedesktop.systemd1.Manager'",
+ [&server, &conn](sdbusplus::message::message& msg) {
+ if (!srvMgrObjects.size())
+ {
+ init(server, conn);
+ }
+ });
+ // this will make sure to initialize the objects, when daemon is
+ // restarted.
+ checkAndInit(server, conn);
+
io.run();
return 0;