Close ssh client after executing a command

Each time ssh.Client.exec_command is called, _get_ssh_connection creates
a new SSHClient instance.
If this instance is not closed after command execution, it might cause a
shortage of resources on the host (many open sockets, and dropbear
processes in a cirros VM).

This commit closes ssh client after processing the remote command.

Closes-Bug: #1853264

Change-Id: If77f401d6d4a1282ce31a31bbd8827db34690e52
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index d4ec6ad..2ac1605 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -196,11 +196,13 @@
 
             exit_status = channel.recv_exit_status()
 
-            if 0 != exit_status:
-                raise exceptions.SSHExecCommandFailed(
-                    command=cmd, exit_status=exit_status,
-                    stderr=err_data, stdout=out_data)
-            return out_data
+        ssh.close()
+
+        if 0 != exit_status:
+            raise exceptions.SSHExecCommandFailed(
+                command=cmd, exit_status=exit_status,
+                stderr=err_data, stdout=out_data)
+        return out_data
 
     def test_connection_auth(self):
         """Raises an exception when we can not connect to server via ssh."""
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
index 37fe646..c849231 100644
--- a/tempest/tests/lib/test_ssh.py
+++ b/tempest/tests/lib/test_ssh.py
@@ -170,7 +170,8 @@
 
     @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
     def test_timeout_in_exec_command(self):
-        chan_mock, poll_mock, _ = self._set_mocks_for_select([0, 0, 0], True)
+        chan_mock, poll_mock, _, _ = (
+            self._set_mocks_for_select([0, 0, 0], True))
 
         # Test for a timeout condition immediately raised
         client = ssh.Client('localhost', 'root', timeout=2)
@@ -187,7 +188,7 @@
 
     @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
     def test_exec_command(self):
-        chan_mock, poll_mock, select_mock = (
+        chan_mock, poll_mock, select_mock, client_mock = (
             self._set_mocks_for_select([[1, 0, 0]], True))
 
         chan_mock.recv_exit_status.return_value = 0
@@ -211,6 +212,8 @@
         chan_mock.recv_stderr.assert_called_once_with(1024)
         chan_mock.recv_exit_status.assert_called_once_with()
 
+        client_mock.close.assert_called_once_with()
+
     def _set_mocks_for_select(self, poll_data, ito_value=False):
         gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
                               '_get_ssh_connection')
@@ -235,14 +238,15 @@
         else:
             poll_mock.poll.return_value = poll_data
 
-        return chan_mock, poll_mock, select_mock
+        return chan_mock, poll_mock, select_mock, client_mock
 
     _utf8_string = six.unichr(1071)
     _utf8_bytes = _utf8_string.encode("utf-8")
 
     @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
     def test_exec_good_command_output(self):
-        chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
+        chan_mock, poll_mock, _, _ = (
+            self._set_mocks_for_select([1, 0, 0]))
         closed_prop = mock.PropertyMock(return_value=True)
         type(chan_mock).closed = closed_prop
 
@@ -257,7 +261,8 @@
 
     @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
     def test_exec_bad_command_output(self):
-        chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
+        chan_mock, poll_mock, _, _ = (
+            self._set_mocks_for_select([1, 0, 0]))
         closed_prop = mock.PropertyMock(return_value=True)
         type(chan_mock).closed = closed_prop