def mock_cumin(mocked_transports, retcode, retvals=None): """Given a mocked cumin.transports, add the necessary mocks for these tests and set the retcode.""" if retvals is None: retvals = [[("host1", b"output1")]] results = [] for retval in retvals: result = [] for host, message in retval: result.append((NodeSet(host), MsgTreeElem(message, parent=MsgTreeElem()))) results.append(result) mocked_transports.clustershell = clustershell mocked_execute = mock.Mock() mocked_execute.return_value = retcode mocked_get_results = mock.Mock() if results: mocked_get_results.side_effect = results else: mocked_get_results.return_value = iter(()) mocked_transports.clustershell.ClusterShellWorker.execute = mocked_execute mocked_transports.clustershell.ClusterShellWorker.get_results = mocked_get_results mocked_transports.Target = Target
def test_results_to_list_callback_raise(self): """It should raise RemoteError if the callback call raises any exception.""" results = (item for item in [(self.hosts, MsgTreeElem(b"test", parent=MsgTreeElem()))]) with pytest.raises( remote.RemoteError, match=r"Unable to extract data with <lambda> for host\[1-9\] from: test", ): remote.RemoteHosts.results_to_list(results, callback=lambda x: 1 / 0)
def set_mocked_icinga_host_outputs(mocked_icinga_host, outputs): """Setup the mocked icinga_host side effect to return the given outputs, one after another.""" outs = [ MsgTreeElem(output.encode(), parent=MsgTreeElem()) for output in outputs ] mocked_icinga_host.run_sync.side_effect = [ iter([(NodeSet("icinga-host"), out)]) for out in outs ]
def test_check_disabled_ok(self): """It should check that all hosts have Puppet disabled.""" host1 = NodeSet("test1.example.com") host2 = NodeSet("test2.example.com") results = [ (host1, MsgTreeElem(b"1", parent=MsgTreeElem())), (host2, MsgTreeElem(b"1", parent=MsgTreeElem())), ] self.mocked_remote_hosts.run_sync.return_value = iter(results) self.puppet_hosts.check_disabled()
def test_get_certificate_metadata_raises(self, json_output, exception_message): """It should raise PuppetMasterError if the Puppet CA returns multiple certificates metadata.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(json_output, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) with pytest.raises(puppet.PuppetMasterError, match=exception_message): self.puppet_master.get_certificate_metadata("test.example.com")
def _get_mock_run_sync( return_value: Optional[bytes] = None, side_effect: Optional[List[bytes]] = None) -> mock.MagicMock: if side_effect is not None: return mock.MagicMock(side_effect=[(iter([ ("test0.local.host", MsgTreeElem(return_value, parent=MsgTreeElem())), ])) for return_value in side_effect]) return mock.MagicMock( return_value=iter([("test0.local.host", MsgTreeElem(return_value, parent=MsgTreeElem()))]))
def test_wait_for_csr_fail(self): """It should raise PuppetMasterError if the certificate is in a wrong state.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_SIGNED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) with pytest.raises( puppet.PuppetMasterError, match="Expected certificate in requested state, got: signed", ): self.puppet_master.wait_for_csr("test.example.com")
def test_sign_wrong_state(self): """It should raise PuppetMasterError if the certificate is not in the requested state.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_SIGNED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) with pytest.raises( puppet.PuppetMasterError, match= "Certificate for test.example.com not in requested state, got: signed", ): self.puppet_master.sign("test.example.com", "00:AA")
def test_check_enabled_raise(self): """It should raise PuppetHostsCheckError if Puppet is disabled on some hosts.""" host1 = NodeSet("test1.example.com") host2 = NodeSet("test2.example.com") results = [ (host1, MsgTreeElem(b"0", parent=MsgTreeElem())), (host2, MsgTreeElem(b"1", parent=MsgTreeElem())), ] self.mocked_remote_hosts.run_sync.return_value = iter(results) with pytest.raises( puppet.PuppetHostsCheckError, match="Puppet is not enabled on those hosts: test2.example.com", ): self.puppet_hosts.check_enabled()
def test_sign_wrong_fingerprint(self): """It should raise PuppetMasterError if the fingerprint doesn't match the one in the CSR.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_REQUESTED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) with pytest.raises( puppet.PuppetMasterError, match= "CSR fingerprint 00:AA for test.example.com does not match provided fingerprint FF:FF", ): self.puppet_master.sign("test.example.com", "FF:FF")
def test_regenerate_certificate_raise(self): """It should raise PuppetHostsError if unable to find any of the fingerprint.""" message = PUPPET_GENERATE_CERTIFICATE_SUCCESS.decode().replace( ": 00:FF", "").encode() results = [(NodeSet("test.example.com"), MsgTreeElem(message, parent=MsgTreeElem()))] self.mocked_remote_hosts.run_sync.side_effect = [ iter(()), iter(results) ] with pytest.raises( puppet.PuppetHostsError, match="Unable to find CSR fingerprints for all hosts", ): self.puppet_hosts.regenerate_certificate()
def test_verify_ok(self): """It should verify that the host has a signed certificate in the Puppet CA.""" json_output = b'{"host":"test.example.com","valid":true}' results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(json_output, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) self.puppet_master.verify("test.example.com") self.mocked_master_host.run_sync.assert_called_once_with( "puppet ca --disable_warnings deprecations --render-as json verify test.example.com", is_safe=True, print_output=False, print_progress_bars=False, )
def test_verify_invalid_json(self): """It should raise PuppetMasterError if there output of the executed command is invalid JSON.""" json_output = b'{"host":"test.example.com",,}' results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(json_output, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) with pytest.raises( puppet.PuppetMasterError, match= ("Unable to parse Puppet master response for command " '"puppet ca --disable_warnings deprecations --render-as json verify test.example.com"' ), ): self.puppet_master.verify("test.example.com")
def test_instance_add_ok(self, requests_mock): """It should issue the remove command on the master host.""" self._set_requests_mock_for_instance(requests_mock) requests_mock.get(self.base_url + "/info", text=self.info) instance = self.ganeti.instance(self.instance) results = [( NodeSet("ganeti-master.example.com"), MsgTreeElem(b"creation logs", parent=MsgTreeElem()), )] self.remote.query.return_value.run_sync.return_value = iter(results) instance.add(row="A", vcpus=2, memory=3, disk=4, link="private") self.remote.query.return_value.run_sync.assert_called_once_with( "gnt-instance add -t drbd -I hail --net 0:link=private --hypervisor-parameters=kvm:boot_order=network " "-o debootstrap+default --no-install -g row_A -B vcpus=2,memory=3g --disk 0:size=4g test.example.com" )
def test_wait_for_csr_timeout(self, mocked_sleep): """It should raise PuppetMasterCheckError if the certificate request doesn't appear.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(b"[]", parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.side_effect = [ iter(results) for _ in range(10) ] with pytest.raises( puppet.PuppetMasterCheckError, match="No certificate found for hostname: test.example.com", ): self.puppet_master.wait_for_csr("test.example.com") assert mocked_sleep.called
def test_regenerate_certificate_errors(self): """It should raise PuppetHostsError and print the Puppet errors if unable to find any of the fingerprint.""" results = [( NodeSet("test.example.com"), MsgTreeElem(PUPPET_GENERATE_CERTIFICATE_FAILED, parent=MsgTreeElem()), )] self.mocked_remote_hosts.run_sync.side_effect = [ iter(()), iter(results) ] with pytest.raises( puppet.PuppetHostsError, match= ("test.example.com: Error: Could not request certificate: The certificate retrieved " "from the master does not match the agent's private key."), ): self.puppet_hosts.regenerate_certificate()
def test_wait_for_csr_already_ok(self): """It should return immediately if the certificate is already requested.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_REQUESTED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) self.puppet_master.wait_for_csr("test.example.com") self.mocked_master_host.run_sync.assert_called_once_with( "puppet ca --disable_warnings deprecations --render-as json " r'list --all --subject "^test\.example\.com$"', is_safe=True, print_output=False, print_progress_bars=False, )
def test_get_ca_servers_handles_multiple_lines_in_command_output(self): """Test test get ca servers handles multiple results.""" self.mocked_remote_hosts.run_sync.return_value = [ ( NodeSet("test0.example.com"), MsgTreeElem( b"test0.puppetmast.er", parent=MsgTreeElem(b"some message that should be ignored", parent=MsgTreeElem()), ), ), ] result = self.puppet_hosts.get_ca_servers() self.mocked_remote_hosts.run_sync.assert_called_once() assert "test0.example.com" in result assert result["test0.example.com"] == "test0.puppetmast.er"
def test_get_ca_servers_explodes_multihost_nodeset_into_single_hosts(self): """Test that get ca servers explodes multihost nodeset into single hosts.""" expected_puppetmaster = "dummy.puppetmast.er" self.mocked_remote_hosts.run_sync.return_value = [ ( NodeSet("test[0-1].example.com"), MsgTreeElem(expected_puppetmaster.encode(), parent=MsgTreeElem()), ), ] result = self.puppet_hosts.get_ca_servers() self.mocked_remote_hosts.run_sync.assert_called_once() assert "test0.example.com" in result assert result["test0.example.com"] == expected_puppetmaster assert "test1.example.com" in result assert result["test1.example.com"] == expected_puppetmaster
def test_sign_alt_dns(self): """It should pass the --allow-dns-alt-names option while signing the certificate.""" requested_results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_REQUESTED, parent=MsgTreeElem()), )] signed_results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_SIGNED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.side_effect = [ iter(requested_results), iter(()), iter(signed_results), ] self.puppet_master.sign("test.example.com", "00:AA", allow_alt_names=True) self.mocked_master_host.run_sync.assert_has_calls([ mock.call( "puppet ca --disable_warnings deprecations --render-as json " r'list --all --subject "^test\.example\.com$"', is_safe=True, print_output=False, print_progress_bars=False, ), mock.call( "puppet cert --disable_warnings deprecations sign --allow-dns-alt-names test.example.com", print_output=False, print_progress_bars=False, ), mock.call( "puppet ca --disable_warnings deprecations --render-as json " r'list --all --subject "^test\.example\.com$"', is_safe=True, print_output=False, print_progress_bars=False, ), ])
def test_get_ca_servers_handles_multiple_results(self): """Test test get ca servers handles multiple results.""" self.mocked_remote_hosts.run_sync.return_value = [ ( NodeSet("test0.example.com"), MsgTreeElem(b"test0.puppetmast.er", parent=MsgTreeElem()), ), ( NodeSet("test1.example.com"), MsgTreeElem(b"test1.puppetmast.er", parent=MsgTreeElem()), ), ] result = self.puppet_hosts.get_ca_servers() self.mocked_remote_hosts.run_sync.assert_called_once() assert "test0.example.com" in result assert result["test0.example.com"] == "test0.puppetmast.er" assert "test1.example.com" in result assert result["test1.example.com"] == "test1.puppetmast.er"
def test_wait_since_timeout(self, mocked_sleep): """It should raise PuppetHostsCheckError if the successful Puppet run is too old within the timeout.""" last_run = datetime.utcnow() # timestamp() consider naive datetime objects as local time. last_run_string = str( int(last_run.replace(tzinfo=timezone.utc).timestamp())) start = last_run + timedelta(seconds=1) nodes = NodeSet("test.example.com") results = [(nodes, MsgTreeElem(last_run_string.encode(), parent=MsgTreeElem()))] self.mocked_remote_hosts.run_sync.side_effect = [results] * 60 self.mocked_remote_hosts.hosts = nodes with pytest.raises(puppet.PuppetHostsCheckError, match="Successful Puppet run too old"): self.puppet_hosts.wait_since(start) assert mocked_sleep.called
def test_get_certificate_metadata_ok(self): """It should return the metadata of the certificate for the host in the Puppet CA.""" results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_SIGNED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) metadata = self.puppet_master.get_certificate_metadata( "test.example.com") assert metadata == json.loads( PUPPET_CA_CERT_METADATA_SIGNED.decode())[0] self.mocked_master_host.run_sync.assert_called_once_with( "puppet ca --disable_warnings deprecations --render-as json list " r'--all --subject "^test\.example\.com$"', is_safe=True, print_output=False, print_progress_bars=False, )
def test_regenerate_certificate_ok(self): """It should delete and regenerate the Puppet certificate.""" results = [( NodeSet("test.example.com"), MsgTreeElem(PUPPET_GENERATE_CERTIFICATE_SUCCESS, parent=MsgTreeElem()), )] self.mocked_remote_hosts.run_sync.side_effect = [ iter(()), iter(results) ] fingerprints = self.puppet_hosts.regenerate_certificate() self.mocked_remote_hosts.run_sync.assert_has_calls([ mock.call("rm -rfv /var/lib/puppet/ssl"), mock.call(puppet.Command("puppet agent --test --color=false", ok_codes=[]), print_output=False), ]) assert fingerprints == {"test.example.com": "00:FF"}
def test_verify_raise(self): """It should raise PuppetMasterError if the certificate is not valid on the Puppet CA.""" json_output = b'{"host":"test.example.com","valid":false,"error":"Error message"}' results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(json_output, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.return_value = iter(results) with pytest.raises( puppet.PuppetMasterError, match="Invalid certificate for test.example.com: Error message", ): self.puppet_master.verify("test.example.com") self.mocked_master_host.run_sync.assert_called_once_with( "puppet ca --disable_warnings deprecations --render-as json verify test.example.com", is_safe=True, print_output=False, print_progress_bars=False, )
def test_sign_fail(self): """It should raise PuppetMasterError if the sign operation fails.""" requested_results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_REQUESTED, parent=MsgTreeElem()), )] sign_results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(b"sign error", parent=MsgTreeElem()), )] signed_results = [( NodeSet("puppetmaster.example.com"), MsgTreeElem(PUPPET_CA_CERT_METADATA_REQUESTED, parent=MsgTreeElem()), )] self.mocked_master_host.run_sync.side_effect = [ iter(requested_results), iter(sign_results), iter(signed_results), ] with pytest.raises( puppet.PuppetMasterError, match= "Expected certificate for test.example.com to be signed, got: requested", ): self.puppet_master.sign("test.example.com", "00:AA")
def test_wait_ok(self): """It should return immediately if there is already successful Puppet run since now.""" last_run = datetime.utcnow() + timedelta(seconds=1) # timestamp() consider naive datetime objects as local time. last_run_string = str( int(last_run.replace(tzinfo=timezone.utc).timestamp())) nodes = NodeSet("test.example.com") results = [(nodes, MsgTreeElem(last_run_string.encode(), parent=MsgTreeElem()))] self.mocked_remote_hosts.run_sync.return_value = iter(results) self.mocked_remote_hosts.hosts = nodes self.puppet_hosts.wait() self.mocked_remote_hosts.run_sync.assert_called_once_with( ("source /usr/local/share/bash/puppet-common.sh && last_run_success && awk /last_run/'{ print $2 }' " '"${PUPPET_SUMMARY}"'), is_safe=True, print_output=False, print_progress_bars=False, )
def test_wait_since_missing_host(self, mocked_sleep): """It should raise PuppetHostsCheckError unable to get the result from some host.""" last_run = datetime.utcnow() # timestamp() consider naive datetime objects as local time. last_run_string = str( int(last_run.replace(tzinfo=timezone.utc).timestamp())) start = last_run - timedelta(seconds=1) nodes = NodeSet("test[1-2].example.com") results = [( NodeSet("test1.example.com"), MsgTreeElem(last_run_string.encode(), parent=MsgTreeElem()), )] self.mocked_remote_hosts.run_sync.side_effect = [results] * 60 self.mocked_remote_hosts.hosts = nodes with pytest.raises( puppet.PuppetHostsCheckError, match= "Unable to get successful Puppet run from: test2.example.com", ): self.puppet_hosts.wait_since(start) assert mocked_sleep.called
def set_mocked_icinga_host_output(mocked_icinga_host, output, n_hosts=1): """Setup the mocked icinga_host return value with the given output.""" out = MsgTreeElem(output.encode(), parent=MsgTreeElem()) mocked_icinga_host.run_sync.return_value = iter( n_hosts * [(NodeSet("icinga-host"), out)])
def test_results_to_list_no_callback(self): """It should return the output string without any conversion.""" results = (item for item in [(self.hosts, MsgTreeElem(b"test", parent=MsgTreeElem()))]) extracted = remote.RemoteHosts.results_to_list(results) assert sorted(extracted) == sorted([(self.hosts, "test")])