You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2020/04/04 20:45:14 UTC

[libcloud] 12/21: Allow user to pass "timeout" argument to ScriptDeployment and ScriptFileDeployment class.

This is an automated email from the ASF dual-hosted git repository.

tomaz pushed a commit to branch 2.8.x
in repository https://gitbox.apache.org/repos/asf/libcloud.git

commit ed30a8e0f6f563c17cf94feb6c13c27c77e08560
Author: Tomaz Muraus <to...@tomaz.me>
AuthorDate: Wed Apr 1 13:08:01 2020 +0200

    Allow user to pass "timeout" argument to ScriptDeployment and
    ScriptFileDeployment class.
    
    With this argument, user can specify optional command run timeout for
    those deployment steps.
---
 libcloud/compute/deployment.py           | 33 +++++++++++++++++++++++++------
 libcloud/compute/ssh.py                  |  6 +++---
 libcloud/test/compute/test_deployment.py | 34 +++++++++++++++++++++++++-------
 3 files changed, 57 insertions(+), 16 deletions(-)

diff --git a/libcloud/compute/deployment.py b/libcloud/compute/deployment.py
index 0328725..7850078 100644
--- a/libcloud/compute/deployment.py
+++ b/libcloud/compute/deployment.py
@@ -138,8 +138,14 @@ class ScriptDeployment(Deployment):
     you are running a plan shell script.
     """
 
-    def __init__(self, script, args=None, name=None, delete=False):
-        # type: (str, Optional[List[str]], Optional[str], bool) -> None
+    def __init__(self,
+                 script,  # type: str
+                 args=None,  # type: Optional[List[str]]
+                 name=None,  # type: Optional[str]
+                 delete=False,  # type bool
+                 timeout=None  # type: Optional[float]
+                 ):
+        # type: (...) -> None
         """
         :type script: ``str``
         :keyword script: Contents of the script to run.
@@ -154,6 +160,9 @@ class ScriptDeployment(Deployment):
 
         :type delete: ``bool``
         :keyword delete: Whether to delete the script on completion.
+
+        :param timeout: Optional run timeout for this command.
+        :type timeout: ``float``
         """
         script = self._get_string_value(argument_name='script',
                                         argument_value=script)
@@ -164,6 +173,7 @@ class ScriptDeployment(Deployment):
         self.stderr = None  # type: Optional[str]
         self.exit_status = None  # type: Optional[int]
         self.delete = delete
+        self.timeout = timeout
         self.name = name  # type: Optional[str]
 
         if self.name is None:
@@ -202,7 +212,8 @@ class ScriptDeployment(Deployment):
         else:
             cmd = name
 
-        self.stdout, self.stderr, self.exit_status = client.run(cmd)
+        self.stdout, self.stderr, self.exit_status = \
+            client.run(cmd, timeout=self.timeout)
 
         if self.delete:
             client.delete(self.name)
@@ -234,8 +245,14 @@ class ScriptFileDeployment(ScriptDeployment):
     the script content.
     """
 
-    def __init__(self, script_file, args=None, name=None, delete=False):
-        # type: (str, Optional[List[str]], Optional[str], bool) -> None
+    def __init__(self,
+                 script_file,  # type: str
+                 args=None,  # type: Optional[List[str]]
+                 name=None,  # type: Optional[str]
+                 delete=False,  # type bool
+                 timeout=None  # type: Optional[float]
+                 ):
+        # type: (...) -> None
         """
         :type script_file: ``str``
         :keyword script_file: Path to a file containing the script to run.
@@ -251,6 +268,9 @@ class ScriptFileDeployment(ScriptDeployment):
 
         :type delete: ``bool``
         :keyword delete: Whether to delete the script on completion.
+
+        :param timeout: Optional run timeout for this command.
+        :type timeout: ``float``
         """
         with open(script_file, 'rb') as fp:
             content = fp.read()  # type: Union[bytes, str]
@@ -262,7 +282,8 @@ class ScriptFileDeployment(ScriptDeployment):
         super(ScriptFileDeployment, self).__init__(script=content,
                                                    args=args,
                                                    name=name,
-                                                   delete=delete)
+                                                   delete=delete,
+                                                   timeout=timeout)
 
 
 class MultiStepDeployment(Deployment):
diff --git a/libcloud/compute/ssh.py b/libcloud/compute/ssh.py
index 887a4e1..0c65abf 100644
--- a/libcloud/compute/ssh.py
+++ b/libcloud/compute/ssh.py
@@ -178,8 +178,8 @@ class BaseSSHClient(object):
         raise NotImplementedError(
             'delete not implemented for this ssh client')
 
-    def run(self, cmd):
-        # type: (str) -> Tuple[str, str, int]
+    def run(self, cmd, timeout=None):
+        # type: (str, Optional[float]) -> Tuple[str, str, int]
         """
         Run a command on a remote node.
 
@@ -616,7 +616,7 @@ class ShellOutSSHClient(BaseSSHClient):
         """
         return True
 
-    def run(self, cmd):
+    def run(self, cmd, timeout=None):
         return self._run_remote_shell_command([cmd])
 
     def put(self, path, contents=None, chmod=None, mode='w'):
diff --git a/libcloud/test/compute/test_deployment.py b/libcloud/test/compute/test_deployment.py
index 93d2d2f..5b96783 100644
--- a/libcloud/test/compute/test_deployment.py
+++ b/libcloud/test/compute/test_deployment.py
@@ -58,15 +58,19 @@ class MockDeployment(Deployment):
 
 class MockClient(BaseSSHClient):
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, throw_on_timeout=False, *args, **kwargs):
         self.stdout = ''
         self.stderr = ''
         self.exit_status = 0
+        self.throw_on_timeout = throw_on_timeout
 
     def put(self, path, contents, chmod=755, mode='w'):
         return contents
 
-    def run(self, name):
+    def run(self, cmd, timeout=None):
+        if self.throw_on_timeout and timeout is not None:
+            raise ValueError("timeout")
+
         return self.stdout, self.stderr, self.exit_status
 
     def delete(self, name):
@@ -118,14 +122,25 @@ class DeploymentTests(unittest.TestCase):
         sd2 = ScriptDeployment(script='foobar', delete=False)
         sd3 = ScriptDeployment(
             script='foobar', delete=False, name='foobarname')
+        sd4 = ScriptDeployment(
+            script='foobar', delete=False, name='foobarname', timeout=10)
 
         self.assertTrue(sd1.name.find('deployment') != '1')
         self.assertEqual(sd3.name, 'foobarname')
+        self.assertEqual(sd3.timeout, None)
+        self.assertEqual(sd4.timeout, 10)
 
         self.assertEqual(self.node, sd1.run(node=self.node,
                                             client=MockClient(hostname='localhost')))
         self.assertEqual(self.node, sd2.run(node=self.node,
                                             client=MockClient(hostname='localhost')))
+        self.assertEqual(self.node, sd3.run(node=self.node,
+                                            client=MockClient(hostname='localhost')))
+
+        assertRaisesRegex(self, ValueError, 'timeout', sd4.run,
+                          node=self.node,
+                          client=MockClient(hostname='localhost',
+                                            throw_on_timeout=True))
 
     def test_script_file_deployment(self):
         file_path = os.path.abspath(__file__)
@@ -137,6 +152,10 @@ class DeploymentTests(unittest.TestCase):
 
         sfd1 = ScriptFileDeployment(script_file=file_path)
         self.assertEqual(sfd1.script, content)
+        self.assertEqual(sfd1.timeout, None)
+
+        sfd2 = ScriptFileDeployment(script_file=file_path, timeout=20)
+        self.assertEqual(sfd2.timeout, 20)
 
     def test_script_deployment_relative_path(self):
         client = Mock()
@@ -146,7 +165,7 @@ class DeploymentTests(unittest.TestCase):
         sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
         sd.run(self.node, client)
 
-        client.run.assert_called_once_with('/home/ubuntu/relative.sh')
+        client.run.assert_called_once_with('/home/ubuntu/relative.sh', timeout=None)
 
     def test_script_deployment_absolute_path(self):
         client = Mock()
@@ -156,7 +175,7 @@ class DeploymentTests(unittest.TestCase):
         sd = ScriptDeployment(script='echo "foo"', name='/root/relative.sh')
         sd.run(self.node, client)
 
-        client.run.assert_called_once_with('/root/relative.sh')
+        client.run.assert_called_once_with('/root/relative.sh', timeout=None)
 
     def test_script_deployment_with_arguments(self):
         client = Mock()
@@ -169,7 +188,7 @@ class DeploymentTests(unittest.TestCase):
         sd.run(self.node, client)
 
         expected = '/root/relative.sh arg1 arg2 --option1=test'
-        client.run.assert_called_once_with(expected)
+        client.run.assert_called_once_with(expected, timeout=None)
 
         client.reset_mock()
 
@@ -179,7 +198,7 @@ class DeploymentTests(unittest.TestCase):
         sd.run(self.node, client)
 
         expected = '/root/relative.sh'
-        client.run.assert_called_once_with(expected)
+        client.run.assert_called_once_with(expected, timeout=None)
 
     def test_script_file_deployment_with_arguments(self):
         file_path = os.path.abspath(__file__)
@@ -194,7 +213,7 @@ class DeploymentTests(unittest.TestCase):
         sfd.run(self.node, client)
 
         expected = '/root/relative.sh arg1 arg2 --option1=test option2'
-        client.run.assert_called_once_with(expected)
+        client.run.assert_called_once_with(expected, timeout=None)
 
     def test_script_deployment_and_sshkey_deployment_argument_types(self):
         class FileObject(object):
@@ -479,6 +498,7 @@ class DeploymentTests(unittest.TestCase):
         # the arguments
         global call_count
         call_count = 0
+
         def create_node(name, image, size, ex_custom_arg_1, ex_custom_arg_2,
                         ex_foo=None, auth=None, **kwargs):
             global call_count