github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/engines/custom/manager.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 custom 21 22 import ( 23 "context" 24 "encoding/json" 25 "fmt" 26 "io" 27 "net" 28 "net/http" 29 "net/url" 30 "regexp" 31 "strings" 32 "time" 33 34 "github.com/spf13/viper" 35 ctrl "sigs.k8s.io/controller-runtime" 36 37 "github.com/1aal/kubeblocks/pkg/common" 38 "github.com/1aal/kubeblocks/pkg/lorry/dcs" 39 "github.com/1aal/kubeblocks/pkg/lorry/engines" 40 ) 41 42 type Manager struct { 43 engines.DBManagerBase 44 actionSvcPorts *[]int 45 client *http.Client 46 } 47 48 var perNodeRegx = regexp.MustCompile("^[^,]*$") 49 50 func NewManager(properties engines.Properties) (engines.DBManager, error) { 51 logger := ctrl.Log.WithName("custom") 52 53 managerBase, err := engines.NewDBManagerBase(logger) 54 if err != nil { 55 return nil, err 56 } 57 58 mgr := &Manager{ 59 actionSvcPorts: &[]int{}, 60 DBManagerBase: *managerBase, 61 } 62 63 actionSvcList := viper.GetString("KB_RSM_ACTION_SVC_LIST") 64 if len(actionSvcList) > 0 { 65 err := json.Unmarshal([]byte(actionSvcList), mgr.actionSvcPorts) 66 if err != nil { 67 return nil, err 68 } 69 } 70 71 // See guidance on proper HTTP client settings here: 72 // https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779 73 dialer := &net.Dialer{ 74 Timeout: 5 * time.Second, 75 } 76 netTransport := &http.Transport{ 77 Dial: dialer.Dial, 78 TLSHandshakeTimeout: 5 * time.Second, 79 } 80 mgr.client = &http.Client{ 81 Timeout: time.Second * 30, 82 Transport: netTransport, 83 } 84 85 return mgr, nil 86 } 87 88 func (mgr *Manager) GetReplicaRole(ctx context.Context, cluster *dcs.Cluster) (string, error) { 89 if mgr.actionSvcPorts == nil { 90 return "", nil 91 } 92 93 var ( 94 lastOutput []byte 95 err error 96 ) 97 98 for _, port := range *mgr.actionSvcPorts { 99 u := fmt.Sprintf("http://127.0.0.1:%d/role?KB_RSM_LAST_STDOUT=%s", port, url.QueryEscape(string(lastOutput))) 100 lastOutput, err = mgr.callAction(ctx, u) 101 if err != nil { 102 return "", err 103 } 104 mgr.Logger.Info("action succeed", "url", u, "output", string(lastOutput)) 105 } 106 finalOutput := strings.TrimSpace(string(lastOutput)) 107 108 if perNodeRegx.MatchString(finalOutput) { 109 return finalOutput, nil 110 } 111 112 // csv format: term,podName,role 113 parseCSV := func(input string) (string, error) { 114 res := common.GlobalRoleSnapshot{} 115 lines := strings.Split(input, "\n") 116 for _, line := range lines { 117 fields := strings.Split(strings.TrimSpace(line), ",") 118 if len(fields) != 3 { 119 return "", err 120 } 121 res.Version = strings.TrimSpace(fields[0]) 122 pair := common.PodRoleNamePair{ 123 PodName: strings.TrimSpace(fields[1]), 124 RoleName: strings.ToLower(strings.TrimSpace(fields[2])), 125 } 126 res.PodRoleNamePairs = append(res.PodRoleNamePairs, pair) 127 } 128 resByte, err := json.Marshal(res) 129 return string(resByte), err 130 } 131 return parseCSV(finalOutput) 132 } 133 134 // callAction performs an HTTP request to local HTTP endpoint specified by actionSvcPort 135 func (mgr *Manager) callAction(ctx context.Context, url string) ([]byte, error) { 136 // compose http request 137 request, err := http.NewRequestWithContext(ctx, "POST", url, nil) 138 if err != nil { 139 return nil, err 140 } 141 142 // send http request 143 resp, err := mgr.client.Do(request) 144 if err != nil { 145 return nil, err 146 } 147 defer resp.Body.Close() 148 149 // parse http response 150 if resp.StatusCode/100 != 2 { 151 return nil, fmt.Errorf("received status code %d", resp.StatusCode) 152 } 153 b, err := io.ReadAll(resp.Body) 154 if err != nil { 155 return nil, err 156 } 157 158 return b, err 159 }