Jay Pipes | 8fe5392 | 2014-01-14 20:08:16 -0500 | [diff] [blame] | 1 | # 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 | |
| 15 | import contextlib |
| 16 | import socket |
| 17 | |
| 18 | import mock |
| 19 | import testtools |
| 20 | |
| 21 | from tempest.common import ssh |
| 22 | from tempest import exceptions |
| 23 | from tempest.tests import base |
| 24 | |
| 25 | |
| 26 | class 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 Kotton | c3128c0 | 2014-01-12 06:59:45 -0800 | [diff] [blame] | 91 | 1000, # LOG.warning() calls time.time() loop 1 |
Jay Pipes | 8fe5392 | 2014-01-14 20:08:16 -0500 | [diff] [blame] | 92 | 1001, # Sleep loop 1 |
Gary Kotton | c3128c0 | 2014-01-12 06:59:45 -0800 | [diff] [blame] | 93 | 1001, # LOG.warning() calls time.time() loop 2 |
Jay Pipes | 8fe5392 | 2014-01-14 20:08:16 -0500 | [diff] [blame] | 94 | 1002 # Sleep loop 2 |
| 95 | ] |
| 96 | |
| 97 | client._get_ssh_connection(sleep=1) |
| 98 | |
| 99 | expected_sleeps = [ |
Gary Kotton | c3128c0 | 2014-01-12 06:59:45 -0800 | [diff] [blame] | 100 | mock.call(2), |
| 101 | mock.call(3) |
Jay Pipes | 8fe5392 | 2014-01-14 20:08:16 -0500 | [diff] [blame] | 102 | ] |
| 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 Kotton | c3128c0 | 2014-01-12 06:59:45 -0800 | [diff] [blame] | 116 | 1000, # LOG.warning() calls time.time() loop 1 |
Jay Pipes | 8fe5392 | 2014-01-14 20:08:16 -0500 | [diff] [blame] | 117 | 1001, # Sleep loop 1 |
Gary Kotton | c3128c0 | 2014-01-12 06:59:45 -0800 | [diff] [blame] | 118 | 1001, # LOG.warning() calls time.time() loop 2 |
Jay Pipes | 8fe5392 | 2014-01-14 20:08:16 -0500 | [diff] [blame] | 119 | 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() |