diff --git a/src/code.cloudfoundry.org/gorouter/config/config.go b/src/code.cloudfoundry.org/gorouter/config/config.go index ad2893eec..8b988349b 100644 --- a/src/code.cloudfoundry.org/gorouter/config/config.go +++ b/src/code.cloudfoundry.org/gorouter/config/config.go @@ -24,6 +24,7 @@ import ( const ( LOAD_BALANCE_RR string = "round-robin" LOAD_BALANCE_LC string = "least-connection" + LOAD_BALANCE_HB string = "hash" AZ_PREF_NONE string = "none" AZ_PREF_LOCAL string = "locally-optimistic" SHARD_ALL string = "all" @@ -38,7 +39,8 @@ const ( ) var ( - LoadBalancingStrategies = []string{LOAD_BALANCE_RR, LOAD_BALANCE_LC} + GlobalLoadBalancingStrategies = []string{LOAD_BALANCE_RR, LOAD_BALANCE_LC} + LoadBalancingStrategies = []string{LOAD_BALANCE_RR, LOAD_BALANCE_LC, LOAD_BALANCE_HB} AZPreferences = []string{AZ_PREF_NONE, AZ_PREF_LOCAL} AllowedShardingModes = []string{SHARD_ALL, SHARD_SEGMENTS, SHARD_SHARED_AND_SEGMENTS} AllowedForwardedClientCertModes = []string{ALWAYS_FORWARD, FORWARD, SANITIZE_SET} @@ -595,6 +597,10 @@ func DefaultConfig() (*Config, error) { return &c, nil } +func IsGlobalLoadBalancingAlgorithmValid(lbAlgo string) bool { + return slices.Contains(GlobalLoadBalancingStrategies, lbAlgo) +} + func IsLoadBalancingAlgorithmValid(lbAlgo string) bool { return slices.Contains(LoadBalancingStrategies, lbAlgo) } @@ -755,8 +761,8 @@ func (c *Config) Process() error { c.RouteServiceEnabled = true } - if !IsLoadBalancingAlgorithmValid(c.LoadBalance) { - return fmt.Errorf("Invalid load balancing algorithm %s. Allowed values are %s", c.LoadBalance, LoadBalancingStrategies) + if !IsGlobalLoadBalancingAlgorithmValid(c.LoadBalance) { + return fmt.Errorf("Invalid global load balancing algorithm %s. Allowed values are %s", c.LoadBalance, GlobalLoadBalancingStrategies) } validAZPref := false diff --git a/src/code.cloudfoundry.org/gorouter/config/config_test.go b/src/code.cloudfoundry.org/gorouter/config/config_test.go index 30ed6a503..8b893f85b 100644 --- a/src/code.cloudfoundry.org/gorouter/config/config_test.go +++ b/src/code.cloudfoundry.org/gorouter/config/config_test.go @@ -47,13 +47,31 @@ zone: meow-zone }) }) + Context("Global Load Balancing Algorithm Validator", func() { + It("Returns false if the value is not in the list of configured global load balancing strategies", func() { + Expect(IsGlobalLoadBalancingAlgorithmValid("wrong.algo")).To(BeFalse()) + }) + + It("Returns true if the value is in the list of configured global load balancing strategies", func() { + Expect(IsGlobalLoadBalancingAlgorithmValid(LOAD_BALANCE_RR)).To(BeTrue()) + }) + + It("Returns false for hash load balancing algorithm as global setting", func() { + Expect(IsGlobalLoadBalancingAlgorithmValid(LOAD_BALANCE_HB)).To(BeFalse()) + }) + }) + Context("Load Balancing Algorithm Validator", func() { It("Returns false if the value is not in the list of configured load balancing strategies", func() { - Expect(IsLoadBalancingAlgorithmValid("wrong.algo")).To(Equal(false)) + Expect(IsLoadBalancingAlgorithmValid("wrong.algo")).To(BeFalse()) }) It("Returns true if the value is in the list of configured load balancing strategies", func() { - Expect(IsLoadBalancingAlgorithmValid(LOAD_BALANCE_RR)).To(Equal(true)) + Expect(IsLoadBalancingAlgorithmValid(LOAD_BALANCE_RR)).To(BeTrue()) + }) + + It("Returns true for hash load balancing algorithm", func() { + Expect(IsLoadBalancingAlgorithmValid(LOAD_BALANCE_HB)).To(BeTrue()) }) }) @@ -73,12 +91,22 @@ balancing_algorithm: least-connection Expect(cfg.LoadBalance).To(Equal(LOAD_BALANCE_LC)) }) + It("can NOT override the load balance strategy to hash (not available as global default)", func() { + cfg, err := DefaultConfig() + Expect(err).ToNot(HaveOccurred()) + var b = []byte(` +balancing_algorithm: hash +`) + cfg.Initialize(b) + Expect(cfg.Process()).To(MatchError("Invalid global load balancing algorithm hash. Allowed values are [round-robin least-connection]")) + }) + It("does not allow an invalid load balance strategy", func() { cfg, err := DefaultConfig() Expect(err).ToNot(HaveOccurred()) cfgForSnippet.LoadBalance = "foo-bar" cfg.Initialize(createYMLSnippet(cfgForSnippet)) - Expect(cfg.Process()).To(MatchError("Invalid load balancing algorithm foo-bar. Allowed values are [round-robin least-connection]")) + Expect(cfg.Process()).To(MatchError("Invalid global load balancing algorithm foo-bar. Allowed values are [round-robin least-connection]")) }) }) @@ -1805,7 +1833,7 @@ load_balancer_healthy_threshold: 10s }) It("setting hop_by_hop_headers_to_filter succeeds", func() { err := config.Initialize(createYMLSnippet(cfgForSnippet)) - Expect(err).NotTo(HaveOccurred()) + Expect(err).ToNot(HaveOccurred()) Expect(config.Process()).To(Succeed()) Expect(config.HopByHopHeadersToFilter).To(Equal([]string{"X-ME", "X-Foo"})) }) diff --git a/src/code.cloudfoundry.org/gorouter/mbus/subscriber.go b/src/code.cloudfoundry.org/gorouter/mbus/subscriber.go index 22ff66511..af17a19b4 100644 --- a/src/code.cloudfoundry.org/gorouter/mbus/subscriber.go +++ b/src/code.cloudfoundry.org/gorouter/mbus/subscriber.go @@ -41,7 +41,9 @@ type RegistryMessage struct { } type RegistryMessageOpts struct { - LoadBalancingAlgorithm string `json:"loadbalancing"` + LoadBalancingAlgorithm string `json:"loadbalancing"` + HashHeaderName string `json:"hash_header"` + HashBalance float64 `json:"hash_balance"` } func (rm *RegistryMessage) makeEndpoint(http2Enabled bool) (*route.Endpoint, error) { @@ -76,6 +78,8 @@ func (rm *RegistryMessage) makeEndpoint(http2Enabled bool) (*route.Endpoint, err UseTLS: useTLS, UpdatedAt: updatedAt, LoadBalancingAlgorithm: rm.Options.LoadBalancingAlgorithm, + HashHeaderName: rm.Options.HashHeaderName, + HashBalanceFactor: rm.Options.HashBalance, }), nil } diff --git a/src/code.cloudfoundry.org/gorouter/mbus/subscriber_test.go b/src/code.cloudfoundry.org/gorouter/mbus/subscriber_test.go index 36ada05f0..ef6fe7967 100644 --- a/src/code.cloudfoundry.org/gorouter/mbus/subscriber_test.go +++ b/src/code.cloudfoundry.org/gorouter/mbus/subscriber_test.go @@ -476,6 +476,82 @@ var _ = Describe("Subscriber", func() { Expect(originalEndpoint).To(Equal(expectedEndpoint)) }) + Context("when HTTP/2 is disabled and the protocol is http2", func() { + BeforeEach(func() { + cfg.EnableHTTP2 = false + }) + It("constructs the endpoint with protocol 'http1'", func() { + msg := mbus.RegistryMessage{ + Host: "host", + App: "app", + Protocol: "http2", + Uris: []route.Uri{"test.example.com"}, + } + + data, err := json.Marshal(msg) + Expect(err).NotTo(HaveOccurred()) + + err = natsClient.Publish("router.register", data) + Expect(err).ToNot(HaveOccurred()) + + Eventually(registry.RegisterCallCount).Should(Equal(1)) + _, originalEndpoint := registry.RegisterArgsForCall(0) + expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host", + AppId: "app", + Protocol: "http1", + }) + + Expect(originalEndpoint).To(Equal(expectedEndpoint)) + }) + }) + }) + + Context("when the message contains a tls port for route", func() { + BeforeEach(func() { + sub = mbus.NewSubscriber(natsClient, registry, cfg, reconnected, logger.Logger) + process = ifrit.Invoke(sub) + Eventually(process.Ready()).Should(BeClosed()) + }) + It("endpoint is constructed with tls port instead of http", func() { + msg := mbus.RegistryMessage{ + Host: "host", + App: "app", + TLSPort: 1999, + ServerCertDomainSAN: "san", + PrivateInstanceID: "id", + PrivateInstanceIndex: "index", + StaleThresholdInSeconds: 120, + Uris: []route.Uri{"test.example.com"}, + Tags: map[string]string{"key": "value"}, + } + + data, err := json.Marshal(msg) + Expect(err).NotTo(HaveOccurred()) + + err = natsClient.Publish("router.register", data) + Expect(err).ToNot(HaveOccurred()) + + Eventually(registry.RegisterCallCount).Should(Equal(1)) + _, originalEndpoint := registry.RegisterArgsForCall(0) + expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host", + AppId: "app", + Port: 1999, + Protocol: "http1", + UseTLS: true, + ServerCertDomainSAN: "san", + PrivateInstanceId: "id", + PrivateInstanceIndex: "index", + StaleThresholdInSeconds: 120, + Tags: map[string]string{"key": "value"}, + }) + + Expect(originalEndpoint).To(Equal(expectedEndpoint)) + }) + }) + + Context("when the message contains per-route options", func() { Context("when the message contains load balancing algorithm option", func() { JustBeforeEach(func() { sub = mbus.NewSubscriber(natsClient, registry, cfg, reconnected, logger.Logger) @@ -498,7 +574,7 @@ var _ = Describe("Subscriber", func() { err = natsClient.Publish("router.register", data) Expect(err).ToNot(HaveOccurred()) - Eventually(registry.RegisterCallCount).Should(Equal(2)) + Eventually(registry.RegisterCallCount).Should(Equal(1)) _, originalEndpoint := registry.RegisterArgsForCall(0) expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ Host: "host", @@ -531,7 +607,7 @@ var _ = Describe("Subscriber", func() { err = natsClient.Publish("router.register", data) Expect(err).ToNot(HaveOccurred()) - Eventually(registry.RegisterCallCount).Should(Equal(2)) + Eventually(registry.RegisterCallCount).Should(Equal(1)) _, originalEndpoint := registry.RegisterArgsForCall(0) expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ Host: "host", @@ -544,18 +620,26 @@ var _ = Describe("Subscriber", func() { }) - Context("when HTTP/2 is disabled and the protocol is http2", func() { - BeforeEach(func() { - cfg.EnableHTTP2 = false + Context("when the message contains hash-based load balancing options", func() { + JustBeforeEach(func() { + sub = mbus.NewSubscriber(natsClient, registry, cfg, reconnected, logger.Logger) + process = ifrit.Invoke(sub) + Eventually(process.Ready()).Should(BeClosed()) }) - It("constructs the endpoint with protocol 'http1'", func() { - msg := mbus.RegistryMessage{ + It("constructs an endpoint with the correct load balancing algorithm (HashBalance is set)", func() { + var expectedLBAlgo = "hash" + + var msg = mbus.RegistryMessage{ Host: "host", App: "app", Protocol: "http2", Uris: []route.Uri{"test.example.com"}, + Options: mbus.RegistryMessageOpts{ + LoadBalancingAlgorithm: expectedLBAlgo, + HashHeaderName: "X-Header", + HashBalance: 1.5, + }, } - data, err := json.Marshal(msg) Expect(err).NotTo(HaveOccurred()) @@ -565,57 +649,82 @@ var _ = Describe("Subscriber", func() { Eventually(registry.RegisterCallCount).Should(Equal(1)) _, originalEndpoint := registry.RegisterArgsForCall(0) expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ - Host: "host", - AppId: "app", - Protocol: "http1", + Host: "host", + AppId: "app", + Protocol: "http2", + LoadBalancingAlgorithm: expectedLBAlgo, + HashHeaderName: "X-Header", + HashBalanceFactor: 1.5, }) Expect(originalEndpoint).To(Equal(expectedEndpoint)) }) - }) - }) - Context("when the message contains a tls port for route", func() { - BeforeEach(func() { - sub = mbus.NewSubscriber(natsClient, registry, cfg, reconnected, logger.Logger) - process = ifrit.Invoke(sub) - Eventually(process.Ready()).Should(BeClosed()) - }) - It("endpoint is constructed with tls port instead of http", func() { - msg := mbus.RegistryMessage{ - Host: "host", - App: "app", - TLSPort: 1999, - ServerCertDomainSAN: "san", - PrivateInstanceID: "id", - PrivateInstanceIndex: "index", - StaleThresholdInSeconds: 120, - Uris: []route.Uri{"test.example.com"}, - Tags: map[string]string{"key": "value"}, - } + It("constructs an endpoint with the correct load balancing algorithm (HashBalance is not set)", func() { + var expectedLBAlgo = "hash" - data, err := json.Marshal(msg) - Expect(err).NotTo(HaveOccurred()) + var msg = mbus.RegistryMessage{ + Host: "host", + App: "app", + Protocol: "http2", + Uris: []route.Uri{"test.example.com"}, + Options: mbus.RegistryMessageOpts{ + LoadBalancingAlgorithm: expectedLBAlgo, + HashHeaderName: "X-Header", + }, + } + data, err := json.Marshal(msg) + Expect(err).NotTo(HaveOccurred()) - err = natsClient.Publish("router.register", data) - Expect(err).ToNot(HaveOccurred()) + err = natsClient.Publish("router.register", data) + Expect(err).ToNot(HaveOccurred()) - Eventually(registry.RegisterCallCount).Should(Equal(1)) - _, originalEndpoint := registry.RegisterArgsForCall(0) - expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ - Host: "host", - AppId: "app", - Port: 1999, - Protocol: "http1", - UseTLS: true, - ServerCertDomainSAN: "san", - PrivateInstanceId: "id", - PrivateInstanceIndex: "index", - StaleThresholdInSeconds: 120, - Tags: map[string]string{"key": "value"}, + Eventually(registry.RegisterCallCount).Should(Equal(1)) + _, originalEndpoint := registry.RegisterArgsForCall(0) + expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host", + AppId: "app", + Protocol: "http2", + LoadBalancingAlgorithm: expectedLBAlgo, + HashHeaderName: "X-Header", + HashBalanceFactor: 0.0, + }) + Expect(expectedEndpoint.HashHeaderName).To(Equal("X-Header")) + Expect(expectedEndpoint.HashBalanceFactor).To(Equal(0.0)) + Expect(originalEndpoint).To(Equal(expectedEndpoint)) }) - Expect(originalEndpoint).To(Equal(expectedEndpoint)) + It("ignores HashRoutingProperties if load balancing algorithm is not 'hash'", func() { + var expectedLBAlgo = "round-robin" + + var msg = mbus.RegistryMessage{ + Host: "host", + App: "app", + Protocol: "http2", + Uris: []route.Uri{"test.example.com"}, + Options: mbus.RegistryMessageOpts{ + LoadBalancingAlgorithm: expectedLBAlgo, + HashHeaderName: "X-Header", + }, + } + data, err := json.Marshal(msg) + Expect(err).NotTo(HaveOccurred()) + + err = natsClient.Publish("router.register", data) + Expect(err).ToNot(HaveOccurred()) + + Eventually(registry.RegisterCallCount).Should(Equal(1)) + _, originalEndpoint := registry.RegisterArgsForCall(0) + expectedEndpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host", + AppId: "app", + Protocol: "http2", + LoadBalancingAlgorithm: expectedLBAlgo, + }) + Expect(expectedEndpoint.HashHeaderName).To(BeEmpty()) + Expect(expectedEndpoint.HashBalanceFactor).To(Equal(0.0)) + Expect(originalEndpoint).To(Equal(expectedEndpoint)) + }) }) }) @@ -839,5 +948,4 @@ var _ = Describe("Subscriber", func() { } }) }) - }) diff --git a/src/code.cloudfoundry.org/gorouter/route/pool.go b/src/code.cloudfoundry.org/gorouter/route/pool.go index 0d9fd8f2d..f089fc15b 100644 --- a/src/code.cloudfoundry.org/gorouter/route/pool.go +++ b/src/code.cloudfoundry.org/gorouter/route/pool.go @@ -94,6 +94,8 @@ type Endpoint struct { UpdatedAt time.Time RoundTripperInit sync.Once LoadBalancingAlgorithm string + HashHeaderName string + HashBalanceFactor float64 } func (e *Endpoint) RoundTripper() ProxyRoundTripper { @@ -123,6 +125,7 @@ func (e *Endpoint) Equal(e2 *Endpoint) bool { if e2 == nil { return false } + return e.ApplicationId == e2.ApplicationId && e.addr == e2.addr && e.Protocol == e2.Protocol && @@ -136,6 +139,8 @@ func (e *Endpoint) Equal(e2 *Endpoint) bool { e.useTls == e2.useTls && e.UpdatedAt.Equal(e2.UpdatedAt) && e.LoadBalancingAlgorithm == e2.LoadBalancingAlgorithm && + e.HashHeaderName == e2.HashHeaderName && + e.HashBalanceFactor == e2.HashBalanceFactor && maps.Equal(e.Tags, e2.Tags) } @@ -200,10 +205,12 @@ type EndpointOpts struct { UseTLS bool UpdatedAt time.Time LoadBalancingAlgorithm string + HashHeaderName string + HashBalanceFactor float64 } func NewEndpoint(opts *EndpointOpts) *Endpoint { - return &Endpoint{ + endpoint := &Endpoint{ ApplicationId: opts.AppId, AvailabilityZone: opts.AvailabilityZone, addr: fmt.Sprintf("%s:%d", opts.Host, opts.Port), @@ -221,6 +228,13 @@ func NewEndpoint(opts *EndpointOpts) *Endpoint { UpdatedAt: opts.UpdatedAt, LoadBalancingAlgorithm: opts.LoadBalancingAlgorithm, } + + if opts.LoadBalancingAlgorithm == config.LOAD_BALANCE_HB && opts.HashHeaderName != "" { // BalanceFactor is optional + endpoint.HashHeaderName = opts.HashHeaderName + endpoint.HashBalanceFactor = opts.HashBalanceFactor + } + + return endpoint } func (e *Endpoint) IsTLS() bool { @@ -587,6 +601,8 @@ func (e *Endpoint) MarshalJSON() ([]byte, error) { PrivateInstanceId string `json:"private_instance_id,omitempty"` ServerCertDomainSAN string `json:"server_cert_domain_san,omitempty"` LoadBalancingAlgorithm string `json:"load_balancing_algorithm,omitempty"` + HashHeader string `json:"hash_header,omitempty"` + HashBalance float64 `json:"hash_balance,omitempty"` } jsonObj.Address = e.addr @@ -600,6 +616,9 @@ func (e *Endpoint) MarshalJSON() ([]byte, error) { jsonObj.PrivateInstanceId = e.PrivateInstanceId jsonObj.ServerCertDomainSAN = e.ServerCertDomainSAN jsonObj.LoadBalancingAlgorithm = e.LoadBalancingAlgorithm + jsonObj.HashHeader = e.HashHeaderName + jsonObj.HashBalance = e.HashBalanceFactor + return json.Marshal(jsonObj) } diff --git a/src/code.cloudfoundry.org/gorouter/route/pool_test.go b/src/code.cloudfoundry.org/gorouter/route/pool_test.go index 7d0655141..31da6c8d7 100644 --- a/src/code.cloudfoundry.org/gorouter/route/pool_test.go +++ b/src/code.cloudfoundry.org/gorouter/route/pool_test.go @@ -39,6 +39,70 @@ var _ = Describe("Endpoint", func() { }) }) }) + + Context("Hash-based Load Balancing", func() { + Context("when endpoint is created with hash options", func() { + var endpoint *route.Endpoint + BeforeEach(func() { + endpoint = route.NewEndpoint(&route.EndpointOpts{ + AppId: "test-app", + Host: "localhost", + Port: 8080, + LoadBalancingAlgorithm: "hash", + HashHeaderName: "X-Header", + HashBalanceFactor: 1.15, + }) + }) + It("should have the correct hash header", func() { + Expect(endpoint.HashHeaderName).To(Equal("X-Header")) + }) + It("should have the correct hash balance", func() { + Expect(endpoint.HashBalanceFactor).To(Equal(1.15)) + }) + It("should have the correct load balancing algorithm", func() { + Expect(endpoint.LoadBalancingAlgorithm).To(Equal("hash")) + }) + }) + + Context("when comparing endpoints with hash options", func() { + var endpoint1, endpoint2, endpoint3 *route.Endpoint + BeforeEach(func() { + opts1 := &route.EndpointOpts{ + AppId: "test-app", + Host: "localhost", + Port: 8080, + LoadBalancingAlgorithm: "hash", + HashHeaderName: "X-Header", + HashBalanceFactor: 1.15, + } + opts2 := &route.EndpointOpts{ + AppId: "test-app", + Host: "localhost", + Port: 8080, + LoadBalancingAlgorithm: "hash", + HashHeaderName: "X-Header", + HashBalanceFactor: 1.15, + } + opts3 := &route.EndpointOpts{ + AppId: "test-app", + Host: "localhost", + Port: 8080, + LoadBalancingAlgorithm: "hash", + HashHeaderName: "X-Header", + HashBalanceFactor: 2.25, + } + endpoint1 = route.NewEndpoint(opts1) + endpoint2 = route.NewEndpoint(opts2) + endpoint3 = route.NewEndpoint(opts3) + }) + It("should return true when endpoints have same hash options", func() { + Expect(endpoint1.Equal(endpoint2)).To(BeTrue()) + }) + It("should return false when endpoints have different hash options", func() { + Expect(endpoint1.Equal(endpoint3)).To(BeFalse()) + }) + }) + }) }) var _ = Describe("EndpointPool", func() { @@ -232,133 +296,137 @@ var _ = Describe("EndpointPool", func() { }) }) - Context("Load Balancing Algorithm of a pool", func() { - It("has a value specified in the pool options", func() { - poolWithLBAlgo := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, + Context("Customizable Per Route Load Balancing", func() { + + Context("Load Balancing Algorithm of a pool", func() { + It("has a value specified in the pool options", func() { + poolWithLBAlgo := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, + }) + Expect(poolWithLBAlgo.LoadBalancingAlgorithm).To(Equal(config.LOAD_BALANCE_RR)) }) - Expect(poolWithLBAlgo.LoadBalancingAlgorithm).To(Equal(config.LOAD_BALANCE_RR)) - }) - It("has an invalid value specified in the pool options", func() { - poolWithLBAlgo2 := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: "wrong-lb-algo", + It("has an invalid value specified in the pool options", func() { + poolWithLBAlgo2 := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: "wrong-lb-algo", + }) + iterator := poolWithLBAlgo2.Endpoints(logger.Logger, "", false, "none", "zone") + Expect(iterator).To(BeAssignableToTypeOf(&route.RoundRobin{})) + Eventually(logger).Should(gbytes.Say(`invalid-pool-load-balancing-algorithm`)) }) - iterator := poolWithLBAlgo2.Endpoints(logger.Logger, "", false, "none", "zone") - Expect(iterator).To(BeAssignableToTypeOf(&route.RoundRobin{})) - Eventually(logger).Should(gbytes.Say(`invalid-pool-load-balancing-algorithm`)) - }) - It("is correctly propagated to the newly created endpoints LOAD_BALANCE_LC ", func() { - poolWithLBAlgoLC := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: config.LOAD_BALANCE_LC, + It("is correctly propagated to the newly created endpoints LOAD_BALANCE_LC ", func() { + poolWithLBAlgoLC := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: config.LOAD_BALANCE_LC, + }) + iterator := poolWithLBAlgoLC.Endpoints(logger.Logger, "", false, "none", "az") + Expect(iterator).To(BeAssignableToTypeOf(&route.LeastConnection{})) + Eventually(logger).Should(gbytes.Say(`endpoint-iterator-with-least-connection-lb-algo`)) }) - iterator := poolWithLBAlgoLC.Endpoints(logger.Logger, "", false, "none", "az") - Expect(iterator).To(BeAssignableToTypeOf(&route.LeastConnection{})) - Eventually(logger).Should(gbytes.Say(`endpoint-iterator-with-least-connection-lb-algo`)) - }) - It("is correctly propagated to the newly created endpoints LOAD_BALANCE_RR ", func() { - poolWithLBAlgoLC := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, + It("is correctly propagated to the newly created endpoints LOAD_BALANCE_RR ", func() { + poolWithLBAlgoLC := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, + }) + iterator := poolWithLBAlgoLC.Endpoints(logger.Logger, "", false, "none", "az") + Expect(iterator).To(BeAssignableToTypeOf(&route.RoundRobin{})) + Eventually(logger).Should(gbytes.Say(`endpoint-iterator-with-round-robin-lb-algo`)) }) - iterator := poolWithLBAlgoLC.Endpoints(logger.Logger, "", false, "none", "az") - Expect(iterator).To(BeAssignableToTypeOf(&route.RoundRobin{})) - Eventually(logger).Should(gbytes.Say(`endpoint-iterator-with-round-robin-lb-algo`)) + }) - }) - Context("Load balancing algorithm of a newly added endpoint", func() { + Context("Load balancing algorithm of a newly added endpoint", func() { - It("is valid and will overwrite the load balancing algorithm of a pool", func() { - pool := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, - }) - expectedLBAlgo := config.LOAD_BALANCE_LC - endpoint := route.NewEndpoint(&route.EndpointOpts{ - Host: "host-1", Port: 1234, - RouteServiceUrl: "url", - LoadBalancingAlgorithm: expectedLBAlgo, + It("is valid and will overwrite the load balancing algorithm of a pool", func() { + pool := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, + }) + expectedLBAlgo := config.LOAD_BALANCE_LC + endpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host-1", Port: 1234, + RouteServiceUrl: "url", + LoadBalancingAlgorithm: expectedLBAlgo, + }) + pool.Put(endpoint) + Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) + Eventually(logger).Should(gbytes.Say(`setting-pool-load-balancing-algorithm-to-that-of-an-endpoint`)) }) - pool.Put(endpoint) - Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) - Eventually(logger).Should(gbytes.Say(`setting-pool-load-balancing-algorithm-to-that-of-an-endpoint`)) - }) - It("is an empty string and the load balancing algorithm of a pool is kept", func() { - expectedLBAlgo := config.LOAD_BALANCE_RR - pool := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: expectedLBAlgo, - }) - endpoint := route.NewEndpoint(&route.EndpointOpts{ - Host: "host-1", Port: 1234, - RouteServiceUrl: "url", + It("is an empty string and the load balancing algorithm of a pool is kept", func() { + expectedLBAlgo := config.LOAD_BALANCE_RR + pool := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: expectedLBAlgo, + }) + endpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host-1", Port: 1234, + RouteServiceUrl: "url", + }) + pool.Put(endpoint) + Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) }) - pool.Put(endpoint) - Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) - }) - It("is not specified in the endpoint options and the load balancing algorithm of a pool is kept", func() { - expectedLBAlgo := config.LOAD_BALANCE_RR - pool := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: expectedLBAlgo, - }) - endpoint := route.NewEndpoint(&route.EndpointOpts{ - Host: "host-1", Port: 1234, - RouteServiceUrl: "url", + It("is not specified in the endpoint options and the load balancing algorithm of a pool is kept", func() { + expectedLBAlgo := config.LOAD_BALANCE_RR + pool := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: expectedLBAlgo, + }) + endpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host-1", Port: 1234, + RouteServiceUrl: "url", + }) + pool.Put(endpoint) + Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) }) - pool.Put(endpoint) - Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) - }) - It("is an invalid value and the load balancing algorithm of a pool is kept", func() { - expectedLBAlgo := config.LOAD_BALANCE_RR - pool := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: expectedLBAlgo, - }) - endpoint := route.NewEndpoint(&route.EndpointOpts{ - Host: "host-1", Port: 1234, - RouteServiceUrl: "url", - LoadBalancingAlgorithm: "invalid-lb-algo", + It("is an invalid value and the load balancing algorithm of a pool is kept", func() { + expectedLBAlgo := config.LOAD_BALANCE_RR + pool := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: expectedLBAlgo, + }) + endpoint := route.NewEndpoint(&route.EndpointOpts{ + Host: "host-1", Port: 1234, + RouteServiceUrl: "url", + LoadBalancingAlgorithm: "invalid-lb-algo", + }) + pool.Put(endpoint) + Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) + Eventually(logger).Should(gbytes.Say(`invalid-endpoint-load-balancing-algorithm-provided-keeping-pool-lb-algo`)) }) - pool.Put(endpoint) - Expect(pool.LoadBalancingAlgorithm).To(Equal(expectedLBAlgo)) - Eventually(logger).Should(gbytes.Say(`invalid-endpoint-load-balancing-algorithm-provided-keeping-pool-lb-algo`)) }) - }) - Context("Load balancing algorithm of a updated endpoint", func() { - It("is will overwrite the load balancing algorithm of the endpoint and pool", func() { - pool := route.NewPool(&route.PoolOpts{ - Logger: logger.Logger, - LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, - }) + Context("Load balancing algorithm of a updated endpoint", func() { + It("will overwrite the load balancing algorithm of the endpoint and pool", func() { + pool := route.NewPool(&route.PoolOpts{ + Logger: logger.Logger, + LoadBalancingAlgorithm: config.LOAD_BALANCE_RR, + }) - endpointOpts := route.EndpointOpts{ - Host: "host-1", - Port: 1234, - RouteServiceUrl: "url", - LoadBalancingAlgorithm: config.LOAD_BALANCE_LC, - } + endpointOpts := route.EndpointOpts{ + Host: "host-1", + Port: 1234, + RouteServiceUrl: "url", + LoadBalancingAlgorithm: config.LOAD_BALANCE_LC, + } - initalEndpoint := route.NewEndpoint(&endpointOpts) + initalEndpoint := route.NewEndpoint(&endpointOpts) - pool.Put(initalEndpoint) - Expect(pool.LoadBalancingAlgorithm).To(Equal(config.LOAD_BALANCE_LC)) + pool.Put(initalEndpoint) + Expect(pool.LoadBalancingAlgorithm).To(Equal(config.LOAD_BALANCE_LC)) - endpointOpts.LoadBalancingAlgorithm = config.LOAD_BALANCE_RR - updatedEndpoint := route.NewEndpoint(&endpointOpts) + endpointOpts.LoadBalancingAlgorithm = config.LOAD_BALANCE_RR + updatedEndpoint := route.NewEndpoint(&endpointOpts) - pool.Put(updatedEndpoint) - Expect(pool.LoadBalancingAlgorithm).To(Equal(config.LOAD_BALANCE_RR)) + pool.Put(updatedEndpoint) + Expect(pool.LoadBalancingAlgorithm).To(Equal(config.LOAD_BALANCE_RR)) + }) }) }) @@ -848,7 +916,9 @@ var _ = Describe("EndpointPool", func() { ServerCertDomainSAN: "pvt_test_san", PrivateInstanceId: "pvt_test_instance_id", UseTLS: true, - LoadBalancingAlgorithm: "lb-meow", + LoadBalancingAlgorithm: "hash", + HashHeaderName: "X-Header", + HashBalanceFactor: 1.25, }) pool.Put(e) @@ -857,7 +927,7 @@ var _ = Describe("EndpointPool", func() { json, err := pool.MarshalJSON() Expect(err).ToNot(HaveOccurred()) - Expect(string(json)).To(Equal(`[{"address":"1.2.3.4:5678","availability_zone":"az-meow","protocol":"http1","tls":false,"ttl":-1,"route_service_url":"https://my-rs.com","tags":null},{"address":"5.6.7.8:5678","availability_zone":"","protocol":"http2","tls":true,"ttl":-1,"tags":null,"private_instance_id":"pvt_test_instance_id","server_cert_domain_san":"pvt_test_san","load_balancing_algorithm":"lb-meow"}]`)) + Expect(string(json)).To(Equal(`[{"address":"1.2.3.4:5678","availability_zone":"az-meow","protocol":"http1","tls":false,"ttl":-1,"route_service_url":"https://my-rs.com","tags":null},{"address":"5.6.7.8:5678","availability_zone":"","protocol":"http2","tls":true,"ttl":-1,"tags":null,"private_instance_id":"pvt_test_instance_id","server_cert_domain_san":"pvt_test_san","load_balancing_algorithm":"hash","hash_header":"X-Header","hash_balance":1.25}]`)) }) Context("when endpoints do not have empty tags", func() {