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

312 lines
10 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_task
import (
"context"
"fmt"
"os"
"os/exec"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
api "github.com/alibaba/OpenSandbox/sandbox-k8s/pkg/task-executor"
)
const (
ImageName = "task-executor-e2e"
TargetContainer = "task-e2e-target"
ExecutorContainer = "task-e2e-executor"
VolumeName = "task-e2e-vol"
HostPort = "5758"
)
var _ = Describe("Task Executor E2E", Ordered, func() {
var client *api.Client
BeforeAll(func() {
// Check docker
_, err := exec.LookPath("docker")
Expect(err).NotTo(HaveOccurred(), "Docker not found, skipping E2E test")
By("Building image")
cmd := exec.Command("docker", "build",
"--build-arg", "PACKAGE=cmd/task-executor/main.go",
"-t", ImageName, "-f", "../../Dockerfile", "../../")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expect(cmd.Run()).To(Succeed())
By("Cleaning up previous runs")
exec.Command("docker", "rm", "-f", TargetContainer, ExecutorContainer).Run()
exec.Command("docker", "volume", "rm", VolumeName).Run()
By("Creating shared volume")
Expect(exec.Command("docker", "volume", "create", VolumeName).Run()).To(Succeed())
By("Starting target container")
targetCmd := exec.Command("docker", "run", "-d", "--name", TargetContainer,
"-v", fmt.Sprintf("%s:/tmp/tasks", VolumeName),
"-e", "SANDBOX_MAIN_CONTAINER=main",
"-e", "TARGET_VAR=hello-from-target",
"golang:1.24", "sleep", "infinity")
targetCmd.Stdout = os.Stdout
targetCmd.Stderr = os.Stderr
Expect(targetCmd.Run()).To(Succeed())
By("Starting executor container in Sidecar Mode")
execCmd := exec.Command("docker", "run", "-d", "--name", ExecutorContainer,
"-v", fmt.Sprintf("%s:/tmp/tasks", VolumeName),
"--privileged",
"-u", "0",
"--pid=container:"+TargetContainer,
"-p", HostPort+":5758",
ImageName,
"-enable-sidecar-mode=true",
"-main-container-name=main",
"-data-dir=/tmp/tasks")
execCmd.Stdout = os.Stdout
execCmd.Stderr = os.Stderr
Expect(execCmd.Run()).To(Succeed())
By("Waiting for executor to be ready")
client = api.NewClient(fmt.Sprintf("http://127.0.0.1:%s", HostPort))
Eventually(func() error {
_, err := client.Get(context.Background())
return err
}, 10*time.Second, 500*time.Millisecond).Should(Succeed(), "Executor failed to become ready")
})
AfterAll(func() {
By("Cleaning up containers")
if CurrentSpecReport().Failed() {
By("Dumping logs")
out, _ := exec.Command("docker", "logs", ExecutorContainer).CombinedOutput()
fmt.Printf("Executor Logs:\n%s\n", string(out))
}
exec.Command("docker", "rm", "-f", TargetContainer, ExecutorContainer).Run()
exec.Command("docker", "volume", "rm", VolumeName).Run()
})
Context("When creating a short-lived task", func() {
taskName := "e2e-test-1"
It("should run and succeed", func() {
By("Creating task")
task := &api.Task{
Name: taskName,
Process: &api.Process{
Command: []string{"sleep", "2"},
},
}
_, err := client.Set(context.Background(), task)
Expect(err).NotTo(HaveOccurred())
By("Waiting for task to succeed")
Eventually(func(g Gomega) {
got, err := client.Get(context.Background())
g.Expect(err).NotTo(HaveOccurred())
g.Expect(got).NotTo(BeNil())
g.Expect(got.Name).To(Equal(taskName))
// Verify state
if got.ProcessStatus != nil && got.ProcessStatus.Terminated != nil {
g.Expect(got.ProcessStatus.Terminated.ExitCode).To(BeZero())
g.Expect(got.ProcessStatus.Terminated.Reason).To(Equal("Succeeded"))
} else {
// Fail if not terminated yet (so Eventually retries)
g.Expect(got.ProcessStatus).NotTo(BeNil(), "Task ProcessStatus is nil")
g.Expect(got.ProcessStatus.Terminated).NotTo(BeNil(), "Task status: %v", got.ProcessStatus)
}
}, 10*time.Second, 1*time.Second).Should(Succeed())
})
It("should be deletable", func() {
By("Deleting task")
_, err := client.Set(context.Background(), nil)
Expect(err).NotTo(HaveOccurred())
By("Verifying deletion")
Eventually(func() *api.Task {
got, _ := client.Get(context.Background())
return got
}, 5*time.Second, 500*time.Millisecond).Should(BeNil())
})
})
Context("When creating a task checking environment variables", func() {
taskName := "e2e-env-test"
It("should inherit environment variables from target container", func() {
By("Creating task running 'env'")
task := &api.Task{
Name: taskName,
Process: &api.Process{
Command: []string{"env"},
},
}
_, err := client.Set(context.Background(), task)
Expect(err).NotTo(HaveOccurred())
By("Waiting for task to succeed")
Eventually(func(g Gomega) {
got, err := client.Get(context.Background())
g.Expect(err).NotTo(HaveOccurred())
g.Expect(got).NotTo(BeNil())
g.Expect(got.Name).To(Equal(taskName))
g.Expect(got.ProcessStatus.Terminated).NotTo(BeNil())
g.Expect(got.ProcessStatus.Terminated.ExitCode).To(BeZero())
}, 10*time.Second, 1*time.Second).Should(Succeed())
By("Verifying stdout contains target container env")
// Read stdout.log from the executor container (which shares the volume)
out, err := exec.Command("docker", "exec", ExecutorContainer, "cat", fmt.Sprintf("/tmp/tasks/%s/stdout.log", taskName)).CombinedOutput()
Expect(err).NotTo(HaveOccurred(), "Failed to read stdout.log: %s", string(out))
outputStr := string(out)
Expect(outputStr).To(ContainSubstring("TARGET_VAR=hello-from-target"), "Task environment should inherit from target container")
})
It("should be deletable", func() {
By("Deleting task")
_, err := client.Set(context.Background(), nil)
Expect(err).NotTo(HaveOccurred())
By("Verifying deletion")
Eventually(func() *api.Task {
got, _ := client.Get(context.Background())
return got
}, 5*time.Second, 500*time.Millisecond).Should(BeNil())
})
})
Context("When creating a task with timeout", func() {
taskName := "e2e-timeout-test"
It("should timeout and be terminated", func() {
By("Creating task with 5 second timeout that runs for 30 seconds")
timeoutSec := int64(5)
task := &api.Task{
Name: taskName,
Process: &api.Process{
Command: []string{"sleep", "30"},
TimeoutSeconds: &timeoutSec,
},
}
_, err := client.Set(context.Background(), task)
Expect(err).NotTo(HaveOccurred())
By("Waiting for task to be terminated (within 15 seconds)")
// After timeout detection, Stop is called and the process is killed.
// Once Stop completes, the exit file is written and state becomes Failed.
Eventually(func(g Gomega) {
got, err := client.Get(context.Background())
g.Expect(err).NotTo(HaveOccurred())
g.Expect(got).NotTo(BeNil())
g.Expect(got.Name).To(Equal(taskName))
// Should be Terminated with exit code 137 (SIGKILL) or 143 (SIGTERM)
// sleep responds to SIGTERM quickly, so we usually get 143
// The state will be "Failed" after exit file is written
if got.ProcessStatus != nil && got.ProcessStatus.Terminated != nil {
g.Expect(got.ProcessStatus.Terminated.ExitCode).To(SatisfyAny(
Equal(int32(137)), // SIGKILL
Equal(int32(143)), // SIGTERM
))
} else {
// Fail if not terminated yet
g.Expect(got.ProcessStatus).NotTo(BeNil(), "Task ProcessStatus is nil")
g.Expect(got.ProcessStatus.Terminated).NotTo(BeNil(), "Task status: %v", got.ProcessStatus)
}
}, 15*time.Second, 1*time.Second).Should(Succeed())
By("Verifying the task was terminated")
got, err := client.Get(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(got.ProcessStatus.Terminated).NotTo(BeNil())
Expect(got.ProcessStatus.Terminated.ExitCode).To(SatisfyAny(
Equal(int32(137)), // SIGKILL
Equal(int32(143)), // SIGTERM
))
// State could be "Failed" (after exit file written) or "Timeout" (during stop)
Expect(got.ProcessStatus.Terminated.Reason).To(SatisfyAny(
Equal("Failed"),
Equal("TaskTimeout"),
))
})
It("should be deletable after timeout", func() {
By("Deleting task")
_, err := client.Set(context.Background(), nil)
Expect(err).NotTo(HaveOccurred())
By("Verifying deletion")
Eventually(func() *api.Task {
got, _ := client.Get(context.Background())
return got
}, 5*time.Second, 500*time.Millisecond).Should(BeNil())
})
})
Context("When creating a task that completes before timeout", func() {
taskName := "e2e-no-timeout-test"
It("should succeed without timeout", func() {
By("Creating task with 60 second timeout that completes in 2 seconds")
timeoutSec := int64(60)
task := &api.Task{
Name: taskName,
Process: &api.Process{
Command: []string{"sleep", "2"},
TimeoutSeconds: &timeoutSec,
},
}
_, err := client.Set(context.Background(), task)
Expect(err).NotTo(HaveOccurred())
By("Waiting for task to succeed")
Eventually(func(g Gomega) {
got, err := client.Get(context.Background())
g.Expect(err).NotTo(HaveOccurred())
g.Expect(got).NotTo(BeNil())
g.Expect(got.Name).To(Equal(taskName))
// Should succeed with exit code 0
if got.ProcessStatus != nil && got.ProcessStatus.Terminated != nil {
g.Expect(got.ProcessStatus.Terminated.ExitCode).To(BeZero())
g.Expect(got.ProcessStatus.Terminated.Reason).To(Equal("Succeeded"))
} else {
g.Expect(got.ProcessStatus).NotTo(BeNil(), "Task ProcessStatus is nil")
g.Expect(got.ProcessStatus.Terminated).NotTo(BeNil(), "Task status: %v", got.ProcessStatus)
}
}, 10*time.Second, 1*time.Second).Should(Succeed())
})
It("should be deletable", func() {
By("Deleting task")
_, err := client.Set(context.Background(), nil)
Expect(err).NotTo(HaveOccurred())
By("Verifying deletion")
Eventually(func() *api.Task {
got, _ := client.Get(context.Background())
return got
}, 5*time.Second, 500*time.Millisecond).Should(BeNil())
})
})
})