go.etcd.io/etcd@v3.3.27+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go (about)

     1  // Copyright 2018 The etcd Authors
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package endpoint resolves etcd entpoints using grpc targets of the form 'endpoint://<id>/<endpoint>'.
    16  package endpoint
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"net"
    22  	"net/url"
    23  	"strings"
    24  	"sync"
    25  
    26  	"google.golang.org/grpc/resolver"
    27  )
    28  
    29  const scheme = "endpoint"
    30  
    31  var (
    32  	targetPrefix = fmt.Sprintf("%s://", scheme)
    33  
    34  	bldr *builder
    35  )
    36  
    37  func init() {
    38  	bldr = &builder{
    39  		resolverGroups: make(map[string]*ResolverGroup),
    40  	}
    41  	resolver.Register(bldr)
    42  }
    43  
    44  type builder struct {
    45  	mu             sync.RWMutex
    46  	resolverGroups map[string]*ResolverGroup
    47  }
    48  
    49  // NewResolverGroup creates a new ResolverGroup with the given id.
    50  func NewResolverGroup(id string) (*ResolverGroup, error) {
    51  	return bldr.newResolverGroup(id)
    52  }
    53  
    54  // ResolverGroup keeps all endpoints of resolvers using a common endpoint://<id>/ target
    55  // up-to-date.
    56  type ResolverGroup struct {
    57  	mu        sync.RWMutex
    58  	id        string
    59  	endpoints []string
    60  	resolvers []*Resolver
    61  }
    62  
    63  func (e *ResolverGroup) addResolver(r *Resolver) {
    64  	e.mu.Lock()
    65  	addrs := epsToAddrs(e.endpoints...)
    66  	e.resolvers = append(e.resolvers, r)
    67  	e.mu.Unlock()
    68  	r.cc.NewAddress(addrs)
    69  }
    70  
    71  func (e *ResolverGroup) removeResolver(r *Resolver) {
    72  	e.mu.Lock()
    73  	for i, er := range e.resolvers {
    74  		if er == r {
    75  			e.resolvers = append(e.resolvers[:i], e.resolvers[i+1:]...)
    76  			break
    77  		}
    78  	}
    79  	e.mu.Unlock()
    80  }
    81  
    82  // SetEndpoints updates the endpoints for ResolverGroup. All registered resolver are updated
    83  // immediately with the new endpoints.
    84  func (e *ResolverGroup) SetEndpoints(endpoints []string) {
    85  	addrs := epsToAddrs(endpoints...)
    86  	e.mu.Lock()
    87  	e.endpoints = endpoints
    88  	for _, r := range e.resolvers {
    89  		r.cc.NewAddress(addrs)
    90  	}
    91  	e.mu.Unlock()
    92  }
    93  
    94  // Target constructs a endpoint target using the endpoint id of the ResolverGroup.
    95  func (e *ResolverGroup) Target(endpoint string) string {
    96  	return Target(e.id, endpoint)
    97  }
    98  
    99  // Target constructs a endpoint resolver target.
   100  func Target(id, endpoint string) string {
   101  	return fmt.Sprintf("%s://%s/%s", scheme, id, endpoint)
   102  }
   103  
   104  // IsTarget checks if a given target string in an endpoint resolver target.
   105  func IsTarget(target string) bool {
   106  	return strings.HasPrefix(target, "endpoint://")
   107  }
   108  
   109  func (e *ResolverGroup) Close() {
   110  	bldr.close(e.id)
   111  }
   112  
   113  // Build creates or reuses an etcd resolver for the etcd cluster name identified by the authority part of the target.
   114  func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) {
   115  	if len(target.Authority) < 1 {
   116  		return nil, fmt.Errorf("'etcd' target scheme requires non-empty authority identifying etcd cluster being routed to")
   117  	}
   118  	id := target.Authority
   119  	es, err := b.getResolverGroup(id)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("failed to build resolver: %v", err)
   122  	}
   123  	r := &Resolver{
   124  		endpointID: id,
   125  		cc:         cc,
   126  	}
   127  	es.addResolver(r)
   128  	return r, nil
   129  }
   130  
   131  func (b *builder) newResolverGroup(id string) (*ResolverGroup, error) {
   132  	b.mu.RLock()
   133  	_, ok := b.resolverGroups[id]
   134  	b.mu.RUnlock()
   135  	if ok {
   136  		return nil, fmt.Errorf("Endpoint already exists for id: %s", id)
   137  	}
   138  
   139  	es := &ResolverGroup{id: id}
   140  	b.mu.Lock()
   141  	b.resolverGroups[id] = es
   142  	b.mu.Unlock()
   143  	return es, nil
   144  }
   145  
   146  func (b *builder) getResolverGroup(id string) (*ResolverGroup, error) {
   147  	b.mu.RLock()
   148  	es, ok := b.resolverGroups[id]
   149  	b.mu.RUnlock()
   150  	if !ok {
   151  		return nil, fmt.Errorf("ResolverGroup not found for id: %s", id)
   152  	}
   153  	return es, nil
   154  }
   155  
   156  func (b *builder) close(id string) {
   157  	b.mu.Lock()
   158  	delete(b.resolverGroups, id)
   159  	b.mu.Unlock()
   160  }
   161  
   162  func (b *builder) Scheme() string {
   163  	return scheme
   164  }
   165  
   166  // Resolver provides a resolver for a single etcd cluster, identified by name.
   167  type Resolver struct {
   168  	endpointID string
   169  	cc         resolver.ClientConn
   170  	sync.RWMutex
   171  }
   172  
   173  // TODO: use balancer.epsToAddrs
   174  func epsToAddrs(eps ...string) (addrs []resolver.Address) {
   175  	addrs = make([]resolver.Address, 0, len(eps))
   176  	for _, ep := range eps {
   177  		addrs = append(addrs, resolver.Address{Addr: ep})
   178  	}
   179  	return addrs
   180  }
   181  
   182  func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {}
   183  
   184  func (r *Resolver) Close() {
   185  	es, err := bldr.getResolverGroup(r.endpointID)
   186  	if err != nil {
   187  		return
   188  	}
   189  	es.removeResolver(r)
   190  }
   191  
   192  // ParseEndpoint endpoint parses an endpoint of the form
   193  // (http|https)://<host>*|(unix|unixs)://<path>)
   194  // and returns a protocol ('tcp' or 'unix'),
   195  // host (or filepath if a unix socket),
   196  // scheme (http, https, unix, unixs).
   197  func ParseEndpoint(endpoint string) (proto string, host string, scheme string) {
   198  	proto = "tcp"
   199  	host = endpoint
   200  	url, uerr := url.Parse(endpoint)
   201  	if uerr != nil || !strings.Contains(endpoint, "://") {
   202  		return proto, host, scheme
   203  	}
   204  	scheme = url.Scheme
   205  
   206  	// strip scheme:// prefix since grpc dials by host
   207  	host = url.Host
   208  	switch url.Scheme {
   209  	case "http", "https":
   210  	case "unix", "unixs":
   211  		proto = "unix"
   212  		host = url.Host + url.Path
   213  	default:
   214  		proto, host = "", ""
   215  	}
   216  	return proto, host, scheme
   217  }
   218  
   219  // ParseTarget parses a endpoint://<id>/<endpoint> string and returns the parsed id and endpoint.
   220  // If the target is malformed, an error is returned.
   221  func ParseTarget(target string) (string, string, error) {
   222  	noPrefix := strings.TrimPrefix(target, targetPrefix)
   223  	if noPrefix == target {
   224  		return "", "", fmt.Errorf("malformed target, %s prefix is required: %s", targetPrefix, target)
   225  	}
   226  	parts := strings.SplitN(noPrefix, "/", 2)
   227  	if len(parts) != 2 {
   228  		return "", "", fmt.Errorf("malformed target, expected %s://<id>/<endpoint>, but got %s", scheme, target)
   229  	}
   230  	return parts[0], parts[1], nil
   231  }
   232  
   233  // Dialer dials a endpoint using net.Dialer.
   234  // Context cancelation and timeout are supported.
   235  func Dialer(ctx context.Context, dialEp string) (net.Conn, error) {
   236  	proto, host, _ := ParseEndpoint(dialEp)
   237  	select {
   238  	case <-ctx.Done():
   239  		return nil, ctx.Err()
   240  	default:
   241  	}
   242  	dialer := &net.Dialer{}
   243  	if deadline, ok := ctx.Deadline(); ok {
   244  		dialer.Deadline = deadline
   245  	}
   246  	return dialer.DialContext(ctx, proto, host)
   247  }