Merge lp:~vila/uci-engine/stores-put-dir into lp:uci-engine

Proposed by Vincent Ladeuil
Status: Superseded
Proposed branch: lp:~vila/uci-engine/stores-put-dir
Merge into: lp:uci-engine
Diff against target: 267 lines (+122/-30)
6 files modified
britney_proxy/britney/process_results.py (+1/-5)
ci-utils/ci_utils/data_store.py (+30/-5)
ci-utils/ci_utils/testing/fixtures.py (+18/-2)
ci-utils/ci_utils/tests/test_data_store.py (+0/-18)
ci-utils/ci_utils/tests/test_fixtures.py (+32/-0)
tests/test_data_store.py (+41/-0)
To merge this branch: bzr merge lp:~vila/uci-engine/stores-put-dir
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Canonical CI Engineering Pending
Review via email: mp+242176@code.launchpad.net

This proposal has been superseded by a proposal from 2014-11-19.

Commit message

Upload a local directory into a swift container

Description of the change

/!\ Not ready for landing.

This implements DataStore.put_dir() in terms of DataStore.put_file() to
upload the content of a local hierarchy in a swift container (uci-britney
requirement).

I ran into swift eventually consistent model here and addressed the
container deletion issues in ad-hoc way that requires a separate MP.

I'll update *this* MP once the above issue is addressed.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:904
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1724/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1724/rebuild

review: Needs Fixing (continuous-integration)
lp:~vila/uci-engine/stores-put-dir updated
905. By Vincent Ladeuil

Fix pep8/pyflakes issues.

906. By Vincent Ladeuil

Raise the overall timeout for swift container deletion as 2mins was almost reached already.

$ ./run-tests tests.test_data_store.TestPutDir ^ci-utils.*test_fixtures test_style

is the proper way to test here, failures in
https://code.launchpad.net/~vila/uci-engine/stores-put-dir/+merge/242176 were caused by using ^ci_utils.*test_fixtures instead of ^ci-utils.*test_fixtures.

Ran 53 tests in 30.065s
OK

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:906
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1725/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1725/rebuild

review: Approve (continuous-integration)
lp:~vila/uci-engine/stores-put-dir updated
907. By Vincent Ladeuil

Meh, lexicographical order please.

908. By Vincent Ladeuil

Merge reliable container deletion fix

909. By Vincent Ladeuil

Merge pre-requisite again

910. By Vincent Ladeuil

Merge pre-requisite again

911. By Vincent Ladeuil

Refactor ci_utils.test_fixtures.TestPutDir based on tests.test_data_store.TestPutdir to the differences are easier to spot.

912. By Vincent Ladeuil

Clarify why ci_utils.tests.test_fixtures.TestFakeDataStorePutDir resemble tests.test_data_store.TestDataStorePutDir and where they differ.

Unmerged revisions

912. By Vincent Ladeuil

Clarify why ci_utils.tests.test_fixtures.TestFakeDataStorePutDir resemble tests.test_data_store.TestDataStorePutDir and where they differ.

911. By Vincent Ladeuil

Refactor ci_utils.test_fixtures.TestPutDir based on tests.test_data_store.TestPutdir to the differences are easier to spot.

910. By Vincent Ladeuil

Merge pre-requisite again

909. By Vincent Ladeuil

Merge pre-requisite again

908. By Vincent Ladeuil

Merge reliable container deletion fix

907. By Vincent Ladeuil

Meh, lexicographical order please.

906. By Vincent Ladeuil

Raise the overall timeout for swift container deletion as 2mins was almost reached already.

$ ./run-tests tests.test_data_store.TestPutDir ^ci-utils.*test_fixtures test_style

is the proper way to test here, failures in
https://code.launchpad.net/~vila/uci-engine/stores-put-dir/+merge/242176 were caused by using ^ci_utils.*test_fixtures instead of ^ci-utils.*test_fixtures.

Ran 53 tests in 30.065s
OK

905. By Vincent Ladeuil

Fix pep8/pyflakes issues.

904. By Vincent Ladeuil

Quick and dirty handling of container deletion: retry/sleep on 409, accept 404 when swift finally deleted the container while we were sleeping.

903. By Vincent Ladeuil

Make the tests pass by returning which files where sent to swift.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'britney_proxy/britney/process_results.py'
2--- britney_proxy/britney/process_results.py 2014-10-30 17:30:49 +0000
3+++ britney_proxy/britney/process_results.py 2014-11-19 12:34:02 +0000
4@@ -51,11 +51,7 @@
5 # container. This won't scale but we'll revisit the layout when we get a better
6 # understanding of the britney needs (and numbers !) -- vila 2014-10-03
7 class BritneyDataStore(data_store.DataStore):
8-
9- def file_name(self, filename):
10- # Allow paths to be used as object names in the container by overriding
11- # the filtering in the base class.
12- return filename
13+ pass
14
15
16 class BritneyResultHandler(object):
17
18=== modified file 'ci-utils/ci_utils/data_store.py'
19--- ci-utils/ci_utils/data_store.py 2014-10-07 10:04:01 +0000
20+++ ci-utils/ci_utils/data_store.py 2014-11-19 12:34:02 +0000
21@@ -93,6 +93,21 @@
22
23 return self.file_path(filename)
24
25+ def put_dir(self, src_dir_name, dst_dir_name):
26+ # Copy files from under 'src_dir_name' to 'dst_dir_name'.
27+ src_root = os.path.abspath(src_dir_name)
28+ prefix_len = len(src_root) + 1
29+ uploaded = []
30+ for root, _, files in os.walk(src_root):
31+ for name in files:
32+ src = os.path.normpath(os.path.join(root, name))
33+ # Replace dir_path by dst_dir_name to get the expected names
34+ dst = os.path.join(dst_dir_name, src[prefix_len:])
35+ with open(src) as f:
36+ self.put_file(dst, f)
37+ uploaded.append(dst)
38+ return uploaded
39+
40 def get_file(self, filename):
41 self.ensure_swift_client()
42 name = self.file_name(filename)
43@@ -136,6 +151,9 @@
44 self.client.delete_object(self.container_id,
45 obj=f['name'])
46 except ClientException as e:
47+ if e.http_status == 404:
48+ # Can happen when we retry
49+ continue
50 raise DataStoreException(
51 "Failed to delete file: {}".format(e)
52 )
53@@ -144,7 +162,9 @@
54 "Failed to get container: {}".format(e)
55 )
56
57- def delete(self, recursive=False):
58+ # Tries 300 times, sleeping 2 secs: takes 5 minutes max, measure
59+ # inconsistency window with a 2s resolution.
60+ def delete(self, recursive=False, retries=300):
61 self.ensure_swift_client()
62 try:
63 if recursive:
64@@ -152,9 +172,14 @@
65
66 self.client.delete_container(self.container_id)
67 except ClientException as e:
68- raise DataStoreException(
69- "Failed to delete container: {}".format(e)
70- )
71+ if retries and e.http_status == 409:
72+ import time
73+ time.sleep(2)
74+ self.delete(recursive, retries=retries - 1)
75+ elif e.http_status != 404:
76+ raise DataStoreException(
77+ "Failed to delete container (retries: {}): {}".format(
78+ retries, e))
79
80 def file_name(self, filename):
81 """Returns the file name that is used inside the data store.
82@@ -162,7 +187,7 @@
83 :param filename: The file name used to build the object name inside the
84 container.
85 """
86- return os.path.basename(filename)
87+ return filename
88
89 def file_path(self, filename):
90 name = self.file_name(filename)
91
92=== modified file 'ci-utils/ci_utils/testing/fixtures.py'
93--- ci-utils/ci_utils/testing/fixtures.py 2014-10-29 11:42:52 +0000
94+++ ci-utils/ci_utils/testing/fixtures.py 2014-11-19 12:34:02 +0000
95@@ -16,8 +16,10 @@
96 import errno
97 import os
98
99+
100 from ucitests import fixtures
101
102+
103 from ci_utils import data_store
104 from ci_utils.json_status import LAST_REVISION
105
106@@ -55,7 +57,15 @@
107 os.mkdir(self.container_id)
108
109 def list_files(self):
110- return sorted(os.listdir(self.container_id))
111+ files = []
112+ prefix_len = len(self.container_id) + 1
113+ for root, dirs, fnames in os.walk(self.container_id):
114+ # To mimick swift, We ignore the directories
115+ for fname in fnames:
116+ # Keep only the path relative to the container
117+ relpath = os.path.join(root, fname)[prefix_len:]
118+ files.append(relpath)
119+ return sorted(files)
120
121 def put_file(self, filename, contents, content_type=None):
122 if os.sep in filename:
123@@ -70,7 +80,13 @@
124 raise
125 full_path = os.path.join(self.container_id, filename)
126 with open(full_path, 'w') as f:
127- f.write(contents)
128+ # Like swift, we accept file and strings for 'contents'
129+ if getattr(contents, 'read', None) is not None:
130+ # File handle
131+ f.write(contents.read())
132+ else:
133+ # string
134+ f.write(contents)
135 return self.file_path(filename)
136
137 def get_file(self, filename):
138
139=== modified file 'ci-utils/ci_utils/tests/test_data_store.py'
140--- ci-utils/ci_utils/tests/test_data_store.py 2014-10-07 10:04:01 +0000
141+++ ci-utils/ci_utils/tests/test_data_store.py 2014-11-19 12:34:02 +0000
142@@ -35,23 +35,5 @@
143 data_store.DataStore.validate_auth_config({})
144
145
146-class TestDataStoreFileName(unittest.TestCase):
147-
148- def setUp(self):
149- super(TestDataStoreFileName, self).setUp()
150- # We won't use any method that requires the auth config so we don't
151- # provide any.
152- self.ds = data_store.DataStore('foo', None)
153-
154- def test_abspath(self):
155- self.assertEquals('foo', self.ds.file_name('/baz/bar/foo'))
156-
157- def test_relpath(self):
158- self.assertEquals('foo', self.ds.file_name('../baz/bar/foo'))
159-
160- def test_basename(self):
161- self.assertEquals('foo', self.ds.file_name('foo'))
162-
163-
164 if __name__ == "__main__":
165 unittest.main()
166
167=== modified file 'ci-utils/ci_utils/tests/test_fixtures.py'
168--- ci-utils/ci_utils/tests/test_fixtures.py 2014-10-29 13:55:35 +0000
169+++ ci-utils/ci_utils/tests/test_fixtures.py 2014-11-19 12:34:02 +0000
170@@ -98,6 +98,38 @@
171 self.assertEqual(errno.ENOTEMPTY, cm.exception.args[0])
172
173
174+class TestPutDir(unittest.TestCase):
175+
176+ def setUp(self):
177+ super(TestPutDir, self).setUp()
178+ uci_fixtures.set_uniq_cwd(self)
179+ self.ds_path = os.path.join(self.uniq_dir, 'data_store')
180+ self.ds = fixtures.FakeDataStore(self.ds_path, None)
181+ self.src_dir = os.path.join(self.uniq_dir, 'src')
182+ os.mkdir(self.src_dir)
183+
184+ def test_put_dir_empty(self):
185+ self.ds.put_dir(self.src_dir, 'dst')
186+ self.assertEqual([], self.ds.list_files())
187+
188+ def test_one_file(self):
189+ uci_fixtures.build_tree('''file: src/foo
190+foo content
191+''')
192+ self.ds.put_dir(self.src_dir, 'dst')
193+ self.assertEqual(['dst/foo'], self.ds.list_files())
194+
195+ def test_tree(self):
196+ uci_fixtures.build_tree('''file: src/foo
197+foo content
198+dir: src/dir
199+file: src/dir/bar
200+bar content
201+''')
202+ self.ds.put_dir(self.src_dir, 'dst')
203+ self.assertEqual(['dst/dir/bar', 'dst/foo'], self.ds.list_files())
204+
205+
206 class TestUsingFakeDataStore(unittest.TestCase):
207
208 def setUp(self):
209
210=== modified file 'tests/test_data_store.py'
211--- tests/test_data_store.py 2014-10-08 09:54:56 +0000
212+++ tests/test_data_store.py 2014-11-19 12:34:02 +0000
213@@ -21,6 +21,8 @@
214 import yaml
215
216
217+from ucitests import fixtures
218+
219 HERE = os.path.abspath(os.path.dirname(__file__))
220
221 sys.path.append(os.path.join(HERE, '..', 'ci-utils'))
222@@ -106,6 +108,45 @@
223 self.assertEqual(200, resp.getcode(), resp.read())
224
225
226+class TestPutDir(unittest.TestCase):
227+
228+ def setUp(self):
229+ super(TestPutDir, self).setUp()
230+ default_config = os.path.join(HERE, '..', 'unit_config')
231+ if not os.path.exists(default_config):
232+ self.skipTest('{} is not available'.format(default_config))
233+ with open(default_config) as fp:
234+ self.auth_config = yaml.safe_load(fp)
235+
236+ self.ds = data_store.DataStore(self.id(), auth_config=self.auth_config)
237+ self.addCleanup(self.ds.delete, recursive=True)
238+ fixtures.set_uniq_cwd(self)
239+ self.src_dir = os.path.join(self.uniq_dir, 'src')
240+ os.mkdir(self.src_dir)
241+
242+ def assertUploaded(self, expected, dst):
243+ uploaded = self.ds.put_dir(self.src_dir, dst)
244+ self.assertEqual(expected, sorted(uploaded))
245+
246+ def test_put_dir_empty(self):
247+ self.assertUploaded([], 'dst')
248+
249+ def test_one_file(self):
250+ fixtures.build_tree('''file: src/foo
251+foo content
252+''')
253+ self.assertUploaded(['dst/foo'], 'dst')
254+
255+ def test_tree(self):
256+ fixtures.build_tree('''file: src/foo
257+foo content
258+dir: src/dir
259+file: src/dir/bar
260+bar content
261+''')
262+ self.assertUploaded(['dst/dir/bar', 'dst/foo'], 'dst')
263+
264+
265 class TestDataStoreBadConfigs(unittest.TestCase):
266
267 def setUp(self):

Subscribers

People subscribed via source and target branches