Writes and verify image

This commit is contained in:
2026-04-04 02:45:46 +08:00
parent 9cb593ffc0
commit 4f490ab37e
20 changed files with 596 additions and 65 deletions

View File

@@ -3,13 +3,17 @@ module example.com/monok8s
go 1.24.0 go 1.24.0
require ( require (
github.com/klauspost/compress v1.18.5
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
golang.org/x/sys v0.31.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.34.0
k8s.io/apiextensions-apiserver v0.34.0 k8s.io/apiextensions-apiserver v0.34.0
k8s.io/apimachinery v0.34.0 k8s.io/apimachinery v0.34.0
k8s.io/cli-runtime v0.34.0 k8s.io/cli-runtime v0.34.0
k8s.io/client-go v0.34.0 k8s.io/client-go v0.34.0
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
sigs.k8s.io/yaml v1.6.0
) )
require ( require (
@@ -48,14 +52,12 @@ require (
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.9.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.34.0 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
@@ -63,5 +65,4 @@ require (
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
) )

View File

@@ -50,6 +50,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=

View File

@@ -2,7 +2,7 @@
VERSION ?= dev VERSION ?= dev
# Target kube version # Target kube version
KUBE_VERSION ?= v1.34.1 KUBE_VERSION ?= v1.35.0
GIT_REV := $(shell git rev-parse HEAD) GIT_REV := $(shell git rev-parse HEAD)
@@ -41,8 +41,8 @@ build-local: .buildinfo
mkdir -p $(BIN_DIR) mkdir -p $(BIN_DIR)
go build -o $(BIN_DIR)/ctl-$(VERSION) ./cmd/ctl go build -o $(BIN_DIR)/ctl-$(VERSION) ./cmd/ctl
run: run-agent:
go run ./cmd/ctl go run -tags dev ./cmd/ctl agent --env-file ./out/cluster.env
clean: clean:
-docker image rm localhost/monok8s/control-agent:$(VERSION) -docker image rm localhost/monok8s/control-agent:$(VERSION)

View File

@@ -0,0 +1,30 @@
package catalog
import (
"encoding/hex"
"fmt"
"strings"
)
func (c *CatalogImage) SHA256() (string, error) {
if c.Checksum == "" {
return "", fmt.Errorf("checksum is empty")
}
const prefix = "sha256:"
if !strings.HasPrefix(c.Checksum, prefix) {
return "", fmt.Errorf("unsupported checksum format (expected sha256:...)")
}
hash := strings.TrimPrefix(c.Checksum, prefix)
if len(hash) != 64 {
return "", fmt.Errorf("invalid sha256 length: got %d, want 64", len(hash))
}
if _, err := hex.DecodeString(hash); err != nil {
return "", fmt.Errorf("invalid sha256 hex: %w", err)
}
return hash, nil
}

View File

@@ -21,19 +21,17 @@ const (
// ResolveCatalog resolves catalog using priority: // ResolveCatalog resolves catalog using priority:
// Inline > ConfigMap > URL > cached // Inline > ConfigMap > URL > cached
func ResolveCatalog( func ResolveCatalog(ctx context.Context,
ctx context.Context,
kubeClient kubernetes.Interface, kubeClient kubernetes.Interface,
namespace string, namespace string, src *monov1alpha1.VersionCatalogSource,
src *monov1alpha1.VersionCatalogSource,
) (*VersionCatalog, error) { ) (*VersionCatalog, error) {
// 1. Inline // Inline
if src != nil && src.Inline != "" { if src != nil && src.Inline != "" {
return parseCatalog([]byte(src.Inline)) return parseCatalog([]byte(src.Inline))
} }
// 2. ConfigMap // ConfigMap
if src != nil && src.ConfigMap != "" { if src != nil && src.ConfigMap != "" {
cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, src.ConfigMap, metav1.GetOptions{}) cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, src.ConfigMap, metav1.GetOptions{})
if err != nil { if err != nil {
@@ -48,7 +46,7 @@ func ResolveCatalog(
return parseCatalog([]byte(data)) return parseCatalog([]byte(data))
} }
// 3. URL // URL
if src != nil && src.URL != "" { if src != nil && src.URL != "" {
cat, err := fetchCatalog(src.URL) cat, err := fetchCatalog(src.URL)
if err == nil { if err == nil {
@@ -64,7 +62,7 @@ func ResolveCatalog(
return nil, fmt.Errorf("fetch catalog failed and no cache: %w", err) return nil, fmt.Errorf("fetch catalog failed and no cache: %w", err)
} }
// 4. cached fallback // fallback cache
if cached, err := loadCached(); err == nil { if cached, err := loadCached(); err == nil {
return cached, nil return cached, nil
} }

View File

@@ -10,4 +10,5 @@ type CatalogImage struct {
Version string `json:"version" yaml:"version"` Version string `json:"version" yaml:"version"`
URL string `json:"url" yaml:"url"` URL string `json:"url" yaml:"url"`
Checksum string `json:"checksum,omitempty" yaml:"checksum,omitempty"` Checksum string `json:"checksum,omitempty" yaml:"checksum,omitempty"`
Size int64 `json:"size,omitempty" yaml:"size,omitempty"`
} }

View File

@@ -190,10 +190,7 @@ func matchesNode(osu *monov1alpha1.OSUpgrade, nodeName string, nodeLabels labels
return true return true
} }
// Cheap fallback in case you temporarily target by explicit hostname-ish labels only. return false
return nodeName != "" && selector.Matches(labels.Set{
"kubernetes.io/hostname": nodeName,
})
} }
func statusPhase(st *monov1alpha1.OSUpgradeStatus) string { func statusPhase(st *monov1alpha1.OSUpgradeStatus) string {

View File

@@ -69,7 +69,7 @@ Supported formats:
} }
} }
var cfg *monov1alpha1.MonoKSConfig // or value, depending on your API var cfg *monov1alpha1.MonoKSConfig
switch { switch {
case strings.TrimSpace(envFile) != "": case strings.TrimSpace(envFile) != "":

View File

@@ -0,0 +1,42 @@
package osimage
import (
"context"
"fmt"
)
func ApplyImageStreamed(ctx context.Context, opts ApplyOptions) (*ApplyResult, error) {
if err := ValidateApplyOptions(opts); err != nil {
return nil, err
}
if err := CheckTargetSafe(opts.TargetPath, opts.ExpectedRawSize); err != nil {
return nil, fmt.Errorf("unsafe target %q: %w", opts.TargetPath, err)
}
src, closeFn, err := OpenDecompressedHTTPStream(ctx, opts.URL, opts.HTTPTimeout)
if err != nil {
return nil, fmt.Errorf("open source stream: %w", err)
}
defer closeFn()
written, err := WriteStreamToTarget(ctx, src, opts.TargetPath, opts.ExpectedRawSize, opts.BufferSize, opts.Progress)
if err != nil {
return nil, fmt.Errorf("write target: %w", err)
}
sum, err := VerifyTargetSHA256(ctx, opts.TargetPath, opts.ExpectedRawSize, opts.BufferSize, opts.Progress)
if err != nil {
return nil, fmt.Errorf("verify target: %w", err)
}
if err := VerifySHA256(sum, opts.ExpectedRawSHA256); err != nil {
return nil, fmt.Errorf("final disk checksum mismatch: %w", err)
}
return &ApplyResult{
BytesWritten: written,
VerifiedSHA256: sum,
VerificationOK: true,
}, nil
}

View File

@@ -0,0 +1,15 @@
package osimage
func PercentOf(done, total int64) int64 {
if total <= 0 {
return 0
}
p := (done * 100) / total
if p < 0 {
return 0
}
if p > 100 {
return 100
}
return p
}

View File

@@ -0,0 +1,121 @@
package osimage
import (
"k8s.io/klog/v2"
"time"
)
type progressState struct {
lastTime time.Time
lastPercent int64
lastBucket int64
}
type ProgressLogger struct {
minInterval time.Duration
bucketSize int64
states map[string]*progressState
}
func NewProgressLogger(minSeconds int, bucketSize int64) *ProgressLogger {
if minSeconds < 0 {
minSeconds = 0
}
if bucketSize <= 0 {
bucketSize = 10
}
return &ProgressLogger{
minInterval: time.Duration(minSeconds) * time.Second,
bucketSize: bucketSize,
states: make(map[string]*progressState),
}
}
func (l *ProgressLogger) state(stage string) *progressState {
s, ok := l.states[stage]
if ok {
return s
}
s = &progressState{
lastPercent: -1,
lastBucket: -1,
}
l.states[stage] = s
return s
}
func (l *ProgressLogger) Log(p Progress) {
if p.BytesTotal <= 0 {
return
}
percent := PercentOf(p.BytesComplete, p.BytesTotal)
now := time.Now()
bucket := percent / l.bucketSize
s := l.state(p.Stage)
// Always log first visible progress
if s.lastPercent == -1 {
s.lastPercent = percent
s.lastBucket = bucket
s.lastTime = now
klog.V(4).InfoS(p.Stage, "progress", percent)
return
}
// Always log completion once
if percent == 100 && s.lastPercent < 100 {
s.lastPercent = 100
s.lastBucket = 100 / l.bucketSize
s.lastTime = now
klog.V(4).InfoS(p.Stage, "progress", 100)
return
}
// Log if we crossed a new milestone bucket
if bucket > s.lastBucket {
s.lastPercent = percent
s.lastBucket = bucket
s.lastTime = now
klog.V(4).InfoS(p.Stage, "progress", percent)
return
}
// Otherwise allow a timed refresh if progress moved
if now.Sub(s.lastTime) >= l.minInterval && percent > s.lastPercent {
s.lastPercent = percent
s.lastTime = now
klog.V(4).InfoS(p.Stage, "progress", percent)
}
}
type TimeBasedUpdater struct {
interval time.Duration
lastRun time.Time
}
func NewTimeBasedUpdater(seconds int) *TimeBasedUpdater {
if seconds <= 0 {
seconds = 15
}
return &TimeBasedUpdater{
interval: time.Duration(seconds) * time.Second,
}
}
func (u *TimeBasedUpdater) Run(fn func() error) error {
now := time.Now()
if !u.lastRun.IsZero() && now.Sub(u.lastRun) < u.interval {
return nil
}
if err := fn(); err != nil {
return err
}
u.lastRun = now
return nil
}

View File

@@ -0,0 +1,50 @@
package osimage
import (
"context"
"fmt"
"io"
"net/http"
"time"
"github.com/klauspost/compress/zstd"
)
func OpenDecompressedHTTPStream(ctx context.Context, url string, timeout time.Duration) (io.Reader, func() error, error) {
if url == "" {
return nil, nil, fmt.Errorf("url is required")
}
if timeout <= 0 {
timeout = 30 * time.Minute
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, nil, fmt.Errorf("build request: %w", err)
}
client := &http.Client{Timeout: timeout}
resp, err := client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("http get %q: %w", url, err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, nil, fmt.Errorf("unexpected status: %s", resp.Status)
}
dec, err := zstd.NewReader(resp.Body)
if err != nil {
resp.Body.Close()
return nil, nil, fmt.Errorf("create zstd decoder: %w", err)
}
closeFn := func() error {
dec.Close()
return resp.Body.Close()
}
return dec, closeFn, nil
}

View File

@@ -0,0 +1,29 @@
package osimage
import "time"
type ApplyOptions struct {
URL string
TargetPath string
ExpectedRawSHA256 string
ExpectedRawSize int64
HTTPTimeout time.Duration
BufferSize int
Progress ProgressFunc
}
type Progress struct {
Stage string
BytesComplete int64
BytesTotal int64
}
type ProgressFunc func(Progress)
type ApplyResult struct {
BytesWritten int64
VerifiedSHA256 string
VerificationOK bool
}

View File

@@ -0,0 +1,104 @@
package osimage
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"strings"
)
func VerifyTargetSHA256(ctx context.Context, targetPath string, expectedSize int64,
bufferSize int, progress ProgressFunc) (string, error) {
if targetPath == "" {
return "", fmt.Errorf("target path is required")
}
if expectedSize <= 0 {
return "", fmt.Errorf("expected raw size is required for verification")
}
if bufferSize <= 0 {
bufferSize = 4 * 1024 * 1024
}
f, err := os.Open(targetPath)
if err != nil {
return "", fmt.Errorf("open target for verify: %w", err)
}
defer f.Close()
h := sha256.New()
buf := make([]byte, bufferSize)
var readTotal int64
limited := io.LimitReader(f, expectedSize)
for {
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
n, err := limited.Read(buf)
if n > 0 {
if _, werr := h.Write(buf[:n]); werr != nil {
return "", fmt.Errorf("hash target: %w", werr)
}
readTotal += int64(n)
if progress != nil {
progress(Progress{
Stage: "verify",
BytesComplete: readTotal,
BytesTotal: expectedSize,
})
}
}
if err == io.EOF {
break
}
if err != nil {
return "", fmt.Errorf("read target: %w", err)
}
}
if readTotal != expectedSize {
return "", fmt.Errorf("verify size mismatch: got %d want %d", readTotal, expectedSize)
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func ValidateApplyOptions(opts ApplyOptions) error {
if opts.URL == "" {
return fmt.Errorf("url is required")
}
if opts.TargetPath == "" {
return fmt.Errorf("target path is required")
}
if opts.ExpectedRawSHA256 == "" {
return fmt.Errorf("expected raw sha256 is required")
}
if opts.ExpectedRawSize <= 0 {
return fmt.Errorf("expected raw size must be > 0")
}
return nil
}
func VerifySHA256(got, expected string) error {
expected = NormalizeSHA256(expected)
if expected == "" {
return nil
}
got = NormalizeSHA256(got)
if got != expected {
return fmt.Errorf("sha256 mismatch: got %s want %s", got, expected)
}
return nil
}
func NormalizeSHA256(s string) string {
return strings.ToLower(strings.TrimSpace(s))
}

View File

@@ -0,0 +1,35 @@
//go:build !dev
package osimage
import (
"fmt"
"os"
"strings"
)
func CheckTargetSafe(targetPath string, expectedRawSize int64) error {
if !strings.HasPrefix(targetPath, "/dev/") {
return fmt.Errorf("target must be a device path under /dev")
}
st, err := os.Stat(targetPath)
if err != nil {
return fmt.Errorf("stat target: %w", err)
}
mode := st.Mode()
if mode&os.ModeDevice == 0 {
return fmt.Errorf("target is not a device")
}
// TODO: Add stronger checks
// - EnsureNotMounted(targetPath)
// - EnsureNotCurrentRoot(targetPath)
// - EnsurePartitionNotWholeDisk(targetPath)
// - EnsureCapacity(targetPath, expectedRawSize)
_ = expectedRawSize
return nil
}

View File

@@ -0,0 +1,7 @@
//go:build dev
package osimage
func CheckTargetSafe(targetPath string, expectedRawSize int64) error {
return nil
}

View File

@@ -0,0 +1,82 @@
package osimage
import (
"context"
"fmt"
"io"
"os"
)
func WriteStreamToTarget(ctx context.Context,
src io.Reader,
targetPath string,
expectedSize int64, bufferSize int,
progress ProgressFunc,
) (int64, error) {
if targetPath == "" {
return 0, fmt.Errorf("target path is required")
}
if bufferSize <= 0 {
bufferSize = 4 * 1024 * 1024
}
f, err := os.OpenFile(targetPath, os.O_WRONLY, 0)
if err != nil {
return 0, fmt.Errorf("open target: %w", err)
}
defer f.Close()
written, err := copyWithProgressBuffer(ctx, f, src, expectedSize, "flash", progress, make([]byte, bufferSize))
if err != nil {
return written, err
}
if expectedSize > 0 && written != expectedSize {
return written, fmt.Errorf("written size mismatch: got %d want %d", written, expectedSize)
}
if err := f.Sync(); err != nil {
return written, fmt.Errorf("sync target: %w", err)
}
return written, nil
}
func copyWithProgressBuffer(ctx context.Context, dst io.Writer, src io.Reader, total int64, stage string, progress ProgressFunc, buf []byte) (int64, error) {
var written int64
for {
select {
case <-ctx.Done():
return written, ctx.Err()
default:
}
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
if progress != nil {
progress(Progress{
Stage: stage,
BytesComplete: written,
BytesTotal: total,
})
}
}
if ew != nil {
return written, ew
}
if nw != nr {
return written, io.ErrShortWrite
}
}
if er != nil {
if er == io.EOF {
return written, nil
}
return written, fmt.Errorf("copy %s: %w", stage, er)
}
}
}

View File

@@ -10,14 +10,12 @@ import (
monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1" monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
"example.com/monok8s/pkg/buildinfo" "example.com/monok8s/pkg/buildinfo"
"example.com/monok8s/pkg/catalog" "example.com/monok8s/pkg/catalog"
"example.com/monok8s/pkg/controller/osimage"
"example.com/monok8s/pkg/kube" "example.com/monok8s/pkg/kube"
) )
func HandleOSUpgrade( func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
ctx context.Context, namespace string, nodeName string,
clients *kube.Clients,
namespace string,
nodeName string,
osu *monov1alpha1.OSUpgrade, osu *monov1alpha1.OSUpgrade,
) error { ) error {
osup, err := ensureProgressHeartbeat(ctx, clients, namespace, nodeName, osu) osup, err := ensureProgressHeartbeat(ctx, clients, namespace, nodeName, osu)
@@ -60,10 +58,12 @@ func HandleOSUpgrade(
osup.Status.TargetVersion = plan.ResolvedTarget osup.Status.TargetVersion = plan.ResolvedTarget
osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseDownloading osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseDownloading
osup.Status.Message = fmt.Sprintf("downloading image: %s", first.URL) osup.Status.Message = fmt.Sprintf("downloading image: %s", first.URL)
now := metav1.Now() now := metav1.Now()
osup.Status.LastUpdatedAt = &now osup.Status.LastUpdatedAt = &now
osup, err = updateProgressStatus(ctx, clients, osup_gvr, osup)
if _, err := updateProgressStatus(ctx, clients, osup_gvr, osup); err != nil { if err != nil {
return fmt.Errorf("update progress status: %w", err) return fmt.Errorf("update progress status: %w", err)
} }
@@ -72,9 +72,63 @@ func HandleOSUpgrade(
"node", nodeName, "node", nodeName,
"resolvedTarget", plan.ResolvedTarget, "resolvedTarget", plan.ResolvedTarget,
"steps", len(plan.Path), "steps", len(plan.Path),
"firstVersion", first.Version, "currentVersion", buildinfo.KubeVersion,
"fSHA256irstVersion", first.Version,
"firstURL", first.URL, "firstURL", first.URL,
"size", first.Size,
) )
imageSHA, err := first.SHA256()
if err != nil {
now = metav1.Now()
return failProgress(ctx, clients, osup, "apply image", err)
}
pLogger := osimage.NewProgressLogger(2, 25)
statusUpdater := osimage.NewTimeBasedUpdater(15)
imageOptions := osimage.ApplyOptions{
URL: first.URL,
TargetPath: "./out/flash.img",
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
}
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
}
osup = updated
return nil
}); err != nil {
klog.ErrorS(err, "throttled progress update failed")
}
},
}
result, err := osimage.ApplyImageStreamed(ctx, imageOptions)
if err != nil {
now = metav1.Now()
return failProgress(ctx, clients, osup, "apply image", err)
}
klog.Info(result)
// TODO: fw_setenv
return nil return nil
} }

View File

@@ -26,11 +26,8 @@ var (
} }
) )
func ensureProgressHeartbeat( func ensureProgressHeartbeat(ctx context.Context, clients *kube.Clients,
ctx context.Context, namespace string, nodeName string,
clients *kube.Clients,
namespace string,
nodeName string,
osu *monov1alpha1.OSUpgrade, osu *monov1alpha1.OSUpgrade,
) (*monov1alpha1.OSUpgradeProgress, error) { ) (*monov1alpha1.OSUpgradeProgress, error) {

View File

@@ -11,7 +11,6 @@ import (
"time" "time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
@@ -363,39 +362,6 @@ func isSupportedWorkerSkew(clusterVersion, nodeVersion string) bool {
return false return false
} }
// This should not try to taint the node directly here.
// Just record intent and let a later reconcile step apply the taint.
func markUnsupportedWorkerVersionSkew(nctx *NodeContext, clusterVersion, nodeVersion string) {
// Replace this with whatever state carrier you already use.
//
// Example:
// nctx.Metadata.UnsupportedWorkerVersionSkew = true
// nctx.Metadata.UnsupportedWorkerVersionSkewReason =
// fmt.Sprintf("unsupported worker version skew: cluster=%s node=%s", clusterVersion, nodeVersion)
_ = nctx
_ = clusterVersion
_ = nodeVersion
}
// Optional helper if you want to probe readiness later through the API.
// Keeping this here in case you want a very cheap liveness call elsewhere.
func apiServerReady(ctx context.Context, kubeconfigPath string) error {
restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
if err != nil {
return err
}
restCfg.Timeout = 5 * time.Second
clientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return err
}
_, err = clientset.CoreV1().Namespaces().Get(ctx, metav1.NamespaceSystem, metav1.GetOptions{})
return err
}
func ValidateRequiredImagesPresent(ctx context.Context, n *NodeContext) error { func ValidateRequiredImagesPresent(ctx context.Context, n *NodeContext) error {
if n.Config.Spec.SkipImageCheck { if n.Config.Spec.SkipImageCheck {
klog.Infof("skipping image check (skipImageCheck=true)") klog.Infof("skipping image check (skipImageCheck=true)")