Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 1 | /* |
| 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át | 1c3b881 | 2021-05-17 13:06:03 +0200 | [diff] [blame^] | 10 | #include <optional> |
Tomáš Pecka | f976c5b | 2021-01-23 21:19:52 +0100 | [diff] [blame] | 11 | #include "IETFSystem.h" |
Tomáš Pecka | 879a603 | 2021-02-03 17:21:48 +0100 | [diff] [blame] | 12 | #include "system_vars.h" |
| 13 | #include "utils/exec.h" |
Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 14 | #include "utils/io.h" |
| 15 | #include "utils/log.h" |
Tomáš Pecka | 272abaf | 2021-01-24 12:28:43 +0100 | [diff] [blame] | 16 | #include "utils/sysrepo.h" |
Jan Kundrát | 51f1d6d | 2021-02-19 00:41:28 +0100 | [diff] [blame] | 17 | #include "utils/time.h" |
Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 18 | |
| 19 | using namespace std::literals; |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | const auto IETF_SYSTEM_MODULE_NAME = "ietf-system"s; |
| 24 | const auto IETF_SYSTEM_STATE_MODULE_PREFIX = "/"s + IETF_SYSTEM_MODULE_NAME + ":system-state/"s; |
Václav Kubernát | 566c940 | 2021-02-10 08:33:54 +0100 | [diff] [blame] | 25 | const auto IETF_SYSTEM_HOSTNAME_PATH = "/ietf-system:system/hostname"; |
Jan Kundrát | 51f1d6d | 2021-02-19 00:41:28 +0100 | [diff] [blame] | 26 | const auto IETF_SYSTEM_STATE_CLOCK_PATH = "/ietf-system:system-state/clock"; |
Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 27 | |
| 28 | /** @brief Returns key=value pairs from (e.g. /etc/os-release) as a std::map */ |
| 29 | std::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át | 566c940 | 2021-02-10 08:33:54 +0100 | [diff] [blame] | 62 | std::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áš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 75 | } |
| 76 | |
| 77 | namespace velia::system { |
| 78 | |
Václav Kubernát | d2927cc | 2021-02-18 04:36:26 +0100 | [diff] [blame] | 79 | void IETFSystem::initStaticProperties(const std::filesystem::path& osRelease) |
Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 80 | { |
Václav Kubernát | 566c940 | 2021-02-10 08:33:54 +0100 | [diff] [blame] | 81 | utils::ensureModuleImplemented(m_srSession, IETF_SYSTEM_MODULE_NAME, "2014-08-06"); |
| 82 | |
Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 83 | 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áš Pecka | 498e91c | 2021-03-02 17:46:47 +0100 | [diff] [blame] | 91 | utils::valuesPush(opsSystemStateData, {}, m_srSession, SR_DS_OPERATIONAL); |
Václav Kubernát | d2927cc | 2021-02-18 04:36:26 +0100 | [diff] [blame] | 92 | } |
Tomáš Pecka | 879a603 | 2021-02-03 17:21:48 +0100 | [diff] [blame] | 93 | |
Václav Kubernát | d2927cc | 2021-02-18 04:36:26 +0100 | [diff] [blame] | 94 | void IETFSystem::initSystemRestart() |
| 95 | { |
Tomáš Pecka | 879a603 | 2021-02-03 17:21:48 +0100 | [diff] [blame] | 96 | 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áš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 110 | } |
Václav Kubernát | d2927cc | 2021-02-18 04:36:26 +0100 | [diff] [blame] | 111 | |
Václav Kubernát | 566c940 | 2021-02-10 08:33:54 +0100 | [diff] [blame] | 112 | void 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 |
| 130 | std::array<char, HOST_NAME_MAX + 1> buffer; |
| 131 | |
| 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át | dcd50f0 | 2021-02-18 23:28:26 +0100 | [diff] [blame] | 153 | /** @short Acknowledge writes to dummy fields so that they're visible in the operational DS */ |
| 154 | void 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át | 51f1d6d | 2021-02-19 00:41:28 +0100 | [diff] [blame] | 165 | /** @short Time and clock callbacks */ |
| 166 | void IETFSystem::initClock() |
| 167 | { |
| 168 | m_srSubscribe->oper_get_items_subscribe(IETF_SYSTEM_MODULE_NAME.c_str(), |
Jan Kundrát | f333b13 | 2021-04-27 15:40:41 +0200 | [diff] [blame] | 169 | [] (auto session, auto, auto, auto, auto, auto& parent) { |
Jan Kundrát | 51f1d6d | 2021-02-19 00:41:28 +0100 | [diff] [blame] | 170 | 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át | 372d277 | 2021-02-18 04:42:16 +0100 | [diff] [blame] | 181 | /** 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át | 566c940 | 2021-02-10 08:33:54 +0100 | [diff] [blame] | 184 | * - Hostname |
Václav Kubernát | 372d277 | 2021-02-18 04:42:16 +0100 | [diff] [blame] | 185 | */ |
Václav Kubernát | d2927cc | 2021-02-18 04:36:26 +0100 | [diff] [blame] | 186 | IETFSystem::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át | 566c940 | 2021-02-10 08:33:54 +0100 | [diff] [blame] | 193 | initHostname(); |
Jan Kundrát | dcd50f0 | 2021-02-18 23:28:26 +0100 | [diff] [blame] | 194 | initDummies(); |
Jan Kundrát | 51f1d6d | 2021-02-19 00:41:28 +0100 | [diff] [blame] | 195 | initClock(); |
Václav Kubernát | d2927cc | 2021-02-18 04:36:26 +0100 | [diff] [blame] | 196 | } |
Tomáš Pecka | 292bc9c | 2021-01-11 22:03:11 +0100 | [diff] [blame] | 197 | } |