ccsession.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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. // $Id$
  15. //
  16. // todo: generalize this and make it into a specific API for all modules
  17. // to use (i.e. connect to cc, send config and commands, get config,
  18. // react on config change announcements)
  19. //
  20. #include <config.h>
  21. #include <stdexcept>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <sys/time.h>
  25. #include <iostream>
  26. #include <fstream>
  27. #include <sstream>
  28. #include <cerrno>
  29. #include <boost/bind.hpp>
  30. #include <boost/foreach.hpp>
  31. #include <cc/data.h>
  32. #include <module_spec.h>
  33. #include <cc/session.h>
  34. #include <exceptions/exceptions.h>
  35. #include <config/ccsession.h>
  36. using namespace std;
  37. using isc::data::Element;
  38. using isc::data::ConstElementPtr;
  39. using isc::data::ElementPtr;
  40. using isc::data::JSONError;
  41. namespace isc {
  42. namespace config {
  43. /// Creates a standard config/command protocol answer message
  44. ConstElementPtr
  45. createAnswer() {
  46. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  47. ElementPtr answer_content = Element::createList();
  48. answer_content->add(Element::create(0));
  49. answer->set("result", answer_content);
  50. return (answer);
  51. }
  52. ConstElementPtr
  53. createAnswer(const int rcode, ConstElementPtr arg) {
  54. if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
  55. isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
  56. }
  57. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  58. ElementPtr answer_content = Element::createList();
  59. answer_content->add(Element::create(rcode));
  60. answer_content->add(arg);
  61. answer->set("result", answer_content);
  62. return (answer);
  63. }
  64. ConstElementPtr
  65. createAnswer(const int rcode, const std::string& arg) {
  66. ElementPtr answer = Element::fromJSON("{\"result\": [] }");
  67. ElementPtr answer_content = Element::createList();
  68. answer_content->add(Element::create(rcode));
  69. answer_content->add(Element::create(arg));
  70. answer->set("result", answer_content);
  71. return (answer);
  72. }
  73. ConstElementPtr
  74. parseAnswer(int &rcode, ConstElementPtr msg) {
  75. if (msg &&
  76. msg->getType() == Element::map &&
  77. msg->contains("result")) {
  78. ConstElementPtr result = msg->get("result");
  79. if (result->getType() != Element::list) {
  80. isc_throw(CCSessionError, "Result element in answer message is not a list");
  81. } else if (result->get(0)->getType() != Element::integer) {
  82. isc_throw(CCSessionError, "First element of result is not an rcode in answer message");
  83. }
  84. rcode = result->get(0)->intValue();
  85. if (result->size() > 1) {
  86. if (rcode == 0 || result->get(1)->getType() == Element::string) {
  87. return (result->get(1));
  88. } else {
  89. isc_throw(CCSessionError, "Error description in result with rcode != 0 is not a string");
  90. }
  91. } else {
  92. if (rcode == 0) {
  93. return (ElementPtr());
  94. } else {
  95. isc_throw(CCSessionError, "Result with rcode != 0 does not have an error description");
  96. }
  97. }
  98. } else {
  99. isc_throw(CCSessionError, "No result part in answer message");
  100. }
  101. }
  102. ConstElementPtr
  103. createCommand(const std::string& command) {
  104. return (createCommand(command, ElementPtr()));
  105. }
  106. ConstElementPtr
  107. createCommand(const std::string& command, ConstElementPtr arg) {
  108. ElementPtr cmd = Element::createMap();
  109. ElementPtr cmd_parts = Element::createList();
  110. cmd_parts->add(Element::create(command));
  111. if (arg) {
  112. cmd_parts->add(arg);
  113. }
  114. cmd->set("command", cmd_parts);
  115. return (cmd);
  116. }
  117. std::string
  118. parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
  119. if (command &&
  120. command->getType() == Element::map &&
  121. command->contains("command")) {
  122. ConstElementPtr cmd = command->get("command");
  123. if (cmd->getType() == Element::list &&
  124. cmd->size() > 0 &&
  125. cmd->get(0)->getType() == Element::string) {
  126. if (cmd->size() > 1) {
  127. arg = cmd->get(1);
  128. } else {
  129. arg = Element::createMap();
  130. }
  131. return (cmd->get(0)->stringValue());
  132. } else {
  133. isc_throw(CCSessionError, "Command part in command message missing, empty, or not a list");
  134. }
  135. } else {
  136. isc_throw(CCSessionError, "Command Element empty or not a map with \"command\"");
  137. }
  138. }
  139. ModuleSpec
  140. ModuleCCSession::readModuleSpecification(const std::string& filename) {
  141. std::ifstream file;
  142. ModuleSpec module_spec;
  143. // this file should be declared in a @something@ directive
  144. file.open(filename.c_str());
  145. if (!file) {
  146. cout << "error opening " << filename << ": " << strerror(errno) << endl;
  147. exit(1);
  148. }
  149. try {
  150. module_spec = moduleSpecFromFile(file, true);
  151. } catch (JSONError pe) {
  152. cout << "Error parsing module specification file: " << pe.what() << endl;
  153. exit(1);
  154. } catch (ModuleSpecError dde) {
  155. cout << "Error reading module specification file: " << dde.what() << endl;
  156. exit(1);
  157. }
  158. file.close();
  159. return (module_spec);
  160. }
  161. void
  162. ModuleCCSession::startCheck() {
  163. // data available on the command channel. process it in the synchronous
  164. // mode.
  165. checkCommand();
  166. // start asynchronous read again.
  167. session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
  168. }
  169. ModuleCCSession::ModuleCCSession(
  170. const std::string& spec_file_name,
  171. isc::cc::AbstractSession& session,
  172. isc::data::ConstElementPtr(*config_handler)(
  173. isc::data::ConstElementPtr new_config),
  174. isc::data::ConstElementPtr(*command_handler)(
  175. const std::string& command, isc::data::ConstElementPtr args)
  176. ) :
  177. session_(session)
  178. {
  179. module_specification_ = readModuleSpecification(spec_file_name);
  180. setModuleSpec(module_specification_);
  181. module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue();
  182. config_handler_ = config_handler;
  183. command_handler_ = command_handler;
  184. session_.establish(NULL);
  185. session_.subscribe(module_name_, "*");
  186. //session_.subscribe("Boss", "*");
  187. //session_.subscribe("statistics", "*");
  188. // send the data specification
  189. ConstElementPtr spec_msg = createCommand("module_spec",
  190. module_specification_.getFullSpec());
  191. unsigned int seq = session_.group_sendmsg(spec_msg, "ConfigManager");
  192. ConstElementPtr answer, env;
  193. session_.group_recvmsg(env, answer, false, seq);
  194. int rcode;
  195. ConstElementPtr err = parseAnswer(rcode, answer);
  196. if (rcode != 0) {
  197. std::cerr << "[" << module_name_ << "] Error in specification: " << answer << std::endl;
  198. }
  199. setLocalConfig(Element::fromJSON("{}"));
  200. // get any stored configuration from the manager
  201. if (config_handler_) {
  202. ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
  203. seq = session_.group_sendmsg(cmd, "ConfigManager");
  204. session_.group_recvmsg(env, answer, false, seq);
  205. ConstElementPtr new_config = parseAnswer(rcode, answer);
  206. if (rcode == 0) {
  207. handleConfigUpdate(new_config);
  208. } else {
  209. std::cerr << "[" << module_name_ << "] Error getting config: " << new_config << std::endl;
  210. }
  211. }
  212. // register callback for asynchronous read
  213. session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
  214. }
  215. /// Validates the new config values, if they are correct,
  216. /// call the config handler with the values that have changed
  217. /// If that results in success, store the new config
  218. ConstElementPtr
  219. ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
  220. ConstElementPtr answer;
  221. ElementPtr errors = Element::createList();
  222. if (!config_handler_) {
  223. answer = createAnswer(1, module_name_ + " does not have a config handler");
  224. } else if (!module_specification_.validateConfig(new_config, false,
  225. errors)) {
  226. std::stringstream ss;
  227. ss << "Error in config validation: ";
  228. BOOST_FOREACH(ConstElementPtr error, errors->listValue()) {
  229. ss << error->stringValue();
  230. }
  231. answer = createAnswer(2, ss.str());
  232. } else {
  233. // remove the values that have not changed
  234. ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
  235. // handle config update
  236. answer = config_handler_(diff);
  237. int rcode;
  238. parseAnswer(rcode, answer);
  239. if (rcode == 0) {
  240. ElementPtr local_config = getLocalConfig();
  241. isc::data::merge(local_config, diff);
  242. setLocalConfig(local_config);
  243. }
  244. }
  245. return (answer);
  246. }
  247. bool
  248. ModuleCCSession::hasQueuedMsgs() const {
  249. return (session_.hasQueuedMsgs());
  250. }
  251. ConstElementPtr
  252. ModuleCCSession::checkConfigUpdateCommand(const std::string& target_module,
  253. ConstElementPtr arg)
  254. {
  255. if (target_module == module_name_) {
  256. return (handleConfigUpdate(arg));
  257. } else {
  258. // ok this update is not for us, if we have this module
  259. // in our remote config list, update that
  260. updateRemoteConfig(target_module, arg);
  261. // we're not supposed to answer to this, so return
  262. return (ElementPtr());
  263. }
  264. }
  265. ConstElementPtr
  266. ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
  267. const std::string& target_module,
  268. ConstElementPtr arg) const
  269. {
  270. if (target_module == module_name_) {
  271. if (command_handler_) {
  272. ElementPtr errors = Element::createList();
  273. if (module_specification_.validateCommand(cmd_str,
  274. arg,
  275. errors)) {
  276. return (command_handler_(cmd_str, arg));
  277. } else {
  278. std::stringstream ss;
  279. ss << "Error in command validation: ";
  280. BOOST_FOREACH(ConstElementPtr error,
  281. errors->listValue()) {
  282. ss << error->stringValue();
  283. }
  284. return (createAnswer(3, ss.str()));
  285. }
  286. } else {
  287. return (createAnswer(1,
  288. "Command given but no "
  289. "command handler for module"));
  290. }
  291. }
  292. return (ElementPtr());
  293. }
  294. int
  295. ModuleCCSession::checkCommand() {
  296. ConstElementPtr cmd, routing, data;
  297. if (session_.group_recvmsg(routing, data, true)) {
  298. /* ignore result messages (in case we're out of sync, to prevent
  299. * pingpongs */
  300. if (data->getType() != Element::map || data->contains("result")) {
  301. return (0);
  302. }
  303. ConstElementPtr arg;
  304. ConstElementPtr answer;
  305. try {
  306. std::string cmd_str = parseCommand(arg, data);
  307. std::string target_module = routing->get("group")->stringValue();
  308. if (cmd_str == "config_update") {
  309. answer = checkConfigUpdateCommand(target_module, arg);
  310. } else {
  311. answer = checkModuleCommand(cmd_str, target_module, arg);
  312. }
  313. } catch (const CCSessionError& re) {
  314. // TODO: Once we have logging and timeouts, we should not
  315. // answer here (potential interference)
  316. answer = createAnswer(1, re.what());
  317. }
  318. if (!isNull(answer)) {
  319. session_.reply(routing, answer);
  320. }
  321. }
  322. return (0);
  323. }
  324. std::string
  325. ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
  326. ModuleSpec rmod_spec = readModuleSpecification(spec_file_name);
  327. std::string module_name = rmod_spec.getFullSpec()->get("module_name")->stringValue();
  328. ConfigData rmod_config = ConfigData(rmod_spec);
  329. session_.subscribe(module_name);
  330. // Get the current configuration values for that module
  331. ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
  332. unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
  333. ConstElementPtr env, answer;
  334. session_.group_recvmsg(env, answer, false, seq);
  335. int rcode;
  336. ConstElementPtr new_config = parseAnswer(rcode, answer);
  337. if (rcode == 0 && new_config) {
  338. ElementPtr local_config = rmod_config.getLocalConfig();
  339. isc::data::merge(local_config, new_config);
  340. rmod_config.setLocalConfig(local_config);
  341. } else {
  342. isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
  343. }
  344. // all ok, add it
  345. remote_module_configs_[module_name] = rmod_config;
  346. return (module_name);
  347. }
  348. void
  349. ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
  350. std::map<std::string, ConfigData>::iterator it;
  351. it = remote_module_configs_.find(module_name);
  352. if (it != remote_module_configs_.end()) {
  353. remote_module_configs_.erase(it);
  354. session_.unsubscribe(module_name);
  355. }
  356. }
  357. ConstElementPtr
  358. ModuleCCSession::getRemoteConfigValue(const std::string& module_name,
  359. const std::string& identifier) const
  360. {
  361. std::map<std::string, ConfigData>::const_iterator it =
  362. remote_module_configs_.find(module_name);
  363. if (it != remote_module_configs_.end()) {
  364. return ((*it).second.getValue(identifier));
  365. } else {
  366. isc_throw(CCSessionError,
  367. "Remote module " + module_name + " not found.");
  368. }
  369. }
  370. void
  371. ModuleCCSession::updateRemoteConfig(const std::string& module_name,
  372. ConstElementPtr new_config)
  373. {
  374. std::map<std::string, ConfigData>::iterator it;
  375. it = remote_module_configs_.find(module_name);
  376. if (it != remote_module_configs_.end()) {
  377. ElementPtr rconf = (*it).second.getLocalConfig();
  378. isc::data::merge(rconf, new_config);
  379. }
  380. }
  381. }
  382. }