diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc index 1b5e47d11a..45710e35ec 100644 --- a/src/lib/config/ccsession.cc +++ b/src/lib/config/ccsession.cc @@ -356,11 +356,40 @@ ModuleCCSession::checkCommand() { } std::string -ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) { - ModuleSpec rmod_spec = readModuleSpecification(spec_file_name); - std::string module_name = rmod_spec.getFullSpec()->get("module_name")->stringValue(); +ModuleCCSession::addRemoteConfig(const std::string& spec_name, + void (*handler)(const std::string& module, + ConstElementPtr), + bool spec_is_filename) +{ + std::string module_name; + ModuleSpec rmod_spec; + if (spec_is_filename) { + // It's a file name, so load it + rmod_spec = readModuleSpecification(spec_name); + module_name = + rmod_spec.getFullSpec()->get("module_name")->stringValue(); + } else { + // It's module name, request it from config manager + ConstElementPtr cmd = Element::fromJSON("{ \"command\": [" + "\"get_module_spec\"," + "{\"module_name\": \"" + + module_name + "\"} ] }"); + unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager"); + ConstElementPtr env, answer; + session_.group_recvmsg(env, answer, false, seq); + int rcode; + ConstElementPtr spec_data = parseAnswer(rcode, answer); + if (rcode == 0 && spec_data) { + rmod_spec = ModuleSpec(spec_data); + module_name = spec_name; + if (module_name != rmod_spec.getModuleName()) { + isc_throw(CCSessionError, "Module name mismatch"); + } + } else { + isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str()); + } + } ConfigData rmod_config = ConfigData(rmod_spec); - session_.subscribe(module_name); // Get the current configuration values for that module ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }"); @@ -370,8 +399,9 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) { session_.group_recvmsg(env, answer, false, seq); int rcode; ConstElementPtr new_config = parseAnswer(rcode, answer); + ElementPtr local_config; if (rcode == 0 && new_config) { - ElementPtr local_config = rmod_config.getLocalConfig(); + local_config = rmod_config.getLocalConfig(); isc::data::merge(local_config, new_config); rmod_config.setLocalConfig(local_config); } else { @@ -380,6 +410,11 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) { // all ok, add it remote_module_configs_[module_name] = rmod_config; + if (handler) { + remote_module_handlers_[module_name] = handler; + handler(module_name, local_config); + } + session_.subscribe(module_name); return (module_name); } @@ -390,6 +425,7 @@ ModuleCCSession::removeRemoteConfig(const std::string& module_name) { it = remote_module_configs_.find(module_name); if (it != remote_module_configs_.end()) { remote_module_configs_.erase(it); + remote_module_handlers_.erase(module_name); session_.unsubscribe(module_name); } } @@ -419,6 +455,11 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name, if (it != remote_module_configs_.end()) { ElementPtr rconf = (*it).second.getLocalConfig(); isc::data::merge(rconf, new_config); + std::map::iterator hit = + remote_module_handlers_.find(module_name); + if (hit != remote_module_handlers_.end()) { + hit->second(module_name, new_config); + } } } diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h index 73648765ae..c845b8f23d 100644 --- a/src/lib/config/ccsession.h +++ b/src/lib/config/ccsession.h @@ -234,24 +234,43 @@ public: /** * Gives access to the configuration values of a different module * Once this function has been called with the name of the specification - * file of the module you want the configuration of, you can use + * file or the module you want the configuration of, you can use * \c getRemoteConfigValue() to get a specific setting. - * Changes are automatically updated, but you cannot specify handlers - * for those changes, must use \c getRemoteConfigValue() to get a value - * This function will subscribe to the relevant module channel. + * Changes are automatically updated, and you can specify handlers + * for those changes. This function will subscribe to the relevant module + * channel. * - * \param spec_file_name The path to the specification file of - * the module we want to have configuration - * values from + * \param spec_name This specifies the module to add. It is either a + * filename of the spec file to use or a name of module + * (in case it's a module name, the spec data is + * downloaded from the configuration manager, therefore + * the configuration manager must know it). If + * spec_is_filenabe is true (the default), then a + * filename is assumed, otherwise a module name. + * \param handler The handler function called whenever there's a change. + * Called once initally from this function. May be NULL + * if you don't want any handler to be called and you're + * fine with requesting the data through + * getRemoteConfigValue() each time. + * + * The handler should not throw, or it'll fall trough and + * the exception will get into strange places, probably + * aborting the application. + * \param spec_is_filename Says if spec_name is filename or module name. * \return The name of the module specified in the given specification * file */ - std::string addRemoteConfig(const std::string& spec_file_name); + std::string addRemoteConfig(const std::string& spec_name, + void (*handler)(const std::string& module_name, + isc::data::ConstElementPtr + update) = NULL, + bool spec_is_filename = true); /** * Removes the module with the given name from the remote config * settings. If the module was not added with \c addRemoteConfig(), - * nothing happens. + * nothing happens. If there was a handler for this config, it is + * removed as well. */ void removeRemoteConfig(const std::string& module_name); @@ -296,7 +315,11 @@ private: const std::string& command, isc::data::ConstElementPtr args); + typedef void (*RemoteHandler)(const std::string&, + isc::data::ConstElementPtr); std::map remote_module_configs_; + std::map remote_module_handlers_; + void updateRemoteConfig(const std::string& module_name, isc::data::ConstElementPtr new_config); }; diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc index f566949419..3564d4bfd1 100644 --- a/src/lib/config/tests/ccsession_unittests.cc +++ b/src/lib/config/tests/ccsession_unittests.cc @@ -346,6 +346,18 @@ TEST_F(CCSessionTest, checkCommand2) { EXPECT_EQ(2, mccs.getValue("item1")->intValue()); } +std::string remote_module_name; +int remote_item1(0); +ConstElementPtr remote_config; +ModuleCCSession *remote_mccs(NULL); + +void remoteHandler(const std::string& module_name, ConstElementPtr config) { + remote_module_name = module_name; + remote_item1 = remote_mccs->getRemoteConfigValue("Spec2", "item1")-> + intValue(); + remote_config = config; +} + TEST_F(CCSessionTest, remoteConfig) { std::string module_name; int item1; @@ -392,6 +404,91 @@ TEST_F(CCSessionTest, remoteConfig) { session.getMessages()->add(createAnswer()); EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")), CCSessionError); + + { + SCOPED_TRACE("With module name"); + // Try adding it with downloading the spec from config manager + ModuleSpec spec(moduleSpecFromFile(ccspecfile("spec2.spec"))); + session.getMessages()->add(createAnswer(0, spec.getFullSpec())); + session.getMessages()->add(createAnswer(0, el("{}"))); + + EXPECT_NO_THROW(module_name = mccs.addRemoteConfig("Spec2", NULL, + false)); + + EXPECT_EQ("Spec2", module_name); + EXPECT_NO_THROW(item1 = + mccs.getRemoteConfigValue(module_name, + "item1")->intValue()); + EXPECT_EQ(1, item1); + + mccs.removeRemoteConfig(module_name); + } + + { + // Try adding it with a handler. + // Pass non-default value to see the handler is called after + // downloading the configuration, not too soon. + SCOPED_TRACE("With handler"); + session.getMessages()->add(createAnswer(0, el("{ \"item1\": 2 }"))); + remote_mccs = &mccs; + module_name = mccs.addRemoteConfig(ccspecfile("spec2.spec"), + remoteHandler); + { + SCOPED_TRACE("Before update"); + EXPECT_EQ("Spec2", module_name); + EXPECT_TRUE(session.haveSubscription("Spec2", "*")); + // Now check the parameters the remote handler stored + // This also checks it was called + EXPECT_EQ("Spec2", remote_module_name); + remote_module_name = ""; + EXPECT_EQ(2, remote_item1); + remote_item1 = 0; + if (remote_config) { + EXPECT_EQ(2, remote_config->get("item1")->intValue()); + } else { + ADD_FAILURE() << "Remote config not set"; + } + remote_config.reset(); + // Make sure normal way still works + item1 = mccs.getRemoteConfigValue(module_name, + "item1")->intValue(); + EXPECT_EQ(2, item1); + } + + { + SCOPED_TRACE("After update"); + session.addMessage(el("{ \"command\": [ \"config_update\", " + "{ \"item1\": 3 } ] }"), module_name, "*"); + mccs.checkCommand(); + EXPECT_EQ("Spec2", remote_module_name); + remote_module_name = ""; + EXPECT_EQ(3, remote_item1); + remote_item1 = 0; + if (remote_config) { + EXPECT_EQ(3, remote_config->get("item1")->intValue()); + } else { + ADD_FAILURE() << "Remote config not set"; + } + remote_config.reset(); + // Make sure normal way still works + item1 = mccs.getRemoteConfigValue(module_name, + "item1")->intValue(); + EXPECT_EQ(3, item1); + } + + remote_mccs = NULL; + mccs.removeRemoteConfig(module_name); + + { + SCOPED_TRACE("When removed"); + // Make sure nothing is called any more + session.addMessage(el("{ \"command\": [ \"config_update\", " + "{ \"item1\": 4 } ] }"), module_name, "*"); + EXPECT_EQ("", remote_module_name); + EXPECT_EQ(0, remote_item1); + EXPECT_FALSE(remote_config); + } + } } TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {