hybrid_platforms_conductor 32.4.1 → 32.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hybrid_platforms_conductor/deployer.rb +9 -8
  3. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +169 -85
  4. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/platform_handler_plugin.rb.sample +1 -1
  5. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +1 -1
  6. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +1 -1
  7. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
  8. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -1
  9. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -1
  10. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -1
  11. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -1
  12. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -1
  13. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +6 -7
  14. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +7 -6
  15. data/lib/hybrid_platforms_conductor/nodes_handler.rb +37 -0
  16. data/lib/hybrid_platforms_conductor/services_handler.rb +9 -13
  17. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  18. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +35 -0
  19. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +65 -5
  20. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +68 -12
  21. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/node_helpers_spec.rb +1 -1
  22. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +47 -9
  23. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/common_spec.rb +28 -0
  24. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/config_dsl_spec.rb +71 -0
  25. data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +2 -1
  26. data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +25 -11
  27. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +50 -11
  28. data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +14 -14
  29. data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +70 -11
  30. data/spec/hybrid_platforms_conductor_test/helpers/platforms_handler_helpers.rb +1 -1
  31. data/spec/hybrid_platforms_conductor_test/helpers/provisioner_proxmox_helpers.rb +2 -2
  32. metadata +12 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e0dfe56d0297ba7c9c90dfbf4737cd6b4754b20a36f7dec0962d62a19b5bf2b
4
- data.tar.gz: 3a0363efcb7dc771c9cf41911d3012fae0a764a465e4bd925f241a422d5f11b0
3
+ metadata.gz: 04d2e323c123bfaa35c0fb61f95dcc7a9ade67b19f184eff75c61419e249ac8e
4
+ data.tar.gz: 25afb265a466103db73b891c62ec9a9ee32e5529bf8d9f0df71df379bab5ec7a
5
5
  SHA512:
6
- metadata.gz: e1dae403085c924a043dfee0185b46478eff9c99fa1d69848f5e92b571e4dfb1dc62ab1f80792d3b1928323eecf72a8f0904317b17facf3b92b6a07b82381112
7
- data.tar.gz: 6156b57048a690c5bc6e91ffd61a8276b84b2a2196206a8b77779ee25fe559aa4a4ec21bf741f015736cb3796b9bf4c33d9ec0203b4e4ff7c44a77c1d06fbe83
6
+ metadata.gz: d8112983393928123c7f8f35395506c4ec5e401e84577e3ab1e24380ab0cd92affd90b6be0cb75645314d06cc8350cd8c478e1d1b9e3c600143296bdec73af47
7
+ data.tar.gz: 6177c45b5489b077ee2d3e6d7535d6c07e94715c755c282ea8dc424e54d35ac1603c4be9d2e2d9b4cbfc4f450463c792a3e449f6dbbc5eb6bba4a0161c695c65
@@ -480,6 +480,7 @@ module HybridPlatformsConductor
480
480
  outputs = @actions_executor.execute_actions(
481
481
  Hash[services.map do |node, node_services|
482
482
  image_id = @nodes_handler.get_image_of(node)
483
+ sudo = (ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} ")
483
484
  # Install My_company corporate certificates if present
484
485
  certificate_actions =
485
486
  if @local_environment && ENV['hpc_certificates']
@@ -489,20 +490,20 @@ module HybridPlatformsConductor
489
490
  when 'debian_9', 'debian_10'
490
491
  [
491
492
  {
492
- remote_bash: "#{ssh_user == 'root' ? '' : 'sudo '}apt update && #{ssh_user == 'root' ? '' : 'sudo '}apt install -y ca-certificates"
493
+ remote_bash: "#{sudo}apt update && #{sudo}apt install -y ca-certificates"
493
494
  },
494
495
  {
495
496
  scp: {
496
497
  ENV['hpc_certificates'] => '/usr/local/share/ca-certificates',
497
498
  :sudo => ssh_user != 'root'
498
499
  },
499
- remote_bash: "#{ssh_user == 'root' ? '' : 'sudo '}update-ca-certificates"
500
+ remote_bash: "#{sudo}update-ca-certificates"
500
501
  }
501
502
  ]
502
503
  when 'centos_7'
503
504
  [
504
505
  {
505
- remote_bash: "#{ssh_user == 'root' ? '' : 'sudo '}yum install -y ca-certificates"
506
+ remote_bash: "#{sudo}yum install -y ca-certificates"
506
507
  },
507
508
  {
508
509
  scp: Hash[Dir.glob("#{ENV['hpc_certificates']}/*.crt").map do |cert_file|
@@ -512,8 +513,8 @@ module HybridPlatformsConductor
512
513
  ]
513
514
  end].merge(sudo: ssh_user != 'root'),
514
515
  remote_bash: [
515
- "#{ssh_user == 'root' ? '' : 'sudo '}update-ca-trust enable",
516
- "#{ssh_user == 'root' ? '' : 'sudo '}update-ca-trust extract"
516
+ "#{sudo}update-ca-trust enable",
517
+ "#{sudo}update-ca-trust extract"
517
518
  ]
518
519
  }
519
520
  ]
@@ -532,7 +533,7 @@ module HybridPlatformsConductor
532
533
  # Install the mutex lock and acquire it
533
534
  {
534
535
  scp: { "#{__dir__}/mutex_dir" => '.' },
535
- remote_bash: "while ! #{ssh_user == 'root' ? '' : 'sudo '}./mutex_dir lock /tmp/hybrid_platforms_conductor_deploy_lock \"$(ps -o ppid= -p $$)\"; do echo -e 'Another deployment is running on #{node}. Waiting for it to finish to continue...' ; sleep 5 ; done"
536
+ remote_bash: "while ! #{sudo}./mutex_dir lock /tmp/hybrid_platforms_conductor_deploy_lock \"$(ps -o ppid= -p $$)\"; do echo -e 'Another deployment is running on #{node}. Waiting for it to finish to continue...' ; sleep 5 ; done"
536
537
  }
537
538
  ] +
538
539
  certificate_actions +
@@ -548,7 +549,7 @@ module HybridPlatformsConductor
548
549
  Hash[services.keys.map do |node|
549
550
  [
550
551
  node,
551
- { remote_bash: "#{ssh_user == 'root' ? '' : 'sudo '}./mutex_dir unlock /tmp/hybrid_platforms_conductor_deploy_lock" }
552
+ { remote_bash: "#{ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "}./mutex_dir unlock /tmp/hybrid_platforms_conductor_deploy_lock" }
552
553
  ]
553
554
  end],
554
555
  timeout: 10,
@@ -595,7 +596,7 @@ module HybridPlatformsConductor
595
596
  [
596
597
  node,
597
598
  {
598
- remote_bash: "#{ssh_user == 'root' ? '' : 'sudo '}mkdir -p /var/log/deployments",
599
+ remote_bash: "#{ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "}mkdir -p /var/log/deployments",
599
600
  scp: {
600
601
  log_file => '/var/log/deployments',
601
602
  :sudo => ssh_user != 'root',
@@ -10,13 +10,46 @@ module HybridPlatformsConductor
10
10
  # Connect to node using SSH
11
11
  class Ssh < HybridPlatformsConductor::Connector
12
12
 
13
+ # Exception raise when a node is not connectable using SSH
14
+ class NotConnectableError < RuntimeError
15
+ end
16
+
13
17
  module PlatformsDslSsh
14
18
 
19
+ # List of SSH connection transformations:
20
+ # * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule
21
+ # * *transform* (Proc): Code called to transform SSH connection info:
22
+ # Parameters::
23
+ # * *node* (String): Node for which we transform the SSH connection
24
+ # * *connection* (String or nil): The connection host or IP, or nil if none
25
+ # * *connection_user* (String): The connection user
26
+ # * *gateway* (String or nil): The gateway name, or nil if none
27
+ # * *gateway_user* (String or nil): The gateway user, or nil if none
28
+ # Result::
29
+ # * String: The transformed connection host or IP, or nil if none
30
+ # * String: The transformed connection user
31
+ # * String or nil: The transformed gateway name, or nil if none
32
+ # * String or nil: The transformed gateway user, or nil if none
33
+ # Array< Hash<Symbol, Object> >
34
+ attr_reader :ssh_connection_transforms
35
+
15
36
  # Initialize the DSL
16
37
  def init_ssh
17
38
  # List of gateway configurations, per gateway config name
18
39
  # Hash<Symbol, String>
19
40
  @gateways = {}
41
+ @ssh_connection_transforms = []
42
+ end
43
+
44
+ # Define a transformation of SSH connection.
45
+ #
46
+ # Parameters::
47
+ # * *transform* (Proc): Code to be called to transform an SSH connection (see ssh_connection_transforms signature for details)
48
+ def transform_ssh_connection(&transform)
49
+ @ssh_connection_transforms << {
50
+ nodes_selectors_stack: current_nodes_selectors_stack,
51
+ transform: transform
52
+ }
20
53
  end
21
54
 
22
55
  # Register a new gateway configuration
@@ -200,7 +233,13 @@ module HybridPlatformsConductor
200
233
  # Parameters::
201
234
  # * *bash_cmds* (String): Bash commands to execute
202
235
  def remote_bash(bash_cmds)
203
- ssh_cmd = "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF"
236
+ ssh_cmd =
237
+ if @nodes_handler.get_ssh_session_exec_of(@node) == 'false'
238
+ # When ExecSession is disabled we need to use stdin directly
239
+ "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'EOF'\n#{bash_cmds}\nEOF"
240
+ else
241
+ "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF"
242
+ end
204
243
  # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size.
205
244
  # 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
245
  if bash_cmds.size > MAX_CMD_ARG_LENGTH
@@ -257,25 +296,30 @@ module HybridPlatformsConductor
257
296
  # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
258
297
  # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
259
298
  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 \
299
+ if @nodes_handler.get_ssh_session_exec_of(@node) == 'false'
300
+ # We don't have ExecSession, so don't use ssh, but scp instead.
301
+ run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:#{to}"
302
+ else
303
+ run_cmd <<~EOS
304
+ cd #{File.dirname(from)} && \
305
+ tar \
306
+ --create \
307
+ --gzip \
274
308
  --file - \
275
- --directory #{to} \
276
- --owner root \
277
- \"
278
- EOS
309
+ #{owner.nil? ? '' : "--owner #{owner}"} \
310
+ #{group.nil? ? '' : "--group #{group}"} \
311
+ #{File.basename(from)} | \
312
+ #{ssh_exec} \
313
+ #{ssh_url} \
314
+ \"#{sudo ? "#{@nodes_handler.sudo_on(@node)} " : ''}tar \
315
+ --extract \
316
+ --gunzip \
317
+ --file - \
318
+ --directory #{to} \
319
+ --owner root \
320
+ \"
321
+ EOS
322
+ end
279
323
  end
280
324
 
281
325
  # Get the ssh executable to be used when connecting to the current node
@@ -291,7 +335,7 @@ module HybridPlatformsConductor
291
335
  # Result::
292
336
  # * String: The ssh URL connecting to the current node
293
337
  def ssh_url
294
- "#{@ssh_user}@hpc.#{@node}"
338
+ "hpc.#{@node}"
295
339
  end
296
340
 
297
341
  # Get an SSH configuration content giving access to nodes of the platforms with the current configuration
@@ -314,14 +358,6 @@ module HybridPlatformsConductor
314
358
  # ENDPOINTS #
315
359
  #############
316
360
 
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
361
  EOS
326
362
 
327
363
  # Add each node
@@ -329,17 +365,37 @@ module HybridPlatformsConductor
329
365
  @nodes_handler.prefetch_metadata_of nodes, %i[private_ips hostname host_ip description]
330
366
  nodes.sort.each do |node|
331
367
  # 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"
368
+ connection, connection_user, gateway, gateway_user = connection_info_for(node, no_exception: true)
369
+ if connection.nil?
370
+ config_content << "# #{node} - Not connectable using SSH - #{@nodes_handler.get_description_of(node) || ''}\n"
371
+ else
372
+ config_content << "# #{node} - #{connection} - #{@nodes_handler.get_description_of(node) || ''}\n"
373
+ config_content << "Host #{ssh_aliases_for(node).join(' ')}\n"
374
+ config_content << " Hostname #{connection}\n"
375
+ config_content << " User \"#{connection_user}\"\n" if connection_user != @ssh_user
376
+ config_content << " ProxyCommand #{ssh_exec} -q -W %h:%p #{gateway_user}@#{gateway}\n" unless gateway.nil?
377
+ if @passwords.key?(node)
378
+ config_content << " PreferredAuthentications password\n"
379
+ config_content << " PubkeyAuthentication no\n"
380
+ end
340
381
  end
341
382
  config_content << "\n"
342
383
  end
384
+ # Add global definitions at the end of the SSH config, as they might be overriden by previous ones, and first match wins.
385
+ config_content << <<~EOS
386
+ ###########
387
+ # GLOBALS #
388
+ ###########
389
+
390
+ Host *
391
+ User #{@ssh_user}
392
+ # Default control socket path to be used when multiplexing SSH connections
393
+ ControlPath #{control_master_file('%h', '%p', '%r')}
394
+ #{open_ssh_major_version >= 7 ? 'PubkeyAcceptedKeyTypes +ssh-dss' : ''}
395
+ #{known_hosts_file.nil? ? '' : "UserKnownHostsFile #{known_hosts_file}"}
396
+ #{@ssh_strict_host_key_checking ? '' : 'StrictHostKeyChecking no'}
397
+
398
+ EOS
343
399
  config_content
344
400
  end
345
401
 
@@ -442,34 +498,48 @@ module HybridPlatformsConductor
442
498
  with_lock_on_control_master_for(node) do |current_users, user_id|
443
499
  working_master = false
444
500
  ssh_exec = ssh_exec_for(node)
445
- ssh_url = "#{@ssh_user}@hpc.#{node}"
501
+ ssh_url = "hpc.#{node}"
446
502
  if current_users.empty?
447
503
  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
504
  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."
505
+ if @nodes_handler.get_ssh_session_exec_of(node) == 'false'
506
+ # Here we have to create a ControlMaster using an interactive session, as the SSH server prohibits ExecSession, and so command executions.
507
+ # We'll do that using another terminal spawned in the background.
508
+ Thread.new do
509
+ log_debug "[ ControlMaster - #{ssh_url} ] - Spawn interactive ControlMaster in separate terminal"
510
+ @cmd_runner.run_cmd "xterm -e '#{ssh_exec} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url}'", log_to_stdout: log_debug?
511
+ log_debug "[ ControlMaster - #{ssh_url} ] - Separate interactive ControlMaster closed"
512
+ end
513
+ out 'External ControlMaster has been spawned.'
514
+ out 'Please login into it, keep its session opened and press enter here when done...'
515
+ $stdin.gets
516
+ exit_status = 0
517
+ else
518
+ # Create the control master
519
+ ssh_control_master_start_cmd = "#{ssh_exec}#{@passwords.key?(node) || @auth_password ? '' : ' -o BatchMode=yes'} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url} true"
520
+ idx_try = 0
521
+ loop do
522
+ stderr = nil
523
+ exit_status, _stdout, stderr = @cmd_runner.run_cmd ssh_control_master_start_cmd, log_to_stdout: log_debug?, no_exception: true, timeout: timeout
524
+ if exit_status == 0
525
+ break
526
+ elsif stderr =~ /System is booting up/
527
+ if idx_try == MAX_RETRIES_FOR_BOOT
528
+ if no_exception
529
+ break
530
+ else
531
+ 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."
532
+ end
463
533
  end
534
+ # Wait a bit and try again
535
+ idx_try += 1
536
+ log_debug "[ ControlMaster - #{ssh_url} ] - System is booting up (try ##{idx_try}). Wait #{WAIT_TIME_FOR_BOOT} seconds before trying ControlMaster's creation again."
537
+ sleep WAIT_TIME_FOR_BOOT
538
+ elsif no_exception
539
+ break
540
+ else
541
+ raise ActionsExecutor::ConnectionError, "Error while starting SSH Control Master with #{ssh_control_master_start_cmd}: #{stderr.strip}"
464
542
  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
543
  end
474
544
  end
475
545
  if exit_status == 0
@@ -504,28 +574,33 @@ module HybridPlatformsConductor
504
574
  end
505
575
  end
506
576
  end
577
+ else
578
+ # We have not created any ControlMaster, but still consider the nodes to be ready to connect
579
+ user_locks = Hash[nodes.map { |node| [node, nil]} ]
507
580
  end
508
581
  yield user_locks.keys
509
582
  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."
583
+ if @ssh_use_control_master
584
+ user_locks_mutex.synchronize do
585
+ user_locks.each do |node, user_id|
586
+ with_lock_on_control_master_for(node, user_id: user_id) do |current_users, user_id|
587
+ ssh_url = "hpc.#{node}"
588
+ 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)
589
+ remaining_users = current_users - [user_id]
590
+ if remaining_users.empty?
591
+ # Stop the ControlMaster
592
+ log_debug "[ ControlMaster - #{ssh_url} ] - Stopping ControlMaster..."
593
+ # Dumb verbose ssh! Tricky trick to just silence what is useless.
594
+ # 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.
595
+ @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
596
+ log_debug "[ ControlMaster - #{ssh_url} ] - ControlMaster stopped"
597
+ # Uncomment if you want to test that the connection has been closed
598
+ # @cmd_runner.run_cmd "#{ssh_exec_for(node)} -O check #{ssh_url}", log_to_stdout: log_debug?, expected_code: 255, timeout: timeout
599
+ else
600
+ log_debug "[ ControlMaster - #{ssh_url} ] - Leaving ControlMaster started as #{remaining_users.size} processes/threads are still using it."
601
+ end
602
+ false
527
603
  end
528
- false
529
604
  end
530
605
  end
531
606
  end
@@ -554,8 +629,9 @@ module HybridPlatformsConductor
554
629
  # TODO: Add test case when control file is missing ad when it is stale
555
630
  # Get the list of existing process/thread ids using this control master
556
631
  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)
632
+ ssh_url = "hpc.#{node}"
633
+ connection, connection_user, _gateway, _gateway_user = connection_info_for(node)
634
+ control_path_file = control_master_file(connection, '22', connection_user)
559
635
  if existing_users.empty?
560
636
  # Make sure there is no stale one.
561
637
  if File.exist?(control_path_file)
@@ -587,7 +663,7 @@ module HybridPlatformsConductor
587
663
  # * *port* (String): The port. Can be a string as ssh config uses wildchars.
588
664
  # * *user* (String): The user
589
665
  def control_master_file(host, port, user)
590
- "#{@tmp_dir}/hpc_actions_executor_mux_#{host}_#{port}_#{user}"
666
+ "#{@tmp_dir}/hpc_ssh_mux_#{host}_#{port}_#{user}"
591
667
  end
592
668
 
593
669
  # Provide a bootstrapped ssh executable that includes an SSH config allowing access to nodes.
@@ -619,7 +695,7 @@ module HybridPlatformsConductor
619
695
  nodes.sort.each do |node|
620
696
  host_keys = @nodes_handler.get_host_keys_of(node)
621
697
  if host_keys && !host_keys.empty?
622
- connection, _gateway, _gateway_user = connection_info_for(node)
698
+ connection, _connection_user, _gateway, _gateway_user = connection_info_for(node)
623
699
  host_keys.each do |host_key|
624
700
  file.puts "#{connection} #{host_key}"
625
701
  end
@@ -652,11 +728,13 @@ module HybridPlatformsConductor
652
728
  #
653
729
  # Parameters::
654
730
  # * *node* (String): The node to access
731
+ # * *no_exception* (Boolean): Should we skip exceptions in case of no connection possible? [default: false]
655
732
  # Result::
656
- # * String: The real hostname or IP to be used to connect
733
+ # * String: The real hostname or IP to be used to connect, or nil if none and no_exception is true
734
+ # * String: The real user to be used to connect, or nil if none and no_exception is true
657
735
  # * 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
736
  # * String or nil: The gateway user to be used, or nil if none.
659
- def connection_info_for(node)
737
+ def connection_info_for(node, no_exception: false)
660
738
  connection =
661
739
  if @nodes_handler.get_host_ip_of(node)
662
740
  @nodes_handler.get_host_ip_of(node)
@@ -665,12 +743,18 @@ module HybridPlatformsConductor
665
743
  elsif @nodes_handler.get_hostname_of(node)
666
744
  @nodes_handler.get_hostname_of(node)
667
745
  else
668
- raise "No connection possible to #{node}"
746
+ nil
669
747
  end
748
+ connection_user = @ssh_user
670
749
  gateway = @nodes_handler.get_gateway_of node
671
750
  gateway_user = @nodes_handler.get_gateway_user_of node
672
751
  gateway_user = @ssh_gateway_user if !gateway.nil? && gateway_user.nil?
673
- [connection, gateway, gateway_user]
752
+ # In case we want to transform the connection info, do it here.
753
+ @nodes_handler.select_confs_for_node(node, @config.ssh_connection_transforms).each do |transform_info|
754
+ connection, connection_user, gateway, gateway_user = transform_info[:transform].call(node, connection, connection_user, gateway, gateway_user)
755
+ end
756
+ raise NotConnectableError, "No connection possible to #{node}" if connection.nil? && !no_exception
757
+ [connection, connection_user, gateway, gateway_user]
674
758
  end
675
759
 
676
760
  # Get the possible SSH aliases for a given node.
@@ -173,7 +173,7 @@ module HybridPlatformsConductor
173
173
  # * *local_environment* (Boolean): Are we deploying to a local environment?
174
174
  def package(services:, secrets:, local_environment:)
175
175
  # This method should take all actions to prepare the repository to be deployed on nodes later.
176
- File.write('temporary_secrets_to_be_deployed.json', secrets)
176
+ File.write("#{@repository_path}/temporary_secrets_to_be_deployed.json", secrets.to_json)
177
177
  # Usually it is meant to package the deployment scripts.
178
178
  @cmd_runner.run_cmd "cd #{@repository_path} && ./scripts/package_in_debian_format.sh"
179
179
  end