Merge lp:~robru/cupstream2distro/new-config-class into lp:cupstream2distro
- new-config-class
- Merge into trunk
Proposed by
Robert Bruce Park
Status: | Merged |
---|---|
Merged at revision: | 1346 |
Proposed branch: | lp:~robru/cupstream2distro/new-config-class |
Merge into: | lp:cupstream2distro |
Diff against target: |
694 lines (+116/-175) 11 files modified
Makefile (+1/-1) citrain/recipes/manager.py (+1/-3) citrain/setup_citrain.py (+7/-13) cupstream2distro/settings.py (+6/-50) cupstream2distro/silomanager.py (+34/-32) cupstream2distro/utils.py (+17/-2) tests/unit/test_recipe_manager.py (+4/-6) tests/unit/test_script_build.py (+1/-2) tests/unit/test_script_setup_citrain.py (+11/-24) tests/unit/test_silomanager.py (+28/-42) tests/unit/test_utils.py (+6/-0) |
To merge this branch: | bzr merge lp:~robru/cupstream2distro/new-config-class |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Bruce Park (community) | Approve | ||
Review via email: mp+283566@code.launchpad.net |
Commit message
New Config class for reading config from disk.
Description of the change
To post a comment you must log in.
Revision history for this message
Robert Bruce Park (robru) wrote : | # |
- 1345. By Robert Bruce Park
-
Rebase on trunk.
- 1346. By Robert Bruce Park
-
100% tests.
Revision history for this message
Robert Bruce Park (robru) wrote : | # |
works great in staging.
review:
Approve
- 1347. By Robert Bruce Park
-
Fix test in trusty.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2016-01-10 10:12:24 +0000 |
3 | +++ Makefile 2016-01-23 01:33:24 +0000 |
4 | @@ -7,7 +7,7 @@ |
5 | find -name '*.xml*' | xargs xmllint --noout |
6 | find -name '*.py' | xargs python3 -m pyflakes |
7 | find -name '*.py' | xargs python3 -m pep8 --ignore=E731 # Don't tell me what to do |
8 | - find -name '*.py' | xargs python3 -m pylint --persistent=no --disable attribute-defined-outside-init,broad-except,deprecated-method,fixme,import-error,invalid-name,no-init,no-member,no-self-use,no-value-for-parameter,protected-access,redefined-variable-type,star-args,too-few-public-methods,too-many-arguments,too-many-instance-attributes,too-many-public-methods,unbalanced-tuple-unpacking,unused-argument,unused-variable |
9 | + find -name '*.py' | xargs python3 -m pylint --persistent=no --disable attribute-defined-outside-init,broad-except,deprecated-method,fixme,import-error,invalid-name,no-init,no-member,no-self-use,no-value-for-parameter,protected-access,redefined-variable-type,star-args,too-few-public-methods,too-many-arguments,too-many-instance-attributes,too-many-public-methods,unbalanced-tuple-unpacking,unsubscriptable-object,unused-argument,unused-variable |
10 | |
11 | performance: |
12 | BEAT_THE_CLOCK=500 make check |
13 | |
14 | === modified file 'citrain/recipes/manager.py' |
15 | --- citrain/recipes/manager.py 2015-11-22 13:37:37 +0000 |
16 | +++ citrain/recipes/manager.py 2016-01-23 01:33:24 +0000 |
17 | @@ -28,9 +28,7 @@ |
18 | from citrain.recipes.secondary import Secondary |
19 | from citrain.recipes.sourcesync import SourceSync |
20 | from citrain.recipes.binarysync import BinarySync |
21 | -from cupstream2distro.settings import STABLE_OVERLAY_PPA |
22 | from cupstream2distro.utils import env, log_value_of |
23 | -from cupstream2distro.launchpadmanager import lp |
24 | from cupstream2distro.errors import CITrainError |
25 | |
26 | |
27 | @@ -83,11 +81,11 @@ |
28 | dest = self.silo_state.dest |
29 | ppa = self.silo_state.ppa |
30 | dual = self.silo_state.dual |
31 | + overlay = self.silo_state.stable_overlay |
32 | for Build, source_names in self.types.items(): |
33 | for name in sorted(source_names): |
34 | build = self.builds[name] = Build(name, series, dest, ppa) |
35 | if dual: |
36 | - overlay = lp.get_ppa(STABLE_OVERLAY_PPA) |
37 | secondary = Secondary if Build is Merge else Manual |
38 | second = secondary(name, dual, overlay, ppa, build) |
39 | self.builds['zzz_' + name] = second |
40 | |
41 | === modified file 'citrain/setup_citrain.py' |
42 | --- citrain/setup_citrain.py 2016-01-05 22:03:49 +0000 |
43 | +++ citrain/setup_citrain.py 2016-01-23 01:33:24 +0000 |
44 | @@ -33,17 +33,15 @@ |
45 | |
46 | from cupstream2distro.launchpadmanager import lp |
47 | from cupstream2distro.utils import ( |
48 | + Config, |
49 | log_value_of, |
50 | run_script, |
51 | suppress, |
52 | utf8_open, |
53 | ) |
54 | from cupstream2distro.settings import ( |
55 | - ALL_SILO_NAMES, |
56 | CITRAIN_BINDIR, |
57 | - PPA_TEAM, |
58 | ROOT_CU2D, |
59 | - SILO_NAME_LIST, |
60 | SILO_NAMES_FILE, |
61 | SILO_RSYNCDIR, |
62 | SILOS_DIR, |
63 | @@ -56,18 +54,15 @@ |
64 | |
65 | def discover_silos(): |
66 | """Query Launchpad to see how many silos are available.""" |
67 | - SILO_NAME_LIST.clear() |
68 | - while ALL_SILO_NAMES: |
69 | - ALL_SILO_NAMES.pop() |
70 | - for ppa in lp.people[PPA_TEAM].ppas: |
71 | + silonames = [] |
72 | + for ppa in lp.people[Config['ppa.team']].ppas: |
73 | dist = ppa.distribution_link.split('/')[-1] |
74 | siloname = '{}/{}'.format(dist, ppa.name) |
75 | if '/landing-' in siloname and '-deleted' not in siloname: |
76 | - SILO_NAME_LIST.setdefault(dist, []) |
77 | - SILO_NAME_LIST[dist].append(siloname) |
78 | - ALL_SILO_NAMES.append(siloname) |
79 | + silonames.append(siloname) |
80 | with utf8_open(SILO_NAMES_FILE, 'w') as filename: |
81 | - filename.write(json.dumps(sorted(ALL_SILO_NAMES))) |
82 | + filename.write(json.dumps(sorted(silonames))) |
83 | + return silonames |
84 | |
85 | |
86 | def template(template_name): |
87 | @@ -114,8 +109,7 @@ |
88 | |
89 | def main(): |
90 | """Generate all Jenkins XML job files.""" |
91 | - discover_silos() |
92 | - for siloname in ALL_SILO_NAMES: |
93 | + for siloname in discover_silos(): |
94 | setup_silo(siloname) |
95 | setup_job('cyphermox-test') |
96 | setup_job('prepare-silo') |
97 | |
98 | === modified file 'cupstream2distro/settings.py' |
99 | --- cupstream2distro/settings.py 2016-01-20 02:32:00 +0000 |
100 | +++ cupstream2distro/settings.py 2016-01-23 01:33:24 +0000 |
101 | @@ -20,22 +20,10 @@ |
102 | Doesn't import anything from any other part of the project. |
103 | """ |
104 | |
105 | -import json |
106 | -import codecs |
107 | import logging |
108 | |
109 | from os import environ |
110 | from os.path import dirname, expanduser, isdir, join, realpath |
111 | -from collections import defaultdict |
112 | - |
113 | - |
114 | -def read_from_disk(filename): |
115 | - """Read a value from a file on disk.""" |
116 | - try: |
117 | - with codecs.open(filename, encoding='utf-8') as data: |
118 | - return data.read().strip() |
119 | - except (IOError, OSError): |
120 | - return None |
121 | |
122 | |
123 | logging.basicConfig( |
124 | @@ -56,46 +44,14 @@ |
125 | |
126 | ROOT_CU2D = dirname(dirname(realpath(__file__))) |
127 | |
128 | -PPA_TEAM_FILE = join(HOME_DIR, 'ppa.team') |
129 | -PPA_TEAM = read_from_disk(PPA_TEAM_FILE) or 'ci-train-staging-area' |
130 | -LANDING_SCHEME = PPA_TEAM + '/{}' |
131 | - |
132 | -STABLE_OVERLAY_PPA = PPA_TEAM + '/ubuntu/stable-phone-overlay' |
133 | - |
134 | - |
135 | -def read_silo_names(filename): |
136 | - """Read the list of available silo PPA names from a file on disk.""" |
137 | - silos = [] |
138 | - bydistro = defaultdict(list) |
139 | - try: |
140 | - silos = json.loads(read_from_disk(filename)) |
141 | - for siloname in silos: |
142 | - bydistro[siloname.split('/')[0]].append(siloname) |
143 | - except (IOError, OSError, ValueError, TypeError): |
144 | - logging.warning('Silo name list not found, run setup_citrain.py.') |
145 | - logging.debug(str(silos)) |
146 | - logging.debug(str(bydistro)) |
147 | - return silos, dict(bydistro) |
148 | - |
149 | - |
150 | SILO_NAMES_FILE = expanduser('~/.ci-train-silo-names') |
151 | -ALL_SILO_NAMES, SILO_NAME_LIST = read_silo_names(SILO_NAMES_FILE) |
152 | - |
153 | -SILO_BUILDPPA_SCHEME = PPA_TEAM + '/{}/{}' |
154 | + |
155 | + |
156 | SILOS_DIR = expanduser('~/silos') |
157 | SILO_RSYNCDIR = '~/out' |
158 | CITRAIN_BINDIR = join(ROOT_CU2D, 'citrain') |
159 | |
160 | - |
161 | -# These paths are hard-coded in lp:cupstream2distro/charm, don't change them |
162 | -BILETO_TOKEN_PATH = expanduser('~/bileto.token') |
163 | -BILETO_TOKEN = read_from_disk(BILETO_TOKEN_PATH) |
164 | - |
165 | -BILETO_IP_PATH = expanduser('~/bileto.ip') |
166 | -BILETO_IP = read_from_disk(BILETO_IP_PATH) |
167 | - |
168 | -BILETO_GET = 'http://{}:8080/v1/ticket/{{}}'.format(BILETO_IP) |
169 | -BILETO_API = 'http://{}:8080/v1/tickets?token={}'.format( |
170 | - BILETO_IP, BILETO_TOKEN) |
171 | -COMMENT_API = 'http://{}:8080/v1/comment?token={}'.format( |
172 | - BILETO_IP, BILETO_TOKEN) |
173 | +# Default values for config files read from disk. |
174 | +DEFAULTS = { |
175 | + 'ppa.team': 'ci-train-staging-area', |
176 | +} |
177 | |
178 | === modified file 'cupstream2distro/silomanager.py' |
179 | --- cupstream2distro/silomanager.py 2016-01-23 00:38:01 +0000 |
180 | +++ cupstream2distro/silomanager.py 2016-01-23 01:33:24 +0000 |
181 | @@ -38,17 +38,11 @@ |
182 | from lazr.restfulclient.errors import PreconditionFailed, NotFound |
183 | |
184 | from cupstream2distro.branchhandling import get_package_name_from_branch |
185 | +from cupstream2distro.settings import SILO_NAMES_FILE, SILOS_DIR |
186 | from cupstream2distro.errors import CITrainError, PrepError |
187 | from cupstream2distro.launchpadmanager import lp |
188 | -from cupstream2distro.settings import ( |
189 | - BILETO_API, |
190 | - BILETO_GET, |
191 | - LANDING_SCHEME, |
192 | - SILO_NAME_LIST, |
193 | - SILOS_DIR, |
194 | - STABLE_OVERLAY_PPA, |
195 | -) |
196 | from cupstream2distro.utils import ( |
197 | + Config, |
198 | env, |
199 | memoize, |
200 | log_value_of, |
201 | @@ -112,9 +106,11 @@ |
202 | |
203 | def update_bileto(**kwargs): |
204 | """Update a ticket in Bileto.""" |
205 | + api = 'http://{}:8080/v1/tickets?token={}'.format( |
206 | + Config['bileto.ip'], Config['bileto.token']) |
207 | try: |
208 | response = requests.post( |
209 | - BILETO_API, data=json.dumps(kwargs), headers=JSON) |
210 | + api, data=json.dumps(kwargs), headers=JSON) |
211 | except IOError as err: |
212 | logging.error('Failed to contact Bileto: %s', scrub(str(err))) |
213 | return |
214 | @@ -140,14 +136,25 @@ |
215 | REQUEST_ID_GLOB = REQUEST_ID_FILE.format('*') |
216 | _lock_fd = None |
217 | |
218 | + @staticmethod |
219 | + @memoize |
220 | + def discover_ppas(distribution='ubuntu'): |
221 | + """Read the cached list of PPAs we have.""" |
222 | + try: |
223 | + silos = json.loads(Config[SILO_NAMES_FILE]) |
224 | + except (IOError, OSError, ValueError, TypeError): |
225 | + raise PrepError('Silo name list not found, run setup_citrain.py') |
226 | + log_value_of.silos() |
227 | + return [silo for silo in silos if silo.startswith(distribution + '/')] |
228 | + |
229 | @classmethod |
230 | def find_first_available(cls, distribution='ubuntu'): |
231 | """Return a siloname for an unassigned silo.""" |
232 | log_value_of.distribution() |
233 | - logging.debug(SILO_NAME_LIST[distribution]) |
234 | - shuffle(SILO_NAME_LIST[distribution]) |
235 | - logging.debug(SILO_NAME_LIST[distribution]) |
236 | - for siloname in SILO_NAME_LIST[distribution]: |
237 | + silonames = cls.discover_ppas(distribution) |
238 | + shuffle(silonames) |
239 | + log_value_of.silonames('Shuffled') |
240 | + for siloname in silonames: |
241 | log_value_of.siloname('Checking if available') |
242 | filename = join(SILOS_DIR, siloname, cls.REQUEST_ID_GLOB) |
243 | if not glob(filename): |
244 | @@ -215,7 +222,6 @@ |
245 | def assign(self, requestid): |
246 | """Assign a new requestid to an available silo.""" |
247 | self.requestid = requestid |
248 | - self.validate_permissions() |
249 | self.find_existing_assignment() |
250 | if not self.siloname: |
251 | self.siloname = SiloState.find_first_available(self._distribution) |
252 | @@ -223,18 +229,6 @@ |
253 | self.set_status('Ready to build') |
254 | logging.info(self.summarize(status=False)) |
255 | |
256 | - def validate_permissions(self): |
257 | - """Ensure the user has permission to assign.""" |
258 | - logging.info('Checking LP teams for %s', env.BUILD_USER_ID) |
259 | - memberships = lp.people[env.BUILD_USER_ID].memberships_details |
260 | - teams = {member.team_link.split('~')[-1] for member in memberships} |
261 | - if not teams & {'ubuntu-core-dev', 'ci-train-ppa-service'}: |
262 | - assigned = len(glob(join( |
263 | - SILOS_DIR, self._distribution, '*', self.REQUEST_ID_GLOB))) |
264 | - total = len(SILO_NAME_LIST[self._distribution]) |
265 | - if total - assigned < 5: |
266 | - raise PrepError('Low on silos: Ask a trainguard to assign.') |
267 | - |
268 | def find_existing_assignment(self): |
269 | """Find & re-use existing assignment if any.""" |
270 | rid = self.requestid |
271 | @@ -273,7 +267,9 @@ |
272 | """Fetch request from Bileto.""" |
273 | rid = self.requestid |
274 | log_value_of.rid('Loading from Bileto') |
275 | - response = requests.get(BILETO_GET.format(rid)).json() |
276 | + response = requests.get( |
277 | + 'http://{}:8080/v1/ticket/{}'.format(Config['bileto.ip'], rid) |
278 | + ).json() |
279 | self._bileto = (response.get('requests') or [{}])[0] |
280 | creator = self.creator |
281 | log_value_of.creator('Bileto says') |
282 | @@ -292,7 +288,7 @@ |
283 | raise PrepError('Field "{}" is mandatory.'.format(field)) |
284 | if not (self._merge_proposals or self.sources or self._sync_request): |
285 | raise PrepError('Need either merges, sources, or a sync.') |
286 | - if self._distribution not in SILO_NAME_LIST: |
287 | + if self._distribution not in ('ubuntu',): |
288 | raise PrepError('{} is unsupported.'.format(self._distribution)) |
289 | if self.dual: |
290 | if self._sync_request: |
291 | @@ -417,7 +413,7 @@ |
292 | def ppa(self): |
293 | """Return the lplib object representing the silo ppa.""" |
294 | siloname = self.siloname or self._siloname |
295 | - return lp.get_ppa(LANDING_SCHEME.format(siloname)) |
296 | + return lp.get_ppa('{}/{}'.format(Config['ppa.team'], siloname)) |
297 | |
298 | @property |
299 | @memoize |
300 | @@ -453,11 +449,17 @@ |
301 | |
302 | @property |
303 | @memoize |
304 | + def stable_overlay(self): |
305 | + """Return launchpadlib object representing stable overlay ppa.""" |
306 | + return lp.get_ppa(Config['ppa.team'] + '/ubuntu/stable-phone-overlay') |
307 | + |
308 | + @property |
309 | + @memoize |
310 | def dest(self): |
311 | """Return launchpadlib object representing the destination archive.""" |
312 | series = self._series |
313 | if series == 'wily+vivid': |
314 | - return lp.get_ppa(STABLE_OVERLAY_PPA) |
315 | + return self.stable_overlay |
316 | if self._dest and '+' not in series: |
317 | return lp.get_ppa(self._dest) |
318 | return lp.distributions[self._distribution].main_archive |
319 | @@ -478,9 +480,9 @@ |
320 | elif archive.isdigit(): |
321 | siloname = '{}/landing-{:03d}'.format( |
322 | self._distribution, int(archive)) |
323 | - return lp.get_ppa(LANDING_SCHEME.format(siloname)) |
324 | + return lp.get_ppa('{}/{}'.format(Config['ppa.team'], siloname)) |
325 | elif archive == 'stable-overlay': |
326 | - return lp.get_ppa(STABLE_OVERLAY_PPA) |
327 | + return self.stable_overlay |
328 | elif archive: |
329 | with suppress(KeyError): |
330 | return lp.distributions[archive].main_archive |
331 | |
332 | === modified file 'cupstream2distro/utils.py' |
333 | --- cupstream2distro/utils.py 2016-01-10 10:12:24 +0000 |
334 | +++ cupstream2distro/utils.py 2016-01-23 01:33:24 +0000 |
335 | @@ -22,7 +22,7 @@ |
336 | import subprocess |
337 | |
338 | import os |
339 | -from os.path import exists, isfile, join |
340 | +from os.path import exists, expanduser, isfile, join |
341 | |
342 | from hashlib import sha256 |
343 | from functools import wraps |
344 | @@ -33,7 +33,7 @@ |
345 | from six import text_type |
346 | from six.moves.urllib.parse import unquote |
347 | |
348 | -from cupstream2distro.settings import SILOS_DIR |
349 | +from cupstream2distro.settings import DEFAULTS, SILOS_DIR |
350 | |
351 | |
352 | TOKEN_SCRUB = re.compile('token=[a-z0-9-]+', flags=re.IGNORECASE) |
353 | @@ -160,6 +160,21 @@ |
354 | return 'Environment Variables:\n' + pformat(dict(os.environ)) |
355 | |
356 | |
357 | +@singleton |
358 | +class Config: |
359 | + """Read configuration values from disk.""" |
360 | + dirs = ('/etc/cupstream2distro', expanduser('~')) |
361 | + |
362 | + @memoize |
363 | + def __getitem__(self, key): |
364 | + """Read configuration value from disk.""" |
365 | + for root in self.dirs: |
366 | + with suppress(FileNotFoundError): |
367 | + with utf8_open(join(root, key)) as data: |
368 | + return data.read().strip() |
369 | + return DEFAULTS.get(key) |
370 | + |
371 | + |
372 | def SILO_DIR(*parts): |
373 | """Return the directory of the currently active silo.""" |
374 | assert env.SILONAME |
375 | |
376 | === modified file 'tests/unit/test_recipe_manager.py' |
377 | --- tests/unit/test_recipe_manager.py 2016-01-10 10:12:24 +0000 |
378 | +++ tests/unit/test_recipe_manager.py 2016-01-23 01:33:24 +0000 |
379 | @@ -95,13 +95,11 @@ |
380 | source.assert_called_once_with( |
381 | 'b', silo_state.series, silo_state.dest, silo_state.ppa) |
382 | |
383 | - @patch('citrain.recipes.manager.lp') |
384 | @patch('citrain.recipes.manager.Merge') |
385 | @patch('citrain.recipes.manager.Manual') |
386 | @patch('citrain.recipes.manager.Secondary') |
387 | - def test_manager_instantiate_build_dual(self, second, man, merge, lp): |
388 | + def test_manager_instantiate_build_dual(self, second, man, merge): |
389 | """Create Secondary build objects.""" |
390 | - overlay = lp.get_ppa.return_value |
391 | dual = Mock() |
392 | dual.name = 'vivid' |
393 | state = Mock( |
394 | @@ -124,11 +122,11 @@ |
395 | 'zzz_c': man.return_value, |
396 | }) |
397 | self.assertEqual(sorted(second.mock_calls), [ |
398 | - call('a', dual, overlay, state.ppa, bman.builds['a']), |
399 | + call('a', dual, state.stable_overlay, state.ppa, bman.builds['a']), |
400 | ]) |
401 | self.assertEqual(sorted(man.mock_calls), [ |
402 | - call('b', dual, overlay, state.ppa, bman.builds['b']), |
403 | - call('c', dual, overlay, state.ppa, bman.builds['c']), |
404 | + call('b', dual, state.stable_overlay, state.ppa, bman.builds['b']), |
405 | + call('c', dual, state.stable_overlay, state.ppa, bman.builds['c']), |
406 | ]) |
407 | |
408 | @patch('citrain.recipes.manager.BuildBase') |
409 | |
410 | === modified file 'tests/unit/test_script_build.py' |
411 | --- tests/unit/test_script_build.py 2016-01-15 00:57:08 +0000 |
412 | +++ tests/unit/test_script_build.py 2016-01-23 01:33:24 +0000 |
413 | @@ -56,8 +56,7 @@ |
414 | bman.do() |
415 | self.assertEqual(bman.names, {'a', 'b', 'd'}) |
416 | |
417 | - @patch('citrain.recipes.manager.lp') |
418 | - def test_buildmanager_instantiate_build_objects_twins(self, lp): |
419 | + def test_buildmanager_instantiate_build_objects_twins(self): |
420 | """Always add twins for all builds.""" |
421 | sources = ['qtmir', 'qtmir-gles', 'foo'] |
422 | silo_state = Mock(all_projects=sources, sources=sources, mps=[]) |
423 | |
424 | === modified file 'tests/unit/test_script_setup_citrain.py' |
425 | --- tests/unit/test_script_setup_citrain.py 2016-01-10 10:12:24 +0000 |
426 | +++ tests/unit/test_script_setup_citrain.py 2016-01-23 01:33:24 +0000 |
427 | @@ -17,18 +17,15 @@ |
428 | """Tests for CI Train Setup script.""" |
429 | |
430 | import glob |
431 | -import json |
432 | |
433 | import os |
434 | from os.path import join |
435 | |
436 | -from collections import defaultdict |
437 | from mock import Mock, call |
438 | |
439 | from tests.unit import CITrainScriptTestCase |
440 | |
441 | -from cupstream2distro.utils import utf8_open |
442 | -from cupstream2distro.settings import PPA_TEAM, read_silo_names |
443 | +from cupstream2distro.utils import Config, utf8_open |
444 | |
445 | # Unused import necessary for code coverage reporting |
446 | from citrain import setup_citrain |
447 | @@ -39,33 +36,18 @@ |
448 | """Test CI Train Setup Script.""" |
449 | scriptname = 'setup_citrain.py' |
450 | |
451 | - def test_read_silo_names(self): |
452 | - """Ensure that we can read the silo name list from disk.""" |
453 | - listfile = join(self.tempdir, 'list') |
454 | - expected = ['ubuntu/one', 'ubuntu/two'] |
455 | - with utf8_open(listfile, 'w') as data: |
456 | - data.write(json.dumps(expected)) |
457 | - silos, distro = read_silo_names(listfile) |
458 | - self.assertEqual(silos, expected) |
459 | - self.assertEqual(distro, dict(ubuntu=silos)) |
460 | - |
461 | def test_discover_silos(self): |
462 | """Ensure that we can query launchpad for existing PPAs.""" |
463 | alpha = Mock(distribution_link='foo/ubuntu') |
464 | alpha.name = 'landing-000' |
465 | omega = Mock(distribution_link='foo/ubuntu') |
466 | omega.name = 'landing-999' |
467 | - self.script.lp.people = {PPA_TEAM: Mock(ppas=[alpha, omega])} |
468 | - self.script.SILO_NAME_LIST = defaultdict(list) |
469 | + self.script.lp.people = {Config['ppa.team']: Mock(ppas=[alpha, omega])} |
470 | self.script.SILO_NAMES_FILE = '/tmp/ci-train-silo-names' |
471 | - self.script.discover_silos() |
472 | - self.assertEqual(self.script.ALL_SILO_NAMES, [ |
473 | + self.assertEqual(self.script.discover_silos(), [ |
474 | 'ubuntu/landing-000', |
475 | 'ubuntu/landing-999', |
476 | ]) |
477 | - self.assertEqual( |
478 | - self.script.SILO_NAME_LIST, |
479 | - dict(ubuntu=self.script.ALL_SILO_NAMES)) |
480 | |
481 | def test_template_names(self): |
482 | """Ensure we can find template filenames properly.""" |
483 | @@ -128,12 +110,15 @@ |
484 | |
485 | def test_main(self): |
486 | """Tie deployment all together.""" |
487 | - self.script.discover_silos = Mock() |
488 | + silos = [ |
489 | + 'ubuntu/landing-{:03d}'.format(x) for x in range(0, 10) |
490 | + ] |
491 | + self.script.discover_silos = Mock(return_value=silos) |
492 | self.script.setup_silo = Mock() |
493 | self.script.setup_job = Mock() |
494 | self.assertEqual(self.script.main(), 0) |
495 | self.assertEqual(self.script.setup_silo.mock_calls, [ |
496 | - call('ubuntu/landing-{:03d}'.format(x)) for x in range(0, 10) |
497 | + call(silo) for silo in silos |
498 | ]) |
499 | self.assertEqual(self.script.setup_job.mock_calls, [ |
500 | call('cyphermox-test'), |
501 | @@ -169,7 +154,9 @@ |
502 | yield val.format(num) |
503 | |
504 | expected = sorted(gen_expected()) |
505 | - self.script.discover_silos = Mock() |
506 | + self.script.discover_silos = Mock(return_value=[ |
507 | + 'ubuntu/landing-{:03d}'.format(x) for x in range(0, 10) |
508 | + ]) |
509 | self.script.JOBS_DIR = self.tempdir |
510 | self.script.os.makedirs = os.makedirs |
511 | self.assertEqual(self.script.main(), 0) |
512 | |
513 | === modified file 'tests/unit/test_silomanager.py' |
514 | --- tests/unit/test_silomanager.py 2016-01-23 00:38:01 +0000 |
515 | +++ tests/unit/test_silomanager.py 2016-01-23 01:33:24 +0000 |
516 | @@ -126,10 +126,11 @@ |
517 | def body(silo_state): |
518 | """Raise a little hell.""" |
519 | raise PrepError('Boo!') |
520 | + PrepError.prefix = 'Bailed: ' |
521 | self.assertEqual(stock_main(body), PrepError.code) |
522 | self.assertEqual(state_mock.mock_calls, [ |
523 | call(self.tempdir, primary=True), |
524 | - call().set_status('Assignment failed: Boo!'), |
525 | + call().set_status('Bailed: Boo!'), |
526 | ]) |
527 | |
528 | def test_silostate_init(self): |
529 | @@ -159,10 +160,9 @@ |
530 | |
531 | @patch('cupstream2distro.silomanager.SiloState.summarize') |
532 | @patch('cupstream2distro.silomanager.SiloState.set_status') |
533 | - @patch('cupstream2distro.silomanager.SiloState.validate_permissions') |
534 | @patch('cupstream2distro.silomanager.SiloState.find_existing_assignment') |
535 | @patch('cupstream2distro.silomanager.SiloState.find_first_available') |
536 | - def test_silostate_assign(self, first, ass_new, valid, save, summ): |
537 | + def test_silostate_assign(self, first, ass_new, save, summ): |
538 | """Do something sensible during silo assignment.""" |
539 | state = SiloState() |
540 | state._bileto['status'] = 'orig' |
541 | @@ -172,37 +172,10 @@ |
542 | state.assign('REQUEST_ID') |
543 | self.assertEqual(state.requestid, 'REQUEST_ID') |
544 | state.set_status.assert_called_once_with('Ready to build') |
545 | - valid.assert_called_once_with() |
546 | ass_new.assert_called_once_with() |
547 | first.assert_called_once_with('blahp') |
548 | summ.assert_called_once_with(status=False) |
549 | |
550 | - @patch('cupstream2distro.silomanager.lp') |
551 | - @patch('cupstream2distro.silomanager.glob') |
552 | - def test_silostate_validate_permissions(self, glob_mock, lp_mock): |
553 | - """Only allow assigning if there's lots of silos free.""" |
554 | - env.BUILD_USER_ID = 'bobru' |
555 | - bobru = Mock(memberships_details=[ |
556 | - Mock(team_link='http://example.com/~ci-train-ppa-service'), |
557 | - ]) |
558 | - lp_mock.people = dict(bobru=bobru) |
559 | - self.state.validate_permissions() |
560 | - self.assertEqual(glob_mock.mock_calls, []) |
561 | - |
562 | - @patch('cupstream2distro.silomanager.lp') |
563 | - @patch('cupstream2distro.silomanager.glob') |
564 | - @patch('cupstream2distro.silomanager.SILO_NAME_LIST', dict(ubuntu=[])) |
565 | - def test_silostate_validate_permissions_fail(self, glob_mock, lp_mock): |
566 | - """Only allow assigning if there's lots of silos free.""" |
567 | - env.BUILD_USER_ID = 'bobru' |
568 | - bobru = Mock(memberships_details=[]) |
569 | - lp_mock.people = dict(bobru=bobru) |
570 | - glob_mock.return_value = ['/path/to/foo'] * 60 |
571 | - with self.assertRaisesRegexp(PrepError, 'Low on silos'): |
572 | - self.state.validate_permissions() |
573 | - glob_mock.assert_called_once_with( |
574 | - self.tempdir + '/ubuntu/*/request_id_*') |
575 | - |
576 | def test_silostate_requestid(self): |
577 | """Ensure requestid property behaves sensible.""" |
578 | func = 'cupstream2distro.silomanager.join' |
579 | @@ -303,10 +276,26 @@ |
580 | self.state.find_existing_assignment() |
581 | self.assertIsNone(self.state.siloname) |
582 | |
583 | + @patch('cupstream2distro.silomanager.json.loads') |
584 | + def test_silostate_discover_ppas(self, json_mock): |
585 | + """Read silo name list from disk.""" |
586 | + json_mock.return_value = ['ubuntu-rtm/001', 'ubuntu/002'] |
587 | + self.assertEqual(SiloState.discover_ppas(), ['ubuntu/002']) |
588 | + |
589 | + @patch('cupstream2distro.silomanager.json.loads') |
590 | + def test_silostate_discover_ppas_broken(self, json_mock): |
591 | + """Read silo name list from disk.""" |
592 | + json_mock.side_effect = ValueError |
593 | + with self.assertRaises(PrepError): |
594 | + SiloState.discover_ppas('broke') |
595 | + |
596 | + @patch('cupstream2distro.silomanager.SiloState.discover_ppas') |
597 | @patch('cupstream2distro.silomanager.os.makedirs') |
598 | @patch('cupstream2distro.silomanager.glob') |
599 | - def test_silostate_find_first_available(self, glob_mock, mkdir): |
600 | + def test_silostate_find_first_available(self, glob_mock, mkdir, disco): |
601 | """Find the first unassigned silo.""" |
602 | + disco.return_value = [ |
603 | + 'ubuntu/landing-{:03d}'.format(x) for x in range(0, 10)] |
604 | glob_mock.return_value = ['/path/to/file'] |
605 | with self.assertRaisesRegexp(PrepError, 'No silos are avail'): |
606 | SiloState.find_first_available() |
607 | @@ -315,21 +304,21 @@ |
608 | '/ubuntu/landing-{:03d}/request_id_*'.format(x)) |
609 | for x in range(0, 10) |
610 | ] |
611 | - self.assertEqual(len(expected), len(glob_mock.mock_calls)) |
612 | - for a_call in expected: |
613 | - self.assertIn(a_call, glob_mock.mock_calls) |
614 | + self.assertListEqual(expected, sorted(glob_mock.mock_calls)) |
615 | self.assertEqual(mkdir.mock_calls, []) |
616 | |
617 | + @patch('cupstream2distro.silomanager.SiloState.discover_ppas') |
618 | @patch('cupstream2distro.silomanager.shuffle', sorted) |
619 | @patch('cupstream2distro.silomanager.os.makedirs') |
620 | @patch('cupstream2distro.silomanager.glob') |
621 | - def test_silostate_find_first_available_found(self, glob_mock, mkdir): |
622 | + def test_silostate_find_first_available_found(self, glob, mkdir, disco): |
623 | """Find first unassgined silo with one available.""" |
624 | - glob_mock.return_value = [] |
625 | + disco.return_value = ['ubuntu/landing-000'] |
626 | + glob.return_value = [] |
627 | self.assertEqual( |
628 | 'ubuntu/landing-000', |
629 | SiloState.find_first_available(distribution='ubuntu')) |
630 | - self.assertEqual(glob_mock.mock_calls, [ |
631 | + self.assertEqual(glob.mock_calls, [ |
632 | call(self.tempdir + '/ubuntu/landing-000/request_id_*'), |
633 | ]) |
634 | mkdir.assert_called_once_with( |
635 | @@ -474,7 +463,6 @@ |
636 | |
637 | @patch('cupstream2distro.silomanager.SiloState.sources', []) |
638 | @patch('cupstream2distro.silomanager.SiloState.requestid', '121') |
639 | - @patch('cupstream2distro.silomanager.BILETO_API', 'example.com') |
640 | def test_silostate_push_to_bileto(self): |
641 | """Ensure that we push status messages to Bileto.""" |
642 | env.BUILD_USER_ID = 'robru' |
643 | @@ -485,12 +473,11 @@ |
644 | self.assertIn('"request_id": "121"', data) |
645 | self.assertIn('"siloname": "ubuntu/landing-123"', data) |
646 | self.assertEqual(silomanager.requests.post.mock_calls, [ |
647 | - call('example.com', data=data, |
648 | + call('http://None:8080/v1/tickets?token=None', data=data, |
649 | headers={'content-type': 'application/json'}), |
650 | ]) |
651 | |
652 | @patch('cupstream2distro.silomanager.SiloState.requestid', '121') |
653 | - @patch('cupstream2distro.silomanager.BILETO_API', 'example.com') |
654 | @patch('cupstream2distro.silomanager.SiloState.sources', []) |
655 | @patch('cupstream2distro.silomanager.requests') |
656 | @patch('cupstream2distro.silomanager.logging') |
657 | @@ -505,7 +492,6 @@ |
658 | |
659 | @patch('cupstream2distro.silomanager.SiloState.sources', []) |
660 | @patch('cupstream2distro.silomanager.SiloState.requestid', '121') |
661 | - @patch('cupstream2distro.silomanager.BILETO_API', 'example.com') |
662 | def test_silostate_push_to_bileto_bad_response(self): |
663 | """Report failures from Bileto.""" |
664 | self.state.status = 'Packages built' |
665 | @@ -515,7 +501,7 @@ |
666 | self.assertIn('"status": "Packages built"', data) |
667 | self.assertIn('"request_id": "121"', data) |
668 | self.assertEqual(silomanager.requests.post.mock_calls, [ |
669 | - call('example.com', data=data, |
670 | + call('http://None:8080/v1/tickets?token=None', data=data, |
671 | headers={'content-type': 'application/json'}), |
672 | ]) |
673 | |
674 | |
675 | === modified file 'tests/unit/test_utils.py' |
676 | --- tests/unit/test_utils.py 2015-10-28 19:40:12 +0000 |
677 | +++ tests/unit/test_utils.py 2016-01-23 01:33:24 +0000 |
678 | @@ -23,6 +23,7 @@ |
679 | |
680 | from cupstream2distro.utils import ( |
681 | SILO_DIR, |
682 | + Config, |
683 | call, |
684 | env, |
685 | log_call, |
686 | @@ -217,3 +218,8 @@ |
687 | for line, new in utf8_inplace(path): |
688 | new.write(line) |
689 | self.assertEqual(os.stat(path).st_mode, orig_mode) |
690 | + |
691 | + def test_config(self): |
692 | + """Ensure that we can read config values from disk.""" |
693 | + Config.dirs = ('/etc/',) |
694 | + self.assertTrue(Config['passwd'].startswith('root')) |
Needs tests, and not yet tested in staging.