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
=== modified file 'src/provisioningserver/boot_images.py'
--- src/provisioningserver/boot_images.py 2012-11-08 06:34:48 +0000
+++ src/provisioningserver/boot_images.py 2012-11-16 14:58:20 +0000
@@ -19,13 +19,13 @@
19 ]19 ]
2020
21import json21import json
22from logging import getLogger
2223
23from apiclient.maas_client import (24from apiclient.maas_client import (
24 MAASClient,25 MAASClient,
25 MAASDispatcher,26 MAASDispatcher,
26 MAASOAuth,27 MAASOAuth,
27 )28 )
28from celery.log import get_task_logger
29from provisioningserver.auth import (29from provisioningserver.auth import (
30 get_recorded_api_credentials,30 get_recorded_api_credentials,
31 get_recorded_maas_url,31 get_recorded_maas_url,
@@ -35,7 +35,7 @@
35from provisioningserver.start_cluster_controller import get_cluster_uuid35from provisioningserver.start_cluster_controller import get_cluster_uuid
3636
3737
38task_logger = get_task_logger(name=__name__)38logger = getLogger(__name__)
3939
4040
41def get_cached_knowledge():41def get_cached_knowledge():
@@ -46,10 +46,10 @@
46 """46 """
47 maas_url = get_recorded_maas_url()47 maas_url = get_recorded_maas_url()
48 if maas_url is None:48 if maas_url is None:
49 task_logger.debug("Not reporting boot images: don't have API URL yet.")49 logger.debug("Not reporting boot images: don't have API URL yet.")
50 api_credentials = get_recorded_api_credentials()50 api_credentials = get_recorded_api_credentials()
51 if api_credentials is None:51 if api_credentials is None:
52 task_logger.debug("Not reporting boot images: don't have API key yet.")52 logger.debug("Not reporting boot images: don't have API key yet.")
53 return maas_url, api_credentials53 return maas_url, api_credentials
5454
5555
5656
=== modified file 'src/provisioningserver/dhcp/leases.py'
--- src/provisioningserver/dhcp/leases.py 2012-10-19 15:08:52 +0000
+++ src/provisioningserver/dhcp/leases.py 2012-11-16 14:58:20 +0000
@@ -34,6 +34,7 @@
3434
35import errno35import errno
36import json36import json
37from logging import getLogger
37from os import (38from os import (
38 fstat,39 fstat,
39 stat,40 stat,
@@ -45,7 +46,6 @@
45 MAASOAuth,46 MAASOAuth,
46 )47 )
47from celery.app import app_or_default48from celery.app import app_or_default
48from celery.log import get_task_logger
49from provisioningserver import cache49from provisioningserver import cache
50from provisioningserver.auth import (50from provisioningserver.auth import (
51 get_recorded_api_credentials,51 get_recorded_api_credentials,
@@ -55,7 +55,7 @@
55from provisioningserver.dhcp.leases_parser import parse_leases55from provisioningserver.dhcp.leases_parser import parse_leases
5656
5757
58task_logger = get_task_logger(name=__name__)58logger = getLogger(__name__)
5959
6060
61# Cache key for the modification time on last-processed leases file.61# Cache key for the modification time on last-processed leases file.
@@ -157,7 +157,7 @@
157 if None in knowledge.values():157 if None in knowledge.values():
158 # The MAAS server hasn't sent us enough information for us to do158 # The MAAS server hasn't sent us enough information for us to do
159 # this yet. Leave it for another time.159 # this yet. Leave it for another time.
160 task_logger.info(160 logger.info(
161 "Not sending DHCP leases to server: not all required knowledge "161 "Not sending DHCP leases to server: not all required knowledge "
162 "received from server yet. "162 "received from server yet. "
163 "Missing: %s"163 "Missing: %s"
@@ -189,7 +189,7 @@
189 timestamp, leases = parse_result189 timestamp, leases = parse_result
190 process_leases(timestamp, leases)190 process_leases(timestamp, leases)
191 else:191 else:
192 task_logger.info(192 logger.info(
193 "The DHCP leases file does not exist. This is only a problem if "193 "The DHCP leases file does not exist. This is only a problem if "
194 "this cluster controller is managing its DHCP server. If that's "194 "this cluster controller is managing its DHCP server. If that's "
195 "the case then you need to install the 'maas-dhcp' package on "195 "the case then you need to install the 'maas-dhcp' package on "
196196
=== modified file 'src/provisioningserver/start_cluster_controller.py'
--- src/provisioningserver/start_cluster_controller.py 2012-10-23 12:48:31 +0000
+++ src/provisioningserver/start_cluster_controller.py 2012-11-16 14:58:20 +0000
@@ -18,6 +18,7 @@
18from grp import getgrnam18from grp import getgrnam
19import httplib19import httplib
20import json20import json
21from logging import getLogger
21import os22import os
22from pwd import getpwnam23from pwd import getpwnam
23from time import sleep24from time import sleep
@@ -31,15 +32,10 @@
31 MAASDispatcher,32 MAASDispatcher,
32 NoAuth,33 NoAuth,
33 )34 )
34from celery.app import app_or_default
35from celery.log import (
36 get_task_logger,
37 setup_logging_subsystem,
38 )
39from provisioningserver.network import discover_networks35from provisioningserver.network import discover_networks
4036
4137
42task_logger = get_task_logger(name=__name__)38logger = getLogger(__name__)
4339
4440
45class ClusterControllerRejected(Exception):41class ClusterControllerRejected(Exception):
@@ -59,7 +55,7 @@
5955
6056
61def log_error(exception):57def log_error(exception):
62 task_logger.info(58 logger.info(
63 "Could not register with region controller: %s."59 "Could not register with region controller: %s."
64 % exception.reason)60 % exception.reason)
6561
@@ -71,6 +67,9 @@
7167
72def get_cluster_uuid():68def get_cluster_uuid():
73 """Read this cluster's UUID from the config."""69 """Read this cluster's UUID from the config."""
70 # Import this lazily. It reads config as a side effect, which can
71 # produce warnings.
72 from celery.app import app_or_default
74 return app_or_default().conf.CLUSTER_UUID73 return app_or_default().conf.CLUSTER_UUID
7574
7675
@@ -145,7 +144,7 @@
145 try:144 try:
146 client.post('api/1.0/nodegroups/', 'refresh_workers')145 client.post('api/1.0/nodegroups/', 'refresh_workers')
147 except URLError as e:146 except URLError as e:
148 task_logger.warn(147 logger.warn(
149 "Could not request secrets from region controller: %s"148 "Could not request secrets from region controller: %s"
150 % e.reason)149 % e.reason)
151150
@@ -164,13 +163,23 @@
164 start_celery(connection_details, user=user, group=group)163 start_celery(connection_details, user=user, group=group)
165164
166165
166def set_up_logging():
167 """Set up logging."""
168 # This import has side effects (it imports celeryconfig) and may
169 # produce warnings (if there is no celeryconfig).
170 # Postpone the import so that we don't go through that every time
171 # anything imports this module.
172 from celery.log import setup_logging_subsystem
173 setup_logging_subsystem()
174
175
167def run(args):176def run(args):
168 """Start the cluster controller.177 """Start the cluster controller.
169178
170 If this system is still awaiting approval as a cluster controller, this179 If this system is still awaiting approval as a cluster controller, this
171 command will keep looping until it gets a definite answer.180 command will keep looping until it gets a definite answer.
172 """181 """
173 setup_logging_subsystem()182 set_up_logging()
174 connection_details = register(args.server_url)183 connection_details = register(args.server_url)
175 while connection_details is None:184 while connection_details is None:
176 sleep(60)185 sleep(60)
177186
=== modified file 'src/provisioningserver/tags.py'
--- src/provisioningserver/tags.py 2012-10-26 10:24:52 +0000
+++ src/provisioningserver/tags.py 2012-11-16 14:58:20 +0000
@@ -19,6 +19,7 @@
1919
2020
21import httplib21import httplib
22from logging import getLogger
22import urllib223import urllib2
2324
24from apiclient.maas_client import (25from apiclient.maas_client import (
@@ -26,7 +27,6 @@
26 MAASDispatcher,27 MAASDispatcher,
27 MAASOAuth,28 MAASOAuth,
28 )29 )
29from celery.log import get_task_logger
30from lxml import etree30from lxml import etree
31from provisioningserver.auth import (31from provisioningserver.auth import (
32 get_recorded_api_credentials,32 get_recorded_api_credentials,
@@ -36,7 +36,7 @@
36import simplejson as json36import simplejson as json
3737
3838
39task_logger = get_task_logger(name=__name__)39logger = getLogger(__name__)
4040
4141
42class MissingCredentials(Exception):42class MissingCredentials(Exception):
@@ -53,15 +53,15 @@
53 """53 """
54 maas_url = get_recorded_maas_url()54 maas_url = get_recorded_maas_url()
55 if maas_url is None:55 if maas_url is None:
56 task_logger.error("Not updating tags: don't have API URL yet.")56 logger.error("Not updating tags: don't have API URL yet.")
57 return None, None57 return None, None
58 api_credentials = get_recorded_api_credentials()58 api_credentials = get_recorded_api_credentials()
59 if api_credentials is None:59 if api_credentials is None:
60 task_logger.error("Not updating tags: don't have API key yet.")60 logger.error("Not updating tags: don't have API key yet.")
61 return None, None61 return None, None
62 nodegroup_uuid = get_recorded_nodegroup_uuid()62 nodegroup_uuid = get_recorded_nodegroup_uuid()
63 if nodegroup_uuid is None:63 if nodegroup_uuid is None:
64 task_logger.error("Not updating tags: don't have UUID yet.")64 logger.error("Not updating tags: don't have UUID yet.")
65 return None, None65 return None, None
66 client = MAASClient(MAASOAuth(*api_credentials), MAASDispatcher(),66 client = MAASClient(MAASOAuth(*api_credentials), MAASDispatcher(),
67 maas_url)67 maas_url)
@@ -119,7 +119,8 @@
119 :param removed: Set of nodes to remove119 :param removed: Set of nodes to remove
120 """120 """
121 path = '/api/1.0/tags/%s/' % (tag_name,)121 path = '/api/1.0/tags/%s/' % (tag_name,)
122 task_logger.debug('Updating nodes for %s %s, adding %s removing %s'122 logger.debug(
123 "Updating nodes for %s %s, adding %s removing %s"
123 % (tag_name, uuid, len(added), len(removed)))124 % (tag_name, uuid, len(added), len(removed)))
124 try:125 try:
125 return process_response(client.post(126 return process_response(client.post(
@@ -131,7 +132,7 @@
131 msg = e.fp.read()132 msg = e.fp.read()
132 else:133 else:
133 msg = e.msg134 msg = e.msg
134 task_logger.info('Got a CONFLICT while updating tag: %s', msg)135 logger.info("Got a CONFLICT while updating tag: %s", msg)
135 return {}136 return {}
136 raise137 raise
137138
@@ -148,8 +149,8 @@
148 try:149 try:
149 xml = etree.XML(hw_xml)150 xml = etree.XML(hw_xml)
150 except etree.XMLSyntaxError as e:151 except etree.XMLSyntaxError as e:
151 task_logger.debug('Invalid hardware_details for %s: %s'152 logger.debug(
152 % (system_id, e))153 "Invalid hardware_details for %s: %s" % (system_id, e))
153 else:154 else:
154 if xpath(xml):155 if xpath(xml):
155 matched = True156 matched = True
@@ -166,16 +167,17 @@
166 batch_size = DEFAULT_BATCH_SIZE167 batch_size = DEFAULT_BATCH_SIZE
167 all_matched = []168 all_matched = []
168 all_unmatched = []169 all_unmatched = []
169 task_logger.debug('processing %d system_ids for tag %s nodegroup %s'170 logger.debug(
170 % (len(system_ids), tag_name, nodegroup_uuid))171 "processing %d system_ids for tag %s nodegroup %s"
172 % (len(system_ids), tag_name, nodegroup_uuid))
171 for i in range(0, len(system_ids), batch_size):173 for i in range(0, len(system_ids), batch_size):
172 selected_ids = system_ids[i:i + batch_size]174 selected_ids = system_ids[i:i + batch_size]
173 details = get_hardware_details_for_nodes(175 details = get_hardware_details_for_nodes(
174 client, nodegroup_uuid, selected_ids)176 client, nodegroup_uuid, selected_ids)
175 matched, unmatched = process_batch(xpath, details)177 matched, unmatched = process_batch(xpath, details)
176 task_logger.debug(178 logger.debug(
177 'processing batch of %d ids received %d details'179 "processing batch of %d ids received %d details"
178 ' (%d matched, %d unmatched)'180 " (%d matched, %d unmatched)"
179 % (len(selected_ids), len(details), len(matched), len(unmatched)))181 % (len(selected_ids), len(details), len(matched), len(unmatched)))
180 all_matched.extend(matched)182 all_matched.extend(matched)
181 all_unmatched.extend(unmatched)183 all_unmatched.extend(unmatched)
@@ -197,8 +199,9 @@
197 """199 """
198 client, nodegroup_uuid = get_cached_knowledge()200 client, nodegroup_uuid = get_cached_knowledge()
199 if not all([client, nodegroup_uuid]):201 if not all([client, nodegroup_uuid]):
200 task_logger.error('Unable to update tag: %s for definition %r'202 logger.error(
201 ' please refresh secrets, then rebuild this tag'203 "Unable to update tag: %s for definition %r. "
204 "Please refresh secrets, then rebuild this tag."
202 % (tag_name, tag_definition))205 % (tag_name, tag_definition))
203 raise MissingCredentials()206 raise MissingCredentials()
204 # We evaluate this early, so we can fail before sending a bunch of data to207 # We evaluate this early, so we can fail before sending a bunch of data to
205208
=== modified file 'src/provisioningserver/tests/test_start_cluster_controller.py'
--- src/provisioningserver/tests/test_start_cluster_controller.py 2012-10-23 12:48:31 +0000
+++ src/provisioningserver/tests/test_start_cluster_controller.py 2012-11-16 14:58:20 +0000
@@ -25,7 +25,10 @@
2525
26from apiclient.maas_client import MAASDispatcher26from apiclient.maas_client import MAASDispatcher
27from apiclient.testing.django import parse_headers_and_body_with_django27from apiclient.testing.django import parse_headers_and_body_with_django
28from fixtures import EnvironmentVariableFixture28from fixtures import (
29 EnvironmentVariableFixture,
30 FakeLogger,
31 )
29from maastesting.factory import factory32from maastesting.factory import factory
30from mock import (33from mock import (
31 call,34 call,
@@ -84,6 +87,10 @@
8487
85 def setUp(self):88 def setUp(self):
86 super(TestStartClusterController, self).setUp()89 super(TestStartClusterController, self).setUp()
90
91 self.useFixture(FakeLogger())
92 self.patch(start_cluster_controller, 'set_up_logging')
93
87 # Patch out anything that could be remotely harmful if we did it94 # Patch out anything that could be remotely harmful if we did it
88 # accidentally in the test. Make the really outrageous ones95 # accidentally in the test. Make the really outrageous ones
89 # raise exceptions.96 # raise exceptions.
9097
=== modified file 'src/provisioningserver/tests/test_tags.py'
--- src/provisioningserver/tests/test_tags.py 2012-10-24 16:29:02 +0000
+++ src/provisioningserver/tests/test_tags.py 2012-11-16 14:58:20 +0000
@@ -12,21 +12,21 @@
12__metaclass__ = type12__metaclass__ = type
13__all__ = []13__all__ = []
1414
15import httplib
16import urllib2
17
15from apiclient.maas_client import MAASClient18from apiclient.maas_client import MAASClient
16import httplib19from fixtures import FakeLogger
17from lxml import etree20from lxml import etree
18from maastesting.factory import factory21from maastesting.factory import factory
19from maastesting.fakemethod import (22from maastesting.fakemethod import (
20 FakeMethod,23 FakeMethod,
21 MultiFakeMethod,24 MultiFakeMethod,
22 )25 )
23import urllib2
24from mock import MagicMock26from mock import MagicMock
25from provisioningserver.auth import (27from provisioningserver import tags
26 get_recorded_nodegroup_uuid,28from provisioningserver.auth import get_recorded_nodegroup_uuid
27 )
28from provisioningserver.testing.testcase import PservTestCase29from provisioningserver.testing.testcase import PservTestCase
29from provisioningserver import tags
3030
3131
32class FakeResponse:32class FakeResponse:
@@ -41,6 +41,10 @@
4141
42class TestTagUpdating(PservTestCase):42class TestTagUpdating(PservTestCase):
4343
44 def setUp(self):
45 super(TestTagUpdating, self).setUp()
46 self.useFixture(FakeLogger())
47
44 def test_get_cached_knowledge_knows_nothing(self):48 def test_get_cached_knowledge_knows_nothing(self):
45 # If we haven't given it any secrets, we should get back nothing49 # If we haven't given it any secrets, we should get back nothing
46 self.assertEqual((None, None), tags.get_cached_knowledge())50 self.assertEqual((None, None), tags.get_cached_knowledge())