package installation
import (
"fmt"
"path/filepath"
"strings"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gitcode.com/openFuyao/e2e-auto-test/e2e/framework/executor"
config "gitcode.com/openFuyao/e2e-auto-test/e2e/installation/bke-config"
"gitcode.com/openFuyao/e2e-auto-test/e2e/installation/utils"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
const InstallationItTimeout = 45 * time.Minute
var _ = SIGDescribe("BKE Cluster Installation", func() {
var (
sshExecutor *executor.SSHExecutor
localExecutor *executor.LocalExecutor
guideConfig *config.GuideNodeConfig
dynamicClient dynamic.Interface
clientSet *kubernetes.Clientset
clusterManager *utils.ClusterManager
resourceManager *utils.ResourceManager
)
BeforeEach(func() {
guideConfig = config.LoadGuideNodeFromEnv()
Expect(guideConfig.Host).NotTo(BeEmpty(), "GUIDE_NODE_HOST 环境变量必须设置")
Expect(guideConfig.Password).NotTo(BeEmpty(), "GUIDE_NODE_PASSWORD 环境变量必须设置")
var err error
sshExecutor, err = executor.NewSSHExecutor(
guideConfig.Host,
guideConfig.Port,
guideConfig.Username,
guideConfig.Password,
)
Expect(err).NotTo(HaveOccurred(), "应该成功连接到引导节点")
kubeconfig := filepath.Join(homedir.HomeDir(), ".kube", "config")
restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
Expect(err).NotTo(HaveOccurred(), "应该成功加载 kubeconfig")
dynamicClient, err = dynamic.NewForConfig(restConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功创建动态客户端")
clusterManager = utils.NewClusterManager(sshExecutor, dynamicClient)
clientSet, err = kubernetes.NewForConfig(restConfig)
Expect(err).NotTo(HaveOccurred())
resourceManager = utils.NewResourceManager(sshExecutor, dynamicClient, clientSet)
localExecutor = executor.NewLocalExecutor(30 * time.Second)
})
Describe("创建 1Master 集群并断 1Master节点的网络", Label("1master", "network-disconnect", "post-init", "skip-temporarily"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
masterNode config.NodeInfo
backupFile string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-1m-disconnect-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 1), "1Master 集群需要至少 1 个节点")
masterNode = nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode})
var err error
backupFile, err = DisconnectNetwork(masterNode, localExecutor)
Expect(err).NotTo(HaveOccurred(), "应该成功断开 master 节点的网络")
})
AfterEach(func() {
err := RestoreNetwork(masterNode, localExecutor, backupFile)
if err != nil {
GinkgoWriter.Printf("恢复 master 节点网络失败: %v\n", err)
}
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
By("触发集群删除")
err := clusterManager.DeleteClusterWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("触发集群删除失败: %v\n", err)
}
By("等待集群完全删除")
Eventually(func() bool {
exists := clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
if exists {
phase, _ := clusterManager.GetClusterPhaseWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群 phase: %s\n", phase)
}
return !exists
}, uninstallTimeout, 60*time.Second).Should(BeTrue(), "集群应该被完全删除")
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("清理配置文件失败: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
})
It("当 master 节点网络断开时应该阻止集群创建", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
GinkgoWriter.Printf("配置文件路径: %s\n", configPath)
By("验证配置文件已上传")
err = clusterManager.GetConfigGenerator().ValidateConfigOnNode(configPath)
Expect(err).NotTo(HaveOccurred(), "配置文件应该存在于引导节点")
By("执行 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待并验证集群创建被阻止")
checkInterval := 30 * time.Second
checkTimeout := 10 * time.Minute
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "Healthy" {
Fail(fmt.Sprintf("集群意外变为 Healthy。预期集群创建被阻止,但集群状态为: %s", state))
}
return state == "DeployFailed" || clusterStatus == "InitializationFailed"
}, checkTimeout, checkInterval).Should(BeTrue(), "集群创建应该被阻止,状态为 DeployFailed 和 InitializationFailed")
finalState, err := clusterManager.GetClusterStatusWithKubeconfig(clusterName, "")
Expect(err).NotTo(HaveOccurred())
Expect(finalState).NotTo(Equal("Healthy"), "当 master 节点网络断开时,集群不应该是 Healthy")
GinkgoWriter.Printf("最终集群状态: %s (预期被阻止,不是 Healthy)\n", finalState)
})
})
Describe("创建 1Master1Worker 业务集群并断开 Worker 节点网络", Label("1master1worker", "network-disconnect", "post-init", "skip-temporarily"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
workerNode config.NodeInfo
backupFile string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-1m1w-disconnect-worker-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 2), "1Master1Worker 集群需要至少 2 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
workerNode = nodes[1]
workerNode.Role = []string{"node"}
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode, workerNode})
var err error
backupFile, err = DisconnectNetwork(workerNode, localExecutor)
Expect(err).NotTo(HaveOccurred(), "应该成功断开 worker 节点的网络")
})
AfterEach(func() {
err := RestoreNetwork(workerNode, localExecutor, backupFile)
if err != nil {
GinkgoWriter.Printf("恢复 worker 节点网络失败: %v\n", err)
}
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
By("触发集群删除")
err := clusterManager.DeleteClusterWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("触发集群删除失败: %v\n", err)
}
By("等待集群完全删除")
Eventually(func() bool {
exists := clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
if exists {
phase, _ := clusterManager.GetClusterPhaseWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群 phase: %s\n", phase)
}
return !exists
}, uninstallTimeout, 60*time.Second).Should(BeTrue(), "集群应该被完全删除")
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("清理配置文件失败: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
})
It("当 worker 节点网络断开时应该成功创建集群并保持 Healthy", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
GinkgoWriter.Printf("配置文件路径: %s\n", configPath)
By("验证配置文件已上传")
err = clusterManager.GetConfigGenerator().ValidateConfigOnNode(configPath)
Expect(err).NotTo(HaveOccurred(), "配置文件应该存在于引导节点")
By("执行 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "DeployFailed" {
Fail(fmt.Sprintf("集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, installTimeout, pollInterval).Should(BeTrue(), "即使 worker 节点网络断开,集群也应该变为 Healthy 和 Ready 状态")
By("验证集群最终状态")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("最终集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "当 worker 节点网络断开时,集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "集群状态应该是 Ready")
})
})
Describe("创建 1Master1Worker 集群并断开 Worker 节点网络后缩容", Label("1m1w-delete-disconnect-1w", "network-disconnect", "post-init", "skip-temporarily"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
workerNode config.NodeInfo
backupFile string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-1m1w-disconnect-worker-scalein-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 2), "1Master1Worker 集群需要至少 2 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
workerNode = nodes[1]
workerNode.Role = []string{"node"}
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode, workerNode})
var err error
backupFile, err = DisconnectNetwork(workerNode, localExecutor)
Expect(err).NotTo(HaveOccurred(), "应该成功断开 worker 节点的网络")
})
AfterEach(func() {
err := RestoreNetwork(workerNode, localExecutor, backupFile)
if err != nil {
GinkgoWriter.Printf("恢复 worker 节点网络失败: %v\n", err)
}
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
By("触发集群删除")
err := clusterManager.DeleteClusterWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("触发集群删除失败: %v\n", err)
}
By("等待集群完全删除")
Eventually(func() bool {
exists := clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
if exists {
phase, _ := clusterManager.GetClusterPhaseWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群 phase: %s\n", phase)
}
return !exists
}, uninstallTimeout, 60*time.Second).Should(BeTrue(), "集群应该被完全删除")
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("清理配置文件失败: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
})
It("当 worker 节点网络断开时应该成功创建集群并保持 Healthy,然后缩容 worker 节点", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
GinkgoWriter.Printf("配置文件路径: %s\n", configPath)
By("验证配置文件已上传")
err = clusterManager.GetConfigGenerator().ValidateConfigOnNode(configPath)
Expect(err).NotTo(HaveOccurred(), "配置文件应该存在于引导节点")
By("执行 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "DeployFailed" {
Fail(fmt.Sprintf("集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, installTimeout, pollInterval).Should(BeTrue(), "即使 worker 节点网络断开,集群也应该变为 Healthy 和 Ready 状态")
By("验证集群在缩容前是 Healthy 和 Ready")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
Expect(err).NotTo(HaveOccurred())
Expect(state).To(Equal("Healthy"), "缩容前集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "缩容前集群状态应该是 Ready")
GinkgoWriter.Printf("缩容前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
By(fmt.Sprintf("缩容 worker 节点: %s", workerNode.IP))
err = clusterManager.ScaleInNode(clusterName, workerNode.IP)
Expect(err).NotTo(HaveOccurred(), "应该成功缩容 worker 节点")
By("等待缩容后集群变为 Ready 和 Healthy")
Eventually(func() bool {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群完整状态失败: %v\n", err)
return false
}
GinkgoWriter.Printf("缩容后当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state == "Healthy" && clusterStatus == "Ready"
}, installTimeout, pollInterval).Should(BeTrue(), "缩容后集群应该变为 Healthy 和 Ready")
By("验证缩容后集群最终状态")
phase, state, clusterStatus, err = clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("缩容后最终集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "缩容后集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "缩容后集群状态应该是 Ready")
By("验证缩容后节点数量减少到 1")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
count, err := checker.GetNodeCountWithKubeconfig("")
Expect(err).NotTo(HaveOccurred(), "应该成功获取节点数量")
Expect(count).To(Equal(1), "缩容后应该有 1 个节点(只有 master 节点)")
GinkgoWriter.Printf("缩容后节点数量: %d (预期: 1)\n", count)
})
})
Describe("创建 1Master 管理集群和 1Master1Worker 业务集群并断开 Worker 节点网络", Label("1m-mgmt-1m1w-workload", "network-disconnect", "post-init", "skip-temporarily"), Ordered, func() {
var (
mgmtClusterName string
mgmtClusterConfig *config.BKEClusterConfig
mgmtConfigPath string
mgmtNodeConfigPath string
mgmtKubeconfigPath string
workloadClusterName string
workloadClusterConfig *config.BKEClusterConfig
workloadConfigPath string
workloadNodeConfigPath string
workerNode config.NodeInfo
)
BeforeAll(func() {
By("在引导集群上创建 1Master 管理集群")
mgmtClusterName = fmt.Sprintf("test-mgmt-1m-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 1), "1Master 管理集群需要至少 1 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
mgmtClusterConfig = config.NewBKEClusterConfigForMgmt(mgmtClusterName, []config.NodeInfo{masterNode})
By("生成管理集群配置文件")
var err error
mgmtConfigPath, mgmtNodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(mgmtClusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成管理集群配置文件")
By("执行管理集群的 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(mgmtConfigPath, mgmtNodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "管理集群创建命令应该成功执行")
By("等待管理集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(mgmtClusterName, "")
GinkgoWriter.Printf("当前管理集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "DeployFailed" {
Fail(fmt.Sprintf("管理集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, mgmtInstallTimeout, pollInterval).Should(BeTrue(), "管理集群应该变为 Healthy 和 Ready 状态")
By("验证管理集群最终状态")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(mgmtClusterName, "")
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("最终管理集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "管理集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "管理集群状态应该是 Ready")
By("获取管理集群 kubeconfig")
mgmtChecker := utils.NewClusterCheckerWithParentKubeconfig(localExecutor, mgmtClusterName, "")
mgmtKubeconfigPath, err = mgmtChecker.SaveKubeconfigToFile()
Expect(err).NotTo(HaveOccurred(), "应该成功获取管理集群 kubeconfig")
GinkgoWriter.Printf("管理集群 kubeconfig 保存到: %s\n", mgmtKubeconfigPath)
By("准备业务集群配置并断开 worker 节点网络")
workloadClusterName = fmt.Sprintf("test-workload-1m1w-disconnect-worker-%d", time.Now().Unix())
Expect(len(nodes)).To(BeNumerically(">=", 3), "1Master1Worker 业务集群需要至少 3 个节点(管理集群使用节点 1,业务集群需要节点 2-3)")
workloadMasterNode := nodes[1]
workloadMasterNode.Role = []string{"master/node", "etcd"}
workerNode = nodes[2]
workerNode.Role = []string{"node"}
workloadClusterConfig = config.NewDefaultBKEClusterConfig(workloadClusterName, []config.NodeInfo{workloadMasterNode, workerNode})
_, err = DisconnectNetwork(workerNode, localExecutor)
Expect(err).NotTo(HaveOccurred(), "应该成功断开 worker 节点的网络")
})
AfterAll(func() {
if workerNode.IP != "" {
err := RestoreNetwork(workerNode, localExecutor, "")
if err != nil {
GinkgoWriter.Printf("恢复 worker 节点网络失败: %v\n", err)
}
}
By("清理业务集群")
if workloadClusterName != "" && mgmtKubeconfigPath != "" {
if clusterManager.ClusterExistsWithKubeconfig(workloadClusterName, mgmtKubeconfigPath) {
if err := clusterManager.DeleteClusterWithKubeconfig(workloadClusterName, mgmtKubeconfigPath); err != nil {
GinkgoWriter.Printf("删除业务集群失败: %v\n", err)
}
Eventually(func() bool {
return !clusterManager.ClusterExistsWithKubeconfig(workloadClusterName, mgmtKubeconfigPath)
}, uninstallTimeout, 10*time.Second).Should(BeTrue())
}
if workloadConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadConfigPath); err != nil {
GinkgoWriter.Printf("清理业务集群配置文件失败: %v\n", err)
}
}
if workloadNodeConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadNodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup workload node config file: %v\n", err)
}
}
}
By("清理管理集群")
mgmtChecker := utils.NewClusterCheckerWithParentKubeconfig(localExecutor, mgmtClusterName, "")
mgmtKubeconfigPathForDelete, err := mgmtChecker.SaveKubeconfigToFile()
if err != nil {
GinkgoWriter.Printf("[DEBUG] 获取管理集群 kubeconfig 失败: %v\n", err)
if mgmtKubeconfigPath != "" {
mgmtKubeconfigPathForDelete = mgmtKubeconfigPath
}
} else if mgmtKubeconfigPath != "" {
defer localExecutor.Exec(fmt.Sprintf("rm -f %s", mgmtKubeconfigPathForDelete))
mgmtKubeconfigPathForDelete = mgmtKubeconfigPath
}
if clusterManager.ClusterExistsWithKubeconfig(mgmtClusterName, "") {
By("在引导集群上触发管理集群删除")
err := clusterManager.DeleteClusterWithKubeconfig(mgmtClusterName, "")
if err != nil {
GinkgoWriter.Printf("在引导集群上触发管理集群删除失败: %v\n", err)
}
} else {
GinkgoWriter.Printf("[DEBUG] 引导集群上不存在管理集群 BC\n")
}
if mgmtKubeconfigPathForDelete != "" {
By("在管理集群上触发管理集群自身 BC 删除")
err := clusterManager.DeleteManagementClusterSelfBC(mgmtKubeconfigPathForDelete)
if err != nil {
GinkgoWriter.Printf("触发管理集群自身 BC 删除失败: %v\n", err)
}
}
By("等待管理集群完全删除")
Eventually(func() bool {
existsOnBootstrap := clusterManager.ClusterExistsWithKubeconfig(mgmtClusterName, "")
existsOnSelf := false
if mgmtKubeconfigPathForDelete != "" {
bcName := "bke-cluster"
namespace := "bke-cluster"
checkSelfCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get bc %s -n %s --no-headers 2>/dev/null", mgmtKubeconfigPathForDelete, bcName, namespace)
result, _ := localExecutor.Exec(checkSelfCmd)
existsOnSelf = result.ExitCode == 0 && strings.TrimSpace(result.Stdout) != ""
if !existsOnSelf {
GinkgoWriter.Printf("[DEBUG] 管理集群自身 BC (bke-cluster) 未找到: exit_code=%d\n", result.ExitCode)
}
}
if existsOnBootstrap || existsOnSelf {
if existsOnBootstrap {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(mgmtClusterName, "")
GinkgoWriter.Printf("[DEBUG] 引导集群上的管理集群 BC 仍存在: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
}
if existsOnSelf {
GinkgoWriter.Printf("[DEBUG] 管理集群自身 BC (bke-cluster) 在管理集群上仍存在\n")
}
return false
} else {
GinkgoWriter.Printf("[DEBUG] 管理集群 BC 资源已删除(引导集群和自身)\n")
return true
}
}, uninstallTimeout, 60*time.Second).Should(BeTrue(), "管理集群应该被完全删除")
existsOnBootstrap := clusterManager.ClusterExistsWithKubeconfig(mgmtClusterName, "")
existsOnSelf := false
if mgmtKubeconfigPathForDelete != "" {
bcName := "bke-cluster"
namespace := "bke-cluster"
checkSelfCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get bc %s -n %s --no-headers 2>/dev/null", mgmtKubeconfigPathForDelete, bcName, namespace)
result, _ := localExecutor.Exec(checkSelfCmd)
existsOnSelf = result.ExitCode == 0 && strings.TrimSpace(result.Stdout) != ""
}
if existsOnBootstrap || existsOnSelf {
By("超时后管理集群仍存在,在所有管理节点上执行强制重置")
nodes := config.LoadTestNodesFromEnv()
if len(nodes) >= 1 {
mgmtNodes := []config.NodeInfo{nodes[0]}
err := clusterManager.ForceResetManagementCluster(mgmtNodes, localExecutor)
if err != nil {
GinkgoWriter.Printf("强制重置管理集群失败: %v\n", err)
} else {
GinkgoWriter.Printf("在所有节点上完成管理集群强制重置\n")
}
} else {
GinkgoWriter.Printf("未找到管理节点,跳过强制重置\n")
}
}
if mgmtConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtConfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群配置文件失败: %v\n", err)
}
}
if mgmtNodeConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtNodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup mgmt node config file: %v\n", err)
}
}
if mgmtKubeconfigPath != "" {
if _, err := localExecutor.Exec(fmt.Sprintf("rm -f %s", mgmtKubeconfigPath)); err != nil {
GinkgoWriter.Printf("清理管理集群 kubeconfig 文件失败: %v\n", err)
}
}
})
It("当 worker 节点网络断开时应该成功创建 1Master1Worker 业务集群并保持 Healthy", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成业务集群配置文件")
var err error
workloadConfigPath, workloadNodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(workloadClusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成业务集群配置文件")
By("在管理集群上创建业务集群")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(workloadConfigPath, workloadNodeConfigPath, mgmtKubeconfigPath)
if err != nil {
namespace := ""
getNsCmd := fmt.Sprintf("yq eval '.metadata.namespace' %s", workloadConfigPath)
result, _ := sshExecutor.Exec(getNsCmd)
if result.ExitCode == 0 {
namespace = strings.TrimSpace(result.Stdout)
bcName := "bke-" + workloadClusterName
getBCCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get bc %s -n %s -o yaml 2>&1", mgmtKubeconfigPath, bcName, namespace)
result, _ := sshExecutor.Exec(getBCCmd)
GinkgoWriter.Printf("[DEBUG] 应用失败后的 BC 资源状态: exit_code=%d, stdout=%s, stderr=%s\n", result.ExitCode, result.Stdout, result.Stderr)
}
}
Expect(err).NotTo(HaveOccurred(), "业务集群创建命令应该成功执行")
By("等待业务集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(workloadClusterName, mgmtKubeconfigPath)
if err != nil {
GinkgoWriter.Printf("获取业务集群完整状态失败: %v, 当前状态: %s\n", err, state)
return false
} else {
GinkgoWriter.Printf("当前业务集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
}
if state == "DeployFailed" {
bcName := "bke-" + workloadClusterName
namespace := "bke-" + workloadClusterName
getBCCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get bc %s -n %s -o yaml 2>&1", mgmtKubeconfigPath, bcName, namespace)
result, _ := sshExecutor.Exec(getBCCmd)
if result.ExitCode == 0 {
GinkgoWriter.Printf("[DEBUG] === DeployFailed 时的 BC 资源 ===\n%s\n", result.Stdout)
}
getEventsCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get events -n %s --sort-by='.lastTimestamp' 2>&1 | tail -20", mgmtKubeconfigPath, namespace)
result, _ = sshExecutor.Exec(getEventsCmd)
if result.ExitCode == 0 {
GinkgoWriter.Printf("[DEBUG] === 最近的事件 ===\n%s\n", result.Stdout)
}
Fail(fmt.Sprintf("业务集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, installTimeout, pollInterval).Should(BeTrue(), "即使 worker 节点网络断开,业务集群也应该变为 Healthy 和 Ready 状态")
By("验证业务集群最终状态")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(workloadClusterName, mgmtKubeconfigPath)
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("最终业务集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "当 worker 节点网络断开时,业务集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "业务集群状态应该是 Ready")
})
})
Describe("创建 1Master 集群并使用无效的 Master 节点用户名密码", Label("1master", "invalid-credentials", "post-init"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-1m-invalid-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 1), "1Master 集群需要至少 1 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
masterNode.Password = "invalid-password"
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode})
})
AfterEach(func() {
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
if err := clusterManager.DeleteClusterWithKubeconfig(clusterName, ""); err != nil {
GinkgoWriter.Printf("删除集群失败: %v\n", err)
}
Eventually(func() bool {
return !clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
}, uninstallTimeout, 10*time.Second).Should(BeTrue())
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("清理配置文件失败: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
time.Sleep(60 * time.Second)
})
It("使用无效的 master 节点凭据应该无法创建集群", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成带有无效 master 节点凭据的集群配置")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("验证配置文件已上传")
err = clusterManager.GetConfigGenerator().ValidateConfigOnNode(configPath)
Expect(err).NotTo(HaveOccurred(), "配置文件应该存在于引导节点")
By("执行 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待并验证集群创建失败")
checkInterval := 30 * time.Second
checkTimeout := 10 * time.Minute
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "Healthy" {
Fail(fmt.Sprintf("集群意外变为 Healthy。预期集群创建失败,但集群状态为: %s", state))
}
return state == "DeployFailed" || clusterStatus == "InitializationFailed"
}, checkTimeout, checkInterval).Should(BeTrue(), "集群创建应该失败,状态为 DeployFailed 和 InitializationFailed")
finalState, err := clusterManager.GetClusterStatusWithKubeconfig(clusterName, "")
Expect(err).NotTo(HaveOccurred())
Expect(finalState).NotTo(Equal("Healthy"), "当 master 节点凭据无效时,集群不应该是 Healthy")
GinkgoWriter.Printf("最终集群状态: %s (预期失败,不是 Healthy)\n", finalState)
})
})
Describe("创建 1Master1Worker 集群并使用无效的 Worker 节点用户名密码", Label("1master1worker", "invalid-credentials", "post-init"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-1m1w-invalid-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 2), "1Master1Worker 集群需要至少 2 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
workerNode := nodes[1]
workerNode.Role = []string{"node"}
workerNode.Username = "invalid-user"
workerNode.Password = "invalid-password"
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode, workerNode})
})
AfterEach(func() {
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
if err := clusterManager.DeleteClusterWithKubeconfig(clusterName, ""); err != nil {
GinkgoWriter.Printf("Failed to delete cluster: %v\n", err)
}
Eventually(func() bool {
return !clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
}, uninstallTimeout, 10*time.Second).Should(BeTrue())
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("清理配置文件失败: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
})
It("即使使用无效的 worker 节点凭据也应该成功创建集群", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成带有无效 worker 节点凭据的集群配置")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("验证配置文件已上传")
err = clusterManager.GetConfigGenerator().ValidateConfigOnNode(configPath)
Expect(err).NotTo(HaveOccurred(), "配置文件应该存在于引导节点")
By("执行 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "DeployFailed" {
Fail(fmt.Sprintf("集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, installTimeout, pollInterval).Should(BeTrue(), "集群应该变为 Healthy 和 Ready 状态")
By("验证集群最终状态")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("最终集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "集群状态应该是 Ready")
})
})
Describe("创建 1Master 管理集群和 1Master1Worker 业务集群并使用无效的 Worker 节点用户名密码", Label("1m-mgmt-1m1w-workload", "invalid-credentials", "post-init"), Ordered, func() {
var (
mgmtClusterName string
mgmtClusterConfig *config.BKEClusterConfig
mgmtConfigPath string
mgmtNodeConfigPath string
mgmtKubeconfigPath string
workloadClusterName string
workloadClusterConfig *config.BKEClusterConfig
workloadConfigPath string
workloadNodeConfigPath string
)
BeforeAll(func() {
By("在引导集群上创建 1Master 管理集群")
mgmtClusterName = fmt.Sprintf("test-mgmt-1m-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 1), "1Master 管理集群需要至少 1 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
mgmtClusterConfig = config.NewBKEClusterConfigForMgmt(mgmtClusterName, []config.NodeInfo{masterNode})
By("生成管理集群配置文件")
var err error
mgmtConfigPath, mgmtNodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(mgmtClusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成管理集群配置文件")
By("执行管理集群的 bke cluster create 命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(mgmtConfigPath, mgmtNodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "管理集群创建命令应该成功执行")
By("等待管理集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(mgmtClusterName, "")
GinkgoWriter.Printf("当前管理集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "DeployFailed" {
Fail(fmt.Sprintf("管理集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, mgmtInstallTimeout, pollInterval).Should(BeTrue(), "管理集群应该变为 Healthy 和 Ready 状态")
By("验证管理集群最终状态")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(mgmtClusterName, "")
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("最终管理集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "管理集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "管理集群状态应该是 Ready")
By("获取管理集群 kubeconfig")
mgmtChecker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, mgmtClusterName, "")
mgmtKubeconfigPath, err = mgmtChecker.SaveKubeconfigToFileWithKubeconfig("")
Expect(err).NotTo(HaveOccurred(), "应该成功获取管理集群 kubeconfig")
GinkgoWriter.Printf("管理集群 kubeconfig 保存到: %s\n", mgmtKubeconfigPath)
})
AfterAll(func() {
By("清理业务集群")
if workloadClusterName != "" && mgmtKubeconfigPath != "" {
if clusterManager.ClusterExistsWithKubeconfig(workloadClusterName, mgmtKubeconfigPath) {
if err := clusterManager.DeleteClusterWithKubeconfig(workloadClusterName, mgmtKubeconfigPath); err != nil {
GinkgoWriter.Printf("删除业务集群失败: %v\n", err)
}
Eventually(func() bool {
return !clusterManager.ClusterExistsWithKubeconfig(workloadClusterName, mgmtKubeconfigPath)
}, uninstallTimeout, 10*time.Second).Should(BeTrue())
}
if workloadConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadConfigPath); err != nil {
GinkgoWriter.Printf("清理业务集群配置文件失败: %v\n", err)
}
}
if workloadNodeConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadNodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup workload node config file: %v\n", err)
}
}
}
By("清理管理集群")
mgmtChecker := utils.NewClusterCheckerWithParentKubeconfig(localExecutor, mgmtClusterName, "")
mgmtKubeconfigPathForDelete, err := mgmtChecker.SaveKubeconfigToFile()
if err != nil {
GinkgoWriter.Printf("[DEBUG] 获取管理集群 kubeconfig 失败: %v\n", err)
if mgmtKubeconfigPath != "" {
mgmtKubeconfigPathForDelete = mgmtKubeconfigPath
}
} else if mgmtKubeconfigPath != "" {
defer localExecutor.Exec(fmt.Sprintf("rm -f %s", mgmtKubeconfigPathForDelete))
mgmtKubeconfigPathForDelete = mgmtKubeconfigPath
}
if clusterManager.ClusterExistsWithKubeconfig(mgmtClusterName, "") {
By("在引导集群上触发管理集群删除")
err := clusterManager.DeleteClusterWithKubeconfig(mgmtClusterName, "")
if err != nil {
GinkgoWriter.Printf("在引导集群上触发管理集群删除失败: %v\n", err)
}
} else {
GinkgoWriter.Printf("[DEBUG] 引导集群上不存在管理集群 BC\n")
}
if mgmtKubeconfigPathForDelete != "" {
By("在管理集群上触发管理集群自身 BC 删除")
err := clusterManager.DeleteManagementClusterSelfBC(mgmtKubeconfigPathForDelete)
if err != nil {
GinkgoWriter.Printf("触发管理集群自身 BC 删除失败: %v\n", err)
}
}
By("等待管理集群完全删除")
Eventually(func() bool {
existsOnBootstrap := clusterManager.ClusterExistsWithKubeconfig(mgmtClusterName, "")
existsOnSelf := false
if mgmtKubeconfigPathForDelete != "" {
bcName := "bke-cluster"
namespace := "bke-cluster"
checkSelfCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get bc %s -n %s --no-headers 2>/dev/null", mgmtKubeconfigPathForDelete, bcName, namespace)
result, _ := localExecutor.Exec(checkSelfCmd)
existsOnSelf = result.ExitCode == 0 && strings.TrimSpace(result.Stdout) != ""
if !existsOnSelf {
GinkgoWriter.Printf("[DEBUG] 管理集群自身 BC (bke-cluster) 未找到: exit_code=%d\n", result.ExitCode)
}
}
if existsOnBootstrap || existsOnSelf {
if existsOnBootstrap {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(mgmtClusterName, "")
GinkgoWriter.Printf("[DEBUG] 引导集群上的管理集群 BC 仍存在: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
}
if existsOnSelf {
GinkgoWriter.Printf("[DEBUG] 管理集群自身 BC (bke-cluster) 在管理集群上仍存在\n")
}
return false
} else {
GinkgoWriter.Printf("[DEBUG] 管理集群 BC 资源已删除(引导集群和自身)\n")
return true
}
}, uninstallTimeout, 60*time.Second).Should(BeTrue(), "管理集群应该被完全删除")
existsOnBootstrap := clusterManager.ClusterExistsWithKubeconfig(mgmtClusterName, "")
existsOnSelf := false
if mgmtKubeconfigPathForDelete != "" {
bcName := "bke-cluster"
namespace := "bke-cluster"
checkSelfCmd := fmt.Sprintf("KUBECONFIG=%s kubectl get bc %s -n %s --no-headers 2>/dev/null", mgmtKubeconfigPathForDelete, bcName, namespace)
result, _ := localExecutor.Exec(checkSelfCmd)
existsOnSelf = result.ExitCode == 0 && strings.TrimSpace(result.Stdout) != ""
}
if existsOnBootstrap || existsOnSelf {
By("超时后管理集群仍存在,在所有管理节点上执行强制重置")
nodes := config.LoadTestNodesFromEnv()
if len(nodes) >= 1 {
mgmtNodes := []config.NodeInfo{nodes[0]}
err := clusterManager.ForceResetManagementCluster(mgmtNodes, localExecutor)
if err != nil {
GinkgoWriter.Printf("强制重置管理集群失败: %v\n", err)
} else {
GinkgoWriter.Printf("在所有节点上完成管理集群强制重置\n")
}
} else {
GinkgoWriter.Printf("未找到管理节点,跳过强制重置\n")
}
}
if mgmtConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtConfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群配置文件失败: %v\n", err)
}
}
if mgmtNodeConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtNodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup mgmt node config file: %v\n", err)
}
}
if mgmtKubeconfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtKubeconfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群 kubeconfig 文件失败: %v\n", err)
}
}
})
It("在管理集群上使用无效的 worker 节点凭据应该成功创建 1Master1Worker 业务集群", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("创建带有无效 worker 节点凭据的 1Master1Worker 业务集群配置")
workloadClusterName = fmt.Sprintf("test-workload-1m1w-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 3), "1Master1Worker 业务集群需要至少 3 个节点(管理集群使用节点 1,业务集群需要节点 2-3)")
masterNode := nodes[1]
masterNode.Role = []string{"master/node", "etcd"}
workerNode := nodes[2]
workerNode.Role = []string{"node"}
workerNode.Username = "invalid-user"
workerNode.Password = "invalid-password"
workloadClusterConfig = config.NewDefaultBKEClusterConfig(workloadClusterName, []config.NodeInfo{masterNode, workerNode})
By("生成业务集群配置文件")
var err error
workloadConfigPath, workloadNodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(workloadClusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成业务集群配置文件")
By("在管理集群上创建业务集群")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(workloadConfigPath, workloadNodeConfigPath, mgmtKubeconfigPath)
Expect(err).NotTo(HaveOccurred(), "业务集群创建命令应该成功执行")
By("等待业务集群状态变为 Healthy 和 Ready")
Eventually(func() bool {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(workloadClusterName, mgmtKubeconfigPath)
GinkgoWriter.Printf("当前业务集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
if state == "DeployFailed" {
Fail(fmt.Sprintf("业务集群创建失败,状态为 DeployFailed。Phase: %s, clusterStatus: %s", phase, clusterStatus))
}
return state == "Healthy" && clusterStatus == "Ready"
}, installTimeout, pollInterval).Should(BeTrue(), "业务集群应该变为 Healthy 和 Ready 状态")
By("验证业务集群最终状态")
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(workloadClusterName, mgmtKubeconfigPath)
Expect(err).NotTo(HaveOccurred())
GinkgoWriter.Printf("最终业务集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
Expect(state).To(Equal("Healthy"), "业务集群状态应该是 Healthy")
Expect(clusterStatus).To(Equal("Ready"), "业务集群状态应该是 Ready")
})
})
Describe("创建 3Master1Worker 管理集群和业务集群并使用自定义证书签名", Label("3m1w-custom", "certificate", "skip-temporarily"), Ordered, func() {
var (
mgmtCluster *Cluster3M1WConfig
workloadCluster *Cluster3M1WConfig
certGenerator *utils.CertificateConfigGenerator
)
BeforeAll(func() {
nodes := config.LoadTestNodesFromEnv()
if len(nodes) < 8 {
Skip(fmt.Sprintf("需要至少 8 个节点(管理集群 4 个 + 业务集群 4 个),当前 %d 个,跳过", len(nodes)))
}
By("在引导节点上生成证书链文件")
err := utils.GenerateCertificateChainOnRemote(sshExecutor, "/etc/openFuyao/certs")
Expect(err).NotTo(HaveOccurred(), "应该成功生成证书链文件")
GinkgoWriter.Printf("证书链文件在引导节点上生成于: %s\n", "/etc/openFuyao/certs")
By("在引导节点上生成证书配置")
certConfigDir := "/etc/openFuyao/certs/cert_config"
certGenerator = utils.NewCertificateConfigGenerator(certConfigDir)
err = certGenerator.GenerateAllCertificateConfigs()
Expect(err).NotTo(HaveOccurred(), "应该成功生成证书配置")
GinkgoWriter.Printf("证书配置在引导节点上生成于: %s\n", certConfigDir)
var err2 error
mgmtCluster, err2 = Create3M1WManagementCluster(
clusterManager,
localExecutor,
"test-mgmt-3m1w-cert",
true,
)
Expect(err2).NotTo(HaveOccurred(), "应该成功创建管理集群")
workloadCluster, err2 = Create3M1WWorkloadCluster(
clusterManager,
localExecutor,
mgmtCluster.KubeconfigPath,
"test-workload-3m1w-cert",
)
Expect(err2).NotTo(HaveOccurred(), "应该成功创建业务集群")
})
It("应该验证所有 master 节点的证书签名", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
nodes := config.LoadTestNodesFromEnv()
if len(nodes) < 8 {
Skip(fmt.Sprintf("需要至少 8 个节点(管理集群 4 个 + 业务集群 4 个),当前 %d 个,跳过", len(nodes)))
}
By("验证管理集群所有 master 节点的证书签名")
for i := 0; i < 3; i++ {
masterNode := nodes[i]
By(fmt.Sprintf("验证管理集群 master 节点 %d (%s) 的证书签名", i+1, masterNode.IP))
verifier := utils.NewCertificateVerifierWithSSH("/etc/kubernetes/pki", localExecutor, masterNode.IP, masterNode.Username, masterNode.Password)
results, err := verifier.VerifyCertificateChain()
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("应该成功验证管理集群 master 节点 %d 的证书链", i+1))
GinkgoWriter.Printf("=== 管理集群 Master 节点 %d (%s) 证书验证结果 ===\n", i+1, masterNode.IP)
verifier.PrintResults(results)
for _, item := range results.Items {
if !item.Success {
GinkgoWriter.Printf("管理集群 master 节点 %d 证书验证失败: %s - %v\n", i+1, item.Name, item.Error)
}
Expect(item.Success).To(BeTrue(), fmt.Sprintf("管理集群 master 节点 %d 证书验证应该成功: %s", i+1, item.Name))
}
}
By("验证业务集群所有 master 节点的证书签名")
for i := 4; i < 7; i++ {
masterNode := nodes[i]
By(fmt.Sprintf("验证业务集群 master 节点 %d (%s) 的证书签名", i+1, masterNode.IP))
verifier := utils.NewCertificateVerifierWithSSH("/etc/kubernetes/pki", localExecutor, masterNode.IP, masterNode.Username, masterNode.Password)
results, err := verifier.VerifyCertificateChain()
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("应该成功验证业务集群 master 节点 %d 的证书链", i+1))
GinkgoWriter.Printf("=== 业务集群 Master 节点 %d (%s) 证书验证结果 ===\n", i+1, masterNode.IP)
verifier.PrintResults(results)
for _, item := range results.Items {
if !item.Success {
GinkgoWriter.Printf("业务集群 master 节点 %d 证书验证失败: %s - %v\n", i+1, item.Name, item.Error)
}
Expect(item.Success).To(BeTrue(), fmt.Sprintf("业务集群 master 节点 %d 证书验证应该成功: %s", i+1, item.Name))
}
}
})
AfterAll(func() {
if workloadCluster != nil && mgmtCluster != nil {
Delete3M1WWorkloadCluster(
clusterManager,
localExecutor,
workloadCluster.ClusterName,
mgmtCluster.KubeconfigPath,
)
if workloadCluster.ConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadCluster.ConfigPath); err != nil {
GinkgoWriter.Printf("清理业务集群配置文件失败: %v\n", err)
}
}
if workloadCluster.NodeConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadCluster.NodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup workload node config file: %v\n", err)
}
}
}
if mgmtCluster != nil {
Delete3M1WManagementCluster(
clusterManager,
localExecutor,
mgmtCluster.ClusterName,
mgmtCluster.KubeconfigPath,
)
if mgmtCluster.ConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtCluster.ConfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群配置文件失败: %v\n", err)
}
}
if mgmtCluster.NodeConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtCluster.NodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup mgmt node config file: %v\n", err)
}
}
if mgmtCluster.KubeconfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtCluster.KubeconfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群 kubeconfig 文件失败: %v\n", err)
}
}
}
By("从引导节点清理证书目录")
if err := clusterManager.CleanupPathRecursive("/etc/openFuyao/certs"); err != nil {
GinkgoWriter.Printf("清理证书目录失败: %v\n", err)
}
})
})
Describe("创建 3Master1Worker 管理集群和业务集群", Label("3m1w-no-custom", "certificate", "skip-temporarily"), Ordered, func() {
var (
mgmtCluster *Cluster3M1WConfig
workloadCluster *Cluster3M1WConfig
)
BeforeAll(func() {
nodes := config.LoadTestNodesFromEnv()
if len(nodes) < 8 {
Skip(fmt.Sprintf("需要至少 8 个节点(管理集群 4 个 + 业务集群 4 个),当前 %d 个,跳过", len(nodes)))
}
By("创建 3Master1Worker 管理集群")
var err error
mgmtCluster, err = Create3M1WManagementCluster(
clusterManager,
localExecutor,
"test-mgmt-3m1w",
true,
)
Expect(err).NotTo(HaveOccurred(), "应该成功创建管理集群")
By("创建 3Master1Worker 业务集群")
workloadCluster, err = Create3M1WWorkloadCluster(
clusterManager,
localExecutor,
mgmtCluster.KubeconfigPath,
"test-workload-3m1w-basic",
)
Expect(err).NotTo(HaveOccurred(), "应该成功创建业务集群")
})
It("应该验证管理集群和业务集群是健康的", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("验证管理集群健康状态")
Eventually(func() string {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatus(mgmtCluster.ClusterName)
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, mgmtInstallTimeout, pollInterval).Should(Equal("Healthy"))
By("验证业务集群健康状态")
Eventually(func() string {
phase, state, clusterStatus, _ := clusterManager.GetClusterFullStatusWithKubeconfig(workloadCluster.ClusterName, mgmtCluster.KubeconfigPath)
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"))
GinkgoWriter.Printf("=== 管理集群和业务集群都是健康的 ===\n")
GinkgoWriter.Printf("管理集群: %s\n", mgmtCluster.ClusterName)
GinkgoWriter.Printf("业务集群: %s\n", workloadCluster.ClusterName)
})
It("应该验证所有 master 节点的 kubeconfig 挂载", SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
nodes := config.LoadTestNodesFromEnv()
if len(nodes) < 8 {
Skip(fmt.Sprintf("需要至少 8 个节点(管理集群 4 个 + 业务集群 4 个),当前 %d 个,跳过", len(nodes)))
}
By("验证管理集群所有 master 节点的 kubeconfig 挂载")
for i := 0; i < 3; i++ {
masterNode := nodes[i]
By(fmt.Sprintf("验证管理集群 master 节点 %d (%s) 的 kubeconfig 挂载", i+1, masterNode.IP))
verifier := utils.NewCertificateVerifierWithSSH("/etc/kubernetes/pki", localExecutor, masterNode.IP, masterNode.Username, masterNode.Password)
results, err := verifier.VerifyKubeconfigMount()
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("应该成功验证管理集群 master 节点 %d 的 kubeconfig 挂载", i+1))
GinkgoWriter.Printf("=== 管理集群 Master 节点 %d (%s) Kubeconfig 挂载验证结果 ===\n", i+1, masterNode.IP)
verifier.PrintResults(results)
for _, item := range results.Items {
if !item.Success {
GinkgoWriter.Printf("管理集群 master 节点 %d kubeconfig 挂载验证失败: %s - %v\n", i+1, item.Name, item.Error)
}
Expect(item.Success).To(BeTrue(), fmt.Sprintf("管理集群 master 节点 %d kubeconfig 挂载验证应该成功: %s", i+1, item.Name))
}
}
By("验证业务集群所有 master 节点的 kubeconfig 挂载")
for i := 4; i < 7; i++ {
masterNode := nodes[i]
By(fmt.Sprintf("验证业务集群 master 节点 %d (%s) 的 kubeconfig 挂载", i+1, masterNode.IP))
verifier := utils.NewCertificateVerifierWithSSH("/etc/kubernetes/pki", localExecutor, masterNode.IP, masterNode.Username, masterNode.Password)
results, err := verifier.VerifyKubeconfigMount()
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("应该成功验证业务集群 master 节点 %d 的 kubeconfig 挂载", i+1))
GinkgoWriter.Printf("=== 业务集群 Master 节点 %d (%s) Kubeconfig 挂载验证结果 ===\n", i+1, masterNode.IP)
verifier.PrintResults(results)
for _, item := range results.Items {
if !item.Success {
GinkgoWriter.Printf("业务集群 master 节点 %d kubeconfig 挂载验证失败: %s - %v\n", i+1, item.Name, item.Error)
}
Expect(item.Success).To(BeTrue(), fmt.Sprintf("业务集群 master 节点 %d kubeconfig 挂载验证应该成功: %s", i+1, item.Name))
}
}
})
AfterAll(func() {
if workloadCluster != nil && mgmtCluster != nil {
Delete3M1WWorkloadCluster(
clusterManager,
localExecutor,
workloadCluster.ClusterName,
mgmtCluster.KubeconfigPath,
)
if workloadCluster.ConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadCluster.ConfigPath); err != nil {
GinkgoWriter.Printf("清理业务集群配置文件失败: %v\n", err)
}
}
if workloadCluster.NodeConfigPath != "" {
if err := clusterManager.CleanupConfig(workloadCluster.NodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup workload node config file: %v\n", err)
}
}
}
if mgmtCluster != nil {
Delete3M1WManagementCluster(
clusterManager,
localExecutor,
mgmtCluster.ClusterName,
mgmtCluster.KubeconfigPath,
)
if mgmtCluster.ConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtCluster.ConfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群配置文件失败: %v\n", err)
}
}
if mgmtCluster.NodeConfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtCluster.NodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup mgmt node config file: %v\n", err)
}
}
if mgmtCluster.KubeconfigPath != "" {
if err := clusterManager.CleanupConfig(mgmtCluster.KubeconfigPath); err != nil {
GinkgoWriter.Printf("清理管理集群 kubeconfig 文件失败: %v\n", err)
}
}
}
})
})
Describe("节点标签功能", Label("node-labels", "post-init"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-labels-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 2), "节点标签亲和性测试至少需要 2 个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
masterNode.Labels = []config.NodeLabel{
{Key: "test-label", Value: "e2e-test"},
{Key: "environment", Value: "testing"},
}
workerNode := nodes[1]
workerNode.Role = []string{"node"}
workerNode.Labels = []config.NodeLabel{
{Key: "addon-node", Value: "redis"},
}
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode, workerNode})
})
AfterEach(func() {
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
if err := clusterManager.DeleteClusterWithKubeconfig(clusterName, ""); err != nil {
GinkgoWriter.Printf("Failed to delete cluster: %v\n", err)
}
Eventually(func() bool {
return !clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
}, uninstallTimeout, 10*time.Second).Should(BeTrue())
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup config file: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
})
It("应该成功创建集群并验证节点标签", Label("node-labels-basic"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证节点标签")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Eventually(func() bool {
hasLabel, _ := checker.CheckNodeHasLabel("test-label", "e2e-test")
return hasLabel
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), "Master 节点应该有 test-label 标签")
Eventually(func() bool {
hasLabel, _ := checker.CheckNodeHasLabel("addon-node", "redis")
return hasLabel
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), "Worker 节点应该有 addon-node 标签")
})
It("应该支持多标签并验证 addon 亲和性部署", Label("node-labels-addon-affinity"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "redis",
Version: "6.2.12",
Block: false,
NodeSelector: map[string]string{
"addon-node": "redis",
},
})
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证 redis addon Pod 运行在带有正确标签的节点上")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Eventually(func() bool {
nodeName, err := checker.GetPodNodeName("", "app.kubernetes.io/name=redis")
if err != nil || nodeName == "" {
return false
}
GinkgoWriter.Printf("redis Pod 运行在节点: %s\n", nodeName)
hasLabel, _ := checker.CheckNodeHasLabel("addon-node", "redis")
return hasLabel
}, 5*time.Minute, 15*time.Second).Should(BeTrue(), "redis 应该部署在带有 addon-node=redis 标签的节点上")
})
It("应该支持 addon 未设置亲和性时的默认部署", Label("node-labels-no-affinity"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "redis",
Version: "6.2.12",
})
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证 redis addon Pod 正常部署")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Eventually(func() bool {
nodeName, _ := checker.GetPodNodeName("", "app.kubernetes.io/name=redis")
return nodeName != ""
}, 5*time.Minute, 15*time.Second).Should(BeTrue(), "未设置亲和性时,redis 应该能正常部署在任意节点")
})
It("应该在 addon 标签不匹配时不部署至节点", Label("node-labels-mismatch"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "redis",
Version: "6.2.12",
NodeSelector: map[string]string{
"non-existent-key": "some-value",
},
})
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证 redis addon Pod 未被调度")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Consistently(func() bool {
nodeName, _ := checker.GetPodNodeName("", "app.kubernetes.io/name=redis")
return nodeName == ""
}, 1*time.Minute, 10*time.Second).Should(BeTrue(), "标签不匹配时,redis Pod 不应该被调度到任何节点")
})
It("应该忽略包含非法字符的节点标签", Label("node-labels-invalid"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.Nodes[1].Labels = append(clusterConfig.Nodes[1].Labels,
config.NodeLabel{Key: "invalid-label", Value: "!!!#@$"},
)
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
GinkgoWriter.Printf("生成的集群配置文件: %s, 节点配置文件: %s\n", configPath, nodeConfigPath)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证非法标签未被应用")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Eventually(func() bool {
hasLabel, _ := checker.CheckNodeHasLabel("invalid-label", "!!!#@$")
return hasLabel
}, 1*time.Minute, 10*time.Second).Should(BeFalse(), "非法字符的标签不应该存在于节点中")
})
})
Describe("Addon 扩展组件安装", Label("addon-installation", "post-init"), func() {
var (
clusterName string
clusterConfig *config.BKEClusterConfig
configPath string
nodeConfigPath string
)
BeforeEach(func() {
clusterName = fmt.Sprintf("test-addon-%d", time.Now().Unix())
nodes := config.LoadTestNodesFromEnv()
Expect(len(nodes)).To(BeNumerically(">=", 1), "至少需要一个节点")
masterNode := nodes[0]
masterNode.Role = []string{"master/node", "etcd"}
clusterConfig = config.NewDefaultBKEClusterConfig(clusterName, []config.NodeInfo{masterNode})
})
AfterEach(func() {
By("清理测试集群")
if clusterManager.ClusterExistsWithKubeconfig(clusterName, "") {
if err := clusterManager.DeleteClusterWithKubeconfig(clusterName, ""); err != nil {
GinkgoWriter.Printf("Failed to delete cluster: %v\n", err)
}
Eventually(func() bool {
return !clusterManager.ClusterExistsWithKubeconfig(clusterName, "")
}, uninstallTimeout, 10*time.Second).Should(BeTrue())
}
if configPath != "" {
if err := clusterManager.CleanupConfig(configPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup config file: %v\n", err)
}
}
if nodeConfigPath != "" {
if err := clusterManager.CleanupConfig(nodeConfigPath); err != nil {
GinkgoWriter.Printf("Failed to cleanup node config file: %v\n", err)
}
}
})
It("应该支持从https格式仓库安装chart", Label("install-chart-from-https-repo", "skip-temporarily"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "nginx",
Version: "22.6.1",
Type: "chart",
ReleaseName: "nginx",
Namespace: "nginx",
})
clusterConfig.ChartRepo = config.BitnamiChartRepoConfig()
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证nginx组件")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Eventually(func() bool {
allReady, notReady, err := checker.CheckNamespacePodsReadyWithKubeconfig("nginx", "")
if err != nil {
return false
}
if !allReady {
GinkgoWriter.Printf("等待 nginx Pod 就绪: %s\n", notReady)
return false
}
return true
}, 5*time.Minute, 10*time.Second).Should(BeTrue(), "nginx 命名空间下的 Pod 应该全部就绪")
})
It("应该支持从http格式仓库安装chart", Label("install-chart-from-http-repo"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
hasPluginManagementService := false
for _, addon := range clusterConfig.Addons {
if addon.Name == "plugin-management-service" || addon.Name == "openfuyao-system-controller" {
hasPluginManagementService = true
}
}
if !hasPluginManagementService {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "plugin-management-service",
Version: "0.0.0-latest",
ReleaseName: "plugin-management",
Namespace: "openfuyao-system",
Type: "chart",
})
}
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "logging-package",
Version: "0.0.0-latest",
ReleaseName: "logging",
Type: "chart",
Namespace: "logging",
})
clusterConfig.ChartRepo = config.GuideNodeChartRepoConfig()
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证日志组件")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
for _, component := range []string{"proxy-logging"} {
Eventually(func() bool {
for _, labelKey := range []string{"app"} {
nodeName, _ := checker.GetPodNodeName("logging", fmt.Sprintf("%s=%s", labelKey, component))
if nodeName != "" {
return true
}
}
return false
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), fmt.Sprintf("应该能找到 %s 组件", component))
}
for _, component := range []string{"loki"} {
Eventually(func() bool {
for _, labelKey := range []string{"app", "name"} {
nodeName, _ := checker.GetPodNodeName("loki", fmt.Sprintf("%s=%s", labelKey, component))
if nodeName != "" {
return true
}
}
return false
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), fmt.Sprintf("应该能找到 %s 组件", component))
}
for _, component := range []string{"logging-operator"} {
Eventually(func() bool {
for _, labelKey := range []string{"app"} {
nodeName, _ := checker.GetPodNodeName("openfuyao-system", fmt.Sprintf("%s=%s", labelKey, component))
if nodeName != "" {
return true
}
}
return false
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), fmt.Sprintf("应该能找到 %s 组件", component))
}
})
It("配置chart仓库不存在的chart", Label("set-chart-repo-not-exist-chart"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "elon-musk",
Version: "0.0.0-elon-musk",
ReleaseName: "elon-musk",
Namespace: "elon-musk-1",
Type: "chart",
})
clusterConfig.ChartRepo = config.OpenFuyaoChartRepoConfig()
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证组件是否安装")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
_, notReady, _ := checker.CheckNamespacePodsReadyWithKubeconfig("elon-musk-1", "")
Expect(notReady).Should(Equal("no pods found"), "elon-musk-1 命名空间下应该无Pod")
})
It("配置异常chart仓库应该安装集群失败", Label("set-err-chart-repo-install-cluster-failed"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "plugin-management-service",
Version: "0.0.0-latest",
ReleaseName: "plugin-management",
Namespace: "openfuyao-system",
Type: "chart",
})
clusterConfig.ChartRepo = &config.ChartRepoConfig{
Domain: "my.chart.repo.elon.musk",
IP: "192.168.1",
Port: "443z",
Prefix: "",
}
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该未报错,但是未创建集群")
By("获取bc")
_, err = clusterManager.GetBkeCluster(clusterConfig.ClusterName, clusterConfig.Namespace)
Expect(err).To(HaveOccurred(), "应该不能获取到bc")
})
It("应该支持安装chart包时配置values.yaml", Label("set-values-yaml-and-install-chart"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
valuesYaml := `
logging-operator:
namespace:
backend: logging
images:
core:
repository: "cr.openfuyao.cn/openfuyao/logging-operator"
pullPolicy: Always
loki-stack:
namespace: loki
promtail:
defaultVolumes:
- name: containers
hostPath:
path: /var/lib/docker/containers
- name: pods
hostPath:
path: /var/log/pods
- name: random
hostPath:
path: /var/log/random
defaultVolumeMounts:
- name: containers
mountPath: /var/lib/docker/containers
readOnly: true
- name: pods
mountPath: /var/log/pods
readOnly: true
- name: random
mountPath: /var/log/random
readOnly: true
loki:
config:
limits_config:
reject_old_samples_max_age: 168h
table_manager:
retention_deletes_enabled: true
retention_period: 240h
alerting_groups:
- name: GenericHighErrorRate
rules:
- alert: GenericHighErrorRate
expr: |
sum by (job, instance) (rate({job=~".+"} |= "error" [5m])) > 0
for: 5m
labels:
severity: critical
loki: logging/k8s.io
annotations:
summary: "High error rate detected in {{ $labels.job }} instance {{ $labels.instance }}"
description: "Job {{ $labels.job }} on instance {{ $labels.instance }} has a high rate of error logs."
- name: GenericExceptionDetected
rules:
- alert: GenericExceptionDetected
expr: |
sum by (job, instance) (rate({job=~".+"} |= "Exception" [5m])) > 0
for: 5m
labels:
severity: warning
loki: logging/k8s.io
annotations:
summary: "Exception detected in {{ $labels.job }} instance {{ $labels.instance }}"
description: "Job {{ $labels.job }} on instance {{ $labels.instance }} has logs containing 'Exception'."
- name: GenericLogVolumeSpike
rules:
- alert: GenericLogVolumeSpike
expr: |
sum by (job, instance) (rate({job=~".+"}[5m])) > 1
for: 5m
labels:
severity: warning
loki: logging/k8s.io
annotations:
summary: "Log volume spike detected in {{ $labels.job }} instance {{ $labels.instance }}"
description: "Job {{ $labels.job }} on instance {{ $labels.instance }} has a spike in log volume."
- name: GenericErrorRateThresholdExceeded
rules:
- alert: GenericErrorRateThresholdExceeded
expr: |
(sum by (job, instance) (rate({job=~".+"} |= "error" [5m])) / sum by (job, instance) (rate({job=~".+"}[5m]))) > 0.05
for: 5m
labels:
severity: critical
loki: logging/k8s.io
annotations:
summary: "High error rate threshold exceeded in {{ $labels.job }} instance {{ $labels.instance }}"
description: "Job {{ $labels.job }} on instance {{ $labels.instance }} has an error rate exceeding the threshold."
- name: GenericServiceUnavailable
rules:
- alert: GenericServiceUnavailable
expr: |
sum by (job, instance) (rate({job=~".+"} |= "service unavailable" [5m])) > 0
for: 5m
labels:
severity: critical
loki: logging/k8s.io
annotations:
summary: "Service unavailable detected in {{ $labels.job }} instance {{ $labels.instance }}"
description: "Job {{ $labels.job }} on instance {{ $labels.instance }} has logs indicating 'service unavailable'."
- name: GenericApplicationStartupFailure
rules:
- alert: GenericApplicationStartupFailure
expr: |
sum by (job, instance) (rate({job=~".+"} |= "startup failure" [5m])) > 0
for: 5m
labels:
severity: critical
loki: logging/k8s.io
annotations:
summary: "Application startup failure detected in {{ $labels.job }} instance {{ $labels.instance }}"
description: "Job {{ $labels.job }} on instance {{ $labels.instance }} has logs indicating 'startup failure'."
proxy-logging:
namespace: logging
enableOAuth: true
images:
core:
repository: "cr.openfuyao.cn/openfuyao/logging-website"
pullPolicy: Always
oauth:
repository: "cr.openfuyao.cn/openfuyao/oauth-proxy"
pullPolicy: Always
`
cmNamespace := fmt.Sprintf("elon-%s", clusterConfig.Namespace)
cmName := fmt.Sprintf("elon-%s", clusterName)
err := resourceManager.CreateConfigMap(cmName, cmNamespace, "values.yaml", valuesYaml)
Expect(err).NotTo(HaveOccurred(), "创建values.yaml configMap失败")
hasPluginManagementService := false
for _, addon := range clusterConfig.Addons {
if addon.Name == "plugin-management-service" || addon.Name == "openfuyao-system-controller" {
hasPluginManagementService = true
}
}
if !hasPluginManagementService {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "plugin-management-service",
Version: "0.0.0-latest",
ReleaseName: "plugin-management",
Namespace: "openfuyao-system",
Type: "chart",
Block: true,
})
}
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "logging-package",
Version: "1.0.3",
Namespace: "logging",
ReleaseName: "logging",
Type: "chart",
ValuesConfigMapRef: &config.ValuesConfigMapRef{
Name: cmName,
Namespace: cmNamespace,
},
})
clusterConfig.ChartRepo = config.OpenFuyaoChartRepoConfig()
By("生成集群配置文件")
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("验证日志组件")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
Eventually(func() bool {
allReady, notReady, err := checker.CheckNamespacePodsReadyWithKubeconfig("loki", "")
if err != nil {
return false
}
if !allReady {
GinkgoWriter.Printf("等待 loki Pod 就绪: %s\n", notReady)
return false
}
return true
}, 5*time.Minute, 10*time.Second).Should(BeTrue(), "loki 命名空间下的 Pod 应该全部就绪")
for _, component := range []string{"proxy-logging", "logging-operator"} {
Eventually(func() bool {
for _, labelKey := range []string{"app"} {
nodeName, _ := checker.GetPodNodeName("logging", fmt.Sprintf("%s=%s", labelKey, component))
if nodeName != "" {
return true
}
}
return false
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), fmt.Sprintf("应该能找到 %s 组件", component))
}
})
It("应该支持从oci格式chart仓库安装、升级、卸载 chart", Label("install-upgrade-delete-chart-from-oci-repo"), SpecTimeout(InstallationItTimeout), func(ctx SpecContext) {
hasPluginManagementService := false
for _, addon := range clusterConfig.Addons {
if addon.Name == "plugin-management-service" || addon.Name == "openfuyao-system-controller" {
hasPluginManagementService = true
}
}
if !hasPluginManagementService {
clusterConfig.ExtraAddons = append(clusterConfig.ExtraAddons, config.AddonConfig{
Name: "plugin-management-service",
Version: "0.0.0-latest",
ReleaseName: "plugin-management",
Namespace: "openfuyao-system",
Type: "chart",
Block: true,
})
}
clusterConfig.ChartRepo = config.OpenFuyaoChartRepoConfig()
By("生成集群配置文件")
var err error
configPath, nodeConfigPath, err = clusterManager.GetConfigGenerator().GenerateAndUpload(clusterConfig)
Expect(err).NotTo(HaveOccurred(), "应该成功生成配置文件")
By("执行集群创建命令")
err = clusterManager.CreateClusterInBackgroundWithKubeconfig(configPath, nodeConfigPath, "")
Expect(err).NotTo(HaveOccurred(), "创建集群命令应该成功执行")
By("等待集群状态变为 Healthy")
Eventually(func() string {
phase, state, clusterStatus, err := clusterManager.GetClusterFullStatusWithKubeconfig(clusterName, "")
if err != nil {
GinkgoWriter.Printf("获取集群状态失败: %v\n", err)
return ""
}
GinkgoWriter.Printf("当前集群状态: phase=%s, state=%s, clusterStatus=%s\n", phase, state, clusterStatus)
failOnClusterFailure(state, clusterStatus)
return state
}, installTimeout, pollInterval).Should(Equal("Healthy"), "集群应该变为 Healthy 状态")
By("安装日志组件")
logAddon := config.AddonConfig{
Name: "logging-package",
Version: "1.0.2",
ReleaseName: "logging",
Type: "chart",
Namespace: "logging",
}
err = clusterManager.AddNewAddons(clusterName, clusterConfig.Namespace, logAddon)
Expect(err).NotTo(HaveOccurred(), "已经添加日志组件")
By("验证日志组件")
checker := utils.NewClusterCheckerWithParentKubeconfig(sshExecutor, clusterName, "")
By("验证logging-package组件")
Eventually(func() bool {
allReady, notReady, err := checker.CheckNamespacePodsReadyWithKubeconfig("logging", "")
if err != nil {
return false
}
if !allReady {
GinkgoWriter.Printf("等待 logging-package Pod 就绪: %s\n", notReady)
return false
}
return true
}, 10*time.Minute, 10*time.Second).Should(BeTrue(), "logging 命名空间下的 Pod 应该全部就绪")
By("验证openfuyao-system命名空间下的组件")
Eventually(func() bool {
allReady, notReady, err := checker.CheckNamespacePodsReadyWithKubeconfig("openfuyao-system", "")
if err != nil {
return false
}
if !allReady {
GinkgoWriter.Printf("等待 openfuyao-system Pod 就绪: %s\n", notReady)
return false
}
return true
}, 5*time.Minute, 10*time.Second).Should(BeTrue(), "openfuyao-system 命名空间下的 Pod 应该全部就绪")
By("验证loki组件")
Eventually(func() bool {
allReady, notReady, err := checker.CheckNamespacePodsReadyWithKubeconfig("loki", "")
if err != nil {
return false
}
if !allReady {
GinkgoWriter.Printf("等待 loki Pod 就绪: %s\n", notReady)
return false
}
return true
}, 5*time.Minute, 10*time.Second).Should(BeTrue(), "loki 命名空间下的 Pod 应该全部就绪")
for _, component := range []string{"proxy-logging"} {
Eventually(func() bool {
for _, labelKey := range []string{"app"} {
nodeName, _ := checker.GetPodNodeName("logging", fmt.Sprintf("%s=%s", labelKey, component))
if nodeName != "" {
return true
}
}
return false
}, 2*time.Minute, 10*time.Second).Should(BeTrue(), fmt.Sprintf("应该能找到 %s 组件", component))
}
By("修改日志组件版本号")
err = clusterManager.PatchBkeClusterAddonVersion(clusterName, clusterConfig.Namespace,
"logging-package", "1.0.3")
Expect(err).NotTo(HaveOccurred(), "日志组件版本号修改成功")
Eventually(func() bool {
allReady, notReady, err := checker.CheckNamespacePodsReadyWithKubeconfig("openfuyao-system", "")
if err != nil {
return false
}
if !allReady {
GinkgoWriter.Printf("等待 openfuyao-system Pod 就绪: %s\n", notReady)
return false
}
return true
}, 5*time.Minute, 10*time.Second).Should(BeTrue(), "openfuyao-system 命名空间下的 pod 应该全部就绪")
By("获取日志组件版本号")
Eventually(func() bool {
images, _ := checker.GetDeploymentImages("openfuyao-system", "logging-operator")
for _, image := range images {
tag := utils.GetImageTag(image)
return tag == "1.0.3"
}
return false
}, 3*time.Minute, 10*time.Second).Should(BeTrue(), "日志组件版本号应该为1.0.3")
By("卸载日志组件")
err = clusterManager.RemoveBkeClusterAddonByName(clusterName, clusterConfig.Namespace, "logging-package")
Expect(err).NotTo(HaveOccurred(), "从bc中删除日志组成功")
By("验证logging命名空间下的Pod被删除")
Eventually(func() bool {
hasPod, _ := checker.IsNamespaceHasPodsWithKubeconfig("logging", "")
return hasPod
}, 3*time.Minute, 10*time.Second).Should(BeFalse(), "logging命名空间下应该没有pod")
Eventually(func() bool {
hasPod, _ := checker.IsNamespaceHasPodByNameWithKubeconfig("openfuyao-system", "logging-operator", "")
return hasPod
}, 3*time.Minute, 10*time.Second).Should(BeFalse(), "openfuyao-system命名空间下应该没有pod")
})
})
})