Merge lp:~jtv/maas/control-dhcpd into lp:~maas-committers/maas/trunk
- control-dhcpd
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2804 |
Proposed branch: | lp:~jtv/maas/control-dhcpd |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
449 lines (+215/-45) 7 files modified
src/maasserver/tests/test_dhcp.py (+3/-4) src/provisioningserver/dhcp/control.py (+79/-0) src/provisioningserver/dhcp/tests/test_control.py (+103/-0) src/provisioningserver/rpc/dhcp.py (+3/-6) src/provisioningserver/rpc/tests/test_dhcp.py (+11/-14) src/provisioningserver/tasks.py (+9/-11) src/provisioningserver/tests/test_tasks.py (+7/-10) |
To merge this branch: | bzr merge lp:~jtv/maas/control-dhcpd |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Review via email: mp+232029@code.launchpad.net |
Commit message
Extract functions for stopping/restarting dhcpd into their own module. We'll be using them in additional places soon.
Description of the change
This was Blake's suggestion in review of my previous branch. It makes things a bit cleaner and also, easier to test.
Jeroen
Julian Edwards (julian-edwards) wrote : | # |
Julian Edwards (julian-edwards) wrote : | # |
You can add custom test matchers or methods now, for example
self.assertIPV4
Jeroen T. Vermeulen (jtv) wrote : | # |
> I'm not sure I like the plethora of restart/start/stop methods. They are only
> one liners anyway and considerably bloat the code with the extra docstrings
> and testing.
They don't bloat the code you'll normally be looking at though — only their own module, which is simple enough to figure out from the function names so you probably won't need to read it.
> Given you're doing this layering, it indicates that the underlying
> "issue_
> wrapper to make it palatable.
Well, it's the other way around: the simple functions were what I wanted, and the internal helper just extracts some commonality between them.
> What's wrong with just:
>
> call_service_
> subcommands consts or enums so you get:
> call_service_
Perhaps nothing wrong with it (apart from the obvious syntax error), but there are a few things about it I don't like. For one, the caller always knows statically whether they want to start the DHCPv4 daemon or stop the DHCPv6 daemon etc. Those things are very similar _in implementation_, but that's of no interest to the caller. In my experience turning the essentials of what the caller wants into parameters for Swiss-army-knife functions invites needless complication in itself.
For another, I wouldn't mind creating an enum for the two IP versions, but also introducing a single-purpose enum for “what do you want the dhcp service to do” to me completely negates the savings from having a single function. (Add a bit of extra confusion on top of that if the enum lives in the conventional ‘enum’ module.) Passing the parameters also weighs down tests that make use of the function: they then ought to cover not only whether the right function gets called, but also whether it gets called in the right way. Which means either verifying the parameters or integration-test the actual call.
> which reads *much* nicer to my eyes and has considerably less code to test and
> maintain.
Not “considerably less.” Four lines in total, which then effectively move into the calling code and need to be tested there, more spread out, with more specific setup, and ultimately in more places.
Julian Edwards (julian-edwards) wrote : | # |
review: approve
On Monday 25 August 2014 05:00:54 you wrote:
> They don't bloat the code you'll normally be looking at though — only their
> own module, which is simple enough to figure out from the function names so
> you probably won't need to read it.
Does this mean we can bloat any code that we don't happen to be looking at at
the particular time?
> > Given you're doing this layering, it indicates that the underlying
> > "issue_
> > another wrapper to make it palatable.
>
> Well, it's the other way around: the simple functions were what I wanted,
> and the internal helper just extracts some commonality between them.
OK.
> > What's wrong with just:
> >
> > call_service_
> > subcommands consts or enums so you get:
> > call_service_
>
> Perhaps nothing wrong with it (apart from the obvious syntax error), but
It's psuedo-code ;)
> there are a few things about it I don't like. For one, the caller always
> knows statically whether they want to start the DHCPv4 daemon or stop the
> DHCPv6 daemon etc. Those things are very similar _in implementation_, but
> that's of no interest to the caller. In my experience turning the
> essentials of what the caller wants into parameters for Swiss-army-knife
> functions invites needless complication in itself.
>
> For another, I wouldn't mind creating an enum for the two IP versions, but
> also introducing a single-purpose enum for “what do you want the dhcp
> service to do” to me completely negates the savings from having a single
> function. (Add a bit of extra confusion on top of that if the enum lives
> in the conventional ‘enum’ module.) Passing the parameters also weighs
> down tests that make use of the function: they then ought to cover not only
> whether the right function gets called, but also whether it gets called in
> the right way. Which means either verifying the parameters or
> integration-test the actual call.
It's a shame you don't like using classes more, because a class property would
have been ideal. I also find that they nicely group functions like this
without exploding the number of files you have with separate modules.
> > which reads *much* nicer to my eyes and has considerably less code to test
> > and maintain.
>
> Not “considerably less.” Four lines in total, which then effectively move
> into the calling code and need to be tested there, more spread out, with
> more specific setup, and ultimately in more places.
The 4 lines go along with the repetitive docstrings as well, you can't
refactor those (well, maybe you can).
Anyway I don't want to block you so I'll approve this. I just wanted to
express some feelings about the code.
Preview Diff
1 | === modified file 'src/maasserver/tests/test_dhcp.py' |
2 | --- src/maasserver/tests/test_dhcp.py 2014-08-18 10:53:21 +0000 |
3 | +++ src/maasserver/tests/test_dhcp.py 2014-08-25 04:26:47 +0000 |
4 | @@ -32,6 +32,7 @@ |
5 | from maasserver.testing.factory import factory |
6 | from maasserver.testing.testcase import MAASServerTestCase |
7 | from maastesting.celery import CeleryFixture |
8 | +from maastesting.matchers import MockCalledOnceWith |
9 | from netaddr import ( |
10 | IPAddress, |
11 | IPNetwork, |
12 | @@ -172,15 +173,13 @@ |
13 | |
14 | def test_configure_dhcp_restarts_dhcp_server(self): |
15 | self.patch(tasks, "sudo_write_file") |
16 | - mocked_check_call = self.patch(tasks, "call_and_check") |
17 | + restart_dhcpv4 = self.patch(tasks, "restart_dhcpv4") |
18 | self.patch(settings, "DHCP_CONNECT", True) |
19 | nodegroup = factory.make_node_group( |
20 | status=NODEGROUP_STATUS.ACCEPTED, |
21 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP) |
22 | configure_dhcp(nodegroup) |
23 | - self.assertEqual( |
24 | - mocked_check_call.call_args[0][0], |
25 | - ['sudo', '-n', 'service', 'maas-dhcp-server', 'restart']) |
26 | + self.assertThat(restart_dhcpv4, MockCalledOnceWith()) |
27 | |
28 | def test_configure_dhcp_is_called_with_valid_dhcp_key(self): |
29 | self.patch(dhcp, 'write_dhcp_config') |
30 | |
31 | === added file 'src/provisioningserver/dhcp/control.py' |
32 | --- src/provisioningserver/dhcp/control.py 1970-01-01 00:00:00 +0000 |
33 | +++ src/provisioningserver/dhcp/control.py 2014-08-25 04:26:47 +0000 |
34 | @@ -0,0 +1,79 @@ |
35 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
36 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
37 | + |
38 | +"""Control the MAAS DHCP daemons.""" |
39 | + |
40 | +from __future__ import ( |
41 | + absolute_import, |
42 | + print_function, |
43 | + unicode_literals, |
44 | + ) |
45 | + |
46 | +str = None |
47 | + |
48 | +__metaclass__ = type |
49 | +__all__ = [ |
50 | + 'restart_dhcpv4', |
51 | + 'restart_dhcpv6', |
52 | + 'stop_dhcpv4', |
53 | + 'stop_dhcpv6', |
54 | + ] |
55 | + |
56 | +from provisioningserver.utils.shell import call_and_check |
57 | + |
58 | + |
59 | +def call_service_script(ip_version, subcommand): |
60 | + """Issue a subcommand to one of the DHCP daemon services. |
61 | + |
62 | + Shells out using `sudo`, and with the `C` locale. |
63 | + |
64 | + :raise ExternalProcessError: if the restart command fails. |
65 | + """ |
66 | + service_names = { |
67 | + 4: 'maas-dhcp-server', |
68 | + 6: 'maas-dhcpv6-server', |
69 | + } |
70 | + name = service_names[ip_version] |
71 | + call_and_check( |
72 | + ['sudo', '-n', 'service', name, subcommand], |
73 | + env={'LC_ALL': 'C'}) |
74 | + |
75 | + |
76 | +def restart_dhcpv4(): |
77 | + """Restart the (IPv4) DHCP daemon. |
78 | + |
79 | + Shells out using `sudo`, and with the `C` locale. |
80 | + |
81 | + :raise ExternalProcessError: if the restart command fails. |
82 | + """ |
83 | + call_service_script(4, 'restart') |
84 | + |
85 | + |
86 | +def restart_dhcpv6(): |
87 | + """Restart the DHCPv6 daemon. |
88 | + |
89 | + Shells out using `sudo`, and with the `C` locale. |
90 | + |
91 | + :raise ExternalProcessError: if the restart command fails. |
92 | + """ |
93 | + call_service_script(6, 'restart') |
94 | + |
95 | + |
96 | +def stop_dhcpv4(): |
97 | + """Stop the (IPv4) DHCP daemon. |
98 | + |
99 | + Shells out using `sudo`, and with the `C` locale. |
100 | + |
101 | + :raise ExternalProcessError: if the restart command fails. |
102 | + """ |
103 | + call_service_script(4, 'stop') |
104 | + |
105 | + |
106 | +def stop_dhcpv6(): |
107 | + """Stop the DHCPv6 daemon. |
108 | + |
109 | + Shells out using `sudo`, and with the `C` locale. |
110 | + |
111 | + :raise ExternalProcessError: if the restart command fails. |
112 | + """ |
113 | + call_service_script(6, 'stop') |
114 | |
115 | === added file 'src/provisioningserver/dhcp/tests/test_control.py' |
116 | --- src/provisioningserver/dhcp/tests/test_control.py 1970-01-01 00:00:00 +0000 |
117 | +++ src/provisioningserver/dhcp/tests/test_control.py 2014-08-25 04:26:47 +0000 |
118 | @@ -0,0 +1,103 @@ |
119 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
120 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
121 | + |
122 | +"""Tests for DHCP control functions.""" |
123 | + |
124 | +from __future__ import ( |
125 | + absolute_import, |
126 | + print_function, |
127 | + unicode_literals, |
128 | + ) |
129 | + |
130 | +str = None |
131 | + |
132 | +__metaclass__ = type |
133 | +__all__ = [] |
134 | + |
135 | +from maastesting.matchers import MockCalledOnceWith |
136 | +from maastesting.testcase import MAASTestCase |
137 | +from mock import ANY |
138 | +from provisioningserver.dhcp import control |
139 | +from provisioningserver.dhcp.control import ( |
140 | + call_service_script, |
141 | + restart_dhcpv4, |
142 | + restart_dhcpv6, |
143 | + stop_dhcpv4, |
144 | + stop_dhcpv6, |
145 | + ) |
146 | + |
147 | + |
148 | +def patch_call_and_check(testcase): |
149 | + """Patch `call_and_check`.""" |
150 | + return testcase.patch(control, 'call_and_check') |
151 | + |
152 | + |
153 | +class TestCallServiceScript(MAASTestCase): |
154 | + """Tests for `call_service_script`.""" |
155 | + |
156 | + def test__fails_on_weird_IP_version(self): |
157 | + self.assertRaises(KeyError, call_service_script, 5, 'restart') |
158 | + |
159 | + def test__controls_maas_dhcp_server_for_IP_version_4(self): |
160 | + call_and_check = patch_call_and_check(self) |
161 | + call_service_script(4, 'stop') |
162 | + self.assertThat( |
163 | + call_and_check, |
164 | + MockCalledOnceWith( |
165 | + ['sudo', '-n', 'service', 'maas-dhcp-server', 'stop'], |
166 | + env=ANY)) |
167 | + |
168 | + def test__controls_maas_dhcpv6_server_for_IP_version_6(self): |
169 | + call_and_check = patch_call_and_check(self) |
170 | + call_service_script(6, 'stop') |
171 | + self.assertThat( |
172 | + call_and_check, |
173 | + MockCalledOnceWith( |
174 | + ['sudo', '-n', 'service', 'maas-dhcpv6-server', 'stop'], |
175 | + env=ANY)) |
176 | + |
177 | + def test__sets_C_locale(self): |
178 | + call_and_check = patch_call_and_check(self) |
179 | + call_service_script(4, 'stop') |
180 | + self.assertThat( |
181 | + call_and_check, |
182 | + MockCalledOnceWith(ANY, env={'LC_ALL': 'C'})) |
183 | + |
184 | + |
185 | +class TestControl(MAASTestCase): |
186 | + |
187 | + def test_restart_dhcpv4(self): |
188 | + call_and_check = patch_call_and_check(self) |
189 | + restart_dhcpv4() |
190 | + self.assertThat( |
191 | + call_and_check, |
192 | + MockCalledOnceWith( |
193 | + ['sudo', '-n', 'service', 'maas-dhcp-server', 'restart'], |
194 | + env={'LC_ALL': 'C'})) |
195 | + |
196 | + def test_restart_dhcpv6(self): |
197 | + call_and_check = patch_call_and_check(self) |
198 | + restart_dhcpv6() |
199 | + self.assertThat( |
200 | + call_and_check, |
201 | + MockCalledOnceWith( |
202 | + ['sudo', '-n', 'service', 'maas-dhcpv6-server', 'restart'], |
203 | + env={'LC_ALL': 'C'})) |
204 | + |
205 | + def test_stop_dhcpv4(self): |
206 | + call_and_check = patch_call_and_check(self) |
207 | + stop_dhcpv4() |
208 | + self.assertThat( |
209 | + call_and_check, |
210 | + MockCalledOnceWith( |
211 | + ['sudo', '-n', 'service', 'maas-dhcp-server', 'stop'], |
212 | + env={'LC_ALL': 'C'})) |
213 | + |
214 | + def test_stop_dhcpv6(self): |
215 | + call_and_check = patch_call_and_check(self) |
216 | + stop_dhcpv6() |
217 | + self.assertThat( |
218 | + call_and_check, |
219 | + MockCalledOnceWith( |
220 | + ['sudo', '-n', 'service', 'maas-dhcpv6-server', 'stop'], |
221 | + env={'LC_ALL': 'C'})) |
222 | |
223 | === modified file 'src/provisioningserver/rpc/dhcp.py' |
224 | --- src/provisioningserver/rpc/dhcp.py 2014-08-25 02:20:15 +0000 |
225 | +++ src/provisioningserver/rpc/dhcp.py 2014-08-25 04:26:47 +0000 |
226 | @@ -20,6 +20,7 @@ |
227 | |
228 | from celery.app import app_or_default |
229 | from provisioningserver.dhcp.config import get_config |
230 | +from provisioningserver.dhcp.control import restart_dhcpv6 |
231 | from provisioningserver.logger import get_maas_logger |
232 | from provisioningserver.omshell import Omshell |
233 | from provisioningserver.rpc.exceptions import ( |
234 | @@ -28,10 +29,7 @@ |
235 | CannotRemoveHostMap, |
236 | ) |
237 | from provisioningserver.utils.fs import sudo_write_file |
238 | -from provisioningserver.utils.shell import ( |
239 | - call_and_check, |
240 | - ExternalProcessError, |
241 | - ) |
242 | +from provisioningserver.utils.shell import ExternalProcessError |
243 | |
244 | |
245 | maaslog = get_maas_logger("dhcp") |
246 | @@ -66,8 +64,7 @@ |
247 | % e.output_as_unicode) |
248 | |
249 | try: |
250 | - call_and_check( |
251 | - ['sudo', '-n', 'service', 'maas-dhcpv6-server', 'restart']) |
252 | + restart_dhcpv6() |
253 | except ExternalProcessError as e: |
254 | maaslog.error( |
255 | "DHCPv6 server failed to restart (for network interfaces %s): %s", |
256 | |
257 | === modified file 'src/provisioningserver/rpc/tests/test_dhcp.py' |
258 | --- src/provisioningserver/rpc/tests/test_dhcp.py 2014-08-22 11:23:35 +0000 |
259 | +++ src/provisioningserver/rpc/tests/test_dhcp.py 2014-08-25 04:26:47 +0000 |
260 | @@ -42,15 +42,15 @@ |
261 | def patch_sudo_write_file(self): |
262 | return self.patch(dhcp, 'sudo_write_file') |
263 | |
264 | - def patch_call_and_check(self): |
265 | - return self.patch(dhcp, 'call_and_check') |
266 | + def patch_restart_dhcpv6(self): |
267 | + return self.patch(dhcp, 'restart_dhcpv6') |
268 | |
269 | def patch_get_config(self): |
270 | return self.patch(dhcp, 'get_config') |
271 | |
272 | def test__extracts_interfaces(self): |
273 | write_file = self.patch_sudo_write_file() |
274 | - self.patch_call_and_check() |
275 | + self.patch_restart_dhcpv6() |
276 | subnets = [make_subnet_config() for _ in range(3)] |
277 | dhcp.configure_dhcpv6(factory.make_name('key'), subnets) |
278 | self.assertThat( |
279 | @@ -61,7 +61,7 @@ |
280 | |
281 | def test__eliminates_duplicate_interfaces(self): |
282 | write_file = self.patch_sudo_write_file() |
283 | - self.patch_call_and_check() |
284 | + self.patch_restart_dhcpv6() |
285 | interface = factory.make_name('interface') |
286 | subnets = [make_subnet_config() for _ in range(2)] |
287 | for subnet in subnets: |
288 | @@ -71,7 +71,7 @@ |
289 | |
290 | def test__composees_dhcpv6_config(self): |
291 | self.patch_sudo_write_file() |
292 | - self.patch_call_and_check() |
293 | + self.patch_restart_dhcpv6() |
294 | get_config = self.patch_get_config() |
295 | omapi_key = factory.make_name('key') |
296 | subnet = make_subnet_config() |
297 | @@ -84,7 +84,7 @@ |
298 | |
299 | def test__writes_dhcpv6_config(self): |
300 | write_file = self.patch_sudo_write_file() |
301 | - self.patch_call_and_check() |
302 | + self.patch_restart_dhcpv6() |
303 | |
304 | subnet = make_subnet_config() |
305 | expected_config = factory.make_name('config') |
306 | @@ -99,7 +99,7 @@ |
307 | |
308 | def test__writes_interfaces_file(self): |
309 | write_file = self.patch_sudo_write_file() |
310 | - self.patch_call_and_check() |
311 | + self.patch_restart_dhcpv6() |
312 | dhcp.configure_dhcpv6(factory.make_name('key'), [make_subnet_config()]) |
313 | self.assertThat( |
314 | write_file, |
315 | @@ -107,17 +107,14 @@ |
316 | |
317 | def test__restarts_dhcpv6_server(self): |
318 | self.patch_sudo_write_file() |
319 | - call_and_check = self.patch_call_and_check() |
320 | + call_and_check = self.patch_restart_dhcpv6() |
321 | dhcp.configure_dhcpv6(factory.make_name('key'), [make_subnet_config()]) |
322 | - self.assertThat( |
323 | - call_and_check, |
324 | - MockCalledWith( |
325 | - ['sudo', '-n', 'service', 'maas-dhcpv6-server', 'restart'])) |
326 | + self.assertThat(call_and_check, MockCalledWith()) |
327 | |
328 | def test__converts_failure_writing_file_to_CannotConfigureDHCP(self): |
329 | self.patch_sudo_write_file().side_effect = ( |
330 | ExternalProcessError(1, "sudo something")) |
331 | - self.patch_call_and_check() |
332 | + self.patch_restart_dhcpv6() |
333 | self.assertRaises( |
334 | exceptions.CannotConfigureDHCP, |
335 | dhcp.configure_dhcpv6, |
336 | @@ -125,7 +122,7 @@ |
337 | |
338 | def test__converts_dhcp_restart_failure_to_CannotConfigureDHCP(self): |
339 | self.patch_sudo_write_file() |
340 | - self.patch_call_and_check().side_effect = ( |
341 | + self.patch_restart_dhcpv6().side_effect = ( |
342 | ExternalProcessError(1, "sudo something")) |
343 | self.assertRaises( |
344 | exceptions.CannotConfigureDHCP, |
345 | |
346 | === modified file 'src/provisioningserver/tasks.py' |
347 | --- src/provisioningserver/tasks.py 2014-08-21 13:27:59 +0000 |
348 | +++ src/provisioningserver/tasks.py 2014-08-25 04:26:47 +0000 |
349 | @@ -47,6 +47,10 @@ |
350 | config, |
351 | detect, |
352 | ) |
353 | +from provisioningserver.dhcp.control import ( |
354 | + restart_dhcpv4, |
355 | + stop_dhcpv4, |
356 | + ) |
357 | from provisioningserver.dhcp.leases import upload_leases |
358 | from provisioningserver.dns.config import ( |
359 | DNSConfig, |
360 | @@ -68,10 +72,7 @@ |
361 | from provisioningserver.utils.env import environment_variables |
362 | from provisioningserver.utils.fs import sudo_write_file |
363 | from provisioningserver.utils.network import find_ip_via_arp |
364 | -from provisioningserver.utils.shell import ( |
365 | - call_and_check, |
366 | - ExternalProcessError, |
367 | - ) |
368 | +from provisioningserver.utils.shell import ExternalProcessError |
369 | |
370 | # For each item passed to refresh_secrets, a refresh function to give it to. |
371 | refresh_functions = { |
372 | @@ -407,7 +408,7 @@ |
373 | @log_exception_text |
374 | def restart_dhcp_server(): |
375 | """Restart the DHCP server.""" |
376 | - call_and_check(['sudo', '-n', 'service', 'maas-dhcp-server', 'restart']) |
377 | + restart_dhcpv4() |
378 | |
379 | |
380 | # Message to put in the DHCP config file when the DHCP server gets stopped. |
381 | @@ -432,12 +433,9 @@ |
382 | sudo_write_file( |
383 | celery_config.DHCP_CONFIG_FILE, DISABLED_DHCP_SERVER) |
384 | try: |
385 | - # Use LC_ALL=C because we use the returned error message when |
386 | - # errors occur. |
387 | - call_and_check( |
388 | - ['sudo', '-n', 'service', 'maas-dhcp-server', 'stop'], |
389 | - env={'LC_ALL': 'C'} |
390 | - ) |
391 | + # This relies on stop_dhcpv4 running in the "C" locale: if it fails, |
392 | + # we need to parse its error output. |
393 | + stop_dhcpv4() |
394 | except ExternalProcessError as error: |
395 | # Upstart issues an "Unknown instance"-type error when trying to |
396 | # stop an already-stopped service. Ignore this error. |
397 | |
398 | === modified file 'src/provisioningserver/tests/test_tasks.py' |
399 | --- src/provisioningserver/tests/test_tasks.py 2014-08-22 10:03:18 +0000 |
400 | +++ src/provisioningserver/tests/test_tasks.py 2014-08-25 04:26:47 +0000 |
401 | @@ -284,18 +284,15 @@ |
402 | stdin=PIPE)) |
403 | |
404 | def test_restart_dhcp_server_sends_command(self): |
405 | - self.patch(tasks, 'call_and_check') |
406 | + self.patch(tasks, 'restart_dhcpv4') |
407 | restart_dhcp_server() |
408 | - self.assertThat(tasks.call_and_check, MockCalledOnceWith( |
409 | - ['sudo', '-n', 'service', 'maas-dhcp-server', 'restart'])) |
410 | + self.assertThat(tasks.restart_dhcpv4, MockCalledOnceWith()) |
411 | |
412 | def test_stop_dhcp_server_sends_command_and_writes_empty_config(self): |
413 | - self.patch(tasks, 'call_and_check') |
414 | + self.patch(tasks, 'stop_dhcpv4') |
415 | self.patch(tasks, 'sudo_write_file') |
416 | stop_dhcp_server() |
417 | - self.assertThat(tasks.call_and_check, MockCalledOnceWith( |
418 | - ['sudo', '-n', 'service', 'maas-dhcp-server', 'stop'], |
419 | - env={'LC_ALL': 'C'})) |
420 | + self.assertThat(tasks.stop_dhcpv4, MockCalledOnceWith()) |
421 | self.assertThat(tasks.sudo_write_file, MockCalledOnceWith( |
422 | celery_config.DHCP_CONFIG_FILE, tasks.DISABLED_DHCP_SERVER)) |
423 | |
424 | @@ -305,7 +302,7 @@ |
425 | output = ' ' + ALREADY_STOPPED_MESSAGE + '\n' |
426 | exception = ExternalProcessError( |
427 | ALREADY_STOPPED_RETURNCODE, [], output=output) |
428 | - self.patch(tasks, 'call_and_check', Mock(side_effect=exception)) |
429 | + self.patch(tasks, 'stop_dhcpv4', Mock(side_effect=exception)) |
430 | self.patch(tasks, 'sudo_write_file') |
431 | self.assertIsNone(stop_dhcp_server()) |
432 | |
433 | @@ -314,7 +311,7 @@ |
434 | returncode = ALREADY_STOPPED_RETURNCODE + 1 |
435 | exception = ExternalProcessError( |
436 | returncode, [], output=ALREADY_STOPPED_MESSAGE) |
437 | - self.patch(tasks, 'call_and_check', Mock(side_effect=exception)) |
438 | + self.patch(tasks, 'stop_dhcpv4', Mock(side_effect=exception)) |
439 | self.patch(tasks, 'sudo_write_file') |
440 | self.assertRaises(ExternalProcessError, stop_dhcp_server) |
441 | |
442 | @@ -323,7 +320,7 @@ |
443 | output = factory.make_string() |
444 | exception = ExternalProcessError( |
445 | ALREADY_STOPPED_RETURNCODE, [], output=output) |
446 | - self.patch(tasks, 'call_and_check', Mock(side_effect=exception)) |
447 | + self.patch(tasks, 'stop_dhcpv4', Mock(side_effect=exception)) |
448 | self.patch(tasks, 'sudo_write_file') |
449 | self.assertRaises(ExternalProcessError, stop_dhcp_server) |
450 |
I'm not sure I like the plethora of restart/start/stop methods. They are only one liners anyway and considerably bloat the code with the extra docstrings and testing.
Given you're doing this layering, it indicates that the underlying "issue_ service_ command" is so poorly interfaced that you need to add another wrapper to make it palatable.
What's wrong with just:
call_service_ command( ip_version= 4, "start") etc? You can even make the subcommands consts or enums so you get: command( IPV4, START)
call_service_
which reads *much* nicer to my eyes and has considerably less code to test and maintain.