[ovs-dev] [RFC PATCH 5/5] added code and documentation to extend ovn-nbctl for port-chain, port-pair-groups, port-pairs and ACL SFC action

John McDowall jmcdowall at paloaltonetworks.com
Tue Dec 27 22:11:46 UTC 2016


These changes provide the necessary CLI support in ovn-nbctl to control the SFC.
There are new commands for port-chain, port-pair-groups, port-pairs and extensions
to ACLs to added a new action 'sfc'.

Co-authored-by: Flavio Fernandes <flavio at flaviof.com>
Reported at: https://mail.openvswitch.org/pipermail/ovs-discuss/2016-March/040381.html
Reported at: https://mail.openvswitch.org/pipermail/ovs-discuss/2016-May/041359.html

Signed-off-by: John McDowall <jmcdowall at paloaltonetworks.com>
---
 ovn/ovn-nb.xml            |  150 +++-
 ovn/utilities/ovn-nbctl.c | 1935 +++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 1943 insertions(+), 142 deletions(-)

diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index a3dc916a5..5a85e4ea7 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -120,7 +120,23 @@
         logical port.
       </p>
     </column>
-
+    <column name="port_chains">
+      <p>
+        The logical port-chains connected to the logical switch.
+      </p>
+      <p>
+        It is an error for multiple logical switches to include the same
+        logical port.
+      </p>
+    </column>
+    <column name="port_pairs">
+      <p>
+        The logical chains that define the service path.
+      </p>
+      <p>
+        Logical chains cannot currently cross logical switch boundaries.
+      </p>
+    </column>
     <column name="load_balancer">
       Load balance a virtual ipv4 address to a set of logical port endpoint
       ipv4 addresses.
@@ -155,7 +171,98 @@
       </column>
     </group>
   </table>
+  <table name="Logical_Port_Chain" title="Logical port chain">
+    <p>
+      Each row represents one logical port chain
+    </p>
+
+    <column name="name">
+      <p>
+        A name for the logical chain.  This name has no special meaning or purpose
+        other than to provide convenience for human interaction with the ovn-nb
+        database.  There is no requirement for the name to be unique.  The
+        logical chains's UUID should be used as the unique identifier.
+      </p>
+    </column>
+
+    <column name="port_pair_groups">
+      <p>
+        The logical list of port pairs that the flow goes through.
+      </p>
+
+      <p>
+        It is an error for a port pair group to be empty.
+      </p>
+    </column>
+    <column name="last_hop_port">
+      The <ref table="Logical_Switch_Port"/> to be used once packet reaches the end
+      of the chain.
+    </column>
+    <group title="Common Columns">
+      <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+  </table>
+  <table name="Logical_Port_Pair_Group" title="logical port pair groups">
+    <p>
+      An ordered port pair list
+    </p>
+
+    <column name="name">
+      <p>
+        Logical port pair group name
+      </p>
+    </column>
+
+    <column name="port_pairs">
+      <p>
+        port pair for this group
+      </p>
+    </column>
+
+    <column name="sortkey">
+      <p>
+        An integer used for ordering instances of <ref table="Logical_Port_Pair_Group"/>
+        in the <ref column="port_pairs" table="Logical_Port_Chain"/> column
+        of <ref table="Logical_Port_Chain"/>.
+      </p>
+    </column>
+    <group title="Common Columns">
+      <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+  </table>
+
+  <table name="Logical_Port_Pair" title="logical port pairs">
+    <p>
+      Ports pairs defining the service
+    </p>
 
+    <column name="name">
+      <p>
+        Logical port pair
+      </p>
+    </column>
+
+    <column name="outport">
+      <p>
+        Out logical port for this port pair. Can be the same value as inport.
+      </p>
+    </column>
+
+    <column name="inport">
+      <p>
+        In logical port for this port pair.
+      </p>
+    </column>
+    <group title="Common Columns">
+      <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+  </table>
   <table name="Logical_Switch_Port" title="L2 logical switch port">
     <p>
       A port within an L2 logical switch.
@@ -853,6 +960,13 @@
           ICMP unreachable message for other IP-based protocols.
           <code>Not implemented--currently treated as drop</code>
         </li>
+
+        <li>
+          <code>sfc</code>: Forward the packet into a logical port chain.
+          The chain to be used -- as well as any other attributes that determine
+          the behavior of the packet while in the chain -- are provided
+          via <ref column="options"/>.
+        </li>
       </ul>
     </column>
 
@@ -868,6 +982,40 @@
       </p>
     </column>
 
+    <group title="Options">
+      <column name = "options">
+        This column provides key/value settings specific to the ACL
+        <ref column="action"/>. The type-specific options are described
+        individually below.
+      </column>
+
+      <group title="Options for action sfc">
+        <p>
+          These options apply when <ref column="action"/> is <code>sfc</code>.
+        </p>
+
+        <column name="options" key="sfc-port-chain">
+          Required when <ref column="action"/> is <code>sfc</code>.
+          The uuid (or name) of the <ref table="Logical_Port_Chain"/> to be used.
+        </column>
+
+        <column name="options" key="sfc-bidirectional">
+          Optional and only applicable when <ref column="action"/> is <code>sfc</code>.
+          When set with value <code>true</code>, the implementation will also add rules to make packets
+          go through the chain in reverse direction. A restriction on making bidirectional chains is
+          that the inport parameter must be present in <ref column="match"/>, as it will be used as the
+          <ref table="Logical_Port_Chain" column="last_hop_port"/>. As expected, all <code>src*</code>
+          fields in <ref column="match"/> will be converted to <code>dst*</code> in order to derive the
+          reverse ACL.
+
+          <p>
+            sfc-bidirectional option is not yet implemented.
+          </p>
+        </column>
+      </group>
+
+    </group>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index af1eeab7f..dc3d45eea 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -323,8 +323,73 @@ Logical switch commands:\n\
   ls-del SWITCH             delete SWITCH and all its ports\n\
   ls-list                   print the names of all logical switches\n\
 \n\
+Logical port-chain commands:\n\
+  lport-chain-add LSWITCH [LPORT-CHAIN]     create a logical port-chain named LPORT-CHAIN\n\
+  lport-chain-del LPORT-CHAIN               delete LPORT-CHAIN but not FLOW-CLASSIFIER\n \
+  lport-chain-list LSWITCH                  print the names of all logical port-chains on LSWITCH\n\
+\n\
+Logical port-pair-groups commands:\n\
+  lport-pair-group-add LPORT-CHAIN LPORT-PAIR-GROUP-NAME\n\
+                           create a logical port-pair-group \n\
+  lport-pair-group-del LPORT-PAIR-GROUP-NAME    delete a port-pair-group, does not delete port-pairs\n\
+                                      or flow-classifier\n\
+  lport-pair-group-list LPORT-CHAIN   print the names of all logical port-pair-groups\n\
+  lport-pair-group-add-port-pair LPORT-PAIR-GROUP LPORT-PAIR add a port pair to a port-group\n\
+  lport-pair-group-del-port-pair LPORT-PAIR-GROUP LPORT-PAIR del a port pair from a port-group\n\
+\n\
+Logical port-pair commands:\n\
+  lport-pair-add LSWITCH LIN-PORT LOUT-PORT [LPORT-PAIR-NAME]\n\
+                                    create a logical port-pair \n\
+  lport-pair-del LPORT-PAIR-NAME    delete a port-pair, does not delete ports\n\
+  lport-pair-list                   print the names of all logical port-pairs\n\
+\n\
+Logical flow-classifier commands:\n\
+  lflow-classifier-add LPORT-CHAIN LIN-PORT [LFLOW-CLASSIFIER-NAME]\n\
+                                                  create a logical flow-classifer \n\
+  lflow-classifier-del LFLOW-CLASSIFIER-NAME      delete a flow-classifier, does not delete ports\n\
+  lflow-classifier-list LPORT-CHAIN               print the names of all logical flow-classifiers on a switch\n\
+  lflow-classifier-set-logical-destination-port LFLOW_CLASSIFIER [LDEST_PORT]\n\
+                                                  set the name of ldest port \n\
+  lflow-classifier-get-logical-destination-port LFLOW_CLASSIFIER\n\
+                                                  get the name of ldest port \n\
+\n\
+Logical router commands:\n\
+  lrouter-add [LROUTER]     create a logical router named LROUTER\n\
+  lrouter-del LROUTER       delete LROUTER and all its ports\n\
+  lrouter-list              print the names of all logical routers\n\
+\n\
+Logical port-chain commands:\n\
+  lsp-chain-add LSWITCH [LSP-CHAIN]     create a logical port-chain named LSP-CHAIN\n\
+  lsp-chain-del LSP-CHAIN               delete LSP-CHAIN but not FLOW-CLASSIFIER\n \
+  lsp-chain-list LSWITCH                print the names of all logical port-chains on LSWITCH\n\
+\n\
+Logical port-pair-groups commands:\n\
+  lsp-pair-group-add LSP-CHAIN LSP-PAIR-GROUP-NAME\n\
+                           create a logical port-pair-group \n\
+  lsp-pair-group-del LSP-PAIR-GROUP-NAME    delete a port-pair-group, does not delete port-pairs\n\
+                                      or flow-classifier\n\
+  lsp-pair-group-list LSP-CHAIN   print the names of all logical port-pair-groups\n\
+  lsp-pair-group-add-port-pair LSP-PAIR-GROUP LSP-PAIR add a port pair to a port-group\n\
+  lsp-pair-group-del-port-pair LSP-PAIR-GROUP LSP-PAIR del a port pair from a port-group\n\
+\n\
+Logical port-pair commands:\n\
+  lsp-pair-add LSWITCH LIN-PORT LOUT-PORT [LSP-PAIR-NAME]\n\
+                                    create a logical port-pair \n\
+  lsp-pair-del LSP-PAIR-NAME    delete a port-pair, does not delete ports\n\
+  lsp-pair-list                   print the names of all logical port-pairs\n\
+\n\
+Logical flow-classifier commands:\n\
+  lflow-classifier-add LSP-CHAIN LIN-PORT [LFLOW-CLASSIFIER-NAME]\n\
+                                                  create a logical flow-classifer \n\
+  lflow-classifier-del LFLOW-CLASSIFIER-NAME      delete a flow-classifier, does not delete ports\n\
+  lflow-classifier-list LSP-CHAIN               print the names of all logical flow-classifiers on a switch\n\
+  lflow-classifier-set-logical-destination-port LFLOW_CLASSIFIER [LDEST_PORT]\n\
+                                                  set the name of ldest port \n\
+  lflow-classifier-get-logical-destination-port LFLOW_CLASSIFIER\n\
+                                                  get the name of ldest port \n\
+\n\
 ACL commands:\n\
-  acl-add SWITCH DIRECTION PRIORITY MATCH ACTION [log]\n\
+  acl-add SWITCH DIRECTION PRIORITY MATCH ACTION [ACL-OPTIONS] [log]\n\
                             add an ACL to SWITCH\n\
   acl-del SWITCH [DIRECTION [PRIORITY MATCH]]\n\
                             remove ACLs from SWITCH\n\
@@ -360,6 +425,28 @@ Logical switch port commands:\n\
                             set dhcpv4 options for PORT\n\
   lsp-get-dhcpv4-options PORT  get the dhcpv4 options for PORT\n\
 \n\
+Logical port-chain commands:\n\
+  lsp-chain-add SWITCH [CHAIN] LAST_PORT create a logical port-chain [named LSP-CHAIN]\n\
+                                         that has LAST_PORT as last hop at the end of chain\n\
+  lsp-chain-del CHAIN                  delete LSP-CHAIN\n\
+  lsp-chain-list [SWITCH]              print the names of all logical port-chains [on SWITCH]\n\
+  lsp-chain-show SWITCH [CHAIN]        print details on port-chains on SWITCH\n\
+\n\
+Logical port-pair-groups commands:\n\
+  lsp-pair-group-add CHAIN [PAIR-GROUP [OFFSET]]\n\
+                    create a logical port-pair-group. Optionally, indicate the order it\n\
+                    should be in chain.\n\
+  lsp-pair-group-del PAIR-GROUP    delete a port-pair-group, does not delete port-pairs\n\
+  lsp-pair-group-list CHAIN    print port-pair-groups for a givan chain\n\
+  lsp-pair-group-add-port-pair PAIR-GROUP LSP-PAIR add a port pair to a port-pair-group\n\
+  lsp-pair-group-del-port-pair PAIR-GROUP LSP-PAIR del a port pair from a port-pair-group\n\
+\n\
+Logical port-pair commands:\n\
+  lsp-pair-add SWITCH PORT-IN PORT-OUT [LSP-PAIR]\n\
+                                     create a logical port-pair\n\
+  lsp-pair-del LSP-PAIR              delete a port-pair, does not delete ports\n\
+  lsp-pair-list [SWITCH [LSP-PAIR]]  print the names of all logical port-pairs\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\
@@ -522,6 +609,119 @@ ls_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist)
     return ls;
 }
 
+static const struct nbrec_logical_port_chain *
+lsp_chain_by_name_or_uuid(struct ctl_context *ctx, const char *id)
+{
+    const struct nbrec_logical_port_chain *lsp_chain = NULL;
+    bool is_uuid = false;
+    struct uuid lsp_chain_uuid;
+
+    if (uuid_from_string(&lsp_chain_uuid, id)) {
+        is_uuid = true;
+        lsp_chain = nbrec_logical_port_chain_get_for_uuid(ctx->idl,
+                                                          &lsp_chain_uuid);
+        printf("found lsp_chain %s\n",id);  // FIXME(ff): debug, remove this
+    }
+
+    if (!lsp_chain) {
+        NBREC_LOGICAL_PORT_CHAIN_FOR_EACH(lsp_chain, ctx->idl) {
+            if (!strcmp(lsp_chain->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lsp_chain) {
+        ctl_fatal("lsp_chain not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lsp_chain;
+}
+static const struct nbrec_logical_port_pair_group *
+lsp_pair_group_by_name_or_uuid(struct ctl_context *ctx, const char *id)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group = NULL;
+    bool is_uuid = false;
+    struct uuid lsp_pair_group_uuid;
+
+    if (uuid_from_string(&lsp_pair_group_uuid, id)) {
+        is_uuid = true;
+        lsp_pair_group = nbrec_logical_port_pair_group_get_for_uuid(ctx->idl,
+                                                                    &lsp_pair_group_uuid);
+        printf("Found lsp_pair_group %s\n",id);  // FIXME(ff): debug, remove this
+    }
+
+    if (!lsp_pair_group) {
+        NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH(lsp_pair_group, ctx->idl) {
+            if (!strcmp(lsp_pair_group->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lsp_pair_group) {
+        ctl_fatal("lsp_pair_group not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lsp_pair_group;
+}
+static const struct nbrec_logical_port_pair *
+lsp_pair_by_name_or_uuid(struct ctl_context *ctx, const char *id)
+{
+    const struct nbrec_logical_port_pair *lsp_pair = NULL;
+    bool is_uuid = false;
+    struct uuid lsp_pair_uuid;
+
+    if (uuid_from_string(&lsp_pair_uuid, id)) {
+        is_uuid = true;
+        lsp_pair = nbrec_logical_port_pair_get_for_uuid(ctx->idl,
+                                                        &lsp_pair_uuid);
+        printf("found lsp_pair %s\n",id);  // FIXME(ff): debug, remove this
+    }
+
+    if (!lsp_pair) {
+        NBREC_LOGICAL_PORT_PAIR_FOR_EACH(lsp_pair, ctx->idl) {
+            if (!strcmp(lsp_pair->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lsp_pair) {
+        ctl_fatal("lsp_pair not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lsp_pair;
+}
+static const struct nbrec_logical_flow_classifier *
+lflow_classifier_by_name_or_uuid(struct ctl_context *ctx, const char *id)
+{
+    const struct nbrec_logical_flow_classifier *lflow_classifier = NULL;
+    bool is_uuid = false;
+    struct uuid lflow_classifier_uuid;
+
+    if (uuid_from_string(&lflow_classifier_uuid, id)) {
+        is_uuid = true;
+        lflow_classifier = nbrec_logical_flow_classifier_get_for_uuid(ctx->idl,
+                                                                      &lflow_classifier_uuid);
+        printf("found lflow_classifier %s\n",id);  // FIXME(ff): debug, remove this
+    }
+
+    if (!lflow_classifier) {
+        NBREC_LOGICAL_FLOW_CLASSIFIER_FOR_EACH(lflow_classifier, ctx->idl) {
+            if (!strcmp(lflow_classifier->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lflow_classifier) {
+        ctl_fatal("lflow_classifier not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lflow_classifier;
+  }
+
 static const struct nbrec_load_balancer *
 lb_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist)
 {
@@ -548,217 +748,1560 @@ lb_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist)
         }
     }
 
-    if (!lb && must_exist) {
-        ctl_fatal("%s: load balancer %s not found", id,
-                is_uuid ? "UUID" : "name");
+    if (!lb && must_exist) {
+        ctl_fatal("%s: load balancer %s not found", id,
+                is_uuid ? "UUID" : "name");
+    }
+
+    return lb;
+}
+
+/* Given pointer to logical router, this routine prints the router
+ * information.  */
+static void
+print_lr(const struct nbrec_logical_router *lr, struct ds *s)
+{
+    ds_put_format(s, "    router "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&lr->header_.uuid), lr->name);
+
+    for (size_t i = 0; i < lr->n_ports; i++) {
+        const struct nbrec_logical_router_port *lrp = lr->ports[i];
+        ds_put_format(s, "        port %s\n", lrp->name);
+        if (lrp->mac) {
+            ds_put_cstr(s, "            mac: ");
+            ds_put_format(s, "\"%s\"\n", lrp->mac);
+        }
+        if (lrp->n_networks) {
+            ds_put_cstr(s, "            networks: [");
+            for (size_t j = 0; j < lrp->n_networks; j++) {
+                ds_put_format(s, "%s\"%s\"",
+                        j == 0 ? "" : ", ",
+                        lrp->networks[j]);
+            }
+            ds_put_cstr(s, "]\n");
+        }
+    }
+}
+
+static void
+print_ls(const struct nbrec_logical_switch *ls, struct ds *s)
+{
+    ds_put_format(s, "    switch "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&ls->header_.uuid), ls->name);
+
+    for (size_t i = 0; i < ls->n_ports; i++) {
+        const struct nbrec_logical_switch_port *lsp = ls->ports[i];
+
+        ds_put_format(s, "        port %s\n", lsp->name);
+        if (lsp->parent_name) {
+            ds_put_format(s, "            parent: %s\n", lsp->parent_name);
+        }
+        if (lsp->n_tag) {
+            ds_put_format(s, "            tag: %"PRIu64"\n", lsp->tag[0]);
+        }
+        if (lsp->n_addresses) {
+            ds_put_cstr(s, "            addresses: [");
+            for (size_t j = 0; j < lsp->n_addresses; j++) {
+                ds_put_format(s, "%s\"%s\"",
+                        j == 0 ? "" : ", ",
+                        lsp->addresses[j]);
+            }
+            ds_put_cstr(s, "]\n");
+        }
+    }
+}
+
+static void
+nbctl_init(struct ctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
+nbctl_pre_sync(struct ctl_context *ctx OVS_UNUSED)
+{
+    if (wait_type != NBCTL_WAIT_NONE) {
+        force_wait = true;
+    } else {
+        VLOG_INFO("\"sync\" command has no effect without --wait");
+    }
+}
+
+static void
+nbctl_sync(struct ctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
+nbctl_show(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *ls;
+
+    if (ctx->argc == 2) {
+        ls = ls_by_name_or_uuid(ctx, ctx->argv[1], false);
+        if (ls) {
+            print_ls(ls, &ctx->output);
+        }
+    } else {
+        NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
+            print_ls(ls, &ctx->output);
+        }
+    }
+    const struct nbrec_logical_router *lr;
+
+    if (ctx->argc == 2) {
+        lr = lr_by_name_or_uuid(ctx, ctx->argv[1], false);
+        if (lr) {
+            print_lr(lr, &ctx->output);
+        }
+    } else {
+        NBREC_LOGICAL_ROUTER_FOR_EACH(lr, ctx->idl) {
+            print_lr(lr, &ctx->output);
+        }
+    }
+}
+
+static void
+nbctl_ls_add(struct ctl_context *ctx)
+{
+    const char *ls_name = ctx->argc == 2 ? ctx->argv[1] : NULL;
+
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
+    if (may_exist && add_duplicate) {
+        ctl_fatal("--may-exist and --add-duplicate may not be used together");
+    }
+
+    if (ls_name) {
+        if (!add_duplicate) {
+            const struct nbrec_logical_switch *ls;
+            NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) {
+                if (!strcmp(ls->name, ls_name)) {
+                    if (may_exist) {
+                        return;
+                    }
+                    ctl_fatal("%s: a switch with this name already exists",
+                              ls_name);
+                }
+            }
+        }
+    } else if (may_exist) {
+        ctl_fatal("--may-exist requires specifying a name");
+    } else if (add_duplicate) {
+        ctl_fatal("--add-duplicate requires specifying a name");
+    }
+
+    struct nbrec_logical_switch *ls;
+    ls = nbrec_logical_switch_insert(ctx->txn);
+    if (ls_name) {
+        nbrec_logical_switch_set_name(ls, ls_name);
+    }
+}
+
+static void
+nbctl_ls_del(struct ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch *ls;
+
+    ls = ls_by_name_or_uuid(ctx, id, must_exist);
+    if (!ls) {
+        return;
+    }
+
+    nbrec_logical_switch_delete(ls);
+}
+
+static void
+nbctl_ls_list(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *ls;
+    struct smap lswitches;
+
+    smap_init(&lswitches);
+    NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
+        smap_add_format(&lswitches, ls->name, UUID_FMT " (%s)",
+                        UUID_ARGS(&ls->header_.uuid), ls->name);
+    }
+    const struct smap_node **nodes = smap_sort(&lswitches);
+    for (size_t i = 0; i < smap_count(&lswitches); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&lswitches);
+    free(nodes);
+}
+
+/*
+ * Port chain CLI Functions
+ */
+static void
+nbctl_lsp_chain_add(struct ctl_context *ctx)
+{
+
+    const struct nbrec_logical_switch *lswitch;
+
+    if (ctx->argc < 2) {
+        /* ensure all arguments are present */
+        ctl_fatal("Invalid number of arguments: (%d), to lsp-chain-add.",ctx->argc);
+    }
+
+    const char *lsp_chain_name = ctx->argc == 3 ? ctx->argv[2] : NULL;
+    lswitch = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+    if (lsp_chain_name) {
+        const struct nbrec_logical_port_chain *lsp_chain;
+        NBREC_LOGICAL_PORT_CHAIN_FOR_EACH(lsp_chain, ctx->idl) {
+            if (strcmp(lsp_chain->name, lsp_chain_name))
+                ctl_fatal("%s: an lsp_chain with this name already exists",
+                          lsp_chain_name);
+        }
+    }
+    struct nbrec_logical_port_chain *lsp_chain;
+    lsp_chain = nbrec_logical_port_chain_insert(ctx->txn);
+    if (lsp_chain_name) {
+        nbrec_logical_port_chain_set_name(lsp_chain, lsp_chain_name);
+    }
+
+    /* Insert the logical port-chain into the logical switch. */
+
+    nbrec_logical_switch_verify_port_chains(lswitch);
+    struct nbrec_logical_port_chain  **new_port_chain = xmalloc(sizeof *new_port_chain *
+                                                                (lswitch->n_port_chains + 1));
+    memcpy(new_port_chain, lswitch->port_chains, sizeof *new_port_chain * lswitch->n_port_chains);
+    new_port_chain[lswitch->n_port_chains] = CONST_CAST(struct nbrec_logical_port_chain *, lsp_chain);
+    nbrec_logical_switch_set_port_chains(lswitch, new_port_chain, lswitch->n_port_chains + 1);
+    free(new_port_chain);
+}
+
+/* Removes lswitch->pair_chain[idx]'. */
+static void
+remove_lsp_chain(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+    const struct nbrec_logical_port_chain *lsp_chain = lswitch->port_chains[idx];
+
+    /* First remove 'lsp-chain' from the array of port-chains.  This is what will
+     * actually cause the logical port-chain to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_chain **new_port_chain
+        = xmemdup(lswitch->port_chains, sizeof *new_port_chain * lswitch->n_port_chains);
+    new_port_chain[idx] = new_port_chain[lswitch->n_port_chains - 1];
+    nbrec_logical_switch_verify_port_chains(lswitch);
+    nbrec_logical_switch_set_port_chains(lswitch, new_port_chain, lswitch->n_port_chains - 1);
+    free(new_port_chain);
+
+    /* Delete 'lsp-chain' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_CHAIN_FOR_EACH later. */
+    nbrec_logical_port_chain_delete(lsp_chain);
+}
+
+static void
+nbctl_lsp_chain_del(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_chain *lsp_chain;
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lsp_chain) {
+        ctl_fatal("Cannot find lsp_chain: %s\n", ctx->argv[1]);
+    }
+
+    /* Find the lswitch that contains 'port-chain', then delete it. */
+    const struct nbrec_logical_switch *lswitch;
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
+        for (size_t i = 0; i < lswitch->n_port_chains; i++) {
+            if (lswitch->port_chains[i] == lsp_chain) {
+                remove_lsp_chain(lswitch,i);
+                printf("Deleted lsp-chain: %s\n", ctx->argv[1]);  // FIXME(ff): debug, remove this
+                return;
+            }
+        }
+    }
+}
+
+static const struct nbrec_logical_switch_port *
+lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id,
+                    bool must_exist)
+{
+    const struct nbrec_logical_switch_port *lsp = NULL;
+
+    struct uuid lsp_uuid;
+    bool is_uuid = uuid_from_string(&lsp_uuid, id);
+    if (is_uuid) {
+        lsp = nbrec_logical_switch_port_get_for_uuid(ctx->idl, &lsp_uuid);
+    }
+
+    if (!lsp) {
+        NBREC_LOGICAL_SWITCH_PORT_FOR_EACH(lsp, ctx->idl) {
+            if (!strcmp(lsp->name, id)) {
+                break;
+            }
+        }
+    }
+
+    if (!lsp && must_exist) {
+        ctl_fatal("%s: port %s not found", id, is_uuid ? "UUID" : "name");
+    }
+
+    return lsp;
+
+}
+
+/*
+ * Port chain CLI Functions
+ */
+static const struct nbrec_logical_port_chain *
+lsp_chain_by_name_or_uuid(struct ctl_context *ctx, const char *id, const bool must_exist)
+{
+    const struct nbrec_logical_port_chain *lsp_chain = NULL;
+    bool is_uuid = false;
+    struct uuid lsp_chain_uuid;
+
+    if (uuid_from_string(&lsp_chain_uuid, id)) {
+        is_uuid = true;
+        lsp_chain = nbrec_logical_port_chain_get_for_uuid(ctx->idl,
+                                                          &lsp_chain_uuid);
+    }
+
+    if (!lsp_chain) {
+        NBREC_LOGICAL_PORT_CHAIN_FOR_EACH(lsp_chain, ctx->idl) {
+            if (!strcmp(lsp_chain->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lsp_chain && must_exist) {
+        ctl_fatal("lsp_chain not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lsp_chain;
+}
+static const struct nbrec_logical_port_pair_group *
+lsp_pair_group_by_name_or_uuid(struct ctl_context *ctx, const char *id, const bool must_exist)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group = NULL;
+    bool is_uuid = false;
+    struct uuid lsp_pair_group_uuid;
+
+    if (uuid_from_string(&lsp_pair_group_uuid, id)) {
+        is_uuid = true;
+        lsp_pair_group = nbrec_logical_port_pair_group_get_for_uuid(ctx->idl,
+                                                                    &lsp_pair_group_uuid);
+    }
+
+    if (!lsp_pair_group) {
+        NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH(lsp_pair_group, ctx->idl) {
+            if (!strcmp(lsp_pair_group->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lsp_pair_group && must_exist) {
+        ctl_fatal("lsp_pair_group not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lsp_pair_group;
+}
+
+static const struct nbrec_logical_port_pair *
+lsp_pair_by_name_or_uuid(struct ctl_context *ctx, const char *id, const bool must_exist)
+{
+    const struct nbrec_logical_port_pair *lsp_pair = NULL;
+    bool is_uuid = false;
+    struct uuid lsp_pair_uuid;
+
+    if (uuid_from_string(&lsp_pair_uuid, id)) {
+        is_uuid = true;
+        lsp_pair = nbrec_logical_port_pair_get_for_uuid(ctx->idl,
+                                                        &lsp_pair_uuid);
+    }
+
+    if (!lsp_pair) {
+        NBREC_LOGICAL_PORT_PAIR_FOR_EACH(lsp_pair, ctx->idl) {
+            if (!strcmp(lsp_pair->name, id)) {
+                break;
+            }
+        }
+    }
+    if (!lsp_pair && must_exist) {
+        ctl_fatal("lsp_pair not found for %s: '%s'",
+                  is_uuid ? "UUID" : "name", id);
+    }
+
+    return lsp_pair;
+}
+
+
+static void
+nbctl_lsp_chain_add(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *lswitch;
+    const struct nbrec_logical_switch_port *last_hop_lsp;
+
+    lswitch = ls_by_name_or_uuid(ctx, ctx->argv[1], true /*must_exist*/);
+    const char *lsp_chain_name = ctx->argc > 3 ? ctx->argv[2] : NULL;
+    const char *last_hop_lsp_name = lsp_chain_name ? ctx->argv[3] : ctx->argv[2];
+
+    const bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    const bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
+    if (may_exist && add_duplicate) {
+        ctl_fatal("--may-exist and --add-duplicate may not be used together");
+    }
+
+    last_hop_lsp = lsp_by_name_or_uuid(ctx, last_hop_lsp_name, true);
+
+    if (lsp_chain_name) {
+        if (!add_duplicate) {
+            const struct nbrec_logical_port_chain *lsp_chain;
+            NBREC_LOGICAL_PORT_CHAIN_FOR_EACH(lsp_chain, ctx->idl) {
+                if (!strcmp(lsp_chain->name, lsp_chain_name)) {
+                    if (may_exist) {
+                        return;
+                    }
+                    ctl_fatal("%s: an lsp_chain with this name already exists",
+                              lsp_chain_name);
+                }
+            }
+        }
+    } else if (may_exist) {
+        ctl_fatal("--may-exist requires specifying a name");
+    } else if (add_duplicate) {
+        ctl_fatal("--add-duplicate requires specifying a name");
+    }
+
+    struct nbrec_logical_port_chain *lsp_chain;
+    lsp_chain = nbrec_logical_port_chain_insert(ctx->txn);
+    if (lsp_chain_name) {
+        nbrec_logical_port_chain_set_name(lsp_chain, lsp_chain_name);
+    }
+    nbrec_logical_port_chain_set_last_hop_port(lsp_chain, last_hop_lsp);
+
+    /* Insert the logical port-chain into the logical switch. */
+
+    nbrec_logical_switch_verify_port_chains(lswitch);
+    struct nbrec_logical_port_chain  **new_port_chain = xmalloc(sizeof *new_port_chain *
+                                                                (lswitch->n_port_chains + 1));
+    memcpy(new_port_chain, lswitch->port_chains, sizeof *new_port_chain * lswitch->n_port_chains);
+    new_port_chain[lswitch->n_port_chains] = CONST_CAST(struct nbrec_logical_port_chain *, lsp_chain);
+    nbrec_logical_switch_set_port_chains(lswitch, new_port_chain, lswitch->n_port_chains + 1);
+    free(new_port_chain);
+}
+
+/* Removes lswitch->pair_chain[idx]'. */
+static void
+remove_lsp_chain(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+    const struct nbrec_logical_port_chain *lsp_chain = lswitch->port_chains[idx];
+
+    /* First remove 'lsp-chain' from the array of port-chains.  This is what will
+     * actually cause the logical port-chain to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_chain **new_port_chain
+        = xmemdup(lswitch->port_chains, sizeof *new_port_chain * lswitch->n_port_chains);
+    new_port_chain[idx] = new_port_chain[lswitch->n_port_chains - 1];
+    nbrec_logical_switch_verify_port_chains(lswitch);
+    nbrec_logical_switch_set_port_chains(lswitch, new_port_chain, lswitch->n_port_chains - 1);
+    free(new_port_chain);
+
+    /* Delete 'lsp-chain' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_CHAIN_FOR_EACH later. */
+    nbrec_logical_port_chain_delete(lsp_chain);
+}
+
+static void
+nbctl_lsp_chain_del(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_chain *lsp_chain;
+    const bool must_exist = !shash_find(&ctx->options, "--if-exists");
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1], must_exist);
+    if (!lsp_chain) {
+        return;
+    }
+
+    /* Find the lswitch that contains 'port-chain', then delete it. */
+    const struct nbrec_logical_switch *lswitch;
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
+        for (size_t i = 0; i < lswitch->n_port_chains; i++) {
+            if (lswitch->port_chains[i] == lsp_chain) {
+                remove_lsp_chain(lswitch,i);
+                return;
+            }
+        }
+    }
+}
+
+static void
+print_lsp_chain_entry(struct ctl_context *ctx,
+                      const struct nbrec_logical_switch *lswitch,
+                      const char *chain_name_filter,
+                      const bool show_switch_name)
+{
+    struct smap lsp_chains;
+    size_t i;
+
+    smap_init(&lsp_chains);
+    for (i = 0; i < lswitch->n_port_chains; i++) {
+        const struct nbrec_logical_port_chain *lsp_chain = lswitch->port_chains[i];
+        if (chain_name_filter && strcmp(chain_name_filter, lsp_chain->name)) {
+            continue;
+        }
+        if (show_switch_name) {
+            smap_add_format(&lsp_chains, lsp_chain->name, UUID_FMT " (%s:%s)",
+                            UUID_ARGS(&lsp_chain->header_.uuid),
+                            lswitch->name, lsp_chain->name);
+        } else {
+            smap_add_format(&lsp_chains, lsp_chain->name, UUID_FMT " (%s)",
+                            UUID_ARGS(&lsp_chain->header_.uuid), lsp_chain->name);
+        }
+    }
+
+    const struct smap_node **nodes = smap_sort(&lsp_chains);
+    for (i = 0; i < smap_count(&lsp_chains); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&lsp_chains);
+    free(nodes);
+}
+
+static void
+nbctl_lsp_chain_list(struct ctl_context *ctx)
+{
+    const char *id = ctx->argc > 1 ? ctx->argv[1] : NULL;
+    const char *chain_name_filter = ctx->argc > 2 ? ctx->argv[2] : NULL;
+    const struct nbrec_logical_switch *lswitch;
+
+    if (id) {
+        lswitch = ls_by_name_or_uuid(ctx, id, true);
+        print_lsp_chain_entry(ctx, lswitch, chain_name_filter, false);
+    } else {
+        NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, ctx->idl) {
+            if (lswitch->n_port_chains == 0) {
+                continue;
+            }
+            print_lsp_chain_entry(ctx, lswitch, chain_name_filter, true);
+        }
+    }
+}
+
+static void
+print_lsp_chain(const struct nbrec_logical_port_chain *lsp_chain,
+                  struct ctl_context *ctx)
+{
+    ds_put_format(&ctx->output, "lsp-chain "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&lsp_chain->header_.uuid), lsp_chain->name);
+
+    for (size_t i = 0; i < lsp_chain->n_port_pair_groups; i++) {
+        const struct nbrec_logical_port_pair_group *lsp_pair_group
+            = lsp_chain->port_pair_groups[i];
+        ds_put_format(&ctx->output, "    lsp-pair-group %s\n", lsp_pair_group->name);
+        for (size_t j = 0; j < lsp_pair_group->n_port_pairs; j++){
+            const struct nbrec_logical_port_pair *lsp_pair = lsp_pair_group->port_pairs[j];
+            ds_put_format(&ctx->output, "        lsp-pair %s\n", lsp_pair->name);
+
+            const struct nbrec_logical_switch_port *linport  = lsp_pair->inport;
+            if (linport) {
+                ds_put_format(&ctx->output, "            lsp-pair inport "UUID_FMT" (%s)\n",
+                              UUID_ARGS(&linport->header_.uuid), linport->name);
+            }
+
+            const struct nbrec_logical_switch_port *loutport = lsp_pair->outport;
+            if (loutport) {
+                ds_put_format(&ctx->output, "            lsp-pair outport "UUID_FMT" (%s)\n",
+                              UUID_ARGS(&loutport->header_.uuid), loutport->name);
+            }
+        }
+    }
+
+    // TODO: iterate ACLs and display the ones that have action 'sfc' and use this lsp_chain
+}
+
+static void
+nbctl_lsp_chain_show(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_chain *lsp_chain;
+
+    if (ctx->argc == 2) {
+        lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1], false);
+        if (lsp_chain) {
+            print_lsp_chain(lsp_chain, ctx);
+        }
+    } else {
+        NBREC_LOGICAL_PORT_CHAIN_FOR_EACH(lsp_chain, ctx->idl) {
+            print_lsp_chain(lsp_chain, ctx);
+        }
+    }
+}
+/* End of port-chain operations */
+
+/*
+ * Port Pair Groups CLI Functions
+ */
+static void
+nbctl_lsp_pair_group_add(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    const char *ppg_name = ctx->argc >= 3 ? ctx->argv[2] : NULL;
+
+    const bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    const bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
+    if (may_exist && add_duplicate) {
+        ctl_fatal("--may-exist and --add-duplicate may not be used together");
+    }
+
+    if (ppg_name) {
+        if (!add_duplicate) {
+            NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH(lsp_pair_group, ctx->idl) {
+                if (!strcmp(lsp_pair_group->name, ppg_name)) {
+                    if (may_exist) {
+                        return;
+                    }
+                    ctl_fatal("%s: an lsp_port_pair_group with this name already exists",
+                              ppg_name);
+                }
+            }
+        }
+    } else if (may_exist) {
+        ctl_fatal("--may-exist requires specifying a name");
+    } else if (add_duplicate) {
+        ctl_fatal("--add-duplicate requires specifying a name");
+    }
+
+    /* check lsp_chain exists */
+    const struct nbrec_logical_port_chain *lsp_chain;
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1], true);
+    if (!lsp_chain) {
+        return;
+    }
+
+    /* create the logical port-pair-group. */
+    lsp_pair_group = nbrec_logical_port_pair_group_insert(ctx->txn);
+    if (ppg_name) {
+        nbrec_logical_port_pair_group_set_name(lsp_pair_group, ctx->argv[2]);
+    }
+
+    int64_t sortkey = (int64_t) lsp_chain->n_port_pair_groups + 1;
+    if (ctx->argc >= 4) {
+        sortkey = (int64_t) atoi(ctx->argv[3]);
+    }
+    nbrec_logical_port_pair_group_set_sortkey(lsp_pair_group, &sortkey, 1);
+
+    /* Insert the logical port-pair-group into the logical switch. */
+    nbrec_logical_port_chain_verify_port_pair_groups(lsp_chain);
+    struct nbrec_logical_port_pair_group  **new_port_pair_group = xmalloc(sizeof *new_port_pair_group *
+                                                                          (lsp_chain->n_port_pair_groups + 1));
+    memcpy(new_port_pair_group, lsp_chain->port_pair_groups, sizeof *new_port_pair_group * lsp_chain->n_port_pair_groups);
+    new_port_pair_group[lsp_chain->n_port_pair_groups] =
+        CONST_CAST(struct nbrec_logical_port_pair_group *,lsp_pair_group);
+    nbrec_logical_port_chain_set_port_pair_groups(lsp_chain, new_port_pair_group, lsp_chain->n_port_pair_groups + 1);
+    free(new_port_pair_group);
+}
+
+/* Removes lsp-pair-group 'lsp_chain->port_pair_group[idx]'. */
+static void
+remove_lsp_pair_group(const struct nbrec_logical_port_chain *lsp_chain, size_t idx)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group = lsp_chain->port_pair_groups[idx];
+
+    /* First remove 'lsp-pair-group' from the array of port-pair-groups.  This is what will
+     * actually cause the logical port-pair-group to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_pair_group **new_port_pair_group
+        = xmemdup(lsp_chain->port_pair_groups, sizeof *new_port_pair_group * lsp_chain->n_port_pair_groups);
+    new_port_pair_group[idx] = new_port_pair_group[lsp_chain->n_port_pair_groups - 1];
+    nbrec_logical_port_chain_verify_port_pair_groups(lsp_chain);
+    nbrec_logical_port_chain_set_port_pair_groups(lsp_chain, new_port_pair_group, lsp_chain->n_port_pair_groups - 1);
+    free(new_port_pair_group);
+
+    /* Delete 'lsp-pair-group' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH later. */
+    nbrec_logical_port_pair_group_delete(lsp_pair_group);
+}
+
+static void
+nbctl_lsp_pair_group_del(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    const bool must_exist = !shash_find(&ctx->options, "--if-exists");
+
+    lsp_pair_group = lsp_pair_group_by_name_or_uuid(ctx, ctx->argv[1], must_exist);
+    if (!lsp_pair_group) {
+        return;
+    }
+
+    /* Find the port-chain that contains 'port-pair-group', then delete it. */
+    const struct nbrec_logical_port_chain *lsp_chain;
+    NBREC_LOGICAL_PORT_CHAIN_FOR_EACH (lsp_chain, ctx->idl) {
+        for (size_t i = 0; i < lsp_chain->n_port_pair_groups; i++) {
+            if (lsp_chain->port_pair_groups[i] == lsp_pair_group) {
+                remove_lsp_pair_group(lsp_chain,i);
+                return;
+            }
+        }
+    }
+    if (must_exist) {
+        ctl_fatal("logical port-pair-group %s is not part of any logical port-chain",
+                  ctx->argv[1]);
+    }
+}
+
+static void
+nbctl_lsp_pair_group_list(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port_chain *lsp_chain;
+    struct smap lsp_pair_groups;
+    size_t i;
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, id, true);
+    if (!lsp_chain) {
+        return;
+    }
+
+    smap_init(&lsp_pair_groups);
+    for (i = 0; i < lsp_chain->n_port_pair_groups; i++) {
+        const struct nbrec_logical_port_pair_group *lsp_pair_group = lsp_chain->port_pair_groups[i];
+        smap_add_format(&lsp_pair_groups, lsp_pair_group->name, UUID_FMT " (%s)",
+                        UUID_ARGS(&lsp_pair_group->header_.uuid), lsp_pair_group->name);
+    }
+    const struct smap_node **nodes = smap_sort(&lsp_pair_groups);
+    for (i = 0; i < smap_count(&lsp_pair_groups); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&lsp_pair_groups);
+    free(nodes);
+}
+
+static void
+nbctl_lsp_pair_group_add_port_pair(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    const struct nbrec_logical_port_pair *lsp_pair;
+    const bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+
+    lsp_pair_group = lsp_pair_group_by_name_or_uuid(ctx, ctx->argv[1], true);
+    if (!lsp_pair_group) {
+        return;
+    }
+
+    /* Check that port-pair exists  */
+    lsp_pair = lsp_pair_by_name_or_uuid(ctx, ctx->argv[2], true);
+    if (!lsp_pair){
+        return;
+    }
+
+    /* Do not add port pair more than once in a given port-pair-group */
+    for (size_t i = 0; i < lsp_pair_group->n_port_pairs; i++) {
+        if (lsp_pair_group->port_pairs[i] == lsp_pair) {
+            if (!may_exist) {
+                ctl_fatal("lsp_pair: %s is already added to port-pair-group %s\n", ctx->argv[2], ctx->argv[1]);
+            }
+            return;
+        }
+    }
+
+    /* Insert the logical port-pair into the logical port-pair-group. */
+    nbrec_logical_port_pair_group_verify_port_pairs(lsp_pair_group);
+    struct nbrec_logical_port_pair  **new_port_pair = xmalloc(sizeof *new_port_pair *
+                                                              (lsp_pair_group->n_port_pairs + 1));
+    memcpy(new_port_pair, lsp_pair_group->port_pairs, sizeof *new_port_pair * lsp_pair_group->n_port_pairs);
+    new_port_pair[lsp_pair_group->n_port_pairs] = CONST_CAST(struct nbrec_logical_port_pair *, lsp_pair);
+    nbrec_logical_port_pair_group_set_port_pairs(lsp_pair_group, new_port_pair, lsp_pair_group->n_port_pairs + 1);
+    free(new_port_pair);
+}
+
+/* Removes port-pair from port-pair-groiup but does not delete it'. */
+static void
+remove_lsp_pair_from_port_pair_group(const struct nbrec_logical_port_pair_group *lsp_pair_group, size_t idx)
+{
+    //TODO Check const struct nbrec_logical_port_pair *lsp_pair = lsp_pair_group->port_pairs[idx];
+
+    /* First remove 'lsp-pair' from the array of port-pairs.  This is what will
+     * actually cause the logical port-pair to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_pair **new_port_pair
+        = xmemdup(lsp_pair_group->port_pairs, sizeof *new_port_pair * lsp_pair_group->n_port_pairs);
+    new_port_pair[idx] = new_port_pair[lsp_pair_group->n_port_pairs - 1];
+    nbrec_logical_port_pair_group_verify_port_pairs(lsp_pair_group);
+    nbrec_logical_port_pair_group_set_port_pairs(lsp_pair_group, new_port_pair, lsp_pair_group->n_port_pairs - 1);
+    free(new_port_pair);
+
+    /* Do not delete actual port-pair as they are owned by a lswitch and can be reused. */
+    //nbrec_logical_port_pair_delete(lsp_pair);
+}
+
+static void
+nbctl_lsp_pair_group_del_port_pair(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_pair *lsp_pair;
+    const bool must_exist = !shash_find(&ctx->options, "--if-exists");
+
+    lsp_pair = lsp_pair_by_name_or_uuid(ctx, ctx->argv[1], must_exist);
+    if (!lsp_pair) {
+        return;
+    }
+
+    /* Find the port-pair_group that contains 'port-pair', then delete it. */
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH (lsp_pair_group, ctx->idl) {
+        for (size_t i = 0; i < lsp_pair_group->n_port_pairs; i++) {
+            if (lsp_pair_group->port_pairs[i] == lsp_pair) {
+                remove_lsp_pair_from_port_pair_group(lsp_pair_group,i);
+                return;
+            }
+        }
+    }
+    if (must_exist) {
+        ctl_fatal("logical port-pair %s is not part of any logical switch",
+                  ctx->argv[1]);
+    }
+}
+/* End of port-pair-group operations */
+
+/*
+ * port-pair operations
+ */
+static void
+nbctl_lsp_pair_add(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *lswitch;
+    const struct nbrec_logical_switch_port *lsp_in,*lsp_out;
+    const struct nbrec_logical_port_pair *lsp_pair;
+
+    const bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    const bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
+
+    lswitch = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+    lsp_in = lsp_by_name_or_uuid(ctx, ctx->argv[2], true);
+    lsp_out = lsp_by_name_or_uuid(ctx, ctx->argv[3], true);
+
+    const char *lsp_pair_name = ctx->argc >= 5 ? ctx->argv[4] : NULL;
+    if (may_exist && add_duplicate) {
+        ctl_fatal("--may-exist and --add-duplicate may not be used together");
+    }
+
+    if (lsp_pair_name) {
+        if (!add_duplicate) {
+            NBREC_LOGICAL_PORT_PAIR_FOR_EACH(lsp_pair, ctx->idl) {
+                if (!strcmp(lsp_pair->name, lsp_pair_name)) {
+                    if (may_exist) {
+                        return;
+                    }
+                    ctl_fatal("%s: an lsp_pair with this name already exists",
+                              lsp_pair_name);
+                }
+            }
+        }
+    } else if (may_exist) {
+        ctl_fatal("--may-exist requires specifying a name");
+    } else if (add_duplicate) {
+        ctl_fatal("--add-duplicate requires specifying a name");
+    }
+
+    /* create the logical port-pair. */
+    lsp_pair = nbrec_logical_port_pair_insert(ctx->txn);
+    nbrec_logical_port_pair_set_inport(lsp_pair, lsp_in);
+    nbrec_logical_port_pair_set_outport(lsp_pair, lsp_out);
+    if (lsp_pair_name) {
+        nbrec_logical_port_pair_set_name(lsp_pair, lsp_pair_name);
+    }
+
+    /* Insert the logical port-pair into the logical port-pair-group. */
+    nbrec_logical_switch_verify_port_pairs(lswitch);
+    struct nbrec_logical_port_pair  **new_port_pair = xmalloc(sizeof *new_port_pair *
+                                                              (lswitch->n_port_pairs + 1));
+    memcpy(new_port_pair, lswitch->port_pairs, sizeof *new_port_pair * lswitch->n_port_pairs);
+    new_port_pair[lswitch->n_port_pairs] = CONST_CAST(struct nbrec_logical_port_pair *, lsp_pair);
+    nbrec_logical_switch_set_port_pairs(lswitch, new_port_pair, lswitch->n_port_pairs + 1);
+    free(new_port_pair);
+}
+/* Removes lswitch->pair_pair[idx]'. */
+static void
+remove_lsp_pair(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+    const struct nbrec_logical_port_pair *lsp_pair = lswitch->port_pairs[idx];
+
+    /* First remove 'lsp-pair' from the array of port-pairs.  This is what will
+     * actually cause the logical port-pair to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_pair **new_port_pair
+        = xmemdup(lswitch->port_pairs, sizeof *new_port_pair * lswitch->n_port_pairs);
+    new_port_pair[idx] = new_port_pair[lswitch->n_port_pairs - 1];
+    nbrec_logical_switch_verify_port_pairs(lswitch);
+    nbrec_logical_switch_set_port_pairs(lswitch, new_port_pair, lswitch->n_port_pairs - 1);
+    free(new_port_pair);
+
+    /* Delete 'lsp-pair' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_PAIR_FOR_EACH later. */
+    nbrec_logical_port_pair_delete(lsp_pair);
+}
+
+static void
+nbctl_lsp_pair_del(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_pair *lsp_pair;
+    const bool must_exist = !shash_find(&ctx->options, "--if-exists");
+
+    lsp_pair = lsp_pair_by_name_or_uuid(ctx, ctx->argv[1], must_exist);
+    if (!lsp_pair) {
+        if (must_exist) {
+            ctl_fatal("Cannot find lsp_pair: %s\n", ctx->argv[1]);
+        }
+    }
+
+    /* Find the port-pair_group that contains 'port-pair', then delete it. */
+    const struct nbrec_logical_switch *lswitch;
+    NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, ctx->idl) {
+        for (size_t i = 0; i < lswitch->n_port_pairs; i++) {
+            if (lswitch->port_pairs[i] == lsp_pair) {
+                remove_lsp_pair(lswitch,i);
+                return;
+            }
+        }
+    }
+    if (must_exist) {
+        ctl_fatal("logical port-pair %s is not part of any logical switch",
+                  ctx->argv[1]);
+    }
+}
+
+static void
+print_lsp_pairs_for_switch(struct ctl_context *ctx,
+                           const struct nbrec_logical_switch *lswitch,
+                           const char *ppair_name_filter,
+                           const bool show_switch_name)
+{
+    struct smap lsp_pairs;
+    size_t i;
+
+    smap_init(&lsp_pairs);
+    for (i = 0; i < lswitch->n_port_pairs; i++) {
+        const struct nbrec_logical_port_pair *lsp_pair = lswitch->port_pairs[i];
+        if (ppair_name_filter && strcmp(ppair_name_filter, lsp_pair->name)) {
+            continue;
+        }
+        const struct nbrec_logical_switch_port *linport  = lsp_pair->inport;
+        const struct nbrec_logical_switch_port *loutport = lsp_pair->outport;
+        const char *linport_name = linport ? linport->name : "<not_set>";
+        const char *loutport_name = loutport ? loutport->name : "<not_set>";
+
+        if (show_switch_name) {
+            smap_add_format(&lsp_pairs, lsp_pair->name, UUID_FMT " (%s:%s) in:%s out:%s",
+                            UUID_ARGS(&lsp_pair->header_.uuid), lswitch->name,
+                            lsp_pair->name, linport_name, loutport_name);
+        } else {
+            smap_add_format(&lsp_pairs, lsp_pair->name, UUID_FMT " (%s) in:%s out:%s",
+                            UUID_ARGS(&lsp_pair->header_.uuid),
+                            lsp_pair->name, linport_name, loutport_name);
+        }
+    }
+    const struct smap_node **nodes = smap_sort(&lsp_pairs);
+    for (i = 0; i < smap_count(&lsp_pairs); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&lsp_pairs);
+    free(nodes);
+}
+
+static void
+nbctl_lsp_pair_list(struct ctl_context *ctx)
+{
+    const char *id = ctx->argc > 1 ? ctx->argv[1] : NULL;
+    const char *pair_name_filter = ctx->argc > 2 ? ctx->argv[2] : NULL;
+    const struct nbrec_logical_switch *lswitch;
+
+    if (id) {
+        lswitch = ls_by_name_or_uuid(ctx, id, true);
+        print_lsp_pairs_for_switch(ctx, lswitch, pair_name_filter, false);
+    } else {
+        NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, ctx->idl) {
+            if (lswitch->n_port_pairs == 0) {
+                continue;
+            }
+            print_lsp_pairs_for_switch(ctx, lswitch, pair_name_filter, true);
+        }
+    }
+}
+/* End of port-pair operations */
+
+static void
+nbctl_lsp_chain_list(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch *lswitch;
+    struct smap lsp_chains;
+    size_t i;
+
+    lswitch = ls_by_name_or_uuid(ctx, id, true);
+    if (!lswitch) {
+        return;
+    }
+
+    smap_init(&lsp_chains);
+    for (i = 0; i < lswitch->n_port_chains; i++) {
+        const struct nbrec_logical_port_chain *lsp_chain = lswitch->port_chains[i];
+        smap_add_format(&lsp_chains, lsp_chain->name, UUID_FMT " (%s)",
+                        UUID_ARGS(&lsp_chain->header_.uuid), lsp_chain->name);
+    }
+    const struct smap_node **nodes = smap_sort(&lsp_chains);
+    for (i = 0; i < smap_count(&lsp_chains); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&lsp_chains);
+    free(nodes);
+}
+
+static void
+print_lsp_chain(const struct nbrec_logical_port_chain *lsp_chain,
+                  struct ctl_context *ctx)
+{
+    const char *port_not_set="Not Set";
+    ds_put_format(&ctx->output, "    lsp-chain "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&lsp_chain->header_.uuid), lsp_chain->name);
+    for (size_t i = 0; i < lsp_chain->n_port_pair_groups; i++) {
+        const struct nbrec_logical_port_pair_group *lsp_pair_group
+            = lsp_chain->port_pair_groups[i];
+        ds_put_format(&ctx->output, "        lsp-pair-group %s\n", lsp_pair_group->name);
+        for (size_t j = 0; j < lsp_pair_group->n_port_pairs; j++){
+            const struct nbrec_logical_switch_port *linport;
+            const struct nbrec_logical_switch_port *loutport;
+            const struct nbrec_logical_port_pair *lsp_pair
+                = lsp_pair_group->port_pairs[j];
+            ds_put_format(&ctx->output, "             lsp-pair %s\n", lsp_pair->name);
+            linport = lsp_pair->inport;
+            ds_put_format(&ctx->output, "                 lsp-pair inport "UUID_FMT" (%s)\n",
+                          UUID_ARGS(&linport->header_.uuid), linport->name);
+            loutport = lsp_pair->outport;
+            ds_put_format(&ctx->output, "                 lsp-pair outport "UUID_FMT" (%s)\n",
+                          UUID_ARGS(&loutport->header_.uuid), loutport->name);
+        }
+    }
+    printf("finished port pairs\n");  // FIXME(ff): debug, remove this
+
+    const struct nbrec_logical_flow_classifier *lflow_classifier = lsp_chain->flow_classifier;
+    printf("Getting flow classifier: %s\n", lflow_classifier->name);  // FIXME(ff): debug, remove this
+    ds_put_format(&ctx->output, "        lflow_classifier %s\n", lflow_classifier->name);
+    if (lflow_classifier->logical_source_port == NULL){
+        ds_put_format(&ctx->output, "          logical-source-port %s\n", port_not_set);
+    } else {
+        ds_put_format(&ctx->output, "          logical-source-port %s\n", lflow_classifier->logical_source_port->name);
+    }
+    if (lflow_classifier->logical_destination_port == NULL){
+        ds_put_format(&ctx->output, "          logical-destination-port %s\n", port_not_set);
+    } else {
+        ds_put_format(&ctx->output, "          logical-destination-port %s\n", lflow_classifier->logical_destination_port->name);
+    }
+    ds_put_format(&ctx->output, "          ethertype: %s\n", lflow_classifier->ethertype);
+    ds_put_format(&ctx->output, "          protocol: %s\n", lflow_classifier->protocol);
+    ds_put_format(&ctx->output, "          source_port_range_min: %ld\n", lflow_classifier->source_port_range_min);
+    ds_put_format(&ctx->output, "          source_port_range_max: %ld\n", lflow_classifier->source_port_range_max);
+    ds_put_format(&ctx->output, "          destination_port_range_min: %ld\n", lflow_classifier->destination_port_range_min);
+    ds_put_format(&ctx->output, "          destination_port_range_max: %ld\n", lflow_classifier->destination_port_range_max);
+    ds_put_format(&ctx->output, "          source_ip_prefix: %ld\n", lflow_classifier->source_ip_prefix);
+    ds_put_format(&ctx->output, "          destination_ip_prefix: %ld\n", lflow_classifier->destination_ip_prefix);
+}
+
+static void
+nbctl_lsp_chain_show(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *lswitch;
+    const struct nbrec_logical_port_chain *lsp_chain;
+    printf("\nIn lsp-chain-show\n");  // FIXME(ff): debug, remove this
+    if (ctx->argc < 2) {
+        /* ensure all arguments are present */
+        ctl_fatal("Invalid number of arguments: (%d), to lsp-chain-show.",ctx->argc);
+    }
+    lswitch = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+    ds_put_format(&ctx->output, " lswitch "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&lswitch->header_.uuid), lswitch->name);
+    if (ctx->argc == 3) {
+        lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[2]);
+        if (lsp_chain) {
+            print_lsp_chain(lsp_chain, ctx);
+        }
+    } else {
+        NBREC_LOGICAL_PORT_CHAIN_FOR_EACH(lsp_chain, ctx->idl) {
+            print_lsp_chain(lsp_chain, ctx);
+        }
+    }
+}
+
+
+static void
+nbctl_lsp_chain_set_flow_classifier(struct ctl_context *ctx)
+{
+
+    const struct nbrec_logical_port_chain *lsp_chain;
+    const struct nbrec_logical_flow_classifier *lflow_classifier = NULL;
+
+    if (ctx->argc < 3){
+        /* ensure all arguments are present */
+        ctl_fatal("Invalid number of arguments: (%d), to lsp-chain-set-flow-classifier.",ctx->argc);
+    }
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lsp_chain){
+        ctl_fatal("Invalid port_chain %s ", ctx->argv[1]);
+    }
+    /* Check flow classifier exists*/
+    lflow_classifier = lflow_classifier_by_name_or_uuid(ctx, ctx->argv[2]);
+    if (!lflow_classifier){
+        ctl_fatal("Invalid flow_classifier %s ", ctx->argv[2]);
+    }
+
+    /* Insert the logical flow-classifier into the logical port-chain. */
+    nbrec_logical_port_chain_verify_flow_classifier(lsp_chain);
+    //struct nbrec_logical_flow_classifier  **new_flow_classifier= xmalloc(sizeof *new_flow_classifier);
+    //memcpy(new_flow_classifier, lsp_chain->flow_classifier, sizeof *new_flow_classifier);
+    //new_flow_classifier = CONST_CAST(struct nbrec_logical_flow_classifier *, lflow_classifier);
+    //nbrec_logical_port_chain_set_flow_classifier(lsp_chain, new_flow_classifier);
+    nbrec_logical_port_chain_set_flow_classifier(lsp_chain, lflow_classifier);  // FIXME (ff): should allow multiple classifiers to same port_chain
+    //free(new_flow_classifier);
+}
+
+static void nbctl_lsp_chain_get_flow_classifier(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port_chain *lsp_chain;
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, id);
+    ds_put_format(&ctx->output, "%s\n", lsp_chain->flow_classifier->name);
+}
+/* End of port-chain operations */
+
+/*
+ * Port Pair Groups CLI Functions
+ */
+static void
+nbctl_lsp_pair_group_add(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    const struct nbrec_logical_port_chain *lsp_chain;
+
+    /* check lsp_chain exists */
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lsp_chain) {
+        return;
+    }
+
+    if (ctx->argc < 2) {
+        /* ensure all arguments are present */
+        ctl_fatal("invalid number of arguments: %d to lsp-pair-groups-add.", ctx->argc);
     }
 
-    return lb;
-}
+    /* create the logical port-pair-group. */
+    lsp_pair_group = nbrec_logical_port_pair_group_insert(ctx->txn);
+    if (ctx->argc == 3){
+        nbrec_logical_port_pair_group_set_name(lsp_pair_group, ctx->argv[2]);
+    }
 
-/* Given pointer to logical router, this routine prints the router
- * information.  */
+    /* Insert the logical port into the logical switch. */
+    nbrec_logical_port_chain_verify_port_pair_groups(lsp_chain);
+    struct nbrec_logical_port_pair_group  **new_port_pair_group = xmalloc(sizeof *new_port_pair_group *
+                                                                          (lsp_chain->n_port_pair_groups + 1));
+    memcpy(new_port_pair_group, lsp_chain->port_pair_groups, sizeof *new_port_pair_group * lsp_chain->n_port_pair_groups);
+    new_port_pair_group[lsp_chain->n_port_pair_groups] =
+        CONST_CAST(struct nbrec_logical_port_pair_group *,lsp_pair_group);
+    nbrec_logical_port_chain_set_port_pair_groups(lsp_chain, new_port_pair_group, lsp_chain->n_port_pair_groups + 1);
+    free(new_port_pair_group);
+}
+/* Removes lsp-pair-group 'lsp_chain->port_pair_group[idx]'. */
 static void
-print_lr(const struct nbrec_logical_router *lr, struct ds *s)
+remove_lsp_pair_group(const struct nbrec_logical_port_chain *lsp_chain, size_t idx)
 {
-    ds_put_format(s, "    router "UUID_FMT" (%s)\n",
-                  UUID_ARGS(&lr->header_.uuid), lr->name);
+    const struct nbrec_logical_port_pair_group *lsp_pair_group = lsp_chain->port_pair_groups[idx];
 
-    for (size_t i = 0; i < lr->n_ports; i++) {
-        const struct nbrec_logical_router_port *lrp = lr->ports[i];
-        ds_put_format(s, "        port %s\n", lrp->name);
-        if (lrp->mac) {
-            ds_put_cstr(s, "            mac: ");
-            ds_put_format(s, "\"%s\"\n", lrp->mac);
-        }
-        if (lrp->n_networks) {
-            ds_put_cstr(s, "            networks: [");
-            for (size_t j = 0; j < lrp->n_networks; j++) {
-                ds_put_format(s, "%s\"%s\"",
-                        j == 0 ? "" : ", ",
-                        lrp->networks[j]);
-            }
-            ds_put_cstr(s, "]\n");
-        }
-    }
+    /* First remove 'lsp-pair-group' from the array of port-pair-groups.  This is what will
+     * actually cause the logical port-pair-group to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_pair_group **new_port_pair_group
+        = xmemdup(lsp_chain->port_pair_groups, sizeof *new_port_pair_group * lsp_chain->n_port_pair_groups);
+    new_port_pair_group[idx] = new_port_pair_group[lsp_chain->n_port_pair_groups - 1];
+    nbrec_logical_port_chain_verify_port_pair_groups(lsp_chain);
+    nbrec_logical_port_chain_set_port_pair_groups(lsp_chain, new_port_pair_group, lsp_chain->n_port_pair_groups - 1);
+    free(new_port_pair_group);
+
+    /* Delete 'lsp-pair-group' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH later. */
+    nbrec_logical_port_pair_group_delete(lsp_pair_group);
 }
 
 static void
-print_ls(const struct nbrec_logical_switch *ls, struct ds *s)
+nbctl_lsp_pair_group_del(struct ctl_context *ctx)
 {
-    ds_put_format(s, "    switch "UUID_FMT" (%s)\n",
-                  UUID_ARGS(&ls->header_.uuid), ls->name);
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
 
-    for (size_t i = 0; i < ls->n_ports; i++) {
-        const struct nbrec_logical_switch_port *lsp = ls->ports[i];
+    lsp_pair_group = lsp_pair_group_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lsp_pair_group) {
+        ctl_fatal("Cannot find lsp_pair_group: %s\n", ctx->argv[1]);
+    }
 
-        ds_put_format(s, "        port %s\n", lsp->name);
-        if (lsp->parent_name) {
-            ds_put_format(s, "            parent: %s\n", lsp->parent_name);
-        }
-        if (lsp->n_tag) {
-            ds_put_format(s, "            tag: %"PRIu64"\n", lsp->tag[0]);
-        }
-        if (lsp->n_addresses) {
-            ds_put_cstr(s, "            addresses: [");
-            for (size_t j = 0; j < lsp->n_addresses; j++) {
-                ds_put_format(s, "%s\"%s\"",
-                        j == 0 ? "" : ", ",
-                        lsp->addresses[j]);
+    /* Find the port-chain that contains 'port-pair-group', then delete it. */
+    const struct nbrec_logical_port_chain *lsp_chain;
+    NBREC_LOGICAL_PORT_CHAIN_FOR_EACH (lsp_chain, ctx->idl) {
+        for (size_t i = 0; i < lsp_chain->n_port_pair_groups; i++) {
+            if (lsp_chain->port_pair_groups[i] == lsp_pair_group) {
+                remove_lsp_pair_group(lsp_chain,i);
+                printf("Deleted lsp-pair-group: %s\n", ctx->argv[1]);  // FIXME(ff): debug, remove this
+                return;
             }
-            ds_put_cstr(s, "]\n");
         }
     }
+    ctl_fatal("logical port-pair-group %s is not part of any logical port-chain",
+              ctx->argv[1]);
 }
 
 static void
-nbctl_init(struct ctl_context *ctx OVS_UNUSED)
+nbctl_lsp_pair_group_list(struct ctl_context *ctx)
 {
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port_chain *lsp_chain;
+    struct smap lsp_pair_groups;
+    size_t i;
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, id);
+    if (!lsp_chain) {
+        return;
+    }
+
+    smap_init(&lsp_pair_groups);
+    for (i = 0; i < lsp_chain->n_port_pair_groups; i++) {
+        const struct nbrec_logical_port_pair_group *lsp_pair_group = lsp_chain->port_pair_groups[i];
+        smap_add_format(&lsp_pair_groups, lsp_pair_group->name, UUID_FMT " (%s)",
+                        UUID_ARGS(&lsp_pair_group->header_.uuid), lsp_pair_group->name);
+    }
+    const struct smap_node **nodes = smap_sort(&lsp_pair_groups);
+    for (i = 0; i < smap_count(&lsp_pair_groups); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&lsp_pair_groups);
+    free(nodes);
 }
 
 static void
-nbctl_pre_sync(struct ctl_context *ctx OVS_UNUSED)
+nbctl_lsp_pair_group_add_port_pair(struct ctl_context *ctx)
 {
-    if (wait_type != NBCTL_WAIT_NONE) {
-        force_wait = true;
-    } else {
-        VLOG_INFO("\"sync\" command has no effect without --wait");
-    }
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    const struct nbrec_logical_port_pair *lsp_pair;
+    const char *lsp_pair_name;
+
+    lsp_pair_group = lsp_pair_group_by_name_or_uuid(ctx, ctx->argv[1]);
+
+    if (ctx->argc < 3) {
+        /* ensure all arguments are present */
+        ctl_fatal("Invalid number of arguments: (%d), to lsp-pair-group-add-port-pair.",ctx->argc);
+    }
+    /* Check that port-pair exists  */
+    lsp_pair_name = ctx->argv[2];
+    lsp_pair = lsp_pair_by_name_or_uuid(ctx, lsp_pair_name);
+    if (!lsp_pair){
+        ctl_fatal("%s: an lsp-pair with this name does not exist",lsp_pair_name);
+    }
+
+    /* Insert the logical port-pair into the logical port-pair-group. */
+    nbrec_logical_port_pair_group_verify_port_pairs(lsp_pair_group);
+    struct nbrec_logical_port_pair  **new_port_pair = xmalloc(sizeof *new_port_pair *
+                                                              (lsp_pair_group->n_port_pairs + 1));
+    memcpy(new_port_pair, lsp_pair_group->port_pairs, sizeof *new_port_pair * lsp_pair_group->n_port_pairs);
+    new_port_pair[lsp_pair_group->n_port_pairs] = CONST_CAST(struct nbrec_logical_port_pair *, lsp_pair);
+    nbrec_logical_port_pair_group_set_port_pairs(lsp_pair_group, new_port_pair, lsp_pair_group->n_port_pairs + 1);
+    free(new_port_pair);
 }
 
+/* Removes port-pair from port-pair-groiup but does not delete it'. */
 static void
-nbctl_sync(struct ctl_context *ctx OVS_UNUSED)
+remove_lsp_pair_from_port_pair_group(const struct nbrec_logical_port_pair_group *lsp_pair_group, size_t idx)
 {
+    //TODO Check const struct nbrec_logical_port_pair *lsp_pair = lsp_pair_group->port_pairs[idx];
+
+    /* First remove 'lsp-pair' from the array of port-pairs.  This is what will
+     * actually cause the logical port-pair to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_pair **new_port_pair
+        = xmemdup(lsp_pair_group->port_pairs, sizeof *new_port_pair * lsp_pair_group->n_port_pairs);
+    new_port_pair[idx] = new_port_pair[lsp_pair_group->n_port_pairs - 1];
+    nbrec_logical_port_pair_group_verify_port_pairs(lsp_pair_group);
+    nbrec_logical_port_pair_group_set_port_pairs(lsp_pair_group, new_port_pair, lsp_pair_group->n_port_pairs - 1);
+    free(new_port_pair);
+
+    /* Do not delete actual port-pair as they are owned by a lswitch and can be reused. */
+    //nbrec_logical_port_pair_delete(lsp_pair);
 }
 
 static void
-nbctl_show(struct ctl_context *ctx)
+nbctl_lsp_pair_group_del_port_pair(struct ctl_context *ctx)
 {
-    const struct nbrec_logical_switch *ls;
+    const struct nbrec_logical_port_pair *lsp_pair;
 
-    if (ctx->argc == 2) {
-        ls = ls_by_name_or_uuid(ctx, ctx->argv[1], false);
-        if (ls) {
-            print_ls(ls, &ctx->output);
-        }
-    } else {
-        NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
-            print_ls(ls, &ctx->output);
-        }
+    lsp_pair = lsp_pair_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lsp_pair) {
+        ctl_fatal("Cannot find lsp_pair: %s\n", ctx->argv[1]);
     }
-    const struct nbrec_logical_router *lr;
 
-    if (ctx->argc == 2) {
-        lr = lr_by_name_or_uuid(ctx, ctx->argv[1], false);
-        if (lr) {
-            print_lr(lr, &ctx->output);
-        }
-    } else {
-        NBREC_LOGICAL_ROUTER_FOR_EACH(lr, ctx->idl) {
-            print_lr(lr, &ctx->output);
+    /* Find the port-pair_group that contains 'port-pair', then delete it. */
+    const struct nbrec_logical_port_pair_group *lsp_pair_group;
+    NBREC_LOGICAL_PORT_PAIR_GROUP_FOR_EACH (lsp_pair_group, ctx->idl) {
+        for (size_t i = 0; i < lsp_pair_group->n_port_pairs; i++) {
+            if (lsp_pair_group->port_pairs[i] == lsp_pair) {
+                remove_lsp_pair_from_port_pair_group(lsp_pair_group,i);
+                printf("Deleted lsp-pair: %s from lsp-group-pair \n", ctx->argv[1]);  // FIXME(ff): debug, remove this
+                return;
+            }
         }
     }
+    ctl_fatal("logical port-pair %s is not part of any logical switch",
+              ctx->argv[1]);
 }
+/* End of port-pair-group operations */
 
+/*
+ * port-pair operations
+ */
 static void
-nbctl_ls_add(struct ctl_context *ctx)
+nbctl_lsp_pair_add(struct ctl_context *ctx)
 {
-    const char *ls_name = ctx->argc == 2 ? ctx->argv[1] : NULL;
+    const char *port_id_in = ctx->argv[2];
+    const char *port_id_out = ctx->argv[3];
 
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
-    if (may_exist && add_duplicate) {
-        ctl_fatal("--may-exist and --add-duplicate may not be used together");
-    }
+    const struct nbrec_logical_switch *lswitch;
+    const struct nbrec_logical_port_pair *lsp_pair;
+    const struct nbrec_logical_switch_port *lsp_in,*lsp_out;
 
-    if (ls_name) {
-        if (!add_duplicate) {
-            const struct nbrec_logical_switch *ls;
-            NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) {
-                if (!strcmp(ls->name, ls_name)) {
-                    if (may_exist) {
-                        return;
-                    }
-                    ctl_fatal("%s: a switch with this name already exists",
-                              ls_name);
-                }
-            }
-        }
-    } else if (may_exist) {
-        ctl_fatal("--may-exist requires specifying a name");
-    } else if (add_duplicate) {
-        ctl_fatal("--add-duplicate requires specifying a name");
+    lswitch = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+    if (ctx->argc < 4) {
+        /* ensure all arguments are present */
+        ctl_fatal("Invalid number of arguments: (%d), to lsp-pair-add.",ctx->argc);
+    }
+    /* Check that ports exist in this switch */
+    lsp_in = lsp_by_name_or_uuid(ctx, port_id_in, false);
+    if (!lsp_in){
+        ctl_fatal("%s: an lsp with this name does not exist",ctx->argv[2]);
+    }
+    lsp_out = lsp_by_name_or_uuid(ctx, port_id_out, false);
+    if (!lsp_out){
+        ctl_fatal("%s: an lsp with this name does not exist",ctx->argv[3]);
     }
 
-    struct nbrec_logical_switch *ls;
-    ls = nbrec_logical_switch_insert(ctx->txn);
-    if (ls_name) {
-        nbrec_logical_switch_set_name(ls, ls_name);
+    /* create the logical port-pair. */
+    lsp_pair = nbrec_logical_port_pair_insert(ctx->txn);
+    nbrec_logical_port_pair_set_inport(lsp_pair, lsp_in);
+    nbrec_logical_port_pair_set_outport(lsp_pair, lsp_out);
+    if (ctx->argc == 5){
+        nbrec_logical_port_pair_set_name(lsp_pair, ctx->argv[4]);
     }
+
+    /* Insert the logical port-pair into the logical port-pair-group. */
+    nbrec_logical_switch_verify_port_pairs(lswitch);
+    struct nbrec_logical_port_pair  **new_port_pair = xmalloc(sizeof *new_port_pair *
+                                                              (lswitch->n_port_pairs + 1));
+    memcpy(new_port_pair, lswitch->port_pairs, sizeof *new_port_pair * lswitch->n_port_pairs);
+    new_port_pair[lswitch->n_port_pairs] = CONST_CAST(struct nbrec_logical_port_pair *, lsp_pair);
+    nbrec_logical_switch_set_port_pairs(lswitch, new_port_pair, lswitch->n_port_pairs + 1);
+    free(new_port_pair);
+}
+/* Removes lswitch->pair_pair[idx]'. */
+static void
+remove_lsp_pair(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+    const struct nbrec_logical_port_pair *lsp_pair = lswitch->port_pairs[idx];
+
+    /* First remove 'lsp-pair' from the array of port-pairs.  This is what will
+     * actually cause the logical port-pair to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port_pair **new_port_pair
+        = xmemdup(lswitch->port_pairs, sizeof *new_port_pair * lswitch->n_port_pairs);
+    new_port_pair[idx] = new_port_pair[lswitch->n_port_pairs - 1];
+    nbrec_logical_switch_verify_port_pairs(lswitch);
+    nbrec_logical_switch_set_port_pairs(lswitch, new_port_pair, lswitch->n_port_pairs - 1);
+    free(new_port_pair);
+
+    /* Delete 'lsp-pair' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_PAIR_FOR_EACH later. */
+    nbrec_logical_port_pair_delete(lsp_pair);
 }
 
 static void
-nbctl_ls_del(struct ctl_context *ctx)
+nbctl_lsp_pair_del(struct ctl_context *ctx)
 {
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch *ls;
+    const struct nbrec_logical_port_pair *lsp_pair;
 
-    ls = ls_by_name_or_uuid(ctx, id, must_exist);
-    if (!ls) {
-        return;
+    lsp_pair = lsp_pair_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lsp_pair) {
+        ctl_fatal("Cannot find lsp_pair: %s\n", ctx->argv[1]);
     }
 
-    nbrec_logical_switch_delete(ls);
+    /* Find the port-pair_group that contains 'port-pair', then delete it. */
+    const struct nbrec_logical_switch *lswitch;
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
+        for (size_t i = 0; i < lswitch->n_port_pairs; i++) {
+            if (lswitch->port_pairs[i] == lsp_pair) {
+                remove_lsp_pair(lswitch,i);
+                printf("Deleted lsp-pair: %s\n", ctx->argv[1]);  // FIXME(ff): debug, remove this
+                return;
+            }
+        }
+    }
+    ctl_fatal("logical port-pair %s is not part of any logical switch",
+              ctx->argv[1]);
 }
 
 static void
-nbctl_ls_list(struct ctl_context *ctx)
+nbctl_lsp_pair_list(struct ctl_context *ctx)
 {
-    const struct nbrec_logical_switch *ls;
-    struct smap lswitches;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch *lswitch;
+    struct smap lsp_pairs;
+    size_t i;
 
-    smap_init(&lswitches);
-    NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
-        smap_add_format(&lswitches, ls->name, UUID_FMT " (%s)",
-                        UUID_ARGS(&ls->header_.uuid), ls->name);
+    lswitch = ls_by_name_or_uuid(ctx, id, true);
+    if (!lswitch) {
+        return;
     }
-    const struct smap_node **nodes = smap_sort(&lswitches);
-    for (size_t i = 0; i < smap_count(&lswitches); i++) {
+
+    smap_init(&lsp_pairs);
+    for (i = 0; i < lswitch->n_port_pairs; i++) {
+        const struct nbrec_logical_port_pair *lsp_pair = lswitch->port_pairs[i];
+        smap_add_format(&lsp_pairs, lsp_pair->name, UUID_FMT " (%s)",
+                        UUID_ARGS(&lsp_pair->header_.uuid), lsp_pair->name);
+    }
+    const struct smap_node **nodes = smap_sort(&lsp_pairs);
+    for (i = 0; i < smap_count(&lsp_pairs); i++) {
         const struct smap_node *node = nodes[i];
         ds_put_format(&ctx->output, "%s\n", node->value);
     }
-    smap_destroy(&lswitches);
+    smap_destroy(&lsp_pairs);
     free(nodes);
 }
-
-static const struct nbrec_logical_switch_port *
-lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id,
-                    bool must_exist)
+/* End of port-pair operations */
+/*
+ * flow_classifier operations
+ */
+static void
+nbctl_lflow_classifier_add(struct ctl_context *ctx)
 {
-    const struct nbrec_logical_switch_port *lsp = NULL;
+    const struct nbrec_logical_port_chain *lsp_chain;
+    const struct nbrec_logical_switch_port *lsp;
+    const char *lsp_name;
+    const struct nbrec_logical_flow_classifier *lflow_classifier;
 
-    struct uuid lsp_uuid;
-    bool is_uuid = uuid_from_string(&lsp_uuid, id);
-    if (is_uuid) {
-        lsp = nbrec_logical_switch_port_get_for_uuid(ctx->idl, &lsp_uuid);
+
+    if (ctx->argc < 3) {
+        /* ensure all arguments are present */
+        ctl_fatal("Invalid number of arguments: (%d), to lflow_classifier-add",ctx->argc);
+    }
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, ctx->argv[1]);
+    /* Check that logical source port exist in this switch */
+    lsp_name = ctx->argv[2];
+    lsp = lsp_by_name_or_uuid(ctx, lsp_name, false);
+    if (!lsp){
+        ctl_fatal("%s: a lsp with this name does not exist",lsp_name);
     }
 
-    if (!lsp) {
-        NBREC_LOGICAL_SWITCH_PORT_FOR_EACH(lsp, ctx->idl) {
-            if (!strcmp(lsp->name, id)) {
-                break;
-            }
+    /* create the logical flow_classifier. */
+    lflow_classifier = nbrec_logical_flow_classifier_insert(ctx->txn);
+    nbrec_logical_flow_classifier_set_logical_source_port(lflow_classifier, lsp);
+    if (ctx->argc == 4){
+        nbrec_logical_flow_classifier_set_name(lflow_classifier, ctx->argv[3]);
+    }
+
+    /* Insert the logical flow_classifier into the logical switch. */
+    nbrec_logical_port_chain_verify_flow_classifier(lsp_chain);
+    //struct nbrec_logical_flow_classifier  **new_flow_classifier = xmalloc(sizeof *new_flow_classifier);
+    //memcpy(new_flow_classifier, lswitch->flow_classifiers, sizeof *new_flow_classifier * lswitch->n_flow_classifiers);
+    //new_flow_classifier[lswitch->n_flow_classifiers] = CONST_CAST(struct nbrec_logical_flow_classifier *, lflow_classifier);
+    nbrec_logical_port_chain_set_flow_classifier(lsp_chain, lflow_classifier);  // FIXME (ff): should allow multiple classifiers to same port_chain
+    //free(new_flow_classifier);
+}
+static void
+nbctl_lflow_classifier_del(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_flow_classifier *lflow_classifier;
+
+    lflow_classifier = lflow_classifier_by_name_or_uuid(ctx, ctx->argv[1]);
+    if (!lflow_classifier) {
+        printf("Cannot find lflow_classifier: %s\n", ctx->argv[1]);  // FIXME(ff): debug, remove this
+        return;
+    }
+
+    /* Find the switch that contains 'flow-classifier', then delete it. */
+    const struct nbrec_logical_port_chain *lsp_chain;
+    NBREC_LOGICAL_PORT_CHAIN_FOR_EACH (lsp_chain, ctx->idl) {
+        if (lsp_chain->flow_classifier == lflow_classifier) {
+            nbrec_logical_flow_classifier_delete(lflow_classifier);
+            printf("Deleted lflow-classifier: %s\n", ctx->argv[1]);  // FIXME(ff): debug, remove this
+            return;
         }
     }
+    ctl_fatal("logical flow-classifier %s is not part of any logical switch",
+              ctx->argv[1]);
+}
 
-    if (!lsp && must_exist) {
-        ctl_fatal("%s: port %s not found", id, is_uuid ? "UUID" : "name");
+static void
+nbctl_lflow_classifier_list(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port_chain *lsp_chain;
+
+
+    lsp_chain = lsp_chain_by_name_or_uuid(ctx, id);
+    if (!lsp_chain) {
+        return;
     }
+    const struct nbrec_logical_flow_classifier *lflow_classifier = lsp_chain->flow_classifier;
+    printf("Getting flow classifier: %s\n", lflow_classifier->name);  // FIXME(ff): debug, remove this
+    ds_put_format(&ctx->output, "        lflow_classifier %s\n", lflow_classifier->name);
+    ds_put_format(&ctx->output, "          logical-source-port %s\n", lflow_classifier->logical_source_port->name);
+    ds_put_format(&ctx->output, "          ethertype: %s\n", lflow_classifier->ethertype);
+    ds_put_format(&ctx->output, "          protocol: %s\n", lflow_classifier->protocol);
 
-    return lsp;
 }
 
+static void
+nbctl_lflow_classifier_set_logical_destination_port(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    const struct nbrec_logical_flow_classifier *lflow_classifier;
+
+    lflow_classifier = lflow_classifier_by_name_or_uuid(ctx, id);
+
+    /* Check port exists if given*/
+    if (!strcmp(ctx->argv[2],"") ){
+        lsp = lsp_by_name_or_uuid(ctx, ctx->argv[2], true);
+        if (!lsp){
+            ctl_fatal("Invalid port %s ", ctx->argv[2]);
+        }
+    }
+    nbrec_logical_flow_classifier_set_logical_destination_port(lflow_classifier,lsp);
+}
+
+static void
+nbctl_lflow_classifier_get_logical_destination_port(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_flow_classifier *lflow_classifier;
+
+    lflow_classifier = lflow_classifier_by_name_or_uuid(ctx, id);
+    ds_put_format(&ctx->output, "%s\n", (lflow_classifier->logical_destination_port)->name);
+}
+/* End of flow-classifier operations */
+
+
+
+
 /* Returns the logical switch that contains 'lsp'. */
 static const struct nbrec_logical_switch *
 lsp_to_ls(const struct ovsdb_idl *idl,
@@ -1296,12 +2839,33 @@ nbctl_acl_add(struct ctl_context *ctx)
 
     /* Validate action. */
     if (strcmp(action, "allow") && strcmp(action, "allow-related")
-        && strcmp(action, "drop") && strcmp(action, "reject")) {
+        && strcmp(action, "drop") && strcmp(action, "reject")
+        && strcmp(action, "sfc")) {
         ctl_fatal("%s: action must be one of \"allow\", \"allow-related\", "
-                  "\"drop\", and \"reject\"", action);
+                  "\"drop\", \"reject\" and \"sfc\"", action);
         return;
     }
 
+    /* Validate ACL Options, if there were any provided. */
+    struct smap acl_options = SMAP_INITIALIZER(&acl_options);
+    if (ctx->argc >= 7) {
+        struct sset acl_options_set;
+        sset_from_delimited_string(&acl_options_set, ctx->argv[6], " ");
+
+        const char *acl_option_tuple;
+        SSET_FOR_EACH (acl_option_tuple, &acl_options_set) {
+            char *key, *value;
+            value = xstrdup(acl_option_tuple);
+            key = strsep(&value, "=");
+            if (value) {
+                smap_add(&acl_options, key, value);
+            }
+            free(key);
+        }
+
+        sset_destroy(&acl_options_set);
+    }
+
     /* Create the acl. */
     struct nbrec_acl *acl = nbrec_acl_insert(ctx->txn);
     nbrec_acl_set_priority(acl, priority);
@@ -1311,6 +2875,9 @@ nbctl_acl_add(struct ctl_context *ctx)
     if (shash_find(&ctx->options, "--log") != NULL) {
         nbrec_acl_set_log(acl, true);
     }
+    if (! smap_is_empty(&acl_options)) {
+        nbrec_acl_set_options(acl, &acl_options);
+    }
 
     /* Insert the acl into the logical switch. */
     nbrec_logical_switch_verify_acls(ls);
@@ -1319,6 +2886,8 @@ nbctl_acl_add(struct ctl_context *ctx)
     new_acls[ls->n_acls] = acl;
     nbrec_logical_switch_set_acls(ls, new_acls, ls->n_acls + 1);
     free(new_acls);
+
+    smap_destroy(&acl_options);
 }
 
 static void
@@ -2978,6 +4547,22 @@ static const struct ctl_table_class tables[] = {
      {{&nbrec_table_logical_switch, &nbrec_logical_switch_col_name, NULL},
       {NULL, NULL, NULL}}},
 
+    {&nbrec_table_logical_port_chain,
+     {{&nbrec_table_logical_port_chain, &nbrec_logical_port_chain_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&nbrec_table_logical_port_pair_group,
+     {{&nbrec_table_logical_port_pair_group, &nbrec_logical_port_pair_group_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&nbrec_table_logical_port_pair,
+     {{&nbrec_table_logical_port_pair, &nbrec_logical_port_pair_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&nbrec_table_logical_flow_classifier,
+     {{&nbrec_table_logical_flow_classifier, &nbrec_logical_flow_classifier_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
     {&nbrec_table_logical_switch_port,
      {{&nbrec_table_logical_switch_port, &nbrec_logical_switch_port_col_name,
        NULL},
@@ -3276,6 +4861,74 @@ static const struct ctl_command_syntax nbctl_commands[] = {
     { "init", 0, 0, "", NULL, nbctl_init, NULL, "", RW },
     { "sync", 0, 0, "", nbctl_pre_sync, nbctl_sync, NULL, "", RO },
     { "show", 0, 1, "[SWITCH]", NULL, nbctl_show, NULL, "", RO },
+    /* lsp-chain commands. */
+    { "lsp-chain-add", 1, 2, "LSWITCH,[LSP-CHAIN]", NULL, nbctl_lsp_chain_add,
+      NULL, "", RW },
+    { "lsp-chain-del", 1, 1, "LSP-CHAIN", NULL, nbctl_lsp_chain_del,
+      NULL, "--if-exists", RW },
+    { "lsp-chain-list", 1, 1, "LSWITCH", NULL, nbctl_lsp_chain_list, NULL, "", RO },
+    { "lsp-chain-show", 1, 2, "LSWITCH [LSP-CHAIN]", NULL, nbctl_lsp_chain_show, NULL, "", RO },
+    { "lsp-chain-get-flow-classifier", 1, 1, "LSP-CHAIN", NULL,
+      nbctl_lsp_chain_get_flow_classifier, NULL, "", RO },
+    { "lsp-chain-set-flow-classifier", 2, 2, "LSP-CHAIN LFLOW-CLASSIFIER", NULL,
+      nbctl_lsp_chain_set_flow_classifier, NULL, "", RW },
+
+    /* lsp-pair-group commands. */
+    { "lsp-pair-group-add", 1, 2, "LSP-CHAIN [LSP-PAIR-GROUP]",
+      NULL, nbctl_lsp_pair_group_add, NULL, "", RW },
+    { "lsp-pair-group-del", 2, 2, "LSP-CHAIN, LSP-PAIR-GROUP", NULL, nbctl_lsp_pair_group_del,
+      NULL, "", RW },
+    { "lsp-pair-group-list", 1, 1, "LSP_CHAIN", NULL, nbctl_lsp_pair_group_list, NULL, "", RO },
+    { "lsp-pair-group-add-port-pair", 2, 2, "LSP-PAIR-GROUP LSP-PAIR",
+      NULL, nbctl_lsp_pair_group_add_port_pair, NULL, "", RW },
+    { "lsp-pair-group-del-port-pair", 2, 2, "LSP-PAIR-GROUP LSP-PAIR",
+      NULL, nbctl_lsp_pair_group_del_port_pair, NULL, "", RW },
+
+    /* lsp-pair commands. */
+    { "lsp-pair-add", 3, 4, "LSWITCH, LSP, LSP [LSP_PAIR_NAME]", NULL, nbctl_lsp_pair_add,
+      NULL, "", RW },
+    { "lsp-pair-del", 1, 1, "LSP-PAIR", NULL, nbctl_lsp_pair_del,
+      NULL, "", RW },
+    { "lsp-pair-list", 1, 1, "LSWITCH", NULL, nbctl_lsp_pair_list, NULL, "", RO },
+
+    /* lflow-classifier commands. */
+    { "lflow-classifier-add", 2, 3, "LSP-CHAIN LSOURCE_PORT [LFLOW-CLASSIFIER-NAME]", NULL,
+      nbctl_lflow_classifier_add, NULL, "", RW },
+    { "lflow-classifier-del", 1, 1, "LFLOW-CLASSIFIER", NULL,
+      nbctl_lflow_classifier_del, NULL, "", RW },
+    { "lflow-classifier-list", 1, 1, "LSP-CHAIN", NULL, nbctl_lflow_classifier_list,
+      NULL, "", RO },
+    { "lflow-classifier-get-logical-destination-port", 1, 1, "LFLOW-CLASSIFIER", NULL,
+      nbctl_lflow_classifier_get_logical_destination_port, NULL, "", RO },
+    { "lflow-classifier-set-logical-destination-port", 2, 2, "LFLOW-CLASSIFIER LDESTINATION_PORT", NULL,
+      nbctl_lflow_classifier_set_logical_destination_port, NULL, "", RO },
+    /* TODO ADD OTHER FLOW-CLASSIFIER PARAMETERS */
+
+    /* lsp-chain commands. */
+    { "lsp-chain-add", 2, 3, "SWITCH [CHAIN] LAST_PORT", NULL, nbctl_lsp_chain_add,
+      NULL, "--may-exist,--add-duplicate", RW },
+    { "lsp-chain-del", 1, 1, "CHAIN", NULL, nbctl_lsp_chain_del,
+      NULL, "--if-exists", RW },
+    { "lsp-chain-list", 0, 2, "[SWITCH [CHAIN]]", NULL, nbctl_lsp_chain_list, NULL, "", RO },
+    { "lsp-chain-show", 0, 1, "[CHAIN]", NULL, nbctl_lsp_chain_show, NULL, "", RO },
+
+    /* lsp-pair-group commands. */
+    { "lsp-pair-group-add", 1, 3, "CHAIN [PAIR-GROUP [OFFSET]]",
+      NULL, nbctl_lsp_pair_group_add, NULL, "--may-exist,--add-duplicate", RW },
+    { "lsp-pair-group-del", 1, 1, "PAIR-GROUP", NULL, nbctl_lsp_pair_group_del,
+      NULL, "--if-exists", RW },
+    { "lsp-pair-group-list", 1, 1, "CHAIN", NULL, nbctl_lsp_pair_group_list, NULL, "", RO },
+    { "lsp-pair-group-add-port-pair", 2, 2, "PAIR-GROUP LSP-PAIR",
+      NULL, nbctl_lsp_pair_group_add_port_pair, NULL, "--may-exist", RW },
+    { "lsp-pair-group-del-port-pair", 2, 2, "PAIR-GROUP LSP-PAIR",
+      NULL, nbctl_lsp_pair_group_del_port_pair, NULL, "--if-exists", RW },
+
+    /* lsp-pair commands. */
+    { "lsp-pair-add", 3, 4, "SWITCH, PORT-IN, PORT-OUT [LSP-PAIR]", NULL, nbctl_lsp_pair_add,
+      NULL, "--may-exist,--add-duplicate", RW },
+    { "lsp-pair-del", 1, 1, "LSP-PAIR", NULL, nbctl_lsp_pair_del,
+      NULL, "--if-exists", RW },
+    { "lsp-pair-list", 0, 2, "[SWITCH [LSP-PAIR]]", NULL, nbctl_lsp_pair_list, NULL, "", RO },
 
     /* logical switch commands. */
     { "ls-add", 0, 1, "[SWITCH]", NULL, nbctl_ls_add, NULL,
@@ -3284,7 +4937,7 @@ static const struct ctl_command_syntax nbctl_commands[] = {
     { "ls-list", 0, 0, "", NULL, nbctl_ls_list, NULL, "", RO },
 
     /* acl commands. */
-    { "acl-add", 5, 5, "SWITCH DIRECTION PRIORITY MATCH ACTION", NULL,
+    { "acl-add", 5, 6, "SWITCH DIRECTION PRIORITY MATCH ACTION [ACL-OPTIONS]", NULL,
       nbctl_acl_add, NULL, "--log", RW },
     { "acl-del", 1, 4, "SWITCH [DIRECTION [PRIORITY MATCH]]", NULL,
       nbctl_acl_del, NULL, "", RW },
-- 
2.11.0



More information about the dev mailing list