blob: b599c2c1146b33615cb204588091edd7fea82190 [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
8#include <libyang/Libyang.hpp>
9#include <libyang/Tree_Data.hpp>
Václav Kubernát02a71152020-01-21 14:52:51 +010010#include "libyang_utils.hpp"
Václav Kubernát26b56082020-02-03 18:28:56 +010011#include "netconf-client.hpp"
Václav Kubernátc31bd602019-03-07 11:44:48 +010012#include "netconf_access.hpp"
13#include "utils.hpp"
14#include "yang_schema.hpp"
15
Jan Kundrát8f63fb72020-01-24 01:40:01 +010016
Václav Kubernátc31bd602019-03-07 11:44:48 +010017NetconfAccess::~NetconfAccess() = default;
18
Václav Kubernátd6282912020-06-23 14:49:34 +020019DatastoreAccess::Tree NetconfAccess::getItems(const std::string& path) const
Václav Kubernátc31bd602019-03-07 11:44:48 +010020{
Jan Kundrátb331b552020-01-23 15:25:29 +010021 Tree res;
Václav Kubernátfe170ad2021-01-08 14:17:02 +010022 auto config = m_session->getData(libnetconf::NmdaDatastore::Operational, (path != "/") ? std::optional{path} : std::nullopt);
Václav Kubernát9456b5c2019-10-02 21:14:52 +020023
24 if (config) {
Václav Kubernátdaf40312020-06-19 11:34:55 +020025 lyNodesToTree(res, config->tree_for());
Václav Kubernátc31bd602019-03-07 11:44:48 +010026 }
Václav Kubernátc31bd602019-03-07 11:44:48 +010027 return res;
28}
29
Václav Kubernátc31bd602019-03-07 11:44:48 +010030NetconfAccess::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 +010031 : m_session(libnetconf::client::Session::connectPubkey(hostname, port, user, pubKey, privKey))
32 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Václav Kubernátc31bd602019-03-07 11:44:48 +010033{
Václav Kubernátc31bd602019-03-07 11:44:48 +010034}
35
Václav Kubernát7a9463f2020-10-30 08:57:59 +010036NetconfAccess::NetconfAccess(const int source, const int sink)
37 : m_session(libnetconf::client::Session::connectFd(source, sink))
38 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
39{
40}
41
Jan Kundrátdab815e2020-01-22 19:44:11 +010042NetconfAccess::NetconfAccess(std::unique_ptr<libnetconf::client::Session>&& session)
43 : m_session(std::move(session))
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010044 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Jan Kundrátdab815e2020-01-22 19:44:11 +010045{
Jan Kundrátdab815e2020-01-22 19:44:11 +010046}
47
Václav Kubernátc31bd602019-03-07 11:44:48 +010048NetconfAccess::NetconfAccess(const std::string& socketPath)
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010049 : m_session(libnetconf::client::Session::connectSocket(socketPath))
50 , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
Václav Kubernátc31bd602019-03-07 11:44:48 +010051{
Václav Kubernátc31bd602019-03-07 11:44:48 +010052}
53
Václav Kubernáte2e15ee2020-02-05 17:38:13 +010054void NetconfAccess::setNcLogLevel(NC_VERB_LEVEL level)
55{
56 libnetconf::client::setLogLevel(level);
57}
58
59void NetconfAccess::setNcLogCallback(const LogCb& callback)
60{
61 libnetconf::client::setLogCallback(callback);
62}
63
Václav Kubernátc31bd602019-03-07 11:44:48 +010064void NetconfAccess::setLeaf(const std::string& path, leaf_data_ value)
65{
Jan Kundrát379bb572020-05-07 03:23:13 +020066 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
67 auto node = m_schema->dataNodeFromPath(path, lyValue);
Václav Kubernátc31bd602019-03-07 11:44:48 +010068 doEditFromDataNode(node);
69}
70
Jan Kundrátcbf288b2020-06-18 20:44:39 +020071void NetconfAccess::createItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +010072{
73 auto node = m_schema->dataNodeFromPath(path);
74 doEditFromDataNode(node);
75}
76
Jan Kundrátcbf288b2020-06-18 20:44:39 +020077void NetconfAccess::deleteItem(const std::string& path)
Václav Kubernátc31bd602019-03-07 11:44:48 +010078{
79 auto node = m_schema->dataNodeFromPath(path);
Václav Kubernátda8e4b92020-02-04 11:56:30 +010080 auto container = *(node->find_path(path.c_str())->data().begin());
81 container->insert_attr(m_schema->getYangModule("ietf-netconf"), "operation", "delete");
Václav Kubernátc31bd602019-03-07 11:44:48 +010082 doEditFromDataNode(node);
83}
84
Václav Kubernátbf65dd72020-05-28 02:32:31 +020085struct impl_toYangInsert {
86 std::string operator()(yang::move::Absolute& absolute)
87 {
88 return absolute == yang::move::Absolute::Begin ? "first" : "last";
89 }
90 std::string operator()(yang::move::Relative& relative)
91 {
92 return relative.m_position == yang::move::Relative::Position::After ? "after" : "before";
93 }
94};
95
96std::string toYangInsert(std::variant<yang::move::Absolute, yang::move::Relative> move)
97{
98 return std::visit(impl_toYangInsert{}, move);
99}
100
101void NetconfAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
102{
103 auto node = m_schema->dataNodeFromPath(source);
104 auto sourceNode = *(node->find_path(source.c_str())->data().begin());
105 auto yangModule = m_schema->getYangModule("yang");
106 sourceNode->insert_attr(yangModule, "insert", toYangInsert(move).c_str());
107
108 if (std::holds_alternative<yang::move::Relative>(move)) {
109 auto relative = std::get<yang::move::Relative>(move);
110 if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
111 sourceNode->insert_attr(yangModule, "value", leafDataToString(relative.m_path.at(".")).c_str());
112 } else {
Václav Kubernát2c4778b2020-06-18 11:55:20 +0200113 sourceNode->insert_attr(yangModule, "key", instanceToString(relative.m_path, node->node_module()->name()).c_str());
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200114 }
115 }
116 doEditFromDataNode(sourceNode);
117}
118
Václav Kubernátc31bd602019-03-07 11:44:48 +0100119void NetconfAccess::doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode)
120{
121 auto data = dataNode->print_mem(LYD_XML, 0);
122 m_session->editConfig(NC_DATASTORE_CANDIDATE, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_TESTSET, NC_RPC_EDIT_ERROPT_STOP, data);
123}
124
125void NetconfAccess::commitChanges()
126{
127 m_session->commit();
128}
129
130void NetconfAccess::discardChanges()
131{
132 m_session->discard();
133}
134
Václav Kubernátb3960f82020-12-01 03:21:48 +0100135DatastoreAccess::Tree NetconfAccess::execute(const std::string& path, const Tree& input)
Jan Kundrát6ee84792020-01-24 01:43:36 +0100136{
137 auto root = m_schema->dataNodeFromPath(path);
138 for (const auto& [k, v] : input) {
Václav Kubernát94bb7cf2021-02-03 09:59:39 +0100139 root->new_path(m_session->libyangContext(), k.c_str(), leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100140 }
141 auto data = root->print_mem(LYD_XML, 0);
142
143 Tree res;
Václav Kubernáta8789602020-07-20 15:18:19 +0200144 auto output = m_session->rpc_or_action(data);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100145 if (output) {
Václav Kubernátd57ec452021-02-05 16:38:03 +0100146 // If there's output, it will be a top-level node. In case of action, the output can be nested so we need to use
Václav Kubernát94bb7cf2021-02-03 09:59:39 +0100147 // find_path to get to the actual output. Also, our `path` is fully prefixed, but the output paths aren't. So
148 // we use outputNode->path() to get the unprefixed path.
149
150 auto outputNode = output->find_path(path.c_str())->data().front();
151 lyNodesToTree(res, {outputNode}, joinPaths(outputNode->path(), "/"));
Jan Kundrát6ee84792020-01-24 01:43:36 +0100152 }
153 return res;
154}
155
Václav Kubernát7160a132020-04-03 02:11:01 +0200156NC_DATASTORE toNcDatastore(Datastore datastore)
157{
158 switch (datastore) {
159 case Datastore::Running:
160 return NC_DATASTORE_RUNNING;
161 case Datastore::Startup:
162 return NC_DATASTORE_STARTUP;
163 }
164 __builtin_unreachable();
165}
166
167void NetconfAccess::copyConfig(const Datastore source, const Datastore destination)
168{
169 m_session->copyConfig(toNcDatastore(source), toNcDatastore(destination));
170}
171
Václav Kubernátb52dc252019-12-04 13:03:39 +0100172std::string NetconfAccess::fetchSchema(const std::string_view module, const
173 std::optional<std::string_view> revision, const
174 std::optional<std::string_view> submodule, const
175 std::optional<std::string_view> submoduleRevision)
Václav Kubernátc31bd602019-03-07 11:44:48 +0100176{
Václav Kubernátb52dc252019-12-04 13:03:39 +0100177 if (submodule) {
178 return m_session->getSchema(*submodule, submoduleRevision);
179 }
Václav Kubernátc31bd602019-03-07 11:44:48 +0100180 return m_session->getSchema(module, revision);
181}
182
183std::vector<std::string> NetconfAccess::listImplementedSchemas()
184{
185 auto data = m_session->get("/ietf-netconf-monitoring:netconf-state/schemas");
186 auto set = data->find_path("/ietf-netconf-monitoring:netconf-state/schemas/schema/identifier");
187
188 std::vector<std::string> res;
189 for (auto it : set->data()) {
190 if (it->schema()->nodetype() == LYS_LEAF) {
191 libyang::Data_Node_Leaf_List leaf(it);
Václav Kubernátfaacd022020-07-08 16:44:38 +0200192 res.emplace_back(leaf.value_str());
Václav Kubernátc31bd602019-03-07 11:44:48 +0100193 }
194 }
195 return res;
196}
197
198std::shared_ptr<Schema> NetconfAccess::schema()
199{
200 return m_schema;
201}
Václav Kubernátab612e92019-11-26 19:51:31 +0100202
203std::vector<ListInstance> NetconfAccess::listInstances(const std::string& path)
204{
205 std::vector<ListInstance> res;
206 auto list = m_schema->dataNodeFromPath(path);
207
208 // This inserts selection nodes - I only want keys not other data
209 // To get the keys, I have to call find_path here - otherwise I would get keys of a top-level node (which might not even be a list)
210 auto keys = libyang::Schema_Node_List{(*(list->find_path(path.c_str())->data().begin()))->schema()}.keys();
211 for (const auto& keyLeaf : keys) {
212 // Have to call find_path here - otherwise I'll have the list, not the leaf inside it
213 auto selectionLeaf = *(m_schema->dataNodeFromPath(keyLeaf->path())->find_path(keyLeaf->path().c_str())->data().begin());
214 auto actualList = *(list->find_path(path.c_str())->data().begin());
215 actualList->insert(selectionLeaf);
216 }
217
Jan Kundrátbb525b42020-02-04 11:56:59 +0100218 auto instances = m_session->get(list->print_mem(LYD_XML, 0));
Václav Kubernátab612e92019-11-26 19:51:31 +0100219
Václav Kubernát45e55462020-02-04 11:19:32 +0100220 if (!instances) {
221 return res;
222 }
Václav Kubernátab612e92019-11-26 19:51:31 +0100223
224 for (const auto& instance : instances->find_path(path.c_str())->data()) {
225 ListInstance instanceRes;
226
227 // I take the first child here, because the first element (the parent of the child()) will be the list
228 for (const auto& keyLeaf : instance->child()->tree_for()) {
Václav Kubernátba84ede2021-02-04 17:21:11 +0100229 // FIXME: even though we specified we only want the key leafs, Netopeer disregards that and sends more data,
230 // even lists and other stuff. We only want keys, so filter out non-leafs and non-keys
231 // https://github.com/CESNET/netopeer2/issues/825
232 if (keyLeaf->schema()->nodetype() != LYS_LEAF) {
233 continue;
234 }
235 if (!std::make_shared<libyang::Schema_Node_Leaf>(keyLeaf->schema())->is_key()) {
236 continue;
237 }
238
Václav Kubernát2e4cafe2020-11-05 01:53:21 +0100239 auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(keyLeaf);
Václav Kubernátb4e5b182020-11-16 19:55:09 +0100240 instanceRes.insert({leafData->schema()->name(), leafValueFromNode(leafData)});
Václav Kubernátab612e92019-11-26 19:51:31 +0100241 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200242 res.emplace_back(instanceRes);
Václav Kubernátab612e92019-11-26 19:51:31 +0100243 }
244
245 return res;
246}
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200247
248std::string NetconfAccess::dump(const DataFormat format) const
249{
250 auto config = m_session->get();
251 if (!config) {
252 return "";
253 }
254 return config->print_mem(format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
255}