On Tue, Jan 30, 2024 at 4:23 PM <[email protected]> wrote:
>
> From: Numan Siddique <[email protected]>
>
> It also moves the ovn-controller specific code from lib/lb.c
> and lib/lb.h to controller/lb.c and controller/lb.h.
>
> Acked-by: Han Zhou <[email protected]>
> Co-authored-by: Dumitru Ceara <[email protected]>
> Signed-off-by: Dumitru Ceara <[email protected]>
> Signed-off-by: Numan Siddique <[email protected]>

Recheck-request: github-robot-_Build_and_Test

> ---
>  controller/automake.mk  |   2 +
>  controller/lb.c         | 146 ++++++++
>  controller/lb.h         |  55 +++
>  controller/lflow.c      |   1 +
>  lib/lb.c                | 771 +---------------------------------------
>  lib/lb.h                | 199 +----------
>  northd/automake.mk      |   4 +-
>  northd/en-lb-data.c     |   1 +
>  northd/en-lr-stateful.c |   1 +
>  northd/en-sync-sb.c     |   1 +
>  northd/lb.c             | 651 +++++++++++++++++++++++++++++++++
>  northd/lb.h             | 189 ++++++++++
>  northd/northd.c         |   1 +
>  13 files changed, 1068 insertions(+), 954 deletions(-)
>  create mode 100644 controller/lb.c
>  create mode 100644 controller/lb.h
>  create mode 100644 northd/lb.c
>  create mode 100644 northd/lb.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index 0dbbd5d26b..a17ff0d60b 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -14,6 +14,8 @@ controller_ovn_controller_SOURCES = \
>         controller/if-status.h \
>         controller/ip-mcast.c \
>         controller/ip-mcast.h \
> +       controller/lb.c \
> +       controller/lb.h \
>         controller/lflow.c \
>         controller/lflow.h \
>         controller/lflow-cache.c \
> diff --git a/controller/lb.c b/controller/lb.c
> new file mode 100644
> index 0000000000..8f9f20ed54
> --- /dev/null
> +++ b/controller/lb.c
> @@ -0,0 +1,146 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * 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>
> +
> +/* OpenvSwitch lib includes. */
> +#include "openvswitch/vlog.h"
> +#include "lib/smap.h"
> +
> +/* OVN includes */
> +#include "lb.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "ovn/lex.h"
> +
> +VLOG_DEFINE_THIS_MODULE(controller_lb);
> +
> +static void
> +ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
> +                           const struct smap *lb_options,
> +                           struct lport_addresses *hairpin_addrs)
> +{
> +    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
> +
> +    if (!addresses) {
> +        return;
> +    }
> +
> +    if (!extract_ip_address(addresses, hairpin_addrs)) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
> +                     addresses, UUID_ARGS(lb_uuid));
> +    }
> +}
> +
> +struct ovn_controller_lb *
> +ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
> +                         const struct smap *template_vars,
> +                         struct sset *template_vars_ref)
> +{
> +    struct ovn_controller_lb *lb = xzalloc(sizeof *lb);
> +    bool template = smap_get_bool(&sbrec_lb->options, "template", false);
> +
> +    lb->slb = sbrec_lb;
> +    lb->n_vips = smap_count(&sbrec_lb->vips);
> +    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> +
> +    struct smap_node *node;
> +    size_t n_vips = 0;
> +
> +    SMAP_FOR_EACH (node, &sbrec_lb->vips) {
> +        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> +
> +        struct lex_str key_s = template
> +                               ? lexer_parse_template_string(node->key,
> +                                                             template_vars,
> +                                                             
> template_vars_ref)
> +                               : lex_str_use(node->key);
> +        struct lex_str value_s = template
> +                               ? lexer_parse_template_string(node->value,
> +                                                             template_vars,
> +                                                             
> template_vars_ref)
> +                               : lex_str_use(node->value);
> +        char *error = ovn_lb_vip_init_explicit(lb_vip,
> +                                               lex_str_get(&key_s),
> +                                               lex_str_get(&value_s));
> +        if (error) {
> +            free(error);
> +        } else {
> +            n_vips++;
> +        }
> +        lex_str_free(&key_s);
> +        lex_str_free(&value_s);
> +    }
> +
> +    lb->proto = IPPROTO_TCP;
> +    if (sbrec_lb->protocol && sbrec_lb->protocol[0]) {
> +        if (!strcmp(sbrec_lb->protocol, "udp")) {
> +            lb->proto = IPPROTO_UDP;
> +        } else if (!strcmp(sbrec_lb->protocol, "sctp")) {
> +            lb->proto = IPPROTO_SCTP;
> +        }
> +    }
> +
> +    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> +     * correct value.
> +     */
> +    lb->n_vips = n_vips;
> +
> +    lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
> +                                           "hairpin_orig_tuple",
> +                                           false);
> +    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
> +    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
> +                               &lb->hairpin_snat_ips);
> +    return lb;
> +}
> +
> +void
> +ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
> +{
> +    for (size_t i = 0; i < lb->n_vips; i++) {
> +        ovn_lb_vip_destroy(&lb->vips[i]);
> +    }
> +    free(lb->vips);
> +    destroy_lport_addresses(&lb->hairpin_snat_ips);
> +    free(lb);
> +}
> +
> +void
> +ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs)
> +{
> +    struct ovn_controller_lb *lb;
> +    HMAP_FOR_EACH_POP (lb, hmap_node, ovn_controller_lbs) {
> +        ovn_controller_lb_destroy(lb);
> +    }
> +
> +    hmap_destroy(ovn_controller_lbs);
> +}
> +
> +struct ovn_controller_lb *
> +ovn_controller_lb_find(const struct hmap *ovn_controller_lbs,
> +                       const struct uuid *uuid)
> +{
> +    struct ovn_controller_lb *lb;
> +    size_t hash = uuid_hash(uuid);
> +    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, ovn_controller_lbs) {
> +        if (uuid_equals(&lb->slb->header_.uuid, uuid)) {
> +            return lb;
> +        }
> +    }
> +    return NULL;
> +}
> +
> diff --git a/controller/lb.h b/controller/lb.h
> new file mode 100644
> index 0000000000..84d51c3329
> --- /dev/null
> +++ b/controller/lb.h
> @@ -0,0 +1,55 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * 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.
> + */
> +
> +#ifndef OVN_CONTROLLER_LB_H
> +#define OVN_CONTROLLER_LB_H 1
> +
> +#include "lib/lb.h"
> +
> +struct sbrec_load_balancer;
> +
> +struct ovn_controller_lb {
> +    struct hmap_node hmap_node;
> +
> +    const struct sbrec_load_balancer *slb; /* May be NULL. */
> +
> +    uint8_t proto;
> +
> +    struct ovn_lb_vip *vips;
> +    size_t n_vips;
> +    bool hairpin_orig_tuple; /* True if ovn-northd stores the original
> +                              * destination tuple in registers.
> +                              */
> +    bool ct_flush; /* True if we should flush CT after backend removal. */
> +
> +    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
> +                                              * as source for hairpinned
> +                                              * traffic.
> +                                              */
> +};
> +
> +struct ovn_controller_lb *ovn_controller_lb_create(
> +    const struct sbrec_load_balancer *,
> +    const struct smap *template_vars,
> +    struct sset *template_vars_ref);
> +void ovn_controller_lb_destroy(struct ovn_controller_lb *);
> +void ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs);
> +struct ovn_controller_lb *ovn_controller_lb_find(
> +    const struct hmap *ovn_controller_lbs,
> +    const struct uuid *uuid);
> +
> +#endif /* OVN_CONTROLLER_LB_H */
> +
> diff --git a/controller/lflow.c b/controller/lflow.c
> index c0cf0aa106..895d17d193 100644
> --- a/controller/lflow.c
> +++ b/controller/lflow.c
> @@ -18,6 +18,7 @@
>  #include "lflow.h"
>  #include "coverage.h"
>  #include "ha-chassis.h"
> +#include "lb.h"
>  #include "lflow-cache.h"
>  #include "local_data.h"
>  #include "lport.h"
> diff --git a/lib/lb.c b/lib/lb.c
> index d0d562b6fb..e67a5fcfd0 100644
> --- a/lib/lb.c
> +++ b/lib/lb.c
> @@ -16,76 +16,16 @@
>  #include <config.h>
>
>  #include "lb.h"
> -#include "lib/ovn-nb-idl.h"
> -#include "lib/ovn-sb-idl.h"
>  #include "lib/ovn-util.h"
> -#include "northd/northd.h"
>  #include "ovn/lex.h"
>
>  /* OpenvSwitch lib includes. */
>  #include "openvswitch/vlog.h"
> -#include "lib/bitmap.h"
> -#include "lib/smap.h"
> -#include "socket-util.h"
>
>  VLOG_DEFINE_THIS_MODULE(lb);
>
> -static const char *lb_neighbor_responder_mode_names[] = {
> -    [LB_NEIGH_RESPOND_REACHABLE] = "reachable",
> -    [LB_NEIGH_RESPOND_ALL] = "all",
> -    [LB_NEIGH_RESPOND_NONE] = "none",
> -};
> -
> -static struct nbrec_load_balancer_health_check *
> -ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> -                        const char *vip_port_str, bool template);
>  static void ovn_lb_backends_clear(struct ovn_lb_vip *vip);
>
> -struct ovn_lb_ip_set *
> -ovn_lb_ip_set_create(void)
> -{
> -    struct ovn_lb_ip_set *lb_ip_set = xzalloc(sizeof *lb_ip_set);
> -
> -    sset_init(&lb_ip_set->ips_v4);
> -    sset_init(&lb_ip_set->ips_v4_routable);
> -    sset_init(&lb_ip_set->ips_v4_reachable);
> -    sset_init(&lb_ip_set->ips_v6);
> -    sset_init(&lb_ip_set->ips_v6_routable);
> -    sset_init(&lb_ip_set->ips_v6_reachable);
> -
> -    return lb_ip_set;
> -}
> -
> -void
> -ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *lb_ip_set)
> -{
> -    if (!lb_ip_set) {
> -        return;
> -    }
> -    sset_destroy(&lb_ip_set->ips_v4);
> -    sset_destroy(&lb_ip_set->ips_v4_routable);
> -    sset_destroy(&lb_ip_set->ips_v4_reachable);
> -    sset_destroy(&lb_ip_set->ips_v6);
> -    sset_destroy(&lb_ip_set->ips_v6_routable);
> -    sset_destroy(&lb_ip_set->ips_v6_reachable);
> -    free(lb_ip_set);
> -}
> -
> -struct ovn_lb_ip_set *
> -ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set)
> -{
> -    struct ovn_lb_ip_set *clone = ovn_lb_ip_set_create();
> -
> -    sset_clone(&clone->ips_v4, &lb_ip_set->ips_v4);
> -    sset_clone(&clone->ips_v4_routable, &lb_ip_set->ips_v4_routable);
> -    sset_clone(&clone->ips_v4_reachable, &lb_ip_set->ips_v4_reachable);
> -    sset_clone(&clone->ips_v6, &lb_ip_set->ips_v6);
> -    sset_clone(&clone->ips_v6_routable, &lb_ip_set->ips_v6_routable);
> -    sset_clone(&clone->ips_v6_reachable, &lb_ip_set->ips_v6_reachable);
> -
> -    return clone;
> -}
> -
>  /* Format for backend ips: "IP1:port1,IP2:port2,...". */
>  static char *
>  ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, const char *value)
> @@ -160,9 +100,9 @@ ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, 
> const char *value)
>      return NULL;
>  }
>
> -static
> -char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
> -                               const char *lb_value)
> +char *
> +ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
> +                         const char *lb_value)
>  {
>      if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str,
>                                           &lb_vip->vip, &lb_vip->vip_port,
> @@ -369,22 +309,6 @@ ovn_lb_vip_format__(const struct ovn_lb_vip *vip, struct 
> ds *s,
>      }
>  }
>
> -/* Formats the VIP in the way the ovn-controller expects it, that is,
> - * template IPv6 variables need to be between brackets too.
> - */
> -static char *
> -ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
> -{
> -    struct ds s = DS_EMPTY_INITIALIZER;
> -
> -    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
> -        ovn_lb_vip_format__(vip, &s, true);
> -    } else {
> -        ovn_lb_vip_format(vip, &s, !!vip->port_str);
> -    }
> -    return ds_steal_cstr(&s);
> -}
> -
>  void
>  ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
>  {
> @@ -417,540 +341,20 @@ ovn_lb_vip_backends_format(const struct ovn_lb_vip 
> *vip, struct ds *s)
>      }
>  }
>
> -static
> -void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
> -                            const struct ovn_lb_vip *lb_vip,
> -                            const struct nbrec_load_balancer *nbrec_lb,
> -                            const char *vip_port_str, const char 
> *backend_ips,
> -                            bool template)
> -{
> -    lb_vip_nb->backend_ips = xstrdup(backend_ips);
> -    lb_vip_nb->n_backends = lb_vip->n_backends;
> -    lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends,
> -                                     sizeof *lb_vip_nb->backends_nb);
> -    lb_vip_nb->lb_health_check =
> -        ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
> -}
> -
> -static void
> -ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
> -                                      const struct ovn_lb_vip *lb_vip,
> -                                      struct ovn_northd_lb_vip *lb_vip_nb)
> -{
> -    struct ds key = DS_EMPTY_INITIALIZER;
> -
> -    for (size_t j = 0; j < lb_vip->n_backends; j++) {
> -        struct ovn_lb_backend *backend = &lb_vip->backends[j];
> -        ds_clear(&key);
> -        ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)
> -                      ? "%s" : "[%s]", backend->ip_str);
> -
> -        const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key));
> -        if (!s) {
> -            continue;
> -        }
> -
> -        char *svc_mon_src_ip = NULL;
> -        char *port_name = xstrdup(s);
> -        char *p = strstr(port_name, ":");
> -        if (p) {
> -            *p = 0;
> -            p++;
> -            struct sockaddr_storage svc_mon_src_addr;
> -            if (!inet_parse_address(p, &svc_mon_src_addr)) {
> -                static struct vlog_rate_limit rl =
> -                    VLOG_RATE_LIMIT_INIT(5, 1);
> -                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
> -            } else {
> -                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
> -                ss_format_address_nobracks(&svc_mon_src_addr,
> -                                            &src_ip_s);
> -                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
> -            }
> -        }
> -
> -        if (svc_mon_src_ip) {
> -            struct ovn_northd_lb_backend *backend_nb =
> -                &lb_vip_nb->backends_nb[j];
> -            backend_nb->health_check = true;
> -            backend_nb->logical_port = xstrdup(port_name);
> -            backend_nb->svc_mon_src_ip = svc_mon_src_ip;
> -        }
> -        free(port_name);
> -    }
> -
> -    ds_destroy(&key);
> -}
> -
> -static
> -void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
> -{
> -    free(vip->backend_ips);
> -    for (size_t i = 0; i < vip->n_backends; i++) {
> -        free(vip->backends_nb[i].logical_port);
> -        free(vip->backends_nb[i].svc_mon_src_ip);
> -    }
> -    free(vip->backends_nb);
> -}
> -
> -static void
> -ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
> -                           const struct smap *lb_options,
> -                           struct lport_addresses *hairpin_addrs)
> -{
> -    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
> -
> -    if (!addresses) {
> -        return;
> -    }
> -
> -    if (!extract_ip_address(addresses, hairpin_addrs)) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
> -                     addresses, UUID_ARGS(lb_uuid));
> -    }
> -}
> -
> -static bool
> -ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb,
> -                         bool routable, bool template)
> -{
> -    if (template && routable) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport 
> "
> -                           "option 'add_route'.  Forcing it to disabled.",
> -                     UUID_ARGS(&nbrec_lb->header_.uuid));
> -        return false;
> -    }
> -    return routable;
> -}
> -
> -static bool
> -ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool 
> template)
> -{
> -    if (!template) {
> -        return true;
> -    }
> -
> -    switch (mode) {
> -    case LB_NEIGH_RESPOND_REACHABLE:
> -        return false;
> -    case LB_NEIGH_RESPOND_ALL:
> -    case LB_NEIGH_RESPOND_NONE:
> -        return true;
> -    }
> -    return false;
> -}
> -
> -static enum lb_neighbor_responder_mode
> -ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb,
> -                      const char *mode, bool template)
> -{
> -    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -    enum lb_neighbor_responder_mode default_mode =
> -        template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE;
> -
> -    if (!mode) {
> -        mode = lb_neighbor_responder_mode_names[default_mode];
> -    }
> -
> -    for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); 
> i++) {
> -        if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) {
> -            if (ovn_lb_neigh_mode_is_valid(i, template)) {
> -                return i;
> -            }
> -            break;
> -        }
> -    }
> -
> -    VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer "
> -                       UUID_FMT", forcing it to %s",
> -                 mode, UUID_ARGS(&nbrec_lb->header_.uuid),
> -                 lb_neighbor_responder_mode_names[default_mode]);
> -    return default_mode;
> -}
> -
> -static struct nbrec_load_balancer_health_check *
> -ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> -                        const char *vip_port_str, bool template)
> -{
> -    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -
> -    if (!nbrec_lb->n_health_check) {
> -        return NULL;
> -    }
> -
> -    if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) {
> -        VLOG_WARN_RL(&rl,
> -                     "SCTP load balancers do not currently support "
> -                     "health checks. Not creating health checks for "
> -                     "load balancer " UUID_FMT,
> -                     UUID_ARGS(&nbrec_lb->header_.uuid));
> -        return NULL;
> -    }
> -
> -    if (template) {
> -        VLOG_WARN_RL(&rl,
> -                     "Template load balancers do not currently support "
> -                     "health checks. Not creating health checks for "
> -                     "load balancer " UUID_FMT,
> -                     UUID_ARGS(&nbrec_lb->header_.uuid));
> -        return NULL;
> -    }
> -
> -    for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
> -        if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) {
> -            return nbrec_lb->health_check[i];
> -        }
> -    }
> -    return NULL;
> -}
> -
> -static void
> -ovn_northd_lb_init(struct ovn_northd_lb *lb,
> -                   const struct nbrec_load_balancer *nbrec_lb)
> -{
> -    bool template = smap_get_bool(&nbrec_lb->options, "template", false);
> -    bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp");
> -    bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp");
> -    int address_family = !strcmp(smap_get_def(&nbrec_lb->options,
> -                                              "address-family", "ipv4"),
> -                                 "ipv4")
> -                         ? AF_INET
> -                         : AF_INET6;
> -
> -    lb->nlb = nbrec_lb;
> -    lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
> -    lb->n_vips = smap_count(&nbrec_lb->vips);
> -    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> -    lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
> -    smap_init(&lb->template_vips);
> -    lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
> -
> -    bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
> -    lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, template);
> -
> -    lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false);
> -    lb->template = template;
> -
> -    const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder");
> -    lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template);
> -
> -    uint32_t affinity_timeout =
> -        smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0);
> -    if (affinity_timeout > UINT16_MAX) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_WARN_RL(&rl, "max affinity_timeout timeout value is %u",
> -                     UINT16_MAX);
> -        affinity_timeout = UINT16_MAX;
> -    }
> -    lb->affinity_timeout = affinity_timeout;
> -
> -    sset_init(&lb->ips_v4);
> -    sset_init(&lb->ips_v6);
> -    struct smap_node *node;
> -    size_t n_vips = 0;
> -
> -    SMAP_FOR_EACH (node, &nbrec_lb->vips) {
> -        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> -        struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
> -
> -        char *error = ovn_lb_vip_init(lb_vip, node->key, node->value,
> -                                      template, address_family);
> -        if (error) {
> -            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -            VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error);
> -            ovn_lb_vip_destroy(lb_vip);
> -            free(error);
> -            continue;
> -        }
> -        lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
> -                                                  "reject", false);
> -        ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb,
> -                               node->key, node->value, template);
> -        if (lb_vip_nb->lb_health_check) {
> -            lb->health_checks = true;
> -        }
> -
> -        if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
> -            sset_add(&lb->ips_v4, lb_vip->vip_str);
> -        } else {
> -            sset_add(&lb->ips_v6, lb_vip->vip_str);
> -        }
> -
> -        if (lb->template && address_family == AF_INET6) {
> -            smap_add_nocopy(&lb->template_vips,
> -                            ovn_lb_vip6_template_format_internal(lb_vip),
> -                            xstrdup(node->value));
> -        }
> -        n_vips++;
> -
> -        if (lb_vip_nb->lb_health_check) {
> -            ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
> -        }
> -    }
> -
> -    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> -     * correct value.
> -     */
> -    lb->n_vips = n_vips;
> -
> -    if (nbrec_lb->n_selection_fields) {
> -        char *proto = NULL;
> -        if (nbrec_lb->protocol && nbrec_lb->protocol[0]) {
> -            proto = nbrec_lb->protocol;
> -        }
> -
> -        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> -        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> -            char *field = lb->nlb->selection_fields[i];
> -            if (!strcmp(field, "tp_src") && proto) {
> -                ds_put_format(&sel_fields, "%s_src,", proto);
> -            } else if (!strcmp(field, "tp_dst") && proto) {
> -                ds_put_format(&sel_fields, "%s_dst,", proto);
> -            } else {
> -                ds_put_format(&sel_fields, "%s,", field);
> -            }
> -        }
> -        ds_chomp(&sel_fields, ',');
> -        lb->selection_fields = ds_steal_cstr(&sel_fields);
> -    }
> -}
> -
> -struct ovn_northd_lb *
> -ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
> -{
> -    struct ovn_northd_lb *lb = xzalloc(sizeof *lb);
> -    ovn_northd_lb_init(lb, nbrec_lb);
> -    return lb;
> -}
> -
> -struct ovn_northd_lb *
> -ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
> -{
> -    struct ovn_northd_lb *lb;
> -    size_t hash = uuid_hash(uuid);
> -    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
> -        if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
> -            return lb;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -const struct smap *
> -ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
> -{
> -    if (!smap_is_empty(&lb->template_vips)) {
> -        return &lb->template_vips;
> -    }
> -    return &lb->nlb->vips;
> -}
> -
> -static void
> -ovn_northd_lb_cleanup(struct ovn_northd_lb *lb)
> -{
> -    for (size_t i = 0; i < lb->n_vips; i++) {
> -        ovn_lb_vip_destroy(&lb->vips[i]);
> -        ovn_northd_lb_vip_destroy(&lb->vips_nb[i]);
> -    }
> -    free(lb->vips);
> -    free(lb->vips_nb);
> -    lb->vips = NULL;
> -    lb->vips_nb = NULL;
> -    smap_destroy(&lb->template_vips);
> -    sset_destroy(&lb->ips_v4);
> -    sset_destroy(&lb->ips_v6);
> -    free(lb->selection_fields);
> -    lb->selection_fields = NULL;
> -    lb->health_checks = false;
> -}
> -
> -void
> -ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
> -{
> -    ovn_northd_lb_cleanup(lb);
> -    free(lb);
> -}
> -
> -void
> -ovn_northd_lb_reinit(struct ovn_northd_lb *lb,
> -                     const struct nbrec_load_balancer *nbrec_lb)
> -{
> -    ovn_northd_lb_cleanup(lb);
> -    ovn_northd_lb_init(lb, nbrec_lb);
> -}
> -
> -static void
> -ovn_lb_group_init(struct ovn_lb_group *lb_group,
> -                  const struct nbrec_load_balancer_group *nbrec_lb_group,
> -                  const struct hmap *lbs)
> -{
> -    lb_group->n_lbs = nbrec_lb_group->n_load_balancer;
> -    lb_group->lbs = xmalloc(lb_group->n_lbs * sizeof *lb_group->lbs);
> -    lb_group->lb_ips = ovn_lb_ip_set_create();
> -
> -    for (size_t i = 0; i < nbrec_lb_group->n_load_balancer; i++) {
> -        const struct uuid *lb_uuid =
> -            &nbrec_lb_group->load_balancer[i]->header_.uuid;
> -        lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
> -        lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
> -    }
> -}
> -
> -/* Constructs a new 'struct ovn_lb_group' object from the Nb LB Group record
> - * and an array of 'struct ovn_northd_lb' objects for its associated
> - * load balancers. */
> -struct ovn_lb_group *
> -ovn_lb_group_create(const struct nbrec_load_balancer_group *nbrec_lb_group,
> -                    const struct hmap *lbs)
> -{
> -    struct ovn_lb_group *lb_group = xzalloc(sizeof *lb_group);
> -    lb_group->uuid = nbrec_lb_group->header_.uuid;
> -    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> -    return lb_group;
> -}
> -
> -static void
> -ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
> -{
> -    ovn_lb_ip_set_destroy(lb_group->lb_ips);
> -    lb_group->lb_ips = NULL;
> -    lb_group->has_routable_lb = false;
> -    free(lb_group->lbs);
> -}
> -
> -void
> -ovn_lb_group_destroy(struct ovn_lb_group *lb_group)
> -{
> -    if (!lb_group) {
> -        return;
> -    }
> -
> -    ovn_lb_group_cleanup(lb_group);
> -    free(lb_group);
> -}
> -
> -void
> -ovn_lb_group_reinit(struct ovn_lb_group *lb_group,
> -                    const struct nbrec_load_balancer_group *nbrec_lb_group,
> -                    const struct hmap *lbs)
> -{
> -    ovn_lb_group_cleanup(lb_group);
> -    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> -}
> -
> -struct ovn_lb_group *
> -ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid)
> -{
> -    struct ovn_lb_group *lb_group;
> -    size_t hash = uuid_hash(uuid);
> -
> -    HMAP_FOR_EACH_WITH_HASH (lb_group, hmap_node, hash, lb_groups) {
> -        if (uuid_equals(&lb_group->uuid, uuid)) {
> -            return lb_group;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -struct ovn_controller_lb *
> -ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
> -                         const struct smap *template_vars,
> -                         struct sset *template_vars_ref)
> -{
> -    struct ovn_controller_lb *lb = xzalloc(sizeof *lb);
> -    bool template = smap_get_bool(&sbrec_lb->options, "template", false);
> -
> -    lb->slb = sbrec_lb;
> -    lb->n_vips = smap_count(&sbrec_lb->vips);
> -    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> -
> -    struct smap_node *node;
> -    size_t n_vips = 0;
> -
> -    SMAP_FOR_EACH (node, &sbrec_lb->vips) {
> -        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> -
> -        struct lex_str key_s = template
> -                               ? lexer_parse_template_string(node->key,
> -                                                             template_vars,
> -                                                             
> template_vars_ref)
> -                               : lex_str_use(node->key);
> -        struct lex_str value_s = template
> -                               ? lexer_parse_template_string(node->value,
> -                                                             template_vars,
> -                                                             
> template_vars_ref)
> -                               : lex_str_use(node->value);
> -        char *error = ovn_lb_vip_init_explicit(lb_vip,
> -                                               lex_str_get(&key_s),
> -                                               lex_str_get(&value_s));
> -        if (error) {
> -            free(error);
> -        } else {
> -            n_vips++;
> -        }
> -        lex_str_free(&key_s);
> -        lex_str_free(&value_s);
> -    }
> -
> -    lb->proto = IPPROTO_TCP;
> -    if (sbrec_lb->protocol && sbrec_lb->protocol[0]) {
> -        if (!strcmp(sbrec_lb->protocol, "udp")) {
> -            lb->proto = IPPROTO_UDP;
> -        } else if (!strcmp(sbrec_lb->protocol, "sctp")) {
> -            lb->proto = IPPROTO_SCTP;
> -        }
> -    }
> -
> -    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> -     * correct value.
> -     */
> -    lb->n_vips = n_vips;
> -
> -    lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
> -                                           "hairpin_orig_tuple",
> -                                           false);
> -    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
> -    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
> -                               &lb->hairpin_snat_ips);
> -    return lb;
> -}
> -
> -void
> -ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
> -{
> -    for (size_t i = 0; i < lb->n_vips; i++) {
> -        ovn_lb_vip_destroy(&lb->vips[i]);
> -    }
> -    free(lb->vips);
> -    destroy_lport_addresses(&lb->hairpin_snat_ips);
> -    free(lb);
> -}
> -
> -void
> -ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs)
> +/* Formats the VIP in the way the ovn-controller expects it, that is,
> + * template IPv6 variables need to be between brackets too.
> + */
> +char *
> +ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
>  {
> -    struct ovn_controller_lb *lb;
> -    HMAP_FOR_EACH_POP (lb, hmap_node, ovn_controller_lbs) {
> -        ovn_controller_lb_destroy(lb);
> -    }
> -
> -    hmap_destroy(ovn_controller_lbs);
> -}
> +    struct ds s = DS_EMPTY_INITIALIZER;
>
> -struct ovn_controller_lb *
> -ovn_controller_lb_find(const struct hmap *ovn_controller_lbs,
> -                       const struct uuid *uuid)
> -{
> -    struct ovn_controller_lb *lb;
> -    size_t hash = uuid_hash(uuid);
> -    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, ovn_controller_lbs) {
> -        if (uuid_equals(&lb->slb->header_.uuid, uuid)) {
> -            return lb;
> -        }
> +    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
> +        ovn_lb_vip_format__(vip, &s, true);
> +    } else {
> +        ovn_lb_vip_format(vip, &s, !!vip->port_str);
>      }
> -    return NULL;
> +    return ds_steal_cstr(&s);
>  }
>
>  static uint32_t
> @@ -1020,150 +424,3 @@ ovn_lb_5tuples_destroy(struct hmap *tuples)
>
>      hmap_destroy(tuples);
>  }
> -
> -void
> -build_lrouter_lb_ips(struct ovn_lb_ip_set *lb_ips,
> -                     const struct ovn_northd_lb *lb)
> -{
> -    add_ips_to_lb_ip_set(lb_ips, lb->routable, &lb->ips_v4, &lb->ips_v6);
> -}
> -
> -void
> -add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                     bool is_routable,
> -                     const struct sset *lb_ips_v4,
> -                     const struct sset *lb_ips_v6)
> -{
> -    const char *ip_address;
> -
> -    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> -        sset_add(&lb_ips->ips_v4, ip_address);
> -        if (is_routable) {
> -            sset_add(&lb_ips->ips_v4_routable, ip_address);
> -        }
> -    }
> -    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> -        sset_add(&lb_ips->ips_v6, ip_address);
> -        if (is_routable) {
> -            sset_add(&lb_ips->ips_v6_routable, ip_address);
> -        }
> -    }
> -}
> -
> -void
> -remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                          bool is_routable,
> -                          const struct sset *lb_ips_v4,
> -                          const struct sset *lb_ips_v6)
> -{
> -    const char *ip_address;
> -
> -    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> -        sset_find_and_delete(&lb_ips->ips_v4, ip_address);
> -        if (is_routable) {
> -            sset_find_and_delete(&lb_ips->ips_v4_routable, ip_address);
> -        }
> -    }
> -    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> -        sset_find_and_delete(&lb_ips->ips_v6, ip_address);
> -        if (is_routable) {
> -            sset_find_and_delete(&lb_ips->ips_v6_routable, ip_address);
> -        }
> -    }
> -}
> -
> -/* lb datapaths functions */
> -struct  ovn_lb_datapaths *
> -ovn_lb_datapaths_create(const struct ovn_northd_lb *lb, size_t 
> n_ls_datapaths,
> -                        size_t n_lr_datapaths)
> -{
> -    struct ovn_lb_datapaths *lb_dps = xzalloc(sizeof *lb_dps);
> -    lb_dps->lb = lb;
> -    lb_dps->nb_ls_map = bitmap_allocate(n_ls_datapaths);
> -    lb_dps->nb_lr_map = bitmap_allocate(n_lr_datapaths);
> -
> -    return lb_dps;
> -}
> -
> -struct ovn_lb_datapaths *
> -ovn_lb_datapaths_find(const struct hmap *lb_dps_map,
> -                      const struct uuid *lb_uuid)
> -{
> -    struct ovn_lb_datapaths *lb_dps;
> -    size_t hash = uuid_hash(lb_uuid);
> -    HMAP_FOR_EACH_WITH_HASH (lb_dps, hmap_node, hash, lb_dps_map) {
> -        if (uuid_equals(&lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
> -            return lb_dps;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -void
> -ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *lb_dps)
> -{
> -    bitmap_free(lb_dps->nb_lr_map);
> -    bitmap_free(lb_dps->nb_ls_map);
> -    free(lb_dps);
> -}
> -
> -void
> -ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
> -                        struct ovn_datapath **ods)
> -{
> -    for (size_t i = 0; i < n; i++) {
> -        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
> -            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
> -            lb_dps->n_nb_lr++;
> -        }
> -    }
> -}
> -
> -void
> -ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
> -                        struct ovn_datapath **ods)
> -{
> -    for (size_t i = 0; i < n; i++) {
> -        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
> -            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
> -            lb_dps->n_nb_ls++;
> -        }
> -    }
> -}
> -
> -struct ovn_lb_group_datapaths *
> -ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
> -                              size_t max_ls_datapaths,
> -                              size_t max_lr_datapaths)
> -{
> -    struct ovn_lb_group_datapaths *lb_group_dps =
> -        xzalloc(sizeof *lb_group_dps);
> -    lb_group_dps->lb_group = lb_group;
> -    lb_group_dps->ls = xmalloc(max_ls_datapaths * sizeof *lb_group_dps->ls);
> -    lb_group_dps->lr = xmalloc(max_lr_datapaths * sizeof *lb_group_dps->lr);
> -
> -    return lb_group_dps;
> -}
> -
> -void
> -ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *lb_group_dps)
> -{
> -    free(lb_group_dps->ls);
> -    free(lb_group_dps->lr);
> -    free(lb_group_dps);
> -}
> -
> -struct ovn_lb_group_datapaths *
> -ovn_lb_group_datapaths_find(const struct hmap *lb_group_dps_map,
> -                            const struct uuid *lb_group_uuid)
> -{
> -    struct ovn_lb_group_datapaths *lb_group_dps;
> -    size_t hash = uuid_hash(lb_group_uuid);
> -
> -    HMAP_FOR_EACH_WITH_HASH (lb_group_dps, hmap_node, hash, 
> lb_group_dps_map) {
> -        if (uuid_equals(&lb_group_dps->lb_group->uuid, lb_group_uuid)) {
> -            return lb_group_dps;
> -        }
> -    }
> -    return NULL;
> -}
> diff --git a/lib/lb.h b/lib/lb.h
> index b8e3c1e8fb..bcc677f82e 100644
> --- a/lib/lb.h
> +++ b/lib/lb.h
> @@ -25,63 +25,8 @@
>  #include "sset.h"
>  #include "uuid.h"
>
> -struct nbrec_load_balancer;
> -struct nbrec_load_balancer_group;
> -struct sbrec_load_balancer;
> -struct sbrec_datapath_binding;
> -struct ovn_datapath;
> -struct ovn_dp_group;
> -struct ovn_port;
>  struct uuid;
>
> -enum lb_neighbor_responder_mode {
> -    LB_NEIGH_RESPOND_REACHABLE,
> -    LB_NEIGH_RESPOND_ALL,
> -    LB_NEIGH_RESPOND_NONE,
> -};
> -
> -/* The "routable" ssets are subsets of the load balancer IPs for which IP
> - * routes and ARP resolution flows are automatically added. */
> -struct ovn_lb_ip_set {
> -    struct sset ips_v4;
> -    struct sset ips_v4_routable;
> -    struct sset ips_v4_reachable;
> -    struct sset ips_v6;
> -    struct sset ips_v6_routable;
> -    struct sset ips_v6_reachable;
> -};
> -
> -struct ovn_lb_ip_set *ovn_lb_ip_set_create(void);
> -void ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *);
> -struct ovn_lb_ip_set *ovn_lb_ip_set_clone(struct ovn_lb_ip_set *);
> -
> -struct ovn_northd_lb {
> -    struct hmap_node hmap_node;
> -
> -    const struct nbrec_load_balancer *nlb; /* May be NULL. */
> -    const char *proto;
> -    char *selection_fields;
> -    struct ovn_lb_vip *vips;
> -    struct ovn_northd_lb_vip *vips_nb;
> -    struct smap template_vips; /* Slightly changed template VIPs, populated
> -                                * if needed.  Until now it's only required
> -                                * for IPv6 template load balancers. */
> -    size_t n_vips;
> -
> -    enum lb_neighbor_responder_mode neigh_mode;
> -    bool controller_event;
> -    bool routable;
> -    bool skip_snat;
> -    bool template;
> -    uint16_t affinity_timeout;
> -
> -    struct sset ips_v4;
> -    struct sset ips_v6;
> -
> -    /* Indicates if the load balancer has health checks configured. */
> -    bool health_checks;
> -};
> -
>  struct ovn_lb_vip {
>      struct in6_addr vip; /* Only used in ovn-controller. */
>      char *vip_str;       /* Actual VIP string representation (without port).
> @@ -113,153 +58,15 @@ struct ovn_lb_backend {
>                            */
>  };
>
> -/* ovn-northd specific backend information. */
> -struct ovn_northd_lb_vip {
> -    char *backend_ips;
> -    struct ovn_northd_lb_backend *backends_nb;
> -    size_t n_backends;
> -
> -    struct nbrec_load_balancer_health_check *lb_health_check;
> -};
> -
> -struct ovn_northd_lb_backend {
> -    bool health_check;
> -    char *logical_port; /* Logical port to which the ip belong to. */
> -    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
> -};
> -
> -struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer 
> *);
> -struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
> -                                         const struct uuid *);
> -const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
> -void ovn_northd_lb_destroy(struct ovn_northd_lb *);
> -void ovn_northd_lb_reinit(struct ovn_northd_lb *,
> -                          const struct nbrec_load_balancer *);
> -
> -void build_lrouter_lb_ips(struct ovn_lb_ip_set *,
> -                          const struct ovn_northd_lb *);
> -void add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                          bool is_routable,
> -                          const struct sset *lb_ips_v4,
> -                          const struct sset *lb_ips_v6);
> -void remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                               bool is_routable,
> -                               const struct sset *lb_ips_v4,
> -                               const struct sset *lb_ips_v6);
> -
> -struct ovn_lb_group {
> -    struct hmap_node hmap_node;
> -    struct uuid uuid;
> -    size_t n_lbs;
> -    struct ovn_northd_lb **lbs;
> -    struct ovn_lb_ip_set *lb_ips;
> -    bool has_routable_lb;
> -};
> -
> -struct ovn_lb_group *ovn_lb_group_create(
> -    const struct nbrec_load_balancer_group *,
> -    const struct hmap *lbs);
> -void ovn_lb_group_destroy(struct ovn_lb_group *lb_group);
> -struct ovn_lb_group *ovn_lb_group_find(const struct hmap *lb_groups,
> -                                       const struct uuid *);
> -void ovn_lb_group_reinit(
> -    struct ovn_lb_group *lb_group,
> -    const struct nbrec_load_balancer_group *,
> -    const struct hmap *lbs);
> -
> -struct ovn_lb_datapaths {
> -    struct hmap_node hmap_node;
> -
> -    const struct ovn_northd_lb *lb;
> -    size_t n_nb_ls;
> -    unsigned long *nb_ls_map;
> -
> -    size_t n_nb_lr;
> -    unsigned long *nb_lr_map;
> -};
> -
> -struct ovn_lb_datapaths *ovn_lb_datapaths_create(const struct ovn_northd_lb 
> *,
> -                                                 size_t n_ls_datapaths,
> -                                                 size_t n_lr_datapaths);
> -struct ovn_lb_datapaths *ovn_lb_datapaths_find(const struct hmap *,
> -                                               const struct uuid *);
> -void ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *);
> -void ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *, size_t n,
> -                             struct ovn_datapath **);
> -void ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *, size_t n,
> -                             struct ovn_datapath **);
> -
> -struct ovn_lb_group_datapaths {
> -    struct hmap_node hmap_node;
> -
> -    const struct ovn_lb_group *lb_group;
> -
> -    /* Datapaths to which 'lb_group' is applied. */
> -    size_t n_ls;
> -    struct ovn_datapath **ls;
> -    size_t n_lr;
> -    struct ovn_datapath **lr;
> -};
> -
> -struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_create(
> -    const struct ovn_lb_group *, size_t max_ls_datapaths,
> -    size_t max_lr_datapaths);
> -
> -void ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *);
> -struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_find(
> -    const struct hmap *lb_group_dps, const struct uuid *);
> -
> -static inline void
> -ovn_lb_group_datapaths_add_ls(struct ovn_lb_group_datapaths *lbg_dps, size_t 
> n,
> -                               struct ovn_datapath **ods)
> -{
> -    memcpy(&lbg_dps->ls[lbg_dps->n_ls], ods, n * sizeof *ods);
> -    lbg_dps->n_ls += n;
> -}
> -
> -static inline void
> -ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
> -                               struct ovn_datapath *lr)
> -{
> -    lbg_dps->lr[lbg_dps->n_lr++] = lr;
> -}
> -
> -struct ovn_controller_lb {
> -    struct hmap_node hmap_node;
> -
> -    const struct sbrec_load_balancer *slb; /* May be NULL. */
> -
> -    uint8_t proto;
> -
> -    struct ovn_lb_vip *vips;
> -    size_t n_vips;
> -    bool hairpin_orig_tuple; /* True if ovn-northd stores the original
> -                              * destination tuple in registers.
> -                              */
> -    bool ct_flush; /* True if we should flush CT after backend removal. */
> -
> -    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
> -                                              * as source for hairpinned
> -                                              * traffic.
> -                                              */
> -};
> -
> -struct ovn_controller_lb *ovn_controller_lb_create(
> -    const struct sbrec_load_balancer *,
> -    const struct smap *template_vars,
> -    struct sset *template_vars_ref);
> -void ovn_controller_lb_destroy(struct ovn_controller_lb *);
> -void ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs);
> -struct ovn_controller_lb *ovn_controller_lb_find(
> -    const struct hmap *ovn_controller_lbs,
> -    const struct uuid *uuid);
> -
>  char *ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key,
>                        const char *lb_value, bool template, int 
> address_family);
> +char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
> +                               const char *lb_value);
>  void ovn_lb_vip_destroy(struct ovn_lb_vip *vip);
>  void ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s,
>                         bool template);
>  void ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s);
> +char *ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip);
>
>  struct ovn_lb_5tuple {
>      struct hmap_node hmap_node;
> diff --git a/northd/automake.mk b/northd/automake.mk
> index 7c6d56a4ff..19abb0dece 100644
> --- a/northd/automake.mk
> +++ b/northd/automake.mk
> @@ -35,7 +35,9 @@ northd_ovn_northd_SOURCES = \
>         northd/ipam.c \
>         northd/ipam.h \
>         northd/lflow-mgr.c \
> -       northd/lflow-mgr.h
> +       northd/lflow-mgr.h \
> +       northd/lb.c \
> +       northd/lb.h
>  northd_ovn_northd_LDADD = \
>         lib/libovn.la \
>         $(OVSDB_LIBDIR)/libovsdb.la \
> diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
> index d06f46a54b..6ad3fbb35f 100644
> --- a/northd/en-lb-data.c
> +++ b/northd/en-lb-data.c
> @@ -25,6 +25,7 @@
>
>  /* OVN includes */
>  #include "en-lb-data.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
> diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c
> index 8665b3c791..3e2a6c254e 100644
> --- a/northd/en-lr-stateful.c
> +++ b/northd/en-lr-stateful.c
> @@ -33,6 +33,7 @@
>  #include "en-lb-data.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
> diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
> index 53f687f220..9ca59d4338 100644
> --- a/northd/en-sync-sb.c
> +++ b/northd/en-sync-sb.c
> @@ -24,6 +24,7 @@
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
>  #include "en-sync-sb.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
> diff --git a/northd/lb.c b/northd/lb.c
> new file mode 100644
> index 0000000000..e35569cb70
> --- /dev/null
> +++ b/northd/lb.c
> @@ -0,0 +1,651 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * 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>
> +
> +/* OVS includes */
> +#include "lib/bitmap.h"
> +#include "openvswitch/vlog.h"
> +#include "socket-util.h"
> +
> +/* OVN includes */
> +#include "lb.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "northd.h"
> +#include "ovn/lex.h"
> +
> +VLOG_DEFINE_THIS_MODULE(northd_lb);
> +
> +static const char *lb_neighbor_responder_mode_names[] = {
> +    [LB_NEIGH_RESPOND_REACHABLE] = "reachable",
> +    [LB_NEIGH_RESPOND_ALL] = "all",
> +    [LB_NEIGH_RESPOND_NONE] = "none",
> +};
> +
> +static struct nbrec_load_balancer_health_check *
> +ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> +                        const char *vip_port_str, bool template);
> +
> +struct ovn_lb_ip_set *
> +ovn_lb_ip_set_create(void)
> +{
> +    struct ovn_lb_ip_set *lb_ip_set = xzalloc(sizeof *lb_ip_set);
> +
> +    sset_init(&lb_ip_set->ips_v4);
> +    sset_init(&lb_ip_set->ips_v4_routable);
> +    sset_init(&lb_ip_set->ips_v4_reachable);
> +    sset_init(&lb_ip_set->ips_v6);
> +    sset_init(&lb_ip_set->ips_v6_routable);
> +    sset_init(&lb_ip_set->ips_v6_reachable);
> +
> +    return lb_ip_set;
> +}
> +
> +void
> +ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *lb_ip_set)
> +{
> +    if (!lb_ip_set) {
> +        return;
> +    }
> +    sset_destroy(&lb_ip_set->ips_v4);
> +    sset_destroy(&lb_ip_set->ips_v4_routable);
> +    sset_destroy(&lb_ip_set->ips_v4_reachable);
> +    sset_destroy(&lb_ip_set->ips_v6);
> +    sset_destroy(&lb_ip_set->ips_v6_routable);
> +    sset_destroy(&lb_ip_set->ips_v6_reachable);
> +    free(lb_ip_set);
> +}
> +
> +struct ovn_lb_ip_set *
> +ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set)
> +{
> +    struct ovn_lb_ip_set *clone = ovn_lb_ip_set_create();
> +
> +    sset_clone(&clone->ips_v4, &lb_ip_set->ips_v4);
> +    sset_clone(&clone->ips_v4_routable, &lb_ip_set->ips_v4_routable);
> +    sset_clone(&clone->ips_v4_reachable, &lb_ip_set->ips_v4_reachable);
> +    sset_clone(&clone->ips_v6, &lb_ip_set->ips_v6);
> +    sset_clone(&clone->ips_v6_routable, &lb_ip_set->ips_v6_routable);
> +    sset_clone(&clone->ips_v6_reachable, &lb_ip_set->ips_v6_reachable);
> +
> +    return clone;
> +}
> +
> +static
> +void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
> +                            const struct ovn_lb_vip *lb_vip,
> +                            const struct nbrec_load_balancer *nbrec_lb,
> +                            const char *vip_port_str, const char 
> *backend_ips,
> +                            bool template)
> +{
> +    lb_vip_nb->backend_ips = xstrdup(backend_ips);
> +    lb_vip_nb->n_backends = lb_vip->n_backends;
> +    lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends,
> +                                     sizeof *lb_vip_nb->backends_nb);
> +    lb_vip_nb->lb_health_check =
> +        ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
> +}
> +
> +static void
> +ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
> +                                      const struct ovn_lb_vip *lb_vip,
> +                                      struct ovn_northd_lb_vip *lb_vip_nb)
> +{
> +    struct ds key = DS_EMPTY_INITIALIZER;
> +
> +    for (size_t j = 0; j < lb_vip->n_backends; j++) {
> +        struct ovn_lb_backend *backend = &lb_vip->backends[j];
> +        ds_clear(&key);
> +        ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)
> +                      ? "%s" : "[%s]", backend->ip_str);
> +
> +        const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key));
> +        if (!s) {
> +            continue;
> +        }
> +
> +        char *svc_mon_src_ip = NULL;
> +        char *port_name = xstrdup(s);
> +        char *p = strstr(port_name, ":");
> +        if (p) {
> +            *p = 0;
> +            p++;
> +            struct sockaddr_storage svc_mon_src_addr;
> +            if (!inet_parse_address(p, &svc_mon_src_addr)) {
> +                static struct vlog_rate_limit rl =
> +                    VLOG_RATE_LIMIT_INIT(5, 1);
> +                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
> +            } else {
> +                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
> +                ss_format_address_nobracks(&svc_mon_src_addr,
> +                                            &src_ip_s);
> +                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
> +            }
> +        }
> +
> +        if (svc_mon_src_ip) {
> +            struct ovn_northd_lb_backend *backend_nb =
> +                &lb_vip_nb->backends_nb[j];
> +            backend_nb->health_check = true;
> +            backend_nb->logical_port = xstrdup(port_name);
> +            backend_nb->svc_mon_src_ip = svc_mon_src_ip;
> +        }
> +        free(port_name);
> +    }
> +
> +    ds_destroy(&key);
> +}
> +
> +static
> +void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
> +{
> +    free(vip->backend_ips);
> +    for (size_t i = 0; i < vip->n_backends; i++) {
> +        free(vip->backends_nb[i].logical_port);
> +        free(vip->backends_nb[i].svc_mon_src_ip);
> +    }
> +    free(vip->backends_nb);
> +}
> +
> +static bool
> +ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb,
> +                         bool routable, bool template)
> +{
> +    if (template && routable) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +        VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport 
> "
> +                           "option 'add_route'.  Forcing it to disabled.",
> +                     UUID_ARGS(&nbrec_lb->header_.uuid));
> +        return false;
> +    }
> +    return routable;
> +}
> +
> +static bool
> +ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool 
> template)
> +{
> +    if (!template) {
> +        return true;
> +    }
> +
> +    switch (mode) {
> +    case LB_NEIGH_RESPOND_REACHABLE:
> +        return false;
> +    case LB_NEIGH_RESPOND_ALL:
> +    case LB_NEIGH_RESPOND_NONE:
> +        return true;
> +    }
> +    return false;
> +}
> +
> +static enum lb_neighbor_responder_mode
> +ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb,
> +                      const char *mode, bool template)
> +{
> +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +    enum lb_neighbor_responder_mode default_mode =
> +        template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE;
> +
> +    if (!mode) {
> +        mode = lb_neighbor_responder_mode_names[default_mode];
> +    }
> +
> +    for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); 
> i++) {
> +        if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) {
> +            if (ovn_lb_neigh_mode_is_valid(i, template)) {
> +                return i;
> +            }
> +            break;
> +        }
> +    }
> +
> +    VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer "
> +                       UUID_FMT", forcing it to %s",
> +                 mode, UUID_ARGS(&nbrec_lb->header_.uuid),
> +                 lb_neighbor_responder_mode_names[default_mode]);
> +    return default_mode;
> +}
> +
> +static struct nbrec_load_balancer_health_check *
> +ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> +                        const char *vip_port_str, bool template)
> +{
> +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +
> +    if (!nbrec_lb->n_health_check) {
> +        return NULL;
> +    }
> +
> +    if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) {
> +        VLOG_WARN_RL(&rl,
> +                     "SCTP load balancers do not currently support "
> +                     "health checks. Not creating health checks for "
> +                     "load balancer " UUID_FMT,
> +                     UUID_ARGS(&nbrec_lb->header_.uuid));
> +        return NULL;
> +    }
> +
> +    if (template) {
> +        VLOG_WARN_RL(&rl,
> +                     "Template load balancers do not currently support "
> +                     "health checks. Not creating health checks for "
> +                     "load balancer " UUID_FMT,
> +                     UUID_ARGS(&nbrec_lb->header_.uuid));
> +        return NULL;
> +    }
> +
> +    for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
> +        if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) {
> +            return nbrec_lb->health_check[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static void
> +ovn_northd_lb_init(struct ovn_northd_lb *lb,
> +                   const struct nbrec_load_balancer *nbrec_lb)
> +{
> +    bool template = smap_get_bool(&nbrec_lb->options, "template", false);
> +    bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp");
> +    bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp");
> +    int address_family = !strcmp(smap_get_def(&nbrec_lb->options,
> +                                              "address-family", "ipv4"),
> +                                 "ipv4")
> +                         ? AF_INET
> +                         : AF_INET6;
> +
> +    lb->nlb = nbrec_lb;
> +    lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
> +    lb->n_vips = smap_count(&nbrec_lb->vips);
> +    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> +    lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
> +    smap_init(&lb->template_vips);
> +    lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
> +
> +    bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
> +    lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, template);
> +
> +    lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false);
> +    lb->template = template;
> +
> +    const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder");
> +    lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template);
> +
> +    uint32_t affinity_timeout =
> +        smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0);
> +    if (affinity_timeout > UINT16_MAX) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +        VLOG_WARN_RL(&rl, "max affinity_timeout timeout value is %u",
> +                     UINT16_MAX);
> +        affinity_timeout = UINT16_MAX;
> +    }
> +    lb->affinity_timeout = affinity_timeout;
> +
> +    sset_init(&lb->ips_v4);
> +    sset_init(&lb->ips_v6);
> +    struct smap_node *node;
> +    size_t n_vips = 0;
> +
> +    SMAP_FOR_EACH (node, &nbrec_lb->vips) {
> +        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> +        struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
> +
> +        char *error = ovn_lb_vip_init(lb_vip, node->key, node->value,
> +                                      template, address_family);
> +        if (error) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +            VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error);
> +            ovn_lb_vip_destroy(lb_vip);
> +            free(error);
> +            continue;
> +        }
> +        lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
> +                                                  "reject", false);
> +        ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb,
> +                               node->key, node->value, template);
> +        if (lb_vip_nb->lb_health_check) {
> +            lb->health_checks = true;
> +        }
> +
> +        if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
> +            sset_add(&lb->ips_v4, lb_vip->vip_str);
> +        } else {
> +            sset_add(&lb->ips_v6, lb_vip->vip_str);
> +        }
> +
> +        if (lb->template && address_family == AF_INET6) {
> +            smap_add_nocopy(&lb->template_vips,
> +                            ovn_lb_vip6_template_format_internal(lb_vip),
> +                            xstrdup(node->value));
> +        }
> +        n_vips++;
> +
> +        if (lb_vip_nb->lb_health_check) {
> +            ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
> +        }
> +    }
> +
> +    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> +     * correct value.
> +     */
> +    lb->n_vips = n_vips;
> +
> +    if (nbrec_lb->n_selection_fields) {
> +        char *proto = NULL;
> +        if (nbrec_lb->protocol && nbrec_lb->protocol[0]) {
> +            proto = nbrec_lb->protocol;
> +        }
> +
> +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> +            char *field = lb->nlb->selection_fields[i];
> +            if (!strcmp(field, "tp_src") && proto) {
> +                ds_put_format(&sel_fields, "%s_src,", proto);
> +            } else if (!strcmp(field, "tp_dst") && proto) {
> +                ds_put_format(&sel_fields, "%s_dst,", proto);
> +            } else {
> +                ds_put_format(&sel_fields, "%s,", field);
> +            }
> +        }
> +        ds_chomp(&sel_fields, ',');
> +        lb->selection_fields = ds_steal_cstr(&sel_fields);
> +    }
> +}
> +
> +struct ovn_northd_lb *
> +ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
> +{
> +    struct ovn_northd_lb *lb = xzalloc(sizeof *lb);
> +    ovn_northd_lb_init(lb, nbrec_lb);
> +    return lb;
> +}
> +
> +struct ovn_northd_lb *
> +ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
> +{
> +    struct ovn_northd_lb *lb;
> +    size_t hash = uuid_hash(uuid);
> +    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
> +        if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
> +            return lb;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +const struct smap *
> +ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
> +{
> +    if (!smap_is_empty(&lb->template_vips)) {
> +        return &lb->template_vips;
> +    }
> +    return &lb->nlb->vips;
> +}
> +
> +static void
> +ovn_northd_lb_cleanup(struct ovn_northd_lb *lb)
> +{
> +    for (size_t i = 0; i < lb->n_vips; i++) {
> +        ovn_lb_vip_destroy(&lb->vips[i]);
> +        ovn_northd_lb_vip_destroy(&lb->vips_nb[i]);
> +    }
> +    free(lb->vips);
> +    free(lb->vips_nb);
> +    lb->vips = NULL;
> +    lb->vips_nb = NULL;
> +    smap_destroy(&lb->template_vips);
> +    sset_destroy(&lb->ips_v4);
> +    sset_destroy(&lb->ips_v6);
> +    free(lb->selection_fields);
> +    lb->selection_fields = NULL;
> +    lb->health_checks = false;
> +}
> +
> +void
> +ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
> +{
> +    ovn_northd_lb_cleanup(lb);
> +    free(lb);
> +}
> +
> +void
> +ovn_northd_lb_reinit(struct ovn_northd_lb *lb,
> +                     const struct nbrec_load_balancer *nbrec_lb)
> +{
> +    ovn_northd_lb_cleanup(lb);
> +    ovn_northd_lb_init(lb, nbrec_lb);
> +}
> +
> +static void
> +ovn_lb_group_init(struct ovn_lb_group *lb_group,
> +                  const struct nbrec_load_balancer_group *nbrec_lb_group,
> +                  const struct hmap *lbs)
> +{
> +    lb_group->n_lbs = nbrec_lb_group->n_load_balancer;
> +    lb_group->lbs = xmalloc(lb_group->n_lbs * sizeof *lb_group->lbs);
> +    lb_group->lb_ips = ovn_lb_ip_set_create();
> +
> +    for (size_t i = 0; i < nbrec_lb_group->n_load_balancer; i++) {
> +        const struct uuid *lb_uuid =
> +            &nbrec_lb_group->load_balancer[i]->header_.uuid;
> +        lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
> +        lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
> +    }
> +}
> +
> +/* Constructs a new 'struct ovn_lb_group' object from the Nb LB Group record
> + * and an array of 'struct ovn_northd_lb' objects for its associated
> + * load balancers. */
> +struct ovn_lb_group *
> +ovn_lb_group_create(const struct nbrec_load_balancer_group *nbrec_lb_group,
> +                    const struct hmap *lbs)
> +{
> +    struct ovn_lb_group *lb_group = xzalloc(sizeof *lb_group);
> +    lb_group->uuid = nbrec_lb_group->header_.uuid;
> +    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> +    return lb_group;
> +}
> +
> +static void
> +ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
> +{
> +    ovn_lb_ip_set_destroy(lb_group->lb_ips);
> +    lb_group->lb_ips = NULL;
> +    lb_group->has_routable_lb = false;
> +    free(lb_group->lbs);
> +}
> +
> +void
> +ovn_lb_group_destroy(struct ovn_lb_group *lb_group)
> +{
> +    if (!lb_group) {
> +        return;
> +    }
> +
> +    ovn_lb_group_cleanup(lb_group);
> +    free(lb_group);
> +}
> +
> +void
> +ovn_lb_group_reinit(struct ovn_lb_group *lb_group,
> +                    const struct nbrec_load_balancer_group *nbrec_lb_group,
> +                    const struct hmap *lbs)
> +{
> +    ovn_lb_group_cleanup(lb_group);
> +    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> +}
> +
> +struct ovn_lb_group *
> +ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid)
> +{
> +    struct ovn_lb_group *lb_group;
> +    size_t hash = uuid_hash(uuid);
> +
> +    HMAP_FOR_EACH_WITH_HASH (lb_group, hmap_node, hash, lb_groups) {
> +        if (uuid_equals(&lb_group->uuid, uuid)) {
> +            return lb_group;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +void
> +build_lrouter_lb_ips(struct ovn_lb_ip_set *lb_ips,
> +                     const struct ovn_northd_lb *lb)
> +{
> +    add_ips_to_lb_ip_set(lb_ips, lb->routable, &lb->ips_v4, &lb->ips_v6);
> +}
> +
> +void
> +add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                     bool is_routable,
> +                     const struct sset *lb_ips_v4,
> +                     const struct sset *lb_ips_v6)
> +{
> +    const char *ip_address;
> +
> +    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> +        sset_add(&lb_ips->ips_v4, ip_address);
> +        if (is_routable) {
> +            sset_add(&lb_ips->ips_v4_routable, ip_address);
> +        }
> +    }
> +    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> +        sset_add(&lb_ips->ips_v6, ip_address);
> +        if (is_routable) {
> +            sset_add(&lb_ips->ips_v6_routable, ip_address);
> +        }
> +    }
> +}
> +
> +void
> +remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                          bool is_routable,
> +                          const struct sset *lb_ips_v4,
> +                          const struct sset *lb_ips_v6)
> +{
> +    const char *ip_address;
> +
> +    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> +        sset_find_and_delete(&lb_ips->ips_v4, ip_address);
> +        if (is_routable) {
> +            sset_find_and_delete(&lb_ips->ips_v4_routable, ip_address);
> +        }
> +    }
> +    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> +        sset_find_and_delete(&lb_ips->ips_v6, ip_address);
> +        if (is_routable) {
> +            sset_find_and_delete(&lb_ips->ips_v6_routable, ip_address);
> +        }
> +    }
> +}
> +
> +/* lb datapaths functions */
> +struct  ovn_lb_datapaths *
> +ovn_lb_datapaths_create(const struct ovn_northd_lb *lb, size_t 
> n_ls_datapaths,
> +                        size_t n_lr_datapaths)
> +{
> +    struct ovn_lb_datapaths *lb_dps = xzalloc(sizeof *lb_dps);
> +    lb_dps->lb = lb;
> +    lb_dps->nb_ls_map = bitmap_allocate(n_ls_datapaths);
> +    lb_dps->nb_lr_map = bitmap_allocate(n_lr_datapaths);
> +
> +    return lb_dps;
> +}
> +
> +void
> +ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *lb_dps)
> +{
> +    bitmap_free(lb_dps->nb_lr_map);
> +    bitmap_free(lb_dps->nb_ls_map);
> +    free(lb_dps);
> +}
> +
> +void
> +ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
> +                        struct ovn_datapath **ods)
> +{
> +    for (size_t i = 0; i < n; i++) {
> +        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
> +            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
> +            lb_dps->n_nb_lr++;
> +        }
> +    }
> +}
> +
> +void
> +ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
> +                        struct ovn_datapath **ods)
> +{
> +    for (size_t i = 0; i < n; i++) {
> +        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
> +            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
> +            lb_dps->n_nb_ls++;
> +        }
> +    }
> +}
> +
> +struct ovn_lb_datapaths *
> +ovn_lb_datapaths_find(const struct hmap *lb_dps_map,
> +                      const struct uuid *lb_uuid)
> +{
> +    struct ovn_lb_datapaths *lb_dps;
> +    size_t hash = uuid_hash(lb_uuid);
> +    HMAP_FOR_EACH_WITH_HASH (lb_dps, hmap_node, hash, lb_dps_map) {
> +        if (uuid_equals(&lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
> +            return lb_dps;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +struct ovn_lb_group_datapaths *
> +ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
> +                              size_t max_ls_datapaths,
> +                              size_t max_lr_datapaths)
> +{
> +    struct ovn_lb_group_datapaths *lb_group_dps =
> +        xzalloc(sizeof *lb_group_dps);
> +    lb_group_dps->lb_group = lb_group;
> +    lb_group_dps->ls = xmalloc(max_ls_datapaths * sizeof *lb_group_dps->ls);
> +    lb_group_dps->lr = xmalloc(max_lr_datapaths * sizeof *lb_group_dps->lr);
> +
> +    return lb_group_dps;
> +}
> +
> +void
> +ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *lb_group_dps)
> +{
> +    free(lb_group_dps->ls);
> +    free(lb_group_dps->lr);
> +    free(lb_group_dps);
> +}
> +
> +struct ovn_lb_group_datapaths *
> +ovn_lb_group_datapaths_find(const struct hmap *lb_group_dps_map,
> +                            const struct uuid *lb_group_uuid)
> +{
> +    struct ovn_lb_group_datapaths *lb_group_dps;
> +    size_t hash = uuid_hash(lb_group_uuid);
> +
> +    HMAP_FOR_EACH_WITH_HASH (lb_group_dps, hmap_node, hash, 
> lb_group_dps_map) {
> +        if (uuid_equals(&lb_group_dps->lb_group->uuid, lb_group_uuid)) {
> +            return lb_group_dps;
> +        }
> +    }
> +    return NULL;
> +}
> diff --git a/northd/lb.h b/northd/lb.h
> new file mode 100644
> index 0000000000..00f81c3533
> --- /dev/null
> +++ b/northd/lb.h
> @@ -0,0 +1,189 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * 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.
> + */
> +
> +#ifndef OVN_NORTHD_LB_H
> +#define OVN_NORTHD_LB_H 1
> +
> +#include "openvswitch/hmap.h"
> +#include "uuid.h"
> +
> +#include "lib/lb.h"
> +
> +struct nbrec_load_balancer;
> +struct nbrec_load_balancer_group;
> +struct ovn_datapath;
> +
> +enum lb_neighbor_responder_mode {
> +    LB_NEIGH_RESPOND_REACHABLE,
> +    LB_NEIGH_RESPOND_ALL,
> +    LB_NEIGH_RESPOND_NONE,
> +};
> +
> +/* The "routable" ssets are subsets of the load balancer IPs for which IP
> + * routes and ARP resolution flows are automatically added. */
> +struct ovn_lb_ip_set {
> +    struct sset ips_v4;
> +    struct sset ips_v4_routable;
> +    struct sset ips_v4_reachable;
> +    struct sset ips_v6;
> +    struct sset ips_v6_routable;
> +    struct sset ips_v6_reachable;
> +};
> +
> +struct ovn_lb_ip_set *ovn_lb_ip_set_create(void);
> +void ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *);
> +struct ovn_lb_ip_set *ovn_lb_ip_set_clone(struct ovn_lb_ip_set *);
> +
> +struct ovn_northd_lb {
> +    struct hmap_node hmap_node;
> +
> +    const struct nbrec_load_balancer *nlb; /* May be NULL. */
> +    const char *proto;
> +    char *selection_fields;
> +    struct ovn_lb_vip *vips;
> +    struct ovn_northd_lb_vip *vips_nb;
> +    struct smap template_vips; /* Slightly changed template VIPs, populated
> +                                * if needed.  Until now it's only required
> +                                * for IPv6 template load balancers. */
> +    size_t n_vips;
> +
> +    enum lb_neighbor_responder_mode neigh_mode;
> +    bool controller_event;
> +    bool routable;
> +    bool skip_snat;
> +    bool template;
> +    uint16_t affinity_timeout;
> +
> +    struct sset ips_v4;
> +    struct sset ips_v6;
> +
> +    /* Indicates if the load balancer has health checks configured. */
> +    bool health_checks;
> +};
> +
> +/* ovn-northd specific backend information. */
> +struct ovn_northd_lb_vip {
> +    char *backend_ips;
> +    struct ovn_northd_lb_backend *backends_nb;
> +    size_t n_backends;
> +
> +    struct nbrec_load_balancer_health_check *lb_health_check;
> +};
> +
> +struct ovn_northd_lb_backend {
> +    bool health_check;
> +    char *logical_port; /* Logical port to which the ip belong to. */
> +    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
> +};
> +
> +struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer 
> *);
> +struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
> +                                         const struct uuid *);
> +const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
> +void ovn_northd_lb_destroy(struct ovn_northd_lb *);
> +void ovn_northd_lb_reinit(struct ovn_northd_lb *,
> +                          const struct nbrec_load_balancer *);
> +
> +void build_lrouter_lb_ips(struct ovn_lb_ip_set *,
> +                          const struct ovn_northd_lb *);
> +void add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                          bool is_routable,
> +                          const struct sset *lb_ips_v4,
> +                          const struct sset *lb_ips_v6);
> +void remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                               bool is_routable,
> +                               const struct sset *lb_ips_v4,
> +                               const struct sset *lb_ips_v6);
> +
> +struct ovn_lb_group {
> +    struct hmap_node hmap_node;
> +    struct uuid uuid;
> +    size_t n_lbs;
> +    struct ovn_northd_lb **lbs;
> +    struct ovn_lb_ip_set *lb_ips;
> +    bool has_routable_lb;
> +};
> +
> +struct ovn_lb_group *ovn_lb_group_create(
> +    const struct nbrec_load_balancer_group *,
> +    const struct hmap *lbs);
> +void ovn_lb_group_destroy(struct ovn_lb_group *lb_group);
> +struct ovn_lb_group *ovn_lb_group_find(const struct hmap *lb_groups,
> +                                       const struct uuid *);
> +void ovn_lb_group_reinit(
> +    struct ovn_lb_group *lb_group,
> +    const struct nbrec_load_balancer_group *,
> +    const struct hmap *lbs);
> +
> +struct ovn_lb_datapaths {
> +    struct hmap_node hmap_node;
> +
> +    const struct ovn_northd_lb *lb;
> +    size_t n_nb_ls;
> +    unsigned long *nb_ls_map;
> +
> +    size_t n_nb_lr;
> +    unsigned long *nb_lr_map;
> +};
> +
> +struct ovn_lb_datapaths *ovn_lb_datapaths_create(const struct ovn_northd_lb 
> *,
> +                                                 size_t n_ls_datapaths,
> +                                                 size_t n_lr_datapaths);
> +struct ovn_lb_datapaths *ovn_lb_datapaths_find(const struct hmap *,
> +                                               const struct uuid *);
> +void ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *);
> +
> +void ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *, size_t n,
> +                             struct ovn_datapath **);
> +void ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *, size_t n,
> +                             struct ovn_datapath **);
> +
> +struct ovn_lb_group_datapaths {
> +    struct hmap_node hmap_node;
> +
> +    const struct ovn_lb_group *lb_group;
> +
> +    /* Datapaths to which 'lb_group' is applied. */
> +    size_t n_ls;
> +    struct ovn_datapath **ls;
> +    size_t n_lr;
> +    struct ovn_datapath **lr;
> +};
> +
> +struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_create(
> +    const struct ovn_lb_group *, size_t max_ls_datapaths,
> +    size_t max_lr_datapaths);
> +
> +void ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *);
> +struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_find(
> +    const struct hmap *lb_group_dps, const struct uuid *);
> +
> +static inline void
> +ovn_lb_group_datapaths_add_ls(struct ovn_lb_group_datapaths *lbg_dps, size_t 
> n,
> +                               struct ovn_datapath **ods)
> +{
> +    memcpy(&lbg_dps->ls[lbg_dps->n_ls], ods, n * sizeof *ods);
> +    lbg_dps->n_ls += n;
> +}
> +
> +static inline void
> +ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
> +                               struct ovn_datapath *lr)
> +{
> +    lbg_dps->lr[lbg_dps->n_lr++] = lr;
> +}
> +
> +#endif /* OVN_NORTHD_LB_H */
> diff --git a/northd/northd.c b/northd/northd.c
> index cb53ec9716..00899e3d1a 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -31,6 +31,7 @@
>  #include "openvswitch/hmap.h"
>  #include "openvswitch/json.h"
>  #include "ovn/lex.h"
> +#include "lb.h"
>  #include "lib/chassis-index.h"
>  #include "lib/ip-mcast-index.h"
>  #include "lib/static-mac-binding-index.h"
> --
> 2.43.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to