Files
devstar/modules/k8s/k8s_test.go
panshuxiao 33a4a54e85 !68 添加了devcontainer在k8s下的重启、暂停、webTerminal功能
* 修复k8s/devcontainer/suite_test
* 改进了DevStar Controller Manager镜像相关代码
* 修改了Dockerfile.rootless以解决go版本问题
* 移动Dockerfile.cotroller-manager位置
* Merge remote-tracking branch 'origin/dev' into devcontainer-on-k8s
* Merge remote-tracking branch 'origin/add_k8s' into AppOnK8s
* Merge remote-tracking branch 'origin/add-dockerfile-method-and-start-s…
*  添加了k8s下的停止、重启devcontainer和webterminal
* Merge branch 'add-dockerfile-method-and-start-stop-container' of https…
* 更新了容器镜像方式的构建、安装和使用方法,但是devcontainer功能还有问题
* fix run postCreateCommand bug
* sh文件方式管理启动脚本
* Merge branch 'add-dockerfile-method-and-start-stop-container' of https…
* add restart command and fix bug
* chore: 补充添加k8s controller的go.mod和go.sum文件
* Merge branch 'add-dockerfile-method-and-start-stop-container' of https…
* 为devstar添加k8s上的controller-manager
* add dockerfile method to create container and save container .restart …
2025-05-13 10:50:26 +00:00

321 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package k8s_agent
import (
"context"
"fmt"
"testing"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestGetKubernetesClient(t *testing.T) {
t.Log("====== 开始测试 GetKubernetesClient 功能 ======")
// 设置测试环境
setting.Devcontainer.Enabled = true
t.Log("Devcontainer.Enabled 已设置为:", setting.Devcontainer.Enabled)
// 创建上下文
ctx := context.Background()
ctxPtr := &ctx
t.Log("尝试连接Kubernetes集群...")
startTime := time.Now()
// 尝试获取Kubernetes客户端
dynamicClient, err := GetKubernetesClient(ctxPtr)
elapsed := time.Since(startTime).Seconds()
t.Logf("连接尝试耗时: %.2f 秒", elapsed)
// 检查结果
if err != nil {
t.Logf("⚠️ 获取Kubernetes动态客户端失败: %v", err)
t.Logf("这可能是因为测试环境没有适当的Kubernetes配置或无法访问集群")
assert.False(t, setting.Devcontainer.Enabled,
"当Kubernetes连接失败时Devcontainer.Enabled应被设置为false")
t.Log("====== 测试完成 - 连接失败 ======")
return
}
t.Log("✅ 成功获取Kubernetes动态客户端")
assert.NotNil(t, dynamicClient, "Kubernetes动态客户端不应为nil")
// 使用动态客户端获取节点信息
t.Log("使用动态客户端获取集群节点信息...")
// 定义Node资源的GVR (Group, Version, Resource)
nodesGVR := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "nodes",
}
// 使用动态客户端获取节点列表
nodesList, err := dynamicClient.Resource(nodesGVR).List(*ctxPtr, metav1.ListOptions{})
if err != nil {
t.Logf("⚠️ 获取节点信息失败: %v", err)
} else {
// 处理和打印节点信息
t.Logf("📊 集群共有 %d 个节点:", len(nodesList.Items))
for i, nodeUnstructured := range nodesList.Items {
nodeName, _, _ := unstructured.NestedString(nodeUnstructured.Object, "metadata", "name")
// 尝试获取节点状态
var nodeStatus string = "Unknown"
conditions, exists, _ := unstructured.NestedSlice(nodeUnstructured.Object, "status", "conditions")
if exists {
for _, conditionObj := range conditions {
condition, ok := conditionObj.(map[string]interface{})
if !ok {
continue
}
conditionType, typeExists, _ := unstructured.NestedString(condition, "type")
if typeExists && conditionType == "Ready" {
status, statusExists, _ := unstructured.NestedString(condition, "status")
if statusExists {
if status == "True" {
nodeStatus = "Ready"
} else {
nodeStatus = "NotReady"
}
}
break
}
}
}
// 尝试获取节点版本
var kubeletVersion string = "Unknown"
version, exists, _ := unstructured.NestedString(nodeUnstructured.Object, "status", "nodeInfo", "kubeletVersion")
if exists {
kubeletVersion = version
}
t.Logf(" 节点 %d: 名称=%s, 状态=%s, 版本=%s",
i+1,
nodeName,
nodeStatus,
kubeletVersion)
}
}
// 使用动态客户端获取命名空间信息
namespacesGVR := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "namespaces",
}
namespacesList, err := dynamicClient.Resource(namespacesGVR).List(*ctxPtr, metav1.ListOptions{})
if err != nil {
t.Logf("⚠️ 获取命名空间信息失败: %v", err)
} else {
t.Logf("📊 集群共有 %d 个命名空间:", len(namespacesList.Items))
for i, nsUnstructured := range namespacesList.Items {
nsName, _, _ := unstructured.NestedString(nsUnstructured.Object, "metadata", "name")
// 尝试获取命名空间状态
nsStatus, exists, _ := unstructured.NestedString(nsUnstructured.Object, "status", "phase")
if !exists {
nsStatus = "Unknown"
}
t.Logf(" 命名空间 %d: %s (状态: %s)",
i+1,
nsName,
nsStatus)
}
}
// 测试DevcontainerApp资源列表
t.Log("尝试列出指定命名空间的DevcontainerApp资源...")
namespaceToTest := "default" // 使用默认命名空间测试
opts := &ListDevcontainersOptions{
Namespace: namespaceToTest,
}
devcontainers, listErr := ListDevcontainers(ctxPtr, dynamicClient, opts)
if listErr != nil {
t.Logf("⚠️ 列出DevcontainerApp资源失败: %v", listErr)
t.Log("这可能是因为缺少权限或CRD未定义")
} else {
t.Logf("✅ 成功获取DevcontainerApp列表命名空间'%s'中有 %d 个DevcontainerApp资源",
namespaceToTest,
len(devcontainers.Items))
// 打印每个DevcontainerApp的基本信息
for i, devcontainer := range devcontainers.Items {
t.Logf(" DevcontainerApp %d:", i+1)
t.Logf(" 名称: %s", devcontainer.Name)
t.Logf(" 创建时间: %v", devcontainer.CreationTimestamp)
t.Logf(" Ready状态: %v", devcontainer.Status.Ready)
t.Logf(" NodePort: %d", devcontainer.Status.NodePortAssigned)
t.Logf(" 镜像: %s", devcontainer.Spec.StatefulSet.Image)
}
}
// 列出所有CRD
t.Log("列出集群中的所有CRD...")
crdsGVR := schema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Version: "v1",
Resource: "customresourcedefinitions",
}
crdsList, err := dynamicClient.Resource(crdsGVR).List(*ctxPtr, metav1.ListOptions{})
if err != nil {
t.Logf("⚠️ 获取CRD列表失败: %v", err)
} else {
t.Logf("📊 集群中共有 %d 个CRD定义", len(crdsList.Items))
// 查找DevcontainerApp CRD
found := false
for _, crdObj := range crdsList.Items {
crdName, exists, _ := unstructured.NestedString(crdObj.Object, "metadata", "name")
if exists && crdName == "devcontainerapps.devcontainer.devstar.cn" {
found = true
t.Log("✅ 找到DevcontainerApp CRD定义")
// 尝试获取CRD版本和组
group, _, _ := unstructured.NestedString(crdObj.Object, "spec", "group")
version, _, _ := unstructured.NestedString(crdObj.Object, "spec", "versions", "0", "name")
scope, _, _ := unstructured.NestedString(crdObj.Object, "spec", "scope")
t.Logf(" 组: %s", group)
t.Logf(" 版本: %s", version)
t.Logf(" 作用域: %s", scope)
break
}
}
if !found {
t.Log("⚠️ 未找到DevcontainerApp CRD定义这可能导致后续测试失败")
}
}
t.Log("====== 测试完成 - 连接成功 ======")
}
// 模拟创建和删除Devcontainer的集成测试
func TestCreateAndDeleteDevcontainer(t *testing.T) {
t.Log("====== 开始测试 CreateAndDeleteDevcontainer 功能 ======")
if testing.Short() {
t.Skip("在短模式下跳过集成测试")
return
}
// 创建上下文
ctx := context.Background()
ctxPtr := &ctx
// 获取客户端
t.Log("尝试获取Kubernetes客户端...")
client, err := GetKubernetesClient(ctxPtr)
if err != nil {
t.Logf("⚠️ 获取Kubernetes客户端失败: %v", err)
t.Skip("跳过后续测试因为无法创建Kubernetes客户端")
return
}
t.Log("✅ 成功获取Kubernetes客户端")
// 创建唯一的测试名称
currentTime := time.Now().UnixNano() / int64(time.Millisecond)
testName := fmt.Sprintf("studio-test-%d", currentTime)
t.Logf("创建测试Devcontainer: %s", testName)
// 创建测试Devcontainer的配置
createOpts := &CreateDevcontainerOptions{
Name: testName,
Namespace: "default",
Image: "devstar.cn/public/base-ssh-devcontainer:ubuntu-20.04-20241014",
GitRepositoryURL: "https://gitee.com/panshuxiao/test-devcontainer.git",
CommandList: []string{
"/bin/bash",
"-c",
"service ssh start && while true; do sleep 60; done",
},
ContainerPort: 22,
ServicePort: 22,
SSHPublicKeyList: []string{
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOmlOiVc18CjXKmVxDwSEqQ8fA2ikZ3p8NqdGV1Gw2cQ panshuxiao@mail.ustc.edu.cn",
},
}
t.Log("开始创建Devcontainer...")
startTime := time.Now()
// 创建Devcontainer
devcontainer, err := CreateDevcontainer(ctxPtr, client, createOpts)
elapsed := time.Since(startTime).Seconds()
if err != nil {
t.Logf("⚠️ 创建测试Devcontainer失败 (耗时: %.2f秒): %v", elapsed, err)
t.Skip("由于创建失败跳过测试的其余部分")
return
}
t.Logf("✅ 成功创建Devcontainer (耗时: %.2f秒)", elapsed)
t.Logf(" 名称: %s", devcontainer.Name)
t.Logf(" 命名空间: %s", devcontainer.Namespace)
t.Logf(" 镜像: %s", devcontainer.Spec.StatefulSet.Image)
// 确保测试结束时删除资源
defer func() {
t.Logf("清理 - 删除测试Devcontainer: %s", testName)
deleteOpts := &DeleteDevcontainerOptions{
Name: createOpts.Name,
Namespace: createOpts.Namespace,
}
deleteStartTime := time.Now()
err := DeleteDevcontainer(ctxPtr, client, deleteOpts)
deleteElapsed := time.Since(deleteStartTime).Seconds()
if err != nil {
t.Logf("⚠️ 清理测试Devcontainer失败 (耗时: %.2f秒): %v", deleteElapsed, err)
} else {
t.Logf("✅ 成功删除测试Devcontainer (耗时: %.2f秒)", deleteElapsed)
}
}()
// 验证创建的Devcontainer
assert.Equal(t, createOpts.Name, devcontainer.Name, "创建的Devcontainer名称应与请求的名称匹配")
// 等待一小段时间让K8s有时间处理资源创建
t.Log("等待15秒钟让Kubernetes有时间处理资源...")
time.Sleep(15 * time.Second)
// 测试获取刚创建的Devcontainer
t.Logf("尝试获取刚创建的Devcontainer: %s", testName)
getOpts := &GetDevcontainerOptions{
Name: createOpts.Name,
Namespace: createOpts.Namespace,
Wait: true,
}
getStartTime := time.Now()
retrieved, err := GetDevcontainer(ctxPtr, client, getOpts)
getElapsed := time.Since(getStartTime).Seconds()
if err != nil {
t.Logf("⚠️ 获取Devcontainer失败 (耗时: %.2f秒): %v", getElapsed, err)
t.Log("注意: 如果Devcontainer尚未Ready这可能是正常的")
} else {
t.Logf("✅ 成功获取Devcontainer (耗时: %.2f秒)", getElapsed)
t.Logf(" 名称: %s", retrieved.Name)
t.Logf(" Ready状态: %v", retrieved.Status.Ready)
t.Logf(" NodePort: %d", retrieved.Status.NodePortAssigned)
assert.Equal(t, createOpts.Name, retrieved.Name, "获取的Devcontainer名称应与创建的名称匹配")
}
t.Log("====== 测试完成 - CreateAndDeleteDevcontainer ======")
}