diff mbox series

[ovs-dev,v6,13/13] northd: Add I-P for NB_Global and SB_Global.

Message ID 20240130212355.1483448-1-numans@ovn.org
State Accepted
Headers show
Series northd lflow incremental processing | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Numan Siddique Jan. 30, 2024, 9:23 p.m. UTC
From: Numan Siddique <numans@ovn.org>

A new engine node "global_config" is added which handles the changes
to NB_Global an SB_Global tables.  It also creates these rows if
not present.

Without the I-P, any changes to the options column of these tables
result in recompute of 'northd' and 'lflow' engine nodes.

Acked-by: Dumitru Ceara <dceara@redhat.com>
Acked-by: Han Zhou <hzhou@ovn.org>
Signed-off-by: Numan Siddique <numans@ovn.org>
---
 northd/aging.c            |  21 +-
 northd/automake.mk        |   2 +
 northd/en-global-config.c | 576 ++++++++++++++++++++++++++++++++++++++
 northd/en-global-config.h |  65 +++++
 northd/en-lflow.c         |  11 +-
 northd/en-northd.c        |  52 ++--
 northd/en-northd.h        |   2 +-
 northd/en-sync-sb.c       |  21 +-
 northd/inc-proc-northd.c  |  38 ++-
 northd/northd.c           | 230 +++------------
 northd/northd.h           |  24 +-
 tests/ovn-northd.at       | 256 +++++++++++++++--
 12 files changed, 1001 insertions(+), 297 deletions(-)
 create mode 100644 northd/en-global-config.c
 create mode 100644 northd/en-global-config.h

Comments

Numan Siddique Feb. 1, 2024, 3:13 p.m. UTC | #1
On Tue, Jan 30, 2024 at 4:25 PM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> A new engine node "global_config" is added which handles the changes
> to NB_Global an SB_Global tables.  It also creates these rows if
> not present.
>
> Without the I-P, any changes to the options column of these tables
> result in recompute of 'northd' and 'lflow' engine nodes.
>
> Acked-by: Dumitru Ceara <dceara@redhat.com>
> Acked-by: Han Zhou <hzhou@ovn.org>
> Signed-off-by: Numan Siddique <numans@ovn.org>

Recheck-request: github-robot-_Build_and_Test

> ---
>  northd/aging.c            |  21 +-
>  northd/automake.mk        |   2 +
>  northd/en-global-config.c | 576 ++++++++++++++++++++++++++++++++++++++
>  northd/en-global-config.h |  65 +++++
>  northd/en-lflow.c         |  11 +-
>  northd/en-northd.c        |  52 ++--
>  northd/en-northd.h        |   2 +-
>  northd/en-sync-sb.c       |  21 +-
>  northd/inc-proc-northd.c  |  38 ++-
>  northd/northd.c           | 230 +++------------
>  northd/northd.h           |  24 +-
>  tests/ovn-northd.at       | 256 +++++++++++++++--
>  12 files changed, 1001 insertions(+), 297 deletions(-)
>  create mode 100644 northd/en-global-config.c
>  create mode 100644 northd/en-global-config.h
>
> diff --git a/northd/aging.c b/northd/aging.c
> index cdf5f4464e..b76963a2dd 100644
> --- a/northd/aging.c
> +++ b/northd/aging.c
> @@ -15,6 +15,7 @@
>
>  #include <config.h>
>
> +#include "en-global-config.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/ovn-nb-idl.h"
>  #include "lib/ovn-sb-idl.h"
> @@ -347,15 +348,10 @@ aging_context_handle_timestamp(struct aging_context *ctx, int64_t timestamp,
>  static uint32_t
>  get_removal_limit(struct engine_node *node, const char *name)
>  {
> -    const struct nbrec_nb_global_table *nb_global_table =
> -            EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> -    const struct nbrec_nb_global *nb =
> -            nbrec_nb_global_table_first(nb_global_table);
> -    if (!nb) {
> -        return 0;
> -    }
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
>
> -    return smap_get_uint(&nb->options, name, 0);
> +    return smap_get_uint(&global_config->nb_options, name, 0);
>  }
>
>  /* MAC binding aging */
> @@ -394,11 +390,14 @@ en_mac_binding_aging_run(struct engine_node *node, void *data OVS_UNUSED)
>  {
>      const struct engine_context *eng_ctx = engine_get_context();
>      struct northd_data *northd_data = engine_get_input_data("northd", node);
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
> +
>      struct aging_waker *waker =
>          engine_get_input_data("mac_binding_aging_waker", node);
>
>      if (!eng_ctx->ovnsb_idl_txn ||
> -        !northd_data->features.mac_binding_timestamp ||
> +        !global_config->features.mac_binding_timestamp ||
>          time_msec() < waker->next_wake_msec) {
>          return;
>      }
> @@ -530,9 +529,11 @@ en_fdb_aging_run(struct engine_node *node, void *data OVS_UNUSED)
>      const struct engine_context *eng_ctx = engine_get_context();
>      struct northd_data *northd_data = engine_get_input_data("northd", node);
>      struct aging_waker *waker = engine_get_input_data("fdb_aging_waker", node);
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
>
>      if (!eng_ctx->ovnsb_idl_txn ||
> -        !northd_data->features.fdb_timestamp ||
> +        !global_config->features.fdb_timestamp ||
>          time_msec() < waker->next_wake_msec) {
>          return;
>      }
> diff --git a/northd/automake.mk b/northd/automake.mk
> index 19abb0dece..d491973a8b 100644
> --- a/northd/automake.mk
> +++ b/northd/automake.mk
> @@ -8,6 +8,8 @@ northd_ovn_northd_SOURCES = \
>         northd/northd.c \
>         northd/northd.h \
>         northd/ovn-northd.c \
> +       northd/en-global-config.c \
> +       northd/en-global-config.h \
>         northd/en-northd.c \
>         northd/en-northd.h \
>         northd/en-lflow.c \
> diff --git a/northd/en-global-config.c b/northd/en-global-config.c
> new file mode 100644
> index 0000000000..9ac5faf995
> --- /dev/null
> +++ b/northd/en-global-config.c
> @@ -0,0 +1,576 @@
> +/*
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include <getopt.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +
> +/* OVS includes */
> +#include "openvswitch/vlog.h"
> +
> +/* OVN includes */
> +#include "debug.h"
> +#include "en-global-config.h"
> +#include "include/ovn/features.h"
> +#include "ipam.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "northd.h"
> +
> +
> +VLOG_DEFINE_THIS_MODULE(en_global_config);
> +
> +/* static function declarations. */
> +static void northd_enable_all_features(struct ed_type_global_config *);
> +static void build_chassis_features(const struct sbrec_chassis_table *,
> +                                   struct chassis_features *);
> +static bool chassis_features_changed(const struct chassis_features *,
> +                                     const struct chassis_features *);
> +static bool config_out_of_sync(const struct smap *config,
> +                               const struct smap *saved_config,
> +                               const char *key, bool must_be_present);
> +static bool check_nb_options_out_of_sync(const struct nbrec_nb_global *,
> +                                         struct ed_type_global_config *);
> +static void update_sb_config_options_to_sbrec(struct ed_type_global_config *,
> +                                              const struct sbrec_sb_global *);
> +
> +void *
> +en_global_config_init(struct engine_node *node OVS_UNUSED,
> +                      struct engine_arg *args OVS_UNUSED)
> +{
> +    struct ed_type_global_config *data = xzalloc(sizeof *data);
> +    smap_init(&data->nb_options);
> +    smap_init(&data->sb_options);
> +    northd_enable_all_features(data);
> +    return data;
> +}
> +
> +void
> +en_global_config_run(struct engine_node *node , void *data)
> +{
> +    const struct engine_context *eng_ctx = engine_get_context();
> +    if (!eng_ctx->ovnnb_idl_txn || !eng_ctx->ovnsb_idl_txn) {
> +        return;
> +    }
> +
> +    const struct nbrec_nb_global_table *nb_global_table =
> +        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> +    const struct sbrec_sb_global_table *sb_global_table =
> +        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> +    const struct sbrec_chassis_table *sbrec_chassis_table =
> +        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
> +
> +    struct ed_type_global_config *config_data = data;
> +
> +    /* Sync ipsec configuration.
> +     * Copy nb_cfg from northbound to southbound database.
> +     * Also set up to update sb_cfg once our southbound transaction commits. */
> +    const struct nbrec_nb_global *nb =
> +        nbrec_nb_global_table_first(nb_global_table);
> +    if (!nb) {
> +        nb = nbrec_nb_global_insert(eng_ctx->ovnnb_idl_txn);
> +    }
> +
> +    const char *mac_addr_prefix = set_mac_prefix(smap_get(&nb->options,
> +                                                          "mac_prefix"));
> +
> +    const char *monitor_mac = smap_get(&nb->options, "svc_monitor_mac");
> +    if (monitor_mac) {
> +        if (eth_addr_from_string(monitor_mac,
> +                                 &config_data->svc_monitor_mac_ea)) {
> +            snprintf(config_data->svc_monitor_mac,
> +                     sizeof config_data->svc_monitor_mac,
> +                     ETH_ADDR_FMT,
> +                     ETH_ADDR_ARGS(config_data->svc_monitor_mac_ea));
> +        } else {
> +            monitor_mac = NULL;
> +        }
> +    }
> +
> +    struct smap *options = &config_data->nb_options;
> +    smap_destroy(options);
> +    smap_clone(options, &nb->options);
> +
> +    smap_replace(options, "mac_prefix", mac_addr_prefix);
> +
> +    if (!monitor_mac) {
> +        eth_addr_random(&config_data->svc_monitor_mac_ea);
> +        snprintf(config_data->svc_monitor_mac,
> +                 sizeof config_data->svc_monitor_mac, ETH_ADDR_FMT,
> +                 ETH_ADDR_ARGS(config_data->svc_monitor_mac_ea));
> +        smap_replace(options, "svc_monitor_mac",
> +                     config_data->svc_monitor_mac);
> +    }
> +
> +    char *max_tunid = xasprintf("%d",
> +        get_ovn_max_dp_key_local(sbrec_chassis_table));
> +    smap_replace(options, "max_tunid", max_tunid);
> +    free(max_tunid);
> +
> +    char *ovn_internal_version = ovn_get_internal_version();
> +    if (strcmp(ovn_internal_version,
> +                smap_get_def(options, "northd_internal_version", ""))) {
> +        smap_replace(options, "northd_internal_version",
> +                     ovn_internal_version);
> +        config_data->ovn_internal_version_changed = true;
> +    } else {
> +        config_data->ovn_internal_version_changed = false;
> +    }
> +
> +    free(ovn_internal_version);
> +
> +    if (!smap_equal(&nb->options, options)) {
> +        nbrec_nb_global_verify_options(nb);
> +        nbrec_nb_global_set_options(nb, options);
> +    }
> +
> +    if (smap_get_bool(&nb->options, "ignore_chassis_features", false)) {
> +        northd_enable_all_features(config_data);
> +    } else {
> +        build_chassis_features(sbrec_chassis_table, &config_data->features);
> +    }
> +
> +    init_debug_config(nb);
> +
> +    const struct sbrec_sb_global *sb =
> +        sbrec_sb_global_table_first(sb_global_table);
> +    if (!sb) {
> +        sb = sbrec_sb_global_insert(eng_ctx->ovnsb_idl_txn);
> +    }
> +    if (nb->ipsec != sb->ipsec) {
> +        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
> +    }
> +
> +    /* Set up SB_Global (depends on chassis features). */
> +    update_sb_config_options_to_sbrec(config_data, sb);
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +void en_global_config_cleanup(void *data OVS_UNUSED)
> +{
> +    struct ed_type_global_config *config_data = data;
> +    smap_destroy(&config_data->nb_options);
> +    smap_destroy(&config_data->sb_options);
> +    destroy_debug_config();
> +}
> +
> +void
> +en_global_config_clear_tracked_data(void *data)
> +{
> +    struct ed_type_global_config *config_data = data;
> +    config_data->tracked = false;
> +    config_data->tracked_data.nb_options_changed = false;
> +    config_data->tracked_data.chassis_features_changed = false;
> +}
> +
> +bool
> +global_config_nb_global_handler(struct engine_node *node, void *data)
> +{
> +    const struct nbrec_nb_global_table *nb_global_table =
> +        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> +    const struct sbrec_sb_global_table *sb_global_table =
> +        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> +
> +    const struct nbrec_nb_global *nb =
> +        nbrec_nb_global_table_first(nb_global_table);
> +    if (!nb) {
> +        return false;
> +    }
> +
> +    const struct sbrec_sb_global *sb =
> +        sbrec_sb_global_table_first(sb_global_table);
> +    if (!sb) {
> +        return false;
> +    }
> +
> +    /* We are only interested in ipsec and options column. */
> +    if (!nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_IPSEC)
> +        && !nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_OPTIONS)) {
> +        return true;
> +    }
> +
> +    if (nb->ipsec != sb->ipsec) {
> +        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
> +    }
> +
> +    struct ed_type_global_config *config_data = data;
> +    config_data->tracked = true;
> +
> +    if (smap_equal(&nb->options, &config_data->nb_options)) {
> +        return true;
> +    }
> +
> +    /* Return false if an option is out of sync and requires updating the
> +     * NB config. (Like svc_monitor_mac, max_tunid and mac_prefix). */
> +    /* Check if svc_monitor_mac has changed or not. */
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "svc_monitor_mac", true)) {
> +        return false;
> +    }
> +
> +    /* Check if max_tunid has changed or not. */
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "max_tunid", true)) {
> +        return false;
> +    }
> +
> +    /* Check if mac_prefix has changed or not. */
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "mac_prefix", true)) {
> +        return false;
> +    }
> +
> +    /* Check if ignore_chassis_features has changed or not. */
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "ignore_chassis_features", false)) {
> +        return false;
> +    }
> +
> +    /* Check if northd_internal_version has changed or not. */
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "northd_internal_version", false)) {
> +        return false;
> +    }
> +
> +    if (check_nb_options_out_of_sync(nb, config_data)) {
> +        config_data->tracked_data.nb_options_changed = true;
> +    }
> +
> +    smap_destroy(&config_data->nb_options);
> +    smap_clone(&config_data->nb_options, &nb->options);
> +
> +    update_sb_config_options_to_sbrec(config_data, sb);
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
> +bool
> +global_config_sb_global_handler(struct engine_node *node, void *data)
> +{
> +    const struct sbrec_sb_global_table *sb_global_table =
> +        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> +
> +    const struct sbrec_sb_global *sb =
> +        sbrec_sb_global_table_first(sb_global_table);
> +    if (!sb) {
> +        return false;
> +    }
> +
> +    struct ed_type_global_config *config_data = data;
> +
> +    if (!smap_equal(&sb->options, &config_data->sb_options)) {
> +        return false;
> +    }
> +
> +    /* No need to update the engine node. */
> +    return true;
> +}
> +
> +bool
> +global_config_sb_chassis_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_global_config *config_data = data;
> +
> +    const struct sbrec_chassis_table *sbrec_chassis_table =
> +        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
> +    const struct sbrec_chassis *chassis;
> +
> +    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, sbrec_chassis_table) {
> +        if (sbrec_chassis_is_new(chassis)
> +            || sbrec_chassis_is_deleted(chassis)
> +            || sbrec_chassis_is_updated(chassis,
> +                                        SBREC_CHASSIS_COL_ENCAPS)) {
> +            return false;
> +        }
> +
> +        for (size_t i = 0; i < chassis->n_encaps; i++) {
> +            if (sbrec_encap_row_get_seqno(chassis->encaps[i],
> +                                          OVSDB_IDL_CHANGE_MODIFY) > 0) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    if (smap_get_bool(&config_data->nb_options, "ignore_chassis_features",
> +                      false)) {
> +        return true;
> +    }
> +
> +    bool reevaluate_chassis_features = false;
> +
> +    /* Check and evaluate chassis features. */
> +    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, sbrec_chassis_table) {
> +        if (sbrec_chassis_is_updated(chassis,
> +                                        SBREC_CHASSIS_COL_OTHER_CONFIG)) {
> +            reevaluate_chassis_features = true;
> +            break;
> +        }
> +    }
> +
> +    if (!reevaluate_chassis_features) {
> +        return true;
> +    }
> +
> +    struct chassis_features present_features = config_data->features;
> +
> +    /* Enable all features before calling build_chassis_features() as
> +    * build_chassis_features() only sets the feature flags to false. */
> +    northd_enable_all_features(config_data);
> +    build_chassis_features(sbrec_chassis_table, &config_data->features);
> +
> +    if (chassis_features_changed(&present_features, &config_data->features)) {
> +        config_data->tracked_data.chassis_features_changed = true;
> +        config_data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +/* generic global config handler for any engine node which has global_config
> + * has an input node . */
> +bool
> +node_global_config_handler(struct engine_node *node, void *data OVS_UNUSED)
> +{
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
> +
> +    if (!global_config->tracked
> +        || global_config->tracked_data.chassis_features_changed
> +        || global_config->tracked_data.nb_options_changed) {
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +/* static functions. */
> +static void
> +northd_enable_all_features(struct ed_type_global_config *data)
> +{
> +    data->features = (struct chassis_features) {
> +        .ct_no_masked_label = true,
> +        .mac_binding_timestamp = true,
> +        .ct_lb_related = true,
> +        .fdb_timestamp = true,
> +        .ls_dpg_column = true,
> +    };
> +}
> +
> +static void
> +build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
> +                       struct chassis_features *chassis_features)
> +{
> +    const struct sbrec_chassis *chassis;
> +
> +    SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) {
> +        /* Only consider local AZ chassis.  Remote ones don't install
> +         * flows generated by the local northd.
> +         */
> +        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
> +            continue;
> +        }
> +
> +        bool ct_no_masked_label =
> +            smap_get_bool(&chassis->other_config,
> +                          OVN_FEATURE_CT_NO_MASKED_LABEL,
> +                          false);
> +        if (!ct_no_masked_label && chassis_features->ct_no_masked_label) {
> +            chassis_features->ct_no_masked_label = false;
> +        }
> +
> +        bool mac_binding_timestamp =
> +            smap_get_bool(&chassis->other_config,
> +                          OVN_FEATURE_MAC_BINDING_TIMESTAMP,
> +                          false);
> +        if (!mac_binding_timestamp &&
> +            chassis_features->mac_binding_timestamp) {
> +            chassis_features->mac_binding_timestamp = false;
> +        }
> +
> +        bool ct_lb_related =
> +            smap_get_bool(&chassis->other_config,
> +                          OVN_FEATURE_CT_LB_RELATED,
> +                          false);
> +        if (!ct_lb_related &&
> +            chassis_features->ct_lb_related) {
> +            chassis_features->ct_lb_related = false;
> +        }
> +
> +        bool fdb_timestamp =
> +            smap_get_bool(&chassis->other_config,
> +                          OVN_FEATURE_FDB_TIMESTAMP,
> +                          false);
> +        if (!fdb_timestamp &&
> +            chassis_features->fdb_timestamp) {
> +            chassis_features->fdb_timestamp = false;
> +        }
> +
> +        bool ls_dpg_column =
> +            smap_get_bool(&chassis->other_config,
> +                          OVN_FEATURE_LS_DPG_COLUMN,
> +                          false);
> +        if (!ls_dpg_column &&
> +            chassis_features->ls_dpg_column) {
> +            chassis_features->ls_dpg_column = false;
> +        }
> +    }
> +}
> +
> +static bool
> +config_out_of_sync(const struct smap *config, const struct smap *saved_config,
> +                   const char *key, bool must_be_present)
> +{
> +    const char *value = smap_get(config, key);
> +    if (!value && must_be_present) {
> +        return true;
> +    }
> +
> +    const char *saved_value = smap_get(saved_config, key);
> +    if (!saved_value && must_be_present) {
> +        return true;
> +    }
> +
> +    if (!value && !saved_value) {
> +        return false;
> +    }
> +
> +    if (!value || !saved_value) {
> +        return true;
> +    }
> +
> +    return strcmp(value, saved_value);
> +}
> +
> +static bool
> +check_nb_options_out_of_sync(const struct nbrec_nb_global *nb,
> +                             struct ed_type_global_config *config_data)
> +{
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "mac_binding_removal_limit", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "fdb_removal_limit", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "controller_event", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "ignore_lsp_down", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "use_ct_inv_match", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "default_acl_drop", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "debug_drop_domain_id", false)) {
> +        init_debug_config(nb);
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "debug_drop_collector_set", false)) {
> +        init_debug_config(nb);
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "use_common_zone", false)) {
> +        return true;
> +    }
> +
> +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> +                           "install_ls_lb_from_router", false)) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static void
> +update_sb_config_options_to_sbrec(struct ed_type_global_config *config_data,
> +                                  const struct sbrec_sb_global *sb)
> +{
> +    struct smap *options = &config_data->sb_options;
> +
> +    smap_destroy(options);
> +    smap_clone(options, &config_data->nb_options);
> +
> +    /* Inform ovn-controllers whether LB flows will use ct_mark (i.e., only
> +     * if all chassis support it).  If not explicitly present in the database
> +     * the default value to be used for this option is 'true'.
> +     */
> +    if (!config_data->features.ct_no_masked_label) {
> +        smap_replace(options, "lb_hairpin_use_ct_mark", "false");
> +    } else {
> +        smap_remove(options, "lb_hairpin_use_ct_mark");
> +    }
> +
> +    /* Hackaround SB_global.options overwrite by NB_Global.options for
> +     * 'sbctl_probe_interval' option.
> +     */
> +    const char *sip = smap_get(&sb->options, "sbctl_probe_interval");
> +    if (sip) {
> +        smap_replace(options, "sbctl_probe_interval", sip);
> +    }
> +
> +    if (!smap_equal(&sb->options, options)) {
> +        sbrec_sb_global_set_options(sb, options);
> +    }
> +}
> +
> +static bool
> +chassis_features_changed(const struct chassis_features *present,
> +                         const struct chassis_features *updated)
> +{
> +    if (present->ct_no_masked_label != updated->ct_no_masked_label) {
> +        return true;
> +    }
> +
> +    if (present->mac_binding_timestamp != updated->mac_binding_timestamp) {
> +        return true;
> +    }
> +
> +    if (present->ct_lb_related != updated->ct_lb_related) {
> +        return true;
> +    }
> +
> +    if (present->fdb_timestamp != updated->fdb_timestamp) {
> +        return true;
> +    }
> +
> +    if (present->ls_dpg_column != updated->ls_dpg_column) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> diff --git a/northd/en-global-config.h b/northd/en-global-config.h
> new file mode 100644
> index 0000000000..436bc7fa35
> --- /dev/null
> +++ b/northd/en-global-config.h
> @@ -0,0 +1,65 @@
> +#ifndef EN_GLOBAL_CONFIG_H
> +#define EN_GLOBAL_CONFIG_H 1
> +
> +#include <config.h>
> +
> +/* OVS includes. */
> +#include "lib/packets.h"
> +#include "lib/smap.h"
> +
> +/* OVN includes. */
> +#include "lib/inc-proc-eng.h"
> +
> +struct nbrec_nb_global;
> +struct sbrec_sb_global;
> +
> +struct chassis_features {
> +    bool ct_no_masked_label;
> +    bool mac_binding_timestamp;
> +    bool ct_lb_related;
> +    bool fdb_timestamp;
> +    bool ls_dpg_column;
> +};
> +
> +struct global_config_tracked_data {
> +    bool nb_options_changed;
> +    bool chassis_features_changed;
> +};
> +
> +/* struct which maintains the data of the engine node global_config. */
> +struct ed_type_global_config {
> +    struct smap nb_options;
> +    struct smap sb_options;
> +    const struct nbrec_nb_global *nb_global;
> +    const struct sbrec_sb_global *sb_global;
> +
> +    /* MAC allocated for service monitor usage. Just one mac is allocated
> +     * for this purpose and ovn-controller's on each chassis will make use
> +     * of this mac when sending out the packets to monitor the services
> +     * defined in Service_Monitor Southbound table. Since these packets
> +     * are locally handled, having just one mac is good enough. */
> +    char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
> +    struct eth_addr svc_monitor_mac_ea;
> +
> +    struct chassis_features features;
> +
> +    bool ovn_internal_version_changed;
> +
> +    bool tracked;
> +    struct global_config_tracked_data tracked_data;
> +};
> +
> +void *en_global_config_init(struct engine_node *, struct engine_arg *);
> +void en_global_config_run(struct engine_node *, void *data);
> +void en_global_config_cleanup(void *data);
> +void en_global_config_clear_tracked_data(void *data);
> +
> +bool global_config_nb_global_handler(struct engine_node *, void *data);
> +bool global_config_sb_global_handler(struct engine_node *, void *data);
> +bool global_config_sb_chassis_handler(struct engine_node *, void *data);
> +
> +/* generic global config handler for any engine node which has global_config
> + * has an input node . */
> +bool node_global_config_handler(struct engine_node *, void *data);
> +
> +#endif /* EN_GLOBAL_CONFIG_H */
> diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> index 525453054b..f1a83839df 100644
> --- a/northd/en-lflow.c
> +++ b/northd/en-lflow.c
> @@ -18,6 +18,7 @@
>  #include <stdlib.h>
>  #include <stdio.h>
>
> +#include "en-global-config.h"
>  #include "en-lflow.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
> @@ -77,10 +78,14 @@ lflow_get_input_data(struct engine_node *node,
>      lflow_input->meter_groups = &sync_meters_data->meter_groups;
>      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
>      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> -    lflow_input->features = &northd_data->features;
> -    lflow_input->ovn_internal_version_changed =
> -                      northd_data->ovn_internal_version_changed;
>      lflow_input->bfd_connections = NULL;
> +
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
> +    lflow_input->features = &global_config->features;
> +    lflow_input->ovn_internal_version_changed =
> +        global_config->ovn_internal_version_changed;
> +    lflow_input->svc_monitor_mac = global_config->svc_monitor_mac;
>  }
>
>  void en_lflow_run(struct engine_node *node, void *data)
> diff --git a/northd/en-northd.c b/northd/en-northd.c
> index 5143603f39..4479b4aff2 100644
> --- a/northd/en-northd.c
> +++ b/northd/en-northd.c
> @@ -19,6 +19,7 @@
>  #include <stdio.h>
>
>  #include "coverage.h"
> +#include "en-global-config.h"
>  #include "en-northd.h"
>  #include "en-lb-data.h"
>  #include "lib/inc-proc-eng.h"
> @@ -65,8 +66,6 @@ northd_get_input_data(struct engine_node *node,
>              engine_get_input("SB_fdb", node),
>              "sbrec_fdb_by_dp_and_port");
>
> -    input_data->nbrec_nb_global_table =
> -        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
>      input_data->nbrec_logical_switch_table =
>          EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
>      input_data->nbrec_logical_router_table =
> @@ -78,8 +77,6 @@ northd_get_input_data(struct engine_node *node,
>      input_data->nbrec_mirror_table =
>          EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>
> -    input_data->sbrec_sb_global_table =
> -        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>      input_data->sbrec_datapath_binding_table =
>          EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node));
>      input_data->sbrec_port_binding_table =
> @@ -109,6 +106,14 @@ northd_get_input_data(struct engine_node *node,
>          engine_get_input_data("lb_data", node);
>      input_data->lbs = &lb_data->lbs;
>      input_data->lbgrps = &lb_data->lbgrps;
> +
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
> +    input_data->nb_options = &global_config->nb_options;
> +    input_data->sb_options = &global_config->sb_options;
> +    input_data->svc_monitor_mac = global_config->svc_monitor_mac;
> +    input_data->svc_monitor_mac_ea = global_config->svc_monitor_mac_ea;
> +    input_data->features = &global_config->features;
>  }
>
>  void
> @@ -129,31 +134,6 @@ en_northd_run(struct engine_node *node, void *data)
>                   eng_ctx->ovnsb_idl_txn);
>      stopwatch_stop(OVNNB_DB_RUN_STOPWATCH_NAME, time_msec());
>      engine_set_node_state(node, EN_UPDATED);
> -
> -}
> -
> -bool
> -northd_nb_nb_global_handler(struct engine_node *node,
> -                            void *data OVS_UNUSED)
> -{
> -    const struct nbrec_nb_global_table *nb_global_table
> -        = EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> -
> -    const struct nbrec_nb_global *nb =
> -        nbrec_nb_global_table_first(nb_global_table);
> -
> -    if (!nb) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_WARN_RL(&rl, "NB_Global is updated but has no record.");
> -        return false;
> -    }
> -
> -    /* We care about the 'options' and 'ipsec' columns only. */
> -    if (nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_OPTIONS) ||
> -        nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_IPSEC)) {
> -        return false;
> -    }
> -    return true;
>  }
>
>  bool
> @@ -242,6 +222,20 @@ northd_lb_data_handler(struct engine_node *node, void *data)
>      return true;
>  }
>
> +bool
> +northd_global_config_handler(struct engine_node *node, void *data OVS_UNUSED)
> +{
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
> +
> +    if (!global_config->tracked
> +        || global_config->tracked_data.nb_options_changed) {
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
>  void
>  *en_northd_init(struct engine_node *node OVS_UNUSED,
>                  struct engine_arg *arg OVS_UNUSED)
> diff --git a/northd/en-northd.h b/northd/en-northd.h
> index 5a88871760..9b7bda32aa 100644
> --- a/northd/en-northd.h
> +++ b/northd/en-northd.h
> @@ -14,7 +14,7 @@ void *en_northd_init(struct engine_node *node OVS_UNUSED,
>                       struct engine_arg *arg);
>  void en_northd_cleanup(void *data);
>  void en_northd_clear_tracked_data(void *data);
> -bool northd_nb_nb_global_handler(struct engine_node *, void *data OVS_UNUSED);
> +bool northd_global_config_handler(struct engine_node *, void *data OVS_UNUSED);
>  bool northd_nb_logical_switch_handler(struct engine_node *, void *data);
>  bool northd_nb_logical_router_handler(struct engine_node *, void *data);
>  bool northd_sb_port_binding_handler(struct engine_node *, void *data);
> diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
> index 9002729b39..9bd8a1fc61 100644
> --- a/northd/en-sync-sb.c
> +++ b/northd/en-sync-sb.c
> @@ -24,6 +24,7 @@
>
>  /* OVN includes. */
>  #include "en-lr-nat.h"
> +#include "en-global-config.h"
>  #include "en-lr-stateful.h"
>  #include "en-sync-sb.h"
>  #include "lb.h"
> @@ -47,7 +48,8 @@ static void sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
>                             const struct nbrec_port_group_table *,
>                             const struct sbrec_address_set_table *,
>                             const struct lr_stateful_table *,
> -                           const struct ovn_datapaths *);
> +                           const struct ovn_datapaths *,
> +                           const char *svc_monitor_macp);
>  static const struct sbrec_address_set *sb_address_set_lookup_by_name(
>      struct ovsdb_idl_index *, const char *name);
>  static void update_sb_addr_set(struct sorted_array *,
> @@ -96,10 +98,13 @@ en_sync_to_sb_addr_set_run(struct engine_node *node, void *data OVS_UNUSED)
>      const struct ed_type_lr_stateful *lr_stateful_data =
>          engine_get_input_data("lr_stateful", node);
>      struct northd_data *northd_data = engine_get_input_data("northd", node);
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
>      sync_addr_sets(eng_ctx->ovnsb_idl_txn, nb_address_set_table,
>                     nb_port_group_table, sb_address_set_table,
>                     &lr_stateful_data->table,
> -                   &northd_data->lr_datapaths);
> +                   &northd_data->lr_datapaths,
> +                   global_config->svc_monitor_mac);
>
>      engine_set_node_state(node, EN_UPDATED);
>  }
> @@ -283,6 +288,8 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
>          EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
>      const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
>          EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
>      const struct engine_context *eng_ctx = engine_get_context();
>      struct ed_type_sync_to_sb_lb_data *data = data_;
>
> @@ -293,7 +300,7 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
>                                 &northd_data->lb_datapaths_map,
>                                 &northd_data->ls_datapaths,
>                                 &northd_data->lr_datapaths,
> -                               &northd_data->features);
> +                               &global_config->features);
>
>      engine_set_node_state(node, EN_UPDATED);
>  }
> @@ -324,12 +331,14 @@ sync_to_sb_lb_northd_handler(struct engine_node *node, void *data_)
>          EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
>      const struct sbrec_load_balancer_table *sb_lb_table =
>          EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> +    struct ed_type_global_config *global_config =
> +        engine_get_input_data("global_config", node);
>      struct ed_type_sync_to_sb_lb_data *data = data_;
>
>      if (!sync_changed_lbs(&data->sb_lbs, eng_ctx->ovnsb_idl_txn, sb_lb_table,
>                            sb_dpgrp_table, &nd->trk_data.trk_lbs,
>                            &nd->ls_datapaths, &nd->lr_datapaths,
> -                          &nd->features)) {
> +                          &global_config->features)) {
>          return false;
>      }
>
> @@ -455,7 +464,8 @@ sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
>                 const struct nbrec_port_group_table *nb_port_group_table,
>                 const struct sbrec_address_set_table *sb_address_set_table,
>                 const struct lr_stateful_table *lr_statefuls,
> -               const struct ovn_datapaths *lr_datapaths)
> +               const struct ovn_datapaths *lr_datapaths,
> +               const char *svc_monitor_macp)
>  {
>      struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets);
>
> @@ -466,7 +476,6 @@ sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
>      }
>
>      /* Service monitor MAC. */
> -    const char *svc_monitor_macp = northd_get_svc_monitor_mac();
>      struct sorted_array svc = sorted_array_create(&svc_monitor_macp, 1, false);
>      sync_addr_set(ovnsb_txn, "svc_monitor_mac", &svc, &sb_address_sets);
>      sorted_array_destroy(&svc);
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index d215c7792b..e1073812c8 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -30,6 +30,7 @@
>  #include "openvswitch/poll-loop.h"
>  #include "openvswitch/vlog.h"
>  #include "inc-proc-northd.h"
> +#include "en-global-config.h"
>  #include "en-lb-data.h"
>  #include "en-lr-stateful.h"
>  #include "en-lr-nat.h"
> @@ -149,6 +150,7 @@ static ENGINE_NODE(fdb_aging, "fdb_aging");
>  static ENGINE_NODE(fdb_aging_waker, "fdb_aging_waker");
>  static ENGINE_NODE(sync_to_sb_lb, "sync_to_sb_lb");
>  static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
> +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(global_config, "global_config");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_stateful, "lr_stateful");
> @@ -168,11 +170,17 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_lb_data, &en_nb_logical_router,
>                       lb_data_logical_router_handler);
>
> +    engine_add_input(&en_global_config, &en_nb_nb_global,
> +                     global_config_nb_global_handler);
> +    engine_add_input(&en_global_config, &en_sb_sb_global,
> +                     global_config_sb_global_handler);
> +    engine_add_input(&en_global_config, &en_sb_chassis,
> +                     global_config_sb_chassis_handler);
> +
>      engine_add_input(&en_northd, &en_nb_mirror, NULL);
>      engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
>      engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL);
>
> -    engine_add_input(&en_northd, &en_sb_sb_global, NULL);
>      engine_add_input(&en_northd, &en_sb_chassis, NULL);
>      engine_add_input(&en_northd, &en_sb_mirror, NULL);
>      engine_add_input(&en_northd, &en_sb_meter, NULL);
> @@ -184,6 +192,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_sb_fdb, NULL);
>      engine_add_input(&en_northd, &en_sb_static_mac_binding, NULL);
>      engine_add_input(&en_northd, &en_sb_chassis_template_var, NULL);
> +    engine_add_input(&en_northd, &en_global_config,
> +                     northd_global_config_handler);
>
>      /* northd engine node uses the sb mac binding table to
>       * cleanup mac binding entries for deleted logical ports
> @@ -198,8 +208,6 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>
>      engine_add_input(&en_northd, &en_sb_port_binding,
>                       northd_sb_port_binding_handler);
> -    engine_add_input(&en_northd, &en_nb_nb_global,
> -                     northd_nb_nb_global_handler);
>      engine_add_input(&en_northd, &en_nb_logical_switch,
>                       northd_nb_logical_switch_handler);
>      engine_add_input(&en_northd, &en_nb_logical_router,
> @@ -217,15 +225,17 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_ls_stateful, &en_port_group,
>                       ls_stateful_port_group_handler);
>
> -    engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_mac_binding_aging_waker, NULL);
> +    engine_add_input(&en_mac_binding_aging, &en_global_config,
> +                     node_global_config_handler);
>
> -    engine_add_input(&en_fdb_aging, &en_nb_nb_global, NULL);
>      engine_add_input(&en_fdb_aging, &en_sb_fdb, NULL);
>      engine_add_input(&en_fdb_aging, &en_northd, NULL);
>      engine_add_input(&en_fdb_aging, &en_fdb_aging_waker, NULL);
> +    engine_add_input(&en_fdb_aging, &en_global_config,
> +                     node_global_config_handler);
>
>      engine_add_input(&en_sync_meters, &en_nb_acl, NULL);
>      engine_add_input(&en_sync_meters, &en_nb_meter, NULL);
> @@ -239,18 +249,22 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_lflow, &en_sb_multicast_group, NULL);
>      engine_add_input(&en_lflow, &en_sb_igmp_group, NULL);
>      engine_add_input(&en_lflow, &en_sb_logical_dp_group, NULL);
> +    engine_add_input(&en_lflow, &en_global_config,
> +                     node_global_config_handler);
>      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
>      engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
>      engine_add_input(&en_lflow, &en_lr_stateful, lflow_lr_stateful_handler);
>      engine_add_input(&en_lflow, &en_ls_stateful, lflow_ls_stateful_handler);
>
> +    engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
> +    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
> +    engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
>      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
>                       sync_to_sb_addr_set_nb_address_set_handler);
>      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_port_group,
>                       sync_to_sb_addr_set_nb_port_group_handler);
> -    engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
> -    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
> -    engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
> +    engine_add_input(&en_sync_to_sb_addr_set, &en_global_config,
> +                     node_global_config_handler);
>
>      engine_add_input(&en_port_group, &en_nb_port_group,
>                       port_group_nb_port_group_handler);
> @@ -260,6 +274,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>       * table too (because of the explicit dependency in the schema). */
>      engine_add_input(&en_port_group, &en_northd, engine_noop_handler);
>
> +    engine_add_input(&en_sync_to_sb_lb, &en_global_config,
> +                     node_global_config_handler);
>      engine_add_input(&en_sync_to_sb_lb, &en_northd,
>                       sync_to_sb_lb_northd_handler);
>      engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
> @@ -365,11 +381,11 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>                                  "sbrec_fdb_by_dp_and_port",
>                                  sbrec_fdb_by_dp_and_port);
>
> -    struct northd_data *northd_data =
> -        engine_get_internal_data(&en_northd);
> +    struct ed_type_global_config *global_config =
> +        engine_get_internal_data(&en_global_config);
>      unixctl_command_register("debug/chassis-features-list", "", 0, 0,
>                               chassis_features_list,
> -                             &northd_data->features);
> +                             &global_config->features);
>  }
>
>  /* Returns true if the incremental processing ended up updating nodes. */
> diff --git a/northd/northd.c b/northd/northd.c
> index 574cd1d116..7a887f2722 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -45,6 +45,7 @@
>  #include "lflow-mgr.h"
>  #include "memory.h"
>  #include "northd.h"
> +#include "en-global-config.h"
>  #include "en-lb-data.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
> @@ -79,14 +80,6 @@ static bool install_ls_lb_from_router;
>  /* Use common zone for SNAT and DNAT if this option is set to "true". */
>  static bool use_common_zone = false;
>
> -/* MAC allocated for service monitor usage. Just one mac is allocatedg5534
> - * for this purpose and ovn-controller's on each chassis will make use
> - * of this mac when sending out the packets to monitor the services
> - * defined in Service_Monitor Southbound table. Since these packets
> - * all locally handled, having just one mac is good enough. */
> -static char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
> -static struct eth_addr svc_monitor_mac_ea;
> -
>  /* If this option is 'true' northd will make use of ct.inv match fields.
>   * Otherwise, it will avoid using it.  The default is true. */
>  static bool use_ct_inv_match = true;
> @@ -297,66 +290,6 @@ ovn_stage_to_datapath_type(enum ovn_stage stage)
>      }
>  }
>
> -static void
> -build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
> -                       struct chassis_features *chassis_features)
> -{
> -    const struct sbrec_chassis *chassis;
> -
> -    SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) {
> -        /* Only consider local AZ chassis.  Remote ones don't install
> -         * flows generated by the local northd.
> -         */
> -        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
> -            continue;
> -        }
> -
> -        bool ct_no_masked_label =
> -            smap_get_bool(&chassis->other_config,
> -                          OVN_FEATURE_CT_NO_MASKED_LABEL,
> -                          false);
> -        if (!ct_no_masked_label && chassis_features->ct_no_masked_label) {
> -            chassis_features->ct_no_masked_label = false;
> -        }
> -
> -        bool mac_binding_timestamp =
> -            smap_get_bool(&chassis->other_config,
> -                          OVN_FEATURE_MAC_BINDING_TIMESTAMP,
> -                          false);
> -        if (!mac_binding_timestamp &&
> -            chassis_features->mac_binding_timestamp) {
> -            chassis_features->mac_binding_timestamp = false;
> -        }
> -
> -        bool ct_lb_related =
> -            smap_get_bool(&chassis->other_config,
> -                          OVN_FEATURE_CT_LB_RELATED,
> -                          false);
> -        if (!ct_lb_related &&
> -            chassis_features->ct_lb_related) {
> -            chassis_features->ct_lb_related = false;
> -        }
> -
> -        bool fdb_timestamp =
> -            smap_get_bool(&chassis->other_config,
> -                          OVN_FEATURE_FDB_TIMESTAMP,
> -                          false);
> -        if (!fdb_timestamp &&
> -            chassis_features->fdb_timestamp) {
> -            chassis_features->fdb_timestamp = false;
> -        }
> -
> -        bool ls_dpg_column =
> -            smap_get_bool(&chassis->other_config,
> -                          OVN_FEATURE_LS_DPG_COLUMN,
> -                          false);
> -        if (!ls_dpg_column &&
> -            chassis_features->ls_dpg_column) {
> -            chassis_features->ls_dpg_column = false;
> -        }
> -    }
> -}
> -
>  static uint32_t
>  allocate_queueid(unsigned long *queue_id_bitmap)
>  {
> @@ -954,7 +887,7 @@ is_vxlan_mode(const struct sbrec_chassis_table *sbrec_chassis_table)
>      return false;
>  }
>
> -static uint32_t
> +uint32_t
>  get_ovn_max_dp_key_local(const struct sbrec_chassis_table *sbrec_chassis_table)
>  {
>      if (is_vxlan_mode(sbrec_chassis_table)) {
> @@ -3367,6 +3300,8 @@ create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
>  static void
>  ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
>                    const struct ovn_northd_lb *lb,
> +                  const char *svc_monitor_mac,
> +                  const struct eth_addr *svc_monitor_mac_ea,
>                    struct hmap *monitor_map, struct hmap *ls_ports,
>                    struct sset *svc_monitor_lsps)
>  {
> @@ -3412,7 +3347,7 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
>              struct eth_addr ea;
>              if (!mon_info->sbrec_mon->src_mac ||
>                  !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) ||
> -                !eth_addr_equals(ea, svc_monitor_mac_ea)) {
> +                !eth_addr_equals(ea, *svc_monitor_mac_ea)) {
>                  sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon,
>                                                    svc_monitor_mac);
>              }
> @@ -3624,6 +3559,8 @@ static void
>  build_lb_svcs(
>      struct ovsdb_idl_txn *ovnsb_txn,
>      const struct sbrec_service_monitor_table *sbrec_service_monitor_table,
> +    const char *svc_monitor_mac,
> +    const struct eth_addr *svc_monitor_mac_ea,
>      struct hmap *ls_ports, struct hmap *lb_dps_map,
>      struct sset *svc_monitor_lsps,
>      struct hmap *svc_monitor_map)
> @@ -3642,7 +3579,8 @@ build_lb_svcs(
>
>      struct ovn_lb_datapaths *lb_dps;
>      HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
> -        ovn_lb_svc_create(ovnsb_txn, lb_dps->lb, svc_monitor_map, ls_ports,
> +        ovn_lb_svc_create(ovnsb_txn, lb_dps->lb, svc_monitor_mac,
> +                          svc_monitor_mac_ea, svc_monitor_map, ls_ports,
>                            svc_monitor_lsps);
>      }
>
> @@ -3713,12 +3651,15 @@ static void
>  build_lb_port_related_data(
>      struct ovsdb_idl_txn *ovnsb_txn,
>      const struct sbrec_service_monitor_table *sbrec_service_monitor_table,
> +    const char *svc_monitor_mac,
> +    const struct eth_addr *svc_monitor_mac_ea,
>      struct ovn_datapaths *lr_datapaths, struct hmap *ls_ports,
>      struct hmap *lb_dps_map, struct hmap *lb_group_dps_map,
>      struct sset *svc_monitor_lsps,
>      struct hmap *svc_monitor_map)
>  {
> -    build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, ls_ports, lb_dps_map,
> +    build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, svc_monitor_mac,
> +                  svc_monitor_mac_ea, ls_ports, lb_dps_map,
>                    svc_monitor_lsps, svc_monitor_map);
>      build_lswitch_lbs_from_lrouter(lr_datapaths, lb_dps_map, lb_group_dps_map);
>  }
> @@ -9047,6 +8988,7 @@ build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
>  static void
>  build_lswitch_arp_nd_service_monitor(const struct ovn_lb_datapaths *lb_dps,
>                                       const struct hmap *ls_ports,
> +                                     const char *svc_monitor_mac,
>                                       struct lflow_table *lflows,
>                                       struct ds *actions,
>                                       struct ds *match)
> @@ -15532,6 +15474,7 @@ struct lswitch_flow_build_info {
>      struct ds match;
>      struct ds actions;
>      size_t thread_lflow_counter;
> +    const char *svc_monitor_mac;
>  };
>
>  /* Helper function to combine all lflow generation which is iterated by
> @@ -15762,6 +15705,7 @@ build_lflows_thread(void *arg)
>                      }
>                      build_lswitch_arp_nd_service_monitor(lb_dps,
>                                                           lsi->ls_ports,
> +                                                         lsi->svc_monitor_mac,
>                                                           lsi->lflows,
>                                                           &lsi->match,
>                                                           &lsi->actions);
> @@ -15888,7 +15832,8 @@ build_lswitch_and_lrouter_flows(
>      const struct hmap *lb_dps_map,
>      const struct hmap *svc_monitor_map,
>      const struct hmap *bfd_connections,
> -    const struct chassis_features *features)
> +    const struct chassis_features *features,
> +    const char *svc_monitor_mac)
>  {
>
>      char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
> @@ -15921,6 +15866,7 @@ build_lswitch_and_lrouter_flows(
>              lsiv[index].features = features;
>              lsiv[index].svc_check_match = svc_check_match;
>              lsiv[index].thread_lflow_counter = 0;
> +            lsiv[index].svc_monitor_mac = svc_monitor_mac;
>              ds_init(&lsiv[index].match);
>              ds_init(&lsiv[index].actions);
>
> @@ -15960,6 +15906,7 @@ build_lswitch_and_lrouter_flows(
>              .bfd_connections = bfd_connections,
>              .features = features,
>              .svc_check_match = svc_check_match,
> +            .svc_monitor_mac = svc_monitor_mac,
>              .match = DS_EMPTY_INITIALIZER,
>              .actions = DS_EMPTY_INITIALIZER,
>          };
> @@ -15999,6 +15946,7 @@ build_lswitch_and_lrouter_flows(
>          stopwatch_start(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
>          HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
>              build_lswitch_arp_nd_service_monitor(lb_dps, lsi.ls_ports,
> +                                                 lsi.svc_monitor_mac,
>                                                   lsi.lflows, &lsi.actions,
>                                                   &lsi.match);
>              build_lrouter_defrag_flows_for_lb(lb_dps, lsi.lflows,
> @@ -16119,7 +16067,8 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
>                                      input_data->lb_datapaths_map,
>                                      input_data->svc_monitor_map,
>                                      input_data->bfd_connections,
> -                                    input_data->features);
> +                                    input_data->features,
> +                                    input_data->svc_monitor_mac);
>
>      if (parallelization_state == STATE_INIT_HASH_SIZES) {
>          parallelization_state = STATE_USE_PARALLELIZATION;
> @@ -16401,6 +16350,7 @@ lflow_handle_northd_lb_changes(struct ovsdb_idl_txn *ovnsb_txn,
>          struct ds actions = DS_EMPTY_INITIALIZER;
>
>          build_lswitch_arp_nd_service_monitor(lb_dps, lflow_input->ls_ports,
> +                                             lflow_input->svc_monitor_mac,
>                                               lflows, &actions,
>                                               &match);
>          build_lrouter_defrag_flows_for_lb(lb_dps, lflows,
> @@ -17179,18 +17129,6 @@ destroy_datapaths_and_ports(struct ovn_datapaths *ls_datapaths,
>      ovn_datapaths_destroy(lr_datapaths);
>  }
>
> -static void
> -northd_enable_all_features(struct northd_data *data)
> -{
> -    data->features = (struct chassis_features) {
> -        .ct_no_masked_label = true,
> -        .mac_binding_timestamp = true,
> -        .ct_lb_related = true,
> -        .fdb_timestamp = true,
> -        .ls_dpg_column = true,
> -    };
> -}
> -
>  void
>  northd_init(struct northd_data *data)
>  {
> @@ -17201,8 +17139,6 @@ northd_init(struct northd_data *data)
>      hmap_init(&data->lb_datapaths_map);
>      hmap_init(&data->lb_group_datapaths_map);
>      ovs_list_init(&data->lr_list);
> -    northd_enable_all_features(data);
> -    data->ovn_internal_version_changed = false;
>      sset_init(&data->svc_monitor_lsps);
>      hmap_init(&data->svc_monitor_map);
>      init_northd_tracked_data(data);
> @@ -17242,7 +17178,6 @@ northd_destroy(struct northd_data *data)
>      destroy_datapaths_and_ports(&data->ls_datapaths, &data->lr_datapaths,
>                                  &data->ls_ports, &data->lr_ports,
>                                  &data->lr_list);
> -    destroy_debug_config();
>
>      sset_destroy(&data->svc_monitor_lsps);
>      destroy_northd_tracked_data(data);
> @@ -17259,83 +17194,22 @@ ovnnb_db_run(struct northd_input *input_data,
>      }
>      stopwatch_start(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>
> -    /* Sync ipsec configuration.
> -     * Copy nb_cfg from northbound to southbound database.
> -     * Also set up to update sb_cfg once our southbound transaction commits. */
> -    const struct nbrec_nb_global *nb = nbrec_nb_global_table_first(
> -                                       input_data->nbrec_nb_global_table);
> -    if (!nb) {
> -        nb = nbrec_nb_global_insert(ovnnb_txn);
> -    }
> -
> -    const char *mac_addr_prefix = set_mac_prefix(smap_get(&nb->options,
> -                                                          "mac_prefix"));
> -
> -    const char *monitor_mac = smap_get(&nb->options, "svc_monitor_mac");
> -    if (monitor_mac) {
> -        if (eth_addr_from_string(monitor_mac, &svc_monitor_mac_ea)) {
> -            snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
> -                     ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
> -        } else {
> -            monitor_mac = NULL;
> -        }
> -    }
> -
> -    struct smap options;
> -    smap_clone(&options, &nb->options);
> -
> -    smap_replace(&options, "mac_prefix", mac_addr_prefix);
> -
> -    if (!monitor_mac) {
> -        eth_addr_random(&svc_monitor_mac_ea);
> -        snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
> -                 ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
> -        smap_replace(&options, "svc_monitor_mac", svc_monitor_mac);
> -    }
> -
> -    char *max_tunid = xasprintf("%d",
> -        get_ovn_max_dp_key_local(input_data->sbrec_chassis_table));
> -    smap_replace(&options, "max_tunid", max_tunid);
> -    free(max_tunid);
> -
> -    char *ovn_internal_version = ovn_get_internal_version();
> -    if (!strcmp(ovn_internal_version,
> -                smap_get_def(&options, "northd_internal_version", ""))) {
> -        data->ovn_internal_version_changed = false;
> -    } else {
> -        smap_replace(&options, "northd_internal_version",
> -                     ovn_internal_version);
> -    }
> -    free(ovn_internal_version);
> -
> -    if (!smap_equal(&nb->options, &options)) {
> -        nbrec_nb_global_verify_options(nb);
> -        nbrec_nb_global_set_options(nb, &options);
> -    }
> -
> -    use_ct_inv_match = smap_get_bool(&nb->options,
> +    use_ct_inv_match = smap_get_bool(input_data->nb_options,
>                                       "use_ct_inv_match", true);
>
>      /* deprecated, use --event instead */
> -    controller_event_en = smap_get_bool(&nb->options,
> +    controller_event_en = smap_get_bool(input_data->nb_options,
>                                          "controller_event", false);
> -    check_lsp_is_up = !smap_get_bool(&nb->options,
> +    check_lsp_is_up = !smap_get_bool(input_data->nb_options,
>                                       "ignore_lsp_down", true);
> -    default_acl_drop = smap_get_bool(&nb->options, "default_acl_drop", false);
> +    default_acl_drop = smap_get_bool(input_data->nb_options,
> +                                     "default_acl_drop", false);
>
> -    install_ls_lb_from_router = smap_get_bool(&nb->options,
> +    install_ls_lb_from_router = smap_get_bool(input_data->nb_options,
>                                                "install_ls_lb_from_router",
>                                                false);
> -    use_common_zone = smap_get_bool(&nb->options, "use_common_zone", false);
> -
> -    if (smap_get_bool(&nb->options, "ignore_chassis_features", false)) {
> -        northd_enable_all_features(data);
> -    } else {
> -        build_chassis_features(input_data->sbrec_chassis_table,
> -                               &data->features);
> -    }
> -
> -    init_debug_config(nb);
> +    use_common_zone = smap_get_bool(input_data->nb_options, "use_common_zone",
> +                                    false);
>
>      build_datapaths(ovnsb_txn,
>                      input_data->nbrec_logical_switch_table,
> @@ -17360,6 +17234,8 @@ ovnnb_db_run(struct northd_input *input_data,
>                  &data->ls_ports, &data->lr_ports);
>      build_lb_port_related_data(ovnsb_txn,
>                                 input_data->sbrec_service_monitor_table,
> +                               input_data->svc_monitor_mac,
> +                               &input_data->svc_monitor_mac_ea,
>                                 &data->lr_datapaths, &data->ls_ports,
>                                 &data->lb_datapaths_map,
>                                 &data->lb_group_datapaths_map,
> @@ -17392,38 +17268,6 @@ ovnnb_db_run(struct northd_input *input_data,
>                                &data->ls_datapaths.datapaths);
>      stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>
> -    /* Set up SB_Global (depends on chassis features). */
> -    const struct sbrec_sb_global *sb = sbrec_sb_global_table_first(
> -                                       input_data->sbrec_sb_global_table);
> -    if (!sb) {
> -        sb = sbrec_sb_global_insert(ovnsb_txn);
> -    }
> -    if (nb->ipsec != sb->ipsec) {
> -        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
> -    }
> -
> -    /* Inform ovn-controllers whether LB flows will use ct_mark (i.e., only
> -     * if all chassis support it).  If not explicitly present in the database
> -     * the default value to be used for this option is 'true'.
> -     */
> -    if (!data->features.ct_no_masked_label) {
> -        smap_replace(&options, "lb_hairpin_use_ct_mark", "false");
> -    } else {
> -        smap_remove(&options, "lb_hairpin_use_ct_mark");
> -    }
> -
> -    /* Hackaround SB_global.options overwrite by NB_Global.options for
> -     * 'sbctl_probe_interval' option.
> -     */
> -    const char *sip = smap_get(&sb->options, "sbctl_probe_interval");
> -    if (sip) {
> -        smap_replace(&options, "sbctl_probe_interval", sip);
> -    }
> -
> -    if (!smap_equal(&sb->options, &options)) {
> -        sbrec_sb_global_set_options(sb, &options);
> -    }
> -    smap_destroy(&options);
>  }
>
>  /* Stores the set of chassis which references an ha_chassis_group.
> @@ -17714,12 +17558,6 @@ ovnsb_db_run(struct ovsdb_idl_txn *ovnnb_txn,
>      ovn_update_ipv6_prefix(lr_ports);
>  }
>
> -const char *
> -northd_get_svc_monitor_mac(void)
> -{
> -    return svc_monitor_mac;
> -}
> -
>  const struct ovn_datapath *
>  northd_get_datapath_for_port(const struct hmap *ls_ports,
>                               const char *port_name)
> diff --git a/northd/northd.h b/northd/northd.h
> index 9d001206c1..b5c175929e 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -27,7 +27,6 @@
>
>  struct northd_input {
>      /* Northbound table references */
> -    const struct nbrec_nb_global_table *nbrec_nb_global_table;
>      const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
>      const struct nbrec_logical_router_table *nbrec_logical_router_table;
>      const struct nbrec_static_mac_binding_table
> @@ -37,7 +36,6 @@ struct northd_input {
>      const struct nbrec_mirror_table *nbrec_mirror_table;
>
>      /* Southbound table references */
> -    const struct sbrec_sb_global_table *sbrec_sb_global_table;
>      const struct sbrec_datapath_binding_table *sbrec_datapath_binding_table;
>      const struct sbrec_port_binding_table *sbrec_port_binding_table;
>      const struct sbrec_mac_binding_table *sbrec_mac_binding_table;
> @@ -57,6 +55,13 @@ struct northd_input {
>      const struct hmap *lbs;
>      const struct hmap *lbgrps;
>
> +    /* Global config data node inputs. */
> +    const struct smap *nb_options;
> +    const struct smap *sb_options;
> +    const char *svc_monitor_mac;
> +    struct eth_addr svc_monitor_mac_ea;
> +    const struct chassis_features *features;
> +
>      /* Indexes */
>      struct ovsdb_idl_index *sbrec_chassis_by_name;
>      struct ovsdb_idl_index *sbrec_chassis_by_hostname;
> @@ -66,14 +71,6 @@ struct northd_input {
>      struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port;
>  };
>
> -struct chassis_features {
> -    bool ct_no_masked_label;
> -    bool mac_binding_timestamp;
> -    bool ct_lb_related;
> -    bool fdb_timestamp;
> -    bool ls_dpg_column;
> -};
> -
>  /* A collection of datapaths. E.g. all logical switch datapaths, or all
>   * logical router datapaths. */
>  struct ovn_datapaths {
> @@ -156,8 +153,6 @@ struct northd_data {
>      struct hmap lb_datapaths_map;
>      struct hmap lb_group_datapaths_map;
>      struct ovs_list lr_list;
> -    bool ovn_internal_version_changed;
> -    struct chassis_features features;
>      struct sset svc_monitor_lsps;
>      struct hmap svc_monitor_map;
>
> @@ -194,6 +189,7 @@ struct lflow_input {
>      const struct chassis_features *features;
>      const struct hmap *svc_monitor_map;
>      bool ovn_internal_version_changed;
> +    const char *svc_monitor_mac;
>  };
>
>  extern int parallelization_state;
> @@ -722,8 +718,6 @@ void bfd_cleanup_connections(const struct nbrec_bfd_table *,
>                               struct hmap *bfd_map);
>  void run_update_worker_pool(int n_threads);
>
> -const char *northd_get_svc_monitor_mac(void);
> -
>  const struct ovn_datapath *northd_get_datapath_for_port(
>      const struct hmap *ls_ports, const char *port_name);
>
> @@ -787,4 +781,6 @@ lr_has_multiple_gw_ports(const struct ovn_datapath *od)
>      return od->n_l3dgw_ports > 1 && !od->is_gw_router;
>  }
>
> +uint32_t get_ovn_max_dp_key_local(const struct sbrec_chassis_table *);
> +
>  #endif /* NORTHD_H */
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 929bb45aed..d8ec3eead5 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -8838,7 +8838,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
>    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
>  ])
>
> -ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
> +check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
>
>  ovn-sbctl dump-flows S0 > S0flows
>  ovn-sbctl dump-flows S1 > S1flows
> @@ -8857,6 +8857,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
>    table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 172.16.0.11 && tcp.dst == 8080), action=(reg0[[1]] = 0; ct_lb_mark(backends=10.0.0.2:8080);)
>  ])
>
> +
>  ovn-sbctl get datapath S0 _uuid > dp_uuids
>  ovn-sbctl get datapath S1 _uuid >> dp_uuids
>  lb_dp_group=$(ovn-sbctl --bare --columns ls_datapath_group find Load_Balancer name=lb0)
> @@ -8865,7 +8866,7 @@ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
>  $(cat dp_uuids | sort)
>  ])
>
> -ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=false
> +check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=false
>
>  ovn-sbctl dump-flows S0 > S0flows
>  ovn-sbctl dump-flows S1 > S1flows
> @@ -9250,12 +9251,11 @@ $4
>  AS_BOX([Create new PG1 and PG2])
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>  check ovn-nbctl --wait=sb -- pg-add pg1 -- pg-add pg2
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl The port_group node recomputes every time a NB port group is added/deleted.
> @@ -9288,12 +9288,11 @@ check ovn-nbctl --wait=sb         \
>  check_column "sw1.1" sb:Port_Group ports name="${sw1_key}_pg1"
>  check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl The port_group node recomputes also every time a port from a new switch
> @@ -9325,12 +9324,11 @@ check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
>  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
>  check_column "sw2.2" sb:Port_Group ports name="${sw2_key}_pg2"
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl The port_group node recomputes also every time a port from a new switch
> @@ -9363,12 +9361,11 @@ check_column "sw2.1 sw2.3" sb:Port_Group ports name="${sw2_key}_pg1"
>  check_column "sw1.2 sw1.3" sb:Port_Group ports name="${sw1_key}_pg2"
>  check_column "sw2.2 sw2.3" sb:Port_Group ports name="${sw2_key}_pg2"
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl We did not change the set of switches a pg is applied to, there should be
> @@ -9406,7 +9403,7 @@ dnl though, therefore "compute: 1".
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl We did not change the set of switches a pg is applied to, there should be
> @@ -9438,12 +9435,11 @@ check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
>  AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
>  ])
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl We changed the set of switches a pg is applied to, there should be
> @@ -9476,12 +9472,11 @@ check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
>  AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
>  ])
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl We changed the set of switches a pg is applied to, there should be
> @@ -9514,12 +9509,11 @@ check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
>  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
>  check_column "sw2.2" sb:Port_Group ports name="${sw2_key}_pg2"
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl We changed the set of switches a pg is applied to, there should be a
> @@ -9554,12 +9548,11 @@ check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
>  AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
>  ])
>
> -dnl The northd node should not recompute, it should handle nb_global update
> -dnl though, therefore "compute: 1".
> +dnl The northd node should not recompute,.
>  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
>  Node: northd
>  - recompute:            0
> -- compute:              1
> +- compute:              0
>  - abort:                0
>  ])
>  dnl We changed the set of switches a pg is applied to, there should be a
> @@ -11922,3 +11915,212 @@ CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  OVN_CLEANUP([hv1])
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([NB_Global and SB_Global incremental processing])
> +
> +ovn_start
> +
> +check_engine_stats() {
> +  node=$1
> +  recompute=$2
> +  compute=$3
> +
> +  echo "__file__:__line__: Checking engine stats for node $node : recompute - \
> +$recompute : compute - $compute"
> +
> +  node_stat=$(as northd ovn-appctl -t ovn-northd inc-engine/show-stats $node)
> +  # node_stat will be of this format :
> +  #     - Node: lflow - recompute: 3 - compute: 0 - abort: 0
> +  node_recompute_ct=$(echo $node_stat | cut -d '-' -f2 | cut -d ':' -f2)
> +  node_compute_ct=$(echo $node_stat | cut -d '-' -f3 | cut -d ':' -f2)
> +
> +  if [[ "$recompute" == "norecompute" ]]; then
> +    # node should not be recomputed
> +    echo "Expecting $node recompute count - $node_recompute_ct to be 0"
> +    check test "$node_recompute_ct" -eq "0"
> +  else
> +    echo "Expecting $node recompute count - $node_recompute_ct not to be 0"
> +    check test "$node_recompute_ct" -ne "0"
> +  fi
> +
> +  if [[ "$compute" == "nocompute" ]]; then
> +    # node should not be computed
> +    echo "Expecting $node compute count - $node_compute_ct to be 0"
> +    check test "$node_compute_ct" -eq "0"
> +  else
> +    echo "Expecting $node compute count - $node_compute_ct not to be 0"
> +    check test "$node_compute_ct" -ne "0"
> +  fi
> +}
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 "00:00:20:20:00:03 10.0.0.3"
> +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24
> +check ovn-nbctl lsp-add sw0 sw0-lr0
> +check ovn-nbctl lsp-set-type sw0-lr0 router
> +check ovn-nbctl lsp-set-addresses sw0-lr0 router
> +check ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +
> +# This should not result in recomputes.
> +check ovn-nbctl --wait=sb set NB_Global . options:foo=bar
> +check_engine_stats global_config norecompute compute
> +check_engine_stats northd norecompute compute
> +check_engine_stats lflow norecompute compute
> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +# This should result in recomputes.
> +check ovn-sbctl set SB_Global . options:bar=foo
> +check_engine_stats global_config recompute compute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
> +
> +# Clears an nb option and checks that recomputes were triggered
> +# and the option was added back by ovn-northd or not depending
> +# on the 'added_back' argument.
> +clear_nb_option() {
> +  option=$1
> +  add_back=$2
> +  echo "clearing the nb option - $option"
> +  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +  check ovn-nbctl --wait=sb remove NB_Global . options $option
> +  check_engine_stats global_config recompute compute
> +  check_engine_stats northd recompute nocompute
> +  check_engine_stats lflow recompute nocompute
> +
> +  local retval=1
> +  if [ "$add_back" == "true" ]; then
> +    retval=0
> +  fi
> +  AT_CHECK([ovn-nbctl get NB_Global . options:$option], [$retval], [ignore], [ignore])
> +}
> +
> +# Clear svc_monitor_mac and few other options which result in recompute.
> +# and ovn-northd should update the nb options back.
> +clear_nb_option svc_monitor_mac true
> +clear_nb_option max_tunid true
> +clear_nb_option mac_prefix true
> +clear_nb_option northd_internal_version true
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-nbctl --wait=sb set NB_Global . options:ignore_chassis_features=true
> +check_engine_stats global_config recompute compute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +clear_nb_option ignore_chassis_features false
> +
> +set_nb_option_lflow_recompute() {
> +  local option=$1
> +  local value=$2
> +  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +  check ovn-nbctl --wait=sb set NB_Global . options:$option=$value
> +  check_engine_stats global_config norecompute compute
> +  check_engine_stats northd recompute nocompute
> +  check_engine_stats lflow recompute nocompute
> +  check_engine_stats mac_binding_aging recompute nocompute
> +  CHECK_NO_CHANGE_AFTER_RECOMPUTE
> +}
> +
> +clear_nb_option_lflow_recompute() {
> +  local option=$1
> +  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +  check ovn-nbctl --wait=sb remove NB_Global . options $option
> +  check_engine_stats global_config norecompute compute
> +  check_engine_stats northd recompute nocompute
> +  check_engine_stats lflow recompute nocompute
> +  check_engine_stats mac_binding_aging recompute nocompute
> +  CHECK_NO_CHANGE_AFTER_RECOMPUTE
> +}
> +
> +set_nb_option_lflow_recompute debug_drop_domain_id 1
> +clear_nb_option_lflow_recompute debug_drop_domain_id
> +
> +set_nb_option_lflow_recompute debug_drop_collector_set 1
> +clear_nb_option_lflow_recompute debug_drop_collector_set
> +
> +set_nb_option_lflow_recompute mac_binding_removal_limit 100
> +clear_nb_option_lflow_recompute mac_binding_removal_limit
> +
> +set_nb_option_lflow_recompute fdb_removal_limit 100
> +clear_nb_option_lflow_recompute fdb_removal_limit
> +
> +set_nb_option_lflow_recompute controller_event true
> +clear_nb_option_lflow_recompute controller_event
> +
> +set_nb_option_lflow_recompute ignore_lsp_down true
> +clear_nb_option_lflow_recompute ignore_lsp_down
> +
> +set_nb_option_lflow_recompute use_ct_inv_match true
> +clear_nb_option_lflow_recompute use_ct_inv_match
> +
> +set_nb_option_lflow_recompute default_acl_drop true
> +clear_nb_option_lflow_recompute default_acl_drop
> +
> +set_nb_option_lflow_recompute use_common_zone true
> +clear_nb_option_lflow_recompute use_common_zone
> +
> +set_nb_option_lflow_recompute install_ls_lb_from_router true
> +clear_nb_option_lflow_recompute install_ls_lb_from_router
> +
> +# Now test changes to chassis for feature changes.
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
> +check ovn-nbctl --wait=sb sync
> +check_engine_stats global_config recompute compute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-sbctl chassis-add ch2 geneve 127.0.0.2
> +check ovn-nbctl --wait=sb sync
> +check_engine_stats global_config recompute compute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +AT_CHECK([ovn-nbctl get NB_Global . options:max_tunid | \
> +sed s/":"//g | sed s/\"//g], [0], [16711680
> +], [])
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-sbctl chassis-del ch2
> +check ovn-nbctl --wait=sb sync
> +check_engine_stats global_config recompute compute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-sbctl set encap . type=vxlan
> +check ovn-nbctl --wait=sb sync
> +check_engine_stats global_config recompute compute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +AT_CHECK([ovn-nbctl get NB_Global . options:max_tunid | \
> +sed s/":"//g | sed s/\"//g], [0], [4095
> +], [])
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-sbctl set chassis . other_config:foo=bar
> +check ovn-nbctl --wait=sb sync
> +check_engine_stats global_config norecompute compute
> +check_engine_stats mac_binding_aging recompute nocompute
> +check_engine_stats fdb_aging recompute nocompute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-sbctl set chassis . other_config:ct-no-masked-label=true
> +check ovn-nbctl --wait=sb sync
> +check_engine_stats global_config norecompute compute
> +check_engine_stats mac_binding_aging recompute nocompute
> +check_engine_stats fdb_aging recompute nocompute
> +check_engine_stats northd recompute nocompute
> +check_engine_stats lflow recompute nocompute
> +
> +AT_CLEANUP
> +])
> --
> 2.43.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Numan Siddique Feb. 1, 2024, 4:59 p.m. UTC | #2
On Thu, Feb 1, 2024 at 10:13 AM Numan Siddique <numans@ovn.org> wrote:
>
> On Tue, Jan 30, 2024 at 4:25 PM <numans@ovn.org> wrote:
> >
> > From: Numan Siddique <numans@ovn.org>
> >
> > A new engine node "global_config" is added which handles the changes
> > to NB_Global an SB_Global tables.  It also creates these rows if
> > not present.
> >
> > Without the I-P, any changes to the options column of these tables
> > result in recompute of 'northd' and 'lflow' engine nodes.
> >
> > Acked-by: Dumitru Ceara <dceara@redhat.com>
> > Acked-by: Han Zhou <hzhou@ovn.org>
> > Signed-off-by: Numan Siddique <numans@ovn.org>
>
> Recheck-request: github-robot-_Build_and_Test

Recheck-request: github-robot-_Build_and_Test

>
> > ---
> >  northd/aging.c            |  21 +-
> >  northd/automake.mk        |   2 +
> >  northd/en-global-config.c | 576 ++++++++++++++++++++++++++++++++++++++
> >  northd/en-global-config.h |  65 +++++
> >  northd/en-lflow.c         |  11 +-
> >  northd/en-northd.c        |  52 ++--
> >  northd/en-northd.h        |   2 +-
> >  northd/en-sync-sb.c       |  21 +-
> >  northd/inc-proc-northd.c  |  38 ++-
> >  northd/northd.c           | 230 +++------------
> >  northd/northd.h           |  24 +-
> >  tests/ovn-northd.at       | 256 +++++++++++++++--
> >  12 files changed, 1001 insertions(+), 297 deletions(-)
> >  create mode 100644 northd/en-global-config.c
> >  create mode 100644 northd/en-global-config.h
> >
> > diff --git a/northd/aging.c b/northd/aging.c
> > index cdf5f4464e..b76963a2dd 100644
> > --- a/northd/aging.c
> > +++ b/northd/aging.c
> > @@ -15,6 +15,7 @@
> >
> >  #include <config.h>
> >
> > +#include "en-global-config.h"
> >  #include "lib/inc-proc-eng.h"
> >  #include "lib/ovn-nb-idl.h"
> >  #include "lib/ovn-sb-idl.h"
> > @@ -347,15 +348,10 @@ aging_context_handle_timestamp(struct aging_context *ctx, int64_t timestamp,
> >  static uint32_t
> >  get_removal_limit(struct engine_node *node, const char *name)
> >  {
> > -    const struct nbrec_nb_global_table *nb_global_table =
> > -            EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> > -    const struct nbrec_nb_global *nb =
> > -            nbrec_nb_global_table_first(nb_global_table);
> > -    if (!nb) {
> > -        return 0;
> > -    }
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> >
> > -    return smap_get_uint(&nb->options, name, 0);
> > +    return smap_get_uint(&global_config->nb_options, name, 0);
> >  }
> >
> >  /* MAC binding aging */
> > @@ -394,11 +390,14 @@ en_mac_binding_aging_run(struct engine_node *node, void *data OVS_UNUSED)
> >  {
> >      const struct engine_context *eng_ctx = engine_get_context();
> >      struct northd_data *northd_data = engine_get_input_data("northd", node);
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> > +
> >      struct aging_waker *waker =
> >          engine_get_input_data("mac_binding_aging_waker", node);
> >
> >      if (!eng_ctx->ovnsb_idl_txn ||
> > -        !northd_data->features.mac_binding_timestamp ||
> > +        !global_config->features.mac_binding_timestamp ||
> >          time_msec() < waker->next_wake_msec) {
> >          return;
> >      }
> > @@ -530,9 +529,11 @@ en_fdb_aging_run(struct engine_node *node, void *data OVS_UNUSED)
> >      const struct engine_context *eng_ctx = engine_get_context();
> >      struct northd_data *northd_data = engine_get_input_data("northd", node);
> >      struct aging_waker *waker = engine_get_input_data("fdb_aging_waker", node);
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> >
> >      if (!eng_ctx->ovnsb_idl_txn ||
> > -        !northd_data->features.fdb_timestamp ||
> > +        !global_config->features.fdb_timestamp ||
> >          time_msec() < waker->next_wake_msec) {
> >          return;
> >      }
> > diff --git a/northd/automake.mk b/northd/automake.mk
> > index 19abb0dece..d491973a8b 100644
> > --- a/northd/automake.mk
> > +++ b/northd/automake.mk
> > @@ -8,6 +8,8 @@ northd_ovn_northd_SOURCES = \
> >         northd/northd.c \
> >         northd/northd.h \
> >         northd/ovn-northd.c \
> > +       northd/en-global-config.c \
> > +       northd/en-global-config.h \
> >         northd/en-northd.c \
> >         northd/en-northd.h \
> >         northd/en-lflow.c \
> > diff --git a/northd/en-global-config.c b/northd/en-global-config.c
> > new file mode 100644
> > index 0000000000..9ac5faf995
> > --- /dev/null
> > +++ b/northd/en-global-config.c
> > @@ -0,0 +1,576 @@
> > +/*
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +
> > +#include <config.h>
> > +
> > +#include <getopt.h>
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +
> > +/* OVS includes */
> > +#include "openvswitch/vlog.h"
> > +
> > +/* OVN includes */
> > +#include "debug.h"
> > +#include "en-global-config.h"
> > +#include "include/ovn/features.h"
> > +#include "ipam.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "northd.h"
> > +
> > +
> > +VLOG_DEFINE_THIS_MODULE(en_global_config);
> > +
> > +/* static function declarations. */
> > +static void northd_enable_all_features(struct ed_type_global_config *);
> > +static void build_chassis_features(const struct sbrec_chassis_table *,
> > +                                   struct chassis_features *);
> > +static bool chassis_features_changed(const struct chassis_features *,
> > +                                     const struct chassis_features *);
> > +static bool config_out_of_sync(const struct smap *config,
> > +                               const struct smap *saved_config,
> > +                               const char *key, bool must_be_present);
> > +static bool check_nb_options_out_of_sync(const struct nbrec_nb_global *,
> > +                                         struct ed_type_global_config *);
> > +static void update_sb_config_options_to_sbrec(struct ed_type_global_config *,
> > +                                              const struct sbrec_sb_global *);
> > +
> > +void *
> > +en_global_config_init(struct engine_node *node OVS_UNUSED,
> > +                      struct engine_arg *args OVS_UNUSED)
> > +{
> > +    struct ed_type_global_config *data = xzalloc(sizeof *data);
> > +    smap_init(&data->nb_options);
> > +    smap_init(&data->sb_options);
> > +    northd_enable_all_features(data);
> > +    return data;
> > +}
> > +
> > +void
> > +en_global_config_run(struct engine_node *node , void *data)
> > +{
> > +    const struct engine_context *eng_ctx = engine_get_context();
> > +    if (!eng_ctx->ovnnb_idl_txn || !eng_ctx->ovnsb_idl_txn) {
> > +        return;
> > +    }
> > +
> > +    const struct nbrec_nb_global_table *nb_global_table =
> > +        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> > +    const struct sbrec_sb_global_table *sb_global_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> > +    const struct sbrec_chassis_table *sbrec_chassis_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
> > +
> > +    struct ed_type_global_config *config_data = data;
> > +
> > +    /* Sync ipsec configuration.
> > +     * Copy nb_cfg from northbound to southbound database.
> > +     * Also set up to update sb_cfg once our southbound transaction commits. */
> > +    const struct nbrec_nb_global *nb =
> > +        nbrec_nb_global_table_first(nb_global_table);
> > +    if (!nb) {
> > +        nb = nbrec_nb_global_insert(eng_ctx->ovnnb_idl_txn);
> > +    }
> > +
> > +    const char *mac_addr_prefix = set_mac_prefix(smap_get(&nb->options,
> > +                                                          "mac_prefix"));
> > +
> > +    const char *monitor_mac = smap_get(&nb->options, "svc_monitor_mac");
> > +    if (monitor_mac) {
> > +        if (eth_addr_from_string(monitor_mac,
> > +                                 &config_data->svc_monitor_mac_ea)) {
> > +            snprintf(config_data->svc_monitor_mac,
> > +                     sizeof config_data->svc_monitor_mac,
> > +                     ETH_ADDR_FMT,
> > +                     ETH_ADDR_ARGS(config_data->svc_monitor_mac_ea));
> > +        } else {
> > +            monitor_mac = NULL;
> > +        }
> > +    }
> > +
> > +    struct smap *options = &config_data->nb_options;
> > +    smap_destroy(options);
> > +    smap_clone(options, &nb->options);
> > +
> > +    smap_replace(options, "mac_prefix", mac_addr_prefix);
> > +
> > +    if (!monitor_mac) {
> > +        eth_addr_random(&config_data->svc_monitor_mac_ea);
> > +        snprintf(config_data->svc_monitor_mac,
> > +                 sizeof config_data->svc_monitor_mac, ETH_ADDR_FMT,
> > +                 ETH_ADDR_ARGS(config_data->svc_monitor_mac_ea));
> > +        smap_replace(options, "svc_monitor_mac",
> > +                     config_data->svc_monitor_mac);
> > +    }
> > +
> > +    char *max_tunid = xasprintf("%d",
> > +        get_ovn_max_dp_key_local(sbrec_chassis_table));
> > +    smap_replace(options, "max_tunid", max_tunid);
> > +    free(max_tunid);
> > +
> > +    char *ovn_internal_version = ovn_get_internal_version();
> > +    if (strcmp(ovn_internal_version,
> > +                smap_get_def(options, "northd_internal_version", ""))) {
> > +        smap_replace(options, "northd_internal_version",
> > +                     ovn_internal_version);
> > +        config_data->ovn_internal_version_changed = true;
> > +    } else {
> > +        config_data->ovn_internal_version_changed = false;
> > +    }
> > +
> > +    free(ovn_internal_version);
> > +
> > +    if (!smap_equal(&nb->options, options)) {
> > +        nbrec_nb_global_verify_options(nb);
> > +        nbrec_nb_global_set_options(nb, options);
> > +    }
> > +
> > +    if (smap_get_bool(&nb->options, "ignore_chassis_features", false)) {
> > +        northd_enable_all_features(config_data);
> > +    } else {
> > +        build_chassis_features(sbrec_chassis_table, &config_data->features);
> > +    }
> > +
> > +    init_debug_config(nb);
> > +
> > +    const struct sbrec_sb_global *sb =
> > +        sbrec_sb_global_table_first(sb_global_table);
> > +    if (!sb) {
> > +        sb = sbrec_sb_global_insert(eng_ctx->ovnsb_idl_txn);
> > +    }
> > +    if (nb->ipsec != sb->ipsec) {
> > +        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
> > +    }
> > +
> > +    /* Set up SB_Global (depends on chassis features). */
> > +    update_sb_config_options_to_sbrec(config_data, sb);
> > +
> > +    engine_set_node_state(node, EN_UPDATED);
> > +}
> > +
> > +void en_global_config_cleanup(void *data OVS_UNUSED)
> > +{
> > +    struct ed_type_global_config *config_data = data;
> > +    smap_destroy(&config_data->nb_options);
> > +    smap_destroy(&config_data->sb_options);
> > +    destroy_debug_config();
> > +}
> > +
> > +void
> > +en_global_config_clear_tracked_data(void *data)
> > +{
> > +    struct ed_type_global_config *config_data = data;
> > +    config_data->tracked = false;
> > +    config_data->tracked_data.nb_options_changed = false;
> > +    config_data->tracked_data.chassis_features_changed = false;
> > +}
> > +
> > +bool
> > +global_config_nb_global_handler(struct engine_node *node, void *data)
> > +{
> > +    const struct nbrec_nb_global_table *nb_global_table =
> > +        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> > +    const struct sbrec_sb_global_table *sb_global_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> > +
> > +    const struct nbrec_nb_global *nb =
> > +        nbrec_nb_global_table_first(nb_global_table);
> > +    if (!nb) {
> > +        return false;
> > +    }
> > +
> > +    const struct sbrec_sb_global *sb =
> > +        sbrec_sb_global_table_first(sb_global_table);
> > +    if (!sb) {
> > +        return false;
> > +    }
> > +
> > +    /* We are only interested in ipsec and options column. */
> > +    if (!nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_IPSEC)
> > +        && !nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_OPTIONS)) {
> > +        return true;
> > +    }
> > +
> > +    if (nb->ipsec != sb->ipsec) {
> > +        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
> > +    }
> > +
> > +    struct ed_type_global_config *config_data = data;
> > +    config_data->tracked = true;
> > +
> > +    if (smap_equal(&nb->options, &config_data->nb_options)) {
> > +        return true;
> > +    }
> > +
> > +    /* Return false if an option is out of sync and requires updating the
> > +     * NB config. (Like svc_monitor_mac, max_tunid and mac_prefix). */
> > +    /* Check if svc_monitor_mac has changed or not. */
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "svc_monitor_mac", true)) {
> > +        return false;
> > +    }
> > +
> > +    /* Check if max_tunid has changed or not. */
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "max_tunid", true)) {
> > +        return false;
> > +    }
> > +
> > +    /* Check if mac_prefix has changed or not. */
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "mac_prefix", true)) {
> > +        return false;
> > +    }
> > +
> > +    /* Check if ignore_chassis_features has changed or not. */
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "ignore_chassis_features", false)) {
> > +        return false;
> > +    }
> > +
> > +    /* Check if northd_internal_version has changed or not. */
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "northd_internal_version", false)) {
> > +        return false;
> > +    }
> > +
> > +    if (check_nb_options_out_of_sync(nb, config_data)) {
> > +        config_data->tracked_data.nb_options_changed = true;
> > +    }
> > +
> > +    smap_destroy(&config_data->nb_options);
> > +    smap_clone(&config_data->nb_options, &nb->options);
> > +
> > +    update_sb_config_options_to_sbrec(config_data, sb);
> > +
> > +    engine_set_node_state(node, EN_UPDATED);
> > +    return true;
> > +}
> > +
> > +bool
> > +global_config_sb_global_handler(struct engine_node *node, void *data)
> > +{
> > +    const struct sbrec_sb_global_table *sb_global_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> > +
> > +    const struct sbrec_sb_global *sb =
> > +        sbrec_sb_global_table_first(sb_global_table);
> > +    if (!sb) {
> > +        return false;
> > +    }
> > +
> > +    struct ed_type_global_config *config_data = data;
> > +
> > +    if (!smap_equal(&sb->options, &config_data->sb_options)) {
> > +        return false;
> > +    }
> > +
> > +    /* No need to update the engine node. */
> > +    return true;
> > +}
> > +
> > +bool
> > +global_config_sb_chassis_handler(struct engine_node *node, void *data)
> > +{
> > +    struct ed_type_global_config *config_data = data;
> > +
> > +    const struct sbrec_chassis_table *sbrec_chassis_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
> > +    const struct sbrec_chassis *chassis;
> > +
> > +    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, sbrec_chassis_table) {
> > +        if (sbrec_chassis_is_new(chassis)
> > +            || sbrec_chassis_is_deleted(chassis)
> > +            || sbrec_chassis_is_updated(chassis,
> > +                                        SBREC_CHASSIS_COL_ENCAPS)) {
> > +            return false;
> > +        }
> > +
> > +        for (size_t i = 0; i < chassis->n_encaps; i++) {
> > +            if (sbrec_encap_row_get_seqno(chassis->encaps[i],
> > +                                          OVSDB_IDL_CHANGE_MODIFY) > 0) {
> > +                return false;
> > +            }
> > +        }
> > +    }
> > +
> > +    if (smap_get_bool(&config_data->nb_options, "ignore_chassis_features",
> > +                      false)) {
> > +        return true;
> > +    }
> > +
> > +    bool reevaluate_chassis_features = false;
> > +
> > +    /* Check and evaluate chassis features. */
> > +    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, sbrec_chassis_table) {
> > +        if (sbrec_chassis_is_updated(chassis,
> > +                                        SBREC_CHASSIS_COL_OTHER_CONFIG)) {
> > +            reevaluate_chassis_features = true;
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (!reevaluate_chassis_features) {
> > +        return true;
> > +    }
> > +
> > +    struct chassis_features present_features = config_data->features;
> > +
> > +    /* Enable all features before calling build_chassis_features() as
> > +    * build_chassis_features() only sets the feature flags to false. */
> > +    northd_enable_all_features(config_data);
> > +    build_chassis_features(sbrec_chassis_table, &config_data->features);
> > +
> > +    if (chassis_features_changed(&present_features, &config_data->features)) {
> > +        config_data->tracked_data.chassis_features_changed = true;
> > +        config_data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +/* generic global config handler for any engine node which has global_config
> > + * has an input node . */
> > +bool
> > +node_global_config_handler(struct engine_node *node, void *data OVS_UNUSED)
> > +{
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> > +
> > +    if (!global_config->tracked
> > +        || global_config->tracked_data.chassis_features_changed
> > +        || global_config->tracked_data.nb_options_changed) {
> > +        return false;
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +/* static functions. */
> > +static void
> > +northd_enable_all_features(struct ed_type_global_config *data)
> > +{
> > +    data->features = (struct chassis_features) {
> > +        .ct_no_masked_label = true,
> > +        .mac_binding_timestamp = true,
> > +        .ct_lb_related = true,
> > +        .fdb_timestamp = true,
> > +        .ls_dpg_column = true,
> > +    };
> > +}
> > +
> > +static void
> > +build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
> > +                       struct chassis_features *chassis_features)
> > +{
> > +    const struct sbrec_chassis *chassis;
> > +
> > +    SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) {
> > +        /* Only consider local AZ chassis.  Remote ones don't install
> > +         * flows generated by the local northd.
> > +         */
> > +        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
> > +            continue;
> > +        }
> > +
> > +        bool ct_no_masked_label =
> > +            smap_get_bool(&chassis->other_config,
> > +                          OVN_FEATURE_CT_NO_MASKED_LABEL,
> > +                          false);
> > +        if (!ct_no_masked_label && chassis_features->ct_no_masked_label) {
> > +            chassis_features->ct_no_masked_label = false;
> > +        }
> > +
> > +        bool mac_binding_timestamp =
> > +            smap_get_bool(&chassis->other_config,
> > +                          OVN_FEATURE_MAC_BINDING_TIMESTAMP,
> > +                          false);
> > +        if (!mac_binding_timestamp &&
> > +            chassis_features->mac_binding_timestamp) {
> > +            chassis_features->mac_binding_timestamp = false;
> > +        }
> > +
> > +        bool ct_lb_related =
> > +            smap_get_bool(&chassis->other_config,
> > +                          OVN_FEATURE_CT_LB_RELATED,
> > +                          false);
> > +        if (!ct_lb_related &&
> > +            chassis_features->ct_lb_related) {
> > +            chassis_features->ct_lb_related = false;
> > +        }
> > +
> > +        bool fdb_timestamp =
> > +            smap_get_bool(&chassis->other_config,
> > +                          OVN_FEATURE_FDB_TIMESTAMP,
> > +                          false);
> > +        if (!fdb_timestamp &&
> > +            chassis_features->fdb_timestamp) {
> > +            chassis_features->fdb_timestamp = false;
> > +        }
> > +
> > +        bool ls_dpg_column =
> > +            smap_get_bool(&chassis->other_config,
> > +                          OVN_FEATURE_LS_DPG_COLUMN,
> > +                          false);
> > +        if (!ls_dpg_column &&
> > +            chassis_features->ls_dpg_column) {
> > +            chassis_features->ls_dpg_column = false;
> > +        }
> > +    }
> > +}
> > +
> > +static bool
> > +config_out_of_sync(const struct smap *config, const struct smap *saved_config,
> > +                   const char *key, bool must_be_present)
> > +{
> > +    const char *value = smap_get(config, key);
> > +    if (!value && must_be_present) {
> > +        return true;
> > +    }
> > +
> > +    const char *saved_value = smap_get(saved_config, key);
> > +    if (!saved_value && must_be_present) {
> > +        return true;
> > +    }
> > +
> > +    if (!value && !saved_value) {
> > +        return false;
> > +    }
> > +
> > +    if (!value || !saved_value) {
> > +        return true;
> > +    }
> > +
> > +    return strcmp(value, saved_value);
> > +}
> > +
> > +static bool
> > +check_nb_options_out_of_sync(const struct nbrec_nb_global *nb,
> > +                             struct ed_type_global_config *config_data)
> > +{
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "mac_binding_removal_limit", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "fdb_removal_limit", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "controller_event", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "ignore_lsp_down", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "use_ct_inv_match", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "default_acl_drop", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "debug_drop_domain_id", false)) {
> > +        init_debug_config(nb);
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "debug_drop_collector_set", false)) {
> > +        init_debug_config(nb);
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "use_common_zone", false)) {
> > +        return true;
> > +    }
> > +
> > +    if (config_out_of_sync(&nb->options, &config_data->nb_options,
> > +                           "install_ls_lb_from_router", false)) {
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static void
> > +update_sb_config_options_to_sbrec(struct ed_type_global_config *config_data,
> > +                                  const struct sbrec_sb_global *sb)
> > +{
> > +    struct smap *options = &config_data->sb_options;
> > +
> > +    smap_destroy(options);
> > +    smap_clone(options, &config_data->nb_options);
> > +
> > +    /* Inform ovn-controllers whether LB flows will use ct_mark (i.e., only
> > +     * if all chassis support it).  If not explicitly present in the database
> > +     * the default value to be used for this option is 'true'.
> > +     */
> > +    if (!config_data->features.ct_no_masked_label) {
> > +        smap_replace(options, "lb_hairpin_use_ct_mark", "false");
> > +    } else {
> > +        smap_remove(options, "lb_hairpin_use_ct_mark");
> > +    }
> > +
> > +    /* Hackaround SB_global.options overwrite by NB_Global.options for
> > +     * 'sbctl_probe_interval' option.
> > +     */
> > +    const char *sip = smap_get(&sb->options, "sbctl_probe_interval");
> > +    if (sip) {
> > +        smap_replace(options, "sbctl_probe_interval", sip);
> > +    }
> > +
> > +    if (!smap_equal(&sb->options, options)) {
> > +        sbrec_sb_global_set_options(sb, options);
> > +    }
> > +}
> > +
> > +static bool
> > +chassis_features_changed(const struct chassis_features *present,
> > +                         const struct chassis_features *updated)
> > +{
> > +    if (present->ct_no_masked_label != updated->ct_no_masked_label) {
> > +        return true;
> > +    }
> > +
> > +    if (present->mac_binding_timestamp != updated->mac_binding_timestamp) {
> > +        return true;
> > +    }
> > +
> > +    if (present->ct_lb_related != updated->ct_lb_related) {
> > +        return true;
> > +    }
> > +
> > +    if (present->fdb_timestamp != updated->fdb_timestamp) {
> > +        return true;
> > +    }
> > +
> > +    if (present->ls_dpg_column != updated->ls_dpg_column) {
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > diff --git a/northd/en-global-config.h b/northd/en-global-config.h
> > new file mode 100644
> > index 0000000000..436bc7fa35
> > --- /dev/null
> > +++ b/northd/en-global-config.h
> > @@ -0,0 +1,65 @@
> > +#ifndef EN_GLOBAL_CONFIG_H
> > +#define EN_GLOBAL_CONFIG_H 1
> > +
> > +#include <config.h>
> > +
> > +/* OVS includes. */
> > +#include "lib/packets.h"
> > +#include "lib/smap.h"
> > +
> > +/* OVN includes. */
> > +#include "lib/inc-proc-eng.h"
> > +
> > +struct nbrec_nb_global;
> > +struct sbrec_sb_global;
> > +
> > +struct chassis_features {
> > +    bool ct_no_masked_label;
> > +    bool mac_binding_timestamp;
> > +    bool ct_lb_related;
> > +    bool fdb_timestamp;
> > +    bool ls_dpg_column;
> > +};
> > +
> > +struct global_config_tracked_data {
> > +    bool nb_options_changed;
> > +    bool chassis_features_changed;
> > +};
> > +
> > +/* struct which maintains the data of the engine node global_config. */
> > +struct ed_type_global_config {
> > +    struct smap nb_options;
> > +    struct smap sb_options;
> > +    const struct nbrec_nb_global *nb_global;
> > +    const struct sbrec_sb_global *sb_global;
> > +
> > +    /* MAC allocated for service monitor usage. Just one mac is allocated
> > +     * for this purpose and ovn-controller's on each chassis will make use
> > +     * of this mac when sending out the packets to monitor the services
> > +     * defined in Service_Monitor Southbound table. Since these packets
> > +     * are locally handled, having just one mac is good enough. */
> > +    char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
> > +    struct eth_addr svc_monitor_mac_ea;
> > +
> > +    struct chassis_features features;
> > +
> > +    bool ovn_internal_version_changed;
> > +
> > +    bool tracked;
> > +    struct global_config_tracked_data tracked_data;
> > +};
> > +
> > +void *en_global_config_init(struct engine_node *, struct engine_arg *);
> > +void en_global_config_run(struct engine_node *, void *data);
> > +void en_global_config_cleanup(void *data);
> > +void en_global_config_clear_tracked_data(void *data);
> > +
> > +bool global_config_nb_global_handler(struct engine_node *, void *data);
> > +bool global_config_sb_global_handler(struct engine_node *, void *data);
> > +bool global_config_sb_chassis_handler(struct engine_node *, void *data);
> > +
> > +/* generic global config handler for any engine node which has global_config
> > + * has an input node . */
> > +bool node_global_config_handler(struct engine_node *, void *data);
> > +
> > +#endif /* EN_GLOBAL_CONFIG_H */
> > diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> > index 525453054b..f1a83839df 100644
> > --- a/northd/en-lflow.c
> > +++ b/northd/en-lflow.c
> > @@ -18,6 +18,7 @@
> >  #include <stdlib.h>
> >  #include <stdio.h>
> >
> > +#include "en-global-config.h"
> >  #include "en-lflow.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-stateful.h"
> > @@ -77,10 +78,14 @@ lflow_get_input_data(struct engine_node *node,
> >      lflow_input->meter_groups = &sync_meters_data->meter_groups;
> >      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
> >      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> > -    lflow_input->features = &northd_data->features;
> > -    lflow_input->ovn_internal_version_changed =
> > -                      northd_data->ovn_internal_version_changed;
> >      lflow_input->bfd_connections = NULL;
> > +
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> > +    lflow_input->features = &global_config->features;
> > +    lflow_input->ovn_internal_version_changed =
> > +        global_config->ovn_internal_version_changed;
> > +    lflow_input->svc_monitor_mac = global_config->svc_monitor_mac;
> >  }
> >
> >  void en_lflow_run(struct engine_node *node, void *data)
> > diff --git a/northd/en-northd.c b/northd/en-northd.c
> > index 5143603f39..4479b4aff2 100644
> > --- a/northd/en-northd.c
> > +++ b/northd/en-northd.c
> > @@ -19,6 +19,7 @@
> >  #include <stdio.h>
> >
> >  #include "coverage.h"
> > +#include "en-global-config.h"
> >  #include "en-northd.h"
> >  #include "en-lb-data.h"
> >  #include "lib/inc-proc-eng.h"
> > @@ -65,8 +66,6 @@ northd_get_input_data(struct engine_node *node,
> >              engine_get_input("SB_fdb", node),
> >              "sbrec_fdb_by_dp_and_port");
> >
> > -    input_data->nbrec_nb_global_table =
> > -        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> >      input_data->nbrec_logical_switch_table =
> >          EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
> >      input_data->nbrec_logical_router_table =
> > @@ -78,8 +77,6 @@ northd_get_input_data(struct engine_node *node,
> >      input_data->nbrec_mirror_table =
> >          EN_OVSDB_GET(engine_get_input("NB_mirror", node));
> >
> > -    input_data->sbrec_sb_global_table =
> > -        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> >      input_data->sbrec_datapath_binding_table =
> >          EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node));
> >      input_data->sbrec_port_binding_table =
> > @@ -109,6 +106,14 @@ northd_get_input_data(struct engine_node *node,
> >          engine_get_input_data("lb_data", node);
> >      input_data->lbs = &lb_data->lbs;
> >      input_data->lbgrps = &lb_data->lbgrps;
> > +
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> > +    input_data->nb_options = &global_config->nb_options;
> > +    input_data->sb_options = &global_config->sb_options;
> > +    input_data->svc_monitor_mac = global_config->svc_monitor_mac;
> > +    input_data->svc_monitor_mac_ea = global_config->svc_monitor_mac_ea;
> > +    input_data->features = &global_config->features;
> >  }
> >
> >  void
> > @@ -129,31 +134,6 @@ en_northd_run(struct engine_node *node, void *data)
> >                   eng_ctx->ovnsb_idl_txn);
> >      stopwatch_stop(OVNNB_DB_RUN_STOPWATCH_NAME, time_msec());
> >      engine_set_node_state(node, EN_UPDATED);
> > -
> > -}
> > -
> > -bool
> > -northd_nb_nb_global_handler(struct engine_node *node,
> > -                            void *data OVS_UNUSED)
> > -{
> > -    const struct nbrec_nb_global_table *nb_global_table
> > -        = EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> > -
> > -    const struct nbrec_nb_global *nb =
> > -        nbrec_nb_global_table_first(nb_global_table);
> > -
> > -    if (!nb) {
> > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > -        VLOG_WARN_RL(&rl, "NB_Global is updated but has no record.");
> > -        return false;
> > -    }
> > -
> > -    /* We care about the 'options' and 'ipsec' columns only. */
> > -    if (nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_OPTIONS) ||
> > -        nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_IPSEC)) {
> > -        return false;
> > -    }
> > -    return true;
> >  }
> >
> >  bool
> > @@ -242,6 +222,20 @@ northd_lb_data_handler(struct engine_node *node, void *data)
> >      return true;
> >  }
> >
> > +bool
> > +northd_global_config_handler(struct engine_node *node, void *data OVS_UNUSED)
> > +{
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> > +
> > +    if (!global_config->tracked
> > +        || global_config->tracked_data.nb_options_changed) {
> > +        return false;
> > +    }
> > +
> > +    return true;
> > +}
> > +
> >  void
> >  *en_northd_init(struct engine_node *node OVS_UNUSED,
> >                  struct engine_arg *arg OVS_UNUSED)
> > diff --git a/northd/en-northd.h b/northd/en-northd.h
> > index 5a88871760..9b7bda32aa 100644
> > --- a/northd/en-northd.h
> > +++ b/northd/en-northd.h
> > @@ -14,7 +14,7 @@ void *en_northd_init(struct engine_node *node OVS_UNUSED,
> >                       struct engine_arg *arg);
> >  void en_northd_cleanup(void *data);
> >  void en_northd_clear_tracked_data(void *data);
> > -bool northd_nb_nb_global_handler(struct engine_node *, void *data OVS_UNUSED);
> > +bool northd_global_config_handler(struct engine_node *, void *data OVS_UNUSED);
> >  bool northd_nb_logical_switch_handler(struct engine_node *, void *data);
> >  bool northd_nb_logical_router_handler(struct engine_node *, void *data);
> >  bool northd_sb_port_binding_handler(struct engine_node *, void *data);
> > diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
> > index 9002729b39..9bd8a1fc61 100644
> > --- a/northd/en-sync-sb.c
> > +++ b/northd/en-sync-sb.c
> > @@ -24,6 +24,7 @@
> >
> >  /* OVN includes. */
> >  #include "en-lr-nat.h"
> > +#include "en-global-config.h"
> >  #include "en-lr-stateful.h"
> >  #include "en-sync-sb.h"
> >  #include "lb.h"
> > @@ -47,7 +48,8 @@ static void sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
> >                             const struct nbrec_port_group_table *,
> >                             const struct sbrec_address_set_table *,
> >                             const struct lr_stateful_table *,
> > -                           const struct ovn_datapaths *);
> > +                           const struct ovn_datapaths *,
> > +                           const char *svc_monitor_macp);
> >  static const struct sbrec_address_set *sb_address_set_lookup_by_name(
> >      struct ovsdb_idl_index *, const char *name);
> >  static void update_sb_addr_set(struct sorted_array *,
> > @@ -96,10 +98,13 @@ en_sync_to_sb_addr_set_run(struct engine_node *node, void *data OVS_UNUSED)
> >      const struct ed_type_lr_stateful *lr_stateful_data =
> >          engine_get_input_data("lr_stateful", node);
> >      struct northd_data *northd_data = engine_get_input_data("northd", node);
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> >      sync_addr_sets(eng_ctx->ovnsb_idl_txn, nb_address_set_table,
> >                     nb_port_group_table, sb_address_set_table,
> >                     &lr_stateful_data->table,
> > -                   &northd_data->lr_datapaths);
> > +                   &northd_data->lr_datapaths,
> > +                   global_config->svc_monitor_mac);
> >
> >      engine_set_node_state(node, EN_UPDATED);
> >  }
> > @@ -283,6 +288,8 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
> >          EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> >      const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
> >          EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> >      const struct engine_context *eng_ctx = engine_get_context();
> >      struct ed_type_sync_to_sb_lb_data *data = data_;
> >
> > @@ -293,7 +300,7 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
> >                                 &northd_data->lb_datapaths_map,
> >                                 &northd_data->ls_datapaths,
> >                                 &northd_data->lr_datapaths,
> > -                               &northd_data->features);
> > +                               &global_config->features);
> >
> >      engine_set_node_state(node, EN_UPDATED);
> >  }
> > @@ -324,12 +331,14 @@ sync_to_sb_lb_northd_handler(struct engine_node *node, void *data_)
> >          EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
> >      const struct sbrec_load_balancer_table *sb_lb_table =
> >          EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_input_data("global_config", node);
> >      struct ed_type_sync_to_sb_lb_data *data = data_;
> >
> >      if (!sync_changed_lbs(&data->sb_lbs, eng_ctx->ovnsb_idl_txn, sb_lb_table,
> >                            sb_dpgrp_table, &nd->trk_data.trk_lbs,
> >                            &nd->ls_datapaths, &nd->lr_datapaths,
> > -                          &nd->features)) {
> > +                          &global_config->features)) {
> >          return false;
> >      }
> >
> > @@ -455,7 +464,8 @@ sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
> >                 const struct nbrec_port_group_table *nb_port_group_table,
> >                 const struct sbrec_address_set_table *sb_address_set_table,
> >                 const struct lr_stateful_table *lr_statefuls,
> > -               const struct ovn_datapaths *lr_datapaths)
> > +               const struct ovn_datapaths *lr_datapaths,
> > +               const char *svc_monitor_macp)
> >  {
> >      struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets);
> >
> > @@ -466,7 +476,6 @@ sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
> >      }
> >
> >      /* Service monitor MAC. */
> > -    const char *svc_monitor_macp = northd_get_svc_monitor_mac();
> >      struct sorted_array svc = sorted_array_create(&svc_monitor_macp, 1, false);
> >      sync_addr_set(ovnsb_txn, "svc_monitor_mac", &svc, &sb_address_sets);
> >      sorted_array_destroy(&svc);
> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> > index d215c7792b..e1073812c8 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -30,6 +30,7 @@
> >  #include "openvswitch/poll-loop.h"
> >  #include "openvswitch/vlog.h"
> >  #include "inc-proc-northd.h"
> > +#include "en-global-config.h"
> >  #include "en-lb-data.h"
> >  #include "en-lr-stateful.h"
> >  #include "en-lr-nat.h"
> > @@ -149,6 +150,7 @@ static ENGINE_NODE(fdb_aging, "fdb_aging");
> >  static ENGINE_NODE(fdb_aging_waker, "fdb_aging_waker");
> >  static ENGINE_NODE(sync_to_sb_lb, "sync_to_sb_lb");
> >  static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
> > +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(global_config, "global_config");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_stateful, "lr_stateful");
> > @@ -168,11 +170,17 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lb_data, &en_nb_logical_router,
> >                       lb_data_logical_router_handler);
> >
> > +    engine_add_input(&en_global_config, &en_nb_nb_global,
> > +                     global_config_nb_global_handler);
> > +    engine_add_input(&en_global_config, &en_sb_sb_global,
> > +                     global_config_sb_global_handler);
> > +    engine_add_input(&en_global_config, &en_sb_chassis,
> > +                     global_config_sb_chassis_handler);
> > +
> >      engine_add_input(&en_northd, &en_nb_mirror, NULL);
> >      engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
> >      engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL);
> >
> > -    engine_add_input(&en_northd, &en_sb_sb_global, NULL);
> >      engine_add_input(&en_northd, &en_sb_chassis, NULL);
> >      engine_add_input(&en_northd, &en_sb_mirror, NULL);
> >      engine_add_input(&en_northd, &en_sb_meter, NULL);
> > @@ -184,6 +192,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_northd, &en_sb_fdb, NULL);
> >      engine_add_input(&en_northd, &en_sb_static_mac_binding, NULL);
> >      engine_add_input(&en_northd, &en_sb_chassis_template_var, NULL);
> > +    engine_add_input(&en_northd, &en_global_config,
> > +                     northd_global_config_handler);
> >
> >      /* northd engine node uses the sb mac binding table to
> >       * cleanup mac binding entries for deleted logical ports
> > @@ -198,8 +208,6 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >
> >      engine_add_input(&en_northd, &en_sb_port_binding,
> >                       northd_sb_port_binding_handler);
> > -    engine_add_input(&en_northd, &en_nb_nb_global,
> > -                     northd_nb_nb_global_handler);
> >      engine_add_input(&en_northd, &en_nb_logical_switch,
> >                       northd_nb_logical_switch_handler);
> >      engine_add_input(&en_northd, &en_nb_logical_router,
> > @@ -217,15 +225,17 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_ls_stateful, &en_port_group,
> >                       ls_stateful_port_group_handler);
> >
> > -    engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_mac_binding_aging_waker, NULL);
> > +    engine_add_input(&en_mac_binding_aging, &en_global_config,
> > +                     node_global_config_handler);
> >
> > -    engine_add_input(&en_fdb_aging, &en_nb_nb_global, NULL);
> >      engine_add_input(&en_fdb_aging, &en_sb_fdb, NULL);
> >      engine_add_input(&en_fdb_aging, &en_northd, NULL);
> >      engine_add_input(&en_fdb_aging, &en_fdb_aging_waker, NULL);
> > +    engine_add_input(&en_fdb_aging, &en_global_config,
> > +                     node_global_config_handler);
> >
> >      engine_add_input(&en_sync_meters, &en_nb_acl, NULL);
> >      engine_add_input(&en_sync_meters, &en_nb_meter, NULL);
> > @@ -239,18 +249,22 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lflow, &en_sb_multicast_group, NULL);
> >      engine_add_input(&en_lflow, &en_sb_igmp_group, NULL);
> >      engine_add_input(&en_lflow, &en_sb_logical_dp_group, NULL);
> > +    engine_add_input(&en_lflow, &en_global_config,
> > +                     node_global_config_handler);
> >      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
> >      engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
> >      engine_add_input(&en_lflow, &en_lr_stateful, lflow_lr_stateful_handler);
> >      engine_add_input(&en_lflow, &en_ls_stateful, lflow_ls_stateful_handler);
> >
> > +    engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
> > +    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
> > +    engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
> >      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
> >                       sync_to_sb_addr_set_nb_address_set_handler);
> >      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_port_group,
> >                       sync_to_sb_addr_set_nb_port_group_handler);
> > -    engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
> > -    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
> > -    engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
> > +    engine_add_input(&en_sync_to_sb_addr_set, &en_global_config,
> > +                     node_global_config_handler);
> >
> >      engine_add_input(&en_port_group, &en_nb_port_group,
> >                       port_group_nb_port_group_handler);
> > @@ -260,6 +274,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >       * table too (because of the explicit dependency in the schema). */
> >      engine_add_input(&en_port_group, &en_northd, engine_noop_handler);
> >
> > +    engine_add_input(&en_sync_to_sb_lb, &en_global_config,
> > +                     node_global_config_handler);
> >      engine_add_input(&en_sync_to_sb_lb, &en_northd,
> >                       sync_to_sb_lb_northd_handler);
> >      engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
> > @@ -365,11 +381,11 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >                                  "sbrec_fdb_by_dp_and_port",
> >                                  sbrec_fdb_by_dp_and_port);
> >
> > -    struct northd_data *northd_data =
> > -        engine_get_internal_data(&en_northd);
> > +    struct ed_type_global_config *global_config =
> > +        engine_get_internal_data(&en_global_config);
> >      unixctl_command_register("debug/chassis-features-list", "", 0, 0,
> >                               chassis_features_list,
> > -                             &northd_data->features);
> > +                             &global_config->features);
> >  }
> >
> >  /* Returns true if the incremental processing ended up updating nodes. */
> > diff --git a/northd/northd.c b/northd/northd.c
> > index 574cd1d116..7a887f2722 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -45,6 +45,7 @@
> >  #include "lflow-mgr.h"
> >  #include "memory.h"
> >  #include "northd.h"
> > +#include "en-global-config.h"
> >  #include "en-lb-data.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-stateful.h"
> > @@ -79,14 +80,6 @@ static bool install_ls_lb_from_router;
> >  /* Use common zone for SNAT and DNAT if this option is set to "true". */
> >  static bool use_common_zone = false;
> >
> > -/* MAC allocated for service monitor usage. Just one mac is allocatedg5534
> > - * for this purpose and ovn-controller's on each chassis will make use
> > - * of this mac when sending out the packets to monitor the services
> > - * defined in Service_Monitor Southbound table. Since these packets
> > - * all locally handled, having just one mac is good enough. */
> > -static char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
> > -static struct eth_addr svc_monitor_mac_ea;
> > -
> >  /* If this option is 'true' northd will make use of ct.inv match fields.
> >   * Otherwise, it will avoid using it.  The default is true. */
> >  static bool use_ct_inv_match = true;
> > @@ -297,66 +290,6 @@ ovn_stage_to_datapath_type(enum ovn_stage stage)
> >      }
> >  }
> >
> > -static void
> > -build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
> > -                       struct chassis_features *chassis_features)
> > -{
> > -    const struct sbrec_chassis *chassis;
> > -
> > -    SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) {
> > -        /* Only consider local AZ chassis.  Remote ones don't install
> > -         * flows generated by the local northd.
> > -         */
> > -        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
> > -            continue;
> > -        }
> > -
> > -        bool ct_no_masked_label =
> > -            smap_get_bool(&chassis->other_config,
> > -                          OVN_FEATURE_CT_NO_MASKED_LABEL,
> > -                          false);
> > -        if (!ct_no_masked_label && chassis_features->ct_no_masked_label) {
> > -            chassis_features->ct_no_masked_label = false;
> > -        }
> > -
> > -        bool mac_binding_timestamp =
> > -            smap_get_bool(&chassis->other_config,
> > -                          OVN_FEATURE_MAC_BINDING_TIMESTAMP,
> > -                          false);
> > -        if (!mac_binding_timestamp &&
> > -            chassis_features->mac_binding_timestamp) {
> > -            chassis_features->mac_binding_timestamp = false;
> > -        }
> > -
> > -        bool ct_lb_related =
> > -            smap_get_bool(&chassis->other_config,
> > -                          OVN_FEATURE_CT_LB_RELATED,
> > -                          false);
> > -        if (!ct_lb_related &&
> > -            chassis_features->ct_lb_related) {
> > -            chassis_features->ct_lb_related = false;
> > -        }
> > -
> > -        bool fdb_timestamp =
> > -            smap_get_bool(&chassis->other_config,
> > -                          OVN_FEATURE_FDB_TIMESTAMP,
> > -                          false);
> > -        if (!fdb_timestamp &&
> > -            chassis_features->fdb_timestamp) {
> > -            chassis_features->fdb_timestamp = false;
> > -        }
> > -
> > -        bool ls_dpg_column =
> > -            smap_get_bool(&chassis->other_config,
> > -                          OVN_FEATURE_LS_DPG_COLUMN,
> > -                          false);
> > -        if (!ls_dpg_column &&
> > -            chassis_features->ls_dpg_column) {
> > -            chassis_features->ls_dpg_column = false;
> > -        }
> > -    }
> > -}
> > -
> >  static uint32_t
> >  allocate_queueid(unsigned long *queue_id_bitmap)
> >  {
> > @@ -954,7 +887,7 @@ is_vxlan_mode(const struct sbrec_chassis_table *sbrec_chassis_table)
> >      return false;
> >  }
> >
> > -static uint32_t
> > +uint32_t
> >  get_ovn_max_dp_key_local(const struct sbrec_chassis_table *sbrec_chassis_table)
> >  {
> >      if (is_vxlan_mode(sbrec_chassis_table)) {
> > @@ -3367,6 +3300,8 @@ create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
> >  static void
> >  ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
> >                    const struct ovn_northd_lb *lb,
> > +                  const char *svc_monitor_mac,
> > +                  const struct eth_addr *svc_monitor_mac_ea,
> >                    struct hmap *monitor_map, struct hmap *ls_ports,
> >                    struct sset *svc_monitor_lsps)
> >  {
> > @@ -3412,7 +3347,7 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
> >              struct eth_addr ea;
> >              if (!mon_info->sbrec_mon->src_mac ||
> >                  !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) ||
> > -                !eth_addr_equals(ea, svc_monitor_mac_ea)) {
> > +                !eth_addr_equals(ea, *svc_monitor_mac_ea)) {
> >                  sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon,
> >                                                    svc_monitor_mac);
> >              }
> > @@ -3624,6 +3559,8 @@ static void
> >  build_lb_svcs(
> >      struct ovsdb_idl_txn *ovnsb_txn,
> >      const struct sbrec_service_monitor_table *sbrec_service_monitor_table,
> > +    const char *svc_monitor_mac,
> > +    const struct eth_addr *svc_monitor_mac_ea,
> >      struct hmap *ls_ports, struct hmap *lb_dps_map,
> >      struct sset *svc_monitor_lsps,
> >      struct hmap *svc_monitor_map)
> > @@ -3642,7 +3579,8 @@ build_lb_svcs(
> >
> >      struct ovn_lb_datapaths *lb_dps;
> >      HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
> > -        ovn_lb_svc_create(ovnsb_txn, lb_dps->lb, svc_monitor_map, ls_ports,
> > +        ovn_lb_svc_create(ovnsb_txn, lb_dps->lb, svc_monitor_mac,
> > +                          svc_monitor_mac_ea, svc_monitor_map, ls_ports,
> >                            svc_monitor_lsps);
> >      }
> >
> > @@ -3713,12 +3651,15 @@ static void
> >  build_lb_port_related_data(
> >      struct ovsdb_idl_txn *ovnsb_txn,
> >      const struct sbrec_service_monitor_table *sbrec_service_monitor_table,
> > +    const char *svc_monitor_mac,
> > +    const struct eth_addr *svc_monitor_mac_ea,
> >      struct ovn_datapaths *lr_datapaths, struct hmap *ls_ports,
> >      struct hmap *lb_dps_map, struct hmap *lb_group_dps_map,
> >      struct sset *svc_monitor_lsps,
> >      struct hmap *svc_monitor_map)
> >  {
> > -    build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, ls_ports, lb_dps_map,
> > +    build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, svc_monitor_mac,
> > +                  svc_monitor_mac_ea, ls_ports, lb_dps_map,
> >                    svc_monitor_lsps, svc_monitor_map);
> >      build_lswitch_lbs_from_lrouter(lr_datapaths, lb_dps_map, lb_group_dps_map);
> >  }
> > @@ -9047,6 +8988,7 @@ build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
> >  static void
> >  build_lswitch_arp_nd_service_monitor(const struct ovn_lb_datapaths *lb_dps,
> >                                       const struct hmap *ls_ports,
> > +                                     const char *svc_monitor_mac,
> >                                       struct lflow_table *lflows,
> >                                       struct ds *actions,
> >                                       struct ds *match)
> > @@ -15532,6 +15474,7 @@ struct lswitch_flow_build_info {
> >      struct ds match;
> >      struct ds actions;
> >      size_t thread_lflow_counter;
> > +    const char *svc_monitor_mac;
> >  };
> >
> >  /* Helper function to combine all lflow generation which is iterated by
> > @@ -15762,6 +15705,7 @@ build_lflows_thread(void *arg)
> >                      }
> >                      build_lswitch_arp_nd_service_monitor(lb_dps,
> >                                                           lsi->ls_ports,
> > +                                                         lsi->svc_monitor_mac,
> >                                                           lsi->lflows,
> >                                                           &lsi->match,
> >                                                           &lsi->actions);
> > @@ -15888,7 +15832,8 @@ build_lswitch_and_lrouter_flows(
> >      const struct hmap *lb_dps_map,
> >      const struct hmap *svc_monitor_map,
> >      const struct hmap *bfd_connections,
> > -    const struct chassis_features *features)
> > +    const struct chassis_features *features,
> > +    const char *svc_monitor_mac)
> >  {
> >
> >      char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
> > @@ -15921,6 +15866,7 @@ build_lswitch_and_lrouter_flows(
> >              lsiv[index].features = features;
> >              lsiv[index].svc_check_match = svc_check_match;
> >              lsiv[index].thread_lflow_counter = 0;
> > +            lsiv[index].svc_monitor_mac = svc_monitor_mac;
> >              ds_init(&lsiv[index].match);
> >              ds_init(&lsiv[index].actions);
> >
> > @@ -15960,6 +15906,7 @@ build_lswitch_and_lrouter_flows(
> >              .bfd_connections = bfd_connections,
> >              .features = features,
> >              .svc_check_match = svc_check_match,
> > +            .svc_monitor_mac = svc_monitor_mac,
> >              .match = DS_EMPTY_INITIALIZER,
> >              .actions = DS_EMPTY_INITIALIZER,
> >          };
> > @@ -15999,6 +15946,7 @@ build_lswitch_and_lrouter_flows(
> >          stopwatch_start(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
> >          HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
> >              build_lswitch_arp_nd_service_monitor(lb_dps, lsi.ls_ports,
> > +                                                 lsi.svc_monitor_mac,
> >                                                   lsi.lflows, &lsi.actions,
> >                                                   &lsi.match);
> >              build_lrouter_defrag_flows_for_lb(lb_dps, lsi.lflows,
> > @@ -16119,7 +16067,8 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
> >                                      input_data->lb_datapaths_map,
> >                                      input_data->svc_monitor_map,
> >                                      input_data->bfd_connections,
> > -                                    input_data->features);
> > +                                    input_data->features,
> > +                                    input_data->svc_monitor_mac);
> >
> >      if (parallelization_state == STATE_INIT_HASH_SIZES) {
> >          parallelization_state = STATE_USE_PARALLELIZATION;
> > @@ -16401,6 +16350,7 @@ lflow_handle_northd_lb_changes(struct ovsdb_idl_txn *ovnsb_txn,
> >          struct ds actions = DS_EMPTY_INITIALIZER;
> >
> >          build_lswitch_arp_nd_service_monitor(lb_dps, lflow_input->ls_ports,
> > +                                             lflow_input->svc_monitor_mac,
> >                                               lflows, &actions,
> >                                               &match);
> >          build_lrouter_defrag_flows_for_lb(lb_dps, lflows,
> > @@ -17179,18 +17129,6 @@ destroy_datapaths_and_ports(struct ovn_datapaths *ls_datapaths,
> >      ovn_datapaths_destroy(lr_datapaths);
> >  }
> >
> > -static void
> > -northd_enable_all_features(struct northd_data *data)
> > -{
> > -    data->features = (struct chassis_features) {
> > -        .ct_no_masked_label = true,
> > -        .mac_binding_timestamp = true,
> > -        .ct_lb_related = true,
> > -        .fdb_timestamp = true,
> > -        .ls_dpg_column = true,
> > -    };
> > -}
> > -
> >  void
> >  northd_init(struct northd_data *data)
> >  {
> > @@ -17201,8 +17139,6 @@ northd_init(struct northd_data *data)
> >      hmap_init(&data->lb_datapaths_map);
> >      hmap_init(&data->lb_group_datapaths_map);
> >      ovs_list_init(&data->lr_list);
> > -    northd_enable_all_features(data);
> > -    data->ovn_internal_version_changed = false;
> >      sset_init(&data->svc_monitor_lsps);
> >      hmap_init(&data->svc_monitor_map);
> >      init_northd_tracked_data(data);
> > @@ -17242,7 +17178,6 @@ northd_destroy(struct northd_data *data)
> >      destroy_datapaths_and_ports(&data->ls_datapaths, &data->lr_datapaths,
> >                                  &data->ls_ports, &data->lr_ports,
> >                                  &data->lr_list);
> > -    destroy_debug_config();
> >
> >      sset_destroy(&data->svc_monitor_lsps);
> >      destroy_northd_tracked_data(data);
> > @@ -17259,83 +17194,22 @@ ovnnb_db_run(struct northd_input *input_data,
> >      }
> >      stopwatch_start(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> >
> > -    /* Sync ipsec configuration.
> > -     * Copy nb_cfg from northbound to southbound database.
> > -     * Also set up to update sb_cfg once our southbound transaction commits. */
> > -    const struct nbrec_nb_global *nb = nbrec_nb_global_table_first(
> > -                                       input_data->nbrec_nb_global_table);
> > -    if (!nb) {
> > -        nb = nbrec_nb_global_insert(ovnnb_txn);
> > -    }
> > -
> > -    const char *mac_addr_prefix = set_mac_prefix(smap_get(&nb->options,
> > -                                                          "mac_prefix"));
> > -
> > -    const char *monitor_mac = smap_get(&nb->options, "svc_monitor_mac");
> > -    if (monitor_mac) {
> > -        if (eth_addr_from_string(monitor_mac, &svc_monitor_mac_ea)) {
> > -            snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
> > -                     ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
> > -        } else {
> > -            monitor_mac = NULL;
> > -        }
> > -    }
> > -
> > -    struct smap options;
> > -    smap_clone(&options, &nb->options);
> > -
> > -    smap_replace(&options, "mac_prefix", mac_addr_prefix);
> > -
> > -    if (!monitor_mac) {
> > -        eth_addr_random(&svc_monitor_mac_ea);
> > -        snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
> > -                 ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
> > -        smap_replace(&options, "svc_monitor_mac", svc_monitor_mac);
> > -    }
> > -
> > -    char *max_tunid = xasprintf("%d",
> > -        get_ovn_max_dp_key_local(input_data->sbrec_chassis_table));
> > -    smap_replace(&options, "max_tunid", max_tunid);
> > -    free(max_tunid);
> > -
> > -    char *ovn_internal_version = ovn_get_internal_version();
> > -    if (!strcmp(ovn_internal_version,
> > -                smap_get_def(&options, "northd_internal_version", ""))) {
> > -        data->ovn_internal_version_changed = false;
> > -    } else {
> > -        smap_replace(&options, "northd_internal_version",
> > -                     ovn_internal_version);
> > -    }
> > -    free(ovn_internal_version);
> > -
> > -    if (!smap_equal(&nb->options, &options)) {
> > -        nbrec_nb_global_verify_options(nb);
> > -        nbrec_nb_global_set_options(nb, &options);
> > -    }
> > -
> > -    use_ct_inv_match = smap_get_bool(&nb->options,
> > +    use_ct_inv_match = smap_get_bool(input_data->nb_options,
> >                                       "use_ct_inv_match", true);
> >
> >      /* deprecated, use --event instead */
> > -    controller_event_en = smap_get_bool(&nb->options,
> > +    controller_event_en = smap_get_bool(input_data->nb_options,
> >                                          "controller_event", false);
> > -    check_lsp_is_up = !smap_get_bool(&nb->options,
> > +    check_lsp_is_up = !smap_get_bool(input_data->nb_options,
> >                                       "ignore_lsp_down", true);
> > -    default_acl_drop = smap_get_bool(&nb->options, "default_acl_drop", false);
> > +    default_acl_drop = smap_get_bool(input_data->nb_options,
> > +                                     "default_acl_drop", false);
> >
> > -    install_ls_lb_from_router = smap_get_bool(&nb->options,
> > +    install_ls_lb_from_router = smap_get_bool(input_data->nb_options,
> >                                                "install_ls_lb_from_router",
> >                                                false);
> > -    use_common_zone = smap_get_bool(&nb->options, "use_common_zone", false);
> > -
> > -    if (smap_get_bool(&nb->options, "ignore_chassis_features", false)) {
> > -        northd_enable_all_features(data);
> > -    } else {
> > -        build_chassis_features(input_data->sbrec_chassis_table,
> > -                               &data->features);
> > -    }
> > -
> > -    init_debug_config(nb);
> > +    use_common_zone = smap_get_bool(input_data->nb_options, "use_common_zone",
> > +                                    false);
> >
> >      build_datapaths(ovnsb_txn,
> >                      input_data->nbrec_logical_switch_table,
> > @@ -17360,6 +17234,8 @@ ovnnb_db_run(struct northd_input *input_data,
> >                  &data->ls_ports, &data->lr_ports);
> >      build_lb_port_related_data(ovnsb_txn,
> >                                 input_data->sbrec_service_monitor_table,
> > +                               input_data->svc_monitor_mac,
> > +                               &input_data->svc_monitor_mac_ea,
> >                                 &data->lr_datapaths, &data->ls_ports,
> >                                 &data->lb_datapaths_map,
> >                                 &data->lb_group_datapaths_map,
> > @@ -17392,38 +17268,6 @@ ovnnb_db_run(struct northd_input *input_data,
> >                                &data->ls_datapaths.datapaths);
> >      stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> >
> > -    /* Set up SB_Global (depends on chassis features). */
> > -    const struct sbrec_sb_global *sb = sbrec_sb_global_table_first(
> > -                                       input_data->sbrec_sb_global_table);
> > -    if (!sb) {
> > -        sb = sbrec_sb_global_insert(ovnsb_txn);
> > -    }
> > -    if (nb->ipsec != sb->ipsec) {
> > -        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
> > -    }
> > -
> > -    /* Inform ovn-controllers whether LB flows will use ct_mark (i.e., only
> > -     * if all chassis support it).  If not explicitly present in the database
> > -     * the default value to be used for this option is 'true'.
> > -     */
> > -    if (!data->features.ct_no_masked_label) {
> > -        smap_replace(&options, "lb_hairpin_use_ct_mark", "false");
> > -    } else {
> > -        smap_remove(&options, "lb_hairpin_use_ct_mark");
> > -    }
> > -
> > -    /* Hackaround SB_global.options overwrite by NB_Global.options for
> > -     * 'sbctl_probe_interval' option.
> > -     */
> > -    const char *sip = smap_get(&sb->options, "sbctl_probe_interval");
> > -    if (sip) {
> > -        smap_replace(&options, "sbctl_probe_interval", sip);
> > -    }
> > -
> > -    if (!smap_equal(&sb->options, &options)) {
> > -        sbrec_sb_global_set_options(sb, &options);
> > -    }
> > -    smap_destroy(&options);
> >  }
> >
> >  /* Stores the set of chassis which references an ha_chassis_group.
> > @@ -17714,12 +17558,6 @@ ovnsb_db_run(struct ovsdb_idl_txn *ovnnb_txn,
> >      ovn_update_ipv6_prefix(lr_ports);
> >  }
> >
> > -const char *
> > -northd_get_svc_monitor_mac(void)
> > -{
> > -    return svc_monitor_mac;
> > -}
> > -
> >  const struct ovn_datapath *
> >  northd_get_datapath_for_port(const struct hmap *ls_ports,
> >                               const char *port_name)
> > diff --git a/northd/northd.h b/northd/northd.h
> > index 9d001206c1..b5c175929e 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -27,7 +27,6 @@
> >
> >  struct northd_input {
> >      /* Northbound table references */
> > -    const struct nbrec_nb_global_table *nbrec_nb_global_table;
> >      const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
> >      const struct nbrec_logical_router_table *nbrec_logical_router_table;
> >      const struct nbrec_static_mac_binding_table
> > @@ -37,7 +36,6 @@ struct northd_input {
> >      const struct nbrec_mirror_table *nbrec_mirror_table;
> >
> >      /* Southbound table references */
> > -    const struct sbrec_sb_global_table *sbrec_sb_global_table;
> >      const struct sbrec_datapath_binding_table *sbrec_datapath_binding_table;
> >      const struct sbrec_port_binding_table *sbrec_port_binding_table;
> >      const struct sbrec_mac_binding_table *sbrec_mac_binding_table;
> > @@ -57,6 +55,13 @@ struct northd_input {
> >      const struct hmap *lbs;
> >      const struct hmap *lbgrps;
> >
> > +    /* Global config data node inputs. */
> > +    const struct smap *nb_options;
> > +    const struct smap *sb_options;
> > +    const char *svc_monitor_mac;
> > +    struct eth_addr svc_monitor_mac_ea;
> > +    const struct chassis_features *features;
> > +
> >      /* Indexes */
> >      struct ovsdb_idl_index *sbrec_chassis_by_name;
> >      struct ovsdb_idl_index *sbrec_chassis_by_hostname;
> > @@ -66,14 +71,6 @@ struct northd_input {
> >      struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port;
> >  };
> >
> > -struct chassis_features {
> > -    bool ct_no_masked_label;
> > -    bool mac_binding_timestamp;
> > -    bool ct_lb_related;
> > -    bool fdb_timestamp;
> > -    bool ls_dpg_column;
> > -};
> > -
> >  /* A collection of datapaths. E.g. all logical switch datapaths, or all
> >   * logical router datapaths. */
> >  struct ovn_datapaths {
> > @@ -156,8 +153,6 @@ struct northd_data {
> >      struct hmap lb_datapaths_map;
> >      struct hmap lb_group_datapaths_map;
> >      struct ovs_list lr_list;
> > -    bool ovn_internal_version_changed;
> > -    struct chassis_features features;
> >      struct sset svc_monitor_lsps;
> >      struct hmap svc_monitor_map;
> >
> > @@ -194,6 +189,7 @@ struct lflow_input {
> >      const struct chassis_features *features;
> >      const struct hmap *svc_monitor_map;
> >      bool ovn_internal_version_changed;
> > +    const char *svc_monitor_mac;
> >  };
> >
> >  extern int parallelization_state;
> > @@ -722,8 +718,6 @@ void bfd_cleanup_connections(const struct nbrec_bfd_table *,
> >                               struct hmap *bfd_map);
> >  void run_update_worker_pool(int n_threads);
> >
> > -const char *northd_get_svc_monitor_mac(void);
> > -
> >  const struct ovn_datapath *northd_get_datapath_for_port(
> >      const struct hmap *ls_ports, const char *port_name);
> >
> > @@ -787,4 +781,6 @@ lr_has_multiple_gw_ports(const struct ovn_datapath *od)
> >      return od->n_l3dgw_ports > 1 && !od->is_gw_router;
> >  }
> >
> > +uint32_t get_ovn_max_dp_key_local(const struct sbrec_chassis_table *);
> > +
> >  #endif /* NORTHD_H */
> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> > index 929bb45aed..d8ec3eead5 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -8838,7 +8838,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> >  ])
> >
> > -ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
> > +check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
> >
> >  ovn-sbctl dump-flows S0 > S0flows
> >  ovn-sbctl dump-flows S1 > S1flows
> > @@ -8857,6 +8857,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
> >    table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 172.16.0.11 && tcp.dst == 8080), action=(reg0[[1]] = 0; ct_lb_mark(backends=10.0.0.2:8080);)
> >  ])
> >
> > +
> >  ovn-sbctl get datapath S0 _uuid > dp_uuids
> >  ovn-sbctl get datapath S1 _uuid >> dp_uuids
> >  lb_dp_group=$(ovn-sbctl --bare --columns ls_datapath_group find Load_Balancer name=lb0)
> > @@ -8865,7 +8866,7 @@ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
> >  $(cat dp_uuids | sort)
> >  ])
> >
> > -ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=false
> > +check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=false
> >
> >  ovn-sbctl dump-flows S0 > S0flows
> >  ovn-sbctl dump-flows S1 > S1flows
> > @@ -9250,12 +9251,11 @@ $4
> >  AS_BOX([Create new PG1 and PG2])
> >  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> >  check ovn-nbctl --wait=sb -- pg-add pg1 -- pg-add pg2
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl The port_group node recomputes every time a NB port group is added/deleted.
> > @@ -9288,12 +9288,11 @@ check ovn-nbctl --wait=sb         \
> >  check_column "sw1.1" sb:Port_Group ports name="${sw1_key}_pg1"
> >  check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl The port_group node recomputes also every time a port from a new switch
> > @@ -9325,12 +9324,11 @@ check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
> >  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
> >  check_column "sw2.2" sb:Port_Group ports name="${sw2_key}_pg2"
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl The port_group node recomputes also every time a port from a new switch
> > @@ -9363,12 +9361,11 @@ check_column "sw2.1 sw2.3" sb:Port_Group ports name="${sw2_key}_pg1"
> >  check_column "sw1.2 sw1.3" sb:Port_Group ports name="${sw1_key}_pg2"
> >  check_column "sw2.2 sw2.3" sb:Port_Group ports name="${sw2_key}_pg2"
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl We did not change the set of switches a pg is applied to, there should be
> > @@ -9406,7 +9403,7 @@ dnl though, therefore "compute: 1".
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl We did not change the set of switches a pg is applied to, there should be
> > @@ -9438,12 +9435,11 @@ check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
> >  AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
> >  ])
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl We changed the set of switches a pg is applied to, there should be
> > @@ -9476,12 +9472,11 @@ check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
> >  AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
> >  ])
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl We changed the set of switches a pg is applied to, there should be
> > @@ -9514,12 +9509,11 @@ check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
> >  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
> >  check_column "sw2.2" sb:Port_Group ports name="${sw2_key}_pg2"
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl We changed the set of switches a pg is applied to, there should be a
> > @@ -9554,12 +9548,11 @@ check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
> >  AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
> >  ])
> >
> > -dnl The northd node should not recompute, it should handle nb_global update
> > -dnl though, therefore "compute: 1".
> > +dnl The northd node should not recompute,.
> >  AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
> >  Node: northd
> >  - recompute:            0
> > -- compute:              1
> > +- compute:              0
> >  - abort:                0
> >  ])
> >  dnl We changed the set of switches a pg is applied to, there should be a
> > @@ -11922,3 +11915,212 @@ CHECK_NO_CHANGE_AFTER_RECOMPUTE
> >  OVN_CLEANUP([hv1])
> >  AT_CLEANUP
> >  ])
> > +
> > +OVN_FOR_EACH_NORTHD_NO_HV([
> > +AT_SETUP([NB_Global and SB_Global incremental processing])
> > +
> > +ovn_start
> > +
> > +check_engine_stats() {
> > +  node=$1
> > +  recompute=$2
> > +  compute=$3
> > +
> > +  echo "__file__:__line__: Checking engine stats for node $node : recompute - \
> > +$recompute : compute - $compute"
> > +
> > +  node_stat=$(as northd ovn-appctl -t ovn-northd inc-engine/show-stats $node)
> > +  # node_stat will be of this format :
> > +  #     - Node: lflow - recompute: 3 - compute: 0 - abort: 0
> > +  node_recompute_ct=$(echo $node_stat | cut -d '-' -f2 | cut -d ':' -f2)
> > +  node_compute_ct=$(echo $node_stat | cut -d '-' -f3 | cut -d ':' -f2)
> > +
> > +  if [[ "$recompute" == "norecompute" ]]; then
> > +    # node should not be recomputed
> > +    echo "Expecting $node recompute count - $node_recompute_ct to be 0"
> > +    check test "$node_recompute_ct" -eq "0"
> > +  else
> > +    echo "Expecting $node recompute count - $node_recompute_ct not to be 0"
> > +    check test "$node_recompute_ct" -ne "0"
> > +  fi
> > +
> > +  if [[ "$compute" == "nocompute" ]]; then
> > +    # node should not be computed
> > +    echo "Expecting $node compute count - $node_compute_ct to be 0"
> > +    check test "$node_compute_ct" -eq "0"
> > +  else
> > +    echo "Expecting $node compute count - $node_compute_ct not to be 0"
> > +    check test "$node_compute_ct" -ne "0"
> > +  fi
> > +}
> > +
> > +check ovn-nbctl ls-add sw0
> > +check ovn-nbctl lr-add lr0
> > +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 "00:00:20:20:00:03 10.0.0.3"
> > +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24
> > +check ovn-nbctl lsp-add sw0 sw0-lr0
> > +check ovn-nbctl lsp-set-type sw0-lr0 router
> > +check ovn-nbctl lsp-set-addresses sw0-lr0 router
> > +check ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +
> > +# This should not result in recomputes.
> > +check ovn-nbctl --wait=sb set NB_Global . options:foo=bar
> > +check_engine_stats global_config norecompute compute
> > +check_engine_stats northd norecompute compute
> > +check_engine_stats lflow norecompute compute
> > +CHECK_NO_CHANGE_AFTER_RECOMPUTE
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +# This should result in recomputes.
> > +check ovn-sbctl set SB_Global . options:bar=foo
> > +check_engine_stats global_config recompute compute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +CHECK_NO_CHANGE_AFTER_RECOMPUTE
> > +
> > +# Clears an nb option and checks that recomputes were triggered
> > +# and the option was added back by ovn-northd or not depending
> > +# on the 'added_back' argument.
> > +clear_nb_option() {
> > +  option=$1
> > +  add_back=$2
> > +  echo "clearing the nb option - $option"
> > +  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +  check ovn-nbctl --wait=sb remove NB_Global . options $option
> > +  check_engine_stats global_config recompute compute
> > +  check_engine_stats northd recompute nocompute
> > +  check_engine_stats lflow recompute nocompute
> > +
> > +  local retval=1
> > +  if [ "$add_back" == "true" ]; then
> > +    retval=0
> > +  fi
> > +  AT_CHECK([ovn-nbctl get NB_Global . options:$option], [$retval], [ignore], [ignore])
> > +}
> > +
> > +# Clear svc_monitor_mac and few other options which result in recompute.
> > +# and ovn-northd should update the nb options back.
> > +clear_nb_option svc_monitor_mac true
> > +clear_nb_option max_tunid true
> > +clear_nb_option mac_prefix true
> > +clear_nb_option northd_internal_version true
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-nbctl --wait=sb set NB_Global . options:ignore_chassis_features=true
> > +check_engine_stats global_config recompute compute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +clear_nb_option ignore_chassis_features false
> > +
> > +set_nb_option_lflow_recompute() {
> > +  local option=$1
> > +  local value=$2
> > +  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +  check ovn-nbctl --wait=sb set NB_Global . options:$option=$value
> > +  check_engine_stats global_config norecompute compute
> > +  check_engine_stats northd recompute nocompute
> > +  check_engine_stats lflow recompute nocompute
> > +  check_engine_stats mac_binding_aging recompute nocompute
> > +  CHECK_NO_CHANGE_AFTER_RECOMPUTE
> > +}
> > +
> > +clear_nb_option_lflow_recompute() {
> > +  local option=$1
> > +  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +  check ovn-nbctl --wait=sb remove NB_Global . options $option
> > +  check_engine_stats global_config norecompute compute
> > +  check_engine_stats northd recompute nocompute
> > +  check_engine_stats lflow recompute nocompute
> > +  check_engine_stats mac_binding_aging recompute nocompute
> > +  CHECK_NO_CHANGE_AFTER_RECOMPUTE
> > +}
> > +
> > +set_nb_option_lflow_recompute debug_drop_domain_id 1
> > +clear_nb_option_lflow_recompute debug_drop_domain_id
> > +
> > +set_nb_option_lflow_recompute debug_drop_collector_set 1
> > +clear_nb_option_lflow_recompute debug_drop_collector_set
> > +
> > +set_nb_option_lflow_recompute mac_binding_removal_limit 100
> > +clear_nb_option_lflow_recompute mac_binding_removal_limit
> > +
> > +set_nb_option_lflow_recompute fdb_removal_limit 100
> > +clear_nb_option_lflow_recompute fdb_removal_limit
> > +
> > +set_nb_option_lflow_recompute controller_event true
> > +clear_nb_option_lflow_recompute controller_event
> > +
> > +set_nb_option_lflow_recompute ignore_lsp_down true
> > +clear_nb_option_lflow_recompute ignore_lsp_down
> > +
> > +set_nb_option_lflow_recompute use_ct_inv_match true
> > +clear_nb_option_lflow_recompute use_ct_inv_match
> > +
> > +set_nb_option_lflow_recompute default_acl_drop true
> > +clear_nb_option_lflow_recompute default_acl_drop
> > +
> > +set_nb_option_lflow_recompute use_common_zone true
> > +clear_nb_option_lflow_recompute use_common_zone
> > +
> > +set_nb_option_lflow_recompute install_ls_lb_from_router true
> > +clear_nb_option_lflow_recompute install_ls_lb_from_router
> > +
> > +# Now test changes to chassis for feature changes.
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
> > +check ovn-nbctl --wait=sb sync
> > +check_engine_stats global_config recompute compute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-sbctl chassis-add ch2 geneve 127.0.0.2
> > +check ovn-nbctl --wait=sb sync
> > +check_engine_stats global_config recompute compute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +AT_CHECK([ovn-nbctl get NB_Global . options:max_tunid | \
> > +sed s/":"//g | sed s/\"//g], [0], [16711680
> > +], [])
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-sbctl chassis-del ch2
> > +check ovn-nbctl --wait=sb sync
> > +check_engine_stats global_config recompute compute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-sbctl set encap . type=vxlan
> > +check ovn-nbctl --wait=sb sync
> > +check_engine_stats global_config recompute compute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +AT_CHECK([ovn-nbctl get NB_Global . options:max_tunid | \
> > +sed s/":"//g | sed s/\"//g], [0], [4095
> > +], [])
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-sbctl set chassis . other_config:foo=bar
> > +check ovn-nbctl --wait=sb sync
> > +check_engine_stats global_config norecompute compute
> > +check_engine_stats mac_binding_aging recompute nocompute
> > +check_engine_stats fdb_aging recompute nocompute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> > +check ovn-sbctl set chassis . other_config:ct-no-masked-label=true
> > +check ovn-nbctl --wait=sb sync
> > +check_engine_stats global_config norecompute compute
> > +check_engine_stats mac_binding_aging recompute nocompute
> > +check_engine_stats fdb_aging recompute nocompute
> > +check_engine_stats northd recompute nocompute
> > +check_engine_stats lflow recompute nocompute
> > +
> > +AT_CLEANUP
> > +])
> > --
> > 2.43.0
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
Dumitru Ceara Feb. 2, 2024, 12:17 p.m. UTC | #3
On 1/30/24 22:23, numans@ovn.org wrote:
> From: Numan Siddique <numans@ovn.org>
> 
> A new engine node "global_config" is added which handles the changes
> to NB_Global an SB_Global tables.  It also creates these rows if
> not present.
> 
> Without the I-P, any changes to the options column of these tables
> result in recompute of 'northd' and 'lflow' engine nodes.
> 
> Acked-by: Dumitru Ceara <dceara@redhat.com>
> Acked-by: Han Zhou <hzhou@ovn.org>
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---

[...]

> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 929bb45aed..d8ec3eead5 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -8838,7 +8838,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
>    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
>  ])
>  
> -ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
> +check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
>  
>  ovn-sbctl dump-flows S0 > S0flows
>  ovn-sbctl dump-flows S1 > S1flows
> @@ -8857,6 +8857,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
>    table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 172.16.0.11 && tcp.dst == 8080), action=(reg0[[1]] = 0; ct_lb_mark(backends=10.0.0.2:8080);)
>  ])
>  
> +

Nit: unrelated.

With that addressed, my ack from v5 stands.

Regards,
Dumitru
diff mbox series

Patch

diff --git a/northd/aging.c b/northd/aging.c
index cdf5f4464e..b76963a2dd 100644
--- a/northd/aging.c
+++ b/northd/aging.c
@@ -15,6 +15,7 @@ 
 
 #include <config.h>
 
+#include "en-global-config.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/ovn-nb-idl.h"
 #include "lib/ovn-sb-idl.h"
@@ -347,15 +348,10 @@  aging_context_handle_timestamp(struct aging_context *ctx, int64_t timestamp,
 static uint32_t
 get_removal_limit(struct engine_node *node, const char *name)
 {
-    const struct nbrec_nb_global_table *nb_global_table =
-            EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
-    const struct nbrec_nb_global *nb =
-            nbrec_nb_global_table_first(nb_global_table);
-    if (!nb) {
-        return 0;
-    }
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
 
-    return smap_get_uint(&nb->options, name, 0);
+    return smap_get_uint(&global_config->nb_options, name, 0);
 }
 
 /* MAC binding aging */
@@ -394,11 +390,14 @@  en_mac_binding_aging_run(struct engine_node *node, void *data OVS_UNUSED)
 {
     const struct engine_context *eng_ctx = engine_get_context();
     struct northd_data *northd_data = engine_get_input_data("northd", node);
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
+
     struct aging_waker *waker =
         engine_get_input_data("mac_binding_aging_waker", node);
 
     if (!eng_ctx->ovnsb_idl_txn ||
-        !northd_data->features.mac_binding_timestamp ||
+        !global_config->features.mac_binding_timestamp ||
         time_msec() < waker->next_wake_msec) {
         return;
     }
@@ -530,9 +529,11 @@  en_fdb_aging_run(struct engine_node *node, void *data OVS_UNUSED)
     const struct engine_context *eng_ctx = engine_get_context();
     struct northd_data *northd_data = engine_get_input_data("northd", node);
     struct aging_waker *waker = engine_get_input_data("fdb_aging_waker", node);
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
 
     if (!eng_ctx->ovnsb_idl_txn ||
-        !northd_data->features.fdb_timestamp ||
+        !global_config->features.fdb_timestamp ||
         time_msec() < waker->next_wake_msec) {
         return;
     }
diff --git a/northd/automake.mk b/northd/automake.mk
index 19abb0dece..d491973a8b 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -8,6 +8,8 @@  northd_ovn_northd_SOURCES = \
 	northd/northd.c \
 	northd/northd.h \
 	northd/ovn-northd.c \
+	northd/en-global-config.c \
+	northd/en-global-config.h \
 	northd/en-northd.c \
 	northd/en-northd.h \
 	northd/en-lflow.c \
diff --git a/northd/en-global-config.c b/northd/en-global-config.c
new file mode 100644
index 0000000000..9ac5faf995
--- /dev/null
+++ b/northd/en-global-config.c
@@ -0,0 +1,576 @@ 
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* OVS includes */
+#include "openvswitch/vlog.h"
+
+/* OVN includes */
+#include "debug.h"
+#include "en-global-config.h"
+#include "include/ovn/features.h"
+#include "ipam.h"
+#include "lib/ovn-nb-idl.h"
+#include "lib/ovn-sb-idl.h"
+#include "northd.h"
+
+
+VLOG_DEFINE_THIS_MODULE(en_global_config);
+
+/* static function declarations. */
+static void northd_enable_all_features(struct ed_type_global_config *);
+static void build_chassis_features(const struct sbrec_chassis_table *,
+                                   struct chassis_features *);
+static bool chassis_features_changed(const struct chassis_features *,
+                                     const struct chassis_features *);
+static bool config_out_of_sync(const struct smap *config,
+                               const struct smap *saved_config,
+                               const char *key, bool must_be_present);
+static bool check_nb_options_out_of_sync(const struct nbrec_nb_global *,
+                                         struct ed_type_global_config *);
+static void update_sb_config_options_to_sbrec(struct ed_type_global_config *,
+                                              const struct sbrec_sb_global *);
+
+void *
+en_global_config_init(struct engine_node *node OVS_UNUSED,
+                      struct engine_arg *args OVS_UNUSED)
+{
+    struct ed_type_global_config *data = xzalloc(sizeof *data);
+    smap_init(&data->nb_options);
+    smap_init(&data->sb_options);
+    northd_enable_all_features(data);
+    return data;
+}
+
+void
+en_global_config_run(struct engine_node *node , void *data)
+{
+    const struct engine_context *eng_ctx = engine_get_context();
+    if (!eng_ctx->ovnnb_idl_txn || !eng_ctx->ovnsb_idl_txn) {
+        return;
+    }
+
+    const struct nbrec_nb_global_table *nb_global_table =
+        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
+    const struct sbrec_sb_global_table *sb_global_table =
+        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
+    const struct sbrec_chassis_table *sbrec_chassis_table =
+        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
+
+    struct ed_type_global_config *config_data = data;
+
+    /* Sync ipsec configuration.
+     * Copy nb_cfg from northbound to southbound database.
+     * Also set up to update sb_cfg once our southbound transaction commits. */
+    const struct nbrec_nb_global *nb =
+        nbrec_nb_global_table_first(nb_global_table);
+    if (!nb) {
+        nb = nbrec_nb_global_insert(eng_ctx->ovnnb_idl_txn);
+    }
+
+    const char *mac_addr_prefix = set_mac_prefix(smap_get(&nb->options,
+                                                          "mac_prefix"));
+
+    const char *monitor_mac = smap_get(&nb->options, "svc_monitor_mac");
+    if (monitor_mac) {
+        if (eth_addr_from_string(monitor_mac,
+                                 &config_data->svc_monitor_mac_ea)) {
+            snprintf(config_data->svc_monitor_mac,
+                     sizeof config_data->svc_monitor_mac,
+                     ETH_ADDR_FMT,
+                     ETH_ADDR_ARGS(config_data->svc_monitor_mac_ea));
+        } else {
+            monitor_mac = NULL;
+        }
+    }
+
+    struct smap *options = &config_data->nb_options;
+    smap_destroy(options);
+    smap_clone(options, &nb->options);
+
+    smap_replace(options, "mac_prefix", mac_addr_prefix);
+
+    if (!monitor_mac) {
+        eth_addr_random(&config_data->svc_monitor_mac_ea);
+        snprintf(config_data->svc_monitor_mac,
+                 sizeof config_data->svc_monitor_mac, ETH_ADDR_FMT,
+                 ETH_ADDR_ARGS(config_data->svc_monitor_mac_ea));
+        smap_replace(options, "svc_monitor_mac",
+                     config_data->svc_monitor_mac);
+    }
+
+    char *max_tunid = xasprintf("%d",
+        get_ovn_max_dp_key_local(sbrec_chassis_table));
+    smap_replace(options, "max_tunid", max_tunid);
+    free(max_tunid);
+
+    char *ovn_internal_version = ovn_get_internal_version();
+    if (strcmp(ovn_internal_version,
+                smap_get_def(options, "northd_internal_version", ""))) {
+        smap_replace(options, "northd_internal_version",
+                     ovn_internal_version);
+        config_data->ovn_internal_version_changed = true;
+    } else {
+        config_data->ovn_internal_version_changed = false;
+    }
+
+    free(ovn_internal_version);
+
+    if (!smap_equal(&nb->options, options)) {
+        nbrec_nb_global_verify_options(nb);
+        nbrec_nb_global_set_options(nb, options);
+    }
+
+    if (smap_get_bool(&nb->options, "ignore_chassis_features", false)) {
+        northd_enable_all_features(config_data);
+    } else {
+        build_chassis_features(sbrec_chassis_table, &config_data->features);
+    }
+
+    init_debug_config(nb);
+
+    const struct sbrec_sb_global *sb =
+        sbrec_sb_global_table_first(sb_global_table);
+    if (!sb) {
+        sb = sbrec_sb_global_insert(eng_ctx->ovnsb_idl_txn);
+    }
+    if (nb->ipsec != sb->ipsec) {
+        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
+    }
+
+    /* Set up SB_Global (depends on chassis features). */
+    update_sb_config_options_to_sbrec(config_data, sb);
+
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+void en_global_config_cleanup(void *data OVS_UNUSED)
+{
+    struct ed_type_global_config *config_data = data;
+    smap_destroy(&config_data->nb_options);
+    smap_destroy(&config_data->sb_options);
+    destroy_debug_config();
+}
+
+void
+en_global_config_clear_tracked_data(void *data)
+{
+    struct ed_type_global_config *config_data = data;
+    config_data->tracked = false;
+    config_data->tracked_data.nb_options_changed = false;
+    config_data->tracked_data.chassis_features_changed = false;
+}
+
+bool
+global_config_nb_global_handler(struct engine_node *node, void *data)
+{
+    const struct nbrec_nb_global_table *nb_global_table =
+        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
+    const struct sbrec_sb_global_table *sb_global_table =
+        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
+
+    const struct nbrec_nb_global *nb =
+        nbrec_nb_global_table_first(nb_global_table);
+    if (!nb) {
+        return false;
+    }
+
+    const struct sbrec_sb_global *sb =
+        sbrec_sb_global_table_first(sb_global_table);
+    if (!sb) {
+        return false;
+    }
+
+    /* We are only interested in ipsec and options column. */
+    if (!nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_IPSEC)
+        && !nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_OPTIONS)) {
+        return true;
+    }
+
+    if (nb->ipsec != sb->ipsec) {
+        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
+    }
+
+    struct ed_type_global_config *config_data = data;
+    config_data->tracked = true;
+
+    if (smap_equal(&nb->options, &config_data->nb_options)) {
+        return true;
+    }
+
+    /* Return false if an option is out of sync and requires updating the
+     * NB config. (Like svc_monitor_mac, max_tunid and mac_prefix). */
+    /* Check if svc_monitor_mac has changed or not. */
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "svc_monitor_mac", true)) {
+        return false;
+    }
+
+    /* Check if max_tunid has changed or not. */
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "max_tunid", true)) {
+        return false;
+    }
+
+    /* Check if mac_prefix has changed or not. */
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "mac_prefix", true)) {
+        return false;
+    }
+
+    /* Check if ignore_chassis_features has changed or not. */
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "ignore_chassis_features", false)) {
+        return false;
+    }
+
+    /* Check if northd_internal_version has changed or not. */
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "northd_internal_version", false)) {
+        return false;
+    }
+
+    if (check_nb_options_out_of_sync(nb, config_data)) {
+        config_data->tracked_data.nb_options_changed = true;
+    }
+
+    smap_destroy(&config_data->nb_options);
+    smap_clone(&config_data->nb_options, &nb->options);
+
+    update_sb_config_options_to_sbrec(config_data, sb);
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
+bool
+global_config_sb_global_handler(struct engine_node *node, void *data)
+{
+    const struct sbrec_sb_global_table *sb_global_table =
+        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
+
+    const struct sbrec_sb_global *sb =
+        sbrec_sb_global_table_first(sb_global_table);
+    if (!sb) {
+        return false;
+    }
+
+    struct ed_type_global_config *config_data = data;
+
+    if (!smap_equal(&sb->options, &config_data->sb_options)) {
+        return false;
+    }
+
+    /* No need to update the engine node. */
+    return true;
+}
+
+bool
+global_config_sb_chassis_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_global_config *config_data = data;
+
+    const struct sbrec_chassis_table *sbrec_chassis_table =
+        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
+    const struct sbrec_chassis *chassis;
+
+    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, sbrec_chassis_table) {
+        if (sbrec_chassis_is_new(chassis)
+            || sbrec_chassis_is_deleted(chassis)
+            || sbrec_chassis_is_updated(chassis,
+                                        SBREC_CHASSIS_COL_ENCAPS)) {
+            return false;
+        }
+
+        for (size_t i = 0; i < chassis->n_encaps; i++) {
+            if (sbrec_encap_row_get_seqno(chassis->encaps[i],
+                                          OVSDB_IDL_CHANGE_MODIFY) > 0) {
+                return false;
+            }
+        }
+    }
+
+    if (smap_get_bool(&config_data->nb_options, "ignore_chassis_features",
+                      false)) {
+        return true;
+    }
+
+    bool reevaluate_chassis_features = false;
+
+    /* Check and evaluate chassis features. */
+    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, sbrec_chassis_table) {
+        if (sbrec_chassis_is_updated(chassis,
+                                        SBREC_CHASSIS_COL_OTHER_CONFIG)) {
+            reevaluate_chassis_features = true;
+            break;
+        }
+    }
+
+    if (!reevaluate_chassis_features) {
+        return true;
+    }
+
+    struct chassis_features present_features = config_data->features;
+
+    /* Enable all features before calling build_chassis_features() as
+    * build_chassis_features() only sets the feature flags to false. */
+    northd_enable_all_features(config_data);
+    build_chassis_features(sbrec_chassis_table, &config_data->features);
+
+    if (chassis_features_changed(&present_features, &config_data->features)) {
+        config_data->tracked_data.chassis_features_changed = true;
+        config_data->tracked = true;
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+/* generic global config handler for any engine node which has global_config
+ * has an input node . */
+bool
+node_global_config_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
+
+    if (!global_config->tracked
+        || global_config->tracked_data.chassis_features_changed
+        || global_config->tracked_data.nb_options_changed) {
+        return false;
+    }
+
+    return true;
+}
+
+/* static functions. */
+static void
+northd_enable_all_features(struct ed_type_global_config *data)
+{
+    data->features = (struct chassis_features) {
+        .ct_no_masked_label = true,
+        .mac_binding_timestamp = true,
+        .ct_lb_related = true,
+        .fdb_timestamp = true,
+        .ls_dpg_column = true,
+    };
+}
+
+static void
+build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
+                       struct chassis_features *chassis_features)
+{
+    const struct sbrec_chassis *chassis;
+
+    SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) {
+        /* Only consider local AZ chassis.  Remote ones don't install
+         * flows generated by the local northd.
+         */
+        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
+            continue;
+        }
+
+        bool ct_no_masked_label =
+            smap_get_bool(&chassis->other_config,
+                          OVN_FEATURE_CT_NO_MASKED_LABEL,
+                          false);
+        if (!ct_no_masked_label && chassis_features->ct_no_masked_label) {
+            chassis_features->ct_no_masked_label = false;
+        }
+
+        bool mac_binding_timestamp =
+            smap_get_bool(&chassis->other_config,
+                          OVN_FEATURE_MAC_BINDING_TIMESTAMP,
+                          false);
+        if (!mac_binding_timestamp &&
+            chassis_features->mac_binding_timestamp) {
+            chassis_features->mac_binding_timestamp = false;
+        }
+
+        bool ct_lb_related =
+            smap_get_bool(&chassis->other_config,
+                          OVN_FEATURE_CT_LB_RELATED,
+                          false);
+        if (!ct_lb_related &&
+            chassis_features->ct_lb_related) {
+            chassis_features->ct_lb_related = false;
+        }
+
+        bool fdb_timestamp =
+            smap_get_bool(&chassis->other_config,
+                          OVN_FEATURE_FDB_TIMESTAMP,
+                          false);
+        if (!fdb_timestamp &&
+            chassis_features->fdb_timestamp) {
+            chassis_features->fdb_timestamp = false;
+        }
+
+        bool ls_dpg_column =
+            smap_get_bool(&chassis->other_config,
+                          OVN_FEATURE_LS_DPG_COLUMN,
+                          false);
+        if (!ls_dpg_column &&
+            chassis_features->ls_dpg_column) {
+            chassis_features->ls_dpg_column = false;
+        }
+    }
+}
+
+static bool
+config_out_of_sync(const struct smap *config, const struct smap *saved_config,
+                   const char *key, bool must_be_present)
+{
+    const char *value = smap_get(config, key);
+    if (!value && must_be_present) {
+        return true;
+    }
+
+    const char *saved_value = smap_get(saved_config, key);
+    if (!saved_value && must_be_present) {
+        return true;
+    }
+
+    if (!value && !saved_value) {
+        return false;
+    }
+
+    if (!value || !saved_value) {
+        return true;
+    }
+
+    return strcmp(value, saved_value);
+}
+
+static bool
+check_nb_options_out_of_sync(const struct nbrec_nb_global *nb,
+                             struct ed_type_global_config *config_data)
+{
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "mac_binding_removal_limit", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "fdb_removal_limit", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "controller_event", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "ignore_lsp_down", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "use_ct_inv_match", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "default_acl_drop", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "debug_drop_domain_id", false)) {
+        init_debug_config(nb);
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "debug_drop_collector_set", false)) {
+        init_debug_config(nb);
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "use_common_zone", false)) {
+        return true;
+    }
+
+    if (config_out_of_sync(&nb->options, &config_data->nb_options,
+                           "install_ls_lb_from_router", false)) {
+        return true;
+    }
+
+    return false;
+}
+
+static void
+update_sb_config_options_to_sbrec(struct ed_type_global_config *config_data,
+                                  const struct sbrec_sb_global *sb)
+{
+    struct smap *options = &config_data->sb_options;
+
+    smap_destroy(options);
+    smap_clone(options, &config_data->nb_options);
+
+    /* Inform ovn-controllers whether LB flows will use ct_mark (i.e., only
+     * if all chassis support it).  If not explicitly present in the database
+     * the default value to be used for this option is 'true'.
+     */
+    if (!config_data->features.ct_no_masked_label) {
+        smap_replace(options, "lb_hairpin_use_ct_mark", "false");
+    } else {
+        smap_remove(options, "lb_hairpin_use_ct_mark");
+    }
+
+    /* Hackaround SB_global.options overwrite by NB_Global.options for
+     * 'sbctl_probe_interval' option.
+     */
+    const char *sip = smap_get(&sb->options, "sbctl_probe_interval");
+    if (sip) {
+        smap_replace(options, "sbctl_probe_interval", sip);
+    }
+
+    if (!smap_equal(&sb->options, options)) {
+        sbrec_sb_global_set_options(sb, options);
+    }
+}
+
+static bool
+chassis_features_changed(const struct chassis_features *present,
+                         const struct chassis_features *updated)
+{
+    if (present->ct_no_masked_label != updated->ct_no_masked_label) {
+        return true;
+    }
+
+    if (present->mac_binding_timestamp != updated->mac_binding_timestamp) {
+        return true;
+    }
+
+    if (present->ct_lb_related != updated->ct_lb_related) {
+        return true;
+    }
+
+    if (present->fdb_timestamp != updated->fdb_timestamp) {
+        return true;
+    }
+
+    if (present->ls_dpg_column != updated->ls_dpg_column) {
+        return true;
+    }
+
+    return false;
+}
diff --git a/northd/en-global-config.h b/northd/en-global-config.h
new file mode 100644
index 0000000000..436bc7fa35
--- /dev/null
+++ b/northd/en-global-config.h
@@ -0,0 +1,65 @@ 
+#ifndef EN_GLOBAL_CONFIG_H
+#define EN_GLOBAL_CONFIG_H 1
+
+#include <config.h>
+
+/* OVS includes. */
+#include "lib/packets.h"
+#include "lib/smap.h"
+
+/* OVN includes. */
+#include "lib/inc-proc-eng.h"
+
+struct nbrec_nb_global;
+struct sbrec_sb_global;
+
+struct chassis_features {
+    bool ct_no_masked_label;
+    bool mac_binding_timestamp;
+    bool ct_lb_related;
+    bool fdb_timestamp;
+    bool ls_dpg_column;
+};
+
+struct global_config_tracked_data {
+    bool nb_options_changed;
+    bool chassis_features_changed;
+};
+
+/* struct which maintains the data of the engine node global_config. */
+struct ed_type_global_config {
+    struct smap nb_options;
+    struct smap sb_options;
+    const struct nbrec_nb_global *nb_global;
+    const struct sbrec_sb_global *sb_global;
+
+    /* MAC allocated for service monitor usage. Just one mac is allocated
+     * for this purpose and ovn-controller's on each chassis will make use
+     * of this mac when sending out the packets to monitor the services
+     * defined in Service_Monitor Southbound table. Since these packets
+     * are locally handled, having just one mac is good enough. */
+    char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
+    struct eth_addr svc_monitor_mac_ea;
+
+    struct chassis_features features;
+
+    bool ovn_internal_version_changed;
+
+    bool tracked;
+    struct global_config_tracked_data tracked_data;
+};
+
+void *en_global_config_init(struct engine_node *, struct engine_arg *);
+void en_global_config_run(struct engine_node *, void *data);
+void en_global_config_cleanup(void *data);
+void en_global_config_clear_tracked_data(void *data);
+
+bool global_config_nb_global_handler(struct engine_node *, void *data);
+bool global_config_sb_global_handler(struct engine_node *, void *data);
+bool global_config_sb_chassis_handler(struct engine_node *, void *data);
+
+/* generic global config handler for any engine node which has global_config
+ * has an input node . */
+bool node_global_config_handler(struct engine_node *, void *data);
+
+#endif /* EN_GLOBAL_CONFIG_H */
diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index 525453054b..f1a83839df 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -18,6 +18,7 @@ 
 #include <stdlib.h>
 #include <stdio.h>
 
+#include "en-global-config.h"
 #include "en-lflow.h"
 #include "en-lr-nat.h"
 #include "en-lr-stateful.h"
@@ -77,10 +78,14 @@  lflow_get_input_data(struct engine_node *node,
     lflow_input->meter_groups = &sync_meters_data->meter_groups;
     lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
     lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
-    lflow_input->features = &northd_data->features;
-    lflow_input->ovn_internal_version_changed =
-                      northd_data->ovn_internal_version_changed;
     lflow_input->bfd_connections = NULL;
+
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
+    lflow_input->features = &global_config->features;
+    lflow_input->ovn_internal_version_changed =
+        global_config->ovn_internal_version_changed;
+    lflow_input->svc_monitor_mac = global_config->svc_monitor_mac;
 }
 
 void en_lflow_run(struct engine_node *node, void *data)
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 5143603f39..4479b4aff2 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -19,6 +19,7 @@ 
 #include <stdio.h>
 
 #include "coverage.h"
+#include "en-global-config.h"
 #include "en-northd.h"
 #include "en-lb-data.h"
 #include "lib/inc-proc-eng.h"
@@ -65,8 +66,6 @@  northd_get_input_data(struct engine_node *node,
             engine_get_input("SB_fdb", node),
             "sbrec_fdb_by_dp_and_port");
 
-    input_data->nbrec_nb_global_table =
-        EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
     input_data->nbrec_logical_switch_table =
         EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
     input_data->nbrec_logical_router_table =
@@ -78,8 +77,6 @@  northd_get_input_data(struct engine_node *node,
     input_data->nbrec_mirror_table =
         EN_OVSDB_GET(engine_get_input("NB_mirror", node));
 
-    input_data->sbrec_sb_global_table =
-        EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
     input_data->sbrec_datapath_binding_table =
         EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node));
     input_data->sbrec_port_binding_table =
@@ -109,6 +106,14 @@  northd_get_input_data(struct engine_node *node,
         engine_get_input_data("lb_data", node);
     input_data->lbs = &lb_data->lbs;
     input_data->lbgrps = &lb_data->lbgrps;
+
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
+    input_data->nb_options = &global_config->nb_options;
+    input_data->sb_options = &global_config->sb_options;
+    input_data->svc_monitor_mac = global_config->svc_monitor_mac;
+    input_data->svc_monitor_mac_ea = global_config->svc_monitor_mac_ea;
+    input_data->features = &global_config->features;
 }
 
 void
@@ -129,31 +134,6 @@  en_northd_run(struct engine_node *node, void *data)
                  eng_ctx->ovnsb_idl_txn);
     stopwatch_stop(OVNNB_DB_RUN_STOPWATCH_NAME, time_msec());
     engine_set_node_state(node, EN_UPDATED);
-
-}
-
-bool
-northd_nb_nb_global_handler(struct engine_node *node,
-                            void *data OVS_UNUSED)
-{
-    const struct nbrec_nb_global_table *nb_global_table
-        = EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
-
-    const struct nbrec_nb_global *nb =
-        nbrec_nb_global_table_first(nb_global_table);
-
-    if (!nb) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "NB_Global is updated but has no record.");
-        return false;
-    }
-
-    /* We care about the 'options' and 'ipsec' columns only. */
-    if (nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_OPTIONS) ||
-        nbrec_nb_global_is_updated(nb, NBREC_NB_GLOBAL_COL_IPSEC)) {
-        return false;
-    }
-    return true;
 }
 
 bool
@@ -242,6 +222,20 @@  northd_lb_data_handler(struct engine_node *node, void *data)
     return true;
 }
 
+bool
+northd_global_config_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
+
+    if (!global_config->tracked
+        || global_config->tracked_data.nb_options_changed) {
+        return false;
+    }
+
+    return true;
+}
+
 void
 *en_northd_init(struct engine_node *node OVS_UNUSED,
                 struct engine_arg *arg OVS_UNUSED)
diff --git a/northd/en-northd.h b/northd/en-northd.h
index 5a88871760..9b7bda32aa 100644
--- a/northd/en-northd.h
+++ b/northd/en-northd.h
@@ -14,7 +14,7 @@  void *en_northd_init(struct engine_node *node OVS_UNUSED,
                      struct engine_arg *arg);
 void en_northd_cleanup(void *data);
 void en_northd_clear_tracked_data(void *data);
-bool northd_nb_nb_global_handler(struct engine_node *, void *data OVS_UNUSED);
+bool northd_global_config_handler(struct engine_node *, void *data OVS_UNUSED);
 bool northd_nb_logical_switch_handler(struct engine_node *, void *data);
 bool northd_nb_logical_router_handler(struct engine_node *, void *data);
 bool northd_sb_port_binding_handler(struct engine_node *, void *data);
diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
index 9002729b39..9bd8a1fc61 100644
--- a/northd/en-sync-sb.c
+++ b/northd/en-sync-sb.c
@@ -24,6 +24,7 @@ 
 
 /* OVN includes. */
 #include "en-lr-nat.h"
+#include "en-global-config.h"
 #include "en-lr-stateful.h"
 #include "en-sync-sb.h"
 #include "lb.h"
@@ -47,7 +48,8 @@  static void sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
                            const struct nbrec_port_group_table *,
                            const struct sbrec_address_set_table *,
                            const struct lr_stateful_table *,
-                           const struct ovn_datapaths *);
+                           const struct ovn_datapaths *,
+                           const char *svc_monitor_macp);
 static const struct sbrec_address_set *sb_address_set_lookup_by_name(
     struct ovsdb_idl_index *, const char *name);
 static void update_sb_addr_set(struct sorted_array *,
@@ -96,10 +98,13 @@  en_sync_to_sb_addr_set_run(struct engine_node *node, void *data OVS_UNUSED)
     const struct ed_type_lr_stateful *lr_stateful_data =
         engine_get_input_data("lr_stateful", node);
     struct northd_data *northd_data = engine_get_input_data("northd", node);
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
     sync_addr_sets(eng_ctx->ovnsb_idl_txn, nb_address_set_table,
                    nb_port_group_table, sb_address_set_table,
                    &lr_stateful_data->table,
-                   &northd_data->lr_datapaths);
+                   &northd_data->lr_datapaths,
+                   global_config->svc_monitor_mac);
 
     engine_set_node_state(node, EN_UPDATED);
 }
@@ -283,6 +288,8 @@  en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
         EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
     const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
         EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
     const struct engine_context *eng_ctx = engine_get_context();
     struct ed_type_sync_to_sb_lb_data *data = data_;
 
@@ -293,7 +300,7 @@  en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
                                &northd_data->lb_datapaths_map,
                                &northd_data->ls_datapaths,
                                &northd_data->lr_datapaths,
-                               &northd_data->features);
+                               &global_config->features);
 
     engine_set_node_state(node, EN_UPDATED);
 }
@@ -324,12 +331,14 @@  sync_to_sb_lb_northd_handler(struct engine_node *node, void *data_)
         EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
     const struct sbrec_load_balancer_table *sb_lb_table =
         EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
+    struct ed_type_global_config *global_config =
+        engine_get_input_data("global_config", node);
     struct ed_type_sync_to_sb_lb_data *data = data_;
 
     if (!sync_changed_lbs(&data->sb_lbs, eng_ctx->ovnsb_idl_txn, sb_lb_table,
                           sb_dpgrp_table, &nd->trk_data.trk_lbs,
                           &nd->ls_datapaths, &nd->lr_datapaths,
-                          &nd->features)) {
+                          &global_config->features)) {
         return false;
     }
 
@@ -455,7 +464,8 @@  sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
                const struct nbrec_port_group_table *nb_port_group_table,
                const struct sbrec_address_set_table *sb_address_set_table,
                const struct lr_stateful_table *lr_statefuls,
-               const struct ovn_datapaths *lr_datapaths)
+               const struct ovn_datapaths *lr_datapaths,
+               const char *svc_monitor_macp)
 {
     struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets);
 
@@ -466,7 +476,6 @@  sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
     }
 
     /* Service monitor MAC. */
-    const char *svc_monitor_macp = northd_get_svc_monitor_mac();
     struct sorted_array svc = sorted_array_create(&svc_monitor_macp, 1, false);
     sync_addr_set(ovnsb_txn, "svc_monitor_mac", &svc, &sb_address_sets);
     sorted_array_destroy(&svc);
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index d215c7792b..e1073812c8 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -30,6 +30,7 @@ 
 #include "openvswitch/poll-loop.h"
 #include "openvswitch/vlog.h"
 #include "inc-proc-northd.h"
+#include "en-global-config.h"
 #include "en-lb-data.h"
 #include "en-lr-stateful.h"
 #include "en-lr-nat.h"
@@ -149,6 +150,7 @@  static ENGINE_NODE(fdb_aging, "fdb_aging");
 static ENGINE_NODE(fdb_aging_waker, "fdb_aging_waker");
 static ENGINE_NODE(sync_to_sb_lb, "sync_to_sb_lb");
 static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
+static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(global_config, "global_config");
 static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
 static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
 static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_stateful, "lr_stateful");
@@ -168,11 +170,17 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_lb_data, &en_nb_logical_router,
                      lb_data_logical_router_handler);
 
+    engine_add_input(&en_global_config, &en_nb_nb_global,
+                     global_config_nb_global_handler);
+    engine_add_input(&en_global_config, &en_sb_sb_global,
+                     global_config_sb_global_handler);
+    engine_add_input(&en_global_config, &en_sb_chassis,
+                     global_config_sb_chassis_handler);
+
     engine_add_input(&en_northd, &en_nb_mirror, NULL);
     engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
     engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL);
 
-    engine_add_input(&en_northd, &en_sb_sb_global, NULL);
     engine_add_input(&en_northd, &en_sb_chassis, NULL);
     engine_add_input(&en_northd, &en_sb_mirror, NULL);
     engine_add_input(&en_northd, &en_sb_meter, NULL);
@@ -184,6 +192,8 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_sb_fdb, NULL);
     engine_add_input(&en_northd, &en_sb_static_mac_binding, NULL);
     engine_add_input(&en_northd, &en_sb_chassis_template_var, NULL);
+    engine_add_input(&en_northd, &en_global_config,
+                     northd_global_config_handler);
 
     /* northd engine node uses the sb mac binding table to
      * cleanup mac binding entries for deleted logical ports
@@ -198,8 +208,6 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
 
     engine_add_input(&en_northd, &en_sb_port_binding,
                      northd_sb_port_binding_handler);
-    engine_add_input(&en_northd, &en_nb_nb_global,
-                     northd_nb_nb_global_handler);
     engine_add_input(&en_northd, &en_nb_logical_switch,
                      northd_nb_logical_switch_handler);
     engine_add_input(&en_northd, &en_nb_logical_router,
@@ -217,15 +225,17 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_ls_stateful, &en_port_group,
                      ls_stateful_port_group_handler);
 
-    engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
     engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
     engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
     engine_add_input(&en_mac_binding_aging, &en_mac_binding_aging_waker, NULL);
+    engine_add_input(&en_mac_binding_aging, &en_global_config,
+                     node_global_config_handler);
 
-    engine_add_input(&en_fdb_aging, &en_nb_nb_global, NULL);
     engine_add_input(&en_fdb_aging, &en_sb_fdb, NULL);
     engine_add_input(&en_fdb_aging, &en_northd, NULL);
     engine_add_input(&en_fdb_aging, &en_fdb_aging_waker, NULL);
+    engine_add_input(&en_fdb_aging, &en_global_config,
+                     node_global_config_handler);
 
     engine_add_input(&en_sync_meters, &en_nb_acl, NULL);
     engine_add_input(&en_sync_meters, &en_nb_meter, NULL);
@@ -239,18 +249,22 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_lflow, &en_sb_multicast_group, NULL);
     engine_add_input(&en_lflow, &en_sb_igmp_group, NULL);
     engine_add_input(&en_lflow, &en_sb_logical_dp_group, NULL);
+    engine_add_input(&en_lflow, &en_global_config,
+                     node_global_config_handler);
     engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
     engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
     engine_add_input(&en_lflow, &en_lr_stateful, lflow_lr_stateful_handler);
     engine_add_input(&en_lflow, &en_ls_stateful, lflow_ls_stateful_handler);
 
+    engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
+    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
+    engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
     engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
                      sync_to_sb_addr_set_nb_address_set_handler);
     engine_add_input(&en_sync_to_sb_addr_set, &en_nb_port_group,
                      sync_to_sb_addr_set_nb_port_group_handler);
-    engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
-    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
-    engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
+    engine_add_input(&en_sync_to_sb_addr_set, &en_global_config,
+                     node_global_config_handler);
 
     engine_add_input(&en_port_group, &en_nb_port_group,
                      port_group_nb_port_group_handler);
@@ -260,6 +274,8 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
      * table too (because of the explicit dependency in the schema). */
     engine_add_input(&en_port_group, &en_northd, engine_noop_handler);
 
+    engine_add_input(&en_sync_to_sb_lb, &en_global_config,
+                     node_global_config_handler);
     engine_add_input(&en_sync_to_sb_lb, &en_northd,
                      sync_to_sb_lb_northd_handler);
     engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
@@ -365,11 +381,11 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                                 "sbrec_fdb_by_dp_and_port",
                                 sbrec_fdb_by_dp_and_port);
 
-    struct northd_data *northd_data =
-        engine_get_internal_data(&en_northd);
+    struct ed_type_global_config *global_config =
+        engine_get_internal_data(&en_global_config);
     unixctl_command_register("debug/chassis-features-list", "", 0, 0,
                              chassis_features_list,
-                             &northd_data->features);
+                             &global_config->features);
 }
 
 /* Returns true if the incremental processing ended up updating nodes. */
diff --git a/northd/northd.c b/northd/northd.c
index 574cd1d116..7a887f2722 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -45,6 +45,7 @@ 
 #include "lflow-mgr.h"
 #include "memory.h"
 #include "northd.h"
+#include "en-global-config.h"
 #include "en-lb-data.h"
 #include "en-lr-nat.h"
 #include "en-lr-stateful.h"
@@ -79,14 +80,6 @@  static bool install_ls_lb_from_router;
 /* Use common zone for SNAT and DNAT if this option is set to "true". */
 static bool use_common_zone = false;
 
-/* MAC allocated for service monitor usage. Just one mac is allocatedg5534
- * for this purpose and ovn-controller's on each chassis will make use
- * of this mac when sending out the packets to monitor the services
- * defined in Service_Monitor Southbound table. Since these packets
- * all locally handled, having just one mac is good enough. */
-static char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
-static struct eth_addr svc_monitor_mac_ea;
-
 /* If this option is 'true' northd will make use of ct.inv match fields.
  * Otherwise, it will avoid using it.  The default is true. */
 static bool use_ct_inv_match = true;
@@ -297,66 +290,6 @@  ovn_stage_to_datapath_type(enum ovn_stage stage)
     }
 }
 
-static void
-build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
-                       struct chassis_features *chassis_features)
-{
-    const struct sbrec_chassis *chassis;
-
-    SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) {
-        /* Only consider local AZ chassis.  Remote ones don't install
-         * flows generated by the local northd.
-         */
-        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
-            continue;
-        }
-
-        bool ct_no_masked_label =
-            smap_get_bool(&chassis->other_config,
-                          OVN_FEATURE_CT_NO_MASKED_LABEL,
-                          false);
-        if (!ct_no_masked_label && chassis_features->ct_no_masked_label) {
-            chassis_features->ct_no_masked_label = false;
-        }
-
-        bool mac_binding_timestamp =
-            smap_get_bool(&chassis->other_config,
-                          OVN_FEATURE_MAC_BINDING_TIMESTAMP,
-                          false);
-        if (!mac_binding_timestamp &&
-            chassis_features->mac_binding_timestamp) {
-            chassis_features->mac_binding_timestamp = false;
-        }
-
-        bool ct_lb_related =
-            smap_get_bool(&chassis->other_config,
-                          OVN_FEATURE_CT_LB_RELATED,
-                          false);
-        if (!ct_lb_related &&
-            chassis_features->ct_lb_related) {
-            chassis_features->ct_lb_related = false;
-        }
-
-        bool fdb_timestamp =
-            smap_get_bool(&chassis->other_config,
-                          OVN_FEATURE_FDB_TIMESTAMP,
-                          false);
-        if (!fdb_timestamp &&
-            chassis_features->fdb_timestamp) {
-            chassis_features->fdb_timestamp = false;
-        }
-
-        bool ls_dpg_column =
-            smap_get_bool(&chassis->other_config,
-                          OVN_FEATURE_LS_DPG_COLUMN,
-                          false);
-        if (!ls_dpg_column &&
-            chassis_features->ls_dpg_column) {
-            chassis_features->ls_dpg_column = false;
-        }
-    }
-}
-
 static uint32_t
 allocate_queueid(unsigned long *queue_id_bitmap)
 {
@@ -954,7 +887,7 @@  is_vxlan_mode(const struct sbrec_chassis_table *sbrec_chassis_table)
     return false;
 }
 
-static uint32_t
+uint32_t
 get_ovn_max_dp_key_local(const struct sbrec_chassis_table *sbrec_chassis_table)
 {
     if (is_vxlan_mode(sbrec_chassis_table)) {
@@ -3367,6 +3300,8 @@  create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
 static void
 ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
                   const struct ovn_northd_lb *lb,
+                  const char *svc_monitor_mac,
+                  const struct eth_addr *svc_monitor_mac_ea,
                   struct hmap *monitor_map, struct hmap *ls_ports,
                   struct sset *svc_monitor_lsps)
 {
@@ -3412,7 +3347,7 @@  ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
             struct eth_addr ea;
             if (!mon_info->sbrec_mon->src_mac ||
                 !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) ||
-                !eth_addr_equals(ea, svc_monitor_mac_ea)) {
+                !eth_addr_equals(ea, *svc_monitor_mac_ea)) {
                 sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon,
                                                   svc_monitor_mac);
             }
@@ -3624,6 +3559,8 @@  static void
 build_lb_svcs(
     struct ovsdb_idl_txn *ovnsb_txn,
     const struct sbrec_service_monitor_table *sbrec_service_monitor_table,
+    const char *svc_monitor_mac,
+    const struct eth_addr *svc_monitor_mac_ea,
     struct hmap *ls_ports, struct hmap *lb_dps_map,
     struct sset *svc_monitor_lsps,
     struct hmap *svc_monitor_map)
@@ -3642,7 +3579,8 @@  build_lb_svcs(
 
     struct ovn_lb_datapaths *lb_dps;
     HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
-        ovn_lb_svc_create(ovnsb_txn, lb_dps->lb, svc_monitor_map, ls_ports,
+        ovn_lb_svc_create(ovnsb_txn, lb_dps->lb, svc_monitor_mac,
+                          svc_monitor_mac_ea, svc_monitor_map, ls_ports,
                           svc_monitor_lsps);
     }
 
@@ -3713,12 +3651,15 @@  static void
 build_lb_port_related_data(
     struct ovsdb_idl_txn *ovnsb_txn,
     const struct sbrec_service_monitor_table *sbrec_service_monitor_table,
+    const char *svc_monitor_mac,
+    const struct eth_addr *svc_monitor_mac_ea,
     struct ovn_datapaths *lr_datapaths, struct hmap *ls_ports,
     struct hmap *lb_dps_map, struct hmap *lb_group_dps_map,
     struct sset *svc_monitor_lsps,
     struct hmap *svc_monitor_map)
 {
-    build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, ls_ports, lb_dps_map,
+    build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, svc_monitor_mac,
+                  svc_monitor_mac_ea, ls_ports, lb_dps_map,
                   svc_monitor_lsps, svc_monitor_map);
     build_lswitch_lbs_from_lrouter(lr_datapaths, lb_dps_map, lb_group_dps_map);
 }
@@ -9047,6 +8988,7 @@  build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
 static void
 build_lswitch_arp_nd_service_monitor(const struct ovn_lb_datapaths *lb_dps,
                                      const struct hmap *ls_ports,
+                                     const char *svc_monitor_mac,
                                      struct lflow_table *lflows,
                                      struct ds *actions,
                                      struct ds *match)
@@ -15532,6 +15474,7 @@  struct lswitch_flow_build_info {
     struct ds match;
     struct ds actions;
     size_t thread_lflow_counter;
+    const char *svc_monitor_mac;
 };
 
 /* Helper function to combine all lflow generation which is iterated by
@@ -15762,6 +15705,7 @@  build_lflows_thread(void *arg)
                     }
                     build_lswitch_arp_nd_service_monitor(lb_dps,
                                                          lsi->ls_ports,
+                                                         lsi->svc_monitor_mac,
                                                          lsi->lflows,
                                                          &lsi->match,
                                                          &lsi->actions);
@@ -15888,7 +15832,8 @@  build_lswitch_and_lrouter_flows(
     const struct hmap *lb_dps_map,
     const struct hmap *svc_monitor_map,
     const struct hmap *bfd_connections,
-    const struct chassis_features *features)
+    const struct chassis_features *features,
+    const char *svc_monitor_mac)
 {
 
     char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
@@ -15921,6 +15866,7 @@  build_lswitch_and_lrouter_flows(
             lsiv[index].features = features;
             lsiv[index].svc_check_match = svc_check_match;
             lsiv[index].thread_lflow_counter = 0;
+            lsiv[index].svc_monitor_mac = svc_monitor_mac;
             ds_init(&lsiv[index].match);
             ds_init(&lsiv[index].actions);
 
@@ -15960,6 +15906,7 @@  build_lswitch_and_lrouter_flows(
             .bfd_connections = bfd_connections,
             .features = features,
             .svc_check_match = svc_check_match,
+            .svc_monitor_mac = svc_monitor_mac,
             .match = DS_EMPTY_INITIALIZER,
             .actions = DS_EMPTY_INITIALIZER,
         };
@@ -15999,6 +15946,7 @@  build_lswitch_and_lrouter_flows(
         stopwatch_start(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
         HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
             build_lswitch_arp_nd_service_monitor(lb_dps, lsi.ls_ports,
+                                                 lsi.svc_monitor_mac,
                                                  lsi.lflows, &lsi.actions,
                                                  &lsi.match);
             build_lrouter_defrag_flows_for_lb(lb_dps, lsi.lflows,
@@ -16119,7 +16067,8 @@  void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
                                     input_data->lb_datapaths_map,
                                     input_data->svc_monitor_map,
                                     input_data->bfd_connections,
-                                    input_data->features);
+                                    input_data->features,
+                                    input_data->svc_monitor_mac);
 
     if (parallelization_state == STATE_INIT_HASH_SIZES) {
         parallelization_state = STATE_USE_PARALLELIZATION;
@@ -16401,6 +16350,7 @@  lflow_handle_northd_lb_changes(struct ovsdb_idl_txn *ovnsb_txn,
         struct ds actions = DS_EMPTY_INITIALIZER;
 
         build_lswitch_arp_nd_service_monitor(lb_dps, lflow_input->ls_ports,
+                                             lflow_input->svc_monitor_mac,
                                              lflows, &actions,
                                              &match);
         build_lrouter_defrag_flows_for_lb(lb_dps, lflows,
@@ -17179,18 +17129,6 @@  destroy_datapaths_and_ports(struct ovn_datapaths *ls_datapaths,
     ovn_datapaths_destroy(lr_datapaths);
 }
 
-static void
-northd_enable_all_features(struct northd_data *data)
-{
-    data->features = (struct chassis_features) {
-        .ct_no_masked_label = true,
-        .mac_binding_timestamp = true,
-        .ct_lb_related = true,
-        .fdb_timestamp = true,
-        .ls_dpg_column = true,
-    };
-}
-
 void
 northd_init(struct northd_data *data)
 {
@@ -17201,8 +17139,6 @@  northd_init(struct northd_data *data)
     hmap_init(&data->lb_datapaths_map);
     hmap_init(&data->lb_group_datapaths_map);
     ovs_list_init(&data->lr_list);
-    northd_enable_all_features(data);
-    data->ovn_internal_version_changed = false;
     sset_init(&data->svc_monitor_lsps);
     hmap_init(&data->svc_monitor_map);
     init_northd_tracked_data(data);
@@ -17242,7 +17178,6 @@  northd_destroy(struct northd_data *data)
     destroy_datapaths_and_ports(&data->ls_datapaths, &data->lr_datapaths,
                                 &data->ls_ports, &data->lr_ports,
                                 &data->lr_list);
-    destroy_debug_config();
 
     sset_destroy(&data->svc_monitor_lsps);
     destroy_northd_tracked_data(data);
@@ -17259,83 +17194,22 @@  ovnnb_db_run(struct northd_input *input_data,
     }
     stopwatch_start(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
 
-    /* Sync ipsec configuration.
-     * Copy nb_cfg from northbound to southbound database.
-     * Also set up to update sb_cfg once our southbound transaction commits. */
-    const struct nbrec_nb_global *nb = nbrec_nb_global_table_first(
-                                       input_data->nbrec_nb_global_table);
-    if (!nb) {
-        nb = nbrec_nb_global_insert(ovnnb_txn);
-    }
-
-    const char *mac_addr_prefix = set_mac_prefix(smap_get(&nb->options,
-                                                          "mac_prefix"));
-
-    const char *monitor_mac = smap_get(&nb->options, "svc_monitor_mac");
-    if (monitor_mac) {
-        if (eth_addr_from_string(monitor_mac, &svc_monitor_mac_ea)) {
-            snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
-                     ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
-        } else {
-            monitor_mac = NULL;
-        }
-    }
-
-    struct smap options;
-    smap_clone(&options, &nb->options);
-
-    smap_replace(&options, "mac_prefix", mac_addr_prefix);
-
-    if (!monitor_mac) {
-        eth_addr_random(&svc_monitor_mac_ea);
-        snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
-                 ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
-        smap_replace(&options, "svc_monitor_mac", svc_monitor_mac);
-    }
-
-    char *max_tunid = xasprintf("%d",
-        get_ovn_max_dp_key_local(input_data->sbrec_chassis_table));
-    smap_replace(&options, "max_tunid", max_tunid);
-    free(max_tunid);
-
-    char *ovn_internal_version = ovn_get_internal_version();
-    if (!strcmp(ovn_internal_version,
-                smap_get_def(&options, "northd_internal_version", ""))) {
-        data->ovn_internal_version_changed = false;
-    } else {
-        smap_replace(&options, "northd_internal_version",
-                     ovn_internal_version);
-    }
-    free(ovn_internal_version);
-
-    if (!smap_equal(&nb->options, &options)) {
-        nbrec_nb_global_verify_options(nb);
-        nbrec_nb_global_set_options(nb, &options);
-    }
-
-    use_ct_inv_match = smap_get_bool(&nb->options,
+    use_ct_inv_match = smap_get_bool(input_data->nb_options,
                                      "use_ct_inv_match", true);
 
     /* deprecated, use --event instead */
-    controller_event_en = smap_get_bool(&nb->options,
+    controller_event_en = smap_get_bool(input_data->nb_options,
                                         "controller_event", false);
-    check_lsp_is_up = !smap_get_bool(&nb->options,
+    check_lsp_is_up = !smap_get_bool(input_data->nb_options,
                                      "ignore_lsp_down", true);
-    default_acl_drop = smap_get_bool(&nb->options, "default_acl_drop", false);
+    default_acl_drop = smap_get_bool(input_data->nb_options,
+                                     "default_acl_drop", false);
 
-    install_ls_lb_from_router = smap_get_bool(&nb->options,
+    install_ls_lb_from_router = smap_get_bool(input_data->nb_options,
                                               "install_ls_lb_from_router",
                                               false);
-    use_common_zone = smap_get_bool(&nb->options, "use_common_zone", false);
-
-    if (smap_get_bool(&nb->options, "ignore_chassis_features", false)) {
-        northd_enable_all_features(data);
-    } else {
-        build_chassis_features(input_data->sbrec_chassis_table,
-                               &data->features);
-    }
-
-    init_debug_config(nb);
+    use_common_zone = smap_get_bool(input_data->nb_options, "use_common_zone",
+                                    false);
 
     build_datapaths(ovnsb_txn,
                     input_data->nbrec_logical_switch_table,
@@ -17360,6 +17234,8 @@  ovnnb_db_run(struct northd_input *input_data,
                 &data->ls_ports, &data->lr_ports);
     build_lb_port_related_data(ovnsb_txn,
                                input_data->sbrec_service_monitor_table,
+                               input_data->svc_monitor_mac,
+                               &input_data->svc_monitor_mac_ea,
                                &data->lr_datapaths, &data->ls_ports,
                                &data->lb_datapaths_map,
                                &data->lb_group_datapaths_map,
@@ -17392,38 +17268,6 @@  ovnnb_db_run(struct northd_input *input_data,
                               &data->ls_datapaths.datapaths);
     stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
 
-    /* Set up SB_Global (depends on chassis features). */
-    const struct sbrec_sb_global *sb = sbrec_sb_global_table_first(
-                                       input_data->sbrec_sb_global_table);
-    if (!sb) {
-        sb = sbrec_sb_global_insert(ovnsb_txn);
-    }
-    if (nb->ipsec != sb->ipsec) {
-        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
-    }
-
-    /* Inform ovn-controllers whether LB flows will use ct_mark (i.e., only
-     * if all chassis support it).  If not explicitly present in the database
-     * the default value to be used for this option is 'true'.
-     */
-    if (!data->features.ct_no_masked_label) {
-        smap_replace(&options, "lb_hairpin_use_ct_mark", "false");
-    } else {
-        smap_remove(&options, "lb_hairpin_use_ct_mark");
-    }
-
-    /* Hackaround SB_global.options overwrite by NB_Global.options for
-     * 'sbctl_probe_interval' option.
-     */
-    const char *sip = smap_get(&sb->options, "sbctl_probe_interval");
-    if (sip) {
-        smap_replace(&options, "sbctl_probe_interval", sip);
-    }
-
-    if (!smap_equal(&sb->options, &options)) {
-        sbrec_sb_global_set_options(sb, &options);
-    }
-    smap_destroy(&options);
 }
 
 /* Stores the set of chassis which references an ha_chassis_group.
@@ -17714,12 +17558,6 @@  ovnsb_db_run(struct ovsdb_idl_txn *ovnnb_txn,
     ovn_update_ipv6_prefix(lr_ports);
 }
 
-const char *
-northd_get_svc_monitor_mac(void)
-{
-    return svc_monitor_mac;
-}
-
 const struct ovn_datapath *
 northd_get_datapath_for_port(const struct hmap *ls_ports,
                              const char *port_name)
diff --git a/northd/northd.h b/northd/northd.h
index 9d001206c1..b5c175929e 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -27,7 +27,6 @@ 
 
 struct northd_input {
     /* Northbound table references */
-    const struct nbrec_nb_global_table *nbrec_nb_global_table;
     const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
     const struct nbrec_logical_router_table *nbrec_logical_router_table;
     const struct nbrec_static_mac_binding_table
@@ -37,7 +36,6 @@  struct northd_input {
     const struct nbrec_mirror_table *nbrec_mirror_table;
 
     /* Southbound table references */
-    const struct sbrec_sb_global_table *sbrec_sb_global_table;
     const struct sbrec_datapath_binding_table *sbrec_datapath_binding_table;
     const struct sbrec_port_binding_table *sbrec_port_binding_table;
     const struct sbrec_mac_binding_table *sbrec_mac_binding_table;
@@ -57,6 +55,13 @@  struct northd_input {
     const struct hmap *lbs;
     const struct hmap *lbgrps;
 
+    /* Global config data node inputs. */
+    const struct smap *nb_options;
+    const struct smap *sb_options;
+    const char *svc_monitor_mac;
+    struct eth_addr svc_monitor_mac_ea;
+    const struct chassis_features *features;
+
     /* Indexes */
     struct ovsdb_idl_index *sbrec_chassis_by_name;
     struct ovsdb_idl_index *sbrec_chassis_by_hostname;
@@ -66,14 +71,6 @@  struct northd_input {
     struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port;
 };
 
-struct chassis_features {
-    bool ct_no_masked_label;
-    bool mac_binding_timestamp;
-    bool ct_lb_related;
-    bool fdb_timestamp;
-    bool ls_dpg_column;
-};
-
 /* A collection of datapaths. E.g. all logical switch datapaths, or all
  * logical router datapaths. */
 struct ovn_datapaths {
@@ -156,8 +153,6 @@  struct northd_data {
     struct hmap lb_datapaths_map;
     struct hmap lb_group_datapaths_map;
     struct ovs_list lr_list;
-    bool ovn_internal_version_changed;
-    struct chassis_features features;
     struct sset svc_monitor_lsps;
     struct hmap svc_monitor_map;
 
@@ -194,6 +189,7 @@  struct lflow_input {
     const struct chassis_features *features;
     const struct hmap *svc_monitor_map;
     bool ovn_internal_version_changed;
+    const char *svc_monitor_mac;
 };
 
 extern int parallelization_state;
@@ -722,8 +718,6 @@  void bfd_cleanup_connections(const struct nbrec_bfd_table *,
                              struct hmap *bfd_map);
 void run_update_worker_pool(int n_threads);
 
-const char *northd_get_svc_monitor_mac(void);
-
 const struct ovn_datapath *northd_get_datapath_for_port(
     const struct hmap *ls_ports, const char *port_name);
 
@@ -787,4 +781,6 @@  lr_has_multiple_gw_ports(const struct ovn_datapath *od)
     return od->n_l3dgw_ports > 1 && !od->is_gw_router;
 }
 
+uint32_t get_ovn_max_dp_key_local(const struct sbrec_chassis_table *);
+
 #endif /* NORTHD_H */
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 929bb45aed..d8ec3eead5 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -8838,7 +8838,7 @@  AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
   table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
 ])
 
-ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
+check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=true
 
 ovn-sbctl dump-flows S0 > S0flows
 ovn-sbctl dump-flows S1 > S1flows
@@ -8857,6 +8857,7 @@  AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
   table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 172.16.0.11 && tcp.dst == 8080), action=(reg0[[1]] = 0; ct_lb_mark(backends=10.0.0.2:8080);)
 ])
 
+
 ovn-sbctl get datapath S0 _uuid > dp_uuids
 ovn-sbctl get datapath S1 _uuid >> dp_uuids
 lb_dp_group=$(ovn-sbctl --bare --columns ls_datapath_group find Load_Balancer name=lb0)
@@ -8865,7 +8866,7 @@  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
 $(cat dp_uuids | sort)
 ])
 
-ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=false
+check ovn-nbctl --wait=sb set NB_Global . options:install_ls_lb_from_router=false
 
 ovn-sbctl dump-flows S0 > S0flows
 ovn-sbctl dump-flows S1 > S1flows
@@ -9250,12 +9251,11 @@  $4
 AS_BOX([Create new PG1 and PG2])
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb -- pg-add pg1 -- pg-add pg2
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl The port_group node recomputes every time a NB port group is added/deleted.
@@ -9288,12 +9288,11 @@  check ovn-nbctl --wait=sb         \
 check_column "sw1.1" sb:Port_Group ports name="${sw1_key}_pg1"
 check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl The port_group node recomputes also every time a port from a new switch
@@ -9325,12 +9324,11 @@  check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
 check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
 check_column "sw2.2" sb:Port_Group ports name="${sw2_key}_pg2"
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl The port_group node recomputes also every time a port from a new switch
@@ -9363,12 +9361,11 @@  check_column "sw2.1 sw2.3" sb:Port_Group ports name="${sw2_key}_pg1"
 check_column "sw1.2 sw1.3" sb:Port_Group ports name="${sw1_key}_pg2"
 check_column "sw2.2 sw2.3" sb:Port_Group ports name="${sw2_key}_pg2"
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl We did not change the set of switches a pg is applied to, there should be
@@ -9406,7 +9403,7 @@  dnl though, therefore "compute: 1".
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl We did not change the set of switches a pg is applied to, there should be
@@ -9438,12 +9435,11 @@  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
 AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
 ])
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl We changed the set of switches a pg is applied to, there should be
@@ -9476,12 +9472,11 @@  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
 AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
 ])
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl We changed the set of switches a pg is applied to, there should be
@@ -9514,12 +9509,11 @@  check_column "sw2.1" sb:Port_Group ports name="${sw2_key}_pg1"
 check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
 check_column "sw2.2" sb:Port_Group ports name="${sw2_key}_pg2"
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl We changed the set of switches a pg is applied to, there should be a
@@ -9554,12 +9548,11 @@  check_column "sw1.2" sb:Port_Group ports name="${sw1_key}_pg2"
 AT_CHECK([fetch_column sb:Port_Group ports name="${sw2_key}_pg2"], [0], [
 ])
 
-dnl The northd node should not recompute, it should handle nb_global update
-dnl though, therefore "compute: 1".
+dnl The northd node should not recompute,.
 AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/show-stats northd], [0], [dnl
 Node: northd
 - recompute:            0
-- compute:              1
+- compute:              0
 - abort:                0
 ])
 dnl We changed the set of switches a pg is applied to, there should be a
@@ -11922,3 +11915,212 @@  CHECK_NO_CHANGE_AFTER_RECOMPUTE
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([NB_Global and SB_Global incremental processing])
+
+ovn_start
+
+check_engine_stats() {
+  node=$1
+  recompute=$2
+  compute=$3
+
+  echo "__file__:__line__: Checking engine stats for node $node : recompute - \
+$recompute : compute - $compute"
+
+  node_stat=$(as northd ovn-appctl -t ovn-northd inc-engine/show-stats $node)
+  # node_stat will be of this format :
+  #     - Node: lflow - recompute: 3 - compute: 0 - abort: 0
+  node_recompute_ct=$(echo $node_stat | cut -d '-' -f2 | cut -d ':' -f2)
+  node_compute_ct=$(echo $node_stat | cut -d '-' -f3 | cut -d ':' -f2)
+
+  if [[ "$recompute" == "norecompute" ]]; then
+    # node should not be recomputed
+    echo "Expecting $node recompute count - $node_recompute_ct to be 0"
+    check test "$node_recompute_ct" -eq "0"
+  else
+    echo "Expecting $node recompute count - $node_recompute_ct not to be 0"
+    check test "$node_recompute_ct" -ne "0"
+  fi
+
+  if [[ "$compute" == "nocompute" ]]; then
+    # node should not be computed
+    echo "Expecting $node compute count - $node_compute_ct to be 0"
+    check test "$node_compute_ct" -eq "0"
+  else
+    echo "Expecting $node compute count - $node_compute_ct not to be 0"
+    check test "$node_compute_ct" -ne "0"
+  fi
+}
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 "00:00:20:20:00:03 10.0.0.3"
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 router
+check ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+
+# This should not result in recomputes.
+check ovn-nbctl --wait=sb set NB_Global . options:foo=bar
+check_engine_stats global_config norecompute compute
+check_engine_stats northd norecompute compute
+check_engine_stats lflow norecompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+# This should result in recomputes.
+check ovn-sbctl set SB_Global . options:bar=foo
+check_engine_stats global_config recompute compute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Clears an nb option and checks that recomputes were triggered
+# and the option was added back by ovn-northd or not depending
+# on the 'added_back' argument.
+clear_nb_option() {
+  option=$1
+  add_back=$2
+  echo "clearing the nb option - $option"
+  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+  check ovn-nbctl --wait=sb remove NB_Global . options $option
+  check_engine_stats global_config recompute compute
+  check_engine_stats northd recompute nocompute
+  check_engine_stats lflow recompute nocompute
+
+  local retval=1
+  if [ "$add_back" == "true" ]; then
+    retval=0
+  fi
+  AT_CHECK([ovn-nbctl get NB_Global . options:$option], [$retval], [ignore], [ignore])
+}
+
+# Clear svc_monitor_mac and few other options which result in recompute.
+# and ovn-northd should update the nb options back.
+clear_nb_option svc_monitor_mac true
+clear_nb_option max_tunid true
+clear_nb_option mac_prefix true
+clear_nb_option northd_internal_version true
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set NB_Global . options:ignore_chassis_features=true
+check_engine_stats global_config recompute compute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+clear_nb_option ignore_chassis_features false
+
+set_nb_option_lflow_recompute() {
+  local option=$1
+  local value=$2
+  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+  check ovn-nbctl --wait=sb set NB_Global . options:$option=$value
+  check_engine_stats global_config norecompute compute
+  check_engine_stats northd recompute nocompute
+  check_engine_stats lflow recompute nocompute
+  check_engine_stats mac_binding_aging recompute nocompute
+  CHECK_NO_CHANGE_AFTER_RECOMPUTE
+}
+
+clear_nb_option_lflow_recompute() {
+  local option=$1
+  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+  check ovn-nbctl --wait=sb remove NB_Global . options $option
+  check_engine_stats global_config norecompute compute
+  check_engine_stats northd recompute nocompute
+  check_engine_stats lflow recompute nocompute
+  check_engine_stats mac_binding_aging recompute nocompute
+  CHECK_NO_CHANGE_AFTER_RECOMPUTE
+}
+
+set_nb_option_lflow_recompute debug_drop_domain_id 1
+clear_nb_option_lflow_recompute debug_drop_domain_id
+
+set_nb_option_lflow_recompute debug_drop_collector_set 1
+clear_nb_option_lflow_recompute debug_drop_collector_set
+
+set_nb_option_lflow_recompute mac_binding_removal_limit 100
+clear_nb_option_lflow_recompute mac_binding_removal_limit
+
+set_nb_option_lflow_recompute fdb_removal_limit 100
+clear_nb_option_lflow_recompute fdb_removal_limit
+
+set_nb_option_lflow_recompute controller_event true
+clear_nb_option_lflow_recompute controller_event
+
+set_nb_option_lflow_recompute ignore_lsp_down true
+clear_nb_option_lflow_recompute ignore_lsp_down
+
+set_nb_option_lflow_recompute use_ct_inv_match true
+clear_nb_option_lflow_recompute use_ct_inv_match
+
+set_nb_option_lflow_recompute default_acl_drop true
+clear_nb_option_lflow_recompute default_acl_drop
+
+set_nb_option_lflow_recompute use_common_zone true
+clear_nb_option_lflow_recompute use_common_zone
+
+set_nb_option_lflow_recompute install_ls_lb_from_router true
+clear_nb_option_lflow_recompute install_ls_lb_from_router
+
+# Now test changes to chassis for feature changes.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
+check ovn-nbctl --wait=sb sync
+check_engine_stats global_config recompute compute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl chassis-add ch2 geneve 127.0.0.2
+check ovn-nbctl --wait=sb sync
+check_engine_stats global_config recompute compute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+AT_CHECK([ovn-nbctl get NB_Global . options:max_tunid | \
+sed s/":"//g | sed s/\"//g], [0], [16711680
+], [])
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl chassis-del ch2
+check ovn-nbctl --wait=sb sync
+check_engine_stats global_config recompute compute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set encap . type=vxlan
+check ovn-nbctl --wait=sb sync
+check_engine_stats global_config recompute compute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+AT_CHECK([ovn-nbctl get NB_Global . options:max_tunid | \
+sed s/":"//g | sed s/\"//g], [0], [4095
+], [])
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set chassis . other_config:foo=bar
+check ovn-nbctl --wait=sb sync
+check_engine_stats global_config norecompute compute
+check_engine_stats mac_binding_aging recompute nocompute
+check_engine_stats fdb_aging recompute nocompute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set chassis . other_config:ct-no-masked-label=true
+check ovn-nbctl --wait=sb sync
+check_engine_stats global_config norecompute compute
+check_engine_stats mac_binding_aging recompute nocompute
+check_engine_stats fdb_aging recompute nocompute
+check_engine_stats northd recompute nocompute
+check_engine_stats lflow recompute nocompute
+
+AT_CLEANUP
+])