package patchmanager import ( "context" "fmt" "strings" "time" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/sds/patching/common" ) const ( k8sAPICallRetrySleep = 5 * time.Second k8sAPICallRetryTimeout = 5 * time.Minute upgradeRetrySleep = 2 * time.Minute //No time out for waiting for upgrade ) // Wait for control-plane to be upgraded. If node is control plane, reboot immediately. func (p *PatchManager) WaitForRebootAllowed() error { if p.Ienpatch.Spec.DisablePatchOrdering { p.Log.Info("disablePatchOrdering is True. Rebooting immediately") return nil } controlPlane, err := p.getControlPlaneHostname() if err != nil { return fmt.Errorf("Could not find control plane: %w", err) } if p.HostName == controlPlane { p.Log.Info("This node is control plane. Scheduling reboot") return nil } p.Log.Info("This node is not the control plane. Waiting for it to be patched.") if err = p.waitForControlPlaneUpgraded(controlPlane); err != nil { return fmt.Errorf("Timeout waiting for control plane to upgrade: %w", err) } p.Log.Info("Control-Plane has been upgraded. Scheduling reboot") return nil } // Wait for ienpatches CR for cp to change to ready func (p *PatchManager) waitForControlPlaneUpgraded(controlplane string) error { var lastError error err := wait.PollUntilContextCancel(p.Ctx, upgradeRetrySleep, true, func(ctx context.Context) (done bool, err error) { lastError = p.K8sClient.Get(ctx, client.ObjectKey{Name: p.Ienpatch.Name, Namespace: p.Ienpatch.Namespace}, p.Ienpatch) if lastError != nil { p.Log.Error(lastError, "Waiting for control-plane upgrade, failed to contact api server. Retrying") return false, nil } msg := conditions.GetMessage(p.Ienpatch, controlplane) if conditions.IsTrue(p.Ienpatch, controlplane) && strings.HasSuffix(msg, p.TargetVer) && strings.HasPrefix(msg, "Success") { return true, nil } p.Log.Info("Waiting for control-plane to upgrade to " + p.TargetVer) return false, nil }) if lastError != nil { return lastError } return err } // Get hostname of control-plane // cluster must have only one control plane func (p *PatchManager) getControlPlaneHostname() (string, error) { nodes := corev1.NodeList{} var lastError error err := wait.PollUntilContextTimeout(p.Ctx, k8sAPICallRetrySleep, k8sAPICallRetryTimeout, true, func(ctx context.Context) (done bool, err error) { lastError = p.K8sClient.List(ctx, &nodes, client.MatchingLabels{common.ControlPlaneLabel: ""}) if lastError != nil { p.Log.Error(lastError, "Attempting to find control-plane, failed to contact api server. Retrying") return false, nil } return true, nil }) if lastError != nil { return "", lastError } if err != nil { return "", err } if len(nodes.Items) == 1 { return nodes.Items[0].Name, nil } return "", fmt.Errorf("Could not find control plane") }