blob: 5683b76d87bbaee5023ae0913d276143773d08bb [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át7a9463f2020-10-30 08:57:59 +010061NetconfAccess::NetconfAccess(const int source, const int sink)
Jan Kundrát0ea563d2023-11-14 16:44:21 +010062 : m_context(std::nullopt, libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::DisableSearchCwd)
Václav Kubernáted4e3782022-03-02 23:57:33 +010063 , m_session(libnetconf::client::Session::connectFd(source, sink, m_context))
64 , m_schema(std::make_shared<YangSchema>(m_context))
Václav Kubernát7a9463f2020-10-30 08:57:59 +010065{
Václav Kubernát06b0f382021-10-04 11:20:47 +020066 checkNMDA();
Václav Kubernát7a9463f2020-10-30 08:57:59 +010067}
68
Jan Kundrátdab815e2020-01-22 19:44:11 +010069NetconfAccess::NetconfAccess(std::unique_ptr<libnetconf::client::Session>&& session)
70 : m_session(std::move(session))
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010071 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Jan Kundrátdab815e2020-01-22 19:44:11 +010072{
Václav Kubernát06b0f382021-10-04 11:20:47 +020073 checkNMDA();
Jan Kundrátdab815e2020-01-22 19:44:11 +010074}
75
Václav Kubernátc31bd602019-03-07 11:44:48 +010076NetconfAccess::NetconfAccess(const std::string& socketPath)
Jan Kundrát0ea563d2023-11-14 16:44:21 +010077 : m_context(std::nullopt, libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::DisableSearchCwd)
Václav Kubernáted4e3782022-03-02 23:57:33 +010078 , m_session(libnetconf::client::Session::connectSocket(socketPath, m_context))
79 , m_schema(std::make_shared<YangSchema>(m_context))
Václav Kubernátc31bd602019-03-07 11:44:48 +010080{
Václav Kubernát06b0f382021-10-04 11:20:47 +020081 checkNMDA();
82}
83
84void NetconfAccess::checkNMDA()
85{
86 auto nmdaMod = m_schema->getYangModule("ietf-netconf-nmda");
87 m_serverHasNMDA = nmdaMod && nmdaMod->implemented();
Václav Kubernátc31bd602019-03-07 11:44:48 +010088}
89
Václav Kubernátbde37ba2022-03-25 15:18:12 +010090void NetconfAccess::setNcLogLevel(libnetconf::LogLevel level)
Václav Kubernáte2e15ee2020-02-05 17:38:13 +010091{
92 libnetconf::client::setLogLevel(level);
93}
94
95void NetconfAccess::setNcLogCallback(const LogCb& callback)
96{
97 libnetconf::client::setLogCallback(callback);
98}
99
Václav Kubernátc31bd602019-03-07 11:44:48 +0100100void NetconfAccess::setLeaf(const std::string& path, leaf_data_ value)
101{
Jan Kundrát379bb572020-05-07 03:23:13 +0200102 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200103 auto nodes = m_schema->dataNodeFromPath(path, lyValue);
104 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100105}
106
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200107void NetconfAccess::createItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100108{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200109 auto nodes = m_schema->dataNodeFromPath(path);
110 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100111}
112
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200113void NetconfAccess::deleteItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100114{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200115 auto nodes = m_schema->dataNodeFromPath(path);
116
117 // When deleting leafs, `nodes.newNode` is opaque, because the leaf does not have a value. We need to use
118 // newAttrOpaqueJSON for opaque leafs.
119 if (nodes.createdNode->isOpaque()) {
120 nodes.createdNode->newAttrOpaqueJSON("ietf-netconf", "ietf-netconf:operation", "delete");
121 } else {
122 nodes.createdNode->newMeta(*m_schema->getYangModule("ietf-netconf"), "operation", "delete");
123 }
124 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100125}
126
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200127struct impl_toYangInsert {
128 std::string operator()(yang::move::Absolute& absolute)
129 {
130 return absolute == yang::move::Absolute::Begin ? "first" : "last";
131 }
132 std::string operator()(yang::move::Relative& relative)
133 {
134 return relative.m_position == yang::move::Relative::Position::After ? "after" : "before";
135 }
136};
137
138std::string toYangInsert(std::variant<yang::move::Absolute, yang::move::Relative> move)
139{
140 return std::visit(impl_toYangInsert{}, move);
141}
142
143void NetconfAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
144{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200145 auto nodes = m_schema->dataNodeFromPath(source);
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100146 auto sourceNode = *(nodes.createdNode->findPath(source));
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200147 auto yangModule = *m_schema->getYangModule("yang");
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100148 sourceNode.newMeta(yangModule, "insert", toYangInsert(move));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200149
150 if (std::holds_alternative<yang::move::Relative>(move)) {
151 auto relative = std::get<yang::move::Relative>(move);
152 if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100153 sourceNode.newMeta(yangModule, "value", leafDataToString(relative.m_path.at(".")));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200154 } else {
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100155 sourceNode.newMeta(yangModule, "key", instanceToString(relative.m_path, std::string{nodes.createdNode->schema().module().name()}));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200156 }
157 }
158 doEditFromDataNode(sourceNode);
159}
160
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200161void NetconfAccess::doEditFromDataNode(libyang::DataNode dataNode)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100162{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200163 auto data = dataNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
Václav Kubernát06b0f382021-10-04 11:20:47 +0200164 if (m_serverHasNMDA) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200165 m_session->editData(targetToDs_set(m_target), std::string{*data});
Václav Kubernát06b0f382021-10-04 11:20:47 +0200166 } else {
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100167 m_session->editConfig(
168 libnetconf::Datastore::Candidate,
169 libnetconf::EditDefaultOp::Merge,
170 libnetconf::EditTestOpt::TestSet,
171 libnetconf::EditErrorOpt::Stop,
172 std::string{*data});
Václav Kubernát06b0f382021-10-04 11:20:47 +0200173 }
Václav Kubernátc31bd602019-03-07 11:44:48 +0100174}
175
176void NetconfAccess::commitChanges()
177{
178 m_session->commit();
179}
180
181void NetconfAccess::discardChanges()
182{
183 m_session->discard();
184}
185
Václav Kubernátb3960f82020-12-01 03:21:48 +0100186DatastoreAccess::Tree NetconfAccess::execute(const std::string& path, const Tree& input)
Jan Kundrát6ee84792020-01-24 01:43:36 +0100187{
Václav Kubernátfbab2d42021-02-05 16:12:34 +0100188 auto inputNode = treeToRpcInput(m_session->libyangContext(), path, input);
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200189 auto data = inputNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100190
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200191 auto output = m_session->rpc_or_action(std::string{*data});
192 if (!output) {
193 return {};
194 }
195 return rpcOutputToTree(*output);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100196}
197
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100198libnetconf::Datastore toNcDatastore(Datastore datastore)
Václav Kubernát7160a132020-04-03 02:11:01 +0200199{
200 switch (datastore) {
201 case Datastore::Running:
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100202 return libnetconf::Datastore::Running;
Václav Kubernát7160a132020-04-03 02:11:01 +0200203 case Datastore::Startup:
Václav Kubernátbde37ba2022-03-25 15:18:12 +0100204 return libnetconf::Datastore::Startup;
Václav Kubernát7160a132020-04-03 02:11:01 +0200205 }
206 __builtin_unreachable();
207}
208
209void NetconfAccess::copyConfig(const Datastore source, const Datastore destination)
210{
211 m_session->copyConfig(toNcDatastore(source), toNcDatastore(destination));
212}
213
Václav Kubernátc31bd602019-03-07 11:44:48 +0100214std::shared_ptr<Schema> NetconfAccess::schema()
215{
216 return m_schema;
217}
Václav Kubernátab612e92019-11-26 19:51:31 +0100218
219std::vector<ListInstance> NetconfAccess::listInstances(const std::string& path)
220{
221 std::vector<ListInstance> res;
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100222 auto keys = m_session->libyangContext().findXPath(path).front().asList().keys();
223 auto nodes = m_session->libyangContext().newPath2(path, std::nullopt, libyang::CreationOptions::Opaque);
Václav Kubernátab612e92019-11-26 19:51:31 +0100224
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200225 // Here we create a tree with "selection leafs" for all they keys of our wanted list. These leafs tell NETCONF, that
226 // we only want the list's keys and not any other data.
Václav Kubernátab612e92019-11-26 19:51:31 +0100227 for (const auto& keyLeaf : keys) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200228 // Selection leafs need to be inserted directly to the list using relative paths, that's why `newNode` is used
229 // here.
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100230 nodes.createdNode->newPath(keyLeaf.name().data(), std::nullopt, libyang::CreationOptions::Opaque);
Václav Kubernátab612e92019-11-26 19:51:31 +0100231 }
232
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200233 // Have to use `newParent` in case our wanted list is a nested list. With `newNode` I would only send the inner
234 // nested list and not the whole tree.
235 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 +0100236
Václav Kubernát45e55462020-02-04 11:19:32 +0100237 if (!instances) {
238 return res;
239 }
Václav Kubernátab612e92019-11-26 19:51:31 +0100240
Jan Kundrátf59b83c2022-03-18 18:12:08 +0100241 for (const auto& instance : instances->findXPath(path)) {
Václav Kubernátab612e92019-11-26 19:51:31 +0100242 ListInstance instanceRes;
243
Jan Kundrát5c3428b2022-06-27 00:10:59 +0200244 for (const auto& keyLeaf : instance.immediateChildren()) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100245 // FIXME: even though we specified we only want the key leafs, Netopeer disregards that and sends more data,
246 // even lists and other stuff. We only want keys, so filter out non-leafs and non-keys
247 // https://github.com/CESNET/netopeer2/issues/825
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200248 if (keyLeaf.schema().nodeType() != libyang::NodeType::Leaf) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100249 continue;
250 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200251 if (!keyLeaf.schema().asLeaf().isKey()) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100252 continue;
253 }
254
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200255 auto leafData = keyLeaf.asTerm();
256 instanceRes.insert({std::string{leafData.schema().name()}, leafValueFromNode(leafData)});
Václav Kubernátab612e92019-11-26 19:51:31 +0100257 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200258 res.emplace_back(instanceRes);
Václav Kubernátab612e92019-11-26 19:51:31 +0100259 }
260
261 return res;
262}
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200263
264std::string NetconfAccess::dump(const DataFormat format) const
265{
266 auto config = m_session->get();
267 if (!config) {
268 return "";
269 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200270 auto str = config->printStr(format == DataFormat::Xml ? libyang::DataFormat::XML : libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
271 if (!str) {
272 return "";
273 }
274
275 return std::string{*str};
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200276}