Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/23393.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
discovery-chain: removes the use of hashstructure_v2 ([github.com/mitchellh/hashstructure/v2] from compiled discovery chain hashing and replaces it with explicit custom hash implementations.
```
6 changes: 1 addition & 5 deletions agent/consul/discovery_chain_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

metrics "github.com/armon/go-metrics"
memdb "github.com/hashicorp/go-memdb"
hashstructure_v2 "github.com/mitchellh/hashstructure/v2"

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/discoverychain"
Expand Down Expand Up @@ -77,10 +76,7 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs
// Generate a hash of the config entry content driving this
// response. Use it to determine if the response is identical to a
// prior wakeup.
newHash, err := hashstructure_v2.Hash(chain, hashstructure_v2.FormatV2, nil)
if err != nil {
return fmt.Errorf("error hashing reply for spurious wakeup suppression: %w", err)
}
newHash := chain.GetHash()

if ranOnce && priorHash == newHash {
priorHash = newHash
Expand Down
250 changes: 250 additions & 0 deletions agent/structs/config_entry_discoverychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,19 @@ type ServiceRoute struct {
Destination *ServiceRouteDestination `json:",omitempty"`
}

func (d *ServiceRoute) getHash() uint64 {
return hashValue(d)
}

func (d *ServiceRoute) appendHash(h *customHasher) {
if d == nil {
return
}

addOptionalValue(h, d.Match)
addOptionalValue(h, d.Destination)
}

// ServiceRouteMatch is a set of criteria that can match incoming L7 requests.
type ServiceRouteMatch struct {
HTTP *ServiceRouteHTTPMatch `json:",omitempty"`
Expand All @@ -367,6 +380,18 @@ type ServiceRouteMatch struct {
// (gRPC, redis, etc) they can go here.
}

func (m *ServiceRouteMatch) getHash() uint64 {
return hashValue(m)
}

func (m *ServiceRouteMatch) appendHash(h *customHasher) {
if m == nil {
return
}

addOptionalValue(h, m.HTTP)
}

func (m *ServiceRouteMatch) IsEmpty() bool {
return m.HTTP == nil || m.HTTP.IsEmpty()
}
Expand All @@ -383,6 +408,30 @@ type ServiceRouteHTTPMatch struct {
Methods []string `json:",omitempty"`
}

func (m *ServiceRouteHTTPMatch) getHash() uint64 {
return hashValue(m)
}

func (m *ServiceRouteHTTPMatch) appendHash(h *customHasher) {
if m == nil {
return
}

h.addString(m.PathExact)
h.addString(m.PathPrefix)
h.addString(m.PathRegex)
h.addBool(m.CaseInsensitive)
addSlice(h, m.Header, func(h *customHasher, header ServiceRouteHTTPMatchHeader) {
h.addUint64((&header).getHash())
})
addSlice(h, m.QueryParam, func(h *customHasher, queryParam ServiceRouteHTTPMatchQueryParam) {
h.addUint64((&queryParam).getHash())
})
addSlice(h, m.Methods, func(h *customHasher, method string) {
h.addString(method)
})
}

func (m *ServiceRouteHTTPMatch) IsEmpty() bool {
return m.PathExact == "" &&
m.PathPrefix == "" &&
Expand All @@ -403,13 +452,46 @@ type ServiceRouteHTTPMatchHeader struct {
Invert bool `json:",omitempty"`
}

func (d *ServiceRouteHTTPMatchHeader) getHash() uint64 {
return hashValue(d)
}

func (d *ServiceRouteHTTPMatchHeader) appendHash(h *customHasher) {
if d == nil {
return
}

h.addString(d.Name)
h.addBool(d.Present)
h.addString(d.Exact)
h.addString(d.Prefix)
h.addString(d.Suffix)
h.addString(d.Regex)
h.addBool(d.Invert)
}

type ServiceRouteHTTPMatchQueryParam struct {
Name string
Present bool `json:",omitempty"`
Exact string `json:",omitempty"`
Regex string `json:",omitempty"`
}

func (d *ServiceRouteHTTPMatchQueryParam) getHash() uint64 {
return hashValue(d)
}

func (d *ServiceRouteHTTPMatchQueryParam) appendHash(h *customHasher) {
if d == nil {
return
}

h.addString(d.Name)
h.addBool(d.Present)
h.addString(d.Exact)
h.addString(d.Regex)
}

// ServiceRouteDestination describes how to proxy the actual matching request
// to a service.
type ServiceRouteDestination struct {
Expand Down Expand Up @@ -474,6 +556,34 @@ type ServiceRouteDestination struct {
ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"`
}

func (d *ServiceRouteDestination) getHash() uint64 {
return hashValue(d)
}

func (d *ServiceRouteDestination) appendHash(h *customHasher) {
if d == nil {
return
}

h.addString(d.Service)
h.addString(d.ServiceSubset)
h.addString(d.Namespace)
h.addString(d.Partition)
h.addString(d.PrefixRewrite)
h.addDuration(d.RequestTimeout)
h.addDuration(d.IdleTimeout)
h.addUint64(uint64(d.NumRetries))
h.addBool(d.RetryOnConnectFailure)
addSlice(h, d.RetryOn, func(h *customHasher, retryOn string) {
h.addString(retryOn)
})
addSlice(h, d.RetryOnStatusCodes, func(h *customHasher, statusCode uint32) {
h.addUint64(uint64(statusCode))
})
addOptionalValue(h, d.RequestHeaders)
addOptionalValue(h, d.ResponseHeaders)
}

func (e *ServiceRouteDestination) MarshalJSON() ([]byte, error) {
type Alias ServiceRouteDestination
exported := &struct {
Expand Down Expand Up @@ -772,6 +882,24 @@ type ServiceSplit struct {
ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"`
}

func (d *ServiceSplit) getHash() uint64 {
return hashValue(d)
}

func (d *ServiceSplit) appendHash(h *customHasher) {
if d == nil {
return
}

h.addFloat32(d.Weight)
h.addString(d.Service)
h.addString(d.ServiceSubset)
h.addString(d.Namespace)
h.addString(d.Partition)
addOptionalValue(h, d.RequestHeaders)
addOptionalValue(h, d.ResponseHeaders)
}

// MergeParent is called by the discovery chain compiler when a split directs to
// another splitter. We refer to the first ServiceSplit as the parent and the
// ServiceSplits of the second splitter as its children. The parent ends up
Expand Down Expand Up @@ -1389,6 +1517,19 @@ type ServiceResolverSubset struct {
OnlyPassing bool `json:",omitempty" alias:"only_passing"`
}

func (c *ServiceResolverSubset) getHash() uint64 {
return hashValue(c)
}

func (c *ServiceResolverSubset) appendHash(h *customHasher) {
if c == nil {
return
}

h.addString(c.Filter)
h.addBool(c.OnlyPassing)
}

type ServiceResolverRedirect struct {
// Service is a service to resolve instead of the current service
// (optional).
Expand Down Expand Up @@ -1525,6 +1666,21 @@ type ServiceResolverFailoverPolicy struct {
Regions []string `json:",omitempty"`
}

func (fp *ServiceResolverFailoverPolicy) getHash() uint64 {
return hashValue(fp)
}

func (fp *ServiceResolverFailoverPolicy) appendHash(h *customHasher) {
if fp == nil {
return
}

h.addString(fp.Mode)
addSlice(h, fp.Regions, func(h *customHasher, region string) {
h.addString(region)
})
}

func (fp *ServiceResolverFailoverPolicy) validate() error {
if fp == nil {
return nil
Expand Down Expand Up @@ -1598,6 +1754,23 @@ type LoadBalancer struct {
HashPolicies []HashPolicy `json:",omitempty" alias:"hash_policies"`
}

func (c *LoadBalancer) getHash() uint64 {
return hashValue(c)
}

func (c *LoadBalancer) appendHash(h *customHasher) {
if c == nil {
return
}

h.addString(c.Policy)
addOptionalValue(h, c.RingHashConfig)
addOptionalValue(h, c.LeastRequestConfig)
addSlice(h, c.HashPolicies, func(h *customHasher, hashPolicy HashPolicy) {
h.addUint64((&hashPolicy).getHash())
})
}

// RingHashConfig contains configuration for the "ring_hash" policy type
type RingHashConfig struct {
// MinimumRingSize determines the minimum number of entries in the hash ring
Expand All @@ -1607,12 +1780,37 @@ type RingHashConfig struct {
MaximumRingSize uint64 `json:",omitempty" alias:"maximum_ring_size"`
}

func (c *RingHashConfig) getHash() uint64 {
return hashValue(c)
}

func (c *RingHashConfig) appendHash(h *customHasher) {
if c == nil {
return
}

h.addUint64(c.MinimumRingSize)
h.addUint64(c.MaximumRingSize)
}

// LeastRequestConfig contains configuration for the "least_request" policy type
type LeastRequestConfig struct {
// ChoiceCount determines the number of random healthy hosts from which to select the one with the least requests.
ChoiceCount uint32 `json:",omitempty" alias:"choice_count"`
}

func (c *LeastRequestConfig) getHash() uint64 {
return hashValue(c)
}

func (c *LeastRequestConfig) appendHash(h *customHasher) {
if c == nil {
return
}

h.addUint64(uint64(c.ChoiceCount))
}

// HashPolicy defines which attributes will be hashed by hash-based LB algorithms
type HashPolicy struct {
// Field is the attribute type to hash on.
Expand All @@ -1638,6 +1836,22 @@ type HashPolicy struct {
Terminal bool `json:",omitempty"`
}

func (c *HashPolicy) getHash() uint64 {
return hashValue(c)
}

func (c *HashPolicy) appendHash(h *customHasher) {
if c == nil {
return
}

h.addString(c.Field)
h.addString(c.FieldValue)
addOptionalValue(h, c.CookieConfig)
h.addBool(c.SourceIP)
h.addBool(c.Terminal)
}

// CookieConfig contains configuration for the "cookie" hash policy type.
// This is specified to have Envoy generate a cookie for a client on its first request.
type CookieConfig struct {
Expand All @@ -1651,6 +1865,20 @@ type CookieConfig struct {
Path string `json:",omitempty"`
}

func (c *CookieConfig) getHash() uint64 {
return hashValue(c)
}

func (c *CookieConfig) appendHash(h *customHasher) {
if c == nil {
return
}

h.addBool(c.Session)
h.addDuration(c.TTL)
h.addString(c.Path)
}

func (lb *LoadBalancer) IsHashBased() bool {
if lb == nil {
return false
Expand Down Expand Up @@ -1847,6 +2075,28 @@ type HTTPHeaderModifiers struct {
Remove []string `json:",omitempty"`
}

func (m *HTTPHeaderModifiers) getHash() uint64 {
return hashValue(m)
}

func (m *HTTPHeaderModifiers) appendHash(h *customHasher) {
if m == nil {
return
}

addSortedStringKeyMap(h, m.Add, func(h *customHasher, key, value string) {
h.addString(key)
h.addString(value)
})
addSortedStringKeyMap(h, m.Set, func(h *customHasher, key, value string) {
h.addString(key)
h.addString(value)
})
addSlice(h, m.Remove, func(h *customHasher, value string) {
h.addString(value)
})
}

func (m *HTTPHeaderModifiers) IsZero() bool {
if m == nil {
return true
Expand Down
Loading
Loading