blob: 2b76e5079931562277909ca0240770b046cf5274 [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át02a71152020-01-21 14:52:51 +01008#include "libyang_utils.hpp"
Václav Kubernát26b56082020-02-03 18:28:56 +01009#include "netconf-client.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)
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010062 : m_session(libnetconf::client::Session::connectPubkey(hostname, port, user, pubKey, privKey))
63 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Václav Kubernátc31bd602019-03-07 11:44:48 +010064{
Václav Kubernát06b0f382021-10-04 11:20:47 +020065 checkNMDA();
Václav Kubernátc31bd602019-03-07 11:44:48 +010066}
67
Václav Kubernát7a9463f2020-10-30 08:57:59 +010068NetconfAccess::NetconfAccess(const int source, const int sink)
69 : m_session(libnetconf::client::Session::connectFd(source, sink))
70 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
71{
Václav Kubernát06b0f382021-10-04 11:20:47 +020072 checkNMDA();
Václav Kubernát7a9463f2020-10-30 08:57:59 +010073}
74
Jan Kundrátdab815e2020-01-22 19:44:11 +010075NetconfAccess::NetconfAccess(std::unique_ptr<libnetconf::client::Session>&& session)
76 : m_session(std::move(session))
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010077 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Jan Kundrátdab815e2020-01-22 19:44:11 +010078{
Václav Kubernát06b0f382021-10-04 11:20:47 +020079 checkNMDA();
Jan Kundrátdab815e2020-01-22 19:44:11 +010080}
81
Václav Kubernátc31bd602019-03-07 11:44:48 +010082NetconfAccess::NetconfAccess(const std::string& socketPath)
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010083 : m_session(libnetconf::client::Session::connectSocket(socketPath))
84 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Václav Kubernátc31bd602019-03-07 11:44:48 +010085{
Václav Kubernát06b0f382021-10-04 11:20:47 +020086 checkNMDA();
87}
88
89void NetconfAccess::checkNMDA()
90{
91 auto nmdaMod = m_schema->getYangModule("ietf-netconf-nmda");
92 m_serverHasNMDA = nmdaMod && nmdaMod->implemented();
Václav Kubernátc31bd602019-03-07 11:44:48 +010093}
94
Václav Kubernáte2e15ee2020-02-05 17:38:13 +010095void NetconfAccess::setNcLogLevel(NC_VERB_LEVEL level)
96{
97 libnetconf::client::setLogLevel(level);
98}
99
100void NetconfAccess::setNcLogCallback(const LogCb& callback)
101{
102 libnetconf::client::setLogCallback(callback);
103}
104
Václav Kubernátc31bd602019-03-07 11:44:48 +0100105void NetconfAccess::setLeaf(const std::string& path, leaf_data_ value)
106{
Jan Kundrát379bb572020-05-07 03:23:13 +0200107 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200108 auto nodes = m_schema->dataNodeFromPath(path, lyValue);
109 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100110}
111
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200112void NetconfAccess::createItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100113{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200114 auto nodes = m_schema->dataNodeFromPath(path);
115 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100116}
117
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200118void NetconfAccess::deleteItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100119{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200120 auto nodes = m_schema->dataNodeFromPath(path);
121
122 // When deleting leafs, `nodes.newNode` is opaque, because the leaf does not have a value. We need to use
123 // newAttrOpaqueJSON for opaque leafs.
124 if (nodes.createdNode->isOpaque()) {
125 nodes.createdNode->newAttrOpaqueJSON("ietf-netconf", "ietf-netconf:operation", "delete");
126 } else {
127 nodes.createdNode->newMeta(*m_schema->getYangModule("ietf-netconf"), "operation", "delete");
128 }
129 doEditFromDataNode(*nodes.createdParent);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100130}
131
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200132struct impl_toYangInsert {
133 std::string operator()(yang::move::Absolute& absolute)
134 {
135 return absolute == yang::move::Absolute::Begin ? "first" : "last";
136 }
137 std::string operator()(yang::move::Relative& relative)
138 {
139 return relative.m_position == yang::move::Relative::Position::After ? "after" : "before";
140 }
141};
142
143std::string toYangInsert(std::variant<yang::move::Absolute, yang::move::Relative> move)
144{
145 return std::visit(impl_toYangInsert{}, move);
146}
147
148void NetconfAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
149{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200150 auto nodes = m_schema->dataNodeFromPath(source);
151 auto sourceNode = *(nodes.createdNode->findPath(source.c_str()));
152 auto yangModule = *m_schema->getYangModule("yang");
153 sourceNode.newMeta(yangModule, "insert", toYangInsert(move).c_str());
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200154
155 if (std::holds_alternative<yang::move::Relative>(move)) {
156 auto relative = std::get<yang::move::Relative>(move);
157 if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200158 sourceNode.newMeta(yangModule, "value", leafDataToString(relative.m_path.at(".")).c_str());
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200159 } else {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200160 sourceNode.newMeta(yangModule, "key", instanceToString(relative.m_path, std::string{nodes.createdNode->schema().module().name()}).c_str());
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200161 }
162 }
163 doEditFromDataNode(sourceNode);
164}
165
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200166void NetconfAccess::doEditFromDataNode(libyang::DataNode dataNode)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100167{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200168 auto data = dataNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
Václav Kubernát06b0f382021-10-04 11:20:47 +0200169 if (m_serverHasNMDA) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200170 m_session->editData(targetToDs_set(m_target), std::string{*data});
Václav Kubernát06b0f382021-10-04 11:20:47 +0200171 } else {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200172 m_session->editConfig(NC_DATASTORE_CANDIDATE, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_TESTSET, NC_RPC_EDIT_ERROPT_STOP, 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át7160a132020-04-03 02:11:01 +0200198NC_DATASTORE toNcDatastore(Datastore datastore)
199{
200 switch (datastore) {
201 case Datastore::Running:
202 return NC_DATASTORE_RUNNING;
203 case Datastore::Startup:
204 return NC_DATASTORE_STARTUP;
205 }
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;
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200222 auto keys = m_session->libyangContext().findXPath(path.c_str()).front().asList().keys();
223 auto nodes = m_session->libyangContext().newPath2(path.c_str(), nullptr, 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.
230 nodes.createdNode->newPath(keyLeaf.name().data(), nullptr, 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
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200241 for (const auto& instance : instances->findXPath(path.c_str())) {
Václav Kubernátab612e92019-11-26 19:51:31 +0100242 ListInstance instanceRes;
243
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200244 for (const auto& keyLeaf : instance.child()->siblings()) {
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}