blob: f8d1f22ac36441a3e824c1ea32a54044af325a8a [file] [log] [blame]
Vernon Maueryba2c0832020-07-15 10:02:38 -07001/*
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"
17
18#include <boost/algorithm/string/replace.hpp>
19#include <cereal/archives/json.hpp>
20#include <cereal/types/tuple.hpp>
21#include <cereal/types/unordered_map.hpp>
22#include <sdbusplus/bus/match.hpp>
23
24#include <filesystem>
25#include <fstream>
26
27std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
28std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
29std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
30 srvMgrObjects;
31static bool unitQueryStarted = false;
32
33static 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.
37static std::array<std::string, 5> serviceNames = {
38 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", "start-ipkvm",
39 "obmc-console"};
40
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,
121 boost::system::error_code /*ec*/,
122 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 {
266 if (!unitQueryStarted)
267 {
268 unitQueryStarted = true;
269 init(server, conn);
270 }
271 }
272 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 }
296 },
297 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
298 "FinishTimestamp");
299}
300
301int main()
302{
303 boost::asio::io_service io;
304 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
305 timer = std::make_unique<boost::asio::steady_timer>(io);
306 initTimer = std::make_unique<boost::asio::steady_timer>(io);
307 conn->request_name(phosphor::service::serviceConfigSrvName);
308 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700309 server.add_manager(phosphor::service::srcCfgMgrBasePath);
310 // Initialize the objects after systemd indicated startup finished.
311 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
312 static_cast<sdbusplus::bus::bus&>(*conn),
313 "type='signal',"
314 "member='StartupFinished',path='/org/freedesktop/systemd1',"
315 "interface='org.freedesktop.systemd1.Manager'",
316 [&server, &conn](sdbusplus::message::message& /*msg*/) {
317 if (!unitQueryStarted)
318 {
319 unitQueryStarted = true;
320 init(server, conn);
321 }
322 });
323 // this will make sure to initialize the objects, when daemon is
324 // restarted.
325 checkAndInit(server, conn);
326
327 io.run();
328
329 return 0;
330}