github.com/kaydxh/golang@v0.0.131/pkg/discovery/consul/discovery.go (about)

     1  /*
     2   *Copyright (c) 2022, kaydxh
     3   *
     4   *Permission is hereby granted, free of charge, to any person obtaining a copy
     5   *of this software and associated documentation files (the "Software"), to deal
     6   *in the Software without restriction, including without limitation the rights
     7   *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   *copies of the Software, and to permit persons to whom the Software is
     9   *furnished to do so, subject to the following conditions:
    10   *
    11   *The above copyright notice and this permission notice shall be included in all
    12   *copies or substantial portions of the Software.
    13   *
    14   *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   *SOFTWARE.
    21   */
    22  package consul
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"net"
    28  	"strconv"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/hashicorp/consul/api"
    33  	"github.com/kaydxh/golang/go/errors"
    34  	"github.com/sirupsen/logrus"
    35  	"go.uber.org/atomic"
    36  )
    37  
    38  type ServiceRegistryServer struct {
    39  	ConsulAddress string
    40  	ServiceId     string
    41  	ServiceName   string
    42  	Tag           []string
    43  	Ip            string
    44  	Port          int
    45  	TTL           time.Duration
    46  	CheckInterval time.Duration
    47  
    48  	inShutdown atomic.Bool // true when when server is in shutdown
    49  
    50  	mu     sync.Mutex
    51  	cancel func()
    52  }
    53  
    54  func NewServiceRegistry(
    55  	consulAddr string,
    56  	serviceName string,
    57  	serviceAddress string,
    58  ) (*ServiceRegistryServer, error) {
    59  	logger := logrus.WithField("module", "service_registry").
    60  		WithField("service_name", serviceName).
    61  		WithField("addr", serviceAddress)
    62  
    63  	host, port, err := net.SplitHostPort(serviceAddress)
    64  	if err != nil {
    65  		logger.WithError(err).Errorln("malformed service serviceAddress")
    66  		return nil, fmt.Errorf("malformed service serviceAddress: %w", err)
    67  	}
    68  
    69  	nport, err := strconv.Atoi(port)
    70  	if err != nil {
    71  		logger.WithField("port", port).
    72  			WithError(err).
    73  			Errorln("malformed service port, must be a number")
    74  		return nil, fmt.Errorf("malformed service port: %w", err)
    75  	}
    76  	serviceId := fmt.Sprintf("%v-%v-%v", serviceName, host, port)
    77  	s := &ServiceRegistryServer{
    78  		ConsulAddress: consulAddr,
    79  		ServiceId:     serviceId,
    80  		ServiceName:   serviceName,
    81  		Tag:           []string{},
    82  		Port:          nport,
    83  		Ip:            host,
    84  		TTL:           300 * time.Second,
    85  		CheckInterval: 10 * time.Second,
    86  	}
    87  	return s, nil
    88  }
    89  
    90  // Run will initialize the backend. It must not block, but may run go routines in the background.
    91  func (srv *ServiceRegistryServer) Run(ctx context.Context) error {
    92  	logger := srv.logger().
    93  		WithField("service_name", srv.ServiceName).
    94  		WithField("service_id", srv.ServiceId)
    95  	logger.Infoln("ConsulRegistry Run")
    96  	if srv.inShutdown.Load() {
    97  		logger.Infoln("ConsulRegistry Shutdown")
    98  		return fmt.Errorf("server closed")
    99  	}
   100  	go func() {
   101  		errors.HandleError(srv.Serve(ctx))
   102  	}()
   103  	return nil
   104  }
   105  
   106  func (srv *ServiceRegistryServer) Serve(ctx context.Context) error {
   107  	logger := srv.logger().
   108  		WithField("service_name", srv.ServiceName).
   109  		WithField("service_id", srv.ServiceId)
   110  	logger.Infoln("ConsulRegistry Serve")
   111  
   112  	if srv.inShutdown.Load() {
   113  		logger.Infoln("ConsulRegistry Shutdown")
   114  		return fmt.Errorf("server closed")
   115  	}
   116  
   117  	defer srv.inShutdown.Store(true)
   118  	ctx, cancel := context.WithCancel(ctx)
   119  	srv.mu.Lock()
   120  	srv.cancel = cancel
   121  	srv.mu.Unlock()
   122  
   123  	t := time.NewTicker(time.Second * 30)
   124  	defer t.Stop()
   125  	for {
   126  		select {
   127  		case <-t.C:
   128  			logger.Infoln("registering service to consul")
   129  			err := srv.Register()
   130  			if err != nil {
   131  				logger.WithError(err).Errorln("register service failed")
   132  				continue
   133  			}
   134  			srv.logger().Info("register service by consul")
   135  
   136  		case <-ctx.Done():
   137  			logger.Infoln("unregistering service to consul")
   138  			err := srv.UnRegister()
   139  			if err != nil {
   140  				logger.WithError(err).Errorln("unregister service failed")
   141  				return err
   142  			}
   143  			srv.logger().Info("unregister service by consul")
   144  			return nil
   145  		}
   146  	}
   147  }
   148  
   149  func (srv *ServiceRegistryServer) Shutdown() {
   150  	srv.inShutdown.Store(true)
   151  	srv.mu.Lock()
   152  	defer srv.mu.Unlock()
   153  	if srv.cancel != nil {
   154  		srv.cancel()
   155  	}
   156  }
   157  
   158  func (srv *ServiceRegistryServer) Register() error {
   159  	config := api.DefaultConfig()
   160  	config.Address = srv.ConsulAddress
   161  	client, err := api.NewClient(config)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	agent := client.Agent()
   166  
   167  	checkUrl := fmt.Sprintf("http://%v:%v/api/%v/v1/health", srv.Ip, srv.Port, srv.ServiceName)
   168  
   169  	reg := &api.AgentServiceRegistration{
   170  		ID:      srv.ServiceId,
   171  		Name:    srv.ServiceName,
   172  		Tags:    srv.Tag,
   173  		Port:    srv.Port,
   174  		Address: srv.Ip,
   175  		Check: &api.AgentServiceCheck{
   176  			Interval:                       srv.CheckInterval.String(),
   177  			HTTP:                           checkUrl,
   178  			DeregisterCriticalServiceAfter: srv.TTL.String(),
   179  		},
   180  	}
   181  
   182  	return agent.ServiceRegister(reg)
   183  }
   184  
   185  func (srv *ServiceRegistryServer) UnRegister() error {
   186  	config := api.DefaultConfig()
   187  	config.Address = srv.ConsulAddress
   188  	client, err := api.NewClient(config)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	return client.Agent().ServiceDeregister(srv.ServiceId)
   194  }
   195  
   196  func (srv *ServiceRegistryServer) logger() logrus.FieldLogger {
   197  	return logrus.
   198  		WithField("module", "service_registry").
   199  		WithField("consul", srv.ConsulAddress).
   200  		WithField("service_name", srv.ServiceName).
   201  		WithField("service_id", srv.ServiceId).
   202  		WithField("ip", srv.Ip).WithField("port", srv.Port)
   203  }