blob: 72e38e7d310328f6554f75f28211c8df9801d07a [file] [log] [blame]
Václav Kubernát74487df2020-06-04 01:29:28 +02001#include <boost/algorithm/string/predicate.hpp>
2#include <experimental/iterator>
Václav Kubernát548cb192020-06-26 14:00:42 +02003#include <fstream>
Václav Kubernát74487df2020-06-04 01:29:28 +02004#include <iostream>
Václav Kubernátcfdb9222021-07-07 22:36:24 +02005#include <libyang-cpp/DataNode.hpp>
6#include <libyang-cpp/Utils.hpp>
Václav Kubernát74487df2020-06-04 01:29:28 +02007#include "UniqueResource.hpp"
8#include "libyang_utils.hpp"
9#include "utils.hpp"
10#include "yang_access.hpp"
11#include "yang_schema.hpp"
12
13namespace {
Václav Kubernát74487df2020-06-04 01:29:28 +020014// Convenient for functions that take m_datastore as an argument
Václav Kubernátcfdb9222021-07-07 22:36:24 +020015using DatastoreType = std::optional<libyang::DataNode>;
Václav Kubernát74487df2020-06-04 01:29:28 +020016}
17
18YangAccess::YangAccess()
Václav Kubernátcfdb9222021-07-07 22:36:24 +020019 : m_ctx(nullptr, libyang::ContextOptions::DisableSearchCwd)
20 , m_datastore(std::nullopt)
21 , m_schema(std::make_shared<YangSchema>(m_ctx))
Václav Kubernáte7248b22020-06-26 15:38:59 +020022{
23}
24
25YangAccess::YangAccess(std::shared_ptr<YangSchema> schema)
Václav Kubernátcfdb9222021-07-07 22:36:24 +020026 : m_ctx(schema->m_context)
27 , m_datastore(std::nullopt)
Václav Kubernáte7248b22020-06-26 15:38:59 +020028 , m_schema(schema)
Václav Kubernát74487df2020-06-04 01:29:28 +020029{
30}
31
Václav Kubernát51fa48e2020-07-08 17:17:34 +020032YangAccess::~YangAccess() = default;
Václav Kubernát74487df2020-06-04 01:29:28 +020033
34[[noreturn]] void YangAccess::getErrorsAndThrow() const
35{
Václav Kubernát74487df2020-06-04 01:29:28 +020036 std::vector<DatastoreError> errorsRes;
Václav Kubernát74487df2020-06-04 01:29:28 +020037
Václav Kubernátcfdb9222021-07-07 22:36:24 +020038 for (const auto& err : m_ctx.getErrors()) {
39 errorsRes.emplace_back(err.message, err.path);
40 }
Václav Kubernát74487df2020-06-04 01:29:28 +020041 throw DatastoreException(errorsRes);
42}
43
44void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value)
45{
Václav Kubernátcfdb9222021-07-07 22:36:24 +020046 try {
47 if (m_datastore) {
48 m_datastore->newPath(path.c_str(), value ? value->c_str() : nullptr, libyang::CreationOptions::Update);
49 } else {
50 m_datastore = m_ctx.newPath(path.c_str(), value ? value->c_str() : nullptr, libyang::CreationOptions::Update);
51 }
52 } catch (libyang::Error&) {
Václav Kubernát74487df2020-06-04 01:29:28 +020053 getErrorsAndThrow();
54 }
Václav Kubernát74487df2020-06-04 01:29:28 +020055}
56
57namespace {
Václav Kubernátcfdb9222021-07-07 22:36:24 +020058void impl_unlink(DatastoreType& datastore, libyang::DataNode what)
Václav Kubernát74487df2020-06-04 01:29:28 +020059{
60 // If the node to be unlinked is the one our datastore variable points to, we need to find a new one to point to (one of its siblings)
Václav Kubernátcfdb9222021-07-07 22:36:24 +020061
62 if (datastore == what) {
63 auto oldDatastore = datastore;
64 do {
65 datastore = datastore->previousSibling();
66 if (datastore == oldDatastore) {
67 // We have gone all the way back to our original node, which means it's the only node in our
68 // datastore.
69 datastore = std::nullopt;
70 break;
71 }
72 } while (datastore->schema().module().name() == "ietf-yang-library");
Václav Kubernát74487df2020-06-04 01:29:28 +020073 }
74
Václav Kubernátcfdb9222021-07-07 22:36:24 +020075 what.unlink();
Václav Kubernát74487df2020-06-04 01:29:28 +020076}
77}
78
79void YangAccess::impl_removeNode(const std::string& path)
80{
Václav Kubernátcfdb9222021-07-07 22:36:24 +020081 if (!m_datastore) {
82 // Otherwise the datastore just doesn't contain the wanted node.
83 throw DatastoreException{{DatastoreError{"Datastore is empty.", path}}};
84 }
85 auto toRemove = m_datastore->findPath(path.c_str());
86 if (!toRemove) {
Václav Kubernát74487df2020-06-04 01:29:28 +020087 // Otherwise the datastore just doesn't contain the wanted node.
88 throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}};
89 }
90
Václav Kubernátcfdb9222021-07-07 22:36:24 +020091 impl_unlink(m_datastore, *toRemove);
Václav Kubernát74487df2020-06-04 01:29:28 +020092}
93
94void YangAccess::validate()
95{
Václav Kubernátcfdb9222021-07-07 22:36:24 +020096 if (m_datastore) {
97 libyang::validateAll(m_datastore);
Václav Kubernáte7248b22020-06-26 15:38:59 +020098 }
Václav Kubernát74487df2020-06-04 01:29:28 +020099}
100
Václav Kubernátd6282912020-06-23 14:49:34 +0200101DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200102{
103 DatastoreAccess::Tree res;
104 if (!m_datastore) {
105 return res;
106 }
107
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200108 auto set = m_datastore->findXPath(path == "/" ? "/*" : path.c_str());
109
110 lyNodesToTree(res, set);
Václav Kubernát74487df2020-06-04 01:29:28 +0200111 return res;
112}
113
114void YangAccess::setLeaf(const std::string& path, leaf_data_ value)
115{
116 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
117 impl_newPath(path, lyValue);
118}
119
120void YangAccess::createItem(const std::string& path)
121{
122 impl_newPath(path);
123}
124
125void YangAccess::deleteItem(const std::string& path)
126{
127 impl_removeNode(path);
128}
129
130namespace {
131struct impl_moveItem {
132 DatastoreType& m_datastore;
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200133 libyang::DataNode m_sourceNode;
Václav Kubernát74487df2020-06-04 01:29:28 +0200134
135 void operator()(yang::move::Absolute absolute) const
136 {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200137 auto set = m_sourceNode.findXPath(m_sourceNode.schema().path().get().get());
138 if (set.size() == 1) { // m_sourceNode is the sole instance, do nothing
Václav Kubernát74487df2020-06-04 01:29:28 +0200139 return;
140 }
141
Václav Kubernát74487df2020-06-04 01:29:28 +0200142 switch (absolute) {
143 case yang::move::Absolute::Begin:
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200144 if (set.front() == m_sourceNode) { // List is already at the beginning, do nothing
Václav Kubernát74487df2020-06-04 01:29:28 +0200145 return;
146 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200147 set.front().insertBefore(m_sourceNode);
148 break;
Václav Kubernát74487df2020-06-04 01:29:28 +0200149 case yang::move::Absolute::End:
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200150 if (set.back() == m_sourceNode) { // List is already at the end, do nothing
Václav Kubernát74487df2020-06-04 01:29:28 +0200151 return;
152 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200153 set.back().insertAfter(m_sourceNode);
154 break;
Václav Kubernát74487df2020-06-04 01:29:28 +0200155 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200156 m_datastore = m_datastore->firstSibling();
Václav Kubernát74487df2020-06-04 01:29:28 +0200157 }
158
159 void operator()(const yang::move::Relative& relative) const
160 {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200161 auto keySuffix = m_sourceNode.schema().nodeType() == libyang::NodeType::List ? instanceToString(relative.m_path)
Václav Kubernát74487df2020-06-04 01:29:28 +0200162 : leafDataToString(relative.m_path.at("."));
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200163 auto destNode = m_sourceNode.findSiblingVal(m_sourceNode.schema(), keySuffix.c_str());
Václav Kubernát74487df2020-06-04 01:29:28 +0200164
Václav Kubernát74487df2020-06-04 01:29:28 +0200165 if (relative.m_position == yang::move::Relative::Position::After) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200166 destNode->insertAfter(m_sourceNode);
Václav Kubernát74487df2020-06-04 01:29:28 +0200167 } else {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200168 destNode->insertBefore(m_sourceNode);
Václav Kubernát74487df2020-06-04 01:29:28 +0200169 }
170 }
Václav Kubernát74487df2020-06-04 01:29:28 +0200171};
172}
173
174void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
175{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200176 if (!m_datastore) {
177 throw DatastoreException{{DatastoreError{"Datastore is empty.", source}}};
Václav Kubernát74487df2020-06-04 01:29:28 +0200178 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200179
180 auto sourceNode = m_datastore->findPath(source.c_str());
181
182 if (!sourceNode) {
183 // The datastore doesn't contain the wanted node.
184 throw DatastoreException{{DatastoreError{"Data node doesn't exist.", source}}};
Václav Kubernát74487df2020-06-04 01:29:28 +0200185 }
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200186 std::visit(impl_moveItem{m_datastore, *sourceNode}, move);
Václav Kubernát74487df2020-06-04 01:29:28 +0200187}
188
189void YangAccess::commitChanges()
190{
191 validate();
192}
193
194void YangAccess::discardChanges()
195{
196}
197
Václav Kubernátb3960f82020-12-01 03:21:48 +0100198[[noreturn]] DatastoreAccess::Tree YangAccess::execute(const std::string& path, const Tree& input)
Václav Kubernát74487df2020-06-04 01:29:28 +0200199{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200200 auto root = [&path, this] {
201 try {
202 return m_ctx.newPath(path.c_str());
203 } catch (libyang::ErrorWithCode& err) {
204 getErrorsAndThrow();
205 }
206 }();
207
Václav Kubernát74487df2020-06-04 01:29:28 +0200208 for (const auto& [k, v] : input) {
Václav Kubernáte7248b22020-06-26 15:38:59 +0200209 if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) {
210 continue;
211 }
Václav Kubernát94bb7cf2021-02-03 09:59:39 +0100212
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200213 try {
214 root.newPath(k.c_str(), leafDataToString(v).c_str(), libyang::CreationOptions::Update);
215 } catch (libyang::ErrorWithCode& err) {
216 getErrorsAndThrow();
217 }
Václav Kubernát74487df2020-06-04 01:29:28 +0200218 }
Václav Kubernátb3960f82020-12-01 03:21:48 +0100219 throw std::logic_error("in-memory datastore doesn't support executing RPC/action");
Václav Kubernát74487df2020-06-04 01:29:28 +0200220}
221
222void YangAccess::copyConfig(const Datastore source, const Datastore dest)
223{
224 if (source == Datastore::Startup && dest == Datastore::Running) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200225 m_datastore = std::nullopt;
Václav Kubernát74487df2020-06-04 01:29:28 +0200226 }
227}
228
229std::shared_ptr<Schema> YangAccess::schema()
230{
231 return m_schema;
232}
233
234std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
235{
236 std::vector<ListInstance> res;
237 if (!m_datastore) {
238 return res;
239 }
240
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200241 auto instances = m_datastore->findXPath(path.c_str());
242 for (const auto& list : instances) {
Václav Kubernát74487df2020-06-04 01:29:28 +0200243 ListInstance instance;
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200244 for (const auto& child : list.child()->siblings()) {
245 if (child.schema().nodeType() == libyang::NodeType::Leaf) {
246 auto leafSchema(child.schema().asLeaf());
247 if (leafSchema.isKey()) {
248 instance.insert({std::string{leafSchema.name()}, leafValueFromNode(child.asTerm())});
Václav Kubernát74487df2020-06-04 01:29:28 +0200249 }
250 }
251 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200252 res.emplace_back(instance);
Václav Kubernát74487df2020-06-04 01:29:28 +0200253 }
254 return res;
255}
256
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200257std::string YangAccess::dump(const DataFormat format) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200258{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200259 if (!m_datastore) {
260 return "";
Václav Kubernát74487df2020-06-04 01:29:28 +0200261 }
262
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200263 auto str = m_datastore->firstSibling().printStr(format == DataFormat::Xml ? libyang::DataFormat::XML : libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
264 if (!str) {
265 return "";
266 }
267
268 return std::string{*str};
Václav Kubernát74487df2020-06-04 01:29:28 +0200269}
270
Václav Kubernát619e6542020-06-29 14:13:43 +0200271void YangAccess::loadModule(const std::string& name)
272{
273 m_schema->loadModule(name);
274}
275
Václav Kubernát74487df2020-06-04 01:29:28 +0200276void YangAccess::addSchemaFile(const std::string& path)
277{
278 m_schema->addSchemaFile(path.c_str());
279}
280
281void YangAccess::addSchemaDir(const std::string& path)
282{
283 m_schema->addSchemaDirectory(path.c_str());
284}
285
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200286void YangAccess::setEnabledFeatures(const std::string& module, const std::vector<std::string>& features)
Václav Kubernát74487df2020-06-04 01:29:28 +0200287{
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200288 m_schema->setEnabledFeatures(module, features);
Václav Kubernát74487df2020-06-04 01:29:28 +0200289}
Václav Kubernát548cb192020-06-26 14:00:42 +0200290
Václav Kubernát0c90dd42022-01-18 00:07:29 +0100291void YangAccess::addDataFile(const std::string& path, const StrictDataParsing strict)
Václav Kubernát548cb192020-06-26 14:00:42 +0200292{
293 std::ifstream fs(path);
294 char firstChar;
295 fs >> firstChar;
296
297 std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
Václav Kubernát0c90dd42022-01-18 00:07:29 +0100298
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200299 auto dataNode = m_ctx.parseDataPath(
300 path.c_str(),
301 firstChar == '{' ? libyang::DataFormat::JSON : libyang::DataFormat::XML,
302 strict == StrictDataParsing::Yes ? std::optional{libyang::ParseOptions::Strict} : std::nullopt,
303 libyang::ValidationOptions::Present);
Václav Kubernát548cb192020-06-26 14:00:42 +0200304
305 if (!m_datastore) {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200306 m_datastore = dataNode;
Václav Kubernát548cb192020-06-26 14:00:42 +0200307 } else {
Václav Kubernátcfdb9222021-07-07 22:36:24 +0200308 m_datastore->merge(*dataNode);
Václav Kubernát548cb192020-06-26 14:00:42 +0200309 }
310
311 validate();
312}