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
=== modified file 'britney_proxy/britney/process_results.py'
--- britney_proxy/britney/process_results.py 2014-10-30 17:30:49 +0000
+++ britney_proxy/britney/process_results.py 2014-11-19 12:34:02 +0000
@@ -51,11 +51,7 @@
51# container. This won't scale but we'll revisit the layout when we get a better51# container. This won't scale but we'll revisit the layout when we get a better
52# understanding of the britney needs (and numbers !) -- vila 2014-10-0352# understanding of the britney needs (and numbers !) -- vila 2014-10-03
53class BritneyDataStore(data_store.DataStore):53class BritneyDataStore(data_store.DataStore):
5454 pass
55 def file_name(self, filename):
56 # Allow paths to be used as object names in the container by overriding
57 # the filtering in the base class.
58 return filename
5955
6056
61class BritneyResultHandler(object):57class BritneyResultHandler(object):
6258
=== modified file 'ci-utils/ci_utils/data_store.py'
--- ci-utils/ci_utils/data_store.py 2014-10-07 10:04:01 +0000
+++ ci-utils/ci_utils/data_store.py 2014-11-19 12:34:02 +0000
@@ -93,6 +93,21 @@
9393
94 return self.file_path(filename)94 return self.file_path(filename)
9595
96 def put_dir(self, src_dir_name, dst_dir_name):
97 # Copy files from under 'src_dir_name' to 'dst_dir_name'.
98 src_root = os.path.abspath(src_dir_name)
99 prefix_len = len(src_root) + 1
100 uploaded = []
101 for root, _, files in os.walk(src_root):
102 for name in files:
103 src = os.path.normpath(os.path.join(root, name))
104 # Replace dir_path by dst_dir_name to get the expected names
105 dst = os.path.join(dst_dir_name, src[prefix_len:])
106 with open(src) as f:
107 self.put_file(dst, f)
108 uploaded.append(dst)
109 return uploaded
110
96 def get_file(self, filename):111 def get_file(self, filename):
97 self.ensure_swift_client()112 self.ensure_swift_client()
98 name = self.file_name(filename)113 name = self.file_name(filename)
@@ -136,6 +151,9 @@
136 self.client.delete_object(self.container_id,151 self.client.delete_object(self.container_id,
137 obj=f['name'])152 obj=f['name'])
138 except ClientException as e:153 except ClientException as e:
154 if e.http_status == 404:
155 # Can happen when we retry
156 continue
139 raise DataStoreException(157 raise DataStoreException(
140 "Failed to delete file: {}".format(e)158 "Failed to delete file: {}".format(e)
141 )159 )
@@ -144,7 +162,9 @@
144 "Failed to get container: {}".format(e)162 "Failed to get container: {}".format(e)
145 )163 )
146164
147 def delete(self, recursive=False):165 # Tries 300 times, sleeping 2 secs: takes 5 minutes max, measure
166 # inconsistency window with a 2s resolution.
167 def delete(self, recursive=False, retries=300):
148 self.ensure_swift_client()168 self.ensure_swift_client()
149 try:169 try:
150 if recursive:170 if recursive:
@@ -152,9 +172,14 @@
152172
153 self.client.delete_container(self.container_id)173 self.client.delete_container(self.container_id)
154 except ClientException as e:174 except ClientException as e:
155 raise DataStoreException(175 if retries and e.http_status == 409:
156 "Failed to delete container: {}".format(e)176 import time
157 )177 time.sleep(2)
178 self.delete(recursive, retries=retries - 1)
179 elif e.http_status != 404:
180 raise DataStoreException(
181 "Failed to delete container (retries: {}): {}".format(
182 retries, e))
158183
159 def file_name(self, filename):184 def file_name(self, filename):
160 """Returns the file name that is used inside the data store.185 """Returns the file name that is used inside the data store.
@@ -162,7 +187,7 @@
162 :param filename: The file name used to build the object name inside the187 :param filename: The file name used to build the object name inside the
163 container.188 container.
164 """189 """
165 return os.path.basename(filename)190 return filename
166191
167 def file_path(self, filename):192 def file_path(self, filename):
168 name = self.file_name(filename)193 name = self.file_name(filename)
169194
=== modified file 'ci-utils/ci_utils/testing/fixtures.py'
--- ci-utils/ci_utils/testing/fixtures.py 2014-10-29 11:42:52 +0000
+++ ci-utils/ci_utils/testing/fixtures.py 2014-11-19 12:34:02 +0000
@@ -16,8 +16,10 @@
16import errno16import errno
17import os17import os
1818
19
19from ucitests import fixtures20from ucitests import fixtures
2021
22
21from ci_utils import data_store23from ci_utils import data_store
22from ci_utils.json_status import LAST_REVISION24from ci_utils.json_status import LAST_REVISION
2325
@@ -55,7 +57,15 @@
55 os.mkdir(self.container_id)57 os.mkdir(self.container_id)
5658
57 def list_files(self):59 def list_files(self):
58 return sorted(os.listdir(self.container_id))60 files = []
61 prefix_len = len(self.container_id) + 1
62 for root, dirs, fnames in os.walk(self.container_id):
63 # To mimick swift, We ignore the directories
64 for fname in fnames:
65 # Keep only the path relative to the container
66 relpath = os.path.join(root, fname)[prefix_len:]
67 files.append(relpath)
68 return sorted(files)
5969
60 def put_file(self, filename, contents, content_type=None):70 def put_file(self, filename, contents, content_type=None):
61 if os.sep in filename:71 if os.sep in filename:
@@ -70,7 +80,13 @@
70 raise80 raise
71 full_path = os.path.join(self.container_id, filename)81 full_path = os.path.join(self.container_id, filename)
72 with open(full_path, 'w') as f:82 with open(full_path, 'w') as f:
73 f.write(contents)83 # Like swift, we accept file and strings for 'contents'
84 if getattr(contents, 'read', None) is not None:
85 # File handle
86 f.write(contents.read())
87 else:
88 # string
89 f.write(contents)
74 return self.file_path(filename)90 return self.file_path(filename)
7591
76 def get_file(self, filename):92 def get_file(self, filename):
7793
=== modified file 'ci-utils/ci_utils/tests/test_data_store.py'
--- ci-utils/ci_utils/tests/test_data_store.py 2014-10-07 10:04:01 +0000
+++ ci-utils/ci_utils/tests/test_data_store.py 2014-11-19 12:34:02 +0000
@@ -35,23 +35,5 @@
35 data_store.DataStore.validate_auth_config({})35 data_store.DataStore.validate_auth_config({})
3636
3737
38class TestDataStoreFileName(unittest.TestCase):
39
40 def setUp(self):
41 super(TestDataStoreFileName, self).setUp()
42 # We won't use any method that requires the auth config so we don't
43 # provide any.
44 self.ds = data_store.DataStore('foo', None)
45
46 def test_abspath(self):
47 self.assertEquals('foo', self.ds.file_name('/baz/bar/foo'))
48
49 def test_relpath(self):
50 self.assertEquals('foo', self.ds.file_name('../baz/bar/foo'))
51
52 def test_basename(self):
53 self.assertEquals('foo', self.ds.file_name('foo'))
54
55
56if __name__ == "__main__":38if __name__ == "__main__":
57 unittest.main()39 unittest.main()
5840
=== modified file 'ci-utils/ci_utils/tests/test_fixtures.py'
--- ci-utils/ci_utils/tests/test_fixtures.py 2014-10-29 13:55:35 +0000
+++ ci-utils/ci_utils/tests/test_fixtures.py 2014-11-19 12:34:02 +0000
@@ -98,6 +98,38 @@
98 self.assertEqual(errno.ENOTEMPTY, cm.exception.args[0])98 self.assertEqual(errno.ENOTEMPTY, cm.exception.args[0])
9999
100100
101class TestPutDir(unittest.TestCase):
102
103 def setUp(self):
104 super(TestPutDir, self).setUp()
105 uci_fixtures.set_uniq_cwd(self)
106 self.ds_path = os.path.join(self.uniq_dir, 'data_store')
107 self.ds = fixtures.FakeDataStore(self.ds_path, None)
108 self.src_dir = os.path.join(self.uniq_dir, 'src')
109 os.mkdir(self.src_dir)
110
111 def test_put_dir_empty(self):
112 self.ds.put_dir(self.src_dir, 'dst')
113 self.assertEqual([], self.ds.list_files())
114
115 def test_one_file(self):
116 uci_fixtures.build_tree('''file: src/foo
117foo content
118''')
119 self.ds.put_dir(self.src_dir, 'dst')
120 self.assertEqual(['dst/foo'], self.ds.list_files())
121
122 def test_tree(self):
123 uci_fixtures.build_tree('''file: src/foo
124foo content
125dir: src/dir
126file: src/dir/bar
127bar content
128''')
129 self.ds.put_dir(self.src_dir, 'dst')
130 self.assertEqual(['dst/dir/bar', 'dst/foo'], self.ds.list_files())
131
132
101class TestUsingFakeDataStore(unittest.TestCase):133class TestUsingFakeDataStore(unittest.TestCase):
102134
103 def setUp(self):135 def setUp(self):
104136
=== modified file 'tests/test_data_store.py'
--- tests/test_data_store.py 2014-10-08 09:54:56 +0000
+++ tests/test_data_store.py 2014-11-19 12:34:02 +0000
@@ -21,6 +21,8 @@
21import yaml21import yaml
2222
2323
24from ucitests import fixtures
25
24HERE = os.path.abspath(os.path.dirname(__file__))26HERE = os.path.abspath(os.path.dirname(__file__))
2527
26sys.path.append(os.path.join(HERE, '..', 'ci-utils'))28sys.path.append(os.path.join(HERE, '..', 'ci-utils'))
@@ -106,6 +108,45 @@
106 self.assertEqual(200, resp.getcode(), resp.read())108 self.assertEqual(200, resp.getcode(), resp.read())
107109
108110
111class TestPutDir(unittest.TestCase):
112
113 def setUp(self):
114 super(TestPutDir, self).setUp()
115 default_config = os.path.join(HERE, '..', 'unit_config')
116 if not os.path.exists(default_config):
117 self.skipTest('{} is not available'.format(default_config))
118 with open(default_config) as fp:
119 self.auth_config = yaml.safe_load(fp)
120
121 self.ds = data_store.DataStore(self.id(), auth_config=self.auth_config)
122 self.addCleanup(self.ds.delete, recursive=True)
123 fixtures.set_uniq_cwd(self)
124 self.src_dir = os.path.join(self.uniq_dir, 'src')
125 os.mkdir(self.src_dir)
126
127 def assertUploaded(self, expected, dst):
128 uploaded = self.ds.put_dir(self.src_dir, dst)
129 self.assertEqual(expected, sorted(uploaded))
130
131 def test_put_dir_empty(self):
132 self.assertUploaded([], 'dst')
133
134 def test_one_file(self):
135 fixtures.build_tree('''file: src/foo
136foo content
137''')
138 self.assertUploaded(['dst/foo'], 'dst')
139
140 def test_tree(self):
141 fixtures.build_tree('''file: src/foo
142foo content
143dir: src/dir
144file: src/dir/bar
145bar content
146''')
147 self.assertUploaded(['dst/dir/bar', 'dst/foo'], 'dst')
148
149
109class TestDataStoreBadConfigs(unittest.TestCase):150class TestDataStoreBadConfigs(unittest.TestCase):
110151
111 def setUp(self):152 def setUp(self):

Subscribers

People subscribed via source and target branches