diff mbox

[ovs-dev,v2] ovn: Make it possible for CMS to detect when the OVN system is up-to-date.

Message ID 1468866610-5334-1-git-send-email-blp@ovn.org
State Superseded
Headers show

Commit Message

Ben Pfaff July 18, 2016, 6:30 p.m. UTC
Until now, there has been no reliable for the CMS (or ovn-nbctl, or
anything else) to detect when changes made to the northbound configuration
have been passed through to the southbound database or to the hypervisors.
This commit adds this feature to the system, by adding sequence numbers
to the northbound and southbound databases and adding code in ovn-nbctl,
ovn-northd, and ovn-controller to keep those sequence numbers up-to-date.

The biggest user-visible change from this commit is new a new option
--wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for ovn-northd
to update the southbound database; with --wait=hv, it waits for the
changes to make their way to Open vSwitch on every hypervisor.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
v1->v2: Rebase to fix up database version number.

 include/openvswitch/list.h      |   6 ++
 lib/ovsdb-idl.c                 |   5 +-
 lib/ovsdb-idl.h                 |  10 ++-
 ovn/controller/chassis.c        |  15 ++--
 ovn/controller/chassis.h        |   5 +-
 ovn/controller/ofctrl.c         | 165 +++++++++++++++++++++++++++++++++++-----
 ovn/controller/ofctrl.h         |   6 +-
 ovn/controller/ovn-controller.c |  20 ++++-
 ovn/northd/ovn-northd.c         |  85 +++++++++++++++++----
 ovn/ovn-architecture.7.xml      |  74 ++++++++++++++++++
 ovn/ovn-nb.ovsschema            |  14 +++-
 ovn/ovn-nb.xml                  |  41 ++++++++++
 ovn/ovn-sb.ovsschema            |  13 +++-
 ovn/ovn-sb.xml                  |  34 +++++++++
 ovn/utilities/ovn-nbctl.8.xml   |  38 +++++++++
 ovn/utilities/ovn-nbctl.c       |  85 ++++++++++++++++++++-
 ovn/utilities/ovn-sbctl.8.in    |   4 +
 ovn/utilities/ovn-sbctl.c       |  21 ++++-
 tests/ofproto-macros.at         |   4 +-
 tests/ovn-nbctl.at              |   1 +
 tutorial/ovs-sandbox            |   5 +-
 utilities/ovs-sim.in            |   5 +-
 22 files changed, 595 insertions(+), 61 deletions(-)

Comments

Ryan Moats July 19, 2016, 3:29 a.m. UTC | #1
"dev" <dev-bounces@openvswitch.org> wrote on 07/18/2016 01:30:10 PM:

> From: Ben Pfaff <blp@ovn.org>
> To: dev@openvswitch.org
> Cc: Ben Pfaff <blp@ovn.org>
> Date: 07/18/2016 01:30 PM
> Subject: [ovs-dev] [PATCH v2] ovn: Make it possible for CMS to
> detect when the OVN system is up-to-date.
> Sent by: "dev" <dev-bounces@openvswitch.org>
>
> Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> anything else) to detect when changes made to the northbound
configuration
> have been passed through to the southbound database or to the
hypervisors.
> This commit adds this feature to the system, by adding sequence numbers
> to the northbound and southbound databases and adding code in ovn-nbctl,
> ovn-northd, and ovn-controller to keep those sequence numbers up-to-date.
>
> The biggest user-visible change from this commit is new a new option
> --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for ovn-northd
> to update the southbound database; with --wait=hv, it waits for the
> changes to make their way to Open vSwitch on every hypervisor.
>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---

Hey Ben, while I like this patch, it is going to put incremental
processing in merge conflict (again) if it lands first. I've put together
a rebased version of this patch that sits on top of the remaining pieces
of incremental processing and passes both compile and unit tests.
Am I breaking process if I submit it as V3 with an updated commit
message and an acked by even though I rebased it?

Ryan
Russell Bryant July 19, 2016, 2:06 p.m. UTC | #2
On Mon, Jul 18, 2016 at 2:30 PM, Ben Pfaff <blp@ovn.org> wrote:

> Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> anything else) to detect when changes made to the northbound configuration
> have been passed through to the southbound database or to the hypervisors.
> This commit adds this feature to the system, by adding sequence numbers
> to the northbound and southbound databases and adding code in ovn-nbctl,
> ovn-northd, and ovn-controller to keep those sequence numbers up-to-date.
>
> The biggest user-visible change from this commit is new a new option
> --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for ovn-northd
> to update the southbound database; with --wait=hv, it waits for the
> changes to make their way to Open vSwitch on every hypervisor.
>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
> v1->v2: Rebase to fix up database version number.
>

Cool feature.  :-)

Thanks a lot for the detailed documentation.  That made it easy to
understand how this is to be used.

I was trying to decide if the OpenStack Neutron plugin would make use of
this.  We currently use the 'up' column of Logical_Switch_Port.  As you
point out in docs, "up" means something different, as it signals the
successful binding of a port to a chassis.  I think that's really what we
want and would continue using.

A downside I thought of to using this from something like Neutron is that
it would block the whole cloud any time there's any sort of problem with
any hypervisor, which I don't think is desirable.

The feature still sounds very useful for testing and debugging, at a
minimum.

If this all makes sense, then I can proceed with a more detailed review of
the implementation.

Thanks,

+        /* Track the flow update. */
> +        struct ofctrl_flow_update *fup, *prev;
> +        LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) {
> +            if (nb_cfg < fup->nb_cfg) {
> +                /* This ofctrl_flow_update is for a configuration later
> than
> +                 * 'nb_cfg'.  This should not normally happen, because it
> means
> +                 * that 'nb_cfg' in the SB_Global table of the southbound
> +                 * database decreased, and it should normally be
> monotonically
> +                 * increasing. */
>

Would this also get hit if nb_cfg overflows?

That would sure be a *lot* of transactions, though ...
Ben Pfaff July 19, 2016, 6:40 p.m. UTC | #3
On Mon, Jul 18, 2016 at 10:29:34PM -0500, Ryan Moats wrote:
> "dev" <dev-bounces@openvswitch.org> wrote on 07/18/2016 01:30:10 PM:
> 
> > From: Ben Pfaff <blp@ovn.org>
> > To: dev@openvswitch.org
> > Cc: Ben Pfaff <blp@ovn.org>
> > Date: 07/18/2016 01:30 PM
> > Subject: [ovs-dev] [PATCH v2] ovn: Make it possible for CMS to
> > detect when the OVN system is up-to-date.
> > Sent by: "dev" <dev-bounces@openvswitch.org>
> >
> > Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> > anything else) to detect when changes made to the northbound
> configuration
> > have been passed through to the southbound database or to the
> hypervisors.
> > This commit adds this feature to the system, by adding sequence numbers
> > to the northbound and southbound databases and adding code in ovn-nbctl,
> > ovn-northd, and ovn-controller to keep those sequence numbers up-to-date.
> >
> > The biggest user-visible change from this commit is new a new option
> > --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for ovn-northd
> > to update the southbound database; with --wait=hv, it waits for the
> > changes to make their way to Open vSwitch on every hypervisor.
> >
> > Signed-off-by: Ben Pfaff <blp@ovn.org>
> > ---
> 
> Hey Ben, while I like this patch, it is going to put incremental
> processing in merge conflict (again) if it lands first. I've put together
> a rebased version of this patch that sits on top of the remaining pieces
> of incremental processing and passes both compile and unit tests.
> Am I breaking process if I submit it as V3 with an updated commit
> message and an acked by even though I rebased it?

You're worrying too much.  I'll rebase my patch as necessary.  I do it
routinely.
Ben Pfaff July 19, 2016, 7:45 p.m. UTC | #4
On Tue, Jul 19, 2016 at 10:06:09AM -0400, Russell Bryant wrote:
> On Mon, Jul 18, 2016 at 2:30 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> > Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> > anything else) to detect when changes made to the northbound configuration
> > have been passed through to the southbound database or to the hypervisors.
> > This commit adds this feature to the system, by adding sequence numbers
> > to the northbound and southbound databases and adding code in ovn-nbctl,
> > ovn-northd, and ovn-controller to keep those sequence numbers up-to-date.
> >
> > The biggest user-visible change from this commit is new a new option
> > --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for ovn-northd
> > to update the southbound database; with --wait=hv, it waits for the
> > changes to make their way to Open vSwitch on every hypervisor.
> >
> > Signed-off-by: Ben Pfaff <blp@ovn.org>
> > ---
> > v1->v2: Rebase to fix up database version number.
> >
> 
> Cool feature.  :-)
> 
> Thanks a lot for the detailed documentation.  That made it easy to
> understand how this is to be used.
> 
> I was trying to decide if the OpenStack Neutron plugin would make use of
> this.  We currently use the 'up' column of Logical_Switch_Port.  As you
> point out in docs, "up" means something different, as it signals the
> successful binding of a port to a chassis.  I think that's really what we
> want and would continue using.
> 
> A downside I thought of to using this from something like Neutron is that
> it would block the whole cloud any time there's any sort of problem with
> any hypervisor, which I don't think is desirable.
> 
> The feature still sounds very useful for testing and debugging, at a
> minimum.
> 
> If this all makes sense, then I can proceed with a more detailed review of
> the implementation.
> 
> Thanks,

That makes sense to me.

I don't envision a CMS using this on a per-transaction basis.  As you
say, it's mostly for testing and debugging.  I can see it being useful
in a couple of ways:

        * First, with some elaboration, a technique like this could be
          used to determine that finer-grained components are up to
          date.  For example, a logical switch is physically distributed
          across a certain set of hypervisors; we could add an hv_cfg
          column to the Logical_Switch table to indicate how up-to-date
          the logical switch is (and have ovn-northd populate it).

        * Second, it may be useful for monitoring.  If the system is
          taking a long time to get up-to-date, then it's a signal to
          look closer.

> +        /* Track the flow update. */
> > +        struct ofctrl_flow_update *fup, *prev;
> > +        LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) {
> > +            if (nb_cfg < fup->nb_cfg) {
> > +                /* This ofctrl_flow_update is for a configuration later
> > than
> > +                 * 'nb_cfg'.  This should not normally happen, because it
> > means
> > +                 * that 'nb_cfg' in the SB_Global table of the southbound
> > +                 * database decreased, and it should normally be
> > monotonically
> > +                 * increasing. */
> >
> 
> Would this also get hit if nb_cfg overflows?
> 
> That would sure be a *lot* of transactions, though ...

By my calculations, if we increment nb_cfg a million times a second, it
would almost 300,000 years to overflow back to -2**63.  By then, it's
likely that our users will have moved on to something new.
Russell Bryant July 19, 2016, 9:01 p.m. UTC | #5
On Tue, Jul 19, 2016 at 3:45 PM, Ben Pfaff <blp@ovn.org> wrote:

> On Tue, Jul 19, 2016 at 10:06:09AM -0400, Russell Bryant wrote:
> > On Mon, Jul 18, 2016 at 2:30 PM, Ben Pfaff <blp@ovn.org> wrote:
> >
> > > Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> > > anything else) to detect when changes made to the northbound
> configuration
> > > have been passed through to the southbound database or to the
> hypervisors.
> > > This commit adds this feature to the system, by adding sequence numbers
> > > to the northbound and southbound databases and adding code in
> ovn-nbctl,
> > > ovn-northd, and ovn-controller to keep those sequence numbers
> up-to-date.
> > >
> > > The biggest user-visible change from this commit is new a new option
> > > --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for
> ovn-northd
> > > to update the southbound database; with --wait=hv, it waits for the
> > > changes to make their way to Open vSwitch on every hypervisor.
> > >
> > > Signed-off-by: Ben Pfaff <blp@ovn.org>
> > > ---
> > > v1->v2: Rebase to fix up database version number.
> > >
> >
> > Cool feature.  :-)
> >
> > Thanks a lot for the detailed documentation.  That made it easy to
> > understand how this is to be used.
> >
> > I was trying to decide if the OpenStack Neutron plugin would make use of
> > this.  We currently use the 'up' column of Logical_Switch_Port.  As you
> > point out in docs, "up" means something different, as it signals the
> > successful binding of a port to a chassis.  I think that's really what we
> > want and would continue using.
> >
> > A downside I thought of to using this from something like Neutron is that
> > it would block the whole cloud any time there's any sort of problem with
> > any hypervisor, which I don't think is desirable.
> >
> > The feature still sounds very useful for testing and debugging, at a
> > minimum.
> >
> > If this all makes sense, then I can proceed with a more detailed review
> of
> > the implementation.
> >
> > Thanks,
>
> That makes sense to me.
>
> I don't envision a CMS using this on a per-transaction basis.  As you
> say, it's mostly for testing and debugging.  I can see it being useful
> in a couple of ways:
>
>         * First, with some elaboration, a technique like this could be
>           used to determine that finer-grained components are up to
>           date.  For example, a logical switch is physically distributed
>           across a certain set of hypervisors; we could add an hv_cfg
>           column to the Logical_Switch table to indicate how up-to-date
>           the logical switch is (and have ovn-northd populate it).
>
>         * Second, it may be useful for monitoring.  If the system is
>           taking a long time to get up-to-date, then it's a signal to
>           look closer.
>

Thanks!  Makes sense to me, too.


> > +        /* Track the flow update. */
> > > +        struct ofctrl_flow_update *fup, *prev;
> > > +        LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node,
> &flow_updates) {
> > > +            if (nb_cfg < fup->nb_cfg) {
> > > +                /* This ofctrl_flow_update is for a configuration
> later
> > > than
> > > +                 * 'nb_cfg'.  This should not normally happen,
> because it
> > > means
> > > +                 * that 'nb_cfg' in the SB_Global table of the
> southbound
> > > +                 * database decreased, and it should normally be
> > > monotonically
> > > +                 * increasing. */
> > >
> >
> > Would this also get hit if nb_cfg overflows?
> >
> > That would sure be a *lot* of transactions, though ...
>
> By my calculations, if we increment nb_cfg a million times a second, it
> would almost 300,000 years to overflow back to -2**63.  By then, it's
> likely that our users will have moved on to something new.
>

lol.  I *guess* it's ok, then.
Ryan Moats July 24, 2016, 7:55 p.m. UTC | #6
Ben Pfaff <blp@ovn.org> wrote on 07/19/2016 01:40:29 PM:

> From: Ben Pfaff <blp@ovn.org>
> To: Ryan Moats/Omaha/IBM@IBMUS
> Cc: dev@openvswitch.org
> Date: 07/19/2016 01:40 PM
> Subject: Re: [ovs-dev] [PATCH v2] ovn: Make it possible for CMS to
> detect when the OVN system is up-to-date.
>
> On Mon, Jul 18, 2016 at 10:29:34PM -0500, Ryan Moats wrote:
> > "dev" <dev-bounces@openvswitch.org> wrote on 07/18/2016 01:30:10 PM:
> >
> > > From: Ben Pfaff <blp@ovn.org>
> > > To: dev@openvswitch.org
> > > Cc: Ben Pfaff <blp@ovn.org>
> > > Date: 07/18/2016 01:30 PM
> > > Subject: [ovs-dev] [PATCH v2] ovn: Make it possible for CMS to
> > > detect when the OVN system is up-to-date.
> > > Sent by: "dev" <dev-bounces@openvswitch.org>
> > >
> > > Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> > > anything else) to detect when changes made to the northbound
> > configuration
> > > have been passed through to the southbound database or to the
> > hypervisors.
> > > This commit adds this feature to the system, by adding sequence
numbers
> > > to the northbound and southbound databases and adding code in
ovn-nbctl,
> > > ovn-northd, and ovn-controller to keep those sequence numbers
up-to-date.
> > >
> > > The biggest user-visible change from this commit is new a new option
> > > --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for
ovn-northd
> > > to update the southbound database; with --wait=hv, it waits for the
> > > changes to make their way to Open vSwitch on every hypervisor.
> > >
> > > Signed-off-by: Ben Pfaff <blp@ovn.org>
> > > ---
> >
> > Hey Ben, while I like this patch, it is going to put incremental
> > processing in merge conflict (again) if it lands first. I've put
together
> > a rebased version of this patch that sits on top of the remaining
pieces
> > of incremental processing and passes both compile and unit tests.
> > Am I breaking process if I submit it as V3 with an updated commit
> > message and an acked by even though I rebased it?
>
> You're worrying too much.  I'll rebase my patch as necessary.  I do it
> routinely.

Ok, can you please send a rebase?  I went to take a look at it today and
it doesn't apply cleanly anymore...
Ben Pfaff July 24, 2016, 8:15 p.m. UTC | #7
On Sun, Jul 24, 2016 at 02:55:25PM -0500, Ryan Moats wrote:
> Ben Pfaff <blp@ovn.org> wrote on 07/19/2016 01:40:29 PM:
> 
> > From: Ben Pfaff <blp@ovn.org>
> > To: Ryan Moats/Omaha/IBM@IBMUS
> > Cc: dev@openvswitch.org
> > Date: 07/19/2016 01:40 PM
> > Subject: Re: [ovs-dev] [PATCH v2] ovn: Make it possible for CMS to
> > detect when the OVN system is up-to-date.
> >
> > On Mon, Jul 18, 2016 at 10:29:34PM -0500, Ryan Moats wrote:
> > > "dev" <dev-bounces@openvswitch.org> wrote on 07/18/2016 01:30:10 PM:
> > >
> > > > From: Ben Pfaff <blp@ovn.org>
> > > > To: dev@openvswitch.org
> > > > Cc: Ben Pfaff <blp@ovn.org>
> > > > Date: 07/18/2016 01:30 PM
> > > > Subject: [ovs-dev] [PATCH v2] ovn: Make it possible for CMS to
> > > > detect when the OVN system is up-to-date.
> > > > Sent by: "dev" <dev-bounces@openvswitch.org>
> > > >
> > > > Until now, there has been no reliable for the CMS (or ovn-nbctl, or
> > > > anything else) to detect when changes made to the northbound
> > > configuration
> > > > have been passed through to the southbound database or to the
> > > hypervisors.
> > > > This commit adds this feature to the system, by adding sequence
> numbers
> > > > to the northbound and southbound databases and adding code in
> ovn-nbctl,
> > > > ovn-northd, and ovn-controller to keep those sequence numbers
> up-to-date.
> > > >
> > > > The biggest user-visible change from this commit is new a new option
> > > > --wait to ovn-nbctl.  With --wait=sb, ovn-nbctl now waits for
> ovn-northd
> > > > to update the southbound database; with --wait=hv, it waits for the
> > > > changes to make their way to Open vSwitch on every hypervisor.
> > > >
> > > > Signed-off-by: Ben Pfaff <blp@ovn.org>
> > > > ---
> > >
> > > Hey Ben, while I like this patch, it is going to put incremental
> > > processing in merge conflict (again) if it lands first. I've put
> together
> > > a rebased version of this patch that sits on top of the remaining
> pieces
> > > of incremental processing and passes both compile and unit tests.
> > > Am I breaking process if I submit it as V3 with an updated commit
> > > message and an acked by even though I rebased it?
> >
> > You're worrying too much.  I'll rebase my patch as necessary.  I do it
> > routinely.
> 
> Ok, can you please send a rebase?  I went to take a look at it today and
> it doesn't apply cleanly anymore...

OK, sent:
        https://patchwork.ozlabs.org/patch/652126/
diff mbox

Patch

diff --git a/include/openvswitch/list.h b/include/openvswitch/list.h
index 5c2cca4..454edf0 100644
--- a/include/openvswitch/list.h
+++ b/include/openvswitch/list.h
@@ -80,6 +80,12 @@  static inline bool ovs_list_is_short(const struct ovs_list *);
     for (INIT_CONTAINER(ITER, (LIST)->prev, MEMBER);                    \
          &(ITER)->MEMBER != (LIST);                                     \
          ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER))
+#define LIST_FOR_EACH_REVERSE_SAFE(ITER, PREV, MEMBER, LIST)        \
+    for (INIT_CONTAINER(ITER, (LIST)->prev, MEMBER);                \
+         (&(ITER)->MEMBER != (LIST)                                 \
+          ? INIT_CONTAINER(PREV, (ITER)->MEMBER.prev, MEMBER), 1    \
+          : 0);                                                     \
+         (ITER) = (PREV))
 #define LIST_FOR_EACH_REVERSE_CONTINUE(ITER, MEMBER, LIST)              \
     for (ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER);           \
          &(ITER)->MEMBER != (LIST);                                     \
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
index 9b3e933..eae2d8f 100644
--- a/lib/ovsdb-idl.c
+++ b/lib/ovsdb-idl.c
@@ -3501,12 +3501,16 @@  ovsdb_idl_loop_commit_and_wait(struct ovsdb_idl_loop *loop)
                 /* If the database has already changed since we started the
                  * commit, re-evaluate it immediately to avoid missing a change
                  * for a while. */
+                loop->cur_cfg = loop->next_cfg;
                 if (ovsdb_idl_get_seqno(loop->idl) != loop->precommit_seqno) {
                     poll_immediate_wake();
                 }
                 break;
 
             case TXN_UNCHANGED:
+                loop->cur_cfg = loop->next_cfg;
+                break;
+
             case TXN_ABORTED:
             case TXN_NOT_LOCKED:
             case TXN_ERROR:
@@ -3515,7 +3519,6 @@  ovsdb_idl_loop_commit_and_wait(struct ovsdb_idl_loop *loop)
             case TXN_UNCOMMITTED:
             case TXN_INCOMPLETE:
                 OVS_NOT_REACHED();
-
             }
             ovsdb_idl_txn_destroy(txn);
             loop->committing_txn = NULL;
diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h
index 5edffc4..81eee69 100644
--- a/lib/ovsdb-idl.h
+++ b/lib/ovsdb-idl.h
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+/* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -290,6 +290,14 @@  struct ovsdb_idl_loop {
     unsigned int precommit_seqno;
 
     struct ovsdb_idl_txn *open_txn;
+
+    /* These members allow a client a simple, stateless way to keep track of
+     * transactions that commit: when a transaction commits successfully,
+     * ovsdb_idl_loop_commit_and_wait() copies 'next_cfg' to 'cur_cfg'.  Thus,
+     * the client can set 'next_cfg' to a value that indicates a successful
+     * commit and check 'cur_cfg' on each iteration. */
+    int64_t cur_cfg;
+    int64_t next_cfg;
 };
 
 #define OVSDB_IDL_LOOP_INITIALIZER(IDL) { .idl = (IDL) }
diff --git a/ovn/controller/chassis.c b/ovn/controller/chassis.c
index d40181b..a430539 100644
--- a/ovn/controller/chassis.c
+++ b/ovn/controller/chassis.c
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2015 Nicira, Inc.
+/* Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -63,11 +63,13 @@  get_bridge_mappings(const struct smap *ext_ids)
     return bridge_mappings ? bridge_mappings : "";
 }
 
-void
+/* Returns this chassis's Chassis record, if it is available and is currently
+ * amenable to a transaction. */
+const struct sbrec_chassis *
 chassis_run(struct controller_ctx *ctx, const char *chassis_id)
 {
     if (!ctx->ovnsb_idl_txn) {
-        return;
+        return NULL;
     }
 
     const struct ovsrec_open_vswitch *cfg;
@@ -77,14 +79,14 @@  chassis_run(struct controller_ctx *ctx, const char *chassis_id)
     cfg = ovsrec_open_vswitch_first(ctx->ovs_idl);
     if (!cfg) {
         VLOG_INFO("No Open_vSwitch row defined.");
-        return;
+        return NULL;
     }
 
     encap_type = smap_get(&cfg->external_ids, "ovn-encap-type");
     encap_ip = smap_get(&cfg->external_ids, "ovn-encap-ip");
     if (!encap_type || !encap_ip) {
         VLOG_INFO("Need to specify an encap type and ip");
-        return;
+        return NULL;
     }
 
     char *tokstr = xstrdup(encap_type);
@@ -143,7 +145,7 @@  chassis_run(struct controller_ctx *ctx, const char *chassis_id)
         if (same) {
             /* Nothing changed. */
             inited = true;
-            return;
+            return chassis_rec;
         } else if (!inited) {
             struct ds cur_encaps = DS_EMPTY_INITIALIZER;
             for (int i = 0; i < chassis_rec->n_encaps; i++) {
@@ -189,6 +191,7 @@  chassis_run(struct controller_ctx *ctx, const char *chassis_id)
     free(encaps);
 
     inited = true;
+    return chassis_rec;
 }
 
 /* Returns true if the database is all cleaned up, false if more work is
diff --git a/ovn/controller/chassis.h b/ovn/controller/chassis.h
index 26017d0..a14da1c 100644
--- a/ovn/controller/chassis.h
+++ b/ovn/controller/chassis.h
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2015 Nicira, Inc.
+/* Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@  struct ovsdb_idl;
 struct ovsrec_bridge;
 
 void chassis_register_ovs_idl(struct ovsdb_idl *);
-void chassis_run(struct controller_ctx *, const char *chassis_id);
+const struct sbrec_chassis *chassis_run(struct controller_ctx *,
+                                        const char *chassis_id);
 bool chassis_cleanup(struct controller_ctx *, const char *chassis_id);
 
 #endif /* ovn/chassis.h */
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
index b451453..d224dc6 100644
--- a/ovn/controller/ofctrl.c
+++ b/ovn/controller/ofctrl.c
@@ -60,14 +60,9 @@  static char *ovn_flow_to_string(const struct ovn_flow *);
 static void ovn_flow_log(const struct ovn_flow *, const char *action);
 static void ovn_flow_destroy(struct ovn_flow *);
 
-static ovs_be32 queue_msg(struct ofpbuf *);
-static void queue_flow_mod(struct ofputil_flow_mod *);
-
 /* OpenFlow connection to the switch. */
 static struct rconn *swconn;
 
-static void queue_group_mod(struct ofputil_group_mod *);
-
 /* Last seen sequence number for 'swconn'.  When this differs from
  * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */
 static unsigned int seqno;
@@ -85,6 +80,30 @@  enum ofctrl_state {
 #undef STATE
 };
 
+/* An in-flight update to the switch's flow table.
+ *
+ * When we receive a barrier reply from the switch with the given 'xid', we
+ * know that the switch is caught up to northbound database sequence number
+ * 'nb_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), so
+ * that it can store it into our Chassis record's nb_cfg column). */
+struct ofctrl_flow_update {
+    struct ovs_list list_node;  /* In 'flow_updates'. */
+    ovs_be32 xid;               /* OpenFlow transaction ID for barrier. */
+    int64_t nb_cfg;             /* Northbound database sequence number. */
+};
+
+static struct ofctrl_flow_update *
+ofctrl_flow_update_from_list_node(const struct ovs_list *list_node)
+{
+    return CONTAINER_OF(list_node, struct ofctrl_flow_update, list_node);
+}
+
+/* Currently in-flight updates. */
+static struct ovs_list flow_updates;
+
+/* nb_cfg of latest committed flow update. */
+static int64_t cur_cfg;
+
 /* Current state. */
 static enum ofctrl_state state;
 
@@ -108,11 +127,15 @@  static struct group_table *groups;
  * S_CLEAR_FLOWS or S_UPDATE_FLOWS, this is really the option we have. */
 static enum mf_field_id mff_ovn_geneve;
 
+static ovs_be32 queue_msg(struct ofpbuf *);
+
 static void ovn_flow_table_clear(struct hmap *flow_table);
 static void ovn_flow_table_destroy(struct hmap *flow_table);
+static struct ofpbuf *encode_flow_mod(struct ofputil_flow_mod *);
 
 static void ovn_group_table_clear(struct group_table *group_table,
                                   bool existing);
+static struct ofpbuf *encode_group_mod(const struct ofputil_group_mod *);
 
 static void ofctrl_recv(const struct ofp_header *, enum ofptype);
 
@@ -122,6 +145,7 @@  ofctrl_init(void)
     swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
     tx_counter = rconn_packet_counter_create();
     hmap_init(&installed_flows);
+    ovs_list_init(&flow_updates);
 }
 
 /* S_NEW, for a new connection.
@@ -320,16 +344,17 @@  run_S_CLEAR_FLOWS(void)
         .table_id = OFPTT_ALL,
         .command = OFPFC_DELETE,
     };
-    queue_flow_mod(&fm);
+    queue_msg(encode_flow_mod(&fm));
     VLOG_DBG("clearing all flows");
 
+    /* Send a group_mod to delete all groups. */
     struct ofputil_group_mod gm;
     memset(&gm, 0, sizeof gm);
     gm.command = OFPGC11_DELETE;
     gm.group_id = OFPG_ALL;
     gm.command_bucket_id = OFPG15_BUCKET_ALL;
     ovs_list_init(&gm.buckets);
-    queue_group_mod(&gm);
+    queue_msg(encode_group_mod(&gm));
     ofputil_bucket_list_destroy(&gm.buckets);
 
     /* Clear installed_flows, to match the state of the switch. */
@@ -340,6 +365,13 @@  run_S_CLEAR_FLOWS(void)
         ovn_group_table_clear(groups, true);
     }
 
+    /* All flow updates are irrelevant now. */
+    struct ofctrl_flow_update *fup, *next;
+    LIST_FOR_EACH_SAFE (fup, next, list_node, &flow_updates) {
+        ovs_list_remove(&fup->list_node);
+        free(fup);
+    }
+
     state = S_UPDATE_FLOWS;
 }
 
@@ -368,7 +400,19 @@  run_S_UPDATE_FLOWS(void)
 static void
 recv_S_UPDATE_FLOWS(const struct ofp_header *oh, enum ofptype type)
 {
-    ofctrl_recv(oh, type);
+    if (type == OFPTYPE_BARRIER_REPLY && !ovs_list_is_empty(&flow_updates)) {
+        struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node(
+            ovs_list_front(&flow_updates));
+        if (fup->xid == oh->xid) {
+            if (fup->nb_cfg >= cur_cfg) {
+                cur_cfg = fup->nb_cfg;
+            }
+            ovs_list_remove(&fup->list_node);
+            free(fup);
+        }
+    } else {
+        ofctrl_recv(oh, type);
+    }
 }
 
 /* Runs the OpenFlow state machine against 'br_int', which is local to the
@@ -459,6 +503,12 @@  ofctrl_destroy(void)
     ovn_flow_table_destroy(&installed_flows);
     rconn_packet_counter_destroy(tx_counter);
 }
+
+int64_t
+ofctrl_get_cur_cfg(void)
+{
+    return cur_cfg;
+}
 
 static ovs_be32
 queue_msg(struct ofpbuf *msg)
@@ -616,15 +666,21 @@  ovn_flow_table_destroy(struct hmap *flow_table)
 
 /* Flow table update. */
 
-static void
-queue_flow_mod(struct ofputil_flow_mod *fm)
+static struct ofpbuf *
+encode_flow_mod(struct ofputil_flow_mod *fm)
 {
     fm->buffer_id = UINT32_MAX;
     fm->out_port = OFPP_ANY;
     fm->out_group = OFPG_ANY;
-    queue_msg(ofputil_encode_flow_mod(fm, OFPUTIL_P_OF13_OXM));
+    return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF13_OXM);
 }
 
+static void
+add_flow_mod(struct ofputil_flow_mod *fm, struct ovs_list *msgs)
+{
+    struct ofpbuf *msg = encode_flow_mod(fm);
+    ovs_list_push_back(msgs, &msg->list_node);
+}
 
 /* group_table. */
 
@@ -662,13 +718,19 @@  ovn_group_table_clear(struct group_table *group_table, bool existing)
     }
 }
 
+static struct ofpbuf *
+encode_group_mod(const struct ofputil_group_mod *gm)
+{
+    return ofputil_encode_group_mod(OFP13_VERSION, gm);
+}
+
 static void
-queue_group_mod(struct ofputil_group_mod *gm)
+add_group_mod(const struct ofputil_group_mod *gm, struct ovs_list *msgs)
 {
-    queue_msg(ofputil_encode_group_mod(OFP13_VERSION, gm));
+    struct ofpbuf *msg = encode_group_mod(gm);
+    ovs_list_push_back(msgs, &msg->list_node);
 }
 
-
 /* Replaces the flow table on the switch, if possible, by the flows in
  * 'flow_table', which should have been added with ofctrl_add_flow().
  * Regardless of whether the flow table is updated, this deletes all of the
@@ -683,7 +745,8 @@  queue_group_mod(struct ofputil_group_mod *gm)
  *
  * This should be called after ofctrl_run() within the main loop. */
 void
-ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
+ofctrl_put(struct hmap *flow_table, struct group_table *group_table,
+           int64_t nb_cfg)
 {
     if (!groups) {
         groups = group_table;
@@ -702,6 +765,9 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
         return;
     }
 
+    /* OpenFlow messages to send to the switch to bring it up-to-date. */
+    struct ovs_list msgs = OVS_LIST_INITIALIZER(&msgs);
+
     /* Iterate through all the desired groups. If there are new ones,
      * add them to the switch. */
     struct group_info *desired;
@@ -719,7 +785,7 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
                                             ds_cstr(&group_string),
                                             &usable_protocols);
             if (!error) {
-                queue_group_mod(&gm);
+                add_group_mod(&gm, &msgs);
             } else {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
                 VLOG_ERR_RL(&rl, "new group %s %s", error,
@@ -746,7 +812,7 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
                 .table_id = i->table_id,
                 .command = OFPFC_DELETE_STRICT,
             };
-            queue_flow_mod(&fm);
+            add_flow_mod(&fm, &msgs);
             ovn_flow_log(i, "removing");
 
             hmap_remove(&installed_flows, &i->hmap_node);
@@ -763,7 +829,7 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
                     .ofpacts_len = d->ofpacts_len,
                     .command = OFPFC_MODIFY_STRICT,
                 };
-                queue_flow_mod(&fm);
+                add_flow_mod(&fm, &msgs);
                 ovn_flow_log(i, "updating");
 
                 /* Replace 'i''s actions by 'd''s. */
@@ -793,7 +859,7 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
             .ofpacts_len = d->ofpacts_len,
             .command = OFPFC_ADD,
         };
-        queue_flow_mod(&fm);
+        add_flow_mod(&fm, &msgs);
         ovn_flow_log(d, "adding");
 
         /* Move 'd' from 'flow_table' to installed_flows. */
@@ -818,7 +884,7 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
                                             ds_cstr(&group_string),
                                             &usable_protocols);
             if (!error) {
-                queue_group_mod(&gm);
+                add_group_mod(&gm, &msgs);
             } else {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
                 VLOG_ERR_RL(&rl, "Error deleting group %d: %s",
@@ -850,4 +916,63 @@  ofctrl_put(struct hmap *flow_table, struct group_table *group_table)
             free(desired);
         }
     }
+
+    if (!ovs_list_is_empty(&msgs)) {
+        /* Add a barrier to the list of messages. */
+        struct ofpbuf *barrier = ofputil_encode_barrier_request(OFP13_VERSION);
+        const struct ofp_header *oh = barrier->data;
+        ovs_be32 xid = oh->xid;
+        ovs_list_push_back(&msgs, &barrier->list_node);
+
+        /* Queue the messages. */
+        struct ofpbuf *msg;
+        LIST_FOR_EACH_POP (msg, list_node, &msgs) {
+            queue_msg(msg);
+        }
+
+        /* Track the flow update. */
+        struct ofctrl_flow_update *fup, *prev;
+        LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) {
+            if (nb_cfg < fup->nb_cfg) {
+                /* This ofctrl_flow_update is for a configuration later than
+                 * 'nb_cfg'.  This should not normally happen, because it means
+                 * that 'nb_cfg' in the SB_Global table of the southbound
+                 * database decreased, and it should normally be monotonically
+                 * increasing. */
+                VLOG_WARN("nb_cfg regressed from %"PRId64" to %"PRId64,
+                          fup->nb_cfg, nb_cfg);
+                ovs_list_remove(&fup->list_node);
+                free(fup);
+            } else if (nb_cfg == fup->nb_cfg) {
+                /* This ofctrl_flow_update is for the same configuration as
+                 * 'nb_cfg'.  Probably, some change to the physical topology
+                 * means that we had to revise the OpenFlow flow table even
+                 * though the logical topology did not change.  Update fp->xid,
+                 * so that we don't send a notification that we're up-to-date
+                 * until we're really caught up. */
+                VLOG_DBG("advanced xid target for nb_cfg=%"PRId64, nb_cfg);
+                fup->xid = xid;
+                goto done;
+            } else {
+                break;
+            }
+        }
+
+        /* Add a flow update. */
+        fup = xmalloc(sizeof *fup);
+        ovs_list_push_back(&flow_updates, &fup->list_node);
+        fup->xid = xid;
+        fup->nb_cfg = nb_cfg;
+    done:;
+    } else if (!ovs_list_is_empty(&flow_updates)) {
+        /* Getting up-to-date with 'nb_cfg' didn't require any extra flow table
+         * changes, so whenever we get up-to-date with the most recent flow
+         * table update, we're also up-to-date with 'nb_cfg'. */
+        struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node(
+            ovs_list_back(&flow_updates));
+        fup->nb_cfg = nb_cfg;
+    } else {
+        /* We were completely up-to-date before and still are. */
+        cur_cfg = nb_cfg;
+    }
 }
diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
index bf5dfd5..cf544b2 100644
--- a/ovn/controller/ofctrl.h
+++ b/ovn/controller/ofctrl.h
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2015 Nicira, Inc.
+/* Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,9 +31,11 @@  struct group_table;
 /* Interface for OVN main loop. */
 void ofctrl_init(void);
 enum mf_field_id ofctrl_run(const struct ovsrec_bridge *br_int);
-void ofctrl_put(struct hmap *flows, struct group_table *group_table);
+void ofctrl_put(struct hmap *flows, struct group_table *group_table,
+                int64_t nb_cfg);
 void ofctrl_wait(void);
 void ofctrl_destroy(void);
+int64_t ofctrl_get_cur_cfg(void);
 
 /* Flow table interface to the rest of ovn-controller. */
 void ofctrl_add_flow(struct hmap *flows, uint8_t table_id, uint16_t priority,
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 28ee13e..1317218 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -299,6 +299,13 @@  update_ct_zones(struct sset *lports, struct hmap *patched_datapaths,
     sset_destroy(&all_users);
 }
 
+static int64_t
+get_nb_cfg(struct ovsdb_idl *idl)
+{
+    const struct sbrec_sb_global *sb = sbrec_sb_global_first(idl);
+    return sb ? sb->nb_cfg : 0;
+}
+
 /* Contains "struct local_datapath" nodes whose hash values are the
  * tunnel_key of datapaths with at least one local port binding. */
 static struct hmap local_datapaths = HMAP_INITIALIZER(&local_datapaths);
@@ -378,6 +385,7 @@  main(int argc, char *argv[])
     char *ovnsb_remote = get_ovnsb_remote(ovs_idl_loop.idl);
     struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
         ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true));
+    ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
 
     /* Track the southbound idl. */
     ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
@@ -422,8 +430,9 @@  main(int argc, char *argv[])
         const struct ovsrec_bridge *br_int = get_br_int(&ctx);
         const char *chassis_id = get_chassis_id(ctx.ovs_idl);
 
+        const struct sbrec_chassis *chassis = NULL;
         if (chassis_id) {
-            chassis_run(&ctx, chassis_id);
+            chassis = chassis_run(&ctx, chassis_id);
             encaps_run(&ctx, br_int, chassis_id);
             binding_run(&ctx, br_int, chassis_id, &local_datapaths);
         }
@@ -450,8 +459,15 @@  main(int argc, char *argv[])
                              br_int, chassis_id, &ct_zones, &flow_table,
                              &local_datapaths, &patched_datapaths);
             }
-            ofctrl_put(&flow_table, &group_table);
+            ofctrl_put(&flow_table, &group_table, get_nb_cfg(ctx.ovnsb_idl));
             hmap_destroy(&flow_table);
+
+            if (chassis && ctx.ovnsb_idl_txn) {
+                int64_t cur_cfg = ofctrl_get_cur_cfg();
+                if (cur_cfg && cur_cfg != chassis->nb_cfg) {
+                    sbrec_chassis_set_nb_cfg(chassis, cur_cfg);
+                }
+            }
         }
 
         sset_destroy(&all_lports);
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 7ce509d..e0fc8db 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -2885,9 +2885,9 @@  sync_address_sets(struct northd_context *ctx)
 }
 
 static void
-ovnnb_db_run(struct northd_context *ctx)
+ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
 {
-    if (!ctx->ovnsb_txn) {
+    if (!ctx->ovnsb_txn || !ovsdb_idl_has_ever_connected(ctx->ovnnb_idl)) {
         return;
     }
     struct hmap datapaths, ports;
@@ -2908,19 +2908,24 @@  ovnnb_db_run(struct northd_context *ctx)
         ovn_port_destroy(&ports, port);
     }
     hmap_destroy(&ports);
+
+    /* Copy nb_cfg from northbound to southbound database.
+     *
+     * Also set up to update sb_cfg once our southbound transaction commits. */
+    const struct nbrec_nb_global *nb = nbrec_nb_global_first(ctx->ovnnb_idl);
+    const struct sbrec_sb_global *sb = sbrec_sb_global_first(ctx->ovnsb_idl);
+    if (nb && sb) {
+        sbrec_sb_global_set_nb_cfg(sb, nb->nb_cfg);
+        sb_loop->next_cfg = nb->nb_cfg;
+    }
 }
 
-/*
- * The only change we get notified about is if the 'chassis' column of the
- * 'Port_Binding' table changes.  When this column is not empty, it means we
- * need to set the corresponding logical port as 'up' in the northbound DB.
- */
+/* Handle changes to the 'chassis' column of the 'Port_Binding' table.  When
+ * this column is not empty, it means we need to set the corresponding logical
+ * port as 'up' in the northbound DB. */
 static void
-ovnsb_db_run(struct northd_context *ctx)
+update_logical_port_status(struct northd_context *ctx)
 {
-    if (!ctx->ovnnb_txn) {
-        return;
-    }
     struct hmap lports_hmap;
     const struct sbrec_port_binding *sb;
     const struct nbrec_logical_switch_port *nb;
@@ -2970,8 +2975,49 @@  ovnsb_db_run(struct northd_context *ctx)
     }
     hmap_destroy(&lports_hmap);
 }
-
 
+/* Updates the sb_cfg and hv_cfg columns in the northbound NB_Global table. */
+static void
+update_northbound_cfg(struct northd_context *ctx,
+                      struct ovsdb_idl_loop *sb_loop)
+{
+    /* Update northbound sb_cfg if appropriate. */
+    const struct nbrec_nb_global *nbg = nbrec_nb_global_first(ctx->ovnnb_idl);
+    int64_t sb_cfg = sb_loop->cur_cfg;
+    if (nbg && sb_cfg && nbg->sb_cfg != sb_cfg) {
+        nbrec_nb_global_set_sb_cfg(nbg, sb_cfg);
+    }
+
+    /* Update northbound hv_cfg if appropriate. */
+    if (nbg) {
+        /* Find minimum nb_cfg among all chassis. */
+        const struct sbrec_chassis *chassis;
+        int64_t hv_cfg = nbg->nb_cfg;
+        SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) {
+            if (chassis->nb_cfg < hv_cfg) {
+                hv_cfg = chassis->nb_cfg;
+            }
+        }
+
+        /* Update hv_cfg. */
+        if (nbg->hv_cfg != hv_cfg) {
+            nbrec_nb_global_set_hv_cfg(nbg, hv_cfg);
+        }
+    }
+}
+
+/* Handle a fairly small set of changes in the southbound database. */
+static void
+ovnsb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
+{
+    if (!ctx->ovnnb_txn || !ovsdb_idl_has_ever_connected(ctx->ovnsb_idl)) {
+        return;
+    }
+
+    update_logical_port_status(ctx);
+    update_northbound_cfg(ctx, sb_loop);
+}
+
 static char *default_nb_db_;
 
 static const char *
@@ -3097,13 +3143,19 @@  main(int argc, char *argv[])
     nbrec_init();
     sbrec_init();
 
-    /* We want to detect all changes to the ovn-nb db. */
+    /* We want to detect (almost) all changes to the ovn-nb db. */
     struct ovsdb_idl_loop ovnnb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
         ovsdb_idl_create(ovnnb_db, &nbrec_idl_class, true, true));
+    ovsdb_idl_omit_alert(ovnnb_idl_loop.idl, &nbrec_nb_global_col_sb_cfg);
+    ovsdb_idl_omit_alert(ovnnb_idl_loop.idl, &nbrec_nb_global_col_hv_cfg);
 
+    /* We want to detect only selected changes to the ovn-sb db. */
     struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
         ovsdb_idl_create(ovnsb_db, &sbrec_idl_class, false, true));
 
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_sb_global);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_nb_cfg);
+
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_logical_flow);
     add_column_noalert(ovnsb_idl_loop.idl,
                        &sbrec_logical_flow_col_logical_datapath);
@@ -3145,6 +3197,9 @@  main(int argc, char *argv[])
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses);
 
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
+
     /* Main loop. */
     exiting = false;
     while (!exiting) {
@@ -3155,8 +3210,8 @@  main(int argc, char *argv[])
             .ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
         };
 
-        ovnnb_db_run(&ctx);
-        ovnsb_db_run(&ctx);
+        ovnnb_db_run(&ctx, &ovnsb_idl_loop);
+        ovnsb_db_run(&ctx, &ovnsb_idl_loop);
 
         unixctl_server_run(unixctl);
         unixctl_server_wait(unixctl);
diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
index ead4feb..fe20a14 100644
--- a/ovn/ovn-architecture.7.xml
+++ b/ovn/ovn-architecture.7.xml
@@ -200,6 +200,80 @@ 
 +-------------------------------+     +-------------------------------+
   </pre>
 
+  <h2>Information Flow in OVN</h2>
+
+  <p>
+    Configuration data in OVN flows from north to south.  The CMS, through its
+    OVN/CMS plugin, passes the logical network configuration to
+    <code>ovn-northd</code> via the northbound database.  In turn,
+    <code>ovn-northd</code> compiles the configuration into a lower-level form
+    and passes it to all of the chassis via the southbound database.
+  </p>
+
+  <p>
+    Status information in OVN flows from south to north.  OVN currently
+    provides only a few forms of status information.  First,
+    <code>ovn-northd</code> populates the <code>up</code> column in the
+    northbound <code>Logical_Switch_Port</code> table: if a logical port's
+    <code>chassis</code> column in the southbound <code>Port_Binding</code>
+    table is nonempty, it sets <code>up</code> to <code>true</code>, otherwise
+    to <code>false</code>.  This allows the CMS to detect when a VM's
+    networking has come up.
+  </p>
+
+  <p>
+    Second, OVN provides feedback to the CMS on the realization of its
+    configuration, that is, whether the configuration provided by the CMS has
+    taken effect.  This feature requires the CMS to participate in a sequence
+    number protocol, which works the following way:
+  </p>
+
+  <ol>
+    <li>
+      When the CMS updates the configuration in the northbound database, as
+      part of the same transaction, it increments the value of the
+      <code>nb_cfg</code> column in the <code>NB_Global</code> table.  (This is
+      only necessary if the CMS wants to know when the configuration has been
+      realized.)
+    </li>
+
+    <li>
+      When <code>ovn-northd</code> updates the southbound database based on a
+      given snapshot of the northbound database, it copies <code>nb_cfg</code>
+      from northbound <code>NB_Global</code> into the southbound database
+      <code>SB_Global</code> table, as part of the same transaction.  (Thus, an
+      observer monitoring both databases can determine when the southbound
+      database is caught up with the northbound.)
+    </li>
+
+    <li>
+      After <code>ovn-northd</code> receives confirmation from the southbound
+      database server that its changes have committed, it updates
+      <code>sb_cfg</code> in the northbound <code>NB_Global</code> table to the
+      <code>nb_cfg</code> version that was pushed down.  (Thus, the CMS or
+      another observer can determine when the southbound database is caught up
+      without a connection to the southbound database.)
+    </li>
+
+    <li>
+      The <code>ovn-controller</code> process on each chassis receives the
+      updated southbound database, with the updated <code>nb_cfg</code>.  This
+      process in turn updates the physical flows installed in the chassis's
+      Open vSwitch instances.  When it receives confirmation from Open vSwitch
+      that the physical flows have been updated, it updates <code>nb_cfg</code>
+      in its own <code>Chassis</code> record in the southbound database.
+    </li>
+
+    <li>
+      <code>ovn-northd</code> monitors the <code>nb_cfg</code> column in all of
+      the <code>Chassis</code> records in the southbound database.  It keeps
+      track of the minimum value among all the records and copies it into the
+      <code>hv_cfg</code> column in the northbound <code>NB_Global</code>
+      table.  (Thus, the CMS or another observer can determine when all of the
+      hypervisors have caught up to the northbound configuration.)
+    </li>
+  </ol>
+
   <h2>Chassis Setup</h2>
 
   <p>
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 460d5bd..272bf92 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,8 +1,18 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.0.0",
-    "cksum": "849073644 7576",
+    "version": "5.1.0",
+    "cksum": "2056575276 8008",
     "tables": {
+        "NB_Global": {
+            "columns": {
+                "nb_cfg": {"type": {"key": "integer"}},
+                "sb_cfg": {"type": {"key": "integer"}},
+                "hv_cfg": {"type": {"key": "integer"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "maxRows": 1,
+            "isRoot": true},
         "Logical_Switch": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index e571eeb..11a921f 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -30,6 +30,47 @@ 
     </dd>
   </dl>
 
+  <table name="NB_Global" title="Northbound configuration">
+    <p>
+      Northbound configuration for an OVN system.  This table must have exactly
+      one row.
+    </p>
+
+    <group title="Status">
+      These columns allow a client to track the overall configuration state of
+      the system.
+
+      <column name="nb_cfg">
+        Sequence number for client to increment.  When a client modifies any
+        part of the northbound database configuration and wishes to wait for
+        <code>ovn-northd</code> and possibly all of the hypervisors to finish
+        applying the changes, it may increment this sequence number.
+      </column>
+
+      <column name="sb_cfg">
+        Sequence number that <code>ovn-northd</code> sets to the value of <ref
+        column="nb_cfg"/> after it finishes applying the corresponding
+        configuration changes to the <ref db="OVN_Southbound"/> database.
+      </column>
+
+      <column name="hv_cfg">
+        Sequence number that <code>ovn-northd</code> sets to the smallest
+        sequence number of all the chassis in the system, as reported in the
+        <code>Chassis</code> table in the southbound database.  Thus, <ref
+        column="hv_cfg"/> equals <ref column="nb_cfg"/> if all chassis are
+        caught up with the northbound configuration (which may never happen, if
+        any chassis is down).  This value can regress, if a chassis was removed
+        from the system and rejoins before catching up.
+      </column>
+    </group>
+
+    <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" title="L2 logical switch">
     <p>
       Each row represents one L2 logical switch.
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index aab4ef5..b594761 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -1,8 +1,16 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "1.5.0",
-    "cksum": "2807058982 6398",
+    "version": "1.6.0",
+    "cksum": "721017171 6774",
     "tables": {
+        "SB_Global": {
+            "columns": {
+                "nb_cfg": {"type": {"key": "integer"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "maxRows": 1,
+            "isRoot": true},
         "Chassis": {
             "columns": {
                 "name": {"type": "string"},
@@ -13,6 +21,7 @@ 
                 "vtep_logical_switches" : {"type": {"key": "string",
                                                     "min": 0,
                                                     "max": "unlimited"}},
+                "nb_cfg": {"type": {"key": "integer"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 1b5bf9e..b328e3d 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -137,6 +137,33 @@ 
     </dd>
   </dl>
 
+  <table name="SB_Global" title="Southbound configuration">
+    <p>
+      Southbound configuration for an OVN system.  This table must have exactly
+      one row.
+    </p>
+
+    <group title="Status">
+      This column allow a client to track the overall configuration state of
+      the system.
+
+      <column name="nb_cfg">
+        Sequence number for the configuration.  When a CMS or
+        <code>ovn-nbctl</code> updates the northbound database, it increments
+        the <code>nb_cfg</code> column in the <code>NB_Global</code> table in
+        the northbound database.  In turn, when <code>ovn-northd</code> updates
+        the southbound database to bring it up to date with these changes, it
+        updates this column to the same value.
+      </column>
+    </group>
+
+    <group title="Common Columns">
+      <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+  </table>
+
   <table name="Chassis" title="Physical Network Hypervisor and Gateway Information">
     <p>
       Each row in this table represents a hypervisor or gateway (a chassis) in
@@ -172,6 +199,13 @@ 
       ovn-controller-vtep will leave this column empty.
     </column>
 
+    <column name="nb_cfg">
+      Sequence number for the configuration.  When <code>ovn-controller</code>
+      updates the configuration of a chassis from the contents of the
+      southbound database, it copies <ref table="SB_Global" column="nb_cfg"/>
+      from the <ref table="SB_Global"/> table into this column.
+    </column>
+
     <column name="external_ids" key="ovn-bridge-mappings">
       <code>ovn-controller</code> populates this key with the set of bridge
       mappings it has been configured to use.  Other applications should treat
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index 6330aa1..c79bf9c 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -12,6 +12,12 @@ 
     <h1>General Commands</h1>
 
     <dl>
+      <dt><code>init</code></dt>
+      <dd>
+        Initializes the database, if it is empty.  If the database has already
+        been initialized, this command has no effect.
+      </dd>
+
       <dt><code>show [<var>switch</var> | <var>router</var>]</code></dt>
       <dd>
         Prints a brief overview of the database contents.  If
@@ -442,6 +448,38 @@ 
     <h1>Options</h1>
 
     <dl>
+      <dt><code>--no-wait</code> | <code>--wait=none</code></dt>
+      <dt><code>--wait=sb</code></dt>
+      <dt><code>--wait=hv</code></dt>
+
+      <dd>
+        <p>
+          These options control whether and how <code>ovn-nbctl</code> waits
+          for the OVN system to become up-to-date with changes made in an
+          <code>ovn-nbctl</code> invocation.
+        </p>
+
+        <p>
+          By default, or if <code>--no-wait</code> or <code>--wait=none</code>,
+          <code>ovn-nbctl</code> exits immediately after confirming that
+          changes have been committed to the northbound database, without
+          waiting.
+        </p>
+
+        <p>
+          With <code>--wait=sb</code>, before <code>ovn-nbctl</code> exits, it
+          waits for <code>ovn-northd</code> to bring the southbound database
+          up-to-date with the northbound database updates.
+        </p>
+
+        <p>
+          With <code>--wait=hv</code>, before <code>ovn-nbctl</code> exits, it
+          additionally waits for all OVN chassis (hypervisors and gateways) to
+          become up-to-date with the northbound database updates.  (This can
+          become an indefinite wait if any chassis is malfunctioning.)
+        </p>
+      </dd>
+
     <dt><code>--db</code> <var>database</var></dt>
     <dd>
       The OVSDB database remote to contact.  If the <env>OVN_NB_DB</env>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 25916da..34f05d8 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -49,6 +49,14 @@  static bool oneline;
 /* --dry-run: Do not commit any changes. */
 static bool dry_run;
 
+/* --wait=TYPE: Wait for configuration change to take effect? */
+enum nbctl_wait_type {
+    NBCTL_WAIT_NONE,            /* Do not wait. */
+    NBCTL_WAIT_SB,              /* Wait for southbound database updates. */
+    NBCTL_WAIT_HV               /* Wait for hypervisors to catch up. */
+};
+static enum nbctl_wait_type wait_type = NBCTL_WAIT_NONE;
+
 /* --timeout: Time to wait for a connection to 'db'. */
 static int timeout;
 
@@ -158,6 +166,8 @@  parse_options(int argc, char *argv[], struct shash *local_options)
     enum {
         OPT_DB = UCHAR_MAX + 1,
         OPT_NO_SYSLOG,
+        OPT_NO_WAIT,
+        OPT_WAIT,
         OPT_DRY_RUN,
         OPT_ONELINE,
         OPT_LOCAL,
@@ -169,6 +179,8 @@  parse_options(int argc, char *argv[], struct shash *local_options)
     static const struct option global_long_options[] = {
         {"db", required_argument, NULL, OPT_DB},
         {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
+        {"no-wait", no_argument, NULL, OPT_NO_WAIT},
+        {"wait", required_argument, NULL, OPT_WAIT},
         {"dry-run", no_argument, NULL, OPT_DRY_RUN},
         {"oneline", no_argument, NULL, OPT_ONELINE},
         {"timeout", required_argument, NULL, 't'},
@@ -225,6 +237,23 @@  parse_options(int argc, char *argv[], struct shash *local_options)
             vlog_set_levels(&this_module, VLF_SYSLOG, VLL_WARN);
             break;
 
+        case OPT_NO_WAIT:
+            wait_type = NBCTL_WAIT_NONE;
+            break;
+
+        case OPT_WAIT:
+            if (!strcmp(optarg, "none")) {
+                wait_type = NBCTL_WAIT_NONE;
+            } else if (!strcmp(optarg, "sb")) {
+                wait_type = NBCTL_WAIT_SB;
+            } else if (!strcmp(optarg, "hv")) {
+                wait_type = NBCTL_WAIT_HV;
+            } else {
+                ctl_fatal("argument to --wait must be "
+                          "\"none\", \"sb\", or \"hv\"");
+            }
+            break;
+
         case OPT_DRY_RUN:
             dry_run = true;
             break;
@@ -363,6 +392,9 @@  Route commands:\n\
 Options:\n\
   --db=DATABASE               connect to DATABASE\n\
                               (default: %s)\n\
+  --no-wait, --wait=none      do not wait for OVN reconfiguration (default)\n\
+  --wait=sb                   wait for southbound database update\n\
+  --wait=hv                   wait for all chassis to catch up\n\
   -t, --timeout=SECS          wait at most SECS seconds\n\
   --dry-run                   do not commit changes to database\n\
   --oneline                   print exactly one line of output per command\n",
@@ -503,6 +535,11 @@  print_ls(const struct nbrec_logical_switch *ls, struct ds *s)
 }
 
 static void
+nbctl_init(struct ctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
 nbctl_show(struct ctl_context *ctx)
 {
     const struct nbrec_logical_switch *ls;
@@ -1887,6 +1924,10 @@  nbctl_lr_route_list(struct ctl_context *ctx)
 }
 
 static const struct ctl_table_class tables[] = {
+    {&nbrec_table_nb_global,
+     {{&nbrec_table_nb_global, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
     {&nbrec_table_logical_switch,
      {{&nbrec_table_logical_switch, &nbrec_logical_switch_col_name, NULL},
       {NULL, NULL, NULL}}},
@@ -1934,9 +1975,14 @@  static void
 run_prerequisites(struct ctl_command *commands, size_t n_commands,
                   struct ovsdb_idl *idl)
 {
-    struct ctl_command *c;
+    ovsdb_idl_add_table(idl, &nbrec_table_nb_global);
+    if (wait_type == NBCTL_WAIT_SB) {
+        ovsdb_idl_add_column(idl, &nbrec_nb_global_col_sb_cfg);
+    } else if (wait_type == NBCTL_WAIT_HV) {
+        ovsdb_idl_add_column(idl, &nbrec_nb_global_col_hv_cfg);
+    }
 
-    for (c = commands; c < &commands[n_commands]; c++) {
+    for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) {
         if (c->syntax->prerequisites) {
             struct ctl_context ctx;
 
@@ -1963,6 +2009,7 @@  do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
     struct ctl_context ctx;
     struct ctl_command *c;
     struct shash_node *node;
+    int64_t next_cfg = 0;
     char *error = NULL;
 
     txn = the_idl_txn = ovsdb_idl_txn_create(idl);
@@ -1972,6 +2019,17 @@  do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
 
     ovsdb_idl_txn_add_comment(txn, "ovs-nbctl: %s", args);
 
+    const struct nbrec_nb_global *nb = nbrec_nb_global_first(idl);
+    if (!nb) {
+        /* XXX add verification that table is empty */
+        nb = nbrec_nb_global_insert(txn);
+    }
+
+    if (wait_type != NBCTL_WAIT_NONE) {
+        ovsdb_idl_txn_increment(txn, &nb->header_,
+                                &nbrec_nb_global_col_nb_cfg);
+    }
+
     symtab = ovsdb_symbol_table_create();
     for (c = commands; c < &commands[n_commands]; c++) {
         ds_init(&c->output);
@@ -2013,6 +2071,9 @@  do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
     }
 
     status = ovsdb_idl_txn_commit_block(txn);
+    if (wait_type != NBCTL_WAIT_NONE && status == TXN_SUCCESS) {
+        next_cfg = ovsdb_idl_txn_get_increment_new_value(txn);
+    }
     if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
         for (c = commands; c < &commands[n_commands]; c++) {
             if (c->syntax->postprocess) {
@@ -2089,6 +2150,25 @@  do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
         shash_destroy_free_data(&c->options);
     }
     free(commands);
+
+    if (wait_type != NBCTL_WAIT_NONE && status != TXN_UNCHANGED) {
+        ovsdb_idl_enable_reconnect(idl);
+        for (;;) {
+            ovsdb_idl_run(idl);
+            NBREC_NB_GLOBAL_FOR_EACH (nb, idl) {
+                int64_t cur_cfg = (wait_type == NBCTL_WAIT_SB
+                                   ? nb->sb_cfg
+                                   : nb->hv_cfg);
+                if (cur_cfg >= next_cfg) {
+                    goto done;
+                }
+            }
+            ovsdb_idl_wait(idl);
+            poll_block();
+        }
+    done: ;
+    }
+
     ovsdb_idl_txn_destroy(txn);
     ovsdb_idl_destroy(idl);
 
@@ -2130,6 +2210,7 @@  nbctl_exit(int status)
 }
 
 static const struct ctl_command_syntax nbctl_commands[] = {
+    { "init", 0, 0, "", NULL, nbctl_init, NULL, "", RW },
     { "show", 0, 1, "[SWITCH]", NULL, nbctl_show, NULL, "", RO },
 
     /* logical switch commands. */
diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in
index 8203771..5f0462a 100644
--- a/ovn/utilities/ovn-sbctl.8.in
+++ b/ovn/utilities/ovn-sbctl.8.in
@@ -108,6 +108,10 @@  sections below.
 .SS "OVN_Southbound Commands"
 These commands work with an \fBOVN_Southbound\fR database as a whole.
 .
+.IP "\fBinit\fR"
+Initializes the database, if it is empty.  If the database has already
+been initialized, this command has no effect.
+.
 .IP "\fBshow\fR"
 Prints a brief overview of the database contents.
 .
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index 21ae98d..b86363c 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -526,6 +526,11 @@  static struct cmd_show_table cmd_show_tables[] = {
 };
 
 static void
+sbctl_init(struct ctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
 cmd_chassis_add(struct ctl_context *ctx)
 {
     struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
@@ -744,6 +749,10 @@  cmd_lflow_list(struct ctl_context *ctx)
 
 
 static const struct ctl_table_class tables[] = {
+    {&sbrec_table_sb_global,
+     {{&sbrec_table_sb_global, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
     {&sbrec_table_chassis,
      {{&sbrec_table_chassis, &sbrec_chassis_col_name, NULL},
       {NULL, NULL, NULL}}},
@@ -817,9 +826,9 @@  static void
 run_prerequisites(struct ctl_command *commands, size_t n_commands,
                   struct ovsdb_idl *idl)
 {
-    struct ctl_command *c;
+    ovsdb_idl_add_table(idl, &sbrec_table_sb_global);
 
-    for (c = commands; c < &commands[n_commands]; c++) {
+    for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) {
         if (c->syntax->prerequisites) {
             struct sbctl_context sbctl_ctx;
 
@@ -855,6 +864,12 @@  do_sbctl(const char *args, struct ctl_command *commands, size_t n_commands,
 
     ovsdb_idl_txn_add_comment(txn, "ovs-sbctl: %s", args);
 
+    const struct sbrec_sb_global *sb = sbrec_sb_global_first(idl);
+    if (!sb) {
+        /* XXX add verification that table is empty */
+        sb = sbrec_sb_global_insert(txn);
+    }
+
     symtab = ovsdb_symbol_table_create();
     for (c = commands; c < &commands[n_commands]; c++) {
         ds_init(&c->output);
@@ -1013,6 +1028,8 @@  sbctl_exit(int status)
 }
 
 static const struct ctl_command_syntax sbctl_commands[] = {
+    { "init", 0, 0, "", NULL, sbctl_init, NULL, "", RW },
+
     /* Chassis commands. */
     {"chassis-add", 3, 3, "CHASSIS ENCAP-TYPE ENCAP-IP", pre_get_info,
      cmd_chassis_add, NULL, "--may-exist", RW},
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index da49eb2..c3af766 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -183,8 +183,8 @@  ovn_init_db () {
 # ovn-sbctl and ovn-nbctl use them by default, and starts ovn-northd running
 # against them.
 ovn_start () {
-    ovn_init_db ovn-sb
-    ovn_init_db ovn-nb
+    ovn_init_db ovn-sb; ovn-sbctl init
+    ovn_init_db ovn-nb; ovn-nbctl init
 
     echo "starting ovn-northd"
     mkdir "$ovs_base"/northd
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 42b2d36..08dd4ed 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -9,6 +9,7 @@  m4_define([OVN_NBCTL_TEST_START],
    dnl Start ovsdb-server.
    AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr])
    on_exit "kill `cat ovsdb-server.pid`"
+   AT_CHECK([ovn-nbctl init])
    AT_CHECK([[sed < stderr '
 /vlog|INFO|opened log file/d
 /ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']])
diff --git a/tutorial/ovs-sandbox b/tutorial/ovs-sandbox
index 412c982..69c2c68 100755
--- a/tutorial/ovs-sandbox
+++ b/tutorial/ovs-sandbox
@@ -1,6 +1,6 @@ 
 #! /bin/sh
 #
-# Copyright (c) 2013, 2015 Nicira, Inc.
+# Copyright (c) 2013, 2015, 2016 Nicira, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -361,6 +361,9 @@  if $ovn; then
     ovs-vsctl set open . external-ids:ovn-encap-type=geneve
     ovs-vsctl set open . external-ids:ovn-encap-ip=127.0.0.1
 
+    ovn-nbctl init
+    ovn-sbctl init
+
     rungdb $gdb_ovn_northd $gdb_ovn_northd_ex ovn-northd --detach \
         --no-chdir --pidfile -vconsole:off --log-file \
         --ovnsb-db=unix:"$sandbox"/ovnsb_db.sock \
diff --git a/utilities/ovs-sim.in b/utilities/ovs-sim.in
index cebbd41..7f60815 100755
--- a/utilities/ovs-sim.in
+++ b/utilities/ovs-sim.in
@@ -1,6 +1,6 @@ 
 #! /usr/bin/env bash
 #
-# Copyright (c) 2013, 2015 Nicira, Inc.
+# Copyright (c) 2013, 2015, 2016 Nicira, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -263,6 +263,9 @@  EOF
     OVN_NB_DB=unix:$sim_base/ovn-nb/ovn-nb.sock; export OVN_NB_DB
     OVN_SB_DB=unix:$sim_base/ovn-sb/ovn-sb.sock; export OVN_SB_DB
 
+    ovn-nbctl init
+    ovn-sbctl init
+
     mkdir "$sim_base"/northd
     as northd ovn-northd $daemon_opts \
 	       --ovnnb-db="$OVN_NB_DB" \