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
322 lines
10 KiB
Go
322 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 gvisor
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/alibaba/OpenSandbox/sandbox-k8s/test/utils"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// runKubectl executes a kubectl command from the project root directory
|
|
func runKubectl(args ...string) (string, error) {
|
|
cmd := exec.Command("kubectl", args...)
|
|
cmd.Dir = "../../.." // Navigate from test/e2e_runtime/gvisor to project root
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return string(output), fmt.Errorf("kubectl %v failed: %w", args, err)
|
|
}
|
|
return string(output), nil
|
|
}
|
|
|
|
var _ = Describe("gVisor RuntimeClass", Ordered, func() {
|
|
const testNamespace = "default"
|
|
|
|
BeforeAll(func() {
|
|
By("installing gVisor RuntimeClass")
|
|
_, err := runKubectl("apply", "-f", "test/e2e_runtime/gvisor/testdata/runtimeclass.yaml")
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to create gVisor RuntimeClass")
|
|
})
|
|
|
|
AfterAll(func() {
|
|
By("cleaning up RuntimeClass")
|
|
_, _ = runKubectl("delete", "runtimeclass", RuntimeClassName, "--ignore-not-found=true")
|
|
})
|
|
|
|
Context("RuntimeClass API", func() {
|
|
It("should create RuntimeClass resources", func() {
|
|
By("verifying RuntimeClass exists")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "runtimeclass", RuntimeClassName, "-o", "json")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
var rcObj struct {
|
|
Handler string `json:"handler"`
|
|
}
|
|
err = json.Unmarshal([]byte(output), &rcObj)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(rcObj.Handler).To(Equal("runsc"))
|
|
}, 30*time.Second).Should(Succeed())
|
|
})
|
|
})
|
|
|
|
Context("Pod with runtimeClassName", func() {
|
|
var podName string
|
|
|
|
BeforeEach(func() {
|
|
podName = fmt.Sprintf("test-pod-gvisor-%d", time.Now().UnixNano())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("cleaning up Pod")
|
|
if podName != "" {
|
|
_, _ = runKubectl("delete", "pod", podName, "-n", testNamespace, "--ignore-not-found=true")
|
|
}
|
|
})
|
|
|
|
It("should create Pod with runtimeClassName", func() {
|
|
By("creating a Pod with runtimeClassName")
|
|
podYAML := fmt.Sprintf(`apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: %s
|
|
namespace: %s
|
|
spec:
|
|
runtimeClassName: %s
|
|
containers:
|
|
- name: test-container
|
|
image: %s
|
|
command: ["sleep", "3600"]
|
|
`, podName, testNamespace, RuntimeClassName, utils.SandboxImage)
|
|
|
|
podFile := filepath.Join("/tmp", fmt.Sprintf("test-pod-%s.yaml", podName))
|
|
err := os.WriteFile(podFile, []byte(podYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(podFile)
|
|
|
|
_, err = runKubectl("apply", "-f", podFile)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to create Pod")
|
|
|
|
By("verifying Pod has runtimeClassName set")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pod", podName, "-n", testNamespace,
|
|
"-o", "jsonpath={.spec.runtimeClassName}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal(RuntimeClassName))
|
|
}, 30*time.Second).Should(Succeed())
|
|
|
|
By("verifying Pod is running with gVisor")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pod", podName, "-n", testNamespace,
|
|
"-o", "jsonpath={.status.phase}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Running"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
})
|
|
})
|
|
|
|
Context("gVisor + egress sidecar incompatibility", func() {
|
|
var podName string
|
|
|
|
BeforeEach(func() {
|
|
podName = fmt.Sprintf("test-gvisor-egress-%d", time.Now().UnixNano())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("cleaning up Pod")
|
|
if podName != "" {
|
|
_, _ = runKubectl("delete", "pod", podName, "-n", testNamespace,
|
|
"--ignore-not-found=true", "--grace-period=0", "--force")
|
|
}
|
|
})
|
|
|
|
It("should fail to start the egress sidecar under gVisor due to missing iptables nat table", func() {
|
|
egressImage := os.Getenv("EGRESS_IMG")
|
|
if egressImage == "" {
|
|
Skip("EGRESS_IMG not set; skipping gVisor + egress incompatibility test")
|
|
}
|
|
|
|
By("creating a Pod with gVisor runtimeClassName and egress sidecar")
|
|
podYAML := fmt.Sprintf(`apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: %s
|
|
namespace: %s
|
|
spec:
|
|
runtimeClassName: %s
|
|
restartPolicy: Never
|
|
containers:
|
|
- name: workload
|
|
image: %s
|
|
command: ["sleep", "300"]
|
|
- name: egress
|
|
image: %s
|
|
securityContext:
|
|
capabilities:
|
|
add: ["NET_ADMIN"]
|
|
env:
|
|
- name: OPENSANDBOX_EGRESS_MODE
|
|
value: "dns+nft"
|
|
- name: OPENSANDBOX_EGRESS_RULES
|
|
value: '{"defaultAction":"deny","egress":[]}'
|
|
`, podName, testNamespace, RuntimeClassName, utils.SandboxImage, egressImage)
|
|
|
|
podFile := filepath.Join("/tmp", fmt.Sprintf("test-pod-%s.yaml", podName))
|
|
err := os.WriteFile(podFile, []byte(podYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(podFile)
|
|
|
|
_, err = runKubectl("apply", "-f", podFile)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to create Pod")
|
|
|
|
By("verifying the egress container terminates with an error")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pod", podName, "-n", testNamespace,
|
|
"-o", "jsonpath={.status.containerStatuses[?(@.name==\"egress\")].state.terminated.exitCode}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).NotTo(BeEmpty(), "egress container should have terminated")
|
|
g.Expect(output).NotTo(Equal("0"), "egress container should exit with non-zero code")
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("verifying egress logs mention iptables nat failure")
|
|
output, err := runKubectl("logs", podName, "-n", testNamespace, "-c", "egress")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(SatisfyAny(
|
|
ContainSubstring("Failed to initialize nft"),
|
|
ContainSubstring("can't initialize iptables table"),
|
|
ContainSubstring("nat"),
|
|
))
|
|
})
|
|
})
|
|
|
|
Context("Pool with gVisor RuntimeClass", func() {
|
|
var poolName string
|
|
var batchSandboxName string
|
|
|
|
BeforeEach(func() {
|
|
poolName = fmt.Sprintf("gvisor-pool-%d", time.Now().UnixNano())
|
|
batchSandboxName = fmt.Sprintf("gvisor-bsbx-%d", time.Now().UnixNano())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("cleaning up BatchSandbox")
|
|
if batchSandboxName != "" {
|
|
_, _ = runKubectl("delete", "batchsandbox", batchSandboxName, "-n", testNamespace, "--ignore-not-found=true")
|
|
}
|
|
By("cleaning up Pool")
|
|
if poolName != "" {
|
|
_, _ = runKubectl("delete", "pool", poolName, "-n", testNamespace, "--ignore-not-found=true")
|
|
}
|
|
})
|
|
|
|
It("should create Pool and allocate Pod with gVisor runtime", func() {
|
|
By("creating a Pool with gVisor runtimeClassName")
|
|
poolYAML := fmt.Sprintf(`apiVersion: sandbox.opensandbox.io/v1alpha1
|
|
kind: Pool
|
|
metadata:
|
|
name: %s
|
|
namespace: %s
|
|
spec:
|
|
template:
|
|
spec:
|
|
runtimeClassName: %s
|
|
containers:
|
|
- name: sandbox-container
|
|
image: %s
|
|
command: ["sleep", "3600"]
|
|
capacitySpec:
|
|
bufferMax: 2
|
|
bufferMin: 1
|
|
poolMax: 5
|
|
poolMin: 1
|
|
`, poolName, testNamespace, RuntimeClassName, utils.SandboxImage)
|
|
|
|
poolFile := filepath.Join("/tmp", fmt.Sprintf("test-pool-%s.yaml", poolName))
|
|
err := os.WriteFile(poolFile, []byte(poolYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(poolFile)
|
|
|
|
_, err = runKubectl("apply", "-f", poolFile)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to create Pool")
|
|
|
|
By("waiting for Pool to have available pods")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pool", poolName, "-n", testNamespace, "-o", "json")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
var poolObj struct {
|
|
Status struct {
|
|
Available int32 `json:"available"`
|
|
} `json:"status"`
|
|
}
|
|
err = json.Unmarshal([]byte(output), &poolObj)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(poolObj.Status.Available).To(BeNumerically(">", 0))
|
|
}, 3*time.Minute).Should(Succeed())
|
|
|
|
By("creating BatchSandbox with poolRef")
|
|
bsbxYAML := fmt.Sprintf(`apiVersion: sandbox.opensandbox.io/v1alpha1
|
|
kind: BatchSandbox
|
|
metadata:
|
|
name: %s
|
|
namespace: %s
|
|
spec:
|
|
replicas: 1
|
|
poolRef: %s
|
|
`, batchSandboxName, testNamespace, poolName)
|
|
|
|
bsbxFile := filepath.Join("/tmp", fmt.Sprintf("test-bsbx-%s.yaml", batchSandboxName))
|
|
err = os.WriteFile(bsbxFile, []byte(bsbxYAML), 0644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(bsbxFile)
|
|
|
|
_, err = runKubectl("apply", "-f", bsbxFile)
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to create BatchSandbox")
|
|
|
|
By("waiting for BatchSandbox to allocate a Pod")
|
|
var podName string
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pods", "-n", testNamespace,
|
|
"-l", fmt.Sprintf("sandbox.opensandbox.io/pool-name=%s", poolName),
|
|
"-o", "jsonpath={.items[0].metadata.name}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).NotTo(BeEmpty())
|
|
podName = output
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("verifying allocated Pod has runtimeClassName set to gVisor")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pod", podName, "-n", testNamespace,
|
|
"-o", "jsonpath={.spec.runtimeClassName}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal(RuntimeClassName))
|
|
}, 30*time.Second).Should(Succeed())
|
|
|
|
By("verifying Pod is running with gVisor")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "pod", podName, "-n", testNamespace,
|
|
"-o", "jsonpath={.status.phase}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("Running"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
|
|
By("verifying BatchSandbox status is ready")
|
|
Eventually(func(g Gomega) {
|
|
output, err := runKubectl("get", "batchsandbox", batchSandboxName, "-n", testNamespace,
|
|
"-o", "jsonpath={.status.ready}")
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(output).To(Equal("1"))
|
|
}, 2*time.Minute).Should(Succeed())
|
|
})
|
|
})
|
|
})
|