Jonathan Hart

Add neighbor lifecycle management.

 * Made PIMNeighbor mostly immutable (apart from updatable timestamp)
 * Equals and hashCode for PIMNeighbor
 * Remove neighbor when we see a HELLO with holdTime==0
 * Periodic task to time out neighbors who haven't sent a HELLO in a while
 * Added a CLI command to view PIM neighbors

Change-Id: I59e52a847f7abcb8e9ac660c2cccace53e46867b
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.pim.cli;
18 +
19 +import org.apache.karaf.shell.commands.Command;
20 +import org.onlab.util.Tools;
21 +import org.onosproject.cli.AbstractShellCommand;
22 +import org.onosproject.pim.impl.PIMInterface;
23 +import org.onosproject.pim.impl.PIMInterfaceService;
24 +import org.onosproject.pim.impl.PIMNeighbor;
25 +
26 +import java.util.Set;
27 +
28 +/**
29 + * Lists PIM neighbors.
30 + */
31 +@Command(scope = "onos", name = "pim-neighbors",
32 + description = "Lists the PIM neighbors")
33 +public class PimNeighborsListCommand extends AbstractShellCommand {
34 +
35 + private static final String INTF_FORMAT = "interface=%s, address=%s";
36 + private static final String NEIGHBOR_FORMAT = " neighbor=%s, uptime=%s, holdtime=%s, drPriority=%s, genId=%s";
37 +
38 + @Override
39 + protected void execute() {
40 + PIMInterfaceService interfaceService = get(PIMInterfaceService.class);
41 +
42 + Set<PIMInterface> interfaces = interfaceService.getPimInterfaces();
43 +
44 + for (PIMInterface intf : interfaces) {
45 + print(INTF_FORMAT, intf.getInterface().name(), intf.getIpAddress());
46 + for (PIMNeighbor neighbor : intf.getNeighbors()) {
47 + // Filter out the PIM neighbor representing 'us'
48 + if (!neighbor.ipAddress().equals(intf.getIpAddress())) {
49 + print(NEIGHBOR_FORMAT, neighbor.ipAddress(),
50 + Tools.timeAgo(neighbor.upTime()), neighbor.holdtime(),
51 + neighbor.priority(), neighbor.generationId());
52 + }
53 + }
54 + }
55 + }
56 +
57 +}
...@@ -32,10 +32,12 @@ import org.onosproject.net.packet.PacketService; ...@@ -32,10 +32,12 @@ import org.onosproject.net.packet.PacketService;
32 import org.slf4j.Logger; 32 import org.slf4j.Logger;
33 33
34 import java.nio.ByteBuffer; 34 import java.nio.ByteBuffer;
35 +import java.util.Collection;
35 import java.util.HashMap; 36 import java.util.HashMap;
36 import java.util.Map; 37 import java.util.Map;
37 import java.util.Random; 38 import java.util.Random;
38 import java.util.Set; 39 import java.util.Set;
40 +import java.util.stream.Collectors;
39 41
40 import static com.google.common.base.Preconditions.checkArgument; 42 import static com.google.common.base.Preconditions.checkArgument;
41 import static com.google.common.base.Preconditions.checkNotNull; 43 import static com.google.common.base.Preconditions.checkNotNull;
...@@ -99,10 +101,7 @@ public final class PIMInterface { ...@@ -99,10 +101,7 @@ public final class PIMInterface {
99 generationId = new Random().nextInt(); 101 generationId = new Random().nextInt();
100 102
101 // Create a PIM Neighbor to represent ourselves for DR election. 103 // Create a PIM Neighbor to represent ourselves for DR election.
102 - PIMNeighbor us = new PIMNeighbor(ourIp, mac); 104 + PIMNeighbor us = new PIMNeighbor(ourIp, mac, holdTime, 0, priority, generationId);
103 -
104 - // Priority and IP address are all we need to DR election.
105 - us.setPriority(priority);
106 105
107 pimNeighbors.put(ourIp, us); 106 pimNeighbors.put(ourIp, us);
108 drIpaddress = ourIp; 107 drIpaddress = ourIp;
...@@ -199,6 +198,32 @@ public final class PIMInterface { ...@@ -199,6 +198,32 @@ public final class PIMInterface {
199 } 198 }
200 199
201 /** 200 /**
201 + * Gets the neighbors seen on this interface.
202 + *
203 + * @return PIM neighbors
204 + */
205 + public Collection<PIMNeighbor> getNeighbors() {
206 + return pimNeighbors.values();
207 + }
208 +
209 + /**
210 + * Checks whether any of our neighbors have expired, and cleans up their
211 + * state if they have.
212 + */
213 + public void checkNeighborTimeouts() {
214 + Set<PIMNeighbor> expired = pimNeighbors.values().stream()
215 + // Don't time ourselves out!
216 + .filter(neighbor -> !neighbor.ipAddress().equals(getIpAddress()))
217 + .filter(neighbor -> neighbor.isExpired())
218 + .collect(Collectors.toSet());
219 +
220 + for (PIMNeighbor neighbor : expired) {
221 + log.info("Timing out neighbor {}", neighbor);
222 + pimNeighbors.remove(neighbor.ipAddress(), neighbor);
223 + }
224 + }
225 +
226 + /**
202 * Multicast a hello message out our interface. This hello message is sent 227 * Multicast a hello message out our interface. This hello message is sent
203 * periodically during the normal PIM Neighbor refresh time, as well as a 228 * periodically during the normal PIM Neighbor refresh time, as well as a
204 * result of a newly created interface. 229 * result of a newly created interface.
...@@ -234,7 +259,7 @@ public final class PIMInterface { ...@@ -234,7 +259,7 @@ public final class PIMInterface {
234 * <li>We <em>may</em> have to create a new neighbor if one does not already exist</li> 259 * <li>We <em>may</em> have to create a new neighbor if one does not already exist</li>
235 * <li>We <em>may</em> need to re-elect a new DR if new information is received</li> 260 * <li>We <em>may</em> need to re-elect a new DR if new information is received</li>
236 * <li>We <em>may</em> need to send an existing neighbor all joins if the genid changed</li> 261 * <li>We <em>may</em> need to send an existing neighbor all joins if the genid changed</li>
237 - * <li>We will refresh the neighbors timestamp</li> 262 + * <li>We will refresh the neighbor's timestamp</li>
238 * </ul> 263 * </ul>
239 * 264 *
240 * @param ethPkt the Ethernet packet header 265 * @param ethPkt the Ethernet packet header
...@@ -259,7 +284,7 @@ public final class PIMInterface { ...@@ -259,7 +284,7 @@ public final class PIMInterface {
259 checkNotNull(dr); 284 checkNotNull(dr);
260 285
261 IpAddress drip = drIpaddress; 286 IpAddress drip = drIpaddress;
262 - int drpri = dr.getPriority(); 287 + int drpri = dr.priority();
263 288
264 // Assume we do not need to run a DR election 289 // Assume we do not need to run a DR election
265 boolean reElectDr = false; 290 boolean reElectDr = false;
...@@ -269,18 +294,24 @@ public final class PIMInterface { ...@@ -269,18 +294,24 @@ public final class PIMInterface {
269 294
270 // Determine if we already have a PIMNeighbor 295 // Determine if we already have a PIMNeighbor
271 PIMNeighbor nbr = pimNeighbors.getOrDefault(srcip, null); 296 PIMNeighbor nbr = pimNeighbors.getOrDefault(srcip, null);
297 + PIMNeighbor newNbr = PIMNeighbor.createPimNeighbor(srcip, nbrmac, hello.getOptions().values());
298 +
272 if (nbr == null) { 299 if (nbr == null) {
273 - nbr = new PIMNeighbor(srcip, hello.getOptions()); 300 + pimNeighbors.putIfAbsent(srcip, newNbr);
274 - checkNotNull(nbr); 301 + nbr = newNbr;
275 - } else { 302 + } else if (!nbr.equals(newNbr)) {
276 - Integer previousGenid = nbr.getGenid(); 303 + if (newNbr.holdtime() == 0) {
277 - nbr.addOptions(hello.getOptions()); 304 + // Neighbor has shut down. Remove them and clean up
278 - if (previousGenid != nbr.getGenid()) { 305 + pimNeighbors.remove(srcip, nbr);
279 - genidChanged = true; 306 + return;
307 + } else {
308 + // Neighbor has changed one of their options.
309 + pimNeighbors.put(srcip, newNbr);
310 + nbr = newNbr;
280 } 311 }
281 } 312 }
282 313
283 - // Refresh this neighbors timestamp 314 + // Refresh this neighbor's timestamp
284 nbr.refreshTimestamp(); 315 nbr.refreshTimestamp();
285 316
286 /* 317 /*
...@@ -300,8 +331,8 @@ public final class PIMInterface { ...@@ -300,8 +331,8 @@ public final class PIMInterface {
300 // Run an election if we need to. Return the elected IP address. 331 // Run an election if we need to. Return the elected IP address.
301 private IpAddress election(PIMNeighbor nbr, IpAddress drIp, int drPriority) { 332 private IpAddress election(PIMNeighbor nbr, IpAddress drIp, int drPriority) {
302 333
303 - IpAddress nbrIp = nbr.getIpaddr(); 334 + IpAddress nbrIp = nbr.ipAddress();
304 - if (nbr.getPriority() > drPriority) { 335 + if (nbr.priority() > drPriority) {
305 return nbrIp; 336 return nbrIp;
306 } 337 }
307 338
......
...@@ -23,6 +23,7 @@ import org.apache.felix.scr.annotations.Deactivate; ...@@ -23,6 +23,7 @@ import org.apache.felix.scr.annotations.Deactivate;
23 import org.apache.felix.scr.annotations.Reference; 23 import org.apache.felix.scr.annotations.Reference;
24 import org.apache.felix.scr.annotations.ReferenceCardinality; 24 import org.apache.felix.scr.annotations.ReferenceCardinality;
25 import org.apache.felix.scr.annotations.Service; 25 import org.apache.felix.scr.annotations.Service;
26 +import org.onlab.util.SafeRecurringTask;
26 import org.onosproject.incubator.net.intf.Interface; 27 import org.onosproject.incubator.net.intf.Interface;
27 import org.onosproject.incubator.net.intf.InterfaceEvent; 28 import org.onosproject.incubator.net.intf.InterfaceEvent;
28 import org.onosproject.incubator.net.intf.InterfaceListener; 29 import org.onosproject.incubator.net.intf.InterfaceListener;
...@@ -58,8 +59,10 @@ public class PIMInterfaceManager implements PIMInterfaceService { ...@@ -58,8 +59,10 @@ public class PIMInterfaceManager implements PIMInterfaceService {
58 private static final Class<PimInterfaceConfig> PIM_INTERFACE_CONFIG_CLASS = PimInterfaceConfig.class; 59 private static final Class<PimInterfaceConfig> PIM_INTERFACE_CONFIG_CLASS = PimInterfaceConfig.class;
59 private static final String PIM_INTERFACE_CONFIG_KEY = "pimInterface"; 60 private static final String PIM_INTERFACE_CONFIG_KEY = "pimInterface";
60 61
61 - // Create a Scheduled Executor service to send PIM hellos 62 + private static final int DEFAULT_TIMEOUT_TASK_PERIOD_MS = 250;
62 - private final ScheduledExecutorService helloScheduler = 63 +
64 + // Create a Scheduled Executor service for recurring tasks
65 + private final ScheduledExecutorService scheduledExecutorService =
63 Executors.newScheduledThreadPool(1); 66 Executors.newScheduledThreadPool(1);
64 67
65 // Wait for a bout 3 seconds before sending the initial hello messages. 68 // Wait for a bout 3 seconds before sending the initial hello messages.
...@@ -69,6 +72,8 @@ public class PIMInterfaceManager implements PIMInterfaceService { ...@@ -69,6 +72,8 @@ public class PIMInterfaceManager implements PIMInterfaceService {
69 // Send PIM hello packets: 30 seconds. 72 // Send PIM hello packets: 30 seconds.
70 private final long pimHelloPeriod = 30; 73 private final long pimHelloPeriod = 30;
71 74
75 + private final int timeoutTaskPeriod = DEFAULT_TIMEOUT_TASK_PERIOD_MS;
76 +
72 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
73 protected PacketService packetService; 78 protected PacketService packetService;
74 79
...@@ -113,18 +118,16 @@ public class PIMInterfaceManager implements PIMInterfaceService { ...@@ -113,18 +118,16 @@ public class PIMInterfaceManager implements PIMInterfaceService {
113 interfaceService.addListener(interfaceListener); 118 interfaceService.addListener(interfaceListener);
114 119
115 // Schedule the periodic hello sender. 120 // Schedule the periodic hello sender.
116 - helloScheduler.scheduleAtFixedRate(new Runnable() { 121 + scheduledExecutorService.scheduleAtFixedRate(
117 - @Override 122 + SafeRecurringTask.wrap(
118 - public void run() { 123 + () -> pimInterfaces.values().forEach(PIMInterface::sendHello)),
119 - try { 124 + initialHelloDelay, pimHelloPeriod, TimeUnit.SECONDS);
120 - for (PIMInterface pif : pimInterfaces.values()) { 125 +
121 - pif.sendHello(); 126 + // Schedule task to periodically time out expired neighbors
122 - } 127 + scheduledExecutorService.scheduleAtFixedRate(
123 - } catch (Exception e) { 128 + SafeRecurringTask.wrap(
124 - log.warn("exception", e); 129 + () -> pimInterfaces.values().forEach(PIMInterface::checkNeighborTimeouts)),
125 - } 130 + 0, timeoutTaskPeriod, TimeUnit.MILLISECONDS);
126 - }
127 - }, initialHelloDelay, pimHelloPeriod, TimeUnit.SECONDS);
128 131
129 log.info("Started"); 132 log.info("Started");
130 } 133 }
...@@ -136,7 +139,7 @@ public class PIMInterfaceManager implements PIMInterfaceService { ...@@ -136,7 +139,7 @@ public class PIMInterfaceManager implements PIMInterfaceService {
136 networkConfig.unregisterConfigFactory(pimConfigFactory); 139 networkConfig.unregisterConfigFactory(pimConfigFactory);
137 140
138 // Shutdown the periodic hello task. 141 // Shutdown the periodic hello task.
139 - helloScheduler.shutdown(); 142 + scheduledExecutorService.shutdown();
140 143
141 log.info("Stopped"); 144 log.info("Stopped");
142 } 145 }
......
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
19 <command> 19 <command>
20 <action class="org.onosproject.pim.cli.PimInterfacesListCommand"/> 20 <action class="org.onosproject.pim.cli.PimInterfacesListCommand"/>
21 </command> 21 </command>
22 + <command>
23 + <action class="org.onosproject.pim.cli.PimNeighborsListCommand"/>
24 + </command>
22 </command-bundle> 25 </command-bundle>
23 26
24 </blueprint> 27 </blueprint>
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onlab.util;
18 +
19 +import org.slf4j.Logger;
20 +import org.slf4j.LoggerFactory;
21 +
22 +/**
23 + * Wrapper for a recurring task which catches all exceptions to prevent task
24 + * being suppressed in a ScheduledExecutorService.
25 + */
26 +public final class SafeRecurringTask implements Runnable {
27 +
28 + private static final Logger log = LoggerFactory.getLogger(SafeRecurringTask.class);
29 +
30 + private final Runnable runnable;
31 +
32 + /**
33 + * Constructor.
34 + *
35 + * @param runnable runnable to wrap
36 + */
37 + private SafeRecurringTask(Runnable runnable) {
38 + this.runnable = runnable;
39 + }
40 +
41 + @Override
42 + public void run() {
43 + if (Thread.currentThread().isInterrupted()) {
44 + log.info("Task interrupted, quitting");
45 + return;
46 + }
47 +
48 + try {
49 + runnable.run();
50 + } catch (Exception e) {
51 + // Catch all exceptions to avoid task being suppressed
52 + log.error("Exception thrown during task", e);
53 + }
54 + }
55 +
56 + /**
57 + * Wraps a runnable in a safe recurring task.
58 + *
59 + * @param runnable runnable to wrap
60 + * @return safe recurring task
61 + */
62 + public static SafeRecurringTask wrap(Runnable runnable) {
63 + return new SafeRecurringTask(runnable);
64 + }
65 +
66 +}