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  }