mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[65-libyang-adaptor] Imported adaptor code from kea-yang
This commit is contained in:
@@ -74,6 +74,9 @@ if test "$cross_compiling" = "yes"; then
|
||||
fi
|
||||
AM_CONDITIONAL([CROSS_COMPILING], [test "$cross_compiling" = "yes"])
|
||||
|
||||
# pkg-config can be required.
|
||||
AC_PATH_PROG([PKG_CONFIG], [pkg-config])
|
||||
|
||||
# Enable low-performing debugging facilities? This option optionally
|
||||
# enables some debugging aids that perform slowly and hence aren't built
|
||||
# by default.
|
||||
@@ -817,7 +820,6 @@ AC_ARG_WITH([cql],
|
||||
[cql_config="$withval"])
|
||||
|
||||
if test "${cql_config}" = "yes" ; then
|
||||
AC_PATH_PROG([PKG_CONFIG], [pkg-config])
|
||||
CQL_CONFIG="$PKG_CONFIG"
|
||||
elif test "${cql_config}" != "no" ; then
|
||||
CQL_CONFIG="${cql_config}"
|
||||
@@ -1586,6 +1588,8 @@ AC_CONFIG_FILES([Makefile
|
||||
src/lib/util/threads/Makefile
|
||||
src/lib/util/threads/tests/Makefile
|
||||
src/lib/util/unittests/Makefile
|
||||
src/lib/yang/Makefile
|
||||
src/lib/yang/tests/Makefile
|
||||
src/share/Makefile
|
||||
src/share/database/Makefile
|
||||
src/share/database/scripts/Makefile
|
||||
|
@@ -1,3 +1,9 @@
|
||||
# The following build order must be maintained.
|
||||
SUBDIRS = exceptions util log cryptolink dns asiolink cc testutils hooks dhcp \
|
||||
config stats asiodns dhcp_ddns eval dhcpsrv cfgrpt process http
|
||||
config stats
|
||||
|
||||
if HAVE_SYSREPO
|
||||
SUBDIRS += yang
|
||||
endif
|
||||
|
||||
SUBDIRS += asiodns dhcp_ddns eval dhcpsrv cfgrpt process http
|
||||
|
27
src/lib/yang/Makefile.am
Normal file
27
src/lib/yang/Makefile.am
Normal file
@@ -0,0 +1,27 @@
|
||||
SUBDIRS = . tests
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
||||
AM_CPPFLAGS += $(BOOST_INCLUDES) $(SYSREPO_CPPFLAGS)
|
||||
AM_CXXFLAGS = $(KEA_CXXFLAGS)
|
||||
|
||||
lib_LTLIBRARIES = libkea-yang.la
|
||||
libkea_yang_la_SOURCES = adaptor.cc adaptor.h
|
||||
|
||||
libkea_yang_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
|
||||
libkea_yang_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
|
||||
libkea_yang_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
|
||||
libkea_yang_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
|
||||
libkea_yang_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
|
||||
libkea_yang_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
|
||||
libkea_yang_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(SYSREPO_LIBS)
|
||||
|
||||
libkea_yang_la_LDFLAGS = -no-undefined -version-info 0:0:0
|
||||
|
||||
# Specify the headers for copying into the installation directory tree.
|
||||
libkea_yang_includedir = $(pkgincludedir)/yang
|
||||
libkea_yang_include_HEADERS = \
|
||||
adaptor.h
|
||||
|
||||
EXTRA_DIST = yang.dox
|
||||
|
||||
CLEANFILES = *.gcno *.gcda
|
280
src/lib/yang/adaptor.cc
Normal file
280
src/lib/yang/adaptor.cc
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <yang/adaptor.h>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::data;
|
||||
|
||||
namespace isc {
|
||||
namespace yang {
|
||||
|
||||
Adaptor::Adaptor() {
|
||||
}
|
||||
|
||||
Adaptor::~Adaptor() {
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
Adaptor::getContext(ConstElementPtr parent)
|
||||
{
|
||||
ConstElementPtr context = parent->get("user-context");
|
||||
ConstElementPtr comment = parent->get("comment");
|
||||
if (!comment) {
|
||||
return (context);
|
||||
}
|
||||
ElementPtr result;
|
||||
if (context) {
|
||||
result = copy(context);
|
||||
} else {
|
||||
result = Element::createMap();
|
||||
}
|
||||
result->set("comment", comment);
|
||||
return (result);
|
||||
}
|
||||
|
||||
void
|
||||
Adaptor::fromParent(const string& name, ConstElementPtr parent,
|
||||
ConstElementPtr list) {
|
||||
ConstElementPtr param = parent->get(name);
|
||||
if (!param) {
|
||||
return;
|
||||
}
|
||||
BOOST_FOREACH(ElementPtr item, list->listValue()) {
|
||||
// don't override?
|
||||
if (item->contains(name)) {
|
||||
continue;
|
||||
}
|
||||
item->set(name, param);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Adaptor::toParent(const string& name, ElementPtr parent,
|
||||
ConstElementPtr list) {
|
||||
ConstElementPtr param;
|
||||
bool first = true;
|
||||
BOOST_FOREACH(ElementPtr item, list->listValue()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
param = item->get(name);
|
||||
} else if ((!param && item->contains(name)) ||
|
||||
(param && !item->contains(name)) ||
|
||||
(param && item->contains(name) &&
|
||||
!param->equals(*item->get(name)))) {
|
||||
isc_throw(BadValue,
|
||||
"inconsistent value of " << name
|
||||
<< " in " << list->str());
|
||||
}
|
||||
}
|
||||
if (!first && param) {
|
||||
BOOST_FOREACH(ElementPtr item, list->listValue()) {
|
||||
if (param) {
|
||||
item->remove(name);
|
||||
}
|
||||
}
|
||||
parent->set(name, param);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief Apply insert.
|
||||
///
|
||||
/// @param key The key of the modification.
|
||||
/// @param value The value of the modification.
|
||||
/// @param scope The place to apply the insert.
|
||||
void apply_insert(ConstElementPtr key, ConstElementPtr value,
|
||||
ElementPtr scope) {
|
||||
if (scope->getType() == Element::map) {
|
||||
if (!key || !value || (key->getType() != Element::string)) {
|
||||
return;
|
||||
}
|
||||
string name = key->stringValue();
|
||||
if (!name.empty() && !scope->contains(name)) {
|
||||
scope->set(name, copy(value));
|
||||
}
|
||||
} else if (scope->getType() == Element::list) {
|
||||
if (value) {
|
||||
scope->add(copy(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Apply replace.
|
||||
///
|
||||
/// For maps same than insert but the new value is set even if the key
|
||||
/// already exists.
|
||||
///
|
||||
/// @param key The key of the modification.
|
||||
/// @param value The value of the modification.
|
||||
/// @param scope The place to apply the replace.
|
||||
void apply_replace(ConstElementPtr key, ConstElementPtr value,
|
||||
ElementPtr scope) {
|
||||
if ((scope->getType() != Element::map) ||
|
||||
!key || !value || (key->getType() != Element::string)) {
|
||||
return;
|
||||
}
|
||||
string name = key->stringValue();
|
||||
if (!name.empty()) {
|
||||
scope->set(name, copy(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Apply delete.
|
||||
///
|
||||
/// @param last The last item of the path.
|
||||
/// @param scope The place to apply the delete.
|
||||
void apply_delete(ConstElementPtr last, ElementPtr scope) {
|
||||
if (scope->getType() == Element::map) {
|
||||
if (!last || (last->getType() != Element::string)) {
|
||||
return;
|
||||
}
|
||||
string name = last->stringValue();
|
||||
if (!name.empty()) {
|
||||
scope->remove(name);
|
||||
}
|
||||
} else if (scope->getType() == Element::list) {
|
||||
if (!last) {
|
||||
return;
|
||||
} else if (last->getType() == Element::integer) {
|
||||
int index = last->intValue();
|
||||
if ((index >= 0) && (index < scope->size())) {
|
||||
scope->remove(index);
|
||||
}
|
||||
} else if (last->getType() == Element::map) {
|
||||
ConstElementPtr key = last->get("key");
|
||||
ConstElementPtr value = last->get("value");
|
||||
if (!key || !value || (key->getType() != Element::string)) {
|
||||
return;
|
||||
}
|
||||
string name = key->stringValue();
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < scope->size(); ++i) {
|
||||
ConstElementPtr item = scope->get(i);
|
||||
if (!item || (item->getType() != Element::map)) {
|
||||
continue;
|
||||
}
|
||||
ConstElementPtr compare = item->get(name);
|
||||
if (compare && value->equals(*compare)) {
|
||||
scope->remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Apply action.
|
||||
///
|
||||
/// @param actions The action list.
|
||||
/// @param scope The current scope.
|
||||
/// @param next The index of the next action.
|
||||
void apply_action(ConstElementPtr actions, ElementPtr scope, size_t next) {
|
||||
if (next == actions->size()) {
|
||||
return;
|
||||
}
|
||||
ConstElementPtr action = actions->get(next);
|
||||
++next;
|
||||
if (!action || (action->getType() != Element::map) ||
|
||||
!action->contains("action")) {
|
||||
apply_action(actions, scope, next);
|
||||
return;
|
||||
}
|
||||
string name = action->get("action")->stringValue();
|
||||
if (name == "insert") {
|
||||
apply_insert(action->get("key"), action->get("value"), scope);
|
||||
} else if (name == "replace") {
|
||||
apply_replace(action->get("key"), action->get("value"), scope);
|
||||
} else if (name == "delete") {
|
||||
apply_delete(action->get("last"), scope);
|
||||
}
|
||||
apply_action(actions, scope, next);
|
||||
}
|
||||
|
||||
/// @brief Modify down.
|
||||
///
|
||||
/// @param path The search list.
|
||||
/// @param actions The action list.
|
||||
/// @param scope The current scope.
|
||||
/// @param next The index of the next item to use in the path.
|
||||
void path_down(ConstElementPtr path, ConstElementPtr actions, ElementPtr scope,
|
||||
size_t next) {
|
||||
if (!scope) {
|
||||
return;
|
||||
}
|
||||
if (next == path->size()) {
|
||||
apply_action(actions, scope, 0);
|
||||
return;
|
||||
}
|
||||
ConstElementPtr step = path->get(next);
|
||||
++next;
|
||||
if (scope->getType() == Element::map) {
|
||||
if (!step || (step->getType() != Element::string)) {
|
||||
return;
|
||||
}
|
||||
string name = step->stringValue();
|
||||
if (name.empty() || !scope->contains(name)) {
|
||||
return;
|
||||
}
|
||||
ElementPtr down = boost::const_pointer_cast<Element>(scope->get(name));
|
||||
if (down) {
|
||||
path_down(path, actions, down, next);
|
||||
}
|
||||
} else if (scope->getType() == Element::list) {
|
||||
if (!step) {
|
||||
return;
|
||||
}
|
||||
auto downs = scope->listValue();
|
||||
if (step->getType() == Element::map) {
|
||||
ConstElementPtr key = step->get("key");
|
||||
ConstElementPtr value = step->get("value");
|
||||
if (!key || !value || (key->getType() != Element::string)) {
|
||||
return;
|
||||
}
|
||||
string name = key->stringValue();
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
for (ElementPtr down : downs) {
|
||||
if (!down || (down->getType() != Element::map)) {
|
||||
continue;
|
||||
}
|
||||
ConstElementPtr compare = down->get(name);
|
||||
if (compare && value->equals(*compare)) {
|
||||
path_down(path, actions, down, next);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (step->getType() != Element::integer) {
|
||||
return;
|
||||
}
|
||||
int index = step->intValue();
|
||||
if (index == -1) {
|
||||
for (ElementPtr down : downs) {
|
||||
path_down(path, actions, down, next);
|
||||
}
|
||||
} else if ((index >= 0) && (index < scope->size())) {
|
||||
path_down(path, actions, scope->getNonConst(index), next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
void
|
||||
Adaptor::modify(ConstElementPtr path, ConstElementPtr actions,
|
||||
ElementPtr config) {
|
||||
path_down(path, actions, config, 0);
|
||||
}
|
||||
|
||||
}; // end of namespace isc::yang
|
||||
}; // end of namespace isc
|
87
src/lib/yang/adaptor.h
Normal file
87
src/lib/yang/adaptor.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef ISC_ADAPTOR_H
|
||||
#define ISC_ADAPTOR_H 1
|
||||
|
||||
#include <cc/data.h>
|
||||
|
||||
namespace isc {
|
||||
namespace yang {
|
||||
|
||||
/// @brief JSON adaptor between canonical Kea and Yang models.
|
||||
///
|
||||
/// An adaptor slightly modifies a JSON configuration between canonical Kea
|
||||
/// what required or rendered by a Yang model, e.g. moving a parameter
|
||||
/// to/from a parent.
|
||||
/// The basic adaptor provides a set of tools.
|
||||
class Adaptor {
|
||||
public:
|
||||
|
||||
/// @brief Constructor.
|
||||
Adaptor();
|
||||
|
||||
/// @brief Destructor.
|
||||
virtual ~Adaptor();
|
||||
|
||||
/// @brief Get user context.
|
||||
///
|
||||
/// Get user-context and/or comment and return it with the comment
|
||||
/// if exists moved inside the user-context (without checking if
|
||||
/// there is already a comment as it should never be the case).
|
||||
static isc::data::ConstElementPtr
|
||||
getContext(isc::data::ConstElementPtr parent);
|
||||
|
||||
/// @brief From parent.
|
||||
///
|
||||
/// Move a parameter from the parent to each item in a list.
|
||||
///
|
||||
/// @param name The parameter name.
|
||||
/// @param parent The parent element.
|
||||
/// @param list The children list.
|
||||
static void fromParent(const std::string& name,
|
||||
isc::data::ConstElementPtr parent,
|
||||
isc::data::ConstElementPtr list);
|
||||
|
||||
/// @brief To parent.
|
||||
///
|
||||
/// Move a parameter from children to the parent.
|
||||
///
|
||||
/// @param name The parameter name.
|
||||
/// @param parent The parent element.
|
||||
/// @param list The children list.
|
||||
static void toParent(const std::string& name,
|
||||
isc::data::ElementPtr parent,
|
||||
isc::data::ConstElementPtr list);
|
||||
|
||||
/// @brief Modify.
|
||||
///
|
||||
/// Smart merging tool, e.g. completing a from yang configuration.
|
||||
///
|
||||
/// A modification is a path and actions:
|
||||
/// - path item can be:
|
||||
/// * a string: current scope is a map, go down following the string
|
||||
/// as a key.
|
||||
/// * a number: current scope is a list, go down the number as an index.
|
||||
/// * special value -1: current scope is a list, apply to all items.
|
||||
/// * map { "<key>": <value> }: current scope is a list, go down to
|
||||
/// the item using the key / value pair.
|
||||
/// - an action can be: insert, replace or delete.
|
||||
///
|
||||
/// @param path The search list to follow down to the place to
|
||||
/// apply the action list.
|
||||
/// @param actions The action list
|
||||
/// @param config The configuration (JSON map) to modify.
|
||||
static void modify(isc::data::ConstElementPtr path,
|
||||
isc::data::ConstElementPtr actions,
|
||||
isc::data::ElementPtr config);
|
||||
|
||||
};
|
||||
|
||||
}; // end of namespace isc::yang
|
||||
}; // end of namespace isc
|
||||
|
||||
#endif // ISC_ADAPTOR_H
|
37
src/lib/yang/tests/Makefile.am
Normal file
37
src/lib/yang/tests/Makefile.am
Normal file
@@ -0,0 +1,37 @@
|
||||
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
|
||||
AM_CPPFLAGS += $(BOOST_INCLUDES) $(SYSREPO_CPPFLAGS)
|
||||
AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples\"
|
||||
AM_CXXFLAGS = $(KEA_CXXFLAGS)
|
||||
|
||||
if USE_STATIC_LINK
|
||||
AM_LDFLAGS = -static
|
||||
endif
|
||||
|
||||
CLEANFILES = *.gcno *.gcda
|
||||
|
||||
TESTS_ENVIRONMENT = \
|
||||
$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
|
||||
|
||||
TESTS =
|
||||
if HAVE_GTEST
|
||||
TESTS += run_unittests
|
||||
run_unittests_SOURCES = adaptor_unittests.cc
|
||||
run_unittests_SOURCES += run_unittests.cc
|
||||
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
|
||||
|
||||
run_unittests_LDADD = $(top_builddir)/src/lib/yang/libkea-yang.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
|
||||
run_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
|
||||
run_unittests_LDADD += $(SYSREPO_LIBS) $(GTEST_LDADD)
|
||||
|
||||
endif
|
||||
|
||||
noinst_PROGRAMS = $(TESTS)
|
390
src/lib/yang/tests/adaptor_unittests.cc
Normal file
390
src/lib/yang/tests/adaptor_unittests.cc
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <yang/adaptor.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::data;
|
||||
using namespace isc::yang;
|
||||
|
||||
namespace {
|
||||
|
||||
// Test get context.
|
||||
TEST(AdaptorTest, getContext) {
|
||||
// Empty.
|
||||
string config = "{\n"
|
||||
"}\n";
|
||||
ConstElementPtr json = Element::fromJSON(config);
|
||||
ConstElementPtr context;
|
||||
ASSERT_NO_THROW(context = Adaptor::getContext(json));
|
||||
EXPECT_FALSE(context);
|
||||
|
||||
// No relevant.
|
||||
config = "{\n"
|
||||
" \"foo\": 1\n"
|
||||
"}\n";
|
||||
json = Element::fromJSON(config);
|
||||
ASSERT_NO_THROW(context = Adaptor::getContext(json));
|
||||
EXPECT_FALSE(context);
|
||||
|
||||
// User context.
|
||||
config = "{\n"
|
||||
" \"foo\": 1,\n"
|
||||
" \"user-context\": { \"bar\": 2 }\n"
|
||||
"}\n";
|
||||
json = Element::fromJSON(config);
|
||||
ASSERT_NO_THROW(context = Adaptor::getContext(json));
|
||||
ASSERT_TRUE(context);
|
||||
EXPECT_EQ("{ \"bar\": 2 }", context->str());
|
||||
|
||||
// Comment.
|
||||
config = "{\n"
|
||||
" \"foo\": 1,\n"
|
||||
" \"comment\": \"a comment\"\n"
|
||||
"}\n";
|
||||
json = Element::fromJSON(config);
|
||||
ASSERT_NO_THROW(context = Adaptor::getContext(json));
|
||||
ASSERT_TRUE(context);
|
||||
EXPECT_EQ("{ \"comment\": \"a comment\" }", context->str());
|
||||
|
||||
// User context and comment.
|
||||
config = "{\n"
|
||||
" \"foo\": 1,\n"
|
||||
" \"user-context\": { \"bar\": 2 },\n"
|
||||
" \"comment\": \"a comment\"\n"
|
||||
"}\n";
|
||||
json = Element::fromJSON(config);
|
||||
ASSERT_NO_THROW(context = Adaptor::getContext(json));
|
||||
ASSERT_TRUE(context);
|
||||
EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
|
||||
|
||||
// User context with conflicting comment and comment.
|
||||
config = "{\n"
|
||||
" \"foo\": 1,\n"
|
||||
" \"user-context\": {\n"
|
||||
" \"bar\": 2,\n"
|
||||
" \"comment\": \"conflicting\"\n"
|
||||
" },\n"
|
||||
" \"comment\": \"a comment\"\n"
|
||||
"}\n";
|
||||
json = Element::fromJSON(config);
|
||||
ASSERT_NO_THROW(context = Adaptor::getContext(json));
|
||||
ASSERT_TRUE(context);
|
||||
EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
|
||||
}
|
||||
|
||||
// Test from parent.
|
||||
TEST(AdaptorTest, fromParent) {
|
||||
string config = "{\n"
|
||||
" \"param1\": 123,\n"
|
||||
" \"param2\": \"foo\",\n"
|
||||
" \"list\": [\n"
|
||||
" {\n"
|
||||
" \"param1\": 234\n"
|
||||
" },{\n"
|
||||
" \"another\": \"entry\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n";
|
||||
|
||||
ConstElementPtr json = Element::fromJSON(config);
|
||||
EXPECT_NO_THROW(Adaptor::fromParent("param1", json, json->get("list")));
|
||||
EXPECT_NO_THROW(Adaptor::fromParent("param2", json, json->get("list")));
|
||||
EXPECT_NO_THROW(Adaptor::fromParent("param3", json, json->get("list")));
|
||||
|
||||
string expected = "{\n"
|
||||
" \"param1\": 123,\n"
|
||||
" \"param2\": \"foo\",\n"
|
||||
" \"list\": [\n"
|
||||
" {\n"
|
||||
" \"param1\": 234,\n"
|
||||
" \"param2\": \"foo\"\n"
|
||||
" },{\n"
|
||||
" \"another\": \"entry\",\n"
|
||||
" \"param1\": 123,\n"
|
||||
" \"param2\": \"foo\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n";
|
||||
EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
|
||||
}
|
||||
|
||||
// Test to parent.
|
||||
TEST(AdaptorTest, toParent) {
|
||||
string config = "{\n"
|
||||
" \"list\": [\n"
|
||||
" {\n"
|
||||
" \"param2\": \"foo\",\n"
|
||||
" \"param3\": 234,\n"
|
||||
" \"param4\": true\n"
|
||||
" },{\n"
|
||||
" \"another\": \"entry\",\n"
|
||||
" \"param2\": \"foo\",\n"
|
||||
" \"param3\": 123,\n"
|
||||
" \"param5\": false\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n";
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
EXPECT_NO_THROW(Adaptor::toParent("param1", json, json->get("list")));
|
||||
EXPECT_TRUE(json->equals(*Element::fromJSON(config)));
|
||||
|
||||
string expected = "{\n"
|
||||
" \"param2\": \"foo\",\n"
|
||||
" \"list\": [\n"
|
||||
" {\n"
|
||||
" \"param3\": 234,\n"
|
||||
" \"param4\": true\n"
|
||||
" },{\n"
|
||||
" \"another\": \"entry\",\n"
|
||||
" \"param3\": 123,\n"
|
||||
" \"param5\": false\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n";
|
||||
|
||||
EXPECT_NO_THROW(Adaptor::toParent("param2",json, json->get("list")));
|
||||
EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
|
||||
|
||||
// param1 has different value so it should throw.
|
||||
EXPECT_THROW(Adaptor::toParent("param3",json, json->get("list")),
|
||||
BadValue);
|
||||
EXPECT_THROW(Adaptor::toParent("param4",json, json->get("list")),
|
||||
BadValue);
|
||||
EXPECT_THROW(Adaptor::toParent("param5",json, json->get("list")),
|
||||
BadValue);
|
||||
// And not modify the value.
|
||||
EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
|
||||
}
|
||||
|
||||
// Test for modify (maps & insert).
|
||||
TEST(AdaptorTest, modifyMapInsert) {
|
||||
string config = "{\n"
|
||||
" \"foo\": {\n"
|
||||
" \"bar\": {\n"
|
||||
"}}}\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
string spath = "[ \"foo\", \"bar\" ]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"insert\",\n"
|
||||
" \"key\": \"test\",\n"
|
||||
" \"value\": 1234\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "{\n"
|
||||
" \"foo\": {\n"
|
||||
" \"bar\": {\n"
|
||||
" \"test\": 1234\n"
|
||||
"}}}\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
// Test for modify (maps & replace).
|
||||
TEST(AdaptorTest, modifyMapReplace) {
|
||||
string config = "{\n"
|
||||
" \"foo\": {\n"
|
||||
" \"bar\": {\n"
|
||||
" \"test1\": 1234,\n"
|
||||
" \"test2\": 1234\n"
|
||||
"}}}\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
string spath = "[ \"foo\", \"bar\" ]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"insert\",\n"
|
||||
" \"key\": \"test1\",\n"
|
||||
" \"value\": 5678\n"
|
||||
"},{\n"
|
||||
" \"action\": \"replace\",\n"
|
||||
" \"key\": \"test2\",\n"
|
||||
" \"value\": 5678\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "{\n"
|
||||
" \"foo\": {\n"
|
||||
" \"bar\": {\n"
|
||||
" \"test1\": 1234,\n"
|
||||
" \"test2\": 5678\n"
|
||||
"}}}\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
// Test for modify (maps & delete).
|
||||
TEST(AdaptorTest, modifyMapDelete) {
|
||||
string config = "{\n"
|
||||
" \"foo\": {\n"
|
||||
" \"bar\": {\n"
|
||||
" \"test\": 1234\n"
|
||||
"}}}\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
string spath = "[ \"foo\", \"bar\" ]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"delete\",\n"
|
||||
" \"last\": \"test\"\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "{\n"
|
||||
" \"foo\": {\n"
|
||||
" \"bar\": {\n"
|
||||
"}}}\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
// Test for modify (lists & insert).
|
||||
TEST(AdaptorTest, modifyListInsert) {
|
||||
string config = "[\n"
|
||||
"[{\n"
|
||||
" \"foo\": \"bar\"\n"
|
||||
"}]]\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
string spath = "[ 0, { \"key\": \"foo\", \"value\": \"bar\" }]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"insert\",\n"
|
||||
" \"key\": \"test\",\n"
|
||||
" \"value\": 1234\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "[\n"
|
||||
"[{\n"
|
||||
" \"foo\": \"bar\",\n"
|
||||
" \"test\": 1234\n"
|
||||
"}]]\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
// Test for modify (list all & insert).
|
||||
TEST(AdaptorTest, modifyListAllInsert) {
|
||||
string config = "[\n"
|
||||
"{},\n"
|
||||
"{},\n"
|
||||
"{ \"test\": 1234 },\n"
|
||||
"]\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
string spath = "[ -1 ]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"insert\",\n"
|
||||
" \"key\": \"test\",\n"
|
||||
" \"value\": 5678\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "[\n"
|
||||
"{ \"test\": 5678 },\n"
|
||||
"{ \"test\": 5678 },\n"
|
||||
"{ \"test\": 1234 }\n"
|
||||
"]\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
TEST(AdaptorTest, modifyListDelete) {
|
||||
string config = "[[\n"
|
||||
"{\n"
|
||||
" \"foo\": \"bar\"\n"
|
||||
"},{\n"
|
||||
"},[\n"
|
||||
"0, 1, 2, 3\n"
|
||||
"]]]\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
string spath = "[ 0 ]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
// Put the positional first as it applies after previous actions...
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"delete\",\n"
|
||||
" \"last\": 2\n"
|
||||
"},{\n"
|
||||
" \"action\": \"delete\",\n"
|
||||
" \"last\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "[[{}]]\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
TEST(AdaptorTest, modifyListAllDelete) {
|
||||
string config = "[[\n"
|
||||
"{\n"
|
||||
" \"foo\": \"bar\"\n"
|
||||
"},{\n"
|
||||
"},[\n"
|
||||
"0, 1, 2, 3\n"
|
||||
"]]]\n";
|
||||
ElementPtr json;
|
||||
ASSERT_NO_THROW(json = Element::fromJSON(config));
|
||||
// The only change from the previous unit test is 0 -> -1.
|
||||
string spath = "[ -1 ]";
|
||||
ConstElementPtr path;
|
||||
ASSERT_NO_THROW(path = Element::fromJSON(spath));
|
||||
// Put the positional first as it applies after previous actions...
|
||||
string sactions = "[\n"
|
||||
"{\n"
|
||||
" \"action\": \"delete\",\n"
|
||||
" \"last\": 2\n"
|
||||
"},{\n"
|
||||
" \"action\": \"delete\",\n"
|
||||
" \"last\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
|
||||
"}]\n";
|
||||
ConstElementPtr actions;
|
||||
ASSERT_NO_THROW(actions = Element::fromJSON(sactions));
|
||||
string result = "[[{}]]\n";
|
||||
ConstElementPtr expected;
|
||||
ASSERT_NO_THROW(expected = Element::fromJSON(result));
|
||||
ASSERT_NO_THROW(Adaptor::modify(path, actions, json));
|
||||
EXPECT_TRUE(expected->equals(*json));
|
||||
}
|
||||
|
||||
}; // end of anonymous namespace
|
20
src/lib/yang/tests/run_unittests.cc
Normal file
20
src/lib/yang/tests/run_unittests.cc
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <util/unittests/run_all.h>
|
||||
#include <log/logger_support.h>
|
||||
|
||||
int
|
||||
main(int argc, char* argv[]) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
isc::log::initLogger();
|
||||
|
||||
return (isc::util::unittests::run_all());
|
||||
}
|
Reference in New Issue
Block a user