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
Showing
6 changed files
with
191 additions
and
31 deletions
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 | } | ... | ... |
This diff is collapsed. Click to expand it.
... | @@ -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 | +} |
-
Please register or login to post a comment