Merge lp:~mpontillo/maas/maas-rack--send-beacons into lp:maas/trunk

Proposed by Mike Pontillo
Status: Rejected
Rejected by: MAAS Lander
Proposed branch: lp:~mpontillo/maas/maas-rack--send-beacons
Merge into: lp:maas/trunk
Prerequisite: lp:~mpontillo/maas/beaconing-packet-format
Diff against target: 429 lines (+166/-10)
5 files modified
src/provisioningserver/__main__.py (+2/-0)
src/provisioningserver/utils/beaconing.py (+2/-2)
src/provisioningserver/utils/network.py (+1/-0)
src/provisioningserver/utils/send_beacons.py (+123/-0)
src/provisioningserver/utils/tests/test_network.py (+38/-8)
To merge this branch: bzr merge lp:~mpontillo/maas/maas-rack--send-beacons
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+325605@code.launchpad.net

Commit message

Add test command for sending out beacons.

To post a comment you must log in.
6090. By Mike Pontillo on 2017-06-14

Add tests for interface index.

6091. By Mike Pontillo on 2017-06-14

Merge branch: lp:~mpontillo/maas/get-all-interfaces--add-ifindex

MAAS Lander (maas-lander) wrote :

Transitioned to Git.

lp:maas has now moved from Bzr to Git.
Please propose your branches with Launchpad using Git.

git clone https://git.launchpad.net/maas

Unmerged revisions

6091. By Mike Pontillo on 2017-06-14

Merge branch: lp:~mpontillo/maas/get-all-interfaces--add-ifindex

6090. By Mike Pontillo on 2017-06-14

Add tests for interface index.

6089. By Mike Pontillo on 2017-06-14

Add test command to send beacons out.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/__main__.py'
2--- src/provisioningserver/__main__.py 2017-06-08 20:41:27 +0000
3+++ src/provisioningserver/__main__.py 2017-06-14 15:45:59 +0000
4@@ -16,6 +16,7 @@
5 import provisioningserver.utils.dhcp
6 import provisioningserver.utils.scan_network
7 from provisioningserver.utils.script import MainScript
8+import provisioningserver.utils.send_beacons
9
10
11 script_commands = {
12@@ -27,6 +28,7 @@
13 'observe-beacons': provisioningserver.utils.beaconing,
14 'observe-mdns': provisioningserver.utils.avahi,
15 'observe-dhcp': provisioningserver.utils.dhcp,
16+ 'send-beacons': provisioningserver.utils.send_beacons,
17 'scan-network': provisioningserver.utils.scan_network,
18 'register': provisioningserver.register_command,
19 'support-dump': provisioningserver.support_dump,
20
21=== modified file 'src/provisioningserver/utils/beaconing.py'
22--- src/provisioningserver/utils/beaconing.py 2017-06-14 15:45:58 +0000
23+++ src/provisioningserver/utils/beaconing.py 2017-06-14 15:45:59 +0000
24@@ -53,7 +53,7 @@
25 class BEACON_ENCAPSULATION:
26 """Enumeration to describe beacon encapsulation types."""
27
28- FERNET_PSK = "fernet_psk"
29+ FERNET_PSK = "fernet-psk"
30
31
32 BeaconPayload = namedtuple('BeaconPayload', (
33@@ -63,7 +63,7 @@
34 ))
35
36
37-def create_beacon_payload(beacon_type, data=None):
38+def create_beacon_payload(beacon_type, data=None) -> BeaconPayload:
39 """Creates a beacon payload of the specified type, with the given data.
40
41 :param beacon_type: The beacon packet type. Indicates the purpose of the
42
43=== modified file 'src/provisioningserver/utils/network.py'
44--- src/provisioningserver/utils/network.py 2017-04-26 21:15:06 +0000
45+++ src/provisioningserver/utils/network.py 2017-06-14 15:45:59 +0000
46@@ -1159,6 +1159,7 @@
47 # Create the interface definition will links for both IPv4 and IPv6.
48 interface = {
49 "type": iface_type,
50+ "index": ipaddr['index'],
51 "links": [],
52 "enabled": True if 'UP' in ipaddr['flags'] else False,
53 "parents": parents,
54
55=== added file 'src/provisioningserver/utils/send_beacons.py'
56--- src/provisioningserver/utils/send_beacons.py 1970-01-01 00:00:00 +0000
57+++ src/provisioningserver/utils/send_beacons.py 2017-06-14 15:45:59 +0000
58@@ -0,0 +1,123 @@
59+# Copyright 2016 Canonical Ltd. This software is licensed under the
60+# GNU Affero General Public License version 3 (see the file LICENSE).
61+
62+"""Utilities for scanning attached networks."""
63+import struct
64+
65+
66+__all__ = [
67+ "add_arguments",
68+ "run"
69+]
70+
71+import select
72+import socket
73+import sys
74+from textwrap import dedent
75+import time
76+
77+from provisioningserver.utils.beaconing import (
78+ BEACON_PORT,
79+ create_beacon_payload,
80+ read_beacon_payload,
81+)
82+from provisioningserver.utils.network import get_all_interfaces_definition
83+from pprint import pformat
84+
85+
86+def add_arguments(parser):
87+ """Add this command's options to the `ArgumentParser`.
88+
89+ Specified by the `ActionScript` interface.
90+ """
91+ parser.description = dedent("""\
92+ Send solicitation beacons to a particular address.
93+ """)
94+ parser.add_argument(
95+ '-v', '--verbose', action='store_true', required=False,
96+ help='Verbose packet output.')
97+ parser.add_argument(
98+ '-s', '--source', type=str, required=False,
99+ help='Source address to send beacons from.')
100+ parser.add_argument(
101+ 'destination', type=str, nargs='?',
102+ help="Destination to send beacon to. If not specified, will use the "
103+ "MAAS multicast group (224.0.0.118).")
104+
105+
106+def do_beaconing(args, source_ip, destination_ip, interfaces, stdout):
107+ """Interprets the specified `args` and `to_scan` dict to perform the scan.
108+
109+ Uses the specified `stdout` and `stderr` for output.
110+ """
111+ clock = time.monotonic()
112+ transport = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
113+ if source_ip is not None:
114+ if ':' not in source_ip:
115+ source_ip = "::ffff:" + source_ip
116+ transport.bind((source_ip, 0))
117+ if destination_ip is None:
118+ destination_ip = "::ffff:224.0.0.118"
119+ elif ':' not in destination_ip:
120+ destination_ip = "::ffff:" + destination_ip
121+ beacon = create_beacon_payload("solicitation")
122+ if "224.0.0.118" in destination_ip:
123+ for ifdata in interfaces.values():
124+ for link in ifdata["links"]:
125+ address = link["address"]
126+ # Strip off CIDRs.
127+ address = address.split("/")[0]
128+ if ':' not in address:
129+ transport.setsockopt(
130+ socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
131+ transport.setsockopt(
132+ socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
133+ socket.inet_aton(address))
134+ destination_ip = "::ffff:224.0.0.118"
135+ transport.sendto(
136+ beacon.packet_bytes, (destination_ip, BEACON_PORT))
137+ else:
138+ # We're sending to an IPv4 multicast address, so we can't
139+ # actually use IPv6 addresses here.
140+ transport.setsockopt(
141+ socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
142+ packed_ifindex = struct.pack('I', ifdata['index'])
143+ transport.setsockopt(
144+ socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF,
145+ packed_ifindex)
146+ destination_ip = "ff02::15a"
147+ transport.sendto(
148+ beacon.packet_bytes, (destination_ip, BEACON_PORT))
149+ else:
150+ transport.sendto(beacon.packet_bytes, (destination_ip, BEACON_PORT))
151+ transport.setblocking(0)
152+ clock_diff = 0
153+ timeout = 5
154+ while clock_diff < timeout:
155+ ready = select.select([transport], [], [], timeout)
156+ if ready[0]:
157+ reply, reply_address = transport.recvfrom(16384)
158+ if args.verbose:
159+ print("%s: %s" % (
160+ pformat(reply), pformat(reply_address)), file=stdout)
161+ reply = read_beacon_payload(reply)
162+ print("%s" % pformat(reply), file=stdout)
163+ clock_diff = time.monotonic() - clock
164+
165+
166+def run(args, stdout=sys.stdout):
167+ """Scan local networks for on-link hosts.
168+
169+ :param args: Parsed output of the arguments added in `add_arguments()`.
170+ :param stdout: Standard output stream to write to.
171+ :param stderr: Standard error stream to write to.
172+ """
173+ # Record the current time so we can figure out how long it took us to
174+ # do all this scanning.
175+ source_ip = args.source
176+ destination_ip = args.destination
177+ interfaces = get_all_interfaces_definition(annotate_with_monitored=False)
178+ if args.verbose:
179+ print("%s" % pformat(interfaces), file=stdout)
180+ do_beaconing(
181+ args, source_ip, destination_ip, interfaces, stdout)
182
183=== modified file 'src/provisioningserver/utils/tests/test_network.py'
184--- src/provisioningserver/utils/tests/test_network.py 2017-04-26 02:29:43 +0000
185+++ src/provisioningserver/utils/tests/test_network.py 2017-06-14 15:45:59 +0000
186@@ -5,6 +5,7 @@
187
188 __all__ = []
189
190+import random
191 import socket
192 from socket import (
193 EAI_BADFLAGS,
194@@ -1174,6 +1175,7 @@
195 "flags": ["UP"],
196 "inet": ["127.0.0.1/32"],
197 "inet6": ["::1"],
198+ "index": 1,
199 },
200 }
201 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))
202@@ -1185,6 +1187,7 @@
203 "mac": factory.make_mac_address(),
204 "flags": ["UP"],
205 "inet": ["192.168.122.2/24"],
206+ "index": 2,
207 },
208 }
209 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))
210@@ -1194,6 +1197,7 @@
211 "vnet": {
212 "type": "ipip",
213 "flags": ["UP"],
214+ "index": 2,
215 },
216 }
217 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))
218@@ -1202,6 +1206,7 @@
219 ip_addr = {
220 "eth0": {
221 "type": "ethernet.physical",
222+ "index": 2,
223 "mac": factory.make_mac_address(),
224 "flags": ["UP"],
225 "inet": ["192.168.122.2/24"],
226@@ -1210,6 +1215,7 @@
227 expected_result = MatchesDict({
228 "eth0": MatchesDict({
229 "type": Equals("physical"),
230+ "index": Equals(ip_addr["eth0"]["index"]),
231 "mac_address": Equals(ip_addr["eth0"]["mac"]),
232 "enabled": Is(True),
233 "parents": Equals([]),
234@@ -1226,6 +1232,7 @@
235 ip_addr = {
236 "eth0": {
237 "type": "ethernet.physical",
238+ "index": random.randint(2, 99),
239 "mac": factory.make_mac_address(),
240 "flags": ["UP"],
241 "inet": ["192.168.122.2/24"],
242@@ -1239,6 +1246,7 @@
243 expected_result = MatchesDict({
244 "eth0": MatchesDict({
245 "type": Equals("physical"),
246+ "index": Equals(ip_addr["eth0"]["index"]),
247 "mac_address": Equals(ip_addr["eth0"]["mac"]),
248 "enabled": Is(True),
249 "parents": Equals([]),
250@@ -1256,6 +1264,7 @@
251 ip_addr = {
252 "eth0": {
253 "type": "ethernet",
254+ "index": random.randint(2, 99),
255 "mac": factory.make_mac_address(),
256 "flags": ["UP"],
257 "inet": ["192.168.122.2/24"],
258@@ -1264,6 +1273,7 @@
259 expected_result = MatchesDict({
260 "eth0": MatchesDict({
261 "type": Equals("physical"),
262+ "index": Equals(ip_addr["eth0"]["index"]),
263 "mac_address": Equals(ip_addr["eth0"]["mac"]),
264 "enabled": Is(True),
265 "parents": Equals([]),
266@@ -1281,6 +1291,7 @@
267 ip_addr = {
268 "eth0": {
269 "type": "ethernet.physical",
270+ "index": random.randint(2, 99),
271 "mac": factory.make_mac_address(),
272 "flags": ["UP"],
273 "inet": ["192.168.122.2/24", "192.168.122.200/32"],
274@@ -1292,6 +1303,7 @@
275 expected_result = MatchesDict({
276 "eth0": MatchesDict({
277 "type": Equals("physical"),
278+ "index": Equals(ip_addr["eth0"]["index"]),
279 "mac_address": Equals(ip_addr["eth0"]["mac"]),
280 "enabled": Is(True),
281 "parents": Equals([]),
282@@ -1315,6 +1327,7 @@
283 ip_addr = {
284 "eth0": {
285 "type": "ethernet.physical",
286+ "index": random.randint(2, 99),
287 "mac": factory.make_mac_address(),
288 "flags": ["UP"],
289 "inet": [
290@@ -1331,6 +1344,7 @@
291 expected_result = MatchesDict({
292 "eth0": MatchesDict({
293 "type": Equals("physical"),
294+ "index": Equals(ip_addr["eth0"]["index"]),
295 "mac_address": Equals(ip_addr["eth0"]["mac"]),
296 "enabled": Is(True),
297 "parents": Equals([]),
298@@ -1365,21 +1379,25 @@
299 ip_addr = {
300 "eth0": {
301 "type": "ethernet.physical",
302+ "index": 2,
303 "mac": factory.make_mac_address(),
304 "flags": [],
305 },
306 "eth1": {
307 "type": "ethernet.physical",
308+ "index": 3,
309 "mac": factory.make_mac_address(),
310 "flags": ["UP"],
311 },
312 "eth2": {
313 "type": "ethernet.physical",
314+ "index": 4,
315 "mac": factory.make_mac_address(),
316 "flags": ["UP"],
317 },
318 "bond0": {
319 "type": "ethernet.bond",
320+ "index": 5,
321 "mac": factory.make_mac_address(),
322 "flags": ["UP"],
323 "bonded_interfaces": ["eth1", "eth2"],
324@@ -1388,6 +1406,7 @@
325 },
326 "bond0.10": {
327 "type": "ethernet.vlan",
328+ "index": 6,
329 "flags": ["UP"],
330 "vid": 10,
331 "inet": ["192.168.123.2/24", "192.168.123.3/32"],
332@@ -1395,6 +1414,7 @@
333 },
334 "vlan20": {
335 "type": "ethernet.vlan",
336+ "index": 7,
337 "mac": factory.make_mac_address(),
338 "flags": ["UP"],
339 "vid": 20,
340@@ -1402,11 +1422,13 @@
341 },
342 "wlan0": {
343 "type": "ethernet.wireless",
344+ "index": 8,
345 "mac": factory.make_mac_address(),
346 "flags": ["UP"],
347 },
348 "br0": {
349 "type": "ethernet.bridge",
350+ "index": 9,
351 "bridged_interfaces": ["eth0"],
352 "mac": factory.make_mac_address(),
353 "flags": ["UP"],
354@@ -1424,6 +1446,7 @@
355 expected_result = MatchesDict({
356 "eth0": MatchesDict({
357 "type": Equals("physical"),
358+ "index": Equals(2),
359 "mac_address": Equals(ip_addr["eth0"]["mac"]),
360 "enabled": Is(False),
361 "parents": Equals([]),
362@@ -1432,6 +1455,7 @@
363 }),
364 "eth1": MatchesDict({
365 "type": Equals("physical"),
366+ "index": Equals(3),
367 "mac_address": Equals(ip_addr["eth1"]["mac"]),
368 "enabled": Is(True),
369 "parents": Equals([]),
370@@ -1440,6 +1464,7 @@
371 }),
372 "eth2": MatchesDict({
373 "type": Equals("physical"),
374+ "index": Equals(4),
375 "mac_address": Equals(ip_addr["eth2"]["mac"]),
376 "enabled": Is(True),
377 "parents": Equals([]),
378@@ -1448,6 +1473,7 @@
379 }),
380 "bond0": MatchesDict({
381 "type": Equals("bond"),
382+ "index": Equals(5),
383 "mac_address": Equals(ip_addr["bond0"]["mac"]),
384 "enabled": Is(True),
385 "parents": Equals(["eth1", "eth2"]),
386@@ -1471,6 +1497,7 @@
387 }),
388 "bond0.10": MatchesDict({
389 "type": Equals("vlan"),
390+ "index": Equals(6),
391 "enabled": Is(True),
392 "parents": Equals(["bond0"]),
393 "vid": Equals(10),
394@@ -1486,24 +1513,27 @@
395 ),
396 "source": Equals("ipaddr"),
397 }),
398+ "vlan20": MatchesDict({
399+ "type": Equals("vlan"),
400+ "index": Equals(7),
401+ "enabled": Is(True),
402+ "parents": Equals(["eth0"]),
403+ "links": Equals([]),
404+ "source": Equals("ipaddr"),
405+ "vid": Equals(20),
406+ }),
407 "wlan0": MatchesDict({
408 "type": Equals("physical"),
409+ "index": Equals(8),
410 "mac_address": Equals(ip_addr["wlan0"]["mac"]),
411 "enabled": Is(True),
412 "parents": Equals([]),
413 "links": Equals([]),
414 "source": Equals("ipaddr"),
415 }),
416- "vlan20": MatchesDict({
417- "type": Equals("vlan"),
418- "enabled": Is(True),
419- "parents": Equals(["eth0"]),
420- "links": Equals([]),
421- "source": Equals("ipaddr"),
422- "vid": Equals(20),
423- }),
424 "br0": MatchesDict({
425 "type": Equals("bridge"),
426+ "index": Equals(9),
427 "mac_address": Equals(ip_addr["br0"]["mac"]),
428 "enabled": Is(True),
429 "parents": Equals(["eth0"]),