Added configuration for PIM interfaces.

Now the PIM application requires PIM Interface configuration for each interface
that will have PIM enabled (no longer uses all ONOS interfaces). The
interface-specific PIM parameters can be tuned.

Change-Id: Ibc284fdbe1b3aa4da48097b3e92470bce4f349a7
diff --git a/apps/pim/pom.xml b/apps/pim/pom.xml
index 3973351..80283e4 100644
--- a/apps/pim/pom.xml
+++ b/apps/pim/pom.xml
@@ -82,37 +82,4 @@
         </dependency>
     </dependencies>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                    <instructions>
-                        <Bundle-SymbolicName>
-                            ${project.groupId}.${project.artifactId}
-                        </Bundle-SymbolicName>
-                        <Import-Package>
-                            org.slf4j,
-                            org.osgi.framework,
-                            com.google.common.*,
-                            org.apache.karaf.shell.commands,
-                            org.apache.karaf.shell.console,
-                            org.onlab.packet.*,
-                            org.onosproject.*,
-                        </Import-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <configuration>
-                    <source>1.8</source>
-                    <target>1.8</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
 </project>
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java b/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java
new file mode 100644
index 0000000..c83871e
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java
@@ -0,0 +1,47 @@
+/*
+ * 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.onosproject.cli.AbstractShellCommand;
+import org.onosproject.pim.impl.PIMInterface;
+import org.onosproject.pim.impl.PIMInterfaceService;
+
+import java.util.Set;
+
+/**
+ * Lists the interfaces where PIM is enabled.
+ */
+@Command(scope = "onos", name = "pim-interfaces",
+        description = "Lists the interfaces where PIM is enabled")
+public class PimInterfacesListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "interfaceName=%s, holdTime=%s, priority=%s, genId=%s";
+
+    @Override
+    protected void execute() {
+        PIMInterfaceService interfaceService = get(PIMInterfaceService.class);
+
+        Set<PIMInterface> interfaces = interfaceService.getPimInterfaces();
+
+        interfaces.forEach(
+                pimIntf -> print(FORMAT, pimIntf.getInterface().name(),
+                        pimIntf.getHoldtime(), pimIntf.getPriority(),
+                        pimIntf.getGenerationId()));
+    }
+
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMApplication.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMApplication.java
index 6aa935e..82adfad 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMApplication.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMApplication.java
@@ -24,14 +24,6 @@
 import org.onlab.packet.IPv4;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
-import org.onosproject.incubator.net.config.basics.ConfigException;
-import org.onosproject.incubator.net.config.basics.InterfaceConfig;
-import org.onosproject.incubator.net.intf.Interface;
-import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.config.NetworkConfigEvent;
-import org.onosproject.net.config.NetworkConfigListener;
-import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.mcast.MulticastRouteService;
@@ -43,7 +35,6 @@
 import org.slf4j.Logger;
 
 import java.util.Optional;
-import java.util.Set;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -72,17 +63,6 @@
     // Create an instance of the PIM packet handler
     protected PIMPacketHandler pimPacketHandler;
 
-    // Get the network configuration updates
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected NetworkConfigService configService;
-
-    // Access defined network (IP) interfaces
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected InterfaceService interfaceService;
-
-    // Internal class used to listen for network configuration changes
-    private InternalConfigListener configListener = new InternalConfigListener();
-
     // Provide interfaces to the pimInterface manager as a result of Netconfig updates.
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PIMInterfaceService pimInterfaceManager;
@@ -94,7 +74,6 @@
      */
     @Activate
     public void activate() {
-
         // Get our application ID
         appId = coreService.registerApplication("org.onosproject.pim");
 
@@ -109,15 +88,9 @@
         packetService.requestPackets(selector.build(), PacketPriority.CONTROL,
                 appId, Optional.empty());
 
-        // Register for notifications from the Network config & Interface services.
-        // We'll use these services to represent "PIMInterfaces"
-
         // Get a copy of the PIM Packet Handler
         pimPacketHandler = new PIMPacketHandler();
 
-        // Listen for network configuration changes
-        configService.addListener(configListener);
-
         log.info("Started");
     }
 
@@ -180,57 +153,4 @@
         }
     }
 
-    /*
-     * This class receives all events from the network config services, then hands the
-     * event off to the PIMInterfaceManager for proper handling.
-     *
-     * TODO: should this move to PIMInterfaceManager?
-     */
-    private class InternalConfigListener implements NetworkConfigListener {
-
-        @Override
-        public void event(NetworkConfigEvent event) {
-
-            log.debug(event.toString());
-            switch (event.type()) {
-                case CONFIG_ADDED:
-                case CONFIG_UPDATED:
-
-                    if (event.configClass() == InterfaceConfig.class) {
-                        InterfaceConfig config = configService.getConfig(
-                                (ConnectPoint) event.subject(),
-                                InterfaceConfig.class);
-
-                        log.debug("Got a network configuration event");
-
-                        // Walk the interfaces and feed them to the PIMInterfaceManager
-                        Set<Interface> intfs;
-                        try {
-                            intfs = config.getInterfaces();
-                            for (Interface intf : intfs) {
-                                pimInterfaceManager.updateInterface(intf);
-                            }
-                        } catch (ConfigException e) {
-                            log.error(e.toString());
-                            return;
-                        }
-                    }
-                    break;
-
-                case CONFIG_REMOVED:
-                    if (event.configClass() == InterfaceConfig.class) {
-                        ConnectPoint cp = (ConnectPoint) event.subject();
-                        //assertNotNull(cp);
-                        pimInterfaceManager.deleteInterface(cp);
-                    }
-                    break;
-
-                case CONFIG_REGISTERED:
-                case CONFIG_UNREGISTERED:
-                default:
-                    log.debug("\tWe are not handling this event type");
-                    break;
-            }
-        }
-    }
 }
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 454c431..ca82052 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
@@ -34,8 +34,10 @@
 import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Random;
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -43,7 +45,7 @@
  * PIM Interface represents an ONOS Interface with IP and MAC addresses for
  * a given ConnectPoint.
  */
-public class PIMInterface {
+public final class PIMInterface {
 
     private final Logger log = getLogger(getClass());
 
@@ -62,10 +64,10 @@
     private int priority   = PIMHelloOption.DEFAULT_PRIORITY;
 
     // Our current genid
-    private int genid      = PIMHelloOption.DEFAULT_GENID;   // Needs to be assigned.
+    private final int generationId;
 
     // The IP address of the DR
-    IpAddress drIpaddress;
+    private IpAddress drIpaddress;
 
     // A map of all our PIM neighbors keyed on our neighbors IP address
     private Map<IpAddress, PIMNeighbor> pimNeighbors = new HashMap<>();
@@ -74,14 +76,28 @@
      * Create a PIMInterface from an ONOS Interface.
      *
      * @param intf the ONOS Interface.
+     * @param holdTime hold time
+     * @param priority priority
+     * @param propagationDelay propagation delay
+     * @param overrideInterval override interval
+     * @param packetService reference to the packet service
      */
-    public PIMInterface(Interface intf, PacketService packetService) {
+    private PIMInterface(Interface intf,
+                        short holdTime,
+                        int priority,
+                        short propagationDelay,
+                        short overrideInterval,
+                        PacketService packetService) {
+
         onosInterface = intf;
         outputTreatment = createOutputTreatment();
+        this.holdtime = holdTime;
         this.packetService = packetService;
         IpAddress ourIp = getIpAddress();
         MacAddress mac = intf.mac();
 
+        generationId = new Random().nextInt();
+
         // Create a PIM Neighbor to represent ourselves for DR election.
         PIMNeighbor us = new PIMNeighbor(ourIp, mac);
 
@@ -178,8 +194,8 @@
      *
      * @return our generation ID
      */
-    public int getGenid() {
-        return genid;
+    public int getGenerationId() {
+        return generationId;
     }
 
     /**
@@ -188,7 +204,6 @@
      * result of a newly created interface.
      */
     public void sendHello() {
-
         // Create the base PIM Packet and mark it a hello packet
         PIMPacket pimPacket = new PIMPacket(PIM.TYPE_HELLO);
 
@@ -199,6 +214,9 @@
         // Create the hello message with options
         PIMHello hello = new PIMHello();
         hello.createDefaultOptions();
+        hello.addOption(PIMHelloOption.createHoldTime(holdtime));
+        hello.addOption(PIMHelloOption.createPriority(priority));
+        hello.addOption(PIMHelloOption.createGenID(generationId));
 
         // Now set the hello option payload
         pimPacket.setPIMPayload(hello);
@@ -266,7 +284,7 @@
         nbr.refreshTimestamp();
 
         /*
-         * the election method will frist determine if an election
+         * the election method will first determine if an election
          * needs to be run, if so it will run the election.  The
          * IP address of the DR will be returned.  If the IP address
          * of the DR is different from what we already have we know a
@@ -280,17 +298,17 @@
     }
 
     // Run an election if we need to.  Return the elected IP address.
-    private IpAddress election(PIMNeighbor nbr, IpAddress drip, int drpri) {
+    private IpAddress election(PIMNeighbor nbr, IpAddress drIp, int drPriority) {
 
-        IpAddress nbrip = nbr.getIpaddr();
-        if (nbr.getPriority() > drpri) {
-            return nbrip;
+        IpAddress nbrIp = nbr.getIpaddr();
+        if (nbr.getPriority() > drPriority) {
+            return nbrIp;
         }
 
-        if (nbrip.compareTo(drip) > 0) {
-            return nbrip;
+        if (nbrIp.compareTo(drIp) > 0) {
+            return nbrIp;
         }
-        return drip;
+        return drIp;
     }
 
     /**
@@ -301,4 +319,105 @@
     public void processJoinPrune(Ethernet ethPkt) {
         // TODO: add Join/Prune processing code.
     }
+
+    /**
+     * Returns a builder for a PIM interface.
+     *
+     * @return PIM interface builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for a PIM interface.
+     */
+    public static class Builder {
+        private Interface intf;
+        private PacketService packetService;
+        private short holdtime = PIMHelloOption.DEFAULT_HOLDTIME;
+        private int priority   = PIMHelloOption.DEFAULT_PRIORITY;
+        private short propagationDelay = PIMHelloOption.DEFAULT_PRUNEDELAY;
+        private short overrideInterval = PIMHelloOption.DEFAULT_OVERRIDEINTERVAL;
+
+        /**
+         * Uses the specified ONOS interface.
+         *
+         * @param intf ONOS interface
+         * @return this PIM interface builder
+         */
+        public Builder withInterface(Interface intf) {
+            this.intf = checkNotNull(intf);
+            return this;
+        }
+
+        /**
+         * Sets the reference to the packet service.
+         *
+         * @param packetService packet service
+         * @return this PIM interface builder
+         */
+        public Builder withPacketService(PacketService packetService) {
+            this.packetService = checkNotNull(packetService);
+            return this;
+        }
+
+        /**
+         * Uses the specified hold time.
+         *
+         * @param holdTime hold time in seconds
+         * @return this PIM interface builder
+         */
+        public Builder withHoldTime(short holdTime) {
+            this.holdtime = holdTime;
+            return this;
+        }
+
+        /**
+         * Uses the specified DR priority.
+         *
+         * @param priority DR priority
+         * @return this PIM interface builder
+         */
+        public Builder withPriority(int priority) {
+            this.priority = priority;
+            return this;
+        }
+
+        /**
+         * Uses the specified propagation delay.
+         *
+         * @param propagationDelay propagation delay in ms
+         * @return this PIM interface builder
+         */
+        public Builder withPropagationDelay(short propagationDelay) {
+            this.propagationDelay = propagationDelay;
+            return this;
+        }
+
+        /**
+         * Uses the specified override interval.
+         *
+         * @param overrideInterval override interval in ms
+         * @return this PIM interface builder
+         */
+        public Builder withOverrideInterval(short overrideInterval) {
+            this.overrideInterval = overrideInterval;
+            return this;
+        }
+
+        /**
+         * Builds the PIM interface.
+         *
+         * @return PIM interface
+         */
+        public PIMInterface build() {
+            checkArgument(intf != null, "Must provide an interface");
+            checkArgument(packetService != null, "Must provide a packet service");
+
+            return new PIMInterface(intf, holdtime, priority, propagationDelay,
+                    overrideInterval, packetService);
+        }
+
+    }
 }
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 711db6c..bcaeb2c 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
@@ -15,6 +15,7 @@
  */
 package org.onosproject.pim.impl;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -23,12 +24,20 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceEvent;
+import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
 import org.onosproject.net.packet.PacketService;
-import org.onosproject.net.provider.ProviderId;
 import org.slf4j.Logger;
+
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -46,8 +55,8 @@
 
     private final Logger log = getLogger(getClass());
 
-    // Create ourselves a provider ID
-    private static final ProviderId PID = new ProviderId("pim", "org.onosproject.pim");
+    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 =
@@ -55,43 +64,76 @@
 
     // Wait for a bout 3 seconds before sending the initial hello messages.
     // TODO: make this tunnable.
-    private final long initialHelloDelay = (long) 3;
+    private final long initialHelloDelay = 3;
 
     // Send PIM hello packets: 30 seconds.
-    private final long pimHelloPeriod = (long) 30;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected InterfaceService interfaceService;
+    private final long pimHelloPeriod = 30;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PacketService packetService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry networkConfig;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
     // Store PIM Interfaces in a map key'd by ConnectPoint
     private final Map<ConnectPoint, PIMInterface> pimInterfaces = Maps.newConcurrentMap();
 
+    private final InternalNetworkConfigListener configListener =
+            new InternalNetworkConfigListener();
+    private final InternalInterfaceListener interfaceListener =
+            new InternalInterfaceListener();
+
+    private final ConfigFactory<ConnectPoint, PimInterfaceConfig> pimConfigFactory
+            = new ConfigFactory<ConnectPoint, PimInterfaceConfig>(
+            SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY, PIM_INTERFACE_CONFIG_CLASS,
+            PIM_INTERFACE_CONFIG_KEY) {
+
+        @Override
+        public PimInterfaceConfig createConfig() {
+            return new PimInterfaceConfig();
+        }
+    };
+
     @Activate
     public void activate() {
-        // Query the Interface service to see if Interfaces already exist.
-        log.info("Started");
+        networkConfig.registerConfigFactory(pimConfigFactory);
 
-        // Create PIM Interfaces for each of the existing ONOS Interfaces.
-        for (Interface intf : interfaceService.getInterfaces()) {
-            pimInterfaces.put(intf.connectPoint(), new PIMInterface(intf, packetService));
+        // Create PIM Interfaces for each of the existing configured interfaces.
+        Set<ConnectPoint> subjects = networkConfig.getSubjects(
+                ConnectPoint.class, PIM_INTERFACE_CONFIG_CLASS);
+        for (ConnectPoint cp : subjects) {
+            PimInterfaceConfig config = networkConfig.getConfig(cp, PIM_INTERFACE_CONFIG_CLASS);
+            updateInterface(config);
         }
 
+        networkConfig.addListener(configListener);
+        interfaceService.addListener(interfaceListener);
+
         // Schedule the periodic hello sender.
         helloScheduler.scheduleAtFixedRate(new Runnable() {
             @Override
             public void run() {
-                for (PIMInterface pif : pimInterfaces.values()) {
-                    pif.sendHello();
+                try {
+                    for (PIMInterface pif : pimInterfaces.values()) {
+                        pif.sendHello();
+                    }
+                } catch (Exception e) {
+                    log.warn("exception", e);
                 }
             }
         }, initialHelloDelay, pimHelloPeriod, TimeUnit.SECONDS);
+
+        log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
+        interfaceService.removeListener(interfaceListener);
+        networkConfig.removeListener(configListener);
+        networkConfig.unregisterConfigFactory(pimConfigFactory);
 
         // Shutdown the periodic hello task.
         helloScheduler.shutdown();
@@ -100,37 +142,6 @@
     }
 
     /**
-     * Update the ONOS Interface with the new Interface.  If the PIMInterface does
-     * not exist we'll create a new one and store it.
-     *
-     * @param intf ONOS Interface.
-     */
-    @Override
-    public void updateInterface(Interface intf) {
-        ConnectPoint cp = intf.connectPoint();
-
-        log.debug("Updating Interface for " + intf.connectPoint().toString());
-        pimInterfaces.compute(cp, (k, v) -> (v == null) ?
-                new PIMInterface(intf, packetService) :
-                v.setInterface(intf));
-    }
-
-    /**
-     * Delete the PIM Interface to the corresponding ConnectPoint.
-     *
-     * @param cp The connect point associated with this interface we want to delete
-     */
-    @Override
-    public void deleteInterface(ConnectPoint cp) {
-
-        PIMInterface pi = pimInterfaces.remove(cp);
-        if (pi == null) {
-            log.warn("We've been asked to remove an interface we3 don't have: " + cp.toString());
-            return;
-        }
-    }
-
-    /**
      * Return the PIMInterface that corresponds to the given ConnectPoint.
      *
      * @param cp The ConnectPoint we want to get the PIMInterface for
@@ -144,4 +155,115 @@
         }
         return pi;
     }
+
+    @Override
+    public Set<PIMInterface> getPimInterfaces() {
+        return ImmutableSet.copyOf(pimInterfaces.values());
+    }
+
+    private void updateInterface(PimInterfaceConfig config) {
+        ConnectPoint cp = config.subject();
+
+        if (!config.isEnabled()) {
+            removeInterface(cp);
+            return;
+        }
+
+        String intfName = config.getInterfaceName();
+        Interface intf = interfaceService.getInterfaceByName(cp, intfName);
+
+        if (intf == null) {
+            log.debug("Interface configuration missing: {}", config.getInterfaceName());
+            return;
+        }
+
+
+        log.debug("Updating Interface for " + intf.connectPoint().toString());
+        pimInterfaces.computeIfAbsent(cp, k -> buildPimInterface(config, intf));
+    }
+
+    private void removeInterface(ConnectPoint cp) {
+        pimInterfaces.remove(cp);
+    }
+
+    private PIMInterface buildPimInterface(PimInterfaceConfig config, Interface intf) {
+        PIMInterface.Builder builder = PIMInterface.builder()
+                .withPacketService(packetService)
+                .withInterface(intf);
+
+        if (config.getHoldTime().isPresent()) {
+            builder.withHoldTime(config.getHoldTime().get());
+        }
+        if (config.getPriority().isPresent()) {
+            builder.withPriority(config.getPriority().get());
+        }
+        if (config.getPropagationDelay().isPresent()) {
+            builder.withPropagationDelay(config.getPropagationDelay().get());
+        }
+        if (config.getOverrideInterval().isPresent()) {
+            builder.withOverrideInterval(config.getOverrideInterval().get());
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Listener for network config events.
+     */
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (event.configClass() != PIM_INTERFACE_CONFIG_CLASS) {
+                return;
+            }
+
+            switch (event.type()) {
+            case CONFIG_REGISTERED:
+            case CONFIG_UNREGISTERED:
+                break;
+            case CONFIG_ADDED:
+            case CONFIG_UPDATED:
+                ConnectPoint cp = (ConnectPoint) event.subject();
+                PimInterfaceConfig config = networkConfig.getConfig(
+                        cp, PIM_INTERFACE_CONFIG_CLASS);
+
+                updateInterface(config);
+                break;
+            case CONFIG_REMOVED:
+                removeInterface((ConnectPoint) event.subject());
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    /**
+     * Listener for interface events.
+     */
+    private class InternalInterfaceListener implements InterfaceListener {
+
+        @Override
+        public void event(InterfaceEvent event) {
+            switch (event.type()) {
+            case INTERFACE_ADDED:
+                PimInterfaceConfig config = networkConfig.getConfig(
+                        event.subject().connectPoint(), PIM_INTERFACE_CONFIG_CLASS);
+
+                if (config != null) {
+                    updateInterface(config);
+                }
+                break;
+            case INTERFACE_UPDATED:
+                break;
+            case INTERFACE_REMOVED:
+                removeInterface(event.subject().connectPoint());
+                break;
+            default:
+                break;
+
+            }
+        }
+    }
 }
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceService.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceService.java
index 528c51d..53db001 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceService.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceService.java
@@ -15,9 +15,10 @@
  */
 package org.onosproject.pim.impl;
 
-import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.net.ConnectPoint;
 
+import java.util.Set;
+
 /**
  * Define the PIMInterfaceService.  PIM will use ONOS Interfaces to
  * define PIM Interfaces.  The PIM Application signed up as a Netconfig
@@ -28,25 +29,17 @@
 public interface PIMInterfaceService {
 
     /**
-     * Update the corresponding PIMInterface.  If the PIMInterface
-     * does not exist it will be created.
-     *
-     * @param intf ONOS Interface.
-     */
-    public void updateInterface(Interface intf);
-
-    /**
-     * Delete the PIMInterface that corresponds to the given ConnectPoint.
-     *
-     * @param cp The connect point associated with this interface.
-     */
-    public void deleteInterface(ConnectPoint cp);
-
-    /**
      * Return the PIMInterface associated with the given ConnectPoint.
      *
      * @param cp The ConnectPoint we want to get the PIMInterface for.
      * @return the PIMInterface if it exists, NULL if it does not exist.
      */
     public PIMInterface getPIMInterface(ConnectPoint cp);
+
+    /**
+     * Retrieves the set of all interfaces running PIM.
+     *
+     * @return set of PIM interfaces
+     */
+    Set<PIMInterface> getPimInterfaces();
 }
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java
new file mode 100644
index 0000000..8d8ad78
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java
@@ -0,0 +1,102 @@
+/*
+ * 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.impl;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.Config;
+
+import java.util.Optional;
+
+/**
+ * Configuration for a PIM interface.
+ */
+public class PimInterfaceConfig extends Config<ConnectPoint> {
+
+    private static final String INTERFACE_NAME = "interfaceName";
+    private static final String ENABLED = "enabled";
+    private static final String HOLD_TIME = "holdTime";
+    private static final String PRIORITY = "priority";
+    private static final String PROPAGATION_DELAY = "propagationDelay";
+    private static final String OVERRIDE_INTERVAL = "overrideInterval";
+
+    /**
+     * Gets the name of the interface. This links the PIM configuration with
+     * an existing ONOS interface.
+     *
+     * @return interface name
+     */
+    public String getInterfaceName() {
+        return node.path(INTERFACE_NAME).asText();
+    }
+
+    /**
+     * Returns whether PIM is enabled on the interface or not.
+     *
+     * @return true if PIM is enabled, otherwise false
+     */
+    public boolean isEnabled() {
+        return node.path(ENABLED).asBoolean(false);
+    }
+
+    /**
+     * Gets the HELLO hold time of the interface.
+     *
+     * @return hold time
+     */
+    public Optional<Short> getHoldTime() {
+        if (node.path(HOLD_TIME).isMissingNode()) {
+            return Optional.empty();
+        }
+        return Optional.of(Short.parseShort(node.path(HOLD_TIME).asText()));
+    }
+
+    /**
+     * Gets the priority of the interface.
+     *
+     * @return priority
+     */
+    public Optional<Integer> getPriority() {
+        if (node.path(PRIORITY).isMissingNode()) {
+            return Optional.empty();
+        }
+        return Optional.of(node.path(PRIORITY).asInt());
+    }
+
+    /**
+     * Gets the propagation delay of the interface.
+     *
+     * @return propagation delay
+     */
+    public Optional<Short> getPropagationDelay() {
+        if (node.path(PROPAGATION_DELAY).isMissingNode()) {
+            return Optional.empty();
+        }
+        return Optional.of(Short.parseShort(node.path(PROPAGATION_DELAY).asText()));
+    }
+
+    /**
+     * Gets the override interval of the interface.
+     *
+     * @return override interval
+     */
+    public Optional<Short> getOverrideInterval() {
+        if (node.path(OVERRIDE_INTERVAL).isMissingNode()) {
+            return Optional.empty();
+        }
+        return Optional.of(Short.parseShort(node.path(OVERRIDE_INTERVAL).asText()));
+    }
+}
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
similarity index 91%
rename from apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml
rename to apps/pim/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index c30e379..780ad9f 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
@@ -17,7 +17,7 @@
 
     <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
         <command>
-            <action class="org.onosproject.pim.cli.PIMShowCommand"/>
+            <action class="org.onosproject.pim.cli.PimInterfacesListCommand"/>
         </command>
     </command-bundle>
 
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java
index 7d05d92..2372e1a 100644
--- a/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java
@@ -23,29 +23,43 @@
 import static org.onlab.packet.PacketUtils.checkBufferLength;
 import static org.onlab.packet.PacketUtils.checkInput;
 
+/**
+ * PIM HELLO option.
+ */
 public class PIMHelloOption {
 
-    /**
+    /*
      * PIM Option types.
      */
     public static final short OPT_HOLDTIME = 1;
-    public static final short OPT_PRUNEDELAY = 2;
-    public static final short OPT_PRIORITY = 19;
-    public static final short OPT_GENID = 20;
-    public static final short OPT_ADDRLIST = 24;
-
+    public static final short HOLDTIME_LENGTH = 2;
     public static final short DEFAULT_HOLDTIME = 105;
-    public static final int DEFAULT_PRUNEDELAY = 2000; // 2,000 ms
+
+    public static final short OPT_PRUNEDELAY = 2;
+    public static final short PRUNEDELAY_LENGTH = 4;
+    public static final short DEFAULT_PRUNEDELAY = 500; // 500 ms
+    public static final short DEFAULT_OVERRIDEINTERVAL = 2500; // 2500 ms
+
+    public static final short OPT_PRIORITY = 19;
+    public static final short PRIORITY_LENGTH = 4;
     public static final int DEFAULT_PRIORITY = 1;
+
+    public static final short OPT_GENID = 20;
+    public static final short GENID_LENGTH = 4;
     public static final int DEFAULT_GENID = 0;
 
+    public static final short OPT_ADDRLIST = 24;
+
     public static final int MINIMUM_OPTION_LEN_BYTES = 4;
 
     // Values for this particular hello option.
-    private short optType;
-    private short optLength;
+    private short optType = 0;
+    private short optLength = 0;
     private byte[] optValue;
 
+    /**
+     * Constructs a new hello option with no fields set.
+     */
     public PIMHelloOption() {
     }
 
@@ -59,25 +73,25 @@
         this.optType = type;
         switch (type) {
             case OPT_HOLDTIME:
-                this.optLength = 2;
+                this.optLength = HOLDTIME_LENGTH;
                 this.optValue = new byte[optLength];
                 ByteBuffer.wrap(this.optValue).putShort(PIMHelloOption.DEFAULT_HOLDTIME);
                 break;
 
             case OPT_PRUNEDELAY:
-                this.optLength = 4;
+                this.optLength = PRUNEDELAY_LENGTH;
                 this.optValue = new byte[this.optLength];
                 ByteBuffer.wrap(this.optValue).putInt(PIMHelloOption.DEFAULT_PRUNEDELAY);
                 break;
 
             case OPT_PRIORITY:
-                this.optLength = 4;
+                this.optLength = PRIORITY_LENGTH;
                 this.optValue = new byte[this.optLength];
                 ByteBuffer.wrap(this.optValue).putInt(PIMHelloOption.DEFAULT_PRIORITY);
                 break;
 
             case OPT_GENID:
-                this.optLength = 4;
+                this.optLength = GENID_LENGTH;
                 this.optValue = new byte[this.optLength];
                 ByteBuffer.wrap(this.optValue).putInt(PIMHelloOption.DEFAULT_GENID);
                 break;
@@ -109,17 +123,85 @@
         return this.optLength;
     }
 
-    public void setValue(ByteBuffer bb) throws DeserializationException {
+    public void setValue(ByteBuffer bb) {
         this.optValue = new byte[this.optLength];
         bb.get(this.optValue, 0, this.optLength);
     }
 
+    public void setValue(byte[] value) {
+        this.optValue = value;
+    }
+
     public byte[] getValue() {
         return this.optValue;
     }
 
+    /**
+     * Creates a new PIM Hello option with the specified values.
+     *
+     * @param type hello option type
+     * @param length option length
+     * @param value option value
+     * @return new PIM Hello option
+     */
+    public static PIMHelloOption create(short type, short length, ByteBuffer value) {
+        PIMHelloOption option = new PIMHelloOption();
+        option.setOptType(type);
+        option.setOptLength(length);
+        value.rewind();
+        option.setValue(value);
+        return option;
+    }
+
+    /**
+     * Creates a new priority option.
+     *
+     * @param priority priority
+     * @return priority option
+     */
+    public static PIMHelloOption createPriority(int priority) {
+        return create(OPT_PRIORITY, PRIORITY_LENGTH,
+                ByteBuffer.allocate(PRIORITY_LENGTH).putInt(priority));
+    }
+
+    /**
+     * Creates a new hold time option.
+     *
+     * @param holdTime hold time
+     * @return hold time option
+     */
+    public static PIMHelloOption createHoldTime(short holdTime) {
+        return create(OPT_HOLDTIME, HOLDTIME_LENGTH,
+                ByteBuffer.allocate(HOLDTIME_LENGTH).putShort(holdTime));
+    }
+
+    /**
+     * Creates a new generation ID option with a particular generation ID.
+     *
+     * @param genId generation ID value
+     * @return generation ID option
+     */
+    public static PIMHelloOption createGenID(int genId) {
+        return create(OPT_GENID, GENID_LENGTH,
+                ByteBuffer.allocate(GENID_LENGTH).putInt(genId));
+    }
+
+    /**
+     * Creates a new LAN Prune Delay option.
+     *
+     * @param propagationDelay prune delay
+     * @param overrideInterval override interval
+     * @return prune delay option
+     */
+    public static PIMHelloOption createPruneDelay(short propagationDelay, short overrideInterval) {
+        return create(OPT_PRUNEDELAY, PRUNEDELAY_LENGTH,
+                ByteBuffer.allocate(PRUNEDELAY_LENGTH)
+                        .putShort(propagationDelay)
+                        .putShort(overrideInterval));
+    }
+
     public static PIMHelloOption deserialize(ByteBuffer bb) throws DeserializationException {
-        checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), 4);
+        checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), MINIMUM_OPTION_LEN_BYTES);
 
         PIMHelloOption opt = new PIMHelloOption();
         opt.setOptType(bb.getShort());
@@ -132,7 +214,7 @@
     }
 
     public byte[] serialize() {
-        int len = 4 + this.optLength;
+        int len = MINIMUM_OPTION_LEN_BYTES + this.optLength;
         ByteBuffer bb = ByteBuffer.allocate(len);
         bb.putShort(this.optType);
         bb.putShort(this.optLength);