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
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/PimNeighborsListCommand.java b/apps/pim/src/main/java/org/onosproject/pim/cli/PimNeighborsListCommand.java
new file mode 100644
index 0000000..605ec81
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/PimNeighborsListCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pim.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.util.Tools;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.pim.impl.PIMInterface;
+import org.onosproject.pim.impl.PIMInterfaceService;
+import org.onosproject.pim.impl.PIMNeighbor;
+
+import java.util.Set;
+
+/**
+ * Lists PIM neighbors.
+ */
+@Command(scope = "onos", name = "pim-neighbors",
+ description = "Lists the PIM neighbors")
+public class PimNeighborsListCommand extends AbstractShellCommand {
+
+ private static final String INTF_FORMAT = "interface=%s, address=%s";
+ private static final String NEIGHBOR_FORMAT = " neighbor=%s, uptime=%s, holdtime=%s, drPriority=%s, genId=%s";
+
+ @Override
+ protected void execute() {
+ PIMInterfaceService interfaceService = get(PIMInterfaceService.class);
+
+ Set<PIMInterface> interfaces = interfaceService.getPimInterfaces();
+
+ for (PIMInterface intf : interfaces) {
+ print(INTF_FORMAT, intf.getInterface().name(), intf.getIpAddress());
+ for (PIMNeighbor neighbor : intf.getNeighbors()) {
+ // Filter out the PIM neighbor representing 'us'
+ if (!neighbor.ipAddress().equals(intf.getIpAddress())) {
+ print(NEIGHBOR_FORMAT, neighbor.ipAddress(),
+ Tools.timeAgo(neighbor.upTime()), neighbor.holdtime(),
+ neighbor.priority(), neighbor.generationId());
+ }
+ }
+ }
+ }
+
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
index ca82052..1bedd9d 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
@@ -32,10 +32,12 @@
import org.slf4j.Logger;
import java.nio.ByteBuffer;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -99,10 +101,7 @@
generationId = new Random().nextInt();
// Create a PIM Neighbor to represent ourselves for DR election.
- PIMNeighbor us = new PIMNeighbor(ourIp, mac);
-
- // Priority and IP address are all we need to DR election.
- us.setPriority(priority);
+ PIMNeighbor us = new PIMNeighbor(ourIp, mac, holdTime, 0, priority, generationId);
pimNeighbors.put(ourIp, us);
drIpaddress = ourIp;
@@ -199,6 +198,32 @@
}
/**
+ * Gets the neighbors seen on this interface.
+ *
+ * @return PIM neighbors
+ */
+ public Collection<PIMNeighbor> getNeighbors() {
+ return pimNeighbors.values();
+ }
+
+ /**
+ * Checks whether any of our neighbors have expired, and cleans up their
+ * state if they have.
+ */
+ public void checkNeighborTimeouts() {
+ Set<PIMNeighbor> expired = pimNeighbors.values().stream()
+ // Don't time ourselves out!
+ .filter(neighbor -> !neighbor.ipAddress().equals(getIpAddress()))
+ .filter(neighbor -> neighbor.isExpired())
+ .collect(Collectors.toSet());
+
+ for (PIMNeighbor neighbor : expired) {
+ log.info("Timing out neighbor {}", neighbor);
+ pimNeighbors.remove(neighbor.ipAddress(), neighbor);
+ }
+ }
+
+ /**
* Multicast a hello message out our interface. This hello message is sent
* periodically during the normal PIM Neighbor refresh time, as well as a
* result of a newly created interface.
@@ -234,7 +259,7 @@
* <li>We <em>may</em> have to create a new neighbor if one does not already exist</li>
* <li>We <em>may</em> need to re-elect a new DR if new information is received</li>
* <li>We <em>may</em> need to send an existing neighbor all joins if the genid changed</li>
- * <li>We will refresh the neighbors timestamp</li>
+ * <li>We will refresh the neighbor's timestamp</li>
* </ul>
*
* @param ethPkt the Ethernet packet header
@@ -259,7 +284,7 @@
checkNotNull(dr);
IpAddress drip = drIpaddress;
- int drpri = dr.getPriority();
+ int drpri = dr.priority();
// Assume we do not need to run a DR election
boolean reElectDr = false;
@@ -269,18 +294,24 @@
// Determine if we already have a PIMNeighbor
PIMNeighbor nbr = pimNeighbors.getOrDefault(srcip, null);
+ PIMNeighbor newNbr = PIMNeighbor.createPimNeighbor(srcip, nbrmac, hello.getOptions().values());
+
if (nbr == null) {
- nbr = new PIMNeighbor(srcip, hello.getOptions());
- checkNotNull(nbr);
- } else {
- Integer previousGenid = nbr.getGenid();
- nbr.addOptions(hello.getOptions());
- if (previousGenid != nbr.getGenid()) {
- genidChanged = true;
+ pimNeighbors.putIfAbsent(srcip, newNbr);
+ nbr = newNbr;
+ } else if (!nbr.equals(newNbr)) {
+ if (newNbr.holdtime() == 0) {
+ // Neighbor has shut down. Remove them and clean up
+ pimNeighbors.remove(srcip, nbr);
+ return;
+ } else {
+ // Neighbor has changed one of their options.
+ pimNeighbors.put(srcip, newNbr);
+ nbr = newNbr;
}
}
- // Refresh this neighbors timestamp
+ // Refresh this neighbor's timestamp
nbr.refreshTimestamp();
/*
@@ -300,8 +331,8 @@
// Run an election if we need to. Return the elected IP address.
private IpAddress election(PIMNeighbor nbr, IpAddress drIp, int drPriority) {
- IpAddress nbrIp = nbr.getIpaddr();
- if (nbr.getPriority() > drPriority) {
+ IpAddress nbrIp = nbr.ipAddress();
+ if (nbr.priority() > drPriority) {
return nbrIp;
}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java
index bcaeb2c..d6205e5 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java
@@ -23,6 +23,7 @@
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.SafeRecurringTask;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceEvent;
import org.onosproject.incubator.net.intf.InterfaceListener;
@@ -58,8 +59,10 @@
private static final Class<PimInterfaceConfig> PIM_INTERFACE_CONFIG_CLASS = PimInterfaceConfig.class;
private static final String PIM_INTERFACE_CONFIG_KEY = "pimInterface";
- // Create a Scheduled Executor service to send PIM hellos
- private final ScheduledExecutorService helloScheduler =
+ private static final int DEFAULT_TIMEOUT_TASK_PERIOD_MS = 250;
+
+ // Create a Scheduled Executor service for recurring tasks
+ private final ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(1);
// Wait for a bout 3 seconds before sending the initial hello messages.
@@ -69,6 +72,8 @@
// Send PIM hello packets: 30 seconds.
private final long pimHelloPeriod = 30;
+ private final int timeoutTaskPeriod = DEFAULT_TIMEOUT_TASK_PERIOD_MS;
+
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@@ -113,18 +118,16 @@
interfaceService.addListener(interfaceListener);
// Schedule the periodic hello sender.
- helloScheduler.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- try {
- for (PIMInterface pif : pimInterfaces.values()) {
- pif.sendHello();
- }
- } catch (Exception e) {
- log.warn("exception", e);
- }
- }
- }, initialHelloDelay, pimHelloPeriod, TimeUnit.SECONDS);
+ scheduledExecutorService.scheduleAtFixedRate(
+ SafeRecurringTask.wrap(
+ () -> pimInterfaces.values().forEach(PIMInterface::sendHello)),
+ initialHelloDelay, pimHelloPeriod, TimeUnit.SECONDS);
+
+ // Schedule task to periodically time out expired neighbors
+ scheduledExecutorService.scheduleAtFixedRate(
+ SafeRecurringTask.wrap(
+ () -> pimInterfaces.values().forEach(PIMInterface::checkNeighborTimeouts)),
+ 0, timeoutTaskPeriod, TimeUnit.MILLISECONDS);
log.info("Started");
}
@@ -136,7 +139,7 @@
networkConfig.unregisterConfigFactory(pimConfigFactory);
// Shutdown the periodic hello task.
- helloScheduler.shutdown();
+ scheduledExecutorService.shutdown();
log.info("Stopped");
}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
index f44cd1b..9e6fa38 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
@@ -15,216 +15,226 @@
*/
package org.onosproject.pim.impl;
+import com.google.common.base.MoreObjects;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.pim.PIMHelloOption;
-import org.slf4j.Logger;
import java.nio.ByteBuffer;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Map;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
-import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkNotNull;
+/**
+ * Represents a PIM neighbor.
+ */
public class PIMNeighbor {
- private final Logger log = getLogger(getClass());
-
// IP Address of this neighbor
- private IpAddress ipAddr;
+ private final IpAddress ipAddr;
// MAC Address of the neighbor (Need for sending J/P)
- private MacAddress macAddr;
+ private final MacAddress macAddr;
// Hello Options
// Our hello opt holdTime
- private short holdTime;
+ private final short holdTime;
// Our hello opt prune delay
- private int pruneDelay;
+ private final int pruneDelay;
// Neighbor priority
- private int priority;
+ private final int priority;
// Our current genId
- private int genId;
+ private final int genId;
+
+ private final long upTime;
// Our timestamp for this neighbor
- private Date lastRefresh;
+ private long lastRefresh;
/**
- * Construct a new PIM Neighbor.
+ * Class constructor.
*
- * @param ipAddr the IP Address of our new neighbor
- * @param opts option map
+ * @param ipAddress neighbor IP address
+ * @param macAddress neighbor MAC address
+ * @param holdTime hold time
+ * @param pruneDelay prune delay
+ * @param priority priority
+ * @param genId generation ID
*/
- public PIMNeighbor(IpAddress ipAddr, Map<Short, PIMHelloOption> opts) {
- this.ipAddr = ipAddr;
- this.addOptions(opts);
+ public PIMNeighbor(IpAddress ipAddress, MacAddress macAddress,
+ short holdTime, int pruneDelay, int priority, int genId) {
+ this.ipAddr = checkNotNull(ipAddress);
+ this.macAddr = checkNotNull(macAddress);
+ this.holdTime = holdTime;
+ this.pruneDelay = pruneDelay;
+ this.priority = priority;
+ this.genId = genId;
+
+ this.upTime = System.currentTimeMillis();
}
/**
- * Construct a new PIM neighbor.
- *
- * @param ipAddr the neighbors IP addr
- * @param macAddr MAC address
- */
- public PIMNeighbor(IpAddress ipAddr, MacAddress macAddr) {
- this.ipAddr = ipAddr;
- this.macAddr = macAddr;
- }
-
- /**
- * Get the MAC address of this neighbor.
- *
- * @return the mac address
- */
- public MacAddress getMacaddr() {
- return macAddr;
- }
-
- /**
- * Get the IP Address of our neighbor.
+ * Gets the IP address of our neighbor.
*
* @return the IP address of our neighbor
*/
- public IpAddress getIpaddr() {
+ public IpAddress ipAddress() {
return ipAddr;
}
/**
- * Set the IP address of our neighbor.
+ * Gets the MAC address of this neighbor.
*
- * @param ipAddr our neighbors IP address
+ * @return the mac address
*/
- public void setIpaddr(IpAddress ipAddr) {
- this.ipAddr = ipAddr;
+ public MacAddress macAddress() {
+ return macAddr;
}
/**
- * Get our neighbors holdTime.
+ * Gets our neighbor's hold time.
*
- * @return the holdTime
+ * @return the hold time
*/
- public short getHoldtime() {
+ public short holdtime() {
return holdTime;
}
/**
- * Set our neighbors holdTime.
+ * Gets our neighbor's prune delay.
*
- * @param holdTime the holdTime
+ * @return our neighbor's prune delay
*/
- public void setHoldtime(short holdTime) {
- this.holdTime = holdTime;
- }
-
- /**
- * Get our neighbors prune delay.
- *
- * @return our neighbors prune delay
- */
- public int getPruneDelay() {
+ public int pruneDelay() {
return pruneDelay;
}
/**
- * Set our neighbors prune delay.
+ * Gets our neighbor's priority.
*
- * @param pruneDelay the prune delay
+ * @return our neighbor's priority
*/
- public void setPruneDelay(int pruneDelay) {
- this.pruneDelay = pruneDelay;
- }
-
- /**
- * Get our neighbors priority.
- *
- * @return our neighbors priority
- */
- public int getPriority() {
+ public int priority() {
return priority;
}
/**
- * Set our neighbors priority.
+ * Gets our neighbor's generation ID.
*
- * @param priority our neighbors priority
+ * @return our neighbor's generation ID
*/
- public void setPriority(int priority) {
- this.priority = priority;
- }
-
- /**
- * Get our neighbors Genid.
- *
- * @return our neighbor Genid
- */
- public int getGenid() {
+ public int generationId() {
return genId;
}
/**
- * Set our neighbors GenId.
+ * Gets the last time we heard a HELLO from this neighbor.
*
- * @param genId our neighbors GenId
+ * @return last refresh time
*/
- public void setGenid(int genId) {
- this.genId = genId;
+ public long lastRefresh() {
+ return lastRefresh;
}
/**
- * Add the options for this neighbor if needed.
+ * Gets the time that we first learnt of this neighbor.
*
- * @param opts the options to be added/modified
- * @return true if options changed, false if no option has changed
+ * @return up time
*/
- public boolean addOptions(Map<Short, PIMHelloOption> opts) {
-
- boolean changed = false;
-
- for (PIMHelloOption opt : opts.values()) {
- Short otype = opt.getOptType();
- ByteBuffer val = ByteBuffer.wrap(opt.getValue());
-
- if (otype == PIMHelloOption.OPT_ADDRLIST) {
- // TODO: Will implement someday
- } else if (otype == PIMHelloOption.OPT_GENID) {
- int newval = val.getInt();
- if (newval != genId) {
- genId = newval;
- changed = true;
- }
- } else if (otype == PIMHelloOption.OPT_HOLDTIME) {
- short newval = val.getShort();
- if (newval != holdTime) {
- holdTime = newval;
- changed = true;
- }
- } else if (otype == PIMHelloOption.OPT_PRIORITY) {
- int newval = val.getInt();
- if (newval != priority) {
- priority = newval;
- changed = true;
- }
- } else if (otype == PIMHelloOption.OPT_PRUNEDELAY) {
- int newval = val.getInt();
- if (newval != pruneDelay) {
- pruneDelay = newval;
- changed = true;
- }
- } else {
- log.warn("received unknown pim hello options" + otype);
- }
- }
- return changed;
+ public long upTime() {
+ return upTime;
}
/**
- * Refresh this neighbors timestamp.
+ * Refreshes this neighbor's last seen timestamp.
*/
public void refreshTimestamp() {
- lastRefresh = Calendar.getInstance().getTime();
+ lastRefresh = System.currentTimeMillis();
}
+
+ /**
+ * Returns whether this neighbor is expired or not.
+ *
+ * @return true if the neighbor is expired, otherwise false
+ */
+ public boolean isExpired() {
+ return lastRefresh + TimeUnit.SECONDS.toMillis(holdTime)
+ < System.currentTimeMillis();
+ }
+
+ /**
+ * Creates a PIM neighbor based on an IP, MAC, and collection of PIM HELLO
+ * options.
+ *
+ * @param ipAddress neighbor IP address
+ * @param macAddress neighbor MAC address
+ * @param opts options from the PIM HELLO packet
+ * @return new PIM neighbor
+ */
+ public static PIMNeighbor createPimNeighbor(IpAddress ipAddress,
+ MacAddress macAddress,
+ Collection<PIMHelloOption> opts) {
+
+ int generationID = PIMHelloOption.DEFAULT_GENID;
+ short holdTime = PIMHelloOption.DEFAULT_HOLDTIME;
+ int priority = PIMHelloOption.DEFAULT_PRIORITY;
+ int pruneDelay = PIMHelloOption.DEFAULT_PRUNEDELAY;
+
+ for (PIMHelloOption opt : opts) {
+ short type = opt.getOptType();
+ ByteBuffer value = ByteBuffer.wrap(opt.getValue());
+
+ if (type == PIMHelloOption.OPT_GENID) {
+ generationID = value.getInt();
+ } else if (type == PIMHelloOption.OPT_HOLDTIME) {
+ holdTime = value.getShort();
+ } else if (type == PIMHelloOption.OPT_PRIORITY) {
+ priority = value.getInt();
+ } else if (type == PIMHelloOption.OPT_PRUNEDELAY) {
+ pruneDelay = value.getInt();
+ } else if (type == PIMHelloOption.OPT_ADDRLIST) {
+ // TODO: Will implement someday
+ }
+ }
+
+ return new PIMNeighbor(ipAddress, macAddress, holdTime, pruneDelay, priority, generationID);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof PIMNeighbor)) {
+ return false;
+ }
+
+ PIMNeighbor that = (PIMNeighbor) other;
+
+ return this.ipAddr.equals(that.ipAddress()) &&
+ this.macAddr.equals(that.macAddress()) &&
+ this.genId == that.genId &&
+ this.holdTime == that.holdTime &&
+ this.priority == that.priority;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipAddr, macAddr, genId, holdTime, priority);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("ipAddress", ipAddr)
+ .add("macAddress", macAddr)
+ .add("generationId", genId)
+ .add("holdTime", holdTime)
+ .add("priority", priority)
+ .add("pruneDelay", pruneDelay)
+ .toString();
+ }
+
}
diff --git a/apps/pim/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/pim/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 780ad9f..012760d 100644
--- a/apps/pim/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/pim/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -19,6 +19,9 @@
<command>
<action class="org.onosproject.pim.cli.PimInterfacesListCommand"/>
</command>
+ <command>
+ <action class="org.onosproject.pim.cli.PimNeighborsListCommand"/>
+ </command>
</command-bundle>
</blueprint>
diff --git a/utils/misc/src/main/java/org/onlab/util/SafeRecurringTask.java b/utils/misc/src/main/java/org/onlab/util/SafeRecurringTask.java
new file mode 100644
index 0000000..756abdd
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/util/SafeRecurringTask.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrapper for a recurring task which catches all exceptions to prevent task
+ * being suppressed in a ScheduledExecutorService.
+ */
+public final class SafeRecurringTask implements Runnable {
+
+ private static final Logger log = LoggerFactory.getLogger(SafeRecurringTask.class);
+
+ private final Runnable runnable;
+
+ /**
+ * Constructor.
+ *
+ * @param runnable runnable to wrap
+ */
+ private SafeRecurringTask(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ if (Thread.currentThread().isInterrupted()) {
+ log.info("Task interrupted, quitting");
+ return;
+ }
+
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ // Catch all exceptions to avoid task being suppressed
+ log.error("Exception thrown during task", e);
+ }
+ }
+
+ /**
+ * Wraps a runnable in a safe recurring task.
+ *
+ * @param runnable runnable to wrap
+ * @return safe recurring task
+ */
+ public static SafeRecurringTask wrap(Runnable runnable) {
+ return new SafeRecurringTask(runnable);
+ }
+
+}