Add ability to remove patches

Change-Id: Ia777213f4e1a5c3f7b3b583b932dfea7590076f1
diff --git a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/cli/PatchListCommand.java b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/cli/PatchListCommand.java
new file mode 100644
index 0000000..9d8f088
--- /dev/null
+++ b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/cli/PatchListCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+/**
+ * This class defines the cli command for the PatchPanel class. It creates
+ * an instance of the PatchPanelService class to call it's method addPatch().
+ * The command takes 2 parameters, 2 connectPoints.
+ */
+package org.onosproject.patchpanel.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.patchpanel.impl.Patch;
+import org.onosproject.patchpanel.impl.PatchPanelService;
+
+/**
+ * Lists the patches.
+ */
+@Command(scope = "onos", name = "patches",
+         description = "Lists the patches")
+public class PatchListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%s: %s <-> %s";
+
+    @Override
+    protected void execute() {
+        PatchPanelService patchPanelService = get(PatchPanelService.class);
+
+        patchPanelService.getPatches().forEach(this::print);
+    }
+
+    private void print(Patch patch) {
+        print(FORMAT, patch.id(), patch.port1(), patch.port2());
+    }
+
+}
diff --git a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/cli/PatchRemoveCommand.java b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/cli/PatchRemoveCommand.java
new file mode 100644
index 0000000..429cd50
--- /dev/null
+++ b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/cli/PatchRemoveCommand.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+/**
+ * This class defines the cli command for the PatchPanel class. It creates
+ * an instance of the PatchPanelService class to call it's method addPatch().
+ * The command takes 2 parameters, 2 connectPoints.
+ */
+package org.onosproject.patchpanel.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.patchpanel.impl.PatchId;
+import org.onosproject.patchpanel.impl.PatchPanelService;
+
+/**
+ * Removes a patch.
+ */
+@Command(scope = "onos", name = "patch-remove",
+         description = "Removes a patch")
+public class PatchRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "patchId", description = "ID of patch to remove",
+            required = true, multiValued = false)
+    String id = null;
+
+    private PatchPanelService patchPanelService;
+
+    @Override
+    protected void execute() {
+        patchPanelService = get(PatchPanelService.class);
+        boolean done = false;
+
+        PatchId patchId = PatchId.patchId(Integer.parseInt(id));
+
+        done = patchPanelService.removePatch(patchId);
+
+        if (done) {
+            log.info("This patch has been removed");
+        } else {
+            log.info("This patch was NOT removed");
+        }
+
+    }
+
+}
diff --git a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/Patch.java b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/Patch.java
new file mode 100644
index 0000000..3b1c821
--- /dev/null
+++ b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/Patch.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-present 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.patchpanel.impl;
+
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Abstraction of a patch between two ports on a switch.
+ */
+public class Patch {
+
+    private final PatchId patchId;
+    private final ConnectPoint port1;
+    private final ConnectPoint port2;
+
+    /**
+     * Creates a new patch.
+     *
+     * @param patchId patch ID
+     * @param port1 first switch port
+     * @param port2 second switch port
+     */
+    public Patch(PatchId patchId, ConnectPoint port1, ConnectPoint port2) {
+        this.patchId = patchId;
+        this.port1 = port1;
+        this.port2 = port2;
+    }
+
+    /**
+     * Gets the patch ID.
+     *
+     * @return patch ID
+     */
+    public PatchId id() {
+        return patchId;
+    }
+
+    /**
+     * Gets the first connect point.
+     *
+     * @return connect point
+     */
+    public ConnectPoint port1() {
+        return port1;
+    }
+
+    /**
+     * Gets the second connect point.
+     *
+     * @return connect point
+     */
+    public ConnectPoint port2() {
+        return port2;
+    }
+}
diff --git a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchId.java b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchId.java
new file mode 100644
index 0000000..d40cb21
--- /dev/null
+++ b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchId.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-present 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.patchpanel.impl;
+
+import org.onlab.util.Identifier;
+
+/**
+ * Identifier of a patch.
+ */
+public final class PatchId extends Identifier<Integer> {
+
+    private PatchId(int id) {
+        super(id);
+    }
+
+    /**
+     * Creates a new patch based of an integer.
+     *
+     * @param id backing value
+     * @return patch ID
+     */
+    public static PatchId patchId(int id) {
+        return new PatchId(id);
+    }
+}
diff --git a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanel.java b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanel.java
index b08be7e..e10b097 100644
--- a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanel.java
+++ b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanel.java
@@ -16,27 +16,37 @@
 
 package org.onosproject.patchpanel.impl;
 
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
 import org.onosproject.cli.net.ConnectPointCompleter;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.packet.PacketPriority;
-import org.onosproject.net.ConnectPoint;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
-import java.util.List;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * This class acts as a software patch panel application.
@@ -47,6 +57,8 @@
 @Service
 public class PatchPanel implements PatchPanelService {
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     // OSGI: help bundle plugin discover runtime package dependency.
     @SuppressWarnings("unused")
     private ConnectPointCompleter connectPointCompleter;
@@ -57,14 +69,17 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
-    private List<ConnectPoint> previous = new ArrayList<>();
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    private Map<PatchId, Patch> patches;
+
     private ApplicationId appId;
-    private ConnectPoint cp1, cp2;
+
+    private AtomicInteger ids = new AtomicInteger();
 
 
     @Activate
     protected void activate() throws NullPointerException {
+        patches = new HashMap<>();
+
         appId = coreService.registerApplication("org.onosproject.patchpanel");
         log.info("Started");
     }
@@ -75,25 +90,60 @@
     }
 
     @Override
-    public boolean addPatch(ConnectPoint num, ConnectPoint num2) {
-        cp1 = num;
-        cp2 = num2;
-        if ((cp1.port().equals(cp2.port())) || (previous.contains(cp1) || previous.contains(cp2))) {
+    public boolean addPatch(ConnectPoint cp1, ConnectPoint cp2) {
+        checkNotNull(cp1);
+        checkNotNull(cp2);
+
+        checkArgument(cp1.deviceId().equals(cp2.deviceId()), "Ports must be on the same device");
+        checkArgument(!cp1.equals(cp2), "Ports cannot be the same");
+
+        if (patches.values().stream()
+                .filter(patch -> patch.port1().equals(cp1) || patch.port1().equals(cp2)
+                        || patch.port2().equals(cp1) || patch.port2().equals(cp2))
+                .findAny().isPresent()) {
             log.info("One or both of these ports are already in use, NO FLOW");
             return false;
-        } else {
-            previous.add(cp1);
-            previous.add(cp2);
-            setFlowRuleService();
-            return true;
         }
+
+        Patch patch = new Patch(PatchId.patchId(ids.incrementAndGet()), cp1, cp2);
+
+        patches.put(patch.id(), patch);
+
+        setFlowRuleService(patch);
+
+        return true;
     }
 
-    public void setFlowRuleService() {
-        PortNumber outPort = cp2.port();
-        PortNumber inPort = cp1.port();
+    @Override
+    public boolean removePatch(PatchId id) {
+        Patch patch = patches.remove(id);
+        if (patch == null) {
+            return false;
+        }
+
+        removePatchFlows(patch);
+        return true;
+    }
+
+    @Override
+    public Set<Patch> getPatches() {
+        return ImmutableSet.copyOf(patches.values());
+    }
+
+    public void setFlowRuleService(Patch patch) {
+        createFlowRules(patch).forEach(flowRuleService::applyFlowRules);
+    }
+
+    private void removePatchFlows(Patch patch) {
+        createFlowRules(patch).forEach(flowRuleService::removeFlowRules);
+    }
+
+    private Collection<FlowRule> createFlowRules(Patch patch) {
+        DeviceId deviceId = patch.port1().deviceId();
+        PortNumber outPort = patch.port2().port();
+        PortNumber inPort = patch.port1().port();
         FlowRule fr = DefaultFlowRule.builder()
-                .forDevice(cp1.deviceId())
+                .forDevice(deviceId)
                 .withSelector(DefaultTrafficSelector.builder().matchInPort(inPort).build())
                 .withTreatment(DefaultTrafficTreatment.builder().setOutput(outPort).build())
                 .withPriority(PacketPriority.REACTIVE.priorityValue())
@@ -101,15 +151,13 @@
                 .fromApp(appId).build();
 
         FlowRule fr2 = DefaultFlowRule.builder()
-                .forDevice(cp1.deviceId())
+                .forDevice(deviceId)
                 .withSelector(DefaultTrafficSelector.builder().matchInPort(outPort).build())
                 .withTreatment(DefaultTrafficTreatment.builder().setOutput(inPort).build())
                 .withPriority(PacketPriority.REACTIVE.priorityValue())
                 .makePermanent()
                 .fromApp(appId).build();
 
-        flowRuleService.applyFlowRules(fr, fr2);
-
+        return ImmutableList.of(fr, fr2);
     }
-
 }
diff --git a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanelService.java b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanelService.java
index affb6a5..4ece312 100644
--- a/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanelService.java
+++ b/apps/patchpanel/src/main/java/org/onosproject/patchpanel/impl/PatchPanelService.java
@@ -15,8 +15,11 @@
  */
 
 package org.onosproject.patchpanel.impl;
+
 import org.onosproject.net.ConnectPoint;
 
+import java.util.Set;
+
 /**
  * A service for the patch panel application to export and use with the cli.
  */
@@ -30,4 +33,19 @@
      * @return      true if the patch was created, false otherwise
      */
     boolean addPatch(ConnectPoint cp, ConnectPoint cp2);
+
+    /**
+     * Removes an existing patch.
+     *
+     * @param id patch ID
+     * @return true if the patch was removed, otherwise false.
+     */
+    boolean removePatch(PatchId id);
+
+    /**
+     * Gets the set of patches in the system.
+     *
+     * @return set of patches
+     */
+    Set<Patch> getPatches();
 }
diff --git a/apps/patchpanel/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/patchpanel/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index b881168..1db5a5d 100644
--- a/apps/patchpanel/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/patchpanel/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -21,6 +21,12 @@
                 <ref component-id="ConnectPointCompleter"/>
             </completers>
         </command>
+        <command>
+            <action class="org.onosproject.patchpanel.cli.PatchRemoveCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.patchpanel.cli.PatchListCommand"/>
+        </command>
     </command-bundle>
     <bean id="ConnectPointCompleter" class="org.onosproject.cli.net.ConnectPointCompleter"/>
 </blueprint>