From 66dc4c5ef4e76646b9bd803bc75284312d4b3e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20L=C3=B3pez=20Ruiz?= Date: Fri, 20 Sep 2024 12:56:39 +0200 Subject: [PATCH] Put srv in backup is not in zone --- pkg/service/endpoints.go | 5 +++ pkg/zone/zone.go | 76 ++++++++++++++++++++++++++++++++++++++++ pkg/zone/zone_test.go | 66 ++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 pkg/zone/zone.go create mode 100644 pkg/zone/zone_test.go diff --git a/pkg/service/endpoints.go b/pkg/service/endpoints.go index 180f692a..087f54aa 100644 --- a/pkg/service/endpoints.go +++ b/pkg/service/endpoints.go @@ -23,6 +23,7 @@ import ( "github.com/haproxytech/kubernetes-ingress/pkg/annotations" "github.com/haproxytech/kubernetes-ingress/pkg/haproxy/api" "github.com/haproxytech/kubernetes-ingress/pkg/store" + "github.com/haproxytech/kubernetes-ingress/pkg/zone" ) // HandleHAProxySrvs handles the haproxy backend servers of the corresponding IngressPath (service + port) @@ -69,6 +70,10 @@ func (s *Service) updateHAProxySrv(client api.HAProxyClient, srvSlot store.HAPro if srvSlot.Address != "" { srv.Address = srvSlot.Address srv.Maintenance = "disabled" + // Put in backup if cross zone traffic is disabled + if zone.IsBackupEnabledForThisIP(srv.Address) { + srv.Backup = "enabled" + } } logger.Tracef("[CONFIG] [BACKEND] [SERVER] backend %s: about to update server in configuration file : models.Server { Name: %s, Port: %d, Address: %s, Maintenance: %s }", s.backend.Name, srv.Name, *srv.Port, srv.Address, srv.Maintenance) // Update server diff --git a/pkg/zone/zone.go b/pkg/zone/zone.go new file mode 100644 index 00000000..9cd8cc29 --- /dev/null +++ b/pkg/zone/zone.go @@ -0,0 +1,76 @@ +package zone + +import ( + "fmt" + "net" + "os" + "strings" +) + +var controllerIP string +var controllerZone string +var zonesInfo string +var zonesCrossEnabled bool = true + +func init() { + controllerIP = os.Getenv("POD_IP") + zonesInfo = os.Getenv("ZONES_INFO") + zonesCrossEnabled = !(strings.ToLower(os.Getenv("ZONES_CROSS_TRAFFIC_ENABLED")) == "false") + + if controllerIP == "" || zonesInfo == "" { + zonesCrossEnabled = true + return + } + + controllerZone = getZoneFromIP(controllerIP, zonesInfo) +} + +// This function is used when haproxy configure a server in backend +func IsBackupEnabledForThisIP(address string) bool { + if zonesCrossEnabled || address == "" || address == "127.0.0.1" { + return false + } + srvZone := getZoneFromIP(address, zonesInfo) + return srvZone != controllerZone +} + +// Private; Auxiliary functions +func getZoneFromIP(address string, zoneSubnets string) string { + input := strings.ReplaceAll(zoneSubnets, " ", "") + subnetGroups := strings.Split(input, ";") + + for _, group := range subnetGroups { + parts := strings.Split(group, ":") + if len(parts) != 2 || parts[1] == "" { + return "unknown" + } + + zoneName := parts[0] + subnets := strings.Split(parts[1], ",") + + for _, subnet := range subnets { + if ok, _ := isIPInSubnet(address, subnet); ok { + return zoneName + } + } + } + + return "unknown" +} + +func isIPInSubnet(ipStr, subnetStr string) (bool, error) { + // Parseamos la IP + ip := net.ParseIP(ipStr) + if ip == nil { + return false, fmt.Errorf("IP inválida: %s", ipStr) + } + + // Parseamos la subnet (dirección IP + máscara) + _, subnet, err := net.ParseCIDR(subnetStr) + if err != nil { + return false, fmt.Errorf("subred inválida: %s", subnetStr) + } + + // Comprobamos si la IP está dentro de la subred + return subnet.Contains(ip), nil +} diff --git a/pkg/zone/zone_test.go b/pkg/zone/zone_test.go new file mode 100644 index 00000000..0359d54f --- /dev/null +++ b/pkg/zone/zone_test.go @@ -0,0 +1,66 @@ +package zone + +import ( + "testing" +) + +func TestGetZoneFromIp(t *testing.T) { + tests := []struct { + name string + address string + zoneSubnets string + expected string + }{ + { + name: "IP belongs to eu-south-2a", + address: "192.168.2.1", + zoneSubnets: "eu-south-2a: 192.168.1.0/24, 192.168.2.0/24; eu-south-2b: 192.168.3.0/24", + expected: "eu-south-2a", + }, + { + name: "IP belongs to eu-south-2b", + address: "192.168.3.10", + zoneSubnets: "eu-south-2a: 192.168.1.0/24, 192.168.2.0/24; eu-south-2b: 192.168.3.0/24", + expected: "eu-south-2b", + }, + { + name: "IP not in any subnet", + address: "10.0.0.1", + zoneSubnets: "eu-south-2a: 192.168.1.0/24, 192.168.2.0/24; eu-south-2b: 192.168.3.0/24", + expected: "unknown", + }, + { + name: "Malformed input - no subnet", + address: "192.168.2.1", + zoneSubnets: "eu-south-2a:;eu-south-2b:192.168.2.0/24", + expected: "unknown", + }, + { + name: "Malformed input - no subnet", + address: "192.168.2.1", + zoneSubnets: "eu-south-2a;eu-south-2b:192.168.2.0/24", + expected: "unknown", + }, + { + name: "IP belongs to overlapping subnets, first match", + address: "192.167.4.10", + zoneSubnets: "eu-south-2a:192.167.4.0/24;eu-south-2b:192.167.4.0/16", + expected: "eu-south-2a", + }, + { + name: "Empty Data", + address: "192.167.4.10", + zoneSubnets: " ", + expected: "unknown", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getZoneFromIP(tt.address, tt.zoneSubnets) + if result != tt.expected { + t.Errorf("expected %s, got %s", tt.expected, result) + } + }) + } +}