Merge lp:~julian-edwards/maas/revert-2819 into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 2822
Proposed branch: lp:~julian-edwards/maas/revert-2819
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 468 lines (+62/-254)
8 files modified
etc/celeryconfig_cluster.py (+8/-0)
src/provisioningserver/dhcp/detect.py (+26/-8)
src/provisioningserver/dhcp/dhcp_probe_service.py (+0/-85)
src/provisioningserver/dhcp/tests/test_detect.py (+13/-4)
src/provisioningserver/dhcp/tests/test_dhcp_probe_service.py (+0/-128)
src/provisioningserver/plugin.py (+0/-12)
src/provisioningserver/tasks.py (+12/-1)
src/provisioningserver/tests/test_plugin.py (+3/-16)
To merge this branch: bzr merge lp:~julian-edwards/maas/revert-2819
Reviewer Review Type Date Requested Status
Julian Edwards (community) selfie Approve
Review via email: mp+232338@code.launchpad.net

Commit message

Revert r2819 (pserv-based dhcp probing service), which could never work as it was using API credentials that are not sent to pserv.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'etc/celeryconfig_cluster.py'
--- etc/celeryconfig_cluster.py 2014-08-22 14:47:15 +0000
+++ etc/celeryconfig_cluster.py 2014-08-26 23:57:04 +0000
@@ -56,4 +56,12 @@
56 'expires': int(REPORT_BOOT_IMAGES_SCHEDULE.total_seconds()),56 'expires': int(REPORT_BOOT_IMAGES_SCHEDULE.total_seconds()),
57 },57 },
58 },58 },
59 'probe-dhcp-servers': {
60 'task': 'provisioningserver.tasks.periodic_probe_dhcp',
61 'schedule': PROBE_DHCP_SERVERS_SCHEDULE,
62 'options': {
63 'queue': CLUSTER_UUID,
64 'expires': int(PROBE_DHCP_SERVERS_SCHEDULE.total_seconds()),
65 },
66 },
59}67}
6068
=== modified file 'src/provisioningserver/dhcp/detect.py'
--- src/provisioningserver/dhcp/detect.py 2014-08-26 10:19:58 +0000
+++ src/provisioningserver/dhcp/detect.py 2014-08-26 23:57:04 +0000
@@ -33,6 +33,11 @@
33 MAASDispatcher,33 MAASDispatcher,
34 MAASOAuth,34 MAASOAuth,
35 )35 )
36from provisioningserver.auth import (
37 get_recorded_api_credentials,
38 get_recorded_nodegroup_uuid,
39 )
40from provisioningserver.cluster_config import get_maas_url
36from provisioningserver.logger import get_maas_logger41from provisioningserver.logger import get_maas_logger
3742
3843
@@ -316,7 +321,7 @@
316 client.post, api_path, 'report_foreign_dhcp', foreign_dhcp_ip=server)321 client.post, api_path, 'report_foreign_dhcp', foreign_dhcp_ip=server)
317322
318323
319def periodic_probe_task(api_knowledge):324def periodic_probe_task():
320 """Probe for DHCP servers and set NodeGroupInterface.foriegn_dhcp.325 """Probe for DHCP servers and set NodeGroupInterface.foriegn_dhcp.
321326
322 This should be run periodically so that the database has an up-to-date327 This should be run periodically so that the database has an up-to-date
@@ -325,12 +330,26 @@
325 NOTE: This uses blocking I/O with sequential polling of interfaces, and330 NOTE: This uses blocking I/O with sequential polling of interfaces, and
326 hence doesn't scale well. It's a future improvement to make331 hence doesn't scale well. It's a future improvement to make
327 to throw it in parallel threads or async I/O.332 to throw it in parallel threads or async I/O.
328
329 :param api_knowledge: A dict of the information needed to be able to
330 make requests to the region's REST API.
331 """333 """
334 # Items that the server must have sent us before we can do this.
335 knowledge = {
336 'maas_url': get_maas_url(),
337 'api_credentials': get_recorded_api_credentials(),
338 'nodegroup_uuid': get_recorded_nodegroup_uuid(),
339 }
340
341 if None in knowledge.values():
342 # The MAAS server hasn't sent us enough information for us to do
343 # this yet. Leave it for another time.
344 maaslog.info(
345 "Not probing for rogue DHCP servers; not all required knowledge "
346 "received from server yet. "
347 "Missing: %s" % ', '.join(sorted(
348 name for name, value in knowledge.items() if value is None)))
349 return
350
332 # Determine all the active interfaces on this cluster (nodegroup).351 # Determine all the active interfaces on this cluster (nodegroup).
333 interfaces = determine_cluster_interfaces(api_knowledge)352 interfaces = determine_cluster_interfaces(knowledge)
334 if interfaces is None:353 if interfaces is None:
335 maaslog.info("No interfaces on cluster, not probing DHCP.")354 maaslog.info("No interfaces on cluster, not probing DHCP.")
336 return355 return
@@ -349,7 +368,6 @@
349 # Only send one, if it gets cleared out then the368 # Only send one, if it gets cleared out then the
350 # next detection pass will send a different one, if it369 # next detection pass will send a different one, if it
351 # still exists.370 # still exists.
352 update_region_controller(371 update_region_controller(knowledge, interface, servers.pop())
353 api_knowledge, interface, servers.pop())
354 else:372 else:
355 update_region_controller(api_knowledge, interface, None)373 update_region_controller(knowledge, interface, None)
356374
=== removed file 'src/provisioningserver/dhcp/dhcp_probe_service.py'
--- src/provisioningserver/dhcp/dhcp_probe_service.py 2014-08-26 15:47:52 +0000
+++ src/provisioningserver/dhcp/dhcp_probe_service.py 1970-01-01 00:00:00 +0000
@@ -1,85 +0,0 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Periodic DHCP probing service."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = [
16 "PeriodicDHCPProbeService",
17 ]
18
19
20from datetime import timedelta
21
22from provisioningserver.auth import get_recorded_api_credentials
23from provisioningserver.cluster_config import get_maas_url
24from provisioningserver.dhcp import detect
25from provisioningserver.logger.log import get_maas_logger
26from twisted.application.internet import TimerService
27from twisted.internet.defer import inlineCallbacks
28from twisted.internet.threads import deferToThread
29
30
31maaslog = get_maas_logger("dhcp.probe")
32
33
34class PeriodicDHCPProbeService(TimerService, object):
35 """Service to probe for DHCP servers on this cluster's network.
36
37 Built on top of Twisted's `TimerService`.
38
39 :param reactor: An `IReactor` instance.
40 :param cluster_uuid: This cluster's UUID.
41 """
42
43 check_interval = timedelta(minutes=1).total_seconds()
44
45 def __init__(self, reactor, cluster_uuid):
46 # Call self.check() every self.check_interval.
47 super(PeriodicDHCPProbeService, self).__init__(
48 self.check_interval, self.try_probe_dhcp)
49 self.clock = reactor
50 self.uuid = cluster_uuid
51
52 def _probe_dhcp(self, knowledge):
53 """Probe for DHCP servers."""
54 return deferToThread(detect.periodic_probe_task, knowledge)
55
56 @inlineCallbacks
57 def try_probe_dhcp(self):
58 # Items that the server must have sent us before we can do
59 # this.
60 knowledge = {
61 'api_credentials': get_recorded_api_credentials(),
62 'maas_url': get_maas_url(),
63 'nodegroup_uuid': self.uuid,
64 }
65
66 if None in knowledge.values():
67 # The MAAS server hasn't sent us enough information for
68 # us to do this yet. Leave it for another time.
69 maaslog.info(
70 "Not probing for rogue DHCP servers; not all "
71 "required knowledge received from server yet. "
72 "Missing: %s" % ', '.join(sorted(
73 name for name, value in knowledge.items()
74 if value is None)))
75 return
76 else:
77 maaslog.debug("Running periodic DHCP probe.")
78 try:
79 d = self._probe_dhcp(knowledge)
80 yield d
81 maaslog.debug("Finished periodic DHCP probe.")
82 except Exception as error:
83 maaslog.error(
84 "Unable to probe for rogue DHCP servers: %s"
85 % unicode(error))
860
=== modified file 'src/provisioningserver/dhcp/tests/test_detect.py'
--- src/provisioningserver/dhcp/tests/test_detect.py 2014-08-26 10:19:58 +0000
+++ src/provisioningserver/dhcp/tests/test_detect.py 2014-08-26 23:57:04 +0000
@@ -30,7 +30,10 @@
30from maastesting.testcase import MAASTestCase30from maastesting.testcase import MAASTestCase
31import mock31import mock
32from provisioningserver import cache32from provisioningserver import cache
33from provisioningserver.auth import NODEGROUP_UUID_CACHE_KEY33from provisioningserver.auth import (
34 NODEGROUP_UUID_CACHE_KEY,
35 record_api_credentials,
36 )
34from provisioningserver.dhcp.detect import (37from provisioningserver.dhcp.detect import (
35 BOOTP_CLIENT_PORT,38 BOOTP_CLIENT_PORT,
36 BOOTP_SERVER_PORT,39 BOOTP_SERVER_PORT,
@@ -510,11 +513,17 @@
510 "Failed talking to region controller, it returned:",513 "Failed talking to region controller, it returned:",
511 self.maaslog.output)514 self.maaslog.output)
512515
516 def test_periodic_probe_task_exits_with_not_enough_knowledge(self):
517 mocked = self.patch(detect_module, 'determine_cluster_interfaces')
518 record_api_credentials(None)
519 periodic_probe_task()
520 self.assertFalse(mocked.called)
521
513 def test_periodic_probe_task_exits_if_no_interfaces(self):522 def test_periodic_probe_task_exits_if_no_interfaces(self):
514 mocked = self.patch(detect_module, 'probe_interface')523 mocked = self.patch(detect_module, 'probe_interface')
515 self.patch(524 self.patch(
516 detect_module, 'determine_cluster_interfaces').return_value = None525 detect_module, 'determine_cluster_interfaces').return_value = None
517 periodic_probe_task(self.knowledge)526 periodic_probe_task()
518 self.assertFalse(mocked.called)527 self.assertFalse(mocked.called)
519528
520 def test_periodic_probe_task_updates_region_with_detected_server(self):529 def test_periodic_probe_task_updates_region_with_detected_server(self):
@@ -526,7 +535,7 @@
526 self.patch(535 self.patch(
527 detect_module, 'probe_dhcp').return_value = {detected_server}536 detect_module, 'probe_dhcp').return_value = {detected_server}
528 mocked_update = self.patch(detect_module, 'update_region_controller')537 mocked_update = self.patch(detect_module, 'update_region_controller')
529 periodic_probe_task(self.knowledge)538 periodic_probe_task()
530 calls = [539 calls = [
531 mock.call(self.knowledge, 'eth0', detected_server),540 mock.call(self.knowledge, 'eth0', detected_server),
532 mock.call(self.knowledge, 'wlan0', detected_server),541 mock.call(self.knowledge, 'wlan0', detected_server),
@@ -541,7 +550,7 @@
541 self.patch(550 self.patch(
542 detect_module, 'probe_dhcp').return_value = set()551 detect_module, 'probe_dhcp').return_value = set()
543 mocked_update = self.patch(detect_module, 'update_region_controller')552 mocked_update = self.patch(detect_module, 'update_region_controller')
544 periodic_probe_task(self.knowledge)553 periodic_probe_task()
545 calls = [554 calls = [
546 mock.call(self.knowledge, 'eth0', None),555 mock.call(self.knowledge, 'eth0', None),
547 mock.call(self.knowledge, 'wlan0', None),556 mock.call(self.knowledge, 'wlan0', None),
548557
=== removed file 'src/provisioningserver/dhcp/tests/test_dhcp_probe_service.py'
--- src/provisioningserver/dhcp/tests/test_dhcp_probe_service.py 2014-08-26 15:45:21 +0000
+++ src/provisioningserver/dhcp/tests/test_dhcp_probe_service.py 1970-01-01 00:00:00 +0000
@@ -1,128 +0,0 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for periodic DHCP prober."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17import os
18
19from apiclient.testing.credentials import make_api_credentials
20from maastesting.factory import factory
21from maastesting.matchers import (
22 get_mock_calls,
23 HasLength,
24 MockCalledOnceWith,
25 MockNotCalled,
26 )
27from mock import ANY
28from provisioningserver import cache
29from provisioningserver.auth import (
30 NODEGROUP_UUID_CACHE_KEY,
31 record_api_credentials,
32 )
33from provisioningserver.dhcp import dhcp_probe_service
34from provisioningserver.dhcp.dhcp_probe_service import (
35 PeriodicDHCPProbeService,
36 )
37from provisioningserver.testing.testcase import PservTestCase
38from testtools.deferredruntest import AsynchronousDeferredRunTest
39from twisted.internet import defer
40from twisted.internet.task import Clock
41
42
43class TestDHCPProbeService(PservTestCase):
44
45 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
46
47 def setUp(self):
48 super(TestDHCPProbeService, self).setUp()
49 self.cluster_uuid = factory.make_UUID()
50 maas_url = 'http://%s.example.com/%s/' % (
51 factory.make_name('host'),
52 factory.make_string(),
53 )
54 api_credentials = make_api_credentials()
55
56 cache.cache.set(NODEGROUP_UUID_CACHE_KEY, self.cluster_uuid)
57 os.environ["MAAS_URL"] = maas_url
58 cache.cache.set('api_credentials', ':'.join(api_credentials))
59
60 self.knowledge = dict(
61 api_credentials=api_credentials,
62 maas_url=maas_url,
63 nodegroup_uuid=self.cluster_uuid)
64
65 def test_is_called_every_interval(self):
66 clock = Clock()
67 service = PeriodicDHCPProbeService(clock, self.cluster_uuid)
68
69 # Avoid actually probing
70 _probe_dhcp = self.patch(service, '_probe_dhcp')
71
72 # Until the service has started, periodic_probe_dhcp() won't
73 # be called.
74 self.assertThat(_probe_dhcp, MockNotCalled())
75
76 # The first call is issued at startup.
77 service.startService()
78 self.assertThat(_probe_dhcp, MockCalledOnceWith(ANY))
79
80 # Wind clock forward one second less than the desired interval.
81 clock.advance(service.check_interval - 1)
82
83 # No more periodic calls made.
84 self.assertEqual(1, len(get_mock_calls(_probe_dhcp)))
85
86 # Wind clock forward one second, past the interval.
87 clock.advance(1)
88
89 # Now there were two calls.
90 self.assertThat(get_mock_calls(_probe_dhcp), HasLength(2))
91
92 def test_download_is_initiated_in_new_thread(self):
93 clock = Clock()
94
95 # We could patch out 'periodic_probe_task' instead here but this
96 # is better because:
97 # 1. The former requires spinning the reactor again before being
98 # able to test the result.
99 # 2. This way there's no thread to clean up after the test.
100 deferToThread = self.patch(dhcp_probe_service, 'deferToThread')
101 deferToThread.return_value = defer.succeed(None)
102 service = PeriodicDHCPProbeService(clock, self.cluster_uuid)
103 service.startService()
104 self.assertThat(
105 deferToThread, MockCalledOnceWith(
106 dhcp_probe_service.detect.periodic_probe_task,
107 self.knowledge))
108
109 def test_no_probe_if_api_credentials_not_set(self):
110 record_api_credentials(None)
111 clock = Clock()
112 service = PeriodicDHCPProbeService(clock, self.cluster_uuid)
113 _probe_dhcp = self.patch(service, '_probe_dhcp')
114 service.startService()
115 self.assertThat(_probe_dhcp, MockNotCalled())
116
117 def test_logs_errors(self):
118 clock = Clock()
119 maaslog = self.patch(dhcp_probe_service, 'maaslog')
120 service = PeriodicDHCPProbeService(clock, self.cluster_uuid)
121 error_message = factory.make_string()
122 self.patch(service, '_probe_dhcp').side_effect = Exception(
123 error_message)
124 service.startService()
125 self.assertThat(
126 maaslog.error, MockCalledOnceWith(
127 "Unable to probe for rogue DHCP servers: %s" %
128 error_message))
1290
=== modified file 'src/provisioningserver/plugin.py'
--- src/provisioningserver/plugin.py 2014-08-26 17:23:16 +0000
+++ src/provisioningserver/plugin.py 2014-08-26 23:57:04 +0000
@@ -31,9 +31,6 @@
31import provisioningserver31import provisioningserver
32from provisioningserver.cluster_config import get_cluster_uuid32from provisioningserver.cluster_config import get_cluster_uuid
33from provisioningserver.config import Config33from provisioningserver.config import Config
34from provisioningserver.dhcp.dhcp_probe_service import (
35 PeriodicDHCPProbeService,
36 )
37from provisioningserver.rpc.boot_images import PeriodicImageDownloadService34from provisioningserver.rpc.boot_images import PeriodicImageDownloadService
38from provisioningserver.rpc.clusterservice import ClusterClientService35from provisioningserver.rpc.clusterservice import ClusterClientService
39from provisioningserver.rpc.power import NodePowerMonitorService36from provisioningserver.rpc.power import NodePowerMonitorService
@@ -242,12 +239,6 @@
242 rpc_service.setName("rpc")239 rpc_service.setName("rpc")
243 return rpc_service240 return rpc_service
244241
245 def _makePeriodicDHCPProbeService(self, rpc_service):
246 dhcp_probe_service = PeriodicDHCPProbeService(
247 reactor, get_cluster_uuid())
248 dhcp_probe_service.setName("dhcp_probe")
249 return dhcp_probe_service
250
251 def makeService(self, options):242 def makeService(self, options):
252 """Construct a service."""243 """Construct a service."""
253 services = provisioningserver.services244 services = provisioningserver.services
@@ -272,7 +263,4 @@
272 rpc_service)263 rpc_service)
273 image_download_service.setServiceParent(services)264 image_download_service.setServiceParent(services)
274265
275 dhcp_probe_service = self._makePeriodicDHCPProbeService(rpc_service)
276 dhcp_probe_service.setServiceParent(services)
277
278 return services266 return services
279267
=== modified file 'src/provisioningserver/tasks.py'
--- src/provisioningserver/tasks.py 2014-08-26 10:23:23 +0000
+++ src/provisioningserver/tasks.py 2014-08-26 23:57:04 +0000
@@ -43,7 +43,10 @@
43 record_api_credentials,43 record_api_credentials,
44 record_nodegroup_uuid,44 record_nodegroup_uuid,
45 )45 )
46from provisioningserver.dhcp import config46from provisioningserver.dhcp import (
47 config,
48 detect,
49 )
47from provisioningserver.dhcp.control import (50from provisioningserver.dhcp.control import (
48 restart_dhcpv4,51 restart_dhcpv4,
49 stop_dhcpv4,52 stop_dhcpv4,
@@ -445,6 +448,14 @@
445 raise448 raise
446449
447450
451@task
452@log_task_events(level=logging.DEBUG)
453@log_exception_text
454def periodic_probe_dhcp():
455 """Probe for foreign DHCP servers."""
456 detect.periodic_probe_task()
457
458
448# =====================================================================459# =====================================================================
449# Boot images-related tasks460# Boot images-related tasks
450# =====================================================================461# =====================================================================
451462
=== modified file 'src/provisioningserver/tests/test_plugin.py'
--- src/provisioningserver/tests/test_plugin.py 2014-08-26 17:23:16 +0000
+++ src/provisioningserver/tests/test_plugin.py 2014-08-26 23:57:04 +0000
@@ -21,9 +21,6 @@
21from maastesting.testcase import MAASTestCase21from maastesting.testcase import MAASTestCase
22import provisioningserver22import provisioningserver
23from provisioningserver import plugin as plugin_module23from provisioningserver import plugin as plugin_module
24from provisioningserver.dhcp.dhcp_probe_service import (
25 PeriodicDHCPProbeService,
26 )
27from provisioningserver.plugin import (24from provisioningserver.plugin import (
28 Options,25 Options,
29 ProvisioningRealm,26 ProvisioningRealm,
@@ -112,11 +109,9 @@
112 service_maker = ProvisioningServiceMaker("Harry", "Hill")109 service_maker = ProvisioningServiceMaker("Harry", "Hill")
113 service = service_maker.makeService(options)110 service = service_maker.makeService(options)
114 self.assertIsInstance(service, MultiService)111 self.assertIsInstance(service, MultiService)
115 expected_services = [112 self.assertSequenceEqual(
116 "dhcp_probe", "image_download", "log", "node_monitor",113 ["image_download", "log", "node_monitor", "oops", "rpc", "tftp"],
117 "oops", "rpc", "tftp",114 sorted(service.namedServices))
118 ]
119 self.assertItemsEqual(expected_services, service.namedServices)
120 self.assertEqual(115 self.assertEqual(
121 len(service.namedServices), len(service.services),116 len(service.namedServices), len(service.services),
122 "Not all services are named.")117 "Not all services are named.")
@@ -138,14 +133,6 @@
138 node_monitor = service.getServiceNamed("node_monitor")133 node_monitor = service.getServiceNamed("node_monitor")
139 self.assertIsInstance(node_monitor, NodePowerMonitorService)134 self.assertIsInstance(node_monitor, NodePowerMonitorService)
140135
141 def test_dhcp_probe_service(self):
142 options = Options()
143 options["config-file"] = self.write_config({})
144 service_maker = ProvisioningServiceMaker("Spike", "Milligan")
145 service = service_maker.makeService(options)
146 dhcp_probe = service.getServiceNamed("dhcp_probe")
147 self.assertIsInstance(dhcp_probe, PeriodicDHCPProbeService)
148
149 def test_tftp_service(self):136 def test_tftp_service(self):
150 # A TFTP service is configured and added to the top-level service.137 # A TFTP service is configured and added to the top-level service.
151 config = {138 config = {