[ovs-dev] [PATCH v1 ovn 1/1] Forwarding group to load balance l2 traffic with liveness detection

Manoj Sharma manoj.sharma at nutanix.com
Mon Jan 6 22:09:50 UTC 2020


A forwarding group is an aggregation of logical switch ports of a
logical switch to load balance traffic across the ports. It also
detects the liveness if the logical switch ports are realized as
OVN tunnel ports on the physical topology.

Signed-off-by: Manoj Sharma <manoj.sharma at nutanix.com>

---
 controller/lflow.c    |  20 ++++
 controller/physical.c |  13 +++
 controller/physical.h |   4 +
 include/ovn/actions.h |  19 +++-
 lib/actions.c         | 122 ++++++++++++++++++++++++
 northd/ovn-northd.c   |  63 +++++++++++++
 ovn-nb.ovsschema      |  18 +++-
 ovn-nb.xml            |  35 +++++++
 tests/ovn-nbctl.at    |  37 ++++++++
 tests/ovn.at          | 124 ++++++++++++++++++++++++
 utilities/ovn-nbctl.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++
 utilities/ovn-trace.c |   3 +
 12 files changed, 709 insertions(+), 3 deletions(-)

diff --git a/controller/lflow.c b/controller/lflow.c
index a689320..49dfa06 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -103,6 +103,25 @@ lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp)
     return false;
 }
 
+/* Given the OVN port name, get its openflow port */
+static bool
+tunnel_ofport_cb(const void *aux_, const char *port_name, ofp_port_t *ofport)
+{
+    const struct lookup_port_aux *aux = aux_;
+
+    const struct sbrec_port_binding *pb
+        = lport_lookup_by_name(aux->sbrec_port_binding_by_name, port_name);
+    if (!pb || (pb->datapath != aux->dp) || !pb->chassis) {
+        return false;
+    }
+
+    if (!get_tunnel_ofport(pb->chassis->name, NULL, ofport)) {
+        return false;
+    }
+
+    return true;
+}
+
 static bool
 is_chassis_resident_cb(const void *c_aux_, const char *port_name)
 {
@@ -681,6 +700,7 @@ consider_logical_flow(
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
     struct ovnact_encode_params ep = {
         .lookup_port = lookup_port_cb,
+        .tunnel_ofport = tunnel_ofport_cb,
         .aux = &aux,
         .is_switch = is_switch(ldp),
         .group_table = group_table,
diff --git a/controller/physical.c b/controller/physical.c
index 500d419..af1d10f 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -1794,3 +1794,16 @@ physical_run(struct ovsdb_idl_index *sbrec_port_binding_by_name,
 
     simap_destroy(&new_tunnel_to_ofport);
 }
+
+bool
+get_tunnel_ofport(const char *chassis_name, char *encap_ip, ofp_port_t *ofport)
+{
+    struct chassis_tunnel *tun = NULL;
+    tun = chassis_tunnel_find(chassis_name, encap_ip);
+    if (!tun) {
+        return false;
+    }
+
+    *ofport = tun->ofport;
+    return true;
+}
diff --git a/controller/physical.h b/controller/physical.h
index c93f6b1..c0e17cd 100644
--- a/controller/physical.h
+++ b/controller/physical.h
@@ -72,4 +72,8 @@ void physical_handle_mc_group_changes(
         const struct simap *ct_zones,
         const struct hmap *local_datapaths,
         struct ovn_desired_flow_table *);
+bool get_tunnel_ofport(
+        const char *chassis_name,
+        char *encap_ip,
+        ofp_port_t *ofport);
 #endif /* controller/physical.h */
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 047a8d7..2d39239 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -89,7 +89,8 @@ struct ovn_extend_table;
     OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
     OVNACT(TRIGGER_EVENT,     ovnact_controller_event) \
     OVNACT(BIND_VPORT,        ovnact_bind_vport)       \
-    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check)
+    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check) \
+    OVNACT(FWD_GROUP,         ovnact_fwd_group)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -359,6 +360,15 @@ struct ovnact_handle_svc_check {
     struct expr_field port;     /* Logical port name. */
 };
 
+/* OVNACT_FWD_GROUP. */
+struct ovnact_fwd_group {
+    struct ovnact ovnact;
+    bool liveness;
+    char **child_ports;       /* Logical ports */
+    size_t n_child_ports;
+    uint8_t ltable;           /* Logical table ID of next table. */
+};
+
 /* 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);
@@ -620,6 +630,13 @@ struct ovnact_encode_params {
      * '*portp' and returns true; otherwise, returns false. */
     bool (*lookup_port)(const void *aux, const char *port_name,
                         unsigned int *portp);
+
+    /* Looks up tunnel port to a chassis by its port name.  If found, stores
+     * its openflow port number in '*ofport' and returns true;
+     * otherwise, returns false. */
+    bool (*tunnel_ofport)(const void *aux, const char *port_name,
+                          ofp_port_t *ofport);
+
     const void *aux;
 
     /* 'true' if the flow is for a switch. */
diff --git a/lib/actions.c b/lib/actions.c
index 051e6c8..73dade9 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -2854,6 +2854,126 @@ ovnact_handle_svc_check_free(struct ovnact_handle_svc_check *sc OVS_UNUSED)
 {
 }
 
+static void
+parse_fwd_group_action(struct action_context *ctx)
+{
+    char *child_port, **child_port_list = NULL;
+    size_t allocated_ports = 0;
+    size_t n_child_ports = 0;
+    bool liveness = false;
+
+    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
+        if (lexer_match_id(ctx->lexer, "liveness")) {
+            liveness = true;
+            lexer_force_match(ctx->lexer, LEX_T_SEMICOLON);
+        }
+        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+            if (ctx->lexer->token.type != LEX_T_STRING) {
+                lexer_syntax_error(ctx->lexer,
+                                   "expecting logical switch port");
+                return;
+            }
+            /* Parse child's logical ports */
+            child_port = xstrdup(ctx->lexer->token.s);
+            lexer_get(ctx->lexer);
+            lexer_match(ctx->lexer, LEX_T_COMMA);
+
+            if (n_child_ports >= allocated_ports) {
+                child_port_list = x2nrealloc(child_port_list, &allocated_ports,
+                                             sizeof *child_port_list);
+            }
+            child_port_list[n_child_ports++] = child_port;
+        }
+    }
+
+    struct ovnact_fwd_group *fwd_group = ovnact_put_FWD_GROUP(ctx->ovnacts);
+    fwd_group->ltable = ctx->pp->cur_ltable + 1;
+    fwd_group->liveness = liveness;
+    fwd_group->child_ports = child_port_list;
+    fwd_group->n_child_ports = n_child_ports;
+}
+
+static void
+format_FWD_GROUP(const struct ovnact_fwd_group *fwd_group, struct ds *s)
+{
+    ds_put_cstr(s, "fwd_group(");
+    if (fwd_group->liveness) {
+        ds_put_cstr(s, "liveness;");
+    }
+    if (fwd_group->n_child_ports) {
+        for (size_t i = 0; i < fwd_group->n_child_ports; i++) {
+            if (i) {
+                ds_put_cstr(s, ", ");
+            }
+
+            ds_put_format(s, "%s", fwd_group->child_ports[i]);
+        }
+    }
+    ds_put_cstr(s, ");");
+}
+
+static void
+encode_FWD_GROUP(const struct ovnact_fwd_group *fwd_group,
+                 const struct ovnact_encode_params *ep,
+                 struct ofpbuf *ofpacts)
+{
+    if (!fwd_group->n_child_ports) {
+        /* Nothing to do without child ports */
+        return;
+    }
+
+    uint32_t reg_index = MFF_LOG_OUTPORT - MFF_REG0;
+    struct ds ds = DS_EMPTY_INITIALIZER;
+
+    ds_put_format(&ds, "type=select,selection_method=dp_hash");
+
+    for (size_t i = 0; i < fwd_group->n_child_ports; i++) {
+        uint32_t  port_tunnel_key;
+        ofp_port_t ofport;
+
+        const char *port_name = fwd_group->child_ports[i];
+
+        /* Find the tunnel key of the logical port */
+        if (!ep->lookup_port(ep->aux, port_name, &port_tunnel_key)) {
+            return;
+        }
+        ds_put_format(&ds, ",bucket=");
+
+        if (fwd_group->liveness) {
+            /* Find the openflow port number of the tunnel port */
+            if (!ep->tunnel_ofport(ep->aux, port_name, &ofport)) {
+                return;
+            }
+
+            /* Watch port for failure, used with BFD */
+            ds_put_format(&ds, "watch_port:%d,", ofport);
+        }
+
+        ds_put_format(&ds, "load=0x%d->NXM_NX_REG%d[0..15]",
+                      port_tunnel_key, reg_index);
+        ds_put_format(&ds, ",resubmit(,%d)", ep->output_ptable);
+    }
+
+    uint32_t table_id = 0;
+    struct ofpact_group *og;
+    table_id = ovn_extend_table_assign_id(ep->group_table, ds_cstr(&ds),
+                                          ep->lflow_uuid);
+    ds_destroy(&ds);
+    if (table_id == EXT_TABLE_ID_INVALID) {
+        return;
+    }
+
+    /* Create an action to set the group */
+    og = ofpact_put_GROUP(ofpacts);
+    og->group_id = table_id;
+}
+
+static void
+ovnact_fwd_group_free(struct ovnact_fwd_group *fwd_group)
+{
+    free(fwd_group->child_ports);
+}
+
 /* Parses an assignment or exchange or put_dhcp_opts action. */
 static void
 parse_set_action(struct action_context *ctx)
@@ -2973,6 +3093,8 @@ parse_action(struct action_context *ctx)
         parse_bind_vport(ctx);
     } else if (lexer_match_id(ctx->lexer, "handle_svc_check")) {
         parse_handle_svc_check(ctx);
+    } else if (lexer_match_id(ctx->lexer, "fwd_group")) {
+        parse_fwd_group_action(ctx);
     } else {
         lexer_syntax_error(ctx->lexer, "expecting action");
     }
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index d91a008..fc07082 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -5419,6 +5419,60 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs)
 }
 
 static void
+build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows)
+{
+    struct ds match = DS_EMPTY_INITIALIZER;
+    struct ds actions = DS_EMPTY_INITIALIZER;
+
+    for (int i = 0; i < od->nbs->n_forwarding_groups; ++i) {
+        const struct nbrec_forwarding_group *fwd_group = NULL;
+        fwd_group = od->nbs->forwarding_groups[i];
+        if (!fwd_group || (fwd_group->n_child_port == 0)) {
+            continue;
+        }
+
+        /* ARP responder for the forwarding group's virtual IP */
+        ds_put_format(&match, "arp.tpa == %s && arp.op == 1",
+                      fwd_group->vip);
+        ds_put_format(&actions,
+            "eth.dst = eth.src; "
+            "eth.src = %s; "
+            "arp.op = 2; /* ARP reply */ "
+            "arp.tha = arp.sha; "
+            "arp.sha = %s; "
+            "arp.tpa = arp.spa; "
+            "arp.spa = %s; "
+            "outport = inport; "
+            "flags.loopback = 1; "
+            "output;",
+            fwd_group->vmac, fwd_group->vmac, fwd_group->vip);
+
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 50,
+                      ds_cstr(&match), ds_cstr(&actions));
+
+        /* L2 lookup for the forwarding group's virtual MAC */
+        ds_clear(&match);
+        ds_put_format(&match, "eth.dst == %s", fwd_group->vmac);
+
+        /* Create a comma separated string of child ports */
+        struct ds group_ports = DS_EMPTY_INITIALIZER;
+        if (fwd_group->liveness) {
+            ds_put_cstr(&group_ports, "liveness;");
+        }
+        for (i = 0; i < (fwd_group->n_child_port - 1); ++i) {
+            ds_put_format(&group_ports, "\"%s\",", fwd_group->child_port[i]);
+        }
+        ds_put_format(&group_ports, "\"%s\"",
+                      fwd_group->child_port[fwd_group->n_child_port - 1]);
+
+        ds_clear(&actions);
+        ds_put_format(&actions, "fwd_group(%s);", group_ports.string);
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 50,
+                      ds_cstr(&match), ds_cstr(&actions));
+    }
+}
+
+static void
 build_lrouter_groups__(struct hmap *ports, struct ovn_datapath *od)
 {
     ovs_assert((od && od->nbr && od->lr_group));
@@ -5718,6 +5772,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         build_stateful(od, lflows, lbs);
     }
 
+    /* Build logical flows for the forwarding groups */
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbs) {
+            continue;
+        }
+
+        build_fwd_group_lflows(od, lflows);
+    }
+
     /* Logical switch ingress table 0: Admission control framework (priority
      * 100). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 12999a4..99b6285 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
     "version": "5.18.0",
-    "cksum": "2806349485 24196",
+    "cksum": "63300136 24879",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -59,7 +59,12 @@
                              "min": 0, "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
+                             "min": 0, "max": "unlimited"}},
+               "forwarding_groups": {
+                    "type": {"key": {"type": "uuid",
+                                     "refTable": "Forwarding_Group",
+                                     "refType": "strong"},
+                                     "min": 0, "max": "unlimited"}}},
             "isRoot": true},
         "Logical_Switch_Port": {
             "columns": {
@@ -113,6 +118,15 @@
                              "min": 0, "max": "unlimited"}}},
             "indexes": [["name"]],
             "isRoot": false},
+        "Forwarding_Group": {
+            "columns": {
+                "name": {"type": "string"},
+                "vip": {"type": "string"},
+                "vmac": {"type": "string"},
+                "liveness": {"type": "boolean"},
+                "child_port": {"type": {"key": "string",
+                                        "min": 1, "max": "unlimited"}}},
+            "isRoot": false},
         "Address_Set": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 5ae52bb..decb4ae 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -197,6 +197,11 @@
       Please see the <ref table="DNS"/> table.
     </column>
 
+    <column name="forwarding_groups">
+      Groups a set of logical port endpoints for traffic going out of the
+      logical switch.
+    </column>
+
     <group title="Naming">
       <p>
         These columns provide names for the logical switch.  From OVN's
@@ -1152,6 +1157,36 @@
     </group>
   </table>
 
+  <table name="Forwarding_Group" title="forwarding group">
+    <p>
+      Each row represents one forwarding group.
+    </p>
+
+    <column name="name">
+      A name for the forwarding group.  This name has no special meaning or
+      purpose other than to provide convenience for human interaction with
+      the ovn-nb database.
+    </column>
+
+    <column name="vip">
+      The virtual IP address assigned to the forwarding group. It will respond
+      with vmac when an ARP request is sent for vip.
+    </column>
+
+    <column name="vmac">
+      The virtual MAC address assigned to the forwarding group.
+    </column>
+
+    <column name="liveness">
+      If set to <code>true</code>, liveness is enabled for child ports
+      otherwise it is disabled.
+    </column>
+
+    <column name="child_port">
+      List of child ports in the forwarding group.
+    </column>
+  </table>
+
   <table name="Address_Set" title="Address Sets">
     <p>
       Each row in this table represents a named set of addresses.
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 2679f1f..077c41e 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -1735,6 +1735,43 @@ $SW1P2
 AT_CHECK([ovn-nbctl pg-del pg1], [0], [ignore])
 AT_CHECK([ovn-nbctl list port_group], [0], [])
 ])
+dnl ---------------------------------------------------------------------
+
+OVN_NBCTL_TEST([ovn_nbctl_fwd_groups], [fwd groups], [
+
+dnl Add fwd-group to a non-existent logical switch
+AT_CHECK([ovn-nbctl fwd-group-add fwd_grp1 ls0 10.1.1.11 00:11:22:33:44:55 lsp1 lsp2], [1], [],
+  [ovn-nbctl: ls0: switch name not found
+])
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+
+dnl Add fwd-group with non-existent logical switch ports
+AT_CHECK([ovn-nbctl fwd-group-add fwd_grp1 ls0 10.1.1.11 00:11:22:33:44:55 lsp1 lsp2], [1], [],
+  [ovn-nbctl: lsp1: logical switch port does not exist
+])
+
+AT_CHECK([ovn-nbctl lsp-add ls0 lsp1])
+AT_CHECK([ovn-nbctl lsp-add ls0 lsp2])
+AT_CHECK([ovn-nbctl fwd-group-add fwd_grp1 ls0 10.1.1.11 00:11:22:33:44:55 lsp1 lsp2])
+AT_CHECK([ovn-nbctl fwd-group-list ls0], [0], [dnl
+FWD_GROUP       LS            VIP             VMAC                  CHILD_PORTS
+fwd_grp1        ls0           10.1.1.11      00:11:22:33:44:55      lsp1 lsp2
+])
+AT_CHECK([ovn-nbctl --bare --columns=name list forwarding_group], [0],
+[fwd_grp1
+])
+
+dnl Add duplicate fwd-group
+AT_CHECK([ovn-nbctl fwd-group-add fwd_grp1 ls0 10.1.1.11 00:11:22:33:44:55 lsp1 lsp2], [1], [],
+  [ovn-nbctl: fwd_grp1: a forwarding group by this name already exists
+])
+
+dnl Delete fwd-group
+AT_CHECK([ovn-nbctl fwd-group-del fwd_grp1], [0], [ignore])
+AT_CHECK([ovn-nbctl list forwarding_group], [0], [])
+
+])
 
 AT_SETUP([ovn-nbctl - daemon retry connection])
 OVN_NBCTL_TEST_START daemon
diff --git a/tests/ovn.at b/tests/ovn.at
index 411b768..b8e8eba 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -17338,3 +17338,127 @@ OVS_WAIT_UNTIL([
 
 OVN_CLEANUP([hv1])
 AT_CLEANUP
+
+AT_SETUP([ovn -- forwarding group: 3 HVs, 1 LR, 2 LS])
+AT_KEYWORDS([forwarding-group])
+ovn_start
+
+# Logical network:
+# One LR - R1 has a logical switch ls1 and ls2 connected to it.
+# Logical switch ls1 has one port while ls2 has two logical switch ports as
+# child ports.
+ovn-nbctl lr-add R1
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Logical switch ls1 to R1 connectivity
+ovn-nbctl lrp-add R1 R1-ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 ls1-R1 -- set Logical_Switch_Port ls1-R1 \
+    type=router options:router-port=R1-ls1 -- lsp-set-addresses ls1-R1 router
+ovn-nbctl lsp-add ls1 lsp11 \
+    -- lsp-set-addresses lsp11 "00:00:00:01:02:01 192.168.1.2"
+
+# Logical switch ls2 to R1 connectivity
+ovn-nbctl lrp-add R1 R1-ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 ls2-R1 -- set Logical_Switch_Port ls2-R1 \
+    type=router options:router-port=R1-ls2 -- lsp-set-addresses ls2-R1 router
+ovn-nbctl lsp-add ls2 lsp21 \
+    -- lsp-set-addresses lsp21 "00:00:00:01:02:01 172.16.1.2"
+ovn-nbctl lsp-add ls2 lsp22 \
+    -- lsp-set-addresses lsp22 "00:00:00:01:02:02 172.16.1.3"
+
+# Create a network
+net_add n1
+
+# Create hypervisor hv1 connected to n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lsp11 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
+
+# Create hypervisor hv2 connected to n1
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lsp21 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
+
+# Create hypervisor hv3 connected to n1
+sim_add hv3
+as hv3
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.3
+ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id=lsp22 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
+
+# Add a forwarding group on ls2 with lsp21 and lsp22 as child ports
+# virtual IP - 172.16.1.11, virtual MAC - 00:11:de:ad:be:ef
+ovn-nbctl fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+sleep 1
+
+# Check logical flow
+AT_CHECK([ovn-sbctl dump-flows | grep ls_in_l2_lkup | grep fwd_group | wc -l], [0], [dnl
+1
+])
+
+# Check openflow rule with "group" on hypervisor
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
+    grep "dl_dst=00:11:de:ad:be:ef actions=group" | wc -l], [0], [dnl
+1
+])
+
+# Verify openflow group members
+for child_port in lsp21 lsp22; do
+    tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=$child_port`
+    AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+        grep "bucket=actions=load:0x"$tunnel_key | wc -l], [0], [dnl
+1
+])
+done
+
+# Send a packet to virtual IP
+src_mac=00:00:00:01:02:01
+dst_mac=00:00:00:01:02:f1
+src_ip=192.168.1.2
+dst_ip=172.16.1.11
+packet="inport==\"lsp11\" && eth.src==$src_mac && eth.dst==$dst_mac &&
+        ip4 && ip.ttl==64 && ip4.src==$src_ip && ip4.dst==$dst_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Check if the packet hit the forwarding group policy
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
+    grep "dl_dst=00:11:de:ad:be:ef actions=group" | \
+    grep "n_packets=1" | wc -l], [0], [dnl
+1
+])
+
+# Delete the forwarding group
+ovn-nbctl fwd-group-del fwd_grp1
+
+# Add a forwarding group with liveness on ls2 with lsp21 and lsp22 as child
+# ports virtual IP - 172.16.1.11, virtual MAC - 00:11:de:ad:be:ef
+ovn-nbctl --liveness fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+sleep 1
+
+# Verify openflow group members
+ofport_lsp21=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv2-0)
+tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=lsp21`
+AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+    grep "bucket=watch_port:$ofport_lsp21,actions=load:0x"$tunnel_key | wc -l], [0], [dnl
+1
+])
+
+ofport_lsp22=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv3-0)
+tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=lsp22`
+AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+    grep "bucket=watch_port:$ofport_lsp22,actions=load:0x"$tunnel_key | wc -l], [0], [dnl
+1
+])
+
+OVN_CLEANUP([hv1], [hv2], [hv3])
+AT_CLEANUP
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 46ba3a9..bc33b40 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -648,6 +648,14 @@ Logical switch port commands:\n\
   lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
   lsp-get-ls PORT           get the logical switch which the port belongs to\n\
 \n\
+Forwarding group commands:\n\
+  [--liveness]\n\
+  fwd-group-add GROUP SWITCH [ADDRESS] [PORT]...\n\
+                            add forwarding group GROUP on SWITCH\n\
+                            set VIP+VMAC for the group\n\
+  fwd-group-del GROUP       delete forwarding group GROUP\n\
+  fwd-group-list SWITCH     print names of all forwarding groups on SWITCH\n\
+\n\
 Logical router commands:\n\
   lr-add [ROUTER]           create a logical router named ROUTER\n\
   lr-del ROUTER             delete ROUTER and all its ports\n\
@@ -4720,6 +4728,244 @@ nbctl_lrp_get_redirect_type(struct ctl_context *ctx)
                   !redirect_type ? "overlay": redirect_type);
 }
 
+static const struct nbrec_forwarding_group *
+fwd_group_by_name_or_uuid(struct ctl_context *ctx, const char *id)
+{
+    const struct nbrec_forwarding_group *fwd_group = NULL;
+    struct uuid fwd_uuid;
+
+    bool is_uuid = uuid_from_string(&fwd_uuid, id);
+    if (is_uuid) {
+        fwd_group = nbrec_forwarding_group_get_for_uuid(ctx->idl, &fwd_uuid);
+    }
+
+    if (!fwd_group) {
+        NBREC_FORWARDING_GROUP_FOR_EACH(fwd_group, ctx->idl) {
+            if (!strcmp(fwd_group->name, id)) {
+                break;
+            }
+        }
+    }
+
+    return fwd_group;
+}
+
+static const struct nbrec_logical_switch *
+fwd_group_to_logical_switch(struct ctl_context *ctx,
+                            const struct nbrec_forwarding_group *fwd_group)
+{
+    if (!fwd_group) {
+        return NULL;
+    }
+
+    const struct nbrec_logical_switch_port *lsp;
+    char *error = lsp_by_name_or_uuid(ctx, fwd_group->child_port[0],
+                                      false, &lsp);
+    if (error) {
+        ctx->error = error;
+        return NULL;
+    }
+    if (!lsp) {
+        return NULL;
+    }
+
+    const struct nbrec_logical_switch *ls;
+    error = lsp_to_ls(ctx->idl, lsp, &ls);
+    if (error) {
+        ctx->error = error;
+        return NULL;
+    }
+
+    if (!ls) {
+        return NULL;
+    }
+
+    return ls;
+}
+
+static void
+nbctl_fwd_group_add(struct ctl_context *ctx)
+{
+    if (ctx->argc <= 5) {
+        ctl_error(ctx, "Usage : ovn-nbctl fwd-group-add group switch vip vmac "
+                  "child_ports...");
+        return;
+    }
+
+    /* Check if the forwarding group already exists */
+    const char *fwd_group_name = ctx->argv[1];
+    if (fwd_group_by_name_or_uuid(ctx, fwd_group_name)) {
+        ctl_error(ctx, "%s: a forwarding group by this name already exists",
+                  fwd_group_name);
+        return;
+    }
+
+    /* Check if the logical switch exists */
+    const char *ls_name = ctx->argv[2];
+    const struct nbrec_logical_switch *ls = NULL;
+    char *error = ls_by_name_or_uuid(ctx, ls_name, true, &ls);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* Virtual IP for the group */
+    ovs_be32 ipv4 = 0;
+    const char *fwd_group_vip = ctx->argv[3];
+    if (!ip_parse(fwd_group_vip, &ipv4)) {
+        ctl_error(ctx, "invalid ip address %s", fwd_group_vip);
+        return;
+    }
+
+    /* Virtual MAC for the group */
+    const char *fwd_group_vmac = ctx->argv[4];
+    struct eth_addr ea;
+    if (!eth_addr_from_string(fwd_group_vmac, &ea)) {
+        ctl_error(ctx, "invalid mac address %s", fwd_group_vmac);
+        return;
+    }
+
+    /* Create the forwarding group */
+    struct nbrec_forwarding_group *fwd_group = NULL;
+    fwd_group = nbrec_forwarding_group_insert(ctx->txn);
+    nbrec_forwarding_group_set_name(fwd_group, fwd_group_name);
+    nbrec_forwarding_group_set_vip(fwd_group, fwd_group_vip);
+    nbrec_forwarding_group_set_vmac(fwd_group, fwd_group_vmac);
+
+    int n_child_port = ctx->argc - 5;
+    const char **child_port = (const char **)&ctx->argv[5];
+
+    /* Verify that child ports belong to the logical switch specified */
+    for (int i = 5; i < ctx->argc; ++i) {
+        const struct nbrec_logical_switch_port *lsp;
+        const char *lsp_name = ctx->argv[i];
+        error = lsp_by_name_or_uuid(ctx, lsp_name, false, &lsp);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+        if (lsp) {
+            error = lsp_to_ls(ctx->idl, lsp, &ls);
+            if (error) {
+                ctx->error = error;
+                return;
+            }
+            if (strcmp(ls->name, ls_name)) {
+                ctl_error(ctx, "%s: port already exists but in logical "
+                          "switch %s", lsp_name, ls->name);
+                return;
+            }
+        } else {
+            ctl_error(ctx, "%s: logical switch port does not exist", lsp_name);
+            return;
+        }
+    }
+    nbrec_forwarding_group_set_child_port(fwd_group, child_port, n_child_port);
+
+    /* Liveness option */
+    bool liveness = shash_find(&ctx->options, "--liveness") != NULL;
+    if (liveness) {
+      nbrec_forwarding_group_set_liveness(fwd_group, true);
+    }
+
+    struct nbrec_forwarding_group **new_fwd_groups =
+            xmalloc(sizeof(*new_fwd_groups) * (ls->n_forwarding_groups + 1));
+    memcpy(new_fwd_groups, ls->forwarding_groups,
+           sizeof *new_fwd_groups * ls->n_forwarding_groups);
+    new_fwd_groups[ls->n_forwarding_groups] = fwd_group;
+    nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups,
+                                               (ls->n_forwarding_groups + 1));
+    free(new_fwd_groups);
+
+}
+
+static void
+nbctl_fwd_group_del(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_forwarding_group *fwd_group = NULL;
+
+    fwd_group = fwd_group_by_name_or_uuid(ctx, id);
+    if (!fwd_group) {
+        return;
+    }
+
+    const struct nbrec_logical_switch *ls = NULL;
+    ls = fwd_group_to_logical_switch(ctx, fwd_group);
+    if (!ls) {
+      return;
+    }
+
+    for (int i = 0; i < ls->n_forwarding_groups; ++i) {
+        if (!strcmp(ls->forwarding_groups[i]->name, fwd_group->name)) {
+            struct nbrec_forwarding_group **new_fwd_groups =
+                xmemdup(ls->forwarding_groups,
+                        sizeof *new_fwd_groups * ls->n_forwarding_groups);
+            new_fwd_groups[i] =
+                ls->forwarding_groups[ls->n_forwarding_groups - 1];
+            nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups,
+                (ls->n_forwarding_groups - 1));
+            free(new_fwd_groups);
+            nbrec_forwarding_group_delete(fwd_group);
+            return;
+        }
+    }
+}
+
+static void
+fwd_group_list_all(struct ctl_context *ctx, const char *ls_name)
+{
+    const struct nbrec_logical_switch *ls;
+    struct ds *s = &ctx->output;
+    const struct nbrec_forwarding_group *fwd_group = NULL;
+
+    if (ls_name) {
+        char *error = ls_by_name_or_uuid(ctx, ls_name, true, &ls);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+        if (!ls) {
+            ctl_error(
+                ctx, "%s: a logical switch with this name does not exist",
+                ls_name);
+            return;
+        }
+    }
+
+    ds_put_format(s, "%-16.16s%-14.16s%-16.7s%-22.21s%s\n",
+                  "FWD_GROUP", "LS", "VIP", "VMAC", "CHILD_PORTS");
+
+    NBREC_FORWARDING_GROUP_FOR_EACH(fwd_group, ctx->idl) {
+        ls = fwd_group_to_logical_switch(ctx, fwd_group);
+        if (!ls) {
+            continue;
+        }
+
+        if (ls_name && (strcmp(ls->name, ls_name))) {
+            continue;
+        }
+
+        ds_put_format(s, "%-16.16s%-14.18s%-15.16s%-9.18s     ",
+                      fwd_group->name, ls->name,
+                      fwd_group->vip, fwd_group->vmac);
+        for (int i = 0; i < fwd_group->n_child_port; ++i) {
+            ds_put_format(s, " %s", fwd_group->child_port[i]);
+        }
+        ds_put_char(s, '\n');
+    }
+}
+
+static void
+nbctl_fwd_group_list(struct ctl_context *ctx)
+{
+    if (ctx->argc == 1) {
+        fwd_group_list_all(ctx, NULL);
+    } else if (ctx->argc == 2) {
+        fwd_group_list_all(ctx, ctx->argv[1]);
+    }
+}
+
 

 struct ipv4_route {
     int priority;
@@ -5704,6 +5950,14 @@ static const struct ctl_command_syntax nbctl_commands[] = {
       nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
     { "lsp-get-ls", 1, 1, "PORT", NULL, nbctl_lsp_get_ls, NULL, "", RO },
 
+    /* forwarding group commands. */
+    { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
+      NULL, nbctl_fwd_group_add, NULL, "--liveness", RW },
+    { "fwd-group-del", 1, 1, "GROUP", NULL, nbctl_fwd_group_del, NULL,
+      "--if-exists", RW },
+    { "fwd-group-list", 0, 1, "[GROUP]", NULL, nbctl_fwd_group_list, NULL,
+      "", RO },
+
     /* logical router commands. */
     { "lr-add", 0, 1, "[ROUTER]", NULL, nbctl_lr_add, NULL,
       "--may-exist,--add-duplicate", RW },
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 2645438..a40700a 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -2224,6 +2224,9 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
 
         case OVNACT_HANDLE_SVC_CHECK:
             break;
+
+        case OVNACT_FWD_GROUP:
+            break;
         }
     }
     ds_destroy(&s);
-- 
1.8.3.1



More information about the dev mailing list