ccsession.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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. using namespace std;
  32. using isc::data::Element;
  33. using isc::data::ConstElementPtr;
  34. using isc::data::ElementPtr;
  35. using isc::data::JSONError;
  36. namespace isc {
  37. namespace config {
  38. /// Creates a standard config/command protocol answer message
  39. ConstElementPtr
  40. createAnswer() {
  41. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  42. ElementPtr answer_content = Element::createList();
  43. answer_content->add(Element::create(0));
  44. answer->set("result", answer_content);
  45. return (answer);
  46. }
  47. ConstElementPtr
  48. createAnswer(const int rcode, ConstElementPtr arg) {
  49. if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
  50. isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
  51. }
  52. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  53. ElementPtr answer_content = Element::createList();
  54. answer_content->add(Element::create(rcode));
  55. answer_content->add(arg);
  56. answer->set("result", answer_content);
  57. return (answer);
  58. }
  59. ConstElementPtr
  60. createAnswer(const int rcode, const std::string& arg) {
  61. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  62. ElementPtr answer_content = Element::createList();
  63. answer_content->add(Element::create(rcode));
  64. answer_content->add(Element::create(arg));
  65. answer->set("result", answer_content);
  66. return (answer);
  67. }
  68. ConstElementPtr
  69. parseAnswer(int &rcode, ConstElementPtr msg) {
  70. if (msg &&
  71. msg->getType() == Element::map &&
  72. msg->contains("result")) {
  73. ConstElementPtr result = msg->get("result");
  74. if (result->getType() != Element::list) {
  75. isc_throw(CCSessionError, "Result element in answer message is not a list");
  76. } else if (result->get(0)->getType() != Element::integer) {
  77. isc_throw(CCSessionError, "First element of result is not an rcode in answer message");
  78. }
  79. rcode = result->get(0)->intValue();
  80. if (result->size() > 1) {
  81. if (rcode == 0 || result->get(1)->getType() == Element::string) {
  82. return (result->get(1));
  83. } else {
  84. isc_throw(CCSessionError, "Error description in result with rcode != 0 is not a string");
  85. }
  86. } else {
  87. if (rcode == 0) {
  88. return (ElementPtr());
  89. } else {
  90. isc_throw(CCSessionError, "Result with rcode != 0 does not have an error description");
  91. }
  92. }
  93. } else {
  94. isc_throw(CCSessionError, "No result part in answer message");
  95. }
  96. }
  97. ConstElementPtr
  98. createCommand(const std::string& command) {
  99. return (createCommand(command, ElementPtr()));
  100. }
  101. ConstElementPtr
  102. createCommand(const std::string& command, ConstElementPtr arg) {
  103. ElementPtr cmd = Element::createMap();
  104. ElementPtr cmd_parts = Element::createList();
  105. cmd_parts->add(Element::create(command));
  106. if (arg) {
  107. cmd_parts->add(arg);
  108. }
  109. cmd->set("command", cmd_parts);
  110. return (cmd);
  111. }
  112. std::string
  113. parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
  114. if (command &&
  115. command->getType() == Element::map &&
  116. command->contains("command")) {
  117. ConstElementPtr cmd = command->get("command");
  118. if (cmd->getType() == Element::list &&
  119. cmd->size() > 0 &&
  120. cmd->get(0)->getType() == Element::string) {
  121. if (cmd->size() > 1) {
  122. arg = cmd->get(1);
  123. } else {
  124. arg = Element::createMap();
  125. }
  126. return (cmd->get(0)->stringValue());
  127. } else {
  128. isc_throw(CCSessionError, "Command part in command message missing, empty, or not a list");
  129. }
  130. } else {
  131. isc_throw(CCSessionError, "Command Element empty or not a map with \"command\"");
  132. }
  133. }
  134. ModuleSpec
  135. ModuleCCSession::readModuleSpecification(const std::string& filename) {
  136. std::ifstream file;
  137. ModuleSpec module_spec;
  138. // this file should be declared in a @something@ directive
  139. file.open(filename.c_str());
  140. if (!file) {
  141. LOG_ERROR(config_logger, CONFIG_FOPEN_ERR).arg(filename).arg(strerror(errno));
  142. isc_throw(CCSessionInitError, strerror(errno));
  143. }
  144. try {
  145. module_spec = moduleSpecFromFile(file, true);
  146. } catch (const JSONError& pe) {
  147. LOG_ERROR(config_logger, CONFIG_JSON_PARSE).arg(filename).arg(pe.what());
  148. isc_throw(CCSessionInitError, pe.what());
  149. } catch (const ModuleSpecError& dde) {
  150. LOG_ERROR(config_logger, CONFIG_MODULE_SPEC).arg(filename).arg(dde.what());
  151. isc_throw(CCSessionInitError, dde.what());
  152. }
  153. file.close();
  154. return (module_spec);
  155. }
  156. void
  157. ModuleCCSession::startCheck() {
  158. // data available on the command channel. process it in the synchronous
  159. // mode.
  160. checkCommand();
  161. // start asynchronous read again.
  162. session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
  163. }
  164. ModuleCCSession::ModuleCCSession(
  165. const std::string& spec_file_name,
  166. isc::cc::AbstractSession& session,
  167. isc::data::ConstElementPtr(*config_handler)(
  168. isc::data::ConstElementPtr new_config),
  169. isc::data::ConstElementPtr(*command_handler)(
  170. const std::string& command, isc::data::ConstElementPtr args),
  171. bool start_immediately, const char* socket_path
  172. ) :
  173. started_(false),
  174. session_(session)
  175. {
  176. module_specification_ = readModuleSpecification(spec_file_name);
  177. setModuleSpec(module_specification_);
  178. module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue();
  179. config_handler_ = config_handler;
  180. command_handler_ = command_handler;
  181. session_.establish(socket_path);
  182. session_.subscribe(module_name_, "*");
  183. //session_.subscribe("Boss", "*");
  184. //session_.subscribe("statistics", "*");
  185. // send the data specification
  186. ConstElementPtr spec_msg = createCommand("module_spec",
  187. module_specification_.getFullSpec());
  188. unsigned int seq = session_.group_sendmsg(spec_msg, "ConfigManager");
  189. ConstElementPtr answer, env;
  190. session_.group_recvmsg(env, answer, false, seq);
  191. int rcode;
  192. ConstElementPtr err = parseAnswer(rcode, answer);
  193. if (rcode != 0) {
  194. LOG_ERROR(config_logger, CONFIG_MANAGER_MOD_SPEC).arg(answer->str());
  195. isc_throw(CCSessionInitError, answer->str());
  196. }
  197. setLocalConfig(Element::fromJSON("{}"));
  198. // get any stored configuration from the manager
  199. if (config_handler_) {
  200. ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
  201. seq = session_.group_sendmsg(cmd, "ConfigManager");
  202. session_.group_recvmsg(env, answer, false, seq);
  203. ConstElementPtr new_config = parseAnswer(rcode, answer);
  204. if (rcode == 0) {
  205. handleConfigUpdate(new_config);
  206. } else {
  207. LOG_ERROR(config_logger, CONFIG_MANAGER_CONFIG).arg(new_config->str());
  208. isc_throw(CCSessionInitError, answer->str());
  209. }
  210. }
  211. if (start_immediately) {
  212. start();
  213. }
  214. }
  215. void
  216. ModuleCCSession::start() {
  217. if (started_) {
  218. isc_throw(CCSessionError, "Module CC session already started");
  219. }
  220. // register callback for asynchronous read
  221. session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
  222. }
  223. /// Validates the new config values, if they are correct,
  224. /// call the config handler with the values that have changed
  225. /// If that results in success, store the new config
  226. ConstElementPtr
  227. ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
  228. ConstElementPtr answer;
  229. ElementPtr errors = Element::createList();
  230. if (!config_handler_) {
  231. answer = createAnswer(1, module_name_ + " does not have a config handler");
  232. } else if (!module_specification_.validateConfig(new_config, false,
  233. errors)) {
  234. std::stringstream ss;
  235. ss << "Error in config validation: ";
  236. BOOST_FOREACH(ConstElementPtr error, errors->listValue()) {
  237. ss << error->stringValue();
  238. }
  239. answer = createAnswer(2, ss.str());
  240. } else {
  241. // remove the values that have not changed
  242. ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
  243. // handle config update
  244. answer = config_handler_(diff);
  245. int rcode;
  246. parseAnswer(rcode, answer);
  247. if (rcode == 0) {
  248. ElementPtr local_config = getLocalConfig();
  249. isc::data::merge(local_config, diff);
  250. setLocalConfig(local_config);
  251. }
  252. }
  253. return (answer);
  254. }
  255. bool
  256. ModuleCCSession::hasQueuedMsgs() const {
  257. return (session_.hasQueuedMsgs());
  258. }
  259. ConstElementPtr
  260. ModuleCCSession::checkConfigUpdateCommand(const std::string& target_module,
  261. ConstElementPtr arg)
  262. {
  263. if (target_module == module_name_) {
  264. return (handleConfigUpdate(arg));
  265. } else {
  266. // ok this update is not for us, if we have this module
  267. // in our remote config list, update that
  268. updateRemoteConfig(target_module, arg);
  269. // we're not supposed to answer to this, so return
  270. return (ElementPtr());
  271. }
  272. }
  273. ConstElementPtr
  274. ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
  275. const std::string& target_module,
  276. ConstElementPtr arg) const
  277. {
  278. if (target_module == module_name_) {
  279. if (command_handler_) {
  280. ElementPtr errors = Element::createList();
  281. if (module_specification_.validateCommand(cmd_str,
  282. arg,
  283. errors)) {
  284. return (command_handler_(cmd_str, arg));
  285. } else {
  286. std::stringstream ss;
  287. ss << "Error in command validation: ";
  288. BOOST_FOREACH(ConstElementPtr error,
  289. errors->listValue()) {
  290. ss << error->stringValue();
  291. }
  292. return (createAnswer(3, ss.str()));
  293. }
  294. } else {
  295. return (createAnswer(1,
  296. "Command given but no "
  297. "command handler for module"));
  298. }
  299. }
  300. return (ElementPtr());
  301. }
  302. int
  303. ModuleCCSession::checkCommand() {
  304. ConstElementPtr cmd, routing, data;
  305. if (session_.group_recvmsg(routing, data, true)) {
  306. /* ignore result messages (in case we're out of sync, to prevent
  307. * pingpongs */
  308. if (data->getType() != Element::map || data->contains("result")) {
  309. return (0);
  310. }
  311. ConstElementPtr arg;
  312. ConstElementPtr answer;
  313. try {
  314. std::string cmd_str = parseCommand(arg, data);
  315. std::string target_module = routing->get("group")->stringValue();
  316. if (cmd_str == "config_update") {
  317. answer = checkConfigUpdateCommand(target_module, arg);
  318. } else {
  319. answer = checkModuleCommand(cmd_str, target_module, arg);
  320. }
  321. } catch (const CCSessionError& re) {
  322. LOG_ERROR(config_logger, CONFIG_CCSESSION_MSG).arg(re.what());
  323. }
  324. if (!isNull(answer)) {
  325. session_.reply(routing, answer);
  326. }
  327. }
  328. return (0);
  329. }
  330. std::string
  331. ModuleCCSession::fetchRemoteSpec(const std::string& module, bool is_filename,
  332. ModuleSpec& spec)
  333. {
  334. if (is_filename) {
  335. // It is a filename, simply load it.
  336. spec = readModuleSpecification(module);
  337. return (spec.getModuleName());
  338. } else {
  339. // It's module name, request it from config manager
  340. // Send the command
  341. ConstElementPtr cmd(createCommand("get_module_spec",
  342. Element::fromJSON("{\"module_name\": \"" + module +
  343. "\"}")));
  344. const unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
  345. // Wait for the answer
  346. ConstElementPtr env, answer;
  347. session_.group_recvmsg(env, answer, false, seq);
  348. int rcode;
  349. ConstElementPtr spec_data = parseAnswer(rcode, answer);
  350. if (rcode == 0 && spec_data) {
  351. // received OK, construct the spec out of it
  352. spec = ModuleSpec(spec_data);
  353. if (module != spec.getModuleName()) {
  354. // It's a different module!
  355. isc_throw(CCSessionError, "Module name mismatch");
  356. }
  357. return (module);
  358. } else {
  359. isc_throw(CCSessionError, "Error getting config for " +
  360. module + ": " + answer->str());
  361. }
  362. }
  363. }
  364. std::string
  365. ModuleCCSession::addRemoteConfig(const std::string& spec_name,
  366. void (*handler)(const std::string& module,
  367. ConstElementPtr),
  368. bool spec_is_filename)
  369. {
  370. // First get the module name, specification and default config
  371. ModuleSpec rmod_spec;
  372. const std::string module_name(fetchRemoteSpec(spec_name, spec_is_filename,
  373. rmod_spec));
  374. ConfigData rmod_config(rmod_spec);
  375. // Get the current configuration values from config manager
  376. ConstElementPtr cmd(createCommand("get_config",
  377. Element::fromJSON("{\"module_name\": \"" +
  378. module_name + "\"}")));
  379. const unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
  380. ConstElementPtr env, answer;
  381. session_.group_recvmsg(env, answer, false, seq);
  382. int rcode;
  383. ConstElementPtr new_config = parseAnswer(rcode, answer);
  384. ElementPtr local_config;
  385. if (rcode == 0 && new_config) {
  386. // Merge the received config into existing local config
  387. local_config = rmod_config.getLocalConfig();
  388. isc::data::merge(local_config, new_config);
  389. rmod_config.setLocalConfig(local_config);
  390. } else {
  391. isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
  392. }
  393. // all ok, add it
  394. remote_module_configs_[module_name] = rmod_config;
  395. if (handler) {
  396. remote_module_handlers_[module_name] = handler;
  397. handler(module_name, local_config);
  398. }
  399. // Make sure we get updates in future
  400. session_.subscribe(module_name);
  401. return (module_name);
  402. }
  403. void
  404. ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
  405. std::map<std::string, ConfigData>::iterator it;
  406. it = remote_module_configs_.find(module_name);
  407. if (it != remote_module_configs_.end()) {
  408. remote_module_configs_.erase(it);
  409. remote_module_handlers_.erase(module_name);
  410. session_.unsubscribe(module_name);
  411. }
  412. }
  413. ConstElementPtr
  414. ModuleCCSession::getRemoteConfigValue(const std::string& module_name,
  415. const std::string& identifier) const
  416. {
  417. std::map<std::string, ConfigData>::const_iterator it =
  418. remote_module_configs_.find(module_name);
  419. if (it != remote_module_configs_.end()) {
  420. return ((*it).second.getValue(identifier));
  421. } else {
  422. isc_throw(CCSessionError,
  423. "Remote module " + module_name + " not found.");
  424. }
  425. }
  426. void
  427. ModuleCCSession::updateRemoteConfig(const std::string& module_name,
  428. ConstElementPtr new_config)
  429. {
  430. std::map<std::string, ConfigData>::iterator it;
  431. it = remote_module_configs_.find(module_name);
  432. if (it != remote_module_configs_.end()) {
  433. ElementPtr rconf = (*it).second.getLocalConfig();
  434. isc::data::merge(rconf, new_config);
  435. std::map<std::string, RemoteHandler>::iterator hit =
  436. remote_module_handlers_.find(module_name);
  437. if (hit != remote_module_handlers_.end()) {
  438. hit->second(module_name, new_config);
  439. }
  440. }
  441. }
  442. }
  443. }