Allows duplicating packets when requesting packet-in via PacketService

If false, the original packet is always sent to the controller.
If true, a copy of the packet is sent to the controller, as long as the packet can be duplicated.
If duplication is not supported, the original packet will be sent to the controller.

Change-Id: I566c799bb0afed03c3fae5815aa926b338cad953
diff --git a/cli/src/main/java/org/onosproject/cli/net/PacketRequestsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/PacketRequestsListCommand.java
index 2bf1feb..1755f82 100644
--- a/cli/src/main/java/org/onosproject/cli/net/PacketRequestsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/PacketRequestsListCommand.java
@@ -27,7 +27,7 @@
         description = "Lists packet requests")
 public class PacketRequestsListCommand extends AbstractShellCommand {
 
-    private static final String FMT = "nodeId=%s appId=%s, priority=%s, criteria=%s";
+    private static final String FMT = "nodeId=%s appId=%s, priority=%s, criteria=%s copy=%s";
 
     @Override
     protected void execute() {
@@ -41,7 +41,8 @@
     }
 
     private void print(PacketRequest request) {
-        print(FMT, request.nodeId(), request.appId().name(), request.priority(), request.selector().criteria());
+        print(FMT, request.nodeId(), request.appId().name(), request.priority(),
+                request.selector().criteria(), request.copy());
     }
 
 }
diff --git a/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java b/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java
index 68427f8..2f3d88a 100644
--- a/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java
+++ b/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java
@@ -34,11 +34,11 @@
     private final ApplicationId appId;
     private final NodeId nodeId;
     private final Optional<DeviceId> deviceId;
-
+    private final boolean copy;
 
     /**
      * Creates a new packet request.
-     *  @param selector  traffic selector
+     * @param selector  traffic selector
      * @param priority  intercept priority
      * @param appId     application id
      * @param nodeId    identifier of node where request originated
@@ -46,11 +46,27 @@
      */
     public DefaultPacketRequest(TrafficSelector selector, PacketPriority priority,
                                 ApplicationId appId, NodeId nodeId, Optional<DeviceId> deviceId) {
+        this(selector, priority, appId, nodeId, deviceId, false);
+    }
+
+    /**
+     * Creates a new packet request.
+     * @param selector  traffic selector
+     * @param priority  intercept priority
+     * @param appId     application id
+     * @param nodeId    identifier of node where request originated
+     * @param deviceId  device id
+     * @param copy      copy flag
+     */
+    public DefaultPacketRequest(TrafficSelector selector, PacketPriority priority,
+                                ApplicationId appId, NodeId nodeId, Optional<DeviceId> deviceId,
+                                boolean copy) {
         this.selector = selector;
         this.priority = priority;
         this.appId = appId;
         this.nodeId = nodeId;
         this.deviceId = deviceId;
+        this.copy = copy;
     }
 
     @Override
@@ -78,8 +94,13 @@
     }
 
     @Override
+    public boolean copy() {
+        return copy;
+    }
+
+    @Override
     public int hashCode() {
-        return Objects.hash(selector, priority, appId, nodeId, deviceId);
+        return Objects.hash(selector, priority, appId, nodeId, deviceId, copy);
     }
 
     @Override
@@ -95,7 +116,8 @@
                 && Objects.equals(this.priority, other.priority)
                 && Objects.equals(this.appId, other.appId)
                 && Objects.equals(this.nodeId, other.nodeId)
-                && Objects.equals(this.deviceId, other.deviceId);
+                && Objects.equals(this.deviceId, other.deviceId)
+                && Objects.equals(this.copy, other.copy);
     }
 
     @Override
@@ -106,6 +128,7 @@
                 .add("appId", appId)
                 .add("nodeId", nodeId)
                 .add("applies to", deviceId.map(DeviceId::toString).orElse("all"))
+                .add("copy", copy)
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java b/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java
index 01116d8..2249102 100644
--- a/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java
+++ b/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java
@@ -28,28 +28,28 @@
 public interface PacketRequest {
 
     /**
-     * Obtain the traffic selector.
+     * Obtains the traffic selector.
      *
      * @return a traffic selector
      */
     TrafficSelector selector();
 
     /**
-     * Obtain the priority.
+     * Obtains the priority.
      *
      * @return a PacketPriority
      */
     PacketPriority priority();
 
     /**
-     * Obtain the application id.
+     * Obtains the application id.
      *
      * @return an application id
      */
     ApplicationId appId();
 
     /**
-     * Obtain the node id.
+     * Obtains the node id.
      *
      * @return an node id
      */
@@ -62,4 +62,11 @@
      */
     Optional<DeviceId> deviceId();
 
+    /**
+     * Obtains copy flag.
+     *
+     * @return true if copy flag is set
+     */
+    boolean copy();
+
 }
diff --git a/core/api/src/main/java/org/onosproject/net/packet/PacketService.java b/core/api/src/main/java/org/onosproject/net/packet/PacketService.java
index adaea07..88bbbb5 100644
--- a/core/api/src/main/java/org/onosproject/net/packet/PacketService.java
+++ b/core/api/src/main/java/org/onosproject/net/packet/PacketService.java
@@ -69,6 +69,22 @@
     void requestPackets(TrafficSelector selector, PacketPriority priority,
                         ApplicationId appId);
 
+    /**
+     * Requests that packets matching the given selector are punted from the
+     * dataplane to the controller.
+     *
+     * @param selector the traffic selector used to match packets
+     * @param priority the priority of the rule
+     * @param appId    the application ID of the requester
+     * @param copy     request a copy of the matching packet to be punted to the controller.
+     *                 <p>
+     *                 If false, the original packet is always sent to the controller.
+     *                 If true, a copy of the packet is sent to the controller,
+     *                 as long as the packet can be duplicated.
+     *                 If duplication is not supported, the original packet will be sent to the controller.
+     */
+    void requestPackets(TrafficSelector selector, PacketPriority priority,
+                        ApplicationId appId, boolean copy);
 
     /**
      * Requests that packets matching the given selector are punted from the
@@ -97,6 +113,23 @@
 
     /**
      * Cancels previous packet requests for packets matching the given
+     * selector to be punted from the dataplane to the controller.
+     *
+     * @param selector the traffic selector used to match packets
+     * @param priority the priority of the rule
+     * @param appId    the application ID of the requester
+     * @param copy     request a copy of the matching packet to be punted to the controller.
+     *                 <p>
+     *                 If false, the original packet is always sent to the controller.
+     *                 If true, a copy of the packet is sent to the controller,
+     *                 as long as the packet can be duplicated.
+     *                 If duplication is not supported, the original packet will be sent to the controller.
+     */
+    void cancelPackets(TrafficSelector selector, PacketPriority priority,
+                       ApplicationId appId, boolean copy);
+
+    /**
+     * Cancels previous packet requests for packets matching the given
      * selector to be punted from the dataplane to the controller. If a
      * deviceId is specified then the packet request is only withdrawn from
      * the device represented by that deviceId.
diff --git a/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java
index 22d4940..7631c03 100644
--- a/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java
@@ -51,6 +51,11 @@
 
     @Override
     public void requestPackets(TrafficSelector selector, PacketPriority priority,
+                               ApplicationId appId, boolean copy) {
+    }
+
+    @Override
+    public void requestPackets(TrafficSelector selector, PacketPriority priority,
                                ApplicationId appId, Optional<DeviceId> deviceId) {
 
     }
@@ -62,6 +67,11 @@
 
     @Override
     public void cancelPackets(TrafficSelector selector, PacketPriority priority,
+                              ApplicationId appId, boolean copy) {
+    }
+
+    @Override
+    public void cancelPackets(TrafficSelector selector, PacketPriority priority,
                               ApplicationId appId, Optional<DeviceId> deviceId) {
 
     }
diff --git a/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java b/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java
index f809a1f..1fe10a5 100644
--- a/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java
+++ b/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java
@@ -34,6 +34,7 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
@@ -180,12 +181,19 @@
     @Override
     public void requestPackets(TrafficSelector selector, PacketPriority priority,
                                ApplicationId appId) {
+        this.requestPackets(selector, priority, appId, false);
+    }
+
+    @Override
+    public void requestPackets(TrafficSelector selector, PacketPriority priority,
+                               ApplicationId appId, boolean copy) {
         checkPermission(PACKET_READ);
         checkNotNull(selector, ERROR_NULL_SELECTOR);
         checkNotNull(appId, ERROR_NULL_APP_ID);
 
         PacketRequest request = new DefaultPacketRequest(selector, priority, appId,
-                                                         localNodeId, Optional.empty());
+                localNodeId, Optional.empty(), copy);
+
         store.requestPackets(request);
     }
 
@@ -202,19 +210,23 @@
                                          localNodeId, deviceId);
 
         store.requestPackets(request);
-
     }
 
     @Override
     public void cancelPackets(TrafficSelector selector, PacketPriority priority,
                               ApplicationId appId) {
+        this.cancelPackets(selector, priority, appId, false);
+    }
+
+    @Override
+    public void cancelPackets(TrafficSelector selector, PacketPriority priority,
+                              ApplicationId appId, boolean copy) {
         checkPermission(PACKET_READ);
         checkNotNull(selector, ERROR_NULL_SELECTOR);
         checkNotNull(appId, ERROR_NULL_APP_ID);
 
-
         PacketRequest request = new DefaultPacketRequest(selector, priority, appId,
-                                                         localNodeId, Optional.empty());
+                localNodeId, Optional.empty(), copy);
         store.cancelPackets(request);
     }
 
@@ -321,12 +333,18 @@
     }
 
     private DefaultForwardingObjective.Builder createBuilder(PacketRequest request) {
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.punt();
+        if (!request.copy()) {
+            tBuilder.wipeDeferred();
+        }
+
         return DefaultForwardingObjective.builder()
                 .withPriority(request.priority().priorityValue())
                 .withSelector(request.selector())
                 .fromApp(appId)
                 .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withTreatment(DefaultTrafficTreatment.builder().punt().build())
+                .withTreatment(tBuilder.build())
                 .makePermanent();
     }