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
1072 lines
42 KiB
Go
1072 lines
42 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 e2e
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/alibaba/OpenSandbox/sandbox-k8s/test/utils"
|
|
)
|
|
|
|
var (
|
|
pauseResumeNamespace = utils.PauseResumeNamespace()
|
|
registryServiceAddr = utils.PauseResumeRegistryAddr()
|
|
registryUsername = utils.PauseResumeRegistryUser()
|
|
registryPassword = utils.PauseResumeRegistryPass()
|
|
)
|
|
|
|
var _ = Describe("PauseResume", Ordered, Label("PauseResume"), func() {
|
|
SetDefaultEventuallyTimeout(3 * time.Minute)
|
|
SetDefaultEventuallyPollingInterval(time.Second)
|
|
|
|
BeforeAll(func() {
|
|
By("creating manager namespace")
|
|
cmd := exec.Command("kubectl", "create", "ns", namespace)
|
|
_, err := utils.Run(cmd)
|
|
if err != nil {
|
|
Expect(err.Error()).To(ContainSubstring("AlreadyExists"))
|
|
}
|
|
|
|
if pauseResumeNamespace != namespace {
|
|
By("creating pause-resume namespace")
|
|
cmd = exec.Command("kubectl", "create", "ns", pauseResumeNamespace)
|
|
_, err = utils.Run(cmd)
|
|
if err != nil {
|
|
Expect(err.Error()).To(ContainSubstring("AlreadyExists"))
|
|
}
|
|
}
|
|
|
|
if psa := utils.PodSecurityEnforce(); psa != "" {
|
|
By("labeling the namespace to enforce the " + psa + " security policy")
|
|
cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace,
|
|
"pod-security.kubernetes.io/enforce="+psa)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with "+psa+" policy")
|
|
} else {
|
|
By("skipping pod-security label (E2E_POD_SECURITY_ENFORCE is empty)")
|
|
}
|
|
|
|
By("installing CRDs")
|
|
cmd = exec.Command("make", "install")
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs")
|
|
|
|
By("deploying the controller-manager")
|
|
cmd = exec.Command("make", "deploy",
|
|
fmt.Sprintf("CONTROLLER_IMG=%s", utils.ControllerImage),
|
|
fmt.Sprintf("SNAPSHOT_REGISTRY=%s", registryServiceAddr))
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager")
|
|
|
|
By("waiting for controller to be ready")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "pods", "-l", "control-plane=controller-manager",
|
|
"-n", namespace, "-o", "jsonpath={.items[0].status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Running"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("creating registry authentication secrets")
|
|
err = createHtpasswdSecret(pauseResumeNamespace)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = createDockerRegistrySecrets(pauseResumeNamespace)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("deploying Docker Registry")
|
|
registryYAML, err := renderTemplate("testdata/registry-deployment.yaml", map[string]interface{}{
|
|
"RegistryImage": utils.RegistrySourceImage(),
|
|
"Namespace": pauseResumeNamespace,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
registryFile := filepath.Join("/tmp", "test-registry.yaml")
|
|
err = os.WriteFile(registryFile, []byte(registryYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(registryFile)
|
|
|
|
cmd = exec.Command("kubectl", "apply", "-f", registryFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for registry to be ready")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "deployment", "docker-registry",
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.availableReplicas}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("1"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
})
|
|
|
|
AfterAll(func() {
|
|
By("cleaning up Docker Registry")
|
|
cmd := exec.Command("kubectl", "delete", "deployment", "docker-registry", "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
cmd = exec.Command("kubectl", "delete", "service", "docker-registry", "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
|
|
By("cleaning up secrets")
|
|
for _, secret := range []string{"registry-auth", "registry-snapshot-push-secret", "registry-pull-secret"} {
|
|
cmd = exec.Command("kubectl", "delete", "secret", secret, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
}
|
|
|
|
By("cleaning up any remaining sandboxsnapshots")
|
|
cmd = exec.Command("kubectl", "delete", "sandboxsnapshots", "--all", "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
|
|
By("cleaning up any remaining batchsandboxes")
|
|
cmd = exec.Command("kubectl", "delete", "batchsandboxes", "--all", "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
|
|
By("undeploying the controller-manager")
|
|
cmd = exec.Command("make", "undeploy")
|
|
utils.Run(cmd)
|
|
|
|
By("uninstalling CRDs")
|
|
cmd = exec.Command("make", "uninstall")
|
|
utils.Run(cmd)
|
|
|
|
By("removing manager namespace")
|
|
cmd = exec.Command("kubectl", "delete", "ns", namespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
})
|
|
|
|
Context("Pause and Resume", func() {
|
|
It("should complete the full pause-resume flow via spec.pause trigger", func() {
|
|
const sandboxName = "test-pause-resume"
|
|
|
|
// --- Step 1: Create BatchSandbox ---
|
|
By("creating BatchSandbox with template")
|
|
bsYAML, err := renderTemplate("testdata/batchsandbox-non-pooled.yaml", map[string]interface{}{
|
|
"BatchSandboxName": sandboxName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"SandboxImage": utils.SandboxImage,
|
|
"Replicas": 1,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
bsFile := filepath.Join("/tmp", "test-pause-resume-bs.yaml")
|
|
err = os.WriteFile(bsFile, []byte(bsYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(bsFile)
|
|
|
|
cmd := exec.Command("kubectl", "apply", "-f", bsFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox to reach the steady Succeed phase")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
// --- Step 2: Write marker file for rootfs verification ---
|
|
By("getting pod name for marker file")
|
|
cmd = exec.Command("kubectl", "get", "pods", "-n", pauseResumeNamespace, "-o", "json")
|
|
podsJSON, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var podList struct {
|
|
Items []struct {
|
|
Metadata struct {
|
|
Name string `json:"name"`
|
|
OwnerReferences []struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
} `json:"ownerReferences"`
|
|
} `json:"metadata"`
|
|
} `json:"items"`
|
|
}
|
|
err = json.Unmarshal([]byte(podsJSON), &podList)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var podName string
|
|
for _, pod := range podList.Items {
|
|
for _, owner := range pod.Metadata.OwnerReferences {
|
|
if owner.Kind == "BatchSandbox" && owner.Name == sandboxName {
|
|
podName = pod.Metadata.Name
|
|
break
|
|
}
|
|
}
|
|
if podName != "" {
|
|
break
|
|
}
|
|
}
|
|
Expect(podName).NotTo(BeEmpty(), "Should find a pod owned by BatchSandbox")
|
|
|
|
markerValue := fmt.Sprintf("pause-test-%d", time.Now().UnixNano())
|
|
By("writing marker file into container for rootfs verification")
|
|
cmd = exec.Command("kubectl", "exec", podName, "-n", pauseResumeNamespace,
|
|
"-c", "sandbox-container", "--", "sh", "-c", fmt.Sprintf("printf '%s' > /tmp/pause-marker", markerValue))
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// --- Step 3: Pause - patch spec.pause=true ---
|
|
By("triggering pause by patching spec.pause=true")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":true}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox phase to be Paused (snapshot ready, pods deleted)")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Paused"))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
// Verify pods are deleted
|
|
By("verifying pods are deleted after pause")
|
|
cmd = exec.Command("kubectl", "get", "pods", "-n", pauseResumeNamespace,
|
|
"-l", "batchsandbox-name="+sandboxName, "-o", "name")
|
|
output, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(BeEmpty(), "Pods should be deleted after pause")
|
|
|
|
By("verifying the reserved internal SandboxSnapshot exists after pause")
|
|
cmd = exec.Command("kubectl", "get", "sandboxsnapshot", sandboxName+"-pause",
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal("Succeed"), "Internal pause snapshot should be ready after pause")
|
|
|
|
// --- Step 4: Resume - patch spec.pause=false ---
|
|
By("triggering resume by patching spec.pause=false")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":false}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for resumed BatchSandbox to return to Succeed")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("verifying the reserved internal SandboxSnapshot is deleted after successful resume")
|
|
cmd = exec.Command("kubectl", "get", "sandboxsnapshot", sandboxName+"-pause",
|
|
"-n", pauseResumeNamespace, "-o", "name")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).To(HaveOccurred(), "Internal pause snapshot should be deleted after successful resume")
|
|
|
|
// --- Step 5: Verify rootfs data persistence ---
|
|
By("getting resumed pod name")
|
|
cmd = exec.Command("kubectl", "get", "pods", "-n", pauseResumeNamespace, "-o", "json")
|
|
resumedPodsJSON, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var resumedPodList struct {
|
|
Items []struct {
|
|
Metadata struct {
|
|
Name string `json:"name"`
|
|
OwnerReferences []struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
} `json:"ownerReferences"`
|
|
} `json:"metadata"`
|
|
} `json:"items"`
|
|
}
|
|
err = json.Unmarshal([]byte(resumedPodsJSON), &resumedPodList)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var resumedPodName string
|
|
for _, pod := range resumedPodList.Items {
|
|
for _, owner := range pod.Metadata.OwnerReferences {
|
|
if owner.Kind == "BatchSandbox" && owner.Name == sandboxName {
|
|
resumedPodName = pod.Metadata.Name
|
|
break
|
|
}
|
|
}
|
|
if resumedPodName != "" {
|
|
break
|
|
}
|
|
}
|
|
Expect(resumedPodName).NotTo(BeEmpty(), "Should find a pod owned by resumed BatchSandbox")
|
|
|
|
By("reading marker file from resumed container to verify rootfs persistence")
|
|
cmd = exec.Command("kubectl", "exec", resumedPodName, "-n", pauseResumeNamespace,
|
|
"-c", "sandbox-container", "--", "cat", "/tmp/pause-marker")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal(markerValue), "Rootfs data should persist across pause/resume")
|
|
|
|
// --- Cleanup ---
|
|
By("cleaning up")
|
|
cmd = exec.Command("kubectl", "delete", "batchsandbox", sandboxName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
})
|
|
|
|
It("should complete pool-based pause-resume via spec.pause trigger", func() {
|
|
const poolName = "test-pool-pause"
|
|
const sandboxName = "test-pool-pause-resume"
|
|
|
|
// --- Step 1: Create Pool CR ---
|
|
By("creating Pool CR")
|
|
poolYAML, err := renderTemplate("testdata/pool-basic.yaml", map[string]interface{}{
|
|
"PoolName": poolName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"SandboxImage": utils.SandboxImage,
|
|
"BufferMax": 2,
|
|
"BufferMin": 1,
|
|
"PoolMax": 5,
|
|
"PoolMin": 1,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
poolFile := filepath.Join("/tmp", "test-pool-pause.yaml")
|
|
err = os.WriteFile(poolFile, []byte(poolYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(poolFile)
|
|
|
|
cmd := exec.Command("kubectl", "apply", "-f", poolFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for Pool to have available pods")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "pool", poolName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.available}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).NotTo(BeEmpty())
|
|
g.Expect(output).NotTo(Equal("0"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
// --- Step 2: Create BatchSandbox with poolRef ---
|
|
By("creating BatchSandbox with poolRef")
|
|
bsYAML, err := renderTemplate("testdata/batchsandbox-pooled-no-expire.yaml", map[string]interface{}{
|
|
"BatchSandboxName": sandboxName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"PoolName": poolName,
|
|
"Replicas": 1,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
bsFile := filepath.Join("/tmp", "test-pool-pause-bs.yaml")
|
|
err = os.WriteFile(bsFile, []byte(bsYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(bsFile)
|
|
|
|
cmd = exec.Command("kubectl", "apply", "-f", bsFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for pooled BatchSandbox to reach the steady Succeed phase")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
// --- Step 3: Get pod name from alloc-status ---
|
|
By("getting pod name from alloc-status annotation")
|
|
var podName string
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace,
|
|
"-o", "jsonpath={.metadata.annotations.sandbox\\.opensandbox\\.io/alloc-status}")
|
|
allocStatusJSON, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(allocStatusJSON).NotTo(BeEmpty(), "alloc-status annotation should exist")
|
|
|
|
var allocStatus struct {
|
|
Pods []string `json:"pods"`
|
|
}
|
|
err = json.Unmarshal([]byte(allocStatusJSON), &allocStatus)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(len(allocStatus.Pods)).To(BeNumerically(">=", 1))
|
|
podName = allocStatus.Pods[0]
|
|
}).Should(Succeed())
|
|
Expect(podName).NotTo(BeEmpty(), "Should have allocated pod name")
|
|
|
|
// --- Step 4: Write marker file ---
|
|
firstMarkerValue := fmt.Sprintf("pool-pause-test-%d", time.Now().UnixNano())
|
|
By("writing marker file into container for rootfs verification")
|
|
cmd = exec.Command("kubectl", "exec", podName, "-n", pauseResumeNamespace,
|
|
"-c", "sandbox-container", "--", "sh", "-c", fmt.Sprintf("printf '%s' > /tmp/pause-marker", firstMarkerValue))
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// --- Step 5: Pause - patch spec.pause=true ---
|
|
By("triggering pause by patching spec.pause=true")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":true}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox phase to be Paused (template solidified from Pool)")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Paused"))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
// Verify template was solidified (no longer nil)
|
|
By("verifying template was solidified from Pool CR")
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.spec.template.spec.containers[0].name}")
|
|
output, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal("sandbox-container"), "Template should be solidified from Pool CR")
|
|
|
|
// --- Step 6: Resume - patch spec.pause=false ---
|
|
By("triggering resume by patching spec.pause=false")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":false}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for resumed BatchSandbox to return to Succeed")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
// Verify poolRef was cleared after resume
|
|
By("verifying poolRef was cleared after resume")
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.spec.poolRef}")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(BeEmpty(), "poolRef should be cleared after resume")
|
|
|
|
// --- Step 7: Verify rootfs data persistence ---
|
|
By("getting resumed pod name")
|
|
cmd = exec.Command("kubectl", "get", "pods", "-n", pauseResumeNamespace, "-o", "json")
|
|
resumedPodsJSON, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var resumedPodList struct {
|
|
Items []struct {
|
|
Metadata struct {
|
|
Name string `json:"name"`
|
|
OwnerReferences []struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
} `json:"ownerReferences"`
|
|
} `json:"metadata"`
|
|
} `json:"items"`
|
|
}
|
|
err = json.Unmarshal([]byte(resumedPodsJSON), &resumedPodList)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var resumedPodName string
|
|
for _, pod := range resumedPodList.Items {
|
|
for _, owner := range pod.Metadata.OwnerReferences {
|
|
if owner.Kind == "BatchSandbox" && owner.Name == sandboxName {
|
|
resumedPodName = pod.Metadata.Name
|
|
break
|
|
}
|
|
}
|
|
if resumedPodName != "" {
|
|
break
|
|
}
|
|
}
|
|
Expect(resumedPodName).NotTo(BeEmpty(), "Should find a pod owned by resumed BatchSandbox")
|
|
|
|
By("waiting for resumed pod container to be ready")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "pod", resumedPodName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.containerStatuses[0].ready}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("true"))
|
|
}, 2*time.Minute, 2*time.Second).Should(Succeed())
|
|
|
|
By("reading marker file from resumed container to verify rootfs persistence")
|
|
cmd = exec.Command("kubectl", "exec", resumedPodName, "-n", pauseResumeNamespace,
|
|
"-c", "sandbox-container", "--", "cat", "/tmp/pause-marker")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal(firstMarkerValue), "Rootfs data should persist across pool-based pause/resume")
|
|
|
|
// --- Cleanup ---
|
|
By("cleaning up")
|
|
cmd = exec.Command("kubectl", "delete", "batchsandbox", sandboxName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
cmd = exec.Command("kubectl", "delete", "pool", poolName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
})
|
|
|
|
It("should complete pool-based pause-resume with process task scheduling", func() {
|
|
const poolName = "test-pool-pause-task"
|
|
const sandboxName = "test-pool-pause-task-resume"
|
|
|
|
By("creating Pool CR with task-executor")
|
|
poolYAML, err := renderTemplate("testdata/pool-with-task-executor.yaml", map[string]interface{}{
|
|
"PoolName": poolName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"TaskExecutorImage": utils.TaskExecutorImage,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
poolFile := filepath.Join("/tmp", "test-pool-pause-task.yaml")
|
|
err = os.WriteFile(poolFile, []byte(poolYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(poolFile)
|
|
|
|
cmd := exec.Command("kubectl", "apply", "-f", poolFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for Pool to have available pods")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "pool", poolName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.available}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).NotTo(BeEmpty())
|
|
g.Expect(output).NotTo(Equal("0"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("creating pooled BatchSandbox with a long-running process task")
|
|
bsYAML, err := renderTemplate("testdata/batchsandbox-pooled-running-task.yaml", map[string]interface{}{
|
|
"BatchSandboxName": sandboxName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"PoolName": poolName,
|
|
"Replicas": 1,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
bsFile := filepath.Join("/tmp", "test-pool-pause-task-bs.yaml")
|
|
err = os.WriteFile(bsFile, []byte(bsYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(bsFile)
|
|
|
|
cmd = exec.Command("kubectl", "apply", "-f", bsFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for the pooled BatchSandbox to allocate and start the task")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.taskRunning}")
|
|
output, err = utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("1"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("getting pooled pod name from alloc-status annotation")
|
|
var pooledPodName string
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace,
|
|
"-o", "jsonpath={.metadata.annotations.sandbox\\.opensandbox\\.io/alloc-status}")
|
|
allocStatusJSON, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(allocStatusJSON).NotTo(BeEmpty())
|
|
|
|
var allocStatus struct {
|
|
Pods []string `json:"pods"`
|
|
}
|
|
err = json.Unmarshal([]byte(allocStatusJSON), &allocStatus)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(allocStatus.Pods).To(HaveLen(1))
|
|
pooledPodName = allocStatus.Pods[0]
|
|
}).Should(Succeed())
|
|
Expect(pooledPodName).NotTo(BeEmpty())
|
|
|
|
taskMarkerValue := fmt.Sprintf("pool-task-pause-test-%d", time.Now().UnixNano())
|
|
By("writing marker file into task-executor container for rootfs verification")
|
|
cmd = exec.Command("kubectl", "exec", pooledPodName, "-n", pauseResumeNamespace,
|
|
"-c", "task-executor", "--", "sh", "-c", fmt.Sprintf("printf '%s' > /tmp/pause-marker", taskMarkerValue))
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("triggering pause by patching spec.pause=true")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":true}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for the BatchSandbox to reach Paused")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Paused"))
|
|
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.allocated}")
|
|
output, err = utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("0"))
|
|
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.ready}")
|
|
output, err = utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("0"))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
By("waiting for the internal SandboxSnapshot to succeed")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "sandboxsnapshot", sandboxName+"-pause",
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
By("verifying the snapshot records the pushed image digest")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "sandboxsnapshot", sandboxName+"-pause",
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.containers[0].imageDigest}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(HavePrefix("sha256:"))
|
|
}, 30*time.Second, time.Second).Should(Succeed())
|
|
|
|
By("verifying template was solidified from Pool CR")
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.spec.template.spec.containers[0].name}")
|
|
output, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal("task-executor"))
|
|
|
|
By("triggering resume by patching spec.pause=false")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":false}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for the resumed BatchSandbox to return to Succeed and taskRunning=1")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.taskRunning}")
|
|
output, err = utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("1"))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
By("verifying poolRef was cleared after resume")
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.spec.poolRef}")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(BeEmpty())
|
|
|
|
By("getting resumed pod name owned by the BatchSandbox")
|
|
cmd = exec.Command("kubectl", "get", "pods", "-n", pauseResumeNamespace, "-o", "json")
|
|
resumedPodsJSON, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var resumedPodList struct {
|
|
Items []struct {
|
|
Metadata struct {
|
|
Name string `json:"name"`
|
|
OwnerReferences []struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
} `json:"ownerReferences"`
|
|
} `json:"metadata"`
|
|
} `json:"items"`
|
|
}
|
|
err = json.Unmarshal([]byte(resumedPodsJSON), &resumedPodList)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var resumedPodName string
|
|
for _, pod := range resumedPodList.Items {
|
|
for _, owner := range pod.Metadata.OwnerReferences {
|
|
if owner.Kind == "BatchSandbox" && owner.Name == sandboxName {
|
|
resumedPodName = pod.Metadata.Name
|
|
break
|
|
}
|
|
}
|
|
if resumedPodName != "" {
|
|
break
|
|
}
|
|
}
|
|
Expect(resumedPodName).NotTo(BeEmpty())
|
|
|
|
By("waiting for resumed task-executor container to be ready")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "pod", resumedPodName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.containerStatuses[0].ready}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("true"))
|
|
}, 2*time.Minute, 2*time.Second).Should(Succeed())
|
|
|
|
By("reading marker file from resumed task-executor container to verify rootfs persistence")
|
|
cmd = exec.Command("kubectl", "exec", resumedPodName, "-n", pauseResumeNamespace,
|
|
"-c", "task-executor", "--", "cat", "/tmp/pause-marker")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal(taskMarkerValue))
|
|
|
|
By("cleaning up")
|
|
cmd = exec.Command("kubectl", "delete", "batchsandbox", sandboxName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
cmd = exec.Command("kubectl", "delete", "pool", poolName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
})
|
|
})
|
|
|
|
Context("Pause Resume Failure", func() {
|
|
It("should transition snapshot to Failed when source sandbox does not exist", func() {
|
|
const snapshotName = "test-pause-fail-no-source"
|
|
|
|
By("creating SandboxSnapshot with non-existent source sandbox")
|
|
snapshotYAML, err := renderTemplate("testdata/sandboxsnapshot.yaml", map[string]interface{}{
|
|
"SnapshotName": snapshotName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"SandboxName": "nonexistent-sandbox",
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
snapshotFile := filepath.Join("/tmp", "test-pause-fail-snapshot.yaml")
|
|
err = os.WriteFile(snapshotFile, []byte(snapshotYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(snapshotFile)
|
|
|
|
cmd := exec.Command("kubectl", "apply", "-f", snapshotFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for SandboxSnapshot to reach Failed phase")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "sandboxsnapshot", snapshotName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Failed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("verifying Failed condition message contains useful information")
|
|
cmd = exec.Command("kubectl", "get", "sandboxsnapshot", snapshotName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.conditions[?(@.type=='Failed')].message}")
|
|
output, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(ContainSubstring("nonexistent-sandbox"), "Error message should mention the missing sandbox")
|
|
|
|
By("verifying no commit job was created")
|
|
cmd = exec.Command("kubectl", "get", "job", "-n", pauseResumeNamespace,
|
|
"-l", "sandbox.opensandbox.io/sandbox-snapshot-name="+snapshotName, "-o", "jsonpath={.items}")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal("[]"), "No commit job should be created for failed snapshot")
|
|
|
|
By("cleaning up")
|
|
cmd = exec.Command("kubectl", "delete", "sandboxsnapshot", snapshotName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
})
|
|
|
|
It("should set Phase=Succeed+PauseFailed when commit/push fails with invalid registry", func() {
|
|
const sandboxName = "test-pause-commit-fail"
|
|
|
|
By("creating BatchSandbox with template")
|
|
bsYAML, err := renderTemplate("testdata/batchsandbox-non-pooled.yaml", map[string]interface{}{
|
|
"BatchSandboxName": sandboxName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"SandboxImage": utils.SandboxImage,
|
|
"Replicas": 1,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
bsFile := filepath.Join("/tmp", "test-pause-commit-fail-bs.yaml")
|
|
err = os.WriteFile(bsFile, []byte(bsYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(bsFile)
|
|
|
|
cmd := exec.Command("kubectl", "apply", "-f", bsFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox to reach the steady Succeed phase")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("patching registry push secret to invalid value (invalid docker config)")
|
|
// Create an invalid docker config JSON (base64 encoded)
|
|
invalidConfig := `{"auths":{"docker-registry.default.svc.cluster.local:5000":{"username":"invalid","password":"wrong","auth":"aW52YWxpZDp3cm9uZw=="}}}`
|
|
encoded := base64.StdEncoding.EncodeToString([]byte(invalidConfig))
|
|
patchData := fmt.Sprintf(`{"data":{".dockerconfigjson":"%s"}}`, encoded)
|
|
cmd = exec.Command("kubectl", "patch", "secret", "registry-snapshot-push-secret", "-n", pauseResumeNamespace,
|
|
"--type=merge", "-p", patchData)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("triggering pause with invalid registry config")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":true}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for PauseFailed condition to be set")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.conditions[?(@.type=='PauseFailed')].status}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("True"))
|
|
}, 15*time.Minute).Should(Succeed())
|
|
|
|
By("verifying phase returned to Succeed")
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal("Succeed"))
|
|
|
|
By("cleaning up")
|
|
cmd = exec.Command("kubectl", "delete", "batchsandbox", sandboxName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
|
|
By("restoring registry push secret to valid credentials")
|
|
err = createDockerRegistrySecrets(pauseResumeNamespace)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("should set Phase=Failed+ResumeFailed when resume fails with invalid image", func() {
|
|
const sandboxName = "test-resume-image-fail"
|
|
|
|
By("creating BatchSandbox with template")
|
|
bsYAML, err := renderTemplate("testdata/batchsandbox-non-pooled.yaml", map[string]interface{}{
|
|
"BatchSandboxName": sandboxName,
|
|
"Namespace": pauseResumeNamespace,
|
|
"SandboxImage": utils.SandboxImage,
|
|
"Replicas": 1,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
bsFile := filepath.Join("/tmp", "test-resume-image-fail-bs.yaml")
|
|
err = os.WriteFile(bsFile, []byte(bsYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(bsFile)
|
|
|
|
cmd := exec.Command("kubectl", "apply", "-f", bsFile)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox to reach the steady Succeed phase")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Succeed"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("triggering pause")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":true}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox to be Paused")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.phase}")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Paused"))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
By("tampering SandboxSnapshot with invalid image URI")
|
|
cmd = exec.Command("kubectl", "patch", "sandboxsnapshot", sandboxName+"-pause",
|
|
"-n", pauseResumeNamespace, "--type=merge", "--subresource=status",
|
|
"-p", `{"status":{"containers":[{"containerName":"sandbox-container","imageUri":"invalid.registry/unreachable/image:nonexistent"}]}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("triggering resume with tampered snapshot")
|
|
cmd = exec.Command("kubectl", "patch", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "--type=merge",
|
|
"-p", `{"spec":{"pause":false}}`)
|
|
_, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("waiting for BatchSandbox to be Failed with ResumeFailed condition")
|
|
Eventually(func(g Gomega) {
|
|
cmd := exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "json")
|
|
output, err := utils.Run(cmd)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Parse phase from JSON
|
|
var bs struct {
|
|
Status struct {
|
|
Phase string `json:"phase"`
|
|
} `json:"status"`
|
|
}
|
|
json.Unmarshal([]byte(output), &bs)
|
|
|
|
if bs.Status.Phase != "Failed" {
|
|
// Print debug info
|
|
cmd = exec.Command("kubectl", "get", "pod", "-n", pauseResumeNamespace,
|
|
"-l", "batch-sandbox.sandbox.opensandbox.io/name="+sandboxName, "-o", "yaml")
|
|
podOutput, _ := utils.Run(cmd)
|
|
fmt.Fprintf(GinkgoWriter, "\n=== Pod status (phase=%s) ===\n%s\n", bs.Status.Phase, podOutput)
|
|
}
|
|
|
|
g.Expect(bs.Status.Phase).To(Equal("Failed"))
|
|
}, 3*time.Minute, 5*time.Second).Should(Succeed())
|
|
|
|
By("verifying ResumeFailed condition is set")
|
|
cmd = exec.Command("kubectl", "get", "batchsandbox", sandboxName,
|
|
"-n", pauseResumeNamespace, "-o", "jsonpath={.status.conditions[?(@.type=='ResumeFailed')].status}")
|
|
output, err := utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal("True"))
|
|
|
|
By("verifying the reserved internal SandboxSnapshot is retained after failed resume")
|
|
cmd = exec.Command("kubectl", "get", "sandboxsnapshot", sandboxName+"-pause",
|
|
"-n", pauseResumeNamespace, "-o", "name")
|
|
output, err = utils.Run(cmd)
|
|
Expect(err).NotTo(HaveOccurred(), "Internal pause snapshot should remain after failed resume")
|
|
Expect(strings.TrimSpace(output)).To(Equal("sandboxsnapshot.sandbox.opensandbox.io/" + sandboxName + "-pause"))
|
|
|
|
By("cleaning up")
|
|
cmd = exec.Command("kubectl", "delete", "batchsandbox", sandboxName, "-n", pauseResumeNamespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
})
|
|
|
|
})
|
|
})
|
|
|
|
// createHtpasswdSecret creates the htpasswd secret for registry authentication.
|
|
// Docker Registry v2 only supports bcrypt hashes, not MD5 ($apr1$) or SHA1.
|
|
func createHtpasswdSecret(namespace string) error {
|
|
htpasswdEntry := ""
|
|
pyCmd := exec.Command("python3", "-c",
|
|
fmt.Sprintf("import bcrypt; print('%s:' + bcrypt.hashpw(b'%s', bcrypt.gensalt(rounds=10)).decode())",
|
|
registryUsername, registryPassword))
|
|
if output, err := pyCmd.Output(); err == nil {
|
|
htpasswdEntry = strings.TrimSpace(string(output))
|
|
}
|
|
|
|
if htpasswdEntry == "" {
|
|
return fmt.Errorf("failed to generate bcrypt htpasswd: python3 bcrypt not available")
|
|
}
|
|
|
|
tmpFile := filepath.Join(os.TempDir(), "htpasswd")
|
|
if err := os.WriteFile(tmpFile, []byte(htpasswdEntry), 0644); err != nil {
|
|
return fmt.Errorf("failed to write htpasswd file: %w", err)
|
|
}
|
|
defer os.Remove(tmpFile)
|
|
|
|
cmd := exec.Command("kubectl", "create", "secret", "generic", "registry-auth",
|
|
"--from-file=htpasswd="+tmpFile, "-n", namespace)
|
|
if _, err := utils.Run(cmd); err != nil {
|
|
cmd = exec.Command("kubectl", "delete", "secret", "registry-auth", "-n", namespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
cmd = exec.Command("kubectl", "create", "secret", "generic", "registry-auth",
|
|
"--from-file=htpasswd="+tmpFile, "-n", namespace)
|
|
if _, err := utils.Run(cmd); err != nil {
|
|
return fmt.Errorf("failed to create registry-auth secret: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createDockerRegistrySecrets creates docker-registry secrets for push/pull.
|
|
func createDockerRegistrySecrets(namespace string) error {
|
|
server := registryServiceAddr
|
|
|
|
cmd := exec.Command("kubectl", "create", "secret", "docker-registry", "registry-snapshot-push-secret",
|
|
"--docker-server="+server,
|
|
"--docker-username="+registryUsername,
|
|
"--docker-password="+registryPassword,
|
|
"-n", namespace)
|
|
if _, err := utils.Run(cmd); err != nil {
|
|
cmd = exec.Command("kubectl", "delete", "secret", "registry-snapshot-push-secret", "-n", namespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
cmd = exec.Command("kubectl", "create", "secret", "docker-registry", "registry-snapshot-push-secret",
|
|
"--docker-server="+server,
|
|
"--docker-username="+registryUsername,
|
|
"--docker-password="+registryPassword,
|
|
"-n", namespace)
|
|
if _, err := utils.Run(cmd); err != nil {
|
|
return fmt.Errorf("failed to create registry-snapshot-push-secret: %w", err)
|
|
}
|
|
}
|
|
|
|
cmd = exec.Command("kubectl", "create", "secret", "docker-registry", "registry-pull-secret",
|
|
"--docker-server="+server,
|
|
"--docker-username="+registryUsername,
|
|
"--docker-password="+registryPassword,
|
|
"-n", namespace)
|
|
if _, err := utils.Run(cmd); err != nil {
|
|
cmd = exec.Command("kubectl", "delete", "secret", "registry-pull-secret", "-n", namespace, "--ignore-not-found=true")
|
|
utils.Run(cmd)
|
|
cmd = exec.Command("kubectl", "create", "secret", "docker-registry", "registry-pull-secret",
|
|
"--docker-server="+server,
|
|
"--docker-username="+registryUsername,
|
|
"--docker-password="+registryPassword,
|
|
"-n", namespace)
|
|
if _, err := utils.Run(cmd); err != nil {
|
|
return fmt.Errorf("failed to create registry-pull-secret: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|