module_spec.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. // Copyright (C) 2010, 2011 Internet Systems Consortium.
  2. //
  3. // Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
  8. // DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. // INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. // FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. // WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. #include <config/module_spec.h>
  16. #include <sstream>
  17. #include <iostream>
  18. #include <fstream>
  19. #include <cerrno>
  20. #include <boost/foreach.hpp>
  21. // todo: add more context to thrown ModuleSpecErrors?
  22. using namespace isc::data;
  23. using namespace isc::config;
  24. namespace {
  25. //
  26. // Private functions
  27. //
  28. void
  29. check_leaf_item(ConstElementPtr spec, const std::string& name,
  30. Element::types type, bool mandatory)
  31. {
  32. if (spec->contains(name)) {
  33. if (spec->get(name)->getType() == type) {
  34. return;
  35. } else {
  36. isc_throw(ModuleSpecError,
  37. name + " not of type " + Element::typeToName(type));
  38. }
  39. } else if (mandatory) {
  40. // todo: want parent item name, and perhaps some info about location
  41. // in list? or just catch and throw new...
  42. // or make this part non-throwing and check return value...
  43. isc_throw(ModuleSpecError, name + " missing in " + spec->str());
  44. }
  45. }
  46. void check_config_item_list(ConstElementPtr spec);
  47. void
  48. check_config_item(ConstElementPtr spec) {
  49. check_leaf_item(spec, "item_name", Element::string, true);
  50. check_leaf_item(spec, "item_type", Element::string, true);
  51. check_leaf_item(spec, "item_optional", Element::boolean, true);
  52. check_leaf_item(spec, "item_default",
  53. Element::nameToType(spec->get("item_type")->stringValue()),
  54. !spec->get("item_optional")->boolValue()
  55. );
  56. // if list, check the list specification
  57. if (Element::nameToType(spec->get("item_type")->stringValue()) == Element::list) {
  58. check_leaf_item(spec, "list_item_spec", Element::map, true);
  59. check_config_item(spec->get("list_item_spec"));
  60. }
  61. if (spec->get("item_type")->stringValue() == "map") {
  62. check_leaf_item(spec, "map_item_spec", Element::list, true);
  63. check_config_item_list(spec->get("map_item_spec"));
  64. } else if (spec->get("item_type")->stringValue() == "named_set") {
  65. check_leaf_item(spec, "named_set_item_spec", Element::map, true);
  66. check_config_item(spec->get("named_set_item_spec"));
  67. }
  68. }
  69. void
  70. check_config_item_list(ConstElementPtr spec) {
  71. if (spec->getType() != Element::list) {
  72. isc_throw(ModuleSpecError, "config_data is not a list of elements");
  73. }
  74. BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
  75. check_config_item(item);
  76. }
  77. }
  78. // checks whether the given element is a valid statistics specification
  79. // returns false if the specification is bad
  80. bool
  81. check_format(ConstElementPtr value, ConstElementPtr format_name) {
  82. typedef std::map<std::string, std::string> format_types;
  83. format_types time_formats;
  84. // TODO: should be added other format types if necessary
  85. time_formats.insert(
  86. format_types::value_type("date-time", "%Y-%m-%dT%H:%M:%SZ") );
  87. time_formats.insert(
  88. format_types::value_type("date", "%Y-%m-%d") );
  89. time_formats.insert(
  90. format_types::value_type("time", "%H:%M:%S") );
  91. BOOST_FOREACH (const format_types::value_type& f, time_formats) {
  92. if (format_name->stringValue() == f.first) {
  93. struct tm tm;
  94. std::vector<char> buf(32);
  95. memset(&tm, 0, sizeof(tm));
  96. // reverse check
  97. return (strptime(value->stringValue().c_str(),
  98. f.second.c_str(), &tm) != NULL
  99. && strftime(&buf[0], buf.size(),
  100. f.second.c_str(), &tm) != 0
  101. && strncmp(value->stringValue().c_str(),
  102. &buf[0], buf.size()) == 0);
  103. }
  104. }
  105. return (false);
  106. }
  107. void check_statistics_item_list(ConstElementPtr spec);
  108. void
  109. check_statistics_item_list(ConstElementPtr spec) {
  110. if (spec->getType() != Element::list) {
  111. isc_throw(ModuleSpecError, "statistics is not a list of elements");
  112. }
  113. BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
  114. check_config_item(item);
  115. // additional checks for statistics
  116. check_leaf_item(item, "item_title", Element::string, true);
  117. check_leaf_item(item, "item_description", Element::string, true);
  118. check_leaf_item(item, "item_format", Element::string, false);
  119. // checks name of item_format and validation of item_default
  120. if (item->contains("item_format")
  121. && item->contains("item_default")) {
  122. if(!check_format(item->get("item_default"),
  123. item->get("item_format"))) {
  124. isc_throw(ModuleSpecError,
  125. "item_default not valid type of item_format");
  126. }
  127. }
  128. }
  129. }
  130. void
  131. check_command(ConstElementPtr spec) {
  132. check_leaf_item(spec, "command_name", Element::string, true);
  133. check_leaf_item(spec, "command_args", Element::list, true);
  134. check_config_item_list(spec->get("command_args"));
  135. }
  136. void
  137. check_command_list(ConstElementPtr spec) {
  138. if (spec->getType() != Element::list) {
  139. isc_throw(ModuleSpecError, "commands is not a list of elements");
  140. }
  141. BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
  142. check_command(item);
  143. }
  144. }
  145. void
  146. check_data_specification(ConstElementPtr spec) {
  147. check_leaf_item(spec, "module_name", Element::string, true);
  148. check_leaf_item(spec, "module_description", Element::string, false);
  149. // config_data is not mandatory; module could just define
  150. // commands and have no config
  151. if (spec->contains("config_data")) {
  152. check_config_item_list(spec->get("config_data"));
  153. }
  154. if (spec->contains("commands")) {
  155. check_command_list(spec->get("commands"));
  156. }
  157. if (spec->contains("statistics")) {
  158. check_statistics_item_list(spec->get("statistics"));
  159. }
  160. }
  161. // checks whether the given element is a valid module specification
  162. // throws a ModuleSpecError if the specification is bad
  163. void
  164. check_module_specification(ConstElementPtr def) {
  165. try {
  166. check_data_specification(def);
  167. } catch (const TypeError& te) {
  168. isc_throw(ModuleSpecError, te.what());
  169. }
  170. }
  171. }
  172. namespace isc {
  173. namespace config {
  174. //
  175. // Public functions
  176. //
  177. ModuleSpec::ModuleSpec(ConstElementPtr module_spec_element,
  178. const bool check)
  179. throw(ModuleSpecError)
  180. {
  181. module_specification = module_spec_element;
  182. if (check) {
  183. check_module_specification(module_specification);
  184. }
  185. }
  186. ConstElementPtr
  187. ModuleSpec::getCommandsSpec() const {
  188. if (module_specification->contains("commands")) {
  189. return (module_specification->get("commands"));
  190. } else {
  191. return (ElementPtr());
  192. }
  193. }
  194. ConstElementPtr
  195. ModuleSpec::getConfigSpec() const {
  196. if (module_specification->contains("config_data")) {
  197. return (module_specification->get("config_data"));
  198. } else {
  199. return (ElementPtr());
  200. }
  201. }
  202. ConstElementPtr
  203. ModuleSpec::getStatisticsSpec() const {
  204. if (module_specification->contains("statistics")) {
  205. return (module_specification->get("statistics"));
  206. } else {
  207. return (ElementPtr());
  208. }
  209. }
  210. const std::string
  211. ModuleSpec::getModuleName() const {
  212. return (module_specification->get("module_name")->stringValue());
  213. }
  214. const std::string
  215. ModuleSpec::getModuleDescription() const {
  216. if (module_specification->contains("module_description")) {
  217. return (module_specification->get("module_description")->stringValue());
  218. } else {
  219. return (std::string(""));
  220. }
  221. }
  222. bool
  223. ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
  224. ConstElementPtr spec = module_specification->find("config_data");
  225. return (validateSpecList(spec, data, full, ElementPtr()));
  226. }
  227. bool
  228. ModuleSpec::validateStatistics(ConstElementPtr data, const bool full) const {
  229. ConstElementPtr spec = module_specification->find("statistics");
  230. return (validateSpecList(spec, data, full, ElementPtr()));
  231. }
  232. bool
  233. ModuleSpec::validateCommand(const std::string& command,
  234. ConstElementPtr args,
  235. ElementPtr errors) const
  236. {
  237. if (args->getType() != Element::map) {
  238. errors->add(Element::create("args for command " +
  239. command + " is not a map"));
  240. return (false);
  241. }
  242. ConstElementPtr commands_spec = module_specification->find("commands");
  243. if (!commands_spec) {
  244. // there are no commands according to the spec.
  245. errors->add(Element::create("The given module has no commands"));
  246. return (false);
  247. }
  248. BOOST_FOREACH(ConstElementPtr cur_command, commands_spec->listValue()) {
  249. if (cur_command->get("command_name")->stringValue() == command) {
  250. return (validateSpecList(cur_command->get("command_args"),
  251. args, true, errors));
  252. }
  253. }
  254. // this command is unknown
  255. errors->add(Element::create("Unknown command " + command));
  256. return (false);
  257. }
  258. bool
  259. ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
  260. ElementPtr errors) const
  261. {
  262. ConstElementPtr spec = module_specification->find("config_data");
  263. return (validateSpecList(spec, data, full, errors));
  264. }
  265. bool
  266. ModuleSpec::validateStatistics(ConstElementPtr data, const bool full,
  267. ElementPtr errors) const
  268. {
  269. ConstElementPtr spec = module_specification->find("statistics");
  270. return (validateSpecList(spec, data, full, errors));
  271. }
  272. ModuleSpec
  273. moduleSpecFromFile(const std::string& file_name, const bool check)
  274. throw(JSONError, ModuleSpecError)
  275. {
  276. std::ifstream file;
  277. file.open(file_name.c_str());
  278. if (!file) {
  279. std::stringstream errs;
  280. errs << "Error opening " << file_name << ": " << strerror(errno);
  281. isc_throw(ModuleSpecError, errs.str());
  282. }
  283. ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
  284. if (module_spec_element->contains("module_spec")) {
  285. return (ModuleSpec(module_spec_element->get("module_spec"), check));
  286. } else {
  287. isc_throw(ModuleSpecError, "No module_spec in specification");
  288. }
  289. }
  290. ModuleSpec
  291. moduleSpecFromFile(std::ifstream& in, const bool check)
  292. throw(JSONError, ModuleSpecError)
  293. {
  294. ConstElementPtr module_spec_element = Element::fromJSON(in);
  295. if (module_spec_element->contains("module_spec")) {
  296. return (ModuleSpec(module_spec_element->get("module_spec"), check));
  297. } else {
  298. isc_throw(ModuleSpecError, "No module_spec in specification");
  299. }
  300. }
  301. namespace {
  302. //
  303. // private functions
  304. //
  305. //
  306. // helper functions for validation
  307. //
  308. bool
  309. check_type(ConstElementPtr spec, ConstElementPtr element) {
  310. std::string cur_item_type;
  311. cur_item_type = spec->get("item_type")->stringValue();
  312. if (cur_item_type == "any") {
  313. return (true);
  314. }
  315. switch (element->getType()) {
  316. case Element::integer:
  317. return (cur_item_type == "integer");
  318. break;
  319. case Element::real:
  320. return (cur_item_type == "real");
  321. break;
  322. case Element::boolean:
  323. return (cur_item_type == "boolean");
  324. break;
  325. case Element::string:
  326. return (cur_item_type == "string");
  327. break;
  328. case Element::list:
  329. return (cur_item_type == "list");
  330. break;
  331. case Element::map:
  332. return (cur_item_type == "map" ||
  333. cur_item_type == "named_set");
  334. break;
  335. }
  336. return (false);
  337. }
  338. }
  339. bool
  340. ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
  341. const bool full, ElementPtr errors) const
  342. {
  343. if (!check_type(spec, data)) {
  344. // we should do some proper error feedback here
  345. // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
  346. // std::cout << spec << std::endl;
  347. if (errors) {
  348. errors->add(Element::create("Type mismatch"));
  349. }
  350. return (false);
  351. }
  352. if (data->getType() == Element::list) {
  353. ConstElementPtr list_spec = spec->get("list_item_spec");
  354. BOOST_FOREACH(ConstElementPtr list_el, data->listValue()) {
  355. if (!check_type(list_spec, list_el)) {
  356. if (errors) {
  357. errors->add(Element::create("Type mismatch"));
  358. }
  359. return (false);
  360. }
  361. if (list_spec->get("item_type")->stringValue() == "map") {
  362. if (!validateItem(list_spec, list_el, full, errors)) {
  363. return (false);
  364. }
  365. }
  366. }
  367. }
  368. if (data->getType() == Element::map) {
  369. // either a normal 'map' or a 'named set' (determined by which
  370. // subspecification it has)
  371. if (spec->contains("map_item_spec")) {
  372. if (!validateSpecList(spec->get("map_item_spec"), data, full, errors)) {
  373. return (false);
  374. }
  375. } else {
  376. typedef std::pair<std::string, ConstElementPtr> maptype;
  377. BOOST_FOREACH(maptype m, data->mapValue()) {
  378. if (!validateItem(spec->get("named_set_item_spec"), m.second, full, errors)) {
  379. return (false);
  380. }
  381. }
  382. }
  383. }
  384. if (spec->contains("item_format")) {
  385. if (!check_format(data, spec->get("item_format"))) {
  386. if (errors) {
  387. errors->add(Element::create("Format mismatch"));
  388. }
  389. return (false);
  390. }
  391. }
  392. return (true);
  393. }
  394. // spec is a map with item_name etc, data is a map
  395. bool
  396. ModuleSpec::validateSpec(ConstElementPtr spec, ConstElementPtr data,
  397. const bool full, ElementPtr errors) const
  398. {
  399. std::string item_name = spec->get("item_name")->stringValue();
  400. bool optional = spec->get("item_optional")->boolValue();
  401. ConstElementPtr data_el;
  402. data_el = data->get(item_name);
  403. if (data_el) {
  404. if (!validateItem(spec, data_el, full, errors)) {
  405. return (false);
  406. }
  407. } else {
  408. if (!optional && full) {
  409. if (errors) {
  410. errors->add(Element::create("Non-optional value missing"));
  411. }
  412. return (false);
  413. }
  414. }
  415. return (true);
  416. }
  417. // spec is a list of maps, data is a map
  418. bool
  419. ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
  420. const bool full, ElementPtr errors) const
  421. {
  422. bool validated = true;
  423. BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
  424. if (!validateSpec(cur_spec_el, data, full, errors)) {
  425. validated = false;
  426. }
  427. }
  428. typedef std::pair<std::string, ConstElementPtr> maptype;
  429. BOOST_FOREACH(maptype m, data->mapValue()) {
  430. bool found = false;
  431. // Ignore 'version' as a config element
  432. if (m.first.compare("version") != 0) {
  433. BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
  434. if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
  435. found = true;
  436. }
  437. }
  438. if (!found) {
  439. validated = false;
  440. if (errors) {
  441. errors->add(Element::create("Unknown item " + m.first));
  442. }
  443. }
  444. }
  445. }
  446. return (validated);
  447. }
  448. }
  449. }