Merge lp:~sinzui/juju-release-tools/retry-swift into lp:juju-release-tools

Proposed by Curtis Hovey
Status: Merged
Merged at revision: 191
Proposed branch: lp:~sinzui/juju-release-tools/retry-swift
Merge into: lp:juju-release-tools
Diff against target: 133 lines (+100/-1)
2 files modified
swift_sync.py (+17/-1)
tests/test_swift_sync.py (+83/-0)
To merge this branch: bzr merge lp:~sinzui/juju-release-tools/retry-swift
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Review via email: mp+261672@code.launchpad.net

Description of the change

Retry swift uploads.

The publish-weekly job has failed two weeks in a row because of swift. Once for canonistack and once for hp. Modern swiftclient supports --retries, but the older version used by precise does not. This branch add a retry rule to make sync_swift more robust.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Looks good.

I presume we're fine with maybe retrying three times per file? Not sure how many swift calls we might reach if the api is just down or something, maybe enough to hit rate limits?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'swift_sync.py'
2--- swift_sync.py 2014-10-03 03:53:22 +0000
3+++ swift_sync.py 2015-06-10 22:27:48 +0000
4@@ -11,6 +11,9 @@
5 import urllib2
6
7
8+MAX_UPLOAD_ATTEMPTS = 3
9+
10+
11 def get_account():
12 cmd = ['swift', 'stat']
13 output = subprocess.check_output(cmd)
14@@ -37,6 +40,7 @@
15 def upload_changes(args, remote_files):
16 container_path = os.path.join(args.container, args.path)
17 count = 0
18+ uploaded_files = []
19 for file_name in args.files:
20 local_path = os.path.join(args.path, file_name)
21 remote_file = remote_files.get(local_path)
22@@ -61,10 +65,22 @@
23 print("Uploading {0}/{1}".format(args.container, local_path))
24 cmd = ['swift', 'upload', container_path, file_name]
25 if not args.dry_run:
26- output = subprocess.check_output(cmd)
27+ uploaded = False
28+ attempt = 1
29+ while not uploaded and attempt <= MAX_UPLOAD_ATTEMPTS:
30+ # python-swiftclient on precise doesn't support --retry.
31+ try:
32+ output = subprocess.check_output(cmd)
33+ uploaded = True
34+ except subprocess.CalledProcessError:
35+ attempt += 1
36+ if attempt > MAX_UPLOAD_ATTEMPTS:
37+ raise
38 print(' '.join(cmd))
39 print(output)
40+ uploaded_files.append(output)
41 print('Uploaded {0} files'.format(count))
42+ return uploaded_files
43
44
45 def main():
46
47=== added file 'tests/test_swift_sync.py'
48--- tests/test_swift_sync.py 1970-01-01 00:00:00 +0000
49+++ tests/test_swift_sync.py 2015-06-10 22:27:48 +0000
50@@ -0,0 +1,83 @@
51+from argparse import Namespace
52+import hashlib
53+from mock import patch
54+import os
55+from subprocess import CalledProcessError
56+from unittest import TestCase
57+
58+from swift_sync import (
59+ upload_changes,
60+)
61+from utils import (
62+ temp_dir,
63+)
64+
65+
66+def make_local_files(base, files):
67+ local_files = []
68+ for name in files:
69+ file_path = os.path.join(base, name)
70+ local_files.append(file_path)
71+ with open(file_path, 'w') as f:
72+ f.write(name)
73+ return local_files
74+
75+
76+class SwiftSyncTestCase(TestCase):
77+
78+ def test_upload_changes(self):
79+ # Only new and changed files are uploaded.
80+ md5 = hashlib.md5()
81+ md5.update('one')
82+ one_hash = md5.hexdigest()
83+ with temp_dir() as base:
84+ remote_name = os.path.join(base, 'one')
85+ remote_files = {
86+ remote_name: {'name': remote_name, 'hash': one_hash}
87+ }
88+ local_files = make_local_files(base, ['one', 'two'])
89+ args = Namespace(
90+ container='foo', path=base, files=local_files,
91+ verbose=False, dry_run=False)
92+ with patch('subprocess.check_output', autospec=True,
93+ return_value='two') as co_mock:
94+ uploaded_files = upload_changes(args, remote_files)
95+ self.assertEqual(['two'], uploaded_files)
96+ co_mock.assert_called_once_with(
97+ ['swift', 'upload', base, os.path.join(base, 'two')])
98+
99+ def test_upload_changes_reties(self):
100+ with temp_dir() as base:
101+ remote_files = {}
102+ local_files = make_local_files(base, ['one'])
103+ args = Namespace(
104+ container='foo', path=base, files=local_files,
105+ verbose=False, dry_run=False)
106+ outputs = [
107+ CalledProcessError(1, 'a'),
108+ CalledProcessError(2, 'b'),
109+ 'one']
110+ with patch('subprocess.check_output', autospec=True,
111+ side_effect=outputs) as co_mock:
112+ uploaded_files = upload_changes(args, remote_files)
113+ self.assertEqual(['one'], uploaded_files)
114+ self.assertEqual(3, co_mock.call_count)
115+ co_mock.assert_called_with(
116+ ['swift', 'upload', base, os.path.join(base, 'one')])
117+
118+ def test_upload_changes_error(self):
119+ with temp_dir() as base:
120+ remote_files = {}
121+ local_files = make_local_files(base, ['one'])
122+ args = Namespace(
123+ container='foo', path=base, files=local_files,
124+ verbose=False, dry_run=False)
125+ outputs = [
126+ CalledProcessError(1, 'a'),
127+ CalledProcessError(2, 'b'),
128+ CalledProcessError(3, 'c')]
129+ with patch('subprocess.check_output', autospec=True,
130+ side_effect=outputs) as co_mock:
131+ with self.assertRaises(CalledProcessError):
132+ upload_changes(args, remote_files)
133+ self.assertEqual(3, co_mock.call_count)

Subscribers

People subscribed via source and target branches