diff mbox series

[ovs-dev,v12] Allow to run multiple controllers on the same machine

Message ID 20201119032052.599236-1-ihrachys@redhat.com
State New
Headers show
Series [ovs-dev,v12] Allow to run multiple controllers on the same machine | expand

Commit Message

Ihar Hrachyshka Nov. 19, 2020, 3:20 a.m. UTC
User stories:
1) NFV: an admin wants to run two separate instances of OVN controller
   using the same database but configuring ports on different bridges.
   Some of these bridges may use DPDK while others may not.

2) Parallel OVN instances: an admin wants to run two separate
   instances of OVN controller using different databases. The
   instances are completely independent and serve different consumers.
   For example, the same machine runs both OpenStack and OpenShift
   stacks, each running its own separate OVN stack.

To serve these use cases, several features should be added to
ovn-controller:

- use different database configuration for multiple controllers;
- customize chassis name used by controller.

=====

For each of the following database configuration options, their
extended chassis specific counterparts are introduced:

external_ids:hostname
external_ids:ovn-bridge
external_ids:ovn-bridge-datapath-type
external_ids:ovn-bridge-mappings
external_ids:ovn-chassis-mac-mappings
external_ids:ovn-cms-options
external_ids:ovn-encap-csum
external_ids:ovn-encap-ip
external_ids:ovn-encap-type
external_ids:ovn-is-interconn
external_ids:ovn-monitor-all
external_ids:ovn-openflow-probe-interval
external_ids:ovn-remote
external_ids:ovn-remote-probe-interval

For example,

external_ids:ovn-bridge -> external_ids:ovn-bridge-<chassis-name>=
external_ids:ovn-encap-ip -> external_ids:ovn-encap-ip-<chassis-name>=
external_ids:ovn-remote -> external_ids:ovn-remote-<chassis-name>=

Priority wise, <chassis-name> specific options take precedence.

=====

For system-id,

You can now pass intended chassis name via CLI argument:

  $ ovn-controller ... -n <chassis_name>

Alternatively, you can configure a chassis name by putting it into the
${ovn_sysconfdir}/system-id-override file before running the
controller.

The latter option may be more useful in container environment where
the same image may be reused for multiple controller instances, where
ovs_sysconfigdir/ovn/system-id-override is a volume mounted into this
generic image. The override file is read once on startup. If you want
to apply a new chassis name to a controller instance, restart it to
reread the file.

Priority wise, this is the order in which different means to configure
the chassis name are used:

- ovn-controller ... -n <chassis_name> CLI argument.
- ${ovs_sysconfdir}/ovn/system-id-override file;
- external_ids:system-id= ovsdb option;

=====

Concurrent chassis running on the same host may inadvertantly remove
patch ports that belong to their peer chassis. To avoid that, patch
ports are now tagged in external-ids:ovn-chassis-id with the
appropriate chassis name, and only patch ports that belong to the
chassis are touched when cleaning up. Also, now only tunnels on the
active integration bridge are being cleaned up.

Note that external-ids:ovn-chassis-id key is already used for tunnel
ports to identify the remote tunnel endpoint. We can reuse the same
key for patch ports because the key usage is not overlapping.

Alternatively, we could introduce a new key with a similar but
different name. This would simplify code changes needed but would
arguably introduce even more confusion. Since the key name is not
entirely self-descriptive for tunnel ports (a better name would be
e.g. ovn-remote-chassis or ovn-peer-chassis), the ideal scenario would
be to rename the key for tunnel endpoints but reuse it for patch
ports. This would involve additional migration steps and is probably
not worth the hassle.

=====

This patch expands tunnel port name to allow for long similar chassis
names used on the same host. It also renames tunnel endpoints as
follows to allow the same endpoints on different bridges:

    ovn-<remote>-<n>  ---->  ovn-<chassis>-<remote>-<n>

=====

Note: this patch assumes that each chassis has its own unique IP.
Future work may consider adding support to specify custom port numbers
for tunneling that would allow to reuse the same IP address for
multiple chassis running on the same host. This work is out of scope
for this patch.

Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>

---

v1: initial implementation.
v2: fixed test case to check ports are claimed by proper chassis.
v2: added NEWS entry.
v2: fixed some compiler warnings.
v2: moved file_system_id declaration inside a function that uses it.
v2: removed unneeded binding.h #include.
v2: docs: better explanation of alternatives to select chassis name.
v3: reverted priority order for chassis configuration: first CLI, then
    system-id file, then ovsdb.
v4: introduce helpers to extract external-ids (per-chassis or global).
v4: introduce per-chassis config options for all keys.
v4: introduce -M (--concurrent) CLI argument to avoid patch ports
    removed by concurrent chassis.
v5: rebased.
v6: switched from -M (--concurrent) to external-ids:ovn-is-concurrent.
v6: with ovn-is-concurrent=true, also avoid removing unknown tunnel
    endpoints.
v7: don't clean up tunnel endpoints from other bridges.
v7: don't clean up patch ports that don't belong to the chassis.
v7: remove ovn-is-concurrent that is no longer needed.
v7: rebased.
v8: rename system-id -> /etc/ovn/system-id-override
v8: read the system-id-override file just once on startup
v8: free() controller_chassis (CLI arg value) on exit
v9: updated commit message, removed notion of ovn-is-concurrent.
v10: rename external-ids:owner -> ovn-chassis-id in patch ports.
v10: use ovn_sysconfdir for system-id-override file location.
v10: clean up patch ports with no ovn-chassis-id tag.
v10: simplify encaps_run to only iterate over br-int ports, not all
     bridges (and then explicitly skipping them).
v10: added test case to validate cleanup for patch and tunnel ports.
v10: minor adjustment in ovn-sb.xml.
v11: added -n to help message.
v11: fixed system tests.
v11: expanded tunnel port name to allow long concurrent chassis names.
v12: rename endpoints: ovn-<remote>-<n> -> ovn-<chassis>-<remote>-<n>.
v12: tests: check no errors happened when endpoints created.
v12: added connectivity test case for 2 virtual chassis on the same
     host.
---
 NEWS                            |   5 +
 controller/chassis.c            |  77 ++++---
 controller/chassis.h            |   3 +-
 controller/encaps.c             |  95 ++++++---
 controller/encaps.h             |   1 -
 controller/ovn-controller.8.xml |  17 +-
 controller/ovn-controller.c     | 106 +++++++--
 controller/ovn-controller.h     |   4 +
 controller/patch.c              |  20 +-
 controller/physical.c           |   2 +-
 lib/ovn-util.c                  |  50 +++++
 lib/ovn-util.h                  |  18 ++
 ovn-sb.xml                      |  10 +-
 tests/ovn-controller.at         |  17 +-
 tests/ovn-macros.at             |  49 ++++-
 tests/ovn-performance.at        |   4 +-
 tests/ovn.at                    | 368 ++++++++++++++++++++++++++------
 tests/ovs-macros.at             |   1 +
 tests/system-ovn.at             |   1 +
 19 files changed, 669 insertions(+), 179 deletions(-)

Comments

Ihar Hrachyshka Nov. 19, 2020, 3:37 a.m. UTC | #1
Note: this patch requires
https://patchwork.ozlabs.org/project/openvswitch/patch/20201119033501.798597-1-ihrachys@redhat.com/
to allow OVN controller to complete tunnel port creation for multiple
virtual colocated chassis to connect to a remote chassis.

On Wed, Nov 18, 2020 at 10:21 PM Ihar Hrachyshka <ihrachys@redhat.com> wrote:
>
> User stories:
> 1) NFV: an admin wants to run two separate instances of OVN controller
>    using the same database but configuring ports on different bridges.
>    Some of these bridges may use DPDK while others may not.
>
> 2) Parallel OVN instances: an admin wants to run two separate
>    instances of OVN controller using different databases. The
>    instances are completely independent and serve different consumers.
>    For example, the same machine runs both OpenStack and OpenShift
>    stacks, each running its own separate OVN stack.
>
> To serve these use cases, several features should be added to
> ovn-controller:
>
> - use different database configuration for multiple controllers;
> - customize chassis name used by controller.
>
> =====
>
> For each of the following database configuration options, their
> extended chassis specific counterparts are introduced:
>
> external_ids:hostname
> external_ids:ovn-bridge
> external_ids:ovn-bridge-datapath-type
> external_ids:ovn-bridge-mappings
> external_ids:ovn-chassis-mac-mappings
> external_ids:ovn-cms-options
> external_ids:ovn-encap-csum
> external_ids:ovn-encap-ip
> external_ids:ovn-encap-type
> external_ids:ovn-is-interconn
> external_ids:ovn-monitor-all
> external_ids:ovn-openflow-probe-interval
> external_ids:ovn-remote
> external_ids:ovn-remote-probe-interval
>
> For example,
>
> external_ids:ovn-bridge -> external_ids:ovn-bridge-<chassis-name>=
> external_ids:ovn-encap-ip -> external_ids:ovn-encap-ip-<chassis-name>=
> external_ids:ovn-remote -> external_ids:ovn-remote-<chassis-name>=
>
> Priority wise, <chassis-name> specific options take precedence.
>
> =====
>
> For system-id,
>
> You can now pass intended chassis name via CLI argument:
>
>   $ ovn-controller ... -n <chassis_name>
>
> Alternatively, you can configure a chassis name by putting it into the
> ${ovn_sysconfdir}/system-id-override file before running the
> controller.
>
> The latter option may be more useful in container environment where
> the same image may be reused for multiple controller instances, where
> ovs_sysconfigdir/ovn/system-id-override is a volume mounted into this
> generic image. The override file is read once on startup. If you want
> to apply a new chassis name to a controller instance, restart it to
> reread the file.
>
> Priority wise, this is the order in which different means to configure
> the chassis name are used:
>
> - ovn-controller ... -n <chassis_name> CLI argument.
> - ${ovs_sysconfdir}/ovn/system-id-override file;
> - external_ids:system-id= ovsdb option;
>
> =====
>
> Concurrent chassis running on the same host may inadvertantly remove
> patch ports that belong to their peer chassis. To avoid that, patch
> ports are now tagged in external-ids:ovn-chassis-id with the
> appropriate chassis name, and only patch ports that belong to the
> chassis are touched when cleaning up. Also, now only tunnels on the
> active integration bridge are being cleaned up.
>
> Note that external-ids:ovn-chassis-id key is already used for tunnel
> ports to identify the remote tunnel endpoint. We can reuse the same
> key for patch ports because the key usage is not overlapping.
>
> Alternatively, we could introduce a new key with a similar but
> different name. This would simplify code changes needed but would
> arguably introduce even more confusion. Since the key name is not
> entirely self-descriptive for tunnel ports (a better name would be
> e.g. ovn-remote-chassis or ovn-peer-chassis), the ideal scenario would
> be to rename the key for tunnel endpoints but reuse it for patch
> ports. This would involve additional migration steps and is probably
> not worth the hassle.
>
> =====
>
> This patch expands tunnel port name to allow for long similar chassis
> names used on the same host. It also renames tunnel endpoints as
> follows to allow the same endpoints on different bridges:
>
>     ovn-<remote>-<n>  ---->  ovn-<chassis>-<remote>-<n>
>
> =====
>
> Note: this patch assumes that each chassis has its own unique IP.
> Future work may consider adding support to specify custom port numbers
> for tunneling that would allow to reuse the same IP address for
> multiple chassis running on the same host. This work is out of scope
> for this patch.
>
> Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
>
> ---
>
> v1: initial implementation.
> v2: fixed test case to check ports are claimed by proper chassis.
> v2: added NEWS entry.
> v2: fixed some compiler warnings.
> v2: moved file_system_id declaration inside a function that uses it.
> v2: removed unneeded binding.h #include.
> v2: docs: better explanation of alternatives to select chassis name.
> v3: reverted priority order for chassis configuration: first CLI, then
>     system-id file, then ovsdb.
> v4: introduce helpers to extract external-ids (per-chassis or global).
> v4: introduce per-chassis config options for all keys.
> v4: introduce -M (--concurrent) CLI argument to avoid patch ports
>     removed by concurrent chassis.
> v5: rebased.
> v6: switched from -M (--concurrent) to external-ids:ovn-is-concurrent.
> v6: with ovn-is-concurrent=true, also avoid removing unknown tunnel
>     endpoints.
> v7: don't clean up tunnel endpoints from other bridges.
> v7: don't clean up patch ports that don't belong to the chassis.
> v7: remove ovn-is-concurrent that is no longer needed.
> v7: rebased.
> v8: rename system-id -> /etc/ovn/system-id-override
> v8: read the system-id-override file just once on startup
> v8: free() controller_chassis (CLI arg value) on exit
> v9: updated commit message, removed notion of ovn-is-concurrent.
> v10: rename external-ids:owner -> ovn-chassis-id in patch ports.
> v10: use ovn_sysconfdir for system-id-override file location.
> v10: clean up patch ports with no ovn-chassis-id tag.
> v10: simplify encaps_run to only iterate over br-int ports, not all
>      bridges (and then explicitly skipping them).
> v10: added test case to validate cleanup for patch and tunnel ports.
> v10: minor adjustment in ovn-sb.xml.
> v11: added -n to help message.
> v11: fixed system tests.
> v11: expanded tunnel port name to allow long concurrent chassis names.
> v12: rename endpoints: ovn-<remote>-<n> -> ovn-<chassis>-<remote>-<n>.
> v12: tests: check no errors happened when endpoints created.
> v12: added connectivity test case for 2 virtual chassis on the same
>      host.
> ---
>  NEWS                            |   5 +
>  controller/chassis.c            |  77 ++++---
>  controller/chassis.h            |   3 +-
>  controller/encaps.c             |  95 ++++++---
>  controller/encaps.h             |   1 -
>  controller/ovn-controller.8.xml |  17 +-
>  controller/ovn-controller.c     | 106 +++++++--
>  controller/ovn-controller.h     |   4 +
>  controller/patch.c              |  20 +-
>  controller/physical.c           |   2 +-
>  lib/ovn-util.c                  |  50 +++++
>  lib/ovn-util.h                  |  18 ++
>  ovn-sb.xml                      |  10 +-
>  tests/ovn-controller.at         |  17 +-
>  tests/ovn-macros.at             |  49 ++++-
>  tests/ovn-performance.at        |   4 +-
>  tests/ovn.at                    | 368 ++++++++++++++++++++++++++------
>  tests/ovs-macros.at             |   1 +
>  tests/system-ovn.at             |   1 +
>  19 files changed, 669 insertions(+), 179 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 47ffc27b8..bb02abbf2 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -23,6 +23,11 @@ OVN v20.09.0 - 28 Sep 2020
>     - Added support for external ip based NAT. Now, besides the logical ip,
>       external ips will also decide if a packet will be NATed or not.
>     - Added support for VXLAN encapsulation (not just for ramp/VTEP switches).
> +   - Added support for multiple ovn-controller instances on the same host
> +     (virtual chassis). Now all external-ids:* configuration options can be
> +     customized for each controller instance running on the same host. The only
> +     option that is not available per chassis is external-ids:system-id, which
> +     stands for the chassis name and can be passed via config file or CLI (-n).
>
>  OVN v20.06.0
>  --------------------------
> diff --git a/controller/chassis.c b/controller/chassis.c
> index 7748fb94c..5d87e3301 100644
> --- a/controller/chassis.c
> +++ b/controller/chassis.c
> @@ -125,9 +125,10 @@ chassis_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>  }
>
>  static const char *
> -get_hostname(const struct smap *ext_ids)
> +get_hostname(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    const char *hostname = smap_get_def(ext_ids, "hostname", "");
> +    const char *hostname = get_chassis_external_id_value(
> +        ext_ids, chassis_id, "hostname", "");
>
>      if (strlen(hostname) == 0) {
>          static char hostname_[HOST_NAME_MAX + 1];
> @@ -143,39 +144,45 @@ get_hostname(const struct smap *ext_ids)
>  }
>
>  static const char *
> -get_bridge_mappings(const struct smap *ext_ids)
> +get_bridge_mappings(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_def(ext_ids, "ovn-bridge-mappings", "");
> +    return get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-bridge-mappings", "");
>  }
>
>  const char *
> -get_chassis_mac_mappings(const struct smap *ext_ids)
> +get_chassis_mac_mappings(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_def(ext_ids, "ovn-chassis-mac-mappings", "");
> +    return get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-chassis-mac-mappings", "");
>  }
>
>  static const char *
> -get_cms_options(const struct smap *ext_ids)
> +get_cms_options(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_def(ext_ids, "ovn-cms-options", "");
> +    return get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-cms-options", "");
>  }
>
>  static const char *
> -get_monitor_all(const struct smap *ext_ids)
> +get_monitor_all(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_def(ext_ids, "ovn-monitor-all", "false");
> +    return get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-monitor-all", "false");
>  }
>
>  static const char *
> -get_enable_lflow_cache(const struct smap *ext_ids)
> +get_enable_lflow_cache(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_def(ext_ids, "ovn-enable-lflow-cache", "true");
> +    return get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-enable-lflow-cache", "true");
>  }
>
>  static const char *
> -get_encap_csum(const struct smap *ext_ids)
> +get_encap_csum(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_def(ext_ids, "ovn-encap-csum", "true");
> +    return get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-encap-csum", "true");
>  }
>
>  static const char *
> @@ -189,9 +196,10 @@ get_datapath_type(const struct ovsrec_bridge *br_int)
>  }
>
>  static bool
> -get_is_interconn(const struct smap *ext_ids)
> +get_is_interconn(const struct smap *ext_ids, const char *chassis_id)
>  {
> -    return smap_get_bool(ext_ids, "ovn-is-interconn", false);
> +    return get_chassis_external_id_value_bool(
> +        ext_ids, chassis_id, "ovn-is-interconn", false);
>  }
>
>  static void
> @@ -278,22 +286,27 @@ chassis_parse_ovs_config(const struct ovsrec_open_vswitch_table *ovs_table,
>          return false;
>      }
>
> -    const char *encap_type = smap_get(&cfg->external_ids, "ovn-encap-type");
> -    const char *encap_ips = smap_get(&cfg->external_ids, "ovn-encap-ip");
> +    const char *chassis_id = get_ovs_chassis_id(cfg);
> +    const struct smap *ext_ids = &cfg->external_ids;
> +
> +    const char *encap_type = get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-encap-type", NULL);
> +    const char *encap_ips = get_chassis_external_id_value(
> +        ext_ids, chassis_id, "ovn-encap-ip", NULL);
>      if (!encap_type || !encap_ips) {
>          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
>          VLOG_INFO_RL(&rl, "Need to specify an encap type and ip");
>          return false;
>      }
>
> -    ovs_cfg->hostname = get_hostname(&cfg->external_ids);
> -    ovs_cfg->bridge_mappings = get_bridge_mappings(&cfg->external_ids);
> +    ovs_cfg->hostname = get_hostname(ext_ids, chassis_id);
> +    ovs_cfg->bridge_mappings = get_bridge_mappings(ext_ids, chassis_id);
>      ovs_cfg->datapath_type = get_datapath_type(br_int);
> -    ovs_cfg->encap_csum = get_encap_csum(&cfg->external_ids);
> -    ovs_cfg->cms_options = get_cms_options(&cfg->external_ids);
> -    ovs_cfg->monitor_all = get_monitor_all(&cfg->external_ids);
> -    ovs_cfg->chassis_macs = get_chassis_mac_mappings(&cfg->external_ids);
> -    ovs_cfg->enable_lflow_cache = get_enable_lflow_cache(&cfg->external_ids);
> +    ovs_cfg->encap_csum = get_encap_csum(ext_ids, chassis_id);
> +    ovs_cfg->cms_options = get_cms_options(ext_ids, chassis_id);
> +    ovs_cfg->monitor_all = get_monitor_all(ext_ids, chassis_id);
> +    ovs_cfg->chassis_macs = get_chassis_mac_mappings(ext_ids, chassis_id);
> +    ovs_cfg->enable_lflow_cache = get_enable_lflow_cache(ext_ids, chassis_id);
>
>      if (!chassis_parse_ovs_encap_type(encap_type, &ovs_cfg->encap_type_set)) {
>          return false;
> @@ -311,7 +324,7 @@ chassis_parse_ovs_config(const struct ovsrec_open_vswitch_table *ovs_table,
>          sset_destroy(&ovs_cfg->encap_ip_set);
>      }
>
> -    ovs_cfg->is_interconn = get_is_interconn(&cfg->external_ids);
> +    ovs_cfg->is_interconn = get_is_interconn(ext_ids, chassis_id);
>
>      return true;
>  }
> @@ -348,7 +361,7 @@ chassis_other_config_changed(const char *bridge_mappings,
>                               const struct sbrec_chassis *chassis_rec)
>  {
>      const char *chassis_bridge_mappings =
> -        get_bridge_mappings(&chassis_rec->other_config);
> +        get_bridge_mappings(&chassis_rec->other_config, NULL);
>
>      if (strcmp(bridge_mappings, chassis_bridge_mappings)) {
>          return true;
> @@ -362,28 +375,28 @@ chassis_other_config_changed(const char *bridge_mappings,
>      }
>
>      const char *chassis_cms_options =
> -        get_cms_options(&chassis_rec->other_config);
> +        get_cms_options(&chassis_rec->other_config, NULL);
>
>      if (strcmp(cms_options, chassis_cms_options)) {
>          return true;
>      }
>
>      const char *chassis_monitor_all =
> -        get_monitor_all(&chassis_rec->other_config);
> +        get_monitor_all(&chassis_rec->other_config, NULL);
>
>      if (strcmp(monitor_all, chassis_monitor_all)) {
>          return true;
>      }
>
>      const char *chassis_enable_lflow_cache =
> -        get_enable_lflow_cache(&chassis_rec->other_config);
> +        get_enable_lflow_cache(&chassis_rec->other_config, NULL);
>
>      if (strcmp(enable_lflow_cache, chassis_enable_lflow_cache)) {
>          return true;
>      }
>
>      const char *chassis_mac_mappings =
> -        get_chassis_mac_mappings(&chassis_rec->other_config);
> +        get_chassis_mac_mappings(&chassis_rec->other_config, NULL);
>      if (strcmp(chassis_macs, chassis_mac_mappings)) {
>          return true;
>      }
> @@ -802,7 +815,7 @@ chassis_get_mac(const struct sbrec_chassis *chassis_rec,
>                  struct eth_addr *chassis_mac)
>  {
>      const char *tokens
> -        = get_chassis_mac_mappings(&chassis_rec->other_config);
> +        = get_chassis_mac_mappings(&chassis_rec->other_config, NULL);
>      if (!tokens[0]) {
>         return false;
>      }
> diff --git a/controller/chassis.h b/controller/chassis.h
> index 220f726b9..c7345f0fa 100644
> --- a/controller/chassis.h
> +++ b/controller/chassis.h
> @@ -49,7 +49,8 @@ bool chassis_get_mac(const struct sbrec_chassis *chassis,
>                       const char *bridge_mapping,
>                       struct eth_addr *chassis_mac);
>  const char *chassis_get_id(void);
> -const char * get_chassis_mac_mappings(const struct smap *ext_ids);
> +const char * get_chassis_mac_mappings(const struct smap *ext_ids,
> +                                      const char *chassis_id);
>
>
>  #endif /* controller/chassis.h */
> diff --git a/controller/encaps.c b/controller/encaps.c
> index 7eac4bb06..bf5c39f5f 100644
> --- a/controller/encaps.c
> +++ b/controller/encaps.c
> @@ -67,13 +67,14 @@ struct chassis_node {
>  };
>
>  static char *
> -tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id)
> +tunnel_create_name(struct tunnel_ctx *tc, const char *this_chassis_id,
> +                   const char *chassis_id)
>  {
>      int i;
>
>      for (i = 0; i < UINT16_MAX; i++) {
>          char *port_name;
> -        port_name = xasprintf("ovn-%.6s-%x", chassis_id, i);
> +        port_name = xasprintf("ovn-%s-%s-%x", this_chassis_id, chassis_id, i);
>
>          if (!sset_contains(&tc->port_names, port_name)) {
>              return port_name;
> @@ -151,7 +152,8 @@ encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id,
>
>  static void
>  tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
> -           const char *new_chassis_id, const struct sbrec_encap *encap)
> +           const char *this_chassis_id, const char *new_chassis_id,
> +           const struct sbrec_encap *encap)
>  {
>      struct smap options = SMAP_INITIALIZER(&options);
>      smap_add(&options, "remote_ip", encap->ip);
> @@ -198,7 +200,8 @@ tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
>       * its name, otherwise generate a new, unique name. */
>      char *port_name = (chassis
>                         ? xstrdup(chassis->port->name)
> -                       : tunnel_create_name(tc, new_chassis_id));
> +                       : tunnel_create_name(tc, this_chassis_id,
> +                                            new_chassis_id));
>      if (!port_name) {
>          VLOG_WARN("Unable to allocate unique name for '%s' tunnel",
>                    new_chassis_id);
> @@ -247,7 +250,9 @@ preferred_encap(const struct sbrec_chassis *chassis_rec)
>   * as there are VTEP of that type (differentiated by remote_ip) on that chassis.
>   */
>  static int
> -chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_sb_global *sbg, struct tunnel_ctx *tc)
> +chassis_tunnel_add(const struct sbrec_chassis *chassis_rec,
> +                   const char *this_chassis_id,
> +                   const struct sbrec_sb_global *sbg, struct tunnel_ctx *tc)
>  {
>      struct sbrec_encap *encap = preferred_encap(chassis_rec);
>      int tuncnt = 0;
> @@ -263,7 +268,8 @@ chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_s
>          if (tun_type != pref_type) {
>              continue;
>          }
> -        tunnel_add(tc, sbg, chassis_rec->name, chassis_rec->encaps[i]);
> +        tunnel_add(tc, sbg, this_chassis_id, chassis_rec->name,
> +                   chassis_rec->encaps[i]);
>          tuncnt++;
>      }
>      return tuncnt;
> @@ -291,9 +297,31 @@ chassis_tzones_overlap(const struct sset *transport_zones,
>      return false;
>  }
>
> +static bool
> +is_tunnel_type(const char *port_type)
> +{
> +    static const char *tunnel_types[3] = { "geneve", "vxlan", "stt" };
> +    for (size_t t = 0; t < 3; t++) {
> +        if (!strcmp(port_type, tunnel_types[t])) {
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
> +static bool
> +is_tunnel_port(const struct ovsrec_port *port)
> +{
> +    for (size_t i = 0; i < port->n_interfaces; i++) {
> +        if (is_tunnel_type(port->interfaces[i]->type)) {
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
>  void
>  encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> -           const struct ovsrec_bridge_table *bridge_table,
>             const struct ovsrec_bridge *br_int,
>             const struct sbrec_chassis_table *chassis_table,
>             const struct sbrec_chassis *this_chassis,
> @@ -305,7 +333,6 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
>      }
>
>      const struct sbrec_chassis *chassis_rec;
> -    const struct ovsrec_bridge *br;
>
>      struct tunnel_ctx tc = {
>          .chassis = SHASH_INITIALIZER(&tc.chassis),
> @@ -320,28 +347,29 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
>
>      /* Collect all port names into tc.port_names.
>       *
> -     * Collect all the OVN-created tunnels into tc.tunnel_hmap. */
> -    OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
> -        for (size_t i = 0; i < br->n_ports; i++) {
> -            const struct ovsrec_port *port = br->ports[i];
> -            sset_add(&tc.port_names, port->name);
> -
> -            /*
> -             * note that the id here is not just the chassis name, but the
> -             * combination of <chassis_name><delim><encap_ip>
> -             */
> -            const char *id = smap_get(&port->external_ids, "ovn-chassis-id");
> -            if (id) {
> -                if (!shash_find(&tc.chassis, id)) {
> -                    struct chassis_node *chassis = xzalloc(sizeof *chassis);
> -                    chassis->bridge = br;
> -                    chassis->port = port;
> -                    shash_add_assert(&tc.chassis, id, chassis);
> -                } else {
> -                    /* Duplicate port for ovn-chassis-id.  Arbitrarily choose
> -                     * to delete this one. */
> -                    ovsrec_bridge_update_ports_delvalue(br, port);
> -                }
> +     * Collect all OVN-created tunnels of the bridge into tc.tunnel_hmap. */
> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> +        const struct ovsrec_port *port = br_int->ports[i];
> +        if (!is_tunnel_port(port)) {
> +            continue;
> +        }
> +        sset_add(&tc.port_names, port->name);
> +
> +        /*
> +         * note that the id here is not just the chassis name, but the
> +         * combination of <chassis_name><delim><encap_ip>
> +         */
> +        const char *id = smap_get(&port->external_ids, "ovn-chassis-id");
> +        if (id) {
> +            if (!shash_find(&tc.chassis, id)) {
> +                struct chassis_node *chassis = xzalloc(sizeof *chassis);
> +                chassis->bridge = br_int;
> +                chassis->port = port;
> +                shash_add_assert(&tc.chassis, id, chassis);
> +            } else {
> +                /* Duplicate port for ovn-chassis-id.  Arbitrarily choose
> +                 * to delete this one. */
> +                ovsrec_bridge_update_ports_delvalue(br_int, port);
>              }
>          }
>      }
> @@ -366,7 +394,8 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
>                  continue;
>              }
>
> -            if (chassis_tunnel_add(chassis_rec, sbg, &tc) == 0) {
> +            if (chassis_tunnel_add(chassis_rec, this_chassis->name,
> +                                   sbg, &tc) == 0) {
>                  VLOG_INFO("Creating encap for '%s' failed", chassis_rec->name);
>                  continue;
>              }
> @@ -381,6 +410,7 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
>          shash_delete(&tc.chassis, node);
>          free(chassis);
>      }
> +
>      shash_destroy(&tc.chassis);
>      sset_destroy(&tc.port_names);
>  }
> @@ -400,6 +430,9 @@ encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
>          = xmalloc(sizeof *br_int->ports * br_int->n_ports);
>      size_t n = 0;
>      for (size_t i = 0; i < br_int->n_ports; i++) {
> +        if (!is_tunnel_port(br_int->ports[i])) {
> +            continue;
> +        }
>          if (!smap_get(&br_int->ports[i]->external_ids, "ovn-chassis-id")) {
>              ports[n++] = br_int->ports[i];
>          }
> diff --git a/controller/encaps.h b/controller/encaps.h
> index f488393c4..aff85097f 100644
> --- a/controller/encaps.h
> +++ b/controller/encaps.h
> @@ -30,7 +30,6 @@ struct sset;
>
>  void encaps_register_ovs_idl(struct ovsdb_idl *);
>  void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> -                const struct ovsrec_bridge_table *,
>                  const struct ovsrec_bridge *br_int,
>                  const struct sbrec_chassis_table *,
>                  const struct sbrec_chassis *,
> diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
> index 16bc47b20..c181a0fa6 100644
> --- a/controller/ovn-controller.8.xml
> +++ b/controller/ovn-controller.8.xml
> @@ -235,6 +235,19 @@
>        </dd>
>      </dl>
>
> +    <p>
> +      Note that every <code>external_ids:*</code> key listed above has its
> +      <code>external_ids:*-chassis_name</code> counterpart keys that allow to
> +      configure values specific to chassis running on the same OVSDB. For
> +      example, if two chassis named <code>blue</code> and <code>red</code> are
> +      available on the same host, then an admin may configure different
> +      <code>ovn-cms-options</code> for each of them by setting
> +      <code>external_ids:ovn-cms-options-blue</code> and
> +      <code>external_ids:ovn-cms-options-red</code> keys in the database. The
> +      only key that is not available for per-chassis configuration is
> +      <code>external_ids:system-id</code>.
> +    </p>
> +
>      <p>
>        <code>ovn-controller</code> reads the following values from the
>        <code>Open_vSwitch</code> database of the local OVS instance:
> @@ -286,7 +299,9 @@
>          The presence of this key identifies a tunnel port within the
>          integration bridge as one created by <code>ovn-controller</code> to
>          reach a remote chassis.  Its value is the chassis ID of the remote
> -        chassis.
> +        chassis. Alternatively, for patch ports, the key identifies the name of
> +        the chassis that owns it, in case of multiple virtual chassis running
> +        on the same host.
>        </dd>
>
>        <dt>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index a06cae3cc..240ca3cc6 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -18,10 +18,14 @@
>  #include "ovn-controller.h"
>
>  #include <errno.h>
> +#include <fcntl.h>
>  #include <getopt.h>
>  #include <signal.h>
>  #include <stdlib.h>
>  #include <string.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <unistd.h>
>
>  #include "bfd.h"
>  #include "binding.h"
> @@ -46,6 +50,7 @@
>  #include "lib/extend-table.h"
>  #include "lib/ip-mcast-index.h"
>  #include "lib/mcast-group-index.h"
> +#include "lib/ovn-dirs.h"
>  #include "lib/ovn-sb-idl.h"
>  #include "lib/ovn-util.h"
>  #include "patch.h"
> @@ -85,6 +90,12 @@ static unixctl_cb_func debug_delay_nb_cfg_report;
>
>  #define CONTROLLER_LOOP_STOPWATCH_NAME "ovn-controller-flow-generation"
>
> +/* These variables never change after initialization and can be safely used in
> + * I-P engine. If later we decide to allow to dynamically change them, I-P
> + * machinery will need some adjustments. */
> +static char *controller_chassis = NULL;
> +static char *system_id_override = NULL;
> +
>  static char *parse_options(int argc, char *argv[]);
>  OVS_NO_RETURN static void usage(void);
>
> @@ -260,7 +271,9 @@ out:
>  static const char *
>  br_int_name(const struct ovsrec_open_vswitch *cfg)
>  {
> -    return smap_get_def(&cfg->external_ids, "ovn-bridge", DEFAULT_BRIDGE_NAME);
> +    return get_chassis_external_id_value(
> +        &cfg->external_ids, get_ovs_chassis_id(cfg),
> +        "ovn-bridge", DEFAULT_BRIDGE_NAME);
>  }
>
>  static const struct ovsrec_bridge *
> @@ -361,8 +374,9 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
>          const struct ovsrec_open_vswitch *cfg;
>          cfg = ovsrec_open_vswitch_table_first(ovs_table);
>          ovs_assert(cfg);
> -        const char *datapath_type = smap_get(&cfg->external_ids,
> -                                             "ovn-bridge-datapath-type");
> +        const char *datapath_type = get_chassis_external_id_value(
> +            &cfg->external_ids, get_ovs_chassis_id(cfg),
> +            "ovn-bridge-datapath-type", NULL);
>          /* Check for the datapath_type and set it only if it is defined in
>           * cfg. */
>          if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) {
> @@ -372,17 +386,46 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
>      return br_int;
>  }
>
> -static const char *
> -get_ovs_chassis_id(const struct ovsrec_open_vswitch_table *ovs_table)
> +static char *get_file_system_id_override(void)
>  {
> -    const struct ovsrec_open_vswitch *cfg
> -        = ovsrec_open_vswitch_table_first(ovs_table);
> +    char *ret = NULL;
> +    char *filename = xasprintf("%s/system-id-override", ovn_sysconfdir());
> +    errno = 0;
> +    int fd = open(filename, O_RDONLY);
> +    if (fd != -1) {
> +        char file_system_id[64];
> +        int nread = read(fd, file_system_id, sizeof file_system_id);
> +        if (nread) {
> +            file_system_id[nread] = '\0';
> +            if (file_system_id[nread - 1] == '\n') {
> +                file_system_id[nread - 1] = '\0';
> +            }
> +            ret = xstrdup(file_system_id);
> +        }
> +        close(fd);
> +    }
> +
> +    free(filename);
> +    return ret;
> +}
> +
> +const char *
> +get_ovs_chassis_id(const struct ovsrec_open_vswitch *cfg)
> +{
> +    if (controller_chassis) {
> +        return controller_chassis;
> +    }
> +
> +    if (system_id_override) {
> +        return system_id_override;
> +    }
> +
>      const char *chassis_id = cfg ? smap_get(&cfg->external_ids, "system-id")
>                                   : NULL;
> -
>      if (!chassis_id) {
>          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -        VLOG_WARN_RL(&rl, "'system-id' in Open_vSwitch database is missing.");
> +        VLOG_WARN_RL(&rl, "Failed to detect system-id, "
> +                          "configuration not found.");
>      }
>
>      return chassis_id;
> @@ -477,10 +520,12 @@ static int
>  get_ofctrl_probe_interval(struct ovsdb_idl *ovs_idl)
>  {
>      const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
> -    return !cfg ? OFCTRL_DEFAULT_PROBE_INTERVAL_SEC :
> -                  smap_get_int(&cfg->external_ids,
> -                               "ovn-openflow-probe-interval",
> -                               OFCTRL_DEFAULT_PROBE_INTERVAL_SEC);
> +    if (!cfg) {
> +        return OFCTRL_DEFAULT_PROBE_INTERVAL_SEC;
> +    }
> +    return get_chassis_external_id_value_int(
> +        &cfg->external_ids, get_ovs_chassis_id(cfg),
> +        "ovn-openflow-probe-interval", OFCTRL_DEFAULT_PROBE_INTERVAL_SEC);
>  }
>
>  /* Retrieves the pointer to the OVN Southbound database from 'ovs_idl' and
> @@ -496,18 +541,21 @@ update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl,
>      }
>
>      /* Set remote based on user configuration. */
> -    const char *remote = smap_get(&cfg->external_ids, "ovn-remote");
> +    const char *chassis_id = get_ovs_chassis_id(cfg);
> +    const char *remote = get_chassis_external_id_value(
> +        &cfg->external_ids, chassis_id, "ovn-remote", NULL);
>      ovsdb_idl_set_remote(ovnsb_idl, remote, true);
>
>      /* Set probe interval, based on user configuration and the remote. */
>      int default_interval = (remote && !stream_or_pstream_needs_probes(remote)
>                              ? 0 : DEFAULT_PROBE_INTERVAL_MSEC);
> -    int interval = smap_get_int(&cfg->external_ids,
> -                                "ovn-remote-probe-interval", default_interval);
> +    int interval = get_chassis_external_id_value_int(
> +        &cfg->external_ids, chassis_id, "ovn-remote-probe-interval",
> +        default_interval);
>      ovsdb_idl_set_probe_interval(ovnsb_idl, interval);
>
> -    bool monitor_all = smap_get_bool(&cfg->external_ids, "ovn-monitor-all",
> -                                     false);
> +    bool monitor_all = get_chassis_external_id_value_bool(
> +        &cfg->external_ids, chassis_id, "ovn-monitor-all", false);
>      if (monitor_all) {
>          /* Always call update_sb_monitors when monitor_all is true.
>           * Otherwise, don't call it here, because there would be unnecessary
> @@ -1166,7 +1214,9 @@ init_binding_ctx(struct engine_node *node,
>      struct ovsrec_bridge_table *bridge_table =
>          (struct ovsrec_bridge_table *)EN_OVSDB_GET(
>              engine_get_input("OVS_bridge", node));
> -    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    const struct ovsrec_open_vswitch *cfg =
> +        ovsrec_open_vswitch_table_first(ovs_table);
> +    const char *chassis_id = get_ovs_chassis_id(cfg);
>      const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
>
>      ovs_assert(br_int && chassis_id);
> @@ -2432,6 +2482,10 @@ main(int argc, char *argv[])
>      exiting = false;
>      restart = false;
>      bool sb_monitor_all = false;
> +
> +    /* Read from system-id-override file once on startup. */
> +    system_id_override = get_file_system_id_override();
> +
>      while (!exiting) {
>          /* If we're paused just run the unixctl server and skip most of the
>           * processing loop.
> @@ -2498,7 +2552,9 @@ main(int argc, char *argv[])
>                  sbrec_chassis_private_table_get(ovnsb_idl_loop.idl);
>              const struct ovsrec_bridge *br_int =
>                  process_br_int(ovs_idl_txn, bridge_table, ovs_table);
> -            const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +            const struct ovsrec_open_vswitch *cfg =
> +                ovsrec_open_vswitch_table_first(ovs_table);
> +            const char *chassis_id = get_ovs_chassis_id(cfg);
>              const struct sbrec_chassis *chassis = NULL;
>              const struct sbrec_chassis_private *chassis_private = NULL;
>              if (chassis_id) {
> @@ -2518,7 +2574,7 @@ main(int argc, char *argv[])
>
>                  if (chassis) {
>                      encaps_run(ovs_idl_txn,
> -                               bridge_table, br_int,
> +                               br_int,
>                                 sbrec_chassis_table_get(ovnsb_idl_loop.idl),
>                                 chassis,
>                                 sbrec_sb_global_first(ovnsb_idl_loop.idl),
> @@ -2779,6 +2835,8 @@ loop_done:
>      ovsdb_idl_loop_destroy(&ovs_idl_loop);
>      ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
>
> +    free(controller_chassis);
> +    free(system_id_override);
>      free(ovs_remote);
>      service_stop();
>
> @@ -2804,6 +2862,7 @@ parse_options(int argc, char *argv[])
>          STREAM_SSL_LONG_OPTIONS,
>          {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
>          {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
> +        {"chassis", required_argument, NULL, 'n'},
>          {NULL, 0, NULL, 0}
>      };
>      char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
> @@ -2836,6 +2895,10 @@ parse_options(int argc, char *argv[])
>              stream_ssl_set_ca_cert_file(optarg, true);
>              break;
>
> +        case 'n':
> +            controller_chassis = xstrdup(optarg);
> +            break;
> +
>          case '?':
>              exit(EXIT_FAILURE);
>
> @@ -2871,6 +2934,7 @@ usage(void)
>      daemon_usage();
>      vlog_usage();
>      printf("\nOther options:\n"
> +           "  -n                      custom chassis name\n"
>             "  -h, --help              display this help message\n"
>             "  -V, --version           display version information\n");
>      exit(EXIT_SUCCESS);
> diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h
> index 5d9466880..9994dd777 100644
> --- a/controller/ovn-controller.h
> +++ b/controller/ovn-controller.h
> @@ -21,6 +21,7 @@
>  #include "lib/ovn-sb-idl.h"
>
>  struct ovsrec_bridge_table;
> +struct ovsrec_open_vswitch;
>
>  /* Linux supports a maximum of 64K zones, which seems like a fine default. */
>  #define MAX_CT_ZONES 65535
> @@ -87,4 +88,7 @@ enum chassis_tunnel_type {
>
>  uint32_t get_tunnel_type(const char *name);
>
> +const char *get_ovs_chassis_id(const struct ovsrec_open_vswitch *cfg);
> +bool is_concurrent_chassis(const struct ovsrec_open_vswitch *cfg);
> +
>  #endif /* controller/ovn-controller.h */
> diff --git a/controller/patch.c b/controller/patch.c
> index a2a7bcd79..3b3df278c 100644
> --- a/controller/patch.c
> +++ b/controller/patch.c
> @@ -76,6 +76,7 @@ create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
>                    const char *key, const char *value,
>                    const struct ovsrec_bridge *src, const char *src_name,
>                    const struct ovsrec_bridge *dst, const char *dst_name,
> +                  const char *chassis_name,
>                    struct shash *existing_ports)
>  {
>      for (size_t i = 0; i < src->n_ports; i++) {
> @@ -101,7 +102,8 @@ create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
>      port = ovsrec_port_insert(ovs_idl_txn);
>      ovsrec_port_set_name(port, src_name);
>      ovsrec_port_set_interfaces(port, &iface, 1);
> -    const struct smap ids = SMAP_CONST1(&ids, key, value);
> +    const struct smap ids = SMAP_CONST2(&ids, key, value,
> +                                        "ovn-chassis-id", chassis_name);
>      ovsrec_port_set_external_ids(port, &ids);
>
>      struct ovsrec_port **ports;
> @@ -157,7 +159,9 @@ add_ovs_bridge_mappings(const struct ovsrec_open_vswitch_table *ovs_table,
>          const char *mappings_cfg;
>          char *cur, *next, *start;
>
> -        mappings_cfg = smap_get(&cfg->external_ids, "ovn-bridge-mappings");
> +        mappings_cfg = get_chassis_external_id_value(
> +            &cfg->external_ids, get_ovs_chassis_id(cfg),
> +            "ovn-bridge-mappings", NULL);
>          if (!mappings_cfg || !mappings_cfg[0]) {
>              return;
>          }
> @@ -269,9 +273,11 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
>          char *name1 = patch_port_name(br_int->name, binding->logical_port);
>          char *name2 = patch_port_name(binding->logical_port, br_int->name);
>          create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
> -                          br_int, name1, br_ln, name2, existing_ports);
> +                          br_int, name1, br_ln, name2, chassis->name,
> +                          existing_ports);
>          create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
> -                          br_ln, name2, br_int, name1, existing_ports);
> +                          br_ln, name2, br_int, name1, chassis->name,
> +                          existing_ports);
>          free(name1);
>          free(name2);
>      }
> @@ -323,6 +329,12 @@ patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
>      SHASH_FOR_EACH_SAFE (port_node, port_next_node, &existing_ports) {
>          port = port_node->data;
>          shash_delete(&existing_ports, port_node);
> +
> +        const char *port_chassis = smap_get(&port->external_ids,
> +                                            "ovn-chassis-id");
> +        if (port_chassis && strcmp(port_chassis, chassis->name)) {
> +            continue;
> +        }
>          remove_port(bridge_table, port);
>      }
>      shash_destroy(&existing_ports);
> diff --git a/controller/physical.c b/controller/physical.c
> index 1bc2c389b..819d97ec0 100644
> --- a/controller/physical.c
> +++ b/controller/physical.c
> @@ -432,7 +432,7 @@ populate_remote_chassis_macs(const struct sbrec_chassis *my_chassis,
>          }
>
>          const char *tokens
> -            = get_chassis_mac_mappings(&chassis->other_config);
> +            = get_chassis_mac_mappings(&chassis->other_config, NULL);
>
>          if (!strlen(tokens)) {
>              continue;
> diff --git a/lib/ovn-util.c b/lib/ovn-util.c
> index abe6b04a7..f0dde27f0 100644
> --- a/lib/ovn-util.c
> +++ b/lib/ovn-util.c
> @@ -722,3 +722,53 @@ ip_address_and_port_from_lb_key(const char *key, char **ip_address,
>      *addr_family = ss.ss_family;
>      return true;
>  }
> +
> +const char *
> +get_chassis_external_id_value(const struct smap *external_ids,
> +                              const char *chassis_id, const char *option_key,
> +                              const char *def)
> +{
> +    const char *option_value = NULL;
> +    if (chassis_id != NULL) {
> +        char *chassis_option_key = xasprintf("%s-%s", option_key, chassis_id);
> +        option_value = smap_get(external_ids, chassis_option_key);
> +        free(chassis_option_key);
> +    }
> +    if (!option_value) {
> +        option_value = smap_get_def(external_ids, option_key, def);
> +    }
> +    return option_value;
> +}
> +
> +int
> +get_chassis_external_id_value_int(const struct smap *external_ids,
> +                                  const char *chassis_id,
> +                                  const char *option_key,
> +                                  int def)
> +{
> +    const char *value = get_chassis_external_id_value(
> +        external_ids, chassis_id, option_key, NULL);
> +
> +    int i_value;
> +    if (!value || !str_to_int(value, 10, &i_value)) {
> +        return def;
> +    }
> +
> +    return i_value;
> +}
> +
> +bool
> +get_chassis_external_id_value_bool(const struct smap *external_ids,
> +                                   const char *chassis_id,
> +                                   const char *option_key,
> +                                   bool def)
> +{
> +    const char *value = get_chassis_external_id_value(
> +        external_ids, chassis_id, option_key, "");
> +
> +    if (def) {
> +        return strcasecmp("false", value) != 0;
> +    } else {
> +        return !strcasecmp("true", value);
> +    }
> +}
> diff --git a/lib/ovn-util.h b/lib/ovn-util.h
> index a39cbef5a..2228770aa 100644
> --- a/lib/ovn-util.h
> +++ b/lib/ovn-util.h
> @@ -18,6 +18,7 @@
>
>  #include "lib/packets.h"
>  #include "include/ovn/version.h"
> +#include "smap.h"
>
>  #define ovn_set_program_name(name) \
>      ovs_set_program_name(name, OVN_PACKAGE_VERSION)
> @@ -164,6 +165,23 @@ char *normalize_v46_prefix(const struct v46_ip *prefix, unsigned int plen);
>  unsigned int ovn_smap_get_uint(const struct smap *smap, const char *key,
>                                 unsigned int def);
>
> +const char *
> +get_chassis_external_id_value(const struct smap *external_ids,
> +                              const char *chassis_id, const char *option_key,
> +                              const char *def);
> +
> +int
> +get_chassis_external_id_value_int(const struct smap *external_ids,
> +                                  const char *chassis_id,
> +                                  const char *option_key,
> +                                  int def);
> +
> +bool
> +get_chassis_external_id_value_bool(const struct smap *external_ids,
> +                                   const char *chassis_id,
> +                                   const char *option_key,
> +                                   bool def);
> +
>  /* Returns a lowercase copy of orig.
>   * Caller must free the returned string.
>   */
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index b1480f218..4261270b6 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -240,10 +240,12 @@
>
>      <column name="name">
>        OVN does not prescribe a particular format for chassis names.
> -      ovn-controller populates this column using <ref key="system-id"
> -      table="Open_vSwitch" column="external_ids" db="Open_vSwitch"/>
> -      in the Open_vSwitch database's <ref table="Open_vSwitch"
> -      db="Open_vSwitch"/> table.  ovn-controller-vtep populates this
> +      ovn-controller populates this column using the <code>-n</code>
> +      CLI argument, or <code>system-id-override</code> configuration file, or
> +      <ref key="system-id" table="Open_vSwitch" column="external_ids"
> +      db="Open_vSwitch"/> in the Open_vSwitch database's
> +      <ref table="Open_vSwitch" db="Open_vSwitch"/> table.
> +      ovn-controller-vtep populates this
>        column with <ref table="Physical_Switch" column="name"
>        db="hardware_vtep"/> in the hardware_vtep database's
>        <ref table="Physical_Switch" db="hardware_vtep"/> table.
> diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
> index 014a97760..740bfa239 100644
> --- a/tests/ovn-controller.at
> +++ b/tests/ovn-controller.at
> @@ -50,8 +50,7 @@ patch
>  # is mirrored into the Chassis record in the OVN_Southbound db.
>  check_bridge_mappings () {
>      local_mappings=$1
> -    sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
> -    OVS_WAIT_UNTIL([test x"${local_mappings}" = x$(ovn-sbctl get Chassis ${sysid} other_config:ovn-bridge-mappings | sed -e 's/\"//g')])
> +    OVS_WAIT_UNTIL([test x"${local_mappings}" = x$(ovn-sbctl get Chassis ${sandbox} other_config:ovn-bridge-mappings | sed -e 's/\"//g')])
>  }
>
>  # Initially there should be no patch ports.
> @@ -133,13 +132,13 @@ ovs-vsctl \
>      -- add-br br-eth2
>  ovn_attach n1 br-phys 192.168.0.1
>
> -sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
> +sysid=${sandbox}
>
>  # Make sure that the datapath_type set in the Bridge table
>  # is mirrored into the Chassis record in the OVN_Southbound db.
>  check_datapath_type () {
>      datapath_type=$1
> -    chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} other_config:datapath-type | sed -e 's/"//g') #"
> +    chassis_datapath_type=$(ovn-sbctl get Chassis ${sandbox} other_config:datapath-type | sed -e 's/"//g') #"
>      test "${datapath_type}" = "${chassis_datapath_type}"
>  }
>
> @@ -187,7 +186,7 @@ OVS_WAIT_UNTIL([
>      test "${expected_iface_types}" = "${chassis_iface_types}"
>  ])
>
> -# Change the value of external_ids:system-id and make sure it's mirrored
> +# Set the value of external_ids:system-id and make sure it's mirrored
>  # in the Chassis record in the OVN_Southbound database.
>  sysid=${sysid}-foo
>  ovs-vsctl set Open_vSwitch . external-ids:system-id="${sysid}"
> @@ -252,7 +251,7 @@ ovs-vsctl \
>  ovn_attach n1 br-phys 192.168.0.1
>
>  check_tunnel_property () {
> -    test "`ovs-vsctl get interface ovn-fakech-0 $1`" = "$2"
> +    test "`ovs-vsctl get interface ovn-hv-fakechassis-0 $1`" = "$2"
>  }
>
>  # Start off with a remote chassis supporting STT
> @@ -265,7 +264,7 @@ OVS_WAIT_UNTIL([check_tunnel_property type stt])
>  # the chassis-id in ovn-chassis-id); if we supply a different IP here
>  # we won't be able to co-relate this to the tunnel port that was created
>  # in the previous step and, as a result, will end up creating another tunnel,
> -# ie. we can't just lookup using "ovn-fakech-0". So, need to use the same IP
> +# ie. we can't just lookup using "ovn-fakechassis-0". So, need to use the same IP
>  # as above, i.e 192.168.0.2, here.
>  encap_uuid=$(ovn-sbctl add chassis fakechassis encaps @encap -- --id=@encap create encap type=geneve ip="192.168.0.2")
>  OVS_WAIT_UNTIL([check_tunnel_property type geneve])
> @@ -275,11 +274,11 @@ ovn-sbctl set encap ${encap_uuid} ip=192.168.0.2
>  OVS_WAIT_UNTIL([check_tunnel_property options:remote_ip "\"192.168.0.2\""])
>
>  # Change the type on the OVS side and check than OVN fixes it
> -ovs-vsctl set interface ovn-fakech-0 type=vxlan
> +ovs-vsctl set interface ovn-hv-fakechassis-0 type=vxlan
>  OVS_WAIT_UNTIL([check_tunnel_property type geneve])
>
>  # Delete the port entirely and it should be resurrected
> -ovs-vsctl del-port ovn-fakech-0
> +ovs-vsctl del-port ovn-hv-fakechassis-0
>  OVS_WAIT_UNTIL([check_tunnel_property type geneve])
>
>  # Gracefully terminate daemons
> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> index 5b9c2dee6..5072e4e7d 100644
> --- a/tests/ovn-macros.at
> +++ b/tests/ovn-macros.at
> @@ -227,7 +227,7 @@ net_attach () {
>
>  # ovn_az_attach AZ NETWORK BRIDGE IP [MASKLEN]
>  ovn_az_attach() {
> -    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan}
> +    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan} intbr=${7-br-int} chassis=$8
>      net_attach $net $bridge || return 1
>
>      mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g`
> @@ -241,15 +241,48 @@ ovn_az_attach() {
>      else
>          ovn_remote=unix:$ovs_base/$az/ovn-sb/ovn-sb.sock
>      fi
> +
> +    if [[ -n "${chassis}" ]]; then
> +        bridge_key=ovn-bridge-${chassis}
> +        remote_key=ovn-remote-${chassis}
> +        encap_type_key=ovn-encap-type-${chassis}
> +        encap_ip_key=ovn-encap-ip-${chassis}
> +        chassis_args="-n $chassis"
> +        chassis_vsctl_args=
> +    else
> +        bridge_key=ovn-bridge
> +        remote_key=ovn-remote
> +        encap_type_key=ovn-encap-type
> +        encap_ip_key=ovn-encap-ip
> +        chassis=$sandbox
> +        chassis_args=
> +        chassis_vsctl_args="-- set Open_vSwitch . external-ids:system-id=$chassis"
> +    fi
> +
>      ovs-vsctl \
> -        -- set Open_vSwitch . external-ids:system-id=$sandbox \
> -        -- set Open_vSwitch . external-ids:ovn-remote=$ovn_remote \
> -        -- set Open_vSwitch . external-ids:ovn-encap-type=$encap \
> -        -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip \
> -        -- --may-exist add-br br-int \
> -        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
> +        $chassis_vsctl_args \
> +        -- set Open_vSwitch . external-ids:$bridge_key=$intbr \
> +        -- set Open_vSwitch . external-ids:$remote_key=$ovn_remote \
> +        -- set Open_vSwitch . external-ids:$encap_type_key=$encap \
> +        -- set Open_vSwitch . external-ids:$encap_ip_key=$ip \
> +        -- --may-exist add-br ${intbr} \
> +        -- set bridge ${intbr} fail-mode=secure other-config:disable-in-band=true \
>          || return 1
> -    start_daemon ovn-controller || return 1
> +
> +    if [[ "${intbr}" = br-int ]]; then
> +        pidfile="${OVS_RUNDIR}/ovn-controller.pid"
> +        logfile="${OVS_LOGDIR}/ovn-controller.log"
> +    else
> +        pidfile="${OVS_RUNDIR}/ovn-controller-${intbr}.pid"
> +        logfile="${OVS_LOGDIR}/ovn-controller-${chassis}.log"
> +    fi
> +
> +    ovn-controller \
> +        ${chassis_args} \
> +        -vconsole:off --detach --no-chdir \
> +        --pidfile=${pidfile} \
> +        --log-file=${logfile} || return 1
> +    on_exit "test -e \"$pidfile\" && kill \`cat \"$pidfile\"\`"
>  }
>
>  # ovn_attach NETWORK BRIDGE IP [MASKLEN]
> diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
> index 6cc5b2174..3e46309dd 100644
> --- a/tests/ovn-performance.at
> +++ b/tests/ovn-performance.at
> @@ -255,12 +255,12 @@ done
>  # Otherwise this may affect the lflow_run count.
>
>  OVS_WAIT_UNTIL([
> -    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
> +    test $(as hv1 ovs-vsctl list interface ovn-hv1-hv2-0 | \
>  grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>  ])
>
>  OVS_WAIT_UNTIL([
> -    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
> +    test $(as hv2 ovs-vsctl list interface ovn-hv2-hv1-0 | \
>  grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>  ])
>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index f154e3d77..b2971dce0 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1735,6 +1735,180 @@ AT_CLEANUP
>
>  AT_BANNER([OVN end-to-end tests])
>
> +AT_SETUP([ovn -- cross-connectivity between two virtual chassis])
> +AT_KEYWORDS([ovn])
> +ovn_start
> +ovn-nbctl ls-add lsw0
> +net_add n1
> +sim_add hv
> +
> +as hv
> +
> +ovn-nbctl lsp-add lsw0 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=n1
> +
> +for i in 1 2; do
> +    chassis=host-$i
> +    ovs-vsctl add-br br-phys-$i
> +    ovn_attach n1 br-phys-$i 192.168.$i.1 24 geneve br-int-$i $chassis
> +
> +    AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings-$chassis=n1:br-phys-$i])
> +
> +    lpname=lp$i
> +    ovn-nbctl lsp-add lsw0 $lpname
> +    ovn-nbctl lsp-set-addresses $lpname "f0:00:00:00:00:0$i"
> +    ovn-nbctl --wait=hv --timeout=3 lsp-set-options $lpname requested-chassis=$chassis
> +    ovs-vsctl add-port br-int-$i vif$i -- set Interface vif$i external-ids:iface-id=$lpname \
> +                                  options:tx_pcap=$i-tx.pcap \
> +                                  options:rxq_pcap=$i-rx.pcap \
> +                                  ofport-request=$i
> +    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lpname` = xup])
> +
> +    pb_chassis_id=$(ovn-sbctl --bare --columns chassis list port_binding $lpname)
> +    pb_chassis_name=$(ovn-sbctl get chassis $pb_chassis_id name)
> +    AT_FAIL_IF([test x$pb_chassis_name != x$chassis])
> +done
> +
> +test_packet() {
> +    local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6
> +
> +    # First try tracing the packet.
> +    uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth"
> +    echo "output(\"$lout\");" > expout
> +    AT_CAPTURE_FILE([trace])
> +    AT_CHECK([ovn-trace --all lsw0 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
> +
> +    # Then actually send a packet, for an end-to-end test.
> +    local packet=$(echo $dst$src | sed 's/://g')${eth}
> +    as hv ovs-appctl netdev-dummy/receive vif$inport $packet
> +    echo $packet >> ${eout#lp}.expected
> +}
> +
> +test_packet 1 f0:00:00:00:00:02 f0:00:00:00:00:01 1234 lp2 lp2
> +test_packet 2 f0:00:00:00:00:01 f0:00:00:00:00:02 1234 lp1 lp1
> +
> +for i in 1 2; do
> +    OVN_CHECK_PACKETS_REMOVE_BROADCAST([$i-tx.pcap], [$i.expected])
> +done
> +
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- 3 virtual hosts, same node])
> +AT_KEYWORDS([ovn])
> +ovn_start
> +ovn-nbctl ls-add lsw0
> +net_add n1
> +sim_add hv
> +
> +as hv
> +for i in 1 2 3; do
> +    chassis=host-$i
> +    ovs-vsctl add-br br-phys-$i
> +    ovn_attach n1 br-phys-$i 192.168.0.$i 24 geneve br-int-$i $chassis
> +
> +    for j in 1 2 3; do
> +        lpname=lp$i$j
> +        ovn-nbctl lsp-add lsw0 $lpname
> +        ovn-nbctl --wait=hv --timeout=3 lsp-set-options $lpname requested-chassis=$chassis
> +        ovs-vsctl add-port br-int-$i vif$i$j -- set Interface vif$i$j external-ids:iface-id=$lpname
> +        OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lpname` = xup])
> +
> +        pb_chassis_id=$(ovn-sbctl --bare --columns chassis list port_binding $lpname)
> +        pb_chassis_name=$(ovn-sbctl get chassis $pb_chassis_id name)
> +        AT_FAIL_IF([test x$pb_chassis_name != x$chassis])
> +    done
> +done
> +
> +for i in 1 2 3; do
> +    > expout
> +    for vif in 1 2 3; do
> +        echo vif$i$vif >> expout
> +    done
> +    AT_CHECK([ovs-vsctl list-ports br-int-$i | grep vif], [0], [expout])
> +done
> +
> +# check that all tunnel endpoints are present
> +AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> +[[ovn-host-1-host-2-0
> +ovn-host-1-host-3-0
> +ovn-host-2-host-1-0
> +ovn-host-2-host-3-0
> +ovn-host-3-host-1-0
> +ovn-host-3-host-2-0
> +]])
> +
> +# check no errors occurred during encaps creation
> +AT_CHECK([ovs-vsctl --bare --columns=error find interface type="geneve" | awk NF | sort], [0], [])
> +
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- system-id in file])
> +AT_KEYWORDS([ovn])
> +
> +ovn_start
> +net_add n1
> +sim_add hv
> +
> +as hv
> +
> +echo otherid > ${OVN_SYSCONFDIR}/system-id-override
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +
> +# system-id-override file overrides chassis name selected via cli
> +echo otherid > expout
> +AT_CHECK([ovn-sbctl --bare --columns name list chassis], [0], [expout])
> +
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- concurrent controllers avoid fighting for each others' resources])
> +AT_KEYWORDS([ovn])
> +
> +ovn_start
> +sim_add hv
> +
> +for i in 1 2; do
> +    net_add n-$i
> +done
> +
> +as hv
> +for i in 1 2; do
> +    AT_CHECK([ovn-nbctl ls-add ls-$i])
> +    AT_CHECK([ovn-nbctl lsp-add ls-$i ln_port-$i])
> +    AT_CHECK([ovn-nbctl lsp-set-addresses ln_port-$i unknown])
> +    AT_CHECK([ovn-nbctl lsp-set-type ln_port-$i localnet])
> +    AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port-$i network_name=phys-$i])
> +done
> +
> +for i in 1 2; do
> +    as hv
> +    ovs-vsctl add-br br-phys-$i
> +    ovs-vsctl set open . external-ids:ovn-bridge-mappings-controller-$i=phys-$i:br-phys-$i
> +    ovn_attach n-$i br-phys-$i 192.168.0.$i 24 geneve br-int-$i controller-$i
> +
> +    ovs-vsctl add-port br-int-$i vif-$i -- set Interface vif-$i external-ids:iface-id=lp-$i
> +    ovn-nbctl lsp-add ls-$i lp-$i
> +    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp-$i` = xup])
> +done
> +
> +# check that both patch ports are present
> +AT_CHECK([ovs-vsctl --bare --columns=name find interface type="patch" | awk NF | sort], [0],
> +[[patch-br-int-1-to-ln_port-1
> +patch-br-int-2-to-ln_port-2
> +patch-ln_port-1-to-br-int-1
> +patch-ln_port-2-to-br-int-2
> +]])
> +
> +# check that both tunnel endpoints are present
> +AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> +[[ovn-controller-1-controller-2-0
> +ovn-controller-2-controller-1-0
> +]])
> +
> +AT_CLEANUP
> +
>  # 3 hypervisors, one logical switch, 3 logical ports per hypervisor
>  AT_SETUP([ovn -- 3 HVs, 1 LS, 3 lports/HV])
>  AT_KEYWORDS([ovnarp])
> @@ -6926,6 +7100,72 @@ OVN_CLEANUP([hv1])
>
>  AT_CLEANUP
>
> +AT_SETUP([ovn -- obsolete patch ports and tunnel endpoints removed])
> +AT_KEYWORDS([cleanup-test])
> +ovn_start
> +
> +net_add n1
> +net_add n2
> +
> +for i in 1 2; do
> +    ovs-vsctl add-br br-phys$i
> +    ovs-vsctl set open . external-ids:ovn-bridge-mappings-hv$i=physnet$i:br-phys$i
> +    ovn_attach n$i br-phys$i 192.168.0.$i 24 geneve br-int$i hv$i
> +done
> +
> +# create irrelevant patch and tunnel ports
> +for i in 1 2; do
> +    # patch without chassis owner set
> +    ovs-vsctl add-port br-int$i fakepatch$i external-ids:ovn-logical-patch-port=fakeport$i -- \
> +              set Interface fakepatch$i type=patch
> +
> +    # patch marked as owned by the chassis
> +    ovs-vsctl add-port br-int$i owned_fakepatch$i external-ids:ovn-logical-patch-port=owned_fakeport$i \
> +                                                  external-ids:ovn-chassis-id=hv$i -- \
> +              set Interface owned_fakepatch$i type=patch
> +
> +    # patch marked as owned by some other chassis
> +    ovs-vsctl add-port br-int$i alien_fakepatch$i external-ids:ovn-logical-patch-port=alien_fakeport$i \
> +                                                  external-ids:ovn-chassis-id=alien_hv$i -- \
> +              set Interface alien_fakepatch$i type=patch
> +
> +    # OVN tunnel endpoint on a bridge owned by a controller
> +    ovs-vsctl add-port br-int$i faketunnel$i external-ids:ovn-chassis-id=fakechassis -- \
> +              set Interface faketunnel$i type=geneve
> +done
> +
> +# tunnel endpoint on a bridge NOT owned by a controller
> +ovs-vsctl add-br alien_br
> +ovs-vsctl add-port alien_br alien_tunnel external-ids:ovn-chassis-id=fakechassis -- \
> +          set Interface alien_tunnel type=geneve
> +
> +AT_CHECK([ovn-nbctl ls-add lsw0])
> +AT_CHECK([ovn-nbctl lsp-add lsw0 lnport])
> +AT_CHECK([ovn-nbctl lsp-set-addresses lnport unknown])
> +AT_CHECK([ovn-nbctl lsp-set-type lnport localnet])
> +AT_CHECK([ovn-nbctl --wait=hv lsp-set-options lnport network_name=physnet1])
> +
> +ovs-vsctl add-port br-int1 vif -- set Interface vif external-ids:iface-id=lp0
> +ovn-nbctl lsp-add lsw0 lp0
> +OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp0` = xup])
> +
> +# check that only patch ports that belong to some other chassis and those for localnet ports are present
> +AT_CHECK([ovs-vsctl --bare --columns=name find interface type="patch" | awk NF | sort], [0],
> +[[alien_fakepatch1
> +alien_fakepatch2
> +patch-br-int1-to-lnport
> +patch-lnport-to-br-int1
> +]])
> +
> +# check that only controller tunnel endpoints and a tunnel on another bridge are present
> +AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> +[[alien_tunnel
> +ovn-hv1-hv2-0
> +ovn-hv2-hv1-0
> +]])
> +
> +AT_CLEANUP
> +
>  AT_SETUP([ovn -- nd_na ])
>  ovn_start
>
> @@ -11365,7 +11605,7 @@ bfd_dump() {
>          for chassis2 in gw1 gw2 hv1 hv2; do
>              if [[ "$chassis" != "$chassis2" ]]; then
>                  echo " -> $chassis2:"
> -                echo "   $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis2-0)"
> +                echo "   $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis-$chassis2-0)"
>              fi
>          done
>          echo "--------------------------"
> @@ -11455,7 +11695,7 @@ wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis
>  as gw1
>  for chassis in gw2 hv1 hv2; do
>      echo "checking gw1 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw1-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
> @@ -11465,7 +11705,7 @@ done
>  as gw2
>  for chassis in gw1 hv1 hv2; do
>      echo "checking gw2 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
> @@ -11474,12 +11714,12 @@ done
>  as hv1
>  for chassis in gw1 gw2; do
>      echo "checking hv1 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
>  # make sure BFD is not enabled to hv2, we don't need it
> -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
> +AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-hv2-0],[0],
>           [[
>  ]])
>
> @@ -11488,12 +11728,12 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
>  as hv2
>  for chassis in gw1 gw2; do
>      echo "checking hv2 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
>  # make sure BFD is not enabled to hv1, we don't need it
> -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
> +AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-hv1-0],[0],
>           [[
>  ]])
>
> @@ -11528,7 +11768,7 @@ as gw2
>  for chassis in gw1 hv1 hv2; do
>      echo "checking gw2 -> $chassis"
>      OVS_WAIT_UNTIL([
> -    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
> +    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0)
>      test "$bfd_cfg" = "enable=true min_rx=2000"
>  ])
>  done
> @@ -11536,7 +11776,7 @@ ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-tx"=1500
>  for chassis in gw1 hv1 hv2; do
>      echo "checking gw2 -> $chassis"
>      OVS_WAIT_UNTIL([
> -    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
> +    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0)
>      test "$bfd_cfg" = "enable=true min_rx=2000 min_tx=1500"
>  ])
>  done
> @@ -11545,7 +11785,7 @@ ovn-nbctl --wait=hv set NB_Global . options:"bfd-mult"=5
>  for chassis in gw1 hv1 hv2; do
>      echo "checking gw2 -> $chassis"
>      OVS_WAIT_UNTIL([
> -    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
> +    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0)
>      test "$bfd_cfg" = "enable=true min_tx=1500 mult=5"
>  ])
>  done
> @@ -11681,7 +11921,7 @@ grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
>  as gw1
>  for chassis in gw2 hv1 hv2; do
>      echo "checking gw1 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw1-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
> @@ -11690,7 +11930,7 @@ done
>  as gw2
>  for chassis in gw1 hv1 hv2; do
>      echo "checking gw2 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
> @@ -11699,12 +11939,12 @@ done
>  as hv1
>  for chassis in gw1 gw2; do
>      echo "checking hv1 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
>  # make sure BFD is not enabled to hv2, we don't need it
> -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
> +AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-hv2-0],[0],
>           [[
>  ]])
>
> @@ -11712,12 +11952,12 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
>  as hv2
>  for chassis in gw1 gw2; do
>      echo "checking hv2 -> $chassis"
> -    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
> +    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-$chassis-0],[0],
>               [[enable=true
>  ]])
>  done
>  # make sure BFD is not enabled to hv1, we don't need it
> -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
> +AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-hv1-0],[0],
>           [[
>  ]])
>
> @@ -15777,42 +16017,42 @@ check ovn-nbctl --wait=hv sync
>  dnl Assert that each Chassis has a tunnel formed to every other Chassis
>  as hv1
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv2-0
> -ovn-hv3-0
> -ovn-hv4-0
> -ovn-hv5-0
> +[[ovn-hv1-hv2-0
> +ovn-hv1-hv3-0
> +ovn-hv1-hv4-0
> +ovn-hv1-hv5-0
>  ]])
>
>  as hv2
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv3-0
> -ovn-hv4-0
> -ovn-hv5-0
> +[[ovn-hv2-hv1-0
> +ovn-hv2-hv3-0
> +ovn-hv2-hv4-0
> +ovn-hv2-hv5-0
>  ]])
>
>  as hv3
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv2-0
> -ovn-hv4-0
> -ovn-hv5-0
> +[[ovn-hv3-hv1-0
> +ovn-hv3-hv2-0
> +ovn-hv3-hv4-0
> +ovn-hv3-hv5-0
>  ]])
>
>  as hv4
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv2-0
> -ovn-hv3-0
> -ovn-hv5-0
> +[[ovn-hv4-hv1-0
> +ovn-hv4-hv2-0
> +ovn-hv4-hv3-0
> +ovn-hv4-hv5-0
>  ]])
>
>  as hv5
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv2-0
> -ovn-hv3-0
> -ovn-hv4-0
> +[[ovn-hv5-hv1-0
> +ovn-hv5-hv2-0
> +ovn-hv5-hv3-0
> +ovn-hv5-hv4-0
>  ]])
>
>  dnl Let's now add some Chassis to different transport zones
> @@ -15843,28 +16083,28 @@ check ovn-nbctl --wait=hv sync
>
>  as hv1
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv2-0
> -ovn-hv3-0
> +[[ovn-hv1-hv2-0
> +ovn-hv1-hv3-0
>  ]])
>
>  as hv2
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> +[[ovn-hv2-hv1-0
>  ]])
>
>  as hv3
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> +[[ovn-hv3-hv1-0
>  ]])
>
>  as hv4
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv5-0
> +[[ovn-hv4-hv5-0
>  ]])
>
>  as hv5
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv4-0
> +[[ovn-hv5-hv4-0
>  ]])
>
>  dnl Removing the transport zones should make all Chassis to create
> @@ -15879,42 +16119,42 @@ check ovn-nbctl --wait=hv sync
>
>  as hv1
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv2-0
> -ovn-hv3-0
> -ovn-hv4-0
> -ovn-hv5-0
> +[[ovn-hv1-hv2-0
> +ovn-hv1-hv3-0
> +ovn-hv1-hv4-0
> +ovn-hv1-hv5-0
>  ]])
>
>  as hv2
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv3-0
> -ovn-hv4-0
> -ovn-hv5-0
> +[[ovn-hv2-hv1-0
> +ovn-hv2-hv3-0
> +ovn-hv2-hv4-0
> +ovn-hv2-hv5-0
>  ]])
>
>  as hv3
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv2-0
> -ovn-hv4-0
> -ovn-hv5-0
> +[[ovn-hv3-hv1-0
> +ovn-hv3-hv2-0
> +ovn-hv3-hv4-0
> +ovn-hv3-hv5-0
>  ]])
>
>  as hv4
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv2-0
> -ovn-hv3-0
> -ovn-hv5-0
> +[[ovn-hv4-hv1-0
> +ovn-hv4-hv2-0
> +ovn-hv4-hv3-0
> +ovn-hv4-hv5-0
>  ]])
>
>  as hv5
>  AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
> -[[ovn-hv1-0
> -ovn-hv2-0
> -ovn-hv3-0
> -ovn-hv4-0
> +[[ovn-hv5-hv1-0
> +ovn-hv5-hv2-0
> +ovn-hv5-hv3-0
> +ovn-hv5-hv4-0
>  ]])
>
>  OVN_CLEANUP([hv1], [hv2], [hv3])
> @@ -20348,14 +20588,14 @@ check ovn-nbctl fwd-group-del fwd_grp1
>  check ovn-nbctl --wait=hv --liveness fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22
>
>  # Verify openflow group members
> -ofport_lsp21=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv2-0)
> +ofport_lsp21=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv1-hv2-0)
>  tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=lsp21`
>  AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
>      grep "bucket=watch_port:$ofport_lsp21,actions=load:0x"$tunnel_key | wc -l], [0], [dnl
>  1
>  ])
>
> -ofport_lsp22=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv3-0)
> +ofport_lsp22=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv1-hv3-0)
>  tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=lsp22`
>  AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
>      grep "bucket=watch_port:$ofport_lsp22,actions=load:0x"$tunnel_key | wc -l], [0], [dnl
> diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
> index 856f5d2d7..a6fa48201 100644
> --- a/tests/ovs-macros.at
> +++ b/tests/ovs-macros.at
> @@ -55,6 +55,7 @@ ovs_setenv() {
>      OVS_LOGDIR=$ovs_dir; export OVS_LOGDIR
>      OVS_DBDIR=$ovs_dir; export OVS_DBDIR
>      OVS_SYSCONFDIR=$ovs_dir; export OVS_SYSCONFDIR
> +    OVN_SYSCONFDIR=$ovs_dir; export OVN_SYSCONFDIR
>      OVS_PKGDATADIR=$ovs_dir; export OVS_PKGDATADIR
>  }
>
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 29f421685..e2c2617e4 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -5443,6 +5443,7 @@ OVS_APP_EXIT_AND_WAIT([ovn-northd])
>
>  as
>  OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
> +/failed to query port patch-.*/d
>  /.*terminating with signal 15.*/d"])
>  AT_CLEANUP
>
> --
> 2.28.0
>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 47ffc27b8..bb02abbf2 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,11 @@  OVN v20.09.0 - 28 Sep 2020
    - Added support for external ip based NAT. Now, besides the logical ip,
      external ips will also decide if a packet will be NATed or not.
    - Added support for VXLAN encapsulation (not just for ramp/VTEP switches).
+   - Added support for multiple ovn-controller instances on the same host
+     (virtual chassis). Now all external-ids:* configuration options can be
+     customized for each controller instance running on the same host. The only
+     option that is not available per chassis is external-ids:system-id, which
+     stands for the chassis name and can be passed via config file or CLI (-n).
 
 OVN v20.06.0
 --------------------------
diff --git a/controller/chassis.c b/controller/chassis.c
index 7748fb94c..5d87e3301 100644
--- a/controller/chassis.c
+++ b/controller/chassis.c
@@ -125,9 +125,10 @@  chassis_register_ovs_idl(struct ovsdb_idl *ovs_idl)
 }
 
 static const char *
-get_hostname(const struct smap *ext_ids)
+get_hostname(const struct smap *ext_ids, const char *chassis_id)
 {
-    const char *hostname = smap_get_def(ext_ids, "hostname", "");
+    const char *hostname = get_chassis_external_id_value(
+        ext_ids, chassis_id, "hostname", "");
 
     if (strlen(hostname) == 0) {
         static char hostname_[HOST_NAME_MAX + 1];
@@ -143,39 +144,45 @@  get_hostname(const struct smap *ext_ids)
 }
 
 static const char *
-get_bridge_mappings(const struct smap *ext_ids)
+get_bridge_mappings(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_def(ext_ids, "ovn-bridge-mappings", "");
+    return get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-bridge-mappings", "");
 }
 
 const char *
-get_chassis_mac_mappings(const struct smap *ext_ids)
+get_chassis_mac_mappings(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_def(ext_ids, "ovn-chassis-mac-mappings", "");
+    return get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-chassis-mac-mappings", "");
 }
 
 static const char *
-get_cms_options(const struct smap *ext_ids)
+get_cms_options(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_def(ext_ids, "ovn-cms-options", "");
+    return get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-cms-options", "");
 }
 
 static const char *
-get_monitor_all(const struct smap *ext_ids)
+get_monitor_all(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_def(ext_ids, "ovn-monitor-all", "false");
+    return get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-monitor-all", "false");
 }
 
 static const char *
-get_enable_lflow_cache(const struct smap *ext_ids)
+get_enable_lflow_cache(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_def(ext_ids, "ovn-enable-lflow-cache", "true");
+    return get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-enable-lflow-cache", "true");
 }
 
 static const char *
-get_encap_csum(const struct smap *ext_ids)
+get_encap_csum(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_def(ext_ids, "ovn-encap-csum", "true");
+    return get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-encap-csum", "true");
 }
 
 static const char *
@@ -189,9 +196,10 @@  get_datapath_type(const struct ovsrec_bridge *br_int)
 }
 
 static bool
-get_is_interconn(const struct smap *ext_ids)
+get_is_interconn(const struct smap *ext_ids, const char *chassis_id)
 {
-    return smap_get_bool(ext_ids, "ovn-is-interconn", false);
+    return get_chassis_external_id_value_bool(
+        ext_ids, chassis_id, "ovn-is-interconn", false);
 }
 
 static void
@@ -278,22 +286,27 @@  chassis_parse_ovs_config(const struct ovsrec_open_vswitch_table *ovs_table,
         return false;
     }
 
-    const char *encap_type = smap_get(&cfg->external_ids, "ovn-encap-type");
-    const char *encap_ips = smap_get(&cfg->external_ids, "ovn-encap-ip");
+    const char *chassis_id = get_ovs_chassis_id(cfg);
+    const struct smap *ext_ids = &cfg->external_ids;
+
+    const char *encap_type = get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-encap-type", NULL);
+    const char *encap_ips = get_chassis_external_id_value(
+        ext_ids, chassis_id, "ovn-encap-ip", NULL);
     if (!encap_type || !encap_ips) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
         VLOG_INFO_RL(&rl, "Need to specify an encap type and ip");
         return false;
     }
 
-    ovs_cfg->hostname = get_hostname(&cfg->external_ids);
-    ovs_cfg->bridge_mappings = get_bridge_mappings(&cfg->external_ids);
+    ovs_cfg->hostname = get_hostname(ext_ids, chassis_id);
+    ovs_cfg->bridge_mappings = get_bridge_mappings(ext_ids, chassis_id);
     ovs_cfg->datapath_type = get_datapath_type(br_int);
-    ovs_cfg->encap_csum = get_encap_csum(&cfg->external_ids);
-    ovs_cfg->cms_options = get_cms_options(&cfg->external_ids);
-    ovs_cfg->monitor_all = get_monitor_all(&cfg->external_ids);
-    ovs_cfg->chassis_macs = get_chassis_mac_mappings(&cfg->external_ids);
-    ovs_cfg->enable_lflow_cache = get_enable_lflow_cache(&cfg->external_ids);
+    ovs_cfg->encap_csum = get_encap_csum(ext_ids, chassis_id);
+    ovs_cfg->cms_options = get_cms_options(ext_ids, chassis_id);
+    ovs_cfg->monitor_all = get_monitor_all(ext_ids, chassis_id);
+    ovs_cfg->chassis_macs = get_chassis_mac_mappings(ext_ids, chassis_id);
+    ovs_cfg->enable_lflow_cache = get_enable_lflow_cache(ext_ids, chassis_id);
 
     if (!chassis_parse_ovs_encap_type(encap_type, &ovs_cfg->encap_type_set)) {
         return false;
@@ -311,7 +324,7 @@  chassis_parse_ovs_config(const struct ovsrec_open_vswitch_table *ovs_table,
         sset_destroy(&ovs_cfg->encap_ip_set);
     }
 
-    ovs_cfg->is_interconn = get_is_interconn(&cfg->external_ids);
+    ovs_cfg->is_interconn = get_is_interconn(ext_ids, chassis_id);
 
     return true;
 }
@@ -348,7 +361,7 @@  chassis_other_config_changed(const char *bridge_mappings,
                              const struct sbrec_chassis *chassis_rec)
 {
     const char *chassis_bridge_mappings =
-        get_bridge_mappings(&chassis_rec->other_config);
+        get_bridge_mappings(&chassis_rec->other_config, NULL);
 
     if (strcmp(bridge_mappings, chassis_bridge_mappings)) {
         return true;
@@ -362,28 +375,28 @@  chassis_other_config_changed(const char *bridge_mappings,
     }
 
     const char *chassis_cms_options =
-        get_cms_options(&chassis_rec->other_config);
+        get_cms_options(&chassis_rec->other_config, NULL);
 
     if (strcmp(cms_options, chassis_cms_options)) {
         return true;
     }
 
     const char *chassis_monitor_all =
-        get_monitor_all(&chassis_rec->other_config);
+        get_monitor_all(&chassis_rec->other_config, NULL);
 
     if (strcmp(monitor_all, chassis_monitor_all)) {
         return true;
     }
 
     const char *chassis_enable_lflow_cache =
-        get_enable_lflow_cache(&chassis_rec->other_config);
+        get_enable_lflow_cache(&chassis_rec->other_config, NULL);
 
     if (strcmp(enable_lflow_cache, chassis_enable_lflow_cache)) {
         return true;
     }
 
     const char *chassis_mac_mappings =
-        get_chassis_mac_mappings(&chassis_rec->other_config);
+        get_chassis_mac_mappings(&chassis_rec->other_config, NULL);
     if (strcmp(chassis_macs, chassis_mac_mappings)) {
         return true;
     }
@@ -802,7 +815,7 @@  chassis_get_mac(const struct sbrec_chassis *chassis_rec,
                 struct eth_addr *chassis_mac)
 {
     const char *tokens
-        = get_chassis_mac_mappings(&chassis_rec->other_config);
+        = get_chassis_mac_mappings(&chassis_rec->other_config, NULL);
     if (!tokens[0]) {
        return false;
     }
diff --git a/controller/chassis.h b/controller/chassis.h
index 220f726b9..c7345f0fa 100644
--- a/controller/chassis.h
+++ b/controller/chassis.h
@@ -49,7 +49,8 @@  bool chassis_get_mac(const struct sbrec_chassis *chassis,
                      const char *bridge_mapping,
                      struct eth_addr *chassis_mac);
 const char *chassis_get_id(void);
-const char * get_chassis_mac_mappings(const struct smap *ext_ids);
+const char * get_chassis_mac_mappings(const struct smap *ext_ids,
+                                      const char *chassis_id);
 
 
 #endif /* controller/chassis.h */
diff --git a/controller/encaps.c b/controller/encaps.c
index 7eac4bb06..bf5c39f5f 100644
--- a/controller/encaps.c
+++ b/controller/encaps.c
@@ -67,13 +67,14 @@  struct chassis_node {
 };
 
 static char *
-tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id)
+tunnel_create_name(struct tunnel_ctx *tc, const char *this_chassis_id,
+                   const char *chassis_id)
 {
     int i;
 
     for (i = 0; i < UINT16_MAX; i++) {
         char *port_name;
-        port_name = xasprintf("ovn-%.6s-%x", chassis_id, i);
+        port_name = xasprintf("ovn-%s-%s-%x", this_chassis_id, chassis_id, i);
 
         if (!sset_contains(&tc->port_names, port_name)) {
             return port_name;
@@ -151,7 +152,8 @@  encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id,
 
 static void
 tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
-           const char *new_chassis_id, const struct sbrec_encap *encap)
+           const char *this_chassis_id, const char *new_chassis_id,
+           const struct sbrec_encap *encap)
 {
     struct smap options = SMAP_INITIALIZER(&options);
     smap_add(&options, "remote_ip", encap->ip);
@@ -198,7 +200,8 @@  tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
      * its name, otherwise generate a new, unique name. */
     char *port_name = (chassis
                        ? xstrdup(chassis->port->name)
-                       : tunnel_create_name(tc, new_chassis_id));
+                       : tunnel_create_name(tc, this_chassis_id,
+                                            new_chassis_id));
     if (!port_name) {
         VLOG_WARN("Unable to allocate unique name for '%s' tunnel",
                   new_chassis_id);
@@ -247,7 +250,9 @@  preferred_encap(const struct sbrec_chassis *chassis_rec)
  * as there are VTEP of that type (differentiated by remote_ip) on that chassis.
  */
 static int
-chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_sb_global *sbg, struct tunnel_ctx *tc)
+chassis_tunnel_add(const struct sbrec_chassis *chassis_rec,
+                   const char *this_chassis_id,
+                   const struct sbrec_sb_global *sbg, struct tunnel_ctx *tc)
 {
     struct sbrec_encap *encap = preferred_encap(chassis_rec);
     int tuncnt = 0;
@@ -263,7 +268,8 @@  chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_s
         if (tun_type != pref_type) {
             continue;
         }
-        tunnel_add(tc, sbg, chassis_rec->name, chassis_rec->encaps[i]);
+        tunnel_add(tc, sbg, this_chassis_id, chassis_rec->name,
+                   chassis_rec->encaps[i]);
         tuncnt++;
     }
     return tuncnt;
@@ -291,9 +297,31 @@  chassis_tzones_overlap(const struct sset *transport_zones,
     return false;
 }
 
+static bool
+is_tunnel_type(const char *port_type)
+{
+    static const char *tunnel_types[3] = { "geneve", "vxlan", "stt" };
+    for (size_t t = 0; t < 3; t++) {
+        if (!strcmp(port_type, tunnel_types[t])) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+is_tunnel_port(const struct ovsrec_port *port)
+{
+    for (size_t i = 0; i < port->n_interfaces; i++) {
+        if (is_tunnel_type(port->interfaces[i]->type)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 void
 encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
-           const struct ovsrec_bridge_table *bridge_table,
            const struct ovsrec_bridge *br_int,
            const struct sbrec_chassis_table *chassis_table,
            const struct sbrec_chassis *this_chassis,
@@ -305,7 +333,6 @@  encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
     }
 
     const struct sbrec_chassis *chassis_rec;
-    const struct ovsrec_bridge *br;
 
     struct tunnel_ctx tc = {
         .chassis = SHASH_INITIALIZER(&tc.chassis),
@@ -320,28 +347,29 @@  encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
 
     /* Collect all port names into tc.port_names.
      *
-     * Collect all the OVN-created tunnels into tc.tunnel_hmap. */
-    OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
-        for (size_t i = 0; i < br->n_ports; i++) {
-            const struct ovsrec_port *port = br->ports[i];
-            sset_add(&tc.port_names, port->name);
-
-            /*
-             * note that the id here is not just the chassis name, but the
-             * combination of <chassis_name><delim><encap_ip>
-             */
-            const char *id = smap_get(&port->external_ids, "ovn-chassis-id");
-            if (id) {
-                if (!shash_find(&tc.chassis, id)) {
-                    struct chassis_node *chassis = xzalloc(sizeof *chassis);
-                    chassis->bridge = br;
-                    chassis->port = port;
-                    shash_add_assert(&tc.chassis, id, chassis);
-                } else {
-                    /* Duplicate port for ovn-chassis-id.  Arbitrarily choose
-                     * to delete this one. */
-                    ovsrec_bridge_update_ports_delvalue(br, port);
-                }
+     * Collect all OVN-created tunnels of the bridge into tc.tunnel_hmap. */
+    for (size_t i = 0; i < br_int->n_ports; i++) {
+        const struct ovsrec_port *port = br_int->ports[i];
+        if (!is_tunnel_port(port)) {
+            continue;
+        }
+        sset_add(&tc.port_names, port->name);
+
+        /*
+         * note that the id here is not just the chassis name, but the
+         * combination of <chassis_name><delim><encap_ip>
+         */
+        const char *id = smap_get(&port->external_ids, "ovn-chassis-id");
+        if (id) {
+            if (!shash_find(&tc.chassis, id)) {
+                struct chassis_node *chassis = xzalloc(sizeof *chassis);
+                chassis->bridge = br_int;
+                chassis->port = port;
+                shash_add_assert(&tc.chassis, id, chassis);
+            } else {
+                /* Duplicate port for ovn-chassis-id.  Arbitrarily choose
+                 * to delete this one. */
+                ovsrec_bridge_update_ports_delvalue(br_int, port);
             }
         }
     }
@@ -366,7 +394,8 @@  encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
                 continue;
             }
 
-            if (chassis_tunnel_add(chassis_rec, sbg, &tc) == 0) {
+            if (chassis_tunnel_add(chassis_rec, this_chassis->name,
+                                   sbg, &tc) == 0) {
                 VLOG_INFO("Creating encap for '%s' failed", chassis_rec->name);
                 continue;
             }
@@ -381,6 +410,7 @@  encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
         shash_delete(&tc.chassis, node);
         free(chassis);
     }
+
     shash_destroy(&tc.chassis);
     sset_destroy(&tc.port_names);
 }
@@ -400,6 +430,9 @@  encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
         = xmalloc(sizeof *br_int->ports * br_int->n_ports);
     size_t n = 0;
     for (size_t i = 0; i < br_int->n_ports; i++) {
+        if (!is_tunnel_port(br_int->ports[i])) {
+            continue;
+        }
         if (!smap_get(&br_int->ports[i]->external_ids, "ovn-chassis-id")) {
             ports[n++] = br_int->ports[i];
         }
diff --git a/controller/encaps.h b/controller/encaps.h
index f488393c4..aff85097f 100644
--- a/controller/encaps.h
+++ b/controller/encaps.h
@@ -30,7 +30,6 @@  struct sset;
 
 void encaps_register_ovs_idl(struct ovsdb_idl *);
 void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
-                const struct ovsrec_bridge_table *,
                 const struct ovsrec_bridge *br_int,
                 const struct sbrec_chassis_table *,
                 const struct sbrec_chassis *,
diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
index 16bc47b20..c181a0fa6 100644
--- a/controller/ovn-controller.8.xml
+++ b/controller/ovn-controller.8.xml
@@ -235,6 +235,19 @@ 
       </dd>
     </dl>
 
+    <p>
+      Note that every <code>external_ids:*</code> key listed above has its
+      <code>external_ids:*-chassis_name</code> counterpart keys that allow to
+      configure values specific to chassis running on the same OVSDB. For
+      example, if two chassis named <code>blue</code> and <code>red</code> are
+      available on the same host, then an admin may configure different
+      <code>ovn-cms-options</code> for each of them by setting
+      <code>external_ids:ovn-cms-options-blue</code> and
+      <code>external_ids:ovn-cms-options-red</code> keys in the database. The
+      only key that is not available for per-chassis configuration is
+      <code>external_ids:system-id</code>.
+    </p>
+
     <p>
       <code>ovn-controller</code> reads the following values from the
       <code>Open_vSwitch</code> database of the local OVS instance:
@@ -286,7 +299,9 @@ 
         The presence of this key identifies a tunnel port within the
         integration bridge as one created by <code>ovn-controller</code> to
         reach a remote chassis.  Its value is the chassis ID of the remote
-        chassis.
+        chassis. Alternatively, for patch ports, the key identifies the name of
+        the chassis that owns it, in case of multiple virtual chassis running
+        on the same host.
       </dd>
 
       <dt>
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index a06cae3cc..240ca3cc6 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -18,10 +18,14 @@ 
 #include "ovn-controller.h"
 
 #include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #include "bfd.h"
 #include "binding.h"
@@ -46,6 +50,7 @@ 
 #include "lib/extend-table.h"
 #include "lib/ip-mcast-index.h"
 #include "lib/mcast-group-index.h"
+#include "lib/ovn-dirs.h"
 #include "lib/ovn-sb-idl.h"
 #include "lib/ovn-util.h"
 #include "patch.h"
@@ -85,6 +90,12 @@  static unixctl_cb_func debug_delay_nb_cfg_report;
 
 #define CONTROLLER_LOOP_STOPWATCH_NAME "ovn-controller-flow-generation"
 
+/* These variables never change after initialization and can be safely used in
+ * I-P engine. If later we decide to allow to dynamically change them, I-P
+ * machinery will need some adjustments. */
+static char *controller_chassis = NULL;
+static char *system_id_override = NULL;
+
 static char *parse_options(int argc, char *argv[]);
 OVS_NO_RETURN static void usage(void);
 
@@ -260,7 +271,9 @@  out:
 static const char *
 br_int_name(const struct ovsrec_open_vswitch *cfg)
 {
-    return smap_get_def(&cfg->external_ids, "ovn-bridge", DEFAULT_BRIDGE_NAME);
+    return get_chassis_external_id_value(
+        &cfg->external_ids, get_ovs_chassis_id(cfg),
+        "ovn-bridge", DEFAULT_BRIDGE_NAME);
 }
 
 static const struct ovsrec_bridge *
@@ -361,8 +374,9 @@  process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
         const struct ovsrec_open_vswitch *cfg;
         cfg = ovsrec_open_vswitch_table_first(ovs_table);
         ovs_assert(cfg);
-        const char *datapath_type = smap_get(&cfg->external_ids,
-                                             "ovn-bridge-datapath-type");
+        const char *datapath_type = get_chassis_external_id_value(
+            &cfg->external_ids, get_ovs_chassis_id(cfg),
+            "ovn-bridge-datapath-type", NULL);
         /* Check for the datapath_type and set it only if it is defined in
          * cfg. */
         if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) {
@@ -372,17 +386,46 @@  process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
     return br_int;
 }
 
-static const char *
-get_ovs_chassis_id(const struct ovsrec_open_vswitch_table *ovs_table)
+static char *get_file_system_id_override(void)
 {
-    const struct ovsrec_open_vswitch *cfg
-        = ovsrec_open_vswitch_table_first(ovs_table);
+    char *ret = NULL;
+    char *filename = xasprintf("%s/system-id-override", ovn_sysconfdir());
+    errno = 0;
+    int fd = open(filename, O_RDONLY);
+    if (fd != -1) {
+        char file_system_id[64];
+        int nread = read(fd, file_system_id, sizeof file_system_id);
+        if (nread) {
+            file_system_id[nread] = '\0';
+            if (file_system_id[nread - 1] == '\n') {
+                file_system_id[nread - 1] = '\0';
+            }
+            ret = xstrdup(file_system_id);
+        }
+        close(fd);
+    }
+
+    free(filename);
+    return ret;
+}
+
+const char *
+get_ovs_chassis_id(const struct ovsrec_open_vswitch *cfg)
+{
+    if (controller_chassis) {
+        return controller_chassis;
+    }
+
+    if (system_id_override) {
+        return system_id_override;
+    }
+
     const char *chassis_id = cfg ? smap_get(&cfg->external_ids, "system-id")
                                  : NULL;
-
     if (!chassis_id) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "'system-id' in Open_vSwitch database is missing.");
+        VLOG_WARN_RL(&rl, "Failed to detect system-id, "
+                          "configuration not found.");
     }
 
     return chassis_id;
@@ -477,10 +520,12 @@  static int
 get_ofctrl_probe_interval(struct ovsdb_idl *ovs_idl)
 {
     const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
-    return !cfg ? OFCTRL_DEFAULT_PROBE_INTERVAL_SEC :
-                  smap_get_int(&cfg->external_ids,
-                               "ovn-openflow-probe-interval",
-                               OFCTRL_DEFAULT_PROBE_INTERVAL_SEC);
+    if (!cfg) {
+        return OFCTRL_DEFAULT_PROBE_INTERVAL_SEC;
+    }
+    return get_chassis_external_id_value_int(
+        &cfg->external_ids, get_ovs_chassis_id(cfg),
+        "ovn-openflow-probe-interval", OFCTRL_DEFAULT_PROBE_INTERVAL_SEC);
 }
 
 /* Retrieves the pointer to the OVN Southbound database from 'ovs_idl' and
@@ -496,18 +541,21 @@  update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl,
     }
 
     /* Set remote based on user configuration. */
-    const char *remote = smap_get(&cfg->external_ids, "ovn-remote");
+    const char *chassis_id = get_ovs_chassis_id(cfg);
+    const char *remote = get_chassis_external_id_value(
+        &cfg->external_ids, chassis_id, "ovn-remote", NULL);
     ovsdb_idl_set_remote(ovnsb_idl, remote, true);
 
     /* Set probe interval, based on user configuration and the remote. */
     int default_interval = (remote && !stream_or_pstream_needs_probes(remote)
                             ? 0 : DEFAULT_PROBE_INTERVAL_MSEC);
-    int interval = smap_get_int(&cfg->external_ids,
-                                "ovn-remote-probe-interval", default_interval);
+    int interval = get_chassis_external_id_value_int(
+        &cfg->external_ids, chassis_id, "ovn-remote-probe-interval",
+        default_interval);
     ovsdb_idl_set_probe_interval(ovnsb_idl, interval);
 
-    bool monitor_all = smap_get_bool(&cfg->external_ids, "ovn-monitor-all",
-                                     false);
+    bool monitor_all = get_chassis_external_id_value_bool(
+        &cfg->external_ids, chassis_id, "ovn-monitor-all", false);
     if (monitor_all) {
         /* Always call update_sb_monitors when monitor_all is true.
          * Otherwise, don't call it here, because there would be unnecessary
@@ -1166,7 +1214,9 @@  init_binding_ctx(struct engine_node *node,
     struct ovsrec_bridge_table *bridge_table =
         (struct ovsrec_bridge_table *)EN_OVSDB_GET(
             engine_get_input("OVS_bridge", node));
-    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    const struct ovsrec_open_vswitch *cfg =
+        ovsrec_open_vswitch_table_first(ovs_table);
+    const char *chassis_id = get_ovs_chassis_id(cfg);
     const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
 
     ovs_assert(br_int && chassis_id);
@@ -2432,6 +2482,10 @@  main(int argc, char *argv[])
     exiting = false;
     restart = false;
     bool sb_monitor_all = false;
+
+    /* Read from system-id-override file once on startup. */
+    system_id_override = get_file_system_id_override();
+
     while (!exiting) {
         /* If we're paused just run the unixctl server and skip most of the
          * processing loop.
@@ -2498,7 +2552,9 @@  main(int argc, char *argv[])
                 sbrec_chassis_private_table_get(ovnsb_idl_loop.idl);
             const struct ovsrec_bridge *br_int =
                 process_br_int(ovs_idl_txn, bridge_table, ovs_table);
-            const char *chassis_id = get_ovs_chassis_id(ovs_table);
+            const struct ovsrec_open_vswitch *cfg =
+                ovsrec_open_vswitch_table_first(ovs_table);
+            const char *chassis_id = get_ovs_chassis_id(cfg);
             const struct sbrec_chassis *chassis = NULL;
             const struct sbrec_chassis_private *chassis_private = NULL;
             if (chassis_id) {
@@ -2518,7 +2574,7 @@  main(int argc, char *argv[])
 
                 if (chassis) {
                     encaps_run(ovs_idl_txn,
-                               bridge_table, br_int,
+                               br_int,
                                sbrec_chassis_table_get(ovnsb_idl_loop.idl),
                                chassis,
                                sbrec_sb_global_first(ovnsb_idl_loop.idl),
@@ -2779,6 +2835,8 @@  loop_done:
     ovsdb_idl_loop_destroy(&ovs_idl_loop);
     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
 
+    free(controller_chassis);
+    free(system_id_override);
     free(ovs_remote);
     service_stop();
 
@@ -2804,6 +2862,7 @@  parse_options(int argc, char *argv[])
         STREAM_SSL_LONG_OPTIONS,
         {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
         {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
+        {"chassis", required_argument, NULL, 'n'},
         {NULL, 0, NULL, 0}
     };
     char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
@@ -2836,6 +2895,10 @@  parse_options(int argc, char *argv[])
             stream_ssl_set_ca_cert_file(optarg, true);
             break;
 
+        case 'n':
+            controller_chassis = xstrdup(optarg);
+            break;
+
         case '?':
             exit(EXIT_FAILURE);
 
@@ -2871,6 +2934,7 @@  usage(void)
     daemon_usage();
     vlog_usage();
     printf("\nOther options:\n"
+           "  -n                      custom chassis name\n"
            "  -h, --help              display this help message\n"
            "  -V, --version           display version information\n");
     exit(EXIT_SUCCESS);
diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h
index 5d9466880..9994dd777 100644
--- a/controller/ovn-controller.h
+++ b/controller/ovn-controller.h
@@ -21,6 +21,7 @@ 
 #include "lib/ovn-sb-idl.h"
 
 struct ovsrec_bridge_table;
+struct ovsrec_open_vswitch;
 
 /* Linux supports a maximum of 64K zones, which seems like a fine default. */
 #define MAX_CT_ZONES 65535
@@ -87,4 +88,7 @@  enum chassis_tunnel_type {
 
 uint32_t get_tunnel_type(const char *name);
 
+const char *get_ovs_chassis_id(const struct ovsrec_open_vswitch *cfg);
+bool is_concurrent_chassis(const struct ovsrec_open_vswitch *cfg);
+
 #endif /* controller/ovn-controller.h */
diff --git a/controller/patch.c b/controller/patch.c
index a2a7bcd79..3b3df278c 100644
--- a/controller/patch.c
+++ b/controller/patch.c
@@ -76,6 +76,7 @@  create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
                   const char *key, const char *value,
                   const struct ovsrec_bridge *src, const char *src_name,
                   const struct ovsrec_bridge *dst, const char *dst_name,
+                  const char *chassis_name,
                   struct shash *existing_ports)
 {
     for (size_t i = 0; i < src->n_ports; i++) {
@@ -101,7 +102,8 @@  create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
     port = ovsrec_port_insert(ovs_idl_txn);
     ovsrec_port_set_name(port, src_name);
     ovsrec_port_set_interfaces(port, &iface, 1);
-    const struct smap ids = SMAP_CONST1(&ids, key, value);
+    const struct smap ids = SMAP_CONST2(&ids, key, value,
+                                        "ovn-chassis-id", chassis_name);
     ovsrec_port_set_external_ids(port, &ids);
 
     struct ovsrec_port **ports;
@@ -157,7 +159,9 @@  add_ovs_bridge_mappings(const struct ovsrec_open_vswitch_table *ovs_table,
         const char *mappings_cfg;
         char *cur, *next, *start;
 
-        mappings_cfg = smap_get(&cfg->external_ids, "ovn-bridge-mappings");
+        mappings_cfg = get_chassis_external_id_value(
+            &cfg->external_ids, get_ovs_chassis_id(cfg),
+            "ovn-bridge-mappings", NULL);
         if (!mappings_cfg || !mappings_cfg[0]) {
             return;
         }
@@ -269,9 +273,11 @@  add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
         char *name1 = patch_port_name(br_int->name, binding->logical_port);
         char *name2 = patch_port_name(binding->logical_port, br_int->name);
         create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
-                          br_int, name1, br_ln, name2, existing_ports);
+                          br_int, name1, br_ln, name2, chassis->name,
+                          existing_ports);
         create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
-                          br_ln, name2, br_int, name1, existing_ports);
+                          br_ln, name2, br_int, name1, chassis->name,
+                          existing_ports);
         free(name1);
         free(name2);
     }
@@ -323,6 +329,12 @@  patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
     SHASH_FOR_EACH_SAFE (port_node, port_next_node, &existing_ports) {
         port = port_node->data;
         shash_delete(&existing_ports, port_node);
+
+        const char *port_chassis = smap_get(&port->external_ids,
+                                            "ovn-chassis-id");
+        if (port_chassis && strcmp(port_chassis, chassis->name)) {
+            continue;
+        }
         remove_port(bridge_table, port);
     }
     shash_destroy(&existing_ports);
diff --git a/controller/physical.c b/controller/physical.c
index 1bc2c389b..819d97ec0 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -432,7 +432,7 @@  populate_remote_chassis_macs(const struct sbrec_chassis *my_chassis,
         }
 
         const char *tokens
-            = get_chassis_mac_mappings(&chassis->other_config);
+            = get_chassis_mac_mappings(&chassis->other_config, NULL);
 
         if (!strlen(tokens)) {
             continue;
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index abe6b04a7..f0dde27f0 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -722,3 +722,53 @@  ip_address_and_port_from_lb_key(const char *key, char **ip_address,
     *addr_family = ss.ss_family;
     return true;
 }
+
+const char *
+get_chassis_external_id_value(const struct smap *external_ids,
+                              const char *chassis_id, const char *option_key,
+                              const char *def)
+{
+    const char *option_value = NULL;
+    if (chassis_id != NULL) {
+        char *chassis_option_key = xasprintf("%s-%s", option_key, chassis_id);
+        option_value = smap_get(external_ids, chassis_option_key);
+        free(chassis_option_key);
+    }
+    if (!option_value) {
+        option_value = smap_get_def(external_ids, option_key, def);
+    }
+    return option_value;
+}
+
+int
+get_chassis_external_id_value_int(const struct smap *external_ids,
+                                  const char *chassis_id,
+                                  const char *option_key,
+                                  int def)
+{
+    const char *value = get_chassis_external_id_value(
+        external_ids, chassis_id, option_key, NULL);
+
+    int i_value;
+    if (!value || !str_to_int(value, 10, &i_value)) {
+        return def;
+    }
+
+    return i_value;
+}
+
+bool
+get_chassis_external_id_value_bool(const struct smap *external_ids,
+                                   const char *chassis_id,
+                                   const char *option_key,
+                                   bool def)
+{
+    const char *value = get_chassis_external_id_value(
+        external_ids, chassis_id, option_key, "");
+
+    if (def) {
+        return strcasecmp("false", value) != 0;
+    } else {
+        return !strcasecmp("true", value);
+    }
+}
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index a39cbef5a..2228770aa 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -18,6 +18,7 @@ 
 
 #include "lib/packets.h"
 #include "include/ovn/version.h"
+#include "smap.h"
 
 #define ovn_set_program_name(name) \
     ovs_set_program_name(name, OVN_PACKAGE_VERSION)
@@ -164,6 +165,23 @@  char *normalize_v46_prefix(const struct v46_ip *prefix, unsigned int plen);
 unsigned int ovn_smap_get_uint(const struct smap *smap, const char *key,
                                unsigned int def);
 
+const char *
+get_chassis_external_id_value(const struct smap *external_ids,
+                              const char *chassis_id, const char *option_key,
+                              const char *def);
+
+int
+get_chassis_external_id_value_int(const struct smap *external_ids,
+                                  const char *chassis_id,
+                                  const char *option_key,
+                                  int def);
+
+bool
+get_chassis_external_id_value_bool(const struct smap *external_ids,
+                                   const char *chassis_id,
+                                   const char *option_key,
+                                   bool def);
+
 /* Returns a lowercase copy of orig.
  * Caller must free the returned string.
  */
diff --git a/ovn-sb.xml b/ovn-sb.xml
index b1480f218..4261270b6 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -240,10 +240,12 @@ 
 
     <column name="name">
       OVN does not prescribe a particular format for chassis names.
-      ovn-controller populates this column using <ref key="system-id"
-      table="Open_vSwitch" column="external_ids" db="Open_vSwitch"/>
-      in the Open_vSwitch database's <ref table="Open_vSwitch"
-      db="Open_vSwitch"/> table.  ovn-controller-vtep populates this
+      ovn-controller populates this column using the <code>-n</code>
+      CLI argument, or <code>system-id-override</code> configuration file, or
+      <ref key="system-id" table="Open_vSwitch" column="external_ids"
+      db="Open_vSwitch"/> in the Open_vSwitch database's
+      <ref table="Open_vSwitch" db="Open_vSwitch"/> table.
+      ovn-controller-vtep populates this
       column with <ref table="Physical_Switch" column="name"
       db="hardware_vtep"/> in the hardware_vtep database's
       <ref table="Physical_Switch" db="hardware_vtep"/> table.
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index 014a97760..740bfa239 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -50,8 +50,7 @@  patch
 # is mirrored into the Chassis record in the OVN_Southbound db.
 check_bridge_mappings () {
     local_mappings=$1
-    sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
-    OVS_WAIT_UNTIL([test x"${local_mappings}" = x$(ovn-sbctl get Chassis ${sysid} other_config:ovn-bridge-mappings | sed -e 's/\"//g')])
+    OVS_WAIT_UNTIL([test x"${local_mappings}" = x$(ovn-sbctl get Chassis ${sandbox} other_config:ovn-bridge-mappings | sed -e 's/\"//g')])
 }
 
 # Initially there should be no patch ports.
@@ -133,13 +132,13 @@  ovs-vsctl \
     -- add-br br-eth2
 ovn_attach n1 br-phys 192.168.0.1
 
-sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
+sysid=${sandbox}
 
 # Make sure that the datapath_type set in the Bridge table
 # is mirrored into the Chassis record in the OVN_Southbound db.
 check_datapath_type () {
     datapath_type=$1
-    chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} other_config:datapath-type | sed -e 's/"//g') #"
+    chassis_datapath_type=$(ovn-sbctl get Chassis ${sandbox} other_config:datapath-type | sed -e 's/"//g') #"
     test "${datapath_type}" = "${chassis_datapath_type}"
 }
 
@@ -187,7 +186,7 @@  OVS_WAIT_UNTIL([
     test "${expected_iface_types}" = "${chassis_iface_types}"
 ])
 
-# Change the value of external_ids:system-id and make sure it's mirrored
+# Set the value of external_ids:system-id and make sure it's mirrored
 # in the Chassis record in the OVN_Southbound database.
 sysid=${sysid}-foo
 ovs-vsctl set Open_vSwitch . external-ids:system-id="${sysid}"
@@ -252,7 +251,7 @@  ovs-vsctl \
 ovn_attach n1 br-phys 192.168.0.1
 
 check_tunnel_property () {
-    test "`ovs-vsctl get interface ovn-fakech-0 $1`" = "$2"
+    test "`ovs-vsctl get interface ovn-hv-fakechassis-0 $1`" = "$2"
 }
 
 # Start off with a remote chassis supporting STT
@@ -265,7 +264,7 @@  OVS_WAIT_UNTIL([check_tunnel_property type stt])
 # the chassis-id in ovn-chassis-id); if we supply a different IP here
 # we won't be able to co-relate this to the tunnel port that was created
 # in the previous step and, as a result, will end up creating another tunnel,
-# ie. we can't just lookup using "ovn-fakech-0". So, need to use the same IP
+# ie. we can't just lookup using "ovn-fakechassis-0". So, need to use the same IP
 # as above, i.e 192.168.0.2, here.
 encap_uuid=$(ovn-sbctl add chassis fakechassis encaps @encap -- --id=@encap create encap type=geneve ip="192.168.0.2")
 OVS_WAIT_UNTIL([check_tunnel_property type geneve])
@@ -275,11 +274,11 @@  ovn-sbctl set encap ${encap_uuid} ip=192.168.0.2
 OVS_WAIT_UNTIL([check_tunnel_property options:remote_ip "\"192.168.0.2\""])
 
 # Change the type on the OVS side and check than OVN fixes it
-ovs-vsctl set interface ovn-fakech-0 type=vxlan
+ovs-vsctl set interface ovn-hv-fakechassis-0 type=vxlan
 OVS_WAIT_UNTIL([check_tunnel_property type geneve])
 
 # Delete the port entirely and it should be resurrected
-ovs-vsctl del-port ovn-fakech-0
+ovs-vsctl del-port ovn-hv-fakechassis-0
 OVS_WAIT_UNTIL([check_tunnel_property type geneve])
 
 # Gracefully terminate daemons
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 5b9c2dee6..5072e4e7d 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -227,7 +227,7 @@  net_attach () {
 
 # ovn_az_attach AZ NETWORK BRIDGE IP [MASKLEN]
 ovn_az_attach() {
-    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan}
+    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan} intbr=${7-br-int} chassis=$8
     net_attach $net $bridge || return 1
 
     mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g`
@@ -241,15 +241,48 @@  ovn_az_attach() {
     else
         ovn_remote=unix:$ovs_base/$az/ovn-sb/ovn-sb.sock
     fi
+
+    if [[ -n "${chassis}" ]]; then
+        bridge_key=ovn-bridge-${chassis}
+        remote_key=ovn-remote-${chassis}
+        encap_type_key=ovn-encap-type-${chassis}
+        encap_ip_key=ovn-encap-ip-${chassis}
+        chassis_args="-n $chassis"
+        chassis_vsctl_args=
+    else
+        bridge_key=ovn-bridge
+        remote_key=ovn-remote
+        encap_type_key=ovn-encap-type
+        encap_ip_key=ovn-encap-ip
+        chassis=$sandbox
+        chassis_args=
+        chassis_vsctl_args="-- set Open_vSwitch . external-ids:system-id=$chassis"
+    fi
+
     ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=$sandbox \
-        -- set Open_vSwitch . external-ids:ovn-remote=$ovn_remote \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=$encap \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip \
-        -- --may-exist add-br br-int \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
+        $chassis_vsctl_args \
+        -- set Open_vSwitch . external-ids:$bridge_key=$intbr \
+        -- set Open_vSwitch . external-ids:$remote_key=$ovn_remote \
+        -- set Open_vSwitch . external-ids:$encap_type_key=$encap \
+        -- set Open_vSwitch . external-ids:$encap_ip_key=$ip \
+        -- --may-exist add-br ${intbr} \
+        -- set bridge ${intbr} fail-mode=secure other-config:disable-in-band=true \
         || return 1
-    start_daemon ovn-controller || return 1
+
+    if [[ "${intbr}" = br-int ]]; then
+        pidfile="${OVS_RUNDIR}/ovn-controller.pid"
+        logfile="${OVS_LOGDIR}/ovn-controller.log"
+    else
+        pidfile="${OVS_RUNDIR}/ovn-controller-${intbr}.pid"
+        logfile="${OVS_LOGDIR}/ovn-controller-${chassis}.log"
+    fi
+
+    ovn-controller \
+        ${chassis_args} \
+        -vconsole:off --detach --no-chdir \
+        --pidfile=${pidfile} \
+        --log-file=${logfile} || return 1
+    on_exit "test -e \"$pidfile\" && kill \`cat \"$pidfile\"\`"
 }
 
 # ovn_attach NETWORK BRIDGE IP [MASKLEN]
diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
index 6cc5b2174..3e46309dd 100644
--- a/tests/ovn-performance.at
+++ b/tests/ovn-performance.at
@@ -255,12 +255,12 @@  done
 # Otherwise this may affect the lflow_run count.
 
 OVS_WAIT_UNTIL([
-    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
+    test $(as hv1 ovs-vsctl list interface ovn-hv1-hv2-0 | \
 grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
 ])
 
 OVS_WAIT_UNTIL([
-    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
+    test $(as hv2 ovs-vsctl list interface ovn-hv2-hv1-0 | \
 grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
 ])
 
diff --git a/tests/ovn.at b/tests/ovn.at
index f154e3d77..b2971dce0 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1735,6 +1735,180 @@  AT_CLEANUP
 
 AT_BANNER([OVN end-to-end tests])
 
+AT_SETUP([ovn -- cross-connectivity between two virtual chassis])
+AT_KEYWORDS([ovn])
+ovn_start
+ovn-nbctl ls-add lsw0
+net_add n1
+sim_add hv
+
+as hv
+
+ovn-nbctl lsp-add lsw0 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=n1
+
+for i in 1 2; do
+    chassis=host-$i
+    ovs-vsctl add-br br-phys-$i
+    ovn_attach n1 br-phys-$i 192.168.$i.1 24 geneve br-int-$i $chassis
+
+    AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings-$chassis=n1:br-phys-$i])
+
+    lpname=lp$i
+    ovn-nbctl lsp-add lsw0 $lpname
+    ovn-nbctl lsp-set-addresses $lpname "f0:00:00:00:00:0$i"
+    ovn-nbctl --wait=hv --timeout=3 lsp-set-options $lpname requested-chassis=$chassis
+    ovs-vsctl add-port br-int-$i vif$i -- set Interface vif$i external-ids:iface-id=$lpname \
+                                  options:tx_pcap=$i-tx.pcap \
+                                  options:rxq_pcap=$i-rx.pcap \
+                                  ofport-request=$i
+    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lpname` = xup])
+
+    pb_chassis_id=$(ovn-sbctl --bare --columns chassis list port_binding $lpname)
+    pb_chassis_name=$(ovn-sbctl get chassis $pb_chassis_id name)
+    AT_FAIL_IF([test x$pb_chassis_name != x$chassis])
+done
+
+test_packet() {
+    local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6
+
+    # First try tracing the packet.
+    uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth"
+    echo "output(\"$lout\");" > expout
+    AT_CAPTURE_FILE([trace])
+    AT_CHECK([ovn-trace --all lsw0 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
+
+    # Then actually send a packet, for an end-to-end test.
+    local packet=$(echo $dst$src | sed 's/://g')${eth}
+    as hv ovs-appctl netdev-dummy/receive vif$inport $packet
+    echo $packet >> ${eout#lp}.expected
+}
+
+test_packet 1 f0:00:00:00:00:02 f0:00:00:00:00:01 1234 lp2 lp2
+test_packet 2 f0:00:00:00:00:01 f0:00:00:00:00:02 1234 lp1 lp1
+
+for i in 1 2; do
+    OVN_CHECK_PACKETS_REMOVE_BROADCAST([$i-tx.pcap], [$i.expected])
+done
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- 3 virtual hosts, same node])
+AT_KEYWORDS([ovn])
+ovn_start
+ovn-nbctl ls-add lsw0
+net_add n1
+sim_add hv
+
+as hv
+for i in 1 2 3; do
+    chassis=host-$i
+    ovs-vsctl add-br br-phys-$i
+    ovn_attach n1 br-phys-$i 192.168.0.$i 24 geneve br-int-$i $chassis
+
+    for j in 1 2 3; do
+        lpname=lp$i$j
+        ovn-nbctl lsp-add lsw0 $lpname
+        ovn-nbctl --wait=hv --timeout=3 lsp-set-options $lpname requested-chassis=$chassis
+        ovs-vsctl add-port br-int-$i vif$i$j -- set Interface vif$i$j external-ids:iface-id=$lpname
+        OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lpname` = xup])
+
+        pb_chassis_id=$(ovn-sbctl --bare --columns chassis list port_binding $lpname)
+        pb_chassis_name=$(ovn-sbctl get chassis $pb_chassis_id name)
+        AT_FAIL_IF([test x$pb_chassis_name != x$chassis])
+    done
+done
+
+for i in 1 2 3; do
+    > expout
+    for vif in 1 2 3; do
+        echo vif$i$vif >> expout
+    done
+    AT_CHECK([ovs-vsctl list-ports br-int-$i | grep vif], [0], [expout])
+done
+
+# check that all tunnel endpoints are present
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-host-1-host-2-0
+ovn-host-1-host-3-0
+ovn-host-2-host-1-0
+ovn-host-2-host-3-0
+ovn-host-3-host-1-0
+ovn-host-3-host-2-0
+]])
+
+# check no errors occurred during encaps creation
+AT_CHECK([ovs-vsctl --bare --columns=error find interface type="geneve" | awk NF | sort], [0], [])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- system-id in file])
+AT_KEYWORDS([ovn])
+
+ovn_start
+net_add n1
+sim_add hv
+
+as hv
+
+echo otherid > ${OVN_SYSCONFDIR}/system-id-override
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+# system-id-override file overrides chassis name selected via cli
+echo otherid > expout
+AT_CHECK([ovn-sbctl --bare --columns name list chassis], [0], [expout])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- concurrent controllers avoid fighting for each others' resources])
+AT_KEYWORDS([ovn])
+
+ovn_start
+sim_add hv
+
+for i in 1 2; do
+    net_add n-$i
+done
+
+as hv
+for i in 1 2; do
+    AT_CHECK([ovn-nbctl ls-add ls-$i])
+    AT_CHECK([ovn-nbctl lsp-add ls-$i ln_port-$i])
+    AT_CHECK([ovn-nbctl lsp-set-addresses ln_port-$i unknown])
+    AT_CHECK([ovn-nbctl lsp-set-type ln_port-$i localnet])
+    AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port-$i network_name=phys-$i])
+done
+
+for i in 1 2; do
+    as hv
+    ovs-vsctl add-br br-phys-$i
+    ovs-vsctl set open . external-ids:ovn-bridge-mappings-controller-$i=phys-$i:br-phys-$i
+    ovn_attach n-$i br-phys-$i 192.168.0.$i 24 geneve br-int-$i controller-$i
+
+    ovs-vsctl add-port br-int-$i vif-$i -- set Interface vif-$i external-ids:iface-id=lp-$i
+    ovn-nbctl lsp-add ls-$i lp-$i
+    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp-$i` = xup])
+done
+
+# check that both patch ports are present
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="patch" | awk NF | sort], [0],
+[[patch-br-int-1-to-ln_port-1
+patch-br-int-2-to-ln_port-2
+patch-ln_port-1-to-br-int-1
+patch-ln_port-2-to-br-int-2
+]])
+
+# check that both tunnel endpoints are present
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-controller-1-controller-2-0
+ovn-controller-2-controller-1-0
+]])
+
+AT_CLEANUP
+
 # 3 hypervisors, one logical switch, 3 logical ports per hypervisor
 AT_SETUP([ovn -- 3 HVs, 1 LS, 3 lports/HV])
 AT_KEYWORDS([ovnarp])
@@ -6926,6 +7100,72 @@  OVN_CLEANUP([hv1])
 
 AT_CLEANUP
 
+AT_SETUP([ovn -- obsolete patch ports and tunnel endpoints removed])
+AT_KEYWORDS([cleanup-test])
+ovn_start
+
+net_add n1
+net_add n2
+
+for i in 1 2; do
+    ovs-vsctl add-br br-phys$i
+    ovs-vsctl set open . external-ids:ovn-bridge-mappings-hv$i=physnet$i:br-phys$i
+    ovn_attach n$i br-phys$i 192.168.0.$i 24 geneve br-int$i hv$i
+done
+
+# create irrelevant patch and tunnel ports
+for i in 1 2; do
+    # patch without chassis owner set
+    ovs-vsctl add-port br-int$i fakepatch$i external-ids:ovn-logical-patch-port=fakeport$i -- \
+              set Interface fakepatch$i type=patch
+
+    # patch marked as owned by the chassis
+    ovs-vsctl add-port br-int$i owned_fakepatch$i external-ids:ovn-logical-patch-port=owned_fakeport$i \
+                                                  external-ids:ovn-chassis-id=hv$i -- \
+              set Interface owned_fakepatch$i type=patch
+
+    # patch marked as owned by some other chassis
+    ovs-vsctl add-port br-int$i alien_fakepatch$i external-ids:ovn-logical-patch-port=alien_fakeport$i \
+                                                  external-ids:ovn-chassis-id=alien_hv$i -- \
+              set Interface alien_fakepatch$i type=patch
+
+    # OVN tunnel endpoint on a bridge owned by a controller
+    ovs-vsctl add-port br-int$i faketunnel$i external-ids:ovn-chassis-id=fakechassis -- \
+              set Interface faketunnel$i type=geneve
+done
+
+# tunnel endpoint on a bridge NOT owned by a controller
+ovs-vsctl add-br alien_br
+ovs-vsctl add-port alien_br alien_tunnel external-ids:ovn-chassis-id=fakechassis -- \
+          set Interface alien_tunnel type=geneve
+
+AT_CHECK([ovn-nbctl ls-add lsw0])
+AT_CHECK([ovn-nbctl lsp-add lsw0 lnport])
+AT_CHECK([ovn-nbctl lsp-set-addresses lnport unknown])
+AT_CHECK([ovn-nbctl lsp-set-type lnport localnet])
+AT_CHECK([ovn-nbctl --wait=hv lsp-set-options lnport network_name=physnet1])
+
+ovs-vsctl add-port br-int1 vif -- set Interface vif external-ids:iface-id=lp0
+ovn-nbctl lsp-add lsw0 lp0
+OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp0` = xup])
+
+# check that only patch ports that belong to some other chassis and those for localnet ports are present
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="patch" | awk NF | sort], [0],
+[[alien_fakepatch1
+alien_fakepatch2
+patch-br-int1-to-lnport
+patch-lnport-to-br-int1
+]])
+
+# check that only controller tunnel endpoints and a tunnel on another bridge are present
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[alien_tunnel
+ovn-hv1-hv2-0
+ovn-hv2-hv1-0
+]])
+
+AT_CLEANUP
+
 AT_SETUP([ovn -- nd_na ])
 ovn_start
 
@@ -11365,7 +11605,7 @@  bfd_dump() {
         for chassis2 in gw1 gw2 hv1 hv2; do
             if [[ "$chassis" != "$chassis2" ]]; then
                 echo " -> $chassis2:"
-                echo "   $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis2-0)"
+                echo "   $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis-$chassis2-0)"
             fi
         done
         echo "--------------------------"
@@ -11455,7 +11695,7 @@  wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis
 as gw1
 for chassis in gw2 hv1 hv2; do
     echo "checking gw1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw1-$chassis-0],[0],
              [[enable=true
 ]])
 done
@@ -11465,7 +11705,7 @@  done
 as gw2
 for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0],[0],
              [[enable=true
 ]])
 done
@@ -11474,12 +11714,12 @@  done
 as hv1
 for chassis in gw1 gw2; do
     echo "checking hv1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-$chassis-0],[0],
              [[enable=true
 ]])
 done
 # make sure BFD is not enabled to hv2, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
+AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-hv2-0],[0],
          [[
 ]])
 
@@ -11488,12 +11728,12 @@  AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
 as hv2
 for chassis in gw1 gw2; do
     echo "checking hv2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-$chassis-0],[0],
              [[enable=true
 ]])
 done
 # make sure BFD is not enabled to hv1, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-hv1-0],[0],
          [[
 ]])
 
@@ -11528,7 +11768,7 @@  as gw2
 for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
     OVS_WAIT_UNTIL([
-    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
+    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0)
     test "$bfd_cfg" = "enable=true min_rx=2000"
 ])
 done
@@ -11536,7 +11776,7 @@  ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-tx"=1500
 for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
     OVS_WAIT_UNTIL([
-    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
+    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0)
     test "$bfd_cfg" = "enable=true min_rx=2000 min_tx=1500"
 ])
 done
@@ -11545,7 +11785,7 @@  ovn-nbctl --wait=hv set NB_Global . options:"bfd-mult"=5
 for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
     OVS_WAIT_UNTIL([
-    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
+    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0)
     test "$bfd_cfg" = "enable=true min_tx=1500 mult=5"
 ])
 done
@@ -11681,7 +11921,7 @@  grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
 as gw1
 for chassis in gw2 hv1 hv2; do
     echo "checking gw1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw1-$chassis-0],[0],
              [[enable=true
 ]])
 done
@@ -11690,7 +11930,7 @@  done
 as gw2
 for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-gw2-$chassis-0],[0],
              [[enable=true
 ]])
 done
@@ -11699,12 +11939,12 @@  done
 as hv1
 for chassis in gw1 gw2; do
     echo "checking hv1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-$chassis-0],[0],
              [[enable=true
 ]])
 done
 # make sure BFD is not enabled to hv2, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
+AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-hv2-0],[0],
          [[
 ]])
 
@@ -11712,12 +11952,12 @@  AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
 as hv2
 for chassis in gw1 gw2; do
     echo "checking hv2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-$chassis-0],[0],
              [[enable=true
 ]])
 done
 # make sure BFD is not enabled to hv1, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-hv1-0],[0],
          [[
 ]])
 
@@ -15777,42 +16017,42 @@  check ovn-nbctl --wait=hv sync
 dnl Assert that each Chassis has a tunnel formed to every other Chassis
 as hv1
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
+[[ovn-hv1-hv2-0
+ovn-hv1-hv3-0
+ovn-hv1-hv4-0
+ovn-hv1-hv5-0
 ]])
 
 as hv2
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
+[[ovn-hv2-hv1-0
+ovn-hv2-hv3-0
+ovn-hv2-hv4-0
+ovn-hv2-hv5-0
 ]])
 
 as hv3
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv4-0
-ovn-hv5-0
+[[ovn-hv3-hv1-0
+ovn-hv3-hv2-0
+ovn-hv3-hv4-0
+ovn-hv3-hv5-0
 ]])
 
 as hv4
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv5-0
+[[ovn-hv4-hv1-0
+ovn-hv4-hv2-0
+ovn-hv4-hv3-0
+ovn-hv4-hv5-0
 ]])
 
 as hv5
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
+[[ovn-hv5-hv1-0
+ovn-hv5-hv2-0
+ovn-hv5-hv3-0
+ovn-hv5-hv4-0
 ]])
 
 dnl Let's now add some Chassis to different transport zones
@@ -15843,28 +16083,28 @@  check ovn-nbctl --wait=hv sync
 
 as hv1
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv2-0
-ovn-hv3-0
+[[ovn-hv1-hv2-0
+ovn-hv1-hv3-0
 ]])
 
 as hv2
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
+[[ovn-hv2-hv1-0
 ]])
 
 as hv3
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
+[[ovn-hv3-hv1-0
 ]])
 
 as hv4
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv5-0
+[[ovn-hv4-hv5-0
 ]])
 
 as hv5
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv4-0
+[[ovn-hv5-hv4-0
 ]])
 
 dnl Removing the transport zones should make all Chassis to create
@@ -15879,42 +16119,42 @@  check ovn-nbctl --wait=hv sync
 
 as hv1
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
+[[ovn-hv1-hv2-0
+ovn-hv1-hv3-0
+ovn-hv1-hv4-0
+ovn-hv1-hv5-0
 ]])
 
 as hv2
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
+[[ovn-hv2-hv1-0
+ovn-hv2-hv3-0
+ovn-hv2-hv4-0
+ovn-hv2-hv5-0
 ]])
 
 as hv3
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv4-0
-ovn-hv5-0
+[[ovn-hv3-hv1-0
+ovn-hv3-hv2-0
+ovn-hv3-hv4-0
+ovn-hv3-hv5-0
 ]])
 
 as hv4
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv5-0
+[[ovn-hv4-hv1-0
+ovn-hv4-hv2-0
+ovn-hv4-hv3-0
+ovn-hv4-hv5-0
 ]])
 
 as hv5
 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
+[[ovn-hv5-hv1-0
+ovn-hv5-hv2-0
+ovn-hv5-hv3-0
+ovn-hv5-hv4-0
 ]])
 
 OVN_CLEANUP([hv1], [hv2], [hv3])
@@ -20348,14 +20588,14 @@  check ovn-nbctl fwd-group-del fwd_grp1
 check ovn-nbctl --wait=hv --liveness fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22
 
 # Verify openflow group members
-ofport_lsp21=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv2-0)
+ofport_lsp21=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv1-hv2-0)
 tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=lsp21`
 AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
     grep "bucket=watch_port:$ofport_lsp21,actions=load:0x"$tunnel_key | wc -l], [0], [dnl
 1
 ])
 
-ofport_lsp22=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv3-0)
+ofport_lsp22=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv1-hv3-0)
 tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=lsp22`
 AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \
     grep "bucket=watch_port:$ofport_lsp22,actions=load:0x"$tunnel_key | wc -l], [0], [dnl
diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
index 856f5d2d7..a6fa48201 100644
--- a/tests/ovs-macros.at
+++ b/tests/ovs-macros.at
@@ -55,6 +55,7 @@  ovs_setenv() {
     OVS_LOGDIR=$ovs_dir; export OVS_LOGDIR
     OVS_DBDIR=$ovs_dir; export OVS_DBDIR
     OVS_SYSCONFDIR=$ovs_dir; export OVS_SYSCONFDIR
+    OVN_SYSCONFDIR=$ovs_dir; export OVN_SYSCONFDIR
     OVS_PKGDATADIR=$ovs_dir; export OVS_PKGDATADIR
 }
 
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 29f421685..e2c2617e4 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -5443,6 +5443,7 @@  OVS_APP_EXIT_AND_WAIT([ovn-northd])
 
 as
 OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/failed to query port patch-.*/d
 /.*terminating with signal 15.*/d"])
 AT_CLEANUP