-
Notifications
You must be signed in to change notification settings - Fork 121
Split ping probe into ping4 and ping6 for dual-stack support #1498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -57,17 +57,24 @@ type Route struct { | |||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const ( | ||||||||||||||||||
| defaultGwRetrieveTimeout = 120 * time.Second | ||||||||||||||||||
| defaultGwProbeTimeout = 120 * time.Second | ||||||||||||||||||
| defaultDNSProbeTimeout = 120 * time.Second | ||||||||||||||||||
| apiServerProbeTimeout = 120 * time.Second | ||||||||||||||||||
| nodeReadinessProbeTimeout = 120 * time.Second | ||||||||||||||||||
| mainRoutingTableID = 254 | ||||||||||||||||||
| ProbesTotalTimeout = defaultGwRetrieveTimeout + | ||||||||||||||||||
| defaultDNSProbeTimeout + | ||||||||||||||||||
| defaultDNSProbeTimeout + | ||||||||||||||||||
| apiServerProbeTimeout + | ||||||||||||||||||
| nodeReadinessProbeTimeout | ||||||||||||||||||
|
|
||||||||||||||||||
| // ProbesTotalTimeout is the worst-case total time for Select() + Run() | ||||||||||||||||||
| // on a dual-stack cluster where both ping4 and ping6 are active. | ||||||||||||||||||
| // Select: ping4 + ping6 + dns | ||||||||||||||||||
| // Run: ping4 + ping6 + dns + api-server + node-readiness | ||||||||||||||||||
| ProbesTotalTimeout = defaultGwProbeTimeout + // Select: ping4 | ||||||||||||||||||
| defaultGwProbeTimeout + // Select: ping6 | ||||||||||||||||||
| defaultDNSProbeTimeout + // Select: dns | ||||||||||||||||||
| defaultGwProbeTimeout + // Run: ping4 | ||||||||||||||||||
| defaultGwProbeTimeout + // Run: ping6 | ||||||||||||||||||
| defaultDNSProbeTimeout + // Run: dns | ||||||||||||||||||
| apiServerProbeTimeout + // Run: api-server | ||||||||||||||||||
| nodeReadinessProbeTimeout // Run: node-readiness | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| func currentStateAsGJson() (gjson.Result, error) { | ||||||||||||||||||
|
|
@@ -140,12 +147,20 @@ func checkNodeReadiness(ctx context.Context, cli client.Client) (bool, error) { | |||||||||||||||||
| return false, nil | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func defaultGw(currentState gjson.Result) (Route, error) { | ||||||||||||||||||
| func defaultGw4(currentState gjson.Result) (Route, error) { | ||||||||||||||||||
| return defaultGwByDestination(currentState, "0.0.0.0/0") | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func defaultGw6(currentState gjson.Result) (Route, error) { | ||||||||||||||||||
| return defaultGwByDestination(currentState, "::/0") | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func defaultGwByDestination(currentState gjson.Result, destination string) (Route, error) { | ||||||||||||||||||
| var found Route | ||||||||||||||||||
| currentState.Get("routes.running").ForEach( | ||||||||||||||||||
| func(_, v gjson.Result) bool { | ||||||||||||||||||
| // we want to pick the next hop related to the "main" table because we may have multiple tables | ||||||||||||||||||
| if (v.Get("destination").String() == "0.0.0.0/0" || v.Get("destination").String() == "::/0") && | ||||||||||||||||||
| if v.Get("destination").String() == destination && | ||||||||||||||||||
| v.Get("table-id").Int() == mainRoutingTableID { | ||||||||||||||||||
| found.nextHop = net.ParseIP(v.Get("next-hop-address").String()) | ||||||||||||||||||
| found.iface = v.Get("next-hop-interface").String() | ||||||||||||||||||
|
|
@@ -156,8 +171,8 @@ func defaultGw(currentState gjson.Result) (Route, error) { | |||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| if found.nextHop == nil { | ||||||||||||||||||
| msg := "default gw missing" | ||||||||||||||||||
| defaultGwLog := log.WithValues("path", "routes.running.next-hop-address", "table-id", mainRoutingTableID) | ||||||||||||||||||
| msg := fmt.Sprintf("default gw missing for destination %s", destination) | ||||||||||||||||||
| defaultGwLog := log.WithValues("path", "routes.running.next-hop-address", "table-id", mainRoutingTableID, "destination", destination) | ||||||||||||||||||
| defaultGwLogDebug := defaultGwLog.V(1) | ||||||||||||||||||
| if defaultGwLogDebug.Enabled() { | ||||||||||||||||||
| defaultGwLogDebug.Info(msg, "state", currentState.String()) | ||||||||||||||||||
|
|
@@ -169,25 +184,31 @@ func defaultGw(currentState gjson.Result) (Route, error) { | |||||||||||||||||
| return found, nil | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func pingCondition(cli client.Client, timeout time.Duration) wait.ConditionWithContextFunc { | ||||||||||||||||||
| func ping4Condition(cli client.Client, timeout time.Duration) wait.ConditionWithContextFunc { | ||||||||||||||||||
| return func(context.Context) (bool, error) { | ||||||||||||||||||
| return runPingByFamily(cli, defaultGw4) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func ping6Condition(cli client.Client, timeout time.Duration) wait.ConditionWithContextFunc { | ||||||||||||||||||
| return func(context.Context) (bool, error) { | ||||||||||||||||||
| return runPing(cli) | ||||||||||||||||||
| return runPingByFamily(cli, defaultGw6) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func runPing(_ client.Client) (bool, error) { | ||||||||||||||||||
| func runPingByFamily(_ client.Client, gwFunc func(gjson.Result) (Route, error)) (bool, error) { | ||||||||||||||||||
| gjsonCurrentState, err := currentStateAsGJson() | ||||||||||||||||||
| if err != nil { | ||||||||||||||||||
| return false, errors.Wrap(err, "failed retrieving current state to retrieve default gw") | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| defaultGw, err := defaultGw(gjsonCurrentState) | ||||||||||||||||||
| gw, err := gwFunc(gjsonCurrentState) | ||||||||||||||||||
| if err != nil { | ||||||||||||||||||
| log.Error(err, "failed to retrieve default gw") | ||||||||||||||||||
| return false, nil | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
206
to
209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logging an error here is redundant because
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| pingOutput, err := ping(defaultGw) | ||||||||||||||||||
| pingOutput, err := ping(gw) | ||||||||||||||||||
| if err != nil { | ||||||||||||||||||
| log.Error(err, fmt.Sprintf("error pinging default gateway -> output: '%s'", pingOutput)) | ||||||||||||||||||
| return false, nil | ||||||||||||||||||
|
|
@@ -270,18 +291,59 @@ func runDNS(_ client.Client, timeout time.Duration) (bool, error) { | |||||||||||||||||
| // the internal connectivity probes | ||||||||||||||||||
| func Select(ctx context.Context, cli client.Client) []Probe { | ||||||||||||||||||
| probes := []Probe{} | ||||||||||||||||||
| externalConnectivityProbes := []Probe{ | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "ping", | ||||||||||||||||||
| externalConnectivityProbes := []Probe{} | ||||||||||||||||||
|
|
||||||||||||||||||
| // Pre-check which address families have a default gateway to avoid | ||||||||||||||||||
| // waiting for the full probe timeout on single-stack clusters. | ||||||||||||||||||
| // Default to including both families (conservative). Only skip a | ||||||||||||||||||
| // family when the other family already has a gateway, proving the | ||||||||||||||||||
| // network is initialized and the missing family genuinely doesn't | ||||||||||||||||||
| // exist. When neither family has a gateway the network may still | ||||||||||||||||||
| // be initializing (e.g. DHCP pending), so we include both and let | ||||||||||||||||||
| // the trial retry until the gateway appears. | ||||||||||||||||||
| hasGw4, hasGw6 := true, true | ||||||||||||||||||
| currentState, err := currentStateAsGJson() | ||||||||||||||||||
| if err != nil { | ||||||||||||||||||
| log.Info(fmt.Sprintf("WARNING: failed to retrieve current state for gateway pre-check, including all ping probes: %v", err)) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| _, err4 := defaultGw4(currentState) | ||||||||||||||||||
| _, err6 := defaultGw6(currentState) | ||||||||||||||||||
| if err4 == nil || err6 == nil { | ||||||||||||||||||
| // At least one family has a gateway — network is initialized. | ||||||||||||||||||
| // Only include families that actually have a gateway. | ||||||||||||||||||
| hasGw4 = err4 == nil | ||||||||||||||||||
| hasGw6 = err6 == nil | ||||||||||||||||||
| if !hasGw4 { | ||||||||||||||||||
| log.Info("No IPv4 default gateway found, skipping ping4 probe selection") | ||||||||||||||||||
| } | ||||||||||||||||||
| if !hasGw6 { | ||||||||||||||||||
| log.Info("No IPv6 default gateway found, skipping ping6 probe selection") | ||||||||||||||||||
| } | ||||||||||||||||||
| } else { | ||||||||||||||||||
| log.Info("No default gateway found for any address family, including all ping probes for retry") | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if hasGw4 { | ||||||||||||||||||
| externalConnectivityProbes = append(externalConnectivityProbes, Probe{ | ||||||||||||||||||
| name: "ping4", | ||||||||||||||||||
| timeout: defaultGwProbeTimeout, | ||||||||||||||||||
| condition: pingCondition, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "dns", | ||||||||||||||||||
| timeout: defaultDNSProbeTimeout, | ||||||||||||||||||
| condition: dnsCondition, | ||||||||||||||||||
| }, | ||||||||||||||||||
| condition: ping4Condition, | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
| if hasGw6 { | ||||||||||||||||||
| externalConnectivityProbes = append(externalConnectivityProbes, Probe{ | ||||||||||||||||||
| name: "ping6", | ||||||||||||||||||
| timeout: defaultGwProbeTimeout, | ||||||||||||||||||
| condition: ping6Condition, | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| externalConnectivityProbes = append(externalConnectivityProbes, Probe{ | ||||||||||||||||||
| name: "dns", | ||||||||||||||||||
| timeout: defaultDNSProbeTimeout, | ||||||||||||||||||
| condition: dnsCondition, | ||||||||||||||||||
| }) | ||||||||||||||||||
|
|
||||||||||||||||||
| for _, p := range externalConnectivityProbes { | ||||||||||||||||||
| err := wait.PollUntilContextTimeout(ctx, time.Second, p.timeout, true /*immediate*/, p.condition(cli, p.timeout)) | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
ProbesTotalTimeoutis over-calculated. A probe is only included in theRun()phase if it successfully passes theSelect()phase. If a probe fails inSelect(), it consumes its timeout there but is not executed inRun(). If it succeeds inSelect(), it consumes negligible time there and may consume its full timeout inRun(). Thus, each probe (ping4, ping6, dns, api-server, node-readiness) contributes at most once to the total worst-case timeout. The total should be the sum of the timeouts for these 5 distinct probes. This aligns with the preference for simpler implementations that cover known requirements without unnecessary complexity.References