The supported upgrade procedure is to always upgrade ovn-controllers first and OVN DBs and ovn-northd later. This leads however to the situation when ovn-controller might try to set the Port_Binding.up field while the Southbound DB isn't yet aware of this field which would trigger transaction failures and control plane/data plane outages.
To avoid such situations ovn-controller only sets the Port_Binding.up field if it was explicitly set to 'false'. This ensures that the SB database was already upgraded. On the ovn-northd side, as soon as ovn-northd is upgraded it will update all existing Port_Bindings and explicitly set 'Port_Binding.up' to false, implicitly notifying ovn-controller that it is safe to write to the field. Reported-by: Numan Siddique <num...@ovn.org> Fixes: 4d3cb42b076b ("binding: Set Logical_Switch_Port.up when all OVS flows are installed.") Signed-off-by: Dumitru Ceara <dce...@redhat.com> --- controller-vtep/binding.c | 6 ++++-- controller/binding.c | 33 ++++++++++++++++++++++--------- northd/ovn-northd.c | 8 ++++++++ tests/ovn.at | 48 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/controller-vtep/binding.c b/controller-vtep/binding.c index d28a598..01d5a16 100644 --- a/controller-vtep/binding.c +++ b/controller-vtep/binding.c @@ -110,9 +110,11 @@ update_pb_chassis(const struct sbrec_port_binding *port_binding_rec, chassis_rec->name); } - bool up = true; sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec); - sbrec_port_binding_set_up(port_binding_rec, &up, 1); + if (port_binding_rec->n_up) { + bool up = true; + sbrec_port_binding_set_up(port_binding_rec, &up, 1); + } } } diff --git a/controller/binding.c b/controller/binding.c index 353debe..efaa109 100644 --- a/controller/binding.c +++ b/controller/binding.c @@ -870,6 +870,11 @@ get_lport_type(const struct sbrec_port_binding *pb) * container and virtual ports). * Otherwise request a notification to be sent when the OVS flows * corresponding to 'pb' have been installed. + * + * Note: + * Updates (directly or through a notification) the 'pb->up' field only if + * it's explicitly set to 'false'. + * This is to ensure compatibility with older versions of ovn-northd. */ static void claimed_lport_set_up(const struct sbrec_port_binding *pb, @@ -885,7 +890,7 @@ claimed_lport_set_up(const struct sbrec_port_binding *pb, return; } - if (pb->chassis != chassis_rec) { + if (pb->chassis != chassis_rec || (pb->n_up && !pb->up[0])) { binding_iface_bound_add(pb->logical_port); } } @@ -973,7 +978,10 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, sbrec_port_binding_set_virtual_parent(pb, NULL); } - sbrec_port_binding_set_up(pb, NULL, 0); + if (pb->n_up) { + bool up = false; + sbrec_port_binding_set_up(pb, &up, 1); + } update_lport_tracking(pb, tracked_datapaths); binding_iface_released_add(pb->logical_port); VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port); @@ -2503,7 +2511,10 @@ binding_seqno_run(struct shash *local_bindings) ovsrec_interface_update_external_ids_delkey( lb->iface, OVN_INSTALLED_EXT_ID); } - sbrec_port_binding_set_up(lb->pb, NULL, 0); + if (lb->pb->n_up) { + bool up = false; + sbrec_port_binding_set_up(lb->pb, &up, 1); + } simap_put(&binding_iface_seqno_map, lb->name, new_seqno); } sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id)); @@ -2536,7 +2547,6 @@ binding_seqno_install(struct shash *local_bindings) SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { struct shash_node *lb_node = shash_find(local_bindings, node->name); - bool up = true; if (!lb_node) { goto del_seqno; @@ -2554,12 +2564,15 @@ binding_seqno_install(struct shash *local_bindings) ovsrec_interface_update_external_ids_setkey(lb->iface, OVN_INSTALLED_EXT_ID, "true"); - sbrec_port_binding_set_up(lb->pb, &up, 1); - - struct shash_node *child_node; - SHASH_FOR_EACH (child_node, &lb->children) { - struct local_binding *lb_child = child_node->data; - sbrec_port_binding_set_up(lb_child->pb, &up, 1); + if (lb->pb->n_up) { + bool up = true; + + sbrec_port_binding_set_up(lb->pb, &up, 1); + struct shash_node *child_node; + SHASH_FOR_EACH (child_node, &lb->children) { + struct local_binding *lb_child = child_node->data; + sbrec_port_binding_set_up(lb_child->pb, &up, 1); + } } del_seqno: diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 4de79d9..ad1ade7 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -3099,6 +3099,14 @@ ovn_port_update_sbrec(struct northd_context *ctx, if (op->tunnel_key != op->sb->tunnel_key) { sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); } + + /* ovn-controller will update 'Port_Binding.up' only if it was explicitly + * set to 'false'. + */ + if (!op->sb->n_up) { + bool up = false; + sbrec_port_binding_set_up(op->sb, &up, 1); + } } /* Remove mac_binding entries that refer to logical_ports which are diff --git a/tests/ovn.at b/tests/ovn.at index c5baa94..53962db 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -23830,7 +23830,7 @@ check ovn-nbctl ls-add ls AS_BOX([add OVS port for existing LSP]) check ovn-nbctl lsp-add ls lsp1 check ovn-nbctl --wait=hv sync -check_column "[]" Port_Binding up logical_port=lsp1 +check_column "false" Port_Binding up logical_port=lsp1 check ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1 check_column "true" Port_Binding up logical_port=lsp1 @@ -23845,5 +23845,51 @@ check_column "true" Port_Binding up logical_port=lsp2 wait_column "true" nb:Logical_Switch_Port up name=lsp2 OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp2 external_ids:ovn-installed` = '"true"']) +AS_BOX([ovn-controller should not reset Port_Binding.up without northd]) +# Pause northd and clear the "up" field to simulate older ovn-northd +# versions writing to the Southbound DB. +as northd ovn-appctl -t ovn-northd pause +as northd-backup ovn-appctl -t ovn-northd pause + +as hv1 ovn-appctl -t ovn-controller debug/pause +check ovn-sbctl clear Port_Binding lsp1 up +as hv1 ovn-appctl -t ovn-controller debug/resume + +# Forcefully release the Port_Binding so ovn-controller reclaims it. +# Make sure the Port_Binding.up field is not updated though. +check ovn-sbctl clear Port_Binding lsp1 chassis +hv1_uuid=$(fetch_column Chassis _uuid name=hv1) +wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp1 +check_column "" Port_Binding up logical_port=lsp1 + +# Once northd should explicitly set the Port_Binding.up field to 'false' and +# ovn-controller sets it to 'true' as soon as the update is processed. +as northd ovn-appctl -t ovn-northd resume +as northd-backup ovn-appctl -t ovn-northd resume +wait_column "true" Port_Binding up logical_port=lsp1 +wait_column "true" nb:Logical_Switch_Port up name=lsp1 + +AS_BOX([ovn-controller should reset Port_Binding.up - from NULL]) +# If Port_Binding.up is cleared externally, ovn-northd resets it to 'false' +# and ovn-controller finally sets it to 'true' once the update is processed. +as hv1 ovn-appctl -t ovn-controller debug/pause +check ovn-sbctl clear Port_Binding lsp1 up +check ovn-nbctl --wait=sb sync +wait_column "false" nb:Logical_Switch_Port up name=lsp1 +as hv1 ovn-appctl -t ovn-controller debug/resume +wait_column "true" Port_Binding up logical_port=lsp1 +wait_column "true" nb:Logical_Switch_Port up name=lsp1 + +AS_BOX([ovn-controller should reset Port_Binding.up - from false]) +# If Port_Binding.up is externally set to 'false', ovn-controller should sets +# it to 'true' once the update is processed. +as hv1 ovn-appctl -t ovn-controller debug/pause +check ovn-sbctl set Port_Binding lsp1 up=false +check ovn-nbctl --wait=sb sync +wait_column "false" nb:Logical_Switch_Port up name=lsp1 +as hv1 ovn-appctl -t ovn-controller debug/resume +wait_column "true" Port_Binding up logical_port=lsp1 +wait_column "true" nb:Logical_Switch_Port up name=lsp1 + OVN_CLEANUP([hv1]) AT_CLEANUP _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev