Merge lp:~allenap/maas/dhcp-leases-parsing--1.5 into lp:maas/1.5
- dhcp-leases-parsing--1.5
- Merge into 1.5
Proposed by
Gavin Panella
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2273 |
Proposed branch: | lp:~allenap/maas/dhcp-leases-parsing--1.5 |
Merge into: | lp:maas/1.5 |
Diff against target: |
773 lines (+407/-212) 2 files modified
src/provisioningserver/dhcp/leases_parser_fast.py (+87/-0) src/provisioningserver/dhcp/tests/test_leases_parser.py (+320/-212) |
To merge this branch: | bzr merge lp:~allenap/maas/dhcp-leases-parsing--1.5 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Review via email: mp+220086@code.launchpad.net |
Commit message
Backport trunk r2335: Faster DHCP leases parser.
Description of the change
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'src/provisioningserver/dhcp/leases_parser_fast.py' |
2 | --- src/provisioningserver/dhcp/leases_parser_fast.py 1970-01-01 00:00:00 +0000 |
3 | +++ src/provisioningserver/dhcp/leases_parser_fast.py 2014-05-19 15:16:18 +0000 |
4 | @@ -0,0 +1,87 @@ |
5 | +# Copyright 2013 Canonical Ltd. This software is licensed under the |
6 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | + |
8 | +"""A speedier version of `leases_parser`. |
9 | + |
10 | +This extracts the relevant stanzas from a leases file, keeping only the |
11 | +most recent "host" and "lease" entries, then uses the existing and |
12 | +properly defined but slow parser to parse them. This massively speeds up |
13 | +parsing a leases file that contains a modest number of unique host and |
14 | +lease entries, but has become very large because of churn. |
15 | +""" |
16 | + |
17 | +from __future__ import ( |
18 | + absolute_import, |
19 | + print_function, |
20 | + unicode_literals, |
21 | + ) |
22 | + |
23 | +str = None |
24 | + |
25 | +__metaclass__ = type |
26 | +__all__ = [ |
27 | + 'parse_leases', |
28 | + ] |
29 | + |
30 | +from collections import defaultdict |
31 | +from datetime import datetime |
32 | +from itertools import chain |
33 | +import re |
34 | + |
35 | +from provisioningserver.dhcp.leases_parser import ( |
36 | + get_host_mac, |
37 | + has_expired, |
38 | + is_host, |
39 | + is_lease, |
40 | + lease_parser, |
41 | + ) |
42 | + |
43 | + |
44 | +re_entry = re.compile( |
45 | + r''' |
46 | + ^\s* # Ignore leading whitespace on each line. |
47 | + (host|lease) # Look only for host or lease stanzas. |
48 | + \s+ # Mandatory whitespace. |
49 | + ([0-9a-fA-F.:]+) # Capture the IP/MAC address for this stanza. |
50 | + \s*{ # Optional whitespace then an opening brace. |
51 | + ''', |
52 | + re.MULTILINE | re.DOTALL | re.VERBOSE) |
53 | + |
54 | + |
55 | +def find_lease_starts(leases_contents): |
56 | + results = defaultdict(dict) |
57 | + for match in re_entry.finditer(leases_contents): |
58 | + stanza, address = match.groups() |
59 | + results[stanza][address] = match.start() |
60 | + return chain.from_iterable( |
61 | + mapping.itervalues() for mapping in results.itervalues()) |
62 | + |
63 | + |
64 | +def extract_leases(leases_contents): |
65 | + starts = find_lease_starts(leases_contents) |
66 | + for start in sorted(starts): |
67 | + record = lease_parser.scanString(leases_contents[start:]) |
68 | + try: |
69 | + token, _, _ = next(record) |
70 | + except StopIteration: |
71 | + pass |
72 | + else: |
73 | + yield token |
74 | + |
75 | + |
76 | +def parse_leases(leases_contents): |
77 | + results = {} |
78 | + now = datetime.utcnow() |
79 | + for entry in extract_leases(leases_contents): |
80 | + if is_lease(entry): |
81 | + if not has_expired(entry, now): |
82 | + results[entry.ip] = entry.hardware.mac |
83 | + elif is_host(entry): |
84 | + mac = get_host_mac(entry) |
85 | + if mac is None: |
86 | + # TODO: Test this. |
87 | + if entry.ip in results: |
88 | + del results[entry.ip] |
89 | + else: |
90 | + results[entry.ip] = mac |
91 | + return results |
92 | |
93 | === modified file 'src/provisioningserver/dhcp/tests/test_leases_parser.py' |
94 | --- src/provisioningserver/dhcp/tests/test_leases_parser.py 2013-10-07 09:12:40 +0000 |
95 | +++ src/provisioningserver/dhcp/tests/test_leases_parser.py 2014-05-19 15:16:18 +0000 |
96 | @@ -20,6 +20,10 @@ |
97 | |
98 | from maastesting.factory import factory |
99 | from maastesting.testcase import MAASTestCase |
100 | +from provisioningserver.dhcp import ( |
101 | + leases_parser, |
102 | + leases_parser_fast, |
103 | + ) |
104 | from provisioningserver.dhcp.leases_parser import ( |
105 | combine_entries, |
106 | gather_hosts, |
107 | @@ -30,197 +34,51 @@ |
108 | is_host, |
109 | is_lease, |
110 | lease_parser, |
111 | - parse_leases, |
112 | - ) |
113 | - |
114 | - |
115 | -class TestLeasesParser(MAASTestCase): |
116 | - |
117 | - def fake_parsed_lease(self, ip=None, mac=None, ends=None, |
118 | - entry_type='lease'): |
119 | - """Fake a lease as produced by the parser.""" |
120 | - if ip is None: |
121 | - ip = factory.getRandomIPAddress() |
122 | - if mac is None: |
123 | - mac = factory.getRandomMACAddress() |
124 | - Hardware = namedtuple('Hardware', ['mac']) |
125 | - Lease = namedtuple( |
126 | - 'Lease', ['lease_or_host', 'ip', 'hardware', 'ends']) |
127 | - return Lease(entry_type, ip, Hardware(mac), ends) |
128 | - |
129 | - def fake_parsed_host(self, ip=None, mac=None): |
130 | - """Fake a host declaration as produced by the parser.""" |
131 | - return self.fake_parsed_lease(ip=ip, mac=mac, entry_type='host') |
132 | - |
133 | - def fake_parsed_rubout(self, ip=None): |
134 | - """Fake a "rubout" host declaration.""" |
135 | - if ip is None: |
136 | - ip = factory.getRandomIPAddress() |
137 | - Rubout = namedtuple('Rubout', ['lease_or_host', 'ip']) |
138 | - return Rubout('host', ip) |
139 | - |
140 | - def test_get_expiry_date_parses_expiry_date(self): |
141 | - lease = self.fake_parsed_lease(ends='0 2011/01/02 03:04:05') |
142 | - self.assertEqual( |
143 | - datetime( |
144 | - year=2011, month=01, day=02, |
145 | - hour=03, minute=04, second=05), |
146 | - get_expiry_date(lease)) |
147 | - |
148 | - def test_get_expiry_date_returns_None_for_never(self): |
149 | - self.assertIsNone( |
150 | - get_expiry_date(self.fake_parsed_lease(ends='never'))) |
151 | - |
152 | - def test_get_expiry_date_returns_None_if_no_expiry_given(self): |
153 | - self.assertIsNone(get_expiry_date(self.fake_parsed_lease(ends=None))) |
154 | - |
155 | - def test_has_expired_returns_False_for_eternal_lease(self): |
156 | - now = datetime.utcnow() |
157 | - self.assertFalse(has_expired(self.fake_parsed_lease(ends=None), now)) |
158 | - |
159 | - def test_has_expired_returns_False_for_future_expiry_date(self): |
160 | - now = datetime.utcnow() |
161 | - later = '1 2035/12/31 23:59:59' |
162 | - self.assertFalse(has_expired(self.fake_parsed_lease(ends=later), now)) |
163 | - |
164 | - def test_has_expired_returns_True_for_past_expiry_date(self): |
165 | - now = datetime.utcnow() |
166 | - earlier = '1 2001/01/01 00:00:00' |
167 | - self.assertTrue( |
168 | - has_expired(self.fake_parsed_lease(ends=earlier), now)) |
169 | - |
170 | - def test_gather_leases_finds_current_leases(self): |
171 | - lease = self.fake_parsed_lease() |
172 | - self.assertEqual( |
173 | - {lease.ip: lease.hardware.mac}, |
174 | - gather_leases([lease])) |
175 | - |
176 | - def test_gather_leases_ignores_expired_leases(self): |
177 | - earlier = '1 2001/01/01 00:00:00' |
178 | - lease = self.fake_parsed_lease(ends=earlier) |
179 | - self.assertEqual({}, gather_leases([lease])) |
180 | - |
181 | - def test_gather_leases_combines_expired_and_current_leases(self): |
182 | - earlier = '1 2001/01/01 00:00:00' |
183 | - ip = factory.getRandomIPAddress() |
184 | - old_owner = factory.getRandomMACAddress() |
185 | - new_owner = factory.getRandomMACAddress() |
186 | - leases = [ |
187 | - self.fake_parsed_lease(ip=ip, mac=old_owner, ends=earlier), |
188 | - self.fake_parsed_lease(ip=ip, mac=new_owner), |
189 | - ] |
190 | - self.assertEqual({ip: new_owner}, gather_leases(leases)) |
191 | - |
192 | - def test_gather_leases_ignores_ordering(self): |
193 | - earlier = '1 2001/01/01 00:00:00' |
194 | - ip = factory.getRandomIPAddress() |
195 | - old_owner = factory.getRandomMACAddress() |
196 | - new_owner = factory.getRandomMACAddress() |
197 | - leases = [ |
198 | - self.fake_parsed_lease(ip=ip, mac=new_owner), |
199 | - self.fake_parsed_lease(ip=ip, mac=old_owner, ends=earlier), |
200 | - ] |
201 | - self.assertEqual({ip: new_owner}, gather_leases(leases)) |
202 | - |
203 | - def test_gather_leases_ignores_host_declarations(self): |
204 | - self.assertEqual({}, gather_leases([self.fake_parsed_host()])) |
205 | - |
206 | - def test_gather_hosts_finds_hosts(self): |
207 | - host = self.fake_parsed_host() |
208 | - self.assertEqual({host.ip: host.hardware.mac}, gather_hosts([host])) |
209 | - |
210 | - def test_gather_hosts_ignores_unaccompanied_rubouts(self): |
211 | - self.assertEqual({}, gather_hosts([self.fake_parsed_rubout()])) |
212 | - |
213 | - def test_gather_hosts_ignores_rubbed_out_entries(self): |
214 | - ip = factory.getRandomIPAddress() |
215 | - hosts = [ |
216 | - self.fake_parsed_host(ip=ip), |
217 | - self.fake_parsed_rubout(ip=ip), |
218 | - ] |
219 | - self.assertEqual({}, gather_hosts(hosts)) |
220 | - |
221 | - def test_gather_hosts_follows_reassigned_host(self): |
222 | - ip = factory.getRandomIPAddress() |
223 | - new_owner = factory.getRandomMACAddress() |
224 | - hosts = [ |
225 | - self.fake_parsed_host(ip=ip), |
226 | - self.fake_parsed_rubout(ip=ip), |
227 | - self.fake_parsed_host(ip=ip, mac=new_owner), |
228 | - ] |
229 | - self.assertEqual({ip: new_owner}, gather_hosts(hosts)) |
230 | - |
231 | - def test_is_lease_and_is_host_recognize_lease(self): |
232 | - params = { |
233 | - 'ip': factory.getRandomIPAddress(), |
234 | - 'mac': factory.getRandomMACAddress(), |
235 | - } |
236 | - [parsed_lease] = lease_parser.searchString(dedent("""\ |
237 | - lease %(ip)s { |
238 | - hardware ethernet %(mac)s; |
239 | - } |
240 | - """ % params)) |
241 | - self.assertEqual( |
242 | - (True, False), |
243 | - (is_lease(parsed_lease), is_host(parsed_lease))) |
244 | - |
245 | - def test_is_lease_and_is_host_recognize_host(self): |
246 | - params = { |
247 | - 'ip': factory.getRandomIPAddress(), |
248 | - 'mac': factory.getRandomMACAddress(), |
249 | - } |
250 | - [parsed_host] = lease_parser.searchString(dedent("""\ |
251 | - host %(ip)s { |
252 | - hardware ethernet %(mac)s; |
253 | - } |
254 | - """ % params)) |
255 | - self.assertEqual( |
256 | - (False, True), |
257 | - (is_lease(parsed_host), is_host(parsed_host))) |
258 | - |
259 | - def test_get_host_mac_returns_None_for_host(self): |
260 | - params = { |
261 | - 'ip': factory.getRandomIPAddress(), |
262 | - 'mac': factory.getRandomMACAddress(), |
263 | - } |
264 | - [parsed_host] = lease_parser.searchString(dedent("""\ |
265 | - host %(ip)s { |
266 | - hardware ethernet %(mac)s; |
267 | - } |
268 | - """ % params)) |
269 | - self.assertEqual(params['mac'], get_host_mac(parsed_host)) |
270 | - |
271 | - def test_get_host_mac_returns_None_for_rubout(self): |
272 | - ip = factory.getRandomIPAddress() |
273 | - [parsed_host] = lease_parser.searchString(dedent("""\ |
274 | - host %s { |
275 | - deleted; |
276 | - } |
277 | - """ % ip)) |
278 | - self.assertIsNone(get_host_mac(parsed_host)) |
279 | - |
280 | - def test_get_host_mac_returns_None_for_rubout_even_with_mac(self): |
281 | - params = { |
282 | - 'ip': factory.getRandomIPAddress(), |
283 | - 'mac': factory.getRandomMACAddress(), |
284 | - } |
285 | - [parsed_host] = lease_parser.searchString(dedent("""\ |
286 | - host %(ip)s { |
287 | - deleted; |
288 | - hardware ethernet %(mac)s; |
289 | - } |
290 | - """ % params)) |
291 | - self.assertIsNone(get_host_mac(parsed_host)) |
292 | + ) |
293 | + |
294 | + |
295 | +def fake_parsed_lease(ip=None, mac=None, ends=None, |
296 | + entry_type='lease'): |
297 | + """Fake a lease as produced by the parser.""" |
298 | + if ip is None: |
299 | + ip = factory.getRandomIPAddress() |
300 | + if mac is None: |
301 | + mac = factory.getRandomMACAddress() |
302 | + Hardware = namedtuple('Hardware', ['mac']) |
303 | + Lease = namedtuple( |
304 | + 'Lease', ['lease_or_host', 'ip', 'hardware', 'ends']) |
305 | + return Lease(entry_type, ip, Hardware(mac), ends) |
306 | + |
307 | + |
308 | +def fake_parsed_host(ip=None, mac=None): |
309 | + """Fake a host declaration as produced by the parser.""" |
310 | + return fake_parsed_lease(ip=ip, mac=mac, entry_type='host') |
311 | + |
312 | + |
313 | +def fake_parsed_rubout(ip=None): |
314 | + """Fake a "rubout" host declaration.""" |
315 | + if ip is None: |
316 | + ip = factory.getRandomIPAddress() |
317 | + Rubout = namedtuple('Rubout', ['lease_or_host', 'ip']) |
318 | + return Rubout('host', ip) |
319 | + |
320 | + |
321 | +class TestLeasesParsers(MAASTestCase): |
322 | + |
323 | + scenarios = ( |
324 | + ("original", dict(parse=leases_parser.parse_leases)), |
325 | + ("fast", dict(parse=leases_parser_fast.parse_leases)), |
326 | + ) |
327 | |
328 | def test_parse_leases_copes_with_empty_file(self): |
329 | - self.assertEqual({}, parse_leases("")) |
330 | + self.assertEqual({}, self.parse("")) |
331 | |
332 | def test_parse_leases_parses_lease(self): |
333 | params = { |
334 | 'ip': factory.getRandomIPAddress(), |
335 | 'mac': factory.getRandomMACAddress(), |
336 | } |
337 | - leases = parse_leases(dedent("""\ |
338 | + leases = self.parse(dedent("""\ |
339 | lease %(ip)s { |
340 | starts 5 2010/01/01 00:00:01; |
341 | ends never; |
342 | @@ -254,7 +112,7 @@ |
343 | 'ip': factory.getRandomIPAddress(), |
344 | 'mac': factory.getRandomMACAddress(), |
345 | } |
346 | - leases = parse_leases(dedent("""\ |
347 | + leases = self.parse(dedent("""\ |
348 | host %(ip)s { |
349 | dynamic; |
350 | hardware ethernet %(mac)s; |
351 | @@ -263,8 +121,36 @@ |
352 | """ % params)) |
353 | self.assertEqual({params['ip']: params['mac']}, leases) |
354 | |
355 | + def test_parse_leases_copes_with_misleading_values(self): |
356 | + params = { |
357 | + 'ip1': factory.getRandomIPAddress(), |
358 | + 'mac1': factory.getRandomMACAddress(), |
359 | + 'ip2': factory.getRandomIPAddress(), |
360 | + 'mac2': factory.getRandomMACAddress(), |
361 | + } |
362 | + leases = self.parse(dedent("""\ |
363 | + host %(ip1)s { |
364 | + dynamic; |
365 | + ### NOTE the following value has a closing brace, and |
366 | + ### also looks like a host record. |
367 | + uid "foo}host 12.34.56.78 { }"; |
368 | + hardware ethernet %(mac1)s; |
369 | + fixed-address %(ip1)s; |
370 | + } |
371 | + ### NOTE the extra indent on the line below. |
372 | + host %(ip2)s { |
373 | + dynamic; |
374 | + hardware ethernet %(mac2)s; |
375 | + fixed-address %(ip2)s; |
376 | + } |
377 | + """ % params)) |
378 | + self.assertEqual( |
379 | + {params['ip1']: params['mac1'], |
380 | + params['ip2']: params['mac2']}, |
381 | + leases) |
382 | + |
383 | def test_parse_leases_parses_host_rubout(self): |
384 | - leases = parse_leases(dedent("""\ |
385 | + leases = self.parse(dedent("""\ |
386 | host %s { |
387 | deleted; |
388 | } |
389 | @@ -277,7 +163,7 @@ |
390 | 'mac': factory.getRandomMACAddress(), |
391 | 'incomplete_ip': factory.getRandomIPAddress(), |
392 | } |
393 | - leases = parse_leases(dedent("""\ |
394 | + leases = self.parse(dedent("""\ |
395 | lease %(ip)s { |
396 | hardware ethernet %(mac)s; |
397 | } |
398 | @@ -291,7 +177,7 @@ |
399 | 'ip': factory.getRandomIPAddress(), |
400 | 'mac': factory.getRandomMACAddress(), |
401 | } |
402 | - leases = parse_leases(dedent("""\ |
403 | + leases = self.parse(dedent("""\ |
404 | # Top comment (ignored). |
405 | lease %(ip)s { # End-of-line comment (ignored). |
406 | # Comment in lease block (ignored). |
407 | @@ -306,7 +192,7 @@ |
408 | 'ip': factory.getRandomIPAddress(), |
409 | 'mac': factory.getRandomMACAddress(), |
410 | } |
411 | - leases = parse_leases(dedent("""\ |
412 | + leases = self.parse(dedent("""\ |
413 | lease %(ip)s { |
414 | hardware ethernet %(mac)s; |
415 | ends 1 2001/01/01 00:00:00; |
416 | @@ -319,7 +205,7 @@ |
417 | 'ip': factory.getRandomIPAddress(), |
418 | 'mac': factory.getRandomMACAddress(), |
419 | } |
420 | - leases = parse_leases(dedent("""\ |
421 | + leases = self.parse(dedent("""\ |
422 | lease %(ip)s { |
423 | hardware ethernet %(mac)s; |
424 | ends never; |
425 | @@ -332,7 +218,7 @@ |
426 | 'ip': factory.getRandomIPAddress(), |
427 | 'mac': factory.getRandomMACAddress(), |
428 | } |
429 | - leases = parse_leases(dedent("""\ |
430 | + leases = self.parse(dedent("""\ |
431 | lease %(ip)s { |
432 | hardware ethernet %(mac)s; |
433 | } |
434 | @@ -345,7 +231,7 @@ |
435 | 'old_owner': factory.getRandomMACAddress(), |
436 | 'new_owner': factory.getRandomMACAddress(), |
437 | } |
438 | - leases = parse_leases(dedent("""\ |
439 | + leases = self.parse(dedent("""\ |
440 | lease %(ip)s { |
441 | hardware ethernet %(old_owner)s; |
442 | } |
443 | @@ -360,7 +246,7 @@ |
444 | 'ip': factory.getRandomIPAddress(), |
445 | 'mac': factory.getRandomMACAddress(), |
446 | } |
447 | - leases = parse_leases(dedent("""\ |
448 | + leases = self.parse(dedent("""\ |
449 | host %(ip)s { |
450 | dynamic; |
451 | hardware ethernet %(mac)s; |
452 | @@ -375,7 +261,7 @@ |
453 | 'ip': factory.getRandomIPAddress(), |
454 | 'mac': factory.getRandomMACAddress(), |
455 | } |
456 | - leases = parse_leases(dedent("""\ |
457 | + leases = self.parse(dedent("""\ |
458 | host %(ip)s { |
459 | hardware ethernet %(mac)s; |
460 | fixed-address %(ip)s; |
461 | @@ -383,13 +269,235 @@ |
462 | """ % params)) |
463 | self.assertEqual({params['ip']: params['mac']}, leases) |
464 | |
465 | + |
466 | +class TestLeasesParserFast(MAASTestCase): |
467 | + |
468 | + def test_expired_lease_does_not_shadow_earlier_host_stanza(self): |
469 | + params = { |
470 | + 'ip': factory.getRandomIPAddress(), |
471 | + 'mac1': factory.getRandomMACAddress(), |
472 | + 'mac2': factory.getRandomMACAddress(), |
473 | + } |
474 | + leases = leases_parser_fast.parse_leases(dedent("""\ |
475 | + host %(ip)s { |
476 | + dynamic; |
477 | + hardware ethernet %(mac1)s; |
478 | + fixed-address %(ip)s; |
479 | + } |
480 | + lease %(ip)s { |
481 | + starts 5 2010/01/01 00:00:01; |
482 | + ends 1 2010/01/01 00:00:02; |
483 | + hardware ethernet %(mac2)s; |
484 | + } |
485 | + """ % params)) |
486 | + # The lease has expired so it doesn't shadow the host stanza, |
487 | + # and so the MAC returned is from the host stanza. |
488 | + self.assertEqual({params["ip"]: params["mac1"]}, leases) |
489 | + |
490 | + def test_active_lease_shadows_earlier_host_stanza(self): |
491 | + params = { |
492 | + 'ip': factory.getRandomIPAddress(), |
493 | + 'mac1': factory.getRandomMACAddress(), |
494 | + 'mac2': factory.getRandomMACAddress(), |
495 | + } |
496 | + leases = leases_parser_fast.parse_leases(dedent("""\ |
497 | + host %(ip)s { |
498 | + dynamic; |
499 | + hardware ethernet %(mac1)s; |
500 | + fixed-address %(ip)s; |
501 | + } |
502 | + lease %(ip)s { |
503 | + starts 5 2010/01/01 00:00:01; |
504 | + hardware ethernet %(mac2)s; |
505 | + } |
506 | + """ % params)) |
507 | + # The lease hasn't expired, so shadows the earlier host stanza. |
508 | + self.assertEqual({params["ip"]: params["mac2"]}, leases) |
509 | + |
510 | + def test_host_stanza_shadows_earlier_active_lease(self): |
511 | + params = { |
512 | + 'ip': factory.getRandomIPAddress(), |
513 | + 'mac1': factory.getRandomMACAddress(), |
514 | + 'mac2': factory.getRandomMACAddress(), |
515 | + } |
516 | + leases = leases_parser_fast.parse_leases(dedent("""\ |
517 | + lease %(ip)s { |
518 | + starts 5 2010/01/01 00:00:01; |
519 | + hardware ethernet %(mac2)s; |
520 | + } |
521 | + host %(ip)s { |
522 | + dynamic; |
523 | + hardware ethernet %(mac1)s; |
524 | + fixed-address %(ip)s; |
525 | + } |
526 | + """ % params)) |
527 | + # The lease hasn't expired, but the host entry is later, so it |
528 | + # shadows the earlier lease stanza. |
529 | + self.assertEqual({params["ip"]: params["mac1"]}, leases) |
530 | + |
531 | + |
532 | +class TestLeasesParserFunctions(MAASTestCase): |
533 | + |
534 | + def test_get_expiry_date_parses_expiry_date(self): |
535 | + lease = fake_parsed_lease(ends='0 2011/01/02 03:04:05') |
536 | + self.assertEqual( |
537 | + datetime( |
538 | + year=2011, month=01, day=02, |
539 | + hour=03, minute=04, second=05), |
540 | + get_expiry_date(lease)) |
541 | + |
542 | + def test_get_expiry_date_returns_None_for_never(self): |
543 | + self.assertIsNone( |
544 | + get_expiry_date(fake_parsed_lease(ends='never'))) |
545 | + |
546 | + def test_get_expiry_date_returns_None_if_no_expiry_given(self): |
547 | + self.assertIsNone(get_expiry_date(fake_parsed_lease(ends=None))) |
548 | + |
549 | + def test_has_expired_returns_False_for_eternal_lease(self): |
550 | + now = datetime.utcnow() |
551 | + self.assertFalse(has_expired(fake_parsed_lease(ends=None), now)) |
552 | + |
553 | + def test_has_expired_returns_False_for_future_expiry_date(self): |
554 | + now = datetime.utcnow() |
555 | + later = '1 2035/12/31 23:59:59' |
556 | + self.assertFalse(has_expired(fake_parsed_lease(ends=later), now)) |
557 | + |
558 | + def test_has_expired_returns_True_for_past_expiry_date(self): |
559 | + now = datetime.utcnow() |
560 | + earlier = '1 2001/01/01 00:00:00' |
561 | + self.assertTrue( |
562 | + has_expired(fake_parsed_lease(ends=earlier), now)) |
563 | + |
564 | + def test_gather_leases_finds_current_leases(self): |
565 | + lease = fake_parsed_lease() |
566 | + self.assertEqual( |
567 | + {lease.ip: lease.hardware.mac}, |
568 | + gather_leases([lease])) |
569 | + |
570 | + def test_gather_leases_ignores_expired_leases(self): |
571 | + earlier = '1 2001/01/01 00:00:00' |
572 | + lease = fake_parsed_lease(ends=earlier) |
573 | + self.assertEqual({}, gather_leases([lease])) |
574 | + |
575 | + def test_gather_leases_combines_expired_and_current_leases(self): |
576 | + earlier = '1 2001/01/01 00:00:00' |
577 | + ip = factory.getRandomIPAddress() |
578 | + old_owner = factory.getRandomMACAddress() |
579 | + new_owner = factory.getRandomMACAddress() |
580 | + leases = [ |
581 | + fake_parsed_lease(ip=ip, mac=old_owner, ends=earlier), |
582 | + fake_parsed_lease(ip=ip, mac=new_owner), |
583 | + ] |
584 | + self.assertEqual({ip: new_owner}, gather_leases(leases)) |
585 | + |
586 | + def test_gather_leases_ignores_ordering(self): |
587 | + earlier = '1 2001/01/01 00:00:00' |
588 | + ip = factory.getRandomIPAddress() |
589 | + old_owner = factory.getRandomMACAddress() |
590 | + new_owner = factory.getRandomMACAddress() |
591 | + leases = [ |
592 | + fake_parsed_lease(ip=ip, mac=new_owner), |
593 | + fake_parsed_lease(ip=ip, mac=old_owner, ends=earlier), |
594 | + ] |
595 | + self.assertEqual({ip: new_owner}, gather_leases(leases)) |
596 | + |
597 | + def test_gather_leases_ignores_host_declarations(self): |
598 | + self.assertEqual({}, gather_leases([fake_parsed_host()])) |
599 | + |
600 | + def test_gather_hosts_finds_hosts(self): |
601 | + host = fake_parsed_host() |
602 | + self.assertEqual({host.ip: host.hardware.mac}, gather_hosts([host])) |
603 | + |
604 | + def test_gather_hosts_ignores_unaccompanied_rubouts(self): |
605 | + self.assertEqual({}, gather_hosts([fake_parsed_rubout()])) |
606 | + |
607 | + def test_gather_hosts_ignores_rubbed_out_entries(self): |
608 | + ip = factory.getRandomIPAddress() |
609 | + hosts = [ |
610 | + fake_parsed_host(ip=ip), |
611 | + fake_parsed_rubout(ip=ip), |
612 | + ] |
613 | + self.assertEqual({}, gather_hosts(hosts)) |
614 | + |
615 | + def test_gather_hosts_follows_reassigned_host(self): |
616 | + ip = factory.getRandomIPAddress() |
617 | + new_owner = factory.getRandomMACAddress() |
618 | + hosts = [ |
619 | + fake_parsed_host(ip=ip), |
620 | + fake_parsed_rubout(ip=ip), |
621 | + fake_parsed_host(ip=ip, mac=new_owner), |
622 | + ] |
623 | + self.assertEqual({ip: new_owner}, gather_hosts(hosts)) |
624 | + |
625 | + def test_is_lease_and_is_host_recognize_lease(self): |
626 | + params = { |
627 | + 'ip': factory.getRandomIPAddress(), |
628 | + 'mac': factory.getRandomMACAddress(), |
629 | + } |
630 | + [parsed_lease] = lease_parser.searchString(dedent("""\ |
631 | + lease %(ip)s { |
632 | + hardware ethernet %(mac)s; |
633 | + } |
634 | + """ % params)) |
635 | + self.assertEqual( |
636 | + (True, False), |
637 | + (is_lease(parsed_lease), is_host(parsed_lease))) |
638 | + |
639 | + def test_is_lease_and_is_host_recognize_host(self): |
640 | + params = { |
641 | + 'ip': factory.getRandomIPAddress(), |
642 | + 'mac': factory.getRandomMACAddress(), |
643 | + } |
644 | + [parsed_host] = lease_parser.searchString(dedent("""\ |
645 | + host %(ip)s { |
646 | + hardware ethernet %(mac)s; |
647 | + } |
648 | + """ % params)) |
649 | + self.assertEqual( |
650 | + (False, True), |
651 | + (is_lease(parsed_host), is_host(parsed_host))) |
652 | + |
653 | + def test_get_host_mac_returns_None_for_host(self): |
654 | + params = { |
655 | + 'ip': factory.getRandomIPAddress(), |
656 | + 'mac': factory.getRandomMACAddress(), |
657 | + } |
658 | + [parsed_host] = lease_parser.searchString(dedent("""\ |
659 | + host %(ip)s { |
660 | + hardware ethernet %(mac)s; |
661 | + } |
662 | + """ % params)) |
663 | + self.assertEqual(params['mac'], get_host_mac(parsed_host)) |
664 | + |
665 | + def test_get_host_mac_returns_None_for_rubout(self): |
666 | + ip = factory.getRandomIPAddress() |
667 | + [parsed_host] = lease_parser.searchString(dedent("""\ |
668 | + host %s { |
669 | + deleted; |
670 | + } |
671 | + """ % ip)) |
672 | + self.assertIsNone(get_host_mac(parsed_host)) |
673 | + |
674 | + def test_get_host_mac_returns_None_for_rubout_even_with_mac(self): |
675 | + params = { |
676 | + 'ip': factory.getRandomIPAddress(), |
677 | + 'mac': factory.getRandomMACAddress(), |
678 | + } |
679 | + [parsed_host] = lease_parser.searchString(dedent("""\ |
680 | + host %(ip)s { |
681 | + deleted; |
682 | + hardware ethernet %(mac)s; |
683 | + } |
684 | + """ % params)) |
685 | + self.assertIsNone(get_host_mac(parsed_host)) |
686 | + |
687 | def test_combine_entries_accepts_host_followed_by_expired_lease(self): |
688 | ip = factory.getRandomIPAddress() |
689 | mac = factory.getRandomMACAddress() |
690 | earlier = '1 2001/01/01 00:00:00' |
691 | entries = [ |
692 | - self.fake_parsed_host(ip=ip, mac=mac), |
693 | - self.fake_parsed_lease(ip=ip, ends=earlier), |
694 | + fake_parsed_host(ip=ip, mac=mac), |
695 | + fake_parsed_lease(ip=ip, ends=earlier), |
696 | ] |
697 | self.assertEqual({ip: mac}, combine_entries(entries)) |
698 | |
699 | @@ -398,8 +506,8 @@ |
700 | mac = factory.getRandomMACAddress() |
701 | earlier = '1 2001/01/01 00:00:00' |
702 | entries = [ |
703 | - self.fake_parsed_lease(ip=ip, ends=earlier), |
704 | - self.fake_parsed_host(ip=ip, mac=mac), |
705 | + fake_parsed_lease(ip=ip, ends=earlier), |
706 | + fake_parsed_host(ip=ip, mac=mac), |
707 | ] |
708 | self.assertEqual({ip: mac}, combine_entries(entries)) |
709 | |
710 | @@ -407,9 +515,9 @@ |
711 | ip = factory.getRandomIPAddress() |
712 | mac = factory.getRandomMACAddress() |
713 | entries = [ |
714 | - self.fake_parsed_host(ip=ip), |
715 | - self.fake_parsed_rubout(ip=ip), |
716 | - self.fake_parsed_lease(ip=ip, mac=mac), |
717 | + fake_parsed_host(ip=ip), |
718 | + fake_parsed_rubout(ip=ip), |
719 | + fake_parsed_lease(ip=ip, mac=mac), |
720 | ] |
721 | self.assertEqual({ip: mac}, combine_entries(entries)) |
722 | |
723 | @@ -418,9 +526,9 @@ |
724 | mac = factory.getRandomMACAddress() |
725 | earlier = '1 2001/01/01 00:00:00' |
726 | entries = [ |
727 | - self.fake_parsed_host(ip=ip), |
728 | - self.fake_parsed_rubout(ip=ip), |
729 | - self.fake_parsed_lease(ip=ip, mac=mac, ends=earlier), |
730 | + fake_parsed_host(ip=ip), |
731 | + fake_parsed_rubout(ip=ip), |
732 | + fake_parsed_lease(ip=ip, mac=mac, ends=earlier), |
733 | ] |
734 | self.assertEqual({}, combine_entries(entries)) |
735 | |
736 | @@ -429,9 +537,9 @@ |
737 | mac = factory.getRandomMACAddress() |
738 | earlier = '1 2001/01/01 00:00:00' |
739 | entries = [ |
740 | - self.fake_parsed_host(ip=ip), |
741 | - self.fake_parsed_lease(ip=ip, mac=mac, ends=earlier), |
742 | - self.fake_parsed_rubout(ip=ip), |
743 | + fake_parsed_host(ip=ip), |
744 | + fake_parsed_lease(ip=ip, mac=mac, ends=earlier), |
745 | + fake_parsed_rubout(ip=ip), |
746 | ] |
747 | self.assertEqual({}, combine_entries(entries)) |
748 | |
749 | @@ -439,9 +547,9 @@ |
750 | ip = factory.getRandomIPAddress() |
751 | mac = factory.getRandomMACAddress() |
752 | entries = [ |
753 | - self.fake_parsed_host(ip=ip), |
754 | - self.fake_parsed_lease(ip=ip, mac=mac), |
755 | - self.fake_parsed_rubout(ip=ip), |
756 | + fake_parsed_host(ip=ip), |
757 | + fake_parsed_lease(ip=ip, mac=mac), |
758 | + fake_parsed_rubout(ip=ip), |
759 | ] |
760 | self.assertEqual({ip: mac}, combine_entries(entries)) |
761 | |
762 | @@ -449,8 +557,8 @@ |
763 | ip = factory.getRandomIPAddress() |
764 | mac = factory.getRandomMACAddress() |
765 | entries = [ |
766 | - self.fake_parsed_host(ip=ip), |
767 | - self.fake_parsed_rubout(ip=ip), |
768 | - self.fake_parsed_host(ip=ip, mac=mac), |
769 | + fake_parsed_host(ip=ip), |
770 | + fake_parsed_rubout(ip=ip), |
771 | + fake_parsed_host(ip=ip, mac=mac), |
772 | ] |
773 | self.assertEqual({ip: mac}, combine_entries(entries)) |