Refactor of UpfProgrammable APIs

Change-Id: I792659ad4a163d7115d7320bb33c11534edd484a
Signed-off-by: Daniele Moro <daniele@opennetworking.org>
(cherry picked from commit a57652d92bdd01b1e77bffbac78a44f96fb385f3)
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java
deleted file mode 100644
index bfed1ce..0000000
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright 2021-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.net.behaviour.upf;
-
-import org.onlab.packet.Ip4Address;
-import org.onlab.util.ImmutableByteSequence;
-
-import java.util.Objects;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * A single Forwarding Action Rule (FAR), an entity described in the 3GPP
- * specifications (although that does not mean that this class is 3GPP
- * compliant). An instance of this class will be generated by a logical switch
- * write request to the database-style FAR P4 table, and the resulting instance
- * should contain all the information needed to reproduce that logical switch
- * FAR in the event of a client read request. The instance should also contain
- * sufficient information (or expose the means to retrieve such information) to
- * generate the corresponding dataplane forwarding state that implements the FAR.
- */
-public final class ForwardingActionRule {
-    // Match Keys
-    private final ImmutableByteSequence sessionId;  // The PFCP session identifier that created this FAR
-    private final int farId;  // PFCP session-local identifier for this FAR
-    // Action parameters
-    private final boolean notifyFlag;  // Should this FAR notify the control plane when it sees a packet?
-    private final boolean dropFlag;
-    private final boolean bufferFlag;
-    private final GtpTunnel tunnel;  // The GTP tunnel that this FAR should encapsulate packets with (if downlink)
-
-    private static final int SESSION_ID_BITWIDTH = 96;
-
-    private ForwardingActionRule(ImmutableByteSequence sessionId, Integer farId,
-                                 boolean notifyFlag, GtpTunnel tunnel, boolean dropFlag, boolean bufferFlag) {
-        this.sessionId = sessionId;
-        this.farId = farId;
-        this.notifyFlag = notifyFlag;
-        this.tunnel = tunnel;
-        this.dropFlag = dropFlag;
-        this.bufferFlag = bufferFlag;
-    }
-
-    /**
-     * Return a new instance of this FAR with the action parameters stripped, leaving only the match keys.
-     *
-     * @return a new FAR with only match keys
-     */
-    public ForwardingActionRule withoutActionParams() {
-        return ForwardingActionRule.builder()
-                .setFarId(farId)
-                .withSessionId(sessionId)
-                .build();
-    }
-
-    public static Builder builder() {
-        return new Builder();
-    }
-
-    /**
-     * Return a string representing the dataplane action applied by this FAR.
-     *
-     * @return a string representing the FAR action
-     */
-    public String actionString() {
-        String actionName;
-        String actionParams = "";
-        if (dropFlag) {
-            actionName = "Drop";
-        } else if (bufferFlag) {
-            actionName = "Buffer";
-        } else if (tunnel != null) {
-            actionName = "Encap";
-            actionParams = String.format("Src=%s, SPort=%d, TEID=%s, Dst=%s",
-                                         tunnel.src().toString(), tunnel.srcPort(),
-                                         tunnel.teid().toString(), tunnel.dst().toString());
-        } else {
-            actionName = "Forward";
-        }
-        if (notifyFlag) {
-            actionName += "+NotifyCP";
-        }
-
-        return String.format("%s(%s)", actionName, actionParams);
-    }
-
-    @Override
-    public String toString() {
-        String matchKeys = String.format("ID=%d, SEID=%s", farId, sessionId.toString());
-        String actionString = actionString();
-
-        return String.format("FAR{Match(%s) -> %s}", matchKeys, actionString);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        ForwardingActionRule that = (ForwardingActionRule) obj;
-
-        // Safe comparisons between potentially null objects
-        return (this.dropFlag == that.dropFlag &&
-                this.bufferFlag == that.bufferFlag &&
-                this.notifyFlag == that.notifyFlag &&
-                this.farId == that.farId &&
-                Objects.equals(this.tunnel, that.tunnel) &&
-                Objects.equals(this.sessionId, that.sessionId));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(sessionId, farId, notifyFlag, tunnel, dropFlag, bufferFlag);
-    }
-
-    /**
-     * Get the ID of the PFCP Session that produced this FAR.
-     *
-     * @return PFCP session ID
-     */
-    public ImmutableByteSequence sessionId() {
-        return sessionId;
-    }
-
-    /**
-     * Get the PFCP session-local ID of the FAR that should apply to packets that match this PDR.
-     *
-     * @return PFCP session-local FAR ID
-     */
-    public int farId() {
-        return farId;
-    }
-
-    /**
-     * True if this FAR does not drop packets.
-     *
-     * @return true if FAR is forwards
-     */
-    public boolean forwards() {
-        return !dropFlag;
-    }
-
-    /**
-     * True if this FAR encapsulates packets in a GTP tunnel, and false otherwise.
-     *
-     * @return true is FAR encapsulates
-     */
-    public boolean encaps() {
-        return tunnel != null;
-    }
-
-    /**
-     * Returns true if this FAR drops packets, and false otherwise.
-     *
-     * @return true if this FAR drops
-     */
-    public boolean drops() {
-        return dropFlag;
-    }
-
-    /**
-     * Returns true if this FAR notifies the control plane on receiving a packet, and false otherwise.
-     *
-     * @return true if this FAR notifies the cp
-     */
-    public boolean notifies() {
-        return notifyFlag;
-    }
-
-
-    /**
-     * Returns true if this FAR buffers incoming packets, and false otherwise.
-     *
-     * @return true if this FAR buffers
-     */
-    public boolean buffers() {
-        return bufferFlag;
-    }
-
-    /**
-     * A description of the tunnel that this FAR will encapsulate packets with, if it is a downlink FAR. If the FAR
-     * is uplink, there will be no such tunnel and this method wil return null.
-     *
-     * @return A GtpTunnel instance containing a tunnel sourceIP, destIP, and GTPU TEID, or null if the FAR is uplink.
-     */
-    public GtpTunnel tunnel() {
-        return tunnel;
-    }
-
-    /**
-     * Get the source UDP port of the GTP tunnel that this FAR will encapsulate packets with.
-     *
-     * @return GTP tunnel source UDP port
-     */
-    public Short tunnelSrcPort() {
-        return tunnel != null ? tunnel.srcPort() : null;
-    }
-
-    /**
-     * Get the source IP of the GTP tunnel that this FAR will encapsulate packets with.
-     *
-     * @return GTP tunnel source IP
-     */
-    public Ip4Address tunnelSrc() {
-        if (tunnel == null) {
-            return null;
-        }
-        return tunnel.src();
-    }
-
-    /**
-     * Get the destination IP of the GTP tunnel that this FAR will encapsulate packets with.
-     *
-     * @return GTP tunnel destination IP
-     */
-    public Ip4Address tunnelDst() {
-        if (tunnel == null) {
-            return null;
-        }
-        return tunnel.dst();
-    }
-
-    /**
-     * Get the identifier of the GTP tunnel that this FAR will encapsulate packets with.
-     *
-     * @return GTP tunnel ID
-     */
-    public ImmutableByteSequence teid() {
-        if (tunnel == null) {
-            return null;
-        }
-        return tunnel.teid();
-    }
-
-    public static class Builder {
-        private ImmutableByteSequence sessionId = null;
-        private Integer farId = null;
-        private GtpTunnel tunnel = null;
-        private boolean dropFlag = false;
-        private boolean bufferFlag = false;
-        private boolean notifyCp = false;
-
-        public Builder() {
-        }
-
-        /**
-         * Set the ID of the PFCP session that created this FAR.
-         *
-         * @param sessionId PFC session ID
-         * @return This builder object
-         */
-        public Builder withSessionId(ImmutableByteSequence sessionId) {
-            this.sessionId = sessionId;
-            return this;
-        }
-
-        /**
-         * Set the ID of the PFCP session that created this FAR.
-         *
-         * @param sessionId PFC session ID
-         * @return This builder object
-         */
-        public Builder withSessionId(long sessionId) {
-            try {
-                this.sessionId = ImmutableByteSequence.copyFrom(sessionId).fit(SESSION_ID_BITWIDTH);
-            } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
-                // This error is literally impossible
-            }
-            return this;
-        }
-
-        /**
-         * Set the PFCP Session-local ID of this FAR.
-         *
-         * @param farId PFCP session-local FAR ID
-         * @return This builder object
-         */
-        public Builder setFarId(int farId) {
-            this.farId = farId;
-            return this;
-        }
-
-        /**
-         * Make this FAR forward incoming packets.
-         *
-         * @param flag the flag value to set
-         * @return This builder object
-         */
-        public Builder setForwardFlag(boolean flag) {
-            this.dropFlag = !flag;
-            return this;
-        }
-
-        /**
-         * Make this FAR drop incoming packets.
-         *
-         * @param flag the flag value to set
-         * @return This builder object
-         */
-        public Builder setDropFlag(boolean flag) {
-            this.dropFlag = flag;
-            return this;
-        }
-
-        /**
-         * Make this FAR buffer incoming packets.
-         *
-         * @param flag the flag value to set
-         * @return This builder object
-         */
-        public Builder setBufferFlag(boolean flag) {
-            this.bufferFlag = flag;
-            return this;
-        }
-
-        /**
-         * Set a flag specifying if the control plane should be notified when this FAR is hit.
-         *
-         * @param notifyCp true if FAR notifies control plane
-         * @return This builder object
-         */
-        public Builder setNotifyFlag(boolean notifyCp) {
-            this.notifyCp = notifyCp;
-            return this;
-        }
-
-        /**
-         * Set the GTP tunnel that this FAR should encapsulate packets with.
-         *
-         * @param tunnel GTP tunnel
-         * @return This builder object
-         */
-        public Builder setTunnel(GtpTunnel tunnel) {
-            this.tunnel = tunnel;
-            return this;
-        }
-
-        /**
-         * Set the unidirectional GTP tunnel that this FAR should encapsulate packets with.
-         *
-         * @param src  GTP tunnel source IP
-         * @param dst  GTP tunnel destination IP
-         * @param teid GTP tunnel ID
-         * @return This builder object
-         */
-        public Builder setTunnel(Ip4Address src, Ip4Address dst, ImmutableByteSequence teid) {
-            return this.setTunnel(GtpTunnel.builder()
-                    .setSrc(src)
-                    .setDst(dst)
-                    .setTeid(teid)
-                    .build());
-        }
-
-        /**
-         * Set the unidirectional GTP tunnel that this FAR should encapsulate packets with.
-         *
-         * @param src     GTP tunnel source IP
-         * @param dst     GTP tunnel destination IP
-         * @param teid    GTP tunnel ID
-         * @param srcPort GTP tunnel UDP source port (destination port is hardcoded as 2152)
-         * @return This builder object
-         */
-        public Builder setTunnel(Ip4Address src, Ip4Address dst, ImmutableByteSequence teid, short srcPort) {
-            return this.setTunnel(GtpTunnel.builder()
-                    .setSrc(src)
-                    .setDst(dst)
-                    .setTeid(teid)
-                    .setSrcPort(srcPort)
-                    .build());
-        }
-
-        public ForwardingActionRule build() {
-            // All match keys are required
-            checkNotNull(sessionId, "Session ID is required");
-            checkNotNull(farId, "FAR ID is required");
-            return new ForwardingActionRule(sessionId, farId, notifyCp, tunnel, dropFlag, bufferFlag);
-        }
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnel.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnel.java
deleted file mode 100644
index bc262c7..0000000
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnel.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2021-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.net.behaviour.upf;
-
-import org.onlab.packet.Ip4Address;
-import org.onlab.util.ImmutableByteSequence;
-
-import java.util.Objects;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * A structure representing a unidirectional GTP tunnel.
- */
-public final class GtpTunnel {
-    private final Ip4Address src;  // The source address of the unidirectional tunnel
-    private final Ip4Address dst;  // The destination address of the unidirectional tunnel
-    private final ImmutableByteSequence teid;  // Tunnel Endpoint Identifier
-    private final short srcPort;  // Tunnel destination port, default 2152
-
-    private GtpTunnel(Ip4Address src, Ip4Address dst, ImmutableByteSequence teid,
-                      Short srcPort) {
-        this.src = src;
-        this.dst = dst;
-        this.teid = teid;
-        this.srcPort = srcPort;
-    }
-
-    public static GtpTunnelBuilder builder() {
-        return new GtpTunnelBuilder();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("GTP-Tunnel(%s -> %s, TEID:%s)",
-                             src.toString(), dst.toString(), teid.toString());
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        GtpTunnel that = (GtpTunnel) obj;
-
-        return (this.src.equals(that.src) &&
-                this.dst.equals(that.dst) &&
-                this.teid.equals(that.teid) &&
-                (this.srcPort == that.srcPort));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(src, dst, teid, srcPort);
-    }
-
-    /**
-     * Get the source IP address of this unidirectional GTP tunnel.
-     *
-     * @return tunnel source IP
-     */
-    public Ip4Address src() {
-        return this.src;
-    }
-
-    /**
-     * Get the destination address of this unidirectional GTP tunnel.
-     *
-     * @return tunnel destination IP
-     */
-    public Ip4Address dst() {
-        return this.dst;
-    }
-
-    /**
-     * Get the ID of this unidirectional GTP tunnel.
-     *
-     * @return tunnel ID
-     */
-    public ImmutableByteSequence teid() {
-        return this.teid;
-    }
-
-
-    /**
-     * Get the source L4 port of this unidirectional GTP tunnel.
-     *
-     * @return tunnel source port
-     */
-    public Short srcPort() {
-        return this.srcPort;
-    }
-
-    public static class GtpTunnelBuilder {
-        private Ip4Address src;
-        private Ip4Address dst;
-        private ImmutableByteSequence teid;
-        private short srcPort = 2152;  // Default value is equal to GTP tunnel dst port
-
-        public GtpTunnelBuilder() {
-            this.src = null;
-            this.dst = null;
-            this.teid = null;
-        }
-
-        /**
-         * Set the source IP address of the unidirectional GTP tunnel.
-         *
-         * @param src GTP tunnel source IP
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setSrc(Ip4Address src) {
-            this.src = src;
-            return this;
-        }
-
-        /**
-         * Set the destination IP address of the unidirectional GTP tunnel.
-         *
-         * @param dst GTP tunnel destination IP
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setDst(Ip4Address dst) {
-            this.dst = dst;
-            return this;
-        }
-
-        /**
-         * Set the identifier of this unidirectional GTP tunnel.
-         *
-         * @param teid tunnel ID
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setTeid(ImmutableByteSequence teid) {
-            this.teid = teid;
-            return this;
-        }
-
-        /**
-         * Set the identifier of this unidirectional GTP tunnel.
-         *
-         * @param teid tunnel ID
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setTeid(long teid) {
-            this.teid = ImmutableByteSequence.copyFrom(teid);
-            return this;
-        }
-
-        /**
-         * Set the source port of this unidirectional GTP tunnel.
-         *
-         * @param srcPort tunnel source port
-         * @return this builder object
-         */
-        public GtpTunnelBuilder setSrcPort(short srcPort) {
-            this.srcPort = srcPort;
-            return this;
-        }
-
-        public GtpTunnel build() {
-            checkNotNull(src, "Tunnel source address cannot be null");
-            checkNotNull(dst, "Tunnel destination address cannot be null");
-            checkNotNull(teid, "Tunnel TEID cannot be null");
-            return new GtpTunnel(this.src, this.dst, this.teid, srcPort);
-        }
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnelPeer.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnelPeer.java
new file mode 100644
index 0000000..a3cf662
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnelPeer.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2021-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.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A structure representing a GTP tunnel peer.
+ * The GTP Tunnel Peer is used by UPF to identify a second end of a GTP tunnel.
+ * The source and destination tunnel IPv4 addresses, and source UDP port are set
+ * based on the information from this structure.
+ */
+@Beta
+public final class GtpTunnelPeer implements UpfEntity {
+    // Match keys
+    private final byte tunPeerId;
+    // Action parameters
+    private final Ip4Address src;  // The source address of the unidirectional tunnel
+    private final Ip4Address dst;  // The destination address of the unidirectional tunnel
+    private final short srcPort;   // Tunnel source port, default 2152
+
+    private GtpTunnelPeer(byte tunPeerId, Ip4Address src, Ip4Address dst, short srcPort) {
+        this.tunPeerId = tunPeerId;
+        this.src = src;
+        this.dst = dst;
+        this.srcPort = srcPort;
+    }
+
+    public static GtpTunnelPeer.Builder builder() {
+        return new GtpTunnelPeer.Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("GTP-Tunnel-Peer(%s -> src:%s, dst:%s srcPort:%s)",
+                             tunPeerId, src.toString(), dst.toString(), srcPort);
+    }
+
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+
+        if (object == null) {
+            return false;
+        }
+
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        GtpTunnelPeer that = (GtpTunnelPeer) object;
+        return (this.tunPeerId == that.tunPeerId &&
+                this.src.equals(that.src) &&
+                this.dst.equals(that.dst) &&
+                (this.srcPort == that.srcPort));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(src, dst, srcPort);
+    }
+
+    /**
+     * Get the ID of the GTP tunnel peer.
+     *
+     * @return GTP tunnel peer ID
+     */
+    public byte tunPeerId() {
+        return tunPeerId;
+    }
+
+    /**
+     * Get the source IP address of this unidirectional GTP tunnel.
+     *
+     * @return tunnel source IP
+     */
+    public Ip4Address src() {
+        return this.src;
+    }
+
+    /**
+     * Get the destination address of this unidirectional GTP tunnel.
+     *
+     * @return tunnel destination IP
+     */
+    public Ip4Address dst() {
+        return this.dst;
+    }
+
+    /**
+     * Get the source L4 port of this unidirectional GTP tunnel.
+     *
+     * @return tunnel source port
+     */
+    public short srcPort() {
+        return this.srcPort;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.TUNNEL_PEER;
+    }
+
+    public static class Builder {
+        private Byte tunPeerId = null;
+        private Ip4Address src = null;
+        private Ip4Address dst = null;
+        private short srcPort = 2152;  // Default value is equal to GTP tunnel dst port
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the ID of the GTP Tunnel peer.
+         *
+         * @param tunPeerId GTP tunnel peer ID
+         * @return This builder object
+         */
+        public GtpTunnelPeer.Builder withTunnelPeerId(byte tunPeerId) {
+            this.tunPeerId = tunPeerId;
+            return this;
+        }
+
+        /**
+         * Set the source IP address of the unidirectional GTP tunnel.
+         *
+         * @param src GTP tunnel source IP
+         * @return This builder object
+         */
+        public GtpTunnelPeer.Builder withSrcAddr(Ip4Address src) {
+            this.src = src;
+            return this;
+        }
+
+        /**
+         * Set the destination IP address of the unidirectional GTP tunnel.
+         *
+         * @param dst GTP tunnel destination IP
+         * @return This builder object
+         */
+        public GtpTunnelPeer.Builder withDstAddr(Ip4Address dst) {
+            this.dst = dst;
+            return this;
+        }
+
+        /**
+         * Set the source port of this unidirectional GTP tunnel.
+         *
+         * @param srcPort tunnel source port
+         * @return this builder object
+         */
+        public GtpTunnelPeer.Builder withSrcPort(short srcPort) {
+            this.srcPort = srcPort;
+            return this;
+        }
+
+        public GtpTunnelPeer build() {
+            checkArgument(tunPeerId != null, "Tunnel Peer ID must be provided");
+            checkArgument(src != null, "Tunnel source address cannot be null");
+            checkArgument(dst != null, "Tunnel destination address cannot be null");
+            return new GtpTunnelPeer(this.tunPeerId, this.src, this.dst, srcPort);
+        }
+
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PacketDetectionRule.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/PacketDetectionRule.java
deleted file mode 100644
index f607d87..0000000
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PacketDetectionRule.java
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Copyright 2021-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.net.behaviour.upf;
-
-import org.onlab.packet.Ip4Address;
-import org.onlab.util.ImmutableByteSequence;
-
-import java.util.Objects;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-/**
- * A single Packet Detection Rule (PDR), an entity described in the 3GPP
- * specifications (although that does not mean that this class is 3GPP
- * compliant). An instance of this class will be generated by a logical switch
- * write request to the database-style PDR P4 table, and the resulting instance
- * should contain all the information needed to reproduce that logical switch
- * PDR in the event of a client read request. The instance should also contain
- * sufficient information (or expose the means to retrieve such information) to
- * generate the corresponding dataplane forwarding state that implements the PDR.
- */
-public final class PacketDetectionRule {
-    // Match keys
-    private final Ip4Address ueAddr;  // The UE IP address that this PDR matches on
-    private final ImmutableByteSequence teid;  // The Tunnel Endpoint ID that this PDR matches on
-    private final Ip4Address tunnelDst;  // The tunnel destination address that this PDR matches on
-    // Action parameters
-    private final ImmutableByteSequence sessionId;  // The ID of the PFCP session that created this PDR
-    private final Integer ctrId;  // Counter ID unique to this PDR
-    private final Integer farId;  // The PFCP session-local ID of the FAR that should apply after this PDR hits
-    private final Type type;
-
-    private final Byte qfi; // QoS Flow Identifier of this PDR
-    private final boolean qfiPush; // True when QFI should be pushed to the packet
-    private final boolean qfiMatch; // True when QFI should be matched
-
-    private static final int SESSION_ID_BITWIDTH = 96;
-
-    private PacketDetectionRule(ImmutableByteSequence sessionId, Integer ctrId,
-                                Integer farId, Ip4Address ueAddr, Byte qfi,
-                                ImmutableByteSequence teid, Ip4Address tunnelDst,
-                                Type type, boolean qfiPush, boolean qfiMatch) {
-        this.ueAddr = ueAddr;
-        this.teid = teid;
-        this.tunnelDst = tunnelDst;
-        this.sessionId = sessionId;
-        this.ctrId = ctrId;
-        this.farId = farId;
-        this.qfi = qfi;
-        this.type = type;
-        this.qfiPush = qfiPush;
-        this.qfiMatch = qfiMatch;
-    }
-
-    public static Builder builder() {
-        return new Builder();
-    }
-
-    /**
-     * Return a string representing the match conditions of this PDR.
-     *
-     * @return a string representing the PDR match conditions
-     */
-    public String matchString() {
-        if (matchesEncapped()) {
-            return matchQfi() ?
-                    String.format("Match(Dst=%s, TEID=%s, QFI=%s)", tunnelDest(), teid(), qfi()) :
-                    String.format("Match(Dst=%s, TEID=%s)", tunnelDest(), teid());
-        } else {
-            return String.format("Match(Dst=%s, !GTP)", ueAddress());
-        }
-    }
-
-    @Override
-    public String toString() {
-        String actionParams = "";
-        if (hasActionParameters()) {
-            actionParams = hasQfi() && !matchQfi() ?
-                    String.format("SEID=%s, FAR=%d, CtrIdx=%d, QFI=%s",
-                                  sessionId.toString(), farId, ctrId, qfi) :
-                    String.format("SEID=%s, FAR=%d, CtrIdx=%d",
-                                  sessionId.toString(), farId, ctrId);
-            actionParams = pushQfi() ? String.format("%s, QFI_PUSH", actionParams) : actionParams;
-        }
-
-        return String.format("PDR{%s -> LoadParams(%s)}",
-                             matchString(), actionParams);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        PacketDetectionRule that = (PacketDetectionRule) obj;
-
-        // Safe comparisons between potentially null objects
-        return (this.type.equals(that.type) &&
-                Objects.equals(this.teid, that.teid) &&
-                Objects.equals(this.tunnelDst, that.tunnelDst) &&
-                Objects.equals(this.ueAddr, that.ueAddr) &&
-                Objects.equals(this.ctrId, that.ctrId) &&
-                Objects.equals(this.sessionId, that.sessionId) &&
-                Objects.equals(this.farId, that.farId) &&
-                Objects.equals(this.qfi, that.qfi) &&
-                Objects.equals(this.qfiPush, that.qfiPush) &&
-                Objects.equals(this.qfiMatch, that.qfiMatch));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(ueAddr, teid, tunnelDst, sessionId, ctrId, farId,
-                            qfi, type);
-    }
-
-    /**
-     * Instances created as a result of DELETE write requests will not have
-     * action parameters, only match keys. This method should be used to avoid
-     * null pointer exceptions in those instances.
-     *
-     * @return true if this instance has PDR action parameters, false otherwise.
-     */
-    public boolean hasActionParameters() {
-        return type == Type.MATCH_ENCAPPED || type == Type.MATCH_UNENCAPPED;
-    }
-
-    /**
-     * Return a new instance of this PDR with the action parameters stripped,
-     * leaving only the match keys.
-     *
-     * @return a new PDR with only match keys
-     */
-    public PacketDetectionRule withoutActionParams() {
-        if (matchesEncapped()) {
-            PacketDetectionRule.Builder pdrBuilder = PacketDetectionRule.builder()
-                    .withTeid(teid)
-                    .withTunnelDst(tunnelDst);
-            if (this.hasQfi() && this.matchQfi()) {
-                pdrBuilder.withQfiMatch();
-                pdrBuilder.withQfi(qfi);
-            }
-            return pdrBuilder.build();
-        } else {
-            return PacketDetectionRule.builder()
-                    .withUeAddr(ueAddr).build();
-        }
-    }
-
-    /**
-     * True if this PDR matches on packets received with a GTP header, and false
-     * otherwise.
-     *
-     * @return true if the PDR matches only encapsulated packets
-     */
-    public boolean matchesEncapped() {
-        return type == Type.MATCH_ENCAPPED ||
-                type == Type.MATCH_ENCAPPED_NO_ACTION;
-    }
-
-    /**
-     * True if this PDR matches on packets received without a GTP header, and
-     * false otherwise.
-     *
-     * @return true if the PDR matches only un-encapsulated packets
-     */
-    public boolean matchesUnencapped() {
-        return type == Type.MATCH_UNENCAPPED ||
-                type == Type.MATCH_UNENCAPPED_NO_ACTION;
-    }
-
-    /**
-     * Get the ID of the PFCP session that produced this PDR.
-     *
-     * @return PFCP session ID
-     */
-    public ImmutableByteSequence sessionId() {
-        return sessionId;
-    }
-
-    /**
-     * Get the UE IP address that this PDR matches on.
-     *
-     * @return UE IP address
-     */
-    public Ip4Address ueAddress() {
-        return ueAddr;
-    }
-
-    /**
-     * Get the identifier of the GTP tunnel that this PDR matches on.
-     *
-     * @return GTP tunnel ID
-     */
-    public ImmutableByteSequence teid() {
-        return teid;
-    }
-
-    /**
-     * Get the destination IP of the GTP tunnel that this PDR matches on.
-     *
-     * @return GTP tunnel destination IP
-     */
-    public Ip4Address tunnelDest() {
-        return tunnelDst;
-    }
-
-    /**
-     * Get the dataplane PDR counter cell ID that this PDR is assigned.
-     *
-     * @return PDR counter cell ID
-     */
-    public int counterId() {
-        return ctrId;
-    }
-
-    /**
-     * Get the PFCP session-local ID of the far that should apply to packets
-     * that this PDR matches.
-     *
-     * @return PFCP session-local FAR ID
-     */
-    public int farId() {
-        return farId;
-    }
-
-    /**
-     * Get the QoS Flow Identifier for this PDR.
-     *
-     * @return QoS Flow Identifier
-     */
-    public byte qfi() {
-        return qfi;
-    }
-
-    /**
-     * Returns whether QFi should be pushed to the packet.
-     *
-     * @return True if the QFI should be pushed to the packet, false otherwise
-     */
-    public boolean pushQfi() {
-        return qfiPush;
-    }
-
-    /**
-     * Returns whether QFI should be matched on the packet or not.
-     *
-     * @return True if QFI should be matched on the packet, false otherwise
-     */
-    public boolean matchQfi() {
-        return qfiMatch;
-    }
-
-    /**
-     * Checks if the PDR has a QFI to match upon or to push on the packet.
-     *
-     * @return True if the PDR has a QFI, false otherwise
-     */
-    public boolean hasQfi() {
-        return qfi != null;
-    }
-
-    private enum Type {
-        /**
-         * Match on packets that are encapsulated in a GTP tunnel.
-         */
-        MATCH_ENCAPPED,
-        /**
-         * Match on packets that are not encapsulated in a GTP tunnel.
-         */
-        MATCH_UNENCAPPED,
-        /**
-         * For PDRs that match on encapsulated packets but do not yet have any
-         * action parameters set.
-         * These are usually built in the context of P4Runtime DELETE write requests.
-         */
-        MATCH_ENCAPPED_NO_ACTION,
-        /**
-         * For PDRs that match on unencapsulated packets but do not yet have any
-         * action parameters set.
-         * These are usually built in the context of P4Runtime DELETE write requests.
-         */
-        MATCH_UNENCAPPED_NO_ACTION
-    }
-
-    public static class Builder {
-        private ImmutableByteSequence sessionId = null;
-        private Integer ctrId = null;
-        private Integer localFarId = null;
-        private Integer schedulingPriority = null;
-        private Ip4Address ueAddr = null;
-        private ImmutableByteSequence teid = null;
-        private Ip4Address tunnelDst = null;
-        private Byte qfi = null;
-        private boolean qfiPush = false;
-        private boolean qfiMatch = false;
-
-        public Builder() {
-
-        }
-
-        /**
-         * Set the ID of the PFCP session that produced this PDR.
-         *
-         * @param sessionId PFCP session ID
-         * @return This builder object
-         */
-        public Builder withSessionId(ImmutableByteSequence sessionId) {
-            this.sessionId = sessionId;
-            return this;
-        }
-
-        /**
-         * Set the ID of the PFCP session that produced this PDR.
-         *
-         * @param sessionId PFCP session ID
-         * @return This builder object
-         */
-        public Builder withSessionId(long sessionId) {
-            try {
-                this.sessionId = ImmutableByteSequence.copyFrom(sessionId)
-                        .fit(SESSION_ID_BITWIDTH);
-            } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
-                // This error is literally impossible
-            }
-            return this;
-        }
-
-        /**
-         * Set the UE IP address that this PDR matches on.
-         *
-         * @param ueAddr UE IP address
-         * @return This builder object
-         */
-        public Builder withUeAddr(Ip4Address ueAddr) {
-            this.ueAddr = ueAddr;
-            return this;
-        }
-
-        /**
-         * Set the dataplane PDR counter cell ID that this PDR is assigned.
-         *
-         * @param ctrId PDR counter cell ID
-         * @return This builder object
-         */
-        public Builder withCounterId(int ctrId) {
-            this.ctrId = ctrId;
-            return this;
-        }
-
-
-        /**
-         * Set the PFCP session-local ID of the far that should apply to packets that this PDR matches.
-         *
-         * @param localFarId PFCP session-local FAR ID
-         * @return This builder object
-         */
-        public Builder withLocalFarId(int localFarId) {
-            this.localFarId = localFarId;
-            return this;
-        }
-
-        /**
-         * Set the QoS Flow Identifier for this PDR.
-         *
-         * @param qfi GTP Tunnel QFI
-         * @return This builder object
-         */
-        public Builder withQfi(byte qfi) {
-            this.qfi = qfi;
-            return this;
-        }
-
-        /**
-         * The QoS Flow Identifier should be pushed to the packet.
-         * This is valid for downstream packets and for 5G traffic only.
-         *
-         * @return This builder object
-         */
-        public Builder withQfiPush() {
-            this.qfiPush = true;
-            return this;
-        }
-
-        /**
-         * The QoS Flow Identifier should be matched to the packet.
-         * This is valid for upstream packets and for 5G traffic only.
-         *
-         * @return This builder object
-         */
-        public Builder withQfiMatch() {
-            this.qfiMatch = true;
-            return this;
-        }
-
-        /**
-         * Set the identifier of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid GTP tunnel ID
-         * @return This builder object
-         */
-        public Builder withTeid(int teid) {
-            this.teid = ImmutableByteSequence.copyFrom(teid);
-            return this;
-        }
-
-        /**
-         * Set the identifier of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid GTP tunnel ID
-         * @return This builder object
-         */
-        public Builder withTeid(ImmutableByteSequence teid) {
-            this.teid = teid;
-            return this;
-        }
-
-        /**
-         * Set the destination IP of the GTP tunnel that this PDR matches on.
-         *
-         * @param tunnelDst GTP tunnel destination IP
-         * @return This builder object
-         */
-        public Builder withTunnelDst(Ip4Address tunnelDst) {
-            this.tunnelDst = tunnelDst;
-            return this;
-        }
-
-        /**
-         * Set the tunnel ID and destination IP of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid      GTP tunnel ID
-         * @param tunnelDst GTP tunnel destination IP
-         * @return This builder object
-         */
-        public Builder withTunnel(ImmutableByteSequence teid, Ip4Address tunnelDst) {
-            this.teid = teid;
-            this.tunnelDst = tunnelDst;
-            return this;
-        }
-
-        /**
-         * Set the tunnel ID, destination IP and QFI of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid      GTP tunnel ID
-         * @param tunnelDst GTP tunnel destination IP
-         * @param qfi       GTP QoS Flow Identifier
-         * @return This builder object
-         */
-        public Builder withTunnel(ImmutableByteSequence teid, Ip4Address tunnelDst, byte qfi) {
-            this.teid = teid;
-            this.tunnelDst = tunnelDst;
-            this.qfi = qfi;
-            return this;
-        }
-
-        public PacketDetectionRule build() {
-            // Some match keys are required.
-            checkArgument(
-                    (ueAddr != null && teid == null && tunnelDst == null) ||
-                            (ueAddr == null && teid != null && tunnelDst != null),
-                    "Either a UE address or a TEID and Tunnel destination must be provided, but not both.");
-            // Action parameters are optional but must be all provided together if they are provided
-            checkArgument(
-                    (sessionId != null && ctrId != null && localFarId != null) ||
-                            (sessionId == null && ctrId == null && localFarId == null),
-                    "PDR action parameters must be provided together or not at all.");
-            checkArgument(!qfiPush || !qfiMatch,
-                          "Either match of push QFI can be true, not both.");
-            checkArgument(!qfiPush || qfi != null,
-                          "A QFI must be provided when pushing QFI to the packet.");
-            checkArgument(!qfiMatch || qfi != null,
-                          "A QFI must be provided when matching QFI on the packet.");
-            Type type;
-            if (teid != null) {
-                if (sessionId != null) {
-                    type = Type.MATCH_ENCAPPED;
-                } else {
-                    type = Type.MATCH_ENCAPPED_NO_ACTION;
-                }
-            } else {
-                if (sessionId != null) {
-                    type = Type.MATCH_UNENCAPPED;
-                } else {
-                    type = Type.MATCH_UNENCAPPED_NO_ACTION;
-                }
-            }
-            return new PacketDetectionRule(sessionId, ctrId, localFarId, ueAddr,
-                                           qfi, teid, tunnelDst, type,
-                                           qfiPush, qfiMatch);
-        }
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionDownlink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionDownlink.java
new file mode 100644
index 0000000..c35410b
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionDownlink.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2021-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.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Session on the UPF-programmable device.
+ * Provides means to set up the UE Session in the downlink direction.
+ */
+@Beta
+public final class SessionDownlink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address ueAddress;
+    // Action parameters
+    private final Byte tunPeerId;
+    private final boolean buffering;
+    private final boolean dropping;
+
+    private SessionDownlink(Ip4Address ipv4Address,
+                            Byte tunPeerId,
+                            boolean buffering,
+                            boolean drop) {
+        this.ueAddress = ipv4Address;
+        this.tunPeerId = tunPeerId;
+        this.buffering = buffering;
+        this.dropping = drop;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object == null) {
+            return false;
+        }
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        SessionDownlink that = (SessionDownlink) object;
+
+        return this.buffering == that.buffering &&
+                this.dropping == that.dropping &&
+                Objects.equals(ueAddress, that.ueAddress) &&
+                Objects.equals(tunPeerId, that.tunPeerId);
+    }
+
+    public int hashCode() {
+        return java.util.Objects.hash(ueAddress, tunPeerId, buffering, dropping);
+    }
+
+    @Override
+    public String toString() {
+        return "UESessionDL{" + matchString() + " -> " + actionString();
+    }
+
+    private String matchString() {
+        return "Match(ue_addr=" + this.ueAddress() + ")";
+    }
+
+    private String actionString() {
+        StringBuilder actionStrBuilder = new StringBuilder("(");
+        if (this.needsBuffering() && this.needsDropping()) {
+            actionStrBuilder.append("BUFF+DROP, ");
+        } else if (this.needsBuffering()) {
+            actionStrBuilder.append("BUFF, ");
+        } else if (this.needsDropping()) {
+            actionStrBuilder.append("DROP, ");
+        } else {
+            actionStrBuilder.append("FWD, ");
+        }
+       return actionStrBuilder.append(" tun_peer=").append(this.tunPeerId()).append(")")
+               .toString();
+    }
+
+    /**
+     * True if this UE Session needs buffering of the downlink traffic.
+     *
+     * @return true if the UE Session needs buffering.
+     */
+    public boolean needsBuffering() {
+        return buffering;
+    }
+
+    /**
+     * True if this UE Session needs dropping of the downlink traffic.
+     *
+     * @return true if the UE Session needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    /**
+     * Get the UE IP address of this downlink UE session.
+     *
+     * @return UE IP address
+     */
+    public Ip4Address ueAddress() {
+        return ueAddress;
+    }
+
+    /**
+     * Get the GTP tunnel peer ID that is set by this UE Session rule.
+     *
+     * @return GTP tunnel peer ID
+     */
+    public Byte tunPeerId() {
+        return tunPeerId;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.SESSION_DOWNLINK;
+    }
+
+    public static class Builder {
+        private Ip4Address ueAddress = null;
+        private Byte tunPeerId = null;
+        private boolean buffer = false;
+        private boolean drop = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the UE IP address that this downlink UE session rule matches on.
+         *
+         * @param ueAddress UE IP address
+         * @return This builder object
+         */
+        public Builder withUeAddress(Ip4Address ueAddress) {
+            this.ueAddress = ueAddress;
+            return this;
+        }
+
+        /**
+         * Set the GTP tunnel peer ID that is set by this UE Session rule.
+         *
+         * @param tunnelPeerId GTP tunnel peer ID
+         * @return This builder object
+         */
+        public Builder withGtpTunnelPeerId(Byte tunnelPeerId) {
+            this.tunPeerId = tunnelPeerId;
+            return this;
+        }
+
+        /**
+         * Sets whether to buffer downlink UE session traffic or not.
+         *
+         * @param buffer True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsBuffering(boolean buffer) {
+            this.buffer = buffer;
+            return this;
+        }
+
+        /**
+         * Sets whether to drop downlink UE session traffic or not.
+         *
+         * @param drop True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean drop) {
+            this.drop = drop;
+            return this;
+        }
+
+        public SessionDownlink build() {
+            // Match fields are required
+            checkNotNull(ueAddress, "UE address must be provided");
+            return new SessionDownlink(ueAddress, tunPeerId, buffer, drop);
+        }
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionUplink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionUplink.java
new file mode 100644
index 0000000..70db84c
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionUplink.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2021-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.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Session on the UPF-programmable device.
+ * Provides means to set up the UE Session in the uplink direction.
+ */
+@Beta
+public final class SessionUplink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address tunDestAddr; // The tunnel destination address (N3/S1U IPv4 address)
+    private final Integer teid;  // The Tunnel Endpoint ID that this UeSession matches on
+
+    // Action parameters
+    private final boolean dropping; // Used to convey dropping information
+
+    private SessionUplink(Ip4Address tunDestAddr,
+                          Integer teid,
+                          boolean drop) {
+        this.tunDestAddr = tunDestAddr;
+        this.teid = teid;
+        this.dropping = drop;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object == null) {
+            return false;
+        }
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        SessionUplink that = (SessionUplink) object;
+
+        return this.dropping == that.dropping &&
+                Objects.equals(tunDestAddr, that.tunDestAddr) &&
+                Objects.equals(teid, that.teid);
+    }
+
+    public int hashCode() {
+        return Objects.hash(tunDestAddr, teid, dropping);
+    }
+
+    @Override
+    public String toString() {
+        return "UESessionUL{" + matchString() + " -> " + actionString();
+    }
+
+    private String matchString() {
+        return "Match(tun_dst_addr=" + this.tunDstAddr() + ", TEID=" + this.teid() + ")";
+    }
+
+    private String actionString() {
+        StringBuilder actionStrBuilder = new StringBuilder("(");
+        if (this.needsDropping()) {
+            actionStrBuilder.append("DROP");
+
+        } else {
+            actionStrBuilder.append("FWD");
+        }
+        return actionStrBuilder.append(")").toString();
+    }
+
+    /**
+     * True if this UE Session needs dropping of the uplink traffic.
+     *
+     * @return true if the UE Session needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    /**
+     * Get the tunnel destination IP address in the uplink UE session (N3/S1U IP address).
+     *
+     * @return UE IP address
+     */
+    public Ip4Address tunDstAddr() {
+        return tunDestAddr;
+    }
+
+    /**
+     * Get the identifier of the GTP tunnel that this UE Session rule matches on.
+     *
+     * @return GTP tunnel ID
+     */
+    public Integer teid() {
+        return teid;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.SESSION_UPLINK;
+    }
+
+    public static class Builder {
+        private Ip4Address tunDstAddr = null;
+        private Integer teid = null;
+        private boolean drop = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the tunnel destination IP address (N3/S1U address) that this UE Session rule matches on.
+         *
+         * @param tunDstAddr The tunnel destination IP address
+         * @return This builder object
+         */
+        public Builder withTunDstAddr(Ip4Address tunDstAddr) {
+            this.tunDstAddr = tunDstAddr;
+            return this;
+        }
+
+        /**
+         * Set the identifier of the GTP tunnel that this UE Session rule matches on.
+         *
+         * @param teid GTP tunnel ID
+         * @return This builder object
+         */
+        public Builder withTeid(Integer teid) {
+            this.teid = teid;
+            return this;
+        }
+
+
+        /**
+         * Sets whether to drop uplink UE session traffic or not.
+         *
+         * @param drop True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean drop) {
+            this.drop = drop;
+            return this;
+        }
+
+        public SessionUplink build() {
+            // Match keys are required.
+            checkNotNull(tunDstAddr, "Tunnel destination must be provided");
+            checkNotNull(teid, "TEID must be provided");
+            return new SessionUplink(tunDstAddr, teid, drop);
+        }
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PdrStats.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
similarity index 76%
rename from core/api/src/main/java/org/onosproject/net/behaviour/upf/PdrStats.java
rename to core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
index eb4ea18..4eee3fe 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PdrStats.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
@@ -17,21 +17,26 @@
 package org.onosproject.net.behaviour.upf;
 
 
+import com.google.common.annotations.Beta;
+
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * A structure for compactly passing PDR counter values for a given counter ID.
- * Contains four counts: Ingress Packets, Ingress Bytes, Egress Packets, Egress Bytes
+ * A structure for compactly passing UPF counter values for a given counter ID.
+ * Contains four counts: Ingress Packets, Ingress Bytes, Egress Packets, Egress Bytes.
+ * UpfCounter can be used ONLY on {@code apply} and {@code readAll} calls in the
+ * {@link UpfDevice} interface.
  */
-public final class PdrStats {
+@Beta
+public final class UpfCounter implements UpfEntity {
     private final int cellId;
     private final long ingressPkts;
     private final long ingressBytes;
     private final long egressPkts;
     private final long egressBytes;
 
-    private PdrStats(int cellId, long ingressPkts, long ingressBytes,
-                     long egressPkts, long egressBytes) {
+    private UpfCounter(int cellId, long ingressPkts, long ingressBytes,
+                       long egressPkts, long egressBytes) {
         this.cellId = cellId;
         this.ingressPkts = ingressPkts;
         this.ingressBytes = ingressBytes;
@@ -45,12 +50,12 @@
 
     @Override
     public String toString() {
-        return String.format("PDR-Stats:{ CellID: %d, Ingress:(%dpkts,%dbytes), Egress:(%dpkts,%dbytes) }",
+        return String.format("Stats:{ CellID: %d, Ingress:(%dpkts,%dbytes), Egress:(%dpkts,%dbytes) }",
                 cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
     }
 
     /**
-     * Get the cell ID (index) of the dataplane PDR counter that produced this set of stats.
+     * Get the cell ID (index) of the dataplane counter that produced this set of stats.
      *
      * @return counter cell ID
      */
@@ -94,6 +99,11 @@
         return egressBytes;
     }
 
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.COUNTER;
+    }
+
     public static class Builder {
         private Integer cellId;
         private long ingressPkts;
@@ -109,7 +119,7 @@
         }
 
         /**
-         * Set the Cell ID (index) of the datalane PDR counter that produced this set of stats.
+         * Set the Cell ID (index) of the datalane counter that produced this set of stats.
          *
          * @param cellId the counter cell ID
          * @return This builder
@@ -120,7 +130,7 @@
         }
 
         /**
-         * Set the number of packets and bytes that hit the PDR counter in the dataplane ingress pipeline.
+         * Set the number of packets and bytes that hit the counter in the dataplane ingress pipeline.
          *
          * @param ingressPkts  ingress packet count
          * @param ingressBytes egress packet count
@@ -133,7 +143,7 @@
         }
 
         /**
-         * Set the number of packets and bytes that hit the PDR counter in the dataplane egress pipeline.
+         * Set the number of packets and bytes that hit the counter in the dataplane egress pipeline.
          *
          * @param egressPkts  egress packet count
          * @param egressBytes egress byte count
@@ -145,9 +155,9 @@
             return this;
         }
 
-        public PdrStats build() {
+        public UpfCounter build() {
             checkNotNull(cellId, "CellID must be provided");
-            return new PdrStats(cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
+            return new UpfCounter(cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java
index e0d9fb4..2b17421 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java
@@ -28,157 +28,94 @@
 public interface UpfDevice {
 
     /**
-     * Remove any state previously created by this API.
+     * Removes any state previously created by this API.
      */
     void cleanUp();
 
     /**
-     * Remove all interfaces currently installed on the UPF-programmable device.
-     */
-    void clearInterfaces();
-
-    /**
-     * Remove all UE flows (PDRs, FARs) currently installed on the UPF-programmable device.
-     */
-    void clearFlows();
-
-    /**
-     * Get all ForwardingActionRules currently installed on the UPF-programmable device.
+     * Applies the given UPF entity to the UPF-programmable device.
      *
-     * @return a collection of installed FARs
-     * @throws UpfProgrammableException if FARs are unable to be read
+     * @param entity The UPF entity to be applied.
+     * @throws UpfProgrammableException if the given UPF entity can not be applied or
+     *                                  the operation is not supported on the given UPF entity.
      */
-    Collection<ForwardingActionRule> getFars() throws UpfProgrammableException;
+    void apply(UpfEntity entity) throws UpfProgrammableException;
 
     /**
-     * Get all PacketDetectionRules currently installed on the UPF-programmable device.
+     * Reads all the UPF entities of the given type from the UPF-programmable device.
      *
-     * @return a collection of installed PDRs
-     * @throws UpfProgrammableException if PDRs are unable to be read
+     * @param entityType The type of entities to read.
+     * @return A collection of installed UPF entities.
+     * @throws UpfProgrammableException if UPF entity type is not available to be read or
+     *                                  the operation is not supported on the given UPF entity type.
      */
-    Collection<PacketDetectionRule> getPdrs() throws UpfProgrammableException;
+    Collection<? extends UpfEntity> readAll(UpfEntityType entityType) throws UpfProgrammableException;
 
     /**
-     * Get all UPF interface lookup entries currently installed on the UPF-programmable device.
+     * Reads the given UPF counter ID from the UPF-programmable device.
      *
-     * @return a collection of installed interfaces
-     * @throws UpfProgrammableException if interfaces are unable to be read
+     * @param counterId The counter ID from which to read.
+     * @return The content of the UPF counter.
+     * @throws UpfProgrammableException if the counter ID is out of bounds.
      */
-    Collection<UpfInterface> getInterfaces() throws UpfProgrammableException;
+    UpfCounter readCounter(int counterId) throws UpfProgrammableException;
 
     /**
-     * Add a Packet Detection Rule (PDR) to the given device.
-     *
-     * @param pdr The PDR to be added
-     * @throws UpfProgrammableException if the PDR cannot be installed, or the counter index is out
-     *                                  of bounds
-     */
-    void addPdr(PacketDetectionRule pdr) throws UpfProgrammableException;
-
-    /**
-     * Remove a previously installed Packet Detection Rule (PDR) from the target device.
-     *
-     * @param pdr The PDR to be removed
-     * @throws UpfProgrammableException if the PDR cannot be found
-     */
-    void removePdr(PacketDetectionRule pdr) throws UpfProgrammableException;
-
-    /**
-     * Add a Forwarding Action Rule (FAR) to the given device.
-     *
-     * @param far The FAR to be added
-     * @throws UpfProgrammableException if the FAR cannot be installed
-     */
-    void addFar(ForwardingActionRule far) throws UpfProgrammableException;
-
-    /**
-     * Remove a previously installed Forwarding Action Rule (FAR) from the target device.
-     *
-     * @param far The FAR to be removed
-     * @throws UpfProgrammableException if the FAR cannot be found
-     */
-    void removeFar(ForwardingActionRule far) throws UpfProgrammableException;
-
-    /**
-     * Install a new interface on the UPF device's interface lookup tables.
-     *
-     * @param upfInterface the interface to install
-     * @throws UpfProgrammableException if the interface cannot be installed
-     */
-    void addInterface(UpfInterface upfInterface) throws UpfProgrammableException;
-
-    /**
-     * Remove a previously installed UPF interface from the target device.
-     *
-     * @param upfInterface the interface to be removed
-     * @throws UpfProgrammableException if the interface cannot be found
-     */
-    void removeInterface(UpfInterface upfInterface) throws UpfProgrammableException;
-
-    /**
-     * Read the the given cell (Counter index) of the PDR counters from the given device.
-     *
-     * @param counterIdx The counter cell index from which to read
-     * @return A structure containing ingress and egress packet and byte counts for the given
-     * cellId.
-     * @throws UpfProgrammableException if the cell ID is out of bounds
-     */
-    PdrStats readCounter(int counterIdx) throws UpfProgrammableException;
-
-    /**
-     * Return the number of PDR counter cells available. The number of cells in the ingress and
-     * egress PDR counters are equivalent.
-     *
-     * @return PDR counter size
-     */
-    long pdrCounterSize();
-
-    /**
-     * Return the number of maximum number of table entries the FAR table supports.
-     *
-     * @return the number of FARs that can be installed
-     */
-    long farTableSize();
-
-    /**
-     * Return the total number of table entries the downlink and uplink PDR tables support. Both
-     * tables support an equal number of entries, so the total is twice the size of either.
-     *
-     * @return the total number of PDRs that can be installed
-     */
-    long pdrTableSize();
-
-    /**
-     * Read the counter contents for all cell indices that are valid on the hardware switch.
-     * {@code maxCounterId} parameter is used to limit the number of counters
-     * retrieved from the UPF device. If the limit given is larger than the
-     * physical limit, the physical limit will be used. A limit of -1 removes
-     * limitations.
+     * Reads the UPF counter contents for all indices that are valid on the
+     * UPF-programmable device. {@code maxCounterId} parameter is used to limit
+     * the number of counters retrieved from the UPF. If the limit given is
+     * larger than the physical limit, the physical limit will be used.
+     * A limit of -1 removes limitations, and it is equivalent of calling
+     * {@link #readAll(UpfEntityType)} passing the {@code COUNTER} {@link UpfEntityType}.
      *
      * @param maxCounterId Maximum counter ID to retrieve from the UPF device.
-     * @return A collection of counter values for all valid hardware counter cells
-     * @throws UpfProgrammableException if the counters are unable to be read
+     * @return A collection of UPF counters for all valid hardware counter cells.
+     * @throws UpfProgrammableException if the counters are unable to be read.
      */
-    Collection<PdrStats> readAllCounters(long maxCounterId) throws UpfProgrammableException;
+    Collection<UpfCounter> readCounters(long maxCounterId) throws UpfProgrammableException;
+
+    /**
+     * Deletes the given UPF entity from the UPF-programmable device.
+     *
+     * @param entity The UPF entity to be removed.
+     * @throws UpfProgrammableException if the given UPF entity is not found or
+     *                                  the operation is not supported on the given UPF entity.
+     */
+    void delete(UpfEntity entity) throws UpfProgrammableException;
+
+    /**
+     * Deletes the given UPF entity from the UPF-programmable device.
+     *
+     * @param entityType The UPF entity type to be removed.
+     * @throws UpfProgrammableException if the given UPF entity is not found or
+     *                                  the operation is not supported on the given UPF entity.
+     */
+    void deleteAll(UpfEntityType entityType) throws UpfProgrammableException;
+
+    /**
+     * Returns the total number of UPF entities of the given type supported by
+     * the UPF-programmable device. For entities that have a direction,returns
+     * the total amount of entities including both the downlink and the uplink
+     * directions.
+     * @param entityType The type of UPF programmable entities to retrieve the size from.
+     * @return The total number of supported UPF entities.
+     * @throws UpfProgrammableException if the operation is not supported on the given UPF entity.
+     */
+    long tableSize(UpfEntityType entityType) throws UpfProgrammableException;
 
     /**
      * Instructs the UPF-programmable device to use GTP-U extension PDU Session Container (PSC) when
      * doing encap of downlink packets, with the given QoS Flow Identifier (QFI).
      *
-     * @param defaultQfi QFI to be used by default for all encapped packets.
      * @throws UpfProgrammableException if operation is not available
      */
-    // FIXME: remove once we expose QFI in logical pipeline
-    //  QFI should be set by the SMF using PFCP
-    void enablePscEncap(int defaultQfi) throws UpfProgrammableException;
+    void enablePscEncap() throws UpfProgrammableException;
 
     /**
-     * Disable PSC encap previously enabled with {@link #enablePscEncap(int)}.
+     * Disable PSC encap previously enabled with {@link #enablePscEncap()}.
      *
      * @throws UpfProgrammableException if operation is not available
      */
-    // FIXME: remove once we expose QFI in logical pipeline
-    //  QFI should be set by the SMF using PFCP
     void disablePscEncap() throws UpfProgrammableException;
 
     /**
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntity.java
similarity index 65%
rename from pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java
rename to core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntity.java
index fe4c590..ad1fbaa 100644
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntity.java
@@ -14,7 +14,19 @@
  * limitations under the License.
  */
 
+package org.onosproject.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+
 /**
- * Unit tests for UPF programmable behaviour.
+ * Abstraction of a UPF entity used to interact with the UPF-programmable device.
  */
-package org.onosproject.pipelines.fabric.impl.behaviour.upf;
+@Beta
+public interface UpfEntity {
+    /**
+     * Returns the type of this entity.
+     *
+     * @return entity type
+     */
+    UpfEntityType type();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java
new file mode 100644
index 0000000..7ec3b19
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021-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.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Type of UPF entity.
+ */
+@Beta
+public enum UpfEntityType {
+    INTERFACE("interface"),
+    TERMINATION_DOWNLINK("termination_downlink"),
+    TERMINATION_UPLINK("termination_uplink"),
+    SESSION_DOWNLINK("session_downlink"),
+    SESSION_UPLINK("session_downlink"),
+    TUNNEL_PEER("tunnel_peer"),
+    COUNTER("counter");
+
+    private final String humanReadableName;
+
+    UpfEntityType(String humanReadableName) {
+        this.humanReadableName = humanReadableName;
+    }
+
+    /**
+     * Returns a human readable representation of this UPF entity type (useful
+     * for logging).
+     *
+     * @return string
+     */
+    public String humanReadableName() {
+        return humanReadableName;
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java
index 3fee11a..f697d15 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.net.behaviour.upf;
 
+import com.google.common.annotations.Beta;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
 
@@ -26,7 +27,8 @@
 /**
  * A UPF device interface, such as a S1U or UE IP address pool.
  */
-public final class UpfInterface {
+@Beta
+public final class UpfInterface implements UpfEntity {
     private final Ip4Prefix prefix;
     private final Type type;
 
@@ -134,7 +136,6 @@
         return type == Type.CORE;
     }
 
-
     /**
      * Check if this UPF interface is for receiving buffered packets as they are released from the dbuf
      * buffering device.
@@ -154,6 +155,11 @@
         return this.prefix;
     }
 
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.INTERFACE;
+    }
+
     public enum Type {
         /**
          * Unknown UPF interface type.
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java
index 159adb0..052084e 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java
@@ -30,13 +30,12 @@
 public interface UpfProgrammable extends HandlerBehaviour, UpfDevice {
     /**
      * Apps are expected to call this method as the first one when they are ready
-     * to install PDRs and FARs.
+     * to install any UPF entity.
      *
      * @return True if initialized, false otherwise.
      */
     boolean init();
 
-
     /**
      * Checks if the given flow rule has been generated by this UPF behaviour.
      *
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java
index 7daa806..96f8c85 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java
@@ -16,14 +16,20 @@
 
 package org.onosproject.net.behaviour.upf;
 
+import com.google.common.annotations.Beta;
+
+import java.util.Optional;
+
 /**
  * An exception indicating a an error happened in the UPF programmable behaviour.
  * Possible errors include the attempted insertion of a malformed flow rule, the
  * reading or writing of an out-of-bounds counter cell, the deletion of a non-existent
  * flow rule, and the attempted insertion of a flow rule into a full table.
  */
+@Beta
 public class UpfProgrammableException extends Exception {
     private final Type type;
+    private final UpfEntityType entityType;
 
     public enum Type {
         /**
@@ -31,13 +37,13 @@
          */
         UNKNOWN,
         /**
-         * The target table is at capacity.
+         * The target entity is at capacity.
          */
-        TABLE_EXHAUSTED,
+        ENTITY_EXHAUSTED,
         /**
-         * A provided counter cell index was out of range.
+         * A provided index was out of range.
          */
-        COUNTER_INDEX_OUT_OF_RANGE,
+        ENTITY_OUT_OF_RANGE,
         /**
          * The UpfProgrammable implementation doesn't support the operation.
          */
@@ -52,6 +58,7 @@
     public UpfProgrammableException(String message) {
         super(message);
         this.type = Type.UNKNOWN;
+        this.entityType = null;
     }
 
     /**
@@ -63,6 +70,20 @@
     public UpfProgrammableException(String message, Type type) {
         super(message);
         this.type = type;
+        this.entityType = null;
+    }
+
+    /**
+     * Creates a new exception for the given message, type and entity type.
+     *
+     * @param message    exception message
+     * @param type       exception type
+     * @param entityType entity type
+     */
+    public UpfProgrammableException(String message, Type type, UpfEntityType entityType) {
+        super(message);
+        this.type = type;
+        this.entityType = entityType;
     }
 
     /**
@@ -73,4 +94,13 @@
     public Type getType() {
         return type;
     }
+
+    /**
+     * Get the type of the entity that generated the exception.
+     *
+     * @return entity type
+     */
+    public Optional<UpfEntityType> getEntityType() {
+        return Optional.ofNullable(entityType);
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationDownlink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationDownlink.java
new file mode 100644
index 0000000..3bc9a91
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationDownlink.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2021-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.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Termination in the downlink direction on the
+ * UPF-programmable device.
+ * Provides means to configure the traffic behavior (e.g. set Traffic Class, GTP TEID, or QFI).
+ */
+@Beta
+public final class UpfTerminationDownlink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address ueSessionId; // UE Session ID, use UE IP address to uniquely identify a session.
+    // Action parameters
+    private final Integer ctrId;  // Counter ID unique to this UPF Termination Rule
+    private final Byte trafficClass;
+    private final Integer teid;  // Tunnel Endpoint Identifier
+    private final Byte qfi; // QoS Flow Identifier
+    private final boolean dropping;
+
+    private UpfTerminationDownlink(Ip4Address ueSessionId, Integer ctrId, Byte trafficClass,
+                                   Integer teid, Byte qfi, boolean dropping) {
+        this.ueSessionId = ueSessionId;
+        this.ctrId = ctrId;
+        this.trafficClass = trafficClass;
+        this.teid = teid;
+        this.qfi = qfi;
+        this.dropping = dropping;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        UpfTerminationDownlink that = (UpfTerminationDownlink) obj;
+
+        // Safe comparisons between potentially null objects
+        return this.dropping == that.dropping &&
+                Objects.equals(this.ueSessionId, that.ueSessionId) &&
+                Objects.equals(this.ctrId, that.ctrId) &&
+                Objects.equals(this.trafficClass, that.trafficClass) &&
+                Objects.equals(this.teid, that.teid) &&
+                Objects.equals(this.qfi, that.qfi);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ueSessionId, ctrId, trafficClass, teid, qfi, dropping);
+    }
+
+    /**
+     * Get UE Session ID associated with UPF Termination rule.
+     *
+     * @return UE Session ID
+     */
+    public Ip4Address ueSessionId() {
+        return ueSessionId;
+    }
+
+    /**
+     * Get PDR Counter ID associated with UPF Termination rule.
+     *
+     * @return PDR counter cell ID
+     */
+    public Integer counterId() {
+        return ctrId;
+    }
+
+    /**
+     * Get Traffic Class set by this UPF Termination rule.
+     *
+     * @return Traffic Class
+     */
+    public Byte trafficClass() {
+        return trafficClass;
+    }
+
+    /**
+     * Get GTP TEID set by this UPF Termination rule.
+     *
+     * @return GTP tunnel ID
+     */
+    public Integer teid() {
+        return teid;
+    }
+
+    /**
+     * Get QoS Flow Identifier set by this UPF Termination rule.
+     *
+     * @return QoS Flow Identifier
+     */
+    public Byte qfi() {
+        return qfi;
+    }
+
+    /**
+     * True if this UPF Termination needs to drop traffic.
+     *
+     * @return true if the UPF Termination needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.TERMINATION_DOWNLINK;
+    }
+
+    @Override
+    public String toString() {
+        return "TerminationUL{" + matchString() + "->" + actionString() + "}";
+    }
+
+    private String matchString() {
+        return "Match(ue_addr=" + this.ueSessionId() + ")";
+    }
+
+    private String actionString() {
+        return "(TEID=" + this.teid() +
+                ", CTR_ID=" + this.counterId() +
+                ", QFI=" + this.qfi() +
+                ", TC=" + this.trafficClass() +
+                ")";
+    }
+
+    public static class Builder {
+        private Ip4Address ueSessionId = null;
+        private Integer ctrId = null;
+        private Byte trafficClass = null;
+        private Integer teid = null;
+        private Byte qfi = null;
+        private boolean drop = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the ID of the UE session.
+         *
+         * @param ueSessionId UE session ID
+         * @return This builder object
+         */
+        public Builder withUeSessionId(Ip4Address ueSessionId) {
+            this.ueSessionId = ueSessionId;
+            return this;
+        }
+
+        /**
+         * Set the dataplane counter cell ID.
+         *
+         * @param ctrId PDR counter cell ID
+         * @return This builder object
+         */
+        public Builder withCounterId(int ctrId) {
+            this.ctrId = ctrId;
+            return this;
+        }
+
+        /**
+         * Set the Traffic Class.
+         *
+         * @param trafficClass Traffic Class
+         * @return This builder object
+         */
+        public Builder withTrafficClass(byte trafficClass) {
+            this.trafficClass = trafficClass;
+            return this;
+        }
+
+        /**
+         * Set the identifier of the unidirectional GTP tunnel that should be used for the UE Session.
+         *
+         * @param teid tunnel ID
+         * @return This builder object
+         */
+        public Builder withTeid(Integer teid) {
+            this.teid = teid;
+            return this;
+        }
+
+        /**
+         * Set the QoS Flow Identifier.
+         *
+         * @param qfi GTP Tunnel QFI
+         * @return This builder object
+         */
+        public Builder withQfi(byte qfi) {
+            this.qfi = qfi;
+            return this;
+        }
+
+        /**
+         * Sets whether to drop downlink UPF termination traffic or not.
+         *
+         * @param drop True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean drop) {
+            this.drop = drop;
+            return this;
+        }
+
+
+        public UpfTerminationDownlink build() {
+            // Match fields must be provided
+            checkNotNull(ueSessionId, "UE session ID must be provided");
+
+            checkNotNull(ctrId, "Counter ID must be provided");
+            // TODO: should we verify that when dropping no other fields are provided
+            return new UpfTerminationDownlink(
+                    this.ueSessionId, this.ctrId, this.trafficClass, this.teid,
+                    this.qfi, this.drop
+            );
+        }
+
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationUplink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationUplink.java
new file mode 100644
index 0000000..74dbc01
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationUplink.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2021-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.net.behaviour.upf;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Termination in the uplink direction on the
+ * UPF-programmable device.
+ * Provides means to configure the traffic behavior (e.g. set Traffic Class).
+ */
+@Beta
+public final class UpfTerminationUplink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address ueSessionId; // UE Session ID, use UE IP address to uniquely identify a session.
+    // Action parameters
+    private final Integer ctrId;  // Counter ID unique to this UPF Termination Rule
+    private final Byte trafficClass;
+    private final boolean dropping;
+
+    private UpfTerminationUplink(Ip4Address ueSessionId, Integer ctrId, Byte trafficClass,
+                                 boolean dropping) {
+        this.ueSessionId = ueSessionId;
+        this.ctrId = ctrId;
+        this.trafficClass = trafficClass;
+        this.dropping = dropping;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        UpfTerminationUplink that = (UpfTerminationUplink) obj;
+
+        // Safe comparisons between potentially null objects
+        return this.dropping == that.dropping &&
+                Objects.equals(this.ueSessionId, that.ueSessionId) &&
+                Objects.equals(this.ctrId, that.ctrId) &&
+                Objects.equals(this.trafficClass, that.trafficClass);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ueSessionId, ctrId, trafficClass, dropping);
+    }
+
+    /**
+     * Get UE Session ID associated with UPF Termination rule.
+     *
+     * @return UE Session ID
+     */
+    public Ip4Address ueSessionId() {
+        return ueSessionId;
+    }
+
+    /**
+     * Get PDR Counter ID associated with UPF Termination rule.
+     *
+     * @return PDR counter cell ID
+     */
+    public Integer counterId() {
+        return ctrId;
+    }
+
+    /**
+     * Get Traffic Class set by this UPF Termination rule.
+     *
+     * @return Traffic Class
+     */
+    public Byte trafficClass() {
+        return trafficClass;
+    }
+
+    /**
+     * True if this UPF Termination needs to drop traffic.
+     *
+     * @return true if the UPF Termination needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.TERMINATION_UPLINK;
+    }
+
+    @Override
+    public String toString() {
+        return "TerminationDL{" + matchString() + "->" + actionString() + "}";
+    }
+
+    private String matchString() {
+        return "Match(ue_addr=" + this.ueSessionId() + ")";
+    }
+
+    private String actionString() {
+        return "(CTR_ID=" + this.counterId() + ", TC=" + this.trafficClass() + ")";
+    }
+
+    public static class Builder {
+        private Ip4Address ueSessionId = null;
+        private Integer ctrId = null;
+        private Byte trafficClass = null;
+        private boolean dropping = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the ID of the UE session.
+         *
+         * @param ueSessionId UE session ID
+         * @return This builder object
+         */
+        public Builder withUeSessionId(Ip4Address ueSessionId) {
+            this.ueSessionId = ueSessionId;
+            return this;
+        }
+
+        /**
+         * Set the dataplane counter cell ID.
+         *
+         * @param ctrId PDR counter cell ID
+         * @return This builder object
+         */
+        public Builder withCounterId(int ctrId) {
+            this.ctrId = ctrId;
+            return this;
+        }
+
+        /**
+         * Set the Traffic Class.
+         *
+         * @param trafficClass Traffic Class
+         * @return This builder object
+         */
+        public Builder withTrafficClass(byte trafficClass) {
+            this.trafficClass = trafficClass;
+            return this;
+        }
+
+        /**
+         * Sets whether to drop uplink UPF termination traffic or not.
+         *
+         * @param dropping True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean dropping) {
+            this.dropping = dropping;
+            return this;
+        }
+
+        public UpfTerminationUplink build() {
+            // Match fields must be provided
+            checkNotNull(ueSessionId, "UE session ID must be provided");
+
+            checkNotNull(ctrId, "Counter ID must be provided");
+            // TODO: should we verify that when dropping no other fields are provided
+            return new UpfTerminationUplink(
+                    this.ueSessionId, this.ctrId, this.trafficClass, this.dropping);
+        }
+
+    }
+
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
index 7d1ee4e..e17b48e 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
@@ -19,8 +19,6 @@
 import org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable;
 
 import org.onosproject.net.behaviour.BngProgrammable;
-import org.onosproject.net.behaviour.upf.UpfProgrammable;
-import org.onosproject.pipelines.fabric.impl.behaviour.upf.FabricUpfProgrammable;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconf;
@@ -101,11 +99,6 @@
         if (profileName.endsWith(BNG_PROFILE_SUFFIX)) {
             pipeconfBuilder.addBehaviour(BngProgrammable.class, FabricBngProgrammable.class);
         }
-        // Add UpfProgrammable behavior for UPF-enabled pipelines.
-        if (profileName.contains(UPF_PROFILE_SUFFIX) ||
-                profileName.endsWith(FULL_PROFILE_SUFFIX)) {
-            pipeconfBuilder.addBehaviour(UpfProgrammable.class, FabricUpfProgrammable.class);
-        }
         return pipeconfBuilder.build();
     }
 
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/cli/ReadInternalUpfStoreCommand.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/cli/ReadInternalUpfStoreCommand.java
deleted file mode 100644
index 31dd975..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/cli/ReadInternalUpfStoreCommand.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.cli;
-
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.Option;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.pipelines.fabric.impl.behaviour.upf.FabricUpfStore;
-import org.onosproject.pipelines.fabric.impl.behaviour.upf.UpfRuleIdentifier;
-
-import java.util.Map;
-
-/**
- * Read internal UPF store of fabric.
- */
-@Service
-@Command(scope = "fabric", name = "upf-read-internal-store",
-        description = "Print internal UPF stores")
-public class ReadInternalUpfStoreCommand extends AbstractShellCommand {
-    @Option(name = "-v", aliases = "--verbose",
-            description = "Print more detail of each entry",
-            required = false, multiValued = false)
-    private boolean verbose = false;
-
-    @Override
-    protected void doExecute() {
-        FabricUpfStore upfStore = get(FabricUpfStore.class);
-
-        if (upfStore == null) {
-            print("Error: FabricUpfStore is null");
-            return;
-        }
-
-        Map<Integer, UpfRuleIdentifier> reverseFarIdMap = upfStore.getReverseFarIdMap();
-        print("reverseFarIdMap size: " + reverseFarIdMap.size());
-        if (verbose) {
-            reverseFarIdMap.entrySet().forEach(entry -> print(entry.toString()));
-        }
-    }
-}
\ No newline at end of file
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/cli/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/cli/package-info.java
deleted file mode 100644
index f78dd7a..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/cli/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2021-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.
- */
-
-/**
- * CLI commands for fabric UPF.
- */
-package org.onosproject.pipelines.fabric.impl.behaviour.cli;
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/DistributedFabricUpfStore.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/DistributedFabricUpfStore.java
deleted file mode 100644
index 7045c2d..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/DistributedFabricUpfStore.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.hash.HashCode;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hashing;
-import org.onlab.util.ImmutableByteSequence;
-import org.onlab.util.KryoNamespace;
-import org.onosproject.store.serializers.KryoNamespaces;
-import org.onosproject.store.service.EventuallyConsistentMap;
-import org.onosproject.store.service.StorageService;
-import org.onosproject.store.service.WallClockTimestamp;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/**
- * Distributed implementation of FabricUpfStore.
- */
-@Component(immediate = true, service = FabricUpfStore.class)
-public final class DistributedFabricUpfStore implements FabricUpfStore {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    protected StorageService storageService;
-
-    protected static final String FAR_ID_MAP_NAME = "fabric-upf-far-id";
-    protected static final KryoNamespace.Builder SERIALIZER = KryoNamespace.newBuilder()
-            .register(KryoNamespaces.API)
-            .register(UpfRuleIdentifier.class);
-
-    // EC map to remember the mapping far_id -> rule_id this is mostly used during reads,
-    // it can be definitely removed by simplifying the logical pipeline
-    protected EventuallyConsistentMap<Integer, UpfRuleIdentifier> reverseFarIdMap;
-
-    @Activate
-    protected void activate() {
-        // Allow unit test to inject reverseFarIdMap here.
-        if (storageService != null) {
-            this.reverseFarIdMap = storageService.<Integer, UpfRuleIdentifier>eventuallyConsistentMapBuilder()
-                    .withName(FAR_ID_MAP_NAME)
-                    .withSerializer(SERIALIZER)
-                    .withTimestampProvider((k, v) -> new WallClockTimestamp())
-                    .build();
-        }
-
-        log.info("Started");
-    }
-
-    @Deactivate
-    protected void deactivate() {
-        reverseFarIdMap.destroy();
-
-        log.info("Stopped");
-    }
-
-    @Override
-    public void reset() {
-        reverseFarIdMap.clear();
-    }
-
-    @Override
-    public Map<Integer, UpfRuleIdentifier> getReverseFarIdMap() {
-        return reverseFarIdMap.entrySet().stream()
-                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-    }
-
-    @Override
-    public int globalFarIdOf(UpfRuleIdentifier farIdPair) {
-        int globalFarId = getGlobalFarIdOf(farIdPair);
-        reverseFarIdMap.put(globalFarId, farIdPair);
-        log.info("{} translated to GlobalFarId={}", farIdPair, globalFarId);
-        return globalFarId;
-    }
-
-    @Override
-    public int removeGlobalFarId(UpfRuleIdentifier farIdPair) {
-        int globalFarId = getGlobalFarIdOf(farIdPair);
-        reverseFarIdMap.remove(globalFarId);
-        return globalFarId;
-    }
-
-    @Override
-    public int globalFarIdOf(ImmutableByteSequence pfcpSessionId, int sessionLocalFarId) {
-        UpfRuleIdentifier farId = new UpfRuleIdentifier(pfcpSessionId, sessionLocalFarId);
-        return globalFarIdOf(farId);
-    }
-
-    @Override
-    public int removeGlobalFarId(ImmutableByteSequence pfcpSessionId, int sessionLocalFarId) {
-        UpfRuleIdentifier farId = new UpfRuleIdentifier(pfcpSessionId, sessionLocalFarId);
-        return removeGlobalFarId(farId);
-    }
-
-    @Override
-    public UpfRuleIdentifier localFarIdOf(int globalFarId) {
-        return reverseFarIdMap.get(globalFarId);
-    }
-
-    // Compute global far id by hashing the pfcp session id and the session local far
-    private int getGlobalFarIdOf(UpfRuleIdentifier farIdPair) {
-        HashFunction hashFunction = Hashing.murmur3_32();
-        HashCode hashCode = hashFunction.newHasher()
-                .putInt(farIdPair.getSessionLocalId())
-                .putBytes(farIdPair.getPfcpSessionId().asArray())
-                .hash();
-        return hashCode.asInt();
-    }
-
-}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammable.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammable.java
deleted file mode 100644
index 310f28d..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammable.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import org.onlab.packet.Ip4Prefix;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.CoreService;
-import org.onosproject.drivers.p4runtime.AbstractP4RuntimeHandlerBehaviour;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.behaviour.upf.ForwardingActionRule;
-import org.onosproject.net.behaviour.upf.PacketDetectionRule;
-import org.onosproject.net.behaviour.upf.PdrStats;
-import org.onosproject.net.behaviour.upf.UpfInterface;
-import org.onosproject.net.behaviour.upf.UpfProgrammable;
-import org.onosproject.net.behaviour.upf.UpfProgrammableException;
-import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowEntry;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.flow.criteria.PiCriterion;
-import org.onosproject.net.packet.DefaultOutboundPacket;
-import org.onosproject.net.packet.OutboundPacket;
-import org.onosproject.net.packet.PacketService;
-import org.onosproject.net.pi.model.PiCounterId;
-import org.onosproject.net.pi.model.PiCounterModel;
-import org.onosproject.net.pi.model.PiTableId;
-import org.onosproject.net.pi.model.PiTableModel;
-import org.onosproject.net.pi.runtime.PiCounterCell;
-import org.onosproject.net.pi.runtime.PiCounterCellHandle;
-import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.pipelines.fabric.impl.FabricPipeconfLoader;
-import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import static org.onosproject.net.behaviour.upf.UpfProgrammableException.Type.UNSUPPORTED_OPERATION;
-import static org.onosproject.net.pi.model.PiCounterType.INDIRECT;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_EGRESS_SPGW_PDR_COUNTER;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_FARS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_INTERFACES;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_PDR_COUNTER;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_UPLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_FAR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_GTPU_IS_VALID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_HAS_QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_IPV4_DST_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TEID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TUNNEL_IPV4_DST;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_UE_ADDR;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_QFI;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FALSE;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.TRUE;
-
-/**
- * Implementation of a UPF programmable device behavior.
- */
-public class FabricUpfProgrammable extends AbstractP4RuntimeHandlerBehaviour
-        implements UpfProgrammable {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private static final int DEFAULT_PRIORITY = 128;
-    private static final long DEFAULT_P4_DEVICE_ID = 1;
-
-    protected FlowRuleService flowRuleService;
-    protected PacketService packetService;
-    protected FabricUpfStore fabricUpfStore;
-    protected FabricUpfTranslator upfTranslator;
-
-    private long farTableSize;
-    private long encappedPdrTableSize;
-    private long unencappedPdrTableSize;
-    private long pdrCounterSize;
-
-    private ApplicationId appId;
-
-    @Override
-    protected boolean setupBehaviour(String opName) {
-        // Already initialized.
-        if (appId != null) {
-            return true;
-        }
-
-        if (!super.setupBehaviour(opName)) {
-            return false;
-        }
-
-        if (!computeHardwareResourceSizes()) {
-            // error message will be printed by computeHardwareResourceSizes()
-            return false;
-        }
-
-        flowRuleService = handler().get(FlowRuleService.class);
-        packetService = handler().get(PacketService.class);
-        fabricUpfStore = handler().get(FabricUpfStore.class);
-        upfTranslator = new FabricUpfTranslator(fabricUpfStore);
-        final CoreService coreService = handler().get(CoreService.class);
-        appId = coreService.getAppId(FabricPipeconfLoader.PIPELINE_APP_NAME_UPF);
-        if (appId == null) {
-            log.warn("Application ID is null. Cannot initialize behaviour.");
-            return false;
-        }
-
-        var capabilities = new FabricCapabilities(pipeconf);
-        if (!capabilities.supportUpf()) {
-            log.warn("Pipeconf {} on {} does not support UPF capabilities, " +
-                             "cannot perform {}",
-                     pipeconf.id(), deviceId, opName);
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean init() {
-        if (setupBehaviour("init()")) {
-            log.info("UpfProgrammable initialized for appId {} and deviceId {}", appId, deviceId);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean fromThisUpf(FlowRule flowRule) {
-        return flowRule.deviceId().equals(this.deviceId) &&
-                flowRule.appId() == appId.id();
-    }
-
-    /**
-     * Grab the capacities for the PDR and FAR tables from the pipeconf. Runs only once, on initialization.
-     *
-     * @return true if resource is fetched successfully, false otherwise.
-     * @throws IllegalStateException when FAR or PDR table can't be found in the pipeline model.
-     */
-    private boolean computeHardwareResourceSizes() {
-        long farTableSize = 0;
-        long encappedPdrTableSize = 0;
-        long unencappedPdrTableSize = 0;
-
-        // Get table sizes of interest
-        for (PiTableModel piTable : pipeconf.pipelineModel().tables()) {
-            if (piTable.id().equals(FABRIC_INGRESS_SPGW_UPLINK_PDRS)) {
-                encappedPdrTableSize = piTable.maxSize();
-            } else if (piTable.id().equals(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS)) {
-                unencappedPdrTableSize = piTable.maxSize();
-            } else if (piTable.id().equals(FABRIC_INGRESS_SPGW_FARS)) {
-                farTableSize = piTable.maxSize();
-            }
-        }
-        if (encappedPdrTableSize == 0) {
-            throw new IllegalStateException("Unable to find uplink PDR table in pipeline model.");
-        }
-        if (unencappedPdrTableSize == 0) {
-            throw new IllegalStateException("Unable to find downlink PDR table in pipeline model.");
-        }
-        if (encappedPdrTableSize != unencappedPdrTableSize) {
-            log.warn("The uplink and downlink PDR tables don't have equal sizes! Using the minimum of the two.");
-        }
-        if (farTableSize == 0) {
-            throw new IllegalStateException("Unable to find FAR table in pipeline model.");
-        }
-        // Get counter sizes of interest
-        long ingressCounterSize = 0;
-        long egressCounterSize = 0;
-        for (PiCounterModel piCounter : pipeconf.pipelineModel().counters()) {
-            if (piCounter.id().equals(FABRIC_INGRESS_SPGW_PDR_COUNTER)) {
-                ingressCounterSize = piCounter.size();
-            } else if (piCounter.id().equals(FABRIC_EGRESS_SPGW_PDR_COUNTER)) {
-                egressCounterSize = piCounter.size();
-            }
-        }
-        if (ingressCounterSize != egressCounterSize) {
-            log.warn("PDR ingress and egress counter sizes are not equal! Using the minimum of the two.");
-        }
-        this.farTableSize = farTableSize;
-        this.encappedPdrTableSize = encappedPdrTableSize;
-        this.unencappedPdrTableSize = unencappedPdrTableSize;
-        this.pdrCounterSize = Math.min(ingressCounterSize, egressCounterSize);
-        return true;
-    }
-
-    @Override
-    public void enablePscEncap(int defaultQfi) throws UpfProgrammableException {
-        throw new UpfProgrammableException("PSC encap is not supported in fabric-v1model",
-                                           UNSUPPORTED_OPERATION);
-    }
-
-    @Override
-    public void disablePscEncap() throws UpfProgrammableException {
-        throw new UpfProgrammableException("PSC encap is not supported in fabric-v1model",
-                                           UNSUPPORTED_OPERATION);
-    }
-
-    @Override
-    public void sendPacketOut(ByteBuffer data) {
-        if (!setupBehaviour("sendPacketOut()")) {
-            return;
-        }
-        final OutboundPacket pkt = new DefaultOutboundPacket(
-                deviceId,
-                // Use TABLE logical port to have pkt routed via pipeline tables.
-                DefaultTrafficTreatment.builder()
-                        .setOutput(PortNumber.TABLE)
-                        .build(),
-                data);
-        packetService.emit(pkt);
-    }
-
-    @Override
-    public void cleanUp() {
-        if (!setupBehaviour("cleanUp()")) {
-            return;
-        }
-        log.info("Clearing all UPF-related table entries.");
-        // Getting flow entries by device ID and filtering by Application ID
-        // is more efficient than getting by Application ID and filtering for a
-        // device ID.
-        List<FlowEntry> flowEntriesToRemove = StreamSupport.stream(
-                flowRuleService.getFlowEntries(deviceId).spliterator(), false)
-                .filter(flowEntry -> flowEntry.appId() == appId.id()).collect(Collectors.toList());
-        flowRuleService.removeFlowRules(flowEntriesToRemove.toArray(new FlowRule[0]));
-        fabricUpfStore.reset();
-    }
-
-    @Override
-    public void clearInterfaces() {
-        if (!setupBehaviour("clearInterfaces()")) {
-            return;
-        }
-        log.info("Clearing all UPF interfaces.");
-        for (FlowRule entry : flowRuleService.getFlowEntries(deviceId)) {
-            if (upfTranslator.isFabricInterface(entry)) {
-                flowRuleService.removeFlowRules(entry);
-            }
-        }
-    }
-
-    @Override
-    public void clearFlows() {
-        if (!setupBehaviour("clearFlows()")) {
-            return;
-        }
-        log.info("Clearing all UE sessions.");
-        int pdrsCleared = 0;
-        int farsCleared = 0;
-        for (FlowRule entry : flowRuleService.getFlowEntries(deviceId)) {
-            if (upfTranslator.isFabricPdr(entry)) {
-                pdrsCleared++;
-                flowRuleService.removeFlowRules(entry);
-            } else if (upfTranslator.isFabricFar(entry)) {
-                farsCleared++;
-                flowRuleService.removeFlowRules(entry);
-            }
-        }
-        log.info("Cleared {} PDRs and {} FARS.", pdrsCleared, farsCleared);
-    }
-
-
-    @Override
-    public Collection<PdrStats> readAllCounters(long maxCounterId) {
-        if (!setupBehaviour("readAllCounters()")) {
-            return null;
-        }
-
-        long counterSize = pdrCounterSize();
-        if (maxCounterId != -1) {
-            counterSize = Math.min(maxCounterId, counterSize);
-        }
-
-        // Prepare PdrStats object builders, one for each counter ID currently in use
-        Map<Integer, PdrStats.Builder> pdrStatBuilders = Maps.newHashMap();
-        for (int cellId = 0; cellId < counterSize; cellId++) {
-            pdrStatBuilders.put(cellId, PdrStats.builder().withCellId(cellId));
-        }
-
-        // Generate the counter cell IDs.
-        Set<PiCounterId> counterIds = ImmutableSet.of(
-                FABRIC_INGRESS_SPGW_PDR_COUNTER,
-                FABRIC_EGRESS_SPGW_PDR_COUNTER
-        );
-
-        // Query the device.
-        Collection<PiCounterCell> counterEntryResponse = client.read(
-                DEFAULT_P4_DEVICE_ID, pipeconf)
-                .counterCells(counterIds)
-                .submitSync()
-                .all(PiCounterCell.class);
-
-        // Process response.
-        counterEntryResponse.forEach(counterCell -> {
-            if (counterCell.cellId().counterType() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
-                return;
-            }
-            if (!pdrStatBuilders.containsKey((int) counterCell.cellId().index())) {
-                // Most likely Up4config.maxUes() is set to a value smaller than what the switch
-                // pipeline can hold.
-                log.debug("Unrecognized index {} when reading all counters, " +
-                                  "that's expected if we are manually limiting maxUes", counterCell);
-                return;
-            }
-            PdrStats.Builder statsBuilder = pdrStatBuilders.get((int) counterCell.cellId().index());
-            if (counterCell.cellId().counterId().equals(FABRIC_INGRESS_SPGW_PDR_COUNTER)) {
-                statsBuilder.setIngress(counterCell.data().packets(),
-                                        counterCell.data().bytes());
-            } else if (counterCell.cellId().counterId().equals(FABRIC_EGRESS_SPGW_PDR_COUNTER)) {
-                statsBuilder.setEgress(counterCell.data().packets(),
-                                       counterCell.data().bytes());
-            } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterCell);
-            }
-        });
-
-        return pdrStatBuilders
-                .values()
-                .stream()
-                .map(PdrStats.Builder::build)
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public long pdrCounterSize() {
-        if (!setupBehaviour("pdrCounterSize()")) {
-            return -1;
-        }
-        return pdrCounterSize;
-    }
-
-    @Override
-    public long farTableSize() {
-        if (!setupBehaviour("farTableSize()")) {
-            return -1;
-        }
-        return farTableSize;
-    }
-
-    @Override
-    public long pdrTableSize() {
-        if (!setupBehaviour("pdrTableSize()")) {
-            return -1;
-        }
-        return Math.min(encappedPdrTableSize, unencappedPdrTableSize) * 2;
-    }
-
-    @Override
-    public PdrStats readCounter(int cellId) throws UpfProgrammableException {
-        if (!setupBehaviour("readCounter()")) {
-            return null;
-        }
-        if (cellId >= pdrCounterSize() || cellId < 0) {
-            throw new UpfProgrammableException("Requested PDR counter cell index is out of bounds.",
-                                               UpfProgrammableException.Type.COUNTER_INDEX_OUT_OF_RANGE);
-        }
-        PdrStats.Builder stats = PdrStats.builder().withCellId(cellId);
-
-        // Make list of cell handles we want to read.
-        List<PiCounterCellHandle> counterCellHandles = List.of(
-                PiCounterCellHandle.of(deviceId,
-                                       PiCounterCellId.ofIndirect(FABRIC_INGRESS_SPGW_PDR_COUNTER, cellId)),
-                PiCounterCellHandle.of(deviceId,
-                                       PiCounterCellId.ofIndirect(FABRIC_EGRESS_SPGW_PDR_COUNTER, cellId)));
-
-        // Query the device.
-        Collection<PiCounterCell> counterEntryResponse = client.read(
-                DEFAULT_P4_DEVICE_ID, pipeconf)
-                .handles(counterCellHandles).submitSync()
-                .all(PiCounterCell.class);
-
-        // Process response.
-        counterEntryResponse.forEach(counterCell -> {
-            if (counterCell.cellId().counterType() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
-                return;
-            }
-            if (cellId != counterCell.cellId().index()) {
-                log.warn("Unrecognized counter index {}, skipping", counterCell);
-                return;
-            }
-            if (counterCell.cellId().counterId().equals(FABRIC_INGRESS_SPGW_PDR_COUNTER)) {
-                stats.setIngress(counterCell.data().packets(), counterCell.data().bytes());
-            } else if (counterCell.cellId().counterId().equals(FABRIC_EGRESS_SPGW_PDR_COUNTER)) {
-                stats.setEgress(counterCell.data().packets(), counterCell.data().bytes());
-            } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterCell);
-            }
-        });
-        return stats.build();
-    }
-
-
-    @Override
-    public void addPdr(PacketDetectionRule pdr) throws UpfProgrammableException {
-        if (!setupBehaviour("addPdr()")) {
-            return;
-        }
-        if (pdr.counterId() >= pdrCounterSize() || pdr.counterId() < 0) {
-            throw new UpfProgrammableException("Counter cell index referenced by PDR is out of bounds.",
-                                               UpfProgrammableException.Type.COUNTER_INDEX_OUT_OF_RANGE);
-        }
-        FlowRule fabricPdr = upfTranslator.pdrToFabricEntry(pdr, deviceId, appId, DEFAULT_PRIORITY);
-        log.info("Installing {}", pdr.toString());
-        flowRuleService.applyFlowRules(fabricPdr);
-        log.debug("PDR added with flowID {}", fabricPdr.id().value());
-    }
-
-
-    @Override
-    public void addFar(ForwardingActionRule far) throws UpfProgrammableException {
-        if (!setupBehaviour("addFar()")) {
-            return;
-        }
-        FlowRule fabricFar = upfTranslator.farToFabricEntry(far, deviceId, appId, DEFAULT_PRIORITY);
-        log.info("Installing {}", far.toString());
-        flowRuleService.applyFlowRules(fabricFar);
-        log.debug("FAR added with flowID {}", fabricFar.id().value());
-    }
-
-    @Override
-    public void addInterface(UpfInterface upfInterface) throws UpfProgrammableException {
-        if (!setupBehaviour("addInterface()")) {
-            return;
-        }
-        FlowRule flowRule = upfTranslator.interfaceToFabricEntry(upfInterface, deviceId, appId, DEFAULT_PRIORITY);
-        log.info("Installing {}", upfInterface);
-        flowRuleService.applyFlowRules(flowRule);
-        log.debug("Interface added with flowID {}", flowRule.id().value());
-        // By default we enable UE-to-UE communication on the UE subnet identified by the CORE interface.
-        // TODO: allow enabling/disabling UE-to-UE via netcfg or other API.
-        log.warn("UE-to-UE traffic is not supported in fabric-v1model");
-    }
-
-    private boolean removeEntry(PiCriterion match, PiTableId tableId, boolean failSilent)
-            throws UpfProgrammableException {
-        FlowRule entry = DefaultFlowRule.builder()
-                .forDevice(deviceId).fromApp(appId).makePermanent()
-                .forTable(tableId)
-                .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
-                .withPriority(DEFAULT_PRIORITY)
-                .build();
-        try {
-            flowRuleService.removeFlowRules(entry);
-            // TODO in future we may need to send other notifications to the pfcp agent
-            //if (!failSilent) {
-            //    throw new UpfProgrammableException("Match criterion " + match.toString() +
-            //            " not found in table " + tableId.toString());
-            //}
-            return true;
-        } catch (Exception e) {
-            log.error("Exception thrown while removing flows", e);
-        }
-        // Assumes that the ONOS state is ok and the pfcp agent
-        // is not asking to remove wrong flows
-        if (!failSilent) {
-            throw new UpfProgrammableException("Unable to remove FlowRule with match criterion " + match.toString() +
-                    " in table " + tableId.toString());
-        }
-        return false;
-    }
-
-    @Override
-    public Collection<PacketDetectionRule> getPdrs() throws UpfProgrammableException {
-        if (!setupBehaviour("getPdrs()")) {
-            return null;
-        }
-        ArrayList<PacketDetectionRule> pdrs = new ArrayList<>();
-        for (FlowRule flowRule : flowRuleService.getFlowEntries(deviceId)) {
-            if (upfTranslator.isFabricPdr(flowRule)) {
-                pdrs.add(upfTranslator.fabricEntryToPdr(flowRule));
-            }
-        }
-        return pdrs;
-    }
-
-    @Override
-    public Collection<ForwardingActionRule> getFars() throws UpfProgrammableException {
-        if (!setupBehaviour("getFars()")) {
-            return null;
-        }
-        ArrayList<ForwardingActionRule> fars = new ArrayList<>();
-        for (FlowRule flowRule : flowRuleService.getFlowEntries(deviceId)) {
-            if (upfTranslator.isFabricFar(flowRule)) {
-                fars.add(upfTranslator.fabricEntryToFar(flowRule));
-            }
-        }
-        return fars;
-    }
-
-    @Override
-    public Collection<UpfInterface> getInterfaces() throws UpfProgrammableException {
-        if (!setupBehaviour("getInterfaces()")) {
-            return null;
-        }
-        ArrayList<UpfInterface> ifaces = new ArrayList<>();
-        for (FlowRule flowRule : flowRuleService.getFlowEntries(deviceId)) {
-            if (upfTranslator.isFabricInterface(flowRule)) {
-                ifaces.add(upfTranslator.fabricEntryToInterface(flowRule));
-            }
-        }
-        return ifaces;
-    }
-
-    @Override
-    public void removePdr(PacketDetectionRule pdr) throws UpfProgrammableException {
-        if (!setupBehaviour("removePdr()")) {
-            return;
-        }
-        final PiCriterion match;
-        final PiTableId tableId;
-        if (pdr.matchesEncapped()) {
-            PiCriterion.Builder criterionBuilder = PiCriterion.builder()
-                    .matchExact(HDR_TEID, pdr.teid().asArray())
-                    .matchExact(HDR_TUNNEL_IPV4_DST, pdr.tunnelDest().toInt());
-            if (pdr.matchQfi()) {
-                criterionBuilder.matchExact(HDR_HAS_QFI, TRUE);
-                criterionBuilder.matchExact(HDR_QFI, pdr.qfi());
-            } else {
-                criterionBuilder.matchExact(HDR_HAS_QFI, FALSE);
-                criterionBuilder.matchExact(HDR_QFI, DEFAULT_QFI);
-            }
-            match = criterionBuilder.build();
-            tableId = FABRIC_INGRESS_SPGW_UPLINK_PDRS;
-        } else {
-            match = PiCriterion.builder()
-                    .matchExact(HDR_UE_ADDR, pdr.ueAddress().toInt())
-                    .build();
-            tableId = FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
-        }
-        log.info("Removing {}", pdr.toString());
-        removeEntry(match, tableId, false);
-    }
-
-    @Override
-    public void removeFar(ForwardingActionRule far) throws UpfProgrammableException {
-        if (!setupBehaviour("removeFar()")) {
-            return;
-        }
-        log.info("Removing {}", far.toString());
-
-        PiCriterion match = PiCriterion.builder()
-                .matchExact(HDR_FAR_ID, fabricUpfStore.removeGlobalFarId(far.sessionId(), far.farId()))
-                .build();
-
-        removeEntry(match, FABRIC_INGRESS_SPGW_FARS, false);
-    }
-
-    @Override
-    public void removeInterface(UpfInterface upfInterface) throws UpfProgrammableException {
-        if (!setupBehaviour("removeInterface()")) {
-            return;
-        }
-        Ip4Prefix ifacePrefix = upfInterface.getPrefix();
-        // If it isn't a core interface (so it is either access/dbuf or unknown), try removing first
-        // access/dbuf interfaces and then fall through in the next step where we try to remove the core flow
-        if (!upfInterface.isCore()) {
-            PiCriterion match1 = PiCriterion.builder()
-                    .matchLpm(HDR_IPV4_DST_ADDR, ifacePrefix.address().toInt(),
-                              ifacePrefix.prefixLength())
-                    .matchExact(HDR_GTPU_IS_VALID, 1)
-                    .build();
-            // removeEntry does return false only for severe issues, before we had
-            // a safe fall through. This part should not be affected since core and access
-            // flows are different in the match keys and should not result in wrong removal
-            removeEntry(match1, FABRIC_INGRESS_SPGW_INTERFACES, true);
-        }
-        // This additional step might be also needed in case of unknown interfaces
-        PiCriterion match2 = PiCriterion.builder()
-                .matchLpm(HDR_IPV4_DST_ADDR, ifacePrefix.address().toInt(),
-                          ifacePrefix.prefixLength())
-                .matchExact(HDR_GTPU_IS_VALID, 0)
-                .build();
-        removeEntry(match2, FABRIC_INGRESS_SPGW_INTERFACES, false);
-    }
-}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfStore.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfStore.java
deleted file mode 100644
index 8e9e92b..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfStore.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onlab.util.ImmutableByteSequence;
-
-import java.util.Map;
-
-/**
- * Stores state required for translation of UPF entities to pipeline-specific ones.
- */
-public interface FabricUpfStore {
-    /**
-     * Clear all state associated with translation.
-     */
-    void reset();
-
-    /**
-     * Returns the reverseFarIdMap.
-     *
-     * @return the farIdMap.
-     */
-    Map<Integer, UpfRuleIdentifier> getReverseFarIdMap();
-
-    /**
-     * Get a globally unique integer identifier for the FAR identified by the given (Session ID, Far
-     * ID) pair.
-     *
-     * @param farIdPair a RuleIdentifier instance uniquely identifying the FAR
-     * @return A globally unique integer identifier
-     */
-    int globalFarIdOf(UpfRuleIdentifier farIdPair);
-
-    /**
-     * Remove the global far id from the system.
-     *
-     * @param farIdPair a RuleIdentifier instance uniquely identifying the FAR
-     * @return A globally unique integer identifier
-     */
-    int removeGlobalFarId(UpfRuleIdentifier farIdPair);
-
-    /**
-     * Get a globally unique integer identifier for the FAR identified by the given (Session ID, Far
-     * ID) pair.
-     *
-     * @param pfcpSessionId     The ID of the PFCP session that produced the FAR ID.
-     * @param sessionLocalFarId The FAR ID.
-     * @return A globally unique integer identifier
-     */
-    int globalFarIdOf(ImmutableByteSequence pfcpSessionId, int sessionLocalFarId);
-
-    /**
-     * Remove the global far id from the system.
-     *
-     * @param pfcpSessionId     The ID of the PFCP session that produced the FAR ID.
-     * @param sessionLocalFarId The FAR ID.
-     * @return A globally unique integer identifier
-     */
-    int removeGlobalFarId(ImmutableByteSequence pfcpSessionId, int sessionLocalFarId);
-
-    /**
-     * Get the corresponding PFCP session ID and session-local FAR ID from a globally unique FAR ID,
-     * or return null if no such mapping is found.
-     *
-     * @param globalFarId globally unique FAR ID
-     * @return the corresponding PFCP session ID and session-local FAR ID, as a RuleIdentifier
-     */
-    UpfRuleIdentifier localFarIdOf(int globalFarId);
-}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslator.java
deleted file mode 100644
index f2e7584..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslator.java
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.apache.commons.lang3.tuple.Pair;
-import org.onlab.packet.Ip4Address;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.behaviour.upf.ForwardingActionRule;
-import org.onosproject.net.behaviour.upf.GtpTunnel;
-import org.onosproject.net.behaviour.upf.PacketDetectionRule;
-import org.onosproject.net.behaviour.upf.UpfInterface;
-import org.onosproject.net.behaviour.upf.UpfProgrammableException;
-import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.criteria.PiCriterion;
-import org.onosproject.net.pi.model.PiActionId;
-import org.onosproject.net.pi.model.PiTableId;
-import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiTableAction;
-
-import java.util.Arrays;
-
-import static org.onosproject.pipelines.fabric.FabricConstants.CTR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.DROP;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_FARS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_INTERFACES;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_IFACE;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_NORMAL_FAR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_PDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_UPLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FAR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_FAR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_GTPU_IS_VALID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_HAS_QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_IPV4_DST_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TEID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TUNNEL_IPV4_DST;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_UE_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.NEEDS_GTPU_DECAP;
-import static org.onosproject.pipelines.fabric.FabricConstants.NEEDS_QFI_PUSH;
-import static org.onosproject.pipelines.fabric.FabricConstants.NOTIFY_CP;
-import static org.onosproject.pipelines.fabric.FabricConstants.QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.SLICE_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.SRC_IFACE;
-import static org.onosproject.pipelines.fabric.FabricConstants.TC;
-import static org.onosproject.pipelines.fabric.FabricConstants.TEID;
-import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_DST_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_SRC_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_SRC_PORT;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_QFI;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_SLICE_ID;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_TC;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FALSE;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.TRUE;
-
-/**
- * Provides logic to translate UPF entities into pipeline-specific ones and vice-versa.
- * Implementation should be stateless, with all state delegated to FabricUpfStore.
- */
-public class FabricUpfTranslator {
-
-    // UPF related constants
-    public static final int INTERFACE_ACCESS = 1;
-    public static final int INTERFACE_CORE = 2;
-    public static final int INTERFACE_DBUF = 3;
-
-    private final FabricUpfStore fabricUpfStore;
-
-    public FabricUpfTranslator(FabricUpfStore fabricUpfStore) {
-        this.fabricUpfStore = fabricUpfStore;
-    }
-
-    /**
-     * Returns true if the given table entry is a Packet Detection Rule from the physical fabric pipeline, and
-     * false otherwise.
-     *
-     * @param entry the entry that may or may not be a fabric.p4 PDR
-     * @return true if the entry is a fabric.p4 PDR
-     */
-    public boolean isFabricPdr(FlowRule entry) {
-        return entry.table().equals(FABRIC_INGRESS_SPGW_UPLINK_PDRS)
-                || entry.table().equals(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS);
-    }
-
-    /**
-     * Returns true if the given table entry is a Forwarding Action Rule from the physical fabric pipeline, and
-     * false otherwise.
-     *
-     * @param entry the entry that may or may not be a fabric.p4 FAR
-     * @return true if the entry is a fabric.p4 FAR
-     */
-    public boolean isFabricFar(FlowRule entry) {
-        return entry.table().equals(FABRIC_INGRESS_SPGW_FARS);
-    }
-
-    /**
-     * Returns true if the given table entry is an interface table entry from the fabric.p4 physical pipeline, and
-     * false otherwise.
-     *
-     * @param entry the entry that may or may not be a fabric.p4 UPF interface
-     * @return true if the entry is a fabric.p4 UPF interface
-     */
-    public boolean isFabricInterface(FlowRule entry) {
-        return entry.table().equals(FABRIC_INGRESS_SPGW_INTERFACES);
-    }
-
-
-    /**
-     * Translate a fabric.p4 PDR table entry to a PacketDetectionRule instance for easier handling.
-     *
-     * @param entry the fabric.p4 entry to translate
-     * @return the corresponding PacketDetectionRule
-     * @throws UpfProgrammableException if the entry cannot be translated
-     */
-    public PacketDetectionRule fabricEntryToPdr(FlowRule entry)
-            throws UpfProgrammableException {
-        var pdrBuilder = PacketDetectionRule.builder();
-        Pair<PiCriterion, PiTableAction> matchActionPair = FabricUpfTranslatorUtil.fabricEntryToPiPair(entry);
-        PiCriterion match = matchActionPair.getLeft();
-        PiAction action = (PiAction) matchActionPair.getRight();
-
-        // Grab keys and parameters that are present for all PDRs
-        int globalFarId = FabricUpfTranslatorUtil.getParamInt(action, FAR_ID);
-        UpfRuleIdentifier farId = fabricUpfStore.localFarIdOf(globalFarId);
-        if (farId == null) {
-            throw new UpfProgrammableException(String.format("Unable to find local far id of %s", globalFarId));
-        }
-
-        pdrBuilder.withCounterId(FabricUpfTranslatorUtil.getParamInt(action, CTR_ID))
-                .withLocalFarId(farId.getSessionLocalId())
-                .withSessionId(farId.getPfcpSessionId());
-
-        PiActionId actionId = action.id();
-        if (actionId.equals(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)) {
-            pdrBuilder.withQfi(FabricUpfTranslatorUtil.getParamByte(action, QFI));
-            if (FabricUpfTranslatorUtil.getParamByte(action, NEEDS_QFI_PUSH) == TRUE) {
-                pdrBuilder.withQfiPush();
-            }
-        }
-
-        if (FabricUpfTranslatorUtil.fieldIsPresent(match, HDR_TEID)) {
-            // F-TEID is only present for GTP-matching PDRs
-            ImmutableByteSequence teid = FabricUpfTranslatorUtil.getFieldValue(match, HDR_TEID);
-            Ip4Address tunnelDst = FabricUpfTranslatorUtil.getFieldAddress(match, HDR_TUNNEL_IPV4_DST);
-            pdrBuilder.withTeid(teid)
-                    .withTunnelDst(tunnelDst);
-            if (FabricUpfTranslatorUtil.fieldIsPresent(match, HDR_HAS_QFI) &&
-                    FabricUpfTranslatorUtil.getFieldByte(match, HDR_HAS_QFI) == TRUE) {
-                pdrBuilder.withQfi(FabricUpfTranslatorUtil.getFieldByte(match, HDR_QFI));
-                pdrBuilder.withQfiMatch();
-            }
-        } else if (FabricUpfTranslatorUtil.fieldIsPresent(match, HDR_UE_ADDR)) {
-            // And UE address is only present for non-GTP-matching PDRs
-            pdrBuilder.withUeAddr(FabricUpfTranslatorUtil.getFieldAddress(match, HDR_UE_ADDR));
-        } else {
-            throw new UpfProgrammableException("Read malformed PDR from dataplane!:" + entry);
-        }
-        return pdrBuilder.build();
-    }
-
-    /**
-     * Translate a fabric.p4 FAR table entry to a ForwardActionRule instance for easier handling.
-     *
-     * @param entry the fabric.p4 entry to translate
-     * @return the corresponding ForwardingActionRule
-     * @throws UpfProgrammableException if the entry cannot be translated
-     */
-    public ForwardingActionRule fabricEntryToFar(FlowRule entry)
-            throws UpfProgrammableException {
-        var farBuilder = ForwardingActionRule.builder();
-        Pair<PiCriterion, PiTableAction> matchActionPair = FabricUpfTranslatorUtil.fabricEntryToPiPair(entry);
-        PiCriterion match = matchActionPair.getLeft();
-        PiAction action = (PiAction) matchActionPair.getRight();
-
-        int globalFarId = FabricUpfTranslatorUtil.getFieldInt(match, HDR_FAR_ID);
-        UpfRuleIdentifier farId = fabricUpfStore.localFarIdOf(globalFarId);
-        if (farId == null) {
-            throw new UpfProgrammableException(String.format("Unable to find local far id of %s", globalFarId));
-        }
-
-        boolean dropFlag = FabricUpfTranslatorUtil.getParamInt(action, DROP) > 0;
-        boolean notifyFlag = FabricUpfTranslatorUtil.getParamInt(action, NOTIFY_CP) > 0;
-
-        // Match keys
-        farBuilder.withSessionId(farId.getPfcpSessionId())
-                .setFarId(farId.getSessionLocalId());
-
-        // Parameters common to all types of FARs
-        farBuilder.setDropFlag(dropFlag)
-                .setNotifyFlag(notifyFlag);
-
-        PiActionId actionId = action.id();
-
-        if (actionId.equals(FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR)
-                || actionId.equals(FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR)) {
-            // Grab parameters specific to encapsulating FARs if they're present
-            Ip4Address tunnelSrc = FabricUpfTranslatorUtil.getParamAddress(action, TUNNEL_SRC_ADDR);
-            Ip4Address tunnelDst = FabricUpfTranslatorUtil.getParamAddress(action, TUNNEL_DST_ADDR);
-            ImmutableByteSequence teid = FabricUpfTranslatorUtil.getParamValue(action, TEID);
-            short tunnelSrcPort = (short) FabricUpfTranslatorUtil.getParamInt(action, TUNNEL_SRC_PORT);
-
-            farBuilder.setBufferFlag(actionId.equals(FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR));
-
-            farBuilder.setTunnel(
-                    GtpTunnel.builder()
-                            .setSrc(tunnelSrc)
-                            .setDst(tunnelDst)
-                            .setTeid(teid)
-                            .setSrcPort(tunnelSrcPort)
-                            .build());
-        }
-        return farBuilder.build();
-    }
-
-    /**
-     * Translate a fabric.p4 interface table entry to a UpfInterface instance for easier handling.
-     *
-     * @param entry the fabric.p4 entry to translate
-     * @return the corresponding UpfInterface
-     * @throws UpfProgrammableException if the entry cannot be translated
-     */
-    public UpfInterface fabricEntryToInterface(FlowRule entry)
-            throws UpfProgrammableException {
-        Pair<PiCriterion, PiTableAction> matchActionPair = FabricUpfTranslatorUtil.fabricEntryToPiPair(entry);
-        PiCriterion match = matchActionPair.getLeft();
-        PiAction action = (PiAction) matchActionPair.getRight();
-
-        var ifaceBuilder = UpfInterface.builder()
-                .setPrefix(FabricUpfTranslatorUtil.getFieldPrefix(match, HDR_IPV4_DST_ADDR));
-
-        int interfaceType = FabricUpfTranslatorUtil.getParamInt(action, SRC_IFACE);
-        if (interfaceType == INTERFACE_ACCESS) {
-            ifaceBuilder.setAccess();
-        } else if (interfaceType == INTERFACE_CORE) {
-            ifaceBuilder.setCore();
-        } else if (interfaceType == INTERFACE_DBUF) {
-            ifaceBuilder.setDbufReceiver();
-        }
-        return ifaceBuilder.build();
-    }
-
-    /**
-     * Translate a ForwardingActionRule to a FlowRule to be inserted into the fabric.p4 pipeline.
-     * A side effect of calling this method is the FAR object's globalFarId is assigned if it was not already.
-     *
-     * @param far      The FAR to be translated
-     * @param deviceId the ID of the device the FlowRule should be installed on
-     * @param appId    the ID of the application that will insert the FlowRule
-     * @param priority the FlowRule's priority
-     * @return the FAR translated to a FlowRule
-     * @throws UpfProgrammableException if the FAR to be translated is malformed
-     */
-    public FlowRule farToFabricEntry(ForwardingActionRule far, DeviceId deviceId, ApplicationId appId, int priority)
-            throws UpfProgrammableException {
-        PiAction action;
-        if (!far.encaps()) {
-            action = PiAction.builder()
-                    .withId(FABRIC_INGRESS_SPGW_LOAD_NORMAL_FAR)
-                    .withParameters(Arrays.asList(
-                            new PiActionParam(DROP, far.drops() ? 1 : 0),
-                            new PiActionParam(NOTIFY_CP, far.notifies() ? 1 : 0)
-                    ))
-                    .build();
-
-        } else {
-            if (far.tunnelSrc() == null || far.tunnelDst() == null
-                    || far.teid() == null || far.tunnel().srcPort() == null) {
-                throw new UpfProgrammableException(
-                        "Not all action parameters present when translating " +
-                                "intermediate encapsulating/buffering FAR to physical FAR!");
-            }
-            // TODO: copy tunnel destination port from logical switch write requests, instead of hardcoding 2152
-            PiActionId actionId = far.buffers() ? FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR :
-                    FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR;
-            action = PiAction.builder()
-                    .withId(actionId)
-                    .withParameters(Arrays.asList(
-                            new PiActionParam(DROP, far.drops() ? 1 : 0),
-                            new PiActionParam(NOTIFY_CP, far.notifies() ? 1 : 0),
-                            new PiActionParam(TEID, far.teid()),
-                            new PiActionParam(TUNNEL_SRC_ADDR, far.tunnelSrc().toInt()),
-                            new PiActionParam(TUNNEL_DST_ADDR, far.tunnelDst().toInt()),
-                            new PiActionParam(TUNNEL_SRC_PORT, far.tunnel().srcPort())
-                    ))
-                    .build();
-        }
-        PiCriterion match = PiCriterion.builder()
-                .matchExact(HDR_FAR_ID, fabricUpfStore.globalFarIdOf(far.sessionId(), far.farId()))
-                .build();
-        return DefaultFlowRule.builder()
-                .forDevice(deviceId).fromApp(appId).makePermanent()
-                .forTable(FABRIC_INGRESS_SPGW_FARS)
-                .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
-                .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
-                .withPriority(priority)
-                .build();
-    }
-
-    /**
-     * Translate a PacketDetectionRule to a FlowRule to be inserted into the fabric.p4 pipeline.
-     * A side effect of calling this method is the PDR object's globalFarId is assigned if it was not already.
-     *
-     * @param pdr      The PDR to be translated
-     * @param deviceId the ID of the device the FlowRule should be installed on
-     * @param appId    the ID of the application that will insert the FlowRule
-     * @param priority the FlowRule's priority
-     * @return the FAR translated to a FlowRule
-     * @throws UpfProgrammableException if the PDR to be translated is malformed
-     */
-    public FlowRule pdrToFabricEntry(PacketDetectionRule pdr, DeviceId deviceId, ApplicationId appId, int priority)
-            throws UpfProgrammableException {
-        final PiCriterion match;
-        final PiTableId tableId;
-        final PiAction action;
-
-        final PiCriterion.Builder matchBuilder = PiCriterion.builder();
-
-        PiAction.Builder actionBuilder = PiAction.builder()
-                .withParameters(Arrays.asList(
-                        new PiActionParam(CTR_ID, pdr.counterId()),
-                        new PiActionParam(FAR_ID, fabricUpfStore.globalFarIdOf(pdr.sessionId(), pdr.farId())),
-                        new PiActionParam(NEEDS_GTPU_DECAP, pdr.matchesEncapped() ?
-                                TRUE : FALSE),
-                        new PiActionParam(TC, DEFAULT_TC)
-                ));
-        PiActionId actionId = FABRIC_INGRESS_SPGW_LOAD_PDR;
-        if (pdr.matchesEncapped()) {
-            tableId = FABRIC_INGRESS_SPGW_UPLINK_PDRS;
-            matchBuilder.matchExact(HDR_TEID, pdr.teid().asArray())
-                    .matchExact(HDR_TUNNEL_IPV4_DST, pdr.tunnelDest().toInt());
-            if (pdr.matchQfi()) {
-                matchBuilder.matchExact(HDR_HAS_QFI, TRUE)
-                        .matchExact(HDR_QFI, pdr.qfi());
-            } else {
-                matchBuilder.matchExact(HDR_HAS_QFI, FALSE)
-                        .matchExact(HDR_QFI, DEFAULT_QFI);
-                if (pdr.hasQfi()) {
-                    actionId = FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
-                    actionBuilder.withParameter(new PiActionParam(QFI, pdr.qfi()))
-                            .withParameter(new PiActionParam(NEEDS_QFI_PUSH, FALSE));
-                }
-            }
-        } else if (pdr.matchesUnencapped()) {
-            tableId = FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
-            matchBuilder.matchExact(HDR_UE_ADDR, pdr.ueAddress().toInt());
-            if (pdr.hasQfi()) {
-                actionBuilder.withParameter(new PiActionParam(QFI, pdr.qfi()))
-                        .withParameter(new PiActionParam(NEEDS_QFI_PUSH, pdr.pushQfi() ? TRUE : FALSE));
-                actionId = FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
-            }
-        } else {
-            throw new UpfProgrammableException("Flexible PDRs not yet supported! Cannot translate " + pdr);
-        }
-        match = matchBuilder.build();
-        action = actionBuilder.withId(actionId)
-                .build();
-        return DefaultFlowRule.builder()
-                .forDevice(deviceId).fromApp(appId).makePermanent()
-                .forTable(tableId)
-                .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
-                .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
-                .withPriority(priority)
-                .build();
-    }
-
-    /**
-     * Translate a UpfInterface to a FlowRule to be inserted into the fabric.p4 pipeline.
-     *
-     * @param upfInterface The interface to be translated
-     * @param deviceId     the ID of the device the FlowRule should be installed on
-     * @param appId        the ID of the application that will insert the FlowRule
-     * @param priority     the FlowRule's priority
-     * @return the UPF interface translated to a FlowRule
-     * @throws UpfProgrammableException if the interface cannot be translated
-     */
-    public FlowRule interfaceToFabricEntry(UpfInterface upfInterface, DeviceId deviceId,
-                                           ApplicationId appId, int priority)
-            throws UpfProgrammableException {
-        int interfaceTypeInt;
-        int gtpuValidity;
-        if (upfInterface.isDbufReceiver()) {
-            interfaceTypeInt = INTERFACE_DBUF;
-            gtpuValidity = 1;
-        } else if (upfInterface.isAccess()) {
-            interfaceTypeInt = INTERFACE_ACCESS;
-            gtpuValidity = 1;
-        } else {
-            interfaceTypeInt = INTERFACE_CORE;
-            gtpuValidity = 0;
-        }
-
-        PiCriterion match = PiCriterion.builder()
-                .matchLpm(HDR_IPV4_DST_ADDR,
-                          upfInterface.prefix().address().toInt(),
-                          upfInterface.prefix().prefixLength())
-                .matchExact(HDR_GTPU_IS_VALID, gtpuValidity)
-                .build();
-        PiAction action = PiAction.builder()
-                .withId(FABRIC_INGRESS_SPGW_LOAD_IFACE)
-                .withParameter(new PiActionParam(SRC_IFACE, interfaceTypeInt))
-                .withParameter(new PiActionParam(SLICE_ID, DEFAULT_SLICE_ID))
-                .build();
-        return DefaultFlowRule.builder()
-                .forDevice(deviceId).fromApp(appId).makePermanent()
-                .forTable(FABRIC_INGRESS_SPGW_INTERFACES)
-                .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
-                .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
-                .withPriority(priority)
-                .build();
-    }
-}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorUtil.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorUtil.java
deleted file mode 100644
index cfbf95e..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorUtil.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.apache.commons.lang3.tuple.Pair;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.Ip4Prefix;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.net.behaviour.upf.UpfProgrammableException;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.criteria.Criterion;
-import org.onosproject.net.flow.criteria.PiCriterion;
-import org.onosproject.net.flow.instructions.Instruction;
-import org.onosproject.net.flow.instructions.PiInstruction;
-import org.onosproject.net.pi.model.PiActionParamId;
-import org.onosproject.net.pi.model.PiMatchFieldId;
-import org.onosproject.net.pi.model.PiMatchType;
-import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiExactFieldMatch;
-import org.onosproject.net.pi.runtime.PiFieldMatch;
-import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
-import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
-import org.onosproject.net.pi.runtime.PiTableAction;
-import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
-
-import java.util.Optional;
-
-/**
- * Utility class for manipulation of FlowRules and PiTableEntry objects specific to fabric-tna.
- */
-final class FabricUpfTranslatorUtil {
-
-    private FabricUpfTranslatorUtil() {
-    }
-
-    static ImmutableByteSequence getFieldValue(PiFieldMatch field, PiMatchFieldId fieldId)
-            throws UpfProgrammableException {
-        if (field == null) {
-            throw new UpfProgrammableException(
-                    String.format("Unable to find field %s where expected!", fieldId.toString()));
-        }
-        if (field.type() == PiMatchType.EXACT) {
-            return ((PiExactFieldMatch) field).value();
-        } else if (field.type() == PiMatchType.LPM) {
-            return ((PiLpmFieldMatch) field).value();
-        } else if (field.type() == PiMatchType.TERNARY) {
-            return ((PiTernaryFieldMatch) field).value();
-        } else if (field.type() == PiMatchType.RANGE) {
-            return ((PiRangeFieldMatch) field).lowValue();
-        } else {
-            throw new UpfProgrammableException(
-                    String.format("Field %s has unknown match type: %s", fieldId.toString(), field.type().toString()));
-        }
-    }
-
-    static ImmutableByteSequence getFieldValue(PiCriterion criterion, PiMatchFieldId fieldId)
-            throws UpfProgrammableException {
-        return getFieldValue(criterion.fieldMatch(fieldId).orElse(null), fieldId);
-    }
-
-    static boolean fieldIsPresent(PiCriterion criterion, PiMatchFieldId fieldId) {
-        return criterion.fieldMatch(fieldId).isPresent();
-    }
-
-    static ImmutableByteSequence getParamValue(PiAction action, PiActionParamId paramId)
-            throws UpfProgrammableException {
-
-        for (PiActionParam param : action.parameters()) {
-            if (param.id().equals(paramId)) {
-                return param.value();
-            }
-        }
-        throw new UpfProgrammableException(
-                String.format("Unable to find parameter %s where expected!", paramId.toString()));
-    }
-
-    static int getFieldInt(PiCriterion criterion, PiMatchFieldId fieldId)
-            throws UpfProgrammableException {
-        return byteSeqToInt(getFieldValue(criterion, fieldId));
-    }
-
-    static byte getFieldByte(PiCriterion criterion, PiMatchFieldId fieldId)
-            throws UpfProgrammableException {
-        return byteSeqToByte(getFieldValue(criterion, fieldId));
-    }
-
-    static int getParamInt(PiAction action, PiActionParamId paramId)
-            throws UpfProgrammableException {
-        return byteSeqToInt(getParamValue(action, paramId));
-    }
-
-    static byte getParamByte(PiAction action, PiActionParamId paramId)
-            throws UpfProgrammableException {
-        return byteSeqToByte(getParamValue(action, paramId));
-    }
-
-    static Ip4Address getParamAddress(PiAction action, PiActionParamId paramId)
-            throws UpfProgrammableException {
-        return Ip4Address.valueOf(getParamValue(action, paramId).asArray());
-    }
-
-    static Ip4Prefix getFieldPrefix(PiCriterion criterion, PiMatchFieldId fieldId) {
-        Optional<PiFieldMatch> optField = criterion.fieldMatch(fieldId);
-        if (optField.isEmpty()) {
-            return null;
-        }
-        PiLpmFieldMatch field = (PiLpmFieldMatch) optField.get();
-        Ip4Address address = Ip4Address.valueOf(field.value().asArray());
-        return Ip4Prefix.valueOf(address, field.prefixLength());
-    }
-
-    static Ip4Address getFieldAddress(PiCriterion criterion, PiMatchFieldId fieldId)
-            throws UpfProgrammableException {
-        return Ip4Address.valueOf(getFieldValue(criterion, fieldId).asArray());
-    }
-
-    static int byteSeqToInt(ImmutableByteSequence sequence) {
-        try {
-            return sequence.fit(32).asReadOnlyBuffer().getInt();
-        } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
-            throw new IllegalArgumentException("Attempted to convert a >4 byte wide sequence to an integer!");
-        }
-    }
-
-    static byte byteSeqToByte(ImmutableByteSequence sequence) {
-        try {
-            return sequence.fit(8).asReadOnlyBuffer().get();
-        } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
-            throw new IllegalArgumentException("Attempted to convert a >1 byte wide sequence to a byte!");
-        }
-    }
-
-    static Pair<PiCriterion, PiTableAction> fabricEntryToPiPair(FlowRule entry) {
-        PiCriterion match = (PiCriterion) entry.selector().getCriterion(Criterion.Type.PROTOCOL_INDEPENDENT);
-        PiTableAction action = null;
-        for (Instruction instruction : entry.treatment().allInstructions()) {
-            if (instruction.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
-                PiInstruction piInstruction = (PiInstruction) instruction;
-                action = piInstruction.action();
-                break;
-            }
-        }
-        return Pair.of(match, action);
-    }
-}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/UpfRuleIdentifier.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/UpfRuleIdentifier.java
deleted file mode 100644
index 5b6c6b6..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/UpfRuleIdentifier.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onlab.util.ImmutableByteSequence;
-
-import java.util.Objects;
-
-/**
- * Wrapper for identifying information of FARs and PDRs.
- */
-public final class UpfRuleIdentifier {
-    private final int sessionlocalId;
-    private final ImmutableByteSequence pfcpSessionId;
-
-    /**
-     * A PDR or FAR can be globally uniquely identified by the combination of the ID of the PFCP session that
-     * produced it, and the ID that the rule was assigned in that PFCP session.
-     *
-     * @param pfcpSessionId  The PFCP session that produced the rule ID
-     * @param sessionlocalId The rule ID
-     */
-    public UpfRuleIdentifier(ImmutableByteSequence pfcpSessionId, int sessionlocalId) {
-        this.pfcpSessionId = pfcpSessionId;
-        this.sessionlocalId = sessionlocalId;
-    }
-
-    /**
-     * Create an instance of this class from the given PFCP session ID and the session-local Rule ID.
-     *
-     * @param pfcpSessionId  PFCP session ID of the rule to identify
-     * @param sessionlocalId session-local Rule ID of the rule to identify
-     * @return a new rule identifier
-     */
-    public static UpfRuleIdentifier of(ImmutableByteSequence pfcpSessionId, int sessionlocalId) {
-        return new UpfRuleIdentifier(pfcpSessionId, sessionlocalId);
-    }
-
-    /**
-     * Get the PFCP session-local rule ID.
-     *
-     * @return session-local rule ID
-     */
-    public int getSessionLocalId() {
-        return sessionlocalId;
-    }
-
-    /**
-     * Get the PFCP session ID.
-     *
-     * @return PFCP session ID
-     */
-    public ImmutableByteSequence getPfcpSessionId() {
-        return pfcpSessionId;
-    }
-
-    @Override
-    public String toString() {
-        return "RuleIdentifier{" +
-                "sessionlocalId=" + sessionlocalId +
-                ", pfcpSessionId=" + pfcpSessionId +
-                '}';
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        UpfRuleIdentifier that = (UpfRuleIdentifier) obj;
-        return (this.sessionlocalId == that.sessionlocalId) && (this.pfcpSessionId.equals(that.pfcpSessionId));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(this.sessionlocalId, this.pfcpSessionId);
-    }
-}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java
deleted file mode 100644
index 42591786..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2021-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.
- */
-
-/**
- * UPF programmable behaviour implementation for fabric-v1model.
- */
-package org.onosproject.pipelines.fabric.impl.behaviour.upf;
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammableTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammableTest.java
deleted file mode 100644
index 50fd8bc..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammableTest.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.collect.ImmutableList;
-import org.junit.Before;
-import org.junit.Test;
-import org.onlab.junit.TestUtils;
-import org.onlab.util.HexString;
-import org.onosproject.TestApplicationId;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.CoreService;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.behaviour.upf.ForwardingActionRule;
-import org.onosproject.net.behaviour.upf.PacketDetectionRule;
-import org.onosproject.net.behaviour.upf.PdrStats;
-import org.onosproject.net.behaviour.upf.UpfInterface;
-import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.config.basics.BasicDeviceConfig;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.DriverData;
-import org.onosproject.net.driver.DriverHandler;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.packet.PacketService;
-import org.onosproject.net.pi.model.PiCounterModel;
-import org.onosproject.net.pi.model.PiTableModel;
-import org.onosproject.net.pi.service.PiPipeconfService;
-import org.onosproject.net.pi.service.PiTranslationService;
-import org.onosproject.p4runtime.api.P4RuntimeController;
-import org.onosproject.pipelines.fabric.impl.FabricPipeconfLoader;
-import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
-
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.ConcurrentMap;
-
-import static junit.framework.TestCase.assertNotNull;
-import static junit.framework.TestCase.assertTrue;
-import static org.easymock.EasyMock.anyString;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_EGRESS_SPGW_PDR_COUNTER;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_FARS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_PDR_COUNTER;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_UPLINK_PDRS;
-
-public class FabricUpfProgrammableTest {
-
-    private static final ApplicationId APP_ID =
-            TestApplicationId.create(FabricPipeconfLoader.PIPELINE_APP_NAME);
-
-    private final DistributedFabricUpfStore upfStore = TestDistributedFabricUpfStore.build();
-    private MockPacketService packetService;
-    private FabricUpfProgrammable upfProgrammable;
-
-    // Bytes of a random but valid Ethernet frame.
-    private static final byte[] ETH_FRAME_BYTES = HexString.fromHexString(
-            "00060708090a0001020304058100000a08004500006a000100004011f92ec0a80001c0a8000204d2005" +
-                    "00056a8d5000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" +
-                    "2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344454" +
-                    "64748494a4b4c4d", "");
-    private static final TrafficTreatment TABLE_OUTPUT_TREATMENT = DefaultTrafficTreatment.builder()
-            .setOutput(PortNumber.TABLE)
-            .build();
-
-    private static final List<PiTableModel> TABLE_MODELS = ImmutableList.of(
-            new MockTableModel(FABRIC_INGRESS_SPGW_UPLINK_PDRS,
-                               TestUpfConstants.PHYSICAL_MAX_PDRS / 2),
-            new MockTableModel(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS,
-                               TestUpfConstants.PHYSICAL_MAX_PDRS / 2),
-            new MockTableModel(FABRIC_INGRESS_SPGW_FARS,
-                               TestUpfConstants.PHYSICAL_MAX_FARS)
-    );
-    private static final List<PiCounterModel> COUNTER_MODELS = ImmutableList.of(
-            new MockCounterModel(FABRIC_INGRESS_SPGW_PDR_COUNTER,
-                                 TestUpfConstants.PHYSICAL_COUNTER_SIZE),
-            new MockCounterModel(FABRIC_EGRESS_SPGW_PDR_COUNTER,
-                                 TestUpfConstants.PHYSICAL_COUNTER_SIZE)
-    );
-
-    @Before
-    public void setUp() throws Exception {
-        FabricCapabilities capabilities = createMock(FabricCapabilities.class);
-        expect(capabilities.supportUpf()).andReturn(true).anyTimes();
-        replay(capabilities);
-
-        // Services mock
-        packetService = new MockPacketService();
-        CoreService coreService = createMock(CoreService.class);
-        NetworkConfigService netcfgService = createMock(NetworkConfigService.class);
-        DeviceService deviceService = createMock(DeviceService.class);
-        PiTranslationService piTranslationService = createMock(PiTranslationService.class);
-        expect(coreService.getAppId(anyString())).andReturn(APP_ID).anyTimes();
-        expect(netcfgService.getConfig(TestUpfConstants.DEVICE_ID, BasicDeviceConfig.class))
-                .andReturn(TestUpfUtils.getBasicConfig(TestUpfConstants.DEVICE_ID, "/basic.json"))
-                .anyTimes();
-        replay(coreService, netcfgService);
-
-        // Mock driverData to get the right device ID
-        DriverData driverData = createMock(DriverData.class);
-        expect(driverData.deviceId()).andReturn(TestUpfConstants.DEVICE_ID).anyTimes();
-        replay(driverData);
-
-        // Mock DriverHandler to get all the required mocked services
-        DriverHandler driverHandler = createMock(DriverHandler.class);
-        expect(driverHandler.get(FlowRuleService.class)).andReturn(new MockFlowRuleService()).anyTimes();
-        expect(driverHandler.get(PacketService.class)).andReturn(packetService).anyTimes();
-        expect(driverHandler.get(FabricUpfStore.class)).andReturn(upfStore).anyTimes();
-        expect(driverHandler.get(NetworkConfigService.class)).andReturn(netcfgService).anyTimes();
-        expect(driverHandler.get(CoreService.class)).andReturn(coreService).anyTimes();
-        expect(driverHandler.get(DeviceService.class)).andReturn(deviceService).anyTimes();
-        expect(driverHandler.get(PiTranslationService.class)).andReturn(piTranslationService).anyTimes();
-        expect(driverHandler.get(PiPipeconfService.class))
-                .andReturn(new MockPiPipeconfService(TABLE_MODELS, COUNTER_MODELS))
-                .anyTimes();
-        expect(driverHandler.get(P4RuntimeController.class))
-                .andReturn(new MockP4RuntimeController(TestUpfConstants.DEVICE_ID,
-                                                       TestUpfConstants.COUNTER_PKTS,
-                                                       TestUpfConstants.COUNTER_BYTES,
-                                                       TestUpfConstants.PHYSICAL_COUNTER_SIZE))
-                .anyTimes();
-        expect(driverHandler.data()).andReturn(driverData).anyTimes();
-        replay(driverHandler);
-
-        upfProgrammable = new FabricUpfProgrammable();
-        TestUtils.setField(upfProgrammable, "handler", driverHandler);
-        TestUtils.setField(upfProgrammable, "data", driverData);
-        ConcurrentMap<DeviceId, URI> channelUris = TestUtils.getField(upfProgrammable, "CHANNEL_URIS");
-        channelUris.put(TestUpfConstants.DEVICE_ID, new URI("grpc://localhost:1234?device_id=1"));
-    }
-
-    @Test
-    public void testUplinkPdr() throws Exception {
-        assertTrue(upfProgrammable.getPdrs().isEmpty());
-        PacketDetectionRule expectedPdr = TestUpfConstants.UPLINK_PDR;
-        upfProgrammable.addPdr(expectedPdr);
-        Collection<PacketDetectionRule> installedPdrs = upfProgrammable.getPdrs();
-        assertThat(installedPdrs.size(), equalTo(1));
-        for (var readPdr : installedPdrs) {
-            assertThat(readPdr, equalTo(expectedPdr));
-        }
-        upfProgrammable.removePdr(expectedPdr.withoutActionParams());
-        assertTrue(upfProgrammable.getPdrs().isEmpty());
-    }
-
-    @Test
-    public void testDownlinkPdr() throws Exception {
-        assertTrue(upfProgrammable.getPdrs().isEmpty());
-        PacketDetectionRule expectedPdr = TestUpfConstants.DOWNLINK_PDR;
-        upfProgrammable.addPdr(expectedPdr);
-        Collection<PacketDetectionRule> installedPdrs = upfProgrammable.getPdrs();
-        assertThat(installedPdrs.size(), equalTo(1));
-        for (var readPdr : installedPdrs) {
-            assertThat(readPdr, equalTo(expectedPdr));
-        }
-        upfProgrammable.removePdr(expectedPdr.withoutActionParams());
-        assertTrue(upfProgrammable.getPdrs().isEmpty());
-    }
-
-    @Test
-    public void testUplinkFar() throws Exception {
-        assertTrue(upfProgrammable.getFars().isEmpty());
-        ForwardingActionRule expectedFar = TestUpfConstants.UPLINK_FAR;
-        upfProgrammable.addFar(expectedFar);
-        Collection<ForwardingActionRule> installedFars = upfProgrammable.getFars();
-        assertThat(installedFars.size(), equalTo(1));
-        for (var readFar : installedFars) {
-            assertThat(readFar, equalTo(expectedFar));
-        }
-        upfProgrammable.removeFar(expectedFar.withoutActionParams());
-        assertTrue(upfProgrammable.getFars().isEmpty());
-    }
-
-    @Test
-    public void testDownlinkFar() throws Exception {
-        assertTrue(upfProgrammable.getFars().isEmpty());
-        ForwardingActionRule expectedFar = TestUpfConstants.DOWNLINK_FAR;
-        upfProgrammable.addFar(expectedFar);
-        Collection<ForwardingActionRule> installedFars = upfProgrammable.getFars();
-        assertThat(installedFars.size(), equalTo(1));
-        for (var readFar : installedFars) {
-            assertThat(readFar, equalTo(expectedFar));
-        }
-        upfProgrammable.removeFar(expectedFar.withoutActionParams());
-        assertTrue(upfProgrammable.getFars().isEmpty());
-    }
-
-    @Test
-    public void testUplinkInterface() throws Exception {
-        assertTrue(upfProgrammable.getInterfaces().isEmpty());
-        UpfInterface expectedInterface = TestUpfConstants.UPLINK_INTERFACE;
-        upfProgrammable.addInterface(expectedInterface);
-        Collection<UpfInterface> installedInterfaces = upfProgrammable.getInterfaces();
-        assertThat(installedInterfaces.size(), equalTo(1));
-        for (var readInterface : installedInterfaces) {
-            assertThat(readInterface, equalTo(expectedInterface));
-        }
-        upfProgrammable.removeInterface(expectedInterface);
-        assertTrue(upfProgrammable.getInterfaces().isEmpty());
-    }
-
-    @Test
-    public void testDownlinkInterface() throws Exception {
-        assertTrue(upfProgrammable.getInterfaces().isEmpty());
-        UpfInterface expectedInterface = TestUpfConstants.DOWNLINK_INTERFACE;
-        upfProgrammable.addInterface(expectedInterface);
-        Collection<UpfInterface> installedInterfaces = upfProgrammable.getInterfaces();
-        assertThat(installedInterfaces.size(), equalTo(1));
-        for (var readInterface : installedInterfaces) {
-            assertThat(readInterface, equalTo(expectedInterface));
-        }
-        upfProgrammable.removeInterface(expectedInterface);
-        assertTrue(upfProgrammable.getInterfaces().isEmpty());
-    }
-
-    @Test
-    public void testClearInterfaces() throws Exception {
-        assertTrue(upfProgrammable.getInterfaces().isEmpty());
-        upfProgrammable.addInterface(TestUpfConstants.UPLINK_INTERFACE);
-        upfProgrammable.addInterface(TestUpfConstants.DOWNLINK_INTERFACE);
-        assertThat(upfProgrammable.getInterfaces().size(), equalTo(2));
-        upfProgrammable.clearInterfaces();
-        assertTrue(upfProgrammable.getInterfaces().isEmpty());
-    }
-
-    @Test
-    public void testReadAllCounters() {
-        Collection<PdrStats> allStats = upfProgrammable.readAllCounters(-1);
-        assertThat(allStats.size(), equalTo(TestUpfConstants.PHYSICAL_COUNTER_SIZE));
-        for (PdrStats stat : allStats) {
-            assertThat(stat.getIngressBytes(), equalTo(TestUpfConstants.COUNTER_BYTES));
-            assertThat(stat.getEgressBytes(), equalTo(TestUpfConstants.COUNTER_BYTES));
-            assertThat(stat.getIngressPkts(), equalTo(TestUpfConstants.COUNTER_PKTS));
-            assertThat(stat.getEgressPkts(), equalTo(TestUpfConstants.COUNTER_PKTS));
-        }
-    }
-
-    @Test
-    public void testReadAllCountersLimitedCounters() {
-        Collection<PdrStats> allStats = upfProgrammable.readAllCounters(10);
-        assertThat(allStats.size(), equalTo(10));
-    }
-
-    @Test
-    public void testReadAllCountersPhysicalLimit() {
-        Collection<PdrStats> allStats = upfProgrammable.readAllCounters(1024);
-        assertThat(allStats.size(), equalTo(TestUpfConstants.PHYSICAL_COUNTER_SIZE));
-    }
-
-    @Test
-    public void testSendPacketOut() {
-        upfProgrammable.sendPacketOut(ByteBuffer.wrap(ETH_FRAME_BYTES));
-        var emittedPkt = packetService.emittedPackets.poll();
-        assertNotNull(emittedPkt);
-        assertThat(emittedPkt.data().array(), equalTo(ETH_FRAME_BYTES));
-        assertThat(emittedPkt.treatment(), equalTo(TABLE_OUTPUT_TREATMENT));
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorTest.java
deleted file mode 100644
index a07826d..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorTest.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.junit.Test;
-import org.onosproject.net.behaviour.upf.ForwardingActionRule;
-import org.onosproject.net.behaviour.upf.PacketDetectionRule;
-import org.onosproject.net.behaviour.upf.UpfInterface;
-import org.onosproject.net.behaviour.upf.UpfProgrammableException;
-import org.onosproject.net.flow.FlowRule;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-
-public class FabricUpfTranslatorTest {
-
-    private final FabricUpfTranslator upfTranslator = new FabricUpfTranslator(TestDistributedFabricUpfStore.build());
-
-    @Test
-    public void fabricEntryToUplinkPdrTest() {
-        fabricToPdrUplink(TestUpfConstants.UPLINK_PDR, TestUpfConstants.FABRIC_UPLINK_PDR);
-    }
-
-    @Test
-    public void fabricEntryToUplinkQosPdrTest() {
-        fabricToPdrUplink(TestUpfConstants.UPLINK_QOS_PDR, TestUpfConstants.FABRIC_UPLINK_QOS_PDR);
-        fabricToPdrUplink(TestUpfConstants.UPLINK_QOS_4G_PDR, TestUpfConstants.FABRIC_UPLINK_QOS_4G_PDR);
-    }
-
-    private void fabricToPdrUplink(PacketDetectionRule expected, FlowRule fabricFlow) {
-        PacketDetectionRule translatedPdr;
-        try {
-            translatedPdr = upfTranslator.fabricEntryToPdr(fabricFlow);
-        } catch (UpfProgrammableException e) {
-            assertThat("Fabric uplink PDR should translate to abstract PDR without error.", false);
-            return;
-        }
-        assertThat("Translated PDR should be uplink.", translatedPdr.matchesEncapped());
-        assertThat(translatedPdr, equalTo(expected));
-    }
-
-    @Test
-    public void fabricEntryToDownlinkPdrTest() {
-        fabricToPdrDownlink(TestUpfConstants.DOWNLINK_PDR, TestUpfConstants.FABRIC_DOWNLINK_PDR);
-    }
-
-    @Test
-    public void fabricEntryToDownlinkQosPdrTest() {
-        fabricToPdrDownlink(TestUpfConstants.DOWNLINK_QOS_PDR, TestUpfConstants.FABRIC_DOWNLINK_QOS_PDR);
-        fabricToPdrDownlink(TestUpfConstants.DOWNLINK_QOS_4G_PDR, TestUpfConstants.FABRIC_DOWNLINK_QOS_4G_PDR);
-    }
-
-    private void fabricToPdrDownlink(PacketDetectionRule expected, FlowRule fabricFlow) {
-        PacketDetectionRule translatedPdr;
-        try {
-            translatedPdr = upfTranslator.fabricEntryToPdr(fabricFlow);
-        } catch (UpfProgrammableException e) {
-            assertThat("Fabric downlink PDR should translate to abstract PDR without error.", false);
-            return;
-        }
-
-        assertThat("Translated PDR should be downlink.", translatedPdr.matchesUnencapped());
-        assertThat(translatedPdr, equalTo(expected));
-    }
-
-    @Test
-    public void fabricEntryToUplinkFarTest() {
-        ForwardingActionRule translatedFar;
-        ForwardingActionRule expectedFar = TestUpfConstants.UPLINK_FAR;
-        try {
-            translatedFar = upfTranslator.fabricEntryToFar(TestUpfConstants.FABRIC_UPLINK_FAR);
-        } catch (UpfProgrammableException e) {
-            assertThat("Fabric uplink FAR should correctly translate to abstract FAR without error",
-                       false);
-            return;
-        }
-        assertThat("Translated FAR should be uplink.", translatedFar.forwards());
-        assertThat(translatedFar, equalTo(expectedFar));
-    }
-
-    @Test
-    public void fabricEntryToDownlinkFarTest() {
-        ForwardingActionRule translatedFar;
-        ForwardingActionRule expectedFar = TestUpfConstants.DOWNLINK_FAR;
-        try {
-            translatedFar = upfTranslator.fabricEntryToFar(TestUpfConstants.FABRIC_DOWNLINK_FAR);
-        } catch (UpfProgrammableException e) {
-            assertThat("Fabric downlink FAR should correctly translate to abstract FAR without error",
-                       false);
-            return;
-        }
-        assertThat("Translated FAR should be downlink.", translatedFar.encaps());
-        assertThat(translatedFar, equalTo(expectedFar));
-    }
-
-    @Test
-    public void fabricEntryToUplinkInterfaceTest() {
-        UpfInterface translatedInterface;
-        UpfInterface expectedInterface = TestUpfConstants.UPLINK_INTERFACE;
-        try {
-            translatedInterface = upfTranslator.fabricEntryToInterface(TestUpfConstants.FABRIC_UPLINK_INTERFACE);
-        } catch (UpfProgrammableException e) {
-            assertThat("Fabric uplink interface should correctly translate to abstract interface without error",
-                       false);
-            return;
-        }
-        assertThat("Translated interface should be uplink.", translatedInterface.isAccess());
-        assertThat(translatedInterface, equalTo(expectedInterface));
-    }
-
-    @Test
-    public void fabricEntryToDownlinkInterfaceTest() {
-        UpfInterface translatedInterface;
-        UpfInterface expectedInterface = TestUpfConstants.DOWNLINK_INTERFACE;
-        try {
-            translatedInterface = upfTranslator.fabricEntryToInterface(TestUpfConstants.FABRIC_DOWNLINK_INTERFACE);
-        } catch (UpfProgrammableException e) {
-            assertThat("Fabric downlink interface should correctly translate to abstract interface without error",
-                       false);
-            return;
-        }
-        assertThat("Translated interface should be downlink.", translatedInterface.isCore());
-        assertThat(translatedInterface, equalTo(expectedInterface));
-    }
-
-    @Test
-    public void uplinkInterfaceToFabricEntryTest() {
-        FlowRule translatedRule;
-        FlowRule expectedRule = TestUpfConstants.FABRIC_UPLINK_INTERFACE;
-        try {
-            translatedRule = upfTranslator.interfaceToFabricEntry(TestUpfConstants.UPLINK_INTERFACE,
-                                                                  TestUpfConstants.DEVICE_ID,
-                                                                  TestUpfConstants.APP_ID,
-                                                                  TestUpfConstants.DEFAULT_PRIORITY);
-        } catch (UpfProgrammableException e) {
-            assertThat("Abstract uplink interface should correctly translate to Fabric interface without error",
-                       false);
-            return;
-        }
-        assertThat(translatedRule, equalTo(expectedRule));
-    }
-
-    @Test
-    public void downlinkInterfaceToFabricEntryTest() {
-        FlowRule translatedRule;
-        FlowRule expectedRule = TestUpfConstants.FABRIC_DOWNLINK_INTERFACE;
-        try {
-            translatedRule = upfTranslator.interfaceToFabricEntry(TestUpfConstants.DOWNLINK_INTERFACE,
-                                                                  TestUpfConstants.DEVICE_ID,
-                                                                  TestUpfConstants.APP_ID,
-                                                                  TestUpfConstants.DEFAULT_PRIORITY);
-        } catch (UpfProgrammableException e) {
-            assertThat("Abstract downlink interface should correctly translate to Fabric interface without error",
-                       false);
-            return;
-        }
-        assertThat(translatedRule, equalTo(expectedRule));
-    }
-
-    @Test
-    public void downlinkPdrToFabricEntryTest() {
-        pdrToFabricDownlink(TestUpfConstants.FABRIC_DOWNLINK_PDR, TestUpfConstants.DOWNLINK_PDR);
-    }
-
-    @Test
-    public void downlinkPdrToFabricQosEntryTest() {
-        pdrToFabricDownlink(TestUpfConstants.FABRIC_DOWNLINK_QOS_PDR, TestUpfConstants.DOWNLINK_QOS_PDR);
-        pdrToFabricDownlink(TestUpfConstants.FABRIC_DOWNLINK_QOS_4G_PDR, TestUpfConstants.DOWNLINK_QOS_4G_PDR);
-    }
-
-    private void pdrToFabricDownlink(FlowRule expected, PacketDetectionRule pdr) {
-        FlowRule translatedRule;
-        try {
-            translatedRule = upfTranslator.pdrToFabricEntry(pdr,
-                                                            TestUpfConstants.DEVICE_ID,
-                                                            TestUpfConstants.APP_ID,
-                                                            TestUpfConstants.DEFAULT_PRIORITY);
-        } catch (UpfProgrammableException e) {
-            assertThat("Abstract downlink PDR should correctly translate to Fabric PDR without error",
-                       false);
-            return;
-        }
-        assertThat(translatedRule, equalTo(expected));
-    }
-
-    @Test
-    public void uplinkFarToFabricEntryTest() {
-        FlowRule translatedRule;
-        FlowRule expectedRule = TestUpfConstants.FABRIC_UPLINK_FAR;
-        try {
-            translatedRule = upfTranslator.farToFabricEntry(TestUpfConstants.UPLINK_FAR,
-                                                            TestUpfConstants.DEVICE_ID,
-                                                            TestUpfConstants.APP_ID,
-                                                            TestUpfConstants.DEFAULT_PRIORITY);
-        } catch (UpfProgrammableException e) {
-            assertThat("Abstract uplink FAR should correctly translate to Fabric FAR without error",
-                       false);
-            return;
-        }
-        assertThat(translatedRule, equalTo(expectedRule));
-    }
-
-    @Test
-    public void uplinkPdrToFabricEntryTest() {
-        pdrToFabricUplink(TestUpfConstants.FABRIC_UPLINK_PDR, TestUpfConstants.UPLINK_PDR);
-    }
-
-    @Test
-    public void uplinkQosPdrToFabricEntryTest() {
-        pdrToFabricUplink(TestUpfConstants.FABRIC_UPLINK_QOS_PDR, TestUpfConstants.UPLINK_QOS_PDR);
-        pdrToFabricUplink(TestUpfConstants.FABRIC_UPLINK_QOS_4G_PDR, TestUpfConstants.UPLINK_QOS_4G_PDR);
-    }
-
-    private void pdrToFabricUplink(FlowRule expected, PacketDetectionRule pdr) {
-        FlowRule translatedRule;
-        try {
-            translatedRule = upfTranslator.pdrToFabricEntry(pdr,
-                                                            TestUpfConstants.DEVICE_ID,
-                                                            TestUpfConstants.APP_ID,
-                                                            TestUpfConstants.DEFAULT_PRIORITY);
-        } catch (UpfProgrammableException e) {
-            assertThat("Abstract uplink PDR should correctly translate to Fabric PDR without error",
-                       false);
-            return;
-        }
-        assertThat(translatedRule, equalTo(expected));
-    }
-
-    @Test
-    public void downlinkFarToFabricEntryTest() {
-        FlowRule translatedRule;
-        FlowRule expectedRule = TestUpfConstants.FABRIC_DOWNLINK_FAR;
-        try {
-            translatedRule = upfTranslator.farToFabricEntry(TestUpfConstants.DOWNLINK_FAR,
-                                                            TestUpfConstants.DEVICE_ID,
-                                                            TestUpfConstants.APP_ID,
-                                                            TestUpfConstants.DEFAULT_PRIORITY);
-        } catch (UpfProgrammableException e) {
-            assertThat("Abstract downlink FAR should correctly translate to Fabric FAR without error",
-                       false);
-            return;
-        }
-        assertThat(translatedRule, equalTo(expectedRule));
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockCounterModel.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockCounterModel.java
deleted file mode 100644
index dcf667b..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockCounterModel.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onosproject.net.pi.model.PiCounterId;
-import org.onosproject.net.pi.model.PiCounterModel;
-import org.onosproject.net.pi.model.PiCounterType;
-import org.onosproject.net.pi.model.PiTableId;
-
-public class MockCounterModel implements PiCounterModel {
-    PiCounterId id;
-    int size;
-
-    public MockCounterModel(PiCounterId id, int size) {
-        this.id = id;
-        this.size = size;
-    }
-
-    @Override
-    public PiCounterId id() {
-        return this.id;
-    }
-
-    @Override
-    public PiCounterType counterType() {
-        return null;
-    }
-
-    @Override
-    public Unit unit() {
-        return null;
-    }
-
-    @Override
-    public PiTableId table() {
-        return null;
-    }
-
-    @Override
-    public long size() {
-        return this.size;
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockFlowRuleService.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockFlowRuleService.java
deleted file mode 100644
index 055a4be..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockFlowRuleService.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.collect.Sets;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.flow.DefaultFlowEntry;
-import org.onosproject.net.flow.FlowEntry;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleOperations;
-import org.onosproject.net.flow.FlowRuleServiceAdapter;
-
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
-
-public class MockFlowRuleService extends FlowRuleServiceAdapter {
-
-    final Set<FlowRule> flows = Sets.newHashSet();
-    boolean success;
-
-    int errorFlow = -1;
-
-    public void setErrorFlow(int errorFlow) {
-        this.errorFlow = errorFlow;
-    }
-
-    public void setFuture(boolean success) {
-        this.success = success;
-    }
-
-    @Override
-    public void apply(FlowRuleOperations ops) {
-        AtomicBoolean thisSuccess = new AtomicBoolean(success);
-        ops.stages().forEach(stage -> stage.forEach(flow -> {
-            if (errorFlow == flow.rule().id().value()) {
-                thisSuccess.set(false);
-            } else {
-                switch (flow.type()) {
-                    case ADD:
-                    case MODIFY: //TODO is this the right behavior for modify?
-                        flows.add(flow.rule());
-                        break;
-                    case REMOVE:
-                        flows.remove(flow.rule());
-                        break;
-                    default:
-                        break;
-                }
-            }
-        }));
-        if (thisSuccess.get()) {
-            ops.callback().onSuccess(ops);
-        } else {
-            ops.callback().onError(ops);
-        }
-    }
-
-    @Override
-    public int getFlowRuleCount() {
-        return flows.size();
-    }
-
-    @Override
-    public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
-        return flows.stream()
-                .filter(flow -> flow.deviceId().equals(deviceId))
-                .map(DefaultFlowEntry::new)
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public void applyFlowRules(FlowRule... flowRules) {
-        for (FlowRule flow : flowRules) {
-            flows.add(flow);
-        }
-    }
-
-    @Override
-    public void removeFlowRules(FlowRule... flowRules) {
-        for (FlowRule flow : flowRules) {
-            flows.remove(flow);
-        }
-    }
-
-    @Override
-    public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) {
-        return flows.stream()
-                .filter(flow -> flow.appId() == appId.id() && flow.groupId().id() == groupId)
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public Iterable<FlowEntry> getFlowEntriesById(ApplicationId id) {
-        return flows.stream()
-                .filter(flow -> flow.appId() == id.id())
-                .map(DefaultFlowEntry::new)
-                .collect(Collectors.toList());
-    }
-}
-
-
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockP4RuntimeController.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockP4RuntimeController.java
deleted file mode 100644
index 5fc42d5..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockP4RuntimeController.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import io.grpc.ManagedChannel;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.device.DeviceAgentListener;
-import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.provider.ProviderId;
-import org.onosproject.p4runtime.api.P4RuntimeClient;
-import org.onosproject.p4runtime.api.P4RuntimeController;
-import org.onosproject.p4runtime.api.P4RuntimeEventListener;
-
-import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-
-/**
- * Currently only used to get mock clients that mock counter read requests.
- */
-public class MockP4RuntimeController implements P4RuntimeController {
-
-    private final P4RuntimeClient mockP4rtClient;
-
-    /**
-     * Used to mock counter read requests.
-     *
-     * @param deviceId The ID of the device
-     * @param packets Packets counter value
-     * @param bytes Bytes counter value
-     * @param counterSize The size of the counter array
-     */
-    public MockP4RuntimeController(DeviceId deviceId, long packets, long bytes, int counterSize) {
-        mockP4rtClient = createMock(P4RuntimeClient.class);
-        expect(mockP4rtClient.read(anyLong(), anyObject(PiPipeconf.class)))
-                .andReturn(new MockReadRequest(deviceId, packets, bytes, counterSize))
-                .anyTimes();
-        replay(mockP4rtClient);
-    }
-
-    @Override
-    public P4RuntimeClient get(DeviceId deviceId) {
-        return mockP4rtClient;
-    }
-
-    @Override
-    public void addListener(P4RuntimeEventListener listener) {
-
-    }
-
-    @Override
-    public void removeListener(P4RuntimeEventListener listener) {
-
-    }
-
-    @Override
-    public boolean create(DeviceId deviceId, ManagedChannel channel) {
-        return false;
-    }
-
-    @Override
-    public void remove(DeviceId deviceId) {
-
-    }
-
-    @Override
-    public void addDeviceAgentListener(DeviceId deviceId, ProviderId providerId,
-                                       DeviceAgentListener listener) {
-
-    }
-
-    @Override
-    public void removeDeviceAgentListener(DeviceId deviceId,
-                                          ProviderId providerId) {
-
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPacketService.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPacketService.java
deleted file mode 100644
index 8dc0928..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPacketService.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.collect.Queues;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.packet.OutboundPacket;
-import org.onosproject.net.packet.PacketPriority;
-import org.onosproject.net.packet.PacketProcessor;
-import org.onosproject.net.packet.PacketProcessorEntry;
-import org.onosproject.net.packet.PacketRequest;
-import org.onosproject.net.packet.PacketService;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.Queue;
-
-public class MockPacketService implements PacketService {
-
-    Queue<OutboundPacket> emittedPackets = Queues.newArrayDeque();
-
-    @Override
-    public void addProcessor(PacketProcessor processor, int priority) {
-
-    }
-
-    @Override
-    public void removeProcessor(PacketProcessor processor) {
-
-    }
-
-    @Override
-    public List<PacketProcessorEntry> getProcessors() {
-        return null;
-    }
-
-    @Override
-    public void requestPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
-
-    }
-
-    @Override
-    public void requestPackets(TrafficSelector selector, PacketPriority priority,
-                               ApplicationId appId, Optional<DeviceId> deviceId) {
-
-    }
-
-    @Override
-    public void cancelPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
-
-    }
-
-    @Override
-    public void cancelPackets(TrafficSelector selector, PacketPriority priority,
-                              ApplicationId appId, Optional<DeviceId> deviceId) {
-
-    }
-
-    @Override
-    public List<PacketRequest> getRequests() {
-        return null;
-    }
-
-    @Override
-    public void emit(OutboundPacket packet) {
-        emittedPackets.add(packet);
-    }
-}
-
-
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPiPipeconfService.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPiPipeconfService.java
deleted file mode 100644
index 20c7839..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPiPipeconfService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.pi.model.PiCounterModel;
-import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.model.PiPipeconfId;
-import org.onosproject.net.pi.model.PiTableModel;
-import org.onosproject.net.pi.service.PiPipeconfListener;
-import org.onosproject.net.pi.service.PiPipeconfService;
-
-import java.util.Collection;
-import java.util.Optional;
-
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-
-public class MockPiPipeconfService implements PiPipeconfService {
-
-    private final PiPipeconf mockPiPipeconf;
-
-    public MockPiPipeconfService(Collection<PiTableModel> tables,
-                                 Collection<PiCounterModel> counters) {
-        mockPiPipeconf = createMock(PiPipeconf.class);
-        expect(mockPiPipeconf.pipelineModel())
-                .andReturn(new MockPiPipelineModel(tables, counters))
-                .anyTimes();
-        replay(mockPiPipeconf);
-    }
-
-    @Override
-    public Optional<PiPipeconf> getPipeconf(PiPipeconfId id) {
-        return Optional.of(mockPiPipeconf);
-    }
-
-    @Override
-    public Optional<PiPipeconf> getPipeconf(DeviceId deviceId) {
-        return Optional.of(mockPiPipeconf);
-    }
-
-    @Override
-    public void register(PiPipeconf pipeconf) throws IllegalStateException {
-
-    }
-
-    @Override
-    public void unregister(PiPipeconfId pipeconfId) throws IllegalStateException {
-
-    }
-
-    @Override
-    public Iterable<PiPipeconf> getPipeconfs() {
-        return null;
-    }
-
-    @Override
-    public void bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) {
-
-    }
-
-    @Override
-    public String getMergedDriver(DeviceId deviceId, PiPipeconfId pipeconfId) {
-        return null;
-    }
-
-    @Override
-    public Optional<PiPipeconfId> ofDevice(DeviceId deviceId) {
-        return Optional.empty();
-    }
-
-    @Override
-    public void addListener(PiPipeconfListener listener) {
-
-    }
-
-    @Override
-    public void removeListener(PiPipeconfListener listener) {
-
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPiPipelineModel.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPiPipelineModel.java
deleted file mode 100644
index 2560346..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockPiPipelineModel.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.collect.Maps;
-import org.onosproject.net.pi.model.PiActionProfileId;
-import org.onosproject.net.pi.model.PiActionProfileModel;
-import org.onosproject.net.pi.model.PiCounterId;
-import org.onosproject.net.pi.model.PiCounterModel;
-import org.onosproject.net.pi.model.PiMeterId;
-import org.onosproject.net.pi.model.PiMeterModel;
-import org.onosproject.net.pi.model.PiPacketOperationModel;
-import org.onosproject.net.pi.model.PiPacketOperationType;
-import org.onosproject.net.pi.model.PiPipelineModel;
-import org.onosproject.net.pi.model.PiRegisterId;
-import org.onosproject.net.pi.model.PiRegisterModel;
-import org.onosproject.net.pi.model.PiTableId;
-import org.onosproject.net.pi.model.PiTableModel;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-
-public class MockPiPipelineModel implements PiPipelineModel {
-
-    private final Map<PiTableId, PiTableModel> tableMap = Maps.newHashMap();
-
-    private final List<PiCounterModel> counters;
-
-    public MockPiPipelineModel(Collection<PiTableModel> tables, Collection<PiCounterModel> counters) {
-        tables.forEach(tableModel -> tableMap.put(tableModel.id(), tableModel));
-        this.counters = List.copyOf(counters);
-    }
-
-    @Override
-    public Optional<String> architecture() {
-        return Optional.empty();
-    }
-
-    @Override
-    public Optional<PiTableModel> table(PiTableId tableId) {
-        return Optional.ofNullable(tableMap.getOrDefault(tableId, null));
-    }
-
-    @Override
-    public Collection<PiTableModel> tables() {
-        return tableMap.values();
-    }
-
-    @Override
-    public Optional<PiCounterModel> counter(PiCounterId counterId) {
-        return Optional.empty();
-    }
-
-    @Override
-    public Collection<PiCounterModel> counters() {
-        return counters;
-    }
-
-    @Override
-    public Optional<PiMeterModel> meter(PiMeterId meterId) {
-        return Optional.empty();
-    }
-
-    @Override
-    public Collection<PiMeterModel> meters() {
-        return null;
-    }
-
-    @Override
-    public Optional<PiRegisterModel> register(PiRegisterId registerId) {
-        return Optional.empty();
-    }
-
-    @Override
-    public Collection<PiRegisterModel> registers() {
-        return null;
-    }
-
-    @Override
-    public Optional<PiActionProfileModel> actionProfiles(PiActionProfileId actionProfileId) {
-        return Optional.empty();
-    }
-
-    @Override
-    public Collection<PiActionProfileModel> actionProfiles() {
-        return null;
-    }
-
-    @Override
-    public Optional<PiPacketOperationModel> packetOperationModel(PiPacketOperationType type) {
-        return Optional.empty();
-    }
-
-
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockReadRequest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockReadRequest.java
deleted file mode 100644
index 32be8d9..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockReadRequest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.pi.model.PiActionProfileId;
-import org.onosproject.net.pi.model.PiCounterId;
-import org.onosproject.net.pi.model.PiMeterId;
-import org.onosproject.net.pi.model.PiTableId;
-import org.onosproject.net.pi.runtime.PiCounterCellHandle;
-import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiHandle;
-import org.onosproject.p4runtime.api.P4RuntimeReadClient;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.stream.LongStream;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * For faking reads to a p4runtime client. Currently only used for testing
- * UP4-specific counter reads, because all other P4 entities that UP4 reads can
- * be read via other ONOS services.
- */
-public class MockReadRequest implements P4RuntimeReadClient.ReadRequest {
-    List<PiHandle> handles;
-    DeviceId deviceId;
-    long packets;
-    long bytes;
-    int counterSize;
-
-    public MockReadRequest(DeviceId deviceId, long packets, long bytes, int counterSize) {
-        this.handles = new ArrayList<>();
-        this.deviceId = deviceId;
-        this.packets = packets;
-        this.bytes = bytes;
-        this.counterSize = counterSize;
-    }
-
-    @Override
-    public CompletableFuture<P4RuntimeReadClient.ReadResponse> submit() {
-        return CompletableFuture.completedFuture(
-                new MockReadResponse(this.handles, this.packets, this.bytes));
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadResponse submitSync() {
-        return new MockReadResponse(this.handles, this.packets, this.bytes);
-    }
-
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest handle(PiHandle handle) {
-        this.handles.add(handle);
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest handles(Iterable<? extends PiHandle> handles) {
-        checkNotNull(handles);
-        handles.forEach(this::handle);
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest tableEntries(PiTableId tableId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest tableEntries(Iterable<PiTableId> tableIds) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest defaultTableEntry(PiTableId tableId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest defaultTableEntry(Iterable<PiTableId> tableIds) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest allTableEntries() {
-        return null;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest allDefaultTableEntries() {
-        return null;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest actionProfileGroups(PiActionProfileId actionProfileId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest actionProfileGroups(Iterable<PiActionProfileId> actionProfileIds) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest actionProfileMembers(PiActionProfileId actionProfileId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest actionProfileMembers(Iterable<PiActionProfileId> actionProfileIds) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest counterCells(PiCounterId counterId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest counterCells(Iterable<PiCounterId> counterIds) {
-        counterIds.forEach(counterId -> {
-            LongStream.range(0, this.counterSize)
-                    .forEach(index -> {
-                        PiCounterCellId cellId =
-                                PiCounterCellId.ofIndirect(counterId, index);
-                        PiCounterCellHandle handle =
-                                PiCounterCellHandle.of(this.deviceId, cellId);
-                        this.handle(handle);
-                    });
-        });
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest directCounterCells(PiTableId tableId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest directCounterCells(Iterable<PiTableId> tableIds) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest meterCells(PiMeterId meterId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest meterCells(Iterable<PiMeterId> meterIds) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest directMeterCells(PiTableId tableId) {
-        return this;
-    }
-
-    @Override
-    public P4RuntimeReadClient.ReadRequest directMeterCells(Iterable<PiTableId> tableIds) {
-        return this;
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockReadResponse.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockReadResponse.java
deleted file mode 100644
index 2dbc11f..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockReadResponse.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onosproject.net.pi.runtime.PiCounterCell;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
-import org.onosproject.net.pi.runtime.PiCounterCellHandle;
-import org.onosproject.net.pi.runtime.PiEntity;
-import org.onosproject.net.pi.runtime.PiEntityType;
-import org.onosproject.net.pi.runtime.PiHandle;
-import org.onosproject.p4runtime.api.P4RuntimeReadClient;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * For faking reads to a p4runtime client. Currently only used for testing
- * UP4-specific counter reads, because all other P4 entities that UP4 reads can
- * be read via other ONOS services.
- */
-public class MockReadResponse implements P4RuntimeReadClient.ReadResponse {
-    List<PiEntity> entities;
-    long packets;
-    long bytes;
-
-    public MockReadResponse(Iterable<? extends PiHandle> handles, long packets, long bytes) {
-        this.entities = new ArrayList<>();
-        this.packets = packets;
-        this.bytes = bytes;
-        checkNotNull(handles);
-        handles.forEach(this::handle);
-    }
-
-    @Override
-    public boolean isSuccess() {
-        return true;
-    }
-
-    public MockReadResponse handle(PiHandle handle) {
-        if (handle.entityType().equals(PiEntityType.COUNTER_CELL)) {
-            PiCounterCellHandle counterHandle = (PiCounterCellHandle) handle;
-            PiCounterCellData data =
-                    new PiCounterCellData(this.packets, this.bytes);
-            PiEntity entity = new PiCounterCell(counterHandle.cellId(), data);
-            this.entities.add(entity);
-        }
-        // Only handles counter cell so far
-
-        return this;
-    }
-
-    @Override
-    public Collection<PiEntity> all() {
-        return this.entities;
-    }
-
-    @Override
-    public <E extends PiEntity> Collection<E> all(Class<E> clazz) {
-        List<E> results = new ArrayList<>();
-        this.entities.forEach(ent -> {
-            if (ent.getClass().equals(clazz)) {
-                results.add(clazz.cast(ent));
-            }
-        });
-        return results;
-    }
-
-    @Override
-    public String explanation() {
-        return null;
-    }
-
-    @Override
-    public Throwable throwable() {
-        return null;
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockTableModel.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockTableModel.java
deleted file mode 100644
index b8b9d26..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/MockTableModel.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onosproject.net.pi.model.PiActionId;
-import org.onosproject.net.pi.model.PiActionModel;
-import org.onosproject.net.pi.model.PiActionProfileModel;
-import org.onosproject.net.pi.model.PiCounterModel;
-import org.onosproject.net.pi.model.PiMatchFieldId;
-import org.onosproject.net.pi.model.PiMatchFieldModel;
-import org.onosproject.net.pi.model.PiMeterModel;
-import org.onosproject.net.pi.model.PiTableId;
-import org.onosproject.net.pi.model.PiTableModel;
-import org.onosproject.net.pi.model.PiTableType;
-
-import java.util.Collection;
-import java.util.Optional;
-
-public class MockTableModel implements PiTableModel {
-    PiTableId id;
-    int size;
-
-    public MockTableModel(PiTableId id, int size) {
-        this.id = id;
-        this.size = size;
-    }
-
-    @Override
-    public PiTableId id() {
-        return this.id;
-    }
-
-    @Override
-    public PiTableType tableType() {
-        return null;
-    }
-
-    @Override
-    public PiActionProfileModel actionProfile() {
-        return null;
-    }
-
-    @Override
-    public long maxSize() {
-        return size;
-    }
-
-    @Override
-    public Collection<PiCounterModel> counters() {
-        return null;
-    }
-
-    @Override
-    public Collection<PiMeterModel> meters() {
-        return null;
-    }
-
-    @Override
-    public boolean supportsAging() {
-        return false;
-    }
-
-    @Override
-    public Collection<PiMatchFieldModel> matchFields() {
-        return null;
-    }
-
-    @Override
-    public Collection<PiActionModel> actions() {
-        return null;
-    }
-
-    @Override
-    public Optional<PiActionModel> constDefaultAction() {
-        return Optional.empty();
-    }
-
-    @Override
-    public boolean isConstantTable() {
-        return false;
-    }
-
-    @Override
-    public boolean oneShotOnly() {
-        return false;
-    }
-
-    @Override
-    public Optional<PiActionModel> action(PiActionId actionId) {
-        return Optional.empty();
-    }
-
-    @Override
-    public Optional<PiMatchFieldModel> matchField(PiMatchFieldId matchFieldId) {
-        return Optional.empty();
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestDistributedFabricUpfStore.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestDistributedFabricUpfStore.java
deleted file mode 100644
index 3bf3b26..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestDistributedFabricUpfStore.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import org.onosproject.store.service.TestEventuallyConsistentMap;
-import org.onosproject.store.service.WallClockTimestamp;
-
-import static org.onosproject.pipelines.fabric.impl.behaviour.upf.DistributedFabricUpfStore.FAR_ID_MAP_NAME;
-import static org.onosproject.pipelines.fabric.impl.behaviour.upf.DistributedFabricUpfStore.SERIALIZER;
-
-
-public final class TestDistributedFabricUpfStore {
-
-    private TestDistributedFabricUpfStore() {
-    }
-
-    public static DistributedFabricUpfStore build() {
-        var store = new DistributedFabricUpfStore();
-        TestEventuallyConsistentMap.Builder<Integer, UpfRuleIdentifier> reverseFarIdMapBuilder =
-                TestEventuallyConsistentMap.builder();
-        reverseFarIdMapBuilder.withName(FAR_ID_MAP_NAME)
-                .withTimestampProvider((k, v) -> new WallClockTimestamp())
-                .withSerializer(SERIALIZER.build());
-        store.reverseFarIdMap = reverseFarIdMapBuilder.build();
-
-        store.activate();
-
-        // Init with some translation state.
-        store.reverseFarIdMap.put(TestUpfConstants.UPLINK_PHYSICAL_FAR_ID,
-                new UpfRuleIdentifier(TestUpfConstants.SESSION_ID, TestUpfConstants.UPLINK_FAR_ID));
-        store.reverseFarIdMap.put(TestUpfConstants.DOWNLINK_PHYSICAL_FAR_ID,
-                new UpfRuleIdentifier(TestUpfConstants.SESSION_ID, TestUpfConstants.DOWNLINK_FAR_ID));
-
-        return store;
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestUpfConstants.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestUpfConstants.java
deleted file mode 100644
index 71c7212..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestUpfConstants.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.google.common.hash.Hashing;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.Ip4Prefix;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.DefaultApplicationId;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.behaviour.upf.ForwardingActionRule;
-import org.onosproject.net.behaviour.upf.PacketDetectionRule;
-import org.onosproject.net.behaviour.upf.UpfInterface;
-import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.criteria.PiCriterion;
-import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionParam;
-
-import java.util.Arrays;
-
-import static org.onosproject.pipelines.fabric.FabricConstants.CTR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.DROP;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_FARS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_INTERFACES;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_IFACE;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_NORMAL_FAR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_PDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR;
-import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_UPLINK_PDRS;
-import static org.onosproject.pipelines.fabric.FabricConstants.FAR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_FAR_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_GTPU_IS_VALID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_HAS_QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_IPV4_DST_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TEID;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TUNNEL_IPV4_DST;
-import static org.onosproject.pipelines.fabric.FabricConstants.HDR_UE_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.NEEDS_GTPU_DECAP;
-import static org.onosproject.pipelines.fabric.FabricConstants.NEEDS_QFI_PUSH;
-import static org.onosproject.pipelines.fabric.FabricConstants.NOTIFY_CP;
-import static org.onosproject.pipelines.fabric.FabricConstants.QFI;
-import static org.onosproject.pipelines.fabric.FabricConstants.SLICE_ID;
-import static org.onosproject.pipelines.fabric.FabricConstants.SRC_IFACE;
-import static org.onosproject.pipelines.fabric.FabricConstants.TC;
-import static org.onosproject.pipelines.fabric.FabricConstants.TEID;
-import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_DST_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_SRC_ADDR;
-import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_SRC_PORT;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_QFI;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_SLICE_ID;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_TC;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FALSE;
-import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.TRUE;
-import static org.onosproject.pipelines.fabric.impl.behaviour.upf.FabricUpfTranslator.INTERFACE_ACCESS;
-import static org.onosproject.pipelines.fabric.impl.behaviour.upf.FabricUpfTranslator.INTERFACE_CORE;
-
-
-public final class TestUpfConstants {
-    public static final DeviceId DEVICE_ID = DeviceId.deviceId("CoolSwitch91");
-    public static final ApplicationId APP_ID = new DefaultApplicationId(5000, "up4");
-    public static final int DEFAULT_PRIORITY = 10;
-    // SESSION_ID_BITWIDTH / 8 = 12
-    public static final ImmutableByteSequence SESSION_ID = ImmutableByteSequence.ofOnes(12);
-    public static final int UPLINK_COUNTER_CELL_ID = 1;
-    public static final int DOWNLINK_COUNTER_CELL_ID = 2;
-    public static final int PDR_ID = 0;  // TODO: PDR ID currently not stored on writes, so all reads are 0
-    public static final int UPLINK_FAR_ID = 1;
-    public static final int UPLINK_PHYSICAL_FAR_ID = Hashing.murmur3_32()
-            .newHasher()
-            .putInt(UPLINK_FAR_ID)
-            .putBytes(SESSION_ID.asArray())
-            .hash()
-            .asInt();
-    public static final int DOWNLINK_FAR_ID = 2;
-    public static final int DOWNLINK_PHYSICAL_FAR_ID = Hashing.murmur3_32()
-            .newHasher()
-            .putInt(DOWNLINK_FAR_ID)
-            .putBytes(SESSION_ID.asArray())
-            .hash()
-            .asInt();
-
-    public static final byte UPLINK_QFI = 1;
-    public static final byte DOWNLINK_QFI = 5;
-
-    public static final ImmutableByteSequence TEID_VALUE = ImmutableByteSequence.copyFrom(0xff);
-    public static final Ip4Address UE_ADDR = Ip4Address.valueOf("17.0.0.1");
-    public static final Ip4Address S1U_ADDR = Ip4Address.valueOf("192.168.0.1");
-    public static final Ip4Address ENB_ADDR = Ip4Address.valueOf("192.168.0.2");
-    public static final Ip4Prefix UE_POOL = Ip4Prefix.valueOf("17.0.0.0/16");
-    // TODO: tunnel source port currently not stored on writes, so all reads are 0
-    public static final short TUNNEL_SPORT = 2160;
-    public static final int PHYSICAL_COUNTER_SIZE = 512;
-    public static final int PHYSICAL_MAX_PDRS = 512;
-    public static final int PHYSICAL_MAX_FARS = 512;
-
-    public static final long COUNTER_BYTES = 12;
-    public static final long COUNTER_PKTS = 15;
-
-    public static final PacketDetectionRule UPLINK_PDR = PacketDetectionRule.builder()
-            .withTunnelDst(S1U_ADDR)
-            .withTeid(TEID_VALUE)
-            .withLocalFarId(UPLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .withCounterId(UPLINK_COUNTER_CELL_ID)
-            .build();
-
-    public static final PacketDetectionRule DOWNLINK_PDR = PacketDetectionRule.builder()
-            .withUeAddr(UE_ADDR)
-            .withLocalFarId(DOWNLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .withCounterId(DOWNLINK_COUNTER_CELL_ID)
-            .build();
-
-    public static final PacketDetectionRule UPLINK_QOS_PDR = PacketDetectionRule.builder()
-            .withTunnelDst(S1U_ADDR)
-            .withTeid(TEID_VALUE)
-            .withLocalFarId(UPLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .withCounterId(UPLINK_COUNTER_CELL_ID)
-            .withQfi(UPLINK_QFI)
-            .withQfiMatch()
-            .build();
-
-    public static final PacketDetectionRule UPLINK_QOS_4G_PDR = PacketDetectionRule.builder()
-            .withTunnelDst(S1U_ADDR)
-            .withTeid(TEID_VALUE)
-            .withLocalFarId(UPLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .withCounterId(UPLINK_COUNTER_CELL_ID)
-            .withQfi(UPLINK_QFI)
-            .build();
-
-    public static final PacketDetectionRule DOWNLINK_QOS_PDR = PacketDetectionRule.builder()
-            .withUeAddr(UE_ADDR)
-            .withLocalFarId(DOWNLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .withCounterId(DOWNLINK_COUNTER_CELL_ID)
-            .withQfi(DOWNLINK_QFI)
-            .withQfiPush()
-            .build();
-
-    public static final PacketDetectionRule DOWNLINK_QOS_4G_PDR = PacketDetectionRule.builder()
-            .withUeAddr(UE_ADDR)
-            .withLocalFarId(DOWNLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .withCounterId(DOWNLINK_COUNTER_CELL_ID)
-            .withQfi(DOWNLINK_QFI)
-            .build();
-
-    public static final ForwardingActionRule UPLINK_FAR = ForwardingActionRule.builder()
-            .setFarId(UPLINK_FAR_ID)
-            .withSessionId(SESSION_ID).build();
-
-    public static final ForwardingActionRule DOWNLINK_FAR = ForwardingActionRule.builder()
-            .setFarId(DOWNLINK_FAR_ID)
-            .withSessionId(SESSION_ID)
-            .setTunnel(S1U_ADDR, ENB_ADDR, TEID_VALUE, TUNNEL_SPORT)
-            .build();
-
-    public static final UpfInterface UPLINK_INTERFACE = UpfInterface.createS1uFrom(S1U_ADDR);
-
-    public static final UpfInterface DOWNLINK_INTERFACE = UpfInterface.createUePoolFrom(UE_POOL);
-
-    public static final FlowRule FABRIC_UPLINK_QOS_PDR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_UPLINK_PDRS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_TEID, TEID_VALUE.asArray())
-                                                   .matchExact(HDR_TUNNEL_IPV4_DST, S1U_ADDR.toInt())
-                                                   .matchExact(HDR_HAS_QFI, TRUE)
-                                                   .matchExact(HDR_QFI, UPLINK_QFI)
-                                           .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_PDR)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(CTR_ID, UPLINK_COUNTER_CELL_ID),
-                                                                  new PiActionParam(FAR_ID, UPLINK_PHYSICAL_FAR_ID),
-                                                                  new PiActionParam(NEEDS_GTPU_DECAP, TRUE),
-                                                                  new PiActionParam(TC, DEFAULT_TC)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_UPLINK_QOS_4G_PDR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_UPLINK_PDRS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_TEID, TEID_VALUE.asArray())
-                                                   .matchExact(HDR_TUNNEL_IPV4_DST, S1U_ADDR.toInt())
-                                                   .matchExact(HDR_HAS_QFI, FALSE)
-                                                   .matchExact(HDR_QFI, DEFAULT_QFI)
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(CTR_ID, UPLINK_COUNTER_CELL_ID),
-                                                                  new PiActionParam(FAR_ID, UPLINK_PHYSICAL_FAR_ID),
-                                                                  new PiActionParam(NEEDS_GTPU_DECAP, TRUE),
-                                                                  new PiActionParam(NEEDS_QFI_PUSH, FALSE),
-                                                                  new PiActionParam(QFI,
-                                                                                    UPLINK_QFI),
-                                                                  new PiActionParam(TC, DEFAULT_TC)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_DOWNLINK_QOS_PDR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_UE_ADDR, UE_ADDR.toInt())
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(CTR_ID, DOWNLINK_COUNTER_CELL_ID),
-                                                                  new PiActionParam(FAR_ID, DOWNLINK_PHYSICAL_FAR_ID),
-                                                                  new PiActionParam(QFI, DOWNLINK_QFI),
-                                                                  new PiActionParam(NEEDS_GTPU_DECAP, FALSE),
-                                                                  new PiActionParam(NEEDS_QFI_PUSH, TRUE),
-                                                                  new PiActionParam(TC, DEFAULT_TC)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_DOWNLINK_QOS_4G_PDR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_UE_ADDR, UE_ADDR.toInt())
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(CTR_ID, DOWNLINK_COUNTER_CELL_ID),
-                                                                  new PiActionParam(FAR_ID, DOWNLINK_PHYSICAL_FAR_ID),
-                                                                  new PiActionParam(QFI, DOWNLINK_QFI),
-                                                                  new PiActionParam(NEEDS_GTPU_DECAP, FALSE),
-                                                                  new PiActionParam(NEEDS_QFI_PUSH, FALSE),
-                                                                  new PiActionParam(TC, DEFAULT_TC)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_UPLINK_PDR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_UPLINK_PDRS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_TEID, TEID_VALUE.asArray())
-                                                   .matchExact(HDR_TUNNEL_IPV4_DST, S1U_ADDR.toInt())
-                                                   .matchExact(HDR_HAS_QFI, FALSE)
-                                                   .matchExact(HDR_QFI, DEFAULT_QFI)
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_PDR)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(CTR_ID, UPLINK_COUNTER_CELL_ID),
-                                                                  new PiActionParam(FAR_ID, UPLINK_PHYSICAL_FAR_ID),
-                                                                  new PiActionParam(NEEDS_GTPU_DECAP, TRUE),
-                                                                  new PiActionParam(TC, DEFAULT_TC)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_DOWNLINK_PDR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_UE_ADDR, UE_ADDR.toInt())
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_PDR)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(CTR_ID, DOWNLINK_COUNTER_CELL_ID),
-                                                                  new PiActionParam(FAR_ID, DOWNLINK_PHYSICAL_FAR_ID),
-                                                                  new PiActionParam(NEEDS_GTPU_DECAP, FALSE),
-                                                                  new PiActionParam(TC, DEFAULT_TC)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_UPLINK_FAR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_FARS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_FAR_ID, UPLINK_PHYSICAL_FAR_ID)
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_NORMAL_FAR)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(DROP, 0),
-                                                                  new PiActionParam(NOTIFY_CP, 0)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_DOWNLINK_FAR = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_FARS)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchExact(HDR_FAR_ID, DOWNLINK_PHYSICAL_FAR_ID)
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR)
-                                                          .withParameters(Arrays.asList(
-                                                                  new PiActionParam(DROP, 0),
-                                                                  new PiActionParam(NOTIFY_CP, 0),
-                                                                  new PiActionParam(TEID, TEID_VALUE),
-                                                                  new PiActionParam(TUNNEL_SRC_ADDR, S1U_ADDR.toInt()),
-                                                                  new PiActionParam(TUNNEL_DST_ADDR, ENB_ADDR.toInt()),
-                                                                  new PiActionParam(TUNNEL_SRC_PORT, TUNNEL_SPORT)
-                                                          ))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_UPLINK_INTERFACE = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_INTERFACES)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchLpm(HDR_IPV4_DST_ADDR,
-                                                             S1U_ADDR.toInt(),
-                                                             32)
-                                                   .matchExact(HDR_GTPU_IS_VALID, 1)
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(
-                                           PiAction.builder()
-                                                   .withId(FABRIC_INGRESS_SPGW_LOAD_IFACE)
-                                                   .withParameter(new PiActionParam(SRC_IFACE, INTERFACE_ACCESS))
-                                                   .withParameter(new PiActionParam(SLICE_ID, DEFAULT_SLICE_ID))
-                                                   .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    public static final FlowRule FABRIC_DOWNLINK_INTERFACE = DefaultFlowRule.builder()
-            .forDevice(DEVICE_ID).fromApp(APP_ID).makePermanent()
-            .forTable(FABRIC_INGRESS_SPGW_INTERFACES)
-            .withSelector(DefaultTrafficSelector.builder()
-                                  .matchPi(PiCriterion.builder()
-                                                   .matchLpm(HDR_IPV4_DST_ADDR,
-                                                             UE_POOL.address().toInt(),
-                                                             UE_POOL.prefixLength())
-                                                   .matchExact(HDR_GTPU_IS_VALID, 0)
-                                                   .build()).build())
-            .withTreatment(DefaultTrafficTreatment.builder()
-                                   .piTableAction(PiAction.builder()
-                                                          .withId(FABRIC_INGRESS_SPGW_LOAD_IFACE)
-                                                          .withParameter(new PiActionParam(SRC_IFACE, INTERFACE_CORE))
-                                                          .withParameter(new PiActionParam(SLICE_ID, DEFAULT_SLICE_ID))
-                                                          .build()).build())
-            .withPriority(DEFAULT_PRIORITY)
-            .build();
-
-    /**
-     * Hidden constructor for utility class.
-     */
-    private TestUpfConstants() {
-    }
-}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestUpfUtils.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestUpfUtils.java
deleted file mode 100644
index 6280f8e..0000000
--- a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/TestUpfUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.config.basics.BasicDeviceConfig;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public final class TestUpfUtils {
-
-    private static final String BASIC_CONFIG_KEY = "basic";
-
-    private TestUpfUtils() {
-        // hide constructor
-    }
-
-    public static BasicDeviceConfig getBasicConfig(DeviceId deviceId, String fileName)
-            throws IOException {
-        BasicDeviceConfig basicCfg = new BasicDeviceConfig();
-        InputStream jsonStream = TestUpfUtils.class.getResourceAsStream(fileName);
-        ObjectMapper mapper = new ObjectMapper();
-        JsonNode jsonNode = mapper.readTree(jsonStream);
-        basicCfg.init(deviceId, BASIC_CONFIG_KEY, jsonNode, mapper, config -> {
-        });
-        return basicCfg;
-    }
-}