Merge lp:~jtv/maas/defer-task-logger into lp:~maas-committers/maas/trunk

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 1353
Proposed branch: lp:~jtv/maas/defer-task-logger
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 352 lines (+63/-40)
6 files modified
src/provisioningserver/boot_images.py (+4/-4)
src/provisioningserver/dhcp/leases.py (+4/-4)
src/provisioningserver/start_cluster_controller.py (+18/-9)
src/provisioningserver/tags.py (+19/-16)
src/provisioningserver/tests/test_start_cluster_controller.py (+8/-1)
src/provisioningserver/tests/test_tags.py (+10/-6)
To merge this branch: bzr merge lp:~jtv/maas/defer-task-logger
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+134679@code.launchpad.net

Commit message

Defer celery imports in pserv that may load config and issue warnings.

Description of the change

Discussed the details with Gavin. This avoids problems with maas-provision importing celery modules that, as a side effect, try to read celeryconfig.py. That module is not always available, e.g. on a dev system, or on a cluster controller that isn't also a region controller where celery hasn't been told to load the right config module, so that it may output inappropriate warnings.

Ultimately the start_cluster_controller command will still need the problematic celery imports, but at least they won't be triggered by mere imports when running other maas-provision commands.

Jeroen

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) :
review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 17/11/12 00:59, Jeroen T. Vermeulen wrote:
> Ultimately the start_cluster_controller command will still need the problematic celery imports

Why? start_cluster_controller is not a task and has nothing to do with
celery (other than execing a celery daemon).

Revision history for this message
Gavin Panella (allenap) wrote :

On 19 November 2012 01:37, Julian Edwards wrote:
> On 17/11/12 00:59, Jeroen T. Vermeulen wrote:
>> Ultimately the start_cluster_controller command will still need the
>> problematic celery imports
>
> Why? start_cluster_controller is not a task and has nothing to do
> with celery (other than execing a celery daemon).

In start_cluster_controller.run(), logging is configured using the
configuration given for Celery. This basically involves calling
setup_logging_subsystem, a Celery function. It's imported late now, so
won't be pulled in at the moment start_cluster_controller is imported,
and therefore not when other maas-provision commands are used.

As to why setup_logging_subsystem is used at all... it's in order to
see start-up messages, i.e. while the cluster is polling the region
for acceptance. It seems sensible to send these messages to the worker
logs; I think it's the first place most people will look.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 19/11/12 20:07, Gavin Panella wrote:
> In start_cluster_controller.run(), logging is configured using the
> configuration given for Celery.

This begs the next question, why? :)

As in, why not just the normal logging.

Revision history for this message
Gavin Panella (allenap) wrote :

On 19 November 2012 12:01, Julian Edwards <email address hidden> wrote:
> On 19/11/12 20:07, Gavin Panella wrote:
>> In start_cluster_controller.run(), logging is configured using the
>> configuration given for Celery.
>
> This begs the next question, why? :)
>
> As in, why not just the normal logging.

Because it goes nowhere, or nowhere obvious.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 19/11/12 22:22, Gavin Panella wrote:
> On 19 November 2012 12:01, Julian Edwards <email address hidden> wrote:
>> On 19/11/12 20:07, Gavin Panella wrote:
>>> In start_cluster_controller.run(), logging is configured using the
>>> configuration given for Celery.
>>
>> This begs the next question, why? :)
>>
>> As in, why not just the normal logging.
>
> Because it goes nowhere, or nowhere obvious.
>

So what is the barrier to fixing that?

/me playing root-cause analysis

Revision history for this message
Gavin Panella (allenap) wrote :

On 19 November 2012 23:19, Julian Edwards <email address hidden> wrote:
> So what is the barrier to fixing that?

Nothing, it's fixed :)

I don't really know what the problem is now. The "hello region, I'm a
cluster, please recognise me" logs are now sent to the same log file
that celeryd logs to, which seems to be a fairly sensible place. We
/could/ add another log file, or reuse one, just for those messages,
but I can't see that we'd get a lot for the complexity it would bring.
I think we'd actually lose clarity.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 20/11/12 19:37, Gavin Panella wrote:
> On 19 November 2012 23:19, Julian Edwards <email address hidden> wrote:
>> So what is the barrier to fixing that?
>
> Nothing, it's fixed :)
>
> I don't really know what the problem is now. The "hello region, I'm a
> cluster, please recognise me" logs are now sent to the same log file
> that celeryd logs to, which seems to be a fairly sensible place. We
> /could/ add another log file, or reuse one, just for those messages,
> but I can't see that we'd get a lot for the complexity it would bring.
> I think we'd actually lose clarity.
>

Ok fair enough.

I thought that it was still using celery logging somewhere.

Revision history for this message
Gavin Panella (allenap) wrote :

On 20 November 2012 10:01, Julian Edwards <email address hidden> wrote:
> I thought that it was still using celery logging somewhere.

It kind of is. It calls setup_logging_subsystem(), which is from
Celery. However, Celery uses the regular Python logging module, and
setup_logging_subsystem configures that, according to the settings in
the Celery config module. The change that Jeroen made ensures that
Celery is only imported when you've unambiguously said "start me a
cluster controller", which, though misnamed, equates to starting
celeryd. Put another way, it's not importing any Celery code until
you've asked it to start Celery.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/boot_images.py'
2--- src/provisioningserver/boot_images.py 2012-11-08 06:34:48 +0000
3+++ src/provisioningserver/boot_images.py 2012-11-16 14:58:20 +0000
4@@ -19,13 +19,13 @@
5 ]
6
7 import json
8+from logging import getLogger
9
10 from apiclient.maas_client import (
11 MAASClient,
12 MAASDispatcher,
13 MAASOAuth,
14 )
15-from celery.log import get_task_logger
16 from provisioningserver.auth import (
17 get_recorded_api_credentials,
18 get_recorded_maas_url,
19@@ -35,7 +35,7 @@
20 from provisioningserver.start_cluster_controller import get_cluster_uuid
21
22
23-task_logger = get_task_logger(name=__name__)
24+logger = getLogger(__name__)
25
26
27 def get_cached_knowledge():
28@@ -46,10 +46,10 @@
29 """
30 maas_url = get_recorded_maas_url()
31 if maas_url is None:
32- task_logger.debug("Not reporting boot images: don't have API URL yet.")
33+ logger.debug("Not reporting boot images: don't have API URL yet.")
34 api_credentials = get_recorded_api_credentials()
35 if api_credentials is None:
36- task_logger.debug("Not reporting boot images: don't have API key yet.")
37+ logger.debug("Not reporting boot images: don't have API key yet.")
38 return maas_url, api_credentials
39
40
41
42=== modified file 'src/provisioningserver/dhcp/leases.py'
43--- src/provisioningserver/dhcp/leases.py 2012-10-19 15:08:52 +0000
44+++ src/provisioningserver/dhcp/leases.py 2012-11-16 14:58:20 +0000
45@@ -34,6 +34,7 @@
46
47 import errno
48 import json
49+from logging import getLogger
50 from os import (
51 fstat,
52 stat,
53@@ -45,7 +46,6 @@
54 MAASOAuth,
55 )
56 from celery.app import app_or_default
57-from celery.log import get_task_logger
58 from provisioningserver import cache
59 from provisioningserver.auth import (
60 get_recorded_api_credentials,
61@@ -55,7 +55,7 @@
62 from provisioningserver.dhcp.leases_parser import parse_leases
63
64
65-task_logger = get_task_logger(name=__name__)
66+logger = getLogger(__name__)
67
68
69 # Cache key for the modification time on last-processed leases file.
70@@ -157,7 +157,7 @@
71 if None in knowledge.values():
72 # The MAAS server hasn't sent us enough information for us to do
73 # this yet. Leave it for another time.
74- task_logger.info(
75+ logger.info(
76 "Not sending DHCP leases to server: not all required knowledge "
77 "received from server yet. "
78 "Missing: %s"
79@@ -189,7 +189,7 @@
80 timestamp, leases = parse_result
81 process_leases(timestamp, leases)
82 else:
83- task_logger.info(
84+ logger.info(
85 "The DHCP leases file does not exist. This is only a problem if "
86 "this cluster controller is managing its DHCP server. If that's "
87 "the case then you need to install the 'maas-dhcp' package on "
88
89=== modified file 'src/provisioningserver/start_cluster_controller.py'
90--- src/provisioningserver/start_cluster_controller.py 2012-10-23 12:48:31 +0000
91+++ src/provisioningserver/start_cluster_controller.py 2012-11-16 14:58:20 +0000
92@@ -18,6 +18,7 @@
93 from grp import getgrnam
94 import httplib
95 import json
96+from logging import getLogger
97 import os
98 from pwd import getpwnam
99 from time import sleep
100@@ -31,15 +32,10 @@
101 MAASDispatcher,
102 NoAuth,
103 )
104-from celery.app import app_or_default
105-from celery.log import (
106- get_task_logger,
107- setup_logging_subsystem,
108- )
109 from provisioningserver.network import discover_networks
110
111
112-task_logger = get_task_logger(name=__name__)
113+logger = getLogger(__name__)
114
115
116 class ClusterControllerRejected(Exception):
117@@ -59,7 +55,7 @@
118
119
120 def log_error(exception):
121- task_logger.info(
122+ logger.info(
123 "Could not register with region controller: %s."
124 % exception.reason)
125
126@@ -71,6 +67,9 @@
127
128 def get_cluster_uuid():
129 """Read this cluster's UUID from the config."""
130+ # Import this lazily. It reads config as a side effect, which can
131+ # produce warnings.
132+ from celery.app import app_or_default
133 return app_or_default().conf.CLUSTER_UUID
134
135
136@@ -145,7 +144,7 @@
137 try:
138 client.post('api/1.0/nodegroups/', 'refresh_workers')
139 except URLError as e:
140- task_logger.warn(
141+ logger.warn(
142 "Could not request secrets from region controller: %s"
143 % e.reason)
144
145@@ -164,13 +163,23 @@
146 start_celery(connection_details, user=user, group=group)
147
148
149+def set_up_logging():
150+ """Set up logging."""
151+ # This import has side effects (it imports celeryconfig) and may
152+ # produce warnings (if there is no celeryconfig).
153+ # Postpone the import so that we don't go through that every time
154+ # anything imports this module.
155+ from celery.log import setup_logging_subsystem
156+ setup_logging_subsystem()
157+
158+
159 def run(args):
160 """Start the cluster controller.
161
162 If this system is still awaiting approval as a cluster controller, this
163 command will keep looping until it gets a definite answer.
164 """
165- setup_logging_subsystem()
166+ set_up_logging()
167 connection_details = register(args.server_url)
168 while connection_details is None:
169 sleep(60)
170
171=== modified file 'src/provisioningserver/tags.py'
172--- src/provisioningserver/tags.py 2012-10-26 10:24:52 +0000
173+++ src/provisioningserver/tags.py 2012-11-16 14:58:20 +0000
174@@ -19,6 +19,7 @@
175
176
177 import httplib
178+from logging import getLogger
179 import urllib2
180
181 from apiclient.maas_client import (
182@@ -26,7 +27,6 @@
183 MAASDispatcher,
184 MAASOAuth,
185 )
186-from celery.log import get_task_logger
187 from lxml import etree
188 from provisioningserver.auth import (
189 get_recorded_api_credentials,
190@@ -36,7 +36,7 @@
191 import simplejson as json
192
193
194-task_logger = get_task_logger(name=__name__)
195+logger = getLogger(__name__)
196
197
198 class MissingCredentials(Exception):
199@@ -53,15 +53,15 @@
200 """
201 maas_url = get_recorded_maas_url()
202 if maas_url is None:
203- task_logger.error("Not updating tags: don't have API URL yet.")
204+ logger.error("Not updating tags: don't have API URL yet.")
205 return None, None
206 api_credentials = get_recorded_api_credentials()
207 if api_credentials is None:
208- task_logger.error("Not updating tags: don't have API key yet.")
209+ logger.error("Not updating tags: don't have API key yet.")
210 return None, None
211 nodegroup_uuid = get_recorded_nodegroup_uuid()
212 if nodegroup_uuid is None:
213- task_logger.error("Not updating tags: don't have UUID yet.")
214+ logger.error("Not updating tags: don't have UUID yet.")
215 return None, None
216 client = MAASClient(MAASOAuth(*api_credentials), MAASDispatcher(),
217 maas_url)
218@@ -119,7 +119,8 @@
219 :param removed: Set of nodes to remove
220 """
221 path = '/api/1.0/tags/%s/' % (tag_name,)
222- task_logger.debug('Updating nodes for %s %s, adding %s removing %s'
223+ logger.debug(
224+ "Updating nodes for %s %s, adding %s removing %s"
225 % (tag_name, uuid, len(added), len(removed)))
226 try:
227 return process_response(client.post(
228@@ -131,7 +132,7 @@
229 msg = e.fp.read()
230 else:
231 msg = e.msg
232- task_logger.info('Got a CONFLICT while updating tag: %s', msg)
233+ logger.info("Got a CONFLICT while updating tag: %s", msg)
234 return {}
235 raise
236
237@@ -148,8 +149,8 @@
238 try:
239 xml = etree.XML(hw_xml)
240 except etree.XMLSyntaxError as e:
241- task_logger.debug('Invalid hardware_details for %s: %s'
242- % (system_id, e))
243+ logger.debug(
244+ "Invalid hardware_details for %s: %s" % (system_id, e))
245 else:
246 if xpath(xml):
247 matched = True
248@@ -166,16 +167,17 @@
249 batch_size = DEFAULT_BATCH_SIZE
250 all_matched = []
251 all_unmatched = []
252- task_logger.debug('processing %d system_ids for tag %s nodegroup %s'
253- % (len(system_ids), tag_name, nodegroup_uuid))
254+ logger.debug(
255+ "processing %d system_ids for tag %s nodegroup %s"
256+ % (len(system_ids), tag_name, nodegroup_uuid))
257 for i in range(0, len(system_ids), batch_size):
258 selected_ids = system_ids[i:i + batch_size]
259 details = get_hardware_details_for_nodes(
260 client, nodegroup_uuid, selected_ids)
261 matched, unmatched = process_batch(xpath, details)
262- task_logger.debug(
263- 'processing batch of %d ids received %d details'
264- ' (%d matched, %d unmatched)'
265+ logger.debug(
266+ "processing batch of %d ids received %d details"
267+ " (%d matched, %d unmatched)"
268 % (len(selected_ids), len(details), len(matched), len(unmatched)))
269 all_matched.extend(matched)
270 all_unmatched.extend(unmatched)
271@@ -197,8 +199,9 @@
272 """
273 client, nodegroup_uuid = get_cached_knowledge()
274 if not all([client, nodegroup_uuid]):
275- task_logger.error('Unable to update tag: %s for definition %r'
276- ' please refresh secrets, then rebuild this tag'
277+ logger.error(
278+ "Unable to update tag: %s for definition %r. "
279+ "Please refresh secrets, then rebuild this tag."
280 % (tag_name, tag_definition))
281 raise MissingCredentials()
282 # We evaluate this early, so we can fail before sending a bunch of data to
283
284=== modified file 'src/provisioningserver/tests/test_start_cluster_controller.py'
285--- src/provisioningserver/tests/test_start_cluster_controller.py 2012-10-23 12:48:31 +0000
286+++ src/provisioningserver/tests/test_start_cluster_controller.py 2012-11-16 14:58:20 +0000
287@@ -25,7 +25,10 @@
288
289 from apiclient.maas_client import MAASDispatcher
290 from apiclient.testing.django import parse_headers_and_body_with_django
291-from fixtures import EnvironmentVariableFixture
292+from fixtures import (
293+ EnvironmentVariableFixture,
294+ FakeLogger,
295+ )
296 from maastesting.factory import factory
297 from mock import (
298 call,
299@@ -84,6 +87,10 @@
300
301 def setUp(self):
302 super(TestStartClusterController, self).setUp()
303+
304+ self.useFixture(FakeLogger())
305+ self.patch(start_cluster_controller, 'set_up_logging')
306+
307 # Patch out anything that could be remotely harmful if we did it
308 # accidentally in the test. Make the really outrageous ones
309 # raise exceptions.
310
311=== modified file 'src/provisioningserver/tests/test_tags.py'
312--- src/provisioningserver/tests/test_tags.py 2012-10-24 16:29:02 +0000
313+++ src/provisioningserver/tests/test_tags.py 2012-11-16 14:58:20 +0000
314@@ -12,21 +12,21 @@
315 __metaclass__ = type
316 __all__ = []
317
318+import httplib
319+import urllib2
320+
321 from apiclient.maas_client import MAASClient
322-import httplib
323+from fixtures import FakeLogger
324 from lxml import etree
325 from maastesting.factory import factory
326 from maastesting.fakemethod import (
327 FakeMethod,
328 MultiFakeMethod,
329 )
330-import urllib2
331 from mock import MagicMock
332-from provisioningserver.auth import (
333- get_recorded_nodegroup_uuid,
334- )
335+from provisioningserver import tags
336+from provisioningserver.auth import get_recorded_nodegroup_uuid
337 from provisioningserver.testing.testcase import PservTestCase
338-from provisioningserver import tags
339
340
341 class FakeResponse:
342@@ -41,6 +41,10 @@
343
344 class TestTagUpdating(PservTestCase):
345
346+ def setUp(self):
347+ super(TestTagUpdating, self).setUp()
348+ self.useFixture(FakeLogger())
349+
350 def test_get_cached_knowledge_knows_nothing(self):
351 # If we haven't given it any secrets, we should get back nothing
352 self.assertEqual((None, None), tags.get_cached_knowledge())