hybrid_platforms_conductor 32.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (244) hide show
  1. checksums.yaml +7 -0
  2. data/bin/check-node +24 -0
  3. data/bin/deploy +12 -0
  4. data/bin/dump_nodes_json +12 -0
  5. data/bin/free_ips +23 -0
  6. data/bin/free_veids +17 -0
  7. data/bin/get_impacted_nodes +43 -0
  8. data/bin/last_deploys +56 -0
  9. data/bin/nodes_to_deploy +104 -0
  10. data/bin/report +10 -0
  11. data/bin/run +39 -0
  12. data/bin/setup +11 -0
  13. data/bin/ssh_config +14 -0
  14. data/bin/test +13 -0
  15. data/bin/topograph +54 -0
  16. data/lib/hybrid_platforms_conductor/action.rb +82 -0
  17. data/lib/hybrid_platforms_conductor/actions_executor.rb +307 -0
  18. data/lib/hybrid_platforms_conductor/bitbucket.rb +123 -0
  19. data/lib/hybrid_platforms_conductor/cmd_runner.rb +188 -0
  20. data/lib/hybrid_platforms_conductor/cmdb.rb +34 -0
  21. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +78 -0
  22. data/lib/hybrid_platforms_conductor/common_config_dsl/confluence.rb +43 -0
  23. data/lib/hybrid_platforms_conductor/common_config_dsl/file_system_tests.rb +110 -0
  24. data/lib/hybrid_platforms_conductor/common_config_dsl/idempotence_tests.rb +38 -0
  25. data/lib/hybrid_platforms_conductor/config.rb +263 -0
  26. data/lib/hybrid_platforms_conductor/confluence.rb +119 -0
  27. data/lib/hybrid_platforms_conductor/connector.rb +84 -0
  28. data/lib/hybrid_platforms_conductor/credentials.rb +127 -0
  29. data/lib/hybrid_platforms_conductor/current_dir_monitor.rb +42 -0
  30. data/lib/hybrid_platforms_conductor/deployer.rb +598 -0
  31. data/lib/hybrid_platforms_conductor/executable.rb +145 -0
  32. data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +44 -0
  33. data/lib/hybrid_platforms_conductor/hpc_plugins/action/interactive.rb +44 -0
  34. data/lib/hybrid_platforms_conductor/hpc_plugins/action/my_action.rb.sample +79 -0
  35. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +63 -0
  36. data/lib/hybrid_platforms_conductor/hpc_plugins/action/ruby.rb +69 -0
  37. data/lib/hybrid_platforms_conductor/hpc_plugins/action/scp.rb +61 -0
  38. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/config.rb +78 -0
  39. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_ip.rb +104 -0
  40. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +114 -0
  41. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/my_cmdb.rb.sample +129 -0
  42. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/platform_handlers.rb +66 -0
  43. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +156 -0
  44. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +702 -0
  45. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/platform_handler_plugin.rb.sample +292 -0
  46. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +148 -0
  47. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/my_provisioner.rb.sample +103 -0
  48. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/podman.rb +125 -0
  49. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +522 -0
  50. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/proxmox_waiter.rb +707 -0
  51. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/reserve_proxmox_container +122 -0
  52. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +69 -0
  53. data/lib/hybrid_platforms_conductor/hpc_plugins/report/mediawiki.rb +164 -0
  54. data/lib/hybrid_platforms_conductor/hpc_plugins/report/my_report_plugin.rb.sample +88 -0
  55. data/lib/hybrid_platforms_conductor/hpc_plugins/report/stdout.rb +61 -0
  56. data/lib/hybrid_platforms_conductor/hpc_plugins/report/templates/confluence_inventory.html.erb +33 -0
  57. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +137 -0
  58. data/lib/hybrid_platforms_conductor/hpc_plugins/test/can_be_checked.rb +21 -0
  59. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +112 -0
  60. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_from_scratch.rb +35 -0
  61. data/lib/hybrid_platforms_conductor/hpc_plugins/test/connection.rb +28 -0
  62. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +44 -0
  63. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_from_scratch.rb +36 -0
  64. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +49 -0
  65. data/lib/hybrid_platforms_conductor/hpc_plugins/test/divergence.rb +25 -0
  66. data/lib/hybrid_platforms_conductor/hpc_plugins/test/executables.rb +46 -0
  67. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +45 -0
  68. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system_hdfs.rb +45 -0
  69. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +25 -0
  70. data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +77 -0
  71. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +38 -0
  72. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +56 -0
  73. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +54 -0
  74. data/lib/hybrid_platforms_conductor/hpc_plugins/test/linear_strategy.rb +47 -0
  75. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +82 -0
  76. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +120 -0
  77. data/lib/hybrid_platforms_conductor/hpc_plugins/test/my_test_plugin.rb.sample +143 -0
  78. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +74 -0
  79. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ports.rb +85 -0
  80. data/lib/hybrid_platforms_conductor/hpc_plugins/test/private_ips.rb +38 -0
  81. data/lib/hybrid_platforms_conductor/hpc_plugins/test/public_ips.rb +38 -0
  82. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre-meltdown-checker.sh +1930 -0
  83. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +56 -0
  84. data/lib/hybrid_platforms_conductor/hpc_plugins/test/veids.rb +31 -0
  85. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +159 -0
  86. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +122 -0
  87. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/my_test_report.rb.sample +48 -0
  88. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/stdout.rb +120 -0
  89. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/_confluence_errors_status.html.erb +46 -0
  90. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/_confluence_gauge.html.erb +49 -0
  91. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/confluence.html.erb +242 -0
  92. data/lib/hybrid_platforms_conductor/io_router.rb +70 -0
  93. data/lib/hybrid_platforms_conductor/json_dumper.rb +88 -0
  94. data/lib/hybrid_platforms_conductor/logger_helpers.rb +319 -0
  95. data/lib/hybrid_platforms_conductor/mutex_dir +76 -0
  96. data/lib/hybrid_platforms_conductor/nodes_handler.rb +597 -0
  97. data/lib/hybrid_platforms_conductor/parallel_threads.rb +97 -0
  98. data/lib/hybrid_platforms_conductor/platform_handler.rb +188 -0
  99. data/lib/hybrid_platforms_conductor/platforms_handler.rb +118 -0
  100. data/lib/hybrid_platforms_conductor/plugin.rb +53 -0
  101. data/lib/hybrid_platforms_conductor/plugins.rb +101 -0
  102. data/lib/hybrid_platforms_conductor/provisioner.rb +181 -0
  103. data/lib/hybrid_platforms_conductor/report.rb +31 -0
  104. data/lib/hybrid_platforms_conductor/reports_handler.rb +84 -0
  105. data/lib/hybrid_platforms_conductor/services_handler.rb +274 -0
  106. data/lib/hybrid_platforms_conductor/test.rb +141 -0
  107. data/lib/hybrid_platforms_conductor/test_by_service.rb +22 -0
  108. data/lib/hybrid_platforms_conductor/test_report.rb +282 -0
  109. data/lib/hybrid_platforms_conductor/tests_runner.rb +590 -0
  110. data/lib/hybrid_platforms_conductor/thycotic.rb +92 -0
  111. data/lib/hybrid_platforms_conductor/topographer.rb +859 -0
  112. data/lib/hybrid_platforms_conductor/topographer/plugin.rb +20 -0
  113. data/lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb +127 -0
  114. data/lib/hybrid_platforms_conductor/topographer/plugins/json.rb +72 -0
  115. data/lib/hybrid_platforms_conductor/topographer/plugins/my_topographer_output_plugin.rb.sample +37 -0
  116. data/lib/hybrid_platforms_conductor/topographer/plugins/svg.rb +30 -0
  117. data/lib/hybrid_platforms_conductor/version.rb +5 -0
  118. data/spec/hybrid_platforms_conductor_test.rb +159 -0
  119. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +43 -0
  120. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/interactive_spec.rb +18 -0
  121. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +102 -0
  122. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/ruby_spec.rb +108 -0
  123. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/scp_spec.rb +79 -0
  124. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions_spec.rb +199 -0
  125. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connection_spec.rb +212 -0
  126. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/cli_options_spec.rb +125 -0
  127. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +50 -0
  128. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connectable_nodes_spec.rb +28 -0
  129. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +448 -0
  130. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +313 -0
  131. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/node_helpers_spec.rb +32 -0
  132. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +134 -0
  133. data/spec/hybrid_platforms_conductor_test/api/actions_executor/logging_spec.rb +256 -0
  134. data/spec/hybrid_platforms_conductor_test/api/actions_executor/parallel_spec.rb +338 -0
  135. data/spec/hybrid_platforms_conductor_test/api/actions_executor/timeout_spec.rb +101 -0
  136. data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +165 -0
  137. data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +238 -0
  138. data/spec/hybrid_platforms_conductor_test/api/deployer/check_spec.rb +9 -0
  139. data/spec/hybrid_platforms_conductor_test/api/deployer/deploy_spec.rb +243 -0
  140. data/spec/hybrid_platforms_conductor_test/api/deployer/parse_deploy_output_spec.rb +104 -0
  141. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +131 -0
  142. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/docker/Dockerfile +10 -0
  143. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/docker_spec.rb +123 -0
  144. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/podman_spec.rb +211 -0
  145. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/config_dsl_spec.rb +126 -0
  146. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/create_spec.rb +290 -0
  147. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/destroy_spec.rb +43 -0
  148. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/ip_spec.rb +60 -0
  149. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/proxmox.json +3 -0
  150. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/destroy_vm_spec.rb +82 -0
  151. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/expired_containers_spec.rb +786 -0
  152. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/ips_assignment_spec.rb +112 -0
  153. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/other_lxc_containers_resources_spec.rb +190 -0
  154. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/pve_node_resources_spec.rb +200 -0
  155. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/retries_spec.rb +35 -0
  156. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/vm_ids_assignment_spec.rb +67 -0
  157. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/start_spec.rb +79 -0
  158. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/state_spec.rb +28 -0
  159. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/stop_spec.rb +41 -0
  160. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/config_spec.rb +33 -0
  161. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_ip_spec.rb +64 -0
  162. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +133 -0
  163. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/platform_handlers_spec.rb +19 -0
  164. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs_plugins_api_spec.rb +446 -0
  165. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/common_spec.rb +127 -0
  166. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/git_diff_impacts_spec.rb +318 -0
  167. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/nodes_selectors_spec.rb +132 -0
  168. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/platform_handlers_plugins_api_spec.rb +60 -0
  169. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/several_platforms_spec.rb +58 -0
  170. data/spec/hybrid_platforms_conductor_test/api/platform_handler_spec.rb +97 -0
  171. data/spec/hybrid_platforms_conductor_test/api/platforms_handler_spec.rb +104 -0
  172. data/spec/hybrid_platforms_conductor_test/api/plugins_spec.rb +243 -0
  173. data/spec/hybrid_platforms_conductor_test/api/reports_handler_spec.rb +44 -0
  174. data/spec/hybrid_platforms_conductor_test/api/services_handler/actions_to_deploy_spec.rb +121 -0
  175. data/spec/hybrid_platforms_conductor_test/api/services_handler/deploy_allowed_spec.rb +142 -0
  176. data/spec/hybrid_platforms_conductor_test/api/services_handler/log_info_spec.rb +101 -0
  177. data/spec/hybrid_platforms_conductor_test/api/services_handler/package_spec.rb +388 -0
  178. data/spec/hybrid_platforms_conductor_test/api/services_handler/parse_deploy_output_spec.rb +274 -0
  179. data/spec/hybrid_platforms_conductor_test/api/services_handler/prepare_for_deploy_spec.rb +264 -0
  180. data/spec/hybrid_platforms_conductor_test/api/tests_runner/common_spec.rb +194 -0
  181. data/spec/hybrid_platforms_conductor_test/api/tests_runner/global_spec.rb +37 -0
  182. data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_check_spec.rb +194 -0
  183. data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_spec.rb +137 -0
  184. data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_ssh_spec.rb +257 -0
  185. data/spec/hybrid_platforms_conductor_test/api/tests_runner/platform_spec.rb +110 -0
  186. data/spec/hybrid_platforms_conductor_test/api/tests_runner/reports_spec.rb +367 -0
  187. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +111 -0
  188. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_reports_plugins/confluence_spec.rb +29 -0
  189. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb.rb +166 -0
  190. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb2.rb +93 -0
  191. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb_others.rb +60 -0
  192. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb_others2.rb +58 -0
  193. data/spec/hybrid_platforms_conductor_test/executables/check-node_spec.rb +35 -0
  194. data/spec/hybrid_platforms_conductor_test/executables/deploy_spec.rb +35 -0
  195. data/spec/hybrid_platforms_conductor_test/executables/get_impacted_nodes_spec.rb +158 -0
  196. data/spec/hybrid_platforms_conductor_test/executables/last_deploys_spec.rb +173 -0
  197. data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +283 -0
  198. data/spec/hybrid_platforms_conductor_test/executables/options/actions_executor_spec.rb +28 -0
  199. data/spec/hybrid_platforms_conductor_test/executables/options/cmd_runner_spec.rb +28 -0
  200. data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +67 -0
  201. data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +251 -0
  202. data/spec/hybrid_platforms_conductor_test/executables/options/nodes_handler_spec.rb +111 -0
  203. data/spec/hybrid_platforms_conductor_test/executables/options/nodes_selectors_spec.rb +71 -0
  204. data/spec/hybrid_platforms_conductor_test/executables/options/reports_handler_spec.rb +54 -0
  205. data/spec/hybrid_platforms_conductor_test/executables/options/tests_runner_spec.rb +139 -0
  206. data/spec/hybrid_platforms_conductor_test/executables/report_spec.rb +60 -0
  207. data/spec/hybrid_platforms_conductor_test/executables/run_spec.rb +173 -0
  208. data/spec/hybrid_platforms_conductor_test/executables/ssh_config_spec.rb +35 -0
  209. data/spec/hybrid_platforms_conductor_test/executables/test_spec.rb +41 -0
  210. data/spec/hybrid_platforms_conductor_test/helpers/actions_executor_helpers.rb +98 -0
  211. data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +92 -0
  212. data/spec/hybrid_platforms_conductor_test/helpers/cmdb_helpers.rb +37 -0
  213. data/spec/hybrid_platforms_conductor_test/helpers/config_helpers.rb +20 -0
  214. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +130 -0
  215. data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +149 -0
  216. data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +812 -0
  217. data/spec/hybrid_platforms_conductor_test/helpers/executables_helpers.rb +96 -0
  218. data/spec/hybrid_platforms_conductor_test/helpers/nodes_handler_helpers.rb +20 -0
  219. data/spec/hybrid_platforms_conductor_test/helpers/platform_handler_helpers.rb +35 -0
  220. data/spec/hybrid_platforms_conductor_test/helpers/platforms_handler_helpers.rb +127 -0
  221. data/spec/hybrid_platforms_conductor_test/helpers/plugins_helpers.rb +48 -0
  222. data/spec/hybrid_platforms_conductor_test/helpers/provisioner_proxmox_helpers.rb +789 -0
  223. data/spec/hybrid_platforms_conductor_test/helpers/reports_handler_helpers.rb +29 -0
  224. data/spec/hybrid_platforms_conductor_test/helpers/services_handler_helpers.rb +20 -0
  225. data/spec/hybrid_platforms_conductor_test/helpers/tests_runner_helpers.rb +38 -0
  226. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem/hpc_plugins/test_plugin_type/test_plugin_id1.rb +22 -0
  227. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem/hpc_plugins/test_plugin_type/test_plugin_id2.rb +22 -0
  228. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem2/sub_dir/hpc_plugins/test_plugin_type/test_plugin_id3.rb +26 -0
  229. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem2/sub_dir/hpc_plugins/test_plugin_type2/test_plugin_id4.rb +26 -0
  230. data/spec/hybrid_platforms_conductor_test/platform_handler_plugins/test.rb +225 -0
  231. data/spec/hybrid_platforms_conductor_test/platform_handler_plugins/test2.rb +11 -0
  232. data/spec/hybrid_platforms_conductor_test/report_plugin.rb +35 -0
  233. data/spec/hybrid_platforms_conductor_test/test_action.rb +66 -0
  234. data/spec/hybrid_platforms_conductor_test/test_connector.rb +151 -0
  235. data/spec/hybrid_platforms_conductor_test/test_plugins/global.rb +30 -0
  236. data/spec/hybrid_platforms_conductor_test/test_plugins/node.rb +53 -0
  237. data/spec/hybrid_platforms_conductor_test/test_plugins/node_check.rb +47 -0
  238. data/spec/hybrid_platforms_conductor_test/test_plugins/node_ssh.rb +42 -0
  239. data/spec/hybrid_platforms_conductor_test/test_plugins/platform.rb +50 -0
  240. data/spec/hybrid_platforms_conductor_test/test_plugins/several_checks.rb +50 -0
  241. data/spec/hybrid_platforms_conductor_test/test_provisioner.rb +95 -0
  242. data/spec/hybrid_platforms_conductor_test/tests_report_plugin.rb +49 -0
  243. data/spec/spec_helper.rb +111 -0
  244. metadata +566 -0
@@ -0,0 +1,66 @@
1
+ module HybridPlatformsConductor
2
+
3
+ module HpcPlugins
4
+
5
+ module Cmdb
6
+
7
+ # CMDB getting metadata from the Platform Handlers directly
8
+ class PlatformHandlers < HybridPlatformsConductor::Cmdb
9
+
10
+ # get_* methods are automatically detected as possible metadata properties this plugin can fill.
11
+ # The property name filled by such method is given by the method's name: get_my_property will fill the :my_property metadata.
12
+ # The method get_others is used specifically to return properties whose name is unknown before fetching them.
13
+
14
+ # Get a specific property for a given set of nodes.
15
+ # [API] - @platforms_handler can be used.
16
+ # [API] - @nodes_handler can be used.
17
+ # [API] - @cmd_runner can be used.
18
+ #
19
+ # Parameters::
20
+ # * *nodes* (Array<String>): The nodes to lookup the property for.
21
+ # * *metadata* (Hash<String, Hash<Symbol,Object> >): Existing metadata for each node. Dependent properties should be present here.
22
+ # Result::
23
+ # * Hash<String, Object>: The corresponding property, per required node.
24
+ # Nodes for which the property can't be fetched can be ommitted.
25
+ def get_services(nodes, metadata)
26
+ Hash[nodes.map { |node| [node, platform_for(node).services_for(node)] }]
27
+ end
28
+
29
+ # Get other properties for a given set of nodes.
30
+ # It's better to not use this method and prefer using methods naming the property being returned.
31
+ # As the nodes_handler can't know in advance which properties will be returned, it will call it every time there is a missing property.
32
+ # If this method always returns the same values, it would be clever to cache it here.
33
+ # [API] - This method is optional.
34
+ # [API] - @platforms_handler can be used.
35
+ # [API] - @nodes_handler can be used.
36
+ # [API] - @cmd_runner can be used.
37
+ #
38
+ # Parameters::
39
+ # * *nodes* (Array<String>): The nodes to lookup the property for.
40
+ # * *metadata* (Hash<String, Hash<Symbol,Object> >): Existing metadata for each node. Dependent properties should be present here.
41
+ # Result::
42
+ # * Hash<String, Hash<Symbol,Object> >: The corresponding properties, per required node.
43
+ # Nodes for which the property can't be fetched can be ommitted.
44
+ def get_others(nodes, metadata)
45
+ Hash[nodes.map { |node| [node, platform_for(node).metadata_for(node)] }]
46
+ end
47
+
48
+ private
49
+
50
+ # Get the platform that defines a node's inventory
51
+ #
52
+ # Parameters::
53
+ # * *node* (String): The node name
54
+ # Result::
55
+ # * PlatformHandler or nil: The platform defining the node's inventory, or nil if none
56
+ def platform_for(node)
57
+ @platforms_handler.known_platforms.find { |platform| platform.known_nodes.include?(node) }
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,156 @@
1
+ module HybridPlatformsConductor
2
+
3
+ module HpcPlugins
4
+
5
+ module Connector
6
+
7
+ # Sample connector
8
+ class MyConnector < HybridPlatformsConductor::Connector
9
+
10
+ # Are dependencies met before using this plugin?
11
+ # [API] - This method is optional
12
+ #
13
+ # Result::
14
+ # * Boolean: Are dependencies met before using this plugin?
15
+ def self.valid?
16
+ true
17
+ end
18
+
19
+ # Add a Mixin to the DSL parsing the platforms configuration file.
20
+ # This can be used by any plugin to add plugin-specific configuration getters and setters, accessible later from NodesHandler instances.
21
+ # An optional initializer can also be given.
22
+ # [API] - Those calls are optional
23
+ module MyDSLExtension
24
+
25
+ attr_accessor :my_property
26
+
27
+ # Initialize the DSL
28
+ def init_my_dsl_extension
29
+ @my_property = 42
30
+ end
31
+
32
+ end
33
+ self.extend_config_dsl_with MyDSLExtension, :init_my_dsl_extension
34
+
35
+ # Initialize the connector.
36
+ # This can be used to initialize global variables that are used for this connector
37
+ # [API] - This method is optional
38
+ # [API] - @cmd_runner can be used
39
+ # [API] - @nodes_handler can be used
40
+ def init
41
+ @logger_ip = nil
42
+ end
43
+
44
+ # Complete an option parser with options meant to control this connector
45
+ # [API] - This method is optional
46
+ # [API] - @cmd_runner can be used
47
+ # [API] - @nodes_handler can be used
48
+ #
49
+ # Parameters::
50
+ # * *options_parser* (OptionParser): The option parser to complete
51
+ def options_parse(options_parser)
52
+ options_parser.on('--logger-ip IP', 'If specified, then log everything to a given IP') do |ip|
53
+ @logger_ip = ip
54
+ end
55
+ end
56
+
57
+ # Validate that parsed parameters are valid
58
+ # [API] - This method is optional
59
+ # [API] - @cmd_runner can be used
60
+ # [API] - @nodes_handler can be used
61
+ def validate_params
62
+ # Check that the logger IP is valid if specified
63
+ raise "Invalid IP: #{@logger_ip}" if @logger_ip && !(@logger_ip =~ /^\d+\.\d+\.\d+\.\d+$/)
64
+ end
65
+
66
+ # Select nodes where this connector can connect.
67
+ # [API] - This method is mandatory
68
+ # [API] - @cmd_runner can be used
69
+ # [API] - @nodes_handler can be used
70
+ #
71
+ # Parameters::
72
+ # * *nodes* (Array<String>): List of candidate nodes
73
+ # Result::
74
+ # * Array<String>: List of nodes we can connect to from the candidates
75
+ def connectable_nodes_from(nodes)
76
+ nodes.select { |node| @nodes_handler.get_ip_of(node) =~ /^192\.168\..+$/ }
77
+ end
78
+
79
+ # Prepare connections to a given set of nodes.
80
+ # Useful to prefetch metadata or open bulk connections.
81
+ # [API] - This method is optional
82
+ # [API] - @cmd_runner can be used
83
+ # [API] - @nodes_handler can be used
84
+ #
85
+ # Parameters::
86
+ # * *nodes* (Array<String>): Nodes to prepare the connection to
87
+ # * *no_exception* (Boolean): Should we still continue if some nodes have connection errors? [default: false]
88
+ # * Proc: Code called with the connections prepared.
89
+ # * Parameters::
90
+ # * *connected_nodes* (Array<String>): The list of connected nodes (should be equal to nodes unless no_exception == true and some nodes failed to connect)
91
+ def with_connection_to(nodes, no_exception: false)
92
+ register_nodes_in_logger(@logger_ip, nodes) if @logger_ip
93
+ yield nodes
94
+ end
95
+
96
+ # Run bash commands on a given node.
97
+ # [API] - This method is mandatory
98
+ # [API] - If defined, then with_connection_to has been called before this method.
99
+ # [API] - @cmd_runner can be used
100
+ # [API] - @nodes_handler can be used
101
+ # [API] - @node can be used to access the node on which we execute the remote bash
102
+ # [API] - @timeout can be used to know when the action should fail
103
+ # [API] - @stdout_io can be used to send stdout output
104
+ # [API] - @stderr_io can be used to send stderr output
105
+ #
106
+ # Parameters::
107
+ # * *bash_cmds* (String): Bash commands to execute
108
+ def remote_bash(bash_cmds)
109
+ MyConnectLib.connect_to(@nodes_handler.get_host_ip_of(@node)).run_bash(bash_cmds)
110
+ end
111
+
112
+ # Execute an interactive shell on the remote node
113
+ # [API] - This method is mandatory
114
+ # [API] - If defined, then with_connection_to has been called before this method.
115
+ # [API] - @cmd_runner can be used
116
+ # [API] - @nodes_handler can be used
117
+ # [API] - @node can be used to access the node on which we execute the remote bash
118
+ # [API] - @timeout can be used to know when the action should fail
119
+ # [API] - @stdout_io can be used to send stdout output
120
+ # [API] - @stderr_io can be used to send stderr output
121
+ def remote_interactive
122
+ MyConnectLib.connect_to(@nodes_handler.get_host_ip_of(@node)).interactive
123
+ end
124
+
125
+ # Copy a file to the remote node in a directory
126
+ # [API] - This method is mandatory
127
+ # [API] - If defined, then with_connection_to has been called before this method.
128
+ # [API] - @cmd_runner can be used
129
+ # [API] - @nodes_handler can be used
130
+ # [API] - @node can be used to access the node on which we execute the remote bash
131
+ # [API] - @timeout can be used to know when the action should fail
132
+ # [API] - @stdout_io can be used to send stdout output
133
+ # [API] - @stderr_io can be used to send stderr output
134
+ #
135
+ # Parameters::
136
+ # * *from* (String): Local file to copy
137
+ # * *to* (String): Remote directory to copy to
138
+ # * *sudo* (Boolean): Do we use sudo to copy? [default: false]
139
+ # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
140
+ # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
141
+ def remote_copy(from, to, sudo: false, owner: nil, group: nil)
142
+ MyConnectLib.connect_to(
143
+ @nodes_handler.get_host_ip_of(@node),
144
+ sudo: sudo,
145
+ user: owner,
146
+ group: group
147
+ ).cp from, to
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
156
+ end
@@ -0,0 +1,702 @@
1
+ require 'erb'
2
+ require 'digest'
3
+
4
+ module HybridPlatformsConductor
5
+
6
+ module HpcPlugins
7
+
8
+ module Connector
9
+
10
+ # Connect to node using SSH
11
+ class Ssh < HybridPlatformsConductor::Connector
12
+
13
+ module PlatformsDslSsh
14
+
15
+ # Initialize the DSL
16
+ def init_ssh
17
+ # List of gateway configurations, per gateway config name
18
+ # Hash<Symbol, String>
19
+ @gateways = {}
20
+ end
21
+
22
+ # Register a new gateway configuration
23
+ #
24
+ # Parameters::
25
+ # * *gateway_conf* (Symbol): Name of the gateway configuration
26
+ # * *ssh_def_erb* (String): Corresponding SSH ERB configuration
27
+ def gateway(gateway_conf, ssh_def_erb)
28
+ raise "Gateway #{gateway_conf} already defined to #{@gateways[gateway_conf]}" if @gateways.key?(gateway_conf)
29
+ @gateways[gateway_conf] = ssh_def_erb
30
+ end
31
+
32
+ # Get the list of known gateway configurations
33
+ #
34
+ # Result::
35
+ # * Array<Symbol>: List of known gateway configuration names
36
+ def known_gateways
37
+ @gateways.keys
38
+ end
39
+
40
+ # Get the SSH configuration for a given gateway configuration name and a list of variables that could be used in the gateway template.
41
+ #
42
+ # Parameters::
43
+ # * *gateway_conf* (Symbol): Name of the gateway configuration.
44
+ # * *variables* (Hash<Symbol,Object>): The possible variables to interpolate in the ERB gateway template [default = {}].
45
+ # Result::
46
+ # * String: The corresponding SSH configuration
47
+ def ssh_for_gateway(gateway_conf, variables = {})
48
+ erb_context = self.clone
49
+ def erb_context.get_binding
50
+ binding
51
+ end
52
+ variables.each do |var_name, var_value|
53
+ erb_context.instance_variable_set("@#{var_name}".to_sym, var_value)
54
+ end
55
+ ERB.new(@gateways[gateway_conf]).result(erb_context.get_binding)
56
+ end
57
+
58
+ end
59
+ self.extend_config_dsl_with PlatformsDslSsh, :init_ssh
60
+
61
+ # Name of the gateway user to be used. [default: ENV['hpc_ssh_gateway_user'] or ubradm]
62
+ # String
63
+ attr_accessor :ssh_gateway_user
64
+
65
+ # Name of the gateways configuration, or nil if no gateway. [default: ENV['hpc_ssh_gateways_conf'] or nil]
66
+ # Symbol or nil
67
+ attr_accessor :ssh_gateways_conf
68
+
69
+ # User name used in SSH connections. [default: ENV['hpc_ssh_user'] or ENV['USER']]
70
+ # String
71
+ attr_accessor :ssh_user
72
+
73
+ # Do we use strict host key checking in our SSH commands? [default: true]
74
+ # Boolean
75
+ attr_accessor :ssh_strict_host_key_checking
76
+
77
+ # Do we use the control master? [default: true]
78
+ # Boolean
79
+ attr_accessor :ssh_use_control_master
80
+
81
+ # Passwords to be used, per node [default: {}]
82
+ # Hash<String, String>
83
+ attr_accessor :passwords
84
+
85
+ # Do we expect some connections to require password authentication? [default: false]
86
+ # Boolean
87
+ attr_accessor :auth_password
88
+
89
+ # String: Sub-path of the system's temporary directory where temporary SSH config are generated
90
+ TMP_SSH_SUB_DIR = 'hpc_ssh'
91
+
92
+ # Initialize the connector.
93
+ # This can be used to initialize global variables that are used for this connector
94
+ # [API] - This method is optional
95
+ # [API] - @cmd_runner can be used
96
+ # [API] - @nodes_handler can be used
97
+ def init
98
+ # Default values
99
+ @ssh_user = ENV['hpc_ssh_user']
100
+ @ssh_user = ENV['USER'] if @ssh_user.nil? || @ssh_user.empty?
101
+ @ssh_use_control_master = true
102
+ @ssh_strict_host_key_checking = true
103
+ @passwords = {}
104
+ @auth_password = false
105
+ @ssh_gateways_conf = ENV['hpc_ssh_gateways_conf'].nil? ? nil : ENV['hpc_ssh_gateways_conf'].to_sym
106
+ @ssh_gateway_user = ENV['hpc_ssh_gateway_user'].nil? ? 'ubradm' : ENV['hpc_ssh_gateway_user']
107
+ # The map of existing ssh directories that have been created, per node that can access them
108
+ # Array< String, Array<String> >
109
+ @ssh_dirs = {}
110
+ # Mutex protecting the map to make sure it's thread-safe
111
+ @ssh_dirs_mutex = Mutex.new
112
+ # Temporary directory used by all ActionsExecutors, even from different processes
113
+ @tmp_dir = "#{Dir.tmpdir}/#{TMP_SSH_SUB_DIR}"
114
+ FileUtils.mkdir_p @tmp_dir
115
+ end
116
+
117
+ # Complete an option parser with options meant to control this connector
118
+ # [API] - This method is optional
119
+ # [API] - @cmd_runner can be used
120
+ # [API] - @nodes_handler can be used
121
+ #
122
+ # Parameters::
123
+ # * *options_parser* (OptionParser): The option parser to complete
124
+ def options_parse(options_parser)
125
+ options_parser.on('-g', '--ssh-gateway-user USER', "Name of the gateway user to be used by the gateways. Can also be set from environment variable hpc_ssh_gateway_user. Defaults to #{@ssh_gateway_user}.") do |user|
126
+ @ssh_gateway_user = user
127
+ end
128
+ options_parser.on('-j', '--ssh-no-control-master', 'If used, don\'t create SSH control masters for connections.') do
129
+ @ssh_use_control_master = false
130
+ end
131
+ options_parser.on('-q', '--ssh-no-host-key-checking', 'If used, don\'t check for SSH host keys.') do
132
+ @ssh_strict_host_key_checking = false
133
+ end
134
+ options_parser.on('-u', '--ssh-user USER', 'Name of user to be used in SSH connections (defaults to hpc_ssh_user or USER environment variables)') do |user|
135
+ @ssh_user = user
136
+ end
137
+ options_parser.on('-w', '--password', 'If used, then expect SSH connections to ask for a password.') do
138
+ @auth_password = true
139
+ end
140
+ options_parser.on('-y', '--ssh-gateways-conf GATEWAYS_CONF', "Name of the gateways configuration to be used. Can also be set from environment variable hpc_ssh_gateways_conf.") do |gateway|
141
+ @ssh_gateways_conf = gateway.to_sym
142
+ end
143
+ end
144
+
145
+ # Validate that parsed parameters are valid
146
+ # [API] - This method is optional
147
+ # [API] - @cmd_runner can be used
148
+ # [API] - @nodes_handler can be used
149
+ def validate_params
150
+ raise 'No SSH user name specified. Please use --ssh-user option or hpc_ssh_user environment variable to set it.' if @ssh_user.nil? || @ssh_user.empty?
151
+ known_gateways = @config.known_gateways
152
+ raise "Unknown gateway configuration provided: #{@ssh_gateways_conf}. Possible values are: #{known_gateways.join(', ')}." if !@ssh_gateways_conf.nil? && !known_gateways.include?(@ssh_gateways_conf)
153
+ end
154
+
155
+ # Select nodes where this connector can connect.
156
+ # [API] - This method is mandatory
157
+ # [API] - @cmd_runner can be used
158
+ # [API] - @nodes_handler can be used
159
+ #
160
+ # Parameters::
161
+ # * *nodes* (Array<String>): List of candidate nodes
162
+ # Result::
163
+ # * Array<String>: List of nodes we can connect to from the candidates
164
+ def connectable_nodes_from(nodes)
165
+ @nodes_handler.prefetch_metadata_of nodes, :host_ip
166
+ nodes.select { |node| @nodes_handler.get_host_ip_of(node) }
167
+ end
168
+
169
+ # Prepare connections to a given set of nodes.
170
+ # Useful to prefetch metadata or open bulk connections.
171
+ # [API] - This method is optional
172
+ # [API] - @cmd_runner can be used
173
+ # [API] - @nodes_handler can be used
174
+ #
175
+ # Parameters::
176
+ # * *nodes* (Array<String>): Nodes to prepare the connection to
177
+ # * *no_exception* (Boolean): Should we still continue if some nodes have connection errors? [default: false]
178
+ # * Proc: Code called with the connections prepared.
179
+ # * Parameters::
180
+ # * *connected_nodes* (Array<String>): The list of connected nodes (should be equal to nodes unless no_exception == true and some nodes failed to connect)
181
+ def with_connection_to(nodes, no_exception: false)
182
+ with_ssh_master_to(nodes, no_exception: no_exception) do |connected_nodes|
183
+ yield connected_nodes
184
+ end
185
+ end
186
+
187
+ # Integer: Max size for an argument that can be executed without getting through an intermediary file
188
+ MAX_CMD_ARG_LENGTH = 131_055
189
+
190
+ # Run bash commands on a given node.
191
+ # [API] - This method is mandatory
192
+ # [API] - If defined, then with_connection_to has been called before this method.
193
+ # [API] - @cmd_runner can be used
194
+ # [API] - @nodes_handler can be used
195
+ # [API] - @node can be used to access the node on which we execute the remote bash
196
+ # [API] - @timeout can be used to know when the action should fail
197
+ # [API] - @stdout_io can be used to send stdout output
198
+ # [API] - @stderr_io can be used to send stderr output
199
+ #
200
+ # Parameters::
201
+ # * *bash_cmds* (String): Bash commands to execute
202
+ def remote_bash(bash_cmds)
203
+ ssh_cmd = "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF"
204
+ # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size.
205
+ # Therefore we need to make sure that if bash_cmds exceeds MAX_CMD_ARG_LENGTH bytes (considering EOF chars) then we use an intermediary shell script to store the commands.
206
+ if bash_cmds.size > MAX_CMD_ARG_LENGTH
207
+ # Write the commands in a file
208
+ temp_file = "#{Dir.tmpdir}/hpc_temp_cmds_#{Digest::MD5.hexdigest(bash_cmds)}.sh"
209
+ File.open(temp_file, 'w+') do |file|
210
+ file.write ssh_cmd
211
+ file.chmod 0700
212
+ end
213
+ begin
214
+ run_cmd(temp_file)
215
+ ensure
216
+ File.unlink(temp_file)
217
+ end
218
+ else
219
+ run_cmd ssh_cmd
220
+ end
221
+ end
222
+
223
+ # Execute an interactive shell on the remote node
224
+ # [API] - This method is mandatory
225
+ # [API] - If defined, then with_connection_to has been called before this method.
226
+ # [API] - @cmd_runner can be used
227
+ # [API] - @nodes_handler can be used
228
+ # [API] - @node can be used to access the node on which we execute the remote bash
229
+ # [API] - @timeout can be used to know when the action should fail
230
+ # [API] - @stdout_io can be used to send stdout output
231
+ # [API] - @stderr_io can be used to send stderr output
232
+ def remote_interactive
233
+ interactive_cmd = "#{ssh_exec} #{ssh_url}"
234
+ out interactive_cmd
235
+ # As we're not using run_cmd here, make sure we handle the dry_run switch ourselves
236
+ if @cmd_runner.dry_run
237
+ out 'Won\'t execute interactive shell in dry_run mode'
238
+ else
239
+ system interactive_cmd
240
+ end
241
+ end
242
+
243
+ # Copy a file to the remote node in a directory
244
+ # [API] - This method is mandatory
245
+ # [API] - If defined, then with_connection_to has been called before this method.
246
+ # [API] - @cmd_runner can be used
247
+ # [API] - @nodes_handler can be used
248
+ # [API] - @node can be used to access the node on which we execute the remote bash
249
+ # [API] - @timeout can be used to know when the action should fail
250
+ # [API] - @stdout_io can be used to send stdout output
251
+ # [API] - @stderr_io can be used to send stderr output
252
+ #
253
+ # Parameters::
254
+ # * *from* (String): Local file to copy
255
+ # * *to* (String): Remote directory to copy to
256
+ # * *sudo* (Boolean): Do we use sudo to copy? [default: false]
257
+ # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
258
+ # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
259
+ def remote_copy(from, to, sudo: false, owner: nil, group: nil)
260
+ run_cmd <<~EOS
261
+ cd #{File.dirname(from)} && \
262
+ tar \
263
+ --create \
264
+ --gzip \
265
+ --file - \
266
+ #{owner.nil? ? '' : "--owner #{owner}"} \
267
+ #{group.nil? ? '' : "--group #{group}"} \
268
+ #{File.basename(from)} | \
269
+ #{ssh_exec} \
270
+ #{ssh_url} \
271
+ \"#{sudo ? 'sudo ' : ''}tar \
272
+ --extract \
273
+ --gunzip \
274
+ --file - \
275
+ --directory #{to} \
276
+ --owner root \
277
+ \"
278
+ EOS
279
+ end
280
+
281
+ # Get the ssh executable to be used when connecting to the current node
282
+ #
283
+ # Result::
284
+ # * String: The ssh executable
285
+ def ssh_exec
286
+ ssh_exec_for @node
287
+ end
288
+
289
+ # Get the ssh URL to be used to connect to the current node
290
+ #
291
+ # Result::
292
+ # * String: The ssh URL connecting to the current node
293
+ def ssh_url
294
+ "#{@ssh_user}@hpc.#{@node}"
295
+ end
296
+
297
+ # Get an SSH configuration content giving access to nodes of the platforms with the current configuration
298
+ #
299
+ # Parameters::
300
+ # * *ssh_exec* (String): SSH command to be used [default: 'ssh']
301
+ # * *known_hosts_file* (String or nil): Path to the known hosts file, or nil for default [default: nil]
302
+ # * *nodes* (Array<String>): List of nodes to generate the config for [default: @nodes_handler.known_nodes]
303
+ # Result::
304
+ # * String: The SSH config
305
+ def ssh_config(ssh_exec: 'ssh', known_hosts_file: nil, nodes: @nodes_handler.known_nodes)
306
+ config_content = <<~EOS
307
+ ############
308
+ # GATEWAYS #
309
+ ############
310
+
311
+ #{@ssh_gateways_conf.nil? || !@config.known_gateways.include?(@ssh_gateways_conf) ? '' : @config.ssh_for_gateway(@ssh_gateways_conf, ssh_exec: ssh_exec, user: @ssh_user)}
312
+
313
+ #############
314
+ # ENDPOINTS #
315
+ #############
316
+
317
+ Host *
318
+ User #{@ssh_user}
319
+ # Default control socket path to be used when multiplexing SSH connections
320
+ ControlPath #{control_master_file('%h', '%p', '%r')}
321
+ #{open_ssh_major_version >= 7 ? 'PubkeyAcceptedKeyTypes +ssh-dss' : ''}
322
+ #{known_hosts_file.nil? ? '' : "UserKnownHostsFile #{known_hosts_file}"}
323
+ #{@ssh_strict_host_key_checking ? '' : 'StrictHostKeyChecking no'}
324
+
325
+ EOS
326
+
327
+ # Add each node
328
+ # Query for the metadata of all nodes at once
329
+ @nodes_handler.prefetch_metadata_of nodes, %i[private_ips hostname host_ip description]
330
+ nodes.sort.each do |node|
331
+ # Generate the conf for the node
332
+ connection, gateway, gateway_user = connection_info_for(node)
333
+ config_content << "# #{node} - #{connection} - #{@nodes_handler.get_description_of(node) || ''}\n"
334
+ config_content << "Host #{ssh_aliases_for(node).join(' ')}\n"
335
+ config_content << " Hostname #{connection}\n"
336
+ config_content << " ProxyCommand #{ssh_exec} -q -W %h:%p #{gateway_user}@#{gateway}\n" unless gateway.nil?
337
+ if @passwords.key?(node)
338
+ config_content << " PreferredAuthentications password\n"
339
+ config_content << " PubkeyAuthentication no\n"
340
+ end
341
+ config_content << "\n"
342
+ end
343
+ config_content
344
+ end
345
+
346
+ private
347
+
348
+ # Is sshpass installed?
349
+ # Keep a cache of it.
350
+ #
351
+ # Result::
352
+ # * Boolean: Is sshpass installed?
353
+ def ssh_pass_installed?
354
+ cache_filled = defined?(@ssh_pass_installed)
355
+ unless cache_filled
356
+ exit_code, _stdout, _stderr = @cmd_runner.run_cmd 'sshpass -V', log_to_stdout: log_debug?, no_exception: true
357
+ @ssh_pass_installed = (exit_code == 0)
358
+ end
359
+ @ssh_pass_installed
360
+ end
361
+
362
+ # Get the env system path
363
+ # Keep a cache of it.
364
+ #
365
+ # Result::
366
+ # * String: The env system path
367
+ def env_system_path
368
+ cache_filled = defined?(@env_system_path)
369
+ unless cache_filled
370
+ _exit_status, stdout, _stderr = @cmd_runner.run_cmd 'which env', log_to_stdout: log_debug?
371
+ @env_system_path = stdout.strip
372
+ end
373
+ @env_system_path
374
+ end
375
+
376
+ # Get the installed ssh version.
377
+ # Mock it in case of dry run.
378
+ # Keep a cache of it.
379
+ #
380
+ # Result::
381
+ # * String: The installed SSH major version
382
+ def open_ssh_major_version
383
+ cache_filled = defined?(@open_ssh_major_version)
384
+ unless cache_filled
385
+ _exit_status, stdout, _stderr = @cmd_runner.run_cmd 'ssh -V 2>&1', log_to_stdout: log_debug?
386
+ # Make sure we have a fake value in case of dry-run
387
+ if @cmd_runner.dry_run
388
+ log_debug 'Mock OpenSSH version because of dry-run mode'
389
+ stdout = 'OpenSSH_7.4p1 Debian-10+deb9u7, OpenSSL 1.0.2u 20 Dec 2019'
390
+ end
391
+ @open_ssh_major_version = stdout.match(/^OpenSSH_(\d)\..+$/)[1].to_i
392
+ end
393
+ @open_ssh_major_version
394
+ end
395
+
396
+ # Return the ssh executable that can be used for a given node
397
+ #
398
+ # Parameters::
399
+ # * *node* (String): The node wanting to access its SSH executable
400
+ # Result::
401
+ # * String: The path to the ssh executable that contains the node's config
402
+ def ssh_exec_for(node)
403
+ "#{@ssh_dirs[node].last}/ssh"
404
+ end
405
+
406
+ # Max number of threads to use to parallelize ControlMaster connections
407
+ MAX_THREADS_CONTROL_MASTER = 32
408
+
409
+ # Max number of retries because a system is booting up
410
+ MAX_RETRIES_FOR_BOOT = 10
411
+
412
+ # Time in seconds to wait between different retries because system is booting up
413
+ WAIT_TIME_FOR_BOOT = 10
414
+
415
+ # Open an SSH control master to multiplex connections to a given list of nodes.
416
+ # This method is re-entrant and reuses the same control masters.
417
+ # It is multi-processes:
418
+ # * A file-mutex is used to keep track of all processes connecting to a user@node.
419
+ # * When the last process has finished using the control master, it closes it.
420
+ #
421
+ # Parameters::
422
+ # * *nodes* (String or Array<String>): The nodes (or single node) for which we open the Control Master.
423
+ # * *timeout* (Integer or nil): Timeout in seconds, or nil if none. [default: nil]
424
+ # * *no_exception* (Boolean): If true, then don't raise any exception in case of impossible connection to the ControlMaster. [default: false]
425
+ # * Proc: Code called while the ControlMaster exists.
426
+ # * Parameters::
427
+ # * *connected_nodes* (Array<String>): The list of connected nodes (should be equal to nodes unless no_exception == true and some nodes failed to connect)
428
+ def with_ssh_master_to(nodes, timeout: nil, no_exception: false)
429
+ nodes = [nodes] if nodes.is_a?(String)
430
+ with_platforms_ssh(nodes: nodes) do
431
+ # List of user_ids that acquired a lock, per node
432
+ user_locks = {}
433
+ user_locks_mutex = Mutex.new
434
+ begin
435
+ if @ssh_use_control_master
436
+ @nodes_handler.for_each_node_in(
437
+ nodes,
438
+ parallel: true,
439
+ nbr_threads_max: MAX_THREADS_CONTROL_MASTER,
440
+ progress: log_debug? ? 'Getting SSH ControlMasters' : nil
441
+ ) do |node|
442
+ with_lock_on_control_master_for(node) do |current_users, user_id|
443
+ working_master = false
444
+ ssh_exec = ssh_exec_for(node)
445
+ ssh_url = "#{@ssh_user}@hpc.#{node}"
446
+ if current_users.empty?
447
+ log_debug "[ ControlMaster - #{ssh_url} ] - Creating SSH ControlMaster..."
448
+ # Create the control master
449
+ ssh_control_master_start_cmd = "#{ssh_exec}#{@passwords.key?(node) || @auth_password ? '' : ' -o BatchMode=yes'} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url} true"
450
+ exit_status = nil
451
+ idx_try = 0
452
+ loop do
453
+ stderr = nil
454
+ exit_status, _stdout, stderr = @cmd_runner.run_cmd ssh_control_master_start_cmd, log_to_stdout: log_debug?, no_exception: true, timeout: timeout
455
+ if exit_status == 0
456
+ break
457
+ elsif stderr =~ /System is booting up/
458
+ if idx_try == MAX_RETRIES_FOR_BOOT
459
+ if no_exception
460
+ break
461
+ else
462
+ raise ActionsExecutor::ConnectionError, "Tried #{idx_try} times to create SSH Control Master with #{ssh_control_master_start_cmd} but system says it's booting up."
463
+ end
464
+ end
465
+ # Wait a bit and try again
466
+ idx_try += 1
467
+ log_debug "[ ControlMaster - #{ssh_url} ] - System is booting up (try ##{idx_try}). Wait #{WAIT_TIME_FOR_BOOT} seconds before trying ControlMaster's creation again."
468
+ sleep WAIT_TIME_FOR_BOOT
469
+ elsif no_exception
470
+ break
471
+ else
472
+ raise ActionsExecutor::ConnectionError, "Error while starting SSH Control Master with #{ssh_control_master_start_cmd}: #{stderr.strip}"
473
+ end
474
+ end
475
+ if exit_status == 0
476
+ log_debug "[ ControlMaster - #{ssh_url} ] - ControlMaster created"
477
+ working_master = true
478
+ else
479
+ log_error "[ ControlMaster - #{ssh_url} ] - ControlMaster could not be started"
480
+ end
481
+ else
482
+ # The control master should already exist
483
+ log_debug "[ ControlMaster - #{ssh_url} ] - Using existing SSH ControlMaster..."
484
+ # Test that it is working
485
+ ssh_control_master_check_cmd = "#{ssh_exec} -O check #{ssh_url}"
486
+ begin
487
+ exit_status, _stdout, _stderr = @cmd_runner.run_cmd ssh_control_master_check_cmd, log_to_stdout: log_debug?, no_exception: no_exception, timeout: timeout
488
+ rescue CmdRunner::UnexpectedExitCodeError
489
+ raise ActionsExecutor::ConnectionError, "Error while checking SSH Control Master with #{ssh_control_master_check_cmd}"
490
+ end
491
+ if exit_status == 0
492
+ log_debug "[ ControlMaster - #{ssh_url} ] - ControlMaster checked ok"
493
+ working_master = true
494
+ else
495
+ log_error "[ ControlMaster - #{ssh_url} ] - ControlMaster could not be used"
496
+ end
497
+ end
498
+ # Make sure we register ourselves among the users if the master is working
499
+ if working_master
500
+ user_locks_mutex.synchronize { user_locks[node] = user_id }
501
+ true
502
+ else
503
+ false
504
+ end
505
+ end
506
+ end
507
+ end
508
+ yield user_locks.keys
509
+ ensure
510
+ user_locks_mutex.synchronize do
511
+ user_locks.each do |node, user_id|
512
+ with_lock_on_control_master_for(node, user_id: user_id) do |current_users, user_id|
513
+ ssh_url = "#{@ssh_user}@hpc.#{node}"
514
+ log_warn "[ ControlMaster - #{ssh_url} ] - Current process/thread was not part of the ControlMaster users anymore whereas it should have been" unless current_users.include?(user_id)
515
+ remaining_users = current_users - [user_id]
516
+ if remaining_users.empty?
517
+ # Stop the ControlMaster
518
+ log_debug "[ ControlMaster - #{ssh_url} ] - Stopping ControlMaster..."
519
+ # Dumb verbose ssh! Tricky trick to just silence what is useless.
520
+ # Don't fail if the connection close fails (but still log the error), as it can be seen as only a warning: it means the connection was closed anyway.
521
+ @cmd_runner.run_cmd "#{ssh_exec_for(node)} -O exit #{ssh_url} 2>&1 | grep -v 'Exit request sent.'", log_to_stdout: log_debug?, expected_code: 1, timeout: timeout, no_exception: true
522
+ log_debug "[ ControlMaster - #{ssh_url} ] - ControlMaster stopped"
523
+ # Uncomment if you want to test that the connection has been closed
524
+ # @cmd_runner.run_cmd "#{ssh_exec_for(node)} -O check #{ssh_url}", log_to_stdout: log_debug?, expected_code: 255, timeout: timeout
525
+ else
526
+ log_debug "[ ControlMaster - #{ssh_url} ] - Leaving ControlMaster started as #{remaining_users.size} processes/threads are still using it."
527
+ end
528
+ false
529
+ end
530
+ end
531
+ end
532
+ end
533
+ end
534
+ end
535
+
536
+ # Get the lock to access users of a given node's ControlMaster.
537
+ # Make sure the lock is released when exiting client code.
538
+ # Handle stalled ControlMaster files as well.
539
+ #
540
+ # Parameters::
541
+ # * *node* (String): Node to access
542
+ # * *user_id* (String or nil): User ID that wants to access the lock, or nil to get a new generated one. [default: nil]
543
+ # * Proc: The code to be called with lock taken
544
+ # * Parameters::
545
+ # * *current_users* (Array<String>): Current user IDs having the lock
546
+ # * *user_id* (String): The user ID
547
+ # * Result::
548
+ # * Boolean: Should we stay as users of the lock?
549
+ def with_lock_on_control_master_for(node, user_id: nil)
550
+ user_id = "#{Process.pid}.#{Thread.current.object_id}.#{SecureRandom.uuid}" if user_id.nil?
551
+ control_master_users_file = "#{@tmp_dir}/#{@ssh_user}.#{node}.users"
552
+ # Make sure we remove our token for this control master
553
+ Futex.new(control_master_users_file).open do
554
+ # TODO: Add test case when control file is missing ad when it is stale
555
+ # Get the list of existing process/thread ids using this control master
556
+ existing_users = File.exist?(control_master_users_file) ? File.read(control_master_users_file).split("\n") : []
557
+ ssh_url = "#{@ssh_user}@hpc.#{node}"
558
+ control_path_file = control_master_file(connection_info_for(node).first, '22', @ssh_user)
559
+ if existing_users.empty?
560
+ # Make sure there is no stale one.
561
+ if File.exist?(control_path_file)
562
+ log_warn "[ ControlMaster - #{ssh_url} ] - Removing stale SSH control file #{control_path_file}"
563
+ File.unlink control_path_file
564
+ end
565
+ elsif !File.exist?(control_path_file)
566
+ # Make sure the control file is still present, otherwise it means we should not have users
567
+ log_warn "[ ControlMaster - #{ssh_url} ] - Missing SSH control file #{control_path_file} whereas the following users were supposed to use it: #{existing_users.join(', ')}"
568
+ existing_users = []
569
+ end
570
+ confirmed_user = yield existing_users, user_id
571
+ user_already_included = existing_users.include?(user_id)
572
+ existing_users_to_update = nil
573
+ if confirmed_user
574
+ existing_users_to_update = existing_users + [user_id] unless user_already_included
575
+ elsif user_already_included
576
+ existing_users_to_update = existing_users - [user_id]
577
+ end
578
+ File.write(control_master_users_file, existing_users_to_update.join("\n")) if existing_users_to_update
579
+ end
580
+ user_id
581
+ end
582
+
583
+ # Return the name of a ControlMaster file used for a given host, port and user
584
+ #
585
+ # Parameters::
586
+ # * *host* (String): The host
587
+ # * *port* (String): The port. Can be a string as ssh config uses wildchars.
588
+ # * *user* (String): The user
589
+ def control_master_file(host, port, user)
590
+ "#{@tmp_dir}/hpc_actions_executor_mux_#{host}_#{port}_#{user}"
591
+ end
592
+
593
+ # Provide a bootstrapped ssh executable that includes an SSH config allowing access to nodes.
594
+ #
595
+ # Parameters::
596
+ # * *nodes* (Array<String>): List of nodes for which we need the config to be created [default: @nodes_handler.known_nodes ]
597
+ # * Proc: Code called with the given ssh executable to be used to get TI config
598
+ def with_platforms_ssh(nodes: @nodes_handler.known_nodes)
599
+ platforms_ssh_dir = Dir.mktmpdir("platforms_ssh_#{self.object_id}", @tmp_dir)
600
+ log_debug "Generate temporary SSH configuration in #{platforms_ssh_dir} for #{nodes.size} nodes..."
601
+ begin
602
+ ssh_conf_file = "#{platforms_ssh_dir}/ssh_config"
603
+ ssh_exec_file = "#{platforms_ssh_dir}/ssh"
604
+ known_hosts_file = "#{platforms_ssh_dir}/known_hosts"
605
+ raise 'sshpass is not installed. Can\'t use automatic passwords handling without it. Please install it.' if !@passwords.empty? && !ssh_pass_installed?
606
+ File.open(ssh_exec_file, 'w+', 0700) do |file|
607
+ file.puts "#!#{env_system_path} bash"
608
+ # TODO: Make a mechanism that uses sshpass and the correct password only for the correct hostname (this requires parsing ssh parameters $*).
609
+ # Current implementation is much simpler: it uses sshpass if at least 1 password is needed, and always uses the first password.
610
+ # So far it is enough for our usage as we intend to use this only when deploying first time using root account, and all root accounts will have the same password.
611
+ file.puts "#{@passwords.empty? ? '' : "sshpass -p#{@passwords.first[1]} "}ssh -F #{ssh_conf_file} $*"
612
+ end
613
+ File.write(ssh_conf_file, ssh_config(ssh_exec: ssh_exec_file, known_hosts_file: known_hosts_file, nodes: nodes))
614
+ # Make sure all host keys are setup in the known hosts file
615
+ File.open(known_hosts_file, 'w+', 0700) do |file|
616
+ if @ssh_strict_host_key_checking
617
+ # In the case of an overriden connection, get host key for the overriden connection
618
+ @nodes_handler.prefetch_metadata_of nodes, :host_keys
619
+ nodes.sort.each do |node|
620
+ host_keys = @nodes_handler.get_host_keys_of(node)
621
+ if host_keys && !host_keys.empty?
622
+ connection, _gateway, _gateway_user = connection_info_for(node)
623
+ host_keys.each do |host_key|
624
+ file.puts "#{connection} #{host_key}"
625
+ end
626
+ end
627
+ end
628
+ end
629
+ end
630
+ # Mark this directory as accessible for the nodes
631
+ @ssh_dirs_mutex.synchronize do
632
+ nodes.each do |node|
633
+ @ssh_dirs[node] = [] unless @ssh_dirs.key?(node)
634
+ @ssh_dirs[node] << platforms_ssh_dir
635
+ end
636
+ end
637
+ yield
638
+ ensure
639
+ # It's very important to remove the directory as soon as it is useless, as it contains eventual passwords
640
+ FileUtils.remove_entry platforms_ssh_dir
641
+ # Mark this directory as not accessible anymore for the nodes
642
+ @ssh_dirs_mutex.synchronize do
643
+ nodes.each do |node|
644
+ # Check that the key exists as it is possible that an exception occurred before setting @ssh_dirs
645
+ @ssh_dirs[node].delete(platforms_ssh_dir) if @ssh_dirs.key?(node)
646
+ end
647
+ end
648
+ end
649
+ end
650
+
651
+ # Get the connection information for a given node.
652
+ #
653
+ # Parameters::
654
+ # * *node* (String): The node to access
655
+ # Result::
656
+ # * String: The real hostname or IP to be used to connect
657
+ # * String or nil: The gateway name to be used (should be defined by the gateways configurations), or nil if no gateway to be used.
658
+ # * String or nil: The gateway user to be used, or nil if none.
659
+ def connection_info_for(node)
660
+ connection =
661
+ if @nodes_handler.get_host_ip_of(node)
662
+ @nodes_handler.get_host_ip_of(node)
663
+ elsif @nodes_handler.get_private_ips_of(node)
664
+ @nodes_handler.get_private_ips_of(node).first
665
+ elsif @nodes_handler.get_hostname_of(node)
666
+ @nodes_handler.get_hostname_of(node)
667
+ else
668
+ raise "No connection possible to #{node}"
669
+ end
670
+ gateway = @nodes_handler.get_gateway_of node
671
+ gateway_user = @nodes_handler.get_gateway_user_of node
672
+ gateway_user = @ssh_gateway_user if !gateway.nil? && gateway_user.nil?
673
+ [connection, gateway, gateway_user]
674
+ end
675
+
676
+ # Get the possible SSH aliases for a given node.
677
+ #
678
+ # Parameters::
679
+ # * *node* (String): The node to access
680
+ # Result::
681
+ # * Array<String>: The list of possible SSH aliases
682
+ def ssh_aliases_for(node)
683
+ aliases = ["hpc.#{node}"]
684
+ # Make sure the real hostname that could be used by other processes also route to the real IP.
685
+ # Especially useful when connections are overriden to a different IP.
686
+ aliases << @nodes_handler.get_hostname_of(node) if @nodes_handler.get_hostname_of(node)
687
+ if @nodes_handler.get_private_ips_of(node)
688
+ aliases.concat(@nodes_handler.get_private_ips_of(node).map do |ip|
689
+ split_ip = ip.split('.').map(&:to_i)
690
+ "hpc.#{(split_ip[0..1] == [172, 16] ? split_ip[2..3] : split_ip).join('.')}"
691
+ end)
692
+ end
693
+ aliases
694
+ end
695
+
696
+ end
697
+
698
+ end
699
+
700
+ end
701
+
702
+ end