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

Revision history for this message
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
=== modified file 'src/provisioningserver/__main__.py'
--- src/provisioningserver/__main__.py 2017-06-08 20:41:27 +0000
+++ src/provisioningserver/__main__.py 2017-06-14 15:45:59 +0000
@@ -16,6 +16,7 @@
16import provisioningserver.utils.dhcp16import provisioningserver.utils.dhcp
17import provisioningserver.utils.scan_network17import provisioningserver.utils.scan_network
18from provisioningserver.utils.script import MainScript18from provisioningserver.utils.script import MainScript
19import provisioningserver.utils.send_beacons
1920
2021
21script_commands = {22script_commands = {
@@ -27,6 +28,7 @@
27 'observe-beacons': provisioningserver.utils.beaconing,28 'observe-beacons': provisioningserver.utils.beaconing,
28 'observe-mdns': provisioningserver.utils.avahi,29 'observe-mdns': provisioningserver.utils.avahi,
29 'observe-dhcp': provisioningserver.utils.dhcp,30 'observe-dhcp': provisioningserver.utils.dhcp,
31 'send-beacons': provisioningserver.utils.send_beacons,
30 'scan-network': provisioningserver.utils.scan_network,32 'scan-network': provisioningserver.utils.scan_network,
31 'register': provisioningserver.register_command,33 'register': provisioningserver.register_command,
32 'support-dump': provisioningserver.support_dump,34 'support-dump': provisioningserver.support_dump,
3335
=== modified file 'src/provisioningserver/utils/beaconing.py'
--- src/provisioningserver/utils/beaconing.py 2017-06-14 15:45:58 +0000
+++ src/provisioningserver/utils/beaconing.py 2017-06-14 15:45:59 +0000
@@ -53,7 +53,7 @@
53class BEACON_ENCAPSULATION:53class BEACON_ENCAPSULATION:
54 """Enumeration to describe beacon encapsulation types."""54 """Enumeration to describe beacon encapsulation types."""
5555
56 FERNET_PSK = "fernet_psk"56 FERNET_PSK = "fernet-psk"
5757
5858
59BeaconPayload = namedtuple('BeaconPayload', (59BeaconPayload = namedtuple('BeaconPayload', (
@@ -63,7 +63,7 @@
63))63))
6464
6565
66def create_beacon_payload(beacon_type, data=None):66def create_beacon_payload(beacon_type, data=None) -> BeaconPayload:
67 """Creates a beacon payload of the specified type, with the given data.67 """Creates a beacon payload of the specified type, with the given data.
6868
69 :param beacon_type: The beacon packet type. Indicates the purpose of the69 :param beacon_type: The beacon packet type. Indicates the purpose of the
7070
=== modified file 'src/provisioningserver/utils/network.py'
--- src/provisioningserver/utils/network.py 2017-04-26 21:15:06 +0000
+++ src/provisioningserver/utils/network.py 2017-06-14 15:45:59 +0000
@@ -1159,6 +1159,7 @@
1159 # Create the interface definition will links for both IPv4 and IPv6.1159 # Create the interface definition will links for both IPv4 and IPv6.
1160 interface = {1160 interface = {
1161 "type": iface_type,1161 "type": iface_type,
1162 "index": ipaddr['index'],
1162 "links": [],1163 "links": [],
1163 "enabled": True if 'UP' in ipaddr['flags'] else False,1164 "enabled": True if 'UP' in ipaddr['flags'] else False,
1164 "parents": parents,1165 "parents": parents,
11651166
=== added file 'src/provisioningserver/utils/send_beacons.py'
--- src/provisioningserver/utils/send_beacons.py 1970-01-01 00:00:00 +0000
+++ src/provisioningserver/utils/send_beacons.py 2017-06-14 15:45:59 +0000
@@ -0,0 +1,123 @@
1# Copyright 2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Utilities for scanning attached networks."""
5import struct
6
7
8__all__ = [
9 "add_arguments",
10 "run"
11]
12
13import select
14import socket
15import sys
16from textwrap import dedent
17import time
18
19from provisioningserver.utils.beaconing import (
20 BEACON_PORT,
21 create_beacon_payload,
22 read_beacon_payload,
23)
24from provisioningserver.utils.network import get_all_interfaces_definition
25from pprint import pformat
26
27
28def add_arguments(parser):
29 """Add this command's options to the `ArgumentParser`.
30
31 Specified by the `ActionScript` interface.
32 """
33 parser.description = dedent("""\
34 Send solicitation beacons to a particular address.
35 """)
36 parser.add_argument(
37 '-v', '--verbose', action='store_true', required=False,
38 help='Verbose packet output.')
39 parser.add_argument(
40 '-s', '--source', type=str, required=False,
41 help='Source address to send beacons from.')
42 parser.add_argument(
43 'destination', type=str, nargs='?',
44 help="Destination to send beacon to. If not specified, will use the "
45 "MAAS multicast group (224.0.0.118).")
46
47
48def do_beaconing(args, source_ip, destination_ip, interfaces, stdout):
49 """Interprets the specified `args` and `to_scan` dict to perform the scan.
50
51 Uses the specified `stdout` and `stderr` for output.
52 """
53 clock = time.monotonic()
54 transport = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
55 if source_ip is not None:
56 if ':' not in source_ip:
57 source_ip = "::ffff:" + source_ip
58 transport.bind((source_ip, 0))
59 if destination_ip is None:
60 destination_ip = "::ffff:224.0.0.118"
61 elif ':' not in destination_ip:
62 destination_ip = "::ffff:" + destination_ip
63 beacon = create_beacon_payload("solicitation")
64 if "224.0.0.118" in destination_ip:
65 for ifdata in interfaces.values():
66 for link in ifdata["links"]:
67 address = link["address"]
68 # Strip off CIDRs.
69 address = address.split("/")[0]
70 if ':' not in address:
71 transport.setsockopt(
72 socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
73 transport.setsockopt(
74 socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
75 socket.inet_aton(address))
76 destination_ip = "::ffff:224.0.0.118"
77 transport.sendto(
78 beacon.packet_bytes, (destination_ip, BEACON_PORT))
79 else:
80 # We're sending to an IPv4 multicast address, so we can't
81 # actually use IPv6 addresses here.
82 transport.setsockopt(
83 socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
84 packed_ifindex = struct.pack('I', ifdata['index'])
85 transport.setsockopt(
86 socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF,
87 packed_ifindex)
88 destination_ip = "ff02::15a"
89 transport.sendto(
90 beacon.packet_bytes, (destination_ip, BEACON_PORT))
91 else:
92 transport.sendto(beacon.packet_bytes, (destination_ip, BEACON_PORT))
93 transport.setblocking(0)
94 clock_diff = 0
95 timeout = 5
96 while clock_diff < timeout:
97 ready = select.select([transport], [], [], timeout)
98 if ready[0]:
99 reply, reply_address = transport.recvfrom(16384)
100 if args.verbose:
101 print("%s: %s" % (
102 pformat(reply), pformat(reply_address)), file=stdout)
103 reply = read_beacon_payload(reply)
104 print("%s" % pformat(reply), file=stdout)
105 clock_diff = time.monotonic() - clock
106
107
108def run(args, stdout=sys.stdout):
109 """Scan local networks for on-link hosts.
110
111 :param args: Parsed output of the arguments added in `add_arguments()`.
112 :param stdout: Standard output stream to write to.
113 :param stderr: Standard error stream to write to.
114 """
115 # Record the current time so we can figure out how long it took us to
116 # do all this scanning.
117 source_ip = args.source
118 destination_ip = args.destination
119 interfaces = get_all_interfaces_definition(annotate_with_monitored=False)
120 if args.verbose:
121 print("%s" % pformat(interfaces), file=stdout)
122 do_beaconing(
123 args, source_ip, destination_ip, interfaces, stdout)
0124
=== modified file 'src/provisioningserver/utils/tests/test_network.py'
--- src/provisioningserver/utils/tests/test_network.py 2017-04-26 02:29:43 +0000
+++ src/provisioningserver/utils/tests/test_network.py 2017-06-14 15:45:59 +0000
@@ -5,6 +5,7 @@
55
6__all__ = []6__all__ = []
77
8import random
8import socket9import socket
9from socket import (10from socket import (
10 EAI_BADFLAGS,11 EAI_BADFLAGS,
@@ -1174,6 +1175,7 @@
1174 "flags": ["UP"],1175 "flags": ["UP"],
1175 "inet": ["127.0.0.1/32"],1176 "inet": ["127.0.0.1/32"],
1176 "inet6": ["::1"],1177 "inet6": ["::1"],
1178 "index": 1,
1177 },1179 },
1178 }1180 }
1179 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))1181 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))
@@ -1185,6 +1187,7 @@
1185 "mac": factory.make_mac_address(),1187 "mac": factory.make_mac_address(),
1186 "flags": ["UP"],1188 "flags": ["UP"],
1187 "inet": ["192.168.122.2/24"],1189 "inet": ["192.168.122.2/24"],
1190 "index": 2,
1188 },1191 },
1189 }1192 }
1190 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))1193 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))
@@ -1194,6 +1197,7 @@
1194 "vnet": {1197 "vnet": {
1195 "type": "ipip",1198 "type": "ipip",
1196 "flags": ["UP"],1199 "flags": ["UP"],
1200 "index": 2,
1197 },1201 },
1198 }1202 }
1199 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))1203 self.assertInterfacesResult(ip_addr, {}, {}, MatchesDict({}))
@@ -1202,6 +1206,7 @@
1202 ip_addr = {1206 ip_addr = {
1203 "eth0": {1207 "eth0": {
1204 "type": "ethernet.physical",1208 "type": "ethernet.physical",
1209 "index": 2,
1205 "mac": factory.make_mac_address(),1210 "mac": factory.make_mac_address(),
1206 "flags": ["UP"],1211 "flags": ["UP"],
1207 "inet": ["192.168.122.2/24"],1212 "inet": ["192.168.122.2/24"],
@@ -1210,6 +1215,7 @@
1210 expected_result = MatchesDict({1215 expected_result = MatchesDict({
1211 "eth0": MatchesDict({1216 "eth0": MatchesDict({
1212 "type": Equals("physical"),1217 "type": Equals("physical"),
1218 "index": Equals(ip_addr["eth0"]["index"]),
1213 "mac_address": Equals(ip_addr["eth0"]["mac"]),1219 "mac_address": Equals(ip_addr["eth0"]["mac"]),
1214 "enabled": Is(True),1220 "enabled": Is(True),
1215 "parents": Equals([]),1221 "parents": Equals([]),
@@ -1226,6 +1232,7 @@
1226 ip_addr = {1232 ip_addr = {
1227 "eth0": {1233 "eth0": {
1228 "type": "ethernet.physical",1234 "type": "ethernet.physical",
1235 "index": random.randint(2, 99),
1229 "mac": factory.make_mac_address(),1236 "mac": factory.make_mac_address(),
1230 "flags": ["UP"],1237 "flags": ["UP"],
1231 "inet": ["192.168.122.2/24"],1238 "inet": ["192.168.122.2/24"],
@@ -1239,6 +1246,7 @@
1239 expected_result = MatchesDict({1246 expected_result = MatchesDict({
1240 "eth0": MatchesDict({1247 "eth0": MatchesDict({
1241 "type": Equals("physical"),1248 "type": Equals("physical"),
1249 "index": Equals(ip_addr["eth0"]["index"]),
1242 "mac_address": Equals(ip_addr["eth0"]["mac"]),1250 "mac_address": Equals(ip_addr["eth0"]["mac"]),
1243 "enabled": Is(True),1251 "enabled": Is(True),
1244 "parents": Equals([]),1252 "parents": Equals([]),
@@ -1256,6 +1264,7 @@
1256 ip_addr = {1264 ip_addr = {
1257 "eth0": {1265 "eth0": {
1258 "type": "ethernet",1266 "type": "ethernet",
1267 "index": random.randint(2, 99),
1259 "mac": factory.make_mac_address(),1268 "mac": factory.make_mac_address(),
1260 "flags": ["UP"],1269 "flags": ["UP"],
1261 "inet": ["192.168.122.2/24"],1270 "inet": ["192.168.122.2/24"],
@@ -1264,6 +1273,7 @@
1264 expected_result = MatchesDict({1273 expected_result = MatchesDict({
1265 "eth0": MatchesDict({1274 "eth0": MatchesDict({
1266 "type": Equals("physical"),1275 "type": Equals("physical"),
1276 "index": Equals(ip_addr["eth0"]["index"]),
1267 "mac_address": Equals(ip_addr["eth0"]["mac"]),1277 "mac_address": Equals(ip_addr["eth0"]["mac"]),
1268 "enabled": Is(True),1278 "enabled": Is(True),
1269 "parents": Equals([]),1279 "parents": Equals([]),
@@ -1281,6 +1291,7 @@
1281 ip_addr = {1291 ip_addr = {
1282 "eth0": {1292 "eth0": {
1283 "type": "ethernet.physical",1293 "type": "ethernet.physical",
1294 "index": random.randint(2, 99),
1284 "mac": factory.make_mac_address(),1295 "mac": factory.make_mac_address(),
1285 "flags": ["UP"],1296 "flags": ["UP"],
1286 "inet": ["192.168.122.2/24", "192.168.122.200/32"],1297 "inet": ["192.168.122.2/24", "192.168.122.200/32"],
@@ -1292,6 +1303,7 @@
1292 expected_result = MatchesDict({1303 expected_result = MatchesDict({
1293 "eth0": MatchesDict({1304 "eth0": MatchesDict({
1294 "type": Equals("physical"),1305 "type": Equals("physical"),
1306 "index": Equals(ip_addr["eth0"]["index"]),
1295 "mac_address": Equals(ip_addr["eth0"]["mac"]),1307 "mac_address": Equals(ip_addr["eth0"]["mac"]),
1296 "enabled": Is(True),1308 "enabled": Is(True),
1297 "parents": Equals([]),1309 "parents": Equals([]),
@@ -1315,6 +1327,7 @@
1315 ip_addr = {1327 ip_addr = {
1316 "eth0": {1328 "eth0": {
1317 "type": "ethernet.physical",1329 "type": "ethernet.physical",
1330 "index": random.randint(2, 99),
1318 "mac": factory.make_mac_address(),1331 "mac": factory.make_mac_address(),
1319 "flags": ["UP"],1332 "flags": ["UP"],
1320 "inet": [1333 "inet": [
@@ -1331,6 +1344,7 @@
1331 expected_result = MatchesDict({1344 expected_result = MatchesDict({
1332 "eth0": MatchesDict({1345 "eth0": MatchesDict({
1333 "type": Equals("physical"),1346 "type": Equals("physical"),
1347 "index": Equals(ip_addr["eth0"]["index"]),
1334 "mac_address": Equals(ip_addr["eth0"]["mac"]),1348 "mac_address": Equals(ip_addr["eth0"]["mac"]),
1335 "enabled": Is(True),1349 "enabled": Is(True),
1336 "parents": Equals([]),1350 "parents": Equals([]),
@@ -1365,21 +1379,25 @@
1365 ip_addr = {1379 ip_addr = {
1366 "eth0": {1380 "eth0": {
1367 "type": "ethernet.physical",1381 "type": "ethernet.physical",
1382 "index": 2,
1368 "mac": factory.make_mac_address(),1383 "mac": factory.make_mac_address(),
1369 "flags": [],1384 "flags": [],
1370 },1385 },
1371 "eth1": {1386 "eth1": {
1372 "type": "ethernet.physical",1387 "type": "ethernet.physical",
1388 "index": 3,
1373 "mac": factory.make_mac_address(),1389 "mac": factory.make_mac_address(),
1374 "flags": ["UP"],1390 "flags": ["UP"],
1375 },1391 },
1376 "eth2": {1392 "eth2": {
1377 "type": "ethernet.physical",1393 "type": "ethernet.physical",
1394 "index": 4,
1378 "mac": factory.make_mac_address(),1395 "mac": factory.make_mac_address(),
1379 "flags": ["UP"],1396 "flags": ["UP"],
1380 },1397 },
1381 "bond0": {1398 "bond0": {
1382 "type": "ethernet.bond",1399 "type": "ethernet.bond",
1400 "index": 5,
1383 "mac": factory.make_mac_address(),1401 "mac": factory.make_mac_address(),
1384 "flags": ["UP"],1402 "flags": ["UP"],
1385 "bonded_interfaces": ["eth1", "eth2"],1403 "bonded_interfaces": ["eth1", "eth2"],
@@ -1388,6 +1406,7 @@
1388 },1406 },
1389 "bond0.10": {1407 "bond0.10": {
1390 "type": "ethernet.vlan",1408 "type": "ethernet.vlan",
1409 "index": 6,
1391 "flags": ["UP"],1410 "flags": ["UP"],
1392 "vid": 10,1411 "vid": 10,
1393 "inet": ["192.168.123.2/24", "192.168.123.3/32"],1412 "inet": ["192.168.123.2/24", "192.168.123.3/32"],
@@ -1395,6 +1414,7 @@
1395 },1414 },
1396 "vlan20": {1415 "vlan20": {
1397 "type": "ethernet.vlan",1416 "type": "ethernet.vlan",
1417 "index": 7,
1398 "mac": factory.make_mac_address(),1418 "mac": factory.make_mac_address(),
1399 "flags": ["UP"],1419 "flags": ["UP"],
1400 "vid": 20,1420 "vid": 20,
@@ -1402,11 +1422,13 @@
1402 },1422 },
1403 "wlan0": {1423 "wlan0": {
1404 "type": "ethernet.wireless",1424 "type": "ethernet.wireless",
1425 "index": 8,
1405 "mac": factory.make_mac_address(),1426 "mac": factory.make_mac_address(),
1406 "flags": ["UP"],1427 "flags": ["UP"],
1407 },1428 },
1408 "br0": {1429 "br0": {
1409 "type": "ethernet.bridge",1430 "type": "ethernet.bridge",
1431 "index": 9,
1410 "bridged_interfaces": ["eth0"],1432 "bridged_interfaces": ["eth0"],
1411 "mac": factory.make_mac_address(),1433 "mac": factory.make_mac_address(),
1412 "flags": ["UP"],1434 "flags": ["UP"],
@@ -1424,6 +1446,7 @@
1424 expected_result = MatchesDict({1446 expected_result = MatchesDict({
1425 "eth0": MatchesDict({1447 "eth0": MatchesDict({
1426 "type": Equals("physical"),1448 "type": Equals("physical"),
1449 "index": Equals(2),
1427 "mac_address": Equals(ip_addr["eth0"]["mac"]),1450 "mac_address": Equals(ip_addr["eth0"]["mac"]),
1428 "enabled": Is(False),1451 "enabled": Is(False),
1429 "parents": Equals([]),1452 "parents": Equals([]),
@@ -1432,6 +1455,7 @@
1432 }),1455 }),
1433 "eth1": MatchesDict({1456 "eth1": MatchesDict({
1434 "type": Equals("physical"),1457 "type": Equals("physical"),
1458 "index": Equals(3),
1435 "mac_address": Equals(ip_addr["eth1"]["mac"]),1459 "mac_address": Equals(ip_addr["eth1"]["mac"]),
1436 "enabled": Is(True),1460 "enabled": Is(True),
1437 "parents": Equals([]),1461 "parents": Equals([]),
@@ -1440,6 +1464,7 @@
1440 }),1464 }),
1441 "eth2": MatchesDict({1465 "eth2": MatchesDict({
1442 "type": Equals("physical"),1466 "type": Equals("physical"),
1467 "index": Equals(4),
1443 "mac_address": Equals(ip_addr["eth2"]["mac"]),1468 "mac_address": Equals(ip_addr["eth2"]["mac"]),
1444 "enabled": Is(True),1469 "enabled": Is(True),
1445 "parents": Equals([]),1470 "parents": Equals([]),
@@ -1448,6 +1473,7 @@
1448 }),1473 }),
1449 "bond0": MatchesDict({1474 "bond0": MatchesDict({
1450 "type": Equals("bond"),1475 "type": Equals("bond"),
1476 "index": Equals(5),
1451 "mac_address": Equals(ip_addr["bond0"]["mac"]),1477 "mac_address": Equals(ip_addr["bond0"]["mac"]),
1452 "enabled": Is(True),1478 "enabled": Is(True),
1453 "parents": Equals(["eth1", "eth2"]),1479 "parents": Equals(["eth1", "eth2"]),
@@ -1471,6 +1497,7 @@
1471 }),1497 }),
1472 "bond0.10": MatchesDict({1498 "bond0.10": MatchesDict({
1473 "type": Equals("vlan"),1499 "type": Equals("vlan"),
1500 "index": Equals(6),
1474 "enabled": Is(True),1501 "enabled": Is(True),
1475 "parents": Equals(["bond0"]),1502 "parents": Equals(["bond0"]),
1476 "vid": Equals(10),1503 "vid": Equals(10),
@@ -1486,24 +1513,27 @@
1486 ),1513 ),
1487 "source": Equals("ipaddr"),1514 "source": Equals("ipaddr"),
1488 }),1515 }),
1516 "vlan20": MatchesDict({
1517 "type": Equals("vlan"),
1518 "index": Equals(7),
1519 "enabled": Is(True),
1520 "parents": Equals(["eth0"]),
1521 "links": Equals([]),
1522 "source": Equals("ipaddr"),
1523 "vid": Equals(20),
1524 }),
1489 "wlan0": MatchesDict({1525 "wlan0": MatchesDict({
1490 "type": Equals("physical"),1526 "type": Equals("physical"),
1527 "index": Equals(8),
1491 "mac_address": Equals(ip_addr["wlan0"]["mac"]),1528 "mac_address": Equals(ip_addr["wlan0"]["mac"]),
1492 "enabled": Is(True),1529 "enabled": Is(True),
1493 "parents": Equals([]),1530 "parents": Equals([]),
1494 "links": Equals([]),1531 "links": Equals([]),
1495 "source": Equals("ipaddr"),1532 "source": Equals("ipaddr"),
1496 }),1533 }),
1497 "vlan20": MatchesDict({
1498 "type": Equals("vlan"),
1499 "enabled": Is(True),
1500 "parents": Equals(["eth0"]),
1501 "links": Equals([]),
1502 "source": Equals("ipaddr"),
1503 "vid": Equals(20),
1504 }),
1505 "br0": MatchesDict({1534 "br0": MatchesDict({
1506 "type": Equals("bridge"),1535 "type": Equals("bridge"),
1536 "index": Equals(9),
1507 "mac_address": Equals(ip_addr["br0"]["mac"]),1537 "mac_address": Equals(ip_addr["br0"]["mac"]),
1508 "enabled": Is(True),1538 "enabled": Is(True),
1509 "parents": Equals(["eth0"]),1539 "parents": Equals(["eth0"]),