[ovs-dev] [ACL Meters 4/7] ovn: Add Meter and Meter_Band tables to the NB and SB databases.

Justin Pettit jpettit at ovn.org
Mon Jul 30 06:46:35 UTC 2018


Add support for configuring meters through the Meter and Meter_Band
tables in the Northbound database.  This commit also has ovn-northd
sync those tables between the Northbound and Southbound databases.

Add support for configuring meters with ovn-nbctl.

Signed-off-by: Justin Pettit <jpettit at ovn.org>
---
 ovn/northd/ovn-northd.c       | 145 ++++++++++++++++++++++++++++++++++
 ovn/ovn-nb.ovsschema          |  33 +++++++-
 ovn/ovn-nb.xml                |  80 +++++++++++++++++++
 ovn/ovn-sb.ovsschema          |  27 ++++++-
 ovn/ovn-sb.xml                |  72 +++++++++++++++++
 ovn/utilities/ovn-nbctl.8.xml |  50 ++++++++++++
 ovn/utilities/ovn-nbctl.c     | 143 +++++++++++++++++++++++++++++++++
 tests/ovn-nbctl.at            |  58 ++++++++++++++
 8 files changed, 604 insertions(+), 4 deletions(-)

diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 04a072ba8de7..45557170edc8 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -6606,6 +6606,140 @@ sync_port_groups(struct northd_context *ctx)
     shash_destroy(&sb_port_groups);
 }
 
+struct band_entry {
+    int64_t rate;
+    int64_t burst_size;
+    const char *action;
+};
+
+static int
+band_cmp(const void *band1_, const void *band2_)
+{
+    const struct band_entry *band1p = band1_;
+    const struct band_entry *band2p = band2_;
+
+    if (band1p->rate != band2p->rate) {
+        return band1p->rate > band2p->rate ? -1 : 1;
+    } else if (band1p->burst_size != band2p->burst_size) {
+        return band1p->burst_size > band2p->burst_size ? -1 : 1;
+    } else {
+        return strcmp(band1p->action, band2p->action);
+    }
+}
+
+static bool
+bands_need_update(const struct nbrec_meter *nb_meter,
+                  const struct sbrec_meter *sb_meter)
+{
+    if (nb_meter->n_bands != sb_meter->n_bands) {
+        return true;
+    }
+
+    /* A single band is the most common scenario, so speed up that
+     * check. */
+    if (nb_meter->n_bands == 1) {
+        struct nbrec_meter_band *nb_band = nb_meter->bands[0];
+        struct sbrec_meter_band *sb_band = sb_meter->bands[0];
+
+        return !(nb_band->rate == sb_band->rate
+                 && nb_band->burst_size == sb_band->burst_size
+                 && !strcmp(sb_band->action, nb_band->action));
+    }
+
+    /* Place the Northbound entries in sorted order. */
+    struct band_entry *nb_bands;
+    nb_bands = xmalloc(sizeof *nb_bands * nb_meter->n_bands);
+    for (size_t i = 0; i < nb_meter->n_bands; i++) {
+        struct nbrec_meter_band *nb_band = nb_meter->bands[i];
+
+        nb_bands[i].rate = nb_band->rate;
+        nb_bands[i].burst_size = nb_band->burst_size;
+        nb_bands[i].action = nb_band->action;
+    }
+    qsort(nb_bands, nb_meter->n_bands, sizeof *nb_bands, band_cmp);
+
+    /* Place the Southbound entries in sorted order. */
+    struct band_entry *sb_bands;
+    sb_bands = xmalloc(sizeof *sb_bands * sb_meter->n_bands);
+    for (size_t i = 0; i < sb_meter->n_bands; i++) {
+        struct sbrec_meter_band *sb_band = sb_meter->bands[i];
+
+        sb_bands[i].rate = sb_band->rate;
+        sb_bands[i].burst_size = sb_band->burst_size;
+        sb_bands[i].action = sb_band->action;
+    }
+    qsort(sb_bands, sb_meter->n_bands, sizeof *sb_bands, band_cmp);
+
+    bool need_update = false;
+    for (size_t i = 0; i < nb_meter->n_bands; i++) {
+        if (nb_bands[i].rate != sb_bands[i].rate
+            || nb_bands[i].burst_size != sb_bands[i].burst_size
+            || strcmp(nb_bands[i].action, nb_bands[i].action)) {
+            need_update = true;
+            goto done;
+        }
+    }
+
+done:
+    free(nb_bands);
+    free(sb_bands);
+
+    return need_update;
+}
+
+/* Each entry in the Meter and Meter_Band tables in OVN_Northbound have
+ * a corresponding entries in the Meter and Meter_Band tables in
+ * OVN_Southbound.
+ */
+static void
+sync_meters(struct northd_context *ctx)
+{
+    struct shash sb_meters = SHASH_INITIALIZER(&sb_meters);
+
+    const struct sbrec_meter *sb_meter;
+    SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) {
+        shash_add(&sb_meters, sb_meter->name, sb_meter);
+    }
+
+    const struct nbrec_meter *nb_meter;
+    NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) {
+        bool new_sb_meter = false;
+
+        sb_meter = shash_find_and_delete(&sb_meters, nb_meter->name);
+        if (!sb_meter) {
+            sb_meter = sbrec_meter_insert(ctx->ovnsb_txn);
+            sbrec_meter_set_name(sb_meter, nb_meter->name);
+            new_sb_meter = true;
+        }
+
+        if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) {
+            struct sbrec_meter_band **sb_bands;
+            sb_bands = xcalloc(nb_meter->n_bands, sizeof *sb_bands);
+            for (size_t i = 0; i < nb_meter->n_bands; i++) {
+                const struct nbrec_meter_band *nb_band = nb_meter->bands[i];
+
+                sb_bands[i] = sbrec_meter_band_insert(ctx->ovnsb_txn);
+
+                sbrec_meter_band_set_action(sb_bands[i], nb_band->action);
+                sbrec_meter_band_set_rate(sb_bands[i], nb_band->rate);
+                sbrec_meter_band_set_burst_size(sb_bands[i],
+                                                nb_band->burst_size);
+            }
+            sbrec_meter_set_bands(sb_meter, sb_bands, nb_meter->n_bands);
+            free(sb_bands);
+        }
+
+        sbrec_meter_set_unit(sb_meter, nb_meter->unit);
+    }
+
+    struct shash_node *node, *next;
+    SHASH_FOR_EACH_SAFE (node, next, &sb_meters) {
+        sbrec_meter_delete(node->data);
+        shash_delete(&sb_meters, node);
+    }
+    shash_destroy(&sb_meters);
+}
+
 /*
  * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
  * and Southbound db.
@@ -6726,6 +6860,7 @@ ovnnb_db_run(struct northd_context *ctx,
 
     sync_address_sets(ctx);
     sync_port_groups(ctx);
+    sync_meters(ctx);
     sync_dns_entries(ctx, &datapaths);
 
     struct ovn_port_group *pg, *next_pg;
@@ -7351,6 +7486,16 @@ main(int argc, char *argv[])
                        &sbrec_rbac_permission_col_insert_delete);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_permission_col_update);
 
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_meter);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_name);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_unit);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_bands);
+
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_meter_band);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_action);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_rate);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_burst_size);
+
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_name);
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 8e6ddec4662f..9a0d8ec70514 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.11.0",
-    "cksum": "1149260021 18713",
+    "version": "5.12.0",
+    "cksum": "2812995200 20238",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -195,6 +195,35 @@
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "isRoot": false},
+        "Meter": {
+            "columns": {
+                "name": {"type": "string"},
+                "unit": {"type": {"key": {"type": "string",
+                                          "enum": ["set", ["kbps", "pktps"]]}}},
+                "bands": {"type": {"key": {"type": "uuid",
+                                           "refTable": "Meter_Band",
+                                           "refType": "strong"},
+                                   "min": 1,
+                                   "max": "unlimited"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "indexes": [["name"]],
+            "isRoot": true},
+        "Meter_Band": {
+            "columns": {
+                "action": {"type": {"key": {"type": "string",
+                                            "enum": ["set", ["drop"]]}}},
+                "rate": {"type": {"key": {"type": "integer",
+                                          "minInteger": 1,
+                                          "maxInteger": 4294967295}}},
+                "burst_size": {"type": {"key": {"type": "integer",
+                                                "minInteger": 0,
+                                                "maxInteger": 4294967295}}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "isRoot": false},
         "Logical_Router": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index e4e72b27cf36..1feb2af52027 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1356,6 +1356,86 @@
     </column>
   </table>
 
+  <table name="Meter" title="Meter table">
+    <p>
+      Each row in this table represents a meter that can be used for QoS or
+      rate-limiting.
+    </p>
+
+    <column name="name">
+      <p>
+        A name for this meter.
+      </p>
+
+      <p>
+        Names that begin with "__" are reserved for OVN internal use and should
+        not be added manually.
+      </p>
+    </column>
+
+    <column name="unit">
+      <p>
+        The unit for <ref column="rate" table="Meter_Band"/> and
+        <ref column="burst_rate" table="Meter_Band"/> parameters in
+        the <ref column="bands"/> entry.  <code>kbps</code> specifies
+        kilobits per second, and <code>pktps</code> specifies packets
+        per second.
+      </p>
+    </column>
+
+    <column name="bands">
+      <p>
+        The bands associated with this meter.  Each band specifies a
+        rate above which the band is to take the action
+        <code>action</code>.  If multiple bands' rates are exceeded,
+        then the band with the highest rate among the exceeded bands is
+        selected.
+      </p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+
+  <table name="Meter_Band" title="Meter_Band table">
+    <p>
+      Each row in this table represents a meter band which specifies the
+      rate above which the configured action should be applied.  These bands
+      are referenced by the <ref column="bands" table="Meter"/> column in
+      the <ref table="Meter"/> table.
+    </p>
+
+    <column name="action">
+      <p>
+        The action to execute when this band matches.  The only supported
+        action is <code>drop</code>.
+      </p>
+    </column>
+
+    <column name="rate">
+      <p>
+        The relative rate limit for this band, in kilobits per second or
+        bits per second, depending on whether the parent <ref table="Meter"/>
+        entry's <ref column="unit" table="Meter"/> column specified
+        <code>kbps</code> or <code>pktps</code>.
+      </p>
+    </column>
+
+    <column name="burst_size">
+      <p>
+        The maximum burst allowed for the band in kilobits or packets,
+        depending on whether <code>kbps</code> or <code>pktps</code> was
+        selected in the parent <ref table="Meter"/> entry's
+        <ref column="unit" table="Meter"/> column.
+      </p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+
   <table name="Logical_Router_Port" title="L3 logical router port">
     <p>
       A port within an L3 logical router.
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index 9e271d433246..ad6ad3b71da0 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "1.15.0",
-    "cksum": "1839738004 13639",
+    "version": "1.16.0",
+    "cksum": "3046632234 14844",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -98,6 +98,29 @@
             "indexes": [["datapath", "tunnel_key"],
                         ["datapath", "name"]],
             "isRoot": true},
+        "Meter": {
+            "columns": {
+                "name": {"type": "string"},
+                "unit": {"type": {"key": {"type": "string",
+                                          "enum": ["set", ["kbps", "pktps"]]}}},
+                "bands": {"type": {"key": {"type": "uuid",
+                                           "refTable": "Meter_Band",
+                                           "refType": "strong"},
+                                   "min": 1,
+                                   "max": "unlimited"}}},
+            "indexes": [["name"]],
+            "isRoot": true},
+        "Meter_Band": {
+            "columns": {
+                "action": {"type": {"key": {"type": "string",
+                                            "enum": ["set", ["drop"]]}}},
+                "rate": {"type": {"key": {"type": "integer",
+                                          "minInteger": 1,
+                                          "maxInteger": 4294967295}}},
+                "burst_size": {"type": {"key": {"type": "integer",
+                                                "minInteger": 0,
+                                                "maxInteger": 4294967295}}}},
+            "isRoot": false},
         "Datapath_Binding": {
             "columns": {
                 "tunnel_key": {
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index f9724d398ce6..57d8a9e042a5 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1923,6 +1923,78 @@ tcp.flags = RST;
     </column>
   </table>
 
+  <table name="Meter" title="Meter table">
+    <p>
+      Each row in this table represents a meter that can be used for QoS or
+      rate-limiting.
+    </p>
+
+    <column name="name">
+      <p>
+        A name for this meter.
+      </p>
+
+      <p>
+        Names that begin with "__" are reserved for OVN internal use and should
+        not be added manually.
+      </p>
+    </column>
+
+    <column name="unit">
+      <p>
+        The unit for <ref column="rate" table="Meter_Band"/> and
+        <ref column="burst_rate" table="Meter_Band"/> parameters in
+        the <ref column="bands"/> entry.  <code>kbps</code> specifies
+        kilobits per second, and <code>pktps</code> specifies packets
+        per second.
+      </p>
+    </column>
+
+    <column name="bands">
+      <p>
+        The bands associated with this meter.  Each band specifies a
+        rate above which the band is to take the action
+        <code>action</code>.  If multiple bands' rates are exceeded,
+        then the band with the highest rate among the exceeded bands is
+        selected.
+      </p>
+    </column>
+  </table>
+
+  <table name="Meter_Band" title="Meter_Band table">
+    <p>
+      Each row in this table represents a meter band which specifies the
+      rate above which the configured action should be applied.  These bands
+      are referenced by the <ref column="bands" table="Meter"/> column in
+      the <ref table="Meter"/> table.
+    </p>
+
+    <column name="action">
+      <p>
+        The action to execute when this band matches.  The only supported
+        action is <code>drop</code>.
+      </p>
+    </column>
+
+    <column name="rate">
+      <p>
+        The relative rate limit for this band, in kilobits per second or
+        bits per second, depending on whether the parent <ref table="Meter"/>
+        entry's <ref column="unit" table="Meter"/> column specified
+        <code>kbps</code> or <code>pktps</code>.
+      </p>
+    </column>
+
+    <column name="burst_size">
+      <p>
+        The maximum burst allowed for the band in kilobits or packets,
+        depending on whether <code>kbps</code> or <code>pktps</code> was
+        selected in the parent <ref table="Meter"/> entry's
+        <ref column="unit" table="Meter"/> column.
+      </p>
+    </column>
+  </table>
+
   <table name="Datapath_Binding" title="Physical-Logical Datapath Bindings">
     <p>
       Each row in this table represents a logical datapath, which implements a
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index 2cd2fab304cd..a8ea7d8cb1e1 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -172,6 +172,56 @@
       </dd>
     </dl>
 
+    <h1>Meter Commands</h1>
+    <dl>
+        <dt><code>meter-add</code> <var>name</var> <var>unit</var> <var>action</var> <var>rate</var> [<var>burst_size</var>]</dt>
+      <dd>
+        <p>
+          Adds the specified meter.  <var>name</var> must be a unique
+          name to identify this meter.  The <var>action</var> argument
+          specifies what should happen when this meter is exceeded.
+          The only supported action is <code>drop</code>.
+        </p>
+
+        <p>
+          The <var>unit</var> specifies the unit for the <var>rate</var>
+          argument; valid values are <code>kbps</code> and
+          <code>pktps</code> for kilobits per second and packets per
+          second, respectively.  The <var>burst_rate</var> option
+          configures the maximum burst allowed for the band in kilobits
+          or packets depending on whether the <var>unit</var> chosen was
+          <code>kbps</code> or <code>pktps</code>, respectively.
+        </p>
+
+        <p>
+          <code>ovn-nbctl</code> only supports adding a meter with a
+          single band, but the other commands support meters with
+          multiple bands.
+        </p>
+
+        <p>
+          Names that start with "__" are reserved for internal use by OVN,
+          so <code>ovn-nbctl</code> does not allow adding them.
+        </p>
+      </dd>
+
+      <dt><code>meter-del</code> [<var>name</var>]</dt>
+      <dd>
+        <p>
+          Deletes meters.  By default, all meters are deleted.  If
+          <var>name</var> is supplied, only the meter with that name
+          will be deleted.
+      </p>
+      </dd>
+
+      <dt><code>meter-list</code></dt>
+      <dd>
+        <p>
+          Lists all meters.
+        </p>
+      </dd>
+    </dl>
+
     <h1>Logical Switch Port Commands</h1>
     <dl>
       <dt>[<code>--may-exist</code>] <code>lsp-add</code> <var>switch</var> <var>port</var></dt>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 3c3e582cb906..9f0e6347c104 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -496,6 +496,12 @@ QoS commands:\n\
                             remove QoS rules from SWITCH\n\
   qos-list SWITCH           print QoS rules for SWITCH\n\
 \n\
+Meter commands:\n\
+  meter-add NAME UNIT ACTION RATE [BURST_SIZE]\n\
+                            add a meter\n\
+  meter-del [NAME]          remove meters\n\
+  meter-list                print meters\n\
+\n\
 Logical switch port commands:\n\
   lsp-add SWITCH PORT       add logical port PORT on SWITCH\n\
   lsp-add SWITCH PORT PARENT TAG\n\
@@ -2290,6 +2296,137 @@ nbctl_qos_del(struct ctl_context *ctx)
     }
 }
 
+static int
+meter_cmp(const void *meter1_, const void *meter2_)
+{
+    struct nbrec_meter *const *meter1p = meter1_;
+    struct nbrec_meter *const *meter2p = meter2_;
+    const struct nbrec_meter *meter1 = *meter1p;
+    const struct nbrec_meter *meter2 = *meter2p;
+
+    return strcmp(meter1->name, meter2->name);
+}
+
+static void
+nbctl_meter_list(struct ctl_context *ctx)
+{
+    const struct nbrec_meter **meters = NULL;
+    const struct nbrec_meter *meter;
+    size_t n_capacity = 0;
+    size_t n_meters = 0;
+
+    NBREC_METER_FOR_EACH (meter, ctx->idl) {
+        if (n_meters == n_capacity) {
+            meters = x2nrealloc(meters, &n_capacity, sizeof *meters);
+        }
+
+        meters[n_meters] = meter;
+        n_meters++;
+    }
+
+    if (n_meters) {
+        qsort(meters, n_meters, sizeof *meters, meter_cmp);
+    }
+
+    for (size_t i = 0; i < n_meters; i++) {
+        meter = meters[i];
+        ds_put_format(&ctx->output, "%s: unit=%s bands:\n", meter->name,
+                      meter->unit);
+
+        for (size_t j = 0; j < meter->n_bands; j++) {
+            const struct nbrec_meter_band *band = meter->bands[j];
+
+            ds_put_format(&ctx->output, "  %s: rate=%"PRId64"",
+                          band->action, band->rate);
+            if (band->burst_size) {
+                ds_put_format(&ctx->output, ", burst_size=%"PRId64"",
+                              band->burst_size);
+            }
+        }
+
+        ds_put_cstr(&ctx->output, "\n");
+    }
+
+    free(meters);
+}
+
+static void
+nbctl_meter_add(struct ctl_context *ctx)
+{
+    const struct nbrec_meter *meter;
+
+    const char *name = ctx->argv[1];
+    NBREC_METER_FOR_EACH (meter, ctx->idl) {
+        if (!strcmp(meter->name, name)) {
+            ctl_fatal("meter with name \"%s\" already exists", name);
+        }
+    }
+
+    if (!strncmp(name, "__", 2)) {
+        ctl_fatal("meter names that begin with \"__\" are reserved");
+    }
+
+    const char *unit = ctx->argv[2];
+    if (strcmp(unit, "kbps") && strcmp(unit, "pktps")) {
+        ctl_fatal("unit must be \"kbps\" or \"pktps\"");
+    }
+
+    const char *action = ctx->argv[3];
+    if (strcmp(action, "drop")) {
+        ctl_fatal("action must be \"drop\"");
+    }
+
+    int64_t rate;
+    if (!ovs_scan(ctx->argv[4], "%"SCNd64, &rate)
+        || rate < 1 || rate > UINT32_MAX) {
+        ctl_fatal("rate must be in the range 1...4294967295");
+    }
+
+    int64_t burst_size = 0;
+    if (ctx->argc > 5) {
+        if (!ovs_scan(ctx->argv[5], "%"SCNd64, &burst_size)
+            || burst_size < 0 || burst_size > UINT32_MAX) {
+            ctl_fatal("burst_size must be in the range 0...4294967295");
+        }
+    }
+
+    /* Create the band.  We only support adding a single band. */
+    struct nbrec_meter_band *band = nbrec_meter_band_insert(ctx->txn);
+    nbrec_meter_band_set_action(band, action);
+    nbrec_meter_band_set_rate(band, rate);
+    nbrec_meter_band_set_burst_size(band, burst_size);
+
+    /* Create the meter. */
+    meter = nbrec_meter_insert(ctx->txn);
+    nbrec_meter_set_name(meter, name);
+    nbrec_meter_set_unit(meter, unit);
+    nbrec_meter_set_bands(meter, &band, 1);
+}
+
+static void
+nbctl_meter_del(struct ctl_context *ctx)
+{
+    const struct nbrec_meter *meter, *next;
+
+    /* If a name is not specified, delete all meters. */
+    if (ctx->argc == 1) {
+        NBREC_METER_FOR_EACH_SAFE (meter, next, ctx->idl) {
+            nbrec_meter_delete(meter);
+        }
+        return;
+    }
+
+    /* Remove the matching meter. */
+    NBREC_METER_FOR_EACH (meter, ctx->idl) {
+        if (strcmp(ctx->argv[1], meter->name)) {
+            continue;
+        }
+
+        nbrec_meter_delete(meter);
+        return;
+    }
+}
+
 static void
 nbctl_lb_add(struct ctl_context *ctx)
 {
@@ -4678,6 +4815,12 @@ static const struct ctl_command_syntax nbctl_commands[] = {
       nbctl_qos_del, NULL, "", RW },
     { "qos-list", 1, 1, "SWITCH", NULL, nbctl_qos_list, NULL, "", RO },
 
+    /* meter commands. */
+    { "meter-add", 4, 5, "NAME UNIT ACTION RATE [BURST_SIZE]", NULL,
+      nbctl_meter_add, NULL, "", RW },
+    { "meter-del", 0, 1, "[NAME]", NULL, nbctl_meter_del, NULL, "", RW },
+    { "meter-list", 0, 0, "", NULL, nbctl_meter_list, NULL, "", RO },
+
     /* logical switch port commands. */
     { "lsp-add", 2, 4, "SWITCH PORT [PARENT] [TAG]", NULL, nbctl_lsp_add,
       NULL, "--may-exist", RW },
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 64e217654c2f..7a1445e312ff 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -323,6 +323,64 @@ OVN_NBCTL_TEST_STOP
 AT_CLEANUP
 
 dnl ---------------------------------------------------------------------
+
+AT_SETUP([ovn-nbctl - Meters])
+OVN_NBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl meter-add meter1 kbps drop 10])
+AT_CHECK([ovn-nbctl meter-add meter2 kbps drop 3 2])
+AT_CHECK([ovn-nbctl meter-add meter3 kbps drop 100 200])
+
+dnl Add duplicate meter name
+AT_CHECK([ovn-nbctl meter-add meter1 kbps drop 10], [1], [], [stderr])
+AT_CHECK([grep 'already exists' stderr], [0], [ignore])
+
+dnl Add reserved meter name
+AT_CHECK([ovn-nbctl meter-add __meter1 kbps drop 10], [1], [], [stderr])
+AT_CHECK([grep 'reserved' stderr], [0], [ignore])
+
+dnl Add meter with invalid rates
+AT_CHECK([ovn-nbctl meter-add meter4 kbps drop 100010111111], [1], [],
+[ovn-nbctl: rate must be in the range 1...4294967295
+])
+
+AT_CHECK([ovn-nbctl meter-add meter4 kbps drop 0], [1], [],
+[ovn-nbctl: rate must be in the range 1...4294967295
+])
+
+dnl Add meter with invalid burst_size
+AT_CHECK([ovn-nbctl meter-add meter4 kbps drop 10 100010111111], [1], [],
+[ovn-nbctl: burst_size must be in the range 0...4294967295
+])
+
+AT_CHECK([ovn-nbctl meter-list], [0], [dnl
+meter1: unit=kbps bands:
+  drop: rate=10
+meter2: unit=kbps bands:
+  drop: rate=3, burst_size=2
+meter3: unit=kbps bands:
+  drop: rate=100, burst_size=200
+])
+
+dnl Delete a single meter.
+AT_CHECK([ovn-nbctl meter-del meter2])
+AT_CHECK([ovn-nbctl meter-list], [0], [dnl
+meter1: unit=kbps bands:
+  drop: rate=10
+meter3: unit=kbps bands:
+  drop: rate=100, burst_size=200
+])
+
+dnl Delete all meters.
+AT_CHECK([ovn-nbctl meter-del])
+AT_CHECK([ovn-nbctl meter-list], [0], [dnl
+])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
+
 AT_SETUP([ovn-nbctl - NATs])
 OVN_NBCTL_TEST_START
 AT_CHECK([ovn-nbctl lr-add lr0])
-- 
2.17.1



More information about the dev mailing list