github.com/polarismesh/polaris@v1.17.8/apiserver/eurekaserver/replicate.go (about) 1 /** 2 * Tencent is pleased to support the open source community by making Polaris available. 3 * 4 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 * 6 * Licensed under the BSD 3-Clause License (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * https://opensource.org/licenses/BSD-3-Clause 11 * 12 * Unless required by applicable law or agreed to in writing, software distributed 13 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 * specific language governing permissions and limitations under the License. 16 */ 17 18 package eurekaserver 19 20 import ( 21 "context" 22 "net/http" 23 "strconv" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/emicklei/go-restful/v3" 29 "github.com/golang/protobuf/ptypes/wrappers" 30 apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage" 31 32 api "github.com/polarismesh/polaris/common/api/v1" 33 "github.com/polarismesh/polaris/common/model" 34 "github.com/polarismesh/polaris/common/utils" 35 "github.com/polarismesh/polaris/service" 36 ) 37 38 const ( 39 actionRegister = "Register" 40 actionHeartbeat = "Heartbeat" 41 actionCancel = "Cancel" 42 actionStatusUpdate = "StatusUpdate" 43 actionDeleteStatusOverride = "DeleteStatusOverride" 44 ) 45 46 const ( 47 headerIdentityName = "DiscoveryIdentity-Name" 48 headerIdentityVersion = "DiscoveryIdentity-Version" 49 headerIdentityId = "DiscoveryIdentity-Id" 50 valueIdentityName = "PolarisServer" 51 ) 52 53 // BatchReplication do the server request replication 54 func (h *EurekaServer) BatchReplication(req *restful.Request, rsp *restful.Response) { 55 eurekalog.Infof("[EUREKA-SERVER] received replicate request %+v", req) 56 sourceSvrName := req.HeaderParameter(headerIdentityName) 57 remoteAddr := req.Request.RemoteAddr 58 if sourceSvrName == valueIdentityName { 59 // we should not process the replication from polaris 60 batchResponse := &ReplicationListResponse{ResponseList: []*ReplicationInstanceResponse{}} 61 if err := writeEurekaResponse(restful.MIME_JSON, batchResponse, req, rsp); nil != err { 62 eurekalog.Errorf("[EurekaServer]fail to write replicate response, client: %s, err: %v", remoteAddr, err) 63 } 64 return 65 } 66 replicateRequest := &ReplicationList{} 67 var err error 68 err = req.ReadEntity(replicateRequest) 69 if nil != err { 70 eurekalog.Errorf("[EUREKA-SERVER] fail to parse peer replicate request, uri: %s, client: %s, err: %v", 71 req.Request.RequestURI, remoteAddr, err) 72 writePolarisStatusCode(req, api.ParseException) 73 writeHeader(http.StatusBadRequest, rsp) 74 return 75 } 76 token, err := getAuthFromEurekaRequestHeader(req) 77 if err != nil { 78 eurekalog.Infof("[EUREKA-SERVER]replicate request get basic auth info fail, code is %d", api.ExecuteException) 79 writePolarisStatusCode(req, api.ExecuteException) 80 writeHeader(http.StatusForbidden, rsp) 81 return 82 } 83 namespace := readNamespaceFromRequest(req, h.namespace) 84 batchResponse, resultCode := h.doBatchReplicate(replicateRequest, token, namespace) 85 if err := writeEurekaResponseWithCode(restful.MIME_JSON, batchResponse, req, rsp, resultCode); nil != err { 86 eurekalog.Errorf("[EurekaServer]fail to write replicate response, client: %s, err: %v", remoteAddr, err) 87 } 88 } 89 90 func (h *EurekaServer) doBatchReplicate( 91 replicateRequest *ReplicationList, token string, namespace string) (*ReplicationListResponse, uint32) { 92 batchResponse := &ReplicationListResponse{} 93 var resultCode = api.ExecuteSuccess 94 itemCount := len(replicateRequest.ReplicationList) 95 if itemCount == 0 { 96 return batchResponse, resultCode 97 } 98 batchResponse.ResponseList = make([]*ReplicationInstanceResponse, itemCount) 99 wg := &sync.WaitGroup{} 100 wg.Add(itemCount) 101 mutex := &sync.Mutex{} 102 for i, inst := range replicateRequest.ReplicationList { 103 go func(idx int, instanceInfo *ReplicationInstance) { 104 defer wg.Done() 105 resp, code := h.dispatch(instanceInfo, token, namespace) 106 if code != api.ExecuteSuccess { 107 atomic.CompareAndSwapUint32(&resultCode, api.ExecuteSuccess, code) 108 eurekalog.Warnf("[EUREKA-SERVER] fail to process replicate instance request, code is %d, "+ 109 "action %s, instance %s, app %s", 110 code, instanceInfo.Action, instanceInfo.Id, instanceInfo.AppName) 111 } 112 mutex.Lock() 113 batchResponse.ResponseList[idx] = resp 114 mutex.Unlock() 115 }(i, inst) 116 } 117 wg.Wait() 118 return batchResponse, resultCode 119 } 120 121 func (h *EurekaServer) dispatch( 122 replicationInstance *ReplicationInstance, token string, namespace string) (*ReplicationInstanceResponse, uint32) { 123 appName := formatReadName(replicationInstance.AppName) 124 ctx := context.WithValue(context.Background(), utils.ContextAuthTokenKey, token) 125 var retCode = api.ExecuteSuccess 126 eurekalog.Debugf("[EurekaServer]dispatch replicate request %+v", replicationInstance) 127 if nil != replicationInstance.InstanceInfo { 128 _ = convertInstancePorts(replicationInstance.InstanceInfo) 129 eurekalog.Debugf("[EurekaServer]dispatch replicate instance %+v, port %+v, sport %+v", 130 replicationInstance.InstanceInfo, replicationInstance.InstanceInfo.Port, replicationInstance.InstanceInfo.SecurePort) 131 } 132 switch replicationInstance.Action { 133 case actionRegister: 134 instanceInfo := replicationInstance.InstanceInfo 135 retCode = h.registerInstances(ctx, namespace, appName, instanceInfo, true) 136 if retCode == api.ExecuteSuccess || retCode == api.ExistedResource || retCode == api.SameInstanceRequest { 137 retCode = api.ExecuteSuccess 138 } 139 case actionHeartbeat: 140 instanceId := replicationInstance.Id 141 retCode = h.renew(ctx, namespace, appName, instanceId, true) 142 if retCode == api.ExecuteSuccess || retCode == api.HeartbeatExceedLimit { 143 retCode = api.ExecuteSuccess 144 } 145 case actionCancel: 146 instanceId := replicationInstance.Id 147 retCode = h.deregisterInstance(ctx, namespace, appName, instanceId, true) 148 if retCode == api.ExecuteSuccess || retCode == api.NotFoundResource || retCode == api.SameInstanceRequest { 149 retCode = api.ExecuteSuccess 150 } 151 case actionStatusUpdate: 152 status := replicationInstance.Status 153 instanceId := replicationInstance.Id 154 retCode = h.updateStatus(ctx, namespace, appName, instanceId, status, true) 155 case actionDeleteStatusOverride: 156 instanceId := replicationInstance.Id 157 retCode = h.updateStatus(ctx, namespace, appName, instanceId, StatusUp, true) 158 } 159 160 statusCode := http.StatusOK 161 if retCode == api.NotFoundResource { 162 statusCode = http.StatusNotFound 163 } 164 return &ReplicationInstanceResponse{ 165 StatusCode: statusCode, 166 }, retCode 167 } 168 169 func eventToInstance(event *model.InstanceEvent, appName string, curTimeMilli int64) *InstanceInfo { 170 instance := &apiservice.Instance{ 171 Id: &wrappers.StringValue{Value: event.Id}, 172 Host: &wrappers.StringValue{Value: event.Instance.GetHost().GetValue()}, 173 Port: &wrappers.UInt32Value{Value: event.Instance.GetPort().GetValue()}, 174 Protocol: &wrappers.StringValue{Value: event.Instance.GetProtocol().GetValue()}, 175 Version: &wrappers.StringValue{Value: event.Instance.GetVersion().GetValue()}, 176 Priority: &wrappers.UInt32Value{Value: event.Instance.GetPriority().GetValue()}, 177 Weight: &wrappers.UInt32Value{Value: event.Instance.GetWeight().GetValue()}, 178 EnableHealthCheck: &wrappers.BoolValue{Value: event.Instance.GetEnableHealthCheck().GetValue()}, 179 HealthCheck: event.Instance.GetHealthCheck(), 180 Healthy: &wrappers.BoolValue{Value: event.Instance.GetHealthy().GetValue()}, 181 Isolate: &wrappers.BoolValue{Value: event.Instance.GetIsolate().GetValue()}, 182 Location: event.Instance.GetLocation(), 183 Metadata: event.Instance.GetMetadata(), 184 } 185 if event.EType == model.EventInstanceTurnHealth { 186 instance.Healthy = &wrappers.BoolValue{Value: true} 187 } else if event.EType == model.EventInstanceTurnUnHealth { 188 instance.Healthy = &wrappers.BoolValue{Value: false} 189 } else if event.EType == model.EventInstanceOpenIsolate { 190 instance.Isolate = &wrappers.BoolValue{Value: true} 191 } else if event.EType == model.EventInstanceCloseIsolate { 192 instance.Isolate = &wrappers.BoolValue{Value: false} 193 } 194 return buildInstance(appName, instance, curTimeMilli) 195 } 196 197 func (h *EurekaServer) shouldReplicate(e model.InstanceEvent) bool { 198 if h.replicateWorkers == nil { 199 return false 200 } 201 if _, exist := h.replicateWorkers.Get(e.Namespace); !exist { 202 return false 203 } 204 205 if len(e.Service) == 0 { 206 eurekalog.Warnf("[EUREKA]fail to replicate, service name is empty for event %s", e) 207 return false 208 } 209 metadata := e.MetaData 210 if len(metadata) > 0 { 211 if value, ok := metadata[MetadataReplicate]; ok { 212 // we should not replicate around 213 isReplicate, _ := strconv.ParseBool(value) 214 return !isReplicate 215 } 216 } 217 return true 218 } 219 220 type EurekaInstanceEventHandler struct { 221 *service.BaseInstanceEventHandler 222 svr *EurekaServer 223 } 224 225 func (e *EurekaInstanceEventHandler) OnEvent(ctx context.Context, any2 any) error { 226 return e.svr.handleInstanceEvent(ctx, any2) 227 } 228 229 func (h *EurekaServer) handleInstanceEvent(ctx context.Context, i interface{}) error { 230 e := i.(model.InstanceEvent) 231 if !h.shouldReplicate(e) { 232 return nil 233 } 234 namespace := e.Namespace 235 appName := formatReadName(e.Service) 236 curTimeMilli := time.Now().UnixMilli() 237 eurekaInstanceId := e.Id 238 if e.Instance.Metadata != nil { 239 if _, ok := e.Instance.Metadata[MetadataInstanceId]; ok { 240 eurekaInstanceId = e.Instance.Metadata[MetadataInstanceId] 241 } 242 } 243 if _, ok := e.MetaData[MetadataInstanceId]; ok { 244 eurekaInstanceId = e.MetaData[MetadataInstanceId] 245 } 246 replicateWorker, _ := h.replicateWorkers.Get(namespace) 247 switch e.EType { 248 case model.EventInstanceOnline, model.EventInstanceUpdate, model.EventInstanceTurnHealth: 249 instanceInfo := eventToInstance(&e, appName, curTimeMilli) 250 replicateWorker.AddReplicateTask(&ReplicationInstance{ 251 AppName: appName, 252 Id: eurekaInstanceId, 253 LastDirtyTimestamp: curTimeMilli, 254 Status: instanceInfo.Status, 255 InstanceInfo: instanceInfo, 256 Action: actionRegister, 257 }) 258 case model.EventInstanceOffline, model.EventInstanceTurnUnHealth: 259 replicateWorker.AddReplicateTask(&ReplicationInstance{ 260 AppName: appName, 261 Id: eurekaInstanceId, 262 Action: actionCancel, 263 }) 264 case model.EventInstanceSendHeartbeat: 265 instanceInfo := eventToInstance(&e, appName, curTimeMilli) 266 rInstance := &ReplicationInstance{ 267 AppName: appName, 268 Id: eurekaInstanceId, 269 Status: instanceInfo.Status, 270 InstanceInfo: instanceInfo, 271 Action: actionHeartbeat, 272 } 273 replicateWorker.AddReplicateTask(rInstance) 274 case model.EventInstanceOpenIsolate, model.EventInstanceCloseIsolate: 275 replicateWorker.AddReplicateTask(&ReplicationInstance{ 276 AppName: appName, 277 Id: eurekaInstanceId, 278 LastDirtyTimestamp: curTimeMilli, 279 Status: parseStatus(e.Instance), 280 Action: actionStatusUpdate, 281 }) 282 283 } 284 return nil 285 }