@@ -117,6 +117,42 @@
</dd>
</dl>
+ <h1>Logical Switch QoS Rule Commands</h1>
+ <dl>
+ <dt>[<code>--may-exist</code>] <code>qos-add</code> <var>switch</var> <var>direction</var> <var>priority</var> <var>match</var> <var>rate</var> <var>burst</var> <var>dscp</var></dt>
+ <dd>
+ <p>
+ Adds the specified QoS rule to <var>switch</var>.
+ <var>direction</var> must be either <code>from-lport</code> or
+ <code>to-lport</code>. <var>priority</var> must be between
+ <code>0</code> and <code>32767</code>, inclusive. <var>dscp</var>
+ must be between <code>0</code> and <code>63</code>, inclusive.
+ <var>rate</var> and <var>burst</var> must be between <code>1</code>
+ and <code>4294967295</code>, inclusive. One of the rate or dscp
+ must be configured. A full description of the fields are in
+ <code>ovn-nb</code>(5). If <code>--may-exist</code> is specified,
+ adding a duplicated QoS rule succeeds but the QoS rule is not
+ really created. Without <code>--may-exist</code>, adding a
+ duplicated QoS rule results in error.
+ </p>
+ </dd>
+
+ <dt><code>qos-del</code> <var>switch</var> [<var>direction</var> [<var>priority</var> <var>match</var>]]</dt>
+ <dd>
+ Deletes QoS rules from <var>switch</var>. If only
+ <var>switch</var> is supplied, all the QoS rules from the logical
+ switch are deleted. If <var>direction</var> is also specified,
+ then all the flows in that direction will be deleted from the
+ logical switch. If all the fields are given, then a single flow
+ that matches all the fields will be deleted.
+ </dd>
+
+ <dt><code>qos-list</code> <var>switch</var></dt>
+ <dd>
+ Lists the QoS rules on <var>switch</var>.
+ </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>
@@ -340,6 +340,13 @@ ACL commands:\n\
remove ACLs from SWITCH\n\
acl-list SWITCH print ACLs for SWITCH\n\
\n\
+QoS commands:\n\
+ qos-add SWITCH DIRECTION PRIORITY MATCH [rate=RATE [burst=BURST]] [dscp=DSCP]\n\
+ add an QoS rule to SWITCH\n\
+ qos-del SWITCH [DIRECTION [PRIORITY MATCH]]\n\
+ remove QoS rules from SWITCH\n\
+ qos-list SWITCH print QoS rules for SWITCH\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\
@@ -1388,6 +1395,26 @@ nbctl_acl_list(struct ctl_context *ctx)
free(acls);
}
+static int
+qos_cmp(const void *qos1_, const void *qos2_)
+{
+ const struct nbrec_qos *const *qos1p = qos1_;
+ const struct nbrec_qos *const *qos2p = qos2_;
+ const struct nbrec_qos *qos1 = *qos1p;
+ const struct nbrec_qos *qos2 = *qos2p;
+
+ int dir1 = dir_encode(qos1->direction);
+ int dir2 = dir_encode(qos2->direction);
+
+ if (dir1 != dir2) {
+ return dir1 < dir2 ? -1 : 1;
+ } else if (qos1->priority != qos2->priority) {
+ return qos1->priority > qos2->priority ? -1 : 1;
+ } else {
+ return strcmp(qos1->match, qos2->match);
+ }
+}
+
static const char *
parse_direction(const char *arg)
{
@@ -1536,6 +1563,202 @@ nbctl_acl_del(struct ctl_context *ctx)
}
static void
+nbctl_qos_list(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *ls;
+ const struct nbrec_qos **qos_rules;
+ size_t i;
+
+ ls = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+ qos_rules = xmalloc(sizeof *qos_rules * ls->n_qos_rules);
+ for (i = 0; i < ls->n_qos_rules; i++) {
+ qos_rules[i] = ls->qos_rules[i];
+ }
+
+ qsort(qos_rules, ls->n_qos_rules, sizeof *qos_rules, qos_cmp);
+
+ for (i = 0; i < ls->n_qos_rules; i++) {
+ const struct nbrec_qos *qos_rule = qos_rules[i];
+ ds_put_format(&ctx->output, "%10s %5"PRId64" (%s)",
+ qos_rule->direction, qos_rule->priority,
+ qos_rule->match);
+ for (size_t j = 0; j < qos_rule->n_bandwidth; j++) {
+ if (!strcmp(qos_rule->key_bandwidth[j], "rate")) {
+ ds_put_format(&ctx->output, " rate=%"PRId64"",
+ qos_rule->value_bandwidth[j]);
+ }
+ }
+ for (size_t j = 0; j < qos_rule->n_bandwidth; j++) {
+ if (!strcmp(qos_rule->key_bandwidth[j], "burst")) {
+ ds_put_format(&ctx->output, " burst=%"PRId64"",
+ qos_rule->value_bandwidth[j]);
+ }
+ }
+ for (size_t j = 0; j < qos_rule->n_action; j++) {
+ if (!strcmp(qos_rule->key_action[j], "dscp")) {
+ ds_put_format(&ctx->output, " dscp=%"PRId64"",
+ qos_rule->value_action[j]);
+ }
+ }
+ ds_put_cstr(&ctx->output, "\n");
+ }
+
+ free(qos_rules);
+}
+
+static void
+nbctl_qos_add(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *ls
+ = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+ const char *direction = parse_direction(ctx->argv[2]);
+ int64_t priority = parse_priority(ctx->argv[3]);
+ int64_t dscp = -1;
+ int64_t rate = 0;
+ int64_t burst = 0;
+
+ for (int i = 5; i < ctx->argc; i++) {
+ if (!strncmp(ctx->argv[i], "dscp=", 5)) {
+ if (!ovs_scan(ctx->argv[i] + 5, "%"SCNd64, &dscp)
+ || dscp < 0 || dscp > 63) {
+ ctl_fatal("%s: dscp must in range 0...63.", ctx->argv[i] + 5);
+ return;
+ }
+ }
+ else if (!strncmp(ctx->argv[i], "rate=", 5)) {
+ if (!ovs_scan(ctx->argv[i] + 5, "%"SCNd64, &rate)
+ || rate < 1 || rate > 4294967295) {
+ ctl_fatal("%s: rate must in range 1...4294967295.",
+ ctx->argv[i] + 5);
+ return;
+ }
+ }
+ else if (!strncmp(ctx->argv[i], "burst=", 6)) {
+ if (!ovs_scan(ctx->argv[i] + 6, "%"SCNd64, &burst)
+ || burst < 1 || burst > 4294967295) {
+ ctl_fatal("%s: burst must in range 1...4294967295.",
+ ctx->argv[i] + 6);
+ return;
+ }
+ } else {
+ ctl_fatal("%s: must be start of \"dscp=\", \"rate=\", \"burst=\".",
+ ctx->argv[i]);
+ return;
+ }
+ }
+
+ /* Validate rate and dscp. */
+ if (-1 == dscp && !rate) {
+ ctl_fatal("One of the rate or dscp must be configured.");
+ return;
+ }
+
+ /* Create the qos. */
+ struct nbrec_qos *qos = nbrec_qos_insert(ctx->txn);
+ nbrec_qos_set_priority(qos, priority);
+ nbrec_qos_set_direction(qos, direction);
+ nbrec_qos_set_match(qos, ctx->argv[4]);
+ if (-1 != dscp) {
+ const char *dscp_key = "dscp";
+ nbrec_qos_set_action(qos, &dscp_key, &dscp, 1);
+ }
+ if (rate) {
+ const char *bandwidth_key[2] = {"rate", "burst"};
+ const int64_t bandwidth_value[2] = {rate, burst};
+ size_t n_bandwidth = 1;
+ if (burst) {
+ n_bandwidth = 2;
+ }
+ nbrec_qos_set_bandwidth(qos, bandwidth_key, bandwidth_value,
+ n_bandwidth);
+ }
+
+ /* Check if same qos rule already exists for the ls */
+ for (size_t i = 0; i < ls->n_qos_rules; i++) {
+ if (!qos_cmp(&ls->qos_rules[i], &qos)) {
+ bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+ if (!may_exist) {
+ ctl_fatal("Same qos already existed on the ls %s.",
+ ctx->argv[1]);
+ }
+ return;
+ }
+ }
+
+ /* Insert the qos rule the logical switch. */
+ nbrec_logical_switch_verify_qos_rules(ls);
+ struct nbrec_qos **new_qos_rules
+ = xmalloc(sizeof *new_qos_rules * (ls->n_qos_rules + 1));
+ nullable_memcpy(new_qos_rules,
+ ls->qos_rules, sizeof *new_qos_rules * ls->n_qos_rules);
+ new_qos_rules[ls->n_qos_rules] = qos;
+ nbrec_logical_switch_set_qos_rules(ls, new_qos_rules,
+ ls->n_qos_rules + 1);
+ free(new_qos_rules);
+}
+
+static void
+nbctl_qos_del(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *ls
+ = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+ if (ctx->argc == 2) {
+ /* If direction, priority, and match are not specified, delete
+ * all QoS rules. */
+ nbrec_logical_switch_verify_qos_rules(ls);
+ nbrec_logical_switch_set_qos_rules(ls, NULL, 0);
+ return;
+ }
+
+ const char *direction = parse_direction(ctx->argv[2]);
+
+ /* If priority and match are not specified, delete all qos_rules with the
+ * specified direction. */
+ if (ctx->argc == 3) {
+ struct nbrec_qos **new_qos_rules
+ = xmalloc(sizeof *new_qos_rules * ls->n_qos_rules);
+
+ int n_qos_rules = 0;
+ for (size_t i = 0; i < ls->n_qos_rules; i++) {
+ if (strcmp(direction, ls->qos_rules[i]->direction)) {
+ new_qos_rules[n_qos_rules++] = ls->qos_rules[i];
+ }
+ }
+
+ nbrec_logical_switch_verify_qos_rules(ls);
+ nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, n_qos_rules);
+ free(new_qos_rules);
+ return;
+ }
+
+ int64_t priority = parse_priority(ctx->argv[3]);
+
+ if (ctx->argc == 4) {
+ ctl_fatal("cannot specify priority without match");
+ }
+
+ /* Remove the matching rule. */
+ for (size_t i = 0; i < ls->n_qos_rules; i++) {
+ struct nbrec_qos *qos = ls->qos_rules[i];
+
+ if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) &&
+ !strcmp(direction, qos->direction)) {
+ struct nbrec_qos **new_qos_rules
+ = xmemdup(ls->qos_rules,
+ sizeof *new_qos_rules * ls->n_qos_rules);
+ new_qos_rules[i] = ls->qos_rules[ls->n_qos_rules - 1];
+ nbrec_logical_switch_verify_qos_rules(ls);
+ nbrec_logical_switch_set_qos_rules(ls, new_qos_rules,
+ ls->n_qos_rules - 1);
+ free(new_qos_rules);
+ return;
+ }
+ }
+}
+
+static void
nbctl_lb_add(struct ctl_context *ctx)
{
const char *lb_name = ctx->argv[1];
@@ -3723,6 +3946,14 @@ static const struct ctl_command_syntax nbctl_commands[] = {
nbctl_acl_del, NULL, "", RW },
{ "acl-list", 1, 1, "SWITCH", NULL, nbctl_acl_list, NULL, "", RO },
+ /* qos commands. */
+ { "qos-add", 5, 7,
+ "SWITCH DIRECTION PRIORITY MATCH [rate=RATE [burst=BURST]] [dscp=DSCP]",
+ NULL, nbctl_qos_add, NULL, "--may-exist", RW },
+ { "qos-del", 1, 4, "SWITCH [DIRECTION [PRIORITY MATCH]]", NULL,
+ nbctl_qos_del, NULL, "", RW },
+ { "qos-list", 1, 1, "SWITCH", NULL, nbctl_qos_list, NULL, "", RO },
+
/* logical switch port commands. */
{ "lsp-add", 2, 4, "SWITCH PORT [PARENT] [TAG]", NULL, nbctl_lsp_add,
NULL, "--may-exist", RW },
@@ -230,6 +230,80 @@ OVN_NBCTL_TEST_STOP
AT_CLEANUP
dnl ---------------------------------------------------------------------
+
+AT_SETUP([ovn-nbctl - QoS])
+OVN_NBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 tcp dscp=63])
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 500 udp rate=100 burst=1000])
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 400 tcp dscp=0 rate=300 burst=3000])
+AT_CHECK([ovn-nbctl qos-add ls0 to-lport 300 tcp dscp=48])
+AT_CHECK([ovn-nbctl qos-add ls0 to-lport 200 ip rate=101])
+AT_CHECK([ovn-nbctl qos-add ls0 to-lport 100 ip4 dscp=13 rate=301 burst=30000])
+
+dnl Add duplicated qos
+AT_CHECK([ovn-nbctl qos-add ls0 to-lport 100 ip4 dscp=11 rate=302 burst=30002], [1], [], [stderr])
+AT_CHECK([grep 'already existed' stderr], [0], [ignore])
+AT_CHECK([ovn-nbctl --may-exist qos-add ls0 to-lport 100 ip4 dscp=11 rate=302 burst=30002])
+
+AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
+from-lport 600 (tcp) dscp=63
+from-lport 500 (udp) rate=100 burst=1000
+from-lport 400 (tcp) rate=300 burst=3000 dscp=0
+ to-lport 300 (tcp) dscp=48
+ to-lport 200 (ip) rate=101
+ to-lport 100 (ip4) rate=301 burst=30000 dscp=13
+])
+
+dnl Delete in one direction.
+AT_CHECK([ovn-nbctl qos-del ls0 to-lport])
+AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
+from-lport 600 (tcp) dscp=63
+from-lport 500 (udp) rate=100 burst=1000
+from-lport 400 (tcp) rate=300 burst=3000 dscp=0
+])
+
+dnl Delete all qos_rules.
+AT_CHECK([ovn-nbctl qos-del ls0])
+AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
+])
+
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip rate=1000101])
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 400 tcp dscp=44])
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 200 ip burst=1000102 rate=301 dscp=19])
+
+dnl Delete a single flow.
+AT_CHECK([ovn-nbctl qos-del ls0 from-lport 400 tcp])
+AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
+from-lport 600 (ip) rate=1000101
+from-lport 200 (ip) rate=301 burst=1000102 dscp=19
+])
+
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip rate=100010111111], [1], [],
+[ovn-nbctl: 100010111111: rate must in range 1...4294967295.
+])
+
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip burst=100010111112 rate=100010], [1], [],
+[ovn-nbctl: 100010111112: burst must in range 1...4294967295.
+])
+
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip dscp=-1], [1], [],
+[ovn-nbctl: -1: dscp must in range 0...63.
+])
+
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip dscpa=-1], [1], [],
+[ovn-nbctl: dscpa=-1: must be start of "dscp=", "rate=", "burst=".
+])
+
+AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip burst=123], [1], [],
+[ovn-nbctl: One of the rate or dscp must be configured.
+])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
AT_SETUP([ovn-nbctl - NATs])
OVN_NBCTL_TEST_START
AT_CHECK([ovn-nbctl lr-add lr0])
@@ -6033,6 +6033,8 @@ 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)
+AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1
+])
check_tos 48
# check at hv without qos meter
@@ -6065,7 +6067,9 @@ ovn-nbctl --wait=hv set QoS $qos_id match="outport\=\=\"lp2\"" direction="to-lpo
check_tos 63
# Disable DSCP marking
-ovn-nbctl --wait=hv clear Logical_Switch lsw0 qos_rules
+ovn-nbctl --wait=hv qos-del lsw0
+AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [0
+])
check_tos 0
# check at hv without qos meter
This patch provides the command line to add/delete/list QoS rule on the logical switch. Signed-off-by: Guoshuai Li <ligs@dtdream.com> --- ovn/utilities/ovn-nbctl.8.xml | 36 +++++++ ovn/utilities/ovn-nbctl.c | 231 ++++++++++++++++++++++++++++++++++++++++++ tests/ovn-nbctl.at | 74 ++++++++++++++ tests/ovn.at | 6 +- 4 files changed, 346 insertions(+), 1 deletion(-)