github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/owner/owner.go (about) 1 // Copyright 2020 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package owner 15 16 import ( 17 "context" 18 "encoding/json" 19 "io" 20 "net/http" 21 "strconv" 22 "time" 23 24 "github.com/gin-gonic/gin" 25 "github.com/pingcap/errors" 26 "github.com/pingcap/log" 27 "github.com/pingcap/tiflow/cdc/api" 28 "github.com/pingcap/tiflow/cdc/api/middleware" 29 "github.com/pingcap/tiflow/cdc/capture" 30 "github.com/pingcap/tiflow/cdc/model" 31 cerror "github.com/pingcap/tiflow/pkg/errors" 32 "github.com/pingcap/tiflow/pkg/logutil" 33 "github.com/tikv/client-go/v2/oracle" 34 "go.etcd.io/etcd/client/v3/concurrency" 35 "go.uber.org/zap" 36 ) 37 38 type commonResp struct { 39 Status bool `json:"status"` 40 Message string `json:"message"` 41 } 42 43 // ChangefeedResp holds the most common usage information for a changefeed 44 type ChangefeedResp struct { 45 FeedState string `json:"state"` 46 TSO uint64 `json:"tso"` 47 Checkpoint string `json:"checkpoint"` 48 RunningError *model.RunningError `json:"error"` 49 } 50 51 // MarshalJSON use to marshal ChangefeedResp 52 func (c ChangefeedResp) MarshalJSON() ([]byte, error) { 53 // alias the original type to prevent recursive call of MarshalJSON 54 type Alias ChangefeedResp 55 if c.FeedState == string(model.StateNormal) { 56 c.RunningError = nil 57 } 58 return json.Marshal(struct { 59 Alias 60 }{ 61 Alias: Alias(c), 62 }) 63 } 64 65 // ownerAPI provides owner APIs. 66 type ownerAPI struct { 67 capture capture.Capture 68 } 69 70 // RegisterOwnerAPIRoutes registers routes for owner APIs. 71 func RegisterOwnerAPIRoutes(router *gin.Engine, capture capture.Capture) { 72 ownerAPI := ownerAPI{capture: capture} 73 owner := router.Group("/capture/owner") 74 75 owner.Use(middleware.ErrorHandleMiddleware()) 76 owner.Use(middleware.LogMiddleware()) 77 78 owner.POST("/resign", gin.WrapF(ownerAPI.handleResignOwner)) 79 owner.POST("/admin", gin.WrapF(ownerAPI.handleChangefeedAdmin)) 80 owner.POST("/rebalance_trigger", gin.WrapF(ownerAPI.handleRebalanceTrigger)) 81 owner.POST("/move_table", gin.WrapF(ownerAPI.handleMoveTable)) 82 owner.POST("/changefeed/query", gin.WrapF(ownerAPI.handleChangefeedQuery)) 83 } 84 85 func handleOwnerResp(w http.ResponseWriter, err error) { 86 if err != nil { 87 if errors.Cause(err) == concurrency.ErrElectionNotLeader { 88 api.WriteError(w, http.StatusBadRequest, err) 89 return 90 } 91 api.WriteError(w, http.StatusInternalServerError, err) 92 return 93 } 94 api.WriteData(w, commonResp{Status: true}) 95 } 96 97 func (h *ownerAPI) handleResignOwner(w http.ResponseWriter, req *http.Request) { 98 if h.capture == nil { 99 // for test only 100 handleOwnerResp(w, concurrency.ErrElectionNotLeader) 101 return 102 } 103 o, err := h.capture.GetOwner() 104 if o != nil { 105 o.AsyncStop() 106 } 107 handleOwnerResp(w, err) 108 } 109 110 func (h *ownerAPI) handleChangefeedAdmin(w http.ResponseWriter, req *http.Request) { 111 if h.capture == nil { 112 // for test only 113 handleOwnerResp(w, concurrency.ErrElectionNotLeader) 114 return 115 } 116 117 err := req.ParseForm() 118 if err != nil { 119 api.WriteError(w, http.StatusInternalServerError, err) 120 return 121 } 122 typeStr := req.Form.Get(api.OpVarAdminJob) 123 typ, err := strconv.ParseInt(typeStr, 10, 64) 124 if err != nil { 125 api.WriteError(w, http.StatusBadRequest, 126 cerror.ErrAPIInvalidParam.GenWithStack("invalid admin job type: %s", typeStr)) 127 return 128 } 129 job := model.AdminJob{ 130 CfID: model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID)), 131 Type: model.AdminJobType(typ), 132 } 133 134 err = api.HandleOwnerJob(req.Context(), h.capture, job) 135 handleOwnerResp(w, err) 136 } 137 138 func (h *ownerAPI) handleRebalanceTrigger(w http.ResponseWriter, req *http.Request) { 139 if h.capture == nil { 140 // for test only 141 handleOwnerResp(w, concurrency.ErrElectionNotLeader) 142 return 143 } 144 145 err := req.ParseForm() 146 if err != nil { 147 api.WriteError(w, http.StatusInternalServerError, err) 148 return 149 } 150 changefeedID := model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID)) 151 if err := model.ValidateChangefeedID(changefeedID.ID); err != nil { 152 api.WriteError(w, http.StatusBadRequest, 153 cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID.ID)) 154 return 155 } 156 157 err = api.HandleOwnerBalance(req.Context(), h.capture, changefeedID) 158 handleOwnerResp(w, err) 159 } 160 161 func (h *ownerAPI) handleMoveTable(w http.ResponseWriter, req *http.Request) { 162 if h.capture == nil { 163 // for test only 164 handleOwnerResp(w, concurrency.ErrElectionNotLeader) 165 return 166 } 167 168 err := req.ParseForm() 169 if err != nil { 170 api.WriteError(w, http.StatusInternalServerError, 171 cerror.WrapError(cerror.ErrInternalServerError, err)) 172 return 173 } 174 changefeedID := model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID)) 175 if err := model.ValidateChangefeedID(changefeedID.ID); err != nil { 176 api.WriteError(w, http.StatusBadRequest, 177 cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID.ID)) 178 return 179 } 180 to := req.Form.Get(api.OpVarTargetCaptureID) 181 if err := model.ValidateChangefeedID(to); err != nil { 182 api.WriteError(w, http.StatusBadRequest, 183 cerror.ErrAPIInvalidParam.GenWithStack("invalid target capture id: %s", to)) 184 return 185 } 186 tableIDStr := req.Form.Get(api.OpVarTableID) 187 tableID, err := strconv.ParseInt(tableIDStr, 10, 64) 188 if err != nil { 189 api.WriteError(w, http.StatusBadRequest, 190 cerror.ErrAPIInvalidParam.GenWithStack("invalid tableID: %s", tableIDStr)) 191 return 192 } 193 194 err = api.HandleOwnerScheduleTable( 195 req.Context(), h.capture, changefeedID, to, tableID) 196 handleOwnerResp(w, err) 197 } 198 199 func (h *ownerAPI) handleChangefeedQuery(w http.ResponseWriter, req *http.Request) { 200 if h.capture == nil { 201 // for test only 202 handleOwnerResp(w, concurrency.ErrElectionNotLeader) 203 return 204 } 205 206 err := req.ParseForm() 207 if err != nil { 208 api.WriteError(w, http.StatusInternalServerError, err) 209 return 210 } 211 changefeedID := model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID)) 212 if err := model.ValidateChangefeedID(changefeedID.ID); err != nil { 213 api.WriteError(w, http.StatusBadRequest, 214 cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID.ID)) 215 return 216 } 217 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 218 defer cancel() 219 cfInfo, err := h.capture.GetEtcdClient().GetChangeFeedInfo(ctx, changefeedID) 220 if err != nil && cerror.ErrChangeFeedNotExists.NotEqual(err) { 221 api.WriteError(w, http.StatusBadRequest, 222 cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID)) 223 return 224 } 225 cfStatus, _, err := h.capture.GetEtcdClient().GetChangeFeedStatus(ctx, changefeedID) 226 if err != nil && cerror.ErrChangeFeedNotExists.NotEqual(err) { 227 api.WriteError(w, http.StatusBadRequest, err) 228 return 229 } 230 231 resp := &ChangefeedResp{} 232 if cfInfo != nil { 233 resp.FeedState = string(cfInfo.State) 234 resp.RunningError = cfInfo.Error 235 } 236 if cfStatus != nil { 237 resp.TSO = cfStatus.CheckpointTs 238 tm := oracle.GetTimeFromTS(cfStatus.CheckpointTs) 239 resp.Checkpoint = tm.Format("2006-01-02 15:04:05.000") 240 } 241 api.WriteData(w, resp) 242 } 243 244 // HandleAdminLogLevel handles requests to set the log level. 245 func HandleAdminLogLevel(w http.ResponseWriter, r *http.Request) { 246 var level string 247 data, err := io.ReadAll(r.Body) 248 r.Body.Close() 249 if err != nil { 250 api.WriteError(w, http.StatusInternalServerError, err) 251 return 252 } 253 err = json.Unmarshal(data, &level) 254 if err != nil { 255 api.WriteError(w, http.StatusBadRequest, 256 cerror.ErrAPIInvalidParam.GenWithStack("invalid log level: %s", err)) 257 return 258 } 259 260 err = logutil.SetLogLevel(level) 261 if err != nil { 262 api.WriteError(w, http.StatusBadRequest, 263 cerror.ErrAPIInvalidParam.GenWithStack("fail to change log level: %s", err)) 264 return 265 } 266 log.Warn("log level changed", zap.String("level", level)) 267 268 api.WriteData(w, struct{}{}) 269 }