Add copy command
Change-Id: I0a88f7fa9a096022dd95e8af8854f980ca34f043
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index ddaac35..f039ea5 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -163,8 +163,23 @@
boost::variant<schemaPath_, dataPath_> m_path;
};
+struct copy_ : x3::position_tagged {
+ static constexpr auto name = "copy";
+ static constexpr auto shortHelp = "copy - copy configuration datastores around";
+ static constexpr auto longHelp = R"(
+ copy <source> <destination>
+
+ Usage:
+ /> copy running startup
+ /> copy startup running)";
+ bool operator==(const copy_& b) const;
+
+ Datastore m_source;
+ Datastore m_destination;
+};
+
struct help_;
-using CommandTypes = boost::mpl::vector<cd_, commit_, create_, delete_, describe_, discard_, get_, help_, ls_, set_>;
+using CommandTypes = boost::mpl::vector<cd_, commit_, copy_, create_, delete_, describe_, discard_, get_, help_, ls_, set_>;
struct help_ : x3::position_tagged {
static constexpr auto name = "help";
static constexpr auto shortHelp = "help - Print help for commands.";
@@ -209,3 +224,4 @@
BOOST_FUSION_ADAPT_STRUCT(help_, m_cmd)
BOOST_FUSION_ADAPT_STRUCT(discard_)
BOOST_FUSION_ADAPT_STRUCT(get_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(copy_, m_source, m_destination)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 667cb83..714657e 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -420,6 +420,8 @@
struct get_class;
+struct copy_class;
+
struct command_class {
template <typename Iterator, typename Exception, typename Context>
x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
diff --git a/src/ast_values.hpp b/src/ast_values.hpp
index b79ac84..f2d1c7e 100644
--- a/src/ast_values.hpp
+++ b/src/ast_values.hpp
@@ -54,6 +54,11 @@
SpecialValue m_value;
};
+enum class Datastore {
+ Running,
+ Startup
+};
+
std::string specialValueToString(const special_& value);
using leaf_data_ = boost::variant<enum_,
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index 6686b92..e70d9c9 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -54,6 +54,7 @@
virtual void commitChanges() = 0;
virtual void discardChanges() = 0;
+ virtual void copyConfig(const Datastore source, const Datastore destination) = 0;
private:
friend class DataQuery;
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 9e4e35f..66fcfb3 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -36,7 +36,7 @@
x3::rule<leaf_path_class, dataPath_> const leafPath = "leafPath";
x3::rule<presenceContainerPath_class, dataPath_> const presenceContainerPath = "presenceContainerPath";
x3::rule<listInstancePath_class, dataPath_> const listInstancePath = "listInstancePath";
-x3::rule<space_separator_class, x3::unused_type> const space_separator = "space_separator";
+x3::rule<space_separator_class, x3::unused_type> const space_separator = "a space";
x3::rule<discard_class, discard_> const discard = "discard";
x3::rule<ls_class, ls_> const ls = "ls";
@@ -48,6 +48,7 @@
x3::rule<commit_class, commit_> const commit = "commit";
x3::rule<describe_class, describe_> const describe = "describe";
x3::rule<help_class, help_> const help = "help";
+x3::rule<copy_class, copy_> const copy = "copy";
x3::rule<command_class, command_> const command = "command";
x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
@@ -217,6 +218,55 @@
auto const help_def =
help_::name > createCommandSuggestions >> -command_names;
+struct datastore_symbol_table : x3::symbols<Datastore> {
+ datastore_symbol_table()
+ {
+ add
+ ("running", Datastore::Running)
+ ("startup", Datastore::Startup);
+ }
+} const datastore;
+
+auto copy_source = x3::rule<class source, Datastore>{"source datastore"} = datastore;
+auto copy_destination = x3::rule<class source, Datastore>{"destination datastore"} = datastore;
+
+const auto datastoreSuggestions = x3::eps[([](auto& ctx) {
+ auto& parserContext = x3::get<parser_context_tag>(ctx);
+ parserContext.m_suggestions = {Completion{"running", " "}, Completion{"startup", " "}};
+ parserContext.m_completionIterator = _where(ctx).begin();
+})];
+
+struct copy_args : x3::parser<copy_args> {
+ using attribute_type = copy_;
+ template <typename It, typename Ctx, typename RCtx>
+ bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, copy_& attr) const
+ {
+ auto& parserContext = x3::get<parser_context_tag>(ctx);
+ auto iterBeforeDestination = begin;
+ auto save_iter = x3::no_skip[x3::eps[([&iterBeforeDestination](auto& ctx) {iterBeforeDestination = _where(ctx).begin();})]];
+ auto grammar = datastoreSuggestions > copy_source > space_separator > datastoreSuggestions > save_iter > copy_destination;
+
+ try {
+ grammar.parse(begin, end, ctx, rctx, attr);
+ } catch (x3::expectation_failure<It>& ex) {
+ using namespace std::string_literals;
+ parserContext.m_errorMsg = "Expected "s + ex.which() + " here:";
+ throw;
+ }
+
+ if (attr.m_source == attr.m_destination) {
+ begin = iterBeforeDestination; // Restoring the iterator here makes the error caret point to the second datastore
+ parserContext.m_errorMsg = "Source datastore and destination datastore can't be the same.";
+ return false;
+ }
+
+ return true;
+ }
+} copy_args;
+
+auto const copy_def =
+ copy_::name > space_separator > copy_args;
+
auto const describe_def =
describe_::name >> space_separator > (dataPathListEnd | dataPath | schemaPath);
@@ -224,7 +274,7 @@
x3::eps;
auto const command_def =
- createCommandSuggestions >> x3::expect[cd | create | delete_rule | set | commit | get | ls | discard | describe | help];
+ createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help];
#if __clang__
#pragma GCC diagnostic pop
@@ -263,6 +313,7 @@
BOOST_SPIRIT_DEFINE(delete_rule)
BOOST_SPIRIT_DEFINE(describe)
BOOST_SPIRIT_DEFINE(help)
+BOOST_SPIRIT_DEFINE(copy)
BOOST_SPIRIT_DEFINE(command)
BOOST_SPIRIT_DEFINE(createPathSuggestions)
BOOST_SPIRIT_DEFINE(createKeySuggestions)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index ca227be..1cfef74 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -80,6 +80,11 @@
std::cout << it << std::endl;
}
+void Interpreter::operator()(const copy_& copy) const
+{
+ m_datastore.copyConfig(copy.m_source, copy.m_destination);
+}
+
std::string Interpreter::buildTypeInfo(const std::string& path) const
{
std::ostringstream ss;
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index a05183f..0dfe2b6 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -25,6 +25,7 @@
void operator()(const describe_&) const;
void operator()(const discard_&) const;
void operator()(const help_&) const;
+ void operator()(const copy_& copy) const;
private:
template <typename T>
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 63045f0..d824324 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -381,6 +381,15 @@
}
}
+void Session::copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination)
+{
+ auto rpc = impl::guarded(nc_rpc_copy(destination, nullptr, source, nullptr, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST));
+ if (!rpc) {
+ throw std::runtime_error("Cannot create copy-config RPC");
+ }
+ impl::do_rpc_ok(this, std::move(rpc));
+}
+
ReportedError::ReportedError(const std::string& what)
: std::runtime_error(what)
{
diff --git a/src/netconf-client.hpp b/src/netconf-client.hpp
index 143f200..2db21c9 100644
--- a/src/netconf-client.hpp
+++ b/src/netconf-client.hpp
@@ -45,6 +45,7 @@
const std::string& data);
void copyConfigFromString(const NC_DATASTORE target, const std::string& data);
std::shared_ptr<libyang::Data_Node> rpc(const std::string& xmlData);
+ void copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination);
void commit();
void discard();
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index f744dc6..5fca200 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -149,6 +149,22 @@
return res;
}
+NC_DATASTORE toNcDatastore(Datastore datastore)
+{
+ switch (datastore) {
+ case Datastore::Running:
+ return NC_DATASTORE_RUNNING;
+ case Datastore::Startup:
+ return NC_DATASTORE_STARTUP;
+ }
+ __builtin_unreachable();
+}
+
+void NetconfAccess::copyConfig(const Datastore source, const Datastore destination)
+{
+ m_session->copyConfig(toNcDatastore(source), toNcDatastore(destination));
+}
+
std::string NetconfAccess::fetchSchema(const std::string_view module, const
std::optional<std::string_view> revision, const
std::optional<std::string_view> submodule, const
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index aa13312..107fcd3 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -42,6 +42,7 @@
void commitChanges() override;
void discardChanges() override;
Tree executeRpc(const std::string& path, const Tree& input) override;
+ void copyConfig(const Datastore source, const Datastore destination) override;
std::shared_ptr<Schema> schema() override;
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index b681893..2d1a1b4 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -267,6 +267,25 @@
return res;
}
+sr_datastore_t toSrDatastore(Datastore datastore)
+{
+ switch (datastore) {
+ case Datastore::Running:
+ return SR_DS_RUNNING;
+ case Datastore::Startup:
+ return SR_DS_STARTUP;
+ }
+ __builtin_unreachable();
+}
+
+void SysrepoAccess::copyConfig(const Datastore source, const Datastore destination)
+{
+ m_session->copy_config(nullptr, toSrDatastore(source), toSrDatastore(destination));
+ if (destination == Datastore::Running) {
+ m_session->refresh();
+ }
+}
+
std::string SysrepoAccess::fetchSchema(const char* module, const char* revision, const char* submodule)
{
std::string schema;
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index cebb564..da5fb75 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -40,6 +40,7 @@
void commitChanges() override;
void discardChanges() override;
+ void copyConfig(const Datastore source, const Datastore destination) override;
private:
std::vector<std::map<std::string, leaf_data_>> listInstances(const std::string& path) override;
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
index eb4949a..607b8ab 100644
--- a/tests/command_completion.cpp
+++ b/tests/command_completion.cpp
@@ -22,21 +22,21 @@
SECTION("")
{
input = "";
- expectedCompletions = {"cd", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe"};
+ expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe"};
expectedContextLength = 0;
}
SECTION(" ")
{
input = " ";
- expectedCompletions = {"cd", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe"};
+ expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe"};
expectedContextLength = 0;
}
SECTION("c")
{
input = "c";
- expectedCompletions = {"cd", "commit", "create"};
+ expectedCompletions = {"cd", "commit", "copy", "create"};
expectedContextLength = 1;
}
@@ -68,5 +68,12 @@
expectedContextLength = 6;
}
+ SECTION("copy datastores")
+ {
+ input = "copy ";
+ expectedCompletions = {"running", "startup"};
+ expectedContextLength = 0;
+ }
+
REQUIRE(parser.completeCommand(input, errorStream) == (Completions{expectedCompletions, expectedContextLength}));
}
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index a6cadc3..a1f9322 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -336,6 +336,20 @@
}
+ SECTION("copying data from startup refreshes the data")
+ {
+ {
+ REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
+ REQUIRE_CALL(mock, write("/example-schema:leafInt16", std::nullopt, "123"s));
+ datastore.setLeaf("/example-schema:leafInt16", int16_t{123});
+ datastore.commitChanges();
+ }
+ REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{{"/example-schema:leafInt16", int16_t{123}}});
+ REQUIRE_CALL(mock, write("/example-schema:leafInt16", "123"s, std::nullopt));
+ datastore.copyConfig(Datastore::Startup, Datastore::Running);
+ REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
+ }
+
waitForCompletionAndBitMore(seq1);
}
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index e9dc870..8798973 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -35,6 +35,7 @@
IMPLEMENT_MOCK0(commitChanges);
IMPLEMENT_MOCK0(discardChanges);
+ IMPLEMENT_MOCK2(copyConfig);
};