Class: HybridPlatformsConductor::HpcPlugins::Test::CheckDeployAndIdempotence

Inherits:
TestByService show all
Defined in:
lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb

Overview

Test that combines the following tests in just 1 test to avoid spawning several Docker containers:

  • check_from_scratch

  • deploy_from_scratch

  • deploy_removes_root_access

  • idempotence

Especially useful if your tests run in an environment having limited Docker resources. This tests uses the testadmin user access once deployed. Don’t forget to add the testadmin private key in your SSH agent if you run this test locally.

Constant Summary

Constants included from LoggerHelpers

LoggerHelpers::LEVELS_MODIFIERS, LoggerHelpers::LEVELS_TO_STDERR

Instance Attribute Summary

Attributes inherited from Test

#errors, #expected_failure, #name, #node, #platform

Instance Method Summary collapse

Methods inherited from TestByService

only_on_nodes

Methods inherited from Test

#assert_equal, #assert_match, #error, #executed, #executed?, #initialize, only_on_nodes, only_on_platforms, #to_s

Methods inherited from Plugin

extend_config_dsl_with, #initialize, valid?

Methods included from LoggerHelpers

#err, #init_loggers, #log_component=, #log_debug?, #log_level=, #out, #section, #set_loggers_format, #stderr_device, #stderr_device=, #stderr_displayed?, #stdout_device, #stdout_device=, #stdout_displayed?, #stdouts_to_s, #with_progress_bar

Constructor Details

This class inherits a constructor from HybridPlatformsConductor::Test

Instance Method Details

#test_for_nodeObject

Check my_test_plugin.rb.sample documentation for signature details.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb', line 24

def test_for_node
  @deployer.with_test_provisioned_instance(@config.tests_provisioner_id, @node, environment: 'check_deploy_and_idempotence', reuse_instance: log_debug?) do |deployer, instance|
    # Check that we can connect with root
    ssh_ok = false
    begin
      Net::SSH.start(instance.ip, 'root', password: 'root_pwd', auth_methods: ['password'], verify_host_key: :never) do |ssh|
        ssh_ok = ssh.exec!('echo Works').strip == 'Works'
      end
    rescue
    end
    assert_equal ssh_ok, true, 'Root does not have access from the empty image'

    if ssh_ok

      # ===== Check from scratch
      deployer.use_why_run = true
      exit_status, _stdout, _stderr = deployer.deploy_on(@node)[@node]
      assert_equal exit_status, 0, "Check-node from scratch returned error code #{exit_status}", log_debug? ? nil : deployer.stdouts_to_s
      # Even if the check has failed, we try to deploy it

      # ===== Deploy from scratch
      deployer.use_why_run = false
      deployer.nbr_retries_on_error = 3
      exit_status, _stdout, _stderr = deployer.deploy_on(@node)[@node]
      assert_equal exit_status, 0, "Deploy from scratch returned error code #{exit_status}", log_debug? ? nil : deployer.stdouts_to_s
      if exit_status == 0
        # As it's possible sshd has to be restarted because of a change in its conf, restart the container.
        # Otherwise you'll get the following error upon reconnection:
        #   System is booting up. See pam_nologin(8)
        #   Authentication failed.
        instance.stop
        instance.with_running_instance(port: 22) do

          unless @nodes_handler.get_root_access_allowed_of(@node)
            # ===== Deploy removes root access
            # Check that we can't connect with root
            ssh_ok = false
            begin
              Net::SSH.start(instance.ip, 'root', password: 'root_pwd', auth_methods: ['password'], verify_host_key: :never) do |ssh|
                ssh_ok = ssh.exec!('echo Works').strip == 'Works'
              end
            rescue
            end
            assert_equal ssh_ok, false, 'Root can still connect on the image after deployment'
            # Even if we can connect using root, run the idempotence test
          end

          # ===== Idempotence
          unless ssh_ok
            # Now that the node has been deployed, use the a_testadmin user for the check-node (as root has no more access)
            deployer.instance_variable_get(:@actions_executor).connector(:ssh).ssh_user = 'a_testadmin'
            deployer.instance_variable_get(:@actions_executor).connector(:ssh).passwords.delete(@node)
          end
          deployer.use_why_run = true
          deployer.nbr_retries_on_error = 0
          # For the idempotence testing activate log debugs, so that in case of failures we have full details
          deployer.log_level = :debug
          exit_status, stdout, stderr = deployer.deploy_on(@node)[@node]
          assert_equal exit_status, 0, "Check-node after deployment returned error code #{exit_status}", log_debug? ? nil : deployer.stdouts_to_s
          # Check that the output of the check-node returns no changes.
          ignored_tasks = (
            @nodes_handler.select_confs_for_node(@node, @config.ignored_idempotence_tasks) +
              @nodes_handler.select_confs_for_node(@node, @config.ignored_divergent_tasks)
          ).inject({}) do |merged_ignored_tasks, conf|
            merged_ignored_tasks.merge(conf[:ignored_tasks])
          end
          @deployer.parse_deploy_output(@node, stdout, stderr).each do |task_info|
            if task_info[:status] == :changed
              if ignored_tasks.key?(task_info[:name])
                # It was expected that this task is not idempotent
                log_debug "Task #{task_info[:name]} was expected to not be idempotent. Reason: #{ignored_tasks[task_info[:name]]}"
              else
                extra_details = task_info.slice(*(task_info.keys - %i[name status diffs]))
                error_details = []
                error_details << "----- Changes:\n#{task_info[:diffs].strip}\n-----" if task_info[:diffs]
                error_details << "----- Additional details:\n#{JSON.pretty_generate(extra_details)}\n-----" unless extra_details.empty?
                error "Task #{task_info[:name]} is not idempotent", error_details.empty? ? nil : error_details.join("\n")
              end
            end
          end

        end
      end
    end
  end
end