On Wed, Aug 19, 2026 at 3:19 PM Dmitrii Shcherbakov <
[email protected]> wrote:

> Hi Ales,
>
> > The cover letter mentions that this is for "operator observability",
> > could you please elaborate on what that means?
>
> Operator observability was raised in the v4 review: namely being able
> to see the status of advertised routes via ovn-sbctl queries.
> https://mail.openvswitch.org/pipermail/ovs-dev/2026-June/433584.html
>
> Whether the backend is healthy or not is just one data point: there may
> be an administrative override or an error during installing a route into
> a VRF which makes this more elaborate. Tracking combinations of those
> factors can be difficult without more detailed status information.
>

Indeed, however this info is somewhat local, which is why I suggested
ovn-appctl.


>
> Aside from the operator use, another analogy could be with how port
> status is reported upwards to OVN SB from ovn-controllers and then also
> by northd to OVN NB for CMS consumption (e.g. Neutron uses that to see
> if a port went operationally up or not and Neutron clients can act on
> it).
>

Right, but the difference here is not negligible, ports propagate through
already existing port_binding. Sure it is still an update, but we don't have
to create a new row with a bunch of strings.

We had a bunch of discussions about general status in SB that could
be propagated, but it always ends up with scalability concerns.


> So I thought this could be utilized by CMS-es in a similar way (once
> exposed to OVN NB). Then a higher-level consumer could get some
> information about the reason why a distributed LB is not working
> correctly without having more privileged access.
>
> To give a concrete example: a CMS-level self-service API to create
> distributed LBs can be used by a cloud tenant to provision LBs but that
> tenant will not have the visibility down to ovn-controller.
>
> I first tried to use external-ids in v6 but to implement RBAC
> https://mail.openvswitch.org/pipermail/ovs-dev/2026-July/433785.html
> I had to switch a separate table.
>

What is the concern with omitting RBAC? For example the port_binding
up doesn't have RBAC so any "rogue" controller could potentially mess
up the status.


> There are also multiple things to report about the routes:
>
> 1) Desired state
> 2) Withdrawal reasons
> 3) Operational state
>
> So it made sense to decompose it into separate rows rather than trying
> to append multiple data points into one string.


> > My main concern here is scalability, as you are creating many entries
> > just for monitoring. Would some ovn-appctl command be enough? Per
> > design this info is per chassis only anyway. Also the health checks
> > can actually be seen by checking the monitors themselves. The
> > advertised/installed can also be provided at ovn-appctl level.
>
> I agree about OVN SB scalability, I have seen what OVN SB overload can
> do to a production deployment in practice and any write workload is a
> concern for sure. I tried to limit that with only enabling status
> reporting for routes of distributed LBs so that the blast radius is
> limited.
>


Still anyone using distributed LBs with dynamic-routing will
get potential SB churn in the status table.


> I am about to publish an ovn-heater PR which exercises status reporting
> as well to provide some concrete means to measure the impact.
>
> To summarize:
>
> 1) I'll publish an ovn-heater PR with some numbers to assess the impact;
>

Sounds good.


> 2) As this was the last patch in the series and so far there aren't
>    any consumers, we can revert it for more discussion and assessment.
>

Maybe for now, avoiding release with [it] would be the best approach
this, as we have discussed, it isn't consumed right now. I can post a
revert CCing other maintainers to hear their opinion.


> 3) I planned adding some ovn-appctl commands in follow-ups so regardless
>    of whether Advertised_Route_Status is kept or not, we can still
>    go down the appctl route to address some operator use-cases at least.
>

It really depends on the consumer, but considering we don't have a concrete
plan how to use that status, at least to me it sounds like there isn't any.
We
can also explore the ovn-appctl route if it's feasible.


>
>
> Best Regards,
> Dmitrii
>
>
Regards,
Ales


> On Wednesday, August 19th, 2026 at 12:54 PM, Ales Musil <[email protected]>
> wrote:
>
> >
> >
> > On Fri, Aug 14, 2026 at 9:27 AM Dmitrii Shcherbakov via dev <
> [email protected]> wrote:
> >
> > > Add Advertised_Route_Status to report one route state on one
> > > chassis, with the following columns:
> > > - desired_status: advertised or withdrawn
> > > - withdrawal_reasons: identifies the controller input that caused
> > > withdrawal; the map can carry additional inputs without adding
> > > new desired-state values
> > > - operational_status: the kernel route reconciliation result
> > > (installed, withdrawn, or unknown)
> > > - error: describes a reconciliation failure
> > > Route and chassis references are weak. Copied route UUID and
> > > chassis name fields provide the index and RBAC identity.
> > >
> > > Each controller can only create, delete and update status rows
> > > that bear its own chassis name. Controller shutdown and
> > > ovn-sbctl chassis-del remove status rows for the chassis. Guard
> > > status cleanup when the system ID is unavailable.
> > >
> > > Desired status records are sorted by route UUID and looked up via
> > > binary search while publishing results. Missing rows are recreated,
> > > modified rows are repaired and unexpected local rows are removed.
> > > The status-table monitor condition is limited to the local chassis
> > > name. If the SB server does not have Advertised_Route_Status, route
> > > handling continues without status publication.
> > >
> > > System-test targets generate the existing TLS test PKI used
> > > by the controller RBAC coverage.
> > >
> > > The SB schema version changes from 21.11.0 to 21.12.0.
> > >
> > > Signed-off-by: Dmitrii Shcherbakov <[email protected]>
> > > ---
> >
> >
> > Hi Dmitrii,
> >
> > I have some questions about this particular patch. This adds
> > a separate table into SB where each row can be managed by only
> > a single chassis. The cover letter mentions that this is for
> > "operator observability", could you please elaborate on what that
> > means?
> >
> > My main concern here is scalability, as you are creating many entries
> > just for monitoring. Would some ovn-appctl command be enough? Per
> > design this info is per chassis only anyway. Also the health checks
> > can actually be seen by checking the monitors themselves. The
> > advertised/installed can also be provided at ovn-appctl level.
> >
> > Regards,
> > Ales
> >
> >
> > > controller/chassis.c | 27 ++-
> > > controller/chassis.h | 4 +-
> > > controller/ovn-controller.c | 272 ++++++++++++++++++++++++++++++-
> > > controller/route-exchange.c | 38 ++++-
> > > controller/route-exchange.h | 5 +-
> > > controller/route.c | 63 +++++++
> > > controller/route.h | 20 +++
> > > northd/ovn-northd.c | 17 ++
> > > ovn-sb.ovsschema | 34 +++-
> > > ovn-sb.xml | 92 +++++++++++
> > > tests/automake.mk | 6 +-
> > > tests/ovn-controller.at | 59 +++++++
> > > tests/ovn-inc-proc-graph-dump.at | 2 +
> > > tests/ovn-northd.at | 11 ++
> > > tests/ovn-sbctl.at | 7 +-
> > > tests/system-ovn.at | 184 +++++++++++++++++++++
> > > utilities/ovn-sbctl.8.xml | 5 +-
> > > utilities/ovn-sbctl.c | 17 ++
> > > 18 files changed, 842 insertions(+), 21 deletions(-)
> > >
> > > diff --git a/controller/chassis.c b/controller/chassis.c
> > > index 0e3bd37d8..07d375a81 100644
> > > --- a/controller/chassis.c
> > > +++ b/controller/chassis.c
> > > @@ -1181,14 +1181,26 @@ chassis_cleanup(struct ovsdb_idl_txn
> *ovs_idl_txn,
> > > struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > const struct ovsrec_open_vswitch_table *ovs_table,
> > > const struct sbrec_chassis *chassis_rec,
> > > - const struct sbrec_chassis_private *chassis_private_rec)
> > > + const struct sbrec_chassis_private *chassis_private_rec,
> > > + const struct sbrec_advertised_route_status_table *status_table)
> > > {
> > > + const char *chassis_name = get_ovs_chassis_id(ovs_table);
> > > + bool has_route_status = false;
> > > + if (status_table && chassis_name) {
> > > + const struct sbrec_advertised_route_status *status;
> > > + SBREC_ADVERTISED_ROUTE_STATUS_TABLE_FOR_EACH (status, status_table) {
> > > + if (!strcmp(status->chassis_name, chassis_name)) {
> > > + has_route_status = true;
> > > + break;
> > > + }
> > > + }
> > > + }
> > > +
> > > if (!chassis_rec && !chassis_private_rec &&
> > > - !is_chassis_idx_stored(ovs_table)) {
> > > + !is_chassis_idx_stored(ovs_table) && !has_route_status) {
> > > return true;
> > > }
> > >
> > > - const char *chassis_name = get_ovs_chassis_id(ovs_table);
> > > if (ovs_idl_txn) {
> > > ovsdb_idl_txn_add_comment(
> > > ovs_idl_txn,
> > > @@ -1207,6 +1219,15 @@ chassis_cleanup(struct ovsdb_idl_txn
> *ovs_idl_txn,
> > > if (chassis_private_rec) {
> > > sbrec_chassis_private_delete(chassis_private_rec);
> > > }
> > > + if (status_table && chassis_name) {
> > > + const struct sbrec_advertised_route_status *status;
> > > + SBREC_ADVERTISED_ROUTE_STATUS_TABLE_FOR_EACH_SAFE (
> > > + status, status_table) {
> > > + if (!strcmp(status->chassis_name, chassis_name)) {
> > > + sbrec_advertised_route_status_delete(status);
> > > + }
> > > + }
> > > + }
> > > }
> > > return false;
> > > }
> > > diff --git a/controller/chassis.h b/controller/chassis.h
> > > index 45dd9537a..3be39c957 100644
> > > --- a/controller/chassis.h
> > > +++ b/controller/chassis.h
> > > @@ -29,6 +29,7 @@ struct ovsrec_open_vswitch_table;
> > > struct sbrec_chassis;
> > > struct sbrec_chassis_table;
> > > struct sbrec_chassis_private_table;
> > > +struct sbrec_advertised_route_status_table;
> > > struct sset;
> > > struct eth_addr;
> > > struct smap;
> > > @@ -50,7 +51,8 @@ bool chassis_cleanup(struct ovsdb_idl_txn
> *ovs_idl_txn,
> > > struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > const struct ovsrec_open_vswitch_table *,
> > > const struct sbrec_chassis *,
> > > - const struct sbrec_chassis_private *);
> > > + const struct sbrec_chassis_private *,
> > > + const struct sbrec_advertised_route_status_table *);
> > > bool chassis_get_mac(const struct sbrec_chassis *chassis,
> > > const char *bridge_mapping,
> > > struct eth_addr *chassis_mac);
> > > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > > index 4dcb4b895..6551a4e30 100644
> > > --- a/controller/ovn-controller.c
> > > +++ b/controller/ovn-controller.c
> > > @@ -175,6 +175,7 @@ struct controller_engine_ctx {
> > > struct if_status_mgr *if_mgr;
> > > const unsigned int *ovnsb_expected_cond_seqno;
> > > const bool *sb_monitor_all;
> > > + const struct sbrec_chassis *chassis;
> > > };
> > >
> > > /* Pending packet to be injected into connected OVS. */
> > > @@ -221,6 +222,7 @@ static char *get_file_system_id(void)
> > > static unsigned int
> > > update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
> > > const struct sbrec_chassis *chassis,
> > > + const char *chassis_id,
> > > const struct simap *local_ifaces,
> > > const struct shash *local_bindings,
> > > struct hmap *local_datapaths,
> > > @@ -241,7 +243,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
> > > *
> > > * Monitor ECMP_Nexthop for local datapaths.
> > > *
> > > - * Monitor Advertised/Learned_Route for local datapaths.
> > > + * Monitor Advertised/Learned_Route for local datapaths and
> > > + * Advertised_Route_Status for the local chassis.
> > > *
> > > * We always monitor patch ports because they allow us to see the
> linkages
> > > * between related logical datapaths. That way, when we know that we
> have
> > > @@ -261,6 +264,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
> > > struct ovsdb_idl_condition tv = OVSDB_IDL_CONDITION_INIT(&tv);
> > > struct ovsdb_idl_condition nh = OVSDB_IDL_CONDITION_INIT(&nh);
> > > struct ovsdb_idl_condition ar = OVSDB_IDL_CONDITION_INIT(&ar);
> > > + struct ovsdb_idl_condition ars = OVSDB_IDL_CONDITION_INIT(&ars);
> > > struct ovsdb_idl_condition lr = OVSDB_IDL_CONDITION_INIT(&lr);
> > > struct ovsdb_idl_condition amb = OVSDB_IDL_CONDITION_INIT(&amb);
> > >
> > > @@ -297,6 +301,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
> > > ovsdb_idl_condition_add_clause_true(&tv);
> > > ovsdb_idl_condition_add_clause_true(&nh);
> > > ovsdb_idl_condition_add_clause_true(&ar);
> > > + ovsdb_idl_condition_add_clause_true(&ars);
> > > ovsdb_idl_condition_add_clause_true(&amb);
> > > goto out;
> > > }
> > > @@ -365,6 +370,16 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
> > > * ones. */
> > > ovsdb_idl_condition_add_clause_true(&ar);
> > > }
> > > +
> > > + /* The configured name is available before the Chassis row and also
> > > + * matches status rows whose chassis weak reference was cleared when
> the
> > > + * Chassis row was deleted. */
> > > + const char *status_chassis_name = chassis ? chassis->name :
> chassis_id;
> > > + if (status_chassis_name) {
> > > + sbrec_advertised_route_status_add_clause_chassis_name(
> > > + &ars, OVSDB_F_EQ, status_chassis_name);
> > > + }
> > > +
> > > if (local_ifaces) {
> > > const char *name;
> > >
> > > @@ -434,6 +449,7 @@ out:;
> > > sbrec_chassis_template_var_set_condition(ovnsb_idl, &tv),
> > > sbrec_ecmp_nexthop_set_condition(ovnsb_idl, &nh),
> > > sbrec_advertised_route_set_condition(ovnsb_idl, &ar),
> > > + sbrec_advertised_route_status_set_condition(ovnsb_idl, &ars),
> > > sbrec_learned_route_set_condition(ovnsb_idl, &lr),
> > > sbrec_advertised_mac_binding_set_condition(ovnsb_idl, &amb),
> > > };
> > > @@ -457,6 +473,7 @@ out:;
> > > ovsdb_idl_condition_destroy(&tv);
> > > ovsdb_idl_condition_destroy(&nh);
> > > ovsdb_idl_condition_destroy(&ar);
> > > + ovsdb_idl_condition_destroy(&ars);
> > > ovsdb_idl_condition_destroy(&lr);
> > > ovsdb_idl_condition_destroy(&amb);
> > > return expected_cond_seqno;
> > > @@ -768,7 +785,8 @@ update_sb_db(struct ovsdb_idl *ovs_idl, struct
> ovsdb_idl *ovnsb_idl,
> > > * extra cost. Instead, it is called after the engine execution only
> > > * when it is necessary. */
> > > unsigned int next_cond_seqno =
> > > - update_sb_monitors(ovnsb_idl, NULL, NULL, NULL, NULL, true);
> > > + update_sb_monitors(ovnsb_idl, NULL, chassis_id,
> > > + NULL, NULL, NULL, true);
> > > if (sb_cond_seqno) {
> > > *sb_cond_seqno = next_cond_seqno;
> > > }
> > > @@ -5277,6 +5295,12 @@ struct ed_type_route {
> > >
> > > /* Contains struct advertise_datapath_entry */
> > > struct hmap announce_routes;
> > > +
> > > + /* Contains struct advertised_route_status recorded by route_run()
> > > + * and published in Advertised_Route_Status by
> > > + * route_exchange_run(). */
> > > + struct vector advertised_route_status;
> > > +
> > > struct ovsdb_idl *ovnsb_idl;
> > > };
> > >
> > > @@ -5334,9 +5358,11 @@ en_route_run(struct engine_node *node, void
> *data)
> > > .filtered_ports = &re_data->filtered_ports,
> > > .tracked_ports_remote = &re_data->tracked_ports_remote,
> > > .announce_routes = &re_data->announce_routes,
> > > + .advertised_route_status = &re_data->advertised_route_status,
> > > };
> > >
> > > route_cleanup(&re_data->announce_routes);
> > > + advertised_route_status_clear(&re_data->advertised_route_status);
> > > tracked_datapaths_clear(r_ctx_out.tracked_re_datapaths);
> > > sset_clear(r_ctx_out.tracked_ports_local);
> > > sset_clear(r_ctx_out.tracked_ports_remote);
> > > @@ -5345,7 +5371,7 @@ en_route_run(struct engine_node *node, void
> *data)
> > > sset_clear(r_ctx_out.filtered_ports);
> > >
> > > route_run(&r_ctx_in, &r_ctx_out);
> > > -
> > > + advertised_route_status_sort(&re_data->advertised_route_status);
> > > return EN_UPDATED;
> > > }
> > >
> > > @@ -5363,6 +5389,8 @@ en_route_init(struct engine_node *node
> OVS_UNUSED,
> > > uuidset_init(&data->relevant_service_monitors);
> > > sset_init(&data->filtered_ports);
> > > hmap_init(&data->announce_routes);
> > > + data->advertised_route_status =
> > > + VECTOR_EMPTY_INITIALIZER(struct advertised_route_status);
> > > data->ovnsb_idl = arg->sb_idl;
> > >
> > > return data;
> > > @@ -5381,6 +5409,7 @@ en_route_cleanup(void *data)
> > > sset_destroy(&re_data->filtered_ports);
> > > route_cleanup(&re_data->announce_routes);
> > > hmap_destroy(&re_data->announce_routes);
> > > + vector_destroy(&re_data->advertised_route_status);
> > > }
> > >
> > > static enum engine_input_handler_result
> > > @@ -5710,10 +5739,113 @@ struct ed_type_route_exchange {
> > > bool sb_changes_pending;
> > > };
> > >
> > > +static void
> > > +advertised_route_get_withdrawal_reasons(
> > > + const struct advertised_route_status *desired,
> > > + struct smap *withdrawal_reasons)
> > > +{
> > > + smap_init(withdrawal_reasons);
> > > + if (desired->withdrawal_reason) {
> > > + smap_add(withdrawal_reasons, desired->withdrawal_reason,
> > > + desired->withdrawal_reason_value);
> > > + }
> > > +}
> > > +
> > > +static char *
> > > +advertised_route_get_operational_state(
> > > + struct ed_type_route *route_data,
> > > + const struct advertised_route_status *desired,
> > > + const char **operational_status)
> > > +{
> > > + struct advertise_datapath_entry *ad =
> > > + advertise_datapath_find(&route_data->announce_routes,
> > > + desired->route->datapath);
> > > + *operational_status = ad && ad->routes_synced
> > > + ? (!strcmp(desired->desired_status, "advertised")
> > > + ? "installed" : "withdrawn")
> > > + : "unknown";
> > > +
> > > + if (!ad || !ad->route_error_description) {
> > > + return NULL;
> > > + }
> > > + return ad->route_error
> > > + ? xasprintf("%s: %s", ad->route_error_description,
> > > + ovs_strerror(ad->route_error))
> > > + : xstrdup(ad->route_error_description);
> > > +}
> > > +
> > > +static enum engine_input_handler_result
> > > +route_exchange_sb_advertised_route_status_handler(
> > > + struct engine_node *node, void *data OVS_UNUSED)
> > > +{
> > > + struct controller_engine_ctx *ctrl_ctx =
> > > + engine_get_context()->client_ctx;
> > > + if (!ctrl_ctx->chassis) {
> > > + return EN_HANDLED_UNCHANGED;
> > > + }
> > > +
> > > + struct ed_type_route *route_data =
> > > + engine_get_input_data("route", node);
> > > + const struct sbrec_advertised_route_status_table *status_table =
> > > + EN_OVSDB_GET(engine_get_input("SB_advertised_route_status", node));
> > > + const struct sbrec_advertised_route_status *sb_status;
> > > +
> > > + SBREC_ADVERTISED_ROUTE_STATUS_TABLE_FOR_EACH_TRACKED (sb_status,
> > > + status_table) {
> > > + if (strcmp(sb_status->chassis_name, ctrl_ctx->chassis->name)) {
> > > + continue;
> > > + }
> > > +
> > > + const struct advertised_route_status *desired =
> > > + advertised_route_status_find(
> > > + &route_data->advertised_route_status,
> > > + sb_status->advertised_route);
> > > + if (sbrec_advertised_route_status_is_deleted(sb_status)) {
> > > + if (desired) {
> > > + return EN_UNHANDLED;
> > > + }
> > > + continue;
> > > + }
> > > +
> > > + const struct uuid *route_uuid = desired
> > > + ? &desired->route->header_.uuid : NULL;
> > > + if (!desired ||
> > > + sb_status->advertised_route != desired->route ||
> > > + !uuid_equals(&sb_status->advertised_route_uuid, route_uuid) ||
> > > + sb_status->chassis != ctrl_ctx->chassis) {
> > > + return EN_UNHANDLED;
> > > + }
> > > +
> > > + const char *operational_status;
> > > + char *error = advertised_route_get_operational_state(
> > > + route_data, desired, &operational_status);
> > > + struct smap withdrawal_reasons;
> > > + advertised_route_get_withdrawal_reasons(
> > > + desired, &withdrawal_reasons);
> > > + bool matches =
> > > + !strcmp(sb_status->desired_status, desired->desired_status) &&
> > > + smap_equal(&sb_status->withdrawal_reasons,
> > > + &withdrawal_reasons) &&
> > > + !strcmp(sb_status->operational_status, operational_status) &&
> > > + nullable_string_is_equal(sb_status->error, error);
> > > + smap_destroy(&withdrawal_reasons);
> > > + free(error);
> > > +
> > > + if (!matches) {
> > > + return EN_UNHANDLED;
> > > + }
> > > + }
> > > +
> > > + return EN_HANDLED_UNCHANGED;
> > > +}
> > > +
> > > static enum engine_node_state
> > > en_route_exchange_run(struct engine_node *node, void *data)
> > > {
> > > struct ed_type_route_exchange *re = data;
> > > + struct controller_engine_ctx *ctrl_ctx =
> > > + engine_get_context()->client_ctx;
> > > +
> > > struct ovsdb_idl_index *sbrec_learned_route_by_datapath =
> > > engine_ovsdb_node_get_index(
> > > engine_get_input("SB_learned_route", node),
> > > @@ -5723,6 +5855,11 @@ en_route_exchange_run(struct engine_node *node,
> void *data)
> > > engine_ovsdb_node_get_index(
> > > engine_get_input("SB_port_binding", node),
> > > "name");
> > > + struct ovsdb_idl_index
> *sbrec_advertised_route_status_by_chassis_name =
> > > + engine_ovsdb_node_get_index(
> > > + engine_get_input("SB_advertised_route_status", node),
> > > + "chassis_name");
> > > +
> > > struct ed_type_route *route_data =
> > > engine_get_input_data("route", node);
> > > struct ed_type_route_table_notify *rt_notify =
> > > @@ -5762,6 +5899,106 @@ en_route_exchange_run(struct engine_node
> *node, void *data)
> > > };
> > >
> > > route_exchange_run(&r_ctx_in, &r_ctx_out);
> > > +
> > > + /* Both references are weak. chassis_name is used by RBAC and
> cleanup. */
> > > + if (r_ctx_in.ovnsb_idl_txn && ctrl_ctx->chassis &&
> > > + sbrec_server_has_advertised_route_status_table(re->sb_idl)) {
> > > + struct uuidset published = UUIDSET_INITIALIZER(&published);
> > > + const struct sbrec_advertised_route_status *sb_status;
> > > + struct sbrec_advertised_route_status *status_filter =
> > > + sbrec_advertised_route_status_index_init_row(
> > > + sbrec_advertised_route_status_by_chassis_name);
> > > + sbrec_advertised_route_status_index_set_chassis_name(
> > > + status_filter, ctrl_ctx->chassis->name);
> > > +
> > > + SBREC_ADVERTISED_ROUTE_STATUS_FOR_EACH_EQUAL (
> > > + sb_status, status_filter,
> > > + sbrec_advertised_route_status_by_chassis_name) {
> > > + const struct advertised_route_status *desired =
> > > + advertised_route_status_find(
> > > + &route_data->advertised_route_status,
> > > + sb_status->advertised_route);
> > > +
> > > + const struct uuid *route_uuid = sb_status->advertised_route
> > > + ? &sb_status->advertised_route->header_.uuid : NULL;
> > > + if (!desired ||
> > > + !uuid_equals(&sb_status->advertised_route_uuid, route_uuid) ||
> > > + sb_status->chassis != ctrl_ctx->chassis ||
> > > + (route_uuid && uuidset_contains(&published, route_uuid))) {
> > > + sbrec_advertised_route_status_delete(sb_status);
> > > + continue;
> > > + }
> > > +
> > > + uuidset_insert(&published, route_uuid);
> > > + const char *operational_status;
> > > + char *error = advertised_route_get_operational_state(
> > > + route_data, desired, &operational_status);
> > > + struct smap withdrawal_reasons;
> > > + advertised_route_get_withdrawal_reasons(
> > > + desired, &withdrawal_reasons);
> > > +
> > > + if (strcmp(sb_status->desired_status,
> > > + desired->desired_status)) {
> > > + sbrec_advertised_route_status_set_desired_status(
> > > + sb_status, desired->desired_status);
> > > + }
> > > + if (!smap_equal(&sb_status->withdrawal_reasons,
> > > + &withdrawal_reasons)) {
> > > + sbrec_advertised_route_status_set_withdrawal_reasons(
> > > + sb_status, &withdrawal_reasons);
> > > + }
> > > + if (strcmp(sb_status->operational_status,
> > > + operational_status)) {
> > > + sbrec_advertised_route_status_set_operational_status(
> > > + sb_status, operational_status);
> > > + }
> > > + if (!nullable_string_is_equal(sb_status->error, error)) {
> > > + sbrec_advertised_route_status_set_error(sb_status, error);
> > > + }
> > > + smap_destroy(&withdrawal_reasons);
> > > + free(error);
> > > + }
> > > + sbrec_advertised_route_status_index_destroy_row(status_filter);
> > > +
> > > + struct advertised_route_status *desired;
> > > + VECTOR_FOR_EACH_PTR (&route_data->advertised_route_status, desired) {
> > > + const struct uuid *route_uuid = &desired->route->header_.uuid;
> > > + if (uuidset_contains(&published, route_uuid)) {
> > > + continue;
> > > + }
> > > +
> > > + struct sbrec_advertised_route_status *new_status =
> > > + sbrec_advertised_route_status_insert(r_ctx_in.ovnsb_idl_txn);
> > > + sbrec_advertised_route_status_set_advertised_route(
> > > + new_status, desired->route);
> > > + sbrec_advertised_route_status_set_advertised_route_uuid(
> > > + new_status, *route_uuid);
> > > + sbrec_advertised_route_status_set_chassis(new_status,
> > > + ctrl_ctx->chassis);
> > > + sbrec_advertised_route_status_set_chassis_name(
> > > + new_status, ctrl_ctx->chassis->name);
> > > + sbrec_advertised_route_status_set_desired_status(
> > > + new_status, desired->desired_status);
> > > + struct smap withdrawal_reasons;
> > > + advertised_route_get_withdrawal_reasons(
> > > + desired, &withdrawal_reasons);
> > > + sbrec_advertised_route_status_set_withdrawal_reasons(
> > > + new_status, &withdrawal_reasons);
> > > + smap_destroy(&withdrawal_reasons);
> > > +
> > > + const char *operational_status;
> > > + char *error = advertised_route_get_operational_state(
> > > + route_data, desired, &operational_status);
> > > + sbrec_advertised_route_status_set_operational_status(
> > > + new_status, operational_status);
> > > + if (error) {
> > > + sbrec_advertised_route_status_set_error(new_status, error);
> > > + }
> > > + free(error);
> > > + }
> > > + uuidset_destroy(&published);
> > > + }
> > > +
> > > route_table_notify_update(&rt_notify->watches);
> > >
> > > re->sb_changes_pending = r_ctx_out.sb_changes_pending;
> > > @@ -6982,6 +7219,7 @@ evpn_arp_vtep_binding_handler(struct engine_node
> *node, void *data OVS_UNUSED)
> > > SB_NODE(chassis_template_var) \
> > > SB_NODE(acl_id) \
> > > SB_NODE(advertised_route) \
> > > + SB_NODE(advertised_route_status) \
> > > SB_NODE(learned_route) \
> > > SB_NODE(advertised_mac_binding) \
> > > SB_NODE(service_monitor)
> > > @@ -7127,6 +7365,8 @@ inc_proc_ovn_controller_init(
> > > engine_noop_handler);
> > > engine_add_input(&en_route_exchange, &en_sb_port_binding,
> > > engine_noop_handler);
> > > + engine_add_input(&en_route_exchange, &en_sb_advertised_route_status,
> > > + route_exchange_sb_advertised_route_status_handler);
> > > engine_add_input(&en_route_exchange, &en_route_table_notify, NULL);
> > > engine_add_input(&en_route_exchange, &en_route_exchange_status, NULL);
> > > engine_add_input(&en_route_exchange, &en_sb_ro,
> > > @@ -7471,6 +7711,16 @@ inc_proc_ovn_controller_init(
> > > &sbrec_learned_route_col_datapath);
> > > engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
> > > sbrec_learned_route_index_by_datapath);
> > > +
> > > + struct ovsdb_idl_index
> > > + *sbrec_advertised_route_status_by_chassis_name =
> > > + ovsdb_idl_index_create1(
> > > + sb_idl_loop->idl,
> > > + &sbrec_advertised_route_status_col_chassis_name);
> > > + engine_ovsdb_node_add_index(
> > > + &en_sb_advertised_route_status, "chassis_name",
> > > + sbrec_advertised_route_status_by_chassis_name);
> > > +
> > > struct ovsdb_idl_index *sbrec_advertised_mac_binding_index_by_dp
> > > = ovsdb_idl_index_create1(sb_idl_loop->idl,
> > > &sbrec_advertised_mac_binding_col_datapath);
> > > @@ -7743,7 +7993,8 @@ main(int argc, char *argv[])
> > > ovsdb_idl_omit(ovnsb_idl_loop.idl,
> > > &sbrec_chassis_private_col_external_ids);
> > >
> > > - update_sb_monitors(ovnsb_idl_loop.idl, NULL, NULL, NULL, NULL,
> false);
> > > + update_sb_monitors(ovnsb_idl_loop.idl, NULL, NULL,
> > > + NULL, NULL, NULL, false);
> > >
> > > stopwatch_create(CONTROLLER_LOOP_STOPWATCH_NAME, SW_MS);
> > > stopwatch_create(OFCTRL_PUT_STOPWATCH_NAME, SW_MS);
> > > @@ -8078,6 +8329,7 @@ main(int argc, char *argv[])
> > > &chassis_private,
> > > sbrec_encaps_index_by_ip_and_type);
> > > }
> > > + ctrl_engine_ctx.chassis = chassis;
> > >
> > > /* If any OVS feature support changed, force a full recompute.
> > > * 'br_int_dp' is valid only if an OVS transaction is possible.
> > > @@ -8286,7 +8538,7 @@ main(int argc, char *argv[])
> > > ovnsb_expected_cond_seqno;
> > > ovnsb_expected_cond_seqno =
> > > update_sb_monitors(
> > > - ovnsb_idl_loop.idl, chassis,
> > > + ovnsb_idl_loop.idl, chassis, chassis_id,
> > > &runtime_data->local_lports,
> > > &runtime_data->lbinding_data.bindings,
> > > &runtime_data->local_datapaths,
> > > @@ -8560,11 +8812,19 @@ loop_done:
> > > ? chassis_private_lookup_by_name(
> > > sbrec_chassis_private_by_name, chassis_id)
> > > : NULL);
> > > + const struct sbrec_advertised_route_status_table *status_table =
> > > + sbrec_server_has_advertised_route_status_table(
> > > + ovnsb_idl_loop.idl)
> > > + ? sbrec_advertised_route_status_table_get(
> > > + ovnsb_idl_loop.idl)
> > > + : NULL;
> > > +
> > > /* Run all of the cleanup functions, even if one of them returns
> > > * false. We're done if all of them return true. */
> > > done = binding_cleanup(ovnsb_idl_txn, port_binding_table, chassis);
> > > done = chassis_cleanup(ovs_idl_txn, ovnsb_idl_txn, ovs_table,
> > > - chassis, chassis_private) && done;
> > > + chassis, chassis_private,
> > > + status_table) && done;
> > > done = encaps_cleanup(ovs_idl_txn, br_int) && done;
> > > done = igmp_group_cleanup(ovnsb_idl_txn, sbrec_igmp_group, chassis)
> > > && done;
> > > diff --git a/controller/route-exchange.c b/controller/route-exchange.c
> > > index 027375071..24d5b5875 100644
> > > --- a/controller/route-exchange.c
> > > +++ b/controller/route-exchange.c
> > > @@ -321,13 +321,19 @@ route_exchange_run(const struct
> route_exchange_ctx_in *r_ctx_in,
> > > int error;
> > >
> > > CLEAR_ROUTE_EXCHANGE_NL_STATUS();
> > > - const struct advertise_datapath_entry *ad;
> > > + struct advertise_datapath_entry *ad;
> > > HMAP_FOR_EACH (ad, node, r_ctx_in->announce_routes) {
> > > + ad->routes_synced = false;
> > > + ad->route_error = 0;
> > > + ad->route_error_description = NULL;
> > > +
> > > uint32_t table_id = route_get_table_id(ad->db);
> > > if (!TABLE_ID_VALID(table_id)) {
> > > VLOG_WARN_RL(&rl, "Unable to sync routes for datapath "UUID_FMT": "
> > > "invalid table id: %"PRIu32,
> > > UUID_ARGS(&ad->db->header_.uuid), table_id);
> > > + ad->route_error = EINVAL;
> > > + ad->route_error_description = "invalid route table ID";
> > > continue;
> > > }
> > >
> > > @@ -340,6 +346,8 @@ route_exchange_run(const struct
> route_exchange_ctx_in *r_ctx_in,
> > > UUID_FMT": %s.", ad->vrf_name,
> > > UUID_ARGS(&ad->db->header_.uuid),
> > > ovs_strerror(error));
> > > + ad->route_error = error;
> > > + ad->route_error_description = "unable to create VRF";
> > > SET_ROUTE_EXCHANGE_NL_STATUS(error);
> > > continue;
> > > }
> > > @@ -363,6 +371,9 @@ route_exchange_run(const struct
> route_exchange_ctx_in *r_ctx_in,
> > > "routes on routing table %"PRIu32,
> > > table_id);
> > > entry->can_sync = false;
> > > + ad->route_error = EBUSY;
> > > + ad->route_error_description =
> > > + "multiple datapaths use the same route table";
> > > } else {
> > > entry->routes = &ad->routes;
> > > }
> > > @@ -383,6 +394,9 @@ route_exchange_run(const struct
> route_exchange_ctx_in *r_ctx_in,
> > > }
> > >
> > > if (!entry->can_sync) {
> > > + ad->route_error = EBUSY;
> > > + ad->route_error_description =
> > > + "multiple datapaths use the same route table";
> > > continue;
> > > }
> > >
> > > @@ -392,6 +406,7 @@ route_exchange_run(const struct
> route_exchange_ctx_in *r_ctx_in,
> > > struct advertised_routes_entry *arte;
> > > HMAP_FOR_EACH_POP (arte, node, &advertised_routes) {
> > > maintained_route_table_add(arte->table_id);
> > > + error = 0;
> > > if (arte->can_sync) {
> > > struct vector received_routes =
> > > VECTOR_EMPTY_INITIALIZER(struct re_nl_received_route_node);
> > > @@ -425,6 +440,27 @@ route_exchange_run(const struct
> route_exchange_ctx_in *r_ctx_in,
> > > vector_destroy(&received_routes);
> > > }
> > >
> > > + struct hmapx_node *dp_node;
> > > + HMAPX_FOR_EACH (dp_node, &arte->datapaths) {
> > > + struct advertise_datapath_entry *adpe =
> > > + advertise_datapath_find(r_ctx_in->announce_routes,
> > > + dp_node->data);
> > > + if (!adpe) {
> > > + continue;
> > > + }
> > > + if (!arte->can_sync) {
> > > + adpe->route_error = EBUSY;
> > > + adpe->route_error_description =
> > > + "multiple datapaths use the same route table";
> > > + } else if (error) {
> > > + adpe->route_error = error;
> > > + adpe->route_error_description =
> > > + "route table reconciliation failed";
> > > + } else {
> > > + adpe->routes_synced = true;
> > > + }
> > > + }
> > > +
> > > hmapx_destroy(&arte->datapaths);
> > > free(arte);
> > > }
> > > diff --git a/controller/route-exchange.h b/controller/route-exchange.h
> > > index a1ef4a359..7ecc66671 100644
> > > --- a/controller/route-exchange.h
> > > +++ b/controller/route-exchange.h
> > > @@ -26,8 +26,9 @@ struct route_exchange_ctx_in {
> > > struct ovsdb_idl_index *sbrec_learned_route_by_datapath;
> > > const struct sbrec_chassis *chassis;
> > >
> > > - /* Contains struct advertise_datapath_entry */
> > > - const struct hmap *announce_routes;
> > > + /* Contains struct advertise_datapath_entry. route_exchange_run()
> records
> > > + * the reconciliation result in each entry. */
> > > + struct hmap *announce_routes;
> > > };
> > >
> > > struct route_exchange_ctx_out {
> > > diff --git a/controller/route.c b/controller/route.c
> > > index 0230e372a..c6accfba3 100644
> > > --- a/controller/route.c
> > > +++ b/controller/route.c
> > > @@ -578,6 +578,58 @@ advertised_datapath_alloc(const struct
> sbrec_datapath_binding *datapath)
> > > return ad;
> > > }
> > >
> > > +static void
> > > +route_record_status(struct route_ctx_out *r_ctx_out,
> > > + const struct sbrec_advertised_route *route,
> > > + const char *desired_status,
> > > + const char *withdrawal_reason,
> > > + const char *withdrawal_reason_value)
> > > +{
> > > + struct advertised_route_status status = {
> > > + .route = route,
> > > + .desired_status = desired_status,
> > > + .withdrawal_reason = withdrawal_reason,
> > > + .withdrawal_reason_value = withdrawal_reason_value,
> > > + };
> > > + vector_push(r_ctx_out->advertised_route_status, &status);
> > > +}
> > > +
> > > +void
> > > +advertised_route_status_clear(struct vector *statuses)
> > > +{
> > > + vector_clear(statuses);
> > > +}
> > > +
> > > +static int
> > > +advertised_route_status_cmp(const void *a_, const void *b_)
> > > +{
> > > + const struct advertised_route_status *a = a_;
> > > + const struct advertised_route_status *b = b_;
> > > +
> > > + return uuid_compare_3way(&a->route->header_.uuid,
> > > + &b->route->header_.uuid);
> > > +}
> > > +
> > > +void
> > > +advertised_route_status_sort(struct vector *statuses)
> > > +{
> > > + vector_qsort(statuses, advertised_route_status_cmp);
> > > +}
> > > +
> > > +const struct advertised_route_status *
> > > +advertised_route_status_find(const struct vector *statuses,
> > > + const struct sbrec_advertised_route *route)
> > > +{
> > > + if (!route) {
> > > + return NULL;
> > > + }
> > > +
> > > + const struct advertised_route_status key = {
> > > + .route = route,
> > > + };
> > > + return vector_bsearch(statuses, &key, advertised_route_status_cmp);
> > > +}
> > > +
> > > void
> > > route_run(struct route_ctx_in *r_ctx_in,
> > > struct route_ctx_out *r_ctx_out)
> > > @@ -736,14 +788,25 @@ route_run(struct route_ctx_in *r_ctx_in,
> > >
> > > if (distributed_lb &&
> > > !smap_get_bool(&route->external_ids, "enabled", true)) {
> > > + route_record_status(r_ctx_out, route, "withdrawn",
> > > + "administrative-policy", "disabled");
> > > continue;
> > > }
> > >
> > > int gate = lb_route_gate_decision(&lb_route_gates, route);
> > > if (gate == 0) {
> > > + if (distributed_lb) {
> > > + route_record_status(r_ctx_out, route,
> > > + "withdrawn", "service-monitor",
> > > + "no-online-backend");
> > > + }
> > > continue;
> > > }
> > >
> > > + if (distributed_lb) {
> > > + route_record_status(r_ctx_out, route, "advertised", NULL, NULL);
> > > + }
> > > +
> > > struct in6_addr nexthop = IN6_IS_ADDR_V4MAPPED(&prefix)
> > > ? ad->ipv4_nexthop : ad->ipv6_nexthop;
> > > if (advertise_route_find(priority, &prefix, plen, &nexthop,
> > > diff --git a/controller/route.h b/controller/route.h
> > > index 5ddc94456..cf575f0a9 100644
> > > --- a/controller/route.h
> > > +++ b/controller/route.h
> > > @@ -68,6 +68,21 @@ struct route_ctx_out {
> > >
> > > /* Contains struct advertise_datapath_entry */
> > > struct hmap *announce_routes;
> > > +
> > > + /* Contains struct advertised_route_status entries recorded by
> > > + * route_run(), sorted by Advertised_Route UUID for publication in
> > > + * Advertised_Route_Status by route_exchange_run(). */
> > > + struct vector *advertised_route_status;
> > > +};
> > > +
> > > +/* Per-route advertisement decision recorded by route_run() so that
> > > + * route_exchange_run() (which holds a writable SB txn) can publish
> it for the
> > > + * local chassis in Advertised_Route_Status. */
> > > +struct advertised_route_status {
> > > + const struct sbrec_advertised_route *route;
> > > + const char *desired_status;
> > > + const char *withdrawal_reason;
> > > + const char *withdrawal_reason_value;
> > > };
> > >
> > > struct advertise_datapath_entry {
> > > @@ -116,6 +131,11 @@ struct advertise_route_entry
> > > advertise_route_from_route_data(const struct route_data *);
> > > void route_run(struct route_ctx_in *, struct route_ctx_out *);
> > > void route_cleanup(struct hmap *announce_routes);
> > > +void advertised_route_status_clear(struct vector *statuses);
> > > +void advertised_route_status_sort(struct vector *statuses);
> > > +const struct advertised_route_status *advertised_route_status_find(
> > > + const struct vector *statuses,
> > > + const struct sbrec_advertised_route *route);
> > > uint32_t route_get_table_id(const struct sbrec_datapath_binding *);
> > > struct advertise_route_entry *
> > > advertise_route_find(unsigned int priority, const struct in6_addr
> *prefix,
> > > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> > > index 499cf9edd..dbb9b7a26 100644
> > > --- a/northd/ovn-northd.c
> > > +++ b/northd/ovn-northd.c
> > > @@ -132,6 +132,10 @@ static const char *rbac_learned_route_auth[] =
> > > {""};
> > > static const char *rbac_learned_route_update[] =
> > > {"datapath", "logical_port", "ip_prefix", "nexthop", "external_ids"};
> > > +static const char *rbac_advertised_route_status_auth[] =
> > > + {"chassis_name"};
> > > +static const char *rbac_advertised_route_status_update[] =
> > > + {"desired_status", "withdrawal_reasons", "operational_status",
> "error"};
> > >
> > > static struct rbac_perm_cfg {
> > > const char *table;
> > > @@ -222,6 +226,14 @@ static struct rbac_perm_cfg {
> > > .update = rbac_bfd_update,
> > > .n_update = ARRAY_SIZE(rbac_bfd_update),
> > > .row = NULL
> > > + },{
> > > + .table = "Advertised_Route_Status",
> > > + .auth = rbac_advertised_route_status_auth,
> > > + .n_auth = ARRAY_SIZE(rbac_advertised_route_status_auth),
> > > + .insdel = true,
> > > + .update = rbac_advertised_route_status_update,
> > > + .n_update = ARRAY_SIZE(rbac_advertised_route_status_update),
> > > + .row = NULL
> > > },{
> > > .table = "Learned_Route",
> > > .auth = rbac_learned_route_auth,
> > > @@ -941,6 +953,11 @@ main(int argc, char *argv[])
> > > /* Omit unused columns. */
> > > ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_sb_global_col_connections);
> > > ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_sb_global_col_ssl);
> > > + for (size_t i = 0;
> > > + i < SBREC_ADVERTISED_ROUTE_STATUS_N_COLUMNS; i++) {
> > > + ovsdb_idl_omit(ovnsb_idl_loop.idl,
> > > + &sbrec_advertised_route_status_columns[i]);
> > > + }
> > >
> > > /* Disable alerting for pure write-only columns. */
> > > ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_nb_cfg);
> > > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> > > index 6b703f328..3587024bf 100644
> > > --- a/ovn-sb.ovsschema
> > > +++ b/ovn-sb.ovsschema
> > > @@ -1,7 +1,7 @@
> > > {
> > > "name": "OVN_Southbound",
> > > - "version": "21.11.0",
> > > - "cksum": "4289271680 36997",
> > > + "version": "21.12.0",
> > > + "cksum": "1561088612 38543",
> > > "tables": {
> > > "SB_Global": {
> > > "columns": {
> > > @@ -682,6 +682,36 @@
> > > "indexes": [["datapath", "logical_port",
> > > "ip_prefix", "tracked_port"]],
> > > "isRoot": true},
> > > + "Advertised_Route_Status": {
> > > + "columns": {
> > > + "advertised_route": {
> > > + "type": {"key": {"type": "uuid",
> > > + "refTable": "Advertised_Route",
> > > + "refType": "weak"},
> > > + "min": 0, "max": 1}},
> > > + "advertised_route_uuid": {"type": "uuid"},
> > > + "chassis": {
> > > + "type": {"key": {"type": "uuid",
> > > + "refTable": "Chassis",
> > > + "refType": "weak"},
> > > + "min": 0, "max": 1}},
> > > + "chassis_name": {"type": "string"},
> > > + "desired_status": {
> > > + "type": {"key": {"type": "string",
> > > + "enum": ["set", ["advertised",
> > > + "withdrawn"]]}}},
> > > + "withdrawal_reasons": {
> > > + "type": {"key": "string", "value": "string",
> > > + "min": 0, "max": "unlimited"}},
> > > + "operational_status": {
> > > + "type": {"key": {"type": "string",
> > > + "enum": ["set", ["installed",
> > > + "withdrawn",
> > > + "unknown"]]}}},
> > > + "error": {
> > > + "type": {"key": "string", "min": 0, "max": 1}}},
> > > + "indexes": [["advertised_route_uuid", "chassis_name"]],
> > > + "isRoot": true},
> > > "Learned_Route": {
> > > "columns": {
> > > "datapath": {"type": {"key": {"type": "uuid",
> > > diff --git a/ovn-sb.xml b/ovn-sb.xml
> > > index 037954800..d5c5210dc 100644
> > > --- a/ovn-sb.xml
> > > +++ b/ovn-sb.xml
> > > @@ -5538,6 +5538,98 @@ tcp.flags = RST;
> > > </dl>
> > > </column>
> > > </table>
> > > +
> > > + <table name="Advertised_Route_Status">
> > > + <p>
> > > + Each record reports the desired and kernel route state of one
> > > + distributed Load Balancer <ref table="Advertised_Route"/> on one
> > > + chassis. Several chassis can report the same route.
> > > + </p>
> > > +
> > > + <p>
> > > + A controller creates a row for each
> > > + <code>external_ids:distributed-lb=true</code> route selected for its
> > > + chassis, including withdrawn routes. Other route types have no status
> > > + rows. A missing row means that the route was not selected for the
> > > + chassis or that the controller has not published its state. Status
> rows
> > > + do not indicate controller liveness.
> > > + </p>
> > > +
> > > + <p>
> > > + <code>ovn-controller</code> manages these rows. RBAC restricts each
> > > + controller to its chassis name. Normal controller shutdown and
> > > + <code>ovn-sbctl chassis-del</code> remove the corresponding rows. A
> > > + controller failure followed by generic database deletion of the
> Chassis
> > > + can leave rows with cleared weak references. The copied route UUID
> and
> > > + chassis name remain available for identifying those rows.
> > > + </p>
> > > +
> > > + <column name="advertised_route">
> > > + The route for which the chassis made the advertisement decision.
> > > + </column>
> > > +
> > > + <column name="advertised_route_uuid">
> > > + A copy of the UUID of
> > > + <ref table="Advertised_Route_Status" column="advertised_route"/>. It
> > > + remains after the weak reference is cleared and avoids duplicate
> empty
> > > + values in the status index when several routes are deleted.
> > > + </column>
> > > +
> > > + <column name="chassis">
> > > + The chassis that made the advertisement decision.
> > > + </column>
> > > +
> > > + <column name="chassis_name">
> > > + A copy of the name of
> > > + <ref table="Advertised_Route_Status" column="chassis"/>. RBAC uses
> this
> > > + value to authorize controller writes. It remains after the weak
> > > + chassis reference is cleared.
> > > + </column>
> > > +
> > > + <column name="desired_status">
> > > + The chassis's current decision. <code>advertised</code> means the
> > > + controller intends to install the route for export, and
> > > + <code>withdrawn</code> means that it does not. The
> > > + <ref table="Advertised_Route_Status" column="withdrawal_reasons"/>
> > > + map describes why a route is withdrawn.
> > > + </column>
> > > +
> > > + <column name="withdrawal_reasons">
> > > + Machine-readable reasons for the current
> > > + <ref table="Advertised_Route_Status" column="desired_status"/>.
> > > + The map is empty when the desired state is <code>advertised</code>.
> > > + Initially, <code>administrative-policy=disabled</code> reports an
> > > + <ref table="Advertised_Route" column="external_ids"/>
> > > + <code>enabled=false</code> override, and
> > > + <code>service-monitor=no-online-backend</code> reports that the
> > > + relevant <ref table="Service_Monitor"/> records prevented
> > > + advertisement. Future controller inputs can add reason keys without
> > > + changing the desired-state values. Readers must ignore unknown keys.
> > > + </column>
> > > +
> > > + <column name="operational_status">
> > > + The result of reconciling the desired state with the chassis's kernel
> > > + routing table. <code>installed</code> means reconciliation completed
> > > + with this route selected. <code>withdrawn</code> means reconciliation
> > > + completed with this route excluded. <code>unknown</code> means
> > > + reconciliation has not yet completed
> > > + or failed. <ref table="Advertised_Route_Status"
> > > + column="error"/> can contain more detail.
> > > +
> > > + <p>
> > > + This column reports OVN's kernel route. It does not report whether an
> > > + external routing daemon exported the prefix. Equivalent
> > > + <ref table="Advertised_Route"/> rows with the same prefix, nexthop
> and
> > > + priority can share one kernel route.
> > > + </p>
> > > + </column>
> > > +
> > > + <column name="error">
> > > + A description of the current route-table reconciliation failure. It
> is
> > > + cleared after successful reconciliation.
> > > + </column>
> > > + </table>
> > > +
> > > <table name="Learned_Route">
> > > <p>
> > > Each record represents a route that learned by ovn using some dynamic
> > > diff --git a/tests/automake.mk b/tests/automake.mk
> > > index 8084357b5..d4c1d3710 100644
> > > --- a/tests/automake.mk
> > > +++ b/tests/automake.mk
> > > @@ -190,17 +190,17 @@ check-userspace-valgrind: all
> $(valgrind_wrappers) $(check_DATA)
> > > check-helgrind: all $(valgrind_wrappers) $(check_DATA)
> > > -$(SHELL) '$(TESTSUITE)' -C tests CHECK_VALGRIND=true
> VALGRIND='$(HELGRIND)' AUTOTEST_PATH='tests/valgrind:$(AUTOTEST_PATH)' -d
> $(TESTSUITEFLAGS)
> > >
> > > -check-system-dpdk: all
> > > +check-system-dpdk: all $(check_DATA)
> > > set $(SHELL) '$(SYSTEM_DPDK_TESTSUITE)' -C tests
> AUTOTEST_PATH='$(AUTOTEST_PATH)'; \
> > > $(SUDO) "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes &&
> $(SUDO) "$$@" --recheck)
> > >
> > > # Run kmod tests. Assume kernel modules has been installed or linked
> into the kernel
> > > -check-kernel: all
> > > +check-kernel: all $(check_DATA)
> > > set $(SHELL) '$(SYSTEM_KMOD_TESTSUITE)' -C tests
> AUTOTEST_PATH='$(AUTOTEST_PATH)'; \
> > > $(SUDO) "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes &&
> $(SUDO) "$$@" --recheck)
> > >
> > >
> > > -check-system-userspace: all
> > > +check-system-userspace: all $(check_DATA)
> > > set $(SHELL) '$(SYSTEM_USERSPACE_TESTSUITE)' -C tests
> AUTOTEST_PATH='$(AUTOTEST_PATH)'; \
> > > $(SUDO) "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes &&
> $(SUDO) "$$@" --recheck)
> > >
> > > diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
> > > index e17ebea76..8af3be8d9 100644
> > > --- a/tests/ovn-controller.at
> > > +++ b/tests/ovn-controller.at
> > > @@ -3006,6 +3006,65 @@ OVN_CLEANUP([hv1])
> > > AT_CLEANUP
> > > ])
> > >
> > > +OVN_FOR_EACH_NORTHD([
> > > +AT_SETUP([ovn-controller - advertised route status schema
> compatibility])
> > > +AT_KEYWORDS([ovn])
> > > +
> > > +ovn_start
> > > +
> > > +# Connect a new ovn-controller to an SB database without
> > > +# Advertised_Route_Status.
> > > +$PYTHON -c '
> > > +import json
> > > +import sys
> > > +
> > > +with open(sys.argv[[1]], encoding="utf-8") as stream:
> > > + schema = json.load(stream)
> > > +schema[["tables"]].pop("Advertised_Route_Status")
> > > +schema[["version"]] = "21.10.0"
> > > +schema.pop("cksum", None)
> > > +json.dump(schema, sys.stdout)
> > > +' "$abs_top_srcdir/ovn-sb.ovsschema" > old-sb.ovsschema
> > > +check ovsdb-client convert $OVN_SB_DB old-sb.ovsschema
> > > +OVS_WAIT_UNTIL([
> > > + test X"`ovsdb-client get-schema-version $OVN_SB_DB OVN_Southbound`" \
> > > + = X21.10.0
> > > +])
> > > +
> > > +# Restart ovsdb-server so that the _Server database reports the old
> schema.
> > > +as ovn-sb
> > > +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> > > +mv "$ovs_base"/ovn-sb/ovsdb-server.log \
> > > + "$ovs_base"/ovn-sb/ovsdb-server-before-schema-restart.log
> > > +as ovn-sb start_daemon ovsdb-server \
> > > + -vjsonrpc \
> > > + --remote=punix:"$ovs_base"/ovn-sb/ovn-sb.sock \
> > > + --remote=db:OVN_Southbound,SB_Global,connections \
> > > + --private-key="$PKIDIR"/testpki-test-privkey.pem \
> > > + --certificate="$PKIDIR"/testpki-test-cert.pem \
> > > + --ca-cert="$PKIDIR"/testpki-cacert.pem \
> > > + "$ovs_base"/ovn-sb/ovn-sb.db
> > > +PARSE_LISTENING_PORT([ovn-sb/ovsdb-server.log], [TCP_PORT])
> > > +SSL_OVN_SB_DB=ssl:127.0.0.1:$TCP_PORT
> > > +export SSL_OVN_SB_DB
> > > +
> > > +net_add n1
> > > +sim_add hv1
> > > +as hv1
> > > +ovs-vsctl add-br br-phys
> > > +ovn_attach n1 br-phys 192.168.0.1
> > > +
> > > +# The controller registers and omits status publication.
> > > +wait_row_count Chassis 1 name=hv1
> > > +OVS_WAIT_FOR_OUTPUT([ovn-appctl -t ovn-controller connection-status],
> > > + [0], [connected
> > > +])
> > > +
> > > +OVN_CLEANUP([hv1
> > > +/lacks Advertised_Route_Status table/d])
> > > +AT_CLEANUP
> > > +])
> > > +
> > > AT_SETUP([ovn-controller - ssl/tls ciphers using command line options])
> > > AT_KEYWORDS([ovn])
> > > AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
> > > diff --git a/tests/ovn-inc-proc-graph-dump.at b/tests/
> ovn-inc-proc-graph-dump.at
> > > index 81f8c151f..ea70293ce 100644
> > > --- a/tests/ovn-inc-proc-graph-dump.at
> > > +++ b/tests/ovn-inc-proc-graph-dump.at
> > > @@ -463,6 +463,7 @@ digraph "Incremental-Processing-Engine" {
> > > SB_datapath_binding -> route
> [[label="route_sb_datapath_binding_handler"]];
> > > SB_service_monitor -> route
> [[label="route_sb_service_monitor_handler"]];
> > > SB_learned_route [[style=filled, shape=box, fillcolor=white,
> label="SB_learned_route"]];
> > > + SB_advertised_route_status [[style=filled, shape=box,
> fillcolor=white, label="SB_advertised_route_status"]];
> > > route_table_notify [[style=filled, shape=box, fillcolor=white,
> label="route_table_notify"]];
> > > route_exchange_status [[style=filled, shape=box, fillcolor=white,
> label="route_exchange_status"]];
> > > route_exchange [[style=filled, shape=box, fillcolor=white,
> label="route_exchange"]];
> > > @@ -471,6 +472,7 @@ digraph "Incremental-Processing-Engine" {
> > > route -> route_exchange [[label=""]];
> > > SB_learned_route -> route_exchange [[label="engine_noop_handler"]];
> > > SB_port_binding -> route_exchange [[label="engine_noop_handler"]];
> > > + SB_advertised_route_status -> route_exchange
> [[label="route_exchange_sb_advertised_route_status_handler"]];
> > > route_table_notify -> route_exchange [[label=""]];
> > > route_exchange_status -> route_exchange [[label=""]];
> > > sb_ro -> route_exchange [[label="route_exchange_sb_ro_handler"]];
> > > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> > > index 8a7707362..e7912108a 100644
> > > --- a/tests/ovn-northd.at
> > > +++ b/tests/ovn-northd.at
> > > @@ -15860,6 +15860,17 @@ AT_SETUP([RBAC -- Recover builtin role and
> permissions])
> > > ovn_start
> > >
> > > wait_row_count RBAC_Role 1 name=ovn-controller
> > > +# Route status has a dedicated chassis-owned table. Do not grant
> > > +# ovn-controller column-wide access to the northd-owned
> Advertised_Route map,
> > > +# where administrative keys such as external_ids:enabled live.
> > > +check_row_count RBAC_Permission 0 table=Advertised_Route
> > > +check_column '[chassis_name]' RBAC_Permission authorization \
> > > + table=Advertised_Route_Status
> > > +check_column true RBAC_Permission insert_delete \
> > > + table=Advertised_Route_Status
> > > +check_column 'desired_status error operational_status
> withdrawal_reasons' \
> > > + RBAC_Permission update \
> > > + table=Advertised_Route_Status
> > > RBR_BUILTIN_PNAMES=$(fetch_column RBAC_Role permissions
> name=ovn-controller | uuidfilt | sed -e 's/<[[^<>]]>*//g' | tr -d '=,{}')
> > >
> > > declare -A RBR_BUILTIN_PERM
> > > diff --git a/tests/ovn-sbctl.at b/tests/ovn-sbctl.at
> > > index 5e9754ffd..2ce682870 100644
> > > --- a/tests/ovn-sbctl.at
> > > +++ b/tests/ovn-sbctl.at
> > > @@ -96,6 +96,11 @@ ch2_uuid=$(ovn-sbctl -d bare --no-headings
> --columns _uuid find chassis name=ch2
> > > check_uuid ovn-sbctl create Chassis_Private name=ch2 chassis=$ch2_uuid
> > > check_row_count Chassis_Private 1
> > >
> > > +check_uuid ovn-sbctl create Advertised_Route_Status \
> > > + advertised_route_uuid=$ch2_uuid chassis=$ch2_uuid chassis_name=ch2 \
> > > + desired_status=advertised operational_status=installed
> > > +check_row_count Advertised_Route_Status 1 chassis_name=ch2
> > > +
> > > AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type
> list encap | sort],
> > > [0], [dnl
> > > 1.2.3.5,geneve
> > > @@ -110,6 +115,7 @@ AT_CHECK([ovn-sbctl -f csv -d bare --no-headings
> --columns ip,type list encap |
> > > 1.2.3.5,vxlan
> > > ])
> > > check_row_count Chassis_Private 0
> > > +check_row_count Advertised_Route_Status 0 chassis_name=ch2
> > >
> > > as ovn-sb
> > > OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> > > @@ -322,4 +328,3 @@ AT_CHECK([ovn-sbctl count-flows wrongDatapath],
> [0], [dnl
> > > Total number of logical flows = 0
> > > ])
> > > ])
> > > -
> > > diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> > > index b28f4c991..ee5c2b810 100644
> > > --- a/tests/system-ovn.at
> > > +++ b/tests/system-ovn.at
> > > @@ -22725,6 +22725,47 @@ wait_row_count sb:Advertised_Route 1
> ip_prefix='"172.16.1.10"'
> > > lb_ar=$(ovn-sbctl --bare --columns=_uuid find Advertised_Route \
> > > ip_prefix=172.16.1.10)
> > > test -n "$lb_ar"
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > +check_column 'service-monitor=no-online-backend' \
> > > + Advertised_Route_Status withdrawal_reasons \
> > > + advertised_route=$lb_ar chassis_name=hv1
> > > +
> > > +# The hv1 role can change its status columns. The checks below reject
> writes
> > > +# to another chassis, identity columns and Advertised_Route.
> > > +if test -n "$SSL_OVN_SB_DB"; then
> > > + # Rejected writes are checked below with their expected nonzero
> status.
> > > + rbac_hv1_sbctl() {
> > > + command ovn-sbctl --db=$SSL_OVN_SB_DB \
> > > + --private-key=$PKIDIR/testpki-hv1-privkey.pem \
> > > + --certificate=$PKIDIR/testpki-hv1-cert.pem \
> > > + --ca-cert=$PKIDIR/testpki-cacert.pem "$@"
> > > + }
> > > +
> > > + hv1_status=$(ovn-sbctl --bare --columns=_uuid \
> > > + find Advertised_Route_Status advertised_route=$lb_ar \
> > > + chassis_name=hv1)
> > > + hv2_status=$(ovn-sbctl create Advertised_Route_Status \
> > > + advertised_route=$lb_ar advertised_route_uuid=$lb_ar \
> > > + chassis_name=hv2 desired_status=advertised \
> > > + operational_status=unknown)
> > > +
> > > + AT_CHECK([rbac_hv1_sbctl set Advertised_Route_Status $hv2_status \
> > > + desired_status=withdrawn], [1], [ignore], [ignore])
> > > + AT_CHECK([rbac_hv1_sbctl destroy Advertised_Route_Status
> $hv2_status],
> > > + [1], [ignore], [ignore])
> > > + check rbac_hv1_sbctl set Advertised_Route_Status $hv1_status \
> > > + withdrawal_reasons:rbac-test=allowed
> > > + AT_CHECK([rbac_hv1_sbctl set Advertised_Route_Status $hv1_status \
> > > + chassis_name=hv2], [1], [ignore], [ignore])
> > > + AT_CHECK([rbac_hv1_sbctl set Advertised_Route_Status $hv1_status \
> > > + advertised_route_uuid=$hv2_status], [1], [ignore], [ignore])
> > > + AT_CHECK([rbac_hv1_sbctl set Advertised_Route $lb_ar \
> > > + external_ids:enabled=false], [1], [ignore], [ignore])
> > > +
> > > + check ovn-sbctl destroy Advertised_Route_Status $hv2_status
> > > +fi
> > >
> > > # The offline monitor withdraws the route.
> > > AT_CHECK([
> > > @@ -22740,6 +22781,7 @@ central_ar=$(ovn-sbctl --bare --columns=_uuid
> find Advertised_Route \
> > > test -n "$central_ar"
> > > AT_CHECK([! ovn-sbctl --bare get Advertised_Route $central_ar
> external_ids |
> > > grep -q 'distributed-lb'])
> > > +wait_row_count sb:Advertised_Route_Status 0 chassis_name=hv1
> > > OVS_WAIT_UNTIL([
> > > ip route list vrf ovnvrf1339 | grep -q "blackhole 172.16.1.10"])
> > >
> > > @@ -22756,6 +22798,9 @@ lb_ar=$(ovn-sbctl --bare --columns=_uuid find
> Advertised_Route \
> > > test -n "$lb_ar"
> > > AT_CHECK([ovn-sbctl --bare get Advertised_Route $lb_ar external_ids |
> > > grep -q 'distributed-lb.*true'])
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > OVS_WAIT_UNTIL([
> > > ! ip route list vrf ovnvrf1339 | grep -q "blackhole 172.16.1.10"])
> > >
> > > @@ -22773,6 +22818,9 @@ check ovn-sbctl chassis-add hv2 geneve
> 169.0.0.2
> > > hv2=$(fetch_column Chassis _uuid name=hv2)
> > > check ovn-sbctl set Port_Binding be0 chassis=$hv2
> > > wait_row_count Service_Monitor 1 logical_port=be0 status=offline
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > AT_CHECK([
> > > ip route list vrf ovnvrf1339 | grep -c "blackhole 172.16.1.10" || true
> > > ], [0], [0
> > > @@ -22783,6 +22831,7 @@ AT_CHECK([
> > > # recompute hv1's route node.
> > > check ovn-nbctl --wait=hv set Logical_Router_Port lr-origin-share \
> > > options:dynamic-routing-redistribute-local-only=true
> > > +wait_row_count sb:Advertised_Route_Status 0 chassis_name=hv1
> > >
> > > sm_v4=$(ovn-sbctl --bare --columns=_uuid find Service_Monitor \
> > > logical_port=be0 ip='"192.168.0.10"' port=80 protocol=tcp)
> > > @@ -22818,12 +22867,23 @@ OVS_WAIT_UNTIL([
> > > route_recompute_ct=$(ovn-appctl inc-engine/show-stats route recompute)
> > > test "$route_recompute_ct" -ne 0
> > > ])
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > +check_column '' Advertised_Route_Status withdrawal_reasons \
> > > + advertised_route=$lb_ar chassis_name=hv1
> > > OVS_WAIT_UNTIL([
> > > ip route list vrf ovnvrf1339 | grep -q "blackhole 172.16.1.10"])
> > >
> > > # Stop the backend and wait for route withdrawal.
> > > kill `cat $be0_pid_file`
> > > wait_row_count Service_Monitor 1 logical_port=be0 status=offline
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > +check_column 'service-monitor=no-online-backend' \
> > > + Advertised_Route_Status withdrawal_reasons \
> > > + advertised_route=$lb_ar chassis_name=hv1
> > > OVS_WAIT_UNTIL([
> > > ! ip route list vrf ovnvrf1339 | grep -q "blackhole 172.16.1.10"])
> > >
> > > @@ -22887,6 +22947,9 @@ OVS_WAIT_UNTIL([
> > > route_recompute_ct=$(ovn-appctl inc-engine/show-stats route recompute)
> > > test "$route_recompute_ct" -ne 0
> > > ])
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > OVS_WAIT_UNTIL([
> > > ! ip route list vrf ovnvrf1339 | grep -q "blackhole 172.16.1.10"])
> > > check ovn-sbctl destroy Service_Monitor $unrelated_sm
> > > @@ -22920,6 +22983,9 @@ AT_CHECK([
> > > ip -6 route list vrf ovnvrf1339 | grep -c "blackhole fd00::200" || true
> > > ], [0], [0
> > > ])
> > > +wait_row_count sb:Advertised_Route_Status 1
> advertised_route=$lb_ar_v6 \
> > > + advertised_route_uuid=$lb_ar_v6 chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > >
> > > # Pause northd so it cannot overwrite the monitor ip, then rewrite the
> > > # ip to its expanded textual form. A forced recompute makes the
> > > @@ -22944,6 +23010,12 @@ AT_CHECK([
> > > ip -6 route list vrf ovnvrf1339 | grep -c "blackhole fd00::200" || true
> > > ], [0], [0
> > > ])
> > > +wait_row_count sb:Advertised_Route_Status 1
> advertised_route=$lb_ar_v6 \
> > > + advertised_route_uuid=$lb_ar_v6 chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > +check_column 'service-monitor=no-online-backend' \
> > > + Advertised_Route_Status withdrawal_reasons \
> > > + advertised_route=$lb_ar_v6 chassis_name=hv1
> > > # Resume northd. It repairs the monitor ip back to compressed form.
> > > check as northd ovn-appctl -t ovn-northd resume
> > > wait_row_count Service_Monitor 1 logical_port=be0 ip='"fd00::10"' \
> > > @@ -22953,6 +23025,9 @@ wait_row_count Service_Monitor 1
> logical_port=be0 ip='"fd00::10"' \
> > > # unconditional.
> > > check ovn-nbctl clear Load_Balancer lb0 health_check
> > > wait_row_count Service_Monitor 0 logical_port=be0
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > OVS_WAIT_UNTIL([
> > > ip route list vrf ovnvrf1339 | grep -q "blackhole 172.16.1.10"])
> > >
> > > @@ -22972,10 +23047,21 @@ check test "$route_recompute_ct" -eq 0
> > > check ovn-sbctl destroy Service_Monitor $ungated_sm
> > > check as northd ovn-appctl -t ovn-northd resume
> > >
> > > +# Add a second VIP so both routes can be deleted in one transaction.
> > > +check ovn-nbctl --wait=hv lb-add lb0 172.16.1.11:80 192.168.0.10:80
> > > +lb_ar2=$(ovn-sbctl --bare --columns=_uuid find Advertised_Route \
> > > + ip_prefix=172.16.1.11)
> > > +test -n "$lb_ar2"
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar2 \
> > > + advertised_route_uuid=$lb_ar2 chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > +
> > > # Remove the routes.
> > > check ovn-nbctl --wait=hv lr-lb-del lr-target lb0
> > > wait_row_count sb:Advertised_Route 0 ip_prefix='"172.16.1.10"'
> > > +wait_row_count sb:Advertised_Route 0 ip_prefix='"172.16.1.11"'
> > > wait_row_count sb:Advertised_Route 0 ip_prefix='"fd00::200"'
> > > +wait_row_count sb:Advertised_Route_Status 0 chassis_name=hv1
> > >
> > > OVS_APP_EXIT_AND_WAIT([ovn-controller])
> > >
> > > @@ -23432,12 +23518,28 @@ lb_ar=$(ovn-sbctl
> --columns=_uuid,external_ids find Advertised_Route \
> > > /source.*lb/ { print uuid; exit }
> > > ')
> > > test -n "$lb_ar"
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > +
> > > +# The NAT route has no status row.
> > > +nat_ar=$(ovn-sbctl --columns=_uuid,tracked_port find Advertised_Route
> \
> > > + ip_prefix=172.16.1.10 | awk '
> > > + /_uuid/ { uuid = $3 }
> > > + /tracked_port/ && length($3) != 36 { print uuid; exit }
> > > + ')
> > > +test -n "$nat_ar"
> > > +wait_row_count sb:Advertised_Route_Status 0 advertised_route=$nat_ar \
> > > + chassis_name=hv1
> > >
> > > # Start the listener and install the LB route.
> > > be_pid_file=$(mktemp be0_http.XXX.pid)
> > > NETNS_DAEMONIZE([be0_ns],
> > > [[$PYTHON $srcdir/test-l7.py http]], [$be_pid_file])
> > > wait_row_count Service_Monitor 1 logical_port=be0 status=online
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > # Both routes are installed at different priorities.
> > > AT_CHECK([
> > > ip route list vrf ovnvrf1342 | grep -c "blackhole 172.16.1.10"
> > > @@ -23446,6 +23548,12 @@ AT_CHECK([
> > >
> > > # Disable the LB route. The NAT route remains.
> > > check ovn-sbctl set Advertised_Route $lb_ar external_ids:enabled=false
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > +check_column 'administrative-policy=disabled' \
> > > + Advertised_Route_Status withdrawal_reasons \
> > > + advertised_route=$lb_ar chassis_name=hv1
> > > AT_CHECK([
> > > ip route list vrf ovnvrf1342 | grep -c "blackhole 172.16.1.10"
> > > ], [0], [1
> > > @@ -23453,10 +23561,73 @@ AT_CHECK([
> > >
> > > # Re-enable the LB route.
> > > check ovn-sbctl remove Advertised_Route $lb_ar external_ids enabled
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > +
> > > +# Delete the local status row and wait for the controller to recreate
> it.
> > > +old_status=$(ovn-sbctl --bare --columns=_uuid \
> > > + find Advertised_Route_Status advertised_route=$lb_ar
> chassis_name=hv1)
> > > +check ovn-sbctl destroy Advertised_Route_Status $old_status
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > +repaired_status=$(ovn-sbctl --bare --columns=_uuid \
> > > + find Advertised_Route_Status advertised_route=$lb_ar
> chassis_name=hv1)
> > > +AT_CHECK([test "$old_status" != "$repaired_status"])
> > > +
> > > +# Corrupt the status and wait for the controller to restore it.
> > > +check ovn-sbctl set Advertised_Route_Status $repaired_status \
> > > + desired_status=withdrawn operational_status=unknown \
> > > + withdrawal_reasons:unexpected=forced-corruption
> error=forced-corruption
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=advertised operational_status=installed
> > > +check_column '' Advertised_Route_Status withdrawal_reasons \
> > > + advertised_route=$lb_ar chassis_name=hv1
> > > +OVS_WAIT_UNTIL([
> > > + test X"`ovn-sbctl --bare --columns=error \
> > > + find Advertised_Route_Status advertised_route=$lb_ar \
> > > + chassis_name=hv1`" = X
> > > +])
> > > +
> > > +# The controller removes an unexpected row bearing its chassis name.
> > > +hv1_chassis=$(fetch_column Chassis _uuid name=hv1)
> > > +unexpected_status=$(ovn-sbctl create Advertised_Route_Status \
> > > + advertised_route_uuid=$hv1_chassis chassis=$hv1_chassis \
> > > + chassis_name=hv1 desired_status=advertised
> operational_status=unknown)
> > > +OVS_WAIT_UNTIL([
> > > + ! ovn-sbctl get Advertised_Route_Status $unexpected_status _uuid \
> > > + 2>/dev/null
> > > +])
> > > +
> > > +# Table 252 (RT_TABLE_COMPAT) is rejected by route exchange. Check the
> > > +# reported error, then restore the VRF table.
> > > +check ovn-nbctl --wait=hv set Logical_Router lr-origin \
> > > + options:dynamic-routing-vrf-id=252
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + chassis_name=hv1 desired_status=advertised operational_status=unknown
> > > +OVS_WAIT_UNTIL([
> > > + ovn-sbctl --bare --columns=error find Advertised_Route_Status \
> > > + advertised_route=$lb_ar chassis_name=hv1 |
> > > + grep -q 'invalid route table ID'
> > > +])
> > > +
> > > +check ovn-nbctl --wait=hv set Logical_Router lr-origin \
> > > + options:dynamic-routing-vrf-id=1342
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + chassis_name=hv1 desired_status=advertised
> operational_status=installed
> > > +OVS_WAIT_UNTIL([
> > > + test X"`ovn-sbctl --bare --columns=error find
> Advertised_Route_Status \
> > > + advertised_route=$lb_ar chassis_name=hv1`" = X
> > > +])
> > >
> > > # Stop the listener and withdraw the LB route.
> > > kill `cat $be_pid_file`
> > > wait_row_count Service_Monitor 1 logical_port=be0 status=offline
> > > +wait_row_count sb:Advertised_Route_Status 1 advertised_route=$lb_ar \
> > > + advertised_route_uuid=$lb_ar chassis_name=hv1 \
> > > + desired_status=withdrawn operational_status=withdrawn
> > > # The NAT route remains installed.
> > > AT_CHECK([
> > > ip route list vrf ovnvrf1342 | grep -c "blackhole 172.16.1.10"
> > > @@ -23515,6 +23686,8 @@ exact_ar=$(ovn-sbctl --bare --columns=_uuid
> find Advertised_Route \
> > > test -n "$exact_ar"
> > > AT_CHECK([! ovn-sbctl --bare get Advertised_Route $exact_ar
> external_ids | \
> > > grep -Eq 'source|distributed-lb|health-checks'])
> > > +wait_row_count sb:Advertised_Route_Status 0
> advertised_route=$exact_ar \
> > > + chassis_name=hv1
> > > AT_CHECK([
> > > ip route list vrf ovnvrf1342 | grep -c "blackhole 172.16.1.20"
> > > ], [0], [1
> > > @@ -23630,6 +23803,12 @@ check ovn-sbctl set Port_Binding be0
> chassis=$hv2
> > > OVS_WAIT_UNTIL([
> > > ! ip route list vrf ovnvrf1342 | grep -q "blackhole 172.16.1.10"])
> > >
> > > +# With the backend remote and local-only enabled, hv1 must not
> > > +# publish an Advertised_Route_Status row for the VIP: the route is
> > > +# not selected for this chassis, so no status is recorded.
> > > +wait_row_count sb:Advertised_Route_Status 0 \
> > > + chassis_name=hv1
> > > +
> > > # Return the backend to hv1.
> > > check ovn-sbctl clear Port_Binding be0 chassis
> > > check ovn-sbctl destroy Chassis $hv2
> > > @@ -23642,6 +23821,11 @@ wait_row_count Port_Binding 1
> logical_port=be0 chassis=$hv1_uuid
> > > OVS_WAIT_UNTIL([
> > > ip route list vrf ovnvrf1342 | grep -q "blackhole 172.16.1.10"])
> > >
> > > +# With the backend local again, hv1 publishes an
> Advertised_Route_Status
> > > +# row with desired_status=advertised.
> > > +wait_row_count sb:Advertised_Route_Status 1 \
> > > + chassis_name=hv1
> > > +
> > > # Disable local-only. Even with the backend on a remote chassis the
> > > # route stays because local-only no longer restricts installation.
> > > check ovn-nbctl --wait=hv set Logical_Router_Port lr-origin-share \
> > > diff --git a/utilities/ovn-sbctl.8.xml b/utilities/ovn-sbctl.8.xml
> > > index 18c4a5d36..12bc58dd1 100644
> > > --- a/utilities/ovn-sbctl.8.xml
> > > +++ b/utilities/ovn-sbctl.8.xml
> > > @@ -322,8 +322,9 @@
> > > <dt>[<code>--if-exists</code>] <var>chassis-del
> <var>chassis</var></var></dt>
> > > <dd>
> > > <p>
> > > - Deletes <var>chassis</var> and its <var>encaps</var> and
> > > - <var>gateway_ports</var>.
> > > + Deletes <var>chassis</var>, its <var>encaps</var> and
> > > + <var>gateway_ports</var>, and its
> > > + <code>Advertised_Route_Status</code> records.
> > > </p>
> > >
> > > <p>
> > > diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> > > index c06bc435c..d7fbc765e 100644
> > > --- a/utilities/ovn-sbctl.c
> > > +++ b/utilities/ovn-sbctl.c
> > > @@ -360,6 +360,9 @@ pre_get_info(struct ctl_context *ctx)
> > >
> > > ovsdb_idl_add_column(ctx->idl, &sbrec_chassis_private_col_name);
> > >
> > > + ovsdb_idl_add_column(ctx->idl,
> > > + &sbrec_advertised_route_status_col_chassis_name);
> > > +
> > > ovsdb_idl_add_column(ctx->idl, &sbrec_encap_col_type);
> > > ovsdb_idl_add_column(ctx->idl, &sbrec_encap_col_ip);
> > >
> > > @@ -510,6 +513,20 @@ cmd_chassis_del(struct ctl_context *ctx)
> > > free(sbctl_ch_priv);
> > > }
> > >
> > > + if (sbrec_server_has_advertised_route_status_table(ctx->idl)) {
> > > + const struct sbrec_advertised_route_status_table
> > > + *status_table =
> > > + sbrec_advertised_route_status_table_get(ctx->idl);
> > > + const struct sbrec_advertised_route_status *status;
> > > + SBREC_ADVERTISED_ROUTE_STATUS_TABLE_FOR_EACH_SAFE (
> > > + status, status_table) {
> > > + if (!strcmp(status->chassis_name,
> > > + sbctl_ch->ch_cfg->name)) {
> > > + sbrec_advertised_route_status_delete(status);
> > > + }
> > > + }
> > > + }
> > > +
> > > sbrec_chassis_delete(sbctl_ch->ch_cfg);
> > > }
> > > shash_find_and_delete(&sbctl_ctx->chassis, ctx->argv[1]);
> > > --
> > > 2.53.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