summaryrefslogtreecommitdiffstats
path: root/src/bin/netconf/netconf.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/netconf/netconf.cc
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/netconf/netconf.cc')
-rw-r--r--src/bin/netconf/netconf.cc745
1 files changed, 745 insertions, 0 deletions
diff --git a/src/bin/netconf/netconf.cc b/src/bin/netconf/netconf.cc
new file mode 100644
index 0000000..c515dec
--- /dev/null
+++ b/src/bin/netconf/netconf.cc
@@ -0,0 +1,745 @@
+// Copyright (C) 2018-2023 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/.
+
+/// @file netconf.cc
+/// Contains the Netconf agent methods.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <netconf/netconf.h>
+#include <netconf/netconf_controller.h>
+#include <netconf/netconf_log.h>
+#include <yang/translator_config.h>
+#include <yang/yang_revisions.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::netconf;
+using namespace isc::yang;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Module change subscription callback.
+class NetconfAgentCallback {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param service_pair The service name and configuration pair.
+ NetconfAgentCallback(const CfgServersMapPair& service_pair)
+ : service_pair_(service_pair) {
+ }
+
+ /// @brief Server name and configuration pair.
+ CfgServersMapPair const service_pair_;
+
+ /// @brief Module configuration change callback.
+ ///
+ /// This callback is called by sysrepo when there is a change to
+ /// module configuration.
+ ///
+ /// @param sess The running datastore session.
+ /// @param module_name The module name.
+ /// @param event The event.
+ /// @param private_ctx The private context.
+ /// @return the sysrepo return code.
+ sysrepo::ErrorCode module_change(Session sess,
+ uint32_t /* subscription_id */,
+ string_view module_name,
+ optional<string_view> /* sub_xpath */,
+ Event event,
+ uint32_t /* request_id */) {
+ ostringstream event_type;
+ switch (event) {
+ case Event::Update:
+ // This could potentially be a hook point for mid-flight
+ // configuration changes.
+ event_type << "Event::Update";
+ break;
+ case Event::Change:
+ event_type << "Event::Change";
+ break;
+ case Event::Done:
+ event_type << "Event::Done";
+ break;
+ case Event::Abort:
+ event_type << "Event::Abort";
+ break;
+ case Event::Enabled:
+ event_type << "Event::Enabled";
+ break;
+ case Event::RPC:
+ event_type << "Event::RPC";
+ break;
+ default:
+ event_type << "UNKNOWN (" << event << ")";
+ break;
+ }
+ LOG_INFO(netconf_logger, NETCONF_CONFIG_CHANGE_EVENT)
+ .arg(event_type.str());
+ NetconfAgent::logChanges(sess, module_name);
+ switch (event) {
+ case Event::Change:
+ return (NetconfAgent::change(sess, service_pair_));
+ case Event::Done:
+ return (NetconfAgent::done(sess, service_pair_));
+ default:
+ return (sysrepo::ErrorCode::Ok);
+ }
+ }
+
+ void event_notif(Session /* session */,
+ uint32_t /* subscription_id */,
+ NotificationType const notification_type,
+ optional<DataNode> const notification_tree,
+ NotificationTimeStamp const /* timestamp */) {
+ string n;
+ switch (notification_type) {
+ case NotificationType::Realtime:
+ n = "NotificationType::Realtime";
+ break;
+ case NotificationType::Replay:
+ n = "NotificationType::Replay";
+ break;
+ case NotificationType::ReplayComplete:
+ n = "NotificationType::ReplayComplete";
+ break;
+ case NotificationType::Terminated:
+ n = "NotificationType::Terminated";
+ break;
+ case NotificationType::Modified:
+ n = "NotificationType::Modified";
+ break;
+ case NotificationType::Suspended:
+ n = "NotificationType::Suspended";
+ break;
+ case NotificationType::Resumed:
+ n = "NotificationType::Resumed";
+ break;
+ }
+
+ optional<string> const str(
+ notification_tree->printStr(DataFormat::JSON, PrintFlags::WithDefaultsExplicit));
+ string const tree(str ? *str : string());
+ LOG_INFO(netconf_logger, NETCONF_NOTIFICATION_RECEIVED)
+ .arg(n)
+ .arg(service_pair_.first)
+ .arg(tree);
+ }
+}; // NetconfAgentCallback
+
+} //anonymous namespace
+
+namespace isc {
+namespace netconf {
+
+NetconfAgent::~NetconfAgent() {
+ clear();
+}
+
+void
+NetconfAgent::init(NetconfCfgMgrPtr cfg_mgr) {
+ // Check for a configuration manager.
+ if (!cfg_mgr) {
+ isc_throw(Unexpected, "missing configuration manager");
+ return;
+ }
+
+ // Retrieve configuration from existing running DHCP daemons.
+ const CfgServersMapPtr& servers =
+ cfg_mgr->getNetconfConfig()->getCfgServersMap();
+ for (auto const& pair : *servers) {
+ keaConfig(pair);
+ }
+
+ // Initialize sysrepo.
+ initSysrepo();
+
+ // Check modules / revisions.
+ checkModules(servers);
+
+ for (auto const& pair : *servers) {
+ yangConfig(pair);
+ subscribeToDataChanges(pair);
+ subscribeToNotifications(pair);
+ }
+}
+
+void
+NetconfAgent::clear() {
+ subscriptions_.clear();
+ running_sess_.reset();
+ startup_sess_.reset();
+}
+
+void
+NetconfAgent::keaConfig(const CfgServersMapPair& service_pair) {
+ string const& server(service_pair.first);
+ CfgServerPtr const& configuration(service_pair.second);
+
+ // If the boot-update flag is not set.
+ if (!configuration->getBootUpdate()) {
+ return;
+ }
+ CfgControlSocketPtr ctrl_sock = configuration->getCfgControlSocket();
+ if (!ctrl_sock) {
+ return;
+ }
+ ControlSocketBasePtr comm;
+ try {
+ comm = controlSocketFactory(ctrl_sock);
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "createControlSocket failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_GET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ ConstElementPtr answer;
+ int rcode;
+ ConstElementPtr config;
+ LOG_INFO(netconf_logger, NETCONF_GET_CONFIG_STARTED)
+ .arg(server);
+ try {
+ answer = comm->configGet(server);
+ config = parseAnswer(rcode, answer);
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "config-get command failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_GET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ if (rcode != CONTROL_RESULT_SUCCESS) {
+ ostringstream msg;
+ msg << "config-get command returned " << answerToText(answer);
+ LOG_ERROR(netconf_logger, NETCONF_GET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ if (!config) {
+ LOG_ERROR(netconf_logger, NETCONF_GET_CONFIG_FAILED)
+ .arg(server)
+ .arg("config-get command returned an empty configuration");
+ return;
+ }
+ LOG_DEBUG(netconf_logger, NETCONF_DBG_TRACE_DETAIL_DATA,
+ NETCONF_GET_CONFIG)
+ .arg(server)
+ .arg(prettyPrint(config));
+}
+
+void
+NetconfAgent::initSysrepo() {
+ try {
+ running_sess_ = Connection{}.sessionStart();
+ running_sess_->switchDatastore(Datastore::Running);
+ startup_sess_ = Connection{}.sessionStart();
+ startup_sess_->switchDatastore(Datastore::Startup);
+ } catch (exception const& ex) {
+ isc_throw(Unexpected, "Can't establish a sysrepo session: "
+ << ex.what());
+ }
+
+ // Retrieve names and revisions of installed modules from sysrepo.
+ getModules();
+}
+
+void NetconfAgent::getModules() {
+ vector<Module> modules;
+ try {
+ Context context(running_sess_->getContext());
+ modules = context.modules();
+ } catch (Error const& ex) {
+ isc_throw(Unexpected, "can't retrieve available modules: " << ex.what());
+ }
+
+ for (Module const& module : modules) {
+ string const name(module.name());
+ if (!module.revision()) {
+ isc_throw(Unexpected,
+ "could not retrieve module revision for module " << name);
+ }
+ string const revision(*module.revision());
+ modules_.emplace(name, revision);
+ }
+}
+
+bool
+NetconfAgent::checkModule(const string& module_name) const {
+ auto module = modules_.find(module_name);
+ if (module == modules_.end()) {
+ LOG_ERROR(netconf_logger, NETCONF_MODULE_MISSING_ERR)
+ .arg(module_name);
+ return (false);
+ }
+ auto modrev = YANG_REVISIONS.find(module_name);
+ if (modrev == YANG_REVISIONS.end()) {
+ // Can't check revision?!
+ // It can happen only with a module which is not in
+ // YANG_REVISIONS but installed so likely on purpose.
+ return (true);
+ }
+ if (modrev->second != module->second) {
+ LOG_ERROR(netconf_logger, NETCONF_MODULE_REVISION_ERR)
+ .arg(module_name)
+ .arg(modrev->second)
+ .arg(module->second);
+ return (false);
+ }
+ return (true);
+}
+
+void
+NetconfAgent::checkModules(CfgServersMapPtr const& servers /* = {} */) const {
+ bool faulty_model(false);
+ if (servers) {
+ for (auto pair : *servers) {
+ if (!checkModule(pair.second->getModel())) {
+ faulty_model = true;
+ }
+ }
+ }
+
+ if (faulty_model) {
+ isc_throw(Unexpected, "YANG module is missing or its revision is not "
+ "supported. Check logs for details.");
+ }
+
+ for (auto modrev : YANG_REVISIONS) {
+ auto module = modules_.find(modrev.first);
+ if (module == modules_.end()) {
+ LOG_WARN(netconf_logger, NETCONF_MODULE_MISSING_WARN)
+ .arg(modrev.first);
+ continue;
+ }
+ if (modrev.second != module->second) {
+ LOG_WARN(netconf_logger, NETCONF_MODULE_REVISION_WARN)
+ .arg(modrev.first)
+ .arg(modrev.second)
+ .arg(module->second);
+ }
+ }
+}
+
+void
+NetconfAgent::yangConfig(const CfgServersMapPair& service_pair) {
+ string const& server(service_pair.first);
+ CfgServerPtr const& configuration(service_pair.second);
+
+ // If we're shutting down, or the boot-update flag is not set or the model
+ // associated with it is not specified.
+ if (!configuration->getBootUpdate() ||
+ configuration->getModel().empty()) {
+ return;
+ }
+
+ // First we need a way to reach the actual servers.
+ CfgControlSocketPtr ctrl_sock = configuration->getCfgControlSocket();
+ if (!ctrl_sock) {
+ return;
+ }
+
+ LOG_INFO(netconf_logger, NETCONF_SET_CONFIG_STARTED)
+ .arg(server);
+ ElementPtr config;
+ try {
+ // Retrieve configuration from Sysrepo.
+ TranslatorConfig tc(*startup_sess_, configuration->getModel());
+ config = tc.getConfig();
+ if (!config) {
+ ostringstream msg;
+ msg << "YANG configuration for "
+ << configuration->getModel()
+ << " is empty";
+ LOG_ERROR(netconf_logger, NETCONF_SET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ } else {
+ LOG_DEBUG(netconf_logger, NETCONF_DBG_TRACE_DETAIL_DATA,
+ NETCONF_SET_CONFIG)
+ .arg(server)
+ .arg(prettyPrint(config));
+ }
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "get YANG configuration for " << server
+ << " failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_SET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ ControlSocketBasePtr comm;
+ try {
+ comm = controlSocketFactory(ctrl_sock);
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "control socket creation failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_SET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ ConstElementPtr answer;
+ int rcode;
+ try {
+ answer = comm->configSet(config, server);
+ parseAnswer(rcode, answer);
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "config-set command failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_SET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ if (rcode != CONTROL_RESULT_SUCCESS) {
+ ostringstream msg;
+ msg << "config-set command returned " << answerToText(answer);
+ LOG_ERROR(netconf_logger, NETCONF_SET_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return;
+ }
+ LOG_INFO(netconf_logger, NETCONF_BOOT_UPDATE_COMPLETED)
+ .arg(server);
+}
+
+void
+NetconfAgent::subscribeToDataChanges(const CfgServersMapPair& service_pair) {
+ string const& server(service_pair.first);
+ CfgServerPtr const& configuration(service_pair.second);
+ string const& model(configuration->getModel());
+
+ // If we're shutting down, or the subscribe-changes flag is not set or
+ // the model is not specified, give up on subscribing.
+ if (!configuration->getSubscribeChanges() || model.empty()) {
+ return;
+ }
+ LOG_INFO(netconf_logger, NETCONF_SUBSCRIBE_CONFIG)
+ .arg(server)
+ .arg(model);
+ auto callback = [=](Session session,
+ uint32_t subscription_id,
+ string_view module_name,
+ optional<string_view> sub_xpath,
+ Event event,
+ uint32_t request_id) {
+ NetconfAgentCallback agent(service_pair);
+ return agent.module_change(session, subscription_id, module_name, sub_xpath, event, request_id);
+ };
+ try {
+ SubscribeOptions options(SubscribeOptions::Default);
+ if (!configuration->getValidateChanges()) {
+ options = options | SubscribeOptions::DoneOnly;
+ }
+ Subscription subscription(
+ running_sess_->onModuleChange(model, callback, nullopt, 0, options));
+ subscriptions_.emplace(server, std::forward<Subscription>(subscription));
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "module change subscribe failed with " << ex.what();
+ msg << "change subscription for model " << model <<
+ " failed with: " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_SUBSCRIBE_CONFIG_FAILED)
+ .arg(server)
+ .arg(configuration->getModel())
+ .arg(msg.str());
+ return;
+ }
+}
+
+void
+NetconfAgent::subscribeToNotifications(const CfgServersMapPair& service_pair) {
+ string const& server(service_pair.first);
+ CfgServerPtr const& configuration(service_pair.second);
+ string const& model(configuration->getModel());
+
+ // If we're shutting down, or the subscribe-changes flag is not set or
+ // the model is not specified, give up on subscribing.
+ if (!configuration->getSubscribeNotifications() || model.empty()) {
+ return;
+ }
+ LOG_INFO(netconf_logger, NETCONF_SUBSCRIBE_NOTIFICATIONS)
+ .arg(server)
+ .arg(model);
+
+ auto callback = [=](Session session,
+ uint32_t subscription_id,
+ NotificationType const notification_type,
+ optional<DataNode> const notification_tree,
+ NotificationTimeStamp const timestamp) {
+ NetconfAgentCallback agent(service_pair);
+ return agent.event_notif(session, subscription_id, notification_type, notification_tree, timestamp);
+ };
+ try {
+ Subscription subscription(running_sess_->onNotification(model, callback));
+ subscriptions_.emplace(server, std::forward<Subscription>(subscription));
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "event notification subscription for model " << model <<
+ " failed with: " << ex.what();
+ LOG_WARN(netconf_logger, NETCONF_NOT_SUBSCRIBED_TO_NOTIFICATIONS)
+ .arg(server)
+ .arg(configuration->getModel())
+ .arg(msg.str());
+ return;
+ }
+}
+
+sysrepo::ErrorCode
+NetconfAgent::change(Session sess, const CfgServersMapPair& service_pair) {
+ string const& server(service_pair.first);
+ CfgServerPtr const& configuration(service_pair.second);
+
+ // If we're shutting down, or the subscribe-changes or the
+ // validate-changes flag is not set or the model associated with
+ // it is not specified.
+ if (!configuration->getSubscribeChanges() ||
+ !configuration->getValidateChanges() ||
+ configuration->getModel().empty()) {
+ return (sysrepo::ErrorCode::Ok);
+ }
+ CfgControlSocketPtr ctrl_sock = configuration->getCfgControlSocket();
+ if (!ctrl_sock) {
+ return (sysrepo::ErrorCode::Ok);
+ }
+ LOG_INFO(netconf_logger, NETCONF_VALIDATE_CONFIG_STARTED)
+ .arg(server);
+ ElementPtr config;
+ try {
+ TranslatorConfig tc(sess, configuration->getModel());
+ config = tc.getConfig();
+ if (!config) {
+ ostringstream msg;
+ msg << "YANG configuration for "
+ << configuration->getModel()
+ << " is empty";
+ LOG_ERROR(netconf_logger, NETCONF_VALIDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::OperationFailed);
+ } else {
+ LOG_DEBUG(netconf_logger, NETCONF_DBG_TRACE_DETAIL_DATA,
+ NETCONF_VALIDATE_CONFIG)
+ .arg(server)
+ .arg(prettyPrint(config));
+ }
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "get YANG configuration for " << server
+ << " failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_VALIDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ }
+ ControlSocketBasePtr comm;
+ try {
+ comm = controlSocketFactory(ctrl_sock);
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "createControlSocket failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_VALIDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::Ok);
+ }
+ ConstElementPtr answer;
+ int rcode;
+ try {
+ answer = comm->configTest(config, server);
+ parseAnswer(rcode, answer);
+ } catch (exception const& ex) {
+ stringstream msg;
+ msg << "configTest failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_VALIDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ }
+ if (rcode != CONTROL_RESULT_SUCCESS) {
+ stringstream msg;
+ msg << "configTest returned " << answerToText(answer);
+ LOG_ERROR(netconf_logger, NETCONF_VALIDATE_CONFIG_REJECTED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ }
+ LOG_INFO(netconf_logger, NETCONF_VALIDATE_CONFIG_COMPLETED)
+ .arg(server);
+ return (sysrepo::ErrorCode::Ok);
+}
+
+sysrepo::ErrorCode
+NetconfAgent::done(Session sess, const CfgServersMapPair& service_pair) {
+ string const& server(service_pair.first);
+ CfgServerPtr const& configuration(service_pair.second);
+
+ // Check if we should and can process this update.
+ if (!configuration->getSubscribeChanges() ||
+ configuration->getModel().empty()) {
+ return (sysrepo::ErrorCode::Ok);
+ }
+ CfgControlSocketPtr ctrl_sock = configuration->getCfgControlSocket();
+ if (!ctrl_sock) {
+ return (sysrepo::ErrorCode::Ok);
+ }
+
+ // All looks good, let's get started. Print an info that we're about
+ // to update the configuration.
+ LOG_INFO(netconf_logger, NETCONF_UPDATE_CONFIG_STARTED)
+ .arg(server);
+
+ // Retrieve the configuration from SYSREPO first.
+ ElementPtr config;
+ try {
+ TranslatorConfig tc(sess, configuration->getModel());
+ config = tc.getConfig();
+ if (!config) {
+ ostringstream msg;
+ msg << "YANG configuration for "
+ << configuration->getModel()
+ << " is empty";
+ LOG_ERROR(netconf_logger, NETCONF_UPDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ } else {
+ LOG_DEBUG(netconf_logger, NETCONF_DBG_TRACE_DETAIL_DATA,
+ NETCONF_UPDATE_CONFIG)
+ .arg(server)
+ .arg(prettyPrint(config));
+ }
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "get YANG configuration for " << server
+ << " failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_UPDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ }
+
+ // Ok, now open the control socket. We need this to send the config to
+ // the server.
+ ControlSocketBasePtr comm;
+ try {
+ comm = controlSocketFactory(ctrl_sock);
+ } catch (exception const& ex) {
+ ostringstream msg;
+ msg << "createControlSocket failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_UPDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::Ok);
+ }
+
+ // Now apply the config using config-set command.
+ ConstElementPtr answer;
+ int rcode;
+ try {
+ answer = comm->configSet(config, server);
+ parseAnswer(rcode, answer);
+ } catch (exception const& ex) {
+ stringstream msg;
+ msg << "configSet failed with " << ex.what();
+ LOG_ERROR(netconf_logger, NETCONF_UPDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ }
+
+ // rcode == CONTROL_RESULT_SUCCESS, unless the docs say otherwise :).
+ if (rcode != CONTROL_RESULT_SUCCESS) {
+ stringstream msg;
+ msg << "configSet returned " << answerToText(answer);
+ LOG_ERROR(netconf_logger, NETCONF_UPDATE_CONFIG_FAILED)
+ .arg(server)
+ .arg(msg.str());
+ return (sysrepo::ErrorCode::ValidationFailed);
+ }
+ LOG_INFO(netconf_logger, NETCONF_UPDATE_CONFIG_COMPLETED)
+ .arg(server);
+ return (sysrepo::ErrorCode::Ok);
+}
+
+void
+NetconfAgent::logChanges(Session sess, string_view const& model) {
+ ostringstream stream;
+ stream << "/" << model << ":*//.";
+ string const xpath(stream.str());
+ ChangeCollection const changes(sess.getChanges(xpath));
+ for (Change const& change : changes) {
+ ostringstream msg;
+ switch (change.operation) {
+ case sysrepo::ChangeOperation::Created:
+ msg << "created: ";
+ break;
+ case sysrepo::ChangeOperation::Deleted:
+ msg << "deleted: ";
+ break;
+ case sysrepo::ChangeOperation::Modified:
+ msg << "modified: ";
+ break;
+ case sysrepo::ChangeOperation::Moved:
+ msg << "moved: ";
+ break;
+ default:
+ msg << "unknown operation (" << change.operation << "): ";
+ }
+ string const path(change.node.path());
+ msg << path;
+ SchemaNode const& schema(change.node.schema());
+ NodeType const node_type(schema.nodeType());
+ if (node_type == NodeType::Container) {
+ msg << " (container)";
+ } else if (node_type == NodeType::List) {
+ msg << " (list)";
+ } else {
+ optional<string> const str(
+ Translator::translateToYang(Translator::translateFromYang(change.node),
+ LeafBaseType::Unknown));
+ if (str) {
+ msg << " = " << *str;
+ }
+ }
+
+ LOG_DEBUG(netconf_logger, NETCONF_DBG_TRACE_DETAIL_DATA,
+ NETCONF_CONFIG_CHANGED_DETAIL)
+ .arg(msg.str());
+ }
+}
+
+void
+NetconfAgent::announceShutdown() const {
+ isc::process::DControllerBasePtr& controller(NetconfController::instance());
+ if (controller) {
+ boost::dynamic_pointer_cast<NetconfController>(controller)
+ ->getNetconfProcess()
+ ->setShutdownFlag(true);
+ }
+}
+
+bool NetconfAgent::shouldShutdown() const {
+ return boost::dynamic_pointer_cast<NetconfController>(NetconfController::instance())
+ ->getNetconfProcess()
+ ->shouldShutdown();
+}
+
+} // namespace netconf
+} // namespace isc