cisco_node_utils_mgx 2.1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +96 -0
  5. data/.travis.yml +17 -0
  6. data/CHANGELOG.md +676 -0
  7. data/CONTRIBUTING.md +43 -0
  8. data/Gemfile +10 -0
  9. data/LICENSE +201 -0
  10. data/README.md +246 -0
  11. data/Rakefile +44 -0
  12. data/SUPPORT.md +3 -0
  13. data/bin/.rubocop.yml +18 -0
  14. data/bin/check_metric_limits.rb +109 -0
  15. data/bin/git/hooks/commit-msg/enforce_style +89 -0
  16. data/bin/git/hooks/hook_lib +115 -0
  17. data/bin/git/hooks/hooks-wrapper +38 -0
  18. data/bin/git/hooks/post-flow-hotfix-start/update-version +24 -0
  19. data/bin/git/hooks/post-flow-release-finish/update-version +29 -0
  20. data/bin/git/hooks/post-flow-release-start/update-version +19 -0
  21. data/bin/git/hooks/post-merge/update-hooks +6 -0
  22. data/bin/git/hooks/post-rewrite/update-hooks +6 -0
  23. data/bin/git/hooks/pre-commit/check_unstaged_changes +18 -0
  24. data/bin/git/hooks/pre-commit/rubocop +25 -0
  25. data/bin/git/hooks/pre-commit/validate-diffs +45 -0
  26. data/bin/git/hooks/pre-commit/validate-yaml +18 -0
  27. data/bin/git/hooks/pre-push/check-changelog +24 -0
  28. data/bin/git/hooks/pre-push/rubocop +7 -0
  29. data/bin/git/update-hooks +123 -0
  30. data/bin/show_running_yang.rb +233 -0
  31. data/cisco_node_utils.gemspec +41 -0
  32. data/docs/README-develop-best-practices.md +521 -0
  33. data/docs/README-develop-node-utils-APIs.md +570 -0
  34. data/docs/README-maintainers.md +77 -0
  35. data/docs/README-test-execution.md +57 -0
  36. data/docs/README-utilities.md +14 -0
  37. data/docs/agent_files.png +0 -0
  38. data/docs/cisco_node_utils.yaml.example +36 -0
  39. data/docs/template-router.rb +123 -0
  40. data/docs/template-test_router.rb +104 -0
  41. data/ext/mkrf_conf.rb +63 -0
  42. data/lib/.rubocop.yml +18 -0
  43. data/lib/cisco_node_utils/aaa_authentication_login.rb +95 -0
  44. data/lib/cisco_node_utils/aaa_authentication_login_service.rb +138 -0
  45. data/lib/cisco_node_utils/aaa_authorization_service.rb +156 -0
  46. data/lib/cisco_node_utils/ace.rb +467 -0
  47. data/lib/cisco_node_utils/acl.rb +101 -0
  48. data/lib/cisco_node_utils/banner.rb +63 -0
  49. data/lib/cisco_node_utils/bfd_global.rb +305 -0
  50. data/lib/cisco_node_utils/bgp.rb +988 -0
  51. data/lib/cisco_node_utils/bgp_af.rb +545 -0
  52. data/lib/cisco_node_utils/bgp_af_aggr_addr.rb +207 -0
  53. data/lib/cisco_node_utils/bgp_neighbor.rb +527 -0
  54. data/lib/cisco_node_utils/bgp_neighbor_af.rb +780 -0
  55. data/lib/cisco_node_utils/bridge_domain.rb +178 -0
  56. data/lib/cisco_node_utils/bridge_domain_vni.rb +206 -0
  57. data/lib/cisco_node_utils/cisco_cmn_utils.rb +444 -0
  58. data/lib/cisco_node_utils/client/client.rb +238 -0
  59. data/lib/cisco_node_utils/client/grpc/client.rb +395 -0
  60. data/lib/cisco_node_utils/client/grpc/ems.proto +148 -0
  61. data/lib/cisco_node_utils/client/grpc/ems.rb +111 -0
  62. data/lib/cisco_node_utils/client/grpc/ems_services.rb +49 -0
  63. data/lib/cisco_node_utils/client/grpc.rb +33 -0
  64. data/lib/cisco_node_utils/client/nxapi/client.rb +368 -0
  65. data/lib/cisco_node_utils/client/nxapi.rb +31 -0
  66. data/lib/cisco_node_utils/client/utils.rb +180 -0
  67. data/lib/cisco_node_utils/client.rb +35 -0
  68. data/lib/cisco_node_utils/cmd_ref/README_YAML.md +590 -0
  69. data/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml +25 -0
  70. data/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml +38 -0
  71. data/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml +40 -0
  72. data/lib/cisco_node_utils/cmd_ref/acl.yaml +48 -0
  73. data/lib/cisco_node_utils/cmd_ref/banner.yaml +11 -0
  74. data/lib/cisco_node_utils/cmd_ref/bfd_global.yaml +117 -0
  75. data/lib/cisco_node_utils/cmd_ref/bgp.yaml +383 -0
  76. data/lib/cisco_node_utils/cmd_ref/bgp_af.yaml +223 -0
  77. data/lib/cisco_node_utils/cmd_ref/bgp_af_aa.yaml +38 -0
  78. data/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml +174 -0
  79. data/lib/cisco_node_utils/cmd_ref/bgp_neighbor_af.yaml +236 -0
  80. data/lib/cisco_node_utils/cmd_ref/bridge_domain.yaml +49 -0
  81. data/lib/cisco_node_utils/cmd_ref/bridge_domain_vni.yaml +33 -0
  82. data/lib/cisco_node_utils/cmd_ref/dhcp_relay_global.yaml +128 -0
  83. data/lib/cisco_node_utils/cmd_ref/dnsclient.yaml +55 -0
  84. data/lib/cisco_node_utils/cmd_ref/encapsulation.yaml +25 -0
  85. data/lib/cisco_node_utils/cmd_ref/evpn_multicast.yaml +12 -0
  86. data/lib/cisco_node_utils/cmd_ref/evpn_multisite.yaml +18 -0
  87. data/lib/cisco_node_utils/cmd_ref/evpn_stormcontrol.yaml +18 -0
  88. data/lib/cisco_node_utils/cmd_ref/evpn_vni.yaml +48 -0
  89. data/lib/cisco_node_utils/cmd_ref/fabricpath.yaml +183 -0
  90. data/lib/cisco_node_utils/cmd_ref/fabricpath_topology.yaml +40 -0
  91. data/lib/cisco_node_utils/cmd_ref/feature.yaml +126 -0
  92. data/lib/cisco_node_utils/cmd_ref/hostname.yaml +8 -0
  93. data/lib/cisco_node_utils/cmd_ref/hsrp_global.yaml +25 -0
  94. data/lib/cisco_node_utils/cmd_ref/images.yaml +8 -0
  95. data/lib/cisco_node_utils/cmd_ref/interface.yaml +781 -0
  96. data/lib/cisco_node_utils/cmd_ref/interface_channel_group.yaml +45 -0
  97. data/lib/cisco_node_utils/cmd_ref/interface_evpn_multisite.yaml +17 -0
  98. data/lib/cisco_node_utils/cmd_ref/interface_hsrp_group.yaml +120 -0
  99. data/lib/cisco_node_utils/cmd_ref/interface_ospf.yaml +112 -0
  100. data/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml +87 -0
  101. data/lib/cisco_node_utils/cmd_ref/interface_service_vni.yaml +42 -0
  102. data/lib/cisco_node_utils/cmd_ref/inventory.yaml +45 -0
  103. data/lib/cisco_node_utils/cmd_ref/ip_multicast.yaml +22 -0
  104. data/lib/cisco_node_utils/cmd_ref/itd_device_group.yaml +83 -0
  105. data/lib/cisco_node_utils/cmd_ref/itd_service.yaml +119 -0
  106. data/lib/cisco_node_utils/cmd_ref/memory.yaml +24 -0
  107. data/lib/cisco_node_utils/cmd_ref/ntp_auth_key.yaml +10 -0
  108. data/lib/cisco_node_utils/cmd_ref/ntp_config.yaml +27 -0
  109. data/lib/cisco_node_utils/cmd_ref/ntp_server.yaml +34 -0
  110. data/lib/cisco_node_utils/cmd_ref/object_group.yaml +32 -0
  111. data/lib/cisco_node_utils/cmd_ref/ospf.yaml +91 -0
  112. data/lib/cisco_node_utils/cmd_ref/ospf_area.yaml +91 -0
  113. data/lib/cisco_node_utils/cmd_ref/ospf_area_vlink.yaml +88 -0
  114. data/lib/cisco_node_utils/cmd_ref/overlay_global.yaml +37 -0
  115. data/lib/cisco_node_utils/cmd_ref/pim.yaml +43 -0
  116. data/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml +86 -0
  117. data/lib/cisco_node_utils/cmd_ref/radius_global.yaml +37 -0
  118. data/lib/cisco_node_utils/cmd_ref/radius_server.yaml +100 -0
  119. data/lib/cisco_node_utils/cmd_ref/radius_server_group.yaml +19 -0
  120. data/lib/cisco_node_utils/cmd_ref/route_map.yaml +601 -0
  121. data/lib/cisco_node_utils/cmd_ref/show_system.yaml +9 -0
  122. data/lib/cisco_node_utils/cmd_ref/show_version.yaml +84 -0
  123. data/lib/cisco_node_utils/cmd_ref/snmp_community.yaml +81 -0
  124. data/lib/cisco_node_utils/cmd_ref/snmp_group.yaml +9 -0
  125. data/lib/cisco_node_utils/cmd_ref/snmp_notification_receiver.yaml +74 -0
  126. data/lib/cisco_node_utils/cmd_ref/snmp_server.yaml +91 -0
  127. data/lib/cisco_node_utils/cmd_ref/snmp_user.yaml +57 -0
  128. data/lib/cisco_node_utils/cmd_ref/snmpnotification.yaml +23 -0
  129. data/lib/cisco_node_utils/cmd_ref/span_session.yaml +65 -0
  130. data/lib/cisco_node_utils/cmd_ref/stp_global.yaml +235 -0
  131. data/lib/cisco_node_utils/cmd_ref/syslog_facility.yaml +10 -0
  132. data/lib/cisco_node_utils/cmd_ref/syslog_server.yaml +34 -0
  133. data/lib/cisco_node_utils/cmd_ref/syslog_settings.yaml +45 -0
  134. data/lib/cisco_node_utils/cmd_ref/system.yaml +7 -0
  135. data/lib/cisco_node_utils/cmd_ref/tacacs_global.yaml +37 -0
  136. data/lib/cisco_node_utils/cmd_ref/tacacs_server.yaml +63 -0
  137. data/lib/cisco_node_utils/cmd_ref/tacacs_server_group.yaml +45 -0
  138. data/lib/cisco_node_utils/cmd_ref/tacacs_server_host.yaml +64 -0
  139. data/lib/cisco_node_utils/cmd_ref/upgrade.yaml +38 -0
  140. data/lib/cisco_node_utils/cmd_ref/vdc.yaml +52 -0
  141. data/lib/cisco_node_utils/cmd_ref/virtual_service.yaml +8 -0
  142. data/lib/cisco_node_utils/cmd_ref/vlan.yaml +106 -0
  143. data/lib/cisco_node_utils/cmd_ref/vpc.yaml +233 -0
  144. data/lib/cisco_node_utils/cmd_ref/vrf.yaml +86 -0
  145. data/lib/cisco_node_utils/cmd_ref/vrf_af.yaml +139 -0
  146. data/lib/cisco_node_utils/cmd_ref/vtp.yaml +32 -0
  147. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep.yaml +114 -0
  148. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep_vni.yaml +71 -0
  149. data/lib/cisco_node_utils/cmd_ref/yang.yaml +7 -0
  150. data/lib/cisco_node_utils/cmd_ref/yum.yaml +68 -0
  151. data/lib/cisco_node_utils/command_reference.rb +724 -0
  152. data/lib/cisco_node_utils/configparser_lib.rb +195 -0
  153. data/lib/cisco_node_utils/constants.rb +40 -0
  154. data/lib/cisco_node_utils/dhcp_relay_global.rb +302 -0
  155. data/lib/cisco_node_utils/dns_domain.rb +93 -0
  156. data/lib/cisco_node_utils/domain_name.rb +82 -0
  157. data/lib/cisco_node_utils/encapsulation.rb +112 -0
  158. data/lib/cisco_node_utils/environment.rb +110 -0
  159. data/lib/cisco_node_utils/evpn_multicast.rb +66 -0
  160. data/lib/cisco_node_utils/evpn_multisite.rb +96 -0
  161. data/lib/cisco_node_utils/evpn_stormcontrol.rb +84 -0
  162. data/lib/cisco_node_utils/evpn_vni.rb +159 -0
  163. data/lib/cisco_node_utils/exceptions.rb +140 -0
  164. data/lib/cisco_node_utils/fabricpath_global.rb +405 -0
  165. data/lib/cisco_node_utils/fabricpath_topology.rb +137 -0
  166. data/lib/cisco_node_utils/feature.rb +377 -0
  167. data/lib/cisco_node_utils/hostname.rb +62 -0
  168. data/lib/cisco_node_utils/hsrp_global.rb +97 -0
  169. data/lib/cisco_node_utils/interface.rb +2128 -0
  170. data/lib/cisco_node_utils/interface_channel_group.rb +142 -0
  171. data/lib/cisco_node_utils/interface_evpn_multisite.rb +72 -0
  172. data/lib/cisco_node_utils/interface_hsrp_group.rb +557 -0
  173. data/lib/cisco_node_utils/interface_ospf.rb +378 -0
  174. data/lib/cisco_node_utils/interface_portchannel.rb +180 -0
  175. data/lib/cisco_node_utils/interface_service_vni.rb +132 -0
  176. data/lib/cisco_node_utils/ip_multicast.rb +90 -0
  177. data/lib/cisco_node_utils/itd_device_group.rb +228 -0
  178. data/lib/cisco_node_utils/itd_device_group_node.rb +144 -0
  179. data/lib/cisco_node_utils/itd_service.rb +511 -0
  180. data/lib/cisco_node_utils/logger.rb +78 -0
  181. data/lib/cisco_node_utils/name_server.rb +64 -0
  182. data/lib/cisco_node_utils/node.rb +443 -0
  183. data/lib/cisco_node_utils/node_util.rb +111 -0
  184. data/lib/cisco_node_utils/ntp_auth_key.rb +67 -0
  185. data/lib/cisco_node_utils/ntp_config.rb +83 -0
  186. data/lib/cisco_node_utils/ntp_server.rb +86 -0
  187. data/lib/cisco_node_utils/object_group.rb +75 -0
  188. data/lib/cisco_node_utils/object_group_entry.rb +143 -0
  189. data/lib/cisco_node_utils/overlay_global.rb +142 -0
  190. data/lib/cisco_node_utils/pim.rb +131 -0
  191. data/lib/cisco_node_utils/pim_group_list.rb +109 -0
  192. data/lib/cisco_node_utils/pim_rp_address.rb +103 -0
  193. data/lib/cisco_node_utils/platform.rb +217 -0
  194. data/lib/cisco_node_utils/portchannel_global.rb +347 -0
  195. data/lib/cisco_node_utils/radius_global.rb +165 -0
  196. data/lib/cisco_node_utils/radius_server.rb +421 -0
  197. data/lib/cisco_node_utils/radius_server_group.rb +117 -0
  198. data/lib/cisco_node_utils/route_map.rb +2540 -0
  199. data/lib/cisco_node_utils/router_ospf.rb +77 -0
  200. data/lib/cisco_node_utils/router_ospf_area.rb +416 -0
  201. data/lib/cisco_node_utils/router_ospf_area_vlink.rb +313 -0
  202. data/lib/cisco_node_utils/router_ospf_vrf.rb +342 -0
  203. data/lib/cisco_node_utils/snmp_notification_receiver.rb +176 -0
  204. data/lib/cisco_node_utils/snmpcommunity.rb +109 -0
  205. data/lib/cisco_node_utils/snmpgroup.rb +54 -0
  206. data/lib/cisco_node_utils/snmpnotification.rb +57 -0
  207. data/lib/cisco_node_utils/snmpserver.rb +132 -0
  208. data/lib/cisco_node_utils/snmpuser.rb +403 -0
  209. data/lib/cisco_node_utils/span_session.rb +149 -0
  210. data/lib/cisco_node_utils/stp_global.rb +676 -0
  211. data/lib/cisco_node_utils/syslog_facility.rb +64 -0
  212. data/lib/cisco_node_utils/syslog_server.rb +146 -0
  213. data/lib/cisco_node_utils/syslog_settings.rb +174 -0
  214. data/lib/cisco_node_utils/tacacs_global.rb +137 -0
  215. data/lib/cisco_node_utils/tacacs_server.rb +173 -0
  216. data/lib/cisco_node_utils/tacacs_server_group.rb +149 -0
  217. data/lib/cisco_node_utils/tacacs_server_host.rb +216 -0
  218. data/lib/cisco_node_utils/upgrade.rb +122 -0
  219. data/lib/cisco_node_utils/vdc.rb +118 -0
  220. data/lib/cisco_node_utils/version.rb +21 -0
  221. data/lib/cisco_node_utils/vlan.rb +301 -0
  222. data/lib/cisco_node_utils/vpc.rb +466 -0
  223. data/lib/cisco_node_utils/vrf.rb +192 -0
  224. data/lib/cisco_node_utils/vrf_af.rb +327 -0
  225. data/lib/cisco_node_utils/vtp.rb +125 -0
  226. data/lib/cisco_node_utils/vxlan_vtep.rb +286 -0
  227. data/lib/cisco_node_utils/vxlan_vtep_vni.rb +331 -0
  228. data/lib/cisco_node_utils/yang.rb +160 -0
  229. data/lib/cisco_node_utils/yum.rb +213 -0
  230. data/lib/cisco_node_utils.rb +21 -0
  231. data/lib/minitest/environment_plugin.rb +31 -0
  232. data/lib/minitest/log_level_plugin.rb +41 -0
  233. data/spec/client_spec.rb +7 -0
  234. data/spec/environment_spec.rb +384 -0
  235. data/spec/grpc_client_spec.rb +23 -0
  236. data/spec/isolate/all_clients_spec.rb +9 -0
  237. data/spec/isolate/grpc_only_spec.rb +16 -0
  238. data/spec/isolate/no_clients_spec.rb +26 -0
  239. data/spec/isolate/nxapi_only_spec.rb +16 -0
  240. data/spec/nxapi_client_spec.rb +42 -0
  241. data/spec/schema.yaml +82 -0
  242. data/spec/shared_examples_for_clients.rb +14 -0
  243. data/spec/spec_helper.rb +91 -0
  244. data/spec/whitespace_spec.rb +10 -0
  245. data/spec/yaml_spec.rb +42 -0
  246. data/tests/.rubocop.yml +18 -0
  247. data/tests/CSCuxdublin-1.0.0-7.0.3.I3.1.lib32_n9000.rpm +0 -0
  248. data/tests/basetest.rb +243 -0
  249. data/tests/ciscotest.rb +577 -0
  250. data/tests/cmd_config.yaml +75 -0
  251. data/tests/cmd_config_invalid.yaml +16 -0
  252. data/tests/n9000_sample-1.0.0-7.0.3.x86_64.rpm +0 -0
  253. data/tests/noop.rb +7 -0
  254. data/tests/platform_info.rb +63 -0
  255. data/tests/tacacs_server.yaml.example +6 -0
  256. data/tests/test_aaa_authentication_login.rb +243 -0
  257. data/tests/test_aaa_authentication_login_service.rb +761 -0
  258. data/tests/test_aaa_authorization_service.rb +874 -0
  259. data/tests/test_ace.rb +304 -0
  260. data/tests/test_acl.rb +185 -0
  261. data/tests/test_banner.rb +85 -0
  262. data/tests/test_bfd_global.rb +272 -0
  263. data/tests/test_bgp_af.rb +875 -0
  264. data/tests/test_bgp_af_aa.rb +108 -0
  265. data/tests/test_bgp_neighbor.rb +596 -0
  266. data/tests/test_bgp_neighbor_af.rb +781 -0
  267. data/tests/test_bridge_domain.rb +198 -0
  268. data/tests/test_bridge_domain_vni.rb +109 -0
  269. data/tests/test_client_utils.rb +111 -0
  270. data/tests/test_cmn_utils.rb +76 -0
  271. data/tests/test_command_config.rb +206 -0
  272. data/tests/test_command_reference.rb +669 -0
  273. data/tests/test_dhcp_relay_global.rb +286 -0
  274. data/tests/test_dns_domain.rb +123 -0
  275. data/tests/test_domain_name.rb +96 -0
  276. data/tests/test_encapsulation.rb +75 -0
  277. data/tests/test_evpn_multicast.rb +65 -0
  278. data/tests/test_evpn_multisite.rb +70 -0
  279. data/tests/test_evpn_stormcontrol.rb +56 -0
  280. data/tests/test_evpn_vni.rb +131 -0
  281. data/tests/test_fabricpath_global.rb +246 -0
  282. data/tests/test_fabricpath_topology.rb +77 -0
  283. data/tests/test_feature.rb +272 -0
  284. data/tests/test_grpc.rb +166 -0
  285. data/tests/test_hostname.rb +64 -0
  286. data/tests/test_hsrp_global.rb +79 -0
  287. data/tests/test_interface.rb +1958 -0
  288. data/tests/test_interface_bdi.rb +80 -0
  289. data/tests/test_interface_channel_group.rb +131 -0
  290. data/tests/test_interface_evpn_multisite.rb +94 -0
  291. data/tests/test_interface_hsrp.rb +134 -0
  292. data/tests/test_interface_hsrp_group.rb +570 -0
  293. data/tests/test_interface_ospf.rb +820 -0
  294. data/tests/test_interface_portchannel.rb +135 -0
  295. data/tests/test_interface_private_vlan.rb +365 -0
  296. data/tests/test_interface_service_vni.rb +203 -0
  297. data/tests/test_interface_svi.rb +210 -0
  298. data/tests/test_interface_switchport.rb +468 -0
  299. data/tests/test_ip_multicast.rb +80 -0
  300. data/tests/test_itd_device_group.rb +145 -0
  301. data/tests/test_itd_device_group_node.rb +199 -0
  302. data/tests/test_itd_service.rb +314 -0
  303. data/tests/test_logger.rb +43 -0
  304. data/tests/test_name_server.rb +94 -0
  305. data/tests/test_node.rb +50 -0
  306. data/tests/test_node_ext.rb +406 -0
  307. data/tests/test_node_util.rb +119 -0
  308. data/tests/test_ntp_auth_key.rb +77 -0
  309. data/tests/test_ntp_config.rb +100 -0
  310. data/tests/test_ntp_server.rb +146 -0
  311. data/tests/test_nxapi.rb +236 -0
  312. data/tests/test_object_group.rb +122 -0
  313. data/tests/test_overlay_global.rb +108 -0
  314. data/tests/test_pim.rb +203 -0
  315. data/tests/test_pim_group_list.rb +147 -0
  316. data/tests/test_pim_rp_address.rb +155 -0
  317. data/tests/test_platform.rb +254 -0
  318. data/tests/test_portchannel_global.rb +322 -0
  319. data/tests/test_radius_global.rb +108 -0
  320. data/tests/test_radius_server.rb +377 -0
  321. data/tests/test_radius_server_group.rb +151 -0
  322. data/tests/test_route_map.rb +1479 -0
  323. data/tests/test_router_bgp.rb +1325 -0
  324. data/tests/test_router_ospf.rb +56 -0
  325. data/tests/test_router_ospf_area.rb +433 -0
  326. data/tests/test_router_ospf_area_vlink.rb +298 -0
  327. data/tests/test_router_ospf_vrf.rb +690 -0
  328. data/tests/test_snmp_notification_receiver.rb +169 -0
  329. data/tests/test_snmpcommunity.rb +422 -0
  330. data/tests/test_snmpgroup.rb +71 -0
  331. data/tests/test_snmpnotification.rb +91 -0
  332. data/tests/test_snmpserver.rb +251 -0
  333. data/tests/test_snmpuser.rb +666 -0
  334. data/tests/test_span_session.rb +155 -0
  335. data/tests/test_stp_global.rb +575 -0
  336. data/tests/test_syslog_facility.rb +80 -0
  337. data/tests/test_syslog_server.rb +119 -0
  338. data/tests/test_syslog_settings.rb +123 -0
  339. data/tests/test_tacacs_global.rb +109 -0
  340. data/tests/test_tacacs_server.rb +436 -0
  341. data/tests/test_tacacs_server_group.rb +434 -0
  342. data/tests/test_tacacs_server_host.rb +427 -0
  343. data/tests/test_upgrade.rb +105 -0
  344. data/tests/test_vdc.rb +64 -0
  345. data/tests/test_vlan.rb +386 -0
  346. data/tests/test_vlan_private.rb +656 -0
  347. data/tests/test_vpc.rb +548 -0
  348. data/tests/test_vrf.rb +248 -0
  349. data/tests/test_vrf_af.rb +288 -0
  350. data/tests/test_vtp.rb +278 -0
  351. data/tests/test_vxlan_vtep.rb +327 -0
  352. data/tests/test_vxlan_vtep_vni.rb +326 -0
  353. data/tests/test_yang.rb +369 -0
  354. data/tests/test_yum.rb +109 -0
  355. data/tests/upgrade_info.yaml.example +3 -0
  356. data/tests/yum_package.yaml +94 -0
  357. metadata +534 -0
@@ -0,0 +1,724 @@
1
+ # Copyright (c) 2014-2016 Cisco and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'exceptions'
16
+ require 'yaml'
17
+
18
+ module Cisco
19
+ # Control a reference for an attribute.
20
+ class CmdRef
21
+ attr_reader :feature, :name, :hash
22
+ attr_reader :auto_default, :multiple, :kind, :default_only, :os_version
23
+ alias_method :auto_default?, :auto_default
24
+ alias_method :default_only?, :default_only
25
+ alias_method :multiple?, :multiple
26
+
27
+ KEYS = %w(default_value default_only
28
+ data_format context value
29
+ get_data_format get_command get_context get_value
30
+ set_data_format set_context set_value
31
+ auto_default multiple kind os_version)
32
+
33
+ def self.keys
34
+ KEYS
35
+ end
36
+
37
+ KINDS = %w(boolean int string symbol)
38
+
39
+ # Construct a CmdRef describing the given (feature, name) pair.
40
+ # Param "values" is a hash with keys as described in KEYS.
41
+ # Param "file" is for debugging purposes only.
42
+ def initialize(feature, name, values, file)
43
+ fail ArgumentError, "'#{values}' is not a hash." unless values.is_a? Hash
44
+
45
+ @feature = feature
46
+ @name = name
47
+ @auto_default = true
48
+ @default_only = false
49
+ @multiple = false
50
+ @kind = nil
51
+ @os_version = nil
52
+
53
+ values_to_hash(values, file)
54
+
55
+ if @hash['get_value'] || @hash['get_command']
56
+ define_helper('getter',
57
+ data_format: @hash['get_data_format'] || :cli,
58
+ command: @hash['get_command'],
59
+ context: @hash['get_context'] || [],
60
+ value: @hash['get_value'])
61
+ end
62
+ if @hash['set_value'] # rubocop:disable Style/GuardClause
63
+ define_helper('setter',
64
+ data_format: @hash['set_data_format'] || :cli,
65
+ context: @hash['set_context'] || [],
66
+ values: @hash['set_value'])
67
+ end
68
+ end
69
+
70
+ def values_to_hash(values, file)
71
+ @hash = {}
72
+ values.each do |key, value|
73
+ unless KEYS.include?(key)
74
+ fail "Unrecognized key #{key} for #{feature}, #{name} in #{file}"
75
+ end
76
+ case key
77
+ when 'auto_default'
78
+ @auto_default = value ? true : false
79
+ when 'data_format', 'get_data_format', 'set_data_format'
80
+ @hash[key] = value.to_sym
81
+ when 'default_only'
82
+ @default_only = true
83
+ # default_value overrides default_only
84
+ @hash['default_value'] ||= value
85
+ when 'multiple'
86
+ @multiple = boolean_default_true(value)
87
+ when 'kind'
88
+ fail "Unknown 'kind': '#{value}'" unless KINDS.include?(value)
89
+ @kind = value.to_sym
90
+ when 'os_version'
91
+ @os_version = value
92
+ else
93
+ # default_value overrides default_only
94
+ @default_only = false if key == 'default_value'
95
+ @hash[key] = value
96
+ end
97
+ end
98
+
99
+ # Inherit general to specific if needed
100
+ if @hash.key?('data_format')
101
+ @hash['get_data_format'] = @hash['data_format'] \
102
+ unless @hash.key?('get_data_format')
103
+ @hash['set_data_format'] = @hash['data_format'] \
104
+ unless @hash.key?('set_data_format')
105
+ end
106
+ if @hash.key?('context')
107
+ @hash['get_context'] = @hash['context'] unless @hash.key?('get_context')
108
+ @hash['set_context'] = @hash['context'] unless @hash.key?('set_context')
109
+ end
110
+ if @hash.key?('value')
111
+ @hash['get_value'] = @hash['value'] unless @hash.key?('get_value')
112
+ @hash['set_value'] = @hash['value'] unless @hash.key?('set_value')
113
+ end
114
+
115
+ @hash.delete_if { |key, _| key != 'default_value' } if @default_only
116
+ end
117
+
118
+ # Does this instance have a valid getter() function?
119
+ # Will be overridden at initialization if so.
120
+ def getter?
121
+ !@hash['getter'].nil?
122
+ end
123
+
124
+ # Does this instance have a valid setter() function?
125
+ # Will be overridden at initialization if so.
126
+ def setter?
127
+ !@hash['setter'].nil?
128
+ end
129
+
130
+ # Default getter method.
131
+ # Will be overridden at initialization if the relevant parameters are set.
132
+ #
133
+ # A non-trivial implementation of this method will take args *or* kwargs,
134
+ # and will return a hash of the form:
135
+ # {
136
+ # data_format: :cli,
137
+ # command: string or nil,
138
+ # context: array<string> or array<regexp>, perhaps empty
139
+ # value: string or regexp,
140
+ # }
141
+ def getter(*args, **kwargs) # rubocop:disable Lint/UnusedMethodArgument
142
+ fail UnsupportedError.new(@feature, @name, 'getter')
143
+ end
144
+
145
+ # Default setter method.
146
+ # Will be overridden at initialization if the relevant parameters are set.
147
+ #
148
+ # A non-trivial implementation of this method will take args *or* kwargs,
149
+ # and will return a hash of the form:
150
+ # {
151
+ # data_format: :cli,
152
+ # context: array<string>, perhaps empty
153
+ # values: array<string>,
154
+ # }
155
+ def setter(*args, **kwargs) # rubocop:disable Lint/UnusedMethodArgument
156
+ fail UnsupportedError.new(@feature, @name, 'setter')
157
+ end
158
+
159
+ # Property with an implicit value of 'true' if no value is given
160
+ def boolean_default_true(value)
161
+ value.nil? || value
162
+ end
163
+
164
+ def key_substitutor(item, kwargs)
165
+ result = item
166
+ kwargs.each do |key, value|
167
+ result = result.sub("<#{key}>", value.to_s)
168
+ end
169
+ unsub = result[/<(\S+)>/, 1]
170
+ fail ArgumentError, \
171
+ "No value specified for '#{unsub}' in '#{result}'" if unsub
172
+ result
173
+ end
174
+
175
+ def printf_substitutor(item, args)
176
+ item = sprintf(item, *args.shift(item.scan(/%/).length))
177
+ [item, args]
178
+ end
179
+
180
+ # Create a helper method for generating the getter/setter values.
181
+ # This method will automatically handle wildcard arguments.
182
+ def define_helper(method_name, base_hash)
183
+ # Which kind of wildcards (if any) do we need to support?
184
+ combined = []
185
+ base_hash.each_value do |v|
186
+ combined += v if v.is_a?(Array)
187
+ combined << v if v.is_a?(String)
188
+ end
189
+ key_value = combined.any? { |i| i.is_a?(String) && /<\S+>/ =~ i }
190
+ printf = combined.any? { |i| i.is_a?(String) && /%/ =~ i }
191
+
192
+ if key_value && printf
193
+ fail 'Invalid mixture of key-value and printf wildcards ' \
194
+ "in #{method_name}: #{combined}"
195
+ elsif key_value
196
+ define_key_value_helper(method_name, base_hash)
197
+ elsif printf
198
+ arg_count = combined.join.scan(/%/).length
199
+ define_printf_helper(method_name, base_hash, arg_count)
200
+ else
201
+ # simple static token(s)
202
+ define_static_helper(method_name, base_hash)
203
+ end
204
+ @hash[method_name] = true
205
+ end
206
+
207
+ def define_key_value_helper(method_name, base_hash)
208
+ # Key-value substitution
209
+ define_singleton_method method_name.to_sym do |*args, **kwargs|
210
+ unless args.empty?
211
+ fail ArgumentError, "#{method_name} requires keyword args, not "\
212
+ 'positional args'
213
+ end
214
+ result = {}
215
+ base_hash.each do |k, v|
216
+ if v.is_a?(String)
217
+ v = key_substitutor(v, kwargs)
218
+ elsif v.is_a?(Array)
219
+ output = []
220
+ v.each do |line|
221
+ # Check for (?) flag indicating optional param
222
+ optional_line = line[/^\(\?\)(.*)/, 1]
223
+ if optional_line
224
+ begin
225
+ line = key_substitutor(optional_line, kwargs)
226
+ rescue ArgumentError # Unsubstituted key - OK to skip this line
227
+ next
228
+ end
229
+ else
230
+ line = key_substitutor(line, kwargs)
231
+ end
232
+ output.push(line)
233
+ end
234
+ v = output
235
+ end
236
+ result[k] = v
237
+ end
238
+ result
239
+ end
240
+ end
241
+
242
+ def define_printf_helper(method_name, base_hash, arg_count)
243
+ define_singleton_method method_name.to_sym do |*args, **kwargs|
244
+ unless kwargs.empty?
245
+ fail ArgumentError, "#{method_name} requires positional args, not " \
246
+ 'keyword args'
247
+ end
248
+ unless args.length == arg_count
249
+ fail ArgumentError, 'wrong number of arguments ' \
250
+ "(#{args.length} for #{arg_count})"
251
+ end
252
+
253
+ result = {}
254
+ base_hash.each do |k, v|
255
+ if v.is_a?(String)
256
+ v, args = printf_substitutor(v, args)
257
+ elsif v.is_a?(Array)
258
+ output = []
259
+ v.each do |line|
260
+ line, args = printf_substitutor(line, args)
261
+ output.push(line)
262
+ end
263
+ v = output
264
+ end
265
+ result[k] = v
266
+ end
267
+ result
268
+ end
269
+ end
270
+
271
+ def define_static_helper(method_name, base_hash)
272
+ # rubocop:disable Lint/UnusedBlockArgument
273
+ define_singleton_method method_name.to_sym do |*args, **kwargs|
274
+ base_hash
275
+ end
276
+ # rubocop:enable Lint/UnusedBlockArgument
277
+ end
278
+
279
+ def convert_to_constant(value)
280
+ # NOTE: This method is now deprecated and should not be used for future
281
+ # development.
282
+ #
283
+ # If value is a string and it is empty OR the first letter is lower case
284
+ # then leave value untouched.
285
+ # If value is a string and the first letter is uppercase this indicates
286
+ # that it could be a constant in Ruby, so attempt to convert it
287
+ # to a Constant.
288
+ if value.is_a?(String) && !value.empty?
289
+ if value[0].chr == value[0].chr.upcase
290
+ begin
291
+ value = Object.const_get(value) if Object.const_defined?(value)
292
+ rescue NameError
293
+ debug("'#{value}' is not a constant")
294
+ end
295
+ end
296
+ end
297
+ value
298
+ end
299
+
300
+ def method_missing(method_name, *args, &block)
301
+ if KEYS.include?(method_name.to_s)
302
+ # ref.foo -> return @hash[foo] or fail IndexError
303
+ method_name = method_name.to_s
304
+ unless @hash.include?(method_name)
305
+ if @default_only
306
+ fail UnsupportedError.new(@feature, @name, method_name)
307
+ end
308
+ fail IndexError, "No #{method_name} defined for #{@feature}, #{@name}"
309
+ end
310
+ # puts("get #{method_name}: '#{@hash[method_name]}'")
311
+ @hash[method_name]
312
+ elsif method_name.to_s[-1] == '?' && \
313
+ KEYS.include?(method_name.to_s[0..-2])
314
+ # ref.foo? -> return true if @hash[foo], else false
315
+ method_name = method_name.to_s[0..-2]
316
+ @hash.include?(method_name)
317
+ else
318
+ super(method_name, *args, &block)
319
+ end
320
+ end
321
+
322
+ # Print useful debugging information about the object.
323
+ def to_s
324
+ str = ''
325
+ str << "Command: #{@feature} #{@name}\n"
326
+ @hash.each { |key, value| str << " #{key}: #{value}\n" }
327
+ str
328
+ end
329
+ end
330
+
331
+ # Placeholder for known but explicitly excluded entry
332
+ # For these, we have an implied default_only value of nil.
333
+ class UnsupportedCmdRef < CmdRef
334
+ def initialize(feature, name, file)
335
+ super(feature, name, { 'default_only' => nil }, file)
336
+ end
337
+ end
338
+
339
+ # Builds reference hash for the platform specified in the product id.
340
+ class CommandReference
341
+ @@debug = false # rubocop:disable Style/ClassVars
342
+
343
+ def self.debug=(value)
344
+ fail ArgumentError, 'Debug must be boolean' unless value == true ||
345
+ value == false
346
+ @@debug = value # rubocop:disable Style/ClassVars
347
+ end
348
+
349
+ attr_reader :data_formats, :files, :platform, :product_id
350
+
351
+ # Constructor.
352
+ # Normal usage is to pass product, platform, data_formats,
353
+ # in which case usual YAML files will be located then the list
354
+ # will be filtered down to only those matching the given settings.
355
+ # For testing purposes (only!) you can pass an explicit list of files to
356
+ # load instead. This list will NOT be filtered further by product_id.
357
+ def initialize(product: nil,
358
+ platform: nil,
359
+ data_formats: [],
360
+ files: nil)
361
+ @product_id = product
362
+ @platform = platform
363
+ @data_formats = data_formats
364
+ @hash = {}
365
+ if files
366
+ @files = files
367
+ else
368
+ @files = Dir.glob(__dir__ + '/cmd_ref/*.yaml')
369
+ end
370
+
371
+ build_cmd_ref
372
+ end
373
+
374
+ # Build complete reference hash.
375
+ def build_cmd_ref
376
+ # Example id's: N3K-C3048TP-1GE, N3K-C3064PQ-10GE, N7K-C7009, N7K-C7009
377
+ debug "Product: #{@product_id}"
378
+ debug "Files being used: #{@files.join(', ')}"
379
+
380
+ @files.each do |file|
381
+ feature = File.basename(file).split('.')[0]
382
+ debug "Processing file '#{file}' as feature '#{feature}'"
383
+ feature_hash = load_yaml(file)
384
+ if feature_hash.empty?
385
+ debug "Feature #{feature} is empty"
386
+ next
387
+ end
388
+ begin
389
+ feature_hash = filter_hash(feature_hash)
390
+ rescue RuntimeError => e
391
+ raise "#{file}: #{e}"
392
+ end
393
+ if feature_hash.empty?
394
+ debug "Feature #{feature} is excluded"
395
+ @hash[feature] = UnsupportedCmdRef.new(feature, nil, file)
396
+ next
397
+ end
398
+
399
+ base_hash = {}
400
+ if feature_hash.key?('_template')
401
+ base_hash = CommandReference.hash_merge(feature_hash['_template'])
402
+ end
403
+
404
+ feature_hash.each do |name, value|
405
+ fail "No entries under '#{name}' in '#{file}'" if value.nil?
406
+ @hash[feature] ||= {}
407
+ if value.empty?
408
+ @hash[feature][name] = UnsupportedCmdRef.new(feature, name, file)
409
+ else
410
+ values = CommandReference.hash_merge(value, base_hash.clone)
411
+ @hash[feature][name] = CmdRef.new(feature, name, values, file)
412
+ end
413
+ end
414
+ end
415
+ end
416
+
417
+ def supports?(feature, property=nil)
418
+ value = @hash[feature]
419
+ value = value[property] if value.is_a?(Hash) && property
420
+ !(value.is_a?(UnsupportedCmdRef) || value.nil?)
421
+ end
422
+
423
+ # Get the command reference
424
+ def lookup(feature, name)
425
+ value = @hash[feature]
426
+ value = value[name] if value.is_a? Hash
427
+ fail IndexError, "No CmdRef defined for #{feature}, #{name}" if value.nil?
428
+ value
429
+ end
430
+
431
+ def empty?
432
+ @hash.empty?
433
+ end
434
+
435
+ # Print debug statements
436
+ def debug(text)
437
+ puts "DEBUG: #{text}" if @@debug
438
+ end
439
+
440
+ KNOWN_PLATFORMS = %w(C3048 C3064 C3132 C3172 N35 N3k N3k-F N5k N6k N7k N9k
441
+ N9k-F XRv9k)
442
+
443
+ def self.platform_to_filter(platform)
444
+ if KNOWN_PLATFORMS.include?(platform)
445
+ case platform
446
+ when 'XRv9k'
447
+ /XRV9/
448
+ when 'N35'
449
+ /N3K-C35/
450
+ when 'N9k'
451
+ # For non-fretta n9k platforms we need to
452
+ # match everything except the trailing -F
453
+ /^N9...(?!.*-F)/
454
+ when 'N9k-F'
455
+ # For fretta n9k we need to include the trailing -F
456
+ /^N9.*-F$/
457
+ when 'N3k'
458
+ # For non-fretta n3k platforms we need to
459
+ # match everything except the trailing -F
460
+ /^N3...(?!.*-F)/
461
+ when 'N3k-F'
462
+ # For fretta n3k we need to include the trailing -F
463
+ /^N3.*-F$/
464
+ else
465
+ Regexp.new platform.tr('k', '')
466
+ end
467
+ else
468
+ fail IndexError, "Unknown platform key '#{platform}'"
469
+ end
470
+ end
471
+
472
+ KNOWN_FILTERS = %w(nexus ios_xr cli nxapi_structured)
473
+
474
+ def self.key_match(key, platform, product_id, data_formats)
475
+ if KNOWN_PLATFORMS.include?(key)
476
+ return platform_to_filter(key) =~ product_id ? true : false
477
+ elsif KNOWN_FILTERS.include?(key)
478
+ return true if data_formats && data_formats.include?(key.to_sym)
479
+ return true if key == platform.to_s
480
+ return false
481
+ else
482
+ return :unknown
483
+ end
484
+ end
485
+
486
+ # Helper method
487
+ # Given a Hash of command reference data as read from YAML, does:
488
+ # - Delete any platform-specific data not applicable to this platform
489
+ # - Delete any product-specific data not applicable to this product_id
490
+ # - Delete any data-model-specific data not supported by this node
491
+ # Returns the filtered hash (possibly empty)
492
+ def self.filter_hash(hash,
493
+ platform: nil,
494
+ product_id: nil,
495
+ data_formats: nil,
496
+ allow_unknown_keys: true)
497
+ result = {}
498
+
499
+ exclude = hash['_exclude'] || []
500
+ exclude.each do |value|
501
+ # We don't allow exclusion by data_format - just platform/product
502
+ if key_match(value, platform, product_id, nil) == true
503
+ debug "Exclude this product (#{product_id}, #{value})"
504
+ return result
505
+ end
506
+ end
507
+
508
+ # to_inspect: sub-keys we want to recurse into
509
+ to_inspect = []
510
+ # regexp_match: did we find a product_id regexp that matches?
511
+ regexp_match = false
512
+
513
+ hash.each do |key, value|
514
+ next if key == '_exclude'
515
+ if CmdRef.keys.include?(key)
516
+ result[key] = value
517
+ elsif key != 'else'
518
+ match = key_match(key, platform, product_id, data_formats)
519
+ next if match == false
520
+ if match == :unknown
521
+ fail "Unrecognized key '#{key}'" unless allow_unknown_keys
522
+ end
523
+ regexp_match = true if match == true
524
+ to_inspect << key
525
+ end
526
+ end
527
+ # If we didn't find any platform regexp match,
528
+ # and an 'else' sub-hash is provided, descend into 'else'
529
+ to_inspect << 'else' if hash.key?('else') && !regexp_match
530
+ # Recurse! Sub-hashes can override the base hash
531
+ to_inspect.each do |key|
532
+ unless hash[key].is_a?(Hash)
533
+ result[key] = hash[key]
534
+ next
535
+ end
536
+ begin
537
+ result[key] = filter_hash(hash[key],
538
+ platform: platform,
539
+ product_id: product_id,
540
+ data_formats: data_formats,
541
+ allow_unknown_keys: false)
542
+ rescue RuntimeError => e
543
+ # Recursively wrap the error as needed to provide context
544
+ raise "[#{key}]: #{e}"
545
+ end
546
+ end
547
+ result
548
+ end
549
+
550
+ def filter_hash(input_hash)
551
+ CommandReference.filter_hash(input_hash,
552
+ platform: platform,
553
+ product_id: product_id,
554
+ data_formats: data_formats)
555
+ end
556
+
557
+ # Helper method
558
+ # Given a suitably filtered Hash of command reference data, does:
559
+ # - Inherit data from the given base_hash (if any) and extend/override it
560
+ # with the given input data.
561
+ def self.hash_merge(input_hash, base_hash=nil)
562
+ return base_hash if input_hash.nil?
563
+ result = base_hash
564
+ result ||= {}
565
+ # to_inspect: sub-hashes we want to recurse into
566
+ to_inspect = []
567
+
568
+ input_hash.each do |key, value|
569
+ if CmdRef.keys.include?(key)
570
+ result[key] = value
571
+ elsif value.is_a?(Hash)
572
+ to_inspect << value
573
+ elsif value.nil?
574
+ next
575
+ else
576
+ fail "Unexpected non-hash data: #{value}"
577
+ end
578
+ end
579
+ # Recurse! Sub-hashes can override the base hash
580
+ to_inspect.each do |hash|
581
+ result = hash_merge(hash, result)
582
+ end
583
+ result
584
+ end
585
+
586
+ # Helper method.
587
+ # Combines the two given values (either or both of which may be arrays)
588
+ # into a single combined array
589
+ # value_append('foo', 'bar') ==> ['foo', 'bar']
590
+ # value_append('foo', ['bar', 'baz']) ==> ['foo', 'bar', 'baz']
591
+ def self.value_append(base_value, new_value)
592
+ base_value = [base_value] unless base_value.is_a?(Array)
593
+ new_value = [new_value] unless new_value.is_a?(Array)
594
+ base_value + new_value
595
+ end
596
+
597
+ def mapping?(node)
598
+ node.class.ancestors.any? { |name| /Map/ =~ name.to_s }
599
+ end
600
+ private :mapping?
601
+
602
+ def get_keys_values_from_map(node)
603
+ # A Psych::Node::Mapping instance has an Array of children in
604
+ # the format [key1, val1, key2, val2]
605
+ key_children = node.children.select.each_with_index { |_, i| i.even? }
606
+ val_children = node.children.select.each_with_index { |_, i| i.odd? }
607
+ debug "children of #{node} mapping: #{key_children}, #{val_children}"
608
+ [key_children, val_children]
609
+ end
610
+ private :get_keys_values_from_map
611
+
612
+ # Validate the YAML node tree before converting it into Ruby
613
+ # data structures.
614
+ #
615
+ # @raise RuntimeError if the node tree is not valid by our constraints.
616
+ #
617
+ # @param node Node to be validated, then recurse to its children.
618
+ # @param filename File that YAML was parsed from, for messages
619
+ # @param depth Depth into the node tree
620
+ # @param parents String describing parents of this node, for messages
621
+ def validate_yaml(node, filename, depth=0, parents=nil)
622
+ return unless node && (mapping?(node) || node.children)
623
+ # Psych wraps everything in a Document instance, which we ignore.
624
+ unless node.class.ancestors.any? { |name| /Document/ =~ name.to_s }
625
+ depth += 1
626
+ end
627
+ debug "Validating #{node.class} at depth #{depth}"
628
+
629
+ # No special validation for non-mapping nodes - just recurse
630
+ unless mapping?(node)
631
+ node.children.each do |child|
632
+ validate_yaml(child, filename, depth, parents)
633
+ end
634
+ return
635
+ end
636
+
637
+ # For Mappings, we validate more extensively:
638
+ # 1. no duplicate keys are allowed (Psych doesn't catch this)
639
+ # 2. Features must be listed in alphabetical order for maintainability
640
+
641
+ # Take advantage of our known YAML structure to assign labels by depth
642
+ label = %w(feature name param).fetch(depth, 'key')
643
+
644
+ # Get the key nodes and value nodes under this mapping
645
+ key_children, val_children = get_keys_values_from_map(node)
646
+ # Get an array of key names
647
+ key_arr = key_children.map(&:value)
648
+
649
+ # Make sure no duplicate key names.
650
+ # If searching from the start of the array finds a name at one index,
651
+ # but searching from the end of the array finds it at a different one,
652
+ # then we have a duplicate.
653
+ dup = key_arr.detect { |e| key_arr.index(e) != key_arr.rindex(e) }
654
+ if dup
655
+ msg = "Duplicate #{label} '#{dup}'#{parents} in #{filename}!"
656
+ fail msg
657
+ end
658
+
659
+ # Enforce alphabetical ordering of features (only).
660
+ # We can extend this later to enforce ordering of names if desired
661
+ # by checking at depth 2 as well.
662
+ if depth == 1
663
+ last_key = nil
664
+ key_arr.each do |key|
665
+ if last_key && key < last_key
666
+ fail "features out of order in #{filename}: (#{last_key} > #{key})"
667
+ end
668
+ last_key = key
669
+ end
670
+ end
671
+
672
+ # Recurse to the children. We get a little fancy here so as to be able
673
+ # to provide more meaningful debug/error messages, such as:
674
+ # Duplicate param 'default_value' under feature 'foo', name 'bar'
675
+ key_children.zip(val_children).each do |key_node, val_node|
676
+ if parents
677
+ new_parents = parents + ", #{label} '#{key_node.value}'"
678
+ else
679
+ new_parents = " under #{label} '#{key_node.value}'"
680
+ end
681
+ validate_yaml(key_node, filename, depth, new_parents) # unnecessary?
682
+ validate_yaml(val_node, filename, depth, new_parents)
683
+ end
684
+ end
685
+ private :validate_yaml
686
+
687
+ # Read in yaml file.
688
+ # The expectation is that a file corresponds to a feature
689
+ def load_yaml(yaml_file)
690
+ fail "File #{yaml_file} doesn't exist." unless File.exist?(yaml_file)
691
+ # Parse YAML file into a tree of nodes
692
+ # Psych::SyntaxError doesn't inherit from StandardError in some versions,
693
+ # so we want to explicitly catch it if using Psych.
694
+ rescue_errors = [::StandardError, ::Psych::SyntaxError]
695
+ yaml_parsed = File.open(yaml_file, 'r') do |f|
696
+ begin
697
+ YAML.parse(f)
698
+ rescue *rescue_errors => e
699
+ raise "unable to parse #{yaml_file}: #{e}"
700
+ end
701
+ end
702
+ return {} unless yaml_parsed
703
+ # Validate the node tree
704
+ validate_yaml(yaml_parsed, yaml_file)
705
+ # If validation passed, convert the node tree to a Ruby Hash.
706
+ yaml_parsed.transform
707
+ end
708
+
709
+ def to_s
710
+ @num_features ||= @hash.values.length
711
+ @num_attributes ||= @hash.values.inject(0) do |sum, n|
712
+ sum + (n.is_a?(Hash) ? n.values.length : 1)
713
+ end
714
+ "CommandReference describing #{@num_features} features " \
715
+ "with #{@num_attributes} attributes in total"
716
+ end
717
+
718
+ def inspect
719
+ "CommandReference for '#{product_id}' " \
720
+ "(platform:'#{platform}', data formats:#{data_formats}) " \
721
+ "based on #{files.length} files"
722
+ end
723
+ end
724
+ end