1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
Canonical Config
================
`lp.services.config` provides singleton access to the Launchpad
configuration, accessed via the `config` module global. It is
responsible for loading the Launchpad schema and the environment's
correct config.
LaunchpadConfig AKA config
--------------------------
LaunchpadConfig is a singleton that manages access to the config. Cached
copies are kept in thread locals ensuring the configuration is thread
safe (not that this will be a problem if we stick with simple
configuration).
>>> from lp.services.config import config
>>> from lp.testing.layers import DatabaseLayer
>>> expected = (
... 'dbname=%s host=localhost' % DatabaseLayer._db_fixture.dbname)
>>> expected == config.database.rw_main_master
True
>>> config.database.db_statement_timeout is None
True
>>> config.launchpad.dbuser
'launchpad_main'
>>> config.librarian.dbuser
'librarian'
Configs are kept from the 'configs' directory.
>>> import os.path, canonical
>>> os.path.join(config.root, 'lib', 'canonical') == os.path.dirname(
... canonical.__file__)
True
LaunchpadConfig loads the conf file from the directory that matches its
instance_name. The instance name is often the same as the LPCONFIG
environment variable (scripts can override this using setInstance()). It
will choose the conf file that matches its process_name if it exists,
otherwise it loads launchpad-lazr.conf. The general rule is
configs/<instance_name>/<process_name>.conf. The testrunner sets the
instance_name to 'testrunner' and additionally uses unique temporary
configs to permit changing the config during tests (but not if we are
using persistent helpers - see lp.testing.layers).
>>> (config.instance_name == 'testrunner' or
... config.instance_name == os.environ['LPCONFIG'])
True
>>> config.process_name
'test'
>>> config.filename
'.../launchpad-lazr.conf'
>>> config.extends.filename
'.../launchpad-lazr.conf'
LaunchpadConfig provides __contains__ and __getitem__ to check and
access lazr.config sections and keys.
>>> 'launchpad' in config
True
>>> config['launchpad']['default_batch_size']
5
The application root directory is assigned to the root attribute.
>>> import lp.services.config
>>> example_path = lp.services.config.__file__
>>> config.root in example_path
True
>>> example_path[len(config.root):]
'/lib/lp/services/...'
The config instance has additional attributes that come from the
ZConfig.
>>> config.devmode
True
>>> len(config.servers)
4
>>> config.threads
4
Working with test configurations
--------------------------------
Tests can update the config with test data. For example, the domain can
be changed for a feature.
>>> test_data = ("""
... [answertracker]
... email_domain: answers.launchpad.dev""")
>>> config.push('test_data', test_data)
>>> config.answertracker.email_domain
'answers.launchpad.dev'
And the test can remove the data when it is done to restore the config.
>>> config.pop('test_data')
(<lazr.config...ConfigData ...>,)
>>> config.answertracker.email_domain
'answers.launchpad.net'
Selecting the conf file with instance and process names
-------------------------------------------------------
The name of the conf file, and the directory from which is resides, is
controlled by the config's process_name and instance_name. These may be
set by their corresponding methods, *before* accessing the config, to
set where the config values are loaded from. After the config is loaded,
changing the instance and process names will have no affect.
Setting just the instance_name will change the directory from which the
conf file is loaded.
>>> from lp.services.config import LaunchpadConfig
>>> test_config = LaunchpadConfig('testrunner', 'test')
>>> test_config.setInstance('development')
>>> test_config.instance_name
'development'
>>> test_config.filename
'.../configs/development/launchpad-lazr.conf'
>>> test_config.extends.filename
'.../config/schema-lazr.conf'
>>> test_config.answertracker.days_before_expiration
15
Changing the instance_name and process_name changes the directory and
conf file name that is loaded.
>>> test_config.setInstance('testrunner')
>>> test_config.instance_name
'testrunner'
>>> test_config.answertracker.days_before_expiration
15
>>> test_config.setProcess('test-process')
>>> test_config.process_name
'test-process'
>>> test_config.filename
'.../configs/testrunner/test-process-lazr.conf'
>>> test_config.extends.filename
'.../configs/testrunner/launchpad-lazr.conf'
>>> test_config.answertracker.days_before_expiration
300
The default 'launchpad-lazr.conf' is loaded if no conf files match the
process's name.
>>> test_config.setInstance('testrunner')
>>> test_config.instance_name
'testrunner'
>>> test_config.setProcess('test_no_conf')
>>> test_config.process_name
'test_no_conf'
>>> test_config.filename
'.../configs/testrunner/launchpad-lazr.conf'
>>> test_config.extends.filename
'.../configs/development/launchpad-lazr.conf'
>>> test_config.answertracker.days_before_expiration
15
>>> config.setInstance(config.instance_name)
The initial instance_name is set via the LPCONFIG environment variable.
Because Config is designed to failover to the default development
environment, and the testrunner overrides the environment and config, we
need to reconfigure the environment and reload the lp.services.config
module to test LaunchpadConfig's behaviour.
Alternatively, the instance name and process name can be specified as
argument to the constructor.
>>> dev_config = LaunchpadConfig('development', 'authserver')
>>> dev_config.instance_name
'development'
>>> dev_config.process_name
'authserver'
>>> dev_config.devmode
True
# XXX sinzui 2008-03-25 bug=78545: This cannot be tested until the
# config can be restored when this test is torn down.
# >>> true_config = config
# >>> import os
# >>> from lp.services.config import LPCONFIG, DEFAULT_SECTION
# >>> os.environ[LPCONFIG] = 'mailman-itests'
# >>> os.environ[DEFAULT_SECTION] = 'default'
# # reload the LaunchpadConfig class object.
# >>> config_module = reload(lp.services.config)
# >>> from lp.services.config import config
# >>> config.filename
# '.../configs/mailman-itests/launchpad-lazr.conf'
# >>> config.extends.filename
# '.../configs/development/launchpad-lazr.conf'
# >>> config.database.dbname
# 'launchpad_dev'
#And the staging ZConfig (that is deprecated) is also selected.
# >>> config.dbname
# 'launchpad_dev'
# >>> config._cache.default
# <SectionValue for canonical 'default'>
# >>> config._cache.testrunner
# Traceback (most recent call last):
# ...
# AttributeError: 'zope.thread.local' object has no attribute 'testrunner'
#We need to reset the config for the testrunner.
# >>> config = true_config
# >>> lp.services.config.config = config
# >>> config.filename
# '.../configs/testrunner/launchpad-lazr.conf'
# >>> config.dbname == DatabaseLayer._db_fixture.dbname
# True
# >>> config._cache.testrunner
# <SectionValue for canonical 'testrunner'>
|