blob: 9be4581964351570376d4343b81036094051e0c7 [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"
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053017#include <boost/algorithm/string/replace.hpp>
18#include <sdbusplus/bus/match.hpp>
19#include <filesystem>
20#include <cereal/archives/json.hpp>
21#include <cereal/types/tuple.hpp>
22#include <cereal/types/unordered_map.hpp>
23#include <fstream>
AppaRao Puli00840472018-10-03 19:37:46 +053024
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +053025std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
26std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
AppaRao Puli00840472018-10-03 19:37:46 +053027std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
28 srvMgrObjects;
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +053029static bool unitQueryStarted = false;
AppaRao Puli00840472018-10-03 19:37:46 +053030
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053031static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
32
33// Base service name list. All instance of these services and
34// units(service/socket) will be managed by this daemon.
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +053035static std::array<std::string, 5> serviceNames = {
36 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", "start-ipkvm",
37 "obmc-console"};
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +053038
39using ListUnitsType =
40 std::tuple<std::string, std::string, std::string, std::string, std::string,
41 std::string, sdbusplus::message::object_path, uint32_t,
42 std::string, sdbusplus::message::object_path>;
43
44enum class ListUnitElements
45{
46 name,
47 descriptionString,
48 loadState,
49 activeState,
50 subState,
51 followedUnit,
52 objectPath,
53 queuedJobType,
54 jobType,
55 jobObject
56};
57
58enum class UnitType
59{
60 service,
61 socket,
62 target,
63 device,
64 invalid
65};
66
67using MonitorListMap =
68 std::unordered_map<std::string, std::tuple<std::string, std::string,
69 std::string, std::string>>;
70MonitorListMap unitsToMonitor;
71
72enum class monitorElement
73{
74 unitName,
75 instanceName,
76 serviceObjPath,
77 socketObjPath
78};
79
80std::tuple<std::string, UnitType, std::string>
81 getUnitNameTypeAndInstance(const std::string& fullUnitName)
82{
83 UnitType type = UnitType::invalid;
84 std::string instanceName;
85 std::string unitName;
86 // get service type
87 auto typePos = fullUnitName.rfind(".");
88 if (typePos != std::string::npos)
89 {
90 const auto& typeStr = fullUnitName.substr(typePos + 1);
91 // Ignore types other than service and socket
92 if (typeStr == "service")
93 {
94 type = UnitType::service;
95 }
96 else if (typeStr == "socket")
97 {
98 type = UnitType::socket;
99 }
100 // get instance name if available
101 auto instancePos = fullUnitName.rfind("@");
102 if (instancePos != std::string::npos)
103 {
104 instanceName =
105 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
106 unitName = fullUnitName.substr(0, instancePos);
107 }
108 else
109 {
110 unitName = fullUnitName.substr(0, typePos);
111 }
112 }
113 return std::make_tuple(unitName, type, instanceName);
114}
115
116static inline void
117 handleListUnitsResponse(sdbusplus::asio::object_server& server,
118 std::shared_ptr<sdbusplus::asio::connection>& conn,
119 boost::system::error_code ec,
120 const std::vector<ListUnitsType>& listUnits)
121{
122 // Loop through all units, and mark all units, which has to be
123 // managed, irrespective of instance name.
124 for (const auto& unit : listUnits)
125 {
126 const auto& fullUnitName =
127 std::get<static_cast<int>(ListUnitElements::name)>(unit);
128 auto [unitName, type, instanceName] =
129 getUnitNameTypeAndInstance(fullUnitName);
130 if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=
131 serviceNames.end())
132 {
133 std::string instantiatedUnitName =
134 unitName + addInstanceName(instanceName, "_40");
135 boost::replace_all(instantiatedUnitName, "-", "_2d");
136 const sdbusplus::message::object_path& objectPath =
137 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
138 // Group the service & socket units togther.. Same services
139 // are managed together.
140 auto it = unitsToMonitor.find(instantiatedUnitName);
141 if (it != unitsToMonitor.end())
142 {
143 auto& value = it->second;
144 if (type == UnitType::service)
145 {
146 std::get<static_cast<int>(monitorElement::unitName)>(
147 value) = unitName;
148 std::get<static_cast<int>(monitorElement::instanceName)>(
149 value) = instanceName;
150 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
151 value) = objectPath;
152 }
153 else if (type == UnitType::socket)
154 {
155 std::get<static_cast<int>(monitorElement::socketObjPath)>(
156 value) = objectPath;
157 }
158 }
159 if (type == UnitType::service)
160 {
161 unitsToMonitor.emplace(instantiatedUnitName,
162 std::make_tuple(unitName, instanceName,
163 objectPath.str, ""));
164 }
165 else if (type == UnitType::socket)
166 {
167 unitsToMonitor.emplace(
168 instantiatedUnitName,
169 std::make_tuple("", "", "", objectPath.str));
170 }
171 }
172 }
173
174 bool updateRequired = false;
175 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
176 if (jsonExist)
177 {
178 std::ifstream file(srvCfgMgrFile);
179 cereal::JSONInputArchive archive(file);
180 MonitorListMap savedMonitorList;
181 archive(savedMonitorList);
182
183 // compare the unit list read from systemd1 and the save list.
184 MonitorListMap diffMap;
185 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
186 begin(savedMonitorList), end(savedMonitorList),
187 std::inserter(diffMap, begin(diffMap)));
188 for (auto& unitIt : diffMap)
189 {
190 auto it = savedMonitorList.find(unitIt.first);
191 if (it == savedMonitorList.end())
192 {
193 savedMonitorList.insert(unitIt);
194 updateRequired = true;
195 }
196 }
197 unitsToMonitor = savedMonitorList;
198 }
199 if (!jsonExist || updateRequired)
200 {
201 std::ofstream file(srvCfgMgrFile);
202 cereal::JSONOutputArchive archive(file);
203 archive(CEREAL_NVP(unitsToMonitor));
204 }
205
206 // create objects for needed services
207 for (auto& it : unitsToMonitor)
208 {
209 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
210 "/" + it.first);
211 std::string instanciatedUnitName =
212 std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
213 addInstanceName(
214 std::get<static_cast<int>(monitorElement::instanceName)>(
215 it.second),
216 "@");
217 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
218 server, conn, objPath,
219 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
220 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
221 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
222 it.second),
223 std::get<static_cast<int>(monitorElement::socketObjPath)>(
224 it.second));
225 srvMgrObjects.emplace(
226 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
227 }
228}
229
230void init(sdbusplus::asio::object_server& server,
231 std::shared_ptr<sdbusplus::asio::connection>& conn)
232{
233 // Go through all systemd units, and dynamically detect and manage
234 // the service daemons
235 conn->async_method_call(
236 [&server, &conn](boost::system::error_code ec,
237 const std::vector<ListUnitsType>& listUnits) {
238 if (ec)
239 {
240 phosphor::logging::log<phosphor::logging::level::ERR>(
241 "async_method_call error: ListUnits failed");
242 return;
243 }
244 handleListUnitsResponse(server, conn, ec, listUnits);
245 },
246 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
247}
248
249void checkAndInit(sdbusplus::asio::object_server& server,
250 std::shared_ptr<sdbusplus::asio::connection>& conn)
251{
252 // Check whether systemd completed all the loading before initializing
253 conn->async_method_call(
254 [&server, &conn](boost::system::error_code ec,
255 const std::variant<uint64_t>& value) {
256 if (ec)
257 {
258 phosphor::logging::log<phosphor::logging::level::ERR>(
259 "async_method_call error: ListUnits failed");
260 return;
261 }
262 if (std::get<uint64_t>(value))
263 {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530264 if (!unitQueryStarted)
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530265 {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530266 unitQueryStarted = true;
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530267 init(server, conn);
268 }
269 }
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530270 else
271 {
272 // FIX-ME: Latest up-stream sync caused issue in receiving
273 // StartupFinished signal. Unable to get StartupFinished signal
274 // from systemd1 hence using poll method too, to trigger it
275 // properly.
276 constexpr size_t pollTimeout = 10; // seconds
277 initTimer->expires_after(std::chrono::seconds(pollTimeout));
278 initTimer->async_wait([&server, &conn](
279 const boost::system::error_code& ec) {
280 if (ec == boost::asio::error::operation_aborted)
281 {
282 // Timer reset.
283 return;
284 }
285 if (ec)
286 {
287 phosphor::logging::log<phosphor::logging::level::ERR>(
288 "service config mgr - init - async wait error.");
289 return;
290 }
291 checkAndInit(server, conn);
292 });
293 }
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530294 },
295 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
296 "FinishTimestamp");
297}
AppaRao Puli00840472018-10-03 19:37:46 +0530298
299int main()
300{
AppaRao Puli00840472018-10-03 19:37:46 +0530301 boost::asio::io_service io;
302 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530303 timer = std::make_unique<boost::asio::steady_timer>(io);
304 initTimer = std::make_unique<boost::asio::steady_timer>(io);
AppaRao Puli00840472018-10-03 19:37:46 +0530305 conn->request_name(phosphor::service::serviceConfigSrvName);
Richard Marian Thomaiyar4ae87cd2019-01-28 20:55:37 +0530306 auto server = sdbusplus::asio::object_server(conn, true);
307 auto mgrInterface =
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530308 server.add_interface(phosphor::service::srcCfgMgrBasePath, "");
Richard Marian Thomaiyar4ae87cd2019-01-28 20:55:37 +0530309 mgrInterface->initialize();
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530310 server.add_manager(phosphor::service::srcCfgMgrBasePath);
311 // Initialize the objects after systemd indicated startup finished.
312 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
313 static_cast<sdbusplus::bus::bus&>(*conn),
314 "type='signal',"
315 "member='StartupFinished',path='/org/freedesktop/systemd1',"
316 "interface='org.freedesktop.systemd1.Manager'",
317 [&server, &conn](sdbusplus::message::message& msg) {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530318 if (!unitQueryStarted)
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530319 {
Richard Marian Thomaiyar33816cf2019-06-14 13:54:50 +0530320 unitQueryStarted = true;
Richard Marian Thomaiyar90f2da32019-05-23 05:37:50 +0530321 init(server, conn);
322 }
323 });
324 // this will make sure to initialize the objects, when daemon is
325 // restarted.
326 checkAndInit(server, conn);
327
AppaRao Puli00840472018-10-03 19:37:46 +0530328 io.run();
329
330 return 0;
331}