blob: dc6b4b9e944453f179a9ba1606af3ba9df9d0b0c [file] [log] [blame]
Tomáš Pecka292bc9c2021-01-11 22:03:11 +01001/*
2 * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@fit.cvut.cz>
5 *
6 */
7
8#include <boost/algorithm/string/predicate.hpp>
9#include <fstream>
Jan Kundrát1c3b8812021-05-17 13:06:03 +020010#include <optional>
Tomáš Peckaf976c5b2021-01-23 21:19:52 +010011#include "IETFSystem.h"
Tomáš Pecka879a6032021-02-03 17:21:48 +010012#include "system_vars.h"
13#include "utils/exec.h"
Tomáš Pecka292bc9c2021-01-11 22:03:11 +010014#include "utils/io.h"
15#include "utils/log.h"
Tomáš Pecka272abaf2021-01-24 12:28:43 +010016#include "utils/sysrepo.h"
Jan Kundrát51f1d6d2021-02-19 00:41:28 +010017#include "utils/time.h"
Tomáš Pecka292bc9c2021-01-11 22:03:11 +010018
19using namespace std::literals;
20
21namespace {
22
23const auto IETF_SYSTEM_MODULE_NAME = "ietf-system"s;
24const auto IETF_SYSTEM_STATE_MODULE_PREFIX = "/"s + IETF_SYSTEM_MODULE_NAME + ":system-state/"s;
Václav Kubernát566c9402021-02-10 08:33:54 +010025const auto IETF_SYSTEM_HOSTNAME_PATH = "/ietf-system:system/hostname";
Jan Kundrát51f1d6d2021-02-19 00:41:28 +010026const auto IETF_SYSTEM_STATE_CLOCK_PATH = "/ietf-system:system-state/clock";
Tomáš Pecka292bc9c2021-01-11 22:03:11 +010027
28/** @brief Returns key=value pairs from (e.g. /etc/os-release) as a std::map */
29std::map<std::string, std::string> parseKeyValueFile(const std::filesystem::path& path)
30{
31 std::map<std::string, std::string> res;
32 std::ifstream ifs(path);
33 if (!ifs.is_open())
34 throw std::invalid_argument("File '" + std::string(path) + "' not found.");
35
36 std::string line;
37 while (std::getline(ifs, line)) {
38 // man os-release: Lines beginning with "#" shall be ignored as comments. Blank lines are permitted and ignored.
39 if (line.empty() || boost::algorithm::starts_with(line, "#")) {
40 continue;
41 }
42
43 size_t equalSignPos = line.find_first_of('=');
44 if (equalSignPos != std::string::npos) {
45 std::string key = line.substr(0, equalSignPos);
46 std::string val = line.substr(equalSignPos + 1);
47
48 // remove quotes from value
49 if (val.length() >= 2 && val.front() == '"' && val.front() == val.back()) {
50 val = val.substr(1, val.length() - 2);
51 }
52
53 res[key] = val;
54 } else { // when there is no = sign, treat the value as empty string
55 res[line] = "";
56 }
57 }
58
59 return res;
60}
61
Václav Kubernát566c9402021-02-10 08:33:54 +010062std::optional<std::string> getHostnameFromChange(const std::shared_ptr<sysrepo::Session> session)
63{
64 std::optional<std::string> res;
65
66 auto data = session->get_data(IETF_SYSTEM_HOSTNAME_PATH);
67 if (data) {
68 auto hostnameNode = data->find_path(IETF_SYSTEM_HOSTNAME_PATH)->data().front();
69 auto leaf = std::make_shared<libyang::Data_Node_Leaf_List>(hostnameNode);
70 res = leaf->value_str();
71 }
72
73 return res;
74}
Tomáš Pecka292bc9c2021-01-11 22:03:11 +010075}
76
77namespace velia::system {
78
Václav Kubernátd2927cc2021-02-18 04:36:26 +010079void IETFSystem::initStaticProperties(const std::filesystem::path& osRelease)
Tomáš Pecka292bc9c2021-01-11 22:03:11 +010080{
Václav Kubernát566c9402021-02-10 08:33:54 +010081 utils::ensureModuleImplemented(m_srSession, IETF_SYSTEM_MODULE_NAME, "2014-08-06");
82
Tomáš Pecka292bc9c2021-01-11 22:03:11 +010083 std::map<std::string, std::string> osReleaseContents = parseKeyValueFile(osRelease);
84
85 std::map<std::string, std::string> opsSystemStateData {
86 {IETF_SYSTEM_STATE_MODULE_PREFIX + "platform/os-name", osReleaseContents.at("NAME")},
87 {IETF_SYSTEM_STATE_MODULE_PREFIX + "platform/os-release", osReleaseContents.at("VERSION")},
88 {IETF_SYSTEM_STATE_MODULE_PREFIX + "platform/os-version", osReleaseContents.at("VERSION")},
89 };
90
Tomáš Pecka498e91c2021-03-02 17:46:47 +010091 utils::valuesPush(opsSystemStateData, {}, m_srSession, SR_DS_OPERATIONAL);
Václav Kubernátd2927cc2021-02-18 04:36:26 +010092}
Tomáš Pecka879a6032021-02-03 17:21:48 +010093
Václav Kubernátd2927cc2021-02-18 04:36:26 +010094void IETFSystem::initSystemRestart()
95{
Tomáš Pecka879a6032021-02-03 17:21:48 +010096 m_srSubscribe->rpc_subscribe(
97 ("/" + IETF_SYSTEM_MODULE_NAME + ":system-restart").c_str(),
98 [this](::sysrepo::S_Session session, [[maybe_unused]] const char* op_path, [[maybe_unused]] const ::sysrepo::S_Vals input, [[maybe_unused]] sr_event_t event, [[maybe_unused]] uint32_t request_id, [[maybe_unused]] ::sysrepo::S_Vals_Holder output) {
99 try {
100 velia::utils::execAndWait(m_log, SYSTEMCTL_EXECUTABLE, {"reboot"}, "", {});
101 } catch(const std::runtime_error& e) {
102 session->set_error("Reboot procedure failed.", nullptr);
103 return SR_ERR_OPERATION_FAILED;
104 }
105
106 return SR_ERR_OK;
107 },
108 0,
109 SR_SUBSCR_CTX_REUSE);
Tomáš Pecka292bc9c2021-01-11 22:03:11 +0100110}
Václav Kubernátd2927cc2021-02-18 04:36:26 +0100111
Václav Kubernát566c9402021-02-10 08:33:54 +0100112void IETFSystem::initHostname()
113{
114 sysrepo::ModuleChangeCb hostNameCbRunning = [this] (auto session, auto, auto, auto, auto) {
115 if (auto newHostname = getHostnameFromChange(session)) {
116 velia::utils::execAndWait(m_log, HOSTNAMECTL_EXECUTABLE, {"set-hostname", *newHostname}, "");
117 }
118 return SR_ERR_OK;
119 };
120
121 sysrepo::ModuleChangeCb hostNameCbStartup = [] (auto session, auto, auto, auto, auto) {
122 if (auto newHostname = getHostnameFromChange(session)) {
123 utils::safeWriteFile(BACKUP_ETC_HOSTNAME_FILE, *newHostname);
124 }
125 return SR_ERR_OK;
126 };
127
128 sysrepo::OperGetItemsCb hostNameCbOperational = [] (auto session, auto, auto, auto, auto, auto& parent) {
129 // + 1 for null-terminating byte, HOST_NAME_MAX doesn't count that
Václav Kubernáte407f742021-05-18 10:47:13 +0200130 std::array<char, HOST_NAME_MAX + 1> buffer{};
Václav Kubernát566c9402021-02-10 08:33:54 +0100131
132 if (gethostname(buffer.data(), buffer.size()) != 0) {
133 throw std::system_error(errno, std::system_category(), "gethostname() failed");
134 }
135
136 parent->new_path(
137 session->get_context(),
138 IETF_SYSTEM_HOSTNAME_PATH,
139 buffer.data(),
140 LYD_ANYDATA_CONSTSTRING,
141 0);
142
143 return SR_ERR_OK;
144 };
145
146 m_srSubscribe->module_change_subscribe(IETF_SYSTEM_MODULE_NAME.c_str(), hostNameCbRunning, IETF_SYSTEM_HOSTNAME_PATH, 0, SR_SUBSCR_DONE_ONLY);
147 m_srSession->session_switch_ds(SR_DS_STARTUP);
148 m_srSubscribe->module_change_subscribe(IETF_SYSTEM_MODULE_NAME.c_str(), hostNameCbStartup, IETF_SYSTEM_HOSTNAME_PATH, 0, SR_SUBSCR_DONE_ONLY);
149 m_srSession->session_switch_ds(SR_DS_OPERATIONAL);
150 m_srSubscribe->oper_get_items_subscribe(IETF_SYSTEM_MODULE_NAME.c_str(), hostNameCbOperational, IETF_SYSTEM_HOSTNAME_PATH);
151}
152
Jan Kundrátdcd50f02021-02-18 23:28:26 +0100153/** @short Acknowledge writes to dummy fields so that they're visible in the operational DS */
154void IETFSystem::initDummies()
155{
156 m_srSession->session_switch_ds(SR_DS_RUNNING);
157 auto ignore = [] (auto, auto, auto, auto, auto) {
158 return SR_ERR_OK;
159 };
160 for (const auto xpath : {"/ietf-system:system/location", "/ietf-system:system/contact"}) {
161 m_srSubscribe->module_change_subscribe(IETF_SYSTEM_MODULE_NAME.c_str(), ignore, xpath, 0, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_DONE_ONLY);
162 }
163}
164
Jan Kundrát51f1d6d2021-02-19 00:41:28 +0100165/** @short Time and clock callbacks */
166void IETFSystem::initClock()
167{
168 m_srSubscribe->oper_get_items_subscribe(IETF_SYSTEM_MODULE_NAME.c_str(),
Jan Kundrátf333b132021-04-27 15:40:41 +0200169 [] (auto session, auto, auto, auto, auto, auto& parent) {
Jan Kundrát51f1d6d2021-02-19 00:41:28 +0100170 parent->new_path(session->get_context(),
171 (IETF_SYSTEM_STATE_CLOCK_PATH + "/current-datetime"s).c_str(),
172 utils::yangTimeFormat(std::chrono::system_clock::now()).c_str(),
173 LYD_ANYDATA_CONSTSTRING,
174 0);
175 return SR_ERR_OK;
176 },
177 IETF_SYSTEM_STATE_CLOCK_PATH,
178 SR_SUBSCR_OPER_MERGE);
179}
180
Václav Kubernát372d2772021-02-18 04:42:16 +0100181/** This class handles multiple system properties and publishes them via the ietf-system model:
182 * - OS-identification data from osRelease file
183 * - Rebooting
Václav Kubernát566c9402021-02-10 08:33:54 +0100184 * - Hostname
Václav Kubernát372d2772021-02-18 04:42:16 +0100185 */
Václav Kubernátd2927cc2021-02-18 04:36:26 +0100186IETFSystem::IETFSystem(std::shared_ptr<::sysrepo::Session> srSession, const std::filesystem::path& osRelease)
187 : m_srSession(std::move(srSession))
188 , m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession))
189 , m_log(spdlog::get("system"))
190{
191 initStaticProperties(osRelease);
192 initSystemRestart();
Václav Kubernát566c9402021-02-10 08:33:54 +0100193 initHostname();
Jan Kundrátdcd50f02021-02-18 23:28:26 +0100194 initDummies();
Jan Kundrát51f1d6d2021-02-19 00:41:28 +0100195 initClock();
Václav Kubernátd2927cc2021-02-18 04:36:26 +0100196}
Tomáš Pecka292bc9c2021-01-11 22:03:11 +0100197}