blob: c849231d7ffba167af3e3e06ad4b074385417086 [file] [log] [blame]
Matthew Treinish9e26ca82016-02-23 11:43:20 -05001# Copyright 2014 OpenStack Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Matthew Treinish9e26ca82016-02-23 11:43:20 -050015import socket
Matthew Treinish9e26ca82016-02-23 11:43:20 -050016
17import mock
18import six
junboli68cb44d2017-08-09 14:55:37 +080019from six import StringIO
Matthew Treinish9e26ca82016-02-23 11:43:20 -050020import testtools
21
22from tempest.lib.common import ssh
23from tempest.lib import exceptions
Matthew Treinishffad78a2016-04-16 14:39:52 -040024from tempest.tests import base
Jordan Pittier0e53b612016-03-03 14:23:17 +010025import tempest.tests.utils as utils
Matthew Treinish9e26ca82016-02-23 11:43:20 -050026
27
28class TestSshClient(base.TestCase):
29
30 SELECT_POLLIN = 1
31
32 @mock.patch('paramiko.RSAKey.from_private_key')
33 @mock.patch('six.StringIO')
34 def test_pkey_calls_paramiko_RSAKey(self, cs_mock, rsa_mock):
35 cs_mock.return_value = mock.sentinel.csio
36 pkey = 'mykey'
37 ssh.Client('localhost', 'root', pkey=pkey)
38 rsa_mock.assert_called_once_with(mock.sentinel.csio)
39 cs_mock.assert_called_once_with('mykey')
40 rsa_mock.reset_mock()
41 cs_mock.reset_mock()
42 pkey = mock.sentinel.pkey
43 # Shouldn't call out to load a file from RSAKey, since
44 # a sentinel isn't a basestring...
45 ssh.Client('localhost', 'root', pkey=pkey)
46 self.assertEqual(0, rsa_mock.call_count)
47 self.assertEqual(0, cs_mock.call_count)
48
49 def _set_ssh_connection_mocks(self):
50 client_mock = mock.MagicMock()
51 client_mock.connect.return_value = True
52 return (self.patch('paramiko.SSHClient'),
53 self.patch('paramiko.AutoAddPolicy'),
54 client_mock)
55
56 def test_get_ssh_connection(self):
57 c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
58 s_mock = self.patch('time.sleep')
59
60 c_mock.return_value = client_mock
61 aa_mock.return_value = mock.sentinel.aa
62
63 # Test normal case for successful connection on first try
64 client = ssh.Client('localhost', 'root', timeout=2)
65 client._get_ssh_connection(sleep=1)
66
67 aa_mock.assert_called_once_with()
68 client_mock.set_missing_host_key_policy.assert_called_once_with(
69 mock.sentinel.aa)
70 expected_connect = [mock.call(
71 'localhost',
Masayuki Igawa55b4cfd2016-08-30 10:29:46 +090072 port=22,
Matthew Treinish9e26ca82016-02-23 11:43:20 -050073 username='root',
74 pkey=None,
75 key_filename=None,
76 look_for_keys=False,
77 timeout=10.0,
YAMAMOTO Takashiae015d12017-01-25 11:36:23 +090078 password=None,
79 sock=None
80 )]
81 self.assertEqual(expected_connect, client_mock.connect.mock_calls)
82 self.assertEqual(0, s_mock.call_count)
83
84 def test_get_ssh_connection_over_ssh(self):
85 c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
86 proxy_client_mock = mock.MagicMock()
87 proxy_client_mock.connect.return_value = True
88 s_mock = self.patch('time.sleep')
89
90 c_mock.side_effect = [client_mock, proxy_client_mock]
91 aa_mock.return_value = mock.sentinel.aa
92
93 proxy_client = ssh.Client('proxy-host', 'proxy-user', timeout=2)
94 client = ssh.Client('localhost', 'root', timeout=2,
95 proxy_client=proxy_client)
96 client._get_ssh_connection(sleep=1)
97
98 aa_mock.assert_has_calls([mock.call(), mock.call()])
99 proxy_client_mock.set_missing_host_key_policy.assert_called_once_with(
100 mock.sentinel.aa)
101 proxy_expected_connect = [mock.call(
102 'proxy-host',
103 port=22,
104 username='proxy-user',
105 pkey=None,
106 key_filename=None,
107 look_for_keys=False,
108 timeout=10.0,
109 password=None,
110 sock=None
111 )]
112 self.assertEqual(proxy_expected_connect,
113 proxy_client_mock.connect.mock_calls)
114 client_mock.set_missing_host_key_policy.assert_called_once_with(
115 mock.sentinel.aa)
116 expected_connect = [mock.call(
117 'localhost',
118 port=22,
119 username='root',
120 pkey=None,
121 key_filename=None,
122 look_for_keys=False,
123 timeout=10.0,
124 password=None,
125 sock=proxy_client_mock.get_transport().open_session()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500126 )]
127 self.assertEqual(expected_connect, client_mock.connect.mock_calls)
128 self.assertEqual(0, s_mock.call_count)
129
Jordan Pittier0e53b612016-03-03 14:23:17 +0100130 @mock.patch('time.sleep')
131 def test_get_ssh_connection_two_attemps(self, sleep_mock):
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500132 c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
133
134 c_mock.return_value = client_mock
135 client_mock.connect.side_effect = [
136 socket.error,
137 mock.MagicMock()
138 ]
139
140 client = ssh.Client('localhost', 'root', timeout=1)
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500141 client._get_ssh_connection(sleep=1)
Jordan Pittier0e53b612016-03-03 14:23:17 +0100142 # We slept 2 seconds: because sleep is "1" and backoff is "1" too
143 sleep_mock.assert_called_once_with(2)
144 self.assertEqual(2, client_mock.connect.call_count)
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500145
146 def test_get_ssh_connection_timeout(self):
147 c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
148
Jordan Pittier0e53b612016-03-03 14:23:17 +0100149 timeout = 2
150 time_mock = self.patch('time.time')
151 time_mock.side_effect = utils.generate_timeout_series(timeout + 1)
152
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500153 c_mock.return_value = client_mock
154 client_mock.connect.side_effect = [
155 socket.error,
156 socket.error,
157 socket.error,
158 ]
159
Jordan Pittier0e53b612016-03-03 14:23:17 +0100160 client = ssh.Client('localhost', 'root', timeout=timeout)
161 # We need to mock LOG here because LOG.info() calls time.time()
162 # in order to preprend a timestamp.
163 with mock.patch.object(ssh, 'LOG'):
164 self.assertRaises(exceptions.SSHTimeout,
165 client._get_ssh_connection)
166
167 # time.time() should be called twice, first to start the timer
168 # and then to compute the timedelta
169 self.assertEqual(2, time_mock.call_count)
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500170
171 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
172 def test_timeout_in_exec_command(self):
Gregory Thiemonge690bae22019-11-20 11:33:56 +0100173 chan_mock, poll_mock, _, _ = (
174 self._set_mocks_for_select([0, 0, 0], True))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500175
176 # Test for a timeout condition immediately raised
177 client = ssh.Client('localhost', 'root', timeout=2)
178 with testtools.ExpectedException(exceptions.TimeoutException):
179 client.exec_command("test")
180
181 chan_mock.fileno.assert_called_once_with()
182 chan_mock.exec_command.assert_called_once_with("test")
183 chan_mock.shutdown_write.assert_called_once_with()
184
185 poll_mock.register.assert_called_once_with(
186 chan_mock, self.SELECT_POLLIN)
187 poll_mock.poll.assert_called_once_with(10)
188
189 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
190 def test_exec_command(self):
Gregory Thiemonge690bae22019-11-20 11:33:56 +0100191 chan_mock, poll_mock, select_mock, client_mock = (
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500192 self._set_mocks_for_select([[1, 0, 0]], True))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500193
194 chan_mock.recv_exit_status.return_value = 0
195 chan_mock.recv.return_value = b''
196 chan_mock.recv_stderr.return_value = b''
197
198 client = ssh.Client('localhost', 'root', timeout=2)
199 client.exec_command("test")
200
201 chan_mock.fileno.assert_called_once_with()
202 chan_mock.exec_command.assert_called_once_with("test")
203 chan_mock.shutdown_write.assert_called_once_with()
204
205 select_mock.assert_called_once_with()
206 poll_mock.register.assert_called_once_with(
207 chan_mock, self.SELECT_POLLIN)
208 poll_mock.poll.assert_called_once_with(10)
209 chan_mock.recv_ready.assert_called_once_with()
210 chan_mock.recv.assert_called_once_with(1024)
211 chan_mock.recv_stderr_ready.assert_called_once_with()
212 chan_mock.recv_stderr.assert_called_once_with(1024)
213 chan_mock.recv_exit_status.assert_called_once_with()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500214
Gregory Thiemonge690bae22019-11-20 11:33:56 +0100215 client_mock.close.assert_called_once_with()
216
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500217 def _set_mocks_for_select(self, poll_data, ito_value=False):
218 gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
219 '_get_ssh_connection')
220 ito_mock = self.patch('tempest.lib.common.ssh.Client._is_timed_out')
221 csp_mock = self.patch(
222 'tempest.lib.common.ssh.Client._can_system_poll')
223 csp_mock.return_value = True
224
225 select_mock = self.patch('select.poll', create=True)
226 client_mock = mock.MagicMock()
227 tran_mock = mock.MagicMock()
228 chan_mock = mock.MagicMock()
229 poll_mock = mock.MagicMock()
230
231 select_mock.return_value = poll_mock
232 gsc_mock.return_value = client_mock
233 ito_mock.return_value = ito_value
234 client_mock.get_transport.return_value = tran_mock
Lucas Alvares Gomes68c197e2016-04-19 18:18:05 +0100235 tran_mock.open_session().__enter__.return_value = chan_mock
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500236 if isinstance(poll_data[0], list):
237 poll_mock.poll.side_effect = poll_data
238 else:
239 poll_mock.poll.return_value = poll_data
240
Gregory Thiemonge690bae22019-11-20 11:33:56 +0100241 return chan_mock, poll_mock, select_mock, client_mock
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500242
243 _utf8_string = six.unichr(1071)
244 _utf8_bytes = _utf8_string.encode("utf-8")
245
246 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
247 def test_exec_good_command_output(self):
Gregory Thiemonge690bae22019-11-20 11:33:56 +0100248 chan_mock, poll_mock, _, _ = (
249 self._set_mocks_for_select([1, 0, 0]))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500250 closed_prop = mock.PropertyMock(return_value=True)
251 type(chan_mock).closed = closed_prop
252
253 chan_mock.recv_exit_status.return_value = 0
254 chan_mock.recv.side_effect = [self._utf8_bytes[0:1],
255 self._utf8_bytes[1:], b'R', b'']
256 chan_mock.recv_stderr.return_value = b''
257
258 client = ssh.Client('localhost', 'root', timeout=2)
259 out_data = client.exec_command("test")
260 self.assertEqual(self._utf8_string + 'R', out_data)
261
262 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
263 def test_exec_bad_command_output(self):
Gregory Thiemonge690bae22019-11-20 11:33:56 +0100264 chan_mock, poll_mock, _, _ = (
265 self._set_mocks_for_select([1, 0, 0]))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500266 closed_prop = mock.PropertyMock(return_value=True)
267 type(chan_mock).closed = closed_prop
268
269 chan_mock.recv_exit_status.return_value = 1
270 chan_mock.recv.return_value = b''
271 chan_mock.recv_stderr.side_effect = [b'R', self._utf8_bytes[0:1],
272 self._utf8_bytes[1:], b'']
273
274 client = ssh.Client('localhost', 'root', timeout=2)
275 exc = self.assertRaises(exceptions.SSHExecCommandFailed,
276 client.exec_command, "test")
277 self.assertIn('R' + self._utf8_string, six.text_type(exc))
278
279 def test_exec_command_no_select(self):
280 gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
281 '_get_ssh_connection')
282 csp_mock = self.patch(
283 'tempest.lib.common.ssh.Client._can_system_poll')
284 csp_mock.return_value = False
285
286 select_mock = self.patch('select.poll', create=True)
287 client_mock = mock.MagicMock()
288 tran_mock = mock.MagicMock()
289 chan_mock = mock.MagicMock()
290
291 # Test for proper reading of STDOUT and STDERROR
292
293 gsc_mock.return_value = client_mock
294 client_mock.get_transport.return_value = tran_mock
Lucas Alvares Gomes68c197e2016-04-19 18:18:05 +0100295 tran_mock.open_session().__enter__.return_value = chan_mock
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500296 chan_mock.recv_exit_status.return_value = 0
297
298 std_out_mock = mock.MagicMock(StringIO)
299 std_err_mock = mock.MagicMock(StringIO)
300 chan_mock.makefile.return_value = std_out_mock
301 chan_mock.makefile_stderr.return_value = std_err_mock
302
303 client = ssh.Client('localhost', 'root', timeout=2)
304 client.exec_command("test")
305
306 chan_mock.makefile.assert_called_once_with('rb', 1024)
307 chan_mock.makefile_stderr.assert_called_once_with('rb', 1024)
308 std_out_mock.read.assert_called_once_with()
309 std_err_mock.read.assert_called_once_with()
310 self.assertFalse(select_mock.called)