Author: tomaz
Date: Mon Mar 11 01:58:13 2013
New Revision: 1454972
URL: http://svn.apache.org/r1454972
Log:
Backport commits from trunk.
Added:
libcloud/branches/0.12.x/libcloud/compute/drivers/digitalocean.py
- copied unchanged from r1454971,
libcloud/trunk/libcloud/compute/drivers/digitalocean.py
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/
- copied from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/create_node.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/create_node.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/destroy_node.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/destroy_node.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/error.txt
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/error.txt
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/ex_create_ssh_key.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/ex_create_ssh_key.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/ex_destroy_ssh_key.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/ex_destroy_ssh_key.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/ex_list_ssh_keys.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/ex_list_ssh_keys.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/list_images.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/list_images.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/list_locations.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/list_locations.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/list_nodes.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/list_nodes.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/list_nodes_empty.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/list_nodes_empty.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/list_sizes.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/list_sizes.json
libcloud/branches/0.12.x/libcloud/test/compute/fixtures/digitalocean/reboot_node.json
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/fixtures/digitalocean/reboot_node.json
libcloud/branches/0.12.x/libcloud/test/compute/test_digitalocean.py
- copied unchanged from r1454971,
libcloud/trunk/libcloud/test/compute/test_digitalocean.py
Modified:
libcloud/branches/0.12.x/ (props changed)
libcloud/branches/0.12.x/CHANGES
libcloud/branches/0.12.x/libcloud/compute/deployment.py
libcloud/branches/0.12.x/libcloud/compute/providers.py
libcloud/branches/0.12.x/libcloud/compute/ssh.py
libcloud/branches/0.12.x/libcloud/compute/types.py
libcloud/branches/0.12.x/libcloud/test/compute/test_deployment.py
libcloud/branches/0.12.x/libcloud/test/compute/test_ssh_client.py
libcloud/branches/0.12.x/libcloud/test/secrets.py-dist
Propchange: libcloud/branches/0.12.x/
------------------------------------------------------------------------------
Merged /libcloud/trunk:r1454172-1454971
Modified: libcloud/branches/0.12.x/CHANGES
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/CHANGES?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/CHANGES (original)
+++ libcloud/branches/0.12.x/CHANGES Mon Mar 11 01:58:13 2013
@@ -1,6 +1,6 @@
-*- coding: utf-8 -*-
-Changes with Apache Libcloud 0.12.2:
+Changes with Apache Libcloud in development:
*) General
@@ -25,7 +25,16 @@ Changes with Apache Libcloud 0.12.2:
is private. (LIBCLOUD-297)
[Grischa Meyer, Tomaz Muraus]
- *) DNS
+ - Add new driver for DigitalOcean provider - https://www.digitalocean.com/.
+ (LIBCLOUD-304)
+ [Tomaz Muraus]
+
+ - Fix a regression in ParamikoSSHClient.run method which caused this methid
+ to only work as expected if you passed an absolute or a relative path to
+ the script to it. (LIBCLOUD-278)
+ [Tomaz Muraus]
+
+ *) DNS
- Allow user to specify 'priority' extra argument when creating a MX or SRV
record.
Modified: libcloud/branches/0.12.x/libcloud/compute/deployment.py
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/compute/deployment.py?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/compute/deployment.py (original)
+++ libcloud/branches/0.12.x/libcloud/compute/deployment.py Mon Mar 11 01:58:13
2013
@@ -149,9 +149,17 @@ class ScriptDeployment(Deployment):
See also L{Deployment.run}
"""
+ file_path = client.put(path=self.name, chmod=int('755', 8),
+ contents=self.script)
- client.put(path=self.name, chmod=int('755', 8), contents=self.script)
- self.stdout, self.stderr, self.exit_status = client.run(self.name)
+ # Pre-pend cwd if user specified a relative path
+ if self.name[0] != '/':
+ base_path = os.path.dirname(file_path)
+ name = os.path.join(base_path, self.name)
+ else:
+ name = self.name
+
+ self.stdout, self.stderr, self.exit_status = client.run(name)
if self.delete:
client.delete(self.name)
Modified: libcloud/branches/0.12.x/libcloud/compute/providers.py
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/compute/providers.py?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/compute/providers.py (original)
+++ libcloud/branches/0.12.x/libcloud/compute/providers.py Mon Mar 11 01:58:13
2013
@@ -129,7 +129,9 @@ DRIVERS = {
Provider.HOSTVIRTUAL:
('libcloud.compute.drivers.hostvirtual', 'HostVirtualNodeDriver'),
Provider.ABIQUO:
- ('libcloud.compute.drivers.abiquo', 'AbiquoNodeDriver')
+ ('libcloud.compute.drivers.abiquo', 'AbiquoNodeDriver'),
+ Provider.DIGITAL_OCEAN:
+ ('libcloud.compute.drivers.digitalocean', 'DigitalOceanNodeDriver')
}
Modified: libcloud/branches/0.12.x/libcloud/compute/ssh.py
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/compute/ssh.py?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/compute/ssh.py (original)
+++ libcloud/branches/0.12.x/libcloud/compute/ssh.py Mon Mar 11 01:58:13 2013
@@ -28,6 +28,10 @@ except ImportError:
# warning on Python 2.6.
# Ref: https://bugs.launchpad.net/paramiko/+bug/392973
+import os
+import subprocess
+import logging
+
from os.path import split as psplit
from os.path import join as pjoin
@@ -66,7 +70,9 @@ class BaseSSHClient(object):
"""
Connect to the remote node over SSH.
- @return: C{bool}
+ @return: True if the connection has been successfuly established, False
+ otherwise.
+ @rtype: C{bool}
"""
raise NotImplementedError(
'connect not implemented for this ssh client')
@@ -86,6 +92,9 @@ class BaseSSHClient(object):
@type mode: C{str}
@keyword mode: Mode in which the file is opened.
+
+ @return: Full path to the location where a file has been saved.
+ @rtype: C{str}
"""
raise NotImplementedError(
'put not implemented for this ssh client')
@@ -96,6 +105,10 @@ class BaseSSHClient(object):
@type path: C{str}
@keyword path: File path on the remote node.
+
+ @return: True if the file has been successfuly deleted, False
+ otherwise.
+ @rtype: C{bool}
"""
raise NotImplementedError(
'delete not implemented for this ssh client')
@@ -115,6 +128,10 @@ class BaseSSHClient(object):
def close(self):
"""
Shutdown connection to the remote node.
+
+ @return: True if the connection has been successfuly closed, False
+ otherwise.
+ @rtype: C{bool}
"""
raise NotImplementedError(
'close not implemented for this ssh client')
@@ -174,6 +191,8 @@ class ParamikoSSHClient(BaseSSHClient):
pass
sftp.chdir(part)
+ cwd = sftp.getcwd()
+
ak = sftp.file(tail, mode=mode)
ak.write(contents)
if chmod is not None:
@@ -181,25 +200,20 @@ class ParamikoSSHClient(BaseSSHClient):
ak.close()
sftp.close()
+ if path[0] == '/':
+ file_path = path
+ else:
+ file_path = pjoin(cwd, path)
+
+ return file_path
+
def delete(self, path):
sftp = self.client.open_sftp()
sftp.unlink(path)
sftp.close()
+ return True
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()
@@ -217,12 +231,112 @@ class ParamikoSSHClient(BaseSSHClient):
def close(self):
self.client.close()
+ return True
class ShellOutSSHClient(BaseSSHClient):
- # TODO: write this one
+ """
+ This client shells out to "ssh" binary to run commands on the remote
+ server.
+
+ Note: This client should not be used in production.
+ """
+
+ def __init__(self, hostname, port=22, username='root', password=None,
+ key=None, timeout=None):
+ super(ShellOutSSHClient, self).__init__(hostname, port, username,
+ password, key, timeout)
+ if self.password:
+ raise ValueError('ShellOutSSHClient only supports key auth')
+
+ child = subprocess.Popen(['ssh'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ child.communicate()
+
+ if child.returncode == 127:
+ raise ValueError('ssh client is not available')
+
+ self.logger = self._get_and_setup_logger()
+
+ def connect(self):
+ """
+ This client doesn't support persistent connections establish a new
+ connection every time "run" method is called.
+ """
+ return True
+
+ def run(self, cmd):
+ return self._run_remote_shell_command([cmd])
+
+ def put(self, path, contents=None, chmod=None, mode='w'):
+ if mode == 'w':
+ redirect = '>'
+ elif mode == 'a':
+ redirect = '>>'
+ else:
+ raise ValueError('Invalid mode: ' + mode)
+
+ cmd = ['echo "%s" %s %s' % (contents, redirect, path)]
+ self._run_remote_shell_command(cmd)
+ return path
+
+ def delete(self, path):
+ cmd = ['rm', '-rf', path]
+ self._run_remote_shell_command(cmd)
+ return True
+
+ def close(self):
+ return True
+
+ def _get_and_setup_logger(self):
+ logger = logging.getLogger('libcloud.compute.ssh')
+ path = os.getenv('LIBCLOUD_DEBUG')
+
+ if path:
+ handler = logging.FileHandler(path)
+ logger.addHandler(handler)
+ logger.setLevel(logging.DEBUG)
+
+ return logger
+
+ def _get_base_ssh_command(self):
+ cmd = ['ssh']
+
+ if self.key:
+ cmd += ['-i', self.key]
+
+ if self.timeout:
+ cmd += ['-oConnectTimeout=%s' % (self.timeout)]
+
+ cmd += ['%s@%s' % (self.username, self.hostname)]
+
+ return cmd
+
+ def _run_remote_shell_command(self, cmd):
+ """
+ Run a command on a remote server.
+
+ @param cmd: Command to run.
+ @type cmd: C{list} of C{str}
+
+ @return: Command stdout, stderr and status code.
+ @rtype: C{tuple}
+ """
+ base_cmd = self._get_base_ssh_command()
+ full_cmd = base_cmd + [' '.join(cmd)]
+
+ self.logger.debug('Executing command: "%s"' % (' '.join(full_cmd)))
+
+ child = subprocess.Popen(full_cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = child.communicate()
+ return (stdout, stderr, child.returncode)
+
+
+class MockSSHClient(BaseSSHClient):
pass
+
SSHClient = ParamikoSSHClient
if not have_paramiko:
- SSHClient = ShellOutSSHClient
+ SSHClient = MockSSHClient
Modified: libcloud/branches/0.12.x/libcloud/compute/types.py
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/compute/types.py?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/compute/types.py (original)
+++ libcloud/branches/0.12.x/libcloud/compute/types.py Mon Mar 11 01:58:13 2013
@@ -110,6 +110,7 @@ class Provider(object):
GRIDSPOT = 'gridspot'
HOSTVIRTUAL = 'hostvirtual'
ABIQUO = 'abiquo'
+ DIGITAL_OCEAN = 'digitalocean'
EC2_US_EAST = 'ec2_us_east'
EC2_EU = 'ec2_eu_west' # deprecated name
Modified: libcloud/branches/0.12.x/libcloud/test/compute/test_deployment.py
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/compute/test_deployment.py?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/test/compute/test_deployment.py (original)
+++ libcloud/branches/0.12.x/libcloud/test/compute/test_deployment.py Mon Mar
11 01:58:13 2013
@@ -111,6 +111,26 @@ class DeploymentTests(unittest.TestCase)
self.assertEqual(self.node, sd2.run(node=self.node,
client=MockClient(hostname='localhost')))
+ def test_script_deployment_relative_path(self):
+ client = Mock()
+ client.put.return_value = '/home/ubuntu/relative.sh'
+ client.run.return_value = ('', '', 0)
+
+ sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
+ sd.run(self.node, client)
+
+ client.run.assert_called_once_with('/home/ubuntu/relative.sh')
+
+ def test_script_deployment_absolute_path(self):
+ client = Mock()
+ client.put.return_value = '/home/ubuntu/relative.sh'
+ client.run.return_value = ('', '', 0)
+
+ sd = ScriptDeployment(script='echo "foo"', name='/root/relative.sh')
+ sd.run(self.node, client)
+
+ client.run.assert_called_once_with('/root/relative.sh')
+
def test_script_deployment_and_sshkey_deployment_argument_types(self):
class FileObject(object):
def __init__(self, name):
Modified: libcloud/branches/0.12.x/libcloud/test/compute/test_ssh_client.py
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/compute/test_ssh_client.py?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/test/compute/test_ssh_client.py (original)
+++ libcloud/branches/0.12.x/libcloud/test/compute/test_ssh_client.py Mon Mar
11 01:58:13 2013
@@ -14,10 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import absolute_import
+
import sys
import unittest
from libcloud.compute.ssh import ParamikoSSHClient
+from libcloud.compute.ssh import ShellOutSSHClient
from libcloud.compute.ssh import have_paramiko
from mock import patch, Mock
@@ -114,40 +117,6 @@ class ParamikoSSHClientTests(unittest.Te
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.
@@ -164,10 +133,69 @@ class ParamikoSSHClientTests(unittest.Te
mock.close()
+
if not ParamikoSSHClient:
class ParamikoSSHClientTests(unittest.TestCase):
pass
+class ShellOutSSHClientTests(unittest.TestCase):
+ def test_password_auth_not_supported(self):
+ try:
+ ShellOutSSHClient(hostname='localhost', username='foo',
+ password='bar')
+ except ValueError:
+ e = sys.exc_info()[1]
+ msg = str(e)
+ self.assertTrue('ShellOutSSHClient only supports key auth' in msg)
+ else:
+ self.fail('Exception was not thrown')
+
+ def test_ssh_executable_not_available(self):
+ class MockChild(object):
+ returncode = 127
+
+ def communicate(*args, **kwargs):
+ pass
+
+ def mock_popen(*args, **kwargs):
+ return MockChild()
+
+ with patch('subprocess.Popen', mock_popen):
+ try:
+ ShellOutSSHClient(hostname='localhost', username='foo')
+ except ValueError:
+ e = sys.exc_info()[1]
+ msg = str(e)
+ self.assertTrue('ssh client is not available' in msg)
+ else:
+ self.fail('Exception was not thrown')
+
+ def test_connect_success(self):
+ client = ShellOutSSHClient(hostname='localhost', username='root')
+ self.assertTrue(client.connect())
+
+ def test_close_success(self):
+ client = ShellOutSSHClient(hostname='localhost', username='root')
+ self.assertTrue(client.close())
+
+ def test_get_base_ssh_command(self):
+ client1 = ShellOutSSHClient(hostname='localhost', username='root')
+ client2 = ShellOutSSHClient(hostname='localhost', username='root',
+ key='/home/my.key')
+ client3 = ShellOutSSHClient(hostname='localhost', username='root',
+ key='/home/my.key', timeout=5)
+
+ cmd1 = client1._get_base_ssh_command()
+ cmd2 = client2._get_base_ssh_command()
+ cmd3 = client3._get_base_ssh_command()
+
+ self.assertEquals(cmd1, ['ssh', 'root@localhost'])
+ self.assertEquals(cmd2, ['ssh', '-i', '/home/my.key',
+ 'root@localhost'])
+ self.assertEquals(cmd3, ['ssh', '-i', '/home/my.key',
+ '-oConnectTimeout=5', 'root@localhost'])
+
+
if __name__ == '__main__':
sys.exit(unittest.main())
Modified: libcloud/branches/0.12.x/libcloud/test/secrets.py-dist
URL:
http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/secrets.py-dist?rev=1454972&r1=1454971&r2=1454972&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/test/secrets.py-dist (original)
+++ libcloud/branches/0.12.x/libcloud/test/secrets.py-dist Mon Mar 11 01:58:13
2013
@@ -39,6 +39,7 @@ JOYENT_PARAMS = ('user', 'key')
VCL_PARAMS = ('user', 'pass', True, 'foo.bar.com')
GRIDSPOT_PARAMS = ('key',)
HOSTVIRTUAL_PARAMS = ('key',)
+DIGITAL_OCEAN_PARAMS = ('user', 'key')
# Storage
STORAGE_S3_PARAMS = ('key', 'secret')