Merge lp:~ricardokirkner/gargoyle/update-0.10.7 into lp:~ubuntuone-pqm-team/gargoyle/stable

Proposed by Ricardo Kirkner
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: 270
Merged at revision: 264
Proposed branch: lp:~ricardokirkner/gargoyle/update-0.10.7
Merge into: lp:~ubuntuone-pqm-team/gargoyle/stable
Diff against target: 346 lines (+188/-16)
9 files modified
.arcconfig (+15/-0)
.gitignore (+0/-1)
.travis.yml (+7/-3)
gargoyle/builtins.py (+12/-1)
gargoyle/management/commands/add_switch.py (+32/-0)
gargoyle/management/commands/remove_switch.py (+14/-0)
gargoyle/nexus_modules.py (+8/-8)
setup.py (+2/-2)
tests/tests.py (+98/-1)
To merge this branch: bzr merge lp:~ricardokirkner/gargoyle/update-0.10.7
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Michael Nelson (community) Approve
Review via email: mp+164202@code.launchpad.net

Commit message

merge trunk@270 (0.10.7)

Description of the change

This branch pulls changes from trunk (upstream) up to tag 0.10.7.

These changes include the contributions made to upstream, so that once this lands we can remove them from our projects.

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) :
review: Approve
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

Rubber-stamping

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.arcconfig'
2--- .arcconfig 1970-01-01 00:00:00 +0000
3+++ .arcconfig 2013-05-16 14:35:12 +0000
4@@ -0,0 +1,15 @@
5+{
6+ "project_id": "gargoyle",
7+ "conduit_uri" : "http://phabricator.local.disqus.net/",
8+ "arcanist_configuration": "DisqusConfiguration",
9+ "copyright_holder": "Disqus, Inc.",
10+ "immutable_history": false,
11+ "differential.field-selector": "DisqusDifferentialFieldSelector",
12+ "phutil_libraries": {
13+ "disqus": "/usr/local/include/php/libdisqus/src"
14+ },
15+ "lint_engine": "ComprehensiveLintEngine",
16+ "lint.pep8.options": "--ignore=W391,W292,W293,E501,E225",
17+ "lint.jshint.prefix": "node_modules/jshint/bin",
18+ "lint.jshint.bin": "hint"
19+}
20
21=== modified file '.gitignore'
22--- .gitignore 2012-11-19 19:17:51 +0000
23+++ .gitignore 2013-05-16 14:35:12 +0000
24@@ -2,7 +2,6 @@
25 *.egg-info
26 *.egg
27 .DS_Store
28-.arcconfig
29 .coverage
30 /dist
31 /build
32
33=== modified file '.travis.yml'
34--- .travis.yml 2012-08-02 20:08:52 +0000
35+++ .travis.yml 2013-05-16 14:35:12 +0000
36@@ -3,11 +3,15 @@
37 - "2.6"
38 - "2.7"
39 env:
40- - DJANGO=1.2.7
41- - DJANGO=1.3.1
42- - DJANGO=1.4
43+ - DJANGO=1.2.7 MODELDICT=1.2.0
44+ - DJANGO=1.3.1 MODELDICT=1.2.0
45+ - DJANGO=1.4 MODELDICT=1.2.0
46+ - DJANGO=1.2.7 MODELDICT=1.4.1
47+ - DJANGO=1.3.1 MODELDICT=1.4.1
48+ - DJANGO=1.4 MODELDICT=1.4.1
49 install:
50 - pip install -q Django==$DJANGO --use-mirrors
51+ - pip install -q django-modeldict==$MODELDICT --use-mirrors
52 - pip install -q flake8 --use-mirrors
53 - pip install -q -e . --use-mirrors
54 script:
55
56=== modified file 'gargoyle/builtins.py'
57--- gargoyle/builtins.py 2012-08-08 00:49:42 +0000
58+++ gargoyle/builtins.py 2013-05-16 14:35:12 +0000
59@@ -15,6 +15,7 @@
60 from django.core.validators import validate_ipv4_address
61
62 import socket
63+import struct
64
65
66 class UserConditionSet(ModelConditionSet):
67@@ -64,13 +65,23 @@
68 # XXX: can we come up w/ a better API?
69 # Ensure we map ``percent`` to the ``id`` column
70 if field_name == 'percent':
71- return sum([int(x) for x in instance.META['REMOTE_ADDR'].split('.')])
72+ return self._ip_to_int(instance.META['REMOTE_ADDR'])
73 elif field_name == 'ip_address':
74 return instance.META['REMOTE_ADDR']
75 elif field_name == 'internal_ip':
76 return instance.META['REMOTE_ADDR'] in settings.INTERNAL_IPS
77 return super(IPAddressConditionSet, self).get_field_value(instance, field_name)
78
79+ def _ip_to_int(self, ip):
80+ if '.' in ip:
81+ # IPv4
82+ return sum([int(x) for x in ip.split('.')])
83+ if ':' in ip:
84+ # IPv6
85+ hi, lo = struct.unpack('!QQ', socket.inet_pton(socket.AF_INET6, ip))
86+ return (hi << 64) | lo
87+ raise ValueError('Invalid IP Address %r' % ip)
88+
89 def get_group_label(self):
90 return 'IP Address'
91
92
93=== added directory 'gargoyle/management'
94=== added file 'gargoyle/management/__init__.py'
95=== added directory 'gargoyle/management/commands'
96=== added file 'gargoyle/management/commands/__init__.py'
97=== added file 'gargoyle/management/commands/add_switch.py'
98--- gargoyle/management/commands/add_switch.py 1970-01-01 00:00:00 +0000
99+++ gargoyle/management/commands/add_switch.py 2013-05-16 14:35:12 +0000
100@@ -0,0 +1,32 @@
101+from optparse import make_option
102+from django.core.management.base import BaseCommand, CommandError
103+
104+from gargoyle.models import Switch, DISABLED, GLOBAL
105+
106+
107+class Command(BaseCommand):
108+ args = 'switch_name'
109+ help = 'Adds or updates the specified gargoyle switch.'
110+
111+ option_list = BaseCommand.option_list + (
112+ make_option(
113+ '--disabled',
114+ action='store_const',
115+ const=DISABLED,
116+ default=GLOBAL,
117+ dest='status',
118+ help='Create a disabled switch.'),
119+ )
120+
121+ def handle(self, *args, **kwargs):
122+ if len(args) != 1:
123+ raise CommandError("Specify a gargoyle switch name to add.")
124+
125+ status = kwargs['status']
126+ switch, created = Switch.objects.get_or_create(
127+ key=args[0],
128+ defaults=dict(status=status),
129+ )
130+ if not created and switch.status != status:
131+ switch.status = status
132+ switch.save()
133
134=== added file 'gargoyle/management/commands/remove_switch.py'
135--- gargoyle/management/commands/remove_switch.py 1970-01-01 00:00:00 +0000
136+++ gargoyle/management/commands/remove_switch.py 2013-05-16 14:35:12 +0000
137@@ -0,0 +1,14 @@
138+from django.core.management.base import BaseCommand, CommandError
139+
140+from gargoyle.models import Switch
141+
142+
143+class Command(BaseCommand):
144+ args = 'switch_name'
145+ help = 'Removes the specified gargoyle switch.'
146+
147+ def handle(self, *args, **kwargs):
148+ if len(args) != 1:
149+ raise CommandError("Specify a gargoyle switch name to remove.")
150+
151+ Switch.objects.filter(key=args[0]).delete()
152
153=== modified file 'gargoyle/nexus_modules.py'
154--- gargoyle/nexus_modules.py 2012-07-30 20:51:17 +0000
155+++ gargoyle/nexus_modules.py 2013-05-16 14:35:12 +0000
156@@ -123,13 +123,13 @@
157 if not key:
158 raise GargoyleException("Key cannot be empty")
159
160- if len(key) > 32:
161- raise GargoyleException("Key must be less than or equal to 32 characters in length")
162+ if len(key) > 64:
163+ raise GargoyleException("Key must be less than or equal to 64 characters in length")
164
165 label = request.POST.get("name", "").strip()
166
167- if len(label) > 32:
168- raise GargoyleException("Name must be less than or equal to 32 characters in length")
169+ if len(label) > 64:
170+ raise GargoyleException("Name must be less than or equal to 64 characters in length")
171
172 switch, created = Switch.objects.get_or_create(
173 key=key,
174@@ -159,13 +159,13 @@
175
176 key = request.POST.get("key")
177
178- if len(key) > 32:
179- raise GargoyleException("Key must be less than or equal to 32 characters in length")
180+ if len(key) > 64:
181+ raise GargoyleException("Key must be less than or equal to 64 characters in length")
182
183 label = request.POST.get("name", "")
184
185- if len(label) > 32:
186- raise GargoyleException("Name must be less than or equal to 32 characters in length")
187+ if len(label) > 64:
188+ raise GargoyleException("Name must be less than or equal to 64 characters in length")
189
190 values = dict(
191 label=label,
192
193=== modified file 'setup.py'
194--- setup.py 2012-12-04 19:56:17 +0000
195+++ setup.py 2013-05-16 14:35:12 +0000
196@@ -17,13 +17,13 @@
197 install_requires = [
198 'django-modeldict>=1.2.0',
199 'nexus>=0.2.3',
200- 'django-jsonfield>=0.8.0',
201+ 'django-jsonfield>=0.8.0,<0.9.4',
202 ]
203
204
205 setup(
206 name='gargoyle',
207- version='0.10.6',
208+ version='0.10.7',
209 author='DISQUS',
210 author_email='opensource@disqus.com',
211 url='http://github.com/disqus/gargoyle',
212
213=== modified file 'tests/tests.py'
214--- tests/tests.py 2012-08-09 22:19:20 +0000
215+++ tests/tests.py 2013-05-16 14:35:12 +0000
216@@ -8,6 +8,8 @@
217 from django.conf import settings
218 from django.contrib.auth.models import User, AnonymousUser
219 from django.core.cache import cache
220+from django.core.management.base import CommandError
221+from django.core.management import call_command
222 from django.http import HttpRequest, Http404, HttpResponse
223 from django.test import TestCase
224 from django.template import Context, Template, TemplateSyntaxError
225@@ -16,6 +18,10 @@
226 from gargoyle.decorators import switch_is_active
227 from gargoyle.helpers import MockRequest
228 from gargoyle.models import Switch, SELECTIVE, DISABLED, GLOBAL, INHERIT
229+from gargoyle.management.commands.add_switch import Command as AddSwitchCmd
230+from gargoyle.management.commands.remove_switch import (
231+ Command as RemoveSwitchCmd
232+)
233 from gargoyle.manager import SwitchManager
234 from gargoyle.testutils import switches
235
236@@ -381,7 +387,7 @@
237 self.assertFalse(self.gargoyle.is_active('test'))
238
239 # in memory cache shouldnt have expired
240- cache.delete(self.gargoyle.cache_key)
241+ cache.delete(self.gargoyle.remote_cache_key)
242 self.assertFalse(self.gargoyle.is_active('test'))
243 switch.status, switch.value = GLOBAL, {}
244 # Ensure post save gets sent
245@@ -524,6 +530,18 @@
246 )
247 self.assertFalse(self.gargoyle.is_active('test', request))
248
249+ self.assertTrue(self.gargoyle.is_active('test', self.gargoyle.as_request(ip_address='::1')))
250+
251+ switch.clear_conditions(
252+ condition_set=condition_set,
253+ )
254+ switch.add_condition(
255+ condition_set=condition_set,
256+ field_name='percent',
257+ condition='0-50',
258+ )
259+ self.assertFalse(self.gargoyle.is_active('test', request))
260+
261 def test_to_dict(self):
262 condition_set = 'gargoyle.builtins.IPAddressConditionSet'
263
264@@ -1073,3 +1091,82 @@
265 self.assertFalse(self.gargoyle.is_active('test'))
266
267 self.assertEquals(self.gargoyle['test'].status, GLOBAL)
268+
269+
270+class CommandAddSwitchTestCase(TestCase):
271+
272+ def setUp(self):
273+ self.gargoyle = SwitchManager(Switch, key='key', value='value', instances=True, auto_create=True)
274+
275+ def test_requires_single_arg(self):
276+ too_few_too_many = [
277+ [],
278+ ['one', 'two'],
279+ ]
280+ for args in too_few_too_many:
281+ command = AddSwitchCmd()
282+
283+ self.assertRaises(CommandError, command.handle, *args)
284+
285+ def test_add_switch_default_status(self):
286+ self.assertNotIn('switch_default', self.gargoyle)
287+
288+ call_command('add_switch', 'switch_default')
289+
290+ self.assertIn('switch_default', self.gargoyle)
291+ self.assertEqual(GLOBAL, self.gargoyle['switch_default'].status)
292+
293+ def test_add_switch_with_status(self):
294+ self.assertNotIn('switch_disabled', self.gargoyle)
295+
296+ call_command('add_switch', 'switch_disabled', status=DISABLED)
297+
298+ self.assertIn('switch_disabled', self.gargoyle)
299+ self.assertEqual(DISABLED, self.gargoyle['switch_disabled'].status)
300+
301+ def test_update_switch_status_disabled(self):
302+ Switch.objects.create(key='test', status=GLOBAL)
303+ self.assertEqual(GLOBAL, self.gargoyle['test'].status)
304+
305+ call_command('add_switch', 'test', status=DISABLED)
306+
307+ self.assertEqual(DISABLED, self.gargoyle['test'].status)
308+
309+ def test_update_switch_status_to_default(self):
310+ Switch.objects.create(key='test', status=DISABLED)
311+ self.assertEqual(DISABLED, self.gargoyle['test'].status)
312+
313+ call_command('add_switch', 'test')
314+
315+ self.assertEqual(GLOBAL, self.gargoyle['test'].status)
316+
317+
318+class CommandRemoveSwitchTestCase(TestCase):
319+
320+ def setUp(self):
321+ self.gargoyle = SwitchManager(Switch, key='key', value='value', instances=True, auto_create=True)
322+
323+ def test_requires_single_arg(self):
324+ too_few_too_many = [
325+ [],
326+ ['one', 'two'],
327+ ]
328+ for args in too_few_too_many:
329+ command = RemoveSwitchCmd()
330+
331+ self.assertRaises(CommandError, command.handle, *args)
332+
333+ def test_removes_switch(self):
334+ Switch.objects.create(key='test')
335+ self.assertIn('test', self.gargoyle)
336+
337+ call_command('remove_switch', 'test')
338+
339+ self.assertNotIn('test', self.gargoyle)
340+
341+ def test_remove_non_switch_doesnt_error(self):
342+ self.assertNotIn('idontexist', self.gargoyle)
343+
344+ call_command('remove_switch', 'idontexist')
345+
346+ self.assertNotIn('idontexist', self.gargoyle)

Subscribers

People subscribed via source and target branches