hybrid_platforms_conductor 32.3.6
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.
- checksums.yaml +7 -0
- data/bin/check-node +24 -0
- data/bin/deploy +12 -0
- data/bin/dump_nodes_json +12 -0
- data/bin/free_ips +23 -0
- data/bin/free_veids +17 -0
- data/bin/get_impacted_nodes +43 -0
- data/bin/last_deploys +56 -0
- data/bin/nodes_to_deploy +104 -0
- data/bin/report +10 -0
- data/bin/run +39 -0
- data/bin/setup +11 -0
- data/bin/ssh_config +14 -0
- data/bin/test +13 -0
- data/bin/topograph +54 -0
- data/lib/hybrid_platforms_conductor/action.rb +82 -0
- data/lib/hybrid_platforms_conductor/actions_executor.rb +307 -0
- data/lib/hybrid_platforms_conductor/bitbucket.rb +123 -0
- data/lib/hybrid_platforms_conductor/cmd_runner.rb +188 -0
- data/lib/hybrid_platforms_conductor/cmdb.rb +34 -0
- data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +78 -0
- data/lib/hybrid_platforms_conductor/common_config_dsl/confluence.rb +43 -0
- data/lib/hybrid_platforms_conductor/common_config_dsl/file_system_tests.rb +110 -0
- data/lib/hybrid_platforms_conductor/common_config_dsl/idempotence_tests.rb +38 -0
- data/lib/hybrid_platforms_conductor/config.rb +263 -0
- data/lib/hybrid_platforms_conductor/confluence.rb +119 -0
- data/lib/hybrid_platforms_conductor/connector.rb +84 -0
- data/lib/hybrid_platforms_conductor/credentials.rb +127 -0
- data/lib/hybrid_platforms_conductor/current_dir_monitor.rb +42 -0
- data/lib/hybrid_platforms_conductor/deployer.rb +598 -0
- data/lib/hybrid_platforms_conductor/executable.rb +145 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +44 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/interactive.rb +44 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/my_action.rb.sample +79 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +63 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/ruby.rb +69 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/scp.rb +61 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/config.rb +78 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_ip.rb +104 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +114 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/my_cmdb.rb.sample +129 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/platform_handlers.rb +66 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +156 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +702 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/platform_handler_plugin.rb.sample +292 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +148 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/my_provisioner.rb.sample +103 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/podman.rb +125 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +522 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/proxmox_waiter.rb +707 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/reserve_proxmox_container +122 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +69 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/mediawiki.rb +164 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/my_report_plugin.rb.sample +88 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/stdout.rb +61 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/templates/confluence_inventory.html.erb +33 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +137 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/can_be_checked.rb +21 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +112 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_from_scratch.rb +35 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/connection.rb +28 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +44 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_from_scratch.rb +36 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +49 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/divergence.rb +25 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/executables.rb +46 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +45 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system_hdfs.rb +45 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +25 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +77 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +38 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +56 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +54 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/linear_strategy.rb +47 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +82 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +120 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/my_test_plugin.rb.sample +143 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +74 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ports.rb +85 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/private_ips.rb +38 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/public_ips.rb +38 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre-meltdown-checker.sh +1930 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +56 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/veids.rb +31 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +159 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +122 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/my_test_report.rb.sample +48 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/stdout.rb +120 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/_confluence_errors_status.html.erb +46 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/_confluence_gauge.html.erb +49 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/confluence.html.erb +242 -0
- data/lib/hybrid_platforms_conductor/io_router.rb +70 -0
- data/lib/hybrid_platforms_conductor/json_dumper.rb +88 -0
- data/lib/hybrid_platforms_conductor/logger_helpers.rb +319 -0
- data/lib/hybrid_platforms_conductor/mutex_dir +76 -0
- data/lib/hybrid_platforms_conductor/nodes_handler.rb +597 -0
- data/lib/hybrid_platforms_conductor/parallel_threads.rb +97 -0
- data/lib/hybrid_platforms_conductor/platform_handler.rb +188 -0
- data/lib/hybrid_platforms_conductor/platforms_handler.rb +118 -0
- data/lib/hybrid_platforms_conductor/plugin.rb +53 -0
- data/lib/hybrid_platforms_conductor/plugins.rb +101 -0
- data/lib/hybrid_platforms_conductor/provisioner.rb +181 -0
- data/lib/hybrid_platforms_conductor/report.rb +31 -0
- data/lib/hybrid_platforms_conductor/reports_handler.rb +84 -0
- data/lib/hybrid_platforms_conductor/services_handler.rb +274 -0
- data/lib/hybrid_platforms_conductor/test.rb +141 -0
- data/lib/hybrid_platforms_conductor/test_by_service.rb +22 -0
- data/lib/hybrid_platforms_conductor/test_report.rb +282 -0
- data/lib/hybrid_platforms_conductor/tests_runner.rb +590 -0
- data/lib/hybrid_platforms_conductor/thycotic.rb +92 -0
- data/lib/hybrid_platforms_conductor/topographer.rb +859 -0
- data/lib/hybrid_platforms_conductor/topographer/plugin.rb +20 -0
- data/lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb +127 -0
- data/lib/hybrid_platforms_conductor/topographer/plugins/json.rb +72 -0
- data/lib/hybrid_platforms_conductor/topographer/plugins/my_topographer_output_plugin.rb.sample +37 -0
- data/lib/hybrid_platforms_conductor/topographer/plugins/svg.rb +30 -0
- data/lib/hybrid_platforms_conductor/version.rb +5 -0
- data/spec/hybrid_platforms_conductor_test.rb +159 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +43 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/interactive_spec.rb +18 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +102 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/ruby_spec.rb +108 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/scp_spec.rb +79 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions_spec.rb +199 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connection_spec.rb +212 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/cli_options_spec.rb +125 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +50 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connectable_nodes_spec.rb +28 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +448 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +313 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/node_helpers_spec.rb +32 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +134 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/logging_spec.rb +256 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/parallel_spec.rb +338 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/timeout_spec.rb +101 -0
- data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +165 -0
- data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +238 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/check_spec.rb +9 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/deploy_spec.rb +243 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/parse_deploy_output_spec.rb +104 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +131 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/docker/Dockerfile +10 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/docker_spec.rb +123 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/podman_spec.rb +211 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/config_dsl_spec.rb +126 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/create_spec.rb +290 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/destroy_spec.rb +43 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/ip_spec.rb +60 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/proxmox.json +3 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/destroy_vm_spec.rb +82 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/expired_containers_spec.rb +786 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/ips_assignment_spec.rb +112 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/other_lxc_containers_resources_spec.rb +190 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/pve_node_resources_spec.rb +200 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/retries_spec.rb +35 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/vm_ids_assignment_spec.rb +67 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/start_spec.rb +79 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/state_spec.rb +28 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/stop_spec.rb +41 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/config_spec.rb +33 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_ip_spec.rb +64 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +133 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/platform_handlers_spec.rb +19 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs_plugins_api_spec.rb +446 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/common_spec.rb +127 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/git_diff_impacts_spec.rb +318 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/nodes_selectors_spec.rb +132 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/platform_handlers_plugins_api_spec.rb +60 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/several_platforms_spec.rb +58 -0
- data/spec/hybrid_platforms_conductor_test/api/platform_handler_spec.rb +97 -0
- data/spec/hybrid_platforms_conductor_test/api/platforms_handler_spec.rb +104 -0
- data/spec/hybrid_platforms_conductor_test/api/plugins_spec.rb +243 -0
- data/spec/hybrid_platforms_conductor_test/api/reports_handler_spec.rb +44 -0
- data/spec/hybrid_platforms_conductor_test/api/services_handler/actions_to_deploy_spec.rb +121 -0
- data/spec/hybrid_platforms_conductor_test/api/services_handler/deploy_allowed_spec.rb +142 -0
- data/spec/hybrid_platforms_conductor_test/api/services_handler/log_info_spec.rb +101 -0
- data/spec/hybrid_platforms_conductor_test/api/services_handler/package_spec.rb +388 -0
- data/spec/hybrid_platforms_conductor_test/api/services_handler/parse_deploy_output_spec.rb +274 -0
- data/spec/hybrid_platforms_conductor_test/api/services_handler/prepare_for_deploy_spec.rb +264 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/common_spec.rb +194 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/global_spec.rb +37 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_check_spec.rb +194 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_spec.rb +137 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_ssh_spec.rb +257 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/platform_spec.rb +110 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/reports_spec.rb +367 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +111 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_reports_plugins/confluence_spec.rb +29 -0
- data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb.rb +166 -0
- data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb2.rb +93 -0
- data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb_others.rb +60 -0
- data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb_others2.rb +58 -0
- data/spec/hybrid_platforms_conductor_test/executables/check-node_spec.rb +35 -0
- data/spec/hybrid_platforms_conductor_test/executables/deploy_spec.rb +35 -0
- data/spec/hybrid_platforms_conductor_test/executables/get_impacted_nodes_spec.rb +158 -0
- data/spec/hybrid_platforms_conductor_test/executables/last_deploys_spec.rb +173 -0
- data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +283 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/actions_executor_spec.rb +28 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/cmd_runner_spec.rb +28 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +67 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +251 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/nodes_handler_spec.rb +111 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/nodes_selectors_spec.rb +71 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/reports_handler_spec.rb +54 -0
- data/spec/hybrid_platforms_conductor_test/executables/options/tests_runner_spec.rb +139 -0
- data/spec/hybrid_platforms_conductor_test/executables/report_spec.rb +60 -0
- data/spec/hybrid_platforms_conductor_test/executables/run_spec.rb +173 -0
- data/spec/hybrid_platforms_conductor_test/executables/ssh_config_spec.rb +35 -0
- data/spec/hybrid_platforms_conductor_test/executables/test_spec.rb +41 -0
- data/spec/hybrid_platforms_conductor_test/helpers/actions_executor_helpers.rb +98 -0
- data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +92 -0
- data/spec/hybrid_platforms_conductor_test/helpers/cmdb_helpers.rb +37 -0
- data/spec/hybrid_platforms_conductor_test/helpers/config_helpers.rb +20 -0
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +130 -0
- data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +149 -0
- data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +812 -0
- data/spec/hybrid_platforms_conductor_test/helpers/executables_helpers.rb +96 -0
- data/spec/hybrid_platforms_conductor_test/helpers/nodes_handler_helpers.rb +20 -0
- data/spec/hybrid_platforms_conductor_test/helpers/platform_handler_helpers.rb +35 -0
- data/spec/hybrid_platforms_conductor_test/helpers/platforms_handler_helpers.rb +127 -0
- data/spec/hybrid_platforms_conductor_test/helpers/plugins_helpers.rb +48 -0
- data/spec/hybrid_platforms_conductor_test/helpers/provisioner_proxmox_helpers.rb +789 -0
- data/spec/hybrid_platforms_conductor_test/helpers/reports_handler_helpers.rb +29 -0
- data/spec/hybrid_platforms_conductor_test/helpers/services_handler_helpers.rb +20 -0
- data/spec/hybrid_platforms_conductor_test/helpers/tests_runner_helpers.rb +38 -0
- data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem/hpc_plugins/test_plugin_type/test_plugin_id1.rb +22 -0
- data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem/hpc_plugins/test_plugin_type/test_plugin_id2.rb +22 -0
- data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem2/sub_dir/hpc_plugins/test_plugin_type/test_plugin_id3.rb +26 -0
- data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem2/sub_dir/hpc_plugins/test_plugin_type2/test_plugin_id4.rb +26 -0
- data/spec/hybrid_platforms_conductor_test/platform_handler_plugins/test.rb +225 -0
- data/spec/hybrid_platforms_conductor_test/platform_handler_plugins/test2.rb +11 -0
- data/spec/hybrid_platforms_conductor_test/report_plugin.rb +35 -0
- data/spec/hybrid_platforms_conductor_test/test_action.rb +66 -0
- data/spec/hybrid_platforms_conductor_test/test_connector.rb +151 -0
- data/spec/hybrid_platforms_conductor_test/test_plugins/global.rb +30 -0
- data/spec/hybrid_platforms_conductor_test/test_plugins/node.rb +53 -0
- data/spec/hybrid_platforms_conductor_test/test_plugins/node_check.rb +47 -0
- data/spec/hybrid_platforms_conductor_test/test_plugins/node_ssh.rb +42 -0
- data/spec/hybrid_platforms_conductor_test/test_plugins/platform.rb +50 -0
- data/spec/hybrid_platforms_conductor_test/test_plugins/several_checks.rb +50 -0
- data/spec/hybrid_platforms_conductor_test/test_provisioner.rb +95 -0
- data/spec/hybrid_platforms_conductor_test/tests_report_plugin.rb +49 -0
- data/spec/spec_helper.rb +111 -0
- 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
|