12998.2.1
by mbp at canonical
Add a feature-flag scram switch for DKIM mail authentication. |
1 |
# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
|
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
3 |
||
7675.754.25
by Martin Pool
Fix lint export warning |
4 |
__all__ = [ |
5 |
'FeatureController', |
|
12156.15.1
by Benji York
add feature flag documentation |
6 |
'flag_info', |
7675.754.40
by Martin Pool
Add a NullFeatureController and use that in LaunchpadTestRequest |
7 |
'NullFeatureController', |
12156.15.1
by Benji York
add feature flag documentation |
8 |
'undocumented_flags', |
12378.1.3
by William Grant
Document value domains. |
9 |
'value_domain_info', |
7675.754.25
by Martin Pool
Fix lint export warning |
10 |
]
|
11 |
||
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
12 |
|
7675.754.57
by Martin Pool
Split out storm access into FeatureRuleSource |
13 |
from lp.services.features.rulesource import ( |
11539.1.6
by Robert Collins
Fix race condition with using features to control timeouts. |
14 |
NullFeatureRuleSource, |
7675.754.57
by Martin Pool
Split out storm access into FeatureRuleSource |
15 |
StormFeatureRuleSource, |
16 |
)
|
|
17 |
||
18 |
||
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
19 |
__metaclass__ = type |
20 |
||
12378.1.3
by William Grant
Document value domains. |
21 |
|
22 |
value_domain_info = sorted([ |
|
23 |
('boolean', |
|
12378.1.4
by William Grant
Make existing empty/non-empty flags boolean. |
24 |
'Any non-empty value is true; an empty value is false.'), |
12378.1.3
by William Grant
Document value domains. |
25 |
('float', |
26 |
'The flag value is set to the given floating point number.'), |
|
13026.1.1
by Jeroen Vermeulen
Document a feature flag I added in too much of a hurry. Document flag defaults. |
27 |
('int', |
28 |
"An integer."), |
|
13445.1.1
by Gary Poster
bug._shouldConfirmBugtasks added |
29 |
('space delimited', |
30 |
'Space-delimited strings.') |
|
12378.1.3
by William Grant
Document value domains. |
31 |
])
|
32 |
||
13026.1.1
by Jeroen Vermeulen
Document a feature flag I added in too much of a hurry. Document flag defaults. |
33 |
# Data for generating web-visible feature flag documentation.
|
34 |
#
|
|
35 |
# Entries for each flag are:
|
|
36 |
# flag name, value domain, prose documentation, default behaviour.
|
|
37 |
#
|
|
38 |
# Value domain as in value_domain_info above.
|
|
39 |
#
|
|
40 |
# NOTE: "default behaviour" does not specify a default value. It
|
|
41 |
# merely documents the code's behaviour if no value is specified.
|
|
12156.15.1
by Benji York
add feature flag documentation |
42 |
flag_info = sorted([ |
13155.1.22
by Curtis Hovey
Register the feature flag. |
43 |
('bugs.bugtracker_components.enabled', |
44 |
'boolean', |
|
45 |
('Enables the display of bugtracker components.'), |
|
46 |
''), |
|
13302.8.20
by Andrew Bennetts
Fix feature flag definition in flags.py to agree with the .pt files. |
47 |
('code.ajax_revision_diffs.enabled', |
13302.8.12
by Jeroen Vermeulen
Wrap inline diff expanders in feature flag. Leave js-action class for the expander icon to the expander widiget. |
48 |
'boolean', |
49 |
("Offer expandable inline diffs for branch revisions."), |
|
50 |
''), |
|
12378.1.1
by William Grant
Document code.branchmergequeue and code.incremental_diffs.enabled feature flags, and clean up the docs for the others. |
51 |
('code.branchmergequeue', |
12378.1.4
by William Grant
Make existing empty/non-empty flags boolean. |
52 |
'boolean', |
12378.1.8
by William Grant
Sentence-case and expand flag docs. |
53 |
'Enables merge queue pages and lists them on branch pages.', |
12378.1.1
by William Grant
Document code.branchmergequeue and code.incremental_diffs.enabled feature flags, and clean up the docs for the others. |
54 |
''), |
55 |
('code.incremental_diffs.enabled', |
|
12378.1.4
by William Grant
Make existing empty/non-empty flags boolean. |
56 |
'boolean', |
12378.1.8
by William Grant
Sentence-case and expand flag docs. |
57 |
'Shows incremental diffs on merge proposals.', |
12378.1.1
by William Grant
Document code.branchmergequeue and code.incremental_diffs.enabled feature flags, and clean up the docs for the others. |
58 |
''), |
12156.15.1
by Benji York
add feature flag documentation |
59 |
('hard_timeout', |
60 |
'float', |
|
12378.1.11
by William Grant
hard_timeout is milliseconds, not seconds. |
61 |
'Sets the hard request timeout in milliseconds.', |
12156.15.1
by Benji York
add feature flag documentation |
62 |
''), |
12998.2.1
by mbp at canonical
Add a feature-flag scram switch for DKIM mail authentication. |
63 |
('mail.dkim_authentication.disabled', |
64 |
'boolean', |
|
65 |
'Disable DKIM authentication checks on incoming mail.', |
|
66 |
''), |
|
12371.1.1
by Robert Collins
Permit turning off targetnamecache like searches. |
67 |
('malone.disable_targetnamesearch', |
12378.1.4
by William Grant
Make existing empty/non-empty flags boolean. |
68 |
'boolean', |
12378.1.8
by William Grant
Sentence-case and expand flag docs. |
69 |
'If true, disables consultation of target names during bug text search.', |
12371.1.1
by Robert Collins
Permit turning off targetnamecache like searches. |
70 |
''), |
12156.15.1
by Benji York
add feature flag documentation |
71 |
('memcache', |
12378.1.7
by William Grant
memcache is now a boolean, defaulting to enabled. |
72 |
'boolean', |
12378.1.8
by William Grant
Sentence-case and expand flag docs. |
73 |
'Enables use of memcached where it is supported.', |
12156.15.1
by Benji York
add feature flag documentation |
74 |
'enabled'), |
12443.2.1
by Robert Collins
Permit enabling profiling via a feature flag. |
75 |
('profiling.enabled', |
76 |
'boolean', |
|
12443.2.5
by Robert Collins
Unbreak +opstats and +haproxy. |
77 |
'Overrides config.profiling.profiling_allowed to permit profiling.', |
12443.2.1
by Robert Collins
Permit enabling profiling via a feature flag. |
78 |
''), |
13026.1.1
by Jeroen Vermeulen
Document a feature flag I added in too much of a hurry. Document flag defaults. |
79 |
('soyuz.derived_series.max_synchronous_syncs', |
80 |
'int', |
|
81 |
"How many package syncs may be done directly in a web request.", |
|
82 |
'100'), |
|
7675.1155.1
by Gavin Panella
Rename soyuz.derived-series-(sync|ui).enabled flags to soyuz.derived_series_(sync|ui).enabled. |
83 |
('soyuz.derived_series_ui.enabled', |
12378.1.9
by William Grant
Document soyuz.derived-series-ui.enabled. |
84 |
'boolean', |
85 |
'Enables derivative distributions pages.', |
|
86 |
''), |
|
7675.1155.1
by Gavin Panella
Rename soyuz.derived-series-(sync|ui).enabled flags to soyuz.derived_series_(sync|ui).enabled. |
87 |
('soyuz.derived_series_sync.enabled', |
13001.1.1
by Julian Edwards
Add soyuz.derived-series-sync.enabled flag to protect the sync button |
88 |
'boolean', |
89 |
'Enables syncing of packages on derivative distributions pages.', |
|
90 |
''), |
|
13543.7.1
by Jeroen Vermeulen
Disable Upgrade Packages button. |
91 |
('soyuz.derived_series_upgrade.enabled', |
92 |
'boolean', |
|
93 |
'Enables mass-upgrade of packages on derivative distributions pages.', |
|
94 |
''), |
|
12558.2.8
by Jeroen Vermeulen
Standardized boolean feature flag, thanks wgrant. |
95 |
('soyuz.derived_series_jobs.enabled', |
96 |
'boolean', |
|
97 |
"Compute package differences for derived distributions.", |
|
98 |
''), |
|
12428.4.16
by Henning Eggers
Added feature flag for sharing information display. |
99 |
('translations.sharing_information.enabled', |
100 |
'boolean', |
|
101 |
'Enables display of sharing information on translation pages.', |
|
102 |
''), |
|
12335.2.2
by Robert Collins
Show server render time when visible_render_time is set, with help from huwshimi. |
103 |
('visible_render_time', |
12378.1.4
by William Grant
Make existing empty/non-empty flags boolean. |
104 |
'boolean', |
12378.1.8
by William Grant
Sentence-case and expand flag docs. |
105 |
'Shows the server-side page render time in the login widget.', |
12335.2.2
by Robert Collins
Show server render time when visible_render_time is set, with help from huwshimi. |
106 |
''), |
12183.10.4
by Huw Wilkins
Renamed private notification flag to match guidlines and added documentation for it. |
107 |
('bugs.private_notification.enabled', |
108 |
'boolean', |
|
109 |
'Changes the appearance of notifications on private bugs.', |
|
110 |
''), |
|
13115.3.2
by Ian Booth
Add feature flag |
111 |
('disclosure.picker_enhancements.enabled', |
112 |
'boolean', |
|
13115.3.3
by Ian Booth
Modify description |
113 |
('Enables the display of extra details in the person picker.'), |
13115.3.2
by Ian Booth
Add feature flag |
114 |
''), |
13171.2.6
by William Grant
Document the new flag. |
115 |
('disclosure.person_affiliation_rank.enabled', |
116 |
'boolean', |
|
117 |
('Enables ranking by pillar affiliation in the person picker.'), |
|
118 |
''), |
|
13445.1.10
by Gary Poster
lint and related cleanups |
119 |
('bugs.autoconfirm.enabled_distribution_names', |
13445.1.1
by Gary Poster
bug._shouldConfirmBugtasks added |
120 |
'space delimited', |
121 |
('Enables auto-confirming bugtasks for distributions (and their ' |
|
13445.1.7
by Gary Poster
some small cleanups |
122 |
'series and packages). Use the default domain. Specify a single '
|
123 |
'asterisk ("*") to enable for all distributions.'), |
|
13445.1.1
by Gary Poster
bug._shouldConfirmBugtasks added |
124 |
'None are enabled'), |
13445.1.10
by Gary Poster
lint and related cleanups |
125 |
('bugs.autoconfirm.enabled_product_names', |
13445.1.1
by Gary Poster
bug._shouldConfirmBugtasks added |
126 |
'space delimited', |
13445.1.7
by Gary Poster
some small cleanups |
127 |
('Enables auto-confirming bugtasks for products (and their ' |
128 |
'series). Use the default domain. Specify a single '
|
|
129 |
'asterisk ("*") to enable for all products.'), |
|
13445.1.1
by Gary Poster
bug._shouldConfirmBugtasks added |
130 |
'None are enabled'), |
12156.15.1
by Benji York
add feature flag documentation |
131 |
])
|
132 |
||
133 |
# The set of all flag names that are documented.
|
|
134 |
documented_flags = set(info[0] for info in flag_info) |
|
135 |
# The set of all the flags names that have been used during the process
|
|
136 |
# lifetime, but were not documented in flag_info.
|
|
137 |
undocumented_flags = set() |
|
138 |
||
139 |
||
140 |
class Memoize(): |
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
141 |
|
142 |
def __init__(self, calc): |
|
143 |
self._known = {} |
|
144 |
self._calc = calc |
|
145 |
||
146 |
def lookup(self, key): |
|
147 |
if key in self._known: |
|
148 |
return self._known[key] |
|
149 |
v = self._calc(key) |
|
150 |
self._known[key] = v |
|
151 |
return v |
|
152 |
||
153 |
||
12156.15.1
by Benji York
add feature flag documentation |
154 |
class ScopeDict(): |
7675.754.35
by Martin Pool
Support looking up feature_scopes in page templates |
155 |
"""Allow scopes to be looked up by getitem"""
|
156 |
||
157 |
def __init__(self, features): |
|
158 |
self.features = features |
|
159 |
||
160 |
def __getitem__(self, scope_name): |
|
161 |
return self.features.isInScope(scope_name) |
|
162 |
||
163 |
||
12156.15.1
by Benji York
add feature flag documentation |
164 |
class FeatureController(): |
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
165 |
"""A FeatureController tells application code what features are active.
|
166 |
||
7675.754.21
by Martin Pool
lint fixes |
167 |
It does this by meshing together two sources of data:
|
11382.7.1
by Martin Pool
Pydoctor/epytext formatting cleanups |
168 |
|
169 |
- feature flags, typically set by an administrator into the database
|
|
170 |
||
171 |
- feature scopes, which would typically be looked up based on attributes
|
|
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
172 |
of the current web request, or the user for whom a job is being run, or
|
173 |
something similar.
|
|
174 |
||
7675.754.17
by Martin Pool
doc cleanups |
175 |
FeatureController presents a high level interface for application code to
|
7675.754.21
by Martin Pool
lint fixes |
176 |
query flag values, without it needing to know that they are stored in the
|
177 |
database.
|
|
7675.754.10
by Martin Pool
For convenience of Python code, give feature name and scope as strings |
178 |
|
179 |
At this level flag names and scope names are presented as strings for
|
|
180 |
easier use in Python code, though the values remain unicode. They
|
|
181 |
should always be ascii like Python identifiers.
|
|
7675.754.12
by Martin Pool
doc |
182 |
|
7675.754.17
by Martin Pool
doc cleanups |
183 |
One instance of FeatureController should be constructed for the lifetime
|
184 |
of code that has consistent configuration values. For instance there will
|
|
185 |
be one per web app request.
|
|
7675.754.14
by Martin Pool
Use plain Storm stores, not lp collections |
186 |
|
7675.754.57
by Martin Pool
Split out storm access into FeatureRuleSource |
187 |
Intended performance: when this object is first asked about a flag, it
|
188 |
will read the whole feature flag table from the database. It is expected
|
|
189 |
to be reasonably small. The scopes may be expensive to compute (eg
|
|
190 |
checking team membership) so they are checked at most once when
|
|
191 |
they are first needed.
|
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
192 |
|
193 |
The controller is then supposed to be held in a thread-local and reused
|
|
194 |
for the duration of the request.
|
|
7675.754.19
by Martin Pool
FeatureController loads everything at construction time |
195 |
|
11382.7.1
by Martin Pool
Pydoctor/epytext formatting cleanups |
196 |
@see: U{https://dev.launchpad.net/LEP/FeatureFlags}
|
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
197 |
"""
|
198 |
||
7675.754.57
by Martin Pool
Split out storm access into FeatureRuleSource |
199 |
def __init__(self, scope_check_callback, rule_source=None): |
7675.754.9
by Martin Pool
FeatureController knows its scope; emphasizes getting single values |
200 |
"""Construct a new view of the features for a set of scopes.
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
201 |
|
202 |
:param scope_check_callback: Given a scope name, says whether
|
|
203 |
it's active or not.
|
|
7675.754.62
by Martin Pool
resolve conflicts against devel |
204 |
|
7675.754.57
by Martin Pool
Split out storm access into FeatureRuleSource |
205 |
:param rule_source: Instance of StormFeatureRuleSource or similar.
|
7675.754.9
by Martin Pool
FeatureController knows its scope; emphasizes getting single values |
206 |
"""
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
207 |
self._known_scopes = Memoize(scope_check_callback) |
208 |
self._known_flags = Memoize(self._checkFlag) |
|
7675.754.33
by Martin Pool
Load feature rules from database only when needed |
209 |
# rules are read from the database the first time they're needed
|
210 |
self._rules = None |
|
7675.754.35
by Martin Pool
Support looking up feature_scopes in page templates |
211 |
self.scopes = ScopeDict(self) |
7675.754.57
by Martin Pool
Split out storm access into FeatureRuleSource |
212 |
if rule_source is None: |
213 |
rule_source = StormFeatureRuleSource() |
|
214 |
self.rule_source = rule_source |
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
215 |
|
216 |
def getFlag(self, flag): |
|
7675.754.39
by Robert Collins
Tweaks from the review. |
217 |
"""Get the value of a specific flag.
|
12156.15.1
by Benji York
add feature flag documentation |
218 |
|
7675.754.39
by Robert Collins
Tweaks from the review. |
219 |
:param flag: A name to lookup. e.g. 'recipes.enabled'
|
11382.7.1
by Martin Pool
Pydoctor/epytext formatting cleanups |
220 |
|
7675.754.39
by Robert Collins
Tweaks from the review. |
221 |
:return: The value of the flag determined by the highest priority rule
|
11382.7.1
by Martin Pool
Pydoctor/epytext formatting cleanups |
222 |
that matched.
|
7675.754.39
by Robert Collins
Tweaks from the review. |
223 |
"""
|
12156.15.1
by Benji York
add feature flag documentation |
224 |
# If this is an undocumented flag, record it.
|
225 |
if flag not in documented_flags: |
|
226 |
undocumented_flags.add(flag) |
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
227 |
return self._known_flags.lookup(flag) |
228 |
||
229 |
def _checkFlag(self, flag): |
|
7675.754.33
by Martin Pool
Load feature rules from database only when needed |
230 |
self._needRules() |
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
231 |
if flag in self._rules: |
7675.754.58
by Martin Pool
Move code to make text form of rules into FeatureRuleSource |
232 |
for scope, priority, value in self._rules[flag]: |
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
233 |
if self._known_scopes.lookup(scope): |
234 |
return value |
|
235 |
||
236 |
def isInScope(self, scope): |
|
237 |
return self._known_scopes.lookup(scope) |
|
238 |
||
7675.754.27
by Martin Pool
Provide FeatureController[name] and request.features for use in TAL |
239 |
def __getitem__(self, flag_name): |
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
240 |
"""FeatureController can be indexed.
|
241 |
||
242 |
This is to support easy zope traversal through eg
|
|
243 |
"request/features/a.b.c". We don't support other collection
|
|
244 |
protocols.
|
|
245 |
||
246 |
Note that calling this the first time for any key may cause
|
|
247 |
arbitrarily large amounts of work to be done to determine if the
|
|
248 |
controller is in any scopes relevant to this flag.
|
|
249 |
"""
|
|
7675.754.27
by Martin Pool
Provide FeatureController[name] and request.features for use in TAL |
250 |
return self.getFlag(flag_name) |
251 |
||
7675.754.6
by Martin Pool
Do more flag lookups through FeatureController facade |
252 |
def getAllFlags(self): |
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
253 |
"""Return a dict of all active flags.
|
7675.754.21
by Martin Pool
lint fixes |
254 |
|
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
255 |
This may be expensive because of evaluating many scopes, so it
|
256 |
shouldn't normally be used by code that only wants to know about one
|
|
257 |
or a few flags.
|
|
7675.754.1
by Martin Pool
Very basic stub tests for feature flags |
258 |
"""
|
7675.754.33
by Martin Pool
Load feature rules from database only when needed |
259 |
self._needRules() |
7675.754.29
by Martin Pool
FeatureController lazily checks scopes as needed |
260 |
return dict((f, self.getFlag(f)) for f in self._rules) |
7675.754.19
by Martin Pool
FeatureController loads everything at construction time |
261 |
|
7675.754.33
by Martin Pool
Load feature rules from database only when needed |
262 |
def _needRules(self): |
263 |
if self._rules is None: |
|
7675.754.60
by Martin Pool
Support parsing rules from text; generally pass them as tuples |
264 |
self._rules = self.rule_source.getAllRulesAsDict() |
7675.754.33
by Martin Pool
Load feature rules from database only when needed |
265 |
|
7675.754.31
by Martin Pool
FeatureController exposes the known features and scopes |
266 |
def usedFlags(self): |
7675.754.39
by Robert Collins
Tweaks from the review. |
267 |
"""Return dict of flags used in this controller so far."""
|
7675.754.31
by Martin Pool
FeatureController exposes the known features and scopes |
268 |
return dict(self._known_flags._known) |
269 |
||
270 |
def usedScopes(self): |
|
271 |
"""Return {scope: active} for scopes that have been used so far."""
|
|
272 |
return dict(self._known_scopes._known) |
|
7675.754.40
by Martin Pool
Add a NullFeatureController and use that in LaunchpadTestRequest |
273 |
|
274 |
||
275 |
class NullFeatureController(FeatureController): |
|
276 |
"""For use in testing: everything is turned off"""
|
|
277 |
||
278 |
def __init__(self): |
|
11539.1.6
by Robert Collins
Fix race condition with using features to control timeouts. |
279 |
FeatureController.__init__(self, lambda scope: None, |
280 |
NullFeatureRuleSource()) |