Merge lp:~mpontillo/maas/beaconing--replies-from-maas into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Rejected
Rejected by: MAAS Lander
Proposed branch: lp:~mpontillo/maas/beaconing--replies-from-maas
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~mpontillo/maas/maas-rack--send-beacons
Diff against target: 247 lines (+139/-5)
2 files modified
src/provisioningserver/utils/beaconing.py (+1/-1)
src/provisioningserver/utils/services.py (+138/-4)
To merge this branch: bzr merge lp:~mpontillo/maas/beaconing--replies-from-maas
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+325607@code.launchpad.net

Commit message

Add network monitoring service which replies to beacon packets.

To post a comment you must log in.
6091. By Mike Pontillo

Merge branch: lp:~mpontillo/maas/maas-rack--send-beacons

6092. By Mike Pontillo

Fix comment.

6093. By Mike Pontillo

Merge trunk.

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

6093. By Mike Pontillo

Merge trunk.

6092. By Mike Pontillo

Fix comment.

6091. By Mike Pontillo

Merge branch: lp:~mpontillo/maas/maas-rack--send-beacons

6090. By Mike Pontillo

Add beacon services.

6089. By Mike Pontillo

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/utils/beaconing.py'
--- src/provisioningserver/utils/beaconing.py 2017-06-19 22:31:03 +0000
+++ src/provisioningserver/utils/beaconing.py 2017-06-19 22:31:03 +0000
@@ -156,7 +156,7 @@
156156
157 def __init__(self, pkt_bytes: bytes):157 def __init__(self, pkt_bytes: bytes):
158 """158 """
159 Create a beaconing packet, given the specified upper-layer packet.159 Create a beaconing packet, given the specified payload bytes.
160160
161 :param pkt_bytes: The input bytes of the beaconing packet.161 :param pkt_bytes: The input bytes of the beaconing packet.
162 :type pkt_bytes: bytes162 :type pkt_bytes: bytes
163163
=== modified file 'src/provisioningserver/utils/services.py'
--- src/provisioningserver/utils/services.py 2017-04-04 07:56:40 +0000
+++ src/provisioningserver/utils/services.py 2017-06-19 22:31:03 +0000
@@ -23,6 +23,7 @@
23 LegacyLogger,23 LegacyLogger,
24)24)
25from provisioningserver.rpc.exceptions import NoConnectionsAvailable25from provisioningserver.rpc.exceptions import NoConnectionsAvailable
26from provisioningserver.utils.beaconing import create_beacon_payload
26from provisioningserver.utils.fs import (27from provisioningserver.utils.fs import (
27 get_maas_provision_command,28 get_maas_provision_command,
28 NamedLock,29 NamedLock,
@@ -47,7 +48,10 @@
47 ProcessDone,48 ProcessDone,
48 ProcessTerminated,49 ProcessTerminated,
49)50)
50from twisted.internet.protocol import ProcessProtocol51from twisted.internet.protocol import (
52 DatagramProtocol,
53 ProcessProtocol,
54)
51from twisted.internet.threads import deferToThread55from twisted.internet.threads import deferToThread
5256
5357
@@ -129,9 +133,6 @@
129 The difference between `JSONPerLineProtocol` and `ProtocolForObserveARP`133 The difference between `JSONPerLineProtocol` and `ProtocolForObserveARP`
130 is that the neighbour observation protocol needs to insert the interface134 is that the neighbour observation protocol needs to insert the interface
131 metadata into the resultant object before the callback.135 metadata into the resultant object before the callback.
132
133 This also ensures that the spawned process is configured as a process
134 group leader for its own process group.
135 """136 """
136137
137 def __init__(self, interface, *args, **kwargs):138 def __init__(self, interface, *args, **kwargs):
@@ -147,6 +148,27 @@
147 log.msg("observe-arp[%s]:" % self.interface, line)148 log.msg("observe-arp[%s]:" % self.interface, line)
148149
149150
151class ProtocolForObserveBeacons(JSONPerLineProtocol):
152 """Protocol used when spawning `maas-rack observe-beacons`.
153
154 The difference between `JSONPerLineProtocol` and `ProtocolForObserveARP`
155 is that the neighbour observation protocol needs to insert the interface
156 metadata into the resultant object before the callback.
157 """
158
159 def __init__(self, interface, *args, **kwargs):
160 super().__init__(*args, **kwargs)
161 self.interface = interface
162
163 def objectReceived(self, obj):
164 obj['interface'] = self.interface
165 super().objectReceived(obj)
166
167 def errLineReceived(self, line):
168 line = line.decode("utf-8").rstrip()
169 log.msg("observe-beacons[%s]:" % self.interface, line)
170
171
150class ProtocolForObserveMDNS(JSONPerLineProtocol):172class ProtocolForObserveMDNS(JSONPerLineProtocol):
151 """Protocol used when spawning `maas-rack observe-mdns`.173 """Protocol used when spawning `maas-rack observe-mdns`.
152174
@@ -257,6 +279,30 @@
257 self.ifname, callback=self.callback)279 self.ifname, callback=self.callback)
258280
259281
282class BeaconingService(ProcessProtocolService):
283 """Service to spawn the per-interface device discovery subprocess."""
284
285 def __init__(self, ifname: str, callback: callable):
286 self.ifname = ifname
287 self.callback = callback
288 super().__init__()
289
290 def getDescription(self) -> str:
291 return "Beaconing process for %s" % self.ifname
292
293 def getProcessParameters(self):
294 maas_rack_cmd = get_maas_provision_command().encode("utf-8")
295 return [
296 maas_rack_cmd,
297 b"observe-beacons",
298 self.ifname.encode("utf-8")
299 ]
300
301 def createProcessProtocol(self):
302 return ProtocolForObserveBeacons(
303 self.ifname, callback=self.callback)
304
305
260class MDNSResolverService(ProcessProtocolService):306class MDNSResolverService(ProcessProtocolService):
261 """Service to spawn the per-interface device discovery subprocess."""307 """Service to spawn the per-interface device discovery subprocess."""
262308
@@ -278,6 +324,17 @@
278 return ProtocolForObserveMDNS(callback=self.callback)324 return ProtocolForObserveMDNS(callback=self.callback)
279325
280326
327class BeaconingSocketProtocol(DatagramProtocol):
328 """Protocol to handle beaconing packets received from the socket layer."""
329
330 def datagramReceived(self, datagram, addr):
331 # Note: Packets aren't processed in this path; we need this in order
332 # to tell the socket layer we're listening to this port. Otherwise, the
333 # stack will send ICMP destination (port) unreachable replies to anyone
334 # trying to send us beacons.
335 pass
336
337
281class NetworksMonitoringLock(NamedLock):338class NetworksMonitoringLock(NamedLock):
282 """Host scoped lock to ensure only one network monitoring service runs."""339 """Host scoped lock to ensure only one network monitoring service runs."""
283340
@@ -324,6 +381,13 @@
324 self.interface_monitor.setName("updateInterfaces")381 self.interface_monitor.setName("updateInterfaces")
325 self.interface_monitor.clock = self.clock382 self.interface_monitor.clock = self.clock
326 self.interface_monitor.setServiceParent(self)383 self.interface_monitor.setServiceParent(self)
384 self.beaconing_socket_protocol = BeaconingSocketProtocol()
385 # Note: This returns a Twisted IListeningPort.
386 self.beaconing_listen_port = reactor.listenMulticast(
387 5240, self.beaconing_socket_protocol, interface='::',
388 listenMultiple=True)
389 self.beacon_transport = self.beaconing_socket_protocol.transport
390 self.beacon_transport.joinGroup("224.0.0.118")
327391
328 def updateInterfaces(self):392 def updateInterfaces(self):
329 """Update interfaces, catching and logging errors.393 """Update interfaces, catching and logging errors.
@@ -388,6 +452,38 @@
388 This MUST be overridden in subclasses.452 This MUST be overridden in subclasses.
389 """453 """
390454
455 def processBeacon(self, beacon, reply_address):
456 payload = beacon['payload']
457 beacon_type = payload['type']
458 if beacon_type == "solicitation":
459 receive_interface_info = {
460 "name": beacon['interface'],
461 "source_ip": beacon['source_ip'],
462 "destination_ip": beacon['destination_ip'],
463 "source_mac": beacon['source_mac'],
464 "destination_mac": beacon['destination_mac'],
465 }
466 if 'vid' in beacon:
467 receive_interface_info['vid'] = beacon['vid']
468 data = {
469 "interface": receive_interface_info
470 }
471 reply_bytes, _ = create_beacon_payload("advertisement", data)
472 self.beacon_transport.write(reply_bytes, reply_address)
473
474 def reportBeacons(self, beacons):
475 """Receives a report of an observed beacon packet."""
476 for beacon in beacons:
477 log.msg("Received beacon: %r" % beacon)
478 reply_ip = beacon['source_ip']
479 reply_port = beacon['source_port']
480 if ':' not in reply_ip:
481 # Since we opened an IPv6-compatible socket, need IPv6 syntax
482 # here to send to IPv4 addresses.
483 reply_ip = '::ffff:' + reply_ip
484 reply_address = (reply_ip, reply_port)
485 self.processBeacon(beacon, reply_address)
486
391 def stopService(self):487 def stopService(self):
392 """Stop the service.488 """Stop the service.
393489
@@ -395,6 +491,7 @@
395 """491 """
396 d = super().stopService()492 d = super().stopService()
397 d.addBoth(callOut, self._releaseSoleResponsibility)493 d.addBoth(callOut, self._releaseSoleResponsibility)
494 self.beaconing_listen_port.stopListening()
398 return d495 return d
399496
400 def _assumeSoleResponsibility(self):497 def _assumeSoleResponsibility(self):
@@ -473,6 +570,13 @@
473 service.setName("neighbour_discovery:" + ifname)570 service.setName("neighbour_discovery:" + ifname)
474 service.setServiceParent(self)571 service.setServiceParent(self)
475572
573 def _startBeaconing(self, ifname):
574 """"Start neighbour discovery service on the specified interface."""
575 service = BeaconingService(ifname, self.reportBeacons)
576 service.clock = self.clock
577 service.setName("beaconing:" + ifname)
578 service.setServiceParent(self)
579
476 def _startMDNSDiscoveryService(self):580 def _startMDNSDiscoveryService(self):
477 """Start resolving mDNS entries on attached networks."""581 """Start resolving mDNS entries on attached networks."""
478 try:582 try:
@@ -520,6 +624,30 @@
520 maaslog.info(624 maaslog.info(
521 "Stopped neighbour observation service for %s." % ifname)625 "Stopped neighbour observation service for %s." % ifname)
522626
627 def _startBeaconingServices(self, new_interfaces):
628 """Start monitoring services for the specified set of interfaces."""
629 for ifname in new_interfaces:
630 # Sanity check to ensure the service isn't already started.
631 try:
632 self.getServiceNamed("beaconing:" + ifname)
633 except KeyError:
634 # This is an expected exception. (The call inside the `try`
635 # is only necessary to ensure the service doesn't exist.)
636 self._startBeaconing(ifname)
637
638 def _stopBeaconingServices(self, deleted_interfaces):
639 """Stop monitoring services for the specified set of interfaces."""
640 for ifname in deleted_interfaces:
641 try:
642 service = self.getServiceNamed("beaconing:" + ifname)
643 except KeyError:
644 # Service doesn't exist, so no need to stop it.
645 pass
646 else:
647 service.disownServiceParent()
648 maaslog.info(
649 "Stopped beaconing service for %s." % ifname)
650
523 def _shouldMonitorMDNS(self, monitoring_state):651 def _shouldMonitorMDNS(self, monitoring_state):
524 # If any interface is configured for mDNS, we must start the monitoring652 # If any interface is configured for mDNS, we must start the monitoring
525 # process. (You cannot select interfaces when using `avahi-browse`.)653 # process. (You cannot select interfaces when using `avahi-browse`.)
@@ -591,11 +719,17 @@
591 log.msg("Starting neighbour discovery for interfaces: %r" % (719 log.msg("Starting neighbour discovery for interfaces: %r" % (
592 new_interfaces))720 new_interfaces))
593 self._startNeighbourDiscoveryServices(new_interfaces)721 self._startNeighbourDiscoveryServices(new_interfaces)
722 # XXX mpontillo 2017-07-12: for testing, just start beaconing
723 # services on all the interfaces enabled for active discovery.
724 self._startBeaconingServices(new_interfaces)
594 if len(deleted_interfaces) > 0:725 if len(deleted_interfaces) > 0:
595 log.msg(726 log.msg(
596 "Stopping neighbour discovery for interfaces: %r" % (727 "Stopping neighbour discovery for interfaces: %r" % (
597 deleted_interfaces))728 deleted_interfaces))
598 self._stopNeighbourDiscoveryServices(deleted_interfaces)729 self._stopNeighbourDiscoveryServices(deleted_interfaces)
730 # XXX mpontillo 2017-07-12: this should be separately configured.
731 # (see similar comment in the 'start' path above.)
732 self._stopBeaconingServices(deleted_interfaces)
599 self._monitored = monitored_interfaces733 self._monitored = monitored_interfaces
600734
601 def _interfacesRecorded(self, interfaces):735 def _interfacesRecorded(self, interfaces):