#include <netconf/netconf_log.h>
#include <cc/command_interpreter.h>
#include <yang/translator_config.h>
+#include <yang/yang_revisions.h>
#include <boost/algorithm/string.hpp>
#include <sstream>
namespace {
-/// @brief Subscription callback.
+/// @brief Module change subscription callback.
class NetconfAgentCallback : public Callback {
public:
/// @brief Constructor.
}
};
+/// @brief Module (un)installation subscription callback.
+class NetconfAgentInstallCallback : public Callback {
+public:
+ /// @brief Module (un)installation callback.
+ ///
+ /// This callback is called by sysrepo when a module is (un)installed.
+ ///
+ /// @param module_name The module name.
+ /// @param revision The module revision.
+ /// @param state The new state of the module (ignored).
+ /// @param private_ctx The private context.
+ void module_install(const char* module_name,
+ const char* revision,
+ sr_module_state_t /*state*/,
+ void* /*private_ctx*/) {
+ if (!module_name || !revision) {
+ // Not for us...
+ return;
+ }
+ LOG_WARN(netconf_logger, NETCONF_MODULE_INSTALL)
+ .arg(module_name)
+ .arg(revision);
+ }
+};
+
} // end of anonymous namespace
namespace isc {
return;
}
+ // Check essential modules / revisions.
+ bool can_start = true;
+ for (auto pair : *servers) {
+ can_start = can_start && checkModule(pair.second->getModel());
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+ }
+ if (!can_start) {
+ cerr << "An essential YNAG module / revision is missing."
+ << endl
+ << "The environment is not suitable for running kea-netconf."
+ << endl;
+ exit(EXIT_FAILURE);
+ }
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+
+ // Check modules / revisions.
+ checkModules();
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+
for (auto pair : *servers) {
if (NetconfProcess::shut_down) {
return;
} catch (const std::exception& ex) {
isc_throw(Unexpected, "Can't connect to sysrepo: " << ex.what());
}
+ if (NetconfProcess::shut_down) {
+ return;
+ }
try {
startup_sess_.reset(new Session(conn_, SR_DS_STARTUP));
+ if (NetconfProcess::shut_down) {
+ return;
+ }
running_sess_.reset(new Session(conn_, SR_DS_RUNNING));
} catch (const std::exception& ex) {
isc_throw(Unexpected, "Can't establish a sysrepo session: "
<< ex.what());
}
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+
+ try {
+ S_Yang_Schemas schemas = startup_sess_->list_schemas();
+ for (size_t i = 0; i < schemas->schema_cnt(); ++i) {
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+ if (!schemas->schema(i) ||
+ !schemas->schema(i)->module_name()) {
+ // Should not happen: skip it.
+ continue;
+ }
+ string module = schemas->schema(i)->module_name();
+ if (!schemas->schema(i)->revision() ||
+ !schemas->schema(i)->revision()->revision()) {
+ // Our modules have revisions: skip it.
+ continue;
+ }
+ string revision = schemas->schema(i)->revision()->revision();
+ modules_.insert(make_pair(module, revision));
+ }
+ } catch (const sysrepo_exception& ex) {
+ isc_throw(Unexpected, "Can't list schemas: " << ex.what());
+ }
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+
+ try {
+ S_Subscribe subs(new Subscribe(startup_sess_));
+ S_Callback cb(new NetconfAgentInstallCallback());
+ subs->module_install_subscribe(cb);
+ subscriptions_.insert(make_pair("__install__", subs));
+ } catch (const sysrepo_exception& ex) {
+ isc_throw(Unexpected, "Can't subscribe moduel install: "
+ << ex.what());
+ }
+}
+
+bool
+NetconfAgent::checkModule(const string& module_name) const {
+ if (module_name.empty()) {
+ return (true);
+ }
+ auto module = modules_.find(module_name);
+ if (module == modules_.end()) {
+ LOG_ERROR(netconf_logger, METCONF_MODULE_MISSING_ERR)
+ .arg(module_name);
+ return (false);
+ }
+ auto modrev = YANG_REVISIONS.find(module_name);
+ if (modrev == YANG_REVISIONS.end()) {
+ // Can't check revision?!
+ return (true);
+ }
+ if (modrev->second != module->second) {
+ LOG_ERROR(netconf_logger, METCONF_MODULE_REVISION_ERR)
+ .arg(module_name)
+ .arg(modrev->second)
+ .arg(module->second);
+ return (false);
+ }
+ return (true);
+}
+
+void
+NetconfAgent::checkModules() const {
+ for (auto modrev : YANG_REVISIONS) {
+ if (NetconfProcess::shut_down) {
+ return;
+ }
+ auto module = modules_.find(modrev.first);
+ if (module == modules_.end()) {
+ LOG_WARN(netconf_logger, METCONF_MODULE_MISSING_WARN)
+ .arg(modrev.first);
+ continue;
+ }
+ if (modrev.second != module->second) {
+ LOG_WARN(netconf_logger, METCONF_MODULE_REVISION_WARN)
+ .arg(modrev.first)
+ .arg(modrev.second)
+ .arg(module->second);
+ }
+ }
}
void
/// @brief Initialize sysrepo sessions.
///
- /// Must be called before init.
+ /// Must be called before init. Collect the list of available
+ /// modules with their revisions.
void initSysrepo();
/// @brief Initialization.
///
+ /// Check available modules / revisions.
/// Get and display Kea server configurations.
/// Load Kea server configurations from YANG datastore.
/// Subscribe configuration changes in YANG datastore.
bool cancel_;
protected:
+ /// @brief Check essential module availability.
+ ///
+ /// Emit a fatal error if an essential one (i.e. required in
+ /// a further phase) is missing or does not have the expected revision.
+ /// The caller (init) will exit().
+ ///
+ /// @param module_name The module name.
+ /// @return true if available, false if not.
+ bool checkModule(const std::string& module_name) const;
+
+ /// @brief Check module availability.
+ ///
+ /// Emit a warning if a module is missing or does not have
+ /// the expected revision.
+ void checkModules() const;
+
/// @brief Get and display Kea server configuration.
///
/// Retrieves current configuration via control socket (unix or http)
S_Session running_sess_;
#endif
+ /// @brief Available modules and revisions in Sysrepo.
+ std::map<const std::string, const std::string> modules_;
+
/// @brief Subscription map.
#ifndef HAVE_PRE_0_7_6_SYSREPO
std::map<const std::string, sysrepo::S_Subscribe> subscriptions_;
The warning message indicates that the configuration change logging
encountered an unexpected condition. Details of it will be logged.
+% NETCONF_MODULE_INSTALL Sysrepo (un)installs a module: %1 (revision %2)
+This warning message indicates that sysrepo reports the installation
+or uninstallation of a module used by Kea. The name and revision of
+the module are printed.
+
+% METCONF_MODULE_MISSING_ERR Missing essential module %1 in sysrepo
+This fatal error message indicates that a module required by Netconf
+configuration is not available in the sysrepo repository. The name of
+the module is printed.
+
+% METCONF_MODULE_MISSING_WARN Missing module %1 in sysrepo
+This warning message indicates that a module used by Kea is not
+available in the sysrepo repository. The name of the module is printed.
+
+% METCONF_MODULE_REVISION_ERR Essential module %1 does have the right revision: expected %2, got %3
+This fatal error message indicates that a module required by Netconf
+configuration is not at the right revision in the sysrepo repository.
+The name, expected and available revisions of the module are printed.
+
+% METCONF_MODULE_REVISION_WARN Module %1 does have the right revision: expected %2, got %3
+This warning message indicates that a module used by Kea is not at the
+right revision in the sysrepo repository. The name, expected and
+available revisions of the module are printed.
+
% NETCONF_RUN_EXIT application is exiting the event loop
This is a debug message issued when the Netconf application exits its
event loop. This is a normal step during kea-netconf shutdown.
EXPECT_TRUE(agent_->conn_);
EXPECT_TRUE(agent_->startup_sess_);
EXPECT_TRUE(agent_->running_sess_);
+ EXPECT_EQ(1, agent_->subscriptions_.size());
}
/// @brief Default change callback (print changes and return OK).
// Try subscribeConfig.
EXPECT_EQ(0, agent_->subscriptions_.size());
ASSERT_NO_THROW(agent_->initSysrepo());
- EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
EXPECT_EQ(1, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(2, agent_->subscriptions_.size());
/// Unsubscribe.
EXPECT_NO_THROW(agent_->subscriptions_.clear());
CfgServersMapPair service_pair = *servers_map->begin();
// Subscribe YANG changes.
- EXPECT_EQ(0, agent_->subscriptions_.size());
- EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
EXPECT_EQ(1, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(2, agent_->subscriptions_.size());
// Launch server.
thread_.reset(new Thread([this]() { fakeServer(); signalStopped(); }));
CfgServersMapPair service_pair = *servers_map->begin();
// Subscribe YANG changes.
- EXPECT_EQ(0, agent_->subscriptions_.size());
- EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
EXPECT_EQ(1, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(2, agent_->subscriptions_.size());
// Launch server twice.
thread_.reset(new Thread([this]()
CfgServersMapPair service_pair = *servers_map->begin();
// Subscribe YANG changes.
- EXPECT_EQ(0, agent_->subscriptions_.size());
- EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
EXPECT_EQ(1, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(2, agent_->subscriptions_.size());
// Change configuration (add invalid user context).
const YRTree tree1 = {
#include <config.h>
+#define KEATEST_MODULE
#include <yang/yang_revisions.h>
#ifndef HAVE_PRE_0_7_6_SYSREPO
// Table of module name / revision.
static const std::map<std::string, std::string> YANG_REVISIONS = {
-// Should be generated automatically.
-// cf src/share/yang/modules/README
-{ "ietf-dhcpv6-types", "2018-09-04" },
-{ "ietf-dhcpv6-options", "2018-09-04" },
-{ "ietf-dhcpv6-server", "2018-09-04" },
-{ "kea-types", "2018-11-20" },
-{ "kea-logging", "2018-11-20" },
-{ "kea-dhcp-types", "2018-11-20" },
-{ "kea-dhcp4-server", "2018-11-20" },
-{ "kea-dhcp6-server", "2018-11-20" },
-{ "kea-ctrl-agent", "2018-11-20" },
-{ "kea-dhcp-ddns", "2018-11-20" },
-{ "keatest-module", "2018-11-20" }
+#ifdef KEATEST_MODULE
+ { "keatest-module", "2018-11-20" },
+#endif // KEATEST_MODULE
+ { "ietf-dhcpv6-types", "2018-09-04" },
+ { "ietf-dhcpv6-options", "2018-09-04" },
+ { "ietf-dhcpv6-server", "2018-09-04" },
+ { "kea-types", "2018-11-20" },
+ { "kea-logging", "2018-11-20" },
+ { "kea-dhcp-types", "2018-11-20" },
+ { "kea-dhcp4-server", "2018-11-20" },
+ { "kea-dhcp6-server", "2018-11-20" },
+ { "kea-ctrl-agent", "2018-11-20" },
+ { "kea-dhcp-ddns", "2018-11-20" }
};
}; // end of namespace isc::yang
--- /dev/null
+// 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_YANG_REVISIONS_H
+#define ISC_YANG_REVISIONS_H 1
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace yang {
+
+// Table of module name / revision.
+static const std::map<std::string, std::string> YANG_REVISIONS = {
+// Fill this by:
+// cd .../src/share/yang/modules
+// sh utils/gen-revisions.sh > r
+// insert the r file here
+};
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_YANG_REVISIONS_H
+++ /dev/null
-Developer tools:
-
-Get revision from file content:
-
-yanglint -f yin xxx | grep '<revision date=' | head -1 | sed \
- 's/.*<revision date="\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)".*/\1/'
-
-Get revision from file name:
-
-echo xxx | sed 's/.*@\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)\..*/\1/'
-
-Get hash:
-
-yanglint -f yin xxx | openssl dgst -sha256
-
-Check revisions:
-
-for m in *.yang
-do
- rev1=`yanglint -f yin $m | grep '<revision date=' | head -1 | sed \
- 's/.*<revision date="\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)".*/\1/'`
- rev2=`echo $m | sed \
- 's/.*@\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)\..*/\1/'`
-
- if test $rev1 != $rev2
- then
- echo revision mismatch on $m
- fi
-done
-
-Check hashes:
-
-for m in *.yang
-do
- hash1=`yanglint -f yin $m | openssl dgst -sha256`
- h=hashes/`basename $m .yang`.hash
- if test -f $h
- then
- hash2=`cat $h`
- if test $hash1 != $hash2
- then
- echo hash mismatch on $m
- fi
- else
- echo missing hash for $m
- fi
-done
-
-Build module / revision table:
-
-for m in ietf-dhcpv6-types*.yang \
- ietf-dhcpv6-options*.yang \
- ietf-dhcpv6-server*.yang \
- kea-types*.yang \
- kea-logging*.yang \
- kea-dhcp-types*.yang \
- kea-dhcp4-server*.yang \
- kea-dhcp6-server*.yang \
- kea-ctrl-agent*.yang \
- kea-dhcp-ddns*.yang \
- keatest-module*.yang \
- end-marker
-do
- if test $m = "end-marker"
- then
- echo '{ "", "" }'
- else
- b=`echo $m | sed 's/\(.*\)@.*/\1/'`
- r=`echo $m | sed 's/.*@\(.*\)\.yang/\1/'`
- echo '{ "'$b'", "'$r'" },'
- fi
-done
-
-
--- /dev/null
+#!/bin/sh
+# 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/.
+
+# Check hashes:
+
+for m in *.yang
+do
+ hash1=`yanglint -f yin $m | openssl dgst -sha256 | sed 's/(stdin)= //'`
+ h=hashes/`basename $m .yang`.hash
+ if test -f $h
+ then
+ hash2=`cat $h`
+ if test $hash1 != $hash2
+ then
+ echo hash mismatch on $m expected $hash1 in $h
+ fi
+ else
+ echo missing hash file $h for $m
+ fi
+done
--- /dev/null
+#!/bin/sh
+# 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/.
+
+# Check revisions:
+
+for m in *.yang
+do
+ rev1=`yanglint -f yin $m | grep '<revision date=' | head -1 | sed \
+ 's/.*<revision date="\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)".*/\1/'`
+ rev2=`echo $m | sed \
+ 's/.*@\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)\..*/\1/'`
+
+ if test $rev1 != $rev2
+ then
+ echo revision mismatch on $m got $rev1
+ fi
+done
--- /dev/null
+#!/bin/sh
+# 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/.
+
+# Build module / revision table:
+
+test=keatest-module*.yang
+last=kea-dhcp-ddns*.yang
+
+for m in keatest-module*.yang \
+ ietf-dhcpv6-types*.yang \
+ ietf-dhcpv6-options*.yang \
+ ietf-dhcpv6-server*.yang \
+ kea-types*.yang \
+ kea-logging*.yang \
+ kea-dhcp-types*.yang \
+ kea-dhcp4-server*.yang \
+ kea-dhcp6-server*.yang \
+ kea-ctrl-agent*.yang \
+ kea-dhcp-ddns*.yang
+do
+ if test $m = $test
+ then
+ echo '#ifdef KEATEST_MODULE'
+ fi
+ b=`echo $m | sed 's/\(.*\)@.*/\1/'`
+ r=`echo $m | sed 's/.*@\(.*\)\.yang/\1/'`
+ c=','
+ if test $m = $last
+ then
+ c=''
+ fi
+ echo ' { "'$b'", "'$r'" }'$c
+ if test $m = $test
+ then
+ echo '#endif // KEATEST_MODULE'
+ fi
+done
+
+