Merge lp:~ursinha/ubuntu-ci-services-itself/ppa-add-private into lp:ubuntu-ci-services-itself
- ppa-add-private
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Ursula Junque | ||||
Approved revision: | 389 | ||||
Merged at revision: | 385 | ||||
Proposed branch: | lp:~ursinha/ubuntu-ci-services-itself/ppa-add-private | ||||
Merge into: | lp:ubuntu-ci-services-itself | ||||
Diff against target: |
287 lines (+93/-31) 7 files modified
juju-deployer/configs/unit_config.yaml.tmpl (+2/-0) ppa-assigner/ppa_assigner/api.py (+6/-2) ppa-assigner/ppa_assigner/launchpad.py (+10/-3) ppa-assigner/ppa_assigner/migrations/0001_initial.py (+2/-0) ppa-assigner/ppa_assigner/models.py (+23/-13) ppa-assigner/ppa_assigner/settings.py (+1/-0) ppa-assigner/ppa_assigner/tests.py (+49/-13) |
||||
To merge this branch: | bzr merge lp:~ursinha/ubuntu-ci-services-itself/ppa-add-private | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Francis Ginther | Approve | ||
Andy Doan (community) | Approve | ||
Review via email: mp+210704@code.launchpad.net |
Commit message
This branch adds 'private' to PPA, adds ability to get private and public information from launchpad and adds a variable to define we're using private or public ppas.
Description of the change
This branch adds 'private' to PPA. It was supposed to have signing_
lp_collection now GETs public and private information, and reserve() only gets ppas according to settings.
Ursula Junque (ursinha) wrote : | # |
- 384. By Ursula Junque
-
Merging trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:384
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Andy Doan (doanac) wrote : | # |
45 +def _lp_get(url):
might be better name _lp_get_
21 - resp = urllib2.
22 + if settings.
23 + resp = _lp_get(url)
24 + else:
25 + resp = urllib2.
Is there really any need to distinguish the 2 code paths? Maybe just always call _lp_get there since you already have the logic in the model to differentiate them?
- 385. By Ursula Junque
-
s/_lp_get/
_lp_get_ authenticated/ - 386. By Ursula Junque
-
all ppa-assigner/
launchpad GETs are now authenticated, bringing also private information
Ursula Junque (ursinha) wrote : | # |
> 45 +def _lp_get(url):
>
> might be better name _lp_get_
r385.
>
> 21 - resp = urllib2.
> 22 + if settings.
> 23 + resp = _lp_get(url)
> 24 + else:
> 25 + resp = urllib2.
>
> Is there really any need to distinguish the 2 code paths? Maybe just always
> call _lp_get there since you already have the logic in the model to
> differentiate them?
I think I was only being overcautious about always getting private information (as other methods also use lp_collection), but at this point I think it shouldn't matter really. As I said before I don't even like it, hehe. Fixed on r386.
Do you think these tests are enough? I deployment similar code yesterday and it seemed to me reserve() wasn't respecting the settings.
Ursula Junque (ursinha) wrote : | # |
> Do you think these tests are enough? I deployment similar code yesterday and
> it seemed to me reserve() wasn't respecting the settings.
> variable, and I couldn't find any logs (gunicorn issue, I think). Is there
> anything else I need to do besides setting that on unit_config? Tests are
> passing now but maybe I'm not testing this as I should.
s/deployment/
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:386
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Andy Doan (doanac) wrote : | # |
> Do you think these tests are enough? I deployment similar code yesterday and
> it seemed to me reserve() wasn't respecting the settings.
> variable, and I couldn't find any logs (gunicorn issue, I think). Is there
> anything else I need to do besides setting that on unit_config? Tests are
> passing now but maybe I'm not testing this as I should.
is it possible you failed to restart django after changing your unit-config and/or settings.py?
139 - qs = PPA.objects.
140 + qs = PPA.objects.
141 + state=PPA.
That looks pretty cut-and-dry to me. I don't think that could do the wrong thing, and:
190 + def testReservePriv
seems to verify that block
- 387. By Ursula Junque
-
Forgot this: engine health page should show total of PPAs available for privacy set
- 388. By Ursula Junque
-
Making tests check if engine health will display only private/public ppas
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:388
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 389. By Ursula Junque
-
Adding private ppa info to the ui for now, to help us testing
Ursula Junque (ursinha) wrote : | # |
I added only the "private ppa only" info to the ui, to help with the confusion enabling private ppas might bring for now.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:389
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'juju-deployer/configs/unit_config.yaml.tmpl' |
2 | --- juju-deployer/configs/unit_config.yaml.tmpl 2014-03-13 15:28:07 +0000 |
3 | +++ juju-deployer/configs/unit_config.yaml.tmpl 2014-03-13 23:30:47 +0000 |
4 | @@ -17,6 +17,8 @@ |
5 | |
6 | ppa_pattern: "ci-pool-\\d+" |
7 | |
8 | +private_only: False |
9 | + |
10 | # for ticket system |
11 | base_image: "http://cloud-images.ubuntu.com/releases/13.10/release-20131015/ubuntu-13.10-server-cloudimg-amd64-disk1.img" |
12 | series: saucy |
13 | |
14 | === modified file 'ppa-assigner/ppa_assigner/api.py' |
15 | --- ppa-assigner/ppa_assigner/api.py 2014-03-12 00:46:19 +0000 |
16 | +++ ppa-assigner/ppa_assigner/api.py 2014-03-13 23:30:47 +0000 |
17 | @@ -115,10 +115,14 @@ |
18 | status = JSONStatus() |
19 | status.add_true_false('launchpad configured', lp, lp) |
20 | |
21 | - count = PPA.objects.all().count() |
22 | + is_private = settings.PRIVATE_ONLY |
23 | + status.add_true_false('private ppas only', is_private, is_private) |
24 | + |
25 | + total_ppas = PPA.objects.filter(private=is_private) |
26 | + count = total_ppas.count() |
27 | status.add_true_false('total ppas', count, count > 0) |
28 | |
29 | - count = PPA.objects.filter(state=PPA.AVAILABLE).count() |
30 | + count = total_ppas.filter(state=PPA.AVAILABLE).count() |
31 | status.add_true_false('available ppas', count, count > 0) |
32 | |
33 | try: |
34 | |
35 | === modified file 'ppa-assigner/ppa_assigner/launchpad.py' |
36 | --- ppa-assigner/ppa_assigner/launchpad.py 2014-03-12 00:46:19 +0000 |
37 | +++ ppa-assigner/ppa_assigner/launchpad.py 2014-03-13 23:30:47 +0000 |
38 | @@ -24,7 +24,7 @@ |
39 | |
40 | def lp_collection(url): |
41 | '''iterate through each item returned in an lp_collection''' |
42 | - resp = urllib2.urlopen(url) |
43 | + resp = _lp_get_authenticated(url) |
44 | resp = resp.read() |
45 | data = json.loads(resp) |
46 | |
47 | @@ -54,12 +54,19 @@ |
48 | return { |
49 | 'Authorization': ', '.join(values), |
50 | 'Accept': 'application/json', |
51 | - 'Content-Type': 'application/x-www-form-urlencoded' |
52 | } |
53 | |
54 | |
55 | def _lp_post(url, data): |
56 | - req = urllib2.Request(url, urllib.urlencode(data), _oauth_headers()) |
57 | + headers = _oauth_headers() |
58 | + headers['Content-Type'] = 'application/x-www-form-urlencoded' |
59 | + req = urllib2.Request(url, urllib.urlencode(data), headers) |
60 | + return urllib2.urlopen(req) |
61 | + |
62 | + |
63 | +def _lp_get_authenticated(url): |
64 | + '''An authenticated GET to obtain also private information.''' |
65 | + req = urllib2.Request(url, headers=_oauth_headers()) |
66 | return urllib2.urlopen(req) |
67 | |
68 | |
69 | |
70 | === modified file 'ppa-assigner/ppa_assigner/migrations/0001_initial.py' |
71 | --- ppa-assigner/ppa_assigner/migrations/0001_initial.py 2014-03-07 22:49:17 +0000 |
72 | +++ ppa-assigner/ppa_assigner/migrations/0001_initial.py 2014-03-13 23:30:47 +0000 |
73 | @@ -12,6 +12,7 @@ |
74 | db.create_table(u'ppa_assigner_ppa', ( |
75 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
76 | ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=4096)), |
77 | + ('private', self.gf('django.db.models.fields.BooleanField')(default=False)), |
78 | ('state', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), |
79 | ('ticket_id', self.gf('django.db.models.fields.PositiveIntegerField')()), |
80 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
81 | @@ -29,6 +30,7 @@ |
82 | 'Meta': {'object_name': 'PPA'}, |
83 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
84 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '4096'}), |
85 | + 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
86 | 'state': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), |
87 | 'ticket_id': ('django.db.models.fields.PositiveIntegerField', [], {}), |
88 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) |
89 | |
90 | === modified file 'ppa-assigner/ppa_assigner/models.py' |
91 | --- ppa-assigner/ppa_assigner/models.py 2014-03-12 00:46:19 +0000 |
92 | +++ ppa-assigner/ppa_assigner/models.py 2014-03-13 23:30:47 +0000 |
93 | @@ -37,6 +37,7 @@ |
94 | ) |
95 | name = models.CharField(max_length=4096, unique=True) |
96 | |
97 | + private = models.BooleanField(default=False) |
98 | state = models.PositiveIntegerField(choices=STATES, default=AVAILABLE) |
99 | ticket_id = models.PositiveIntegerField() |
100 | timestamp = models.DateTimeField(auto_now_add=True) |
101 | @@ -58,22 +59,25 @@ |
102 | # note: LP will throttle calls to list PPAs. So you can occassionally |
103 | # add a PPA, and not see it update for the first few calls to this |
104 | # method, but it will eventually get in sync |
105 | - model_ppas = set([x.name for x in PPA.objects.all()]) |
106 | - add = lp_ppas - model_ppas |
107 | - remove = model_ppas - lp_ppas |
108 | + lp_ppas_ = set(lp_ppas.keys()) |
109 | + model_ppas = set([ppa.name for ppa in PPA.objects.all()]) |
110 | + add = lp_ppas_ - model_ppas |
111 | + remove = model_ppas - lp_ppas_ |
112 | |
113 | logging.info('adding new ppas: %r', add) |
114 | - for x in add: |
115 | - PPA.objects.create(name=x, ticket_id=0) |
116 | + for ppa_name in add: |
117 | + lp_ppa = lp_ppas[ppa_name] |
118 | + PPA.objects.create(name=ppa_name, ticket_id=0, |
119 | + private=lp_ppa['private']) |
120 | |
121 | logging.info('removing ppas: %r', remove) |
122 | - for x in remove: |
123 | - ppa = PPA.objects.get(name=x) |
124 | + for ppa_name in remove: |
125 | + ppa = PPA.objects.get(name=ppa_name) |
126 | if ppa.state == PPA.RESERVED: |
127 | # log something, but we may as well delete. because if the |
128 | # ppa doesn't exist in LP we are already in a catastrophic |
129 | # situation and shouldn't let any other user re-use this |
130 | - logging.error('ppa %s appears to be in use', x) |
131 | + logging.error('ppa %s appears to be in use', ppa_name) |
132 | ppa.delete() |
133 | |
134 | @staticmethod |
135 | @@ -83,11 +87,16 @@ |
136 | url = url % settings.LAUNCHPAD_PPA_OWNER |
137 | logging.info('populating ppa model from: %s', url) |
138 | try: |
139 | + ppas = {} |
140 | pat = re.compile(settings.PPA_PATTERN) |
141 | - ppas = ['ppa:%s/%s' % (settings.LAUNCHPAD_PPA_OWNER, x['name']) |
142 | - for x in launchpad.lp_collection(url) |
143 | - if pat.match(x['name'])] |
144 | - PPA._merge_ppas(set(ppas)) |
145 | + for ppa in launchpad.lp_collection(url): |
146 | + if pat.match(ppa['name']): |
147 | + ppa_name = 'ppa:{}/{}'.format(settings.LAUNCHPAD_PPA_OWNER, |
148 | + ppa['name']) |
149 | + ppas[ppa_name] = { |
150 | + 'private': ppa['private'], |
151 | + } |
152 | + PPA._merge_ppas(ppas) |
153 | except urllib2.URLError: |
154 | logging.exception('Could not populate PPAs from launchpad') |
155 | raise |
156 | @@ -97,7 +106,8 @@ |
157 | # "select_for_update" provides proper db-level locking so we |
158 | # don't accidentally have 2 concurrent requests find and reserve the |
159 | # same PPA |
160 | - qs = PPA.objects.select_for_update().filter(state=PPA.AVAILABLE) |
161 | + qs = PPA.objects.select_for_update().filter( |
162 | + state=PPA.AVAILABLE, private=settings.PRIVATE_ONLY) |
163 | if qs.count() == 0: |
164 | raise PPA.DoesNotExist('No free PPAs exist in pool') |
165 | pk = qs[0].pk |
166 | |
167 | === modified file 'ppa-assigner/ppa_assigner/settings.py' |
168 | --- ppa-assigner/ppa_assigner/settings.py 2014-03-12 01:35:01 +0000 |
169 | +++ ppa-assigner/ppa_assigner/settings.py 2014-03-13 23:30:47 +0000 |
170 | @@ -51,6 +51,7 @@ |
171 | OAUTH_TOKEN_SECRET = _cfg.get('oauth_token_secret', None) |
172 | OAUTH_REALM = _cfg.get('oauth_realm', 'https://api.launchpad.net/') |
173 | PPA_PATTERN = _cfg.get('ppa_pattern', r'ci-pool-\d+') |
174 | +PRIVATE_ONLY = _cfg.get('private_only', False) |
175 | |
176 | DATABASES = { |
177 | 'default': { |
178 | |
179 | === modified file 'ppa-assigner/ppa_assigner/tests.py' |
180 | --- ppa-assigner/ppa_assigner/tests.py 2014-03-12 00:46:19 +0000 |
181 | +++ ppa-assigner/ppa_assigner/tests.py 2014-03-13 23:30:47 +0000 |
182 | @@ -39,6 +39,7 @@ |
183 | |
184 | def setUp(self): |
185 | super(TestModel, self).setUp() |
186 | + settings.PRIVATE_ONLY = False |
187 | for n in range(5): |
188 | name = 'ci_pool-%s' % str(n).zfill(3) |
189 | PPA.objects.create(name=name, ticket_id=0) |
190 | @@ -57,6 +58,16 @@ |
191 | with self.assertRaises(PPA.DoesNotExist): |
192 | PPA.reserve(1234) |
193 | |
194 | + def testReservePrivateOnly(self): |
195 | + PPA.objects.create(name='ci_pool-998', ticket_id=0) |
196 | + settings.PRIVATE_ONLY = True |
197 | + with self.assertRaises(PPA.DoesNotExist): |
198 | + PPA.reserve(1234) |
199 | + PPA.objects.create(name='ci_pool-999', ticket_id=0, private=True) |
200 | + p = PPA.reserve(1234) |
201 | + self.assertEquals(p.ticket_id, 1234) |
202 | + self.assertEquals(p.private, True) |
203 | + |
204 | |
205 | class TestConcurrency(TransactionTestCase): |
206 | # inspired by, but heavily modified: |
207 | @@ -146,6 +157,19 @@ |
208 | obj = self.createResource('ppa/', params) |
209 | self.assertEqual(obj['ticket_id'], params['ticket_id']) |
210 | |
211 | + def testReservePrivateOnly(self): |
212 | + PPA.objects.create(name='ci-pool_996', ticket_id=0) |
213 | + params = {'ticket_id': 1234} |
214 | + # There are only public PPAs available. |
215 | + settings.PRIVATE_ONLY = True |
216 | + with self.assertRaisesRegexp(AssertionError, '404 != 201'): |
217 | + self.post('ppa/', params) |
218 | + |
219 | + # Creating one private PPA to be reserved. |
220 | + PPA.objects.create(name='ci-pool_997', ticket_id=0, private=True) |
221 | + obj = self.createResource('ppa/', params) |
222 | + self.assertEqual(obj['ticket_id'], params['ticket_id']) |
223 | + |
224 | def testReserveNoFree(self): |
225 | PPA.objects.update(state=PPA.DIRTY) |
226 | params = {'ticket_id': 1234} |
227 | @@ -184,12 +208,13 @@ |
228 | @mock.patch('urllib2.urlopen') |
229 | def testPopulateMerge(self, urlopen): |
230 | ignored = [ |
231 | - {'name': 'foo'}, |
232 | - {'name': 'ci-pool'}, |
233 | + {'name': 'foo', 'private': False}, |
234 | + {'name': 'ci-pool', 'private': False}, |
235 | ] |
236 | expected = [ |
237 | - {'name': 'ci-pool-001'}, |
238 | - {'name': 'ci-pool-002'}, |
239 | + {'name': 'ci-pool-001', 'private': False}, |
240 | + {'name': 'ci-pool-002', 'private': True}, |
241 | + {'name': 'ci-pool-003', 'private': False}, |
242 | ] |
243 | data = { |
244 | 'total_size': 2, |
245 | @@ -201,9 +226,17 @@ |
246 | urlopen.return_value = resp |
247 | ppa_owner = settings.LAUNCHPAD_PPA_OWNER |
248 | self.patch('ppa/', {'populate': True}) |
249 | - expected = ['ppa:%s/%s' % (ppa_owner, x['name']) for x in expected] |
250 | - found = [x.name for x in PPA.objects.all().order_by('name')] |
251 | - self.assertListEqual(expected, found) |
252 | + expected = dict(('ppa:{}/{}'.format(ppa_owner, x['name']), |
253 | + x['private']) for x in expected) |
254 | + found = dict((x.name, x.private) for x in PPA.objects.all()) |
255 | + self.assertEqual(len(found), len(expected)) |
256 | + self.assertListEqual(sorted(expected.keys()), sorted(found.keys())) |
257 | + for ppa_name, ppa_privacy in found.iteritems(): |
258 | + self.assertEqual(expected[ppa_name], ppa_privacy) |
259 | + private_ppa = PPA.objects.filter(private=True) |
260 | + self.assertEqual(private_ppa.count(), 1) |
261 | + self.assertEqual(PPA.objects.filter(private=False).count(), 2) |
262 | + self.assertEqual(PPA.objects.all().count(), 3) |
263 | |
264 | def testCopyBadParams(self): |
265 | resource = self._resource('ppa_copy/') |
266 | @@ -227,12 +260,15 @@ |
267 | |
268 | def testStatus(self): |
269 | status = self.get('status/') |
270 | - total = PPA.objects.all().count() |
271 | - avail = PPA.objects.filter(state=PPA.AVAILABLE).count() |
272 | - self.assertEqual('total ppas', status[1]['label']) |
273 | - self.assertEqual(total, status[1]['value']) |
274 | - self.assertEqual('available ppas', status[2]['label']) |
275 | - self.assertEqual(avail, status[2]['value']) |
276 | + total_ppas = PPA.objects.filter(private=settings.PRIVATE_ONLY) |
277 | + total = total_ppas.count() |
278 | + avail = total_ppas.filter(state=PPA.AVAILABLE).count() |
279 | + self.assertEqual('private ppas only', status[1]['label']) |
280 | + self.assertEqual(True, status[1]['value']) |
281 | + self.assertEqual('total ppas', status[2]['label']) |
282 | + self.assertEqual(total, status[2]['value']) |
283 | + self.assertEqual('available ppas', status[3]['label']) |
284 | + self.assertEqual(avail, status[3]['value']) |
285 | |
286 | |
287 | class TestCleaner(TestCase): |
I don't like the check on lp_collection because that's lying. It's not fetching private information only, but *also*.