Skip to content

Commit d467e6d

Browse files
authored
[release-1.15] fix(ovn-central): tolerate transient duplicate leaders (#7145)
Signed-off-by: zhangzujian <zhangzujian.7@gmail.com>
1 parent 8c0107b commit d467e6d

2 files changed

Lines changed: 260 additions & 65 deletions

File tree

pkg/ovn_leader_checker/ovn.go

Lines changed: 141 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ import (
3737
)
3838

3939
const (
40-
OvnNorthdServiceName = "ovn-northd"
41-
OvnNorthdPid = "/var/run/ovn/ovn-northd.pid"
42-
DefaultProbeInterval = 5
43-
MaxFailCount = 3
44-
northdDialTimeout = 3 * time.Second
40+
OvnNorthdServiceName = "ovn-northd"
41+
OvnNorthdPid = "/var/run/ovn/ovn-northd.pid"
42+
DefaultProbeInterval = 5
43+
MaxFailCount = 3
44+
maxDuplicateLeaderObservations = 3
45+
northdDialTimeout = 3 * time.Second
4546
)
4647

4748
var failCount int
@@ -50,14 +51,29 @@ var labelSelector = labels.Set{discoveryv1.LabelServiceName: OvnNorthdServiceNam
5051

5152
// Configuration is the controller conf
5253
type Configuration struct {
53-
KubeConfigFile string
54-
KubeClient kubernetes.Interface
55-
ProbeInterval int
56-
EnableCompact bool
57-
IsICDBServer bool
58-
localAddress string
59-
remoteAddresses []string
60-
singleReplica bool
54+
KubeConfigFile string
55+
KubeClient kubernetes.Interface
56+
ProbeInterval int
57+
EnableCompact bool
58+
IsICDBServer bool
59+
localAddress string
60+
remoteAddresses []string
61+
singleReplica bool
62+
duplicateLeaderObservations map[string]int
63+
}
64+
65+
func (c *Configuration) observeDuplicateLeader(database string, duplicate bool) (int, bool) {
66+
if !duplicate {
67+
delete(c.duplicateLeaderObservations, database)
68+
return 0, false
69+
}
70+
if c.duplicateLeaderObservations == nil {
71+
c.duplicateLeaderObservations = make(map[string]int)
72+
}
73+
74+
c.duplicateLeaderObservations[database]++
75+
count := c.duplicateLeaderObservations[database]
76+
return count, count >= maxDuplicateLeaderObservations
6177
}
6278

6379
// ParseFlags parses cmd args then init kubeclient and conf
@@ -198,8 +214,8 @@ func checkOvnIsAlive() bool {
198214
return true
199215
}
200216

201-
// isDBLeader checks whether the ovn db at address is leader for the given database
202-
func isDBLeader(address, database string) bool {
217+
// isDBLeader checks whether the ovn db at address is leader for the given database.
218+
func isDBLeader(address, database string) (bool, error) {
203219
var dbAddr string
204220
switch database {
205221
case ovnnb.DatabaseName:
@@ -211,8 +227,7 @@ func isDBLeader(address, database string) bool {
211227
case util.DatabaseICSB:
212228
dbAddr = ovs.OvsdbServerAddress(address, intstr.FromInt32(util.ICSBDatabasePort))
213229
default:
214-
klog.Errorf("isDBLeader: unsupported database %s", database)
215-
return false
230+
return false, fmt.Errorf("unsupported database %s", database)
216231
}
217232

218233
result, err := ovs.Query(dbAddr, serverdb.DatabaseName, 1, ovsdb.Operation{
@@ -226,29 +241,24 @@ func isDBLeader(address, database string) bool {
226241
Columns: []string{"leader"},
227242
})
228243
if err != nil {
229-
klog.Errorf("failed to query leader info from ovsdb-server %s for database %s: %v", address, database, err)
230-
return false
244+
return false, fmt.Errorf("failed to query leader info from ovsdb-server %s for database %s: %w", address, database, err)
231245
}
232246
if len(result) != 1 {
233-
klog.Errorf("unexpected number of results when querying leader info from ovsdb-server %s for database %s: %d", address, database, len(result))
234-
return false
247+
return false, fmt.Errorf("unexpected number of results when querying leader info from ovsdb-server %s for database %s: %d", address, database, len(result))
235248
}
236249
if len(result[0].Rows) == 0 {
237-
klog.Errorf("no rows returned when querying leader info from ovsdb-server %s for database %s", address, database)
238-
return false
250+
return false, fmt.Errorf("no rows returned when querying leader info from ovsdb-server %s for database %s", address, database)
239251
}
240252
if len(result[0].Rows) != 1 {
241-
klog.Errorf("unexpected number of rows when querying leader info from ovsdb-server %s for database %s: %d", address, database, len(result[0].Rows))
242-
return false
253+
return false, fmt.Errorf("unexpected number of rows when querying leader info from ovsdb-server %s for database %s: %d", address, database, len(result[0].Rows))
243254
}
244255

245256
leader, ok := result[0].Rows[0]["leader"].(bool)
246257
if !ok {
247-
klog.Errorf("unexpected data format for leader info from ovsdb-server %s for database %s: %v", address, database, result[0].Rows[0]["leader"])
248-
return false
258+
return false, fmt.Errorf("unexpected data format for leader info from ovsdb-server %s for database %s: %v", address, database, result[0].Rows[0]["leader"])
249259
}
250260

251-
return leader
261+
return leader, nil
252262
}
253263

254264
func checkNorthdActive() bool {
@@ -466,6 +476,103 @@ func validateRaftHeader(data map[string]any, dbCID string) error {
466476
return nil
467477
}
468478

479+
type dbLeaderQueryFunc func(address, database string) (bool, error)
480+
481+
func checkDuplicateDBLeader(
482+
cfg *Configuration,
483+
localLeader bool,
484+
localQueryErr error,
485+
component, database string,
486+
queryLeader dbLeaderQueryFunc,
487+
) {
488+
if localQueryErr != nil {
489+
klog.Error(localQueryErr)
490+
return
491+
}
492+
if !localLeader {
493+
cfg.observeDuplicateLeader(database, false)
494+
return
495+
}
496+
497+
var remoteLeader string
498+
remoteQueryFailed := false
499+
for addr := range slices.Values(cfg.remoteAddresses) {
500+
leader, err := queryLeader(addr, database)
501+
if err != nil {
502+
klog.Error(err)
503+
remoteQueryFailed = true
504+
continue
505+
}
506+
if leader {
507+
remoteLeader = addr
508+
break
509+
}
510+
}
511+
512+
if remoteLeader == "" {
513+
if !remoteQueryFailed {
514+
cfg.observeDuplicateLeader(database, false)
515+
}
516+
return
517+
}
518+
519+
count, confirmed := cfg.observeDuplicateLeader(database, true)
520+
if !confirmed {
521+
klog.Warningf("found another %s leader at %s (%d/%d consecutive observations), waiting for raft convergence",
522+
component, remoteLeader, count, maxDuplicateLeaderObservations)
523+
return
524+
}
525+
526+
klog.Fatalf("found another %s leader at %s for %d consecutive observations, exiting process to restart",
527+
component, remoteLeader, count)
528+
}
529+
530+
func doICDBLeaderCheck(cfg *Configuration, podName, podNamespace string) {
531+
icNbLeader, icNbLeaderErr := isDBLeader(cfg.localAddress, util.DatabaseICNB)
532+
icSbLeader, icSbLeaderErr := isDBLeader(cfg.localAddress, util.DatabaseICSB)
533+
if icNbLeaderErr != nil {
534+
klog.Error(icNbLeaderErr)
535+
}
536+
if icSbLeaderErr != nil {
537+
klog.Error(icSbLeaderErr)
538+
}
539+
540+
patch := util.KVPatch{
541+
"ovn-ic-nb-leader": strconv.FormatBool(icNbLeader),
542+
"ovn-ic-sb-leader": strconv.FormatBool(icSbLeader),
543+
}
544+
if err := util.PatchLabels(cfg.KubeClient.CoreV1().Pods(podNamespace), podName, patch); err != nil {
545+
klog.Errorf("failed to patch labels for pod %s/%s: %v", podNamespace, podName, err)
546+
return
547+
}
548+
549+
if icNbLeader {
550+
if err := updateTS(); err != nil {
551+
klog.Errorf("update ts num failed err: %v", err)
552+
return
553+
}
554+
}
555+
556+
for addr := range slices.Values(cfg.remoteAddresses) {
557+
if icNbLeader {
558+
remoteLeader, err := isDBLeader(addr, util.DatabaseICNB)
559+
if err != nil {
560+
klog.Error(err)
561+
} else if remoteLeader {
562+
klog.Fatalf("found another ovn-ic-nb leader at %s, exiting process to restart", addr)
563+
}
564+
}
565+
if icSbLeader {
566+
remoteLeader, err := isDBLeader(addr, util.DatabaseICSB)
567+
if err != nil {
568+
klog.Error(err)
569+
} else if remoteLeader {
570+
klog.Fatalf("found another ovn-ic-sb leader at %s, exiting process to restart", addr)
571+
}
572+
}
573+
}
574+
}
575+
469576
func doOvnLeaderCheck(cfg *Configuration, podName, podNamespace string) {
470577
if podName == "" || podNamespace == "" {
471578
util.LogFatalAndExit(nil, "env variables POD_NAME and POD_NAMESPACE must be set")
@@ -510,8 +617,11 @@ func doOvnLeaderCheck(cfg *Configuration, podName, podNamespace string) {
510617
return
511618
}
512619

513-
nbLeader := isDBLeader(cfg.localAddress, ovnnb.DatabaseName)
514-
sbLeader := isDBLeader(cfg.localAddress, ovnsb.DatabaseName)
620+
nbLeader, nbLeaderErr := isDBLeader(cfg.localAddress, ovnnb.DatabaseName)
621+
sbLeader, sbLeaderErr := isDBLeader(cfg.localAddress, ovnsb.DatabaseName)
622+
checkDuplicateDBLeader(cfg, nbLeader, nbLeaderErr, "ovn-nb", ovnnb.DatabaseName, isDBLeader)
623+
checkDuplicateDBLeader(cfg, sbLeader, sbLeaderErr, "ovn-sb", ovnsb.DatabaseName, isDBLeader)
624+
515625
northdActive := checkNorthdActive()
516626
patch := util.KVPatch{
517627
"ovn-nb-leader": strconv.FormatBool(nbLeader),
@@ -529,15 +639,6 @@ func doOvnLeaderCheck(cfg *Configuration, podName, podNamespace string) {
529639
}
530640
}
531641

532-
for addr := range slices.Values(cfg.remoteAddresses) {
533-
if nbLeader && isDBLeader(addr, ovnnb.DatabaseName) {
534-
klog.Fatalf("found another ovn-nb leader at %s, exiting process to restart", addr)
535-
}
536-
if sbLeader && isDBLeader(addr, ovnsb.DatabaseName) {
537-
klog.Fatalf("found another ovn-sb leader at %s, exiting process to restart", addr)
538-
}
539-
}
540-
541642
if cfg.EnableCompact {
542643
compactOvnDatabase("nb")
543644
compactOvnDatabase("sb")
@@ -546,32 +647,7 @@ func doOvnLeaderCheck(cfg *Configuration, podName, podNamespace string) {
546647
backupRaftHeader("nb")
547648
backupRaftHeader("sb")
548649
} else {
549-
icNbLeader := isDBLeader(cfg.localAddress, util.DatabaseICNB)
550-
icSbLeader := isDBLeader(cfg.localAddress, util.DatabaseICSB)
551-
patch := util.KVPatch{
552-
"ovn-ic-nb-leader": strconv.FormatBool(icNbLeader),
553-
"ovn-ic-sb-leader": strconv.FormatBool(icSbLeader),
554-
}
555-
if err := util.PatchLabels(cfg.KubeClient.CoreV1().Pods(podNamespace), podName, patch); err != nil {
556-
klog.Errorf("failed to patch labels for pod %s/%s: %v", podNamespace, podName, err)
557-
return
558-
}
559-
560-
if icNbLeader {
561-
if err := updateTS(); err != nil {
562-
klog.Errorf("update ts num failed err: %v", err)
563-
return
564-
}
565-
}
566-
567-
for addr := range slices.Values(cfg.remoteAddresses) {
568-
if icNbLeader && isDBLeader(addr, util.DatabaseICNB) {
569-
klog.Fatalf("found another ovn-ic-nb leader at %s, exiting process to restart", addr)
570-
}
571-
if icSbLeader && isDBLeader(addr, util.DatabaseICSB) {
572-
klog.Fatalf("found another ovn-ic-sb leader at %s, exiting process to restart", addr)
573-
}
574-
}
650+
doICDBLeaderCheck(cfg, podName, podNamespace)
575651
}
576652
}
577653

0 commit comments

Comments
 (0)