snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package dns implements a dns resolver to be installed as the default resolver
// in grpc.
package dns
import (
"time"
"google.golang.org/grpc/internal/resolver/dns"
"google.golang.org/grpc/resolver"
)
// SetResolvingTimeout sets the maximum duration for DNS resolution requests.
//
// This function affects the global timeout used by all channels using the DNS
// name resolver scheme.
//
// It must be called only at application startup, before any gRPC calls are
// made. Modifying this value after initialization is not thread-safe.
//
// The default value is 30 seconds. Setting the timeout too low may result in
// premature timeouts during resolution, while setting it too high may lead to
// unnecessary delays in service discovery. Choose a value appropriate for your
// specific needs and network environment.
func SetResolvingTimeout(timeout time.Duration) {
dns.ResolvingTimeout = timeout
}
// NewBuilder creates a dnsBuilder which is used to factory DNS resolvers.
//
// Deprecated: import grpc and use resolver.Get("dns") instead.
func NewBuilder() resolver.Builder {
return dns.NewBuilder()
}
// SetMinResolutionInterval sets the default minimum interval at which DNS
// re-resolutions are allowed. This helps to prevent excessive re-resolution.
//
// It must be called only at application startup, before any gRPC calls are
// made. Modifying this value after initialization is not thread-safe.
func SetMinResolutionInterval(d time.Duration) {
dns.MinResolutionInterval = d
}

130
resolver/manual/manual.go Normal file
View File

@@ -0,0 +1,130 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package manual defines a resolver that can be used to manually send resolved
// addresses to ClientConn.
package manual
import (
"sync"
"google.golang.org/grpc/resolver"
)
// NewBuilderWithScheme creates a new manual resolver builder with the given
// scheme. Every instance of the manual resolver may only ever be used with a
// single grpc.ClientConn. Otherwise, bad things will happen.
func NewBuilderWithScheme(scheme string) *Resolver {
return &Resolver{
BuildCallback: func(resolver.Target, resolver.ClientConn, resolver.BuildOptions) {},
UpdateStateCallback: func(error) {},
ResolveNowCallback: func(resolver.ResolveNowOptions) {},
CloseCallback: func() {},
scheme: scheme,
}
}
// Resolver is also a resolver builder.
// It's build() function always returns itself.
type Resolver struct {
// BuildCallback is called when the Build method is called. Must not be
// nil. Must not be changed after the resolver may be built.
BuildCallback func(resolver.Target, resolver.ClientConn, resolver.BuildOptions)
// UpdateStateCallback is called when the UpdateState method is called on
// the resolver. The value passed as argument to this callback is the value
// returned by the resolver.ClientConn. Must not be nil. Must not be
// changed after the resolver may be built.
UpdateStateCallback func(err error)
// ResolveNowCallback is called when the ResolveNow method is called on the
// resolver. Must not be nil. Must not be changed after the resolver may
// be built.
ResolveNowCallback func(resolver.ResolveNowOptions)
// CloseCallback is called when the Close method is called. Must not be
// nil. Must not be changed after the resolver may be built.
CloseCallback func()
scheme string
// Fields actually belong to the resolver.
// Guards access to below fields.
mu sync.Mutex
cc resolver.ClientConn
// Storing the most recent state update makes this resolver resilient to
// restarts, which is possible with channel idleness.
lastSeenState *resolver.State
}
// InitialState adds initial state to the resolver so that UpdateState doesn't
// need to be explicitly called after Dial.
func (r *Resolver) InitialState(s resolver.State) {
r.lastSeenState = &s
}
// Build returns itself for Resolver, because it's both a builder and a resolver.
func (r *Resolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
r.mu.Lock()
defer r.mu.Unlock()
// Call BuildCallback after locking to avoid a race when UpdateState or CC
// is called before Build returns.
r.BuildCallback(target, cc, opts)
r.cc = cc
if r.lastSeenState != nil {
err := r.cc.UpdateState(*r.lastSeenState)
go r.UpdateStateCallback(err)
}
return r, nil
}
// Scheme returns the manual resolver's scheme.
func (r *Resolver) Scheme() string {
return r.scheme
}
// ResolveNow is a noop for Resolver.
func (r *Resolver) ResolveNow(o resolver.ResolveNowOptions) {
r.ResolveNowCallback(o)
}
// Close is a noop for Resolver.
func (r *Resolver) Close() {
r.CloseCallback()
}
// UpdateState calls UpdateState(s) on the channel. If the resolver has not
// been Built before, this instead sets the initial state of the resolver, like
// InitialState.
func (r *Resolver) UpdateState(s resolver.State) {
r.mu.Lock()
defer r.mu.Unlock()
r.lastSeenState = &s
if r.cc == nil {
return
}
err := r.cc.UpdateState(s)
r.UpdateStateCallback(err)
}
// CC returns r's ClientConn when r was last Built. Panics if the resolver has
// not been Built before.
func (r *Resolver) CC() resolver.ClientConn {
r.mu.Lock()
defer r.mu.Unlock()
if r.cc == nil {
panic("Manual resolver instance has not yet been built.")
}
return r.cc
}

View File

@@ -0,0 +1,63 @@
/*
*
* Copyright 2023 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package manual_test
import (
"errors"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
)
func TestResolver(t *testing.T) {
r := manual.NewBuilderWithScheme("whatever")
r.InitialState(resolver.State{
Addresses: []resolver.Address{
{Addr: "address"},
},
})
t.Run("cc_panics", func(t *testing.T) {
defer func() {
want := "Manual resolver instance has not yet been built."
if r := recover(); r != want {
t.Errorf("expected panic %q, got %q", want, r)
}
}()
r.CC()
})
t.Run("happy_path", func(t *testing.T) {
cc, err := grpc.NewClient("whatever://localhost",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithResolvers(r))
if err != nil {
t.Errorf("grpc.NewClient() failed: %v", err)
}
defer cc.Close()
cc.Connect()
r.UpdateState(resolver.State{Addresses: []resolver.Address{
{Addr: "ok"},
}})
r.CC().ReportError(errors.New("example"))
})
}

281
resolver/map.go Normal file
View File

@@ -0,0 +1,281 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package resolver
import (
"encoding/base64"
"iter"
"sort"
"strings"
)
type addressMapEntry[T any] struct {
addr Address
value T
}
// AddressMap is an AddressMapV2[any]. It will be deleted in an upcoming
// release of grpc-go.
//
// Deprecated: use the generic AddressMapV2 type instead.
type AddressMap = AddressMapV2[any]
// AddressMapV2 is a map of addresses to arbitrary values taking into account
// Attributes. BalancerAttributes are ignored, as are Metadata and Type.
// Multiple accesses may not be performed concurrently. Must be created via
// NewAddressMap; do not construct directly.
type AddressMapV2[T any] struct {
// The underlying map is keyed by an Address with fields that we don't care
// about being set to their zero values. The only fields that we care about
// are `Addr`, `ServerName` and `Attributes`. Since we need to be able to
// distinguish between addresses with same `Addr` and `ServerName`, but
// different `Attributes`, we cannot store the `Attributes` in the map key.
//
// The comparison operation for structs work as follows:
// Struct values are comparable if all their fields are comparable. Two
// struct values are equal if their corresponding non-blank fields are equal.
//
// The value type of the map contains a slice of addresses which match the key
// in their `Addr` and `ServerName` fields and contain the corresponding value
// associated with them.
m map[Address]addressMapEntryList[T]
}
func toMapKey(addr *Address) Address {
return Address{Addr: addr.Addr, ServerName: addr.ServerName}
}
type addressMapEntryList[T any] []*addressMapEntry[T]
// NewAddressMap creates a new AddressMapV2[any].
//
// Deprecated: use the generic NewAddressMapV2 constructor instead.
func NewAddressMap() *AddressMap {
return NewAddressMapV2[any]()
}
// NewAddressMapV2 creates a new AddressMapV2.
func NewAddressMapV2[T any]() *AddressMapV2[T] {
return &AddressMapV2[T]{m: make(map[Address]addressMapEntryList[T])}
}
// find returns the index of addr in the addressMapEntry slice, or -1 if not
// present.
func (l addressMapEntryList[T]) find(addr Address) int {
for i, entry := range l {
// Attributes are the only thing to match on here, since `Addr` and
// `ServerName` are already equal.
if entry.addr.Attributes.Equal(addr.Attributes) {
return i
}
}
return -1
}
// Get returns the value for the address in the map, if present.
func (a *AddressMapV2[T]) Get(addr Address) (value T, ok bool) {
addrKey := toMapKey(&addr)
entryList := a.m[addrKey]
if entry := entryList.find(addr); entry != -1 {
return entryList[entry].value, true
}
return value, false
}
// Set updates or adds the value to the address in the map.
func (a *AddressMapV2[T]) Set(addr Address, value T) {
addrKey := toMapKey(&addr)
entryList := a.m[addrKey]
if entry := entryList.find(addr); entry != -1 {
entryList[entry].value = value
return
}
a.m[addrKey] = append(entryList, &addressMapEntry[T]{addr: addr, value: value})
}
// Delete removes addr from the map.
func (a *AddressMapV2[T]) Delete(addr Address) {
addrKey := toMapKey(&addr)
entryList := a.m[addrKey]
entry := entryList.find(addr)
if entry == -1 {
return
}
if len(entryList) == 1 {
entryList = nil
} else {
copy(entryList[entry:], entryList[entry+1:])
entryList = entryList[:len(entryList)-1]
}
a.m[addrKey] = entryList
}
// Len returns the number of entries in the map.
func (a *AddressMapV2[T]) Len() int {
ret := 0
for _, entryList := range a.m {
ret += len(entryList)
}
return ret
}
// Keys returns a slice of all current map keys.
// Deprecated: Use AddressMapV2.All() instead.
func (a *AddressMapV2[T]) Keys() []Address {
ret := make([]Address, 0, a.Len())
for _, entryList := range a.m {
for _, entry := range entryList {
ret = append(ret, entry.addr)
}
}
return ret
}
// Values returns a slice of all current map values.
// Deprecated: Use AddressMapV2.All() instead.
func (a *AddressMapV2[T]) Values() []T {
ret := make([]T, 0, a.Len())
for _, entryList := range a.m {
for _, entry := range entryList {
ret = append(ret, entry.value)
}
}
return ret
}
// All returns an iterator over all elements.
func (a *AddressMapV2[T]) All() iter.Seq2[Address, T] {
return func(yield func(Address, T) bool) {
for _, entryList := range a.m {
for _, entry := range entryList {
if !yield(entry.addr, entry.value) {
return
}
}
}
}
}
type endpointMapKey string
// EndpointMap is a map of endpoints to arbitrary values keyed on only the
// unordered set of address strings within an endpoint. This map is not thread
// safe, thus it is unsafe to access concurrently. Must be created via
// NewEndpointMap; do not construct directly.
type EndpointMap[T any] struct {
endpoints map[endpointMapKey]endpointData[T]
}
type endpointData[T any] struct {
// decodedKey stores the original key to avoid decoding when iterating on
// EndpointMap keys.
decodedKey Endpoint
value T
}
// NewEndpointMap creates a new EndpointMap.
func NewEndpointMap[T any]() *EndpointMap[T] {
return &EndpointMap[T]{
endpoints: make(map[endpointMapKey]endpointData[T]),
}
}
// encodeEndpoint returns a string that uniquely identifies the unordered set of
// addresses within an endpoint.
func encodeEndpoint(e Endpoint) endpointMapKey {
addrs := make([]string, 0, len(e.Addresses))
// base64 encoding the address strings restricts the characters present
// within the strings. This allows us to use a delimiter without the need of
// escape characters.
for _, addr := range e.Addresses {
addrs = append(addrs, base64.StdEncoding.EncodeToString([]byte(addr.Addr)))
}
sort.Strings(addrs)
// " " should not appear in base64 encoded strings.
return endpointMapKey(strings.Join(addrs, " "))
}
// Get returns the value for the address in the map, if present.
func (em *EndpointMap[T]) Get(e Endpoint) (value T, ok bool) {
val, found := em.endpoints[encodeEndpoint(e)]
if found {
return val.value, true
}
return value, false
}
// Set updates or adds the value to the address in the map.
func (em *EndpointMap[T]) Set(e Endpoint, value T) {
en := encodeEndpoint(e)
em.endpoints[en] = endpointData[T]{
decodedKey: Endpoint{Addresses: e.Addresses},
value: value,
}
}
// Len returns the number of entries in the map.
func (em *EndpointMap[T]) Len() int {
return len(em.endpoints)
}
// Keys returns a slice of all current map keys, as endpoints specifying the
// addresses present in the endpoint keys, in which uniqueness is determined by
// the unordered set of addresses. Thus, endpoint information returned is not
// the full endpoint data (drops duplicated addresses and attributes) but can be
// used for EndpointMap accesses.
// Deprecated: Use EndpointMap.All() instead.
func (em *EndpointMap[T]) Keys() []Endpoint {
ret := make([]Endpoint, 0, len(em.endpoints))
for _, en := range em.endpoints {
ret = append(ret, en.decodedKey)
}
return ret
}
// Values returns a slice of all current map values.
// Deprecated: Use EndpointMap.All() instead.
func (em *EndpointMap[T]) Values() []T {
ret := make([]T, 0, len(em.endpoints))
for _, val := range em.endpoints {
ret = append(ret, val.value)
}
return ret
}
// All returns an iterator over all elements.
// The map keys are endpoints specifying the addresses present in the endpoint
// map, in which uniqueness is determined by the unordered set of addresses.
// Thus, endpoint information returned is not the full endpoint data (drops
// duplicated addresses and attributes) but can be used for EndpointMap
// accesses.
func (em *EndpointMap[T]) All() iter.Seq2[Endpoint, T] {
return func(yield func(Endpoint, T) bool) {
for _, en := range em.endpoints {
if !yield(en.decodedKey, en.value) {
return
}
}
}
}
// Delete removes the specified endpoint from the map.
func (em *EndpointMap[T]) Delete(e Endpoint) {
en := encodeEndpoint(e)
delete(em.endpoints, en)
}

365
resolver/map_test.go Normal file
View File

@@ -0,0 +1,365 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package resolver
import (
"fmt"
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/attributes"
)
// Note: each address is different from addr1 by one value. addr7 matches
// addr1, since the only difference is BalancerAttributes, which are not
// compared.
var (
addr1 = Address{Addr: "a1", Attributes: attributes.New("a1", 3), ServerName: "s1"}
addr2 = Address{Addr: "a2", Attributes: attributes.New("a1", 3), ServerName: "s1"}
addr3 = Address{Addr: "a1", Attributes: attributes.New("a2", 3), ServerName: "s1"}
addr4 = Address{Addr: "a1", Attributes: attributes.New("a1", 2), ServerName: "s1"}
addr5 = Address{Addr: "a1", Attributes: attributes.New("a1", "3"), ServerName: "s1"}
addr6 = Address{Addr: "a1", Attributes: attributes.New("a1", 3), ServerName: "s2"}
addr7 = Address{Addr: "a1", Attributes: attributes.New("a1", 3), ServerName: "s1", BalancerAttributes: attributes.New("xx", 3)}
endpoint1 = Endpoint{Addresses: []Address{{Addr: "addr1"}}}
endpoint2 = Endpoint{Addresses: []Address{{Addr: "addr2"}}}
endpoint3 = Endpoint{Addresses: []Address{{Addr: "addr3"}}}
endpoint4 = Endpoint{Addresses: []Address{{Addr: "addr4"}}}
endpoint5 = Endpoint{Addresses: []Address{{Addr: "addr5"}}}
endpoint6 = Endpoint{Addresses: []Address{{Addr: "addr6"}}}
endpoint7 = Endpoint{Addresses: []Address{{Addr: "addr7"}}}
endpoint12 = Endpoint{Addresses: []Address{{Addr: "addr1"}, {Addr: "addr2"}}}
endpoint21 = Endpoint{Addresses: []Address{{Addr: "addr2"}, {Addr: "addr1"}}}
endpoint123 = Endpoint{Addresses: []Address{{Addr: "addr1"}, {Addr: "addr2"}, {Addr: "addr3"}}}
)
func (s) TestAddressMap_Length(t *testing.T) {
addrMap := NewAddressMapV2[any]()
if got := addrMap.Len(); got != 0 {
t.Fatalf("addrMap.Len() = %v; want 0", got)
}
for i := 0; i < 10; i++ {
addrMap.Set(addr1, nil)
if got, want := addrMap.Len(), 1; got != want {
t.Fatalf("addrMap.Len() = %v; want %v", got, want)
}
addrMap.Set(addr7, nil) // aliases addr1
}
for i := 0; i < 10; i++ {
addrMap.Set(addr2, nil)
if got, want := addrMap.Len(), 2; got != want {
t.Fatalf("addrMap.Len() = %v; want %v", got, want)
}
}
}
func (s) TestAddressMap_Get(t *testing.T) {
addrMap := NewAddressMapV2[int]()
addrMap.Set(addr1, 1)
if got, ok := addrMap.Get(addr2); ok || got != 0 {
t.Fatalf("addrMap.Get(addr1) = %v, %v; want 0, false", got, ok)
}
addrMap.Set(addr2, 2)
addrMap.Set(addr3, 3)
addrMap.Set(addr4, 4)
addrMap.Set(addr5, 5)
addrMap.Set(addr6, 6)
addrMap.Set(addr7, 7) // aliases addr1
if got, ok := addrMap.Get(addr1); !ok || got != 7 {
t.Fatalf("addrMap.Get(addr1) = %v, %v; want %v, true", got, ok, 7)
}
if got, ok := addrMap.Get(addr2); !ok || got != 2 {
t.Fatalf("addrMap.Get(addr2) = %v, %v; want %v, true", got, ok, 2)
}
if got, ok := addrMap.Get(addr3); !ok || got != 3 {
t.Fatalf("addrMap.Get(addr3) = %v, %v; want %v, true", got, ok, 3)
}
if got, ok := addrMap.Get(addr4); !ok || got != 4 {
t.Fatalf("addrMap.Get(addr4) = %v, %v; want %v, true", got, ok, 4)
}
if got, ok := addrMap.Get(addr5); !ok || got != 5 {
t.Fatalf("addrMap.Get(addr5) = %v, %v; want %v, true", got, ok, 5)
}
if got, ok := addrMap.Get(addr6); !ok || got != 6 {
t.Fatalf("addrMap.Get(addr6) = %v, %v; want %v, true", got, ok, 6)
}
if got, ok := addrMap.Get(addr7); !ok || got != 7 {
t.Fatalf("addrMap.Get(addr7) = %v, %v; want %v, true", got, ok, 7)
}
}
func (s) TestAddressMap_Delete(t *testing.T) {
addrMap := NewAddressMapV2[any]()
addrMap.Set(addr1, 1)
addrMap.Set(addr2, 2)
if got, want := addrMap.Len(), 2; got != want {
t.Fatalf("addrMap.Len() = %v; want %v", got, want)
}
addrMap.Delete(addr3)
addrMap.Delete(addr4)
addrMap.Delete(addr5)
addrMap.Delete(addr6)
addrMap.Delete(addr7) // aliases addr1
if got, ok := addrMap.Get(addr1); ok || got != nil {
t.Fatalf("addrMap.Get(addr1) = %v, %v; want nil, false", got, ok)
}
if got, ok := addrMap.Get(addr7); ok || got != nil {
t.Fatalf("addrMap.Get(addr7) = %v, %v; want nil, false", got, ok)
}
if got, ok := addrMap.Get(addr2); !ok || got.(int) != 2 {
t.Fatalf("addrMap.Get(addr2) = %v, %v; want %v, true", got, ok, 2)
}
}
func (s) TestAddressMap_Keys(t *testing.T) {
addrMap := NewAddressMapV2[int]()
addrMap.Set(addr1, 1)
addrMap.Set(addr2, 2)
addrMap.Set(addr3, 3)
addrMap.Set(addr4, 4)
addrMap.Set(addr5, 5)
addrMap.Set(addr6, 6)
addrMap.Set(addr7, 7) // aliases addr1
want := []Address{addr1, addr2, addr3, addr4, addr5, addr6}
got := addrMap.Keys()
if d := cmp.Diff(want, got, cmp.Transformer("sort", func(in []Address) []Address {
out := append([]Address(nil), in...)
sort.Slice(out, func(i, j int) bool { return fmt.Sprint(out[i]) < fmt.Sprint(out[j]) })
return out
})); d != "" {
t.Fatalf("addrMap.Keys returned unexpected elements (-want, +got):\n%v", d)
}
}
func (s) TestAddressMap_Values(t *testing.T) {
addrMap := NewAddressMapV2[int]()
addrMap.Set(addr1, 1)
addrMap.Set(addr2, 2)
addrMap.Set(addr3, 3)
addrMap.Set(addr4, 4)
addrMap.Set(addr5, 5)
addrMap.Set(addr6, 6)
addrMap.Set(addr7, 7) // aliases addr1
want := []int{2, 3, 4, 5, 6, 7}
got := addrMap.Values()
sort.Ints(got)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("addrMap.Values returned unexpected elements (-want, +got):\n%v", diff)
}
}
func (s) TestAddressMap_All(t *testing.T) {
addrMap := NewAddressMapV2[int]()
addrMap.Set(addr1, 1)
addrMap.Set(addr2, 2)
addrMap.Set(addr3, 3)
addrMap.Set(addr4, 4)
addrMap.Set(addr5, 5)
addrMap.Set(addr6, 6)
addrMap.Set(addr7, 7) // aliases addr1
type pair struct {
K Address
V int
}
want := []pair{{addr1, 7}, {addr2, 2}, {addr3, 3}, {addr4, 4}, {addr5, 5}, {addr6, 6}}
var got []pair
for k, v := range addrMap.All() {
got = append(got, pair{k, v})
}
if d := cmp.Diff(want, got, cmp.Transformer("sort", func(in []pair) []pair {
out := append([]pair(nil), in...)
sort.Slice(out, func(i, j int) bool { return out[i].V < out[j].V })
return out
})); d != "" {
t.Fatalf("addrMap.All returned unexpected elements (-want, +got):\n%v", d)
}
}
func (s) TestEndpointMap_Length(t *testing.T) {
em := NewEndpointMap[struct{}]()
// Should be empty at creation time.
if got := em.Len(); got != 0 {
t.Fatalf("em.Len() = %v; want 0", got)
}
// Add two endpoints with the same unordered set of addresses. This should
// amount to one endpoint. It should also not take into account attributes.
em.Set(endpoint12, struct{}{})
em.Set(endpoint21, struct{}{})
if got := em.Len(); got != 1 {
t.Fatalf("em.Len() = %v; want 1", got)
}
// Add another unique endpoint. This should cause the length to be 2.
em.Set(endpoint123, struct{}{})
if got := em.Len(); got != 2 {
t.Fatalf("em.Len() = %v; want 2", got)
}
}
func (s) TestEndpointMap_Get(t *testing.T) {
em := NewEndpointMap[int]()
em.Set(endpoint1, 1)
// The second endpoint endpoint21 should override.
em.Set(endpoint12, 1)
em.Set(endpoint21, 2)
em.Set(endpoint3, 3)
em.Set(endpoint4, 4)
em.Set(endpoint5, 5)
em.Set(endpoint6, 6)
em.Set(endpoint7, 7)
if got, ok := em.Get(endpoint1); !ok || got != 1 {
t.Fatalf("em.Get(endpoint1) = %v, %v; want %v, true", got, ok, 1)
}
if got, ok := em.Get(endpoint12); !ok || got != 2 {
t.Fatalf("em.Get(endpoint12) = %v, %v; want %v, true", got, ok, 2)
}
if got, ok := em.Get(endpoint21); !ok || got != 2 {
t.Fatalf("em.Get(endpoint21) = %v, %v; want %v, true", got, ok, 2)
}
if got, ok := em.Get(endpoint3); !ok || got != 3 {
t.Fatalf("em.Get(endpoint1) = %v, %v; want %v, true", got, ok, 3)
}
if got, ok := em.Get(endpoint4); !ok || got != 4 {
t.Fatalf("em.Get(endpoint1) = %v, %v; want %v, true", got, ok, 4)
}
if got, ok := em.Get(endpoint5); !ok || got != 5 {
t.Fatalf("em.Get(endpoint1) = %v, %v; want %v, true", got, ok, 5)
}
if got, ok := em.Get(endpoint6); !ok || got != 6 {
t.Fatalf("em.Get(endpoint1) = %v, %v; want %v, true", got, ok, 6)
}
if got, ok := em.Get(endpoint7); !ok || got != 7 {
t.Fatalf("em.Get(endpoint1) = %v, %v; want %v, true", got, ok, 7)
}
if _, ok := em.Get(endpoint123); ok {
t.Fatalf("em.Get(endpoint123) = _, %v; want _, false", ok)
}
}
func (s) TestEndpointMap_Delete(t *testing.T) {
em := NewEndpointMap[struct{}]()
// Initial state of system: [1, 2, 3, 12]
em.Set(endpoint1, struct{}{})
em.Set(endpoint2, struct{}{})
em.Set(endpoint3, struct{}{})
em.Set(endpoint12, struct{}{})
// Delete: [2, 21]
em.Delete(endpoint2)
em.Delete(endpoint21)
// [1, 3] should be present:
if _, ok := em.Get(endpoint1); !ok {
t.Fatalf("em.Get(endpoint1) = %v, want true", ok)
}
if _, ok := em.Get(endpoint3); !ok {
t.Fatalf("em.Get(endpoint3) = %v, want true", ok)
}
// [2, 12] should not be present:
if _, ok := em.Get(endpoint2); ok {
t.Fatalf("em.Get(endpoint2) = %v, want false", ok)
}
if _, ok := em.Get(endpoint12); ok {
t.Fatalf("em.Get(endpoint12) = %v, want false", ok)
}
if _, ok := em.Get(endpoint21); ok {
t.Fatalf("em.Get(endpoint21) = %v, want false", ok)
}
}
func (s) TestEndpointMap_Values(t *testing.T) {
em := NewEndpointMap[int]()
em.Set(endpoint1, 1)
// The second endpoint endpoint21 should override.
em.Set(endpoint12, 1)
em.Set(endpoint21, 2)
em.Set(endpoint3, 3)
em.Set(endpoint4, 4)
em.Set(endpoint5, 5)
em.Set(endpoint6, 6)
em.Set(endpoint7, 7)
want := []int{1, 2, 3, 4, 5, 6, 7}
got := em.Values()
sort.Ints(got)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("em.Values() returned unexpected elements (-want, +got):\n%v", diff)
}
}
func (s) TestEndpointMap_All(t *testing.T) {
em := NewEndpointMap[int]()
em.Set(endpoint1, 1)
// The second endpoint endpoint21 should override.
em.Set(endpoint12, 1)
em.Set(endpoint21, 2)
em.Set(endpoint3, 3)
em.Set(endpoint4, 4)
em.Set(endpoint5, 5)
em.Set(endpoint6, 6)
em.Set(endpoint7, 7)
type pair struct {
K Endpoint
V int
}
want := []pair{{endpoint1, 1}, {endpoint21, 2}, {endpoint3, 3}, {endpoint4, 4}, {endpoint5, 5}, {endpoint6, 6}, {endpoint7, 7}}
var got []pair
for k, v := range em.All() {
got = append(got, pair{k, v})
}
if d := cmp.Diff(want, got, cmp.Transformer("sort", func(in []pair) []pair {
out := append([]pair(nil), in...)
sort.Slice(out, func(i, j int) bool { return out[i].V < out[j].V })
return out
})); d != "" {
t.Fatalf("em.All returned unexpected elements (-want, +got):\n%v", d)
}
}
// BenchmarkEndpointMap benchmarks map operations that are expected to run
// faster than O(n). This test doesn't run O(n) operations including listing
// keys and values.
func BenchmarkEndpointMap(b *testing.B) {
em := NewEndpointMap[any]()
for i := range b.N {
em.Set(Endpoint{
Addresses: []Address{{Addr: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)}},
}, i)
}
for i := range b.N {
em.Get(Endpoint{
Addresses: []Address{{Addr: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)}},
})
}
for i := range b.N {
em.Delete(Endpoint{
Addresses: []Address{{Addr: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)}},
})
}
}

View File

@@ -0,0 +1,26 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package passthrough implements a pass-through resolver. It sends the target
// name without scheme back to gRPC as resolved address.
//
// Deprecated: this package is imported by grpc and should not need to be
// imported directly by users.
package passthrough
import _ "google.golang.org/grpc/internal/resolver/passthrough" // import for side effects after package was moved

359
resolver/resolver.go Normal file
View File

@@ -0,0 +1,359 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package resolver defines APIs for name resolution in gRPC.
// All APIs in this package are experimental.
package resolver
import (
"context"
"errors"
"fmt"
"net"
"net/url"
"strings"
"google.golang.org/grpc/attributes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/experimental/stats"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/serviceconfig"
)
var (
// m is a map from scheme to resolver builder.
m = make(map[string]Builder)
// defaultScheme is the default scheme to use.
defaultScheme = "passthrough"
)
// TODO(bar) install dns resolver in init(){}.
// Register registers the resolver builder to the resolver map. b.Scheme will
// be used as the scheme registered with this builder. The registry is case
// sensitive, and schemes should not contain any uppercase characters.
//
// NOTE: this function must only be called during initialization time (i.e. in
// an init() function), and is not thread-safe. If multiple Resolvers are
// registered with the same name, the one registered last will take effect.
func Register(b Builder) {
m[b.Scheme()] = b
}
// Get returns the resolver builder registered with the given scheme.
//
// If no builder is register with the scheme, nil will be returned.
func Get(scheme string) Builder {
if b, ok := m[scheme]; ok {
return b
}
return nil
}
// SetDefaultScheme sets the default scheme that will be used. The default
// scheme is initially set to "passthrough".
//
// NOTE: this function must only be called during initialization time (i.e. in
// an init() function), and is not thread-safe. The scheme set last overrides
// previously set values.
func SetDefaultScheme(scheme string) {
defaultScheme = scheme
internal.UserSetDefaultScheme = true
}
// GetDefaultScheme gets the default scheme that will be used by grpc.Dial. If
// SetDefaultScheme is never called, the default scheme used by grpc.NewClient is "dns" instead.
func GetDefaultScheme() string {
return defaultScheme
}
// Address represents a server the client connects to.
//
// # Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type Address struct {
// Addr is the server address on which a connection will be established.
Addr string
// ServerName is the name of this address.
// If non-empty, the ServerName is used as the transport certification authority for
// the address, instead of the hostname from the Dial target string. In most cases,
// this should not be set.
//
// WARNING: ServerName must only be populated with trusted values. It
// is insecure to populate it with data from untrusted inputs since untrusted
// values could be used to bypass the authority checks performed by TLS.
ServerName string
// Attributes contains arbitrary data about this address intended for
// consumption by the SubConn.
Attributes *attributes.Attributes
// BalancerAttributes contains arbitrary data about this address intended
// for consumption by the LB policy. These attributes do not affect SubConn
// creation, connection establishment, handshaking, etc.
//
// Deprecated: when an Address is inside an Endpoint, this field should not
// be used, and it will eventually be removed entirely.
BalancerAttributes *attributes.Attributes
// Metadata is the information associated with Addr, which may be used
// to make load balancing decision.
//
// Deprecated: use Attributes instead.
Metadata any
}
// Equal returns whether a and o are identical. Metadata is compared directly,
// not with any recursive introspection.
//
// This method compares all fields of the address. When used to tell apart
// addresses during subchannel creation or connection establishment, it might be
// more appropriate for the caller to implement custom equality logic.
func (a Address) Equal(o Address) bool {
return a.Addr == o.Addr && a.ServerName == o.ServerName &&
a.Attributes.Equal(o.Attributes) &&
a.BalancerAttributes.Equal(o.BalancerAttributes) &&
a.Metadata == o.Metadata
}
// String returns JSON formatted string representation of the address.
func (a Address) String() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("{Addr: %q, ", a.Addr))
sb.WriteString(fmt.Sprintf("ServerName: %q, ", a.ServerName))
if a.Attributes != nil {
sb.WriteString(fmt.Sprintf("Attributes: %v, ", a.Attributes.String()))
}
if a.BalancerAttributes != nil {
sb.WriteString(fmt.Sprintf("BalancerAttributes: %v", a.BalancerAttributes.String()))
}
sb.WriteString("}")
return sb.String()
}
// BuildOptions includes additional information for the builder to create
// the resolver.
type BuildOptions struct {
// DisableServiceConfig indicates whether a resolver implementation should
// fetch service config data.
DisableServiceConfig bool
// DialCreds is the transport credentials used by the ClientConn for
// communicating with the target gRPC service (set via
// WithTransportCredentials). In cases where a name resolution service
// requires the same credentials, the resolver may use this field. In most
// cases though, it is not appropriate, and this field may be ignored.
DialCreds credentials.TransportCredentials
// CredsBundle is the credentials bundle used by the ClientConn for
// communicating with the target gRPC service (set via
// WithCredentialsBundle). In cases where a name resolution service
// requires the same credentials, the resolver may use this field. In most
// cases though, it is not appropriate, and this field may be ignored.
CredsBundle credentials.Bundle
// Dialer is the custom dialer used by the ClientConn for dialling the
// target gRPC service (set via WithDialer). In cases where a name
// resolution service requires the same dialer, the resolver may use this
// field. In most cases though, it is not appropriate, and this field may
// be ignored.
Dialer func(context.Context, string) (net.Conn, error)
// Authority is the effective authority of the clientconn for which the
// resolver is built.
Authority string
// MetricsRecorder is the metrics recorder to do recording.
MetricsRecorder stats.MetricsRecorder
}
// An Endpoint is one network endpoint, or server, which may have multiple
// addresses with which it can be accessed.
// TODO(i/8773) : make resolver.Endpoint and resolver.Address immutable
type Endpoint struct {
// Addresses contains a list of addresses used to access this endpoint.
Addresses []Address
// Attributes contains arbitrary data about this endpoint intended for
// consumption by the LB policy.
Attributes *attributes.Attributes
}
// State contains the current Resolver state relevant to the ClientConn.
type State struct {
// Addresses is the latest set of resolved addresses for the target.
//
// If a resolver sets Addresses but does not set Endpoints, one Endpoint
// will be created for each Address before the State is passed to the LB
// policy. The BalancerAttributes of each entry in Addresses will be set
// in Endpoints.Attributes, and be cleared in the Endpoint's Address's
// BalancerAttributes.
//
// Soon, Addresses will be deprecated and replaced fully by Endpoints.
Addresses []Address
// Endpoints is the latest set of resolved endpoints for the target.
//
// If a resolver produces a State containing Endpoints but not Addresses,
// it must take care to ensure the LB policies it selects will support
// Endpoints.
Endpoints []Endpoint
// ServiceConfig contains the result from parsing the latest service
// config. If it is nil, it indicates no service config is present or the
// resolver does not provide service configs.
ServiceConfig *serviceconfig.ParseResult
// Attributes contains arbitrary data about the resolver intended for
// consumption by the load balancing policy.
Attributes *attributes.Attributes
}
// ClientConn contains the callbacks for resolver to notify any updates
// to the gRPC ClientConn.
//
// This interface is to be implemented by gRPC. Users should not need a
// brand new implementation of this interface. For the situations like
// testing, the new implementation should embed this interface. This allows
// gRPC to add new methods to this interface.
type ClientConn interface {
// UpdateState updates the state of the ClientConn appropriately.
//
// If an error is returned, the resolver should try to resolve the
// target again. The resolver should use a backoff timer to prevent
// overloading the server with requests. If a resolver is certain that
// reresolving will not change the result, e.g. because it is
// a watch-based resolver, returned errors can be ignored.
//
// If the resolved State is the same as the last reported one, calling
// UpdateState can be omitted.
UpdateState(State) error
// ReportError notifies the ClientConn that the Resolver encountered an
// error. The ClientConn then forwards this error to the load balancing
// policy.
ReportError(error)
// NewAddress is called by resolver to notify ClientConn a new list
// of resolved addresses.
// The address list should be the complete list of resolved addresses.
//
// Deprecated: Use UpdateState instead.
NewAddress(addresses []Address)
// ParseServiceConfig parses the provided service config and returns an
// object that provides the parsed config.
ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult
}
// Target represents a target for gRPC, as specified in:
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
// It is parsed from the target string that gets passed into Dial or DialContext
// by the user. And gRPC passes it to the resolver and the balancer.
//
// If the target follows the naming spec, and the parsed scheme is registered
// with gRPC, we will parse the target string according to the spec. If the
// target does not contain a scheme or if the parsed scheme is not registered
// (i.e. no corresponding resolver available to resolve the endpoint), we will
// apply the default scheme, and will attempt to reparse it.
type Target struct {
// URL contains the parsed dial target with an optional default scheme added
// to it if the original dial target contained no scheme or contained an
// unregistered scheme. Any query params specified in the original dial
// target can be accessed from here.
URL url.URL
}
// Endpoint retrieves endpoint without leading "/" from either `URL.Path`
// or `URL.Opaque`. The latter is used when the former is empty.
func (t Target) Endpoint() string {
endpoint := t.URL.Path
if endpoint == "" {
endpoint = t.URL.Opaque
}
// For targets of the form "[scheme]://[authority]/endpoint, the endpoint
// value returned from url.Parse() contains a leading "/". Although this is
// in accordance with RFC 3986, we do not want to break existing resolver
// implementations which expect the endpoint without the leading "/". So, we
// end up stripping the leading "/" here. But this will result in an
// incorrect parsing for something like "unix:///path/to/socket". Since we
// own the "unix" resolver, we can workaround in the unix resolver by using
// the `URL` field.
return strings.TrimPrefix(endpoint, "/")
}
// String returns the canonical string representation of Target.
func (t Target) String() string {
return t.URL.Scheme + "://" + t.URL.Host + "/" + t.Endpoint()
}
// Builder creates a resolver that will be used to watch name resolution updates.
type Builder interface {
// Build creates a new resolver for the given target.
//
// gRPC dial calls Build synchronously, and fails if the returned error is
// not nil.
Build(target Target, cc ClientConn, opts BuildOptions) (Resolver, error)
// Scheme returns the scheme supported by this resolver. Scheme is defined
// at https://github.com/grpc/grpc/blob/master/doc/naming.md. The returned
// string should not contain uppercase characters, as they will not match
// the parsed target's scheme as defined in RFC 3986.
Scheme() string
}
// ResolveNowOptions includes additional information for ResolveNow.
type ResolveNowOptions struct{}
// Resolver watches for the updates on the specified target.
// Updates include address updates and service config updates.
type Resolver interface {
// ResolveNow will be called by gRPC to try to resolve the target name
// again. It's just a hint, resolver can ignore this if it's not necessary.
//
// It could be called multiple times concurrently.
ResolveNow(ResolveNowOptions)
// Close closes the resolver.
Close()
}
// AuthorityOverrider is implemented by Builders that wish to override the
// default authority for the ClientConn.
// By default, the authority used is target.Endpoint().
type AuthorityOverrider interface {
// OverrideAuthority returns the authority to use for a ClientConn with the
// given target. The implementation must generate it without blocking,
// typically in line, and must keep it unchanged.
//
// The returned string must be a valid ":authority" header value, i.e. be
// encoded according to
// [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2) as
// necessary.
OverrideAuthority(Target) string
}
// ValidateEndpoints validates endpoints from a petiole policy's perspective.
// Petiole policies should call this before calling into their children. See
// [gRPC A61](https://github.com/grpc/proposal/blob/master/A61-IPv4-IPv6-dualstack-backends.md)
// for details.
func ValidateEndpoints(endpoints []Endpoint) error {
if len(endpoints) == 0 {
return errors.New("endpoints list is empty")
}
for _, endpoint := range endpoints {
for range endpoint.Addresses {
return nil
}
}
return errors.New("endpoints list contains no addresses")
}

105
resolver/resolver_test.go Normal file
View File

@@ -0,0 +1,105 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package resolver
import (
"testing"
"google.golang.org/grpc/internal/grpctest"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
// TestValidateEndpoints tests different scenarios of resolver addresses being
// validated by the ValidateEndpoint helper.
func (s) TestValidateEndpoints(t *testing.T) {
addr1 := Address{Addr: "addr1"}
addr2 := Address{Addr: "addr2"}
addr3 := Address{Addr: "addr3"}
addr4 := Address{Addr: "addr4"}
tests := []struct {
name string
endpoints []Endpoint
wantErr bool
}{
{
name: "duplicate-address-across-endpoints",
endpoints: []Endpoint{
{Addresses: []Address{addr1}},
{Addresses: []Address{addr1}},
},
wantErr: false,
},
{
name: "duplicate-address-same-endpoint",
endpoints: []Endpoint{
{Addresses: []Address{addr1, addr1}},
},
wantErr: false,
},
{
name: "duplicate-address-across-endpoints-plural-addresses",
endpoints: []Endpoint{
{Addresses: []Address{addr1, addr2, addr3}},
{Addresses: []Address{addr3, addr4}},
},
wantErr: false,
},
{
name: "no-shared-addresses",
endpoints: []Endpoint{
{Addresses: []Address{addr1, addr2}},
{Addresses: []Address{addr3, addr4}},
},
wantErr: false,
},
{
name: "endpoint-with-no-addresses",
endpoints: []Endpoint{
{Addresses: []Address{addr1, addr2}},
{Addresses: []Address{}},
},
wantErr: false,
},
{
name: "empty-endpoints-list",
endpoints: []Endpoint{},
wantErr: true,
},
{
name: "endpoint-list-with-no-addresses",
endpoints: []Endpoint{{}, {}},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := ValidateEndpoints(test.endpoints)
if (err != nil) != test.wantErr {
t.Fatalf("ValidateEndpoints() wantErr: %v, got: %v", test.wantErr, err)
}
})
}
}

60
resolver/ringhash/attr.go Normal file
View File

@@ -0,0 +1,60 @@
/*
*
* Copyright 2025 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package ringhash implements resolver related functions for the ring_hash
// load balancing policy.
package ringhash
import (
"google.golang.org/grpc/resolver"
)
type hashKeyType string
// hashKeyKey is the key to store the ring hash key attribute in
// a resolver.Endpoint attribute.
const hashKeyKey = hashKeyType("grpc.resolver.ringhash.hash_key")
// SetHashKey sets the hash key for this endpoint. Combined with the ring_hash
// load balancing policy, it allows placing the endpoint on the ring based on an
// arbitrary string instead of the IP address. If hashKey is empty, the endpoint
// is returned unmodified.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func SetHashKey(endpoint resolver.Endpoint, hashKey string) resolver.Endpoint {
if hashKey == "" {
return endpoint
}
endpoint.Attributes = endpoint.Attributes.WithValue(hashKeyKey, hashKey)
return endpoint
}
// HashKey returns the hash key attribute of endpoint. If this attribute is
// not set, it returns the empty string.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func HashKey(endpoint resolver.Endpoint) string {
hashKey, _ := endpoint.Attributes.Value(hashKeyKey).(string)
return hashKey
}