blob: 6634dc6d743a844563717226ca4e47d7039644f7 [file] [log] [blame]
Václav Kubernátc31bd602019-03-07 11:44:48 +01001/*
2 * Copyright (C) 2019 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Václav Kubernát <kubernat@cesnet.cz>
5 *
6*/
7
Václav Kubernátbde37ba2022-03-25 15:18:12 +01008#include <libnetconf2-cpp/netconf-client.hpp>
Václav Kubernát02a71152020-01-21 14:52:51 +01009#include "libyang_utils.hpp"
Václav Kubernátc31bd602019-03-07 11:44:48 +010010#include "netconf_access.hpp"
11#include "utils.hpp"
12#include "yang_schema.hpp"
13
Jan Kundrát8f63fb72020-01-24 01:40:01 +010014
Václav Kubernátc31bd602019-03-07 11:44:48 +010015NetconfAccess::~NetconfAccess() = default;
Václav Kubernátf5d75152020-12-03 03:52:34 +010016namespace {
17auto targetToDs_get(const DatastoreTarget target)
18{
19 switch (target) {
20 case DatastoreTarget::Operational:
21 return libnetconf::NmdaDatastore::Operational;
22 case DatastoreTarget::Running:
23 return libnetconf::NmdaDatastore::Running;
24 case DatastoreTarget::Startup:
25 return libnetconf::NmdaDatastore::Startup;
26 }
Václav Kubernátc31bd602019-03-07 11:44:48 +010027
Václav Kubernátf5d75152020-12-03 03:52:34 +010028 __builtin_unreachable();
29}
30
31auto targetToDs_set(const DatastoreTarget target)
32{
33 switch (target) {
34 case DatastoreTarget::Operational:
35 case DatastoreTarget::Running:
36 return libnetconf::NmdaDatastore::Candidate;
37 case DatastoreTarget::Startup:
38 return libnetconf::NmdaDatastore::Startup;
39 }
40
41 __builtin_unreachable();
42}
43}
Václav Kubernátd6282912020-06-23 14:49:34 +020044DatastoreAccess::Tree NetconfAccess::getItems(const std::string& path) const
Václav Kubernátc31bd602019-03-07 11:44:48 +010045{
Jan Kundrátb331b552020-01-23 15:25:29 +010046 Tree res;
Václav Kubernát06b0f382021-10-04 11:20:47 +020047 auto config = [this, &path] {
48 if (m_serverHasNMDA) {
49 return m_session->getData(targetToDs_get(m_target), (path != "/") ? std::optional{path} : std::nullopt);
50 }
51
52 return m_session->get((path != "/") ? std::optional{path} : std::nullopt);
53 }();
Václav Kubernát9456b5c2019-10-02 21:14:52 +020054
55 if (config) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +020056 lyNodesToTree(res, config->siblings());
Václav Kubernátc31bd602019-03-07 11:44:48 +010057 }
Václav Kubernátc31bd602019-03-07 11:44:48 +010058 return res;
59}
60
Václav Kubernátc31bd602019-03-07 11:44:48 +010061NetconfAccess::NetconfAccess(const std::string& hostname, uint16_t port, const std::string& user, const std::string& pubKey, const std::string& privKey)
Jan Kundrátf59b83c2022-03-18 18:12:08 +010062 : m_context(std::nullopt, libyang::ContextOptions::SetPrivParsed)
Václav Kubernáted4e3782022-03-02 23:57:33 +010063 , m_session(libnetconf::client::Session::connectPubkey(hostname, port, user, pubKey, privKey, m_context))
64 , m_schema(std::make_shared<YangSchema>(m_context))
Václav Kubernátc31bd602019-03-07 11:44:48 +010065{
Václav Kubernát06b0f382021-10-04 11:20:47 +020066 checkNMDA();
Václav Kubernátc31bd602019-03-07 11:44:48 +010067}
68
Václav Kubernát7a9463f2020-10-30 08:57:59 +010069NetconfAccess::NetconfAccess(const int source, const int sink)
Jan Kundrátf59b83c2022-03-18 18:12:08 +010070 : m_context(std::nullopt, libyang::ContextOptions::SetPrivParsed)
Václav Kubernáted4e3782022-03-02 23:57:33 +010071 , m_session(libnetconf::client::Session::connectFd(source, sink, m_context))
72 , m_schema(std::make_shared<YangSchema>(m_context))
Václav Kubernát7a9463f2020-10-30 08:57:59 +010073{
Václav Kubernát06b0f382021-10-04 11:20:47 +020074 checkNMDA();
Václav Kubernát7a9463f2020-10-30 08:57:59 +010075}
76
Jan Kundrátdab815e2020-01-22 19:44:11 +010077NetconfAccess::NetconfAccess(std::unique_ptr<libnetconf::client::Session>&& session)
78 : m_session(std::move(session))
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010079 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Jan Kundrátdab815e2020-01-22 19:44:11 +010080{
Václav Kubernát06b0f382021-10-04 11:20:47 +020081 checkNMDA();
Jan Kundrátdab815e2020-01-22 19:44:11 +010082}
83
Václav Kubernátc31bd602019-03-07 11:44:48 +010084NetconfAccess::NetconfAccess(const std::string& socketPath)
Jan Kundrátf59b83c2022-03-18 18:12:08 +010085 : m_context(std::nullopt, libyang::ContextOptions::SetPrivParsed)
Václav Kubernáted4e3782022-03-02 23:57:33 +010086 , m_session(libnetconf::client::Session::connectSocket(socketPath, m_context))
87 , m_schema(std::make_shared<YangSchema>(m_context))
Václav Kubernátc31bd602019-03-07 11:44:48 +010088{
Václav Kubernát06b0f382021-10-04 11:20:47 +020089 checkNMDA();
90}
91
92void NetconfAccess::checkNMDA()
93{
94 auto nmdaMod = m_schema->getYangModule("ietf-netconf-nmda");
95 m_serverHasNMDA = nmdaMod && nmdaMod->implemented();
Václav Kubernátc31bd602019-03-07 11:44:48 +010096}
97
Václav Kubernátbde37ba2022-03-25 15:18:12 +010098void NetconfAccess::setNcLogLevel(libnetconf::LogLevel level)
Václav Kubernáte2e15ee2020-02-05 17:38:13 +010099{
100 libnetconf::client::setLogLevel(level);
101}
102
103void NetconfAccess::setNcLogCallback(const LogCb& callback)
104{
105 libnetconf::client::setLogCallback(callback);
106}
107
Václav Kubernátc31bd602019-03-07 11:44:48 +0100108void NetconfAccess::setLeaf(const std::string& path, leaf_data_ value)
109{
Jan Kundrát379bb572020-05-07 03:23:13 +0200110 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200111 auto nodes = m_schema->dataNodeFromPath(path, lyValue);
112 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100113}
114
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200115void NetconfAccess::createItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100116{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200117 auto nodes = m_schema->dataNodeFromPath(path);
118 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100119}
120
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200121void NetconfAccess::deleteItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100122{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200123 auto nodes = m_schema->dataNodeFromPath(path);
124
125 // When deleting leafs, `nodes.newNode` is opaque, because the leaf does not have a value. We need to use
126 // newAttrOpaqueJSON for opaque leafs.
127 if (nodes.createdNode->isOpaque()) {
128 nodes.createdNode->newAttrOpaqueJSON("ietf-netconf", "ietf-netconf:operation", "delete");
129 } else {
130 nodes.createdNode->newMeta(*m_schema->getYangModule("ietf-netconf"), "operation", "delete");
131 }
132 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100133}
134
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200135struct impl_toYangInsert {
136 std::string operator()(yang::move::Absolute& absolute)
137 {
138 return absolute == yang::move::Absolute::Begin ? "first" : "last";
139 }
140 std::string operator()(yang::move::Relative& relative)
141 {
142 return relative.m_position == yang::move::Relative::Position::After ? "after" : "before";
143 }
144};
145
146std::string toYangInsert(std::variant<yang::move::Absolute, yang::move::Relative> move)
147{
148 return std::visit(impl_toYangInsert{}, move);
149}
150
151void NetconfAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
152{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200153 auto nodes = m_schema->dataNodeFromPath(source);
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100154 auto sourceNode = *(nodes.createdNode->findPath(source));
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200155 auto yangModule = *m_schema->getYangModule("yang");
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100156 sourceNode.newMeta(yangModule, "insert", toYangInsert(move));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200157
158 if (std::holds_alternative<yang::move::Relative>(move)) {
159 auto relative = std::get<yang::move::Relative>(move);
160 if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100161 sourceNode.newMeta(yangModule, "value", leafDataToString(relative.m_path.at(".")));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200162 } else {
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100163 sourceNode.newMeta(yangModule, "key", instanceToString(relative.m_path, std::string{nodes.createdNode->schema().module().name()}));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200164 }
165 }
166 doEditFromDataNode(sourceNode);
167}
168
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200169void NetconfAccess::doEditFromDataNode(libyang::DataNode dataNode)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100170{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200171 auto data = dataNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
Václav Kubernát06b0f382021-10-04 11:20:47 +0200172 if (m_serverHasNMDA) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200173 m_session->editData(targetToDs_set(m_target), std::string{*data});
Václav Kubernát06b0f382021-10-04 11:20:47 +0200174 } else {
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100175 m_session->editConfig(
176 libnetconf::Datastore::Candidate,
177 libnetconf::EditDefaultOp::Merge,
178 libnetconf::EditTestOpt::TestSet,
179 libnetconf::EditErrorOpt::Stop,
180 std::string{*data});
Václav Kubernát06b0f382021-10-04 11:20:47 +0200181 }
Václav Kubernátc31bd602019-03-07 11:44:48 +0100182}
183
184void NetconfAccess::commitChanges()
185{
186 m_session->commit();
187}
188
189void NetconfAccess::discardChanges()
190{
191 m_session->discard();
192}
193
Václav Kubernátb3960f82020-12-01 03:21:48 +0100194DatastoreAccess::Tree NetconfAccess::execute(const std::string& path, const Tree& input)
Jan Kundrát6ee84792020-01-24 01:43:36 +0100195{
Václav Kubernátfbab2d42021-02-05 16:12:34 +0100196 auto inputNode = treeToRpcInput(m_session->libyangContext(), path, input);
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200197 auto data = inputNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100198
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200199 auto output = m_session->rpc_or_action(std::string{*data});
200 if (!output) {
201 return {};
202 }
203 return rpcOutputToTree(*output);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100204}
205
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100206libnetconf::Datastore toNcDatastore(Datastore datastore)
Václav Kubernát7160a132020-04-03 02:11:01 +0200207{
208 switch (datastore) {
209 case Datastore::Running:
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100210 return libnetconf::Datastore::Running;
Václav Kubernát7160a132020-04-03 02:11:01 +0200211 case Datastore::Startup:
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100212 return libnetconf::Datastore::Startup;
Václav Kubernát7160a132020-04-03 02:11:01 +0200213 }
214 __builtin_unreachable();
215}
216
217void NetconfAccess::copyConfig(const Datastore source, const Datastore destination)
218{
219 m_session->copyConfig(toNcDatastore(source), toNcDatastore(destination));
220}
221
Václav Kubernátc31bd602019-03-07 11:44:48 +0100222std::shared_ptr<Schema> NetconfAccess::schema()
223{
224 return m_schema;
225}
Václav Kubernátab612e92019-11-26 19:51:31 +0100226
227std::vector<ListInstance> NetconfAccess::listInstances(const std::string& path)
228{
229 std::vector<ListInstance> res;
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100230 auto keys = m_session->libyangContext().findXPath(path).front().asList().keys();
231 auto nodes = m_session->libyangContext().newPath2(path, std::nullopt, libyang::CreationOptions::Opaque);
Václav Kubernátab612e92019-11-26 19:51:31 +0100232
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200233 // Here we create a tree with "selection leafs" for all they keys of our wanted list. These leafs tell NETCONF, that
234 // we only want the list's keys and not any other data.
Václav Kubernátab612e92019-11-26 19:51:31 +0100235 for (const auto& keyLeaf : keys) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200236 // Selection leafs need to be inserted directly to the list using relative paths, that's why `newNode` is used
237 // here.
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100238 nodes.createdNode->newPath(keyLeaf.name().data(), std::nullopt, libyang::CreationOptions::Opaque);
Václav Kubernátab612e92019-11-26 19:51:31 +0100239 }
240
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200241 // Have to use `newParent` in case our wanted list is a nested list. With `newNode` I would only send the inner
242 // nested list and not the whole tree.
243 auto instances = m_session->get(std::string{*nodes.createdParent->printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings)});
Václav Kubernátab612e92019-11-26 19:51:31 +0100244
Václav Kubernát45e55462020-02-04 11:19:32 +0100245 if (!instances) {
246 return res;
247 }
Václav Kubernátab612e92019-11-26 19:51:31 +0100248
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100249 for (const auto& instance : instances->findXPath(path)) {
Václav Kubernátab612e92019-11-26 19:51:31 +0100250 ListInstance instanceRes;
251
Jan Kundrát5c3428b2022-06-27 00:10:59 +0200252 for (const auto& keyLeaf : instance.immediateChildren()) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100253 // FIXME: even though we specified we only want the key leafs, Netopeer disregards that and sends more data,
254 // even lists and other stuff. We only want keys, so filter out non-leafs and non-keys
255 // https://github.com/CESNET/netopeer2/issues/825
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200256 if (keyLeaf.schema().nodeType() != libyang::NodeType::Leaf) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100257 continue;
258 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200259 if (!keyLeaf.schema().asLeaf().isKey()) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100260 continue;
261 }
262
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200263 auto leafData = keyLeaf.asTerm();
264 instanceRes.insert({std::string{leafData.schema().name()}, leafValueFromNode(leafData)});
Václav Kubernátab612e92019-11-26 19:51:31 +0100265 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200266 res.emplace_back(instanceRes);
Václav Kubernátab612e92019-11-26 19:51:31 +0100267 }
268
269 return res;
270}
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200271
272std::string NetconfAccess::dump(const DataFormat format) const
273{
274 auto config = m_session->get();
275 if (!config) {
276 return "";
277 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200278 auto str = config->printStr(format == DataFormat::Xml ? libyang::DataFormat::XML : libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
279 if (!str) {
280 return "";
281 }
282
283 return std::string{*str};
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200284}