~allenap/maas/regiond-leader

« back to all changes in this revision

Viewing changes to src/provisioningserver/pserv_services/tests/test_neighbours.py

  • Committer: MAAS Lander
  • Author(s): Gavin Panella
  • Date: 2015-04-28 15:55:34 UTC
  • mfrom: (3835.4.18 neighbours-service)
  • Revision ID: maas_lander-20150428155534-zcrpqa1fl6vl7txp
[r=rvb,mpontillo][bug=][author=allenap] New service NeighboursService that tracks link-layer neighbours.

This avoids running `arp -n` and `ip neigh` 10s or 100s times per second, each time blocking the reactor.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2015 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Tests for `provisioningserver.pserv_services.neighbours`."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = []
 
16
 
 
17
from maastesting.matchers import MockCalledOnceWith
 
18
from maastesting.testcase import (
 
19
    MAASTestCase,
 
20
    MAASTwistedRunTest,
 
21
)
 
22
from mock import ANY
 
23
from netaddr import (
 
24
    EUI,
 
25
    IPAddress,
 
26
)
 
27
from provisioningserver.pserv_services import neighbours
 
28
from provisioningserver.pserv_services.neighbours import NeighboursService
 
29
from provisioningserver.rpc.testing import TwistedLoggerFixture
 
30
from provisioningserver.utils.network import NeighboursProtocol
 
31
from testtools.matchers import (
 
32
    Equals,
 
33
    Is,
 
34
)
 
35
from twisted.internet.defer import inlineCallbacks
 
36
 
 
37
# The lines in the following output containing "lladdr" in the 4th column will
 
38
# become part of the parsed mappings. Other lines will be silently ignored.
 
39
example_ip_neigh_output = b"""\
 
40
fe80::9e97:26ff:fe94:f884 dev eth0 lladdr 9c:97:26:94:f8:84 router STALE
 
41
2001:8b0:1219:a75e:9e97::f884 dev eth0 lladdr 9c:97:26:94:f8:84 router STALE
 
42
172.16.1.254 dev eth1 lladdr 00:50:56:f9:33:8e STALE
 
43
192.168.1.254 dev eth0 lladdr 9c:97:26:94:f8:84 STALE
 
44
172.16.1.1 dev eth1 lladdr 00:50:56:c0:00:01 STALE
 
45
172.16.1.1 dev eth1 lladdr 00:50:56:c0:00:02 STALE
 
46
10.0.3.166 dev lxcbr0 lladdr 00:16:3e:da:8b:9e STALE
 
47
10.0.3.167 dev lxcbr0 lladdr 00:16:3e:da:8b:9e STALE
 
48
10.155.0.4 dev wlan0  FAILED
 
49
2.2.2.2 dev wlan0  INCOMPLETE
 
50
"""
 
51
 
 
52
example_ip_neigh_mappings = (
 
53
    NeighboursProtocol.collateNeighbours(
 
54
        NeighboursProtocol.parseOutput(
 
55
            example_ip_neigh_output.splitlines())))
 
56
 
 
57
 
 
58
class TestNeighboursService(MAASTestCase):
 
59
    """Tests for `NeighboursService`."""
 
60
 
 
61
    def setUp(self):
 
62
        super(TestNeighboursService, self).setUp()
 
63
        self.reactor = self.patch(neighbours, "reactor")
 
64
 
 
65
    def test__update_spawns_process(self):
 
66
        NeighboursService().update()
 
67
        self.assertThat(
 
68
            self.reactor.spawnProcess,
 
69
            MockCalledOnceWith(ANY, b"ip", (b"ip", b"neigh")))
 
70
 
 
71
    def test__find_ip_addresses(self):
 
72
        service = NeighboursService()
 
73
        service.update().callback(example_ip_neigh_mappings)
 
74
        # Multiple IP addresses or MAC addresses can be returned.
 
75
        self.assertThat(
 
76
            service.find_ip_addresses(EUI("00:50:56:c0:00:01")),
 
77
            Equals({IPAddress("172.16.1.1")}))
 
78
        self.assertThat(
 
79
            service.find_ip_addresses(EUI("00:16:3e:da:8b:9e")),
 
80
            Equals({IPAddress("10.0.3.166"), IPAddress("10.0.3.167")}))
 
81
        # Some addresses will yield empty results.
 
82
        self.assertThat(
 
83
            service.find_ip_addresses(EUI("12:34:56:78:90:ab")), Equals(set()))
 
84
 
 
85
    def test__find_ip_address(self):
 
86
        service = NeighboursService()
 
87
        service.update().callback(example_ip_neigh_mappings)
 
88
        # A single IP address or MAC address will be returned.
 
89
        self.assertThat(
 
90
            service.find_ip_address(EUI("00:50:56:c0:00:01")),
 
91
            Equals(IPAddress("172.16.1.1")))
 
92
        self.assertThat(
 
93
            service.find_ip_address(EUI("00:16:3e:da:8b:9e")),
 
94
            Equals(IPAddress("10.0.3.166")))
 
95
        # Some addresses will yield None.
 
96
        self.assertThat(
 
97
            service.find_ip_address(EUI("12:34:56:78:90:ab")), Is(None))
 
98
 
 
99
    def test__find_mac_addresses(self):
 
100
        service = NeighboursService()
 
101
        service.update().callback(example_ip_neigh_mappings)
 
102
        # Multiple IP addresses or MAC addresses can be returned.
 
103
        self.assertThat(
 
104
            service.find_mac_addresses(IPAddress("172.16.1.1")),
 
105
            Equals({EUI("00:50:56:c0:00:01"), EUI("00:50:56:c0:00:02")}))
 
106
        self.assertThat(
 
107
            service.find_mac_addresses(IPAddress("10.0.3.166")),
 
108
            Equals({EUI("00:16:3e:da:8b:9e")}))
 
109
        # Some addresses will yield empty results.
 
110
        self.assertThat(
 
111
            service.find_mac_addresses(IPAddress("1.2.3.4")), Equals(set()))
 
112
 
 
113
    def test__find_mac_address(self):
 
114
        service = NeighboursService()
 
115
        service.update().callback(example_ip_neigh_mappings)
 
116
        # A single IP address or MAC address will be returned.
 
117
        self.assertThat(
 
118
            service.find_mac_address(IPAddress("172.16.1.1")),
 
119
            Equals(EUI("00:50:56:c0:00:01")))
 
120
        self.assertThat(
 
121
            service.find_mac_address(IPAddress("10.0.3.166")),
 
122
            Equals(EUI("00:16:3e:da:8b:9e")))
 
123
        # Some addresses will yield None.
 
124
        self.assertThat(
 
125
            service.find_mac_address(IPAddress("1.2.3.4")), Is(None))
 
126
 
 
127
 
 
128
class TestNeighboursServiceLive(MAASTestCase):
 
129
    """Tests for `NeighboursService` with a reactor."""
 
130
 
 
131
    run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
 
132
 
 
133
    @inlineCallbacks
 
134
    def test__update_catches_failures_spawning_the_process(self):
 
135
        non_executable = self.make_file()
 
136
        service = NeighboursService()
 
137
        service.command = (non_executable.encode("ascii"), )
 
138
        with TwistedLoggerFixture() as logger:
 
139
            yield service.update()
 
140
        self.assertDocTestMatches(
 
141
            """\
 
142
            `ip neigh` wrote to stderr (an error may be reported separately):
 
143
            ...
 
144
            OSError: [Errno 13] Permission denied
 
145
            ...
 
146
            Updating neighbours failed.
 
147
            Traceback (most recent call last):
 
148
            Failure: twisted.internet.error.ProcessTerminated: ...
 
149
            """,
 
150
            logger.output)
 
151
 
 
152
    @inlineCallbacks
 
153
    def test__update_catches_failures_coming_from_the_process(self):
 
154
        service = NeighboursService()
 
155
        service.command = (b"false", )
 
156
        with TwistedLoggerFixture() as logger:
 
157
            yield service.update()
 
158
        self.assertDocTestMatches(
 
159
            """\
 
160
            Updating neighbours failed.
 
161
            Traceback (most recent call last):
 
162
            Failure: twisted.internet.error.ProcessTerminated: ...
 
163
            """,
 
164
            logger.output)