1# This file is part of Buildbot. Buildbot is free software: you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Copyright Buildbot Team Members 15 16 17import os 18from unittest import mock 19 20from twisted.internet import defer 21from twisted.trial import unittest 22 23from buildbot.machine.generic import LocalWakeAction 24from buildbot.machine.generic import LocalWOLAction 25from buildbot.machine.generic import RemoteSshSuspendAction 26from buildbot.machine.generic import RemoteSshWakeAction 27from buildbot.machine.generic import RemoteSshWOLAction 28from buildbot.test.fake.private_tempdir import MockPrivateTemporaryDirectory 29from buildbot.test.util import config 30from buildbot.test.util.misc import TestReactorMixin 31from buildbot.test.util.runprocess import ExpectMaster 32from buildbot.test.util.runprocess import MasterRunProcessMixin 33 34 35class FakeManager: 36 37 def __init__(self, reactor, basedir=None): 38 self.master = mock.Mock() 39 self.master.basedir = basedir 40 self.master.reactor = reactor 41 42 def renderSecrets(self, args): 43 return defer.succeed(args) 44 45 46class TestActions(MasterRunProcessMixin, config.ConfigErrorsMixin, TestReactorMixin, 47 unittest.TestCase): 48 def setUp(self): 49 self.setUpTestReactor() 50 self.setup_master_run_process() 51 52 def tearDown(self): 53 pass 54 55 @defer.inlineCallbacks 56 def test_local_wake_action(self): 57 self.expect_commands( 58 ExpectMaster(['cmd', 'arg1', 'arg2']) 59 .exit(1), 60 61 ExpectMaster(['cmd', 'arg1', 'arg2']) 62 .exit(0), 63 ) 64 65 manager = FakeManager(self.reactor) 66 action = LocalWakeAction(['cmd', 'arg1', 'arg2']) 67 self.assertFalse((yield action.perform(manager))) 68 self.assertTrue((yield action.perform(manager))) 69 self.assert_all_commands_ran() 70 71 def test_local_wake_action_command_not_list(self): 72 with self.assertRaisesConfigError('command parameter must be a list'): 73 LocalWakeAction('not-list') 74 75 @defer.inlineCallbacks 76 def test_local_wol_action(self): 77 self.expect_commands( 78 ExpectMaster(['wol', '00:11:22:33:44:55']) 79 .exit(1), 80 81 ExpectMaster(['wakeonlan', '00:11:22:33:44:55']) 82 .exit(0), 83 ) 84 85 manager = FakeManager(self.reactor) 86 action = LocalWOLAction('00:11:22:33:44:55', wolBin='wol') 87 self.assertFalse((yield action.perform(manager))) 88 89 action = LocalWOLAction('00:11:22:33:44:55') 90 self.assertTrue((yield action.perform(manager))) 91 self.assert_all_commands_ran() 92 93 @mock.patch('buildbot.util.private_tempdir.PrivateTemporaryDirectory', 94 new_callable=MockPrivateTemporaryDirectory) 95 @mock.patch('buildbot.util.misc.writeLocalFile') 96 @defer.inlineCallbacks 97 def test_remote_ssh_wake_action_no_keys(self, write_local_file_mock, 98 temp_dir_mock): 99 self.expect_commands( 100 ExpectMaster(['ssh', '-o', 'BatchMode=yes', 'remote_host', 'remotebin', 'arg1']) 101 .exit(1), 102 103 ExpectMaster(['ssh', '-o', 'BatchMode=yes', 'remote_host', 'remotebin', 'arg1']) 104 .exit(0), 105 ) 106 107 manager = FakeManager(self.reactor) 108 action = RemoteSshWakeAction('remote_host', ['remotebin', 'arg1']) 109 self.assertFalse((yield action.perform(manager))) 110 self.assertTrue((yield action.perform(manager))) 111 self.assert_all_commands_ran() 112 113 self.assertEqual(temp_dir_mock.dirs, []) 114 write_local_file_mock.assert_not_called() 115 116 @mock.patch('buildbot.util.private_tempdir.PrivateTemporaryDirectory', 117 new_callable=MockPrivateTemporaryDirectory) 118 @mock.patch('buildbot.util.misc.writeLocalFile') 119 @defer.inlineCallbacks 120 def test_remote_ssh_wake_action_with_keys(self, write_local_file_mock, 121 temp_dir_mock): 122 temp_dir_path = os.path.join('path-to-master', 'ssh-@@@') 123 ssh_key_path = os.path.join(temp_dir_path, 'ssh-key') 124 ssh_known_hosts_path = os.path.join(temp_dir_path, 'ssh-known-hosts') 125 126 self.expect_commands( 127 ExpectMaster(['ssh', '-o', 'BatchMode=yes', '-i', ssh_key_path, 128 '-o', 'UserKnownHostsFile={0}'.format(ssh_known_hosts_path), 129 'remote_host', 'remotebin', 'arg1']) 130 .exit(0), 131 ) 132 133 manager = FakeManager(self.reactor, 'path-to-master') 134 action = RemoteSshWakeAction('remote_host', ['remotebin', 'arg1'], 135 sshKey='ssh_key', 136 sshHostKey='ssh_host_key') 137 self.assertTrue((yield action.perform(manager))) 138 139 self.assert_all_commands_ran() 140 141 self.assertEqual(temp_dir_mock.dirs, 142 [(temp_dir_path, 0o700)]) 143 144 self.assertSequenceEqual(write_local_file_mock.call_args_list, [ 145 mock.call(ssh_key_path, 'ssh_key', mode=0o400), 146 mock.call(ssh_known_hosts_path, '* ssh_host_key'), 147 ]) 148 149 def test_remote_ssh_wake_action_sshBin_not_str(self): 150 with self.assertRaisesConfigError('sshBin parameter must be a string'): 151 RemoteSshWakeAction('host', ['cmd'], sshBin=123) 152 153 def test_remote_ssh_wake_action_host_not_str(self): 154 with self.assertRaisesConfigError('host parameter must be a string'): 155 RemoteSshWakeAction(123, ['cmd']) 156 157 def test_remote_ssh_wake_action_command_not_list(self): 158 with self.assertRaisesConfigError( 159 'remoteCommand parameter must be a list'): 160 RemoteSshWakeAction('host', 'cmd') 161 162 @mock.patch('buildbot.util.private_tempdir.PrivateTemporaryDirectory', 163 new_callable=MockPrivateTemporaryDirectory) 164 @mock.patch('buildbot.util.misc.writeLocalFile') 165 @defer.inlineCallbacks 166 def test_remote_ssh_wol_action_no_keys(self, write_local_file_mock, 167 temp_dir_mock): 168 self.expect_commands( 169 ExpectMaster(['ssh', '-o', 'BatchMode=yes', 'remote_host', 'wakeonlan', 170 '00:11:22:33:44:55']) 171 .exit(0), 172 173 ExpectMaster(['ssh', '-o', 'BatchMode=yes', 'remote_host', 'wolbin', 174 '00:11:22:33:44:55']) 175 .exit(0), 176 ) 177 178 manager = FakeManager(self.reactor) 179 action = RemoteSshWOLAction('remote_host', '00:11:22:33:44:55') 180 self.assertTrue((yield action.perform(manager))) 181 182 action = RemoteSshWOLAction('remote_host', '00:11:22:33:44:55', 183 wolBin='wolbin') 184 self.assertTrue((yield action.perform(manager))) 185 self.assert_all_commands_ran() 186 187 self.assertEqual(temp_dir_mock.dirs, []) 188 write_local_file_mock.assert_not_called() 189 190 @mock.patch('buildbot.util.private_tempdir.PrivateTemporaryDirectory', 191 new_callable=MockPrivateTemporaryDirectory) 192 @mock.patch('buildbot.util.misc.writeLocalFile') 193 @defer.inlineCallbacks 194 def test_remote_ssh_suspend_action_no_keys(self, write_local_file_mock, 195 temp_dir_mock): 196 self.expect_commands( 197 ExpectMaster(['ssh', '-o', 'BatchMode=yes', 'remote_host', 'systemctl', 'suspend']) 198 .exit(0), 199 200 ExpectMaster(['ssh', '-o', 'BatchMode=yes', 'remote_host', 'dosuspend', 'arg1']) 201 .exit(0), 202 ) 203 204 manager = FakeManager(self.reactor) 205 action = RemoteSshSuspendAction('remote_host') 206 self.assertTrue((yield action.perform(manager))) 207 208 action = RemoteSshSuspendAction('remote_host', 209 remoteCommand=['dosuspend', 'arg1']) 210 self.assertTrue((yield action.perform(manager))) 211 self.assert_all_commands_ran() 212 213 self.assertEqual(temp_dir_mock.dirs, []) 214 write_local_file_mock.assert_not_called() 215