Merge lp:~rvb/maas/bug-1061409 into lp:maas/trunk

Proposed by Raphaël Badin on 2012-10-04
Status: Merged
Approved by: Raphaël Badin on 2012-10-04
Approved revision: 1164
Merged at revision: 1167
Proposed branch: lp:~rvb/maas/bug-1061409
Merge into: lp:maas/trunk
Diff against target: 168 lines (+78/-11)
2 files modified
src/provisioningserver/dhcp/leases.py (+44/-11)
src/provisioningserver/dhcp/tests/test_leases.py (+34/-0)
To merge this branch: bzr merge lp:~rvb/maas/bug-1061409
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve on 2012-10-04
Review via email: mp+127979@code.launchpad.net

Commit Message

This branch changes the DHCP lease parsing code so that the DHCP parsing task won't error if the DHCP lease file is not present.

Description of the Change

This branch changes the DHCP lease parsing code so that the DHCP parsing task won't error if the DHCP lease file is not present. Instead, it will display a message in the log file: this message is, I'm afraid, the best we can do because we don't know on the cluster controller side if DHCP is enabled for this cluster or not.

To post a comment you must log in.
Jeroen T. Vermeulen (jtv) wrote :

The code and tests look fine. However the log notice comes across as confusing for a reader who doesn't already know how things work. It currently says...

89 + "Unable to parse the DHCP leases file. You might want to install "
90 + "the 'maas-dhcp' package. Note that this is perfectly fine "
91 + "if this cluster is not managing its DHCP server.")

I would switch those last two sentences around, so that the message conveys information in this order:
1. There's no leases file.
2. Which is fine, unless you want MAAS to manage DHCP.
3. If you want MAAS to manage DHCP, the problem is that maas-dhcp hasn't been installed.

Several things need attention in the details of the text, in my view:

 * It doesn't help that the "this is perfectly fine" is ambiguous -- it could refer to the inability to parse the leases file, or to installation of maas-dhcp.

 * Is "Unable to parse the DHCP leases file" really the best information we can give? There's a big difference between "the leases file does not exist" and "the parser had a problem with the contents of the leases file." I think this message is only meant for the former case.

 * Hinting that people may be able to solve their problems by installing maas-dhcp is an open invitation for people to set up accidental rogue DHCP servers.

 * "Note that" doesn't carry a lot of meaning. Better leave it out.

Please fix up before landing!

Jeroen

Raphaël Badin (rvb) wrote :

[...]
> Several things need attention in the details of the text, in my view:
>
> * It doesn't help that the "this is perfectly fine" is ambiguous -- it could
> refer to the inability to parse the leases file, or to installation of maas-
> dhcp.
>
> * Is "Unable to parse the DHCP leases file" really the best information we
> can give? There's a big difference between "the leases file does not exist"
> and "the parser had a problem with the contents of the leases file." I think
> this message is only meant for the former case.
>
> * Hinting that people may be able to solve their problems by installing maas-
> dhcp is an open invitation for people to set up accidental rogue DHCP servers.
>
> * "Note that" doesn't carry a lot of meaning. Better leave it out.
>
> Please fix up before landing!
>
> Jeroen

You're definitely right… how about:

        task_logger.info(
            "The DHCP leases file does not exit. This is only a problem if "
            "this cluster controller is managing its DHCP server. If that's "
            "the case then you need to install the 'maas-dhcp' package on "
            "this cluster controller.")

lp:~rvb/maas/bug-1061409 updated on 2012-10-04
1164. By Raphaël Badin on 2012-10-04

Fix typo.

Raphaël Badin (rvb) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/dhcp/leases.py'
2--- src/provisioningserver/dhcp/leases.py 2012-09-28 10:16:59 +0000
3+++ src/provisioningserver/dhcp/leases.py 2012-10-04 12:26:22 +0000
4@@ -32,6 +32,7 @@
5 ]
6
7
8+import errno
9 import json
10 from os import (
11 fstat,
12@@ -67,8 +68,19 @@
13
14
15 def get_leases_timestamp():
16- """Return the last modification timestamp of the DHCP leases file."""
17- return stat(get_leases_file()).st_mtime
18+ """Return the last modification timestamp of the DHCP leases file.
19+
20+ None will be returned if the DHCP lease file cannot be found.
21+ """
22+ try:
23+ return stat(get_leases_file()).st_mtime
24+ except OSError as exception:
25+ # Return None only if the exception is a "No such file or
26+ # directory" exception.
27+ if exception.errno == errno.ENOENT:
28+ return None
29+ else:
30+ raise
31
32
33 def parse_leases_file():
34@@ -77,10 +89,19 @@
35 :return: A tuple: (timestamp, leases). The `timestamp` is the last
36 modification time of the leases file, and `leases` is a dict
37 mapping leased IP addresses to their associated MAC addresses.
38+ None will be returned if the DHCP lease file cannot be found.
39 """
40- with open(get_leases_file(), 'rb') as leases_file:
41- contents = leases_file.read().decode('utf-8')
42- return fstat(leases_file.fileno()).st_mtime, parse_leases(contents)
43+ try:
44+ with open(get_leases_file(), 'rb') as leases_file:
45+ contents = leases_file.read().decode('utf-8')
46+ return fstat(leases_file.fileno()).st_mtime, parse_leases(contents)
47+ except IOError as exception:
48+ # Return None only if the exception is a "No such file or
49+ # directory" exception.
50+ if exception.errno == errno.ENOENT:
51+ return None
52+ else:
53+ raise
54
55
56 def check_lease_changes():
57@@ -93,11 +114,15 @@
58
59 if get_leases_timestamp() == previous_leases_time:
60 return None
61- timestamp, leases = parse_leases_file()
62- if leases == previous_leases:
63- return None
64+ parse_result = parse_leases_file()
65+ if parse_result is not None:
66+ timestamp, leases = parse_result
67+ if leases == previous_leases:
68+ return None
69+ else:
70+ return timestamp, leases
71 else:
72- return timestamp, leases
73+ return None
74
75
76 def record_lease_state(last_change, leases):
77@@ -155,8 +180,16 @@
78 server restarts, or zone-file update commands getting lost on their
79 way to the DNS server.
80 """
81- timestamp, leases = parse_leases_file()
82- process_leases(timestamp, leases)
83+ parse_result = parse_leases_file()
84+ if parse_result:
85+ timestamp, leases = parse_result
86+ process_leases(timestamp, leases)
87+ else:
88+ task_logger.info(
89+ "The DHCP leases file does not exist. This is only a problem if "
90+ "this cluster controller is managing its DHCP server. If that's "
91+ "the case then you need to install the 'maas-dhcp' package on "
92+ "this cluster controller.")
93
94
95 def update_leases():
96
97=== modified file 'src/provisioningserver/dhcp/tests/test_leases.py'
98--- src/provisioningserver/dhcp/tests/test_leases.py 2012-09-28 10:16:59 +0000
99+++ src/provisioningserver/dhcp/tests/test_leases.py 2012-10-04 12:26:22 +0000
100@@ -16,6 +16,8 @@
101 datetime,
102 timedelta,
103 )
104+import errno
105+import os
106 from textwrap import dedent
107
108 from apiclient.maas_client import MAASClient
109@@ -25,6 +27,7 @@
110 age_file,
111 get_write_time,
112 )
113+from mock import Mock
114 from provisioningserver import cache
115 from provisioningserver.auth import (
116 MAAS_URL_CACHE_KEY,
117@@ -196,6 +199,29 @@
118 update_leases()
119 self.assertSequenceEqual([], parser.calls)
120
121+ def redirect_parser_to_non_existing_file(self):
122+ file_name = self.make_file()
123+ os.remove(file_name)
124+ self.redirect_parser(file_name)
125+
126+ def test_parse_leases_file_returns_None_if_file_does_not_exist(self):
127+ self.redirect_parser_to_non_existing_file()
128+ self.assertIsNone(parse_leases_file())
129+
130+ def test_get_leases_timestamp_returns_None_if_file_does_not_exist(self):
131+ self.redirect_parser_to_non_existing_file()
132+ self.assertIsNone(parse_leases_file())
133+
134+ def test_parse_leases_file_errors_if_unexpected_exception(self):
135+ exception = IOError(errno.EBUSY, factory.getRandomString())
136+ self.patch(leases_module, 'open', Mock(side_effect=exception))
137+ self.assertRaises(IOError, parse_leases_file)
138+
139+ def test_get_leases_timestamp_errors_if_unexpected_exception(self):
140+ exception = OSError(errno.EBUSY, factory.getRandomString())
141+ self.patch(leases_module, 'open', Mock(side_effect=exception))
142+ self.assertRaises(OSError, parse_leases_file)
143+
144 def test_check_lease_changes_returns_tuple_if_lease_added(self):
145 leases = factory.make_random_leases()
146 self.set_lease_state(
147@@ -206,6 +232,10 @@
148 (get_write_time(leases_file), leases),
149 check_lease_changes())
150
151+ def test_check_lease_returns_None_if_lease_file_does_not_exist(self):
152+ self.redirect_parser_to_non_existing_file()
153+ self.assertIsNone(check_lease_changes())
154+
155 def test_check_lease_changes_returns_tuple_if_leases_dropped(self):
156 self.set_lease_state(
157 datetime.utcnow() - timedelta(seconds=10),
158@@ -227,6 +257,10 @@
159 self.set_lease_state(datetime.utcnow(), leases.copy())
160 self.assertIsNone(check_lease_changes())
161
162+ def test_upload_leases_does_not_try_to_process_non_existing_file(self):
163+ self.redirect_parser_to_non_existing_file()
164+ self.assertIsNone(upload_leases()) # No exception.
165+
166 def test_update_leases_processes_leases_if_changed(self):
167 self.set_lease_state()
168 self.patch(leases_module, 'send_leases', FakeMethod())