From 09b7382a10c0c029b3823b5a62b90c5c18076c83 Mon Sep 17 00:00:00 2001 From: Guillaume LEGRAIN Date: Wed, 3 Jul 2024 10:38:58 +0200 Subject: [PATCH 1/3] Add config to gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index b8a95f6e..addeb984 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ bin/ command testing +ca/ +config/ +envoy/ + From 29e22eaf0326937dec88878460b3625cfa20b44f Mon Sep 17 00:00:00 2001 From: Lyonel Martinez Date: Mon, 12 Sep 2022 17:31:45 +0200 Subject: [PATCH 2/3] feat(weight-annotation): Annotation to add load-balancing weight --- README.md | 5 +++++ pkg/envoy/boilerplate.go | 7 ++++--- pkg/envoy/ingress_translator.go | 31 +++++++++++++++++++++------- pkg/envoy/ingress_translator_test.go | 26 +++++++++++++---------- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6d2d4057..37909810 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Yggdrasil allows for some customisation of the route and cluster config per Ingr |--------------------------------------------------------------|----------| | [yggdrasil.uswitch.com/healthcheck-path](#health-check-path) | string | | [yggdrasil.uswitch.com/timeout](#timeout) | duration | +| [yggdrasil.uswitch.com/weight](#weight) | uint32 | | [yggdrasil.uswitch.com/retry-on](#retries) | string | ### Health Check Path @@ -90,6 +91,9 @@ Allows for adjusting the timeout in envoy. Currently this will set the following * [config.route.v3.RetryPolicy.PerTryTimeout](https://www.envoyproxy.io/docs/envoy/v1.19.0/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-retrypolicy-per-try-timeout) * [config.cluster.v3.Cluster.ConnectTimeout](https://www.envoyproxy.io/docs/envoy/v1.19.0/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout) +### Weight +Allows for adjusting the [load balancer weights](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/endpoint/v3/endpoint_components.proto#config-endpoint-v3-lbendpoint) in envoy. + ### Retries Allows overwriting the default retry policy's [config.route.v3.RetryPolicy.RetryOn](https://www.envoyproxy.io/docs/envoy/v1.19.0/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-retrypolicy-retry-on) set by the `--retry-on` flag (default 5xx). Accepts a comma-separated list of retry-on policies. @@ -105,6 +109,7 @@ metadata: annotations: yggdrasil.uswitch.com/healthcheck-path: /healthz yggdrasil.uswitch.com/timeout: 30s + yggdrasil.uswitch.com/weight: "12" yggdrasil.uswitch.com/retry-on: gateway-error,connect-failure spec: rules: diff --git a/pkg/envoy/boilerplate.go b/pkg/envoy/boilerplate.go index d2579dfc..a79306bf 100644 --- a/pkg/envoy/boilerplate.go +++ b/pkg/envoy/boilerplate.go @@ -400,14 +400,14 @@ func makeListener(filterChains []*listener.FilterChain, envoyListenerIpv4Address return &listener, nil } -func makeAddresses(addresses []string, upstreamPort uint32) []*core.Address { +func makeAddresses(addresses []LBHost, upstreamPort uint32) []*core.Address { envoyAddresses := []*core.Address{} for _, address := range addresses { envoyAddress := &core.Address{ Address: &core.Address_SocketAddress{ SocketAddress: &core.SocketAddress{ - Address: address, + Address: address.Host, PortSpecifier: &core.SocketAddress_PortValue{ PortValue: upstreamPort, }, @@ -475,7 +475,8 @@ func makeCluster(c cluster, ca string, healthCfg UpstreamHealthCheck, outlierPer for idx, address := range addresses { endpoints[idx] = &endpoint.LbEndpoint{ - HostIdentifier: &endpoint.LbEndpoint_Endpoint{Endpoint: &endpoint.Endpoint{Address: address}}, + HostIdentifier: &endpoint.LbEndpoint_Endpoint{Endpoint: &endpoint.Endpoint{Address: address}}, + LoadBalancingWeight: &wrappers.UInt32Value{Value: c.Hosts[idx].Weight}, } } diff --git a/pkg/envoy/ingress_translator.go b/pkg/envoy/ingress_translator.go index fca04dc1..d3bf5551 100644 --- a/pkg/envoy/ingress_translator.go +++ b/pkg/envoy/ingress_translator.go @@ -7,6 +7,7 @@ import ( "fmt" "regexp" "sort" + "strconv" "strings" "time" @@ -90,12 +91,17 @@ func (v *virtualHost) Equals(other *virtualHost) bool { v.RetryOn == other.RetryOn } +type LBHost struct { + Host string + Weight uint32 +} + type cluster struct { Name string VirtualHost string HealthCheckPath string Timeout time.Duration - Hosts []string + Hosts []LBHost } func (c *cluster) identity() string { @@ -127,8 +133,12 @@ func (c *cluster) Equals(other *cluster) bool { return false } - sort.Strings(c.Hosts) - sort.Strings(other.Hosts) + sort.Slice(c.Hosts[:], func(i, j int) bool { + return c.Hosts[i].Host < c.Hosts[j].Host + }) + sort.Slice(other.Hosts[:], func(i, j int) bool { + return other.Hosts[i].Host < other.Hosts[j].Host + }) for i, host := range c.Hosts { if host != other.Hosts[i] { @@ -197,15 +207,15 @@ func newEnvoyIngress(host string) *envoyIngress { cluster: &cluster{ Name: clusterName, VirtualHost: host, - Hosts: []string{}, + Hosts: []LBHost{}, Timeout: (30 * time.Second), HealthCheckPath: "", }, } } -func (ing *envoyIngress) addUpstream(host string) { - ing.cluster.Hosts = append(ing.cluster.Hosts, host) +func (ing *envoyIngress) addUpstream(host string, weight uint32) { + ing.cluster.Hosts = append(ing.cluster.Hosts, LBHost{host, weight}) } func (ing *envoyIngress) addHealthCheckPath(path string) { @@ -311,7 +321,14 @@ func translateIngresses(ingresses []*k8s.Ingress, syncSecrets bool, secrets []*v } envoyIngress := envoyIngresses[ruleHost] - envoyIngress.addUpstream(j) + + if weight64, err := strconv.ParseUint(i.Annotations["yggdrasil.uswitch.com/weight"], 10, 32); err == nil { + if weight64 != 0 { + envoyIngress.addUpstream(j, uint32(weight64)) + } + } else { + envoyIngress.addUpstream(j, 1) + } if i.Annotations["yggdrasil.uswitch.com/healthcheck-path"] != "" { envoyIngress.addHealthCheckPath(i.Annotations["yggdrasil.uswitch.com/healthcheck-path"]) diff --git a/pkg/envoy/ingress_translator_test.go b/pkg/envoy/ingress_translator_test.go index b9042206..a2d8a6e8 100644 --- a/pkg/envoy/ingress_translator_test.go +++ b/pkg/envoy/ingress_translator_test.go @@ -131,8 +131,8 @@ func TestVirtualHostEquality(t *testing.T) { } func TestClusterEquality(t *testing.T) { - a := &cluster{Name: "foo", Hosts: []string{"host1", "host2"}} - b := &cluster{Name: "foo", Hosts: []string{"host1", "host2"}} + a := &cluster{Name: "foo", Hosts: []LBHost{{"host1", 1}, {"host2", 1}}} + b := &cluster{Name: "foo", Hosts: []LBHost{{"host1", 1}, {"host2", 1}}} if !a.Equals(b) { t.Error() @@ -142,17 +142,17 @@ func TestClusterEquality(t *testing.T) { t.Error("cluster is equals nil, expect not to be equal") } - c := &cluster{Name: "bar", Hosts: []string{"host1", "host2"}} + c := &cluster{Name: "bar", Hosts: []LBHost{{"host1", 1}, {"host2", 1}}} if a.Equals(c) { t.Error("clusters have different names, expected not to be equal") } - d := &cluster{Name: "foo", Hosts: []string{"host1"}} // missing host2 + d := &cluster{Name: "foo", Hosts: []LBHost{{"host1", 1}}} // missing host2 if a.Equals(d) { t.Error("clusters have different hosts, should be different") } - e := &cluster{Name: "foo", Hosts: []string{"bad1", "bad2"}} + e := &cluster{Name: "foo", Hosts: []LBHost{{"bad1", 1}, {"bad2", 1}}} if a.Equals(e) { t.Error("cluster hosts are different, shouldn't be equal") } @@ -162,7 +162,7 @@ func TestClusterEquality(t *testing.T) { t.Error("no hosts set") } - g := &cluster{Name: "foo", Hosts: []string{"host1", "host2"}, Timeout: (5 * time.Second)} + g := &cluster{Name: "foo", Hosts: []LBHost{{"host1", 1}, {"host2", 1}}, Timeout: (5 * time.Second)} if a.Equals(g) { t.Error("clusters with different timeout values should not be equal") } @@ -268,8 +268,12 @@ func TestGeneratesForSingleIngress(t *testing.T) { if c.Clusters[0].Name != "foo_app_com" { t.Errorf("expected cluster to be named after ingress host, was %s", c.Clusters[0].Name) } - if c.Clusters[0].Hosts[0] != "foo.cluster.com" { - t.Errorf("expected cluster host for foo.cluster.com, was %s", c.Clusters[0].Hosts[0]) + if c.Clusters[0].Hosts[0].Host != "foo.cluster.com" { + t.Errorf("expected cluster host for foo.cluster.com, was %s", c.Clusters[0].Hosts[0].Host) + } + + if c.Clusters[0].Hosts[0].Weight != 1 { + t.Errorf("expected cluster host's weight for 1, was %s", c.Clusters[0].Hosts[0].Weight) } if c.VirtualHosts[0].UpstreamCluster != c.Clusters[0].Name { @@ -304,10 +308,10 @@ func TestGeneratesForMultipleIngressSharingSpecHost(t *testing.T) { if len(c.Clusters[0].Hosts) != 2 { t.Errorf("expected 2 host, was %d", len(c.Clusters[0].Hosts)) } - if c.Clusters[0].Hosts[0] != "foo.com" { + if c.Clusters[0].Hosts[0].Host != "foo.com" { t.Errorf("expected cluster host for foo.com, was %s", c.Clusters[0].Hosts[0]) } - if c.Clusters[0].Hosts[1] != "bar.com" { + if c.Clusters[0].Hosts[1].Host != "bar.com" { t.Errorf("expected cluster host for bar.com, was %s", c.Clusters[0].Hosts[1]) } @@ -340,7 +344,7 @@ func TestFilterNonMatchingIngresses(t *testing.T) { func TestIngressWithIP(t *testing.T) { ingress := newIngressIP("app.com", "127.0.0.1") c := translateIngresses([]*k8s.Ingress{ingress}, false, []*v1.Secret{}) - if c.Clusters[0].Hosts[0] != "127.0.0.1" { + if c.Clusters[0].Hosts[0].Host != "127.0.0.1" { t.Errorf("expected cluster host to be IP address, was %s", c.Clusters[0].Hosts[0]) } } From 52ab4f4bd1f4b114f2f669164cbb125063e98481 Mon Sep 17 00:00:00 2001 From: Laurent Marchaud Date: Thu, 4 Jul 2024 19:33:00 +0200 Subject: [PATCH 3/3] Fix tests Signed-off-by: Laurent Marchaud --- pkg/envoy/ingress_translator_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/envoy/ingress_translator_test.go b/pkg/envoy/ingress_translator_test.go index a2d8a6e8..53e3094d 100644 --- a/pkg/envoy/ingress_translator_test.go +++ b/pkg/envoy/ingress_translator_test.go @@ -273,7 +273,7 @@ func TestGeneratesForSingleIngress(t *testing.T) { } if c.Clusters[0].Hosts[0].Weight != 1 { - t.Errorf("expected cluster host's weight for 1, was %s", c.Clusters[0].Hosts[0].Weight) + t.Errorf("expected cluster host's weight for 1, was %d", c.Clusters[0].Hosts[0].Weight) } if c.VirtualHosts[0].UpstreamCluster != c.Clusters[0].Name { @@ -309,10 +309,10 @@ func TestGeneratesForMultipleIngressSharingSpecHost(t *testing.T) { t.Errorf("expected 2 host, was %d", len(c.Clusters[0].Hosts)) } if c.Clusters[0].Hosts[0].Host != "foo.com" { - t.Errorf("expected cluster host for foo.com, was %s", c.Clusters[0].Hosts[0]) + t.Errorf("expected cluster host for foo.com, was %s", c.Clusters[0].Hosts[0].Host) } if c.Clusters[0].Hosts[1].Host != "bar.com" { - t.Errorf("expected cluster host for bar.com, was %s", c.Clusters[0].Hosts[1]) + t.Errorf("expected cluster host for bar.com, was %s", c.Clusters[0].Hosts[1].Host) } if c.VirtualHosts[0].UpstreamCluster != c.Clusters[0].Name { @@ -345,7 +345,7 @@ func TestIngressWithIP(t *testing.T) { ingress := newIngressIP("app.com", "127.0.0.1") c := translateIngresses([]*k8s.Ingress{ingress}, false, []*v1.Secret{}) if c.Clusters[0].Hosts[0].Host != "127.0.0.1" { - t.Errorf("expected cluster host to be IP address, was %s", c.Clusters[0].Hosts[0]) + t.Errorf("expected cluster host to be IP address, was %s", c.Clusters[0].Hosts[0].Host) } }