Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 1 | #include <boost/algorithm/string/predicate.hpp> |
| 2 | #include <experimental/iterator> |
Václav Kubernát | 548cb19 | 2020-06-26 14:00:42 +0200 | [diff] [blame] | 3 | #include <fstream> |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 4 | #include <iostream> |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 5 | #include <libyang-cpp/DataNode.hpp> |
| 6 | #include <libyang-cpp/Utils.hpp> |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 7 | #include "UniqueResource.hpp" |
| 8 | #include "libyang_utils.hpp" |
| 9 | #include "utils.hpp" |
| 10 | #include "yang_access.hpp" |
| 11 | #include "yang_schema.hpp" |
| 12 | |
| 13 | namespace { |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 14 | // Convenient for functions that take m_datastore as an argument |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 15 | using DatastoreType = std::optional<libyang::DataNode>; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 16 | } |
| 17 | |
| 18 | YangAccess::YangAccess() |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 19 | : m_ctx(nullptr, libyang::ContextOptions::DisableSearchCwd) |
| 20 | , m_datastore(std::nullopt) |
| 21 | , m_schema(std::make_shared<YangSchema>(m_ctx)) |
Václav Kubernát | e7248b2 | 2020-06-26 15:38:59 +0200 | [diff] [blame] | 22 | { |
| 23 | } |
| 24 | |
| 25 | YangAccess::YangAccess(std::shared_ptr<YangSchema> schema) |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 26 | : m_ctx(schema->m_context) |
| 27 | , m_datastore(std::nullopt) |
Václav Kubernát | e7248b2 | 2020-06-26 15:38:59 +0200 | [diff] [blame] | 28 | , m_schema(schema) |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 29 | { |
| 30 | } |
| 31 | |
Václav Kubernát | 51fa48e | 2020-07-08 17:17:34 +0200 | [diff] [blame] | 32 | YangAccess::~YangAccess() = default; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 33 | |
| 34 | [[noreturn]] void YangAccess::getErrorsAndThrow() const |
| 35 | { |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 36 | std::vector<DatastoreError> errorsRes; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 37 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 38 | for (const auto& err : m_ctx.getErrors()) { |
| 39 | errorsRes.emplace_back(err.message, err.path); |
| 40 | } |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 41 | throw DatastoreException(errorsRes); |
| 42 | } |
| 43 | |
| 44 | void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value) |
| 45 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 46 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 53 | getErrorsAndThrow(); |
| 54 | } |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 55 | } |
| 56 | |
| 57 | namespace { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 58 | void impl_unlink(DatastoreType& datastore, libyang::DataNode what) |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 59 | { |
| 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át | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 61 | |
| 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 73 | } |
| 74 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 75 | what.unlink(); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 76 | } |
| 77 | } |
| 78 | |
| 79 | void YangAccess::impl_removeNode(const std::string& path) |
| 80 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 81 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 87 | // 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át | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 91 | impl_unlink(m_datastore, *toRemove); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 92 | } |
| 93 | |
| 94 | void YangAccess::validate() |
| 95 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 96 | if (m_datastore) { |
| 97 | libyang::validateAll(m_datastore); |
Václav Kubernát | e7248b2 | 2020-06-26 15:38:59 +0200 | [diff] [blame] | 98 | } |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 99 | } |
| 100 | |
Václav Kubernát | d628291 | 2020-06-23 14:49:34 +0200 | [diff] [blame] | 101 | DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 102 | { |
| 103 | DatastoreAccess::Tree res; |
| 104 | if (!m_datastore) { |
| 105 | return res; |
| 106 | } |
| 107 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 108 | auto set = m_datastore->findXPath(path == "/" ? "/*" : path.c_str()); |
| 109 | |
| 110 | lyNodesToTree(res, set); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 111 | return res; |
| 112 | } |
| 113 | |
| 114 | void 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 | |
| 120 | void YangAccess::createItem(const std::string& path) |
| 121 | { |
| 122 | impl_newPath(path); |
| 123 | } |
| 124 | |
| 125 | void YangAccess::deleteItem(const std::string& path) |
| 126 | { |
| 127 | impl_removeNode(path); |
| 128 | } |
| 129 | |
| 130 | namespace { |
| 131 | struct impl_moveItem { |
| 132 | DatastoreType& m_datastore; |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 133 | libyang::DataNode m_sourceNode; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 134 | |
| 135 | void operator()(yang::move::Absolute absolute) const |
| 136 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 137 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 139 | return; |
| 140 | } |
| 141 | |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 142 | switch (absolute) { |
| 143 | case yang::move::Absolute::Begin: |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 144 | if (set.front() == m_sourceNode) { // List is already at the beginning, do nothing |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 145 | return; |
| 146 | } |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 147 | set.front().insertBefore(m_sourceNode); |
| 148 | break; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 149 | case yang::move::Absolute::End: |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 150 | if (set.back() == m_sourceNode) { // List is already at the end, do nothing |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 151 | return; |
| 152 | } |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 153 | set.back().insertAfter(m_sourceNode); |
| 154 | break; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 155 | } |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 156 | m_datastore = m_datastore->firstSibling(); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | void operator()(const yang::move::Relative& relative) const |
| 160 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 161 | auto keySuffix = m_sourceNode.schema().nodeType() == libyang::NodeType::List ? instanceToString(relative.m_path) |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 162 | : leafDataToString(relative.m_path.at(".")); |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 163 | auto destNode = m_sourceNode.findSiblingVal(m_sourceNode.schema(), keySuffix.c_str()); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 164 | |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 165 | if (relative.m_position == yang::move::Relative::Position::After) { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 166 | destNode->insertAfter(m_sourceNode); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 167 | } else { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 168 | destNode->insertBefore(m_sourceNode); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 169 | } |
| 170 | } |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 171 | }; |
| 172 | } |
| 173 | |
| 174 | void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move) |
| 175 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 176 | if (!m_datastore) { |
| 177 | throw DatastoreException{{DatastoreError{"Datastore is empty.", source}}}; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 178 | } |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 179 | |
| 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 185 | } |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 186 | std::visit(impl_moveItem{m_datastore, *sourceNode}, move); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | void YangAccess::commitChanges() |
| 190 | { |
| 191 | validate(); |
| 192 | } |
| 193 | |
| 194 | void YangAccess::discardChanges() |
| 195 | { |
| 196 | } |
| 197 | |
Václav Kubernát | b3960f8 | 2020-12-01 03:21:48 +0100 | [diff] [blame] | 198 | [[noreturn]] DatastoreAccess::Tree YangAccess::execute(const std::string& path, const Tree& input) |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 199 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 200 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 208 | for (const auto& [k, v] : input) { |
Václav Kubernát | e7248b2 | 2020-06-26 15:38:59 +0200 | [diff] [blame] | 209 | if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) { |
| 210 | continue; |
| 211 | } |
Václav Kubernát | 94bb7cf | 2021-02-03 09:59:39 +0100 | [diff] [blame] | 212 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 213 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 218 | } |
Václav Kubernát | b3960f8 | 2020-12-01 03:21:48 +0100 | [diff] [blame] | 219 | throw std::logic_error("in-memory datastore doesn't support executing RPC/action"); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 220 | } |
| 221 | |
| 222 | void YangAccess::copyConfig(const Datastore source, const Datastore dest) |
| 223 | { |
| 224 | if (source == Datastore::Startup && dest == Datastore::Running) { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 225 | m_datastore = std::nullopt; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 226 | } |
| 227 | } |
| 228 | |
| 229 | std::shared_ptr<Schema> YangAccess::schema() |
| 230 | { |
| 231 | return m_schema; |
| 232 | } |
| 233 | |
| 234 | std::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át | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 241 | auto instances = m_datastore->findXPath(path.c_str()); |
| 242 | for (const auto& list : instances) { |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 243 | ListInstance instance; |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 244 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 249 | } |
| 250 | } |
| 251 | } |
Václav Kubernát | faacd02 | 2020-07-08 16:44:38 +0200 | [diff] [blame] | 252 | res.emplace_back(instance); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 253 | } |
| 254 | return res; |
| 255 | } |
| 256 | |
Václav Kubernát | 70d7f7a | 2020-06-23 14:40:40 +0200 | [diff] [blame] | 257 | std::string YangAccess::dump(const DataFormat format) const |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 258 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 259 | if (!m_datastore) { |
| 260 | return ""; |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 261 | } |
| 262 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 263 | 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át | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 269 | } |
| 270 | |
Václav Kubernát | 619e654 | 2020-06-29 14:13:43 +0200 | [diff] [blame] | 271 | void YangAccess::loadModule(const std::string& name) |
| 272 | { |
| 273 | m_schema->loadModule(name); |
| 274 | } |
| 275 | |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 276 | void YangAccess::addSchemaFile(const std::string& path) |
| 277 | { |
| 278 | m_schema->addSchemaFile(path.c_str()); |
| 279 | } |
| 280 | |
| 281 | void YangAccess::addSchemaDir(const std::string& path) |
| 282 | { |
| 283 | m_schema->addSchemaDirectory(path.c_str()); |
| 284 | } |
| 285 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 286 | void YangAccess::setEnabledFeatures(const std::string& module, const std::vector<std::string>& features) |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 287 | { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 288 | m_schema->setEnabledFeatures(module, features); |
Václav Kubernát | 74487df | 2020-06-04 01:29:28 +0200 | [diff] [blame] | 289 | } |
Václav Kubernát | 548cb19 | 2020-06-26 14:00:42 +0200 | [diff] [blame] | 290 | |
Václav Kubernát | 0c90dd4 | 2022-01-18 00:07:29 +0100 | [diff] [blame] | 291 | void YangAccess::addDataFile(const std::string& path, const StrictDataParsing strict) |
Václav Kubernát | 548cb19 | 2020-06-26 14:00:42 +0200 | [diff] [blame] | 292 | { |
| 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át | 0c90dd4 | 2022-01-18 00:07:29 +0100 | [diff] [blame] | 298 | |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 299 | 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át | 548cb19 | 2020-06-26 14:00:42 +0200 | [diff] [blame] | 304 | |
| 305 | if (!m_datastore) { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 306 | m_datastore = dataNode; |
Václav Kubernát | 548cb19 | 2020-06-26 14:00:42 +0200 | [diff] [blame] | 307 | } else { |
Václav Kubernát | cfdb922 | 2021-07-07 22:36:24 +0200 | [diff] [blame^] | 308 | m_datastore->merge(*dataNode); |
Václav Kubernát | 548cb19 | 2020-06-26 14:00:42 +0200 | [diff] [blame] | 309 | } |
| 310 | |
| 311 | validate(); |
| 312 | } |