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  }