add registry base interface, mdns, noop implementations, add resolver, client

This commit is contained in:
2020-11-08 19:28:33 +01:00
parent 87b947cea3
commit 9f5f03b862
29 changed files with 4341 additions and 41 deletions

View File

@ -6,12 +6,15 @@ import (
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"github.com/jinzhu/gorm"
"go.uber.org/multierr"
"google.golang.org/grpc"
"gitlab.bertha.cloud/partitio/lab/grpc/certs"
"gitlab.bertha.cloud/partitio/lab/grpc/registry"
"gitlab.bertha.cloud/partitio/lab/grpc/utils/addr"
)
/*
@ -27,32 +30,34 @@ GLOBAL OPTIONS:
--ca_cert CA_CERT
--server_cert SERVER_CERT
--server_key SERVER_KEY
--register_ttl REGISTER_TTL
--register_interval REGISTER_INTERVAL
--server_address SERVER_ADDRESS
--server_address SERVER_ADDRESS
--server_name SERVER_NAME
--broker BROKER
--broker_address BROKER_ADDRESS
--registry REGISTRY
--registry_address REGISTRY_ADDRESS
--db_path DB_PATH
*/
type Options interface {
Context() context.Context
Name() string
Version() string
Address() string
Reflection() bool
Secure() bool
CACert() string
Cert() string
Key() string
TLSConfig() *tls.Config
Secure() bool
Registry() registry.Registry
DB() *gorm.DB
BeforeStart() []func() error
AfterStart() []func() error
@ -90,6 +95,18 @@ func WithName(name string) Option {
}
}
func WithVersion(version string) Option {
return func(o *options) {
o.version = version
}
}
func WithRegistry(registry registry.Registry) Option {
return func(o *options) {
o.registry = registry
}
}
// WithContext specifies a context for the service.
// Can be used to signal shutdown of the service.
// Can be used for extra option values.
@ -215,6 +232,7 @@ func WithSubscriberInterceptor(w ...interface{}) Option {
type options struct {
ctx context.Context
name string
version string
address string
secure bool
reflection bool
@ -224,6 +242,8 @@ type options struct {
tlsConfig *tls.Config
db *gorm.DB
registry registry.Registry
beforeStart []func() error
afterStart []func() error
beforeStop []func() error
@ -237,13 +257,17 @@ type options struct {
clientInterceptors []grpc.UnaryClientInterceptor
streamClientInterceptors []grpc.StreamClientInterceptor
error error
error error
}
func (o *options) Name() string {
return o.name
}
func (o *options) Version() string {
return o.version
}
func (o *options) Context() context.Context {
return o.ctx
}
@ -252,12 +276,12 @@ func (o *options) Address() string {
return o.address
}
func (o *options) Reflection() bool {
return o.reflection
func (o *options) Registry() registry.Registry {
return o.registry
}
func (o *options) Secure() bool {
return o.secure
func (o *options) Reflection() bool {
return o.reflection
}
func (o *options) CACert() string {
@ -276,6 +300,10 @@ func (o *options) TLSConfig() *tls.Config {
return o.tlsConfig
}
func (o *options) Secure() bool {
return o.secure
}
func (o *options) DB() *gorm.DB {
return o.db
}
@ -317,20 +345,36 @@ func (o *options) StreamClientInterceptors() []grpc.StreamClientInterceptor {
}
func (o *options) parseTLSConfig() error {
if (o.tlsConfig != nil) {
if o.tlsConfig != nil {
return nil
}
if !o.hasTLSConfig() {
if !o.secure {
return nil
}
cert, err := certs.New(o.address, "localhost", "127.0.0.1", o.name)
var hosts []string
if host, _, err := net.SplitHostPort(o.address); err == nil {
if len(host) == 0 {
hosts = addr.IPs()
} else {
hosts = []string{host}
}
}
for i, h := range hosts {
a, err := addr.Extract(h)
if err != nil {
return err
}
hosts[i] = a
}
// generate a certificate
cert, err := certs.New(hosts...)
if err != nil {
return err
}
o.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
ClientAuth: tls.NoClientCert,
}
return nil
}

View File

@ -4,15 +4,25 @@ import (
"context"
"net"
"os"
"strings"
"sync"
"time"
"github.com/google/uuid"
grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/jinzhu/gorm"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.uber.org/multierr"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
"gitlab.bertha.cloud/partitio/lab/grpc/registry"
"gitlab.bertha.cloud/partitio/lab/grpc/registry/noop"
"gitlab.bertha.cloud/partitio/lab/grpc/utils/addr"
"gitlab.bertha.cloud/partitio/lab/grpc/utils/backoff"
net2 "gitlab.bertha.cloud/partitio/lab/grpc/utils/net"
)
type Service interface {
@ -37,6 +47,10 @@ type service struct {
list net.Listener
mu sync.Mutex
running bool
id string
regSvc *registry.Service
closed chan struct{}
}
func newService(opts ...Option) (*service, error) {
@ -44,6 +58,7 @@ func newService(opts ...Option) (*service, error) {
s := &service{
opts: parseFlags(NewOptions()),
cmd: cmd,
id: uuid.New().String(),
}
s.mu.Lock()
defer s.mu.Unlock()
@ -62,6 +77,9 @@ func newService(opts ...Option) (*service, error) {
}
}
}()
if s.opts.registry == nil {
s.opts.registry = noop.New()
}
var err error
s.list, err = net.Listen("tcp", s.opts.address)
if err != nil {
@ -78,13 +96,12 @@ func newService(opts ...Option) (*service, error) {
return s.run()
}
gopts := []grpc.ServerOption{
grpc.Creds(credentials.NewTLS(s.opts.tlsConfig)),
grpc.UnaryInterceptor(
grpcmiddleware.ChainUnaryServer(s.opts.serverInterceptors...),
),
}
if s.opts.tlsConfig != nil {
gopts = append(gopts)
gopts = append(gopts, grpc.Creds(credentials.NewTLS(s.opts.tlsConfig)))
}
s.server = grpc.NewServer(append(gopts, s.opts.serverOpts...)...)
if s.opts.reflection {
@ -109,15 +126,95 @@ func (s *service) Cmd() *cobra.Command {
return s.cmd
}
func (s *service) register() error {
const (
defaultRegisterInterval = time.Second * 30
defaultRegisterTTL = time.Second * 90
)
regFunc := func(service *registry.Service) error {
var regErr error
for i := 0; i < 3; i++ {
// set the ttl
rOpts := []registry.RegisterOption{registry.RegisterTTL(defaultRegisterTTL)}
// attempt to register
if err := s.opts.Registry().Register(service, rOpts...); err != nil {
// set the error
regErr = err
// backoff then retry
time.Sleep(backoff.Do(i + 1))
continue
}
// success so nil error
regErr = nil
break
}
return regErr
}
var err error
var advt, host, port string
//// check the advertise address first
//// if it exists then use it, otherwise
//// use the address
//if len(config.Advertise) > 0 {
// advt = config.Advertise
//} else {
advt = s.opts.address
//}
if cnt := strings.Count(advt, ":"); cnt >= 1 {
// ipv6 address in format [host]:port or ipv4 host:port
host, port, err = net.SplitHostPort(advt)
if err != nil {
return err
}
} else {
host = s.opts.address
}
addr, err := addr.Extract(host)
if err != nil {
return err
}
// register service
node := &registry.Node{
Id: s.opts.name + "-" + s.id,
Address: net2.HostPort(addr, port),
}
s.regSvc = &registry.Service{
Name: s.opts.name,
Version: s.opts.version,
Nodes: []*registry.Node{node},
}
// register the service
if err := regFunc(s.regSvc); err != nil {
return err
}
return nil
}
func (s *service) run() error {
s.mu.Lock()
s.closed = make(chan struct{})
for i := range s.opts.beforeStart {
if err := s.opts.beforeStart[i](); err != nil {
s.mu.Unlock()
return err
}
}
s.running = true
s.opts.address = s.list.Addr().String()
if err := s.register(); err != nil {
return err
}
s.running = true
errs := make(chan error)
go func() {
errs <- s.server.Serve(s.list)
@ -130,7 +227,11 @@ func (s *service) run() error {
}
}
s.mu.Unlock()
return <-errs
if err := <-errs; err != nil{
logrus.Error(err)
return err
}
return nil
}
func (s *service) Start() error {
@ -138,11 +239,15 @@ func (s *service) Start() error {
}
func (s *service) Stop() error {
defer close(s.closed)
s.mu.Lock()
defer s.mu.Unlock()
if !s.running {
return nil
}
if err := s.opts.registry.Deregister(s.regSvc); err != nil {
logrus.Errorf("failed to deregister service: %v", err)
}
for i := range s.opts.beforeStop {
if err := s.opts.beforeStop[i](); err != nil {
return err
@ -153,9 +258,10 @@ func (s *service) Stop() error {
s.running = false
for i := range s.opts.afterStop {
if err := s.opts.afterStop[i](); err != nil {
return err
}
}
logrus.Info("server stopped")
return nil
}
@ -164,5 +270,6 @@ func (s *service) Close() error {
if s.opts.db != nil {
err = multierr.Append(s.opts.db.Close(), err)
}
<-s.closed
return err
}