Merge lp:~abentley/workspace-runner/scp-retry into lp:workspace-runner

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 19
Proposed branch: lp:~abentley/workspace-runner/scp-retry
Merge into: lp:workspace-runner
Diff against target: 128 lines (+38/-21)
2 files modified
workspace_runner/__init__.py (+22/-17)
workspace_runner/tests/__init__.py (+16/-4)
To merge this branch: bzr merge lp:~abentley/workspace-runner/scp-retry
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+263007@code.launchpad.net

Commit message

Retry scp on retcode 1.

Description of the change

This branch updates the workspace runner to retry scp when it exits with status 1.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'workspace_runner/__init__.py'
--- workspace_runner/__init__.py 2015-06-25 15:06:58 +0000
+++ workspace_runner/__init__.py 2015-06-25 17:06:42 +0000
@@ -38,23 +38,28 @@
38 return cls(ssh_connection, tempdir)38 return cls(ssh_connection, tempdir)
3939
4040
41def retry_ssh(func):41def retry_ssh(retcodes=(255,)):
42 """Decorator that retries a function if it looks like SSH failed.42 """Return a decorator that retries a function if it looks like SSH failed.
4343
44 SSH connection failures are a CalledProcessError with returncode 255.44 SSH connection failures are a CalledProcessError with returncode 255.
45
46 Custom retcodes can be specified for operations that have different
47 failures, like scp, which returns 1.
45 """48 """
46 def decorated(*args, **kwargs):49 def decorator(func):
47 for x in range(3):50 def decorated(*args, **kwargs):
48 if x != 0:51 for x in range(3):
49 logging.info('Retrying due to ssh failure.')52 if x != 0:
50 try:53 logging.info('Retrying due to ssh failure.')
51 return func(*args, **kwargs)54 try:
52 except subprocess.CalledProcessError as e:55 return func(*args, **kwargs)
53 if e.returncode != 255:56 except subprocess.CalledProcessError as e:
54 raise57 if e.returncode not in retcodes:
55 else:58 raise
56 raise59 else:
57 return decorated60 raise
61 return decorated
62 return decorator
5863
5964
60class SSHConnection:65class SSHConnection:
@@ -81,7 +86,7 @@
81 command = ['ssh'] + self.get_ssh_options() + [self.host] + args86 command = ['ssh'] + self.get_ssh_options() + [self.host] + args
82 return command87 return command
8388
84 @retry_ssh89 @retry_ssh()
85 def call_with_input(self, args, stdin):90 def call_with_input(self, args, stdin):
86 """Run a command remotely, accepting stdin."""91 """Run a command remotely, accepting stdin."""
87 remote_command = ' '.join(quote(x) for x in args)92 remote_command = ' '.join(quote(x) for x in args)
@@ -96,7 +101,7 @@
96 retcode, command, output=output)101 retcode, command, output=output)
97 return output[0]102 return output[0]
98103
99 @retry_ssh104 @retry_ssh((1, 255))
100 def scp_to_remote(self, sources, dest_path):105 def scp_to_remote(self, sources, dest_path):
101 """Copy files to the specified path on the remote host."""106 """Copy files to the specified path on the remote host."""
102 dest_loc = '{}:{}/'.format(self.host, quote(dest_path))107 dest_loc = '{}:{}/'.format(self.host, quote(dest_path))
@@ -131,7 +136,7 @@
131 shutil.rmtree(sys.argv[1])136 shutil.rmtree(sys.argv[1])
132 """), [self.workspace])137 """), [self.workspace])
133138
134 @retry_ssh139 @retry_ssh()
135 def run(self, args, out_file):140 def run(self, args, out_file):
136 """Run a command in the workspace."""141 """Run a command in the workspace."""
137 command = self.ssh_connection.get_ssh_cmd(142 command = self.ssh_connection.get_ssh_cmd(
138143
=== modified file 'workspace_runner/tests/__init__.py'
--- workspace_runner/tests/__init__.py 2015-06-25 15:06:58 +0000
+++ workspace_runner/tests/__init__.py 2015-06-25 17:06:42 +0000
@@ -130,7 +130,7 @@
130 def test_retry_until_failure(self):130 def test_retry_until_failure(self):
131 attempts = []131 attempts = []
132132
133 @retry_ssh133 @retry_ssh()
134 def fail_repeatedly():134 def fail_repeatedly():
135 attempts.append('attempt')135 attempts.append('attempt')
136 raise subprocess.CalledProcessError(255, [])136 raise subprocess.CalledProcessError(255, [])
@@ -142,7 +142,7 @@
142 def test_immediate_success(self):142 def test_immediate_success(self):
143 attempts = []143 attempts = []
144144
145 @retry_ssh145 @retry_ssh()
146 def succeed():146 def succeed():
147 attempts.append('success')147 attempts.append('success')
148 return 'success'148 return 'success'
@@ -153,7 +153,7 @@
153 def test_no_retry_non_ssh(self):153 def test_no_retry_non_ssh(self):
154 attempts = []154 attempts = []
155155
156 @retry_ssh156 @retry_ssh()
157 def fail_repeatedly():157 def fail_repeatedly():
158 attempts.append('attempt')158 attempts.append('attempt')
159 raise subprocess.CalledProcessError(254, [])159 raise subprocess.CalledProcessError(254, [])
@@ -162,10 +162,22 @@
162 fail_repeatedly()162 fail_repeatedly()
163 self.assertEqual(len(attempts), 1)163 self.assertEqual(len(attempts), 1)
164164
165 def test_custom_retcodes(self):
166 attempts = []
167
168 @retry_ssh((254,))
169 def fail_repeatedly():
170 attempts.append('attempt')
171 raise subprocess.CalledProcessError(254, [])
172
173 with self.assertRaises(subprocess.CalledProcessError):
174 fail_repeatedly()
175 self.assertEqual(len(attempts), 3)
176
165 def test_nearly_fail(self):177 def test_nearly_fail(self):
166 attempts = []178 attempts = []
167179
168 @retry_ssh180 @retry_ssh()
169 def nearly_fail():181 def nearly_fail():
170 attempts.append('attempt')182 attempts.append('attempt')
171 if len(attempts) == 3:183 if len(attempts) == 3:

Subscribers

People subscribed via source and target branches