7f7b2c
From 6ab1ea8fcd83712ae3e79b96fe9494db92a1d836 Mon Sep 17 00:00:00 2001
7f7b2c
From: Ilya Maximets <i.maximets@ovn.org>
7f7b2c
Date: Fri, 11 Dec 2020 11:59:15 +0100
7f7b2c
Subject: [PATCH 2/7] nbctl: Cache to which switch or router particular port
7f7b2c
 belongs.
7f7b2c
7f7b2c
nbctl always iterates over all ports in all logical switches or routers
7f7b2c
to find to which logical router/switch current port belongs.  This
7f7b2c
could be optimized by iterating only once and caching the current
7f7b2c
state.  This should improve a little bit performance of this utility
7f7b2c
in case where many updates are passed in a single nbctl command.
7f7b2c
7f7b2c
However, this change alone will slightly reduce performance of
7f7b2c
standalone commands, since we're iterating twice over ports on port
7f7b2c
deletion.
7f7b2c
7f7b2c
Cache is required for the upcoming change that will make nbctl to use
7f7b2c
partial set updates.  It will allow us to drop redundant iterations
7f7b2c
over ports, i.e. to not duplicate work.
7f7b2c
7f7b2c
Acked-by: Dumitru Ceara <dceara@redhat.com>
7f7b2c
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
7f7b2c
Signed-off-by: Numan Siddique <numans@ovn.org>
7f7b2c
---
7f7b2c
 utilities/ovn-nbctl.c | 209 +++++++++++++++++++++++++++++-------------
7f7b2c
 1 file changed, 146 insertions(+), 63 deletions(-)
7f7b2c
7f7b2c
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
7f7b2c
index 3a95f6b1f..cbf2cb1f1 100644
7f7b2c
--- a/utilities/ovn-nbctl.c
7f7b2c
+++ b/utilities/ovn-nbctl.c
7f7b2c
@@ -125,6 +125,61 @@ static char * OVS_WARN_UNUSED_RESULT main_loop(const char *args,
7f7b2c
                                                const struct timer *);
7f7b2c
 static void server_loop(struct ovsdb_idl *idl, int argc, char *argv[]);
7f7b2c
 
7f7b2c
+/* A context for keeping track of which switch/router certain ports are
7f7b2c
+ * connected to. */
7f7b2c
+struct nbctl_context {
7f7b2c
+    struct ctl_context base;
7f7b2c
+    struct shash lsp_to_ls_map;
7f7b2c
+    struct shash lrp_to_lr_map;
7f7b2c
+    bool context_valid;
7f7b2c
+};
7f7b2c
+
7f7b2c
+static void
7f7b2c
+nbctl_context_init(struct nbctl_context *nbctx)
7f7b2c
+{
7f7b2c
+    nbctx->context_valid = false;
7f7b2c
+    shash_init(&nbctx->lsp_to_ls_map);
7f7b2c
+    shash_init(&nbctx->lrp_to_lr_map);
7f7b2c
+}
7f7b2c
+
7f7b2c
+static void
7f7b2c
+nbctl_context_destroy(struct nbctl_context *nbctx)
7f7b2c
+{
7f7b2c
+    nbctx->context_valid = false;
7f7b2c
+    shash_destroy(&nbctx->lsp_to_ls_map);
7f7b2c
+    shash_destroy(&nbctx->lrp_to_lr_map);
7f7b2c
+}
7f7b2c
+
7f7b2c
+/* Casts 'base' into 'struct nbctl_context' and initializes it if needed. */
7f7b2c
+static struct nbctl_context *
7f7b2c
+nbctl_context_get(struct ctl_context *base)
7f7b2c
+{
7f7b2c
+    struct nbctl_context *nbctx;
7f7b2c
+
7f7b2c
+    nbctx = CONTAINER_OF(base, struct nbctl_context, base);
7f7b2c
+
7f7b2c
+    if (nbctx->context_valid) {
7f7b2c
+        return nbctx;
7f7b2c
+    }
7f7b2c
+
7f7b2c
+    const struct nbrec_logical_switch *ls;
7f7b2c
+    NBREC_LOGICAL_SWITCH_FOR_EACH (ls, base->idl) {
7f7b2c
+        for (size_t i = 0; i < ls->n_ports; i++) {
7f7b2c
+            shash_add_once(&nbctx->lsp_to_ls_map, ls->ports[i]->name, ls);
7f7b2c
+        }
7f7b2c
+    }
7f7b2c
+
7f7b2c
+    const struct nbrec_logical_router *lr;
7f7b2c
+    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, base->idl) {
7f7b2c
+        for (size_t i = 0; i < lr->n_ports; i++) {
7f7b2c
+            shash_add_once(&nbctx->lrp_to_lr_map, lr->ports[i]->name, lr);
7f7b2c
+        }
7f7b2c
+    }
7f7b2c
+
7f7b2c
+    nbctx->context_valid = true;
7f7b2c
+    return nbctx;
7f7b2c
+}
7f7b2c
+
7f7b2c
 int
7f7b2c
 main(int argc, char *argv[])
7f7b2c
 {
7f7b2c
@@ -1249,6 +1304,7 @@ static void
7f7b2c
 nbctl_ls_del(struct ctl_context *ctx)
7f7b2c
 {
7f7b2c
     bool must_exist = !shash_find(&ctx->options, "--if-exists");
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
     const char *id = ctx->argv[1];
7f7b2c
     const struct nbrec_logical_switch *ls = NULL;
7f7b2c
 
7f7b2c
@@ -1261,6 +1317,11 @@ nbctl_ls_del(struct ctl_context *ctx)
7f7b2c
         return;
7f7b2c
     }
7f7b2c
 
7f7b2c
+    /* Updating runtime cache. */
7f7b2c
+    for (size_t i = 0; i < ls->n_ports; i++) {
7f7b2c
+        shash_find_and_delete(&nbctx->lsp_to_ls_map, ls->ports[i]->name);
7f7b2c
+    }
7f7b2c
+
7f7b2c
     nbrec_logical_switch_delete(ls);
7f7b2c
 }
7f7b2c
 
7f7b2c
@@ -1317,22 +1378,19 @@ lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id,
7f7b2c
 
7f7b2c
 /* Returns the logical switch that contains 'lsp'. */
7f7b2c
 static char * OVS_WARN_UNUSED_RESULT
7f7b2c
-lsp_to_ls(const struct ovsdb_idl *idl,
7f7b2c
+lsp_to_ls(struct ctl_context *ctx,
7f7b2c
           const struct nbrec_logical_switch_port *lsp,
7f7b2c
           const struct nbrec_logical_switch **ls_p)
7f7b2c
 {
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
     const struct nbrec_logical_switch *ls;
7f7b2c
     *ls_p = NULL;
7f7b2c
 
7f7b2c
-    NBREC_LOGICAL_SWITCH_FOR_EACH (ls, idl) {
7f7b2c
-        for (size_t i = 0; i < ls->n_ports; i++) {
7f7b2c
-            if (ls->ports[i] == lsp) {
7f7b2c
-                *ls_p = ls;
7f7b2c
-                return NULL;
7f7b2c
-            }
7f7b2c
-        }
7f7b2c
+    ls = shash_find_data(&nbctx->lsp_to_ls_map, lsp->name);
7f7b2c
+    if (ls) {
7f7b2c
+        *ls_p = ls;
7f7b2c
+        return NULL;
7f7b2c
     }
7f7b2c
-
7f7b2c
     /* Can't happen because of the database schema */
7f7b2c
     return xasprintf("logical port %s is not part of any logical switch",
7f7b2c
                      lsp->name);
7f7b2c
@@ -1353,6 +1411,7 @@ static void
7f7b2c
 nbctl_lsp_add(struct ctl_context *ctx)
7f7b2c
 {
7f7b2c
     bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
 
7f7b2c
     const struct nbrec_logical_switch *ls = NULL;
7f7b2c
     char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
7f7b2c
@@ -1395,7 +1454,7 @@ nbctl_lsp_add(struct ctl_context *ctx)
7f7b2c
         }
7f7b2c
 
7f7b2c
         const struct nbrec_logical_switch *lsw;
7f7b2c
-        error = lsp_to_ls(ctx->idl, lsp, &lsw;;
7f7b2c
+        error = lsp_to_ls(ctx, lsp, &lsw;;
7f7b2c
         if (error) {
7f7b2c
             ctx->error = error;
7f7b2c
             return;
7f7b2c
@@ -1456,13 +1515,21 @@ nbctl_lsp_add(struct ctl_context *ctx)
7f7b2c
                                              lsp);
7f7b2c
     nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports + 1);
7f7b2c
     free(new_ports);
7f7b2c
+
7f7b2c
+    /* Updating runtime cache. */
7f7b2c
+    shash_add(&nbctx->lsp_to_ls_map, lsp_name, ls);
7f7b2c
 }
7f7b2c
 
7f7b2c
 /* Removes logical switch port 'ls->ports[idx]'. */
7f7b2c
 static void
7f7b2c
-remove_lsp(const struct nbrec_logical_switch *ls, size_t idx)
7f7b2c
+remove_lsp(struct ctl_context *ctx, size_t idx,
7f7b2c
+           const struct nbrec_logical_switch *ls,
7f7b2c
+           const struct nbrec_logical_switch_port *lsp)
7f7b2c
 {
7f7b2c
-    const struct nbrec_logical_switch_port *lsp = ls->ports[idx];
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
+
7f7b2c
+    /* Updating runtime cache. */
7f7b2c
+    shash_find_and_delete(&nbctx->lsp_to_ls_map, lsp->name);
7f7b2c
 
7f7b2c
     /* First remove 'lsp' from the array of ports.  This is what will
7f7b2c
      * actually cause the logical port to be deleted when the transaction is
7f7b2c
@@ -1498,18 +1565,18 @@ nbctl_lsp_del(struct ctl_context *ctx)
7f7b2c
 
7f7b2c
     /* Find the switch that contains 'lsp', then delete it. */
7f7b2c
     const struct nbrec_logical_switch *ls;
7f7b2c
-    NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) {
7f7b2c
-        for (size_t i = 0; i < ls->n_ports; i++) {
7f7b2c
-            if (ls->ports[i] == lsp) {
7f7b2c
-                remove_lsp(ls, i);
7f7b2c
-                return;
7f7b2c
-            }
7f7b2c
+
7f7b2c
+    error = lsp_to_ls(ctx, lsp, &ls);
7f7b2c
+    if (error) {
7f7b2c
+        ctx->error = error;
7f7b2c
+        return;
7f7b2c
+    }
7f7b2c
+    for (size_t i = 0; i < ls->n_ports; i++) {
7f7b2c
+        if (ls->ports[i] == lsp) {
7f7b2c
+            remove_lsp(ctx, i, ls, lsp);
7f7b2c
+            break;
7f7b2c
         }
7f7b2c
     }
7f7b2c
-
7f7b2c
-    /* Can't happen because of the database schema. */
7f7b2c
-    ctl_error(ctx, "logical port %s is not part of any logical switch",
7f7b2c
-              ctx->argv[1]);
7f7b2c
 }
7f7b2c
 
7f7b2c
 static void
7f7b2c
@@ -1658,7 +1725,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx)
7f7b2c
     }
7f7b2c
 
7f7b2c
     const struct nbrec_logical_switch *ls;
7f7b2c
-    error = lsp_to_ls(ctx->idl, lsp, &ls);
7f7b2c
+    error = lsp_to_ls(ctx, lsp, &ls);
7f7b2c
     if (error) {
7f7b2c
         ctx->error = error;
7f7b2c
         return;
7f7b2c
@@ -3383,6 +3450,7 @@ static void
7f7b2c
 nbctl_lr_del(struct ctl_context *ctx)
7f7b2c
 {
7f7b2c
     bool must_exist = !shash_find(&ctx->options, "--if-exists");
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
     const char *id = ctx->argv[1];
7f7b2c
     const struct nbrec_logical_router *lr = NULL;
7f7b2c
 
7f7b2c
@@ -3395,6 +3463,11 @@ nbctl_lr_del(struct ctl_context *ctx)
7f7b2c
         return;
7f7b2c
     }
7f7b2c
 
7f7b2c
+    /* Updating runtime cache. */
7f7b2c
+    for (size_t i = 0; i < lr->n_ports; i++) {
7f7b2c
+        shash_find_and_delete(&nbctx->lrp_to_lr_map, lr->ports[i]->name);
7f7b2c
+    }
7f7b2c
+
7f7b2c
     nbrec_logical_router_delete(lr);
7f7b2c
 }
7f7b2c
 
7f7b2c
@@ -4672,20 +4745,18 @@ lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
7f7b2c
 
7f7b2c
 /* Returns the logical router that contains 'lrp'. */
7f7b2c
 static char * OVS_WARN_UNUSED_RESULT
7f7b2c
-lrp_to_lr(const struct ovsdb_idl *idl,
7f7b2c
+lrp_to_lr(struct ctl_context *ctx,
7f7b2c
           const struct nbrec_logical_router_port *lrp,
7f7b2c
           const struct nbrec_logical_router **lr_p)
7f7b2c
 {
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
     const struct nbrec_logical_router *lr;
7f7b2c
     *lr_p = NULL;
7f7b2c
 
7f7b2c
-    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, idl) {
7f7b2c
-        for (size_t i = 0; i < lr->n_ports; i++) {
7f7b2c
-            if (lr->ports[i] == lrp) {
7f7b2c
-                *lr_p = lr;
7f7b2c
-                return NULL;
7f7b2c
-            }
7f7b2c
-        }
7f7b2c
+    lr = shash_find_data(&nbctx->lrp_to_lr_map, lrp->name);
7f7b2c
+    if (lr) {
7f7b2c
+        *lr_p = lr;
7f7b2c
+        return NULL;
7f7b2c
     }
7f7b2c
 
7f7b2c
     /* Can't happen because of the database schema */
7f7b2c
@@ -4898,6 +4969,7 @@ static void
7f7b2c
 nbctl_lrp_add(struct ctl_context *ctx)
7f7b2c
 {
7f7b2c
     bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
 
7f7b2c
     const struct nbrec_logical_router *lr = NULL;
7f7b2c
     char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
7f7b2c
@@ -4947,7 +5019,7 @@ nbctl_lrp_add(struct ctl_context *ctx)
7f7b2c
         }
7f7b2c
 
7f7b2c
         const struct nbrec_logical_router *bound_lr;
7f7b2c
-        error = lrp_to_lr(ctx->idl, lrp, &bound_lr);
7f7b2c
+        error = lrp_to_lr(ctx, lrp, &bound_lr);
7f7b2c
         if (error) {
7f7b2c
             ctx->error = error;
7f7b2c
             return;
7f7b2c
@@ -5053,13 +5125,21 @@ nbctl_lrp_add(struct ctl_context *ctx)
7f7b2c
                                              lrp);
7f7b2c
     nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports + 1);
7f7b2c
     free(new_ports);
7f7b2c
+
7f7b2c
+    /* Updating runtime cache. */
7f7b2c
+    shash_add(&nbctx->lrp_to_lr_map, lrp->name, lr);
7f7b2c
 }
7f7b2c
 
7f7b2c
 /* Removes logical router port 'lr->ports[idx]'. */
7f7b2c
 static void
7f7b2c
-remove_lrp(const struct nbrec_logical_router *lr, size_t idx)
7f7b2c
+remove_lrp(struct ctl_context *ctx, size_t idx,
7f7b2c
+           const struct nbrec_logical_router *lr,
7f7b2c
+           const struct nbrec_logical_router_port *lrp)
7f7b2c
 {
7f7b2c
-    const struct nbrec_logical_router_port *lrp = lr->ports[idx];
7f7b2c
+    struct nbctl_context *nbctx = nbctl_context_get(ctx);
7f7b2c
+
7f7b2c
+    /* Updating runtime cache. */
7f7b2c
+    shash_find_and_delete(&nbctx->lrp_to_lr_map, lrp->name);
7f7b2c
 
7f7b2c
     /* First remove 'lrp' from the array of ports.  This is what will
7f7b2c
      * actually cause the logical port to be deleted when the transaction is
7f7b2c
@@ -5095,18 +5175,18 @@ nbctl_lrp_del(struct ctl_context *ctx)
7f7b2c
 
7f7b2c
     /* Find the router that contains 'lrp', then delete it. */
7f7b2c
     const struct nbrec_logical_router *lr;
7f7b2c
-    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->idl) {
7f7b2c
-        for (size_t i = 0; i < lr->n_ports; i++) {
7f7b2c
-            if (lr->ports[i] == lrp) {
7f7b2c
-                remove_lrp(lr, i);
7f7b2c
-                return;
7f7b2c
-            }
7f7b2c
+
7f7b2c
+    error = lrp_to_lr(ctx, lrp, &lr);
7f7b2c
+    if (error) {
7f7b2c
+        ctx->error = error;
7f7b2c
+        return;
7f7b2c
+    }
7f7b2c
+    for (size_t i = 0; i < lr->n_ports; i++) {
7f7b2c
+        if (lr->ports[i] == lrp) {
7f7b2c
+            remove_lrp(ctx, i, lr, lrp);
7f7b2c
+            break;
7f7b2c
         }
7f7b2c
     }
7f7b2c
-
7f7b2c
-    /* Can't happen because of the database schema. */
7f7b2c
-    ctl_error(ctx, "logical port %s is not part of any logical router",
7f7b2c
-              ctx->argv[1]);
7f7b2c
 }
7f7b2c
 
7f7b2c
 /* Print a list of logical router ports. */
7f7b2c
@@ -5280,7 +5360,7 @@ fwd_group_to_logical_switch(struct ctl_context *ctx,
7f7b2c
     }
7f7b2c
 
7f7b2c
     const struct nbrec_logical_switch *ls;
7f7b2c
-    error = lsp_to_ls(ctx->idl, lsp, &ls);
7f7b2c
+    error = lsp_to_ls(ctx, lsp, &ls);
7f7b2c
     if (error) {
7f7b2c
         ctx->error = error;
7f7b2c
         return NULL;
7f7b2c
@@ -5355,7 +5435,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx)
7f7b2c
             return;
7f7b2c
         }
7f7b2c
         if (lsp) {
7f7b2c
-            error = lsp_to_ls(ctx->idl, lsp, &ls);
7f7b2c
+            error = lsp_to_ls(ctx, lsp, &ls);
7f7b2c
             if (error) {
7f7b2c
                 ctx->error = error;
7f7b2c
                 return;
7f7b2c
@@ -6236,7 +6316,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
7f7b2c
     struct ovsdb_idl_txn *txn;
7f7b2c
     enum ovsdb_idl_txn_status status;
7f7b2c
     struct ovsdb_symbol_table *symtab;
7f7b2c
-    struct ctl_context ctx;
7f7b2c
+    struct nbctl_context ctx;
7f7b2c
     struct ctl_command *c;
7f7b2c
     struct shash_node *node;
7f7b2c
     int64_t next_cfg = 0;
7f7b2c
@@ -6273,25 +6353,26 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
7f7b2c
         ds_init(&c->output);
7f7b2c
         c->table = NULL;
7f7b2c
     }
7f7b2c
-    ctl_context_init(&ctx, NULL, idl, txn, symtab, NULL);
7f7b2c
+    nbctl_context_init(&ctx;;
7f7b2c
+    ctl_context_init(&ctx.base, NULL, idl, txn, symtab, NULL);
7f7b2c
     for (c = commands; c < &commands[n_commands]; c++) {
7f7b2c
-        ctl_context_init_command(&ctx, c);
7f7b2c
+        ctl_context_init_command(&ctx.base, c);
7f7b2c
         if (c->syntax->run) {
7f7b2c
-            (c->syntax->run)(&ctx;;
7f7b2c
+            (c->syntax->run)(&ctx.base);
7f7b2c
         }
7f7b2c
-        if (ctx.error) {
7f7b2c
-            error = xstrdup(ctx.error);
7f7b2c
-            ctl_context_done(&ctx, c);
7f7b2c
+        if (ctx.base.error) {
7f7b2c
+            error = xstrdup(ctx.base.error);
7f7b2c
+            ctl_context_done(&ctx.base, c);
7f7b2c
             goto out_error;
7f7b2c
         }
7f7b2c
-        ctl_context_done_command(&ctx, c);
7f7b2c
+        ctl_context_done_command(&ctx.base, c);
7f7b2c
 
7f7b2c
-        if (ctx.try_again) {
7f7b2c
-            ctl_context_done(&ctx, NULL);
7f7b2c
+        if (ctx.base.try_again) {
7f7b2c
+            ctl_context_done(&ctx.base, NULL);
7f7b2c
             goto try_again;
7f7b2c
         }
7f7b2c
     }
7f7b2c
-    ctl_context_done(&ctx, NULL);
7f7b2c
+    ctl_context_done(&ctx.base, NULL);
7f7b2c
 
7f7b2c
     SHASH_FOR_EACH (node, &symtab->sh) {
7f7b2c
         struct ovsdb_symbol *symbol = node->data;
7f7b2c
@@ -6322,14 +6403,14 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
7f7b2c
     if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
7f7b2c
         for (c = commands; c < &commands[n_commands]; c++) {
7f7b2c
             if (c->syntax->postprocess) {
7f7b2c
-                ctl_context_init(&ctx, c, idl, txn, symtab, NULL);
7f7b2c
-                (c->syntax->postprocess)(&ctx;;
7f7b2c
-                if (ctx.error) {
7f7b2c
-                    error = xstrdup(ctx.error);
7f7b2c
-                    ctl_context_done(&ctx, c);
7f7b2c
+                ctl_context_init(&ctx.base, c, idl, txn, symtab, NULL);
7f7b2c
+                (c->syntax->postprocess)(&ctx.base);
7f7b2c
+                if (ctx.base.error) {
7f7b2c
+                    error = xstrdup(ctx.base.error);
7f7b2c
+                    ctl_context_done(&ctx.base, c);
7f7b2c
                     goto out_error;
7f7b2c
                 }
7f7b2c
-                ctl_context_done(&ctx, c);
7f7b2c
+                ctl_context_done(&ctx.base, c);
7f7b2c
             }
7f7b2c
         }
7f7b2c
     }
7f7b2c
@@ -6417,6 +6498,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
7f7b2c
     done: ;
7f7b2c
     }
7f7b2c
 
7f7b2c
+    nbctl_context_destroy(&ctx;;
7f7b2c
     ovsdb_symbol_table_destroy(symtab);
7f7b2c
     ovsdb_idl_txn_destroy(txn);
7f7b2c
     the_idl_txn = NULL;
7f7b2c
@@ -6434,6 +6516,7 @@ out_error:
7f7b2c
     ovsdb_idl_txn_destroy(txn);
7f7b2c
     the_idl_txn = NULL;
7f7b2c
 
7f7b2c
+    nbctl_context_destroy(&ctx;;
7f7b2c
     ovsdb_symbol_table_destroy(symtab);
7f7b2c
     return error;
7f7b2c
 }
7f7b2c
-- 
7f7b2c
2.28.0
7f7b2c