[ovs-dev,v3] ovn: OVN Support QoS meter

Message ID 20171113122329.3504-1-ligs@dtdream.com
State New
Headers show
Series
  • [ovs-dev,v3] ovn: OVN Support QoS meter
Related show

Commit Message

Guoshuai Li Nov. 13, 2017, 12:23 p.m.
This feature is used to limit the bandwidth of flows, such as floating IP.

ovn-northd changes:
1. add bandwidth column in NB's QOS table.
2. add QOS_METER stages in Logical switch ingress/egress.
3. add set_meter() action in SB's LFlow table.

ovn-controller changes:
add meter_table for meter action process openflow meter table.

Now, This feature is only supported in DPDK.

Signed-off-by: Guoshuai Li <ligs@dtdream.com>
---

v3: Fix bandwidth mix error.
    rebasing.
v2: Fix Ingress/Egress Table id error.

---
 NEWS                            |   1 +
 include/ovn/actions.h           |  31 +++++++-
 ovn/controller/lflow.c          |   9 ++-
 ovn/controller/lflow.h          |   2 +
 ovn/controller/ofctrl.c         | 158 +++++++++++++++++++++++++++++++++++++++-
 ovn/controller/ofctrl.h         |   6 +-
 ovn/controller/ovn-controller.c |  25 ++++++-
 ovn/lib/actions.c               | 114 +++++++++++++++++++++++++++++
 ovn/northd/ovn-northd.8.xml     |  54 ++++++++++----
 ovn/northd/ovn-northd.c         | 108 +++++++++++++++++----------
 ovn/ovn-nb.ovsschema            |  12 ++-
 ovn/ovn-nb.xml                  |  16 ++++
 ovn/ovn-sb.xml                  |  15 ++++
 ovn/utilities/ovn-trace.c       |   4 +
 tests/ovn.at                    |   7 +-
 tests/test-ovn.c                |   8 ++
 16 files changed, 504 insertions(+), 66 deletions(-)

Patch

diff --git a/NEWS b/NEWS
index a93237f90..1482ee4c2 100644
--- a/NEWS
+++ b/NEWS
@@ -68,6 +68,7 @@  v2.8.0 - 31 Aug 2017
        gateway.
      * Add support for ACL logging.
      * ovn-northd now has native support for active-standby high availability.
+     * Add support for QoS bandwidth limt with DPDK.
    - Tracing with ofproto/trace now traces through recirculation.
    - OVSDB:
      * New support for role-based access control (see ovsdb-server(1)).
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 85a484ffa..f4a90bb52 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -74,7 +74,8 @@  struct simap;
     OVNACT(DNS_LOOKUP,        ovnact_dns_lookup)      \
     OVNACT(LOG,               ovnact_log)             \
     OVNACT(PUT_ND_RA_OPTS,    ovnact_put_opts)        \
-    OVNACT(ND_NS,             ovnact_nest)
+    OVNACT(ND_NS,             ovnact_nest)            \
+    OVNACT(SET_METER,         ovnact_set_meter)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -280,6 +281,13 @@  struct ovnact_log {
     char *name;
 };
 
+/* OVNACT_SET_METER. */
+struct ovnact_set_meter {
+    struct ovnact ovnact;
+    uint32_t rate;              /* 32-bit rate field. */
+    uint32_t burst;             /* 32-bit burst rate field. */
+};
+
 /* Internal use by the helpers below. */
 void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
 void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
@@ -356,6 +364,24 @@  struct group_info {
                          * group_table's 'group_ids' bitmap. */
 };
 
+#define MAX_OVN_METERS 65535
+
+struct meter_table {
+    unsigned long *meter_ids;  /* Used as a bitmap with value set
+                                * for allocated meter ids in either
+                                * desired_meters or existing_meters. */
+    struct hmap desired_meters;
+    struct hmap existing_meters;
+};
+
+struct meter_info {
+    struct hmap_node hmap_node;
+    struct ds meter;
+    uint32_t meter_id;
+    bool new_meter_id;  /* 'True' if 'meter_id' was reserved from
+                         * meter_table's 'meter_ids' bitmap. */
+};
+
 enum action_opcode {
     /* "arp { ...actions... }".
      *
@@ -507,6 +533,9 @@  struct ovnact_encode_params {
     /* A struct to figure out the group_id for group actions. */
     struct group_table *group_table;
 
+    /* A struct to figure out the meter_id for meter actions. */
+    struct meter_table *meter_table;
+
     /* OVN maps each logical flow table (ltable), one-to-one, onto a physical
      * OpenFlow flow table (ptable).  A number of parameters describe this
      * mapping and data related to flow tables:
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index a62ec6ebe..614df0638 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -62,6 +62,7 @@  static void consider_logical_flow(struct controller_ctx *ctx,
                                   const struct sbrec_logical_flow *lflow,
                                   const struct hmap *local_datapaths,
                                   struct group_table *group_table,
+                                  struct meter_table *meter_table,
                                   const struct sbrec_chassis *chassis,
                                   struct hmap *dhcp_opts,
                                   struct hmap *dhcpv6_opts,
@@ -144,6 +145,7 @@  add_logical_flows(struct controller_ctx *ctx,
                   const struct chassis_index *chassis_index,
                   const struct hmap *local_datapaths,
                   struct group_table *group_table,
+                  struct meter_table *meter_table,
                   const struct sbrec_chassis *chassis,
                   const struct shash *addr_sets,
                   struct hmap *flow_table,
@@ -174,7 +176,7 @@  add_logical_flows(struct controller_ctx *ctx,
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
         consider_logical_flow(ctx, chassis_index,
                               lflow, local_datapaths,
-                              group_table, chassis,
+                              group_table, meter_table, chassis,
                               &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
                               &conj_id_ofs, addr_sets, flow_table,
                               active_tunnels, local_lport_ids);
@@ -191,6 +193,7 @@  consider_logical_flow(struct controller_ctx *ctx,
                       const struct sbrec_logical_flow *lflow,
                       const struct hmap *local_datapaths,
                       struct group_table *group_table,
+                      struct meter_table *meter_table,
                       const struct sbrec_chassis *chassis,
                       struct hmap *dhcp_opts,
                       struct hmap *dhcpv6_opts,
@@ -263,6 +266,7 @@  consider_logical_flow(struct controller_ctx *ctx,
         .is_switch = is_switch(ldp),
         .is_gateway_router = is_gateway_router(ldp, local_datapaths),
         .group_table = group_table,
+        .meter_table = meter_table,
 
         .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
         .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE,
@@ -435,13 +439,14 @@  lflow_run(struct controller_ctx *ctx,
           const struct chassis_index *chassis_index,
           const struct hmap *local_datapaths,
           struct group_table *group_table,
+          struct meter_table *meter_table,
           const struct shash *addr_sets,
           struct hmap *flow_table,
           struct sset *active_tunnels,
           struct sset *local_lport_ids)
 {
     add_logical_flows(ctx, chassis_index, local_datapaths,
-                      group_table, chassis, addr_sets, flow_table,
+                      group_table, meter_table, chassis, addr_sets, flow_table,
                       active_tunnels, local_lport_ids);
     add_neighbor_flows(ctx, flow_table);
 }
diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h
index bfb7415e2..ad8d0a4c7 100644
--- a/ovn/controller/lflow.h
+++ b/ovn/controller/lflow.h
@@ -38,6 +38,7 @@ 
 struct chassis_index;
 struct controller_ctx;
 struct group_table;
+struct meter_table;
 struct hmap;
 struct sbrec_chassis;
 struct simap;
@@ -67,6 +68,7 @@  void lflow_run(struct controller_ctx *,
                const struct chassis_index *,
                const struct hmap *local_datapaths,
                struct group_table *group_table,
+               struct meter_table *meter_table,
                const struct shash *addr_sets,
                struct hmap *flow_table,
                struct sset *active_tunnels,
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
index 90afddbb3..9936db5a4 100644
--- a/ovn/controller/ofctrl.c
+++ b/ovn/controller/ofctrl.c
@@ -133,6 +133,9 @@  static struct hmap installed_flows;
 /* A reference to the group_table. */
 static struct group_table *groups;
 
+/* A reference to the meter_table. */
+static struct meter_table *meters;
+
 /* MFF_* field ID for our Geneve option.  In S_TLV_TABLE_MOD_SENT, this is
  * the option we requested (we don't know whether we obtained it yet).  In
  * S_CLEAR_FLOWS or S_UPDATE_FLOWS, this is really the option we have. */
@@ -144,13 +147,15 @@  static struct ofpbuf *encode_flow_mod(struct ofputil_flow_mod *);
 
 static struct ofpbuf *encode_group_mod(const struct ofputil_group_mod *);
 
+static struct ofpbuf *encode_meter_mod(const struct ofputil_meter_mod *);
+
 static void ovn_flow_table_clear(struct hmap *flow_table);
 static void ovn_flow_table_destroy(struct hmap *flow_table);
 
 static void ofctrl_recv(const struct ofp_header *, enum ofptype);
 
 void
-ofctrl_init(struct group_table *group_table)
+ofctrl_init(struct group_table *group_table, struct meter_table *meter_table)
 {
     swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
     tx_counter = rconn_packet_counter_create();
@@ -158,6 +163,7 @@  ofctrl_init(struct group_table *group_table)
     ovs_list_init(&flow_updates);
     ovn_init_symtab(&symtab);
     groups = group_table;
+    meters = meter_table;
 }
 
 /* S_NEW, for a new connection.
@@ -388,6 +394,18 @@  run_S_CLEAR_FLOWS(void)
         ovn_group_table_clear(groups, true);
     }
 
+    /* Send a meter_mod to delete all meters. */
+    struct ofputil_meter_mod mm;
+    memset(&mm, 0, sizeof mm);
+    mm.command = OFPMC13_DELETE;
+    mm.meter.meter_id = OFPM13_ALL;
+    queue_msg(encode_meter_mod(&mm));
+
+    /* Clear existing meters, to match the state of the switch. */
+    if (meters) {
+        ovn_meter_table_clear(meters, true);
+    }
+
     /* All flow updates are irrelevant now. */
     struct ofctrl_flow_update *fup, *next;
     LIST_FOR_EACH_SAFE (fup, next, list_node, &flow_updates) {
@@ -797,7 +815,60 @@  add_group_mod(const struct ofputil_group_mod *gm, struct ovs_list *msgs)
     struct ofpbuf *msg = encode_group_mod(gm);
     ovs_list_push_back(msgs, &msg->list_node);
 }
-
+
+/* meter_table. */
+
+/* Finds and returns a meter_info in 'existing_meters' whose key is identical
+ * to 'target''s key, or NULL if there is none. */
+static struct meter_info *
+ovn_meter_lookup(struct hmap *exisiting_meters,
+                 const struct meter_info *target)
+{
+    struct meter_info *e;
+
+    HMAP_FOR_EACH_WITH_HASH(e, hmap_node, target->hmap_node.hash,
+                            exisiting_meters) {
+        if (e->meter_id == target->meter_id) {
+            return e;
+        }
+   }
+    return NULL;
+}
+
+/* Clear either desired_meters or existing_meters in meter_table. */
+void
+ovn_meter_table_clear(struct meter_table *meter_table, bool existing)
+{
+    struct meter_info *m, *next;
+    struct hmap *target_meter = existing
+                                ? &meter_table->existing_meters
+                                : &meter_table->desired_meters;
+
+    HMAP_FOR_EACH_SAFE (m, next, hmap_node, target_meter) {
+        hmap_remove(target_meter, &m->hmap_node);
+        /* Don't unset bitmap for desired meter_info if the meter_id
+         * was not freshly reserved. */
+        if (existing || m->new_meter_id) {
+            bitmap_set0(meter_table->meter_ids, m->meter_id);
+        }
+        ds_destroy(&m->meter);
+        free(m);
+    }
+}
+
+static struct ofpbuf *
+encode_meter_mod(const struct ofputil_meter_mod *mm)
+{
+    return ofputil_encode_meter_mod(OFP13_VERSION, mm);
+}
+
+static void
+add_meter_mod(const struct ofputil_meter_mod *mm, struct ovs_list *msgs)
+{
+    struct ofpbuf *msg = encode_meter_mod(mm);
+    ovs_list_push_back(msgs, &msg->list_node);
+}
+
 static void
 add_ct_flush_zone(uint16_t zone_id, struct ovs_list *msgs)
 {
@@ -833,6 +904,12 @@  ofctrl_can_put(void)
  * 'groups->desired_groups' and frees them. (The hmap itself isn't
  * destroyed.)
  *
+ * Replaces the meter table on the switch, if possible, by the contents of
+ * 'meters->desired_meters'.  Regardless of whether the meter table
+ * is updated, this deletes all the meters from the
+ * 'meters->desired_meters' and frees them. (The hmap itself isn't
+ * destroyed.)
+ *
  * Sends conntrack flush messages to each zone in 'pending_ct_zones' that
  * is in the CT_ZONE_OF_QUEUED state and then moves the zone into the
  * CT_ZONE_OF_SENT state.
@@ -891,6 +968,35 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
         }
     }
 
+    /* Iterate through all the desired meters. If there are new ones,
+     * add them to the switch. */
+    struct meter_info *desired_meter;
+    HMAP_FOR_EACH(desired_meter, hmap_node, &meters->desired_meters) {
+        if (!ovn_meter_lookup(&meters->existing_meters, desired_meter)
+            && desired_meter->meter_id) {
+            /* Create and install new meter. */
+            struct ofputil_meter_mod mm;
+            enum ofputil_protocol usable_protocols;
+            char *error;
+            struct ds meter_string = DS_EMPTY_INITIALIZER;
+            ds_put_format(&meter_string, "meter=%u,%s",
+                          desired_meter->meter_id,
+                          ds_cstr(&desired_meter->meter));
+
+            error = parse_ofp_meter_mod_str(&mm, ds_cstr(&meter_string),
+                                            OFPMC13_ADD, &usable_protocols);
+            if (!error) {
+                add_meter_mod(&mm, &msgs);
+            } else {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_ERR_RL(&rl, "new meter %s %s", error,
+                         ds_cstr(&meter_string));
+                free(error);
+            }
+            ds_destroy(&meter_string);
+        }
+    }
+
     /* Iterate through all of the installed flows.  If any of them are no
      * longer desired, delete them; if any of them should have different
      * actions, update them. */
@@ -1012,6 +1118,54 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
         }
     }
 
+    /* Iterate through the installed meters from previous runs. If they
+     * are not needed delete them. */
+    struct meter_info *installed_meter, *next_meter;
+    HMAP_FOR_EACH_SAFE(installed_meter, next_meter, hmap_node,
+                       &meters->existing_meters) {
+        if (!ovn_meter_lookup(&meters->desired_meters, installed_meter)) {
+            /* Delete the meter. */
+            struct ofputil_meter_mod mm;
+            enum ofputil_protocol usable_protocols;
+            char *error;
+            struct ds meter_string = DS_EMPTY_INITIALIZER;
+            ds_put_format(&meter_string, "meter=%u", installed_meter->meter_id);
+
+            error = parse_ofp_meter_mod_str(&mm, ds_cstr(&meter_string),
+                                            OFPMC13_DELETE, &usable_protocols);
+            if (!error) {
+                add_meter_mod(&mm, &msgs);
+            } else {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_ERR_RL(&rl, "Error deleting meter %d: %s",
+                         installed_meter->meter_id, error);
+                free(error);
+            }
+            ds_destroy(&meter_string);
+
+            /* Remove 'installed_meter' from 'meters->existing_meters' */
+            hmap_remove(&meters->existing_meters, &installed_meter->hmap_node);
+            ds_destroy(&installed_meter->meter);
+
+            /* Dealloc meter_id. */
+            bitmap_set0(meters->meter_ids, installed_meter->meter_id);
+            free(installed_meter);
+        }
+    }
+
+    /* Move the contents of desired_meters to existing_meters. */
+    HMAP_FOR_EACH_SAFE(desired_meter, next_meter, hmap_node,
+                       &meters->desired_meters) {
+        hmap_remove(&meters->desired_meters, &desired_meter->hmap_node);
+        if (!ovn_meter_lookup(&meters->existing_meters, desired_meter)) {
+            hmap_insert(&meters->existing_meters, &desired_meter->hmap_node,
+                        desired_meter->hmap_node.hash);
+        } else {
+           ds_destroy(&desired_meter->meter);
+           free(desired_meter);
+        }
+    }
+
     if (!ovs_list_is_empty(&msgs)) {
         /* Add a barrier to the list of messages. */
         struct ofpbuf *barrier = ofputil_encode_barrier_request(OFP13_VERSION);
diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
index d83f6aec4..e680e2d61 100644
--- a/ovn/controller/ofctrl.h
+++ b/ovn/controller/ofctrl.h
@@ -24,6 +24,7 @@ 
 
 struct controller_ctx;
 struct group_table;
+struct meter_table;
 struct hmap;
 struct match;
 struct ofpbuf;
@@ -31,7 +32,7 @@  struct ovsrec_bridge;
 struct shash;
 
 /* Interface for OVN main loop. */
-void ofctrl_init(struct group_table *group_table);
+void ofctrl_init(struct group_table *group_table, struct meter_table *meter_table);
 enum mf_field_id ofctrl_run(const struct ovsrec_bridge *br_int,
                             struct shash *pending_ct_zones);
 bool ofctrl_can_put(void);
@@ -58,4 +59,7 @@  void ofctrl_flow_table_clear(void);
 void ovn_group_table_clear(struct group_table *group_table,
                            bool existing);
 
+void ovn_meter_table_clear(struct meter_table *meter_table,
+                           bool existing);
+
 #endif /* ovn/ofctrl.h */
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index c286ccbca..9d0f82898 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -599,9 +599,16 @@  main(int argc, char *argv[])
     hmap_init(&group_table.desired_groups);
     hmap_init(&group_table.existing_groups);
 
+    /* Initialize meter ids for QoS. */
+    struct meter_table meter_table;
+    meter_table.meter_ids = bitmap_allocate(MAX_OVN_METERS);
+    bitmap_set1(meter_table.meter_ids, 0); /* Meter id 0 is invalid. */
+    hmap_init(&meter_table.desired_meters);
+    hmap_init(&meter_table.existing_meters);
+
     daemonize_complete();
 
-    ofctrl_init(&group_table);
+    ofctrl_init(&group_table, &meter_table);
     pinctrl_init();
     lflow_init();
 
@@ -711,8 +718,8 @@  main(int argc, char *argv[])
                     struct hmap flow_table = HMAP_INITIALIZER(&flow_table);
                     lflow_run(&ctx, chassis,
                               &chassis_index, &local_datapaths, &group_table,
-                              &addr_sets, &flow_table, &active_tunnels,
-                              &local_lport_ids);
+                              &meter_table, &addr_sets, &flow_table,
+                              &active_tunnels, &local_lport_ids);
 
                     if (chassis_id) {
                         bfd_run(&ctx, br_int, chassis, &local_datapaths,
@@ -857,6 +864,18 @@  main(int argc, char *argv[])
     }
     hmap_destroy(&group_table.existing_groups);
 
+    bitmap_free(meter_table.meter_ids);
+    hmap_destroy(&meter_table.desired_meters);
+
+    struct meter_info *installed_meter, *next_meter;
+    HMAP_FOR_EACH_SAFE(installed_meter, next_meter, hmap_node,
+                       &meter_table.existing_meters) {
+        hmap_remove(&meter_table.existing_meters, &installed_meter->hmap_node);
+        ds_destroy(&installed_meter->meter);
+        free(installed_meter);
+    }
+    hmap_destroy(&meter_table.existing_meters);
+
     ovsdb_idl_loop_destroy(&ovs_idl_loop);
     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
 
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 69a2172a8..059de2a38 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -2139,6 +2139,118 @@  ovnact_log_free(struct ovnact_log *log)
     free(log->name);
 }
 
+static void
+parse_set_meter_action(struct action_context *ctx)
+{
+    int rate;
+    int burst = 0;
+
+    if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
+        lexer_error(ctx->lexer,
+                    "\"set_meter\" action not allowed in last table.");
+        return;
+    }
+
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+    lexer_force_int(ctx->lexer, &rate);
+    if (lexer_match(ctx->lexer, LEX_T_COMMA)) {
+        lexer_force_int(ctx->lexer, &burst);
+    }
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+
+    struct ovnact_set_meter *cl = ovnact_put_SET_METER(ctx->ovnacts);
+    cl->rate = (uint32_t)rate;
+    cl->burst = (uint32_t)burst;
+}
+
+static void
+format_SET_METER(const struct ovnact_set_meter *cl, struct ds *s)
+{
+    if (cl->burst) {
+        ds_put_format(s, "set_meter(%d ,%d);", cl->rate, cl->burst);
+    } else {
+        ds_put_format(s, "set_meter(%d);", cl->rate);
+    }
+}
+
+static void
+encode_SET_METER(const struct ovnact_set_meter *cl,
+                 const struct ovnact_encode_params *ep,
+                 struct ofpbuf *ofpacts)
+{
+    uint32_t meter_id = 0, hash;
+    struct meter_info *meter_info;
+    struct ofpact_meter *om;
+
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    if (cl->burst) {
+        ds_put_format(&ds,
+                      "kbps burst stats bands=type=drop rate=%d burst_size=%d",
+                      cl->rate, cl->burst);
+    } else {
+        ds_put_format(&ds, "kbps stats bands=type=drop rate=%d", cl->rate);
+    }
+
+    hash = hash_string(ds_cstr(&ds), 0);
+
+    /* Check whether we have non installed but allocated meter_id. */
+    HMAP_FOR_EACH_WITH_HASH (meter_info, hmap_node, hash,
+                             &ep->meter_table->desired_meters) {
+        if (!strcmp(ds_cstr(&meter_info->meter), ds_cstr(&ds))) {
+            meter_id = meter_info->meter_id;
+            break;
+        }
+    }
+
+    if (!meter_id) {
+        /* Check whether we already have an installed entry for this
+         * combination. */
+        HMAP_FOR_EACH_WITH_HASH (meter_info, hmap_node, hash,
+                                 &ep->meter_table->existing_meters) {
+            if (!strcmp(ds_cstr(&meter_info->meter), ds_cstr(&ds))) {
+                meter_id = meter_info->meter_id;
+            }
+        }
+
+        bool new_meter_id = false;
+        if (!meter_id) {
+            /* Reserve a new meter_id. */
+            meter_id = bitmap_scan(ep->meter_table->meter_ids, 0, 1,
+                                   MAX_OVN_METERS + 1);
+            new_meter_id = true;
+        }
+
+        if (meter_id == MAX_OVN_METERS + 1) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_ERR_RL(&rl, "out of meter ids");
+
+            ds_destroy(&ds);
+            return;
+        }
+        bitmap_set1(ep->meter_table->meter_ids, meter_id);
+
+        meter_info = xmalloc(sizeof *meter_info);
+        meter_info->meter = ds;
+        meter_info->meter_id = meter_id;
+        meter_info->hmap_node.hash = hash;
+        meter_info->new_meter_id = new_meter_id;
+
+        hmap_insert(&ep->meter_table->desired_meters,
+                    &meter_info->hmap_node, meter_info->hmap_node.hash);
+    } else {
+        ds_destroy(&ds);
+    }
+
+    /* Create an action to set the meter. */
+    om = ofpact_put_METER(ofpacts);
+    om->meter_id = meter_id;
+}
+
+static void
+ovnact_set_meter_free(struct ovnact_set_meter *ct OVS_UNUSED)
+{
+}
+
 /* Parses an assignment or exchange or put_dhcp_opts action. */
 static void
 parse_set_action(struct action_context *ctx)
@@ -2226,6 +2338,8 @@  parse_action(struct action_context *ctx)
         parse_SET_QUEUE(ctx);
     } else if (lexer_match_id(ctx->lexer, "log")) {
         parse_LOG(ctx);
+    } else if (lexer_match_id(ctx->lexer, "set_meter")) {
+        parse_set_meter_action(ctx);
     } else {
         lexer_syntax_error(ctx->lexer, "expecting action");
     }
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 27bb7c89d..28f0c61e2 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -366,7 +366,28 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 8: LB</h3>
+    <h3>Ingress Table 8: <code>from-lport</code> QoS meter</h3>
+
+    <p>
+      Logical flows in this table closely reproduce those in the
+      <code>QoS</code> table <code>bandwidth</code> column in the
+      <code>OVN_Northbound</code> database for the <code>from-lport</code>
+      direction.
+    </p>
+
+    <ul>
+      <li>
+        For every qos_rules for every logical switch a flow will be added at
+        priorities mentioned in the QoS table.
+      </li>
+
+      <li>
+        One priority-0 fallback flow that matches all packets and advances to
+        the next table.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 9: LB</h3>
 
     <p>
       It contains a priority-0 flow that simply moves traffic to the next
@@ -379,7 +400,7 @@ 
       connection.)
     </p>
 
-    <h3>Ingress Table 9: Stateful</h3>
+    <h3>Ingress Table 10: Stateful</h3>
 
     <ul>
       <li>
@@ -424,7 +445,7 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 10: ARP/ND responder</h3>
+    <h3>Ingress Table 11: ARP/ND responder</h3>
 
     <p>
       This table implements ARP/ND responder in a logical switch for known
@@ -574,7 +595,7 @@  nd_na {
       </li>
     </ul>
 
-    <h3>Ingress Table 11: DHCP option processing</h3>
+    <h3>Ingress Table 12: DHCP option processing</h3>
 
     <p>
       This table adds the DHCPv4 options to a DHCPv4 packet from the
@@ -634,7 +655,7 @@  next;
       </li>
     </ul>
 
-    <h3>Ingress Table 12: DHCP responses</h3>
+    <h3>Ingress Table 13: DHCP responses</h3>
 
     <p>
       This table implements DHCP responder for the DHCP replies generated by
@@ -716,7 +737,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 13 DNS Lookup</h3>
+    <h3>Ingress Table 14 DNS Lookup</h3>
 
     <p>
       This table looks up and resolves the DNS names to the corresponding
@@ -745,7 +766,7 @@  reg0[4] = dns_lookup(); next;
       </li>
     </ul>
 
-    <h3>Ingress Table 14 DNS Responses</h3>
+    <h3>Ingress Table 15 DNS Responses</h3>
 
     <p>
       This table implements DNS responder for the DNS replies generated by
@@ -780,7 +801,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 15 Destination Lookup</h3>
+    <h3>Ingress Table 16 Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
@@ -882,7 +903,14 @@  output;
       <code>to-lport</code> qos rules.
     </p>
 
-    <h3>Egress Table 6: Stateful</h3>
+    <h3>Egress Table 6: <code>to-lport</code> QoS meter</h3>
+
+    <p>
+      This is similar to ingress table <code>QoS meter</code> except for
+      <code>to-lport</code> qos rules.
+    </p>
+
+    <h3>Egress Table 7: Stateful</h3>
 
     <p>
       This is similar to ingress table <code>Stateful</code> except that
@@ -897,18 +925,18 @@  output;
         A priority 34000 logical flow is added for each logical port which
         has DHCPv4 options defined to allow the DHCPv4 reply packet and which has
         DHCPv6 options defined to allow the DHCPv6 reply packet from the
-        <code>Ingress Table 12: DHCP responses</code>.
+        <code>Ingress Table 13: DHCP responses</code>.
       </li>
 
       <li>
         A priority 34000 logical flow is added for each logical switch datapath
         configured with DNS records with the match <code>udp.dst = 53</code>
         to allow the DNS reply packet from the
-        <code>Ingress Table 14:DNS responses</code>.
+        <code>Ingress Table 15:DNS responses</code>.
       </li>
     </ul>
 
-    <h3>Egress Table 7: Egress Port Security - IP</h3>
+    <h3>Egress Table 8: Egress Port Security - IP</h3>
 
     <p>
       This is similar to the port security logic in table
@@ -918,7 +946,7 @@  output;
       <code>ip4.src</code> and <code>ip6.src</code>
     </p>
 
-    <h3>Egress Table 8: Egress Port Security - L2</h3>
+    <h3>Egress Table 9: Egress Port Security - L2</h3>
 
     <p>
       This is similar to the ingress port security logic in ingress table
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 6e1c36729..9bc8b5371 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -108,25 +108,27 @@  enum ovn_stage {
     PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   5, "ls_in_pre_stateful")  \
     PIPELINE_STAGE(SWITCH, IN,  ACL,            6, "ls_in_acl")           \
     PIPELINE_STAGE(SWITCH, IN,  QOS_MARK,       7, "ls_in_qos_mark")      \
-    PIPELINE_STAGE(SWITCH, IN,  LB,             8, "ls_in_lb")            \
-    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,       9, "ls_in_stateful")      \
-    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    10, "ls_in_arp_rsp")       \
-    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  11, "ls_in_dhcp_options")  \
-    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 12, "ls_in_dhcp_response") \
-    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,      13, "ls_in_dns_lookup") \
-    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  14, "ls_in_dns_response") \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       15, "ls_in_l2_lkup")       \
-                                                                      \
-    /* Logical switch egress stages. */                               \
-    PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")     \
-    PIPELINE_STAGE(SWITCH, OUT, PRE_ACL,      1, "ls_out_pre_acl")     \
-    PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful")  \
-    PIPELINE_STAGE(SWITCH, OUT, LB,           3, "ls_out_lb")            \
+    PIPELINE_STAGE(SWITCH, IN,  QOS_METER,      8, "ls_in_qos_meter")     \
+    PIPELINE_STAGE(SWITCH, IN,  LB,             9, "ls_in_lb")            \
+    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      10, "ls_in_stateful")      \
+    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    11, "ls_in_arp_rsp")       \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  12, "ls_in_dhcp_options")  \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 13, "ls_in_dhcp_response") \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    14, "ls_in_dns_lookup")    \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  15, "ls_in_dns_response")  \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       16, "ls_in_l2_lkup")       \
+                                                                          \
+    /* Logical switch egress stages. */                                   \
+    PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
+    PIPELINE_STAGE(SWITCH, OUT, PRE_ACL,      1, "ls_out_pre_acl")        \
+    PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful")   \
+    PIPELINE_STAGE(SWITCH, OUT, LB,           3, "ls_out_lb")             \
     PIPELINE_STAGE(SWITCH, OUT, ACL,          4, "ls_out_acl")            \
     PIPELINE_STAGE(SWITCH, OUT, QOS_MARK,     5, "ls_out_qos_mark")       \
-    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     6, "ls_out_stateful")       \
-    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP,  7, "ls_out_port_sec_ip")    \
-    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2,  8, "ls_out_port_sec_l2")    \
+    PIPELINE_STAGE(SWITCH, OUT, QOS_METER,    6, "ls_out_qos_meter")      \
+    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     7, "ls_out_stateful")       \
+    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP,  8, "ls_out_port_sec_ip")    \
+    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2,  9, "ls_out_port_sec_l2")    \
                                                                       \
     /* Logical router ingress stages. */                              \
     PIPELINE_STAGE(ROUTER, IN,  ADMISSION,      0, "lr_in_admission")    \
@@ -3383,21 +3385,49 @@  static void
 build_qos(struct ovn_datapath *od, struct hmap *lflows) {
     ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_MARK, 0, "1", "next;");
     ovn_lflow_add(lflows, od, S_SWITCH_OUT_QOS_MARK, 0, "1", "next;");
+    ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_METER, 0, "1", "next;");
+    ovn_lflow_add(lflows, od, S_SWITCH_OUT_QOS_METER, 0, "1", "next;");
 
     for (size_t i = 0; i < od->nbs->n_qos_rules; i++) {
         struct nbrec_qos *qos = od->nbs->qos_rules[i];
         bool ingress = !strcmp(qos->direction, "from-lport") ? true :false;
         enum ovn_stage stage = ingress ? S_SWITCH_IN_QOS_MARK : S_SWITCH_OUT_QOS_MARK;
+        uint32_t rate = 0;
+        uint32_t burst = 0;
+
+        for (size_t j = 0; j < qos->n_action; j++) {
+            if (!strcmp(qos->key_action[j], "dscp")) {
+                struct ds dscp_action = DS_EMPTY_INITIALIZER;
+
+                ds_put_format(&dscp_action, "ip.dscp = %d; next;",
+                              (uint8_t)qos->value_action[j]);
+                ovn_lflow_add(lflows, od, stage,
+                              qos->priority,
+                              qos->match, ds_cstr(&dscp_action));
+                ds_destroy(&dscp_action);
+            }
+        }
 
-        if (!strcmp(qos->key_action, "dscp")) {
-            struct ds dscp_action = DS_EMPTY_INITIALIZER;
-
-            ds_put_format(&dscp_action, "ip.dscp = %d; next;",
-                          (uint8_t)qos->value_action);
+        for (size_t n = 0; n < qos->n_bandwidth; n++) {
+            if (!strcmp(qos->key_bandwidth[n], "rate")) {
+                rate = (uint32_t)qos->value_bandwidth[n];
+            } else if (!strcmp(qos->key_bandwidth[n], "burst")) {
+                burst = (uint32_t)qos->value_bandwidth[n];
+            }
+        }
+        if (rate) {
+            struct ds meter_action = DS_EMPTY_INITIALIZER;
+            stage = ingress ? S_SWITCH_IN_QOS_METER : S_SWITCH_OUT_QOS_METER;
+            if (burst) {
+                ds_put_format(&meter_action, "set_meter(%d, %d); next;",
+                              rate, burst);
+            } else {
+                ds_put_format(&meter_action, "set_meter(%d); next;", rate);
+            }
             ovn_lflow_add(lflows, od, stage,
                           qos->priority,
-                          qos->match, ds_cstr(&dscp_action));
-            ds_destroy(&dscp_action);
+                          qos->match, ds_cstr(&meter_action));
+            ds_destroy(&meter_action);
         }
     }
 }
@@ -3513,7 +3543,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
     struct ds actions = DS_EMPTY_INITIALIZER;
 
     /* Build pre-ACL and ACL tables for both ingress and egress.
-     * Ingress tables 3 through 9.  Egress tables 0 through 6. */
+     * Ingress tables 3 through 10.  Egress tables 0 through 7. */
     struct ovn_datapath *od;
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
@@ -3596,7 +3626,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
     }
 
-    /* Ingress table 10: ARP/ND responder, skip requests coming from localnet
+    /* Ingress table 11: ARP/ND responder, skip requests coming from localnet
      * and vtep ports. (priority 100); see ovn-northd.8.xml for the
      * rationale. */
     HMAP_FOR_EACH (op, key_node, ports) {
@@ -3613,7 +3643,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 10: ARP/ND responder, reply for known IPs.
+    /* Ingress table 11: ARP/ND responder, reply for known IPs.
      * (priority 50). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbsp) {
@@ -3708,7 +3738,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 10: ARP/ND responder, by default goto next.
+    /* Ingress table 11: ARP/ND responder, by default goto next.
      * (priority 0)*/
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
@@ -3718,7 +3748,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;");
     }
 
-    /* Logical switch ingress table 11 and 12: DHCP options and response
+    /* Logical switch ingress table 12 and 13: DHCP options and response
          * priority 100 flows. */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbsp) {
@@ -3820,7 +3850,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Logical switch ingress table 13 and 14: DNS lookup and response
+    /* Logical switch ingress table 14 and 15: DNS lookup and response
      * priority 100 flows.
      */
     HMAP_FOR_EACH (od, key_node, datapaths) {
@@ -3852,9 +3882,9 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ds_destroy(&action);
     }
 
-    /* Ingress table 11 and 12: DHCP options and response, by default goto next.
+    /* Ingress table 12 and 13: DHCP options and response, by default goto next.
      * (priority 0).
-     * Ingress table 13 and 14: DNS lookup and response, by default goto next.
+     * Ingress table 14 and 15: DNS lookup and response, by default goto next.
      * (priority 0).*/
 
     HMAP_FOR_EACH (od, key_node, datapaths) {
@@ -3868,7 +3898,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
     }
 
-    /* Ingress table 15: Destination lookup, broadcast and multicast handling
+    /* Ingress table 16: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbsp) {
@@ -3888,7 +3918,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                       "outport = \""MC_FLOOD"\"; output;");
     }
 
-    /* Ingress table 13: Destination lookup, unicast handling (priority 50), */
+    /* Ingress table 16: Destination lookup, unicast handling (priority 50), */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbsp) {
             continue;
@@ -3988,7 +4018,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 13: Destination lookup for unknown MACs (priority 0). */
+    /* Ingress table 16: Destination lookup for unknown MACs (priority 0). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
@@ -4000,8 +4030,8 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Egress tables 6: Egress port security - IP (priority 0)
-     * Egress table 7: Egress port security L2 - multicast/broadcast
+    /* Egress tables 8: Egress port security - IP (priority 0)
+     * Egress table 9: Egress port security L2 - multicast/broadcast
      *                 (priority 100). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
@@ -4013,10 +4043,10 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                       "output;");
     }
 
-    /* Egress table 6: Egress port security - IP (priorities 90 and 80)
+    /* Egress table 8: Egress port security - IP (priorities 90 and 80)
      * if port security enabled.
      *
-     * Egress table 7: Egress port security - L2 (priorities 50 and 150).
+     * Egress table 9: Egress port security - L2 (priorities 50 and 150).
      *
      * Priority 50 rules implement port security for enabled logical port.
      *
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index fcd878cf2..3bbb7441a 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.8.1",
-    "cksum": "607160660 16929",
+    "version": "5.8.2",
+    "cksum": "4173627069 17350",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -164,7 +164,13 @@ 
                                             "enum": ["set", ["dscp"]]},
                                     "value": {"type": "integer",
                                               "minInteger": 0,
-                                              "maxInteger": 63}}},
+                                              "maxInteger": 63},
+                                    "min": 0, "max": "unlimited"}},
+                "bandwidth": {"type": {"key": {"type": "string",
+                                               "enum": ["set", ["rate", "burst"]]},
+                                       "value": {"type": "integer",
+                                                 "minInteger": 1},
+                                       "min": 0, "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index ee54960bc..385837233 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1254,6 +1254,22 @@ 
       </ul>
     </column>
 
+    <column name="bandwidth">
+      <p>
+         The bandwidth limit to be performed on the matched packet.
+         Currently only supported in the userspace by dpdk.
+      </p>
+      <ul>
+        <li>
+          <code>rate</code>: The value of rate limit.
+        </li>
+        <li>
+          <code>burst</code>: The value of burst rate limit. This is optional
+          and needs to specify the <code>rate</code> first.
+        </li>
+      </ul>
+    </column>
+
     <column name="external_ids">
       See <em>External IDs</em> at the beginning of this document.
     </column>
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index ca8cbecdd..91dff9308 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1634,6 +1634,21 @@ 
             </code>
           </p>
         </dd>
+
+        <dt><code>set_meter(<var>rate</var>);</code></dt>
+        <dt><code>set_meter(<var>rate</var>, <var>burst</var>);</code></dt>
+        <dd>
+          <p>
+            <b>Parameters</b>: rate limit int field <var>rate</var>, burst rate limits
+            int field <var>burst</var>.
+          </p>
+
+          <p>
+            This action sets the rate limit for a flow.
+          </p>
+
+          <p><b>Example:</b> <code>set_meter(100, 1000);</code></p>
+        </dd>
       </dl>
 
       <dl>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 7ff4a2682..06d4ddf8e 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -1888,6 +1888,10 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
         case OVNACT_LOG:
             execute_log(ovnact_get_LOG(a), uflow, super);
             break;
+
+        case OVNACT_SET_METER:
+            /* Nothing to do. */
+            break;
         }
 
     }
diff --git a/tests/ovn.at b/tests/ovn.at
index f051d86f5..877a18833 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -5953,7 +5953,7 @@  OVN_CLEANUP([hv])
 AT_CLEANUP
 
 
-AT_SETUP([ovn -- DSCP marking check])
+AT_SETUP([ovn -- DSCP marking and meter check])
 AT_KEYWORDS([ovn])
 ovn_start
 
@@ -6020,13 +6020,16 @@  AT_CHECK([get_final_nw_tos], [0], [none
 check_tos 0
 
 # Mark DSCP with a valid value
-qos_id=$(ovn-nbctl --wait=hv -- --id=@lp1-qos create QoS priority=100 action=dscp=48 match="inport\=\=\"lp1\"" direction="from-lport" -- set Logical_Switch lsw0 qos_rules=@lp1-qos)
+qos_id=$(ovn-nbctl --wait=hv -- --id=@lp1-qos create QoS priority=100 action=dscp=48 bandwidth=rate=100,burst=1000 match="inport\=\=\"lp1\"" direction="from-lport" -- set Logical_Switch lsw0 qos_rules=@lp1-qos)
 check_tos 48
 
 # Update the DSCP marking
 ovn-nbctl --wait=hv set QoS $qos_id action=dscp=63
 check_tos 63
 
+# Update the meter rate
+ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=65535,burst=65535
+
 ovn-nbctl --wait=hv set QoS $qos_id match="outport\=\=\"lp2\"" direction="to-lport"
 check_tos 63
 
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index f9a5085f7..24d934d4f 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1213,6 +1213,13 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
     hmap_init(&group_table.desired_groups);
     hmap_init(&group_table.existing_groups);
 
+    /* Initialize meter ids for QoS. */
+    struct meter_table meter_table;
+    meter_table.meter_ids = bitmap_allocate(MAX_OVN_METERS);
+    bitmap_set1(meter_table.meter_ids, 0); /* Meter id 0 is invalid. */
+    hmap_init(&meter_table.desired_meters);
+    hmap_init(&meter_table.existing_meters);
+
     simap_init(&ports);
     simap_put(&ports, "eth0", 5);
     simap_put(&ports, "eth1", 6);
@@ -1252,6 +1259,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
                 .aux = &ports,
                 .is_switch = true,
                 .group_table = &group_table,
+                .meter_table = &meter_table,
 
                 .pipeline = OVNACT_P_INGRESS,
                 .ingress_ptable = 8,