github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/client/httpclient_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 client 21 22 import ( 23 "context" 24 "fmt" 25 "net/http" 26 "net/http/httptest" 27 "strconv" 28 "strings" 29 "time" 30 31 "github.com/golang/mock/gomock" 32 . "github.com/onsi/ginkgo/v2" 33 . "github.com/onsi/gomega" 34 corev1 "k8s.io/api/core/v1" 35 36 "github.com/1aal/kubeblocks/pkg/constant" 37 "github.com/1aal/kubeblocks/pkg/lorry/dcs" 38 "github.com/1aal/kubeblocks/pkg/lorry/engines/models" 39 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 40 ) 41 42 const ( 43 msg = "not implemented for test" 44 ) 45 46 var _ = Describe("Lorry HTTP Client", func() { 47 var pod *corev1.Pod 48 49 BeforeEach(func() { 50 podName := "pod-for-lorry-test" 51 pod = testapps.NewPodFactory("default", podName). 52 AddContainer(corev1.Container{ 53 Name: testapps.DefaultNginxContainerName, 54 Command: []string{"lorry", "--port", strconv.Itoa(lorryHTTPPort)}, 55 Image: testapps.NginxImage}). 56 GetObject() 57 pod.Spec.Containers[0].Ports = []corev1.ContainerPort{ 58 { 59 ContainerPort: int32(lorryHTTPPort), 60 Name: constant.LorryHTTPPortName, 61 Protocol: "TCP", 62 }, 63 { 64 ContainerPort: int32(50001), 65 Name: constant.LorryGRPCPortName, 66 Protocol: "TCP", 67 }, 68 } 69 pod.Status.PodIP = "127.0.0.1" 70 }) 71 72 Context("new HTTPClient", func() { 73 It("without lorry service, return nil", func() { 74 podWithoutLorry := pod.DeepCopy() 75 podWithoutLorry.Spec.Containers[0].Ports = nil 76 lorryClient, err := NewHTTPClientWithPod(podWithoutLorry) 77 Expect(err).ShouldNot(HaveOccurred()) 78 Expect(lorryClient).Should(BeNil()) 79 }) 80 81 It("without pod ip, failed", func() { 82 podWithoutPodIP := pod.DeepCopy() 83 podWithoutPodIP.Status.PodIP = "" 84 _, err := NewHTTPClientWithPod(podWithoutPodIP) 85 Expect(err).Should(HaveOccurred()) 86 }) 87 88 It("success", func() { 89 lorryClient, err := NewHTTPClientWithPod(pod) 90 Expect(err).ShouldNot(HaveOccurred()) 91 Expect(lorryClient).ShouldNot(BeNil()) 92 }) 93 }) 94 95 Context("request with timeout", func() { 96 var httpServer *httptest.Server 97 var port int 98 var lorryClient *HTTPClient 99 100 BeforeEach(func() { 101 pod1 := pod.DeepCopy() 102 body := []byte("{\"role\": \"leader\"}") 103 httpServer, port = newHTTPServer(body) 104 pod1.Spec.Containers[0].Ports[0].ContainerPort = int32(port) 105 lorryClient, _ = NewHTTPClientWithPod(pod1) 106 Expect(lorryClient).ShouldNot(BeNil()) 107 }) 108 109 AfterEach(func() { 110 httpServer.Close() 111 }) 112 113 It("response in time", func() { 114 lorryClient.ReconcileTimeout = 1 * time.Second 115 _, err := lorryClient.GetRole(context.TODO()) 116 Expect(err).ShouldNot(HaveOccurred()) 117 Expect(lorryClient.cache).Should(BeEmpty()) 118 }) 119 120 It("response timeout", func() { 121 lorryClient.ReconcileTimeout = 50 * time.Millisecond 122 _, err := lorryClient.GetRole(context.TODO()) 123 Expect(err).Should(HaveOccurred()) 124 // wait client to get response and cache it 125 time.Sleep(200 * time.Millisecond) 126 Expect(lorryClient.cache).Should(HaveLen(1)) 127 }) 128 129 It("response by cache", func() { 130 lorryClient.ReconcileTimeout = 50 * time.Millisecond 131 // get response from server, and timeout 132 _, err := lorryClient.GetRole(context.TODO()) 133 Expect(err).Should(HaveOccurred()) 134 // wait client to get response and cache it 135 time.Sleep(200 * time.Millisecond) 136 // get response from cache 137 _, err = lorryClient.GetRole(context.TODO()) 138 Expect(err).ShouldNot(HaveOccurred()) 139 Expect(lorryClient.cache).Should(BeEmpty()) 140 }) 141 }) 142 143 Context("get replica role", func() { 144 var lorryClient *HTTPClient 145 146 BeforeEach(func() { 147 lorryClient, _ = NewHTTPClientWithPod(pod) 148 Expect(lorryClient).ShouldNot(BeNil()) 149 }) 150 151 It("success", func() { 152 role := "leader" 153 mockDBManager.EXPECT().GetReplicaRole(gomock.Any(), gomock.Any()).Return(role, nil) 154 Expect(lorryClient.GetRole(context.TODO())).Should(Equal(role)) 155 }) 156 157 It("not implemented", func() { 158 mockDBManager.EXPECT().GetReplicaRole(gomock.Any(), gomock.Any()).Return(string(""), fmt.Errorf(msg)) 159 role, err := lorryClient.GetRole(context.TODO()) 160 Expect(err).Should(HaveOccurred()) 161 Expect(err.Error()).Should(ContainSubstring(msg)) 162 Expect(role).Should(BeEmpty()) 163 }) 164 }) 165 166 Context("list system accounts", func() { 167 var lorryClient *HTTPClient 168 var systemAccounts []models.UserInfo 169 170 BeforeEach(func() { 171 lorryClient, _ = NewHTTPClientWithPod(pod) 172 Expect(lorryClient).ShouldNot(BeNil()) 173 systemAccounts = []models.UserInfo{ 174 { 175 UserName: "kb-admin1", 176 }, 177 { 178 UserName: "kb-admin2", 179 }, 180 } 181 }) 182 183 It("success", func() { 184 mockDBManager.EXPECT().ListSystemAccounts(gomock.Any()).Return(systemAccounts, nil) 185 Expect(lorryClient.ListSystemAccounts(context.TODO())).Should(HaveLen(2)) 186 }) 187 188 It("not implemented", func() { 189 mockDBManager.EXPECT().ListSystemAccounts(gomock.Any()).Return(nil, fmt.Errorf(msg)) 190 accounts, err := lorryClient.ListSystemAccounts(context.TODO()) 191 Expect(err).Should(HaveOccurred()) 192 Expect(err.Error()).Should(ContainSubstring(msg)) 193 Expect(accounts).Should(BeEmpty()) 194 }) 195 }) 196 197 Context("create user", func() { 198 var lorryClient *HTTPClient 199 200 BeforeEach(func() { 201 lorryClient, _ = NewHTTPClientWithPod(pod) 202 Expect(lorryClient).ShouldNot(BeNil()) 203 }) 204 205 It("success", func() { 206 mockDBManager.EXPECT().CreateUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 207 Expect(lorryClient.CreateUser(context.TODO(), "user-test", "password-test")).Should(Succeed()) 208 }) 209 210 It("not implemented", func() { 211 mockDBManager.EXPECT().CreateUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf(msg)) 212 err := lorryClient.CreateUser(context.TODO(), "user-test", "password-test") 213 Expect(err).Should(HaveOccurred()) 214 Expect(err.Error()).Should(ContainSubstring(msg)) 215 }) 216 }) 217 218 Context("delete user", func() { 219 var lorryClient *HTTPClient 220 221 BeforeEach(func() { 222 lorryClient, _ = NewHTTPClientWithPod(pod) 223 Expect(lorryClient).ShouldNot(BeNil()) 224 }) 225 226 It("success", func() { 227 mockDBManager.EXPECT().DeleteUser(gomock.Any(), gomock.Any()).Return(nil) 228 Expect(lorryClient.DeleteUser(context.TODO(), "user-test")).Should(Succeed()) 229 }) 230 231 It("not implemented", func() { 232 mockDBManager.EXPECT().DeleteUser(gomock.Any(), gomock.Any()).Return(fmt.Errorf(msg)) 233 err := lorryClient.DeleteUser(context.TODO(), "user-test") 234 Expect(err).Should(HaveOccurred()) 235 Expect(err.Error()).Should(ContainSubstring(msg)) 236 }) 237 }) 238 239 Context("describe user", func() { 240 var lorryClient *HTTPClient 241 var userInfo *models.UserInfo 242 243 BeforeEach(func() { 244 lorryClient, _ = NewHTTPClientWithPod(pod) 245 Expect(lorryClient).ShouldNot(BeNil()) 246 userInfo = &models.UserInfo{ 247 UserName: "kb-admin1", 248 } 249 }) 250 251 It("success", func() { 252 mockDBManager.EXPECT().DescribeUser(gomock.Any(), gomock.Any()).Return(userInfo, nil) 253 user, err := lorryClient.DescribeUser(context.TODO(), "user-test") 254 Expect(err).Should(Succeed()) 255 Expect(user).ShouldNot(BeZero()) 256 }) 257 258 It("not implemented", func() { 259 mockDBManager.EXPECT().DescribeUser(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf(msg)) 260 _, err := lorryClient.DescribeUser(context.TODO(), "user-test") 261 Expect(err).Should(HaveOccurred()) 262 Expect(err.Error()).Should(ContainSubstring(msg)) 263 }) 264 }) 265 266 Context("grant user role", func() { 267 var lorryClient *HTTPClient 268 269 BeforeEach(func() { 270 lorryClient, _ = NewHTTPClientWithPod(pod) 271 Expect(lorryClient).ShouldNot(BeNil()) 272 }) 273 274 It("success", func() { 275 mockDBManager.EXPECT().GrantUserRole(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 276 Expect(lorryClient.GrantUserRole(context.TODO(), "user-test", "readwrite")).Should(Succeed()) 277 }) 278 279 It("not implemented", func() { 280 mockDBManager.EXPECT().GrantUserRole(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf(msg)) 281 err := lorryClient.GrantUserRole(context.TODO(), "user-test", "readwrite") 282 Expect(err).Should(HaveOccurred()) 283 Expect(err.Error()).Should(ContainSubstring(msg)) 284 }) 285 }) 286 287 Context("revoke user role", func() { 288 var lorryClient *HTTPClient 289 290 BeforeEach(func() { 291 lorryClient, _ = NewHTTPClientWithPod(pod) 292 Expect(lorryClient).ShouldNot(BeNil()) 293 }) 294 295 It("success", func() { 296 mockDBManager.EXPECT().RevokeUserRole(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 297 Expect(lorryClient.RevokeUserRole(context.TODO(), "user-test", "readwrite")).Should(Succeed()) 298 }) 299 300 It("not implemented", func() { 301 mockDBManager.EXPECT().RevokeUserRole(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf(msg)) 302 err := lorryClient.RevokeUserRole(context.TODO(), "user-test", "readwrite") 303 Expect(err).Should(HaveOccurred()) 304 Expect(err.Error()).Should(ContainSubstring(msg)) 305 }) 306 }) 307 308 Context("list users", func() { 309 var lorryClient *HTTPClient 310 var users []models.UserInfo 311 312 BeforeEach(func() { 313 lorryClient, _ = NewHTTPClientWithPod(pod) 314 Expect(lorryClient).ShouldNot(BeNil()) 315 users = []models.UserInfo{ 316 { 317 UserName: "user1", 318 }, 319 { 320 UserName: "user2", 321 }, 322 } 323 }) 324 325 It("success", func() { 326 mockDBManager.EXPECT().ListUsers(gomock.Any()).Return(users, nil) 327 Expect(lorryClient.ListUsers(context.TODO())).Should(HaveLen(2)) 328 }) 329 330 It("not implemented", func() { 331 mockDBManager.EXPECT().ListUsers(gomock.Any()).Return(nil, fmt.Errorf(msg)) 332 users, err := lorryClient.ListUsers(context.TODO()) 333 Expect(err).Should(HaveOccurred()) 334 Expect(err.Error()).Should(ContainSubstring(msg)) 335 Expect(users).Should(BeEmpty()) 336 }) 337 }) 338 339 Context("join member", func() { 340 var lorryClient *HTTPClient 341 var cluster *dcs.Cluster 342 343 BeforeEach(func() { 344 lorryClient, _ = NewHTTPClientWithPod(pod) 345 Expect(lorryClient).ShouldNot(BeNil()) 346 cluster = &dcs.Cluster{} 347 }) 348 349 It("success if join once", func() { 350 mockDBManager.EXPECT().JoinCurrentMemberToCluster(gomock.Any(), gomock.Any()).Return(nil) 351 mockDCSStore.EXPECT().GetCluster().Return(cluster, nil) 352 Expect(lorryClient.JoinMember(context.TODO())).Should(Succeed()) 353 }) 354 355 It("success if join twice", func() { 356 mockDBManager.EXPECT().JoinCurrentMemberToCluster(gomock.Any(), gomock.Any()).Return(nil).Times(2) 357 mockDCSStore.EXPECT().GetCluster().Return(cluster, nil).Times(2) 358 // first join 359 Expect(lorryClient.JoinMember(context.TODO())).Should(Succeed()) 360 // second join 361 Expect(lorryClient.JoinMember(context.TODO())).Should(Succeed()) 362 }) 363 364 It("not implemented", func() { 365 mockDBManager.EXPECT().JoinCurrentMemberToCluster(gomock.Any(), gomock.Any()).Return(fmt.Errorf(msg)) 366 mockDCSStore.EXPECT().GetCluster().Return(cluster, nil) 367 err := lorryClient.JoinMember(context.TODO()) 368 Expect(err).Should(HaveOccurred()) 369 Expect(err.Error()).Should(ContainSubstring(msg)) 370 }) 371 }) 372 373 Context("leave member", func() { 374 var lorryClient *HTTPClient 375 var cluster *dcs.Cluster 376 var podName string 377 378 BeforeEach(func() { 379 lorryClient, _ = NewHTTPClientWithPod(pod) 380 Expect(lorryClient).ShouldNot(BeNil()) 381 podName = "pod-test" 382 383 cluster = &dcs.Cluster{ 384 HaConfig: &dcs.HaConfig{DeleteMembers: make(map[string]dcs.MemberToDelete)}, 385 Members: []dcs.Member{{Name: podName}}, 386 } 387 }) 388 389 It("success if leave once", func() { 390 mockDBManager.EXPECT().GetCurrentMemberName().Return("pod-test").Times(2) 391 mockDBManager.EXPECT().LeaveMemberFromCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 392 mockDCSStore.EXPECT().GetCluster().Return(cluster, nil) 393 mockDCSStore.EXPECT().UpdateHaConfig().Return(nil) 394 Expect(lorryClient.LeaveMember(context.TODO())).Should(Succeed()) 395 Expect(cluster.HaConfig.DeleteMembers).Should(HaveLen(1)) 396 }) 397 398 It("success if leave twice", func() { 399 mockDBManager.EXPECT().GetCurrentMemberName().Return("pod-test").Times(4) 400 mockDBManager.EXPECT().LeaveMemberFromCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) 401 mockDCSStore.EXPECT().GetCluster().Return(cluster, nil).Times(2) 402 mockDCSStore.EXPECT().UpdateHaConfig().Return(nil) 403 // first leave 404 Expect(lorryClient.LeaveMember(context.TODO())).Should(Succeed()) 405 Expect(cluster.HaConfig.DeleteMembers).Should(HaveLen(1)) 406 // second leave 407 Expect(lorryClient.LeaveMember(context.TODO())).Should(Succeed()) 408 Expect(cluster.HaConfig.DeleteMembers).Should(HaveLen(1)) 409 }) 410 411 It("not implemented", func() { 412 mockDBManager.EXPECT().GetCurrentMemberName().Return("pod-test").Times(2) 413 mockDBManager.EXPECT().LeaveMemberFromCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf(msg)) 414 mockDCSStore.EXPECT().GetCluster().Return(cluster, nil) 415 mockDCSStore.EXPECT().UpdateHaConfig().Return(nil) 416 err := lorryClient.LeaveMember(context.TODO()) 417 Expect(err).Should(HaveOccurred()) 418 Expect(err.Error()).Should(ContainSubstring(msg)) 419 }) 420 }) 421 }) 422 423 func newHTTPServer(resp []byte) (*httptest.Server, int) { 424 s := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 425 time.Sleep(100 * time.Millisecond) 426 writer.WriteHeader(200) 427 _, _ = writer.Write(resp) 428 })) 429 addr := s.Listener.Addr().String() 430 index := strings.LastIndex(addr, ":") 431 portStr := addr[index+1:] 432 port, _ := strconv.Atoi(portStr) 433 return s, port 434 }