Added the mcast-delete cli command and supporting class methods.
The changes also support withdrawing the associated intents.

Change-Id: I2d81d051c1bbbb5909175ec1544b45e4c208ba92
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java b/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java
new file mode 100644
index 0000000..ded5a1a
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 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.mfwd.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+
+import org.onosproject.mfwd.impl.McastRouteTable;
+
+/**
+ * Delete a multicast route.
+ */
+@Command(scope = "onos", name = "mcast-delete",
+        description = "Delete a multicast route flow")
+public class McastDeleteCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "sAddr",
+            description = "IP Address of the multicast source. '*' can be used for any source (*, G) entry",
+            required = true, multiValued = false)
+    String sAddr = null;
+
+    @Argument(index = 1, name = "gAddr",
+            description = "IP Address of the multicast group",
+            required = true, multiValued = false)
+    String gAddr = null;
+
+    @Override
+    protected void execute() {
+        McastRouteTable mrib = McastRouteTable.getInstance();
+        mrib.removeRoute(sAddr, gAddr);
+    }
+}
+
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
index 69b4dbd..68f3b45 100644
--- a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
@@ -185,6 +185,7 @@
                 return;
             }
 
+            entry.setIntent();
             McastIntentManager im = McastIntentManager.getInstance();
             im.setIntent(entry);
 
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
index 4a96eea..67a6045 100644
--- a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
@@ -22,7 +22,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.Ethernet;
-import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.SinglePointToMultiPointIntent;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.flow.DefaultTrafficSelector;
@@ -54,6 +54,7 @@
      */
     @Deactivate
     public void deactivate() {
+        withdrawAllIntents();
     }
 
     /**
@@ -70,14 +71,15 @@
     /**
      * Install the PointToMultipoint forwarding intent.
      * @param mroute multicast route entry
+     * @return the intent that has been set or null otherwise
      */
-    public void setIntent(McastRoute mroute) {
+    public SinglePointToMultiPointIntent setIntent(McastRoute mroute) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
 
         if (mroute.getIngressPoint() == null ||
                 mroute.getEgressPoints().isEmpty()) {
-            return;
+            return null;
         }
 
         /*
@@ -102,14 +104,27 @@
                         build();
 
         intentService.submit(intent);
-        mroute.setIntent(intent);
+        return intent;
     }
 
     /**
-     * Withdraw the intent from the network.
-     * @param mroute the multicast route representing the intent
+     * Withdraw the intent represented by this route.
+     * @param mroute the mcast route whose intent we want to remove
      */
-    public void withdrawIntent(McastRoute mroute) {
-        Key key = mroute.getIntentKey();
+    public void withdrawIntent(McastRouteBase mroute) {
+        Intent intent = intentService.getIntent(mroute.getIntentKey());
+        intentService.withdraw(intent);
+    }
+
+    /**
+     * Withdraw all intents.
+     *
+     * This will be called from the deactivate method so we don't leave
+     * a mess behind us after we leave.
+     */
+    public void withdrawAllIntents() {
+        for (Intent intent : intentService.getIntents()) {
+            intentService.withdraw(intent);
+        }
     }
 }
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
index e499454..397ce35 100644
--- a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
@@ -18,7 +18,6 @@
 import org.onlab.packet.IpPrefix;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.intent.Key;
-import org.onosproject.net.intent.SinglePointToMultiPointIntent;
 
 import java.util.Set;
 
@@ -87,10 +86,15 @@
     public Set<ConnectPoint> getEgressPoints();
 
     /**
-     * Set the Intent key.
-     * @param intent intent
+     * Have the McastIntentManager create an intent, attempt to
+     * install the intent and then save the key.
      */
-    public void setIntent(SinglePointToMultiPointIntent intent);
+    public void setIntent();
+
+    /**
+     * Withdraw the intent if it has been installed.
+     */
+    public void withdrawIntent();
 
     /**
      * Get the intent key.
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
index 2c00268..d6b86b8 100644
--- a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
@@ -216,6 +216,35 @@
     }
 
     /**
+     * Have the McastIntentManager create and set the intent, then save the intent key.
+     *
+     * If we already have an intent, we will first withdraw the existing intent and
+     * replace it with a new one.  This will support the case where the ingress connectPoint
+     * or group of egress connectPoints change.
+     */
+    public void setIntent() {
+        if (this.intentKey != null) {
+            this.withdrawIntent();
+        }
+        McastIntentManager im = McastIntentManager.getInstance();
+        SinglePointToMultiPointIntent intent = im.setIntent(this);
+        this.intentKey = intent.key();
+    }
+
+    /**
+     * Withdraw the intent and set the key to null.
+     */
+    public void withdrawIntent() {
+        if (intentKey == null) {
+            // nothing to withdraw
+            return;
+        }
+        McastIntentManager im = McastIntentManager.getInstance();
+        im.withdrawIntent(this);
+        this.intentKey = null;
+    }
+
+    /**
      * Pretty Print this Multicast Route.  Works for McastRouteSource and McastRouteGroup.
      * @return pretty string of the multicast route
      */
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
index ccf6472..2159093 100644
--- a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
@@ -56,16 +56,6 @@
     }
 
     /**
-     * Add a new McastRouteSource to this group.
-     *
-     * @param src the multicast source
-     */
-    public void addSource(McastRouteSource src) {
-        checkNotNull(src);
-        this.sources.put(src.getSaddr(), src);
-    }
-
-    /**
      * Find a specific multicast source address for this group.
      * @param saddr the source address
      * @return the multicast source route or null if it does not exist
@@ -81,4 +71,36 @@
     public HashMap<IpPrefix, McastRouteSource> getSources() {
         return this.sources;
     }
+
+    /**
+     * Add a new McastRouteSource to this group.
+     *
+     * @param src the multicast source
+     */
+    public void addSource(McastRouteSource src) {
+        checkNotNull(src);
+        this.sources.put(src.getSaddr(), src);
+    }
+
+    /**
+     * Remove the source with this specific IpPrefix from this group entry.
+     * @param spfx IP Prefix of the source to be removed
+     * @return the source route that was just removed
+     */
+    public McastRouteSource removeSource(IpPrefix spfx) {
+        McastRouteSource src = this.sources.remove(spfx);
+        src.withdrawIntent();
+        return src;
+    }
+
+    /**
+     * Remove all sources from this.
+     */
+    public void removeSources() {
+        for (McastRouteSource src : this.sources.values()) {
+            src.withdrawIntent();
+            this.sources.remove(src.getSaddr());
+        }
+    }
+
 }
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
index 9ef7f7a..bb3cced 100644
--- a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
@@ -89,7 +89,7 @@
      *
      * @param group The McastRouteGroup to save
      */
-    private void storeMcastGroup(McastRouteGroup group) {
+    private void storeGroup(McastRouteGroup group) {
         if (group.isIp4()) {
             mrib4.put(group.getGaddr(), group);
         } else {
@@ -100,12 +100,21 @@
     }
 
     /**
+     * remove the group.
+     * @param group the group to be removed
      */
+    private void removeGroup(McastRouteGroup group) {
+        IpPrefix gpfx = group.getGaddr();
+        if (gpfx.isIp4()) {
+            mrib4.remove(gpfx);
+        } else if (ipv6Enabled) {
+            mrib6.remove(gpfx);
+        }
+    }
+
     /**
      * Add a multicast route to the MRIB.  This function will.
      *
-     * TODO: check the addresses to determine if the have a /mask if not add one.
-     *
      * @param saddr source address * or x.x.x.x or x.x.x.x/y
      * @param gaddr group address x.x.x.x or x.x.x.x/y
      * @return the multicast route
@@ -152,6 +161,7 @@
                 }
             }
         }
+
         /**
          * If the source prefix length is 0 then we have our (*, g) entry, we can
          * just return now.
@@ -182,17 +192,60 @@
     }
 
     /**
-     * Save the multicast group in the multicast route table.
-     * @param group the group address
+     * Delete a multicast route from the MRIB.
+     *
+     * @param saddr source address * or x.x.x.x or x.x.x.x/y
+     * @param gaddr group address x.x.x.x or x.x.x.x/y
      */
-    private void storeGroup(McastRouteGroup group) {
-        if (group.isIp4()) {
-            mrib4.put(group.getGaddr(), group);
-        } else {
+    public void removeRoute(String saddr, String gaddr) {
+        IpPrefix gpfx = IpPrefix.valueOf(gaddr);
+        IpPrefix spfx = IpPrefix.valueOf(0, 0);
+        if (saddr != null && !saddr.equals("*")) {
+            spfx = IpPrefix.valueOf(saddr);
+        }
+        removeRoute(spfx, gpfx);
+    }
 
-            if (ipv6Enabled) {
-                mrib6.put(group.getGaddr(), group);
+    /**
+     * Remove a multicast route.
+     *
+     * @param spfx the source prefix
+     * @param gpfx the group prefix
+     */
+    public void removeRoute(IpPrefix spfx, IpPrefix gpfx) {
+
+        /**
+         * If a group route (*, g) does not exist we will need to make so we
+         * can start attaching our sources to the group entry.
+         */
+        McastRouteGroup group = findMcastGroup(gpfx);
+        if (group == null) {
+            // The group does not exist, we can't remove it.
+            return;
+        }
+
+        /**
+         * If the source prefix length is 0 then we have a (*, g) entry, which
+         * means we will remove this group and all of it's sources. We will
+         * also withdraw it's intent if need be.
+         */
+        if (spfx.prefixLength() > 0) {
+            group.removeSource(spfx);
+
+            /*
+             * Now a little house keeping. If this group has no more sources
+             * nor egress connectPoints git rid of it.
+             */
+            if (group.getSources().size() == 0 &&
+                    group.getEgressPoints().size() == 0) {
+                removeGroup(group);
             }
+
+        } else {
+            // Group remove has been explicitly requested.
+            group.removeSources();
+            group.withdrawIntent();
+            removeGroup(group);
         }
     }
 
diff --git a/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 4048f36..966cb4f 100644
--- a/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -20,6 +20,9 @@
             <action class="org.onosproject.mfwd.cli.McastJoinCommand"/>

         </command>

         <command>

+            <action class="org.onosproject.mfwd.cli.McastDeleteCommand"/>

+        </command>

+        <command>

             <action class="org.onosproject.mfwd.cli.McastShowCommand"/>

         </command>

     </command-bundle>