Drop using BNG attachment IDs in favor or dynamically allocated line IDs

The current implementation of BngProgrammable for fabric.p4 uses
attachment IDs as line IDs, thus forcing apps such as bngc to be aware
of such implementation detail and to manage the allocation of such IDs.
Unfortunately, allocation of IDs is dependent on the device (P4 program)
implementation (e.g., line counter size), and so it should not be left
to apps.

This patch removes the need for attachment IDs at all and instead relies
on a driver-level service to dynamically allocate line IDs based on the
attachment attributes (currently s-tag, c-tag, mac address).

The current implementation of the allocation logic is a trivial one,
i.e. non-distributed and non-optimized.

Change-Id: Ie960936ee750cf565b8de41370085ecf9d49e931
(cherry picked from commit 6aa2a6ea743e8104ee3c62acb7d26acbd1452614)
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/BngProgrammable.java b/core/api/src/main/java/org/onosproject/net/behaviour/BngProgrammable.java
index 3f6f9e9..ccf9fb9 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/BngProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/BngProgrammable.java
@@ -19,7 +19,6 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
-import org.onlab.util.Identifier;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.driver.HandlerBehaviour;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
@@ -194,43 +193,38 @@
      * identifies a L2/L2.5 tunnel line between the RG and the BNG.
      */
     interface Attachment {
-        /**
-         * Get the identifier of the attachment.
-         *
-         * @return The identifier of the attachment.
-         */
-        AttachmentId attachmentId();
 
         /**
-         * Get the application ID that generated the attachment.
+         * Returns the application that is responsible for managing the
+         * attachment.
          *
          * @return The application ID.
          */
         ApplicationId appId();
 
         /**
-         * Get the VLAN S-tag of the attachment.
+         * Returns the VLAN S-tag of the attachment.
          *
          * @return The VLAN S-tag of the attachment.
          */
         VlanId sTag();
 
         /**
-         * Get the VLAN C-tag of the attachment.
+         * Returns the VLAN C-tag of the attachment.
          *
          * @return The VLAN C-tag of the attachment.
          */
         VlanId cTag();
 
         /**
-         * Get the MAC address of the attachment.
+         * Returns the MAC address of the attachment.
          *
          * @return The MAC address of the attachment.
          */
         MacAddress macAddress();
 
         /**
-         * Get the IP address of the attachment.
+         * Returns the IP address of the attachment.
          *
          * @return The IP address of the attachment.
          */
@@ -244,28 +238,31 @@
         boolean lineActive();
 
         /**
-         * Get the type of attachment.
+         * Returns the type of attachment.
          *
          * @return type of attachment
          */
         AttachmentType type();
 
         /**
-         * Get the PPPoE session ID of the attachment. This method is meaningful
-         * only if the attachment type is PPPoE.
+         * Returns the PPPoE session ID of the attachment. This method is
+         * meaningful only if the attachment type is PPPoE.
          *
          * @return The PPPoE session ID.
          */
         short pppoeSessionId();
 
+        /**
+         * Types of attachment.
+         */
         enum AttachmentType {
             /**
-             * Implemented as PPPoE attachment.
+             * PPPoE attachment.
              */
             PPPoE,
 
             /**
-             * Implemented as IPoE attachment.
+             * IPoE attachment.
              */
             // TODO: unsupported.
             IPoE
@@ -273,20 +270,6 @@
     }
 
     /**
-     * The identifier of the attachment.
-     */
-    class AttachmentId extends Identifier<Long> {
-        /**
-         * Identifies the attachment at network level.
-         *
-         * @param id long value
-         */
-        public AttachmentId(long id) {
-            super(id);
-        }
-    }
-
-    /**
      * An exception indicating a an error happened in the BNG programmable
      * behaviour.
      */
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
index 6def74f..19b3206 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
@@ -72,7 +72,7 @@
             }
         } catch (IOException e) {
             log.error("Unable to read CPU port file of {}: {}",
-                      pipeconf.id(), e.getMessage());
+                    pipeconf.id(), e.getMessage());
             return Optional.empty();
         }
     }
@@ -86,4 +86,31 @@
         }
         return false;
     }
+
+    /**
+     * Returns true if the pipeconf supports BNG user plane capabilities, false
+     * otherwise.
+     *
+     * @return boolean
+     */
+    public boolean supportBng() {
+        return pipeconf.pipelineModel()
+                .counter(FabricConstants.FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_C_LINE_RX)
+                .isPresent();
+    }
+
+    /**
+     * Returns the maximum number of BNG lines supported, or 0 if this pipeconf
+     * does not support BNG capabilities.
+     *
+     * @return maximum number of lines supported
+     */
+    public long bngMaxLineCount() {
+        if (!supportBng()) {
+            return 0;
+        }
+        return pipeconf.pipelineModel()
+                .counter(FabricConstants.FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_C_LINE_RX)
+                .orElseThrow().size();
+    }
 }
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngLineIdAllocator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngLineIdAllocator.java
new file mode 100644
index 0000000..624df91
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngLineIdAllocator.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.behaviour.BngProgrammable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An allocator of line IDs from a fixed range/pool. Used to map attachments to
+ * IDs for counters and other indirect P4 resources.
+ * <p>
+ * An implementation of this interface should use the {@link Handle} class to
+ * uniquely identify attachments and determine whether an ID has already been
+ * allocated or not.
+ */
+public interface FabricBngLineIdAllocator {
+
+    /**
+     * Returns a new ID for the given attachment. The implementation is expected
+     * to be idempotent, i.e., if an ID was previously allocated, then no new
+     * IDs should be allocated but the previous one should be returned.
+     *
+     * @param attachment the attachment instance
+     * @return the ID
+     * @throws IdExhaustedException if all IDs are currently allocated.
+     */
+    long allocate(BngProgrammable.Attachment attachment) throws IdExhaustedException;
+
+    /**
+     * Releases any ID previously allocated for the given attachment. If one was
+     * not allocated, calling this method should be a no-op.
+     *
+     * @param attachment the attachment instance
+     */
+    void release(BngProgrammable.Attachment attachment);
+
+    /**
+     * Releases the given ID, if allocated, otherwise calling this method should
+     * be a no-op.
+     *
+     * @param id the ID to release
+     */
+    void release(long id);
+
+    /**
+     * Returns the maximum number of IDs that can be allocated, independently of
+     * the current state.
+     *
+     * @return maximum number of IDs
+     */
+    long size();
+
+    /**
+     * Returns the number of currently available IDs that can be allocated for
+     * new attachments.
+     *
+     * @return free ID count
+     */
+    long freeCount();
+
+    /**
+     * Returns the number of currently allocated IDs.
+     *
+     * @return allocated ID count
+     */
+    long allocatedCount();
+
+    /**
+     * An identifier of an attachment in the scope of the same instance of a
+     * {@link FabricBngLineIdAllocator}.
+     */
+    class Handle {
+
+        private final VlanId stag;
+        private final VlanId ctag;
+        private final MacAddress macAddress;
+
+        public Handle(BngProgrammable.Attachment attachment) {
+            this.stag = checkNotNull(attachment.sTag());
+            this.ctag = checkNotNull(attachment.cTag());
+            this.macAddress = checkNotNull(attachment.macAddress());
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final Handle that = (Handle) o;
+            return Objects.equal(stag, that.stag) &&
+                    Objects.equal(ctag, that.ctag) &&
+                    Objects.equal(macAddress, that.macAddress);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(stag, ctag, macAddress);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                    .add("stag", stag)
+                    .add("ctag", ctag)
+                    .add("macAddress", macAddress)
+                    .toString();
+        }
+    }
+
+    /**
+     * Signals that no more IDs are currently available, but some have to be
+     * released before allocating a new one.
+     */
+    class IdExhaustedException extends Exception {
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammable.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammable.java
index 9951667..1a08e73 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammable.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammable.java
@@ -18,8 +18,10 @@
 
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.drivers.p4runtime.AbstractP4RuntimeHandlerBehaviour;
 import org.onosproject.net.behaviour.BngProgrammable;
@@ -34,21 +36,30 @@
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.criteria.PiCriterion;
 import org.onosproject.net.pi.model.PiCounterId;
+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.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellHandle;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.p4runtime.api.P4RuntimeWriteClient;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
 import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
+/**
+ * Implementation of BngProgrammable for fabric.p4.
+ */
 public class FabricBngProgrammable extends AbstractP4RuntimeHandlerBehaviour
         implements BngProgrammable {
 
@@ -56,9 +67,6 @@
     private static final int DEFAULT_PRIORITY = 10;
     // The index at which control plane packets are counted before the attachment is created.
     private static final int DEFAULT_CONTROL_INDEX = 0;
-    // FIXME: retrieve this value from the table size in the PipelineModel
-    // Max number of supported attachments, useful to make sure to not read/write non-existing counters.
-    private static final int MAX_SUPPORTED_ATTACHMENTS = 1000;
 
     private static final ImmutableBiMap<BngCounterType, PiCounterId> COUNTER_MAP =
             ImmutableBiMap.<BngCounterType, PiCounterId>builder()
@@ -74,6 +82,8 @@
             ImmutableSet.of(BngCounterType.UPSTREAM_RX, BngCounterType.DOWNSTREAM_DROPPED);
 
     private FlowRuleService flowRuleService;
+    private FabricBngProgrammableService bngProgService;
+    private FabricCapabilities capabilities;
 
     @Override
     protected boolean setupBehaviour(String opName) {
@@ -81,6 +91,16 @@
             return false;
         }
         flowRuleService = handler().get(FlowRuleService.class);
+        bngProgService = handler().get(FabricBngProgrammableService.class);
+        capabilities = new FabricCapabilities(pipeconf);
+
+        if (!capabilities.supportBng()) {
+            log.warn("Pipeconf {} on {} does not support BNG capabilities, " +
+                            "cannot perform {}",
+                    pipeconf.id(), deviceId, opName);
+            return false;
+        }
+
         return true;
     }
 
@@ -94,75 +114,84 @@
     }
 
     @Override
-    public void cleanUp(ApplicationId appId) throws BngProgrammableException {
+    public void cleanUp(ApplicationId appId) {
         if (!setupBehaviour("cleanUp()")) {
             return;
         }
-        flowRuleService.removeFlowRulesById(appId);
+        // Remove flow rules.
+        var flowEntries = flowRuleService.getFlowEntriesById(appId);
+        flowRuleService.removeFlowRules(
+                Iterables.toArray(flowEntries, FlowRule.class));
+        // Release line IDs found in removed flow rules.
+        getLineIdsFromFlowRules(flowEntries)
+                .forEach(this::releaseLineId);
+        // Reset counters.
         this.resetControlTrafficCounter();
     }
 
     @Override
-    public void setupAttachment(Attachment attachmentInfo) throws BngProgrammableException {
+    public void setupAttachment(Attachment attachment) throws BngProgrammableException {
         if (!setupBehaviour("setupAttachment()")) {
             return;
         }
-        checkAttachment(attachmentInfo);
+        checkAttachment(attachment);
         List<FlowRule> lstFlowRules = Lists.newArrayList();
-        lstFlowRules.add(buildTLineMapFlowRule(attachmentInfo));
+        lstFlowRules.add(buildTLineMapFlowRule(attachment));
         // If the line is not active do not generate the rule for the table
         // t_pppoe_term_v4 since term_disabled is @defaultonly action
-        if (attachmentInfo.lineActive()) {
-            lstFlowRules.add(buildTPppoeTermV4FlowRule(attachmentInfo));
+        if (attachment.lineActive()) {
+            lstFlowRules.add(buildTPppoeTermV4FlowRule(attachment));
         }
-        lstFlowRules.add(buildTLineSessionMapFlowRule(attachmentInfo));
+        lstFlowRules.add(buildTLineSessionMapFlowRule(attachment));
 
         lstFlowRules.forEach(flowRule -> flowRuleService.applyFlowRules(flowRule));
     }
 
     @Override
-    public void removeAttachment(Attachment attachmentInfo) throws BngProgrammableException {
+    public void removeAttachment(Attachment attachment) throws BngProgrammableException {
         if (!setupBehaviour("removeAttachment()")) {
             return;
         }
-        checkAttachment(attachmentInfo);
+        checkAttachment(attachment);
         List<FlowRule> lstFlowRules = Lists.newArrayList();
-        lstFlowRules.add(buildTLineMapFlowRule(attachmentInfo));
-        lstFlowRules.add(buildTPppoeTermV4FlowRule(attachmentInfo));
-        lstFlowRules.add(buildTLineSessionMapFlowRule(attachmentInfo));
+        lstFlowRules.add(buildTLineMapFlowRule(attachment));
+        lstFlowRules.add(buildTPppoeTermV4FlowRule(attachment));
+        lstFlowRules.add(buildTLineSessionMapFlowRule(attachment));
 
         lstFlowRules.forEach(flowRule -> flowRuleService.removeFlowRules(flowRule));
+
+        releaseLineId(attachment);
     }
 
     @Override
-    public Map<BngCounterType, PiCounterCellData> readCounters(Attachment attachmentInfo)
+    public Map<BngCounterType, PiCounterCellData> readCounters(Attachment attachment)
             throws BngProgrammableException {
         if (!setupBehaviour("readCounters()")) {
             return Maps.newHashMap();
         }
-        checkAttachment(attachmentInfo);
-        return readCounters(attachmentInfo.attachmentId().id(), Set.of(BngCounterType.values()));
+        checkAttachment(attachment);
+        return readCounters(lineId(attachment), Set.of(BngCounterType.values()));
     }
 
     @Override
-    public PiCounterCellData readCounter(Attachment attachmentInfo, BngCounterType counter)
+    public PiCounterCellData readCounter(Attachment attachment, BngCounterType counter)
             throws BngProgrammableException {
         if (!setupBehaviour("readCounter()")) {
             return null;
         }
-        checkAttachment(attachmentInfo);
-        return readCounters(attachmentInfo.attachmentId().id(), Set.of(counter))
+        checkAttachment(attachment);
+        return readCounters(lineId(attachment), Set.of(counter))
                 .getOrDefault(counter, null);
     }
 
     @Override
-    public void resetCounters(Attachment attachmentInfo)
+    public void resetCounters(Attachment attachment)
             throws BngProgrammableException {
         if (!setupBehaviour("resetCounters()")) {
             return;
         }
-        checkAttachment(attachmentInfo);
-        resetCounters(attachmentInfo.attachmentId().id(), Set.of(BngCounterType.values()));
+        checkAttachment(attachment);
+        resetCounters(lineId(attachment), Set.of(BngCounterType.values()));
     }
 
     @Override
@@ -176,17 +205,17 @@
     }
 
     @Override
-    public void resetCounter(Attachment attachmentInfo, BngCounterType counter)
+    public void resetCounter(Attachment attachment, BngCounterType counter)
             throws BngProgrammableException {
         if (!setupBehaviour("resetCounter()")) {
             return;
         }
-        checkAttachment(attachmentInfo);
-        resetCounters(attachmentInfo.attachmentId().id(), Set.of(counter));
+        checkAttachment(attachment);
+        resetCounters(lineId(attachment), Set.of(counter));
     }
 
     @Override
-    public void resetControlTrafficCounter() throws BngProgrammableException {
+    public void resetControlTrafficCounter() {
         if (!setupBehaviour("resetControlTrafficCounter()")) {
             return;
         }
@@ -198,7 +227,6 @@
      *
      * @param index    The index of the counter.
      * @param counters The set of counters to read.
-     * @throws BngProgrammableException
      */
     private Map<BngCounterType, PiCounterCellData> readCounters(
             long index,
@@ -226,8 +254,8 @@
             }
             readValues.putAll(counterEntryResponse.stream().collect(
                     Collectors.toMap(counterCell -> COUNTER_MAP.inverse()
-                                             .get(counterCell.cellId().counterId()),
-                                     PiCounterCell::data)));
+                                    .get(counterCell.cellId().counterId()),
+                            PiCounterCell::data)));
         }
         return readValues;
     }
@@ -238,7 +266,7 @@
      * @param index    The index of the counter.
      * @param counters The set of counters to reset.
      */
-    private void resetCounters(long index, Set<BngCounterType> counters) throws BngProgrammableException {
+    private void resetCounters(long index, Set<BngCounterType> counters) {
         Set<PiCounterCellId> counterCellIds = counters.stream()
                 .filter(c -> !UNSUPPORTED_COUNTER.contains(c))
                 .map(c -> PiCounterCellId.ofIndirect(COUNTER_MAP.get(c), index))
@@ -259,25 +287,17 @@
                 .all();
         counterEntryResponse.stream().filter(counterEntryResp -> !counterEntryResp.isSuccess())
                 .forEach(counterEntryResp -> log.warn("A counter was not reset correctly: {}",
-                                                      counterEntryResp.explanation()));
+                        counterEntryResp.explanation()));
     }
 
     /**
      * Preliminary check on the submitted attachment.
-     *
-     * @param attachmentInfo
-     * @throws BngProgrammableException If the attachment is not supported.
      */
-    private void checkAttachment(Attachment attachmentInfo) throws BngProgrammableException {
-        if (attachmentInfo.type() != Attachment.AttachmentType.PPPoE) {
+    private void checkAttachment(Attachment attachment) throws BngProgrammableException {
+        if (attachment.type() != Attachment.AttachmentType.PPPoE) {
             throw new BngProgrammableException(
                     "Attachment {} is not a PPPoE Attachment");
         }
-        if (attachmentInfo.attachmentId().id() >= MAX_SUPPORTED_ATTACHMENTS) {
-            throw new BngProgrammableException(
-                    "Attachment ID too big. Value:" + attachmentInfo.attachmentId().id().toString() +
-                            ", MAX:" + MAX_SUPPORTED_ATTACHMENTS);
-        }
     }
 
     /**
@@ -295,18 +315,16 @@
     /**
      * Build the Flow Rule for the table t_pppoe_term_v4 of the ingress
      * upstream.
-     *
-     * @param attachment
-     * @return
      */
-    private FlowRule buildTPppoeTermV4FlowRule(Attachment attachment) {
+    private FlowRule buildTPppoeTermV4FlowRule(Attachment attachment)
+            throws BngProgrammableException {
         PiCriterion criterion = PiCriterion.builder()
                 .matchExact(FabricConstants.HDR_LINE_ID,
-                            attachment.attachmentId().id())
+                        lineId(attachment))
                 .matchExact(FabricConstants.HDR_IPV4_SRC,
-                            attachment.ipAddress().toOctets())
+                        attachment.ipAddress().toOctets())
                 .matchExact(FabricConstants.HDR_PPPOE_SESSION_ID,
-                            attachment.pppoeSessionId())
+                        attachment.pppoeSessionId())
                 // TODO: match on MAC SRC address (antispoofing)
 //                    .matchExact(FabricConstants.HDR_ETH_SRC,
 //                                attachment.macAddress.toBytes())
@@ -316,29 +334,27 @@
                 .build();
         PiAction action = PiAction.builder()
                 .withId(attachment.lineActive() ?
-                                FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_ENABLED_V4 :
-                                FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_DISABLED)
+                        FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_ENABLED_V4 :
+                        FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_DISABLED)
                 .build();
         TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
                 .piTableAction(action)
                 .build();
         return buildFlowRule(trafficSelector,
-                             instTreatment,
-                             FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_TERM_V4,
-                             attachment.appId());
+                instTreatment,
+                FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_TERM_V4,
+                attachment.appId());
     }
 
     /**
      * Build the Flow Rule for the table t_line_session_map of the ingress
      * downstream.
-     *
-     * @param attachment
-     * @return
      */
-    private FlowRule buildTLineSessionMapFlowRule(Attachment attachment) {
+    private FlowRule buildTLineSessionMapFlowRule(Attachment attachment)
+            throws BngProgrammableException {
         PiCriterion criterion = PiCriterion.builder()
                 .matchExact(FabricConstants.HDR_LINE_ID,
-                            attachment.attachmentId().id())
+                        lineId(attachment))
                 .build();
         TrafficSelector trafficSelector = DefaultTrafficSelector.builder()
                 .matchPi(criterion)
@@ -348,7 +364,7 @@
             action = PiAction.builder()
                     .withId(FabricConstants.FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_SET_SESSION)
                     .withParameter(new PiActionParam(FabricConstants.PPPOE_SESSION_ID,
-                                                     attachment.pppoeSessionId()))
+                            attachment.pppoeSessionId()))
                     .build();
         } else {
             action = PiAction.builder()
@@ -359,24 +375,22 @@
                 .piTableAction(action)
                 .build();
         return buildFlowRule(trafficSelector,
-                             instTreatment,
-                             FabricConstants.FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_T_LINE_SESSION_MAP,
-                             attachment.appId());
+                instTreatment,
+                FabricConstants.FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_T_LINE_SESSION_MAP,
+                attachment.appId());
     }
 
     /**
      * Build the flow rule for the table t_line_map of the BNG-U (common to both
      * upstream and downstream).
-     *
-     * @param attachment
-     * @return
      */
-    private FlowRule buildTLineMapFlowRule(Attachment attachment) {
+    private FlowRule buildTLineMapFlowRule(Attachment attachment)
+            throws BngProgrammableException {
         PiCriterion criterion = PiCriterion.builder()
                 .matchExact(FabricConstants.HDR_S_TAG,
-                            attachment.sTag().toShort())
+                        attachment.sTag().toShort())
                 .matchExact(FabricConstants.HDR_C_TAG,
-                            attachment.cTag().toShort())
+                        attachment.cTag().toShort())
                 .build();
         TrafficSelector trafficSelector = DefaultTrafficSelector.builder()
                 .matchPi(criterion)
@@ -384,22 +398,19 @@
         PiAction action = PiAction.builder()
                 .withId(FabricConstants.FABRIC_INGRESS_BNG_INGRESS_SET_LINE)
                 .withParameter(new PiActionParam(FabricConstants.LINE_ID,
-                                                 attachment.attachmentId().id()))
+                        lineId(attachment)))
                 .build();
         TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
                 .piTableAction(action)
                 .build();
         return buildFlowRule(trafficSelector,
-                             instTreatment,
-                             FabricConstants.FABRIC_INGRESS_BNG_INGRESS_T_LINE_MAP,
-                             attachment.appId());
+                instTreatment,
+                FabricConstants.FABRIC_INGRESS_BNG_INGRESS_T_LINE_MAP,
+                attachment.appId());
     }
 
     /**
      * Build the flow rule for the table t_pppoe_cp of the ingress upstream.
-     *
-     * @param criterion Criterion to build the flow rule.
-     * @return The built flow rule.
      */
     private FlowRule buildTPppoeCpFlowRule(PiCriterion criterion, ApplicationId appId) {
         TrafficSelector trafficSelector = DefaultTrafficSelector.builder()
@@ -407,14 +418,14 @@
                 .build();
         TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
                 .piTableAction(PiAction.builder()
-                                       .withId(FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_PUNT_TO_CPU)
-                                       .build()
+                        .withId(FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_PUNT_TO_CPU)
+                        .build()
                 )
                 .build();
         return buildFlowRule(trafficSelector,
-                             instTreatment,
-                             FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_CP,
-                             appId);
+                instTreatment,
+                FabricConstants.FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_CP,
+                appId);
     }
 
     private FlowRule buildFlowRule(TrafficSelector trafficSelector,
@@ -431,4 +442,43 @@
                 .makePermanent()
                 .build();
     }
+
+    private long lineId(Attachment attachment) throws BngProgrammableException {
+        try {
+            return bngProgService.getLineIdAllocator(deviceId, capabilities.bngMaxLineCount()).allocate(attachment);
+        } catch (FabricBngLineIdAllocator.IdExhaustedException e) {
+            throw new BngProgrammableException("Line IDs exhausted, unable to allocate a new one");
+        }
+    }
+
+    private void releaseLineId(Attachment attachment) {
+        bngProgService.getLineIdAllocator(deviceId, capabilities.bngMaxLineCount()).release(attachment);
+    }
+
+    private void releaseLineId(long id) {
+        bngProgService.getLineIdAllocator(deviceId, capabilities.bngMaxLineCount()).release(id);
+    }
+
+    private Set<Long> getLineIdsFromFlowRules(Iterable<? extends FlowRule> rules) {
+        // Extract the line ID found in the flow rule selector.
+        return StreamSupport.stream(rules.spliterator(), true)
+                .map(f -> (PiCriterion) f.selector().getCriterion(Criterion.Type.PROTOCOL_INDEPENDENT))
+                .filter(Objects::nonNull)
+                .map(c -> c.fieldMatch(FabricConstants.HDR_LINE_ID))
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .filter(m -> m.type() == PiMatchType.EXACT)
+                .map(m -> ((PiExactFieldMatch) m).value())
+                .map(b -> {
+                    try {
+                        return b.fit(Long.BYTES * 8);
+                    } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+                        log.error("Invalid line ID found in flow rule: {} is bigger than a long! BUG?", b);
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .map(b -> b.asReadOnlyBuffer().getLong())
+                .collect(Collectors.toSet());
+    }
 }
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammableService.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammableService.java
new file mode 100644
index 0000000..5d664b4
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammableService.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import com.google.common.collect.Maps;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+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 java.util.Map;
+
+import static java.lang.String.format;
+
+/**
+ * Service that handles state necessary for the operations of {@link
+ * FabricBngProgrammable}.
+ */
+@Component(immediate = true, service = FabricBngProgrammableService.class)
+public final class FabricBngProgrammableService {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+    private Map<DeviceId, SimpleBngLineIdAllocator> allocators;
+
+    @Activate
+    public void activate() {
+        allocators = Maps.newHashMap();
+        deviceService.addListener(deviceListener);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        deviceService.removeListener(deviceListener);
+        allocators.clear();
+        allocators = null;
+    }
+
+    /**
+     * Returns a {@link FabricBngLineIdAllocator} for the given device and
+     * size.
+     *
+     * @param deviceId device ID
+     * @param size     size of the allocator
+     * @return allocator instance
+     * @throws IllegalArgumentException if an existing allocator is found for
+     *                                  the given device ID but with different
+     *                                  size.
+     */
+    FabricBngLineIdAllocator getLineIdAllocator(DeviceId deviceId, long size) {
+        return allocators.compute(deviceId, (d, allocator) -> {
+            if (allocator != null) {
+                if (allocator.size() == size) {
+                    return allocator;
+                } else {
+                    throw new IllegalArgumentException(format(
+                            "An allocator already exists for %s with size %d, " +
+                                    "but one was requested with different size (%d)",
+                            deviceId, allocators.get(deviceId).size(), size));
+                }
+            }
+            return new SimpleBngLineIdAllocator(size);
+        });
+    }
+
+    /**
+     * Internal device event listener.
+     */
+    public class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            if (event.type() == DeviceEvent.Type.DEVICE_REMOVED) {
+                allocators.remove(event.subject().id());
+            }
+        }
+    }
+}
+
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/SimpleBngLineIdAllocator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/SimpleBngLineIdAllocator.java
new file mode 100644
index 0000000..007ac11
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/SimpleBngLineIdAllocator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Lists;
+
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+import static org.onosproject.net.behaviour.BngProgrammable.Attachment;
+
+/**
+ * Trivial, thread-safe, non-distributed implementation of {@link
+ * FabricBngLineIdAllocator}.
+ */
+final class SimpleBngLineIdAllocator implements FabricBngLineIdAllocator {
+
+    private final long size;
+    private final BiMap<Handle, Long> allocated = HashBiMap.create();
+    private final Queue<Long> released = Lists.newLinkedList();
+
+    /**
+     * Creates a new allocator that can allocate and hold up to the given number
+     * of IDs.
+     *
+     * @param size maximum number of IDs to allocate.
+     */
+    SimpleBngLineIdAllocator(long size) {
+        this.size = size;
+        for (long i = 0; i < size; i++) {
+            released.add(i);
+        }
+    }
+
+    @Override
+    public long allocate(Attachment attachment) throws IdExhaustedException {
+        final Handle handle = new Handle(attachment);
+        synchronized (this) {
+            if (allocated.containsKey(handle)) {
+                return allocated.get(handle);
+            }
+            try {
+                final long id = released.remove();
+                allocated.put(handle, id);
+                return id;
+            } catch (NoSuchElementException e) {
+                // All IDs are taken;
+                throw new IdExhaustedException();
+            }
+        }
+    }
+
+    @Override
+    public void release(Attachment attachment) {
+        final Handle handle = new Handle(attachment);
+        synchronized (this) {
+            Long id = allocated.remove(handle);
+            if (id != null) {
+                released.add(id);
+            }
+        }
+    }
+
+    @Override
+    public void release(long id) {
+        synchronized (this) {
+            if (allocated.inverse().containsKey(id)) {
+                allocated.inverse().remove(id);
+                released.add(id);
+            }
+        }
+    }
+
+    @Override
+    public long size() {
+        return size;
+    }
+
+    @Override
+    public long freeCount() {
+        return released.size();
+    }
+
+    @Override
+    public long allocatedCount() {
+        return allocated.size();
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/service/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/service/package-info.java
deleted file mode 100644
index c57b047..0000000
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/service/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2017-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.
- */
-
-/**
- * Fabric pipeconf service implementations.
- */
-package org.onosproject.pipelines.fabric.impl.service;
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngLineIdAllocatorHandleTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngLineIdAllocatorHandleTest.java
new file mode 100644
index 0000000..bff0b24
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngLineIdAllocatorHandleTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+/**
+ * Tests for FabricBngLineIdAllocator.Handle.
+ */
+public class FabricBngLineIdAllocatorHandleTest {
+
+    @Test
+    public void equalityTest() {
+        var handle1 = new FabricBngLineIdAllocator.Handle(
+                new MockAttachment(1));
+        var sameAsHandle1 = new FabricBngLineIdAllocator.Handle(
+                new MockAttachment(1));
+        var handle2 = new FabricBngLineIdAllocator.Handle(
+                new MockAttachment(2));
+
+        new EqualsTester()
+                .addEqualityGroup(handle1, sameAsHandle1)
+                .addEqualityGroup(handle2)
+                .testEquals();
+    }
+}
\ No newline at end of file
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammableServiceTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammableServiceTest.java
new file mode 100644
index 0000000..28023a2
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/FabricBngProgrammableServiceTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for FabricBngProgrammableService.
+ */
+public class FabricBngProgrammableServiceTest {
+
+    private static final int SIZE = 10;
+    private static final DeviceId DEVICE_ID_1 = DeviceId.deviceId("device:1");
+    private static final DeviceId DEVICE_ID_2 = DeviceId.deviceId("device:2");
+
+    private final FabricBngProgrammableService service = new FabricBngProgrammableService();
+
+    @Before
+    public void setUp() throws Exception {
+        service.deviceService = new MockDeviceService();
+        service.activate();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        service.deactivate();
+        try {
+            service.getLineIdAllocator(DEVICE_ID_1, SIZE);
+            fail("Service methods should fail after deactivation");
+        } catch (NullPointerException e) {
+            // Expected.
+        }
+    }
+
+    @Test
+    public void getLineIdAllocatorTest() {
+        var allocator1 = service.getLineIdAllocator(DEVICE_ID_1, SIZE);
+        var sameAsAllocator1 = service.getLineIdAllocator(DEVICE_ID_1, SIZE);
+        var allocator2 = service.getLineIdAllocator(DEVICE_ID_2, SIZE);
+
+        assertEquals(allocator1.size(), SIZE);
+        assertEquals(allocator1, sameAsAllocator1);
+        assertNotEquals(allocator1, allocator2);
+
+        try {
+            service.getLineIdAllocator(DEVICE_ID_1, SIZE + 1);
+            fail("Retrieving allocators with different size should fail");
+        } catch (IllegalArgumentException e) {
+            // Expected.
+        }
+    }
+}
\ No newline at end of file
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/MockAttachment.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/MockAttachment.java
new file mode 100644
index 0000000..a0668be
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/MockAttachment.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.behaviour.BngProgrammable;
+
+/**
+ * Mock implementation of BngProgrammable.Attachment.
+ */
+class MockAttachment implements BngProgrammable.Attachment {
+
+    private final VlanId stag;
+    private final VlanId ctag;
+    private final MacAddress macAddress;
+
+    MockAttachment(int seed) {
+        this.stag = VlanId.vlanId((short) seed);
+        this.ctag = VlanId.vlanId((short) seed);
+        this.macAddress = MacAddress.valueOf(seed);
+    }
+
+    @Override
+    public ApplicationId appId() {
+        return null;
+    }
+
+    @Override
+    public VlanId sTag() {
+        return stag;
+    }
+
+    @Override
+    public VlanId cTag() {
+        return ctag;
+    }
+
+    @Override
+    public MacAddress macAddress() {
+        return macAddress;
+    }
+
+    @Override
+    public IpAddress ipAddress() {
+        return null;
+    }
+
+    @Override
+    public boolean lineActive() {
+        return false;
+    }
+
+    @Override
+    public AttachmentType type() {
+        return null;
+    }
+
+    @Override
+    public short pppoeSessionId() {
+        return 0;
+    }
+}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/MockDeviceService.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/MockDeviceService.java
new file mode 100644
index 0000000..a69d0af
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/MockDeviceService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+
+import java.util.List;
+
+/**
+ * Mock implmentation of DeviceService.
+ */
+class MockDeviceService implements DeviceService {
+    @Override
+    public int getDeviceCount() {
+        return 0;
+    }
+
+    @Override
+    public Iterable<Device> getDevices() {
+        return null;
+    }
+
+    @Override
+    public Iterable<Device> getDevices(Device.Type type) {
+        return null;
+    }
+
+    @Override
+    public Iterable<Device> getAvailableDevices() {
+        return null;
+    }
+
+    @Override
+    public Iterable<Device> getAvailableDevices(Device.Type type) {
+        return null;
+    }
+
+    @Override
+    public Device getDevice(DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public MastershipRole getRole(DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public List<Port> getPorts(DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+        return null;
+    }
+
+    @Override
+    public boolean isAvailable(DeviceId deviceId) {
+        return false;
+    }
+
+    @Override
+    public String localStatus(DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        return 0;
+    }
+
+    @Override
+    public void addListener(DeviceListener listener) {
+
+    }
+
+    @Override
+    public void removeListener(DeviceListener listener) {
+
+    }
+}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/SimpleBngLineIdAllocatorTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/SimpleBngLineIdAllocatorTest.java
new file mode 100644
index 0000000..3a9c064
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/bng/SimpleBngLineIdAllocatorTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.%
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.bng;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for SimpleBngLineIdAllocator.
+ */
+public class SimpleBngLineIdAllocatorTest {
+
+    private static final int SIZE = 10;
+
+    @Test
+    public void allocateAndReleaseTest() throws FabricBngLineIdAllocator.IdExhaustedException {
+        var allocator = new SimpleBngLineIdAllocator(SIZE);
+
+        var id1 = allocator.allocate(new MockAttachment(1));
+        var sameAsId1 = allocator.allocate(new MockAttachment(1));
+
+        var id2 = allocator.allocate(new MockAttachment(2));
+
+        assertEquals(allocator.allocatedCount(), 2);
+        assertEquals(allocator.freeCount(), SIZE - allocator.allocatedCount());
+
+        assertEquals(id1, sameAsId1);
+        assertNotEquals(id1, id2);
+
+        allocator.release(new MockAttachment(1));
+        assertEquals(allocator.allocatedCount(), 1);
+        assertEquals(allocator.freeCount(), SIZE - allocator.allocatedCount());
+
+        allocator.release(id2);
+        assertEquals(allocator.allocatedCount(), 0);
+        assertEquals(allocator.freeCount(), SIZE);
+    }
+
+    @Test
+    public void exhaustionTest() throws FabricBngLineIdAllocator.IdExhaustedException {
+        var allocator = new SimpleBngLineIdAllocator(SIZE);
+        var equalTester = new EqualsTester();
+        for (int i = 0; i < SIZE; i++) {
+            // Add ID to equality group to later make sure that all IDs are
+            // different.
+            equalTester.addEqualityGroup(
+                    allocator.allocate(new MockAttachment(i)));
+        }
+
+        assertEquals(allocator.allocatedCount(), SIZE);
+        assertEquals(allocator.freeCount(), 0);
+        equalTester.testEquals();
+
+        try {
+            allocator.allocate(new MockAttachment(SIZE + 1));
+            fail("IdExhaustedException not thrown");
+        } catch (FabricBngLineIdAllocator.IdExhaustedException e) {
+            // Expected.
+        }
+
+        for (int i = 0; i < SIZE; i++) {
+            allocator.release(new MockAttachment(i));
+        }
+
+        assertEquals(allocator.allocatedCount(), 0);
+        assertEquals(allocator.freeCount(), SIZE);
+    }
+
+}
\ No newline at end of file