Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

517 lines
17 KiB
Go

// Copyright 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controller
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
sandboxv1alpha1 "github.com/alibaba/OpenSandbox/sandbox-k8s/apis/sandbox/v1alpha1"
)
func newTestSnapshotReconciler(objs ...client.Object) *SandboxSnapshotReconciler {
scheme := k8sruntime.NewScheme()
utilruntime.Must(corev1.AddToScheme(scheme))
utilruntime.Must(batchv1.AddToScheme(scheme))
utilruntime.Must(sandboxv1alpha1.AddToScheme(scheme))
fakeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithStatusSubresource(&sandboxv1alpha1.SandboxSnapshot{}).
WithObjects(objs...).
Build()
return &SandboxSnapshotReconciler{
Client: fakeClient,
Scheme: scheme,
Recorder: record.NewFakeRecorder(10),
}
}
func TestSandboxSnapshotHandleCommitting_SetsSucceedReadyCondition(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhaseCommitting,
Containers: []sandboxv1alpha1.ContainerSnapshot{
{ContainerName: "main", ImageURI: "registry/test:tag"},
},
},
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot-commit",
Namespace: "default",
},
Status: batchv1.JobStatus{
Succeeded: 1,
},
}
r := newTestSnapshotReconciler(snapshot, job)
result, err := r.handleCommitting(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, ctrl.Result{}, result)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot", Namespace: "default"}, updated))
assert.Equal(t, sandboxv1alpha1.SandboxSnapshotPhaseSucceed, updated.Status.Phase)
foundReady := false
for _, cond := range updated.Status.Conditions {
if cond.Type == sandboxv1alpha1.SandboxSnapshotConditionReady {
foundReady = true
assert.Equal(t, sandboxv1alpha1.ConditionTrue, cond.Status)
assert.Equal(t, "SnapshotReady", cond.Reason)
assert.NotNil(t, cond.LastTransitionTime)
}
if cond.Type == sandboxv1alpha1.SandboxSnapshotConditionFailed {
assert.NotEqual(t, sandboxv1alpha1.ConditionTrue, cond.Status)
}
}
assert.True(t, foundReady, "Ready condition should be set after successful commit")
}
func TestSandboxSnapshotHandleCommitting_PersistsImageDigestsFromTerminationMessage(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhaseCommitting,
Containers: []sandboxv1alpha1.ContainerSnapshot{
{ContainerName: "main", ImageURI: "registry/test-main:tag"},
{ContainerName: "sidecar", ImageURI: "registry/test-sidecar:tag"},
},
},
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot-commit",
Namespace: "default",
},
Status: batchv1.JobStatus{
Succeeded: 1,
},
}
commitPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot-commit-abcde",
Namespace: "default",
Labels: map[string]string{
"job-name": "test-snapshot-commit",
},
},
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{
{
Name: CommitJobContainerName,
State: corev1.ContainerState{
Terminated: &corev1.ContainerStateTerminated{
ExitCode: 0,
Message: `{"containers":[{"name":"main","image":"registry/test-main:tag","digest":"sha256:main"},{"name":"sidecar","image":"registry/test-sidecar:tag","digest":"sha256:sidecar"}]}`,
},
},
},
},
},
}
r := newTestSnapshotReconciler(snapshot, job, commitPod)
result, err := r.handleCommitting(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, ctrl.Result{}, result)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot", Namespace: "default"}, updated))
assert.Equal(t, sandboxv1alpha1.SandboxSnapshotPhaseSucceed, updated.Status.Phase)
require.Len(t, updated.Status.Containers, 2)
assert.Equal(t, "sha256:main", updated.Status.Containers[0].ImageDigest)
assert.Equal(t, "sha256:sidecar", updated.Status.Containers[1].ImageDigest)
}
func TestUpdateSnapshotStatus_DoesNotDowngradeSucceededSnapshot(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhaseSucceed,
Conditions: []sandboxv1alpha1.SandboxSnapshotCondition{
{
Type: sandboxv1alpha1.SandboxSnapshotConditionReady,
Status: sandboxv1alpha1.ConditionTrue,
Reason: "SnapshotReady",
},
},
},
}
r := newTestSnapshotReconciler(snapshot)
err := r.updateSnapshotStatus(context.Background(), snapshot, sandboxv1alpha1.SandboxSnapshotPhaseFailed, "CommitJobFailed", "late failure")
require.NoError(t, err)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot", Namespace: "default"}, updated))
assert.Equal(t, sandboxv1alpha1.SandboxSnapshotPhaseSucceed, updated.Status.Phase)
for _, cond := range updated.Status.Conditions {
if cond.Type == sandboxv1alpha1.SandboxSnapshotConditionFailed {
assert.NotEqual(t, sandboxv1alpha1.ConditionTrue, cond.Status)
}
}
}
func TestSandboxSnapshotHandleCommitting_KeepsRetryingWhenJobHasNotTerminallyFailed(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhaseCommitting,
Containers: []sandboxv1alpha1.ContainerSnapshot{
{ContainerName: "main", ImageURI: "registry/test:tag"},
},
},
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot-commit",
Namespace: "default",
},
Status: batchv1.JobStatus{
Active: 1,
Failed: 1,
},
}
r := newTestSnapshotReconciler(snapshot, job)
result, err := r.handleCommitting(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, 5*time.Second, result.RequeueAfter)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot", Namespace: "default"}, updated))
assert.Equal(t, sandboxv1alpha1.SandboxSnapshotPhaseCommitting, updated.Status.Phase)
}
func TestSandboxSnapshotHandleCommitting_CreatesUnpauseJobWhenCommitJobFailed(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhaseCommitting,
SourcePodName: "source-pod",
SourceNodeName: "node-a",
Containers: []sandboxv1alpha1.ContainerSnapshot{
{ContainerName: "main", ImageURI: "registry/test-main:tag"},
{ContainerName: "sidecar", ImageURI: "registry/test-sidecar:tag"},
},
},
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot-commit",
Namespace: "default",
},
Status: batchv1.JobStatus{
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobFailed,
Status: corev1.ConditionTrue,
Reason: "DeadlineExceeded",
Message: "Job was active longer than specified deadline",
},
},
},
}
r := newTestSnapshotReconciler(snapshot, job)
result, err := r.handleCommitting(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, ctrl.Result{}, result)
cleanupJob := &batchv1.Job{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot-unpause", Namespace: "default"}, cleanupJob))
require.Len(t, cleanupJob.Spec.Template.Spec.Containers, 1)
cleanupContainer := cleanupJob.Spec.Template.Spec.Containers[0]
assert.Equal(t, []string{"/usr/local/bin/image-committer"}, cleanupContainer.Command)
assert.Equal(t, []string{"unpause", "source-pod", "default", "main", "sidecar"}, cleanupContainer.Args)
assert.Equal(t, "node-a", cleanupJob.Spec.Template.Spec.NodeName)
}
func TestSandboxSnapshotHandlePending_MissingRegistrySetsFailedCondition(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhasePending,
},
}
r := newTestSnapshotReconciler(snapshot)
result, err := r.handlePending(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, ctrl.Result{}, result)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot", Namespace: "default"}, updated))
assert.Equal(t, sandboxv1alpha1.SandboxSnapshotPhaseFailed, updated.Status.Phase)
foundFailed := false
for _, cond := range updated.Status.Conditions {
if cond.Type == sandboxv1alpha1.SandboxSnapshotConditionFailed {
foundFailed = true
assert.Equal(t, sandboxv1alpha1.ConditionTrue, cond.Status)
assert.Equal(t, "RegistryNotConfigured", cond.Reason)
assert.Contains(t, cond.Message, "snapshot-registry")
}
}
assert.True(t, foundFailed, "Failed condition should be set when registry config is missing")
}
func TestSandboxSnapshotHandlePending_UsesSourcePodContainersWhenTemplateMissing(t *testing.T) {
bs := &sandboxv1alpha1.BatchSandbox{
ObjectMeta: metav1.ObjectMeta{
Name: "test-bs",
Namespace: "default",
Generation: 2,
UID: types.UID("test-bs-uid"),
},
Spec: sandboxv1alpha1.BatchSandboxSpec{
PoolRef: "test-pool",
},
}
setSandboxAllocation(bs, SandboxAllocation{Pods: []string{"pool-pod"}})
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pool-pod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node-a",
Containers: []corev1.Container{
{Name: "sandbox-container", Image: "pool-image:latest"},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
}
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "sandbox.opensandbox.io/v1alpha1",
Kind: "BatchSandbox",
Name: "test-bs",
UID: types.UID("test-bs-uid"),
Controller: ptrToBool(true),
BlockOwnerDeletion: ptrToBool(true),
},
},
},
Spec: sandboxv1alpha1.SandboxSnapshotSpec{
SandboxName: "test-bs",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhasePending,
},
}
r := newTestSnapshotReconciler(bs, pod, snapshot)
r.SnapshotRegistry = "registry.default.svc.cluster.local:5000"
result, err := r.handlePending(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, time.Second, result.RequeueAfter)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot", Namespace: "default"}, updated))
assert.Equal(t, sandboxv1alpha1.SandboxSnapshotPhaseCommitting, updated.Status.Phase)
assert.Equal(t, "pool-pod", updated.Status.SourcePodName)
assert.Equal(t, "node-a", updated.Status.SourceNodeName)
require.Len(t, updated.Status.Containers, 1)
assert.Equal(t, "sandbox-container", updated.Status.Containers[0].ContainerName)
assert.Equal(t, "registry.default.svc.cluster.local:5000/test-bs-sandbox-container:snap-gen2", updated.Status.Containers[0].ImageURI)
job := &batchv1.Job{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: "test-snapshot-commit", Namespace: "default"}, job))
}
func TestSandboxSnapshotHandlePending_PublicSnapshotUsesSnapshotIDTag(t *testing.T) {
bs := &sandboxv1alpha1.BatchSandbox{
ObjectMeta: metav1.ObjectMeta{
Name: "test-bs",
Namespace: "default",
Generation: 7,
},
Spec: sandboxv1alpha1.BatchSandboxSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "sandbox", Image: "python:3.11"},
},
},
},
},
}
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-bs-0",
Namespace: "default",
Labels: map[string]string{
LabelBatchSandboxNameKey: "test-bs",
},
},
Spec: corev1.PodSpec{
NodeName: "node-a",
Containers: []corev1.Container{
{Name: "sandbox", Image: "python:3.11"},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
}
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "osb-snap-11111111222243338444555555555555",
Namespace: "default",
},
Spec: sandboxv1alpha1.SandboxSnapshotSpec{
SandboxName: "test-bs",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
Phase: sandboxv1alpha1.SandboxSnapshotPhasePending,
},
}
r := newTestSnapshotReconciler(bs, pod, snapshot)
r.SnapshotRegistry = "registry.default.svc.cluster.local:5000"
result, err := r.handlePending(context.Background(), snapshot)
require.NoError(t, err)
assert.Equal(t, time.Second, result.RequeueAfter)
updated := &sandboxv1alpha1.SandboxSnapshot{}
require.NoError(t, r.Get(context.Background(), types.NamespacedName{Name: snapshot.Name, Namespace: "default"}, updated))
require.Len(t, updated.Status.Containers, 1)
assert.Equal(
t,
"registry.default.svc.cluster.local:5000/test-bs-sandbox:snap-11111111222243338444555555555555",
updated.Status.Containers[0].ImageURI,
)
}
func TestBuildCommitJob_SetsBoundedBackoffLimit(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
SourcePodName: "test-pod",
SourceNodeName: "node-1",
Containers: []sandboxv1alpha1.ContainerSnapshot{
{
ContainerName: "main",
ImageURI: "registry.example.com/test:tag",
},
},
},
}
r := newTestSnapshotReconciler(snapshot)
r.SnapshotPushSecret = "registry-snapshot-push-secret"
job, err := r.buildCommitJob(snapshot)
require.NoError(t, err)
require.NotNil(t, job.Spec.BackoffLimit)
assert.Equal(t, DefaultCommitJobBackoffLimit, *job.Spec.BackoffLimit)
assert.Equal(t, fmt.Sprintf("%s-commit", snapshot.Name), job.Name)
}
func TestBuildCommitJob_ExecutesImageCommitterDirectlyWithIsolatedArgs(t *testing.T) {
snapshot := &sandboxv1alpha1.SandboxSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Status: sandboxv1alpha1.SandboxSnapshotStatus{
SourcePodName: "pod-with-shell-chars-$(touch /tmp/nope)",
SourceNodeName: "node-1",
Containers: []sandboxv1alpha1.ContainerSnapshot{
{
ContainerName: "main;echo nope",
ImageURI: "registry.example.com/test:tag",
},
},
},
}
r := newTestSnapshotReconciler(snapshot)
r.SnapshotRegistryInsecure = true
job, err := r.buildCommitJob(snapshot)
require.NoError(t, err)
require.Len(t, job.Spec.Template.Spec.Containers, 1)
container := job.Spec.Template.Spec.Containers[0]
assert.Equal(t, []string{"/usr/local/bin/image-committer"}, container.Command)
assert.Equal(t, []string{
"pod-with-shell-chars-$(touch /tmp/nope)",
"default",
"main;echo nope:registry.example.com/test:tag",
}, container.Args)
assert.Contains(t, container.Env, corev1.EnvVar{Name: "SNAPSHOT_REGISTRY_INSECURE", Value: "true"})
require.NotNil(t, container.SecurityContext)
require.NotNil(t, container.SecurityContext.AllowPrivilegeEscalation)
assert.False(t, *container.SecurityContext.AllowPrivilegeEscalation)
require.NotNil(t, container.SecurityContext.Capabilities)
assert.Equal(t, []corev1.Capability{"ALL"}, container.SecurityContext.Capabilities.Drop)
}