blob: b07f6bccfb4b64401f051b81a2a4a0d3fec77abf [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
15from io import StringIO
16import socket
Matthew Treinish9e26ca82016-02-23 11:43:20 -050017
18import mock
19import six
20import 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',
72 username='root',
73 pkey=None,
74 key_filename=None,
75 look_for_keys=False,
76 timeout=10.0,
77 password=None
78 )]
79 self.assertEqual(expected_connect, client_mock.connect.mock_calls)
80 self.assertEqual(0, s_mock.call_count)
81
Jordan Pittier0e53b612016-03-03 14:23:17 +010082 @mock.patch('time.sleep')
83 def test_get_ssh_connection_two_attemps(self, sleep_mock):
Matthew Treinish9e26ca82016-02-23 11:43:20 -050084 c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
85
86 c_mock.return_value = client_mock
87 client_mock.connect.side_effect = [
88 socket.error,
89 mock.MagicMock()
90 ]
91
92 client = ssh.Client('localhost', 'root', timeout=1)
Matthew Treinish9e26ca82016-02-23 11:43:20 -050093 client._get_ssh_connection(sleep=1)
Jordan Pittier0e53b612016-03-03 14:23:17 +010094 # We slept 2 seconds: because sleep is "1" and backoff is "1" too
95 sleep_mock.assert_called_once_with(2)
96 self.assertEqual(2, client_mock.connect.call_count)
Matthew Treinish9e26ca82016-02-23 11:43:20 -050097
98 def test_get_ssh_connection_timeout(self):
99 c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
100
Jordan Pittier0e53b612016-03-03 14:23:17 +0100101 timeout = 2
102 time_mock = self.patch('time.time')
103 time_mock.side_effect = utils.generate_timeout_series(timeout + 1)
104
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500105 c_mock.return_value = client_mock
106 client_mock.connect.side_effect = [
107 socket.error,
108 socket.error,
109 socket.error,
110 ]
111
Jordan Pittier0e53b612016-03-03 14:23:17 +0100112 client = ssh.Client('localhost', 'root', timeout=timeout)
113 # We need to mock LOG here because LOG.info() calls time.time()
114 # in order to preprend a timestamp.
115 with mock.patch.object(ssh, 'LOG'):
116 self.assertRaises(exceptions.SSHTimeout,
117 client._get_ssh_connection)
118
119 # time.time() should be called twice, first to start the timer
120 # and then to compute the timedelta
121 self.assertEqual(2, time_mock.call_count)
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500122
123 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
124 def test_timeout_in_exec_command(self):
125 chan_mock, poll_mock, _ = self._set_mocks_for_select([0, 0, 0], True)
126
127 # Test for a timeout condition immediately raised
128 client = ssh.Client('localhost', 'root', timeout=2)
129 with testtools.ExpectedException(exceptions.TimeoutException):
130 client.exec_command("test")
131
132 chan_mock.fileno.assert_called_once_with()
133 chan_mock.exec_command.assert_called_once_with("test")
134 chan_mock.shutdown_write.assert_called_once_with()
135
136 poll_mock.register.assert_called_once_with(
137 chan_mock, self.SELECT_POLLIN)
138 poll_mock.poll.assert_called_once_with(10)
139
140 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
141 def test_exec_command(self):
142 chan_mock, poll_mock, select_mock = (
143 self._set_mocks_for_select([[1, 0, 0]], True))
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500144
145 chan_mock.recv_exit_status.return_value = 0
146 chan_mock.recv.return_value = b''
147 chan_mock.recv_stderr.return_value = b''
148
149 client = ssh.Client('localhost', 'root', timeout=2)
150 client.exec_command("test")
151
152 chan_mock.fileno.assert_called_once_with()
153 chan_mock.exec_command.assert_called_once_with("test")
154 chan_mock.shutdown_write.assert_called_once_with()
155
156 select_mock.assert_called_once_with()
157 poll_mock.register.assert_called_once_with(
158 chan_mock, self.SELECT_POLLIN)
159 poll_mock.poll.assert_called_once_with(10)
160 chan_mock.recv_ready.assert_called_once_with()
161 chan_mock.recv.assert_called_once_with(1024)
162 chan_mock.recv_stderr_ready.assert_called_once_with()
163 chan_mock.recv_stderr.assert_called_once_with(1024)
164 chan_mock.recv_exit_status.assert_called_once_with()
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500165
166 def _set_mocks_for_select(self, poll_data, ito_value=False):
167 gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
168 '_get_ssh_connection')
169 ito_mock = self.patch('tempest.lib.common.ssh.Client._is_timed_out')
170 csp_mock = self.patch(
171 'tempest.lib.common.ssh.Client._can_system_poll')
172 csp_mock.return_value = True
173
174 select_mock = self.patch('select.poll', create=True)
175 client_mock = mock.MagicMock()
176 tran_mock = mock.MagicMock()
177 chan_mock = mock.MagicMock()
178 poll_mock = mock.MagicMock()
179
180 select_mock.return_value = poll_mock
181 gsc_mock.return_value = client_mock
182 ito_mock.return_value = ito_value
183 client_mock.get_transport.return_value = tran_mock
Lucas Alvares Gomes68c197e2016-04-19 18:18:05 +0100184 tran_mock.open_session().__enter__.return_value = chan_mock
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500185 if isinstance(poll_data[0], list):
186 poll_mock.poll.side_effect = poll_data
187 else:
188 poll_mock.poll.return_value = poll_data
189
190 return chan_mock, poll_mock, select_mock
191
192 _utf8_string = six.unichr(1071)
193 _utf8_bytes = _utf8_string.encode("utf-8")
194
195 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
196 def test_exec_good_command_output(self):
197 chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
198 closed_prop = mock.PropertyMock(return_value=True)
199 type(chan_mock).closed = closed_prop
200
201 chan_mock.recv_exit_status.return_value = 0
202 chan_mock.recv.side_effect = [self._utf8_bytes[0:1],
203 self._utf8_bytes[1:], b'R', b'']
204 chan_mock.recv_stderr.return_value = b''
205
206 client = ssh.Client('localhost', 'root', timeout=2)
207 out_data = client.exec_command("test")
208 self.assertEqual(self._utf8_string + 'R', out_data)
209
210 @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
211 def test_exec_bad_command_output(self):
212 chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
213 closed_prop = mock.PropertyMock(return_value=True)
214 type(chan_mock).closed = closed_prop
215
216 chan_mock.recv_exit_status.return_value = 1
217 chan_mock.recv.return_value = b''
218 chan_mock.recv_stderr.side_effect = [b'R', self._utf8_bytes[0:1],
219 self._utf8_bytes[1:], b'']
220
221 client = ssh.Client('localhost', 'root', timeout=2)
222 exc = self.assertRaises(exceptions.SSHExecCommandFailed,
223 client.exec_command, "test")
224 self.assertIn('R' + self._utf8_string, six.text_type(exc))
225
226 def test_exec_command_no_select(self):
227 gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
228 '_get_ssh_connection')
229 csp_mock = self.patch(
230 'tempest.lib.common.ssh.Client._can_system_poll')
231 csp_mock.return_value = False
232
233 select_mock = self.patch('select.poll', create=True)
234 client_mock = mock.MagicMock()
235 tran_mock = mock.MagicMock()
236 chan_mock = mock.MagicMock()
237
238 # Test for proper reading of STDOUT and STDERROR
239
240 gsc_mock.return_value = client_mock
241 client_mock.get_transport.return_value = tran_mock
Lucas Alvares Gomes68c197e2016-04-19 18:18:05 +0100242 tran_mock.open_session().__enter__.return_value = chan_mock
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500243 chan_mock.recv_exit_status.return_value = 0
244
245 std_out_mock = mock.MagicMock(StringIO)
246 std_err_mock = mock.MagicMock(StringIO)
247 chan_mock.makefile.return_value = std_out_mock
248 chan_mock.makefile_stderr.return_value = std_err_mock
249
250 client = ssh.Client('localhost', 'root', timeout=2)
251 client.exec_command("test")
252
253 chan_mock.makefile.assert_called_once_with('rb', 1024)
254 chan_mock.makefile_stderr.assert_called_once_with('rb', 1024)
255 std_out_mock.read.assert_called_once_with()
256 std_err_mock.read.assert_called_once_with()
257 self.assertFalse(select_mock.called)