blob: a6eedc46681bd0b38132ba21b30a7046fb40c0d0 [file] [log] [blame]
Jay Pipes8fe53922014-01-14 20:08:16 -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
15import contextlib
16import socket
17
18import mock
19import testtools
20
21from tempest.common import ssh
22from tempest import exceptions
23from tempest.tests import base
24
25
26class TestSshClient(base.TestCase):
27
28 def test_pkey_calls_paramiko_RSAKey(self):
29 with contextlib.nested(
30 mock.patch('paramiko.RSAKey.from_private_key'),
31 mock.patch('cStringIO.StringIO')) as (rsa_mock, cs_mock):
32 cs_mock.return_value = mock.sentinel.csio
33 pkey = 'mykey'
34 ssh.Client('localhost', 'root', pkey=pkey)
35 rsa_mock.assert_called_once_with(mock.sentinel.csio)
36 cs_mock.assert_called_once_with('mykey')
37 rsa_mock.reset_mock()
38 cs_mock.rest_mock()
39 pkey = mock.sentinel.pkey
40 # Shouldn't call out to load a file from RSAKey, since
41 # a sentinel isn't a basestring...
42 ssh.Client('localhost', 'root', pkey=pkey)
43 rsa_mock.assert_not_called()
44 cs_mock.assert_not_called()
45
46 def test_get_ssh_connection(self):
47 c_mock = self.patch('paramiko.SSHClient')
48 aa_mock = self.patch('paramiko.AutoAddPolicy')
49 s_mock = self.patch('time.sleep')
50 t_mock = self.patch('time.time')
51
52 aa_mock.return_value = mock.sentinel.aa
53
54 def reset_mocks():
55 aa_mock.reset_mock()
56 c_mock.reset_mock()
57 s_mock.reset_mock()
58 t_mock.reset_mock()
59
60 # Test normal case for successful connection on first try
61 client_mock = mock.MagicMock()
62 c_mock.return_value = client_mock
63 client_mock.connect.return_value = True
64
65 client = ssh.Client('localhost', 'root', timeout=2)
66 client._get_ssh_connection(sleep=1)
67
68 aa_mock.assert_called_once_with()
69 client_mock.set_missing_host_key_policy.assert_called_once_with(
70 mock.sentinel.aa)
71 expected_connect = [mock.call(
72 'localhost',
73 username='root',
74 pkey=None,
75 key_filename=None,
76 look_for_keys=False,
77 timeout=10.0,
78 password=None
79 )]
80 self.assertEqual(expected_connect, client_mock.connect.mock_calls)
81 s_mock.assert_not_called()
82 t_mock.assert_called_once_with()
83
84 reset_mocks()
85
86 # Test case when connection fails on first two tries and
87 # succeeds on third try (this validates retry logic)
88 client_mock.connect.side_effect = [socket.error, socket.error, True]
89 t_mock.side_effect = [
90 1000, # Start time
Gary Kottonc3128c02014-01-12 06:59:45 -080091 1000, # LOG.warning() calls time.time() loop 1
Jay Pipes8fe53922014-01-14 20:08:16 -050092 1001, # Sleep loop 1
Gary Kottonc3128c02014-01-12 06:59:45 -080093 1001, # LOG.warning() calls time.time() loop 2
Jay Pipes8fe53922014-01-14 20:08:16 -050094 1002 # Sleep loop 2
95 ]
96
97 client._get_ssh_connection(sleep=1)
98
99 expected_sleeps = [
Gary Kottonc3128c02014-01-12 06:59:45 -0800100 mock.call(2),
101 mock.call(3)
Jay Pipes8fe53922014-01-14 20:08:16 -0500102 ]
103 self.assertEqual(expected_sleeps, s_mock.mock_calls)
104
105 reset_mocks()
106
107 # Test case when connection fails on first three tries and
108 # exceeds the timeout, so expect to raise a Timeout exception
109 client_mock.connect.side_effect = [
110 socket.error,
111 socket.error,
112 socket.error
113 ]
114 t_mock.side_effect = [
115 1000, # Start time
Gary Kottonc3128c02014-01-12 06:59:45 -0800116 1000, # LOG.warning() calls time.time() loop 1
Jay Pipes8fe53922014-01-14 20:08:16 -0500117 1001, # Sleep loop 1
Gary Kottonc3128c02014-01-12 06:59:45 -0800118 1001, # LOG.warning() calls time.time() loop 2
Jay Pipes8fe53922014-01-14 20:08:16 -0500119 1002, # Sleep loop 2
120 1003, # Sleep loop 3
121 1004 # LOG.error() calls time.time()
122 ]
123
124 with testtools.ExpectedException(exceptions.SSHTimeout):
125 client._get_ssh_connection()
126
127 def test_exec_command(self):
128 gsc_mock = self.patch('tempest.common.ssh.Client._get_ssh_connection')
129 ito_mock = self.patch('tempest.common.ssh.Client._is_timed_out')
130 select_mock = self.patch('select.poll')
131
132 client_mock = mock.MagicMock()
133 tran_mock = mock.MagicMock()
134 chan_mock = mock.MagicMock()
135 poll_mock = mock.MagicMock()
136
137 def reset_mocks():
138 gsc_mock.reset_mock()
139 ito_mock.reset_mock()
140 select_mock.reset_mock()
141 poll_mock.reset_mock()
142 client_mock.reset_mock()
143 tran_mock.reset_mock()
144 chan_mock.reset_mock()
145
146 select_mock.return_value = poll_mock
147 gsc_mock.return_value = client_mock
148 ito_mock.return_value = True
149 client_mock.get_transport.return_value = tran_mock
150 tran_mock.open_session.return_value = chan_mock
151 poll_mock.poll.side_effect = [
152 [0, 0, 0]
153 ]
154
155 # Test for a timeout condition immediately raised
156 client = ssh.Client('localhost', 'root', timeout=2)
157 with testtools.ExpectedException(exceptions.TimeoutException):
158 client.exec_command("test")
159
160 chan_mock.fileno.assert_called_once_with()
161 chan_mock.exec_command.assert_called_once_with("test")
162 chan_mock.shutdown_write.assert_called_once_with()
163
164 SELECT_POLLIN = 1
165 poll_mock.register.assert_called_once_with(chan_mock, SELECT_POLLIN)
166 poll_mock.poll.assert_called_once_with(10)
167
168 # Test for proper reading of STDOUT and STDERROR and closing
169 # of all file descriptors.
170
171 reset_mocks()
172
173 select_mock.return_value = poll_mock
174 gsc_mock.return_value = client_mock
175 ito_mock.return_value = False
176 client_mock.get_transport.return_value = tran_mock
177 tran_mock.open_session.return_value = chan_mock
178 poll_mock.poll.side_effect = [
179 [1, 0, 0]
180 ]
181 closed_prop = mock.PropertyMock(return_value=True)
182 type(chan_mock).closed = closed_prop
183 chan_mock.recv_exit_status.return_value = 0
184 chan_mock.recv.return_value = ''
185 chan_mock.recv_stderr.return_value = ''
186
187 client = ssh.Client('localhost', 'root', timeout=2)
188 client.exec_command("test")
189
190 chan_mock.fileno.assert_called_once_with()
191 chan_mock.exec_command.assert_called_once_with("test")
192 chan_mock.shutdown_write.assert_called_once_with()
193
194 SELECT_POLLIN = 1
195 poll_mock.register.assert_called_once_with(chan_mock, SELECT_POLLIN)
196 poll_mock.poll.assert_called_once_with(10)
197 chan_mock.recv_ready.assert_called_once_with()
198 chan_mock.recv.assert_called_once_with(1024)
199 chan_mock.recv_stderr_ready.assert_called_once_with()
200 chan_mock.recv_stderr.assert_called_once_with(1024)
201 chan_mock.recv_exit_status.assert_called_once_with()
202 closed_prop.assert_called_once_with()