[SDFAB-352][SDFAB-353] Retrieve MeterFeatures from the P4RT southbound, Extend MeterProviderService and revisit MeterStore

Change-Id: If0dae53643988cb551ff5020abd792cb6d33ff6b
diff --git a/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterFeatures.java b/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterFeatures.java
index bd5d998..3806250 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterFeatures.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterFeatures.java
@@ -22,6 +22,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -29,7 +30,8 @@
  */
 public final class DefaultMeterFeatures implements MeterFeatures {
     private DeviceId deviceId;
-    private long maxMeter;
+    private long startIndex;
+    private long endIndex;
     private Set<Band.Type> bandTypes;
     private Set<Meter.Unit> units;
     private boolean burst;
@@ -37,13 +39,16 @@
     private short maxBands;
     private short maxColor;
     private Set<MeterFeaturesFlag> features;
+    private MeterScope scope;
 
-    private DefaultMeterFeatures(DeviceId did, long maxMeter,
+    private DefaultMeterFeatures(DeviceId did, long startIndex, long endIndex,
                                  Set<Band.Type> bandTypes, Set<Meter.Unit> units,
                                  boolean burst, boolean stats,
-                                 short maxBands, short maxColor, Set<MeterFeaturesFlag> flag) {
+                                 short maxBands, short maxColor, Set<MeterFeaturesFlag> flag,
+                                 MeterScope scope) {
         this.deviceId = did;
-        this.maxMeter = maxMeter;
+        this.startIndex = startIndex;
+        this.endIndex = endIndex;
         this.bandTypes = bandTypes;
         this.burst = burst;
         this.stats = stats;
@@ -51,6 +56,7 @@
         this.maxBands = maxBands;
         this.maxColor = maxColor;
         this.features = flag;
+        this.scope = scope;
     }
 
     @Override
@@ -60,7 +66,18 @@
 
     @Override
     public long maxMeter() {
-        return maxMeter;
+        // For OpenFlow meter, return end index as maxMeter
+        return scope.isGlobal() ? endIndex + 1 : endIndex - startIndex + 1;
+    }
+
+    @Override
+    public long startIndex() {
+        return startIndex;
+    }
+
+    @Override
+    public long endIndex() {
+        return endIndex;
     }
 
     @Override
@@ -98,6 +115,11 @@
         return features;
     }
 
+    @Override
+    public MeterScope scope() {
+        return scope;
+    }
+
     public static Builder builder() {
         return new Builder();
     }
@@ -111,13 +133,15 @@
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
                 .add("deviceId", deviceId())
-                .add("maxMeter", maxMeter())
+                .add("startIndex", startIndex())
+                .add("endIndex", endIndex())
                 .add("maxBands", maxBands())
                 .add("maxColor", maxColor())
                 .add("bands", bandTypes())
                 .add("burst", isBurstSupported())
                 .add("stats", isStatsSupported())
                 .add("units", unitTypes())
+                .add("scope", scope())
                 .toString();
     }
 
@@ -127,6 +151,8 @@
     public static final class Builder implements MeterFeatures.Builder {
         private DeviceId did;
         private long mmeter = 0L;
+        private long starti = -1L;
+        private long endi = -1L;
         private short mbands = 0;
         private short mcolors = 0;
         private Set<Band.Type> bandTypes = new HashSet<>();
@@ -134,6 +160,7 @@
         private boolean burst = false;
         private boolean stats = false;
         private Set<MeterFeaturesFlag> features = Sets.newHashSet();
+        private MeterScope mscope = MeterScope.globalScope();
 
         @Override
         public MeterFeatures.Builder forDevice(DeviceId deviceId) {
@@ -148,6 +175,18 @@
         }
 
         @Override
+        public MeterFeatures.Builder withStartIndex(long startIndex) {
+            starti = startIndex;
+            return this;
+        }
+
+        @Override
+        public MeterFeatures.Builder withEndIndex(long endIndex) {
+            endi = endIndex;
+            return this;
+        }
+
+        @Override
         public MeterFeatures.Builder withMaxBands(short maxBands) {
             mbands = maxBands;
             return this;
@@ -190,9 +229,33 @@
         }
 
         @Override
+        public MeterFeatures.Builder withScope(MeterScope scope) {
+            mscope = scope;
+            return this;
+        }
+
+        @Override
         public MeterFeatures build() {
+            // In case some functions are using maxMeter
+            // and both indexes are not set
+            // Start index will be
+            // 1, if it is global scope (An OpenFlow meter)
+            // 0, for the rest (A P4RT meter)
+            if (mmeter != 0L && starti == -1L && endi == -1L) {
+                starti = mscope.isGlobal() ? 1 : 0;
+                endi = mmeter - 1;
+            }
+            // If one of the index is unset/unvalid value, treated as no meter features
+            if (starti <= -1 || endi <= -1) {
+                starti = -1;
+                endi = -1;
+            }
+
             checkNotNull(did, "Must specify a device");
-            return new DefaultMeterFeatures(did, mmeter, bandTypes, units1, burst, stats, mbands, mcolors, features);
+            checkArgument(starti <= endi, "Start index must be less than or equal to end index");
+
+            return new DefaultMeterFeatures(did, starti, endi, bandTypes, units1, burst,
+                                            stats, mbands, mcolors, features, mscope);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterFeatures.java b/core/api/src/main/java/org/onosproject/net/meter/MeterFeatures.java
index 2749687..ff846eb 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterFeatures.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterFeatures.java
@@ -35,10 +35,26 @@
      * Returns the maximum number of meters accepted by the device.
      *
      * @return the maximum meter value.
+     * @deprecated in onos-2.5 replaced by {@link #startIndex()} and {@link #endIndex()}
      */
+    @Deprecated
     long maxMeter();
 
     /**
+     * Returns the start index (inclusive) of the meters.
+     *
+     * @return the start index
+     */
+    long startIndex();
+
+    /**
+     * Returns the end index (inclusive) of the meters.
+     *
+     * @return the end index
+     */
+    long endIndex();
+
+    /**
      * Returns band types supported.
      *
      * @return the band types supported.
@@ -89,6 +105,13 @@
     Set<MeterFeaturesFlag> features();
 
     /**
+     * Returns Meter Scope.
+     *
+     * @return meter scope
+     */
+    MeterScope scope();
+
+    /**
      * A meter features builder.
      */
     interface Builder {
@@ -105,10 +128,28 @@
          *
          * @param maxMeter the maximum meters available
          * @return this builder
+         * @deprecated in onos-2.5 replaced by {@link #withStartIndex(long)} and {@link #withEndIndex(long)}
          */
+        @Deprecated
         Builder withMaxMeters(long maxMeter);
 
         /**
+         * Assigns the start index (inclusive) for this meter features.
+         *
+         * @param startIndex the start index
+         * @return this builder
+         */
+        Builder withStartIndex(long startIndex);
+
+        /**
+         * Assigns the end index (inclusive) for this meter features.
+         *
+         * @param endIndex the end index
+         * @return this builder
+         */
+        Builder withEndIndex(long endIndex);
+
+        /**
          * Assigns the max bands value for this meter features.
          *
          * @param maxBands the maximum bands available.
@@ -165,6 +206,14 @@
         Builder withFeatures(Set<MeterFeaturesFlag> featureFlags);
 
         /**
+         * Assigns the meter scope.
+         *
+         * @param scope the scope
+         * @return this builder
+         */
+        Builder withScope(MeterScope scope);
+
+        /**
          * Builds the Meter Features based on the specified parameters.
          *
          * @return the meter features
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterFeaturesKey.java b/core/api/src/main/java/org/onosproject/net/meter/MeterFeaturesKey.java
index fa19a5f..2b5eb07 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterFeaturesKey.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterFeaturesKey.java
@@ -23,7 +23,9 @@
  * A meter features key represents a meter features uniquely.
  * Right now only deviceId is used but this class might be useful in
  * virtualization in which a unique deviceId could have multiple features (guess).
+ * @deprecated in onos-2.5 replaced by {@link MeterTableKey}
  */
+@Deprecated
 public final class MeterFeaturesKey {
     private final DeviceId deviceId;
 
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterKey.java b/core/api/src/main/java/org/onosproject/net/meter/MeterKey.java
index 8c528b2..21717b6 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterKey.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterKey.java
@@ -26,9 +26,9 @@
 public final class MeterKey {
 
     private final DeviceId deviceId;
-    private final MeterId id;
+    private final MeterCellId id;
 
-    private MeterKey(DeviceId deviceId, MeterId id) {
+    private MeterKey(DeviceId deviceId, MeterCellId id) {
         this.deviceId = deviceId;
         this.id = id;
     }
@@ -37,7 +37,22 @@
         return deviceId;
     }
 
+    /**
+     * @return a MeterId iff the id is a MeterId
+     * otherwise, return null
+     * @deprecated in onos-2.5 replaced by {@link #key(DeviceId,MeterCellId)}
+     * extends MeterKey to support both MeterId and PiMeterCellId
+     */
+    @Deprecated
     public MeterId meterId() {
+        if (id instanceof MeterId) {
+            return (MeterId) id;
+        } else {
+            return null;
+        }
+    }
+
+    public MeterCellId meterCellId() {
         return id;
     }
 
@@ -63,10 +78,22 @@
     public String toString() {
         return toStringHelper(this)
                 .add("deviceId", deviceId)
-                .add("meterId", id).toString();
+                .add("meterCellId", id).toString();
     }
 
+    /**
+     * @param deviceId a DeviceId
+     * @param id a MeterId
+     * @return a MeterKey contains DeviceId and MeterId
+     * @deprecated in onos-2.5 replaced by {@link #key(DeviceId,MeterCellId)}
+     * extends MeterKey to support both MeterId and PiMeterCellId
+     */
+    @Deprecated
     public static MeterKey key(DeviceId deviceId, MeterId id) {
         return new MeterKey(deviceId, id);
     }
+
+    public static MeterKey key(DeviceId deviceId, MeterCellId id) {
+        return new MeterKey(deviceId, id);
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterProgrammable.java b/core/api/src/main/java/org/onosproject/net/meter/MeterProgrammable.java
index 4b8ea07..74eab4d 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterProgrammable.java
@@ -42,4 +42,11 @@
      * @return completable future with the collection of meters
      */
     CompletableFuture<Collection<Meter>> getMeters();
+
+    /**
+     * Queries the meter features from the device.
+     *
+     * @return completable future with the collection of meter features
+     */
+    CompletableFuture<Collection<MeterFeatures>> getMeterFeatures();
 }
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java b/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java
index 29c251c..e96e224 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java
@@ -54,6 +54,14 @@
     void pushMeterFeatures(DeviceId deviceId,
                            MeterFeatures meterfeatures);
 
+    /**
+     * Pushes the collection of meter features collected from the device.
+     *
+     * @param deviceId the device Id
+     * @param meterfeatures the meter features
+     */
+    void pushMeterFeatures(DeviceId deviceId,
+                           Collection<MeterFeatures> meterfeatures);
 
     /**
      * Delete meter features collected from the device.
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterScope.java b/core/api/src/main/java/org/onosproject/net/meter/MeterScope.java
new file mode 100644
index 0000000..06eb448
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterScope.java
@@ -0,0 +1,69 @@
+/*
+ * 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.meter;
+
+import org.onlab.util.Identifier;
+
+/**
+ * Scope of Meter Features.
+ *
+ * There are multiple meter tables in a P4RT device,
+ * to distinguish and represent them uniquely,
+ * we added a Scope field.
+ *
+ * For P4RT device, value will be the PiMeterId.
+ * For OF device, we use "global" by default, since there is only 1 table in OF.
+ * In general, a Meter Scope is referring to a Meter Table.
+ * It can be a PiMeterId or "global" for the single OF meter table.
+ *
+ * During runtime, users need to provide a PiMeterId to indicate which Meter Cell they
+ * are intended to modify. The value will then be used to create a Meter Scope
+ * for the rest of the process.
+ * If no PiMeterId is provided, a "global" Meter Scope is created.
+ */
+public class MeterScope extends Identifier<String> {
+
+    public static final String METER_GLOBAL_SCOPE = "global";
+
+    /**
+     * Create a Meter Scope from id string.
+     * @param scope the scope
+     * @return a Meter Scope
+     */
+    public static MeterScope of(String scope) {
+        return new MeterScope(scope);
+    }
+
+    /**
+     * Create a global Meter Scope.
+     * @return a Meter Scope
+     */
+    public static MeterScope globalScope() {
+        return new MeterScope(METER_GLOBAL_SCOPE);
+    }
+
+    MeterScope(String scope) {
+        super(scope);
+    }
+
+    /**
+     * Global scope or not.
+     * @return true if global scope, false if not.
+     */
+    public boolean isGlobal() {
+        return identifier.equals(METER_GLOBAL_SCOPE);
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterService.java b/core/api/src/main/java/org/onosproject/net/meter/MeterService.java
index 991715c..cf99b5e 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterService.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterService.java
@@ -74,7 +74,9 @@
      * @param deviceId the device id
      * @return the allocated meter id, null if there is an internal error
      * or there are no meter ids available
+     * @deprecated in onos-2.5
      */
+    @Deprecated
     MeterId allocateMeterId(DeviceId deviceId);
 
     /**
@@ -82,7 +84,9 @@
      *
      * @param deviceId the device id
      * @param meterId the id to be freed
+     * @deprecated in onos-2.5
      */
+    @Deprecated
     void freeMeterId(DeviceId deviceId, MeterId meterId);
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java b/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java
index 675592a..21696e4 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java
@@ -112,15 +112,26 @@
      * Delete this meter immediately.
      *
      * @param m a meter
+     * @deprecated in onos-2.5 renamed {@link #purgeMeter(Meter)}
      */
+    @Deprecated
     void deleteMeterNow(Meter m);
 
     /**
+     * Delete this meter immediately.
+     *
+     * @param m a meter
+     */
+    void purgeMeter(Meter m);
+
+    /**
      * Retrieve maximum meters available for the device.
      *
      * @param key the meter features key
      * @return the maximum number of meters supported by the device
+     * @deprecated in onos-2.5, Max meters is replaced by start and end index
      */
+    @Deprecated
     long getMaxMeters(MeterFeaturesKey key);
 
     /**
@@ -129,15 +140,30 @@
      * @param deviceId the device id
      * @return the meter Id or null if it was not possible
      * to allocate a meter id
+     * @deprecated in onos-2.5 replaced by {@link #allocateMeterId(DeviceId, MeterScope)}
      */
+    @Deprecated
     MeterId allocateMeterId(DeviceId deviceId);
 
     /**
+     * Allocates the first available MeterId.
+     *
+     * @param deviceId the device id
+     * @param meterScope the meter scope
+     * @return the meter Id or null if it was not possible
+     * to allocate a meter id
+     */
+    MeterCellId allocateMeterId(DeviceId deviceId, MeterScope meterScope);
+
+    /**
      * Frees the given meter id.
      *
      * @param deviceId the device id
      * @param meterId  the id to be freed
+     * @deprecated in onos-2.5, freeing an ID is closely related to removal of a meter
+     * so, this function is no longer exposed on interface
      */
+    @Deprecated
     void freeMeterId(DeviceId deviceId, MeterId meterId);
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterTableKey.java b/core/api/src/main/java/org/onosproject/net/meter/MeterTableKey.java
new file mode 100644
index 0000000..dc76fee
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterTableKey.java
@@ -0,0 +1,67 @@
+/*
+ * 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.meter;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * MeterTableKey is used to represent a single meter table in each device uniquely.
+ */
+public final class MeterTableKey {
+    private final DeviceId deviceId;
+    private final MeterScope scope;
+
+    private MeterTableKey(DeviceId deviceId, MeterScope scope) {
+        this.deviceId = deviceId;
+        this.scope = scope;
+    }
+
+    public static MeterTableKey key(DeviceId did, MeterScope scope) {
+        return new MeterTableKey(did, scope);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        MeterTableKey mtk = (MeterTableKey) obj;
+        return Objects.equals(deviceId, mtk.deviceId()) && Objects.equals(scope, mtk.scope());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(deviceId, scope);
+    }
+
+    @Override
+    public String toString() {
+        return "mtk@" + deviceId.toString() + " scope:" + scope.toString();
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    public MeterScope scope() {
+        return scope;
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/meter/impl/MeterDriverProvider.java b/core/net/src/main/java/org/onosproject/net/meter/impl/MeterDriverProvider.java
index e6151b0..cf7d198 100644
--- a/core/net/src/main/java/org/onosproject/net/meter/impl/MeterDriverProvider.java
+++ b/core/net/src/main/java/org/onosproject/net/meter/impl/MeterDriverProvider.java
@@ -24,6 +24,7 @@
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterFeatures;
 import org.onosproject.net.meter.MeterOperation;
 import org.onosproject.net.meter.MeterOperations;
 import org.onosproject.net.meter.MeterProgrammable;
@@ -36,6 +37,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
@@ -147,6 +149,17 @@
         meterProviderService.pushMeterMetrics(deviceId, meters);
     }
 
+    private void getMeterFeatures(DeviceId deviceId) {
+        Collection<MeterFeatures> meterFeatures = Collections.emptySet();
+        try {
+            meterFeatures = getMeterProgrammable(deviceId).getMeterFeatures().get(pollFrequency, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            log.warn("Unable to get the Meter Features from {}, error: {}", deviceId, e.getMessage());
+            log.debug("Exception: ", e);
+        }
+        meterProviderService.pushMeterFeatures(deviceId, meterFeatures);
+    }
+
     private MeterProgrammable getMeterProgrammable(DeviceId deviceId) {
         Device device = deviceService.getDevice(deviceId);
         if (device.is(MeterProgrammable.class)) {
@@ -173,6 +186,19 @@
 
         private void handleEvent(DeviceEvent event) {
             Device device = event.subject();
+
+            switch (event.type()) {
+                case DEVICE_ADDED:
+                    getMeterFeatures(device.id());
+                    break;
+                case DEVICE_REMOVED:
+                case DEVICE_SUSPENDED:
+                    meterProviderService.deleteMeterFeatures(device.id());
+                    break;
+                default:
+                    break;
+            }
+
             boolean isRelevant = mastershipService.isLocalMaster(device.id()) &&
                     deviceService.isAvailable(device.id());
 
diff --git a/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java b/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java
index 363876c..03bb5ca 100644
--- a/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java
+++ b/core/net/src/main/java/org/onosproject/net/meter/impl/MeterManager.java
@@ -417,7 +417,7 @@
                 // and we purge the meter from the store
                 } else if (m.state() == MeterState.PENDING_REMOVE) {
                     log.debug("Delete meter {} now in store", m.id());
-                    store.deleteMeterNow(m);
+                    store.purgeMeter(m);
                 }
             });
         }
@@ -428,6 +428,11 @@
         }
 
         @Override
+        public void pushMeterFeatures(DeviceId deviceId, Collection<MeterFeatures> meterfeatures) {
+            meterfeatures.forEach(mf -> store.storeMeterFeatures(mf));
+        }
+
+        @Override
         public void deleteMeterFeatures(DeviceId deviceId) {
             store.deleteMeterFeatures(deviceId);
         }
diff --git a/core/net/src/test/java/org/onosproject/net/meter/impl/MeterManagerTest.java b/core/net/src/test/java/org/onosproject/net/meter/impl/MeterManagerTest.java
index 52970dd..09e9774 100644
--- a/core/net/src/test/java/org/onosproject/net/meter/impl/MeterManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/meter/impl/MeterManagerTest.java
@@ -575,6 +575,12 @@
             mProgrammableAdded.setState(MeterState.ADDED);
             return CompletableFuture.completedFuture(ImmutableList.of(mProgrammableAdded));
         }
+
+        @Override
+        public CompletableFuture<Collection<MeterFeatures>> getMeterFeatures() {
+            //Currently unused.
+            return CompletableFuture.completedFuture(Collections.emptySet());
+        }
     }
 
     private class TestProvider extends AbstractProvider implements MeterProvider {
diff --git a/core/store/dist/src/main/java/org/onosproject/store/meter/impl/DistributedMeterStore.java b/core/store/dist/src/main/java/org/onosproject/store/meter/impl/DistributedMeterStore.java
index 11570f7..2fdba6f 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/meter/impl/DistributedMeterStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/meter/impl/DistributedMeterStore.java
@@ -31,6 +31,7 @@
 import org.onosproject.net.meter.DefaultMeter;
 import org.onosproject.net.meter.DefaultMeterFeatures;
 import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterCellId;
 import org.onosproject.net.meter.MeterEvent;
 import org.onosproject.net.meter.MeterFailReason;
 import org.onosproject.net.meter.MeterFeatures;
@@ -39,10 +40,14 @@
 import org.onosproject.net.meter.MeterId;
 import org.onosproject.net.meter.MeterKey;
 import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterScope;
 import org.onosproject.net.meter.MeterState;
 import org.onosproject.net.meter.MeterStore;
 import org.onosproject.net.meter.MeterStoreDelegate;
 import org.onosproject.net.meter.MeterStoreResult;
+import org.onosproject.net.meter.MeterTableKey;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.primitives.DefaultDistributedSet;
 import org.onosproject.store.serializers.KryoNamespaces;
@@ -50,12 +55,16 @@
 import org.onosproject.store.service.ConsistentMap;
 import org.onosproject.store.service.DistributedPrimitive;
 import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
 import org.onosproject.store.service.MapEvent;
 import org.onosproject.store.service.MapEventListener;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.StorageException;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.Versioned;
+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;
@@ -64,6 +73,8 @@
 import org.slf4j.Logger;
 
 import java.util.Collection;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -73,6 +84,8 @@
 
 import static org.onosproject.store.meter.impl.DistributedMeterStore.ReuseStrategy.FIRST_FIT;
 import static org.onosproject.net.meter.MeterFailReason.TIMEOUT;
+import static org.onosproject.net.meter.MeterCellId.MeterCellType.INDEX;
+import static org.onosproject.net.meter.MeterCellId.MeterCellType.PIPELINE_INDEPENDENT;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -88,20 +101,22 @@
     // Meters map related objects
     private static final String METERSTORE = "onos-meter-store";
     private ConsistentMap<MeterKey, MeterData> meters;
-    private MapEventListener<MeterKey, MeterData> mapListener = new InternalMapEventListener();
+    private MapEventListener<MeterKey, MeterData> metersMapListener = new InternalMetersMapEventListener();
     private Map<MeterKey, MeterData> metersMap;
 
     // Meters features related objects
     private static final String METERFEATURESSTORE = "onos-meter-features-store";
-    private ConsistentMap<MeterFeaturesKey, MeterFeatures> meterFeatures;
+    private EventuallyConsistentMap<MeterTableKey, MeterFeatures> metersFeatures;
+    private EventuallyConsistentMapListener<MeterTableKey, MeterFeatures> featuresMapListener =
+        new InternalFeaturesMapEventListener();
 
     // Meters id related objects
     private static final String AVAILABLEMETERIDSTORE = "onos-meters-available-store";
     // Available meter identifiers
-    private DistributedSet<MeterKey> availableMeterIds;
+    private ConcurrentMap<MeterTableKey, DistributedSet<MeterKey>> availableMeterIds;
     // Atomic counter map for generation of new identifiers;
     private static final String METERIDSTORE = "onos-meters-id-store";
-    private AtomicCounterMap<DeviceId> meterIdGenerators;
+    private AtomicCounterMap<MeterTableKey> meterIdGenerators;
 
     // Serializer related objects
     private static final KryoNamespace.Builder APP_KRYO_BUILDER = KryoNamespace.newBuilder()
@@ -147,35 +162,44 @@
         meters = storageService.<MeterKey, MeterData>consistentMapBuilder()
                     .withName(METERSTORE)
                     .withSerializer(serializer).build();
-        meters.addListener(mapListener);
-        // Init meter features map (meaningful only for OpenFlow protocol)
-        meterFeatures = storageService.<MeterFeaturesKey, MeterFeatures>consistentMapBuilder()
-                .withName(METERFEATURESSTORE)
-                .withSerializer(Serializer.using(KryoNamespaces.API,
-                                                 MeterFeaturesKey.class,
-                                                 MeterFeatures.class,
-                                                 DefaultMeterFeatures.class,
-                                                 Band.Type.class,
-                                                 Meter.Unit.class,
-                                                 MeterFailReason.class,
-                                                 MeterFeaturesFlag.class)).build();
+        meters.addListener(metersMapListener);
         metersMap = meters.asJavaMap();
-        // Init the set of the available ids
-        availableMeterIds = new DefaultDistributedSet<>(storageService.<MeterKey>setBuilder()
-                .withName(AVAILABLEMETERIDSTORE)
-                .withSerializer(Serializer.using(KryoNamespaces.API,
-                                                 MeterKey.class)).build(),
-                DistributedPrimitive.DEFAULT_OPERATION_TIMEOUT_MILLIS);
+        // Init meter features map
+        metersFeatures = storageService.<MeterTableKey, MeterFeatures>eventuallyConsistentMapBuilder()
+                .withName(METERFEATURESSTORE)
+                .withTimestampProvider((key, features) -> new WallClockTimestamp())
+                .withSerializer(KryoNamespace.newBuilder()
+                                .register(KryoNamespaces.API)
+                                .register(MeterTableKey.class)
+                                .register(MeterFeatures.class)
+                                .register(DefaultMeterFeatures.class)
+                                .register(DefaultBand.class)
+                                .register(Band.Type.class)
+                                .register(Meter.Unit.class)
+                                .register(MeterFailReason.class)
+                                .register(MeterFeaturesFlag.class)).build();
+        metersFeatures.addListener(featuresMapListener);
+        // Init the map of the available ids set
+        // Set will be created when a new Meter Features is pushed to the store
+        availableMeterIds = new ConcurrentHashMap<>();
         // Init atomic map counters
-        meterIdGenerators = storageService.<DeviceId>atomicCounterMapBuilder()
+        meterIdGenerators = storageService.<MeterTableKey>atomicCounterMapBuilder()
                 .withName(METERIDSTORE)
-                .withSerializer(Serializer.using(KryoNamespaces.API)).build();
+                .withSerializer(Serializer.using(KryoNamespaces.API,
+                                                 MeterTableKey.class,
+                                                 MeterScope.class)).build();
         log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
-        meters.removeListener(mapListener);
+        meters.removeListener(metersMapListener);
+        metersFeatures.removeListener(featuresMapListener);
+        meters.destroy();
+        metersFeatures.destroy();
+        availableMeterIds.forEach((key, set) -> {
+            set.destroy();
+        });
         log.info("Stopped");
     }
 
@@ -229,11 +253,11 @@
 
     @Override
     public MeterStoreResult storeMeterFeatures(MeterFeatures meterfeatures) {
-        // Store meter features, this is done once for each device
+        // Store meter features, this is done once for each features of every device
         MeterStoreResult result = MeterStoreResult.success();
-        MeterFeaturesKey key = MeterFeaturesKey.key(meterfeatures.deviceId());
+        MeterTableKey key = MeterTableKey.key(meterfeatures.deviceId(), meterfeatures.scope());
         try {
-            meterFeatures.putIfAbsent(key, meterfeatures);
+            metersFeatures.put(key, meterfeatures);
         } catch (StorageException e) {
             log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
                     e.getMessage(), e);
@@ -244,16 +268,20 @@
 
     @Override
     public MeterStoreResult deleteMeterFeatures(DeviceId deviceId) {
-        // Remove meter features - these ops are meaningful only for OpenFlow
         MeterStoreResult result = MeterStoreResult.success();
-        MeterFeaturesKey key = MeterFeaturesKey.key(deviceId);
         try {
-            meterFeatures.remove(key);
+            Set<MeterTableKey> keys = metersFeatures.keySet().stream()
+                                        .filter(key -> key.deviceId().equals(deviceId))
+                                        .collect(Collectors.toUnmodifiableSet());
+            keys.forEach(k -> {
+                metersFeatures.remove(k);
+            });
         } catch (StorageException e) {
             log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
-                    e.getMessage(), e);
+                        e.getMessage(), e);
             result = MeterStoreResult.fail(TIMEOUT);
         }
+
         return result;
     }
 
@@ -327,13 +355,27 @@
 
     @Override
     public void deleteMeterNow(Meter m) {
+        // This method is renamed in onos-2.5
+        purgeMeter(m);
+    }
+
+    @Override
+    public void purgeMeter(Meter m) {
         // Once we receive the ack from the sb
         // create the key and remove definitely the meter
         MeterKey key = MeterKey.key(m.deviceId(), m.id());
         try {
             if (Versioned.valueOrNull(meters.remove(key)) != null) {
                 // Free the id
-                freeMeterId(m.deviceId(), m.id());
+                MeterScope scope;
+                if (m.meterCellId().type() == PIPELINE_INDEPENDENT) {
+                    PiMeterCellId piMeterCellId = (PiMeterCellId) m.meterCellId();
+                    scope = MeterScope.of(piMeterCellId.meterId().id());
+                } else {
+                    scope = MeterScope.globalScope();
+                }
+                MeterTableKey meterTableKey = MeterTableKey.key(m.deviceId(), scope);
+                freeMeterId(meterTableKey, m.meterCellId());
             }
         } catch (StorageException e) {
             log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
@@ -350,16 +392,41 @@
                 .collect(Collectors.toList());
         // Remove definitely the meter
         metersPendingRemove.forEach(versionedMeterKey
-                -> deleteMeterNow(versionedMeterKey.value().meter()));
+                -> purgeMeter(versionedMeterKey.value().meter()));
     }
 
     @Override
     public long getMaxMeters(MeterFeaturesKey key) {
         // Leverage the meter features to know the max id
-        MeterFeatures features = Versioned.valueOrElse(meterFeatures.get(key), null);
+        // Create a Meter Table key with FeaturesKey's device and global scope
+        MeterTableKey meterTableKey = MeterTableKey.key(key.deviceId(), MeterScope.globalScope());
+        return getMaxMeters(meterTableKey);
+    }
+
+    private long getMaxMeters(MeterTableKey key) {
+        // Leverage the meter features to know the max id
+        MeterFeatures features = metersFeatures.get(key);
         return features == null ? 0L : features.maxMeter();
     }
 
+    private long getStartIndex(MeterTableKey key) {
+        // Leverage the meter features to know the start id
+        // Since we are using index now
+        // if there is no features related to the key
+        // -1 is returned
+        MeterFeatures features = metersFeatures.get(key);
+        return features == null ? -1L : features.startIndex();
+    }
+
+    private long getEndIndex(MeterTableKey key) {
+        // Leverage the meter features to know the max id
+        // Since we are using index now
+        // if there is no features related to the key
+        // -1 is returned
+        MeterFeatures features = metersFeatures.get(key);
+        return features == null ? -1L : features.endIndex();
+    }
+
     // queryMaxMeters is implemented in FullMetersAvailable behaviour.
     private long queryMaxMeters(DeviceId device) {
         // Get driver handler for this device
@@ -371,18 +438,31 @@
         }
         // Get the behavior
         MeterQuery query = handler.behaviour(MeterQuery.class);
+        // Insert a new available key set to the map
+        String setName = AVAILABLEMETERIDSTORE + "-" + device + "global";
+        MeterTableKey meterTableKey = MeterTableKey.key(device, MeterScope.globalScope());
+        insertAvailableKeySet(meterTableKey, setName);
         // Return as max meter the result of the query
         return query.getMaxMeters();
     }
 
-    private boolean updateMeterIdAvailability(DeviceId deviceId, MeterId id,
+    private boolean updateMeterIdAvailability(MeterTableKey meterTableKey, MeterCellId id,
                                               boolean available) {
+        // Retrieve the set first
+        DistributedSet<MeterKey> keySet = availableMeterIds.get(meterTableKey);
+        if (keySet == null) {
+            // A reusable set should be inserted when a features is pushed
+            log.warn("Reusable Key set for device: {} scope: {} not found",
+                meterTableKey.deviceId(), meterTableKey.scope());
+            return false;
+        }
         // According to available, make available or unavailable a meter key
-        return available ? availableMeterIds.add(MeterKey.key(deviceId, id)) :
-                availableMeterIds.remove(MeterKey.key(deviceId, id));
+        DeviceId deviceId = meterTableKey.deviceId();
+        return available ? keySet.add(MeterKey.key(deviceId, id)) :
+                keySet.remove(MeterKey.key(deviceId, id));
     }
 
-    private MeterId getNextAvailableId(Set<MeterId> availableIds) {
+    private MeterCellId getNextAvailableId(Set<MeterCellId> availableIds) {
         // If there are no available ids
         if (availableIds.isEmpty()) {
             // Just end the cycle
@@ -399,18 +479,27 @@
     }
 
     // Implements reuse strategy
-    private MeterId firstReusableMeterId(DeviceId deviceId) {
+    private MeterCellId firstReusableMeterId(MeterTableKey meterTableKey) {
+        // Create a Table key and use it to retrieve the reusable meterCellId set
+        DistributedSet<MeterKey> keySet = availableMeterIds.get(meterTableKey);
+        if (keySet == null) {
+            // A reusable set should be inserted when a features is pushed
+            log.warn("Reusable Key set for device: {} scope: {} not found",
+                meterTableKey.deviceId(), meterTableKey.scope());
+            return null;
+        }
         // Filter key related to device id, and reduce to meter ids
-        Set<MeterId> localAvailableMeterIds = availableMeterIds.stream()
-                .filter(meterKey -> meterKey.deviceId().equals(deviceId))
+        Set<MeterCellId> localAvailableMeterIds = keySet.stream()
+                .filter(meterKey ->
+                    meterKey.deviceId().equals(meterTableKey.deviceId()))
                 .map(MeterKey::meterId)
                 .collect(Collectors.toSet());
         // Get next available id
-        MeterId meterId = getNextAvailableId(localAvailableMeterIds);
+        MeterCellId meterId = getNextAvailableId(localAvailableMeterIds);
         // Iterate until there are items
         while (meterId != null) {
             // If we are able to reserve the id
-            if (updateMeterIdAvailability(deviceId, meterId, false)) {
+            if (updateMeterIdAvailability(meterTableKey, meterId, false)) {
                 // Just end
                 return meterId;
             }
@@ -425,49 +514,86 @@
 
     @Override
     public MeterId allocateMeterId(DeviceId deviceId) {
-        // Init steps
-        MeterId meterId;
+        // We use global scope for MeterId
+        return (MeterId) allocateMeterId(deviceId, MeterScope.globalScope());
+    }
+
+    @Override
+    public MeterCellId allocateMeterId(DeviceId deviceId, MeterScope meterScope) {
+        MeterTableKey meterTableKey = MeterTableKey.key(deviceId, meterScope);
+        MeterCellId meterCellId;
         long id;
-        // Try to reuse meter id
-        meterId = firstReusableMeterId(deviceId);
-        // We found a reusable id, return
-        if (meterId != null) {
-            return meterId;
+        // First, search for reusable key
+        meterCellId = firstReusableMeterId(meterTableKey);
+        if (meterCellId != null) {
+            // A reusable key is found
+            return meterCellId;
         }
-        // If there was no reusable MeterId we have to generate a new value
-        // using maxMeters as upper limit.
-        long maxMeters = getMaxMeters(MeterFeaturesKey.key(deviceId));
+        // If there was no reusable meter id we have to generate a new value
+        // using start and end index as lower and upper bound respectively.
+        long startIndex = getStartIndex(meterTableKey);
+        long endIndex = getEndIndex(meterTableKey);
         // If the device does not give us MeterFeatures
-        if (maxMeters == 0L) {
+        if (startIndex == -1L || endIndex == -1L) {
             // MeterFeatures couldn't be retrieved, fallback to queryMeters.
-            maxMeters = queryMaxMeters(deviceId);
-        }
-        // If we don't know the max, cannot proceed
-        if (maxMeters == 0L) {
-            return null;
+            // Only meaningful to OpenFLow
+            long maxMeters = queryMaxMeters(deviceId);
+            // If we don't know the max, cannot proceed
+            if (maxMeters == 0L) {
+                return null;
+            } else {
+                // OpenFlow meter index starts from 1, ends with max-1
+                startIndex = 1L;
+                endIndex = maxMeters - 1;
+            }
         }
         // Get a new value
-        id = meterIdGenerators.incrementAndGet(deviceId);
-        // Check with the max, and if the value is bigger, cannot proceed
-        if (id >= maxMeters) {
+        // If the value is smaller than the start index, get another one
+        do {
+            id = meterIdGenerators.incrementAndGet(meterTableKey);
+        } while (id < startIndex);
+        // Check with the end index, and if the value is bigger, cannot proceed
+        if (id > endIndex) {
             return null;
         }
         // Done, return the value
-        return MeterId.meterId(id);
+        // If we are using global scope, return a MeterId
+        // Else, return a PiMeterId
+        if (meterScope.isGlobal()) {
+            return MeterId.meterId(id);
+        } else {
+            return PiMeterCellId.ofIndirect(PiMeterId.of(meterScope.id()), id);
+        }
+
     }
 
     @Override
     public void freeMeterId(DeviceId deviceId, MeterId meterId) {
+        MeterTableKey meterTableKey = MeterTableKey.key(deviceId, MeterScope.globalScope());
+        freeMeterId(meterTableKey, meterId);
+    }
+
+    private void freeMeterId(MeterTableKey meterTableKey, MeterCellId meterCellId) {
+        long index;
+        if (meterCellId.type() == PIPELINE_INDEPENDENT) {
+            PiMeterCellId piMeterCellId = (PiMeterCellId) meterCellId;
+            index = piMeterCellId.index();
+        } else if (meterCellId.type() == INDEX) {
+            MeterId meterId = (MeterId) meterCellId;
+            index = meterId.id();
+        } else {
+            return;
+        }
         // Avoid to free meter not allocated
-        if (meterIdGenerators.get(deviceId) < meterId.id()) {
+        if (meterIdGenerators.get(meterTableKey) < index) {
             return;
         }
         // Update the availability
-        updateMeterIdAvailability(deviceId, meterId, true);
+        updateMeterIdAvailability(meterTableKey, meterCellId, true);
     }
 
     // Enabling the events distribution across the cluster
-    private class InternalMapEventListener implements MapEventListener<MeterKey, MeterData> {
+    private class InternalMetersMapEventListener implements MapEventListener<MeterKey, MeterData> {
         @Override
         public void event(MapEvent<MeterKey, MeterData> event) {
             MeterKey key = event.key();
@@ -523,8 +649,42 @@
                 default:
                     log.warn("Unknown Map event type {}", event.type());
             }
-
         }
     }
 
+    private class InternalFeaturesMapEventListener implements
+        EventuallyConsistentMapListener<MeterTableKey, MeterFeatures> {
+        @Override
+        public void event(EventuallyConsistentMapEvent<MeterTableKey, MeterFeatures> event) {
+            MeterTableKey meterTableKey = event.key();
+            MeterFeatures meterFeatures = event.value();
+            switch (event.type()) {
+                case PUT:
+                    // Put a new available meter id set to the map
+                    String setName = AVAILABLEMETERIDSTORE + "-" +
+                        meterFeatures.deviceId() + meterFeatures.scope().id();
+                    insertAvailableKeySet(meterTableKey, setName);
+                    break;
+                case REMOVE:
+                    // Remove the set
+                    DistributedSet<MeterKey> set = availableMeterIds.remove(meterTableKey);
+                    if (set != null) {
+                        set.destroy();
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private void insertAvailableKeySet(MeterTableKey meterTableKey, String setName) {
+        DistributedSet<MeterKey> availableMeterIdSet =
+            new DefaultDistributedSet<>(storageService.<MeterKey>setBuilder()
+                .withName(setName)
+                .withSerializer(Serializer.using(KryoNamespaces.API,
+                                                MeterKey.class)).build(),
+                DistributedPrimitive.DEFAULT_OPERATION_TIMEOUT_MILLIS);
+        availableMeterIds.put(meterTableKey, availableMeterIdSet);
+    }
 }
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
index 796bea6..0bcfcaf 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
@@ -19,13 +19,18 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Sets;
 import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMeterMirror;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.meter.Band;
 import org.onosproject.net.meter.DefaultBand;
 import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.DefaultMeterFeatures;
 import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterFeatures;
 import org.onosproject.net.meter.MeterOperation;
 import org.onosproject.net.meter.MeterProgrammable;
+import org.onosproject.net.meter.MeterScope;
 import org.onosproject.net.meter.MeterState;
 import org.onosproject.net.pi.model.PiMeterId;
 import org.onosproject.net.pi.model.PiMeterModel;
@@ -45,6 +50,8 @@
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
  * Implementation of MeterProgrammable behaviour for P4Runtime.
  */
@@ -148,4 +155,90 @@
 
         return CompletableFuture.completedFuture(meters);
     }
+
+    @Override
+    public CompletableFuture<Collection<MeterFeatures>> getMeterFeatures() {
+
+        if (!setupBehaviour("getMeterFeatures()")) {
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+
+        Collection<MeterFeatures> meterFeatures = new HashSet<>();
+        pipeconf.pipelineModel().meters().forEach(
+            m -> meterFeatures.add(new P4RuntimeMeterFeaturesBuilder(m, deviceId).build()));
+
+        return CompletableFuture.completedFuture(meterFeatures);
+    }
+
+    /**
+     * P4 meter features builder.
+     */
+    public class P4RuntimeMeterFeaturesBuilder {
+        private final PiMeterModel piMeterModel;
+        private DeviceId deviceId;
+
+        private static final long PI_METER_START_INDEX = 0L;
+        private static final short PI_METER_MAX_BAND = 2;
+        private static final short PI_METER_MAX_COLOR = 3;
+
+        public P4RuntimeMeterFeaturesBuilder(PiMeterModel piMeterModel, DeviceId deviceId) {
+            this.piMeterModel = checkNotNull(piMeterModel);
+            this.deviceId = deviceId;
+        }
+
+        /**
+         * To build a MeterFeatures using the PiMeterModel object
+         * retrieved from pipeconf.
+         *
+         * @return the meter features object
+         */
+        public MeterFeatures build() {
+            /*
+             * We set the basic values before to extract the other information.
+             */
+            MeterFeatures.Builder builder = DefaultMeterFeatures.builder()
+                    .forDevice(deviceId)
+                    // The scope value will be PiMeterId
+                    .withScope(MeterScope.of(piMeterModel.id().id()))
+                    .withMaxBands(PI_METER_MAX_BAND)
+                    .withMaxColors(PI_METER_MAX_COLOR)
+                    .withStartIndex(PI_METER_START_INDEX)
+                    .withEndIndex(piMeterModel.size() - 1);
+            /*
+             * Pi meter only support NONE type
+             */
+            Set<Band.Type> bands = Sets.newHashSet();
+            bands.add(Band.Type.NONE);
+            builder.withBandTypes(bands);
+            /*
+             * We extract the supported units;
+             */
+            Set<Meter.Unit> units = Sets.newHashSet();
+            if (piMeterModel.unit() == PiMeterModel.Unit.BYTES) {
+                units.add(Meter.Unit.KB_PER_SEC);
+            } else if (piMeterModel.unit() == PiMeterModel.Unit.PACKETS) {
+                units.add(Meter.Unit.PKTS_PER_SEC);
+            }
+            builder.withUnits(units);
+            /*
+             * Burst is supported ?
+             */
+            builder.hasBurst(true);
+            /*
+             * Stats are supported ?
+             */
+            builder.hasStats(false);
+
+            return builder.build();
+        }
+
+        /**
+         * To build an empty meter features.
+         * @param deviceId the device id
+         * @return the meter features
+         */
+        public MeterFeatures noMeterFeatures(DeviceId deviceId) {
+            return DefaultMeterFeatures.noMeterFeatures(deviceId);
+        }
+    }
 }
diff --git a/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml b/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml
index 0750805..67edaab 100644
--- a/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml
+++ b/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml
@@ -28,9 +28,8 @@
                    impl="org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.net.group.GroupProgrammable"
                    impl="org.onosproject.drivers.p4runtime.P4RuntimeGroupProgrammable"/>
-        <!-- FIXME: re-enable when ONOS-7720 is fixed
         <behaviour api="org.onosproject.net.meter.MeterProgrammable"
-                   impl="org.onosproject.drivers.p4runtime.P4RuntimeMeterProgrammable"/>-->
+                   impl="org.onosproject.drivers.p4runtime.P4RuntimeMeterProgrammable"/>
         <property name="supportPacketRequest">true</property>
     </driver>
 </drivers>
diff --git a/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/util/MeterFeaturesBuilder.java b/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/util/MeterFeaturesBuilder.java
index 35f9dde..4fc61aa 100644
--- a/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/util/MeterFeaturesBuilder.java
+++ b/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/util/MeterFeaturesBuilder.java
@@ -53,6 +53,8 @@
     private final OFMeterFeatures ofMeterFeatures;
     private DeviceId deviceId;
 
+    private static final long OF_METER_START_INDEX = 1L;
+
     public MeterFeaturesBuilder(OFMeterFeatures features, DeviceId deviceId) {
         this.ofMeterFeatures = checkNotNull(features);
         this.deviceId = deviceId;
@@ -72,7 +74,8 @@
                 .forDevice(deviceId)
                 .withMaxBands(ofMeterFeatures.getMaxBands())
                 .withMaxColors(ofMeterFeatures.getMaxColor())
-                .withMaxMeters(ofMeterFeatures.getMaxMeter());
+                .withStartIndex(OF_METER_START_INDEX)
+                .withEndIndex(ofMeterFeatures.getMaxMeter());
         /*
          * We extract the supported band types.
          */