ccsession.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. // Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <stdexcept>
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include <sys/time.h>
  19. #include <iostream>
  20. #include <fstream>
  21. #include <sstream>
  22. #include <cerrno>
  23. #include <boost/bind.hpp>
  24. #include <boost/foreach.hpp>
  25. #include <cc/data.h>
  26. #include <module_spec.h>
  27. #include <cc/session.h>
  28. #include <exceptions/exceptions.h>
  29. #include <config/config_log.h>
  30. #include <config/ccsession.h>
  31. #include <log/logger_support.h>
  32. #include <log/logger_specification.h>
  33. #include <log/logger_manager.h>
  34. #include <log/logger_name.h>
  35. using namespace std;
  36. using isc::data::Element;
  37. using isc::data::ConstElementPtr;
  38. using isc::data::ElementPtr;
  39. using isc::data::JSONError;
  40. namespace isc {
  41. namespace config {
  42. /// Creates a standard config/command protocol answer message
  43. ConstElementPtr
  44. createAnswer() {
  45. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  46. ElementPtr answer_content = Element::createList();
  47. answer_content->add(Element::create(0));
  48. answer->set("result", answer_content);
  49. return (answer);
  50. }
  51. ConstElementPtr
  52. createAnswer(const int rcode, ConstElementPtr arg) {
  53. if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
  54. isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
  55. }
  56. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  57. ElementPtr answer_content = Element::createList();
  58. answer_content->add(Element::create(rcode));
  59. answer_content->add(arg);
  60. answer->set("result", answer_content);
  61. return (answer);
  62. }
  63. ConstElementPtr
  64. createAnswer(const int rcode, const std::string& arg) {
  65. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  66. ElementPtr answer_content = Element::createList();
  67. answer_content->add(Element::create(rcode));
  68. answer_content->add(Element::create(arg));
  69. answer->set("result", answer_content);
  70. return (answer);
  71. }
  72. ConstElementPtr
  73. parseAnswer(int &rcode, ConstElementPtr msg) {
  74. if (msg &&
  75. msg->getType() == Element::map &&
  76. msg->contains("result")) {
  77. ConstElementPtr result = msg->get("result");
  78. if (result->getType() != Element::list) {
  79. isc_throw(CCSessionError, "Result element in answer message is not a list");
  80. } else if (result->get(0)->getType() != Element::integer) {
  81. isc_throw(CCSessionError, "First element of result is not an rcode in answer message");
  82. }
  83. rcode = result->get(0)->intValue();
  84. if (result->size() > 1) {
  85. if (rcode == 0 || result->get(1)->getType() == Element::string) {
  86. return (result->get(1));
  87. } else {
  88. isc_throw(CCSessionError, "Error description in result with rcode != 0 is not a string");
  89. }
  90. } else {
  91. if (rcode == 0) {
  92. return (ElementPtr());
  93. } else {
  94. isc_throw(CCSessionError, "Result with rcode != 0 does not have an error description");
  95. }
  96. }
  97. } else {
  98. isc_throw(CCSessionError, "No result part in answer message");
  99. }
  100. }
  101. ConstElementPtr
  102. createCommand(const std::string& command) {
  103. return (createCommand(command, ElementPtr()));
  104. }
  105. ConstElementPtr
  106. createCommand(const std::string& command, ConstElementPtr arg) {
  107. ElementPtr cmd = Element::createMap();
  108. ElementPtr cmd_parts = Element::createList();
  109. cmd_parts->add(Element::create(command));
  110. if (arg) {
  111. cmd_parts->add(arg);
  112. }
  113. cmd->set("command", cmd_parts);
  114. return (cmd);
  115. }
  116. std::string
  117. parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
  118. if (command &&
  119. command->getType() == Element::map &&
  120. command->contains("command")) {
  121. ConstElementPtr cmd = command->get("command");
  122. if (cmd->getType() == Element::list &&
  123. cmd->size() > 0 &&
  124. cmd->get(0)->getType() == Element::string) {
  125. if (cmd->size() > 1) {
  126. arg = cmd->get(1);
  127. } else {
  128. arg = Element::createMap();
  129. }
  130. return (cmd->get(0)->stringValue());
  131. } else {
  132. isc_throw(CCSessionError, "Command part in command message missing, empty, or not a list");
  133. }
  134. } else {
  135. isc_throw(CCSessionError, "Command Element empty or not a map with \"command\"");
  136. }
  137. }
  138. namespace {
  139. // Temporary workaround functions for missing functionality in
  140. // getValue() (main problem described in ticket #993)
  141. // This returns either the value set for the given relative id,
  142. // or its default value
  143. // (intentially defined here so this interface does not get
  144. // included in ConfigData as it is)
  145. ConstElementPtr getValueOrDefault(ConstElementPtr config_part,
  146. const std::string& relative_id,
  147. const ConfigData& config_data,
  148. const std::string& full_id) {
  149. if (config_part->contains(relative_id)) {
  150. return config_part->get(relative_id);
  151. } else {
  152. return config_data.getDefaultValue(full_id);
  153. }
  154. }
  155. // Reads a output_option subelement of a logger configuration,
  156. // and sets the values thereing to the given OutputOption struct,
  157. // or defaults values if they are not provided (from config_data).
  158. void
  159. readOutputOptionConf(isc::log::OutputOption& output_option,
  160. ConstElementPtr output_option_el,
  161. const ConfigData& config_data)
  162. {
  163. ConstElementPtr destination_el = getValueOrDefault(output_option_el,
  164. "destination", config_data,
  165. "loggers/output_options/destination");
  166. output_option.destination = isc::log::getDestination(destination_el->stringValue());
  167. ConstElementPtr output_el = getValueOrDefault(output_option_el,
  168. "output", config_data,
  169. "loggers/output_options/output");
  170. if (output_option.destination == isc::log::OutputOption::DEST_CONSOLE) {
  171. output_option.stream = isc::log::getStream(output_el->stringValue());
  172. } else if (output_option.destination == isc::log::OutputOption::DEST_FILE) {
  173. output_option.filename = output_el->stringValue();
  174. } else if (output_option.destination == isc::log::OutputOption::DEST_SYSLOG) {
  175. output_option.facility = output_el->stringValue();
  176. }
  177. output_option.flush = getValueOrDefault(output_option_el,
  178. "flush", config_data,
  179. "loggers/output_options/flush")->boolValue();
  180. output_option.maxsize = getValueOrDefault(output_option_el,
  181. "maxsize", config_data,
  182. "loggers/output_options/maxsize")->intValue();
  183. output_option.maxver = getValueOrDefault(output_option_el,
  184. "maxver", config_data,
  185. "loggers/output_options/maxver")->intValue();
  186. }
  187. // Reads a full 'loggers' configuration, and adds the loggers therein
  188. // to the given vector, fills in blanks with defaults from config_data
  189. void
  190. readLoggersConf(std::vector<isc::log::LoggerSpecification>& specs,
  191. ConstElementPtr logger,
  192. const ConfigData& config_data)
  193. {
  194. std::string lname = logger->get("name")->stringValue();
  195. // If the first part of the name is '*', it is meant for 'every
  196. // program', so we replace it with whatever is set as the root
  197. // logger.
  198. // We could tokenize the string, but if * is used, the string
  199. // should either be "*", or start with "*.", so it's easier to
  200. // look directly
  201. if (lname[0] == '*' && (lname.length() == 1 || lname[1] == '.')) {
  202. lname = isc::log::getRootLoggerName();
  203. }
  204. ConstElementPtr severity_el = getValueOrDefault(logger,
  205. "severity", config_data,
  206. "loggers/severity");
  207. isc::log::Severity severity = isc::log::getSeverity(
  208. severity_el->stringValue());
  209. int dbg_level = getValueOrDefault(logger, "debuglevel",
  210. config_data,
  211. "loggers/debuglevel")->intValue();
  212. bool additive = getValueOrDefault(logger, "additive", config_data,
  213. "loggers/additive")->boolValue();
  214. isc::log::LoggerSpecification logger_spec(
  215. lname, severity, dbg_level, additive
  216. );
  217. if (logger->contains("output_options")) {
  218. BOOST_FOREACH(ConstElementPtr output_option_el,
  219. logger->get("output_options")->listValue()) {
  220. // create outputoptions
  221. isc::log::OutputOption output_option;
  222. readOutputOptionConf(output_option,
  223. output_option_el,
  224. config_data);
  225. logger_spec.addOutputOption(output_option);
  226. }
  227. }
  228. specs.push_back(logger_spec);
  229. }
  230. } // end anonymous namespace
  231. void
  232. default_logconfig_handler(const std::string& module_name,
  233. ConstElementPtr new_config,
  234. const ConfigData& config_data) {
  235. config_data.getModuleSpec().validateConfig(new_config, true);
  236. std::vector<isc::log::LoggerSpecification> specs;
  237. if (new_config->contains("loggers")) {
  238. BOOST_FOREACH(ConstElementPtr logger,
  239. new_config->get("loggers")->listValue()) {
  240. readLoggersConf(specs, logger, config_data);
  241. }
  242. }
  243. isc::log::LoggerManager logger_manager;
  244. logger_manager.process(specs.begin(), specs.end());
  245. }
  246. ModuleSpec
  247. ModuleCCSession::readModuleSpecification(const std::string& filename) {
  248. std::ifstream file;
  249. ModuleSpec module_spec;
  250. // this file should be declared in a @something@ directive
  251. file.open(filename.c_str());
  252. if (!file) {
  253. LOG_ERROR(config_logger, CONFIG_FOPEN_ERR).arg(filename).arg(strerror(errno));
  254. isc_throw(CCSessionInitError, strerror(errno));
  255. }
  256. try {
  257. module_spec = moduleSpecFromFile(file, true);
  258. } catch (const JSONError& pe) {
  259. LOG_ERROR(config_logger, CONFIG_JSON_PARSE).arg(filename).arg(pe.what());
  260. isc_throw(CCSessionInitError, pe.what());
  261. } catch (const ModuleSpecError& dde) {
  262. LOG_ERROR(config_logger, CONFIG_MODULE_SPEC).arg(filename).arg(dde.what());
  263. isc_throw(CCSessionInitError, dde.what());
  264. }
  265. file.close();
  266. return (module_spec);
  267. }
  268. void
  269. ModuleCCSession::startCheck() {
  270. // data available on the command channel. process it in the synchronous
  271. // mode.
  272. checkCommand();
  273. // start asynchronous read again.
  274. session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
  275. }
  276. ModuleCCSession::ModuleCCSession(
  277. const std::string& spec_file_name,
  278. isc::cc::AbstractSession& session,
  279. isc::data::ConstElementPtr(*config_handler)(
  280. isc::data::ConstElementPtr new_config),
  281. isc::data::ConstElementPtr(*command_handler)(
  282. const std::string& command, isc::data::ConstElementPtr args),
  283. bool start_immediately,
  284. bool handle_logging
  285. ) :
  286. started_(false),
  287. session_(session)
  288. {
  289. module_specification_ = readModuleSpecification(spec_file_name);
  290. setModuleSpec(module_specification_);
  291. module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue();
  292. config_handler_ = config_handler;
  293. command_handler_ = command_handler;
  294. session_.establish(NULL);
  295. session_.subscribe(module_name_, "*");
  296. // send the data specification
  297. ConstElementPtr spec_msg = createCommand("module_spec",
  298. module_specification_.getFullSpec());
  299. unsigned int seq = session_.group_sendmsg(spec_msg, "ConfigManager");
  300. ConstElementPtr answer, env;
  301. session_.group_recvmsg(env, answer, false, seq);
  302. int rcode;
  303. ConstElementPtr err = parseAnswer(rcode, answer);
  304. if (rcode != 0) {
  305. LOG_ERROR(config_logger, CONFIG_MANAGER_MOD_SPEC).arg(answer->str());
  306. isc_throw(CCSessionInitError, answer->str());
  307. }
  308. setLocalConfig(Element::fromJSON("{}"));
  309. // get any stored configuration from the manager
  310. if (config_handler_) {
  311. ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
  312. seq = session_.group_sendmsg(cmd, "ConfigManager");
  313. session_.group_recvmsg(env, answer, false, seq);
  314. ConstElementPtr new_config = parseAnswer(rcode, answer);
  315. if (rcode == 0) {
  316. handleConfigUpdate(new_config);
  317. } else {
  318. LOG_ERROR(config_logger, CONFIG_MANAGER_CONFIG).arg(new_config->str());
  319. isc_throw(CCSessionInitError, answer->str());
  320. }
  321. }
  322. // Keep track of logging settings automatically
  323. if (handle_logging) {
  324. addRemoteConfig("Logging", default_logconfig_handler, false);
  325. }
  326. if (start_immediately) {
  327. start();
  328. }
  329. }
  330. void
  331. ModuleCCSession::start() {
  332. if (started_) {
  333. isc_throw(CCSessionError, "Module CC session already started");
  334. }
  335. // register callback for asynchronous read
  336. session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
  337. started_ = true;
  338. }
  339. /// Validates the new config values, if they are correct,
  340. /// call the config handler with the values that have changed
  341. /// If that results in success, store the new config
  342. ConstElementPtr
  343. ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
  344. ConstElementPtr answer;
  345. ElementPtr errors = Element::createList();
  346. if (!config_handler_) {
  347. answer = createAnswer(1, module_name_ + " does not have a config handler");
  348. } else if (!module_specification_.validateConfig(new_config, false,
  349. errors)) {
  350. std::stringstream ss;
  351. ss << "Error in config validation: ";
  352. BOOST_FOREACH(ConstElementPtr error, errors->listValue()) {
  353. ss << error->stringValue();
  354. }
  355. answer = createAnswer(2, ss.str());
  356. } else {
  357. // remove the values that have not changed
  358. ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
  359. // handle config update
  360. answer = config_handler_(diff);
  361. int rcode;
  362. parseAnswer(rcode, answer);
  363. if (rcode == 0) {
  364. ElementPtr local_config = getLocalConfig();
  365. isc::data::merge(local_config, diff);
  366. setLocalConfig(local_config);
  367. }
  368. }
  369. return (answer);
  370. }
  371. bool
  372. ModuleCCSession::hasQueuedMsgs() const {
  373. return (session_.hasQueuedMsgs());
  374. }
  375. ConstElementPtr
  376. ModuleCCSession::checkConfigUpdateCommand(const std::string& target_module,
  377. ConstElementPtr arg)
  378. {
  379. if (target_module == module_name_) {
  380. return (handleConfigUpdate(arg));
  381. } else {
  382. // ok this update is not for us, if we have this module
  383. // in our remote config list, update that
  384. updateRemoteConfig(target_module, arg);
  385. // we're not supposed to answer to this, so return
  386. return (ElementPtr());
  387. }
  388. }
  389. ConstElementPtr
  390. ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
  391. const std::string& target_module,
  392. ConstElementPtr arg) const
  393. {
  394. if (target_module == module_name_) {
  395. if (command_handler_) {
  396. ElementPtr errors = Element::createList();
  397. if (module_specification_.validateCommand(cmd_str,
  398. arg,
  399. errors)) {
  400. return (command_handler_(cmd_str, arg));
  401. } else {
  402. std::stringstream ss;
  403. ss << "Error in command validation: ";
  404. BOOST_FOREACH(ConstElementPtr error,
  405. errors->listValue()) {
  406. ss << error->stringValue();
  407. }
  408. return (createAnswer(3, ss.str()));
  409. }
  410. } else {
  411. return (createAnswer(1,
  412. "Command given but no "
  413. "command handler for module"));
  414. }
  415. }
  416. return (ElementPtr());
  417. }
  418. int
  419. ModuleCCSession::checkCommand() {
  420. ConstElementPtr cmd, routing, data;
  421. if (session_.group_recvmsg(routing, data, true)) {
  422. /* ignore result messages (in case we're out of sync, to prevent
  423. * pingpongs */
  424. if (data->getType() != Element::map || data->contains("result")) {
  425. return (0);
  426. }
  427. ConstElementPtr arg;
  428. ConstElementPtr answer;
  429. try {
  430. std::string cmd_str = parseCommand(arg, data);
  431. std::string target_module = routing->get("group")->stringValue();
  432. if (cmd_str == "config_update") {
  433. answer = checkConfigUpdateCommand(target_module, arg);
  434. } else {
  435. answer = checkModuleCommand(cmd_str, target_module, arg);
  436. }
  437. } catch (const CCSessionError& re) {
  438. LOG_ERROR(config_logger, CONFIG_CCSESSION_MSG).arg(re.what());
  439. } catch (const std::exception& stde) {
  440. // No matter what unexpected error happens, we do not want
  441. // to crash because of an incoming event, so we log the
  442. // exception and continue to run
  443. LOG_ERROR(config_logger, CONFIG_CCSESSION_MSG_INTERNAL).arg(stde.what());
  444. }
  445. if (!isNull(answer)) {
  446. session_.reply(routing, answer);
  447. }
  448. }
  449. return (0);
  450. }
  451. ModuleSpec
  452. ModuleCCSession::fetchRemoteSpec(const std::string& module, bool is_filename) {
  453. if (is_filename) {
  454. // It is a filename, simply load it.
  455. return (readModuleSpecification(module));
  456. } else {
  457. // It's module name, request it from config manager
  458. // Send the command
  459. ConstElementPtr cmd(createCommand("get_module_spec",
  460. Element::fromJSON("{\"module_name\": \"" + module +
  461. "\"}")));
  462. unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
  463. ConstElementPtr env, answer;
  464. session_.group_recvmsg(env, answer, false, seq);
  465. int rcode;
  466. ConstElementPtr spec_data = parseAnswer(rcode, answer);
  467. if (rcode == 0 && spec_data) {
  468. // received OK, construct the spec out of it
  469. ModuleSpec spec = ModuleSpec(spec_data);
  470. if (module != spec.getModuleName()) {
  471. // It's a different module!
  472. isc_throw(CCSessionError, "Module name mismatch");
  473. }
  474. return (spec);
  475. } else {
  476. isc_throw(CCSessionError, "Error getting config for " +
  477. module + ": " + answer->str());
  478. }
  479. }
  480. }
  481. std::string
  482. ModuleCCSession::addRemoteConfig(const std::string& spec_name,
  483. void (*handler)(const std::string& module,
  484. ConstElementPtr,
  485. const ConfigData&),
  486. bool spec_is_filename)
  487. {
  488. // First get the module name, specification and default config
  489. const ModuleSpec rmod_spec(fetchRemoteSpec(spec_name, spec_is_filename));
  490. const std::string module_name(rmod_spec.getModuleName());
  491. ConfigData rmod_config(rmod_spec);
  492. // Get the current configuration values from config manager
  493. ConstElementPtr cmd(createCommand("get_config",
  494. Element::fromJSON("{\"module_name\": \"" +
  495. module_name + "\"}")));
  496. const unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
  497. ConstElementPtr env, answer;
  498. session_.group_recvmsg(env, answer, false, seq);
  499. int rcode;
  500. ConstElementPtr new_config = parseAnswer(rcode, answer);
  501. ElementPtr local_config;
  502. if (rcode == 0 && new_config) {
  503. // Merge the received config into existing local config
  504. local_config = rmod_config.getLocalConfig();
  505. isc::data::merge(local_config, new_config);
  506. rmod_config.setLocalConfig(local_config);
  507. } else {
  508. isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
  509. }
  510. // all ok, add it
  511. remote_module_configs_[module_name] = rmod_config;
  512. if (handler) {
  513. remote_module_handlers_[module_name] = handler;
  514. handler(module_name, local_config, rmod_config);
  515. }
  516. // Make sure we get updates in future
  517. session_.subscribe(module_name);
  518. return (module_name);
  519. }
  520. void
  521. ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
  522. std::map<std::string, ConfigData>::iterator it;
  523. it = remote_module_configs_.find(module_name);
  524. if (it != remote_module_configs_.end()) {
  525. remote_module_configs_.erase(it);
  526. remote_module_handlers_.erase(module_name);
  527. session_.unsubscribe(module_name);
  528. }
  529. }
  530. ConstElementPtr
  531. ModuleCCSession::getRemoteConfigValue(const std::string& module_name,
  532. const std::string& identifier) const
  533. {
  534. std::map<std::string, ConfigData>::const_iterator it =
  535. remote_module_configs_.find(module_name);
  536. if (it != remote_module_configs_.end()) {
  537. return ((*it).second.getValue(identifier));
  538. } else {
  539. isc_throw(CCSessionError,
  540. "Remote module " + module_name + " not found.");
  541. }
  542. }
  543. void
  544. ModuleCCSession::updateRemoteConfig(const std::string& module_name,
  545. ConstElementPtr new_config)
  546. {
  547. std::map<std::string, ConfigData>::iterator it;
  548. it = remote_module_configs_.find(module_name);
  549. if (it != remote_module_configs_.end()) {
  550. ElementPtr rconf = (*it).second.getLocalConfig();
  551. isc::data::merge(rconf, new_config);
  552. std::map<std::string, RemoteHandler>::iterator hit =
  553. remote_module_handlers_.find(module_name);
  554. if (hit != remote_module_handlers_.end()) {
  555. hit->second(module_name, new_config, it->second);
  556. }
  557. }
  558. }
  559. }
  560. }