blob: d15a4b95036504dfd0cf76fc6d78b7e0bb74bd13 [file] [log] [blame]
AppaRao Puli00840472018-10-03 19:37:46 +05301/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#include "srvcfg_manager.hpp"
AppaRao Puliee853eb2020-05-29 00:53:20 +053017
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053018#include <boost/algorithm/string/replace.hpp>
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053019#include <cereal/archives/json.hpp>
20#include <cereal/types/tuple.hpp>
21#include <cereal/types/unordered_map.hpp>
AppaRao Puliee853eb2020-05-29 00:53:20 +053022#include <sdbusplus/bus/match.hpp>
23
24#include <filesystem>
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053025#include <fstream>
AppaRao Puli00840472018-10-03 19:37:46 +053026
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +053027std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
28std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
AppaRao Puli00840472018-10-03 19:37:46 +053029std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
30 srvMgrObjects;
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +053031static bool unitQueryStarted = false;
AppaRao Puli00840472018-10-03 19:37:46 +053032
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053033static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
34
35// Base service name list. All instance of these services and
36// units(service/socket) will be managed by this daemon.
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +053037static std::array<std::string, 5> serviceNames = {
38 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", "start-ipkvm",
39 "obmc-console"};
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053040
41using ListUnitsType =
42 std::tuple<std::string, std::string, std::string, std::string, std::string,
43 std::string, sdbusplus::message::object_path, uint32_t,
44 std::string, sdbusplus::message::object_path>;
45
46enum class ListUnitElements
47{
48 name,
49 descriptionString,
50 loadState,
51 activeState,
52 subState,
53 followedUnit,
54 objectPath,
55 queuedJobType,
56 jobType,
57 jobObject
58};
59
60enum class UnitType
61{
62 service,
63 socket,
64 target,
65 device,
66 invalid
67};
68
69using MonitorListMap =
70 std::unordered_map<std::string, std::tuple<std::string, std::string,
71 std::string, std::string>>;
72MonitorListMap unitsToMonitor;
73
74enum class monitorElement
75{
76 unitName,
77 instanceName,
78 serviceObjPath,
79 socketObjPath
80};
81
82std::tuple<std::string, UnitType, std::string>
83 getUnitNameTypeAndInstance(const std::string& fullUnitName)
84{
85 UnitType type = UnitType::invalid;
86 std::string instanceName;
87 std::string unitName;
88 // get service type
89 auto typePos = fullUnitName.rfind(".");
90 if (typePos != std::string::npos)
91 {
92 const auto& typeStr = fullUnitName.substr(typePos + 1);
93 // Ignore types other than service and socket
94 if (typeStr == "service")
95 {
96 type = UnitType::service;
97 }
98 else if (typeStr == "socket")
99 {
100 type = UnitType::socket;
101 }
102 // get instance name if available
103 auto instancePos = fullUnitName.rfind("@");
104 if (instancePos != std::string::npos)
105 {
106 instanceName =
107 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
108 unitName = fullUnitName.substr(0, instancePos);
109 }
110 else
111 {
112 unitName = fullUnitName.substr(0, typePos);
113 }
114 }
115 return std::make_tuple(unitName, type, instanceName);
116}
117
118static inline void
119 handleListUnitsResponse(sdbusplus::asio::object_server& server,
120 std::shared_ptr<sdbusplus::asio::connection>& conn,
Tom Joseph82e95572020-05-07 20:28:05 +0530121 boost::system::error_code /*ec*/,
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530122 const std::vector<ListUnitsType>& listUnits)
123{
124 // Loop through all units, and mark all units, which has to be
125 // managed, irrespective of instance name.
126 for (const auto& unit : listUnits)
127 {
128 const auto& fullUnitName =
129 std::get<static_cast<int>(ListUnitElements::name)>(unit);
130 auto [unitName, type, instanceName] =
131 getUnitNameTypeAndInstance(fullUnitName);
132 if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=
133 serviceNames.end())
134 {
135 std::string instantiatedUnitName =
136 unitName + addInstanceName(instanceName, "_40");
137 boost::replace_all(instantiatedUnitName, "-", "_2d");
138 const sdbusplus::message::object_path& objectPath =
139 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
140 // Group the service & socket units togther.. Same services
141 // are managed together.
142 auto it = unitsToMonitor.find(instantiatedUnitName);
143 if (it != unitsToMonitor.end())
144 {
145 auto& value = it->second;
146 if (type == UnitType::service)
147 {
148 std::get<static_cast<int>(monitorElement::unitName)>(
149 value) = unitName;
150 std::get<static_cast<int>(monitorElement::instanceName)>(
151 value) = instanceName;
152 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
153 value) = objectPath;
154 }
155 else if (type == UnitType::socket)
156 {
157 std::get<static_cast<int>(monitorElement::socketObjPath)>(
158 value) = objectPath;
159 }
160 }
161 if (type == UnitType::service)
162 {
163 unitsToMonitor.emplace(instantiatedUnitName,
164 std::make_tuple(unitName, instanceName,
165 objectPath.str, ""));
166 }
167 else if (type == UnitType::socket)
168 {
169 unitsToMonitor.emplace(
170 instantiatedUnitName,
171 std::make_tuple("", "", "", objectPath.str));
172 }
173 }
174 }
175
176 bool updateRequired = false;
177 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
178 if (jsonExist)
179 {
180 std::ifstream file(srvCfgMgrFile);
181 cereal::JSONInputArchive archive(file);
182 MonitorListMap savedMonitorList;
183 archive(savedMonitorList);
184
185 // compare the unit list read from systemd1 and the save list.
186 MonitorListMap diffMap;
187 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
188 begin(savedMonitorList), end(savedMonitorList),
189 std::inserter(diffMap, begin(diffMap)));
190 for (auto& unitIt : diffMap)
191 {
192 auto it = savedMonitorList.find(unitIt.first);
193 if (it == savedMonitorList.end())
194 {
195 savedMonitorList.insert(unitIt);
196 updateRequired = true;
197 }
198 }
199 unitsToMonitor = savedMonitorList;
200 }
201 if (!jsonExist || updateRequired)
202 {
203 std::ofstream file(srvCfgMgrFile);
204 cereal::JSONOutputArchive archive(file);
205 archive(CEREAL_NVP(unitsToMonitor));
206 }
207
208 // create objects for needed services
209 for (auto& it : unitsToMonitor)
210 {
211 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
212 "/" + it.first);
213 std::string instanciatedUnitName =
214 std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
215 addInstanceName(
216 std::get<static_cast<int>(monitorElement::instanceName)>(
217 it.second),
218 "@");
219 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
220 server, conn, objPath,
221 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
222 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
223 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
224 it.second),
225 std::get<static_cast<int>(monitorElement::socketObjPath)>(
226 it.second));
227 srvMgrObjects.emplace(
228 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
229 }
230}
231
232void init(sdbusplus::asio::object_server& server,
233 std::shared_ptr<sdbusplus::asio::connection>& conn)
234{
235 // Go through all systemd units, and dynamically detect and manage
236 // the service daemons
237 conn->async_method_call(
238 [&server, &conn](boost::system::error_code ec,
239 const std::vector<ListUnitsType>& listUnits) {
240 if (ec)
241 {
242 phosphor::logging::log<phosphor::logging::level::ERR>(
243 "async_method_call error: ListUnits failed");
244 return;
245 }
246 handleListUnitsResponse(server, conn, ec, listUnits);
247 },
248 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
249}
250
251void checkAndInit(sdbusplus::asio::object_server& server,
252 std::shared_ptr<sdbusplus::asio::connection>& conn)
253{
254 // Check whether systemd completed all the loading before initializing
255 conn->async_method_call(
256 [&server, &conn](boost::system::error_code ec,
257 const std::variant<uint64_t>& value) {
258 if (ec)
259 {
260 phosphor::logging::log<phosphor::logging::level::ERR>(
261 "async_method_call error: ListUnits failed");
262 return;
263 }
264 if (std::get<uint64_t>(value))
265 {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530266 if (!unitQueryStarted)
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530267 {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530268 unitQueryStarted = true;
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530269 init(server, conn);
270 }
271 }
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530272 else
273 {
274 // FIX-ME: Latest up-stream sync caused issue in receiving
275 // StartupFinished signal. Unable to get StartupFinished signal
276 // from systemd1 hence using poll method too, to trigger it
277 // properly.
278 constexpr size_t pollTimeout = 10; // seconds
279 initTimer->expires_after(std::chrono::seconds(pollTimeout));
280 initTimer->async_wait([&server, &conn](
281 const boost::system::error_code& ec) {
282 if (ec == boost::asio::error::operation_aborted)
283 {
284 // Timer reset.
285 return;
286 }
287 if (ec)
288 {
289 phosphor::logging::log<phosphor::logging::level::ERR>(
290 "service config mgr - init - async wait error.");
291 return;
292 }
293 checkAndInit(server, conn);
294 });
295 }
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530296 },
297 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
298 "FinishTimestamp");
299}
AppaRao Puli00840472018-10-03 19:37:46 +0530300
301int main()
302{
AppaRao Puli00840472018-10-03 19:37:46 +0530303 boost::asio::io_service io;
304 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530305 timer = std::make_unique<boost::asio::steady_timer>(io);
306 initTimer = std::make_unique<boost::asio::steady_timer>(io);
AppaRao Puli00840472018-10-03 19:37:46 +0530307 conn->request_name(phosphor::service::serviceConfigSrvName);
Richard Marian Thomaiyar4ae87cd2019-01-28 20:55:37 +0530308 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Mauery130b6bb2020-07-15 10:03:01 -0700309 auto mgrIntf = server.add_interface(phosphor::service::srcCfgMgrBasePath,
310 phosphor::service::srcCfgMgrIntf);
311 mgrIntf->initialize();
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530312 server.add_manager(phosphor::service::srcCfgMgrBasePath);
313 // Initialize the objects after systemd indicated startup finished.
314 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
315 static_cast<sdbusplus::bus::bus&>(*conn),
316 "type='signal',"
317 "member='StartupFinished',path='/org/freedesktop/systemd1',"
318 "interface='org.freedesktop.systemd1.Manager'",
Tom Joseph82e95572020-05-07 20:28:05 +0530319 [&server, &conn](sdbusplus::message::message& /*msg*/) {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530320 if (!unitQueryStarted)
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530321 {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530322 unitQueryStarted = true;
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530323 init(server, conn);
324 }
325 });
326 // this will make sure to initialize the objects, when daemon is
327 // restarted.
328 checkAndInit(server, conn);
329
AppaRao Puli00840472018-10-03 19:37:46 +0530330 io.run();
331
332 return 0;
333}