penguin/monok8s

k8s image for Mono Gateway Dev Kit

commit d662162921509f5bea4786f13094fd3a49231fbfc85249d11cc4f2c1cea198c3

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-04-05T21:18:06Z
subjectFixed some race conditions
commit d662162921509f5bea4786f13094fd3a49231fbfc85249d11cc4f2c1cea198c3
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-04-05T21:18:06Z

    Fixed some race conditions
---
 clitools/pkg/controller/osimage/progress.go   |  23 +++-
 clitools/pkg/controller/osupgrade/handler.go  | 127 +++++++++++++-----
 clitools/pkg/controller/osupgrade/progress.go | 181 +++++++++++++++++++-------
 initramfs/rootfs-extra/init                   |  19 ++-
 4 files changed, 261 insertions(+), 89 deletions(-)

diff --git a/clitools/pkg/controller/osimage/progress.go b/clitools/pkg/controller/osimage/progress.go
index e393f20..461ed8b 100644
--- a/clitools/pkg/controller/osimage/progress.go
+++ b/clitools/pkg/controller/osimage/progress.go
@@ -2,6 +2,7 @@ package osimage
 
 import (
 	"k8s.io/klog/v2"
+	"sync"
 	"time"
 )
 
@@ -92,8 +93,10 @@ func (l *ProgressLogger) Log(p Progress) {
 }
 
 type TimeBasedUpdater struct {
+	mu       sync.Mutex
 	interval time.Duration
 	lastRun  time.Time
+	inFlight bool
 }
 
 func NewTimeBasedUpdater(seconds int) *TimeBasedUpdater {
@@ -106,16 +109,28 @@ func NewTimeBasedUpdater(seconds int) *TimeBasedUpdater {
 }
 
 func (u *TimeBasedUpdater) Run(fn func() error) error {
+	u.mu.Lock()
 	now := time.Now()
 
-	if !u.lastRun.IsZero() && now.Sub(u.lastRun) < u.interval {
+	if u.inFlight {
+		u.mu.Unlock()
 		return nil
 	}
 
-	if err := fn(); err != nil {
-		return err
+	if !u.lastRun.IsZero() && now.Sub(u.lastRun) < u.interval {
+		u.mu.Unlock()
+		return nil
 	}
 
 	u.lastRun = now
-	return nil
+	u.inFlight = true
+	u.mu.Unlock()
+
+	defer func() {
+		u.mu.Lock()
+		u.inFlight = false
+		u.mu.Unlock()
+	}()
+
+	return fn()
 }
diff --git a/clitools/pkg/controller/osupgrade/handler.go b/clitools/pkg/controller/osupgrade/handler.go
index 8cd16f3..76fec0a 100644
--- a/clitools/pkg/controller/osupgrade/handler.go
+++ b/clitools/pkg/controller/osupgrade/handler.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"sync/atomic"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/klog/v2"
@@ -16,9 +17,42 @@ import (
 	"example.com/monok8s/pkg/node/uboot"
 )
 
+type UpgradeRunner struct {
+	running   atomic.Bool
+	rebooting atomic.Bool
+}
+
+var r UpgradeRunner
+
+func (r *UpgradeRunner) Run(fn func() error) error {
+	if r.rebooting.Load() {
+		return nil
+	}
+
+	if !r.running.CompareAndSwap(false, true) {
+		return nil
+	}
+	defer r.running.Store(false)
+
+	if r.rebooting.Load() {
+		return nil
+	}
+
+	return fn()
+}
+
 func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
 	namespace string, nodeName string,
 	osu *monov1alpha1.OSUpgrade,
+) error {
+	return r.Run(func() error {
+		return handleOSUpgradeLocked(ctx, clients, namespace, nodeName, osu)
+	})
+}
+
+func handleOSUpgradeLocked(ctx context.Context, clients *kube.Clients,
+	namespace string, nodeName string,
+	osu *monov1alpha1.OSUpgrade,
 ) error {
 	osup, err := ensureProgressHeartbeat(ctx, clients, namespace, nodeName, osu)
 	if err != nil {
@@ -57,14 +91,16 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
 
 	first := plan.Path[0]
 
-	osup.Status.TargetVersion = plan.ResolvedTarget
-	osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseDownloading
-	osup.Status.Message = fmt.Sprintf("downloading image: %s", first.URL)
-
-	now := metav1.Now()
-	osup.Status.LastUpdatedAt = &now
-	osup, err = updateProgressStatus(ctx, clients, osup_gvr, osup)
-
+	updated, err := updateProgressRobust(ctx, clients, osup.Namespace, osup.Name, func(cur *monov1alpha1.OSUpgradeProgress) {
+		now := metav1.Now()
+		cur.Status.TargetVersion = plan.ResolvedTarget
+		cur.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseDownloading
+		cur.Status.Message = fmt.Sprintf("downloading image: %s", first.URL)
+		cur.Status.LastUpdatedAt = &now
+	})
+	if updated != nil {
+		osup = updated
+	}
 	if err != nil {
 		return fmt.Errorf("update progress status: %w", err)
 	}
@@ -82,7 +118,6 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
 
 	imageSHA, err := first.SHA256()
 	if err != nil {
-		now = metav1.Now()
 		return failProgress(ctx, clients, osup, "apply image", err)
 	}
 
@@ -91,31 +126,38 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
 
 	imageOptions := osimage.ApplyOptions{
 		URL:               first.URL,
-		TargetPath:        "/dev/sda?",
+		TargetPath:        "/dev/mksaltpart",
 		ExpectedRawSHA256: imageSHA,
 		ExpectedRawSize:   first.Size,
 		BufferSize:        6 * 1024 * 1024,
 		Progress: func(p osimage.Progress) {
 			pLogger.Log(p)
-			if err := statusUpdater.Run(func() error {
 
-				now := metav1.Now()
-				switch p.Stage {
-				case "flash":
-					osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseWriting
-				case "verify":
-					osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseVerifying
+			if err := statusUpdater.Run(func() error {
+				updated, err := updateProgressRobust(ctx, clients, osup.Namespace, osup.Name, func(cur *monov1alpha1.OSUpgradeProgress) {
+					now := metav1.Now()
+
+					switch p.Stage {
+					case "flash":
+						cur.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseWriting
+					case "verify":
+						cur.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseVerifying
+					}
+
+					cur.Status.TargetVersion = plan.ResolvedTarget
+					cur.Status.LastUpdatedAt = &now
+					cur.Status.Message = fmt.Sprintf(
+						"%s: %d%%",
+						p.Stage,
+						osimage.PercentOf(p.BytesComplete, p.BytesTotal),
+					)
+				})
+				if updated != nil {
+					osup = updated
 				}
-				osup.Status.LastUpdatedAt = &now
-				osup.Status.Message = fmt.Sprintf("%s: %d%%", p.Stage, osimage.PercentOf(p.BytesComplete, p.BytesTotal))
-
-				updated, err := updateProgressStatus(ctx, clients, osup_gvr, osup)
 				if err != nil {
-					klog.ErrorS(err, "update progress status")
-					return err
+					return fmt.Errorf("update progress status: %w", err)
 				}
-
-				osup = updated
 				return nil
 			}); err != nil {
 				klog.ErrorS(err, "throttled progress update failed")
@@ -125,7 +167,6 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
 
 	result, err := osimage.ApplyImageStreamed(ctx, imageOptions)
 	if err != nil {
-		now = metav1.Now()
 		return failProgress(ctx, clients, osup, "apply image", err)
 	}
 
@@ -136,18 +177,36 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
 		return failProgress(ctx, clients, osup, "set boot env", err)
 	}
 
-	now = metav1.Now()
-	osup.Status.LastUpdatedAt = &now
-	osup.Status.Message = "image applied, verified, and next boot environment updated"
-	osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseRebooting
-
-	osup, err = updateProgressStatus(ctx, clients, osup_gvr, osup)
+	updated, err = updateProgressRobust(ctx, clients, osup.Namespace, osup.Name, func(cur *monov1alpha1.OSUpgradeProgress) {
+		now := metav1.Now()
+		cur.Status.TargetVersion = plan.ResolvedTarget
+		cur.Status.Message = "image applied, verified, and next boot environment updated"
+		cur.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseRebooting
+		cur.Status.LastUpdatedAt = &now
+	})
+	if updated != nil {
+		osup = updated
+	}
 	if err != nil {
 		return fmt.Errorf("update progress status: %w", err)
 	}
 
 	// TODO: Drain the node here
-	// TODO: Issue Reboot
+	// Get all running pods outta here!
+	// kubectl.Run()
+	// Wait for the node to be drained
+	// kubectl.Wait()
+
+	r.rebooting.Store(true)
+	if err := triggerReboot(); err != nil {
+		r.rebooting.Store(false)
+		return fmt.Errorf("trigger reboot: %w", err)
+	}
+	select {}
+}
 
-	return nil
+func triggerReboot() error {
+	_ = os.WriteFile("/proc/sysrq-trigger", []byte("s\n"), 0)
+	_ = os.WriteFile("/proc/sysrq-trigger", []byte("u\n"), 0)
+	return os.WriteFile("/proc/sysrq-trigger", []byte("b\n"), 0)
 }
diff --git a/clitools/pkg/controller/osupgrade/progress.go b/clitools/pkg/controller/osupgrade/progress.go
index a7c501d..71a79aa 100644
--- a/clitools/pkg/controller/osupgrade/progress.go
+++ b/clitools/pkg/controller/osupgrade/progress.go
@@ -3,12 +3,14 @@ package osupgrade
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/util/retry"
 	"k8s.io/klog/v2"
 
 	monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
@@ -30,13 +32,11 @@ func ensureProgressHeartbeat(ctx context.Context, clients *kube.Clients,
 	namespace string, nodeName string,
 	osu *monov1alpha1.OSUpgrade,
 ) (*monov1alpha1.OSUpgradeProgress, error) {
-
 	name := fmt.Sprintf("%s-%s", osu.Name, nodeName)
 	now := metav1.Now()
 
 	currentVersion := buildinfo.KubeVersion
 	targetVersion := ""
-
 	if osu.Status != nil {
 		targetVersion = osu.Status.ResolvedVersion
 	}
@@ -74,39 +74,129 @@ func ensureProgressHeartbeat(ctx context.Context, clients *kube.Clients,
 		return nil, fmt.Errorf("create OSUpgradeProgress %s/%s: %w", namespace, name, err)
 	}
 
-	existing, err := getProgress(ctx, clients, osup_gvr, namespace, name)
+	var out *monov1alpha1.OSUpgradeProgress
+	err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
+		existing, err := getProgress(ctx, clients, osup_gvr, namespace, name)
+		if err != nil {
+			return fmt.Errorf("get existing OSUpgradeProgress %s/%s: %w", namespace, name, err)
+		}
+
+		// Keep spec aligned with source and node.
+		existing.Spec.NodeName = nodeName
+		existing.Spec.SourceRef.Name = osu.Name
+
+		existing, err = updateProgressSpec(ctx, clients, osup_gvr, existing)
+		if err != nil {
+			if isUnknownUpdateResult(err) {
+				latest, getErr := getProgress(ctx, clients, osup_gvr, namespace, name)
+				if getErr == nil {
+					out = latest
+				}
+			}
+			return fmt.Errorf("update OSUpgradeProgress spec %s/%s: %w", namespace, name, err)
+		}
+
+		if existing.Status == nil {
+			existing.Status = &monov1alpha1.OSUpgradeProgressStatus{}
+		}
+
+		existing.Status.CurrentVersion = currentVersion
+		existing.Status.TargetVersion = targetVersion
+		existing.Status.LastUpdatedAt = &now
+
+		if existing.Status.Phase == "" {
+			existing.Status.Phase = monov1alpha1.OSUpgradeProgressPhasePending
+		}
+		if existing.Status.Message == "" {
+			existing.Status.Message = "acknowledged"
+		}
+
+		existing, err = updateProgressStatus(ctx, clients, osup_gvr, existing)
+		if err != nil {
+			if isUnknownUpdateResult(err) {
+				latest, getErr := getProgress(ctx, clients, osup_gvr, namespace, name)
+				if getErr == nil {
+					out = latest
+				}
+			}
+			return fmt.Errorf("update OSUpgradeProgress status %s/%s: %w", namespace, name, err)
+		}
+
+		out = existing
+		return nil
+	})
 	if err != nil {
-		return nil, fmt.Errorf("get existing OSUpgradeProgress %s/%s: %w", namespace, name, err)
+		if out != nil {
+			return out, nil
+		}
+		return nil, err
 	}
 
-	// Spec should remain aligned with the source and node.
-	existing.Spec.NodeName = nodeName
-	existing.Spec.SourceRef.Name = osu.Name
-
-	if existing, err = updateProgressSpec(ctx, clients, osup_gvr, existing); err != nil {
-		return nil, fmt.Errorf("update OSUpgradeProgress spec %s/%s: %w", namespace, name, err)
-	}
+	klog.InfoS("updated osupgradeprogress", "name", out.Name, "namespace", out.Namespace)
+	return out, nil
+}
 
-	if existing.Status == nil {
-		existing.Status = &monov1alpha1.OSUpgradeProgressStatus{}
+func updateProgressRobust(
+	ctx context.Context,
+	clients *kube.Clients,
+	namespace string,
+	name string,
+	mutate func(*monov1alpha1.OSUpgradeProgress),
+) (*monov1alpha1.OSUpgradeProgress, error) {
+	var out *monov1alpha1.OSUpgradeProgress
+
+	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
+		current, err := getProgress(ctx, clients, osup_gvr, namespace, name)
+		if err != nil {
+			return err
+		}
+
+		if current.Status == nil {
+			current.Status = &monov1alpha1.OSUpgradeProgressStatus{}
+		}
+
+		mutate(current)
+
+		updated, err := updateProgressStatus(ctx, clients, osup_gvr, current)
+		if err != nil {
+			if isUnknownUpdateResult(err) {
+				latest, getErr := getProgress(ctx, clients, osup_gvr, namespace, name)
+				if getErr == nil {
+					out = latest
+				}
+			}
+			return err
+		}
+
+		out = updated
+		return nil
+	})
+
+	if err != nil && out != nil {
+		// Unknown-result case: caller gets latest known server state plus error.
+		return out, err
 	}
 
-	existing.Status.CurrentVersion = currentVersion
-	existing.Status.TargetVersion = targetVersion
-	existing.Status.LastUpdatedAt = &now
+	return out, err
+}
 
-	// Only set phase/message if they are still empty, so later real state machine
-	// updates are not clobbered by the heartbeat.
-	if existing.Status.Phase == "" {
-		existing.Status.Phase = monov1alpha1.OSUpgradeProgressPhasePending
+func isUnknownUpdateResult(err error) bool {
+	if err == nil {
+		return false
 	}
 
-	if existing, err = updateProgressStatus(ctx, clients, osup_gvr, existing); err != nil {
-		return nil, fmt.Errorf("update OSUpgradeProgress status %s/%s: %w", namespace, name, err)
+	if apierrors.IsTimeout(err) ||
+		apierrors.IsServerTimeout(err) ||
+		apierrors.IsTooManyRequests(err) {
+		return true
 	}
 
-	klog.InfoS("updated osupgradeprogress", "name", existing.Name, "namespace", existing.Namespace)
-	return existing, nil
+	msg := strings.ToLower(err.Error())
+	return strings.Contains(msg, "request timed out") ||
+		strings.Contains(msg, "context deadline exceeded") ||
+		strings.Contains(msg, "etcdserver: request timed out") ||
+		strings.Contains(msg, "connection reset by peer") ||
+		strings.Contains(msg, "http2: client connection lost")
 }
 
 func createProgress(
@@ -199,17 +289,18 @@ func failProgress(
 	action string,
 	cause error,
 ) error {
-	now := metav1.Now()
+	_, err := updateProgressRobust(ctx, clients, osup.Namespace, osup.Name, func(cur *monov1alpha1.OSUpgradeProgress) {
+		now := metav1.Now()
 
-	if osup.Status == nil {
-		osup.Status = &monov1alpha1.OSUpgradeProgressStatus{}
-	}
+		if cur.Status == nil {
+			cur.Status = &monov1alpha1.OSUpgradeProgressStatus{}
+		}
 
-	osup.Status.LastUpdatedAt = &now
-	osup.Status.Message = fmt.Sprintf("%s: %v", action, cause)
-	osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseFailed
-
-	if _, err := updateProgressStatus(ctx, clients, osup_gvr, osup); err != nil {
+		cur.Status.LastUpdatedAt = &now
+		cur.Status.Message = fmt.Sprintf("%s: %v", action, cause)
+		cur.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseFailed
+	})
+	if err != nil {
 		klog.ErrorS(err, "failed to update osupgradeprogress status after error",
 			"action", action,
 			"name", osup.Name,
@@ -226,18 +317,18 @@ func markProgressCompleted(
 	osup *monov1alpha1.OSUpgradeProgress,
 	message string,
 ) error {
-	now := metav1.Now()
-
-	if osup.Status == nil {
-		osup.Status = &monov1alpha1.OSUpgradeProgressStatus{}
-	}
-
-	osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseCompleted
-	osup.Status.Message = message
-	osup.Status.LastUpdatedAt = &now
-	osup.Status.CompletedAt = &now
-
-	_, err := updateProgressStatus(ctx, clients, osup_gvr, osup)
+	_, err := updateProgressRobust(ctx, clients, osup.Namespace, osup.Name, func(cur *monov1alpha1.OSUpgradeProgress) {
+		now := metav1.Now()
+
+		if cur.Status == nil {
+			cur.Status = &monov1alpha1.OSUpgradeProgressStatus{}
+		}
+
+		cur.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseCompleted
+		cur.Status.Message = message
+		cur.Status.LastUpdatedAt = &now
+		cur.Status.CompletedAt = &now
+	})
 	if err != nil {
 		return fmt.Errorf("mark progress completed: %w", err)
 	}
diff --git a/initramfs/rootfs-extra/init b/initramfs/rootfs-extra/init
index aa76fa6..1483568 100755
--- a/initramfs/rootfs-extra/init
+++ b/initramfs/rootfs-extra/init
@@ -108,7 +108,7 @@ wait_for_partnames() {
 
         sleep 1
         i=$((i + 1))
-		log "Still waiting for $@ to populate($i)"
+        log "Still waiting for $@ to populate($i)"
     done
 
     return 1
@@ -174,8 +174,6 @@ resolve_preferred_root() {
     find_part_by_partuuid "$pref_root"
 }
 
-# Decide which root PARTNAME we want for the requested slot.
-# Keep a compatibility fallback for legacy "rootfs" as slot A.
 wanted_root_labels_for_slot() {
     slot="$1"
 
@@ -184,8 +182,7 @@ wanted_root_labels_for_slot() {
             echo "rootfsB"
             ;;
         *)
-            # Try modern rootfsA first, then legacy rootfs
-            echo "rootfsA rootfs"
+            echo "rootfsA"
             ;;
     esac
 }
@@ -232,7 +229,7 @@ PREFERRED_PARTUUID="$(get_cmdline_arg pref_root || true)"
 
 ROOT_DEV="$(resolve_preferred_root "$PREFERRED_PARTUUID" || true)"
 if [ -n "$ROOT_DEV" ]; then
-	log "Using preferred root device: $ROOT_DEV"
+    log "Using preferred root device: $ROOT_DEV"
 fi
 
 if [ -z "$ROOT_DEV" ]; then
@@ -276,6 +273,16 @@ mount_or_panic -t overlay overlay \
     -o "lowerdir=/newroot/etc,upperdir=/newroot/data/etc-overlay/upper,workdir=/newroot/data/etc-overlay/work" \
     /newroot/etc
 
+if [ "$BOOT_PART" = "A" ]; then
+    ALT_PART="$(find_sibling_part_on_same_disk "$ROOT_DEV" rootfsB || true)"
+else
+    ALT_PART="$(find_sibling_part_on_same_disk "$ROOT_DEV" rootfsA || true)"
+fi
+
+if [ -n "$ALT_PART" ]; then
+    ln -sf "$ALT_PART" /dev/mksaltpart
+fi
+
 mount_or_panic --move /dev /newroot/dev
 mount_or_panic --move /proc /newroot/proc
 mount_or_panic --move /sys /newroot/sys