Author: tomaz
Date: Mon Jan 28 03:05:09 2013
New Revision: 1439234
URL: http://svn.apache.org/viewvc?rev=1439234&view=rev
Log:
Fix the ScriptDeployment step to work correctly if user provides a relative path
to the script.
Contributed by Jaume devesa, part of LIBCLOUD-278.
Modified:
libcloud/trunk/CHANGES
libcloud/trunk/libcloud/compute/ssh.py
libcloud/trunk/libcloud/test/compute/test_ssh_client.py
libcloud/trunk/tox.ini
Modified: libcloud/trunk/CHANGES
URL:
http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1439234&r1=1439233&r2=1439234&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Mon Jan 28 03:05:09 2013
@@ -111,6 +111,10 @@ Changes with Apache Libcloud in developm
- Improve error handling in the Brightbox driver.
[Tomaz Muraus]
+ - Fix the ScriptDeployment step to work correctly if user provides a
+ relative path to the script. (LIBCLOUD-278)
+ [Jaume Devesa]
+
*) Storage
- Add a new local storage driver.
Modified: libcloud/trunk/libcloud/compute/ssh.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/ssh.py?rev=1439234&r1=1439233&r2=1439234&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/ssh.py (original)
+++ libcloud/trunk/libcloud/compute/ssh.py Mon Jan 28 03:05:09 2013
@@ -29,6 +29,7 @@ except ImportError:
# Ref: https://bugs.launchpad.net/paramiko/+bug/392973
from os.path import split as psplit
+from os.path import join as pjoin
class BaseSSHClient(object):
@@ -120,6 +121,7 @@ class BaseSSHClient(object):
class ParamikoSSHClient(BaseSSHClient):
+
"""
A SSH Client powered by Paramiko.
"""
@@ -179,6 +181,19 @@ class ParamikoSSHClient(BaseSSHClient):
sftp.close()
def run(self, cmd):
+ if cmd[0] != '/':
+ # If 'cmd' based on relative path,
+ # set the absoute path joining the HOME path
+ sftp = self.client.open_sftp()
+ # Chdir to its own directory is mandatory because otherwise
+ # the 'getcwd()' method returns None
+ sftp.chdir('.')
+ cwd = sftp.getcwd()
+ sftp.close()
+
+ # Join the command to the current path
+ cmd = pjoin(cwd, cmd)
+
# based on exec_command()
bufsize = -1
t = self.client.get_transport()
Modified: libcloud/trunk/libcloud/test/compute/test_ssh_client.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/compute/test_ssh_client.py?rev=1439234&r1=1439233&r2=1439234&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/compute/test_ssh_client.py (original)
+++ libcloud/trunk/libcloud/test/compute/test_ssh_client.py Mon Jan 28 03:05:09
2013
@@ -17,13 +17,156 @@
import sys
import unittest
-import libcloud.compute.ssh
+from libcloud.compute.ssh import ParamikoSSHClient
+from libcloud.compute.ssh import have_paramiko
-from mock import Mock
+from mock import patch, Mock
+
+if not have_paramiko:
+ ParamikoSSHClient = None
class ParamikoSSHClientTests(unittest.TestCase):
- pass
+ @patch('paramiko.SSHClient', Mock)
+ def setUp(self):
+ """
+ Creates the object patching the actual connection.
+ """
+ conn_params = {'hostname': 'dummy.host.org',
+ 'port': 8822,
+ 'username': 'ubuntu',
+ 'key': '~/.ssh/ubuntu_ssh',
+ 'timeout': '600'}
+ self.ssh_cli = ParamikoSSHClient(**conn_params)
+
+ @patch('paramiko.SSHClient', Mock)
+ def test_create_with_password(self):
+ """
+ Initialize object with password.
+
+ Just to have better coverage, initialize the object
+ with the 'password' value instead of the 'key'.
+ """
+ conn_params = {'hostname': 'dummy.host.org',
+ 'username': 'ubuntu',
+ 'password': 'ubuntu'}
+ mock = ParamikoSSHClient(**conn_params)
+ mock.connect()
+
+ expected_conn = {'username': 'ubuntu',
+ 'password': 'ubuntu',
+ 'allow_agent': False,
+ 'hostname': 'dummy.host.org',
+ 'look_for_keys': False,
+ 'port': 22}
+ mock.client.connect.assert_called_once_with(**expected_conn)
+
+ @patch('paramiko.SSHClient', Mock)
+ def test_create_without_credentials(self):
+ """
+ Initialize object with no credentials.
+
+ Just to have better coverage, initialize the object
+ without 'password' neither 'key'.
+ """
+ conn_params = {'hostname': 'dummy.host.org',
+ 'username': 'ubuntu'}
+ mock = ParamikoSSHClient(**conn_params)
+ mock.connect()
+
+ expected_conn = {'username': 'ubuntu',
+ 'hostname': 'dummy.host.org',
+ 'allow_agent': True,
+ 'look_for_keys': True,
+ 'port': 22}
+ mock.client.connect.assert_called_once_with(**expected_conn)
+
+ def test_basic_usage_absolute_path(self):
+ """
+ Basic execution.
+ """
+ mock = self.ssh_cli
+ # script to execute
+ sd = "/root/random_script.sh"
+
+ # Connect behavior
+ mock.connect()
+ mock_cli = mock.client # The actual mocked object: SSHClient
+ expected_conn = {'username': 'ubuntu',
+ 'key_filename': '~/.ssh/ubuntu_ssh',
+ 'allow_agent': False,
+ 'hostname': 'dummy.host.org',
+ 'look_for_keys': False,
+ 'timeout': '600',
+ 'port': 8822}
+ mock_cli.connect.assert_called_once_with(**expected_conn)
+
+ mock.put(sd)
+ # Make assertions over 'put' method
+ mock_cli.open_sftp().chdir.assert_called_with('root')
+ mock_cli.open_sftp().file.assert_called_once_with('random_script.sh',
+ mode='w')
+
+ mock.run(sd)
+ # Make assertions over 'run' method
+ mock_cli.get_transport().open_session().exec_command \
+ .assert_called_once_with(sd)
+
+ mock.close()
+
+ def test_run_script_with_relative_path(self):
+ """
+ Execute script with relative path.
+ """
+ mock = self.ssh_cli
+
+ # Define behaviour then ask for 'current directory'
+ mock.client.open_sftp().getcwd.return_value = '/home/ubuntu/'
+
+ # Script without full path
+ sd = 'random_script.sh'
+
+ # Without assertions because they are the same than the previous
+ # 'test_basic_usage' method
+ mock.connect()
+
+ mock_cli = mock.client # The actual mocked object: SSHClient
+
+ mock.put(sd, chmod=600)
+ # Make assertions over 'put' method
+ mock_cli.open_sftp().file.assert_called_once_with('random_script.sh',
+ mode='w')
+ mock_cli.open_sftp().file().chmod.assert_called_once_with(600)
+
+ mock.run(sd)
+ # Make assertions over the 'run' method
+ mock_cli.open_sftp().chdir.assert_called_with(".")
+ mock_cli.open_sftp().getcwd.assert_called_once()
+ full_sd = '/home/ubuntu/random_script.sh'
+ mock_cli.get_transport().open_session().exec_command \
+ .assert_called_once_with(full_sd)
+
+ mock.close()
+
+ def test_delete_script(self):
+ """
+ Provide a basic test with 'delete' action.
+ """
+ mock = self.ssh_cli
+ # script to execute
+ sd = '/root/random_script.sh'
+
+ mock.connect()
+
+ mock.delete(sd)
+ # Make assertions over the 'delete' method
+ mock.client.open_sftp().unlink.assert_called_with(sd)
+
+ mock.close()
+
+if not ParamikoSSHClient:
+ class ParamikoSSHClientTests(unittest.TestCase):
+ pass
if __name__ == '__main__':
Modified: libcloud/trunk/tox.ini
URL:
http://svn.apache.org/viewvc/libcloud/trunk/tox.ini?rev=1439234&r1=1439233&r2=1439234&view=diff
==============================================================================
--- libcloud/trunk/tox.ini (original)
+++ libcloud/trunk/tox.ini Mon Jan 28 03:05:09 2013
@@ -5,6 +5,7 @@ envlist = py25,py26,py27,pypy,py32,py33
[testenv]
deps = mock
lockfile
+ paramiko
commands = python setup.py test
[testenv:py25]
@@ -12,6 +13,7 @@ deps = mock
lockfile
ssl
simplejson
+ paramiko
[testenv:py32]
deps = mock