[ONOS-7051] Support for P4Runtime meters

Change-Id: Id71374af65aeb84b71636b4ec230dc6001a77a8b
diff --git a/core/api/src/main/java/org/onosproject/net/meter/Band.java b/core/api/src/main/java/org/onosproject/net/meter/Band.java
index 5b75ab7..9f9479c 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/Band.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/Band.java
@@ -45,7 +45,14 @@
         /**
          * Defines an experimental meter band.
          */
-        EXPERIMENTAL
+        EXPERIMENTAL,
+
+        /**
+         * Defines a meter band with no action, used to mark
+         * packets internally in the pipeline, i.e. without
+         * modifying the packet headers.
+         */
+        NONE,
     }
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java b/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java
index 7be5e90..ad0256e 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java
@@ -25,6 +25,7 @@
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.meter.MeterCellId.MeterCellType.INDEX;
 
 /**
  * A default implementation of a meter.
@@ -32,7 +33,7 @@
 public final class DefaultMeter implements Meter, MeterEntry  {
 
 
-    private final MeterId id;
+    private final MeterCellId cellId;
     private final ApplicationId appId;
     private final Unit unit;
     private final boolean burst;
@@ -45,11 +46,10 @@
     private long packets;
     private long bytes;
 
-    private DefaultMeter(DeviceId deviceId, MeterId id, ApplicationId appId,
-                        Unit unit, boolean burst,
-                        Collection<Band> bands) {
+    private DefaultMeter(DeviceId deviceId, MeterCellId cellId, ApplicationId appId,
+                         Unit unit, boolean burst, Collection<Band> bands) {
         this.deviceId = deviceId;
-        this.id = id;
+        this.cellId = cellId;
         this.appId = appId;
         this.unit = unit;
         this.burst = burst;
@@ -63,7 +63,16 @@
 
     @Override
     public MeterId id() {
-        return id;
+        // Workaround until we remove this method. Deprecated in 1.13.
+        // Should use meterCellId() instead.
+        return cellId.type() == INDEX
+                ? (MeterId) cellId
+                : MeterId.meterId((cellId.hashCode()));
+    }
+
+    @Override
+    public MeterCellId meterCellId() {
+        return cellId;
     }
 
     @Override
@@ -144,7 +153,7 @@
     public String toString() {
         return toStringHelper(this)
                 .add("device", deviceId)
-                .add("id", id)
+                .add("cellId", cellId)
                 .add("appId", appId.name())
                 .add("unit", unit)
                 .add("isBurst", burst)
@@ -161,7 +170,7 @@
             return false;
         }
         DefaultMeter that = (DefaultMeter) o;
-        return Objects.equal(id, that.id) &&
+        return Objects.equal(cellId, that.cellId) &&
                 Objects.equal(appId, that.appId) &&
                 Objects.equal(unit, that.unit) &&
                 Objects.equal(deviceId, that.deviceId);
@@ -169,19 +178,18 @@
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(id, appId, unit, deviceId);
+        return Objects.hashCode(cellId, appId, unit, deviceId);
     }
 
     public static final class Builder implements Meter.Builder {
 
-        private MeterId id;
+        private MeterCellId cellId;
         private ApplicationId appId;
         private Unit unit = Unit.KB_PER_SEC;
         private boolean burst = false;
         private Collection<Band> bands;
         private DeviceId deviceId;
 
-
         @Override
         public Meter.Builder forDevice(DeviceId deviceId) {
             this.deviceId = deviceId;
@@ -190,7 +198,13 @@
 
         @Override
         public Meter.Builder withId(MeterId id) {
-            this.id = id;
+            this.withCellId(id);
+            return this;
+        }
+
+        @Override
+        public Meter.Builder withCellId(MeterCellId cellId) {
+            this.cellId = cellId;
             return this;
         }
 
@@ -224,8 +238,8 @@
             checkNotNull(bands, "Must have bands.");
             checkArgument(!bands.isEmpty(), "Must have at least one band.");
             checkNotNull(appId, "Must have an application id");
-            checkNotNull(id, "Must specify a meter id");
-            return new DefaultMeter(deviceId, id, appId, unit, burst, bands);
+            checkArgument(cellId != null, "Must specify a cell id.");
+            return new DefaultMeter(deviceId, cellId, appId, unit, burst, bands);
         }
 
 
diff --git a/core/api/src/main/java/org/onosproject/net/meter/Meter.java b/core/api/src/main/java/org/onosproject/net/meter/Meter.java
index 3cb4511..3d7544e 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/Meter.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/Meter.java
@@ -17,13 +17,14 @@
 
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.service.PiTranslatable;
 
 import java.util.Collection;
 
 /**
- * Represents a generalized meter to be deployed on a device.
+ * Represents a generalized meter cell configuration to be deployed on a device.
  */
-public interface Meter {
+public interface Meter extends PiTranslatable {
 
     enum Unit {
         /**
@@ -48,10 +49,19 @@
      * This meters id.
      *
      * @return a meter id
+     * @deprecated in Nightingale release (version 1.13.0). Use {@link #meterCellId()} instead.
      */
+    @Deprecated
     MeterId id();
 
     /**
+     * Returns the meter cell identifier of this meter.
+     *
+     * @return a meter identifier
+     */
+    MeterCellId meterCellId();
+
+    /**
      * The id of the application which created this meter.
      *
      * @return an application id
@@ -132,10 +142,21 @@
          *
          * @param id a e
          * @return this
+         * @deprecated in Nightingale release (version 1.13.0). Use {@link
+         * #withCellId(MeterCellId)} instead.
          */
+        @Deprecated
         Builder withId(MeterId id);
 
         /**
+         * Assigns the id to this meter cell.
+         *
+         * @param meterId a meter cell identifier
+         * @return this
+         */
+        Builder withCellId(MeterCellId meterId);
+
+        /**
          * Assigns the application that built this meter.
          *
          * @param appId an application id
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterCellId.java b/core/api/src/main/java/org/onosproject/net/meter/MeterCellId.java
new file mode 100644
index 0000000..900b79c
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterCellId.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.meter;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * A representation of a meter cell identifier.
+ * Uniquely identifies a meter cell in the scope of a single device.
+ */
+@Beta
+public interface MeterCellId {
+
+    /**
+     * Types of meter cell identifier.
+     */
+    enum MeterCellType {
+        /**
+         * Signifies that the meter cell can be identified with an integer index.
+         * Valid for pipelines that defines one global meter table, e.g. as with
+         * OpenFlow meters.
+         */
+        INDEX,
+
+        /**
+         * Signifies that the meter cell identifier is pipeline-independent.
+         */
+        PIPELINE_INDEPENDENT
+    }
+
+    /**
+     * Return the type of this meter cell identifier.
+     *
+     * @return type
+     */
+    MeterCellType type();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterId.java b/core/api/src/main/java/org/onosproject/net/meter/MeterId.java
index e735984..7c28880 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterId.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterId.java
@@ -20,15 +20,26 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 /**
- * A representation of a meter identifier.
- * Uniquely identifies a meter in the scope of a single device.
+ * A representation of a meter cell identifier. Uniquely identifies a meter cell
+ * in the scope of a single device.
  * <p>
- * The meter_id field uniquely identifies a meter within a switch. Meters are
- * defined starting with meter_id=1 up to the maximum number of meters that the
- * switch can support. The OpenFlow protocol also defines some additional
- * virtual meters that can not be associated with flows:
+ * This ID uniquely identifies a meter cell within in a switch that maintains
+ * only one meter instance. If a switch supports multiple meter instances (like
+ * in P4), then {@link org.onosproject.net.pi.runtime.PiMeterCellId} should be
+ * used. In this case, meter cells are defined starting with id=1 up to the
+ * maximum number of cells that the switch can support. The OpenFlow protocol
+ * also defines some additional virtual meter cells that can not be associated
+ * with flows.
  */
-public final class MeterId extends Identifier<Long> {
+public final class MeterId extends Identifier<Long> implements MeterCellId {
+
+    // TODO: should rename this class to SimpleMeterCellId to distinguish it
+    // from PiMeterId and PiMeterCellId. From ONOS-7051, to follow the P4
+    // abstraction there can be multiple instances of a meter in a data plane,
+    // each meter instance is made of multiple cells. This class is based on the
+    // OpenFlow abstraction where, following P4 terminology, the data plane
+    // maintains only one meter instance. What is described here as a MeterId is
+    // indeed the identifier of a meter cell.
 
     /**  Flow meters can use any number up to MAX. */
     public static final long MAX = 0xFFFF0000L;
@@ -63,4 +74,9 @@
         checkArgument(id <= MAX, "id cannot be larger than {}", MAX);
         return new MeterId(id);
     }
+
+    @Override
+    public MeterCellType type() {
+        return MeterCellType.INDEX;
+    }
 }
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 06a303f..4b8ea07 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,6 +42,4 @@
      * @return completable future with the collection of meters
      */
     CompletableFuture<Collection<Meter>> getMeters();
-}
-
-
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java b/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java
index bba2388..9060b95 100644
--- a/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java
+++ b/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java
@@ -141,7 +141,6 @@
          * @return a meter request
          */
         MeterRequest remove();
-
     }
 
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
index 21c09de..5ef452b 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
@@ -36,5 +36,10 @@
     /**
      * Action profile group member.
      */
-    GROUP_MEMBER
+    GROUP_MEMBER,
+
+    /**
+     * Meter config.
+     */
+    METER_CELL_CONFIG
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java
new file mode 100644
index 0000000..efa53ff
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Represents a band used within a meter.
+ */
+@Beta
+public class PiMeterBand {
+    private final long rate;
+    private final long burst;
+
+    /**
+     * Creates a band with rate and burst.
+     *
+     * @param rate  rate of this band
+     * @param burst burst of this band
+     */
+    public PiMeterBand(long rate, long burst) {
+        this.rate = rate;
+        this.burst = burst;
+    }
+
+    /**
+     * Returns the rate of this band.
+     *
+     * @return rate of this band
+     */
+    public long rate() {
+        return rate;
+    }
+
+    /**
+     * Returns the burst of this band.
+     *
+     * @return burst of this band
+     */
+    public long burst() {
+        return burst;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(rate, burst);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof PiMeterBand) {
+            PiMeterBand that = (PiMeterBand) obj;
+            return Objects.equals(rate, that.rate) &&
+                    Objects.equals(burst, that.burst);
+
+        }
+        return false;
+    }
+
+    public String toString() {
+        return toStringHelper(this)
+                .add("rate", rate)
+                .add("burst", burst).toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java
new file mode 100644
index 0000000..96ba124
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Configuration of a meter cell of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiMeterCellConfig implements PiEntity {
+
+    private final PiMeterCellId cellId;
+    private final ImmutableList<PiMeterBand> piMeterBands;
+
+    /**
+     * Creates a new meter cell configuration for the given cell identifier and meter bands.
+     *
+     * @param cellId  meter cell identifier
+     * @param piMeterBands meter bands
+     */
+    private PiMeterCellConfig(PiMeterCellId cellId, Collection<PiMeterBand> piMeterBands) {
+        this.cellId = cellId;
+        this.piMeterBands = ImmutableList.copyOf(piMeterBands);
+    }
+
+    /**
+     * Returns the cell identifier.
+     *
+     * @return cell identifier
+     */
+    public PiMeterCellId cellId() {
+        return cellId;
+    }
+
+    /**
+     * Returns the collection of bands of this cell.
+     *
+     * @return meter bands
+     */
+    public Collection<PiMeterBand> meterBands() {
+        return piMeterBands;
+    }
+
+    @Override
+    public PiEntityType piEntityType() {
+        return PiEntityType.METER_CELL_CONFIG;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PiMeterCellConfig)) {
+            return false;
+        }
+        PiMeterCellConfig that = (PiMeterCellConfig) o;
+
+        return piMeterBands.containsAll((that.piMeterBands)) &&
+                piMeterBands.size() == that.piMeterBands.size() &&
+                Objects.equal(cellId, that.cellId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(cellId, piMeterBands);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("cellId", cellId)
+                .add("meterBands", piMeterBands)
+                .toString();
+    }
+
+    /**
+     * Returns a meter cell configuration builder.
+     *
+     * @return a new builder
+     */
+    public static PiMeterCellConfig.Builder builder() {
+        return new PiMeterCellConfig.Builder();
+    }
+
+    public static final class Builder {
+        private  PiMeterCellId cellId;
+        private List<PiMeterBand> bands = new ArrayList<>();
+
+
+        private Builder() {
+            // Hides constructor.
+        }
+
+        /**
+         * Sets the meter cell identifier for this meter.
+         *
+         * @param meterCellId meter cell identifier
+         * @return this
+         */
+        public PiMeterCellConfig.Builder withMeterCellId(PiMeterCellId meterCellId) {
+            this.cellId = meterCellId;
+            return this;
+        }
+
+
+        /**
+         * Sets a meter band of this meter.
+         *
+         * @param band meter band
+         * @return this
+         */
+        public PiMeterCellConfig.Builder withMeterBand(PiMeterBand band) {
+            this.bands.add(band);
+            return this;
+        }
+
+        /**
+         * Builds the meter cell configuration.
+         *
+         * @return a new meter cell configuration
+         */
+        public PiMeterCellConfig build() {
+            checkNotNull(cellId);
+            return new PiMeterCellConfig(cellId, bands);
+        }
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellId.java
new file mode 100644
index 0000000..09cf7bf
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellId.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import org.onosproject.net.meter.MeterCellId;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterType;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a meter cell in a protocol-independent pipeline.
+ */
+@Beta
+public final class PiMeterCellId implements MeterCellId {
+
+    private final PiMeterId meterId;
+    private final PiMeterType meterType;
+    private final long index;
+    private final PiTableEntry tableEntry;
+
+    private PiMeterCellId(PiMeterId meterId, PiMeterType meterType, long index,
+                            PiTableEntry tableEntry) {
+        this.meterId = meterId;
+        this.meterType = meterType;
+        this.index = index;
+        this.tableEntry = tableEntry;
+    }
+
+    /**
+     * Returns the identifier of the meter instance where this cell is contained.
+     *
+     * @return meter identifier
+     */
+    public PiMeterId meterId() {
+        return meterId;
+    }
+
+    /**
+     * Returns the type of the meter identified.
+     *
+     * @return meter type
+     */
+    public PiMeterType meterType() {
+        return meterType;
+    }
+
+    /**
+     * Returns the meter index to which this cell ID is associated.
+     * Meaningful only if the meter is of type {@link PiMeterType#INDIRECT}.
+     *
+     * @return meter index
+     */
+    public long index() {
+        return index;
+    }
+
+    /**
+     * Returns the table entry to which this cell ID is associated.
+     * Meaningful only if the meter is of type {@link PiMeterType#DIRECT}, otherwise returns null.
+     *
+     * @return PI table entry or null
+     */
+    public PiTableEntry tableEntry() {
+        return tableEntry;
+    }
+
+    @Override
+    public MeterCellType type() {
+        return MeterCellType.PIPELINE_INDEPENDENT;
+    }
+
+    /**
+     * Return a direct meter cell ID for the given meter ID and table entry.
+     *
+     * @param meterId  meter ID
+     * @param tableEntry table entry
+     * @return meter cell ID
+     */
+    public static PiMeterCellId ofDirect(PiMeterId meterId, PiTableEntry tableEntry) {
+        checkNotNull(meterId);
+        checkNotNull(tableEntry);
+        return new PiMeterCellId(meterId, PiMeterType.DIRECT, -1, tableEntry);
+    }
+
+    /**
+     * Return an indirect meter cell ID for the given meter ID and index.
+     *
+     * @param meterId meter ID
+     * @param index     index
+     * @return meter cell ID
+     */
+    public static PiMeterCellId ofIndirect(PiMeterId meterId, long index) {
+        checkNotNull(meterId);
+        checkArgument(index >= 0, "Index must be a positive number");
+        return new PiMeterCellId(meterId, PiMeterType.INDIRECT, index, null);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final PiMeterCellId other = (PiMeterCellId) obj;
+        return Objects.equal(this.meterId, other.meterId)
+                && Objects.equal(this.meterType, other.meterType)
+                && Objects.equal(this.index, other.index)
+                && Objects.equal(this.tableEntry, other.tableEntry);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(meterId, meterType, index, tableEntry);
+    }
+
+    @Override
+    public String toString() {
+        return meterId.toString() + ':'
+                + (meterType == PiMeterType.DIRECT ? tableEntry.toString() : String.valueOf(index));
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java
new file mode 100644
index 0000000..ad2af9d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Global identifier of a PI meter cell configuration applied to a device, uniquely defined
+ * by a device ID and meter cell ID.
+ */
+@Beta
+public final class PiMeterHandle extends PiHandle<PiMeterCellConfig> {
+
+    private PiMeterHandle(DeviceId deviceId, PiMeterCellConfig meterCellConfig) {
+        super(deviceId, meterCellConfig);
+    }
+
+    /**
+     * Creates a new handle for the given device ID and PI meter cell configuration.
+     *
+     * @param deviceId device ID
+     * @param meterCellConfig meter config
+     * @return PI meter handle
+     */
+    public static PiMeterHandle of(DeviceId deviceId,
+                                   PiMeterCellConfig meterCellConfig) {
+        return new PiMeterHandle(deviceId, meterCellConfig);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(deviceId(),
+                                piEntity().cellId());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        PiMeterHandle that = (PiMeterHandle) o;
+        return Objects.equal(deviceId(), that.deviceId()) &&
+                Objects.equal(piEntity().cellId(),
+                              that.piEntity().cellId());
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("deviceId", deviceId())
+                .add("meterCellId", piEntity().cellId())
+                .toString();
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiMeterTranslationStore.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiMeterTranslationStore.java
new file mode 100644
index 0000000..ddd6ab3
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiMeterTranslationStore.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+
+/**
+ * A PI translation store that keeps track of which meters have been
+ * translated to which PI meters.
+ */
+@Beta
+public interface PiMeterTranslationStore
+        extends PiTranslationStore<Meter, PiMeterCellConfig> {
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiMeterTranslator.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiMeterTranslator.java
new file mode 100644
index 0000000..b52ad64
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiMeterTranslator.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+
+/**
+ * A translator of meters to PI Meter Configs.
+ */
+@Beta
+public interface PiMeterTranslator
+        extends PiTranslator<Meter, PiMeterCellConfig> {
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
index c7a95c3..20cef35 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
@@ -38,4 +38,11 @@
      * @return group translator
      */
     PiGroupTranslator groupTranslator();
+
+    /**
+     * Returns a meter translator.
+     *
+     * @return meter translator
+     */
+    PiMeterTranslator meterTranslator();
 }
diff --git a/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java b/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java
index 3d69601..0716d7d 100644
--- a/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java
+++ b/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java
@@ -80,6 +80,11 @@
         }
 
         @Override
+        public MeterCellId meterCellId() {
+            return null;
+        }
+
+        @Override
         public ApplicationId appId() {
             return null;
         }
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 5d7965b..04e3e56 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
@@ -30,6 +30,7 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.MeterCellId.MeterCellType;
 import org.onosproject.net.meter.Meter;
 import org.onosproject.net.meter.MeterEvent;
 import org.onosproject.net.meter.MeterFailReason;
@@ -132,10 +133,10 @@
                     }
                 });
 
-            };
+        };
 
         executorService = newFixedThreadPool(numThreads,
-                groupedThreads(GROUP_THREAD_NAME, WORKER_PATTERN, log));
+                                             groupedThreads(GROUP_THREAD_NAME, WORKER_PATTERN, log));
         modified(context);
         log.info("Started");
     }
@@ -146,7 +147,7 @@
             readComponentConfiguration(context);
         }
         defaultProvider.init(deviceService, createProviderService(defaultProvider),
-                mastershipService, fallbackMeterPollFrequency);
+                             mastershipService, fallbackMeterPollFrequency);
     }
 
     @Deactivate
@@ -289,7 +290,7 @@
                     // The meter is missing in the device. Reinstall!
                     log.debug("Adding meter missing in device {} {}", deviceId, m);
                     provider().performMeterOperation(deviceId,
-                            new MeterOperation(m, MeterOperation.Type.ADD));
+                                                     new MeterOperation(m, MeterOperation.Type.ADD));
                 }
             });
 
@@ -297,12 +298,19 @@
             meterEntriesMap.entrySet().stream()
                     .filter(md -> !allMeters.stream().anyMatch(m -> m.id().equals(md.getKey())))
                     .forEach(mio -> {
-                        // The meter is missin in onos. Uninstall!
-                        log.debug("Remove meter in device not in onos {} {}", deviceId, mio.getKey());
                         Meter meter = mio.getValue();
-                        provider().performMeterOperation(deviceId,
-                                new MeterOperation(meter, MeterOperation.Type.REMOVE));
-            });
+                        // FIXME: Removing a meter is meaningfull for OpenFlow, but not for P4Runtime.
+                        // In P4Runtime meter cells cannot be removed. For the
+                        // moment, we make the distinction between OpenFlow and
+                        // P4Runtime by looking at the MeterCellType (always
+                        // INDEX for OpenFlow).
+                        if (meter.meterCellId().type() == MeterCellType.INDEX) {
+                            // The meter is missing in onos. Uninstall!
+                            log.debug("Remove meter in device not in onos {} {}", deviceId, mio.getKey());
+                            provider().performMeterOperation(deviceId,
+                                                             new MeterOperation(meter, MeterOperation.Type.REMOVE));
+                        }
+                    });
 
             meterEntries.stream()
                     .filter(m -> allMeters.stream()
@@ -339,11 +347,11 @@
             switch (event.type()) {
                 case METER_ADD_REQ:
                     executorService.execute(new MeterInstaller(deviceId, event.subject(),
-                            MeterOperation.Type.ADD));
+                                                               MeterOperation.Type.ADD));
                     break;
                 case METER_REM_REQ:
                     executorService.execute(new MeterInstaller(deviceId, event.subject(),
-                            MeterOperation.Type.REMOVE));
+                                                               MeterOperation.Type.REMOVE));
                     break;
                 case METER_ADDED:
                     log.info("Meter added {}", event.subject());
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiMeterTranslatorImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiMeterTranslatorImpl.java
new file mode 100644
index 0000000..3831699
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiMeterTranslatorImpl.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.net.pi.impl;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiMeterBand;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
+import org.onosproject.net.pi.service.PiTranslationException;
+
+import static org.onosproject.net.meter.MeterCellId.MeterCellType.PIPELINE_INDEPENDENT;
+
+/**
+ * Implementation of meter translation logic.
+ */
+final class PiMeterTranslatorImpl {
+
+    private PiMeterTranslatorImpl() {
+        // Hides constructor.
+    }
+
+    private static final int TRTCM_RATES = 2;
+
+    /**
+     * Returns a PI meter config equivalent to the given meter, for the given pipeconf and device.
+     *
+     * @param meter    meter
+     * @param pipeconf pipeconf
+     * @param device   device
+     * @return PI meter configs
+     * @throws PiTranslationException if the meter cannot be translated
+     */
+    static PiMeterCellConfig translate(Meter meter, PiPipeconf pipeconf, Device device) throws PiTranslationException {
+
+        if (meter.meterCellId().type() != PIPELINE_INDEPENDENT) {
+            throw new PiTranslationException("PI meter cell type must be PIPELINE_INDEPENDENT!");
+        }
+
+        // FIXME: we might want to move this check to P4Runtime driver or protocol layer.
+        // In general, This check is more of P4Runtime limitation, we should do this check in the low level layer.
+        if (meter.bands().size() > TRTCM_RATES) {
+            throw new PiTranslationException("PI meter can not have more than 2 bands!");
+        }
+
+
+        PiMeterCellConfig.Builder builder = PiMeterCellConfig.builder();
+        for (Band band : meter.bands()) {
+            if (band.type() != Band.Type.NONE) {
+                throw new PiTranslationException("PI meter can not have band with other types except NONE!");
+            }
+
+            PiMeterBand piMeterBand = new PiMeterBand(band.rate(), band.burst());
+            builder.withMeterBand(piMeterBand);
+        }
+
+        return builder.withMeterCellId((PiMeterCellId) meter.meterCellId()).build();
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
index 9780fac..c69ced9 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
@@ -27,13 +27,17 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.group.Group;
+import org.onosproject.net.meter.Meter;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.service.PiFlowRuleTranslationStore;
 import org.onosproject.net.pi.service.PiFlowRuleTranslator;
 import org.onosproject.net.pi.service.PiGroupTranslationStore;
 import org.onosproject.net.pi.service.PiGroupTranslator;
+import org.onosproject.net.pi.service.PiMeterTranslationStore;
+import org.onosproject.net.pi.service.PiMeterTranslator;
 import org.onosproject.net.pi.service.PiTranslationException;
 import org.onosproject.net.pi.service.PiTranslationService;
 import org.slf4j.Logger;
@@ -59,13 +63,18 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private PiGroupTranslationStore groupTranslationStore;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private PiMeterTranslationStore meterTranslationStore;
+
     private PiFlowRuleTranslator flowRuleTranslator;
     private PiGroupTranslator groupTranslator;
+    private PiMeterTranslator meterTranslator;
 
     @Activate
     public void activate() {
         flowRuleTranslator = new InternalFlowRuleTranslator(flowRuleTranslationStore);
         groupTranslator = new InternalGroupTranslator(groupTranslationStore);
+        meterTranslator = new InternalMeterTranslator(meterTranslationStore);
         log.info("Started");
     }
 
@@ -73,6 +82,7 @@
     public void deactivate() {
         flowRuleTranslator = null;
         groupTranslator = null;
+        meterTranslator = null;
         log.info("Stopped");
     }
 
@@ -86,6 +96,11 @@
         return groupTranslator;
     }
 
+    @Override
+    public PiMeterTranslator meterTranslator() {
+        return meterTranslator;
+    }
+
     private Device getDevice(DeviceId deviceId) throws PiTranslationException {
         final Device device = deviceService.getDevice(deviceId);
         if (device == null) {
@@ -125,5 +140,21 @@
                     .translate(original, pipeconf, getDevice(original.deviceId()));
         }
     }
+
+    private final class InternalMeterTranslator
+            extends AbstractPiTranslatorImpl<Meter, PiMeterCellConfig>
+            implements PiMeterTranslator {
+
+        private InternalMeterTranslator(PiMeterTranslationStore store) {
+            super(store);
+        }
+
+        @Override
+        public PiMeterCellConfig translate(Meter original, PiPipeconf pipeconf)
+                throws PiTranslationException {
+            return PiMeterTranslatorImpl
+                    .translate(original, pipeconf, getDevice(original.deviceId()));
+        }
+    }
 }
 
diff --git a/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiMeterTranslationStore.java b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiMeterTranslationStore.java
new file mode 100644
index 0000000..ef7e9f1
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiMeterTranslationStore.java
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.store.pi.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.service.PiMeterTranslationStore;
+
+/**
+ * Distributed implementation of a PI translation store for meters.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedPiMeterTranslationStore
+        extends AbstractDistributedPiTranslationStore<Meter, PiMeterCellConfig>
+        implements PiMeterTranslationStore {
+
+    private static final String MAP_SIMPLE_NAME = "meter";
+
+    @Override
+    protected String mapSimpleName() {
+        return MAP_SIMPLE_NAME;
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 90bb979..6e8b25a 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -199,6 +199,8 @@
 import org.onosproject.net.intent.constraint.ProtectionConstraint;
 import org.onosproject.net.intent.constraint.WaypointConstraint;
 import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.meter.MeterCellId;
+import org.onosproject.net.meter.MeterCellId.MeterCellType;
 import org.onosproject.net.meter.MeterId;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.DefaultPacketRequest;
@@ -235,6 +237,7 @@
 import org.onosproject.net.pi.runtime.PiHandle;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.service.PiPipeconfConfig;
 import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
@@ -409,6 +412,8 @@
                     Instructions.StatTriggerInstruction.class,
                     StatTriggerFlag.class,
                     StatTriggerField.class,
+                    MeterCellId.class,
+                    MeterCellType.class,
                     MeterId.class,
                     Version.class,
                     ControllerNode.State.class,
@@ -660,6 +665,7 @@
                     PiMatchFieldId.class,
                     PiMatchType.class,
                     PiMeterId.class,
+                    PiMeterCellId.class,
                     PiMeterType.class,
                     PiPacketOperationType.class,
                     PiPipeconfId.class,
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
new file mode 100644
index 0000000..04654c8
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.drivers.p4runtime;
+
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMeterMirror;
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.DefaultBand;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterProgrammable;
+import org.onosproject.net.meter.MeterState;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterModel;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMeterHandle;
+import org.onosproject.net.pi.service.PiMeterTranslator;
+import org.onosproject.net.pi.service.PiTranslationException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Implementation of MeterProgrammable behaviour for P4Runtime.
+ */
+public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviour implements MeterProgrammable {
+
+    private static final int METER_LOCK_EXPIRE_TIME_IN_MIN = 10;
+    private static final LoadingCache<PiMeterHandle, Lock>
+            ENTRY_LOCKS = CacheBuilder.newBuilder()
+            .expireAfterAccess(METER_LOCK_EXPIRE_TIME_IN_MIN, TimeUnit.MINUTES)
+            .build(new CacheLoader<PiMeterHandle, Lock>() {
+                @Override
+                public Lock load(PiMeterHandle handle) {
+                    return new ReentrantLock();
+                }
+            });
+
+    private PiMeterTranslator translator;
+    private P4RuntimeMeterMirror meterMirror;
+    private PiPipelineModel pipelineModel;
+
+    @Override
+    protected boolean setupBehaviour() {
+        if (!super.setupBehaviour()) {
+            return false;
+        }
+
+        translator = piTranslationService.meterTranslator();
+        meterMirror = handler().get(P4RuntimeMeterMirror.class);
+        pipelineModel = pipeconf.pipelineModel();
+        return true;
+    }
+
+    @Override
+    public CompletableFuture<Boolean> performMeterOperation(MeterOperation meterOp) {
+
+        return CompletableFuture.completedFuture(processMeterOp(meterOp));
+    }
+
+    private boolean processMeterOp(MeterOperation meterOp) {
+
+        if (meterOp.type() != MeterOperation.Type.MODIFY) {
+            log.warn("P4runtime meter operations must be MODIFY!");
+            return false;
+        }
+
+        PiMeterCellConfig piMeterCellConfig;
+        try {
+            piMeterCellConfig = translator.translate(meterOp.meter(), pipeconf);
+        } catch (PiTranslationException e) {
+            log.warn("Unable translate meter, aborting meter operation {}: {}", meterOp.type(), e.getMessage());
+            log.debug("exception", e);
+            return false;
+        }
+
+        final PiMeterHandle handle = PiMeterHandle.of(deviceId, piMeterCellConfig);
+        ENTRY_LOCKS.getUnchecked(handle).lock();
+        boolean result = false;
+        try {
+            if (client.writeMeterCells(newArrayList(piMeterCellConfig), pipeconf).get()) {
+                meterMirror.put(handle, piMeterCellConfig);
+                result = true;
+            }
+
+        } catch (InterruptedException | ExecutionException e) {
+            log.warn("Exception while modify meter entry:", e);
+        } finally {
+            ENTRY_LOCKS.getUnchecked(handle).unlock();
+        }
+
+        return result;
+    }
+
+    @Override
+    public CompletableFuture<Collection<Meter>> getMeters() {
+
+        if (!setupBehaviour()) {
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+
+        Collection<PiMeterCellConfig> piMeterCellConfigs;
+
+        Set<PiMeterId> meterIds = new HashSet<>();
+        for (PiMeterModel mode : pipelineModel.meters()) {
+            meterIds.add(mode.id());
+        }
+
+        try {
+            piMeterCellConfigs = client.readAllMeterCells(meterIds, pipeconf).get();
+        } catch (InterruptedException | ExecutionException e) {
+            log.warn("Exception while reading meters from {}: {}", deviceId, e.toString());
+            log.debug("", e);
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+
+        Collection<Meter> meters = piMeterCellConfigs.stream()
+                .map(p -> {
+                    DefaultMeter meter = (DefaultMeter) DefaultMeter.builder()
+                            .withBands(p.meterBands().stream().map(b -> DefaultBand.builder()
+                                    .withRate(b.rate())
+                                    .burstSize(b.burst())
+                                    .ofType(Band.Type.NONE)
+                                    .build()).collect(Collectors.toList()))
+                            .withCellId(p.cellId()).build();
+                    meter.setState(MeterState.ADDED);
+                    return meter;
+                })
+                .collect(Collectors.toList());
+
+        return CompletableFuture.completedFuture(meters);
+    }
+}
\ No newline at end of file
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java
new file mode 100644
index 0000000..f5c1778
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.drivers.p4runtime.mirror;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.pi.runtime.PiMeterHandle;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.store.serializers.KryoNamespaces;
+
+/**
+ * Distributed implementation of a P4Runtime meter mirror.
+ */
+@Component(immediate = true)
+@Service
+public final class DistributedP4RuntimeMeterMirror
+        extends AbstractDistributedP4RuntimeMirror
+        <PiMeterHandle, PiMeterCellConfig>
+        implements P4RuntimeMeterMirror {
+
+    private static final String DIST_MAP_NAME = "onos-p4runtime-meter-mirror";
+
+    @Override
+    String mapName() {
+        return DIST_MAP_NAME;
+    }
+
+    @Override
+    KryoNamespace storeSerializer() {
+        return KryoNamespace.newBuilder()
+                .register(KryoNamespaces.API)
+                .register(TimedEntry.class)
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java
new file mode 100644
index 0000000..668492a
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.drivers.p4runtime.mirror;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMeterHandle;
+
+/**
+ * Mirror of meters installed on a P4Runtime device.
+ */
+@Beta
+public interface P4RuntimeMeterMirror
+        extends P4RuntimeMirror<PiMeterHandle, PiMeterCellConfig> {
+}
diff --git a/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml b/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml
index 3f00bde..5ee2c92 100644
--- a/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml
+++ b/drivers/p4runtime/src/main/resources/p4runtime-drivers.xml
@@ -24,6 +24,8 @@
                    impl="org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.net.group.GroupProgrammable"
                    impl="org.onosproject.drivers.p4runtime.P4RuntimeGroupProgrammable"/>
+        <behaviour api="org.onosproject.net.meter.MeterProgrammable"
+                   impl="org.onosproject.drivers.p4runtime.P4RuntimeMeterProgrammable"/>
         <property name="supportPacketRequest">true</property>
     </driver>
 </drivers>
diff --git a/incubator/protobuf/services/nb/src/test/java/org/onosproject/incubator/protobuf/services/nb/GrpcNbMeterServiceTest.java b/incubator/protobuf/services/nb/src/test/java/org/onosproject/incubator/protobuf/services/nb/GrpcNbMeterServiceTest.java
index 46ade10..c9e7180 100644
--- a/incubator/protobuf/services/nb/src/test/java/org/onosproject/incubator/protobuf/services/nb/GrpcNbMeterServiceTest.java
+++ b/incubator/protobuf/services/nb/src/test/java/org/onosproject/incubator/protobuf/services/nb/GrpcNbMeterServiceTest.java
@@ -32,22 +32,22 @@
 import org.onosproject.grpc.net.meter.models.MeterProtoOuterClass;
 import org.onosproject.grpc.net.meter.models.MeterRequestProtoOuterClass;
 import org.onosproject.net.DeviceId;
-
 import org.onosproject.net.meter.Band;
 import org.onosproject.net.meter.DefaultBand;
 import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterCellId;
 import org.onosproject.net.meter.MeterId;
-import org.onosproject.net.meter.MeterService;
-import org.onosproject.net.meter.MeterState;
 import org.onosproject.net.meter.MeterListener;
 import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.meter.MeterState;
 
 import java.io.IOException;
-import java.util.Set;
-import java.util.HashSet;
-import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.assertTrue;
 
@@ -231,6 +231,11 @@
         }
 
         @Override
+        public MeterCellId meterCellId() {
+            return this.id();
+        }
+
+        @Override
         public ApplicationId appId() {
             return this.appId;
         }
diff --git a/pipelines/basic/src/main/resources/basic.p4 b/pipelines/basic/src/main/resources/basic.p4
index 387cd0d..a44e10e 100644
--- a/pipelines/basic/src/main/resources/basic.p4
+++ b/pipelines/basic/src/main/resources/basic.p4
@@ -23,9 +23,11 @@
 #include "include/parsers.p4"
 #include "include/actions.p4"
 #include "include/port_counters.p4"
+#include "include/port_meters.p4"
 #include "include/checksums.p4"
 #include "include/packet_io.p4"
 #include "include/table0.p4"
+#include "include/host_meter_table.p4"
 #include "include/wcmp.p4"
 
 //------------------------------------------------------------------------------
@@ -38,8 +40,10 @@
 
     apply {
         port_counters_ingress.apply(hdr, standard_metadata);
+        port_meters_ingress.apply(hdr, standard_metadata);
         packetio_ingress.apply(hdr, standard_metadata);
         table0_control.apply(hdr, local_metadata, standard_metadata);
+        host_meter_control.apply(hdr, local_metadata, standard_metadata);
         wcmp_control.apply(hdr, local_metadata, standard_metadata);
      }
 }
@@ -54,6 +58,7 @@
 
     apply {
         port_counters_egress.apply(hdr, standard_metadata);
+        port_meters_egress.apply(hdr, standard_metadata);
         packetio_egress.apply(hdr, standard_metadata);
     }
 }
diff --git a/pipelines/basic/src/main/resources/include/custom_headers.p4 b/pipelines/basic/src/main/resources/include/custom_headers.p4
index 035e322..a57b076 100644
--- a/pipelines/basic/src/main/resources/include/custom_headers.p4
+++ b/pipelines/basic/src/main/resources/include/custom_headers.p4
@@ -29,6 +29,7 @@
     bit<16>       l4_src_port;
     bit<16>       l4_dst_port;
     next_hop_id_t next_hop_id;
+    bit<32>       meter_tag;
 }
 
-#endif
\ No newline at end of file
+#endif
diff --git a/pipelines/basic/src/main/resources/include/defines.p4 b/pipelines/basic/src/main/resources/include/defines.p4
index 0b0eeda..d9ec28d 100644
--- a/pipelines/basic/src/main/resources/include/defines.p4
+++ b/pipelines/basic/src/main/resources/include/defines.p4
@@ -27,4 +27,5 @@
 
 const port_t CPU_PORT = 255;
 
+enum MeterColor_t {GREEN, YELLOW, RED};
 #endif
diff --git a/pipelines/basic/src/main/resources/include/host_meter_table.p4 b/pipelines/basic/src/main/resources/include/host_meter_table.p4
new file mode 100644
index 0000000..6f581e1
--- /dev/null
+++ b/pipelines/basic/src/main/resources/include/host_meter_table.p4
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#ifndef __HOST_METER_TABLE__
+#define __HOST_METER_TABLE__
+
+#include "headers.p4"
+#include "defines.p4"
+
+control host_meter_control(inout headers_t hdr,
+                           inout local_metadata_t local_metadata,
+                           inout standard_metadata_t standard_metadata) {
+
+    direct_meter<bit<32>>(MeterType.bytes) host_meter;
+
+    action read_meter() {
+        host_meter.read(local_metadata.meter_tag);
+    }
+
+    table host_meter_table {
+        key = {
+            hdr.ethernet.src_addr   : lpm;
+        }
+        actions = {
+            read_meter();
+            NoAction;
+        }
+        meters = host_meter;
+        default_action = NoAction();
+    }
+
+    apply {
+        if (host_meter_table.apply().hit && local_metadata.meter_tag == 2) {
+            mark_to_drop();
+        }
+     }
+}
+
+#endif
diff --git a/pipelines/basic/src/main/resources/include/port_meters.p4 b/pipelines/basic/src/main/resources/include/port_meters.p4
new file mode 100644
index 0000000..710d19a
--- /dev/null
+++ b/pipelines/basic/src/main/resources/include/port_meters.p4
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef METERS
+#define METERS
+#include "defines.p4"
+
+control port_meters_ingress(inout headers_t hdr,
+                            inout standard_metadata_t standard_metadata) {
+    meter(MAX_PORTS, MeterType.bytes) ingress_port_meter;
+    MeterColor_t ingress_color = MeterColor_t.GREEN;
+
+    apply {
+        ingress_port_meter.execute_meter<MeterColor_t>((bit<32>)standard_metadata.ingress_port, ingress_color);
+        if (ingress_color == MeterColor_t.RED) {
+            mark_to_drop();
+        } 
+    }
+}
+
+control port_meters_egress(inout headers_t hdr,
+                           inout standard_metadata_t standard_metadata) {
+
+    meter(MAX_PORTS, MeterType.bytes) egress_port_meter;
+    MeterColor_t egress_color = MeterColor_t.GREEN;
+
+    apply {
+        egress_port_meter.execute_meter<MeterColor_t>((bit<32>)standard_metadata.egress_port, egress_color);
+        if (egress_color == MeterColor_t.RED) {
+            mark_to_drop();
+        } 
+    }
+}
+#endif
diff --git a/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json b/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json
index ac7b4f2..b90e0d6 100644
--- a/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json
+++ b/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json
@@ -11,9 +11,17 @@
       "fields" : [
         ["tmp", 32, false],
         ["tmp_0", 32, false],
+        ["port_meters_ingress_ingress_color_0", 32, false],
+        ["host_meter_control_tmp_1", 1, false],
+        ["host_meter_control_tmp_2", 1, false],
+        ["tmp_1", 32, false],
+        ["tmp_2", 32, false],
+        ["port_meters_egress_egress_color_0", 32, false],
         ["local_metadata_t.l4_src_port", 16, false],
         ["local_metadata_t.l4_dst_port", 16, false],
-        ["local_metadata_t.next_hop_id", 16, false]
+        ["local_metadata_t.next_hop_id", 16, false],
+        ["local_metadata_t.meter_tag", 32, false],
+        ["_padding_2", 6, false]
       ]
     },
     {
@@ -177,14 +185,23 @@
   "header_union_stacks" : [],
   "field_lists" : [],
   "errors" : [
-    ["NoError", 0],
-    ["PacketTooShort", 1],
-    ["NoMatch", 2],
-    ["StackOutOfBounds", 3],
-    ["HeaderTooShort", 4],
-    ["ParserTimeout", 5]
+    ["NoError", 1],
+    ["PacketTooShort", 2],
+    ["NoMatch", 3],
+    ["StackOutOfBounds", 4],
+    ["HeaderTooShort", 5],
+    ["ParserTimeout", 6]
   ],
-  "enums" : [],
+  "enums" : [
+    {
+      "name" : "MeterColor_t",
+      "entries" : [
+        ["GREEN", 0],
+        ["RED", 2],
+        ["YELLOW", 1]
+      ]
+    }
+  ],
   "parsers" : [
     {
       "name" : "parser",
@@ -414,7 +431,7 @@
       "name" : "deparser",
       "id" : 0,
       "source_info" : {
-        "filename" : "./include/parsers.p4",
+        "filename" : "include/parsers.p4",
         "line" : 72,
         "column" : 8,
         "source_fragment" : "deparser"
@@ -422,13 +439,58 @@
       "order" : ["packet_in", "ethernet", "ipv4", "tcp", "udp"]
     }
   ],
-  "meter_arrays" : [],
+  "meter_arrays" : [
+    {
+      "name" : "port_meters_ingress.ingress_port_meter",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "include/port_meters.p4",
+        "line" : 23,
+        "column" : 32,
+        "source_fragment" : "ingress_port_meter"
+      },
+      "is_direct" : false,
+      "size" : 511,
+      "rate_count" : 2,
+      "type" : "bytes"
+    },
+    {
+      "name" : "host_meter_control.host_meter",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "include/host_meter_table.p4",
+        "line" : 27,
+        "column" : 43,
+        "source_fragment" : "host_meter"
+      },
+      "is_direct" : true,
+      "rate_count" : 2,
+      "type" : "bytes",
+      "size" : 1024,
+      "binding" : "host_meter_control.host_meter_table",
+      "result_target" : ["scalars", "local_metadata_t.meter_tag"]
+    },
+    {
+      "name" : "port_meters_egress.egress_port_meter",
+      "id" : 2,
+      "source_info" : {
+        "filename" : "include/port_meters.p4",
+        "line" : 37,
+        "column" : 32,
+        "source_fragment" : "egress_port_meter"
+      },
+      "is_direct" : false,
+      "size" : 511,
+      "rate_count" : 2,
+      "type" : "bytes"
+    }
+  ],
   "counter_arrays" : [
     {
       "name" : "port_counters_ingress.ingress_port_counter",
       "id" : 0,
       "source_info" : {
-        "filename" : "./include/port_counters.p4",
+        "filename" : "include/port_counters.p4",
         "line" : 26,
         "column" : 38,
         "source_fragment" : "ingress_port_counter"
@@ -452,7 +514,7 @@
       "name" : "port_counters_egress.egress_port_counter",
       "id" : 3,
       "source_info" : {
-        "filename" : "./include/port_counters.p4",
+        "filename" : "include/port_counters.p4",
         "line" : 36,
         "column" : 38,
         "source_fragment" : "egress_port_counter"
@@ -488,7 +550,7 @@
             }
           ],
           "source_info" : {
-            "filename" : "./include/actions.p4",
+            "filename" : "include/actions.p4",
             "line" : 28,
             "column" : 36,
             "source_fragment" : "port; ..."
@@ -519,7 +581,7 @@
             }
           ],
           "source_info" : {
-            "filename" : "./include/actions.p4",
+            "filename" : "include/actions.p4",
             "line" : 28,
             "column" : 36,
             "source_fragment" : "port; ..."
@@ -545,8 +607,8 @@
             }
           ],
           "source_info" : {
-            "filename" : "./include/headers.p4",
-            "line" : 19,
+            "filename" : "include/defines.p4",
+            "line" : 28,
             "column" : 24,
             "source_fragment" : "255; ..."
           }
@@ -562,7 +624,7 @@
           "op" : "drop",
           "parameters" : [],
           "source_info" : {
-            "filename" : "./include/actions.p4",
+            "filename" : "include/actions.p4",
             "line" : 32,
             "column" : 4,
             "source_fragment" : "mark_to_drop()"
@@ -577,8 +639,14 @@
       "primitives" : []
     },
     {
-      "name" : "table0_control.set_next_hop_id",
+      "name" : "NoAction",
       "id" : 5,
+      "runtime_data" : [],
+      "primitives" : []
+    },
+    {
+      "name" : "table0_control.set_next_hop_id",
+      "id" : 6,
       "runtime_data" : [
         {
           "name" : "next_hop_id",
@@ -599,7 +667,7 @@
             }
           ],
           "source_info" : {
-            "filename" : "./include/table0.p4",
+            "filename" : "include/table0.p4",
             "line" : 30,
             "column" : 8,
             "source_fragment" : "local_metadata.next_hop_id = next_hop_id"
@@ -608,49 +676,31 @@
       ]
     },
     {
+      "name" : "host_meter_control.read_meter",
+      "id" : 7,
+      "runtime_data" : [],
+      "primitives" : []
+    },
+    {
       "name" : "act",
-      "id" : 6,
+      "id" : 8,
       "runtime_data" : [],
       "primitives" : [
         {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["packet_out", "egress_port"]
-            }
-          ],
+          "op" : "drop",
+          "parameters" : [],
           "source_info" : {
-            "filename" : "./include/packet_io.p4",
-            "line" : 27,
+            "filename" : "include/port_meters.p4",
+            "line" : 29,
             "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec = hdr.packet_out.egress_port"
-          }
-        },
-        {
-          "op" : "remove_header",
-          "parameters" : [
-            {
-              "type" : "header",
-              "value" : "packet_out"
-            }
-          ],
-          "source_info" : {
-            "filename" : "./include/packet_io.p4",
-            "line" : 28,
-            "column" : 12,
-            "source_fragment" : "hdr.packet_out.setInvalid()"
+            "source_fragment" : "mark_to_drop()"
           }
         }
       ]
     },
     {
       "name" : "act_0",
-      "id" : 7,
+      "id" : 9,
       "runtime_data" : [],
       "primitives" : [
         {
@@ -692,58 +742,66 @@
             }
           ],
           "source_info" : {
-            "filename" : "./include/port_counters.p4",
+            "filename" : "include/port_counters.p4",
             "line" : 29,
             "column" : 8,
             "source_fragment" : "ingress_port_counter.count((bit<32>) standard_metadata.ingress_port)"
           }
-        }
-      ]
-    },
-    {
-      "name" : "act_1",
-      "id" : 8,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "add_header",
-          "parameters" : [
-            {
-              "type" : "header",
-              "value" : "packet_in"
-            }
-          ],
-          "source_info" : {
-            "filename" : "./include/packet_io.p4",
-            "line" : 38,
-            "column" : 12,
-            "source_fragment" : "hdr.packet_in.setValid()"
-          }
         },
         {
           "op" : "assign",
           "parameters" : [
             {
               "type" : "field",
-              "value" : ["packet_in", "ingress_port"]
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "ingress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "execute_meter",
+          "parameters" : [
+            {
+              "type" : "meter_array",
+              "value" : "port_meters_ingress.ingress_port_meter"
             },
             {
               "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "port_meters_ingress_ingress_color_0"]
             }
           ],
           "source_info" : {
-            "filename" : "./include/packet_io.p4",
-            "line" : 39,
-            "column" : 12,
-            "source_fragment" : "hdr.packet_in.ingress_port = standard_metadata.ingress_port"
+            "filename" : "include/port_meters.p4",
+            "line" : 27,
+            "column" : 8,
+            "source_fragment" : "ingress_port_meter.execute_meter<MeterColor_t>((bit<32>)standard_metadata.ingress_port, ingress_color)"
           }
         }
       ]
     },
     {
-      "name" : "act_2",
-      "id" : 9,
+      "name" : "act_1",
+      "id" : 10,
       "runtime_data" : [],
       "primitives" : [
         {
@@ -751,7 +809,224 @@
           "parameters" : [
             {
               "type" : "field",
-              "value" : ["scalars", "tmp_0"]
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["packet_out", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/packet_io.p4",
+            "line" : 27,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.egress_spec = hdr.packet_out.egress_port"
+          }
+        },
+        {
+          "op" : "remove_header",
+          "parameters" : [
+            {
+              "type" : "header",
+              "value" : "packet_out"
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/packet_io.p4",
+            "line" : 28,
+            "column" : 12,
+            "source_fragment" : "hdr.packet_out.setInvalid()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_2",
+      "id" : 11,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "host_meter_control_tmp_1"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "b2d",
+                  "left" : null,
+                  "right" : {
+                    "type" : "bool",
+                    "value" : true
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "name" : "act_3",
+      "id" : 12,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "host_meter_control_tmp_1"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "b2d",
+                  "left" : null,
+                  "right" : {
+                    "type" : "bool",
+                    "value" : false
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "name" : "act_4",
+      "id" : 13,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "host_meter_control_tmp_2"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "b2d",
+                  "left" : null,
+                  "right" : {
+                    "type" : "bool",
+                    "value" : false
+                  }
+                }
+              }
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/host_meter_table.p4",
+            "line" : 46,
+            "column" : 12,
+            "source_fragment" : "host_meter_table.apply().hit && local_metadata.meter_tag == 2"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_5",
+      "id" : 14,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "host_meter_control_tmp_2"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "b2d",
+                  "left" : null,
+                  "right" : {
+                    "type" : "expression",
+                    "value" : {
+                      "op" : "==",
+                      "left" : {
+                        "type" : "field",
+                        "value" : ["scalars", "local_metadata_t.meter_tag"]
+                      },
+                      "right" : {
+                        "type" : "hexstr",
+                        "value" : "0x00000002"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/host_meter_table.p4",
+            "line" : 46,
+            "column" : 12,
+            "source_fragment" : "host_meter_table.apply().hit && local_metadata.meter_tag == 2"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_6",
+      "id" : 15,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "drop",
+          "parameters" : [],
+          "source_info" : {
+            "filename" : "include/host_meter_table.p4",
+            "line" : 47,
+            "column" : 12,
+            "source_fragment" : "mark_to_drop()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_7",
+      "id" : 16,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "drop",
+          "parameters" : [],
+          "source_info" : {
+            "filename" : "include/port_meters.p4",
+            "line" : 43,
+            "column" : 12,
+            "source_fragment" : "mark_to_drop()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_8",
+      "id" : 17,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_1"]
             },
             {
               "type" : "expression",
@@ -781,15 +1056,105 @@
             },
             {
               "type" : "field",
-              "value" : ["scalars", "tmp_0"]
+              "value" : ["scalars", "tmp_1"]
             }
           ],
           "source_info" : {
-            "filename" : "./include/port_counters.p4",
+            "filename" : "include/port_counters.p4",
             "line" : 39,
             "column" : 8,
             "source_fragment" : "egress_port_counter.count((bit<32>) standard_metadata.egress_port)"
           }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_2"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "egress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "execute_meter",
+          "parameters" : [
+            {
+              "type" : "meter_array",
+              "value" : "port_meters_egress.egress_port_meter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_2"]
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "port_meters_egress_egress_color_0"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/port_meters.p4",
+            "line" : 41,
+            "column" : 8,
+            "source_fragment" : "egress_port_meter.execute_meter<MeterColor_t>((bit<32>)standard_metadata.egress_port, egress_color)"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_9",
+      "id" : 18,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "add_header",
+          "parameters" : [
+            {
+              "type" : "header",
+              "value" : "packet_in"
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/packet_io.p4",
+            "line" : 38,
+            "column" : 12,
+            "source_fragment" : "hdr.packet_in.setValid()"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["packet_in", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/packet_io.p4",
+            "line" : 39,
+            "column" : 12,
+            "source_fragment" : "hdr.packet_in.ingress_port = standard_metadata.ingress_port"
+          }
         }
       ]
     }
@@ -800,7 +1165,7 @@
       "id" : 0,
       "source_info" : {
         "filename" : "basic.p4",
-        "line" : 35,
+        "line" : 37,
         "column" : 8,
         "source_fragment" : "ingress"
       },
@@ -816,14 +1181,14 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [7],
+          "action_ids" : [9],
           "actions" : ["act_0"],
           "base_default_next" : "node_3",
           "next_tables" : {
             "act_0" : "node_3"
           },
           "default_entry" : {
-            "action_id" : 7,
+            "action_id" : 9,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
@@ -839,14 +1204,37 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [6],
+          "action_ids" : [8],
           "actions" : ["act"],
-          "base_default_next" : null,
+          "base_default_next" : "node_5",
           "next_tables" : {
-            "act" : null
+            "act" : "node_5"
           },
           "default_entry" : {
-            "action_id" : 6,
+            "action_id" : 8,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_1",
+          "id" : 2,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [10],
+          "actions" : ["act_1"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_1" : null
+          },
+          "default_entry" : {
+            "action_id" : 10,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
@@ -854,9 +1242,9 @@
         },
         {
           "name" : "table0_control.table0",
-          "id" : 2,
+          "id" : 3,
           "source_info" : {
-            "filename" : "./include/table0.p4",
+            "filename" : "include/table0.p4",
             "line" : 33,
             "column" : 10,
             "source_fragment" : "table0"
@@ -911,16 +1299,17 @@
           "match_type" : "ternary",
           "type" : "simple",
           "max_size" : 1024,
+          "with_counters" : true,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [0, 2, 5, 3],
+          "action_ids" : [0, 2, 6, 3],
           "actions" : ["set_egress_port", "send_to_cpu", "table0_control.set_next_hop_id", "_drop"],
-          "base_default_next" : "node_6",
+          "base_default_next" : "host_meter_control.host_meter_table",
           "next_tables" : {
-            "set_egress_port" : "node_6",
-            "send_to_cpu" : "node_6",
-            "table0_control.set_next_hop_id" : "node_6",
-            "_drop" : "node_6"
+            "set_egress_port" : "host_meter_control.host_meter_table",
+            "send_to_cpu" : "host_meter_control.host_meter_table",
+            "table0_control.set_next_hop_id" : "host_meter_control.host_meter_table",
+            "_drop" : "host_meter_control.host_meter_table"
           },
           "default_entry" : {
             "action_id" : 3,
@@ -930,10 +1319,161 @@
           }
         },
         {
-          "name" : "wcmp_control.wcmp_table",
-          "id" : 3,
+          "name" : "host_meter_control.host_meter_table",
+          "id" : 4,
           "source_info" : {
-            "filename" : "./include/wcmp.p4",
+            "filename" : "include/host_meter_table.p4",
+            "line" : 33,
+            "column" : 10,
+            "source_fragment" : "host_meter_table"
+          },
+          "key" : [
+            {
+              "match_type" : "lpm",
+              "target" : ["ethernet", "src_addr"],
+              "mask" : null
+            }
+          ],
+          "match_type" : "lpm",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : "host_meter_control.host_meter",
+          "action_ids" : [7, 4],
+          "actions" : ["host_meter_control.read_meter", "NoAction"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "__HIT__" : "tbl_act_2",
+            "__MISS__" : "tbl_act_3"
+          },
+          "default_entry" : {
+            "action_id" : 4,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        },
+        {
+          "name" : "tbl_act_2",
+          "id" : 5,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [11],
+          "actions" : ["act_2"],
+          "base_default_next" : "node_11",
+          "next_tables" : {
+            "act_2" : "node_11"
+          },
+          "default_entry" : {
+            "action_id" : 11,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_3",
+          "id" : 6,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [12],
+          "actions" : ["act_3"],
+          "base_default_next" : "node_11",
+          "next_tables" : {
+            "act_3" : "node_11"
+          },
+          "default_entry" : {
+            "action_id" : 12,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_4",
+          "id" : 7,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [13],
+          "actions" : ["act_4"],
+          "base_default_next" : "node_14",
+          "next_tables" : {
+            "act_4" : "node_14"
+          },
+          "default_entry" : {
+            "action_id" : 13,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_5",
+          "id" : 8,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [14],
+          "actions" : ["act_5"],
+          "base_default_next" : "node_14",
+          "next_tables" : {
+            "act_5" : "node_14"
+          },
+          "default_entry" : {
+            "action_id" : 14,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_6",
+          "id" : 9,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [15],
+          "actions" : ["act_6"],
+          "base_default_next" : "node_16",
+          "next_tables" : {
+            "act_6" : "node_16"
+          },
+          "default_entry" : {
+            "action_id" : 15,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "wcmp_control.wcmp_table",
+          "id" : 10,
+          "source_info" : {
+            "filename" : "include/wcmp.p4",
             "line" : 30,
             "column" : 10,
             "source_fragment" : "wcmp_table"
@@ -949,9 +1489,10 @@
           "type" : "indirect_ws",
           "action_profile" : "wcmp_control.wcmp_selector",
           "max_size" : 1024,
+          "with_counters" : true,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [1, 4],
+          "action_ids" : [1, 5],
           "actions" : ["set_egress_port", "NoAction"],
           "base_default_next" : null,
           "next_tables" : {
@@ -997,7 +1538,33 @@
           "name" : "node_3",
           "id" : 0,
           "source_info" : {
-            "filename" : "./include/packet_io.p4",
+            "filename" : "include/port_meters.p4",
+            "line" : 28,
+            "column" : 12,
+            "source_fragment" : "ingress_color == MeterColor_t.RED"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "==",
+              "left" : {
+                "type" : "field",
+                "value" : ["scalars", "port_meters_ingress_ingress_color_0"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00000002"
+              }
+            }
+          },
+          "true_next" : "tbl_act_0",
+          "false_next" : "node_5"
+        },
+        {
+          "name" : "node_5",
+          "id" : 1,
+          "source_info" : {
+            "filename" : "include/packet_io.p4",
             "line" : 26,
             "column" : 12,
             "source_fragment" : "standard_metadata.ingress_port == CPU_PORT"
@@ -1016,14 +1583,55 @@
               }
             }
           },
-          "true_next" : "tbl_act_0",
+          "true_next" : "tbl_act_1",
           "false_next" : "table0_control.table0"
         },
         {
-          "name" : "node_6",
-          "id" : 1,
+          "name" : "node_11",
+          "id" : 2,
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "not",
+              "left" : null,
+              "right" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "d2b",
+                  "left" : null,
+                  "right" : {
+                    "type" : "field",
+                    "value" : ["scalars", "host_meter_control_tmp_1"]
+                  }
+                }
+              }
+            }
+          },
+          "true_next" : "tbl_act_4",
+          "false_next" : "tbl_act_5"
+        },
+        {
+          "name" : "node_14",
+          "id" : 3,
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "d2b",
+              "left" : null,
+              "right" : {
+                "type" : "field",
+                "value" : ["scalars", "host_meter_control_tmp_2"]
+              }
+            }
+          },
+          "true_next" : "tbl_act_6",
+          "false_next" : "node_16"
+        },
+        {
+          "name" : "node_16",
+          "id" : 4,
           "source_info" : {
-            "filename" : "./include/wcmp.p4",
+            "filename" : "include/wcmp.p4",
             "line" : 48,
             "column" : 12,
             "source_fragment" : "local_metadata.next_hop_id != 0"
@@ -1052,15 +1660,15 @@
       "id" : 1,
       "source_info" : {
         "filename" : "basic.p4",
-        "line" : 51,
+        "line" : 55,
         "column" : 8,
         "source_fragment" : "egress"
       },
-      "init_table" : "tbl_act_1",
+      "init_table" : "tbl_act_7",
       "tables" : [
         {
-          "name" : "tbl_act_1",
-          "id" : 4,
+          "name" : "tbl_act_7",
+          "id" : 11,
           "key" : [],
           "match_type" : "exact",
           "type" : "simple",
@@ -1068,22 +1676,22 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [9],
-          "actions" : ["act_2"],
-          "base_default_next" : "node_11",
+          "action_ids" : [17],
+          "actions" : ["act_8"],
+          "base_default_next" : "node_21",
           "next_tables" : {
-            "act_2" : "node_11"
+            "act_8" : "node_21"
           },
           "default_entry" : {
-            "action_id" : 9,
+            "action_id" : 17,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
           }
         },
         {
-          "name" : "tbl_act_2",
-          "id" : 5,
+          "name" : "tbl_act_8",
+          "id" : 12,
           "key" : [],
           "match_type" : "exact",
           "type" : "simple",
@@ -1091,14 +1699,37 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [8],
-          "actions" : ["act_1"],
-          "base_default_next" : null,
+          "action_ids" : [16],
+          "actions" : ["act_7"],
+          "base_default_next" : "node_23",
           "next_tables" : {
-            "act_1" : null
+            "act_7" : "node_23"
           },
           "default_entry" : {
-            "action_id" : 8,
+            "action_id" : 16,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_9",
+          "id" : 13,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [18],
+          "actions" : ["act_9"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_9" : null
+          },
+          "default_entry" : {
+            "action_id" : 18,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
@@ -1108,10 +1739,36 @@
       "action_profiles" : [],
       "conditionals" : [
         {
-          "name" : "node_11",
-          "id" : 2,
+          "name" : "node_21",
+          "id" : 5,
           "source_info" : {
-            "filename" : "./include/packet_io.p4",
+            "filename" : "include/port_meters.p4",
+            "line" : 42,
+            "column" : 12,
+            "source_fragment" : "egress_color == MeterColor_t.RED"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "==",
+              "left" : {
+                "type" : "field",
+                "value" : ["scalars", "port_meters_egress_egress_color_0"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00000002"
+              }
+            }
+          },
+          "true_next" : "tbl_act_8",
+          "false_next" : "node_23"
+        },
+        {
+          "name" : "node_23",
+          "id" : 6,
+          "source_info" : {
+            "filename" : "include/packet_io.p4",
             "line" : 37,
             "column" : 12,
             "source_fragment" : "standard_metadata.egress_port == CPU_PORT"
@@ -1131,7 +1788,7 @@
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_2"
+          "true_next" : "tbl_act_9"
         }
       ]
     }
diff --git a/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.p4info b/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.p4info
index 8010892..9ec66ba 100644
--- a/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.p4info
+++ b/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.p4info
@@ -76,6 +76,27 @@
 }
 tables {
   preamble {
+    id: 33597882
+    name: "host_meter_control.host_meter_table"
+    alias: "host_meter_table"
+  }
+  match_fields {
+    id: 1
+    name: "hdr.ethernet.src_addr"
+    bitwidth: 48
+    match_type: LPM
+  }
+  action_refs {
+    id: 16832719
+  }
+  action_refs {
+    id: 16800567
+  }
+  direct_resource_ids: 318776014
+  size: 1024
+}
+tables {
+  preamble {
     id: 33592597
     name: "wcmp_control.wcmp_table"
     alias: "wcmp_table"
@@ -142,6 +163,13 @@
     bitwidth: 16
   }
 }
+actions {
+  preamble {
+    id: 16832719
+    name: "host_meter_control.read_meter"
+    alias: "read_meter"
+  }
+}
 action_profiles {
   preamble {
     id: 285259294
@@ -196,6 +224,39 @@
   }
   direct_table_id: 33592597
 }
+meters {
+  preamble {
+    id: 318770010
+    name: "port_meters_ingress.ingress_port_meter"
+    alias: "ingress_port_meter"
+  }
+  spec {
+    unit: BYTES
+  }
+  size: 511
+}
+meters {
+  preamble {
+    id: 318779497
+    name: "port_meters_egress.egress_port_meter"
+    alias: "egress_port_meter"
+  }
+  spec {
+    unit: BYTES
+  }
+  size: 511
+}
+direct_meters {
+  preamble {
+    id: 318776014
+    name: "host_meter_control.host_meter"
+    alias: "host_meter"
+  }
+  spec {
+    unit: BYTES
+  }
+  direct_table_id: 33597882
+}
 controller_packet_metadata {
   preamble {
     id: 2868941301
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
index 7ecbd80..91254cf 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
@@ -23,6 +23,9 @@
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
+import org.onosproject.net.pi.model.PiMeterId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.model.PiTableId;
@@ -152,6 +155,36 @@
                                                             PiPipeconf pipeconf);
 
     /**
+     * Returns the configuration of all meter cells for the given set of meter identifiers and pipeconf.
+     *
+     * @param meterIds   meter identifiers
+     * @param pipeconf   pipeconf
+     * @return collection of meter configurations
+     */
+    CompletableFuture<Collection<PiMeterCellConfig>> readAllMeterCells(Set<PiMeterId> meterIds,
+                                                                       PiPipeconf pipeconf);
+
+    /**
+     * Returns a collection of meter configurations corresponding to the given set of meter cell identifiers,
+     * for the given pipeconf.
+     *
+     * @param cellIds    set of meter cell identifiers
+     * @param pipeconf   pipeconf
+     * @return collection of meter configrations
+     */
+    CompletableFuture<Collection<PiMeterCellConfig>> readMeterCells(Set<PiMeterCellId> cellIds,
+                                                                    PiPipeconf pipeconf);
+
+    /**
+     * Performs a write operation for the given meter configurations and pipeconf.
+     *
+     * @param cellConfigs  meter cell configurations
+     * @param pipeconf     pipeconf currently deployed on the device
+     * @return true if the operation was successful, false otherwise.
+     */
+    CompletableFuture<Boolean> writeMeterCells(Collection<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf);
+
+    /**
      * Shutdown the client by terminating any active RPC such as the stream channel.
      */
     void shutdown();
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java
new file mode 100644
index 0000000..c30e2c8
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java
@@ -0,0 +1,242 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiMeterBand;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.slf4j.Logger;
+import p4.P4RuntimeOuterClass.MeterConfig;
+import p4.P4RuntimeOuterClass.MeterEntry;
+import p4.P4RuntimeOuterClass.DirectMeterEntry;
+import p4.P4RuntimeOuterClass.Entity;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.slf4j.LoggerFactory.getLogger;
+import static p4.P4RuntimeOuterClass.Entity.EntityCase.*;
+
+/**
+ * Encoder/decoder of PI meter cell configurations to meter entry protobuf messages, and vice versa.
+ */
+final class MeterEntryCodec {
+
+    private static final Logger log = getLogger(MeterEntryCodec.class);
+
+    private MeterEntryCodec() {
+        // Hides constructor.
+    }
+
+    /**
+     * Returns a collection of P4Runtime entity protobuf messages describing both meter or direct meter entries,
+     * encoded from the given collection of PI meter cell configurations, for the given pipeconf. If a PI meter cell
+     * configurations cannot be encoded, it is skipped, hence the returned collection might have different size than the
+     * input one.
+     * <p>
+     * This method takes as parameter also a map between numeric P4Info IDs and PI meter IDs, that will be populated
+     * during the process and that is then needed to aid in the decode process.
+     *
+     * @param cellConfigs  meter cell configurations
+     * @param meterIdMap   meter ID map (empty, it will be populated during this method execution)
+     * @param pipeconf     pipeconf
+     * @return collection of entity messages describing both meter or direct meter entries
+     */
+    static Collection<Entity> encodePiMeterCellConfigs(Collection<PiMeterCellConfig> cellConfigs,
+                                                       Map<Integer, PiMeterId> meterIdMap,
+                                                       PiPipeconf pipeconf) {
+        return cellConfigs
+                .stream()
+                .map(cellConfig -> {
+                    try {
+                        return encodePiMeterCellConfig(cellConfig, meterIdMap, pipeconf);
+                    } catch (P4InfoBrowser.NotFoundException | EncodeException e) {
+                        log.warn("Unable to encode PI meter cell id: {}", e.getMessage());
+                        log.debug("exception", e);
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns a collection of PI meter cell configurations, decoded from the given P4Runtime entity protobuf messages
+     * describing both meter or direct meter entries, for the given meter ID map (populated by {@link
+     * #encodePiMeterCellConfigs(Collection, Map, PiPipeconf)}), and pipeconf. If an entity message cannot be encoded,
+     * it is skipped, hence the returned collection might have different size than the input one.
+     *
+     * @param entities     P4Runtime entity messages
+     * @param meterIdMap   meter ID map (previously populated)
+     * @param pipeconf     pipeconf
+     * @return collection of PI meter cell data
+     */
+    static Collection<PiMeterCellConfig> decodeMeterEntities(Collection<Entity> entities,
+                                                               Map<Integer, PiMeterId> meterIdMap,
+                                                               PiPipeconf pipeconf) {
+        return entities
+                .stream()
+                .filter(entity -> entity.getEntityCase() == METER_ENTRY ||
+                        entity.getEntityCase() == DIRECT_METER_ENTRY)
+                .map(entity -> {
+                    try {
+                        return decodeMeterEntity(entity, meterIdMap, pipeconf);
+                    } catch (EncodeException | P4InfoBrowser.NotFoundException e) {
+                        log.warn("Unable to decode meter entity message: {}", e.getMessage());
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    private static Entity encodePiMeterCellConfig(PiMeterCellConfig config, Map<Integer, PiMeterId> meterIdMap,
+                                                  PiPipeconf pipeconf)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        int meterId;
+        Entity entity;
+        //The band with bigger burst is peak if rate of them is equal,
+        //if bands are not specificed, using default value(0).
+        long cir = 0;
+        long cburst = 0;
+        long pir = 0;
+        long pburst = 0;
+        PiMeterBand[] bands = config.meterBands().toArray(new PiMeterBand[config.meterBands().size()]);
+        if (bands.length == 2) {
+            if (bands[0].rate() > bands[1].rate()) {
+                cir = bands[1].rate();
+                cburst = bands[1].burst();
+                pir = bands[0].rate();
+                pburst = bands[0].burst();
+            } else {
+                cir = bands[0].rate();
+                cburst = bands[0].burst();
+                pir = bands[1].rate();
+                pburst = bands[1].burst();
+            }
+        }
+
+        // Encode PI cell ID into entity message and add to read request.
+        switch (config.cellId().meterType()) {
+            case INDIRECT:
+                meterId = browser.meters().getByName(config.cellId().meterId().id()).getPreamble().getId();
+                entity = Entity.newBuilder().setMeterEntry(MeterEntry
+                                                                   .newBuilder().setMeterId(meterId)
+                                                                   .setIndex(config.cellId().index())
+                                                                   .setConfig(MeterConfig.newBuilder()
+                                                                                      .setCir(cir)
+                                                                                      .setCburst(cburst)
+                                                                                      .setPir(pir)
+                                                                                      .setPburst(pburst)
+                                                                                      .build())
+                                                                   .build())
+                        .build();
+                break;
+            case DIRECT:
+                meterId = browser.directMeters().getByName(config.cellId().meterId().id()).getPreamble().getId();
+                DirectMeterEntry.Builder entryBuilder = DirectMeterEntry.newBuilder()
+                        .setMeterId(meterId)
+                        .setConfig(MeterConfig.newBuilder()
+                                           .setCir(cir)
+                                           .setCburst(cburst)
+                                           .setPir(pir)
+                                           .setPburst(pburst)
+                                           .build());
+
+                if (!config.cellId().tableEntry().equals(PiTableEntry.EMTPY)) {
+                    entryBuilder.setTableEntry(TableEntryEncoder.encode(config.cellId().tableEntry(), pipeconf));
+                }
+                entity = Entity.newBuilder().setDirectMeterEntry(entryBuilder.build()).build();
+                break;
+            default:
+                throw new EncodeException(format("Unrecognized PI meter cell ID type '%s'",
+                                                 config.cellId().meterType()));
+        }
+        meterIdMap.put(meterId, config.cellId().meterId());
+
+        return entity;
+    }
+
+    private static PiMeterCellConfig decodeMeterEntity(Entity entity, Map<Integer, PiMeterId> meterIdMap,
+                                                         PiPipeconf pipeconf)
+            throws EncodeException, P4InfoBrowser.NotFoundException {
+
+        int meterId;
+        MeterConfig meterConfig;
+
+        if (entity.getEntityCase() == METER_ENTRY) {
+            meterId = entity.getMeterEntry().getMeterId();
+            meterConfig = entity.getMeterEntry().getConfig();
+        } else {
+            meterId = entity.getDirectMeterEntry().getMeterId();
+            meterConfig = entity.getDirectMeterEntry().getConfig();
+        }
+
+        // Process only meter IDs that were requested in the first place.
+        if (!meterIdMap.containsKey(meterId)) {
+            throw new EncodeException(format("Unrecognized meter ID '%s'", meterId));
+        }
+
+        PiMeterId piMeterId = meterIdMap.get(meterId);
+        if (!pipeconf.pipelineModel().meter(piMeterId).isPresent()) {
+            throw new EncodeException(format("Unable to find meter '{}' in pipeline model",  meterId));
+        }
+
+        PiMeterType piMeterType = pipeconf.pipelineModel().meter(piMeterId).get().meterType();
+        // Compute PI cell ID.
+        PiMeterCellId piCellId;
+
+        switch (piMeterType) {
+            case INDIRECT:
+                if (entity.getEntityCase() != METER_ENTRY) {
+                    throw new EncodeException(format(
+                            "Meter ID '%s' is indirect, but processed entity is %s",
+                            piMeterId, entity.getEntityCase()));
+                }
+                piCellId = PiMeterCellId.ofIndirect(piMeterId, entity.getMeterEntry().getIndex());
+                break;
+            case DIRECT:
+                if (entity.getEntityCase() != DIRECT_METER_ENTRY) {
+                    throw new EncodeException(format(
+                            "Meter ID '%s' is direct, but processed entity is %s",
+                            piMeterId, entity.getEntityCase()));
+                }
+                PiTableEntry piTableEntry = TableEntryEncoder.decode(entity.getDirectMeterEntry().getTableEntry(),
+                                                                     pipeconf);
+                piCellId = PiMeterCellId.ofDirect(piMeterId, piTableEntry);
+                break;
+            default:
+                throw new EncodeException(format("Unrecognized PI meter ID type '%s'", piMeterType));
+        }
+
+        PiMeterCellConfig.Builder builder = PiMeterCellConfig.builder();
+        builder.withMeterBand(new PiMeterBand(meterConfig.getCir(), meterConfig.getCburst()));
+        builder.withMeterBand(new PiMeterBand(meterConfig.getPir(), meterConfig.getPburst()));
+
+        return builder.withMeterCellId(piCellId).build();
+    }
+}
\ No newline at end of file
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index ac6d9f0..609c5c5 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -44,6 +44,10 @@
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiEntity;
+import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMeterCellId;
+import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiMeterId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.service.PiPipeconfService;
@@ -127,6 +131,8 @@
     private Map<Uint128, CompletableFuture<Boolean>> arbitrationUpdateMap = Maps.newConcurrentMap();
     protected Uint128 p4RuntimeElectionId;
 
+    private static final long DEFAULT_INDEX = 0;
+
     /**
      * Default constructor.
      *
@@ -275,6 +281,51 @@
     public CompletableFuture<Boolean> sendMasterArbitrationUpdate() {
         return supplyInContext(this::doArbitrationUpdate, "arbitrationUpdate");
     }
+    public CompletableFuture<Boolean> writeMeterCells(Collection<PiMeterCellConfig> cellIds, PiPipeconf pipeconf) {
+
+        return supplyInContext(() -> doWriteMeterCells(cellIds, pipeconf),
+                               "writeMeterCells");
+    }
+
+    @Override
+    public CompletableFuture<Collection<PiMeterCellConfig>> readMeterCells(Set<PiMeterCellId> cellIds,
+                                                                           PiPipeconf pipeconf) {
+        return supplyInContext(() -> doReadMeterCells(cellIds, pipeconf),
+                               "readMeterCells-" + cellIds.hashCode());
+    }
+
+    @Override
+    public CompletableFuture<Collection<PiMeterCellConfig>> readAllMeterCells(Set<PiMeterId> meterIds,
+                                                                                PiPipeconf pipeconf) {
+
+        /*
+        From p4runtime.proto, the scope of a ReadRequest is defined as follows:
+        MeterEntry:
+            - All meter cells for all meters if meter_id = 0 (default).
+            - All meter cells for given meter_id if index = 0 (default).
+        DirectCounterEntry:
+            - All meter cells for all meters if meter_id = 0 (default).
+            - All meter cells for given meter_id if table_entry.match is empty.
+         */
+
+        Set<PiMeterCellId> cellIds = Sets.newHashSet();
+        for (PiMeterId meterId : meterIds) {
+            PiMeterType meterType = pipeconf.pipelineModel().meter(meterId).get().meterType();
+            switch (meterType) {
+                case INDIRECT:
+                    cellIds.add(PiMeterCellId.ofIndirect(meterId, DEFAULT_INDEX));
+                    break;
+                case DIRECT:
+                    cellIds.add(PiMeterCellId.ofDirect(meterId, PiTableEntry.EMTPY));
+                    break;
+                default:
+                    log.warn("Unrecognized PI meter type '{}'", meterType);
+            }
+        }
+
+        return supplyInContext(() -> doReadMeterCells(cellIds, pipeconf),
+                               "readAllMeterCells-" + cellIds.hashCode());
+    }
 
     /* Blocking method implementations below */
 
@@ -738,6 +789,74 @@
         }
     }
 
+    private Collection<PiMeterCellConfig> doReadMeterCells(Collection<PiMeterCellId> cellIds, PiPipeconf pipeconf) {
+
+        // We use this map to remember the original PI meter IDs of the returned response.
+        Map<Integer, PiMeterId> meterIdMap = Maps.newHashMap();
+        Collection<PiMeterCellConfig> piMeterCellConfigs = cellIds.stream()
+                .map(cellId -> PiMeterCellConfig.builder()
+                        .withMeterCellId(cellId).build())
+                .collect(Collectors.toList());
+
+        final ReadRequest request = ReadRequest.newBuilder()
+                .setDeviceId(p4DeviceId)
+                .addAllEntities(MeterEntryCodec.encodePiMeterCellConfigs(piMeterCellConfigs, meterIdMap, pipeconf))
+                .build();
+
+        if (request.getEntitiesList().size() == 0) {
+            return Collections.emptyList();
+        }
+
+        final Iterable<ReadResponse> responses;
+        try {
+            responses = () -> blockingStub.read(request);
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to read meters config: {}", e.getMessage());
+            log.debug("exception", e);
+            return Collections.emptyList();
+        }
+
+        List<Entity> entities = StreamSupport.stream(responses.spliterator(), false)
+                .map(ReadResponse::getEntitiesList)
+                .flatMap(List::stream)
+                .collect(Collectors.toList());
+
+        return MeterEntryCodec.decodeMeterEntities(entities, meterIdMap, pipeconf);
+    }
+
+    private boolean doWriteMeterCells(Collection<PiMeterCellConfig> cellIds, PiPipeconf pipeconf) {
+
+        final Map<Integer, PiMeterId> meterIdMap = Maps.newHashMap();
+        WriteRequest.Builder writeRequestBuilder = WriteRequest.newBuilder();
+
+        Collection<Update> updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellIds, meterIdMap, pipeconf)
+                .stream()
+                .map(meterEntryMsg ->
+                             Update.newBuilder()
+                                     .setEntity(meterEntryMsg)
+                                     .setType(UPDATE_TYPES.get(WriteOperationType.MODIFY))
+                                     .build())
+                .collect(Collectors.toList());
+
+        if (updateMsgs.size() == 0) {
+            return true;
+        }
+
+        writeRequestBuilder
+                .setDeviceId(p4DeviceId)
+                .setElectionId(p4RuntimeElectionId)
+                .addAllUpdates(updateMsgs)
+                .build();
+        try {
+            blockingStub.write(writeRequestBuilder.build());
+            return true;
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to write meter entries : {}", e.getMessage());
+            log.debug("exception", e);
+            return false;
+        }
+    }
+
     /**
      * Returns the internal P4 device ID associated with this client.
      *
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/MetersResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/MetersResourceTest.java
index 235ca0f..65b932d 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/MetersResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/MetersResourceTest.java
@@ -42,6 +42,7 @@
 import org.onosproject.net.meter.Band;
 import org.onosproject.net.meter.DefaultBand;
 import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterCellId;
 import org.onosproject.net.meter.MeterId;
 import org.onosproject.net.meter.MeterService;
 import org.onosproject.net.meter.MeterState;
@@ -138,6 +139,11 @@
         }
 
         @Override
+        public MeterCellId meterCellId() {
+            return this.id();
+        }
+
+        @Override
         public ApplicationId appId() {
             return this.appId;
         }