Merge lp:~allenap/maas/ntp-service-in-rack into lp:~maas-committers/maas/trunk
- ntp-service-in-rack
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5279 |
Proposed branch: | lp:~allenap/maas/ntp-service-in-rack |
Merge into: | lp:~maas-committers/maas/trunk |
Prerequisite: | lp:~allenap/maas/ntp-service-in-rack-skeleton |
Diff against target: |
357 lines (+156/-42) 9 files modified
src/maastesting/factory.py (+13/-5) src/provisioningserver/plugin.py (+9/-4) src/provisioningserver/rackdservices/testing.py (+36/-0) src/provisioningserver/rackdservices/tests/test_ntp.py (+4/-28) src/provisioningserver/rackdservices/tests/test_service_monitor_service.py (+9/-1) src/provisioningserver/service_monitor.py (+27/-0) src/provisioningserver/tests/test_plugin.py (+1/-1) src/provisioningserver/tests/test_service_monitor.py (+56/-2) src/provisioningserver/utils/service_monitor.py (+1/-1) |
To merge this branch: | bzr merge lp:~allenap/maas/ntp-service-in-rack |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+303380@code.launchpad.net |
Commit message
Monitor the NTP service in the rack when it's not also a region controller.
Description of the change
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~allenap/maas/ntp-service-in-rack into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Get:2 http://
Get:3 http://
Hit:4 http://
Fetched 190 kB in 0s (439 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubun
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the newest version (3:6.03+
python-formencode is already the newest version (1.3.0-0ubuntu5).
python-lxml is already the newest version (3.5.0-1build1).
python-netaddr is already the newest version (0.7.18-1).
python-netifaces is already the newest version (0.10.4-...
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~allenap/maas/ntp-service-in-rack into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Hit:2 http://
Hit:3 http://
Hit:4 http://
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubun
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the newest version (3:6.03+
python-formencode is already the newest version (1.3.0-0ubuntu5).
python-lxml is already the newest version (3.5.0-1build1).
python-netaddr is already the newest version (0.7.18-1).
python-netifaces is already the newest version (0.10.4-0.1build2).
python-psycopg2 is already the newest ve...
Preview Diff
1 | === modified file 'src/maastesting/factory.py' |
2 | --- src/maastesting/factory.py 2016-08-09 15:56:46 +0000 |
3 | +++ src/maastesting/factory.py 2016-08-19 13:45:36 +0000 |
4 | @@ -10,6 +10,7 @@ |
5 | ] |
6 | |
7 | import datetime |
8 | +from enum import Enum |
9 | from functools import partial |
10 | import http.client |
11 | import io |
12 | @@ -168,15 +169,22 @@ |
13 | def pick_enum(self, enum, *, but_not=EMPTY_SET): |
14 | """Pick a random item from an enumeration class. |
15 | |
16 | - :param enum: An enumeration class such as `NODE_STATUS`. |
17 | + :param enum: An enumeration class such as `NODE_STATUS`. Can also be |
18 | + an `enum.Enum` subclass. |
19 | :return: The value of one of its items. |
20 | :param but_not: A list of choices' IDs to exclude. |
21 | :type but_not: Sequence. |
22 | """ |
23 | - return random.choice([ |
24 | - value for key, value in vars(enum).items() |
25 | - if not key.startswith('_') and value not in but_not |
26 | - ]) |
27 | + if issubclass(enum, Enum): |
28 | + return random.choice([ |
29 | + value for value in enum |
30 | + if value not in but_not |
31 | + ]) |
32 | + else: |
33 | + return random.choice([ |
34 | + value for key, value in vars(enum).items() |
35 | + if not key.startswith('_') and value not in but_not |
36 | + ]) |
37 | |
38 | def pick_port(self, port_min=1024, port_max=65535): |
39 | assert port_min >= 0 and port_max <= 65535 |
40 | |
41 | === modified file 'src/provisioningserver/plugin.py' |
42 | --- src/provisioningserver/plugin.py 2016-08-09 16:45:55 +0000 |
43 | +++ src/provisioningserver/plugin.py 2016-08-19 13:45:36 +0000 |
44 | @@ -150,6 +150,12 @@ |
45 | service_monitor.setName("service_monitor") |
46 | return service_monitor |
47 | |
48 | + def _makeNetworkTimeProtocolService(self, rpc_service): |
49 | + from provisioningserver.rackdservices import ntp |
50 | + ntp_service = ntp.RackNetworkTimeProtocolService(rpc_service, reactor) |
51 | + ntp_service.setName("ntp") |
52 | + return ntp_service |
53 | + |
54 | def _makeServices(self, tftp_root, tftp_port): |
55 | # Several services need to make use of the RPC service. |
56 | rpc_service = self._makeRPCService() |
57 | @@ -160,12 +166,11 @@ |
58 | yield self._makeLeaseSocketService(rpc_service) |
59 | yield self._makeNodePowerMonitorService() |
60 | yield self._makeServiceMonitorService(rpc_service) |
61 | - yield self._makeImageDownloadService( |
62 | - rpc_service, tftp_root) |
63 | + yield self._makeImageDownloadService(rpc_service, tftp_root) |
64 | + yield self._makeNetworkTimeProtocolService(rpc_service) |
65 | # The following are network-accessible services. |
66 | yield self._makeImageService(tftp_root) |
67 | - yield self._makeTFTPService( |
68 | - tftp_root, tftp_port, rpc_service) |
69 | + yield self._makeTFTPService(tftp_root, tftp_port, rpc_service) |
70 | |
71 | def _configureCrochet(self): |
72 | # Prevent other libraries from starting the reactor via crochet. |
73 | |
74 | === added file 'src/provisioningserver/rackdservices/testing.py' |
75 | --- src/provisioningserver/rackdservices/testing.py 1970-01-01 00:00:00 +0000 |
76 | +++ src/provisioningserver/rackdservices/testing.py 2016-08-19 13:45:36 +0000 |
77 | @@ -0,0 +1,36 @@ |
78 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
79 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
80 | + |
81 | +"""Testing resources for `provisioningserver.rackdservices`.""" |
82 | + |
83 | +__all__ = [ |
84 | + "prepareRegionForGetControllerType", |
85 | +] |
86 | + |
87 | +from maastesting.factory import factory |
88 | +from maastesting.twisted import always_succeed_with |
89 | +from provisioningserver import services |
90 | +from provisioningserver.rpc import region |
91 | +from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture |
92 | + |
93 | + |
94 | +def prepareRegionForGetControllerType(test, is_region=False, is_rack=True): |
95 | + """Set up a mock region controller that responds to `GetControllerType`. |
96 | + |
97 | + In addition it sets the MAAS ID to a new random value. It arranges for |
98 | + tear-down at the end of the test. |
99 | + |
100 | + :return: The running RPC service, and the protocol instance. |
101 | + """ |
102 | + fixture = test.useFixture(MockLiveClusterToRegionRPCFixture()) |
103 | + protocol, connecting = fixture.makeEventLoop(region.GetControllerType) |
104 | + protocol.RegisterRackController.side_effect = always_succeed_with( |
105 | + {"system_id": factory.make_name("maas-id")}) |
106 | + protocol.GetControllerType.side_effect = always_succeed_with({ |
107 | + "is_region": is_region, "is_rack": is_rack}) |
108 | + |
109 | + def connected(teardown): |
110 | + test.addCleanup(teardown) |
111 | + return services.getServiceNamed("rpc"), protocol |
112 | + |
113 | + return connecting.addCallback(connected) |
114 | |
115 | === modified file 'src/provisioningserver/rackdservices/tests/test_ntp.py' |
116 | --- src/provisioningserver/rackdservices/tests/test_ntp.py 2016-08-17 14:48:54 +0000 |
117 | +++ src/provisioningserver/rackdservices/tests/test_ntp.py 2016-08-19 13:45:36 +0000 |
118 | @@ -19,18 +19,16 @@ |
119 | MAASTestCase, |
120 | MAASTwistedRunTest, |
121 | ) |
122 | -from maastesting.twisted import ( |
123 | - always_succeed_with, |
124 | - TwistedLoggerFixture, |
125 | -) |
126 | -from provisioningserver import services |
127 | +from maastesting.twisted import TwistedLoggerFixture |
128 | from provisioningserver.rackdservices import ntp |
129 | +from provisioningserver.rackdservices.testing import ( |
130 | + prepareRegionForGetControllerType, |
131 | +) |
132 | from provisioningserver.rpc import ( |
133 | common, |
134 | exceptions, |
135 | region, |
136 | ) |
137 | -from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture |
138 | from provisioningserver.rpc.testing.doubles import FakeConnectionToRegion |
139 | from testtools.matchers import ( |
140 | Equals, |
141 | @@ -80,28 +78,6 @@ |
142 | return common.Client(conn) |
143 | |
144 | |
145 | -def prepareRegionForGetControllerType(test, is_region=False, is_rack=True): |
146 | - """Set up a mock region controller that responds to `GetControllerType`. |
147 | - |
148 | - In addition it sets the MAAS ID to a new random value. It arranges for |
149 | - tear-down at the end of the test. |
150 | - |
151 | - :return: The running RPC service, and the protocol instance. |
152 | - """ |
153 | - fixture = test.useFixture(MockLiveClusterToRegionRPCFixture()) |
154 | - protocol, connecting = fixture.makeEventLoop(region.GetControllerType) |
155 | - protocol.RegisterRackController.side_effect = always_succeed_with( |
156 | - {"system_id": factory.make_name("maas-id")}) |
157 | - protocol.GetControllerType.side_effect = always_succeed_with({ |
158 | - "is_region": is_region, "is_rack": is_rack}) |
159 | - |
160 | - def connected(teardown): |
161 | - test.addCleanup(teardown) |
162 | - return services.getServiceNamed("rpc"), protocol |
163 | - |
164 | - return connecting.addCallback(connected) |
165 | - |
166 | - |
167 | class TestRackNetworkTimeProtocolService(MAASTestCase): |
168 | """Tests for `RackNetworkTimeProtocolService`.""" |
169 | |
170 | |
171 | === modified file 'src/provisioningserver/rackdservices/tests/test_service_monitor_service.py' |
172 | --- src/provisioningserver/rackdservices/tests/test_service_monitor_service.py 2016-07-30 01:17:54 +0000 |
173 | +++ src/provisioningserver/rackdservices/tests/test_service_monitor_service.py 2016-08-19 13:45:36 +0000 |
174 | @@ -30,6 +30,7 @@ |
175 | from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture |
176 | from provisioningserver.service_monitor import service_monitor |
177 | from provisioningserver.utils.service_monitor import ( |
178 | + AlwaysOnService, |
179 | SERVICE_STATE, |
180 | ServiceState, |
181 | ) |
182 | @@ -111,7 +112,14 @@ |
183 | protocol, connecting = self.patch_rpc_methods() |
184 | self.addCleanup((yield connecting)) |
185 | |
186 | - service = self.pick_service() |
187 | + class ExampleService(AlwaysOnService): |
188 | + name = service_name = factory.make_name("service") |
189 | + |
190 | + service = ExampleService() |
191 | + # Inveigle this new service into the service monitor. |
192 | + self.addCleanup(service_monitor._services.pop, service.name) |
193 | + service_monitor._services[service.name] = service |
194 | + |
195 | state = ServiceState(SERVICE_STATE.ON, "running") |
196 | mock_ensureServices = self.patch(service_monitor, "ensureServices") |
197 | mock_ensureServices.return_value = succeed({ |
198 | |
199 | === modified file 'src/provisioningserver/service_monitor.py' |
200 | --- src/provisioningserver/service_monitor.py 2016-08-18 15:21:07 +0000 |
201 | +++ src/provisioningserver/service_monitor.py 2016-08-19 13:45:36 +0000 |
202 | @@ -6,6 +6,9 @@ |
203 | __all__ = [ |
204 | ] |
205 | |
206 | +from provisioningserver.rpc import getRegionClient |
207 | +from provisioningserver.rpc.exceptions import NoConnectionsAvailable |
208 | +from provisioningserver.rpc.region import GetControllerType |
209 | from provisioningserver.utils.service_monitor import ( |
210 | AlwaysOnService, |
211 | Service, |
212 | @@ -61,10 +64,34 @@ |
213 | service_name = "tgt" |
214 | |
215 | |
216 | +class NTPService(Service): |
217 | + """Monitored NTP service.""" |
218 | + |
219 | + name = "ntp" |
220 | + service_name = "ntp" |
221 | + |
222 | + def getExpectedState(self): |
223 | + try: |
224 | + client = getRegionClient() |
225 | + except NoConnectionsAvailable: |
226 | + return SERVICE_STATE.ANY, None |
227 | + else: |
228 | + d = client(GetControllerType, system_id=client.localIdent) |
229 | + d.addCallback(self._getExpectedStateForControllerType) |
230 | + return d |
231 | + |
232 | + def _getExpectedStateForControllerType(self, controller_type): |
233 | + if controller_type["is_rack"] and not controller_type["is_region"]: |
234 | + return SERVICE_STATE.ON, None |
235 | + else: |
236 | + return SERVICE_STATE.ANY, None |
237 | + |
238 | + |
239 | # Global service monitor for rackd. NOTE that changes to this need to be |
240 | # mirrored in maasserver.model.services. |
241 | service_monitor = ServiceMonitor( |
242 | DHCPv4Service(), |
243 | DHCPv6Service(), |
244 | + NTPService(), |
245 | TGTService(), |
246 | ) |
247 | |
248 | === modified file 'src/provisioningserver/tests/test_plugin.py' |
249 | --- src/provisioningserver/tests/test_plugin.py 2016-07-30 01:17:54 +0000 |
250 | +++ src/provisioningserver/tests/test_plugin.py 2016-08-19 13:45:36 +0000 |
251 | @@ -99,7 +99,7 @@ |
252 | self.assertIsInstance(service, MultiService) |
253 | expected_services = [ |
254 | "dhcp_probe", "networks_monitor", "image_download", |
255 | - "lease_socket_service", "node_monitor", "rpc", "tftp", |
256 | + "lease_socket_service", "node_monitor", "ntp", "rpc", "tftp", |
257 | "image_service", "service_monitor", |
258 | ] |
259 | self.assertItemsEqual(expected_services, service.namedServices) |
260 | |
261 | === modified file 'src/provisioningserver/tests/test_service_monitor.py' |
262 | --- src/provisioningserver/tests/test_service_monitor.py 2016-08-18 15:21:07 +0000 |
263 | +++ src/provisioningserver/tests/test_service_monitor.py 2016-08-19 13:45:36 +0000 |
264 | @@ -8,15 +8,24 @@ |
265 | from unittest.mock import sentinel |
266 | |
267 | from maastesting.factory import factory |
268 | -from maastesting.testcase import MAASTestCase |
269 | +from maastesting.testcase import ( |
270 | + MAASTestCase, |
271 | + MAASTwistedRunTest, |
272 | +) |
273 | +from provisioningserver.rackdservices.testing import ( |
274 | + prepareRegionForGetControllerType, |
275 | +) |
276 | from provisioningserver.service_monitor import ( |
277 | DHCPService, |
278 | DHCPv4Service, |
279 | DHCPv6Service, |
280 | + NTPService, |
281 | service_monitor, |
282 | TGTService, |
283 | ) |
284 | from provisioningserver.utils.service_monitor import SERVICE_STATE |
285 | +from testtools.matchers import Equals |
286 | +from twisted.internet.defer import inlineCallbacks |
287 | |
288 | |
289 | class TestDHCPService(MAASTestCase): |
290 | @@ -99,8 +108,53 @@ |
291 | self.assertEqual((SERVICE_STATE.ON, None), tgt.getExpectedState()) |
292 | |
293 | |
294 | +class TestNTPService(MAASTestCase): |
295 | + |
296 | + def test_name_and_service_name(self): |
297 | + ntp = NTPService() |
298 | + self.assertEqual("ntp", ntp.service_name) |
299 | + self.assertEqual("ntp", ntp.name) |
300 | + |
301 | + |
302 | +class TestNTPService_Scenarios(MAASTestCase): |
303 | + |
304 | + run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) |
305 | + |
306 | + scenarios = ( |
307 | + ("rack", dict( |
308 | + is_region=False, is_rack=True, |
309 | + expected_state=SERVICE_STATE.ON, |
310 | + )), |
311 | + ("region", dict( |
312 | + is_region=True, is_rack=False, |
313 | + expected_state=SERVICE_STATE.ANY, |
314 | + )), |
315 | + ("region+rack", dict( |
316 | + is_region=True, is_rack=True, |
317 | + expected_state=SERVICE_STATE.ANY, |
318 | + )), |
319 | + ("machine", dict( |
320 | + is_region=False, is_rack=False, |
321 | + expected_state=SERVICE_STATE.ANY, |
322 | + )), |
323 | + ) |
324 | + |
325 | + def setUp(self): |
326 | + super(TestNTPService_Scenarios, self).setUp() |
327 | + return prepareRegionForGetControllerType( |
328 | + self, is_region=self.is_region, is_rack=self.is_rack) |
329 | + |
330 | + @inlineCallbacks |
331 | + def test_getExpectedState(self): |
332 | + ntp = NTPService() |
333 | + self.assertThat( |
334 | + (yield ntp.getExpectedState()), |
335 | + Equals((self.expected_state, None))) |
336 | + |
337 | + |
338 | class TestGlobalServiceMonitor(MAASTestCase): |
339 | |
340 | def test__includes_all_services(self): |
341 | self.assertItemsEqual( |
342 | - ["dhcpd", "dhcpd6", "tgt"], service_monitor._services.keys()) |
343 | + ["dhcpd", "dhcpd6", "ntp", "tgt"], |
344 | + service_monitor._services) |
345 | |
346 | === modified file 'src/provisioningserver/utils/service_monitor.py' |
347 | --- src/provisioningserver/utils/service_monitor.py 2016-08-18 15:33:43 +0000 |
348 | +++ src/provisioningserver/utils/service_monitor.py 2016-08-19 13:45:36 +0000 |
349 | @@ -107,7 +107,7 @@ |
350 | """ |
351 | def deriveStatusInfo(expected_state_and_info, service): |
352 | expected_state, status_info = expected_state_and_info |
353 | - _check_service_state_observed(expected_state) |
354 | + _check_service_state_expected(expected_state) |
355 | if status_info is None: |
356 | status_info = "" |
357 | if self.active_state == SERVICE_STATE.UNKNOWN: |
Looks good.