Add support for P4Runtime clone sessions via Group API

Clone sessions can now be created by defining groups with new type CLONE

The PI framework has been refactored to abstract commonality between
multicast groups and clone sessions as both are managed as part of the
P4Runtime packet replication engine (PRE).

Change-Id: I2f23c629b7de1931d5cab96ec76aef26130ce418
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CloneSessionEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CloneSessionEntryCodec.java
new file mode 100644
index 0000000..337d76a
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CloneSessionEntryCodec.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.p4runtime.ctl.codec;
+
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiCloneSessionEntry;
+import org.onosproject.net.pi.runtime.PiCloneSessionEntryHandle;
+import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
+import p4.v1.P4RuntimeOuterClass;
+
+import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS;
+
+/**
+ * Codec for P4Runtime CloneSessionEntry.
+ */
+public final class CloneSessionEntryCodec
+        extends AbstractEntityCodec<PiCloneSessionEntry, PiCloneSessionEntryHandle,
+        P4RuntimeOuterClass.CloneSessionEntry, Object> {
+
+    @Override
+    protected P4RuntimeOuterClass.CloneSessionEntry encode(
+            PiCloneSessionEntry piEntity, Object ignored,
+            PiPipeconf pipeconf, P4InfoBrowser browser) throws CodecException {
+        return P4RuntimeOuterClass.CloneSessionEntry.newBuilder()
+                .setSessionId(piEntity.sessionId())
+                .addAllReplicas(
+                        CODECS.preReplica().encodeAll(
+                                piEntity.replicas(), null, pipeconf))
+                .setClassOfService(piEntity.classOfService())
+                .setPacketLengthBytes(piEntity.maxPacketLengthBytes())
+                .build();
+    }
+
+    @Override
+    protected P4RuntimeOuterClass.CloneSessionEntry encodeKey(
+            PiCloneSessionEntryHandle handle, Object metadata,
+            PiPipeconf pipeconf, P4InfoBrowser browser) {
+        return P4RuntimeOuterClass.CloneSessionEntry.newBuilder()
+                .setSessionId(handle.sessionId()).build();
+    }
+
+    @Override
+    protected P4RuntimeOuterClass.CloneSessionEntry encodeKey(
+            PiCloneSessionEntry piEntity, Object metadata,
+            PiPipeconf pipeconf, P4InfoBrowser browser) {
+        return P4RuntimeOuterClass.CloneSessionEntry.newBuilder()
+                .setSessionId(piEntity.sessionId()).build();
+    }
+
+    @Override
+    protected PiCloneSessionEntry decode(
+            P4RuntimeOuterClass.CloneSessionEntry message, Object ignored,
+            PiPipeconf pipeconf, P4InfoBrowser browser) throws CodecException {
+        return PiCloneSessionEntry.builder()
+                .withSessionId(message.getSessionId())
+                .addReplicas(
+                        CODECS.preReplica().decodeAll(
+                                message.getReplicasList(), null, pipeconf))
+                .withClassOfService(message.getClassOfService())
+                .withMaxPacketLengthBytes(message.getPacketLengthBytes())
+                .build();
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java
index 771f5da..30e8d9d 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java
@@ -34,6 +34,8 @@
     private final HandleCodec handle;
     private final MeterEntryCodec meterEntry;
     private final MulticastGroupEntryCodec multicastGroupEntry;
+    private final CloneSessionEntryCodec cloneSessionEntry;
+    private final PreReplicaCodec preReplica;
     private final PacketInCodec packetIn;
     private final PacketMetadataCodec packetMetadata;
     private final PacketOutCodec packetOut;
@@ -51,6 +53,8 @@
         this.handle = new HandleCodec();
         this.meterEntry = new MeterEntryCodec();
         this.multicastGroupEntry = new MulticastGroupEntryCodec();
+        this.cloneSessionEntry = new CloneSessionEntryCodec();
+        this.preReplica = new PreReplicaCodec();
         this.packetIn = new PacketInCodec();
         this.packetMetadata = new PacketMetadataCodec();
         this.packetOut = new PacketOutCodec();
@@ -101,6 +105,14 @@
         return multicastGroupEntry;
     }
 
+    CloneSessionEntryCodec cloneSessionEntry() {
+        return cloneSessionEntry;
+    }
+
+    PreReplicaCodec preReplica() {
+        return preReplica;
+    }
+
     DirectMeterEntryCodec directMeterEntry() {
         return directMeterEntry;
     }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java
index dc10419..e063faa 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java
@@ -19,10 +19,12 @@
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiActionProfileGroup;
 import org.onosproject.net.pi.runtime.PiActionProfileMember;
+import org.onosproject.net.pi.runtime.PiCloneSessionEntry;
 import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiEntity;
 import org.onosproject.net.pi.runtime.PiMeterCellConfig;
 import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiPreEntry;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
 import p4.v1.P4RuntimeOuterClass;
@@ -56,13 +58,29 @@
                         CODECS.actionProfileMember().encode(
                                 (PiActionProfileMember) piEntity, null, pipeconf))
                         .build();
-            case PRE_MULTICAST_GROUP_ENTRY:
-                return p4Entity.setPacketReplicationEngineEntry(
-                        P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder()
-                                .setMulticastGroupEntry(CODECS.multicastGroupEntry().encode(
-                                        (PiMulticastGroupEntry) piEntity, null, pipeconf))
-                                .build())
-                        .build();
+            case PRE_ENTRY:
+                final PiPreEntry preEntry = (PiPreEntry) piEntity;
+                switch (preEntry.preEntryType()) {
+                    case MULTICAST_GROUP:
+                        return p4Entity.setPacketReplicationEngineEntry(
+                                P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder()
+                                        .setMulticastGroupEntry(CODECS.multicastGroupEntry().encode(
+                                                (PiMulticastGroupEntry) piEntity, null, pipeconf))
+                                        .build())
+                                .build();
+                    case CLONE_SESSION:
+                        return p4Entity.setPacketReplicationEngineEntry(
+                                P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder()
+                                        .setCloneSessionEntry(CODECS.cloneSessionEntry().encode(
+                                                (PiCloneSessionEntry) piEntity, null, pipeconf))
+                                        .build())
+                                .build();
+                    default:
+                        throw new CodecException(format(
+                                "Encoding of %s of type %s is not supported",
+                                piEntity.piEntityType(),
+                                preEntry.preEntryType()));
+                }
             case METER_CELL_CONFIG:
                 final PiMeterCellConfig meterCellConfig = (PiMeterCellConfig) piEntity;
                 switch (meterCellConfig.cellId().meterType()) {
@@ -102,7 +120,6 @@
                                 counterCell.cellId().counterType()));
                 }
             case REGISTER_CELL:
-            case PRE_CLONE_SESSION_ENTRY:
             default:
                 throw new CodecException(format(
                         "Encoding of %s not supported",
@@ -113,7 +130,7 @@
     @Override
     protected PiEntity decode(
             P4RuntimeOuterClass.Entity message, Object ignored, PiPipeconf pipeconf, P4InfoBrowser browser)
-            throws CodecException, P4InfoBrowser.NotFoundException {
+            throws CodecException {
         switch (message.getEntityCase()) {
             case TABLE_ENTRY:
                 return CODECS.tableEntry().decode(
@@ -143,6 +160,9 @@
                                 message.getPacketReplicationEngineEntry()
                                         .getMulticastGroupEntry(), null, pipeconf);
                     case CLONE_SESSION_ENTRY:
+                        return CODECS.cloneSessionEntry().decode(
+                                message.getPacketReplicationEngineEntry()
+                                        .getCloneSessionEntry(), null, pipeconf);
                     case TYPE_NOT_SET:
                     default:
                         throw new CodecException(format(
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java
index dc617cf..7a9c727 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java
@@ -19,10 +19,12 @@
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiActionProfileGroupHandle;
 import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle;
+import org.onosproject.net.pi.runtime.PiCloneSessionEntryHandle;
 import org.onosproject.net.pi.runtime.PiCounterCellHandle;
 import org.onosproject.net.pi.runtime.PiHandle;
 import org.onosproject.net.pi.runtime.PiMeterCellHandle;
 import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
+import org.onosproject.net.pi.runtime.PiPreEntryHandle;
 import org.onosproject.net.pi.runtime.PiTableEntryHandle;
 import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
 import p4.v1.P4RuntimeOuterClass;
@@ -55,13 +57,29 @@
                         CODECS.actionProfileMember().encodeKey(
                                 (PiActionProfileMemberHandle) piHandle, null, pipeconf))
                         .build();
-            case PRE_MULTICAST_GROUP_ENTRY:
-                return p4Entity.setPacketReplicationEngineEntry(
-                        P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder()
-                                .setMulticastGroupEntry(CODECS.multicastGroupEntry().encodeKey(
-                                        (PiMulticastGroupEntryHandle) piHandle, null, pipeconf))
-                                .build())
-                        .build();
+            case PRE_ENTRY:
+                final PiPreEntryHandle preEntryHandle = (PiPreEntryHandle) piHandle;
+                switch (preEntryHandle.preEntryType()) {
+                    case MULTICAST_GROUP:
+                        return p4Entity.setPacketReplicationEngineEntry(
+                                P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder()
+                                        .setMulticastGroupEntry(CODECS.multicastGroupEntry().encodeKey(
+                                                (PiMulticastGroupEntryHandle) piHandle, null, pipeconf))
+                                        .build())
+                                .build();
+                    case CLONE_SESSION:
+                        return p4Entity.setPacketReplicationEngineEntry(
+                                P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder()
+                                        .setCloneSessionEntry(CODECS.cloneSessionEntry().encodeKey(
+                                                (PiCloneSessionEntryHandle) piHandle, null, pipeconf))
+                                        .build())
+                                .build();
+                    default:
+                        throw new CodecException(format(
+                                "Encoding of handle for %s of type %s is not supported",
+                                piHandle.entityType(),
+                                preEntryHandle.preEntryType()));
+                }
             case METER_CELL_CONFIG:
                 final PiMeterCellHandle meterCellHandle = (PiMeterCellHandle) piHandle;
                 switch (meterCellHandle.cellId().meterType()) {
@@ -101,7 +119,6 @@
                                 counterCellHandle.cellId().counterType()));
                 }
             case REGISTER_CELL:
-            case PRE_CLONE_SESSION_ENTRY:
             default:
                 throw new CodecException(format(
                         "Encoding of handle for %s not supported",
@@ -113,7 +130,7 @@
     protected PiHandle decode(
             P4RuntimeOuterClass.Entity message, Object ignored,
             PiPipeconf pipeconf, P4InfoBrowser browser)
-            throws CodecException, P4InfoBrowser.NotFoundException {
+            throws CodecException {
         throw new CodecException("Decoding of Entity to PiHandle is not supported");
     }
 }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java
index 38080cc..bcc4555 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java
@@ -16,16 +16,13 @@
 
 package org.onosproject.p4runtime.ctl.codec;
 
-import org.onosproject.net.PortNumber;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
 import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
-import org.onosproject.net.pi.runtime.PiPreReplica;
 import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
 import p4.v1.P4RuntimeOuterClass;
-import p4.v1.P4RuntimeOuterClass.Replica;
 
-import static java.lang.String.format;
+import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS;
 
 /**
  * Codec for P4Runtime MulticastGroupEntry.
@@ -38,25 +35,12 @@
     protected P4RuntimeOuterClass.MulticastGroupEntry encode(
             PiMulticastGroupEntry piEntity, Object ignored,
             PiPipeconf pipeconf, P4InfoBrowser browser) throws CodecException {
-        final P4RuntimeOuterClass.MulticastGroupEntry.Builder msgBuilder =
-                P4RuntimeOuterClass.MulticastGroupEntry.newBuilder()
-                        .setMulticastGroupId(piEntity.groupId());
-        for (PiPreReplica replica : piEntity.replicas()) {
-            final int p4PortId;
-            try {
-                p4PortId = Math.toIntExact(replica.egressPort().toLong());
-            } catch (ArithmeticException e) {
-                throw new CodecException(format(
-                        "Cannot cast 64 bit port value '%s' to 32 bit",
-                        replica.egressPort()));
-            }
-            msgBuilder.addReplicas(
-                    Replica.newBuilder()
-                            .setEgressPort(p4PortId)
-                            .setInstance(replica.instanceId())
-                            .build());
-        }
-        return msgBuilder.build();
+        return P4RuntimeOuterClass.MulticastGroupEntry.newBuilder()
+                .setMulticastGroupId(piEntity.groupId())
+                .addAllReplicas(
+                        CODECS.preReplica().encodeAll(
+                                piEntity.replicas(), null, pipeconf))
+                .build();
     }
 
     @Override
@@ -78,13 +62,12 @@
     @Override
     protected PiMulticastGroupEntry decode(
             P4RuntimeOuterClass.MulticastGroupEntry message, Object ignored,
-            PiPipeconf pipeconf, P4InfoBrowser browser) {
-        final PiMulticastGroupEntry.Builder piEntryBuilder = PiMulticastGroupEntry.builder();
-        piEntryBuilder.withGroupId(message.getMulticastGroupId());
-        message.getReplicasList().stream()
-                .map(r -> new PiPreReplica(
-                        PortNumber.portNumber(r.getEgressPort()), r.getInstance()))
-                .forEach(piEntryBuilder::addReplica);
-        return piEntryBuilder.build();
+            PiPipeconf pipeconf, P4InfoBrowser browser) throws CodecException {
+        return PiMulticastGroupEntry.builder()
+                .withGroupId(message.getMulticastGroupId())
+                .addReplicas(
+                        CODECS.preReplica().decodeAll(
+                                message.getReplicasList(), null, pipeconf))
+                .build();
     }
 }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PreReplicaCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PreReplicaCodec.java
new file mode 100644
index 0000000..7402b14
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PreReplicaCodec.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.p4runtime.ctl.codec;
+
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiPreReplica;
+import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
+import p4.v1.P4RuntimeOuterClass;
+
+import static java.lang.String.format;
+
+/**
+ * Codec for P4Runtime PRE Replica.
+ */
+public class PreReplicaCodec extends AbstractCodec<PiPreReplica,
+        P4RuntimeOuterClass.Replica, Object> {
+
+    @Override
+    protected P4RuntimeOuterClass.Replica encode(
+            PiPreReplica replica, Object ignore,
+            PiPipeconf pipeconf, P4InfoBrowser browser)
+            throws CodecException, P4InfoBrowser.NotFoundException {
+        final int p4PortId;
+        try {
+            p4PortId = Math.toIntExact(replica.egressPort().toLong());
+        } catch (ArithmeticException e) {
+            throw new CodecException(format(
+                    "Cannot cast 64 bit port value '%s' to 32 bit",
+                    replica.egressPort()));
+        }
+        return P4RuntimeOuterClass.Replica.newBuilder()
+                .setEgressPort(p4PortId)
+                .setInstance(replica.instanceId())
+                .build();
+    }
+
+    @Override
+    protected PiPreReplica decode(
+            P4RuntimeOuterClass.Replica message, Object ignore,
+            PiPipeconf pipeconf, P4InfoBrowser browser)
+            throws CodecException, P4InfoBrowser.NotFoundException {
+        return new PiPreReplica(
+                PortNumber.portNumber(message.getEgressPort()),
+                message.getInstance());
+    }
+}