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 }