github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/cluster_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package cluster
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  
    26  	. "github.com/onsi/ginkgo/v2"
    27  	. "github.com/onsi/gomega"
    28  
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/cli-runtime/pkg/genericiooptions"
    31  	"k8s.io/cli-runtime/pkg/resource"
    32  	"k8s.io/client-go/kubernetes/scheme"
    33  	clientfake "k8s.io/client-go/rest/fake"
    34  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    35  
    36  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    37  	"github.com/1aal/kubeblocks/pkg/cli/create"
    38  	kbclidelete "github.com/1aal/kubeblocks/pkg/cli/delete"
    39  	"github.com/1aal/kubeblocks/pkg/cli/testing"
    40  	"github.com/1aal/kubeblocks/pkg/cli/types"
    41  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    42  )
    43  
    44  var _ = Describe("Cluster", func() {
    45  	const (
    46  		testComponentPath                    = "../../testing/testdata/component.yaml"
    47  		testComponentWithClassPath           = "../../testing/testdata/component_with_class_1c1g.yaml"
    48  		testComponentWithInvalidClassPath    = "../../testing/testdata/component_with_invalid_class.yaml"
    49  		testComponentWithResourcePath        = "../../testing/testdata/component_with_resource_1c1g.yaml"
    50  		testComponentWithInvalidResourcePath = "../../testing/testdata/component_with_invalid_resource.yaml"
    51  		testClusterPath                      = "../../testing/testdata/cluster.yaml"
    52  	)
    53  
    54  	const (
    55  		clusterName = "test"
    56  		namespace   = "default"
    57  	)
    58  	var streams genericiooptions.IOStreams
    59  	var tf *cmdtesting.TestFactory
    60  	// test if DEFAULT_STORAGE_CLASS is not set in config.yaml
    61  	fakeNilConfigData := map[string]string{
    62  		"config.yaml": `# the default storage class name.
    63      #DEFAULT_STORAGE_CLASS: ""`,
    64  	}
    65  	fakeConfigData := map[string]string{
    66  		"config.yaml": `# the default storage class name.
    67      DEFAULT_STORAGE_CLASS: ""`,
    68  	}
    69  	fakeConfigDataWithDefaultSC := map[string]string{
    70  		"config.yaml": `# the default storage class name.
    71      DEFAULT_STORAGE_CLASS: kb-default-sc`,
    72  	}
    73  	BeforeEach(func() {
    74  		streams, _, _, _ = genericiooptions.NewTestIOStreams()
    75  		tf = cmdtesting.NewTestFactory().WithNamespace(namespace)
    76  		cd := testing.FakeClusterDef()
    77  		fakeDefaultStorageClass := testing.FakeStorageClass(testing.StorageClassName, testing.IsDefault)
    78  		tf.FakeDynamicClient = testing.FakeDynamicClient(cd, fakeDefaultStorageClass, testing.FakeClusterVersion(), testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName))
    79  		tf.Client = &clientfake.RESTClient{}
    80  	})
    81  
    82  	AfterEach(func() {
    83  		tf.Cleanup()
    84  	})
    85  
    86  	Context("create", func() {
    87  		It("without name", func() {
    88  			o := &CreateOptions{
    89  				ClusterDefRef:     testing.ClusterDefName,
    90  				ClusterVersionRef: testing.ClusterVersionName,
    91  				SetFile:           testComponentPath,
    92  				UpdatableFlags: UpdatableFlags{
    93  					TerminationPolicy: "Delete",
    94  				},
    95  				CreateOptions: create.CreateOptions{
    96  					Factory:   tf,
    97  					Dynamic:   tf.FakeDynamicClient,
    98  					IOStreams: streams,
    99  				},
   100  			}
   101  			o.Options = o
   102  			Expect(o.Complete()).To(Succeed())
   103  			Expect(o.Validate()).To(Succeed())
   104  			Expect(o.Name).ShouldNot(BeEmpty())
   105  			Expect(o.Run()).Should(HaveOccurred())
   106  		})
   107  	})
   108  
   109  	Context("run", func() {
   110  		var o *CreateOptions
   111  
   112  		BeforeEach(func() {
   113  			clusterDef := testing.FakeClusterDef()
   114  			resourceConstraint := testapps.NewComponentResourceConstraintFactory(testapps.DefaultResourceConstraintName).
   115  				AddConstraints(testapps.ProductionResourceConstraint).
   116  				AddSelector(appsv1alpha1.ClusterResourceConstraintSelector{
   117  					ClusterDefRef: clusterDef.Name,
   118  					Components: []appsv1alpha1.ComponentResourceConstraintSelector{
   119  						{
   120  							ComponentDefRef: testing.ComponentDefName,
   121  							Rules:           []string{"c1"},
   122  						},
   123  					},
   124  				}).
   125  				GetObject()
   126  
   127  			tf.FakeDynamicClient = testing.FakeDynamicClient(
   128  				clusterDef,
   129  				testing.FakeStorageClass(testing.StorageClassName, testing.IsDefault),
   130  				testing.FakeClusterVersion(),
   131  				testing.FakeComponentClassDef(fmt.Sprintf("custom-%s", testing.ComponentDefName), clusterDef.Name, testing.ComponentDefName),
   132  				testing.FakeComponentClassDef("custom-mysql", clusterDef.Name, "mysql"),
   133  				testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData),
   134  				testing.FakeSecret(types.DefaultNamespace, clusterName),
   135  				resourceConstraint,
   136  			)
   137  			o = &CreateOptions{
   138  				CreateOptions: create.CreateOptions{
   139  					IOStreams:       streams,
   140  					Name:            clusterName,
   141  					Dynamic:         tf.FakeDynamicClient,
   142  					CueTemplateName: CueTemplateName,
   143  					Factory:         tf,
   144  					GVR:             types.ClusterGVR(),
   145  				},
   146  				SetFile:           "",
   147  				ClusterDefRef:     testing.ClusterDefName,
   148  				ClusterVersionRef: testing.ClusterVersionName,
   149  				UpdatableFlags: UpdatableFlags{
   150  					PodAntiAffinity: "Preferred",
   151  					TopologyKeys:    []string{"kubernetes.io/hostname"},
   152  					NodeLabels:      map[string]string{"testLabelKey": "testLabelValue"},
   153  					TolerationsRaw:  []string{"engineType=mongo:NoSchedule"},
   154  					Tenancy:         string(appsv1alpha1.SharedNode),
   155  				},
   156  			}
   157  			o.TerminationPolicy = "WipeOut"
   158  		})
   159  
   160  		Run := func() {
   161  			o.CreateOptions.Options = o
   162  			o.Args = []string{clusterName}
   163  			Expect(o.CreateOptions.Complete()).Should(Succeed())
   164  			Expect(o.Namespace).To(Equal(namespace))
   165  			Expect(o.Name).To(Equal(clusterName))
   166  			Expect(o.Run()).Should(Succeed())
   167  		}
   168  
   169  		It("validate tolerations", func() {
   170  			Expect(len(o.TolerationsRaw)).Should(Equal(1))
   171  			Expect(o.Complete()).Should(Succeed())
   172  			Expect(len(o.Tolerations)).Should(Equal(1))
   173  		})
   174  
   175  		It("validate termination policy should be set", func() {
   176  			o.TerminationPolicy = ""
   177  			Expect(o.Validate()).Should(HaveOccurred())
   178  		})
   179  
   180  		It("should succeed if component with valid class", func() {
   181  			o.Values = []string{fmt.Sprintf("type=%s,class=%s", testing.ComponentDefName, testapps.Class1c1gName)}
   182  			Expect(o.Complete()).Should(Succeed())
   183  			Expect(o.Validate()).Should(Succeed())
   184  			Run()
   185  		})
   186  
   187  		It("should fail if component with invalid class", func() {
   188  			o.Values = []string{fmt.Sprintf("type=%s,class=class-not-exists", testing.ComponentDefName)}
   189  			Expect(o.Complete()).Should(HaveOccurred())
   190  		})
   191  
   192  		It("should succeed if component with resource meets the resource constraint", func() {
   193  			o.Values = []string{fmt.Sprintf("type=%s,cpu=1,memory=1Gi", testing.ComponentDefName)}
   194  			Expect(o.Complete()).Should(Succeed())
   195  			Expect(o.Validate()).Should(Succeed())
   196  			Run()
   197  		})
   198  
   199  		It("should succeed if component with resource with smaller unit meets the constraint", func() {
   200  			o.Values = []string{fmt.Sprintf("type=%s,cpu=1000m,memory=1024Mi", testing.ComponentDefName)}
   201  			Expect(o.Complete()).Should(Succeed())
   202  			Expect(o.Validate()).Should(Succeed())
   203  			Run()
   204  		})
   205  
   206  		It("should fail if component with resource not meets the constraint", func() {
   207  			o.Values = []string{fmt.Sprintf("type=%s,cpu=1,memory=100Gi", testing.ComponentDefName)}
   208  			Expect(o.Complete()).Should(HaveOccurred())
   209  		})
   210  
   211  		It("should succeed if component with cpu meets the constraint", func() {
   212  			o.Values = []string{fmt.Sprintf("type=%s,cpu=1", testing.ComponentDefName)}
   213  			Expect(o.Complete()).Should(Succeed())
   214  			Expect(o.Validate()).Should(Succeed())
   215  			Run()
   216  		})
   217  
   218  		It("should fail if component with cpu not meets the constraint", func() {
   219  			o.Values = []string{fmt.Sprintf("type=%s,cpu=1024", testing.ComponentDefName)}
   220  			Expect(o.Complete()).Should(HaveOccurred())
   221  		})
   222  
   223  		It("should fail if component with memory not meets the constraint", func() {
   224  			o.Values = []string{fmt.Sprintf("type=%s,memory=1Ti", testing.ComponentDefName)}
   225  			Expect(o.Complete()).Should(HaveOccurred())
   226  		})
   227  
   228  		It("should succeed if component doesn't have class definition", func() {
   229  			o.Values = []string{fmt.Sprintf("type=%s,cpu=3,memory=7Gi", testing.ExtraComponentDefName)}
   230  			Expect(o.Complete()).Should(Succeed())
   231  			Expect(o.Validate()).Should(Succeed())
   232  			Run()
   233  		})
   234  
   235  		It("should fail if component with storage not meets the constraint", func() {
   236  			o.Values = []string{fmt.Sprintf("type=%s,storage=500Mi", testing.ComponentDefName)}
   237  			Expect(o.Complete()).Should(HaveOccurred())
   238  
   239  			o.Values = []string{fmt.Sprintf("type=%s,storage=1Pi", testing.ComponentDefName)}
   240  			Expect(o.Complete()).Should(HaveOccurred())
   241  		})
   242  
   243  		It("should fail if create cluster by non-existed file", func() {
   244  			o.SetFile = "test.yaml"
   245  			Expect(o.Complete()).Should(HaveOccurred())
   246  		})
   247  
   248  		It("should succeed if create cluster by empty file", func() {
   249  			o.SetFile = ""
   250  			Expect(o.Complete()).Should(Succeed())
   251  			Expect(o.Validate()).Should(Succeed())
   252  			Run()
   253  		})
   254  
   255  		It("should succeed if create cluster by file without class and resource", func() {
   256  			o.SetFile = testComponentPath
   257  			Expect(o.Complete()).Should(Succeed())
   258  			Expect(o.Validate()).Should(Succeed())
   259  			Run()
   260  		})
   261  
   262  		It("should succeed if create cluster by file with class", func() {
   263  			o.SetFile = testComponentWithClassPath
   264  			Expect(o.Complete()).Should(Succeed())
   265  			Expect(o.Validate()).Should(Succeed())
   266  			Run()
   267  		})
   268  
   269  		It("should succeed if create cluster by file with resource", func() {
   270  			o.SetFile = testComponentWithResourcePath
   271  			Expect(o.Complete()).Should(Succeed())
   272  			Expect(o.Validate()).Should(Succeed())
   273  			Run()
   274  		})
   275  
   276  		It("should fail if create cluster by file with non-existed class", func() {
   277  			o.SetFile = testComponentWithInvalidClassPath
   278  			Expect(o.Complete()).Should(HaveOccurred())
   279  		})
   280  
   281  		It("should succeed if create cluster with a complete config file", func() {
   282  			o.SetFile = testClusterPath
   283  			Expect(o.Complete()).Should(Succeed())
   284  			Expect(o.Validate()).Should(Succeed())
   285  		})
   286  	})
   287  
   288  	Context("create validate", func() {
   289  		var o *CreateOptions
   290  		BeforeEach(func() {
   291  			o = &CreateOptions{
   292  				ClusterDefRef:     testing.ClusterDefName,
   293  				ClusterVersionRef: testing.ClusterVersionName,
   294  				SetFile:           testComponentPath,
   295  				UpdatableFlags: UpdatableFlags{
   296  					TerminationPolicy: "Delete",
   297  				},
   298  				CreateOptions: create.CreateOptions{
   299  					Factory:   tf,
   300  					Namespace: namespace,
   301  					Name:      "mycluster",
   302  					Dynamic:   tf.FakeDynamicClient,
   303  					IOStreams: streams,
   304  				},
   305  				ComponentSpecs: make([]map[string]interface{}, 1),
   306  			}
   307  			o.ComponentSpecs[0] = make(map[string]interface{})
   308  			o.ComponentSpecs[0]["volumeClaimTemplates"] = make([]interface{}, 1)
   309  			vct := o.ComponentSpecs[0]["volumeClaimTemplates"].([]interface{})
   310  			vct[0] = make(map[string]interface{})
   311  			vct[0].(map[string]interface{})["spec"] = make(map[string]interface{})
   312  			spec := vct[0].(map[string]interface{})["spec"]
   313  			spec.(map[string]interface{})["storageClassName"] = testing.StorageClassName
   314  		})
   315  
   316  		It("can validate whether the ClusterDefRef is null when create a new cluster ", func() {
   317  			Expect(o.ClusterDefRef).ShouldNot(BeEmpty())
   318  			Expect(o.Validate()).Should(Succeed())
   319  			o.ClusterDefRef = ""
   320  			Expect(o.Validate()).Should(HaveOccurred())
   321  		})
   322  
   323  		It("can validate whether the TerminationPolicy is null when create a new cluster ", func() {
   324  			Expect(o.TerminationPolicy).ShouldNot(BeEmpty())
   325  			Expect(o.Validate()).Should(Succeed())
   326  			o.TerminationPolicy = ""
   327  			Expect(o.Validate()).Should(HaveOccurred())
   328  		})
   329  
   330  		It("can validate whether the ClusterVersionRef is null and can't get latest version from client when create a new cluster ", func() {
   331  			Expect(o.ClusterVersionRef).ShouldNot(BeEmpty())
   332  			Expect(o.Validate()).Should(Succeed())
   333  			o.ClusterVersionRef = ""
   334  			Expect(o.Validate()).Should(Succeed())
   335  		})
   336  
   337  		It("can validate whether --set and --set-file both are specified when create a new cluster ", func() {
   338  			Expect(o.SetFile).ShouldNot(BeEmpty())
   339  			Expect(o.Values).Should(BeNil())
   340  			Expect(o.Validate()).Should(Succeed())
   341  			o.Values = []string{"notEmpty"}
   342  			Expect(o.Validate()).Should(HaveOccurred())
   343  		})
   344  
   345  		It("can validate the cluster name must begin with a letter and can only contain lowercase letters, numbers, and '-'.", func() {
   346  			type fn func()
   347  			var succeed = func(name string) fn {
   348  				return func() {
   349  					o.Name = name
   350  					Expect(o.Validate()).Should(Succeed())
   351  				}
   352  			}
   353  			var failed = func(name string) fn {
   354  				return func() {
   355  					o.Name = name
   356  					Expect(o.Validate()).Should(HaveOccurred())
   357  				}
   358  			}
   359  			// more case to add
   360  			invalidCase := []string{
   361  				"1abcd", "abcd-", "-abcd", "abc#d", "ABCD", "*&(&%",
   362  			}
   363  
   364  			validCase := []string{
   365  				"abcd", "abcd1", "a1-2b-3d",
   366  			}
   367  
   368  			for i := range invalidCase {
   369  				failed(invalidCase[i])
   370  			}
   371  
   372  			for i := range validCase {
   373  				succeed(validCase[i])
   374  			}
   375  
   376  		})
   377  
   378  		It("can validate whether the name is not longer than 16 characters when create a new cluster", func() {
   379  			Expect(len(o.Name)).Should(BeNumerically("<=", 16))
   380  			Expect(o.Validate()).Should(Succeed())
   381  			moreThan16 := 17
   382  			bytes := make([]byte, 0)
   383  			var clusterNameMoreThan16 string
   384  			for i := 0; i < moreThan16; i++ {
   385  				bytes = append(bytes, byte(i%26+'a'))
   386  			}
   387  			clusterNameMoreThan16 = string(bytes)
   388  			Expect(len(clusterNameMoreThan16)).Should(BeNumerically(">", 16))
   389  			o.Name = clusterNameMoreThan16
   390  			Expect(o.Validate()).Should(HaveOccurred())
   391  		})
   392  
   393  		Context("validate storageClass", func() {
   394  
   395  			It("can get all StorageClasses in K8S and check out if the cluster have a default StorageClasses by GetStorageClasses()", func() {
   396  				storageClasses, existedDefault, err := getStorageClasses(o.Dynamic)
   397  				Expect(err).Should(Succeed())
   398  				Expect(storageClasses).Should(HaveKey(testing.StorageClassName))
   399  				Expect(existedDefault).Should(BeTrue())
   400  				fakeNotDefaultStorageClass := testing.FakeStorageClass(testing.StorageClassName, testing.IsNotDefault)
   401  				tf.FakeDynamicClient = testing.FakeDynamicClient(testing.FakeClusterDef(), fakeNotDefaultStorageClass, testing.FakeClusterVersion(), testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName))
   402  				storageClasses, existedDefault, err = getStorageClasses(tf.FakeDynamicClient)
   403  				Expect(err).Should(Succeed())
   404  				Expect(storageClasses).Should(HaveKey(testing.StorageClassName))
   405  				Expect(existedDefault).ShouldNot(BeTrue())
   406  			})
   407  
   408  			It("can specify the StorageClass and the StorageClass must exist", func() {
   409  				Expect(validateStorageClass(o.Dynamic, o.ComponentSpecs)).Should(Succeed())
   410  				fakeNotDefaultStorageClass := testing.FakeStorageClass(testing.StorageClassName+"-other", testing.IsNotDefault)
   411  				FakeDynamicClientWithNotDefaultSC := testing.FakeDynamicClient(testing.FakeClusterDef(), fakeNotDefaultStorageClass, testing.FakeClusterVersion(), testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName))
   412  				Expect(validateStorageClass(FakeDynamicClientWithNotDefaultSC, o.ComponentSpecs)).Should(HaveOccurred())
   413  			})
   414  
   415  			It("can get valiate the default StorageClasses", func() {
   416  				vct := o.ComponentSpecs[0]["volumeClaimTemplates"].([]interface{})
   417  				spec := vct[0].(map[string]interface{})["spec"]
   418  				delete(spec.(map[string]interface{}), "storageClassName")
   419  				Expect(validateStorageClass(o.Dynamic, o.ComponentSpecs)).Should(Succeed())
   420  				FakeDynamicClientWithNotDefaultSC := testing.FakeDynamicClient(testing.FakeClusterDef(), testing.FakeStorageClass(testing.StorageClassName+"-other", testing.IsNotDefault), testing.FakeClusterVersion(), testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName))
   421  				Expect(validateStorageClass(FakeDynamicClientWithNotDefaultSC, o.ComponentSpecs)).Should(HaveOccurred())
   422  				// It can validate 'DEFAULT_STORAGE_CLASS' in ConfigMap for cloud K8S
   423  				FakeDynamicClientWithConfigDefaultSC := testing.FakeDynamicClient(testing.FakeClusterDef(), testing.FakeStorageClass(testing.StorageClassName+"-other", testing.IsNotDefault), testing.FakeClusterVersion(), testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigDataWithDefaultSC), testing.FakeSecret(types.DefaultNamespace, clusterName))
   424  				Expect(validateStorageClass(FakeDynamicClientWithConfigDefaultSC, o.ComponentSpecs)).Should(Succeed())
   425  			})
   426  
   427  			It("validateDefaultSCInConfig test", func() {
   428  				have, err := validateDefaultSCInConfig(testing.FakeDynamicClient(testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName)))
   429  				Expect(err).Should(Succeed())
   430  				Expect(have).Should(BeFalse())
   431  				have, err = validateDefaultSCInConfig(testing.FakeDynamicClient(testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigDataWithDefaultSC), testing.FakeSecret(types.DefaultNamespace, clusterName)))
   432  				Expect(err).Should(Succeed())
   433  				Expect(have).Should(BeTrue())
   434  				have, err = validateDefaultSCInConfig(testing.FakeDynamicClient(testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeNilConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName)))
   435  				Expect(err).Should(Succeed())
   436  				Expect(have).Should(BeFalse())
   437  				have, err = validateDefaultSCInConfig(testing.FakeDynamicClient(testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, nil), testing.FakeSecret(types.DefaultNamespace, clusterName)))
   438  				Expect(err).Should(Succeed())
   439  				Expect(have).Should(BeFalse())
   440  				have, err = validateDefaultSCInConfig(testing.FakeDynamicClient(testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, map[string]string{"not-config-yaml": "error situation"}), testing.FakeSecret(types.DefaultNamespace, clusterName)))
   441  				Expect(err).Should(Succeed())
   442  				Expect(have).Should(BeFalse())
   443  
   444  			})
   445  		})
   446  
   447  	})
   448  
   449  	Context("delete cluster", func() {
   450  		var o *kbclidelete.DeleteOptions
   451  
   452  		BeforeEach(func() {
   453  			tf = testing.NewTestFactory(namespace)
   454  
   455  			_ = appsv1alpha1.AddToScheme(scheme.Scheme)
   456  			codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   457  			clusters := testing.FakeClusterList()
   458  
   459  			tf.UnstructuredClient = &clientfake.RESTClient{
   460  				GroupVersion:         schema.GroupVersion{Group: types.AppsAPIGroup, Version: types.AppsAPIVersion},
   461  				NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   462  				Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   463  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &clusters.Items[0])}, nil
   464  				}),
   465  			}
   466  
   467  			tf.Client = tf.UnstructuredClient
   468  			o = &kbclidelete.DeleteOptions{
   469  				Factory:     tf,
   470  				IOStreams:   streams,
   471  				GVR:         types.ClusterGVR(),
   472  				AutoApprove: true,
   473  			}
   474  		})
   475  
   476  		It("validata delete cluster by name", func() {
   477  			Expect(deleteCluster(o, []string{})).Should(HaveOccurred())
   478  			Expect(deleteCluster(o, []string{clusterName})).Should(Succeed())
   479  			o.LabelSelector = fmt.Sprintf("clusterdefinition.kubeblocks.io/name=%s", testing.ClusterDefName)
   480  			// todo:  there is an issue with rendering the name of the "info" element, and efforts are being made to resolve it.
   481  			// Expect(deleteCluster(o, []string{})).Should(Succeed())
   482  			Expect(deleteCluster(o, []string{clusterName})).Should(HaveOccurred())
   483  		})
   484  
   485  	})
   486  	It("delete", func() {
   487  		cmd := NewDeleteCmd(tf, streams)
   488  		Expect(cmd).ShouldNot(BeNil())
   489  	})
   490  
   491  	It("cluster", func() {
   492  		cmd := NewClusterCmd(tf, streams)
   493  		Expect(cmd).ShouldNot(BeNil())
   494  		Expect(cmd.HasSubCommands()).To(BeTrue())
   495  	})
   496  
   497  	It("connect", func() {
   498  		cmd := NewConnectCmd(tf, streams)
   499  		Expect(cmd).ShouldNot(BeNil())
   500  	})
   501  
   502  	It("list-logs-type", func() {
   503  		cmd := NewListLogsCmd(tf, streams)
   504  		Expect(cmd).ShouldNot(BeNil())
   505  	})
   506  
   507  	It("logs", func() {
   508  		cmd := NewLogsCmd(tf, streams)
   509  		Expect(cmd).ShouldNot(BeNil())
   510  	})
   511  })