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 <[email protected]> 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
