github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/describe_ops_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 "bytes" 24 "fmt" 25 "net/http" 26 "time" 27 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 corev1 "k8s.io/api/core/v1" 31 apiresource "k8s.io/apimachinery/pkg/api/resource" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/cli-runtime/pkg/genericiooptions" 36 "k8s.io/cli-runtime/pkg/resource" 37 "k8s.io/client-go/kubernetes/scheme" 38 clientfake "k8s.io/client-go/rest/fake" 39 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 40 41 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 42 clitesting "github.com/1aal/kubeblocks/pkg/cli/testing" 43 "github.com/1aal/kubeblocks/pkg/cli/types" 44 ) 45 46 var _ = Describe("Expose", func() { 47 const ( 48 namespace = "test" 49 opsName = "test-ops" 50 componentName = "test_stateful" 51 componentName1 = "test_stateless" 52 clusterVersionName = "test-cluster-0.1" 53 ) 54 55 var ( 56 streams genericiooptions.IOStreams 57 tf *cmdtesting.TestFactory 58 ) 59 60 BeforeEach(func() { 61 streams, _, _, _ = genericiooptions.NewTestIOStreams() 62 tf = clitesting.NewTestFactory(namespace) 63 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 64 httpResp := func(obj runtime.Object) *http.Response { 65 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, obj)} 66 } 67 68 tf.UnstructuredClient = &clientfake.RESTClient{ 69 GroupVersion: schema.GroupVersion{Group: types.AppsAPIGroup, Version: types.AppsAPIVersion}, 70 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 71 Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 72 urlPrefix := "/api/v1/namespaces/" + namespace 73 mapping := map[string]*http.Response{ 74 "/api/v1/nodes/" + clitesting.NodeName: httpResp(clitesting.FakeNode()), 75 urlPrefix + "/services": httpResp(&corev1.ServiceList{}), 76 urlPrefix + "/events": httpResp(&corev1.EventList{}), 77 // urlPrefix + "/pods": httpResp(pods), 78 } 79 return mapping[req.URL.Path], nil 80 }), 81 } 82 83 tf.Client = tf.UnstructuredClient 84 }) 85 86 AfterEach(func() { 87 tf.Cleanup() 88 }) 89 90 It("describe", func() { 91 cmd := NewDescribeOpsCmd(tf, streams) 92 Expect(cmd).ShouldNot(BeNil()) 93 }) 94 95 It("complete", func() { 96 o := newDescribeOpsOptions(tf, streams) 97 Expect(o.complete(nil).Error()).Should(Equal("OpsRequest name should be specified")) 98 Expect(o.complete([]string{opsName})).Should(Succeed()) 99 Expect(o.names).Should(Equal([]string{opsName})) 100 Expect(o.client).ShouldNot(BeNil()) 101 Expect(o.dynamic).ShouldNot(BeNil()) 102 Expect(o.namespace).Should(Equal(namespace)) 103 }) 104 105 generateOpsObject := func(opsName string, opsType appsv1alpha1.OpsType) *appsv1alpha1.OpsRequest { 106 return &appsv1alpha1.OpsRequest{ 107 ObjectMeta: metav1.ObjectMeta{ 108 Name: opsName, 109 Namespace: namespace, 110 }, 111 Spec: appsv1alpha1.OpsRequestSpec{ 112 ClusterRef: "test-cluster", 113 Type: opsType, 114 }, 115 } 116 } 117 118 describeOps := func(opsType appsv1alpha1.OpsType, completeOps func(ops *appsv1alpha1.OpsRequest)) { 119 randomStr := clitesting.GetRandomStr() 120 ops := generateOpsObject(opsName+randomStr, opsType) 121 completeOps(ops) 122 tf.FakeDynamicClient = clitesting.FakeDynamicClient(ops) 123 o := newDescribeOpsOptions(tf, streams) 124 Expect(o.complete([]string{opsName + randomStr})).Should(Succeed()) 125 Expect(o.run()).Should(Succeed()) 126 } 127 128 fakeOpsStatusAndProgress := func() appsv1alpha1.OpsRequestStatus { 129 objectKey := "Pod/test-pod-wessxd" 130 objectKey1 := "Pod/test-pod-xsdfwe" 131 return appsv1alpha1.OpsRequestStatus{ 132 StartTimestamp: metav1.NewTime(time.Now().Add(-1 * time.Minute)), 133 CompletionTimestamp: metav1.NewTime(time.Now()), 134 Progress: "1/2", 135 Phase: appsv1alpha1.OpsFailedPhase, 136 Components: map[string]appsv1alpha1.OpsRequestComponentStatus{ 137 componentName: { 138 Phase: appsv1alpha1.FailedClusterCompPhase, 139 ProgressDetails: []appsv1alpha1.ProgressStatusDetail{ 140 { 141 ObjectKey: objectKey, 142 Status: appsv1alpha1.SucceedProgressStatus, 143 StartTime: metav1.NewTime(time.Now().Add(-59 * time.Second)), 144 EndTime: metav1.NewTime(time.Now().Add(-39 * time.Second)), 145 Message: fmt.Sprintf("Successfully vertical scale Pod: %s in Component: %s", objectKey, componentName), 146 }, 147 { 148 ObjectKey: objectKey1, 149 Status: appsv1alpha1.FailedProgressStatus, 150 StartTime: metav1.NewTime(time.Now().Add(-39 * time.Second)), 151 EndTime: metav1.NewTime(time.Now().Add(-1 * time.Second)), 152 Message: fmt.Sprintf("Failed to vertical scale Pod: %s in Component: %s", objectKey1, componentName), 153 }, 154 }, 155 }, 156 }, 157 Conditions: []metav1.Condition{ 158 { 159 Type: "Processing", 160 Reason: "ProcessingOps", 161 Status: metav1.ConditionTrue, 162 Message: "Start to process the OpsRequest.", 163 }, 164 { 165 Type: "Failed", 166 Reason: "FailedScale", 167 Status: metav1.ConditionFalse, 168 Message: "Failed to process the OpsRequest.", 169 }, 170 }, 171 } 172 } 173 174 testPrintLastConfiguration := func(config appsv1alpha1.LastConfiguration, 175 opsType appsv1alpha1.OpsType, expectStrings ...string) { 176 o := newDescribeOpsOptions(tf, streams) 177 if opsType == appsv1alpha1.UpgradeType { 178 // capture stdout 179 done := clitesting.Capture() 180 o.printLastConfiguration(config, opsType) 181 capturedOutput, err := done() 182 Expect(err).Should(Succeed()) 183 Expect(clitesting.ContainExpectStrings(capturedOutput, expectStrings...)).Should(BeTrue()) 184 return 185 } 186 o.printLastConfiguration(config, opsType) 187 out := o.Out.(*bytes.Buffer) 188 Expect(clitesting.ContainExpectStrings(out.String(), expectStrings...)).Should(BeTrue()) 189 } 190 191 It("run", func() { 192 By("test describe Upgrade") 193 describeOps(appsv1alpha1.UpgradeType, func(ops *appsv1alpha1.OpsRequest) { 194 ops.Spec.Upgrade = &appsv1alpha1.Upgrade{ 195 ClusterVersionRef: clusterVersionName, 196 } 197 }) 198 199 By("test describe Restart") 200 describeOps(appsv1alpha1.RestartType, func(ops *appsv1alpha1.OpsRequest) { 201 ops.Spec.RestartList = []appsv1alpha1.ComponentOps{ 202 {ComponentName: componentName}, 203 {ComponentName: componentName1}, 204 } 205 }) 206 207 By("test describe VerticalScaling") 208 resourceRequirements := corev1.ResourceRequirements{ 209 Requests: corev1.ResourceList{ 210 "cpu": apiresource.MustParse("100m"), 211 "memory": apiresource.MustParse("200Mi"), 212 }, 213 Limits: corev1.ResourceList{ 214 "cpu": apiresource.MustParse("300m"), 215 "memory": apiresource.MustParse("400Mi"), 216 }, 217 } 218 fakeVerticalScalingSpec := func() []appsv1alpha1.VerticalScaling { 219 return []appsv1alpha1.VerticalScaling{ 220 { 221 ComponentOps: appsv1alpha1.ComponentOps{ 222 ComponentName: componentName, 223 }, 224 ResourceRequirements: resourceRequirements, 225 }, 226 } 227 } 228 describeOps(appsv1alpha1.VerticalScalingType, func(ops *appsv1alpha1.OpsRequest) { 229 ops.Spec.VerticalScalingList = fakeVerticalScalingSpec() 230 }) 231 232 By("test describe HorizontalScaling") 233 describeOps(appsv1alpha1.HorizontalScalingType, func(ops *appsv1alpha1.OpsRequest) { 234 ops.Spec.HorizontalScalingList = []appsv1alpha1.HorizontalScaling{ 235 { 236 ComponentOps: appsv1alpha1.ComponentOps{ 237 ComponentName: componentName, 238 }, 239 Replicas: 1, 240 }, 241 } 242 }) 243 244 By("test describe VolumeExpansion and print OpsRequest status") 245 volumeClaimTemplates := []appsv1alpha1.OpsRequestVolumeClaimTemplate{ 246 { 247 Name: "data", 248 Storage: apiresource.MustParse("2Gi"), 249 }, 250 { 251 Name: "log", 252 Storage: apiresource.MustParse("4Gi"), 253 }, 254 } 255 describeOps(appsv1alpha1.VolumeExpansionType, func(ops *appsv1alpha1.OpsRequest) { 256 ops.Spec.VolumeExpansionList = []appsv1alpha1.VolumeExpansion{ 257 { 258 ComponentOps: appsv1alpha1.ComponentOps{ 259 ComponentName: componentName, 260 }, 261 VolumeClaimTemplates: volumeClaimTemplates, 262 }, 263 } 264 }) 265 266 By("test printing OpsRequest status and conditions") 267 describeOps(appsv1alpha1.VerticalScalingType, func(ops *appsv1alpha1.OpsRequest) { 268 ops.Spec.VerticalScalingList = fakeVerticalScalingSpec() 269 ops.Status = fakeOpsStatusAndProgress() 270 }) 271 272 By("test printing OpsRequest last configuration") 273 testPrintLastConfiguration(appsv1alpha1.LastConfiguration{ 274 ClusterVersionRef: clusterVersionName, 275 }, appsv1alpha1.UpgradeType, "\nLast Configuration", 276 fmt.Sprintf("%-20s%s", "Cluster Version:", clusterVersionName+"\n")) 277 278 By("test verticalScaling last configuration") 279 testPrintLastConfiguration(appsv1alpha1.LastConfiguration{ 280 Components: map[string]appsv1alpha1.LastComponentConfiguration{ 281 componentName: { 282 ResourceRequirements: resourceRequirements, 283 }, 284 }, 285 }, appsv1alpha1.VerticalScalingType, "100m", "200Mi", "300m", "400Mi", 286 "REQUEST-CPU", "REQUEST-MEMORY", "LIMIT-CPU", "LIMIT-MEMORY") 287 288 By("test HorizontalScaling last configuration") 289 replicas := int32(2) 290 testPrintLastConfiguration(appsv1alpha1.LastConfiguration{ 291 Components: map[string]appsv1alpha1.LastComponentConfiguration{ 292 componentName: { 293 Replicas: &replicas, 294 }, 295 }, 296 }, appsv1alpha1.HorizontalScalingType, "COMPONENT", "REPLICAS", componentName, "2") 297 298 By("test VolumeExpansion last configuration") 299 testPrintLastConfiguration(appsv1alpha1.LastConfiguration{ 300 Components: map[string]appsv1alpha1.LastComponentConfiguration{ 301 componentName: { 302 VolumeClaimTemplates: volumeClaimTemplates, 303 }, 304 }, 305 }, appsv1alpha1.VolumeExpansionType, "VOLUME-CLAIM-TEMPLATE", "STORAGE", "data", "2Gi", "log") 306 307 }) 308 })