pkg/appgw/backendaddresspools.go (150 lines of code) (raw):

// ------------------------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // -------------------------------------------------------------------------------------------- package appgw import ( "fmt" "sort" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/brownfield" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/events" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/sorter" n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network" "github.com/Azure/go-autorest/autorest/to" v1 "k8s.io/api/core/v1" "k8s.io/klog/v2" ) func (c *appGwConfigBuilder) BackendAddressPools(cbCtx *ConfigBuilderContext) error { pools := c.getPools(cbCtx) if pools != nil { sort.Sort(sorter.ByBackendPoolName(pools)) } c.appGw.BackendAddressPools = &pools return nil } func (c appGwConfigBuilder) getPools(cbCtx *ConfigBuilderContext) []n.ApplicationGatewayBackendAddressPool { if c.mem.pools != nil { return *c.mem.pools } defaultPool := defaultBackendAddressPool(c.appGwIdentifier) klog.V(3).Infof("Created default backend pool %s", *defaultPool.Name) managedPoolsByName := map[string]*n.ApplicationGatewayBackendAddressPool{ *defaultPool.Name: &defaultPool, } _, _, serviceBackendPairMap, err := c.getBackendsAndSettingsMap(cbCtx) if err != nil { klog.Error("Error fetching Backends and Settings: ", err) } for backendID, serviceBackendPair := range serviceBackendPairMap { if pool := c.getBackendAddressPool(backendID, serviceBackendPair, managedPoolsByName); pool != nil { managedPoolsByName[*pool.Name] = pool klog.V(3).Infof("Created backend pool %s for service %s", *pool.Name, backendID.serviceKey()) } } if cbCtx.EnvVariables.EnableIstioIntegration { _, _, istioServiceBackendPairMap, _ := c.getIstioDestinationsAndSettingsMap(cbCtx) for destinationID, serviceBackendPair := range istioServiceBackendPairMap { if pool := c.getIstioBackendAddressPool(destinationID, serviceBackendPair, managedPoolsByName); pool != nil { managedPoolsByName[*pool.Name] = pool klog.V(3).Infof("Created backend pool %s for service %s", *pool.Name, destinationID.serviceKey()) } } } var agicCreatedPools []n.ApplicationGatewayBackendAddressPool for _, managedPool := range managedPoolsByName { agicCreatedPools = append(agicCreatedPools, *managedPool) } if cbCtx.EnvVariables.EnableBrownfieldDeployment { er := brownfield.NewExistingResources(c.appGw, cbCtx.ProhibitedTargets, &defaultPool) // Split the existing pools we obtained from App Gateway into ones AGIC is and is not allowed to change. existingBlacklisted, existingNonBlacklisted := er.GetBlacklistedPools() brownfield.LogPools(existingBlacklisted, existingNonBlacklisted, agicCreatedPools) // MergePools would produce unique list of pools based on Name. Blacklisted pools, which have the same name // as a managed pool would be overwritten. return brownfield.MergePools(existingBlacklisted, agicCreatedPools) } c.mem.pools = &agicCreatedPools return agicCreatedPools } func (c *appGwConfigBuilder) newBackendPoolMap(cbCtx *ConfigBuilderContext) map[backendIdentifier]*n.ApplicationGatewayBackendAddressPool { defaultPool := defaultBackendAddressPool(c.appGwIdentifier) addressPools := map[string]*n.ApplicationGatewayBackendAddressPool{ *defaultPool.Name: &defaultPool, } backendPoolMap := make(map[backendIdentifier]*n.ApplicationGatewayBackendAddressPool) _, _, serviceBackendPairMap, _ := c.getBackendsAndSettingsMap(cbCtx) for backendID, serviceBackendPair := range serviceBackendPairMap { backendPoolMap[backendID] = &defaultPool if pool := c.getBackendAddressPool(backendID, serviceBackendPair, addressPools); pool != nil { backendPoolMap[backendID] = pool } } return backendPoolMap } func (c *appGwConfigBuilder) getBackendAddressPool(backendID backendIdentifier, serviceBackendPair serviceBackendPortPair, addressPools map[string]*n.ApplicationGatewayBackendAddressPool) *n.ApplicationGatewayBackendAddressPool { endpoints, err := c.k8sContext.GetEndpointsByService(backendID.serviceKey()) if err != nil { klog.Errorf(err.Error()) c.recorder.Event(backendID.Ingress, v1.EventTypeWarning, events.ReasonEndpointsEmpty, err.Error()) return nil } for _, subset := range endpoints.Subsets { if _, portExists := getUniqueTCPPorts(subset)[serviceBackendPair.BackendPort]; portExists { poolName := generateAddressPoolName(backendID.serviceFullName(), serviceBackendPortToStr(backendID.Backend.Service.Port), serviceBackendPair.BackendPort) // The same service might be referenced in multiple ingress resources, this might result in multiple `serviceBackendPairMap` having the same service key but different // ingress resource. Thus, while generating the backend address pool, we should make sure that we are generating unique backend address pools. if pool, ok := addressPools[poolName]; ok { return pool } return c.newPool(poolName, subset) } logLine := fmt.Sprintf("Backend target port %d does not have matching endpoint port", serviceBackendPair.BackendPort) klog.Error(logLine) c.recorder.Event(backendID.Ingress, v1.EventTypeWarning, events.ReasonBackendPortTargetMatch, logLine) } return nil } func getUniqueTCPPorts(subset v1.EndpointSubset) map[Port]interface{} { ports := make(map[Port]interface{}) for _, endpointsPort := range subset.Ports { if endpointsPort.Protocol == v1.ProtocolTCP { ports[Port(endpointsPort.Port)] = nil } } return ports } func (c *appGwConfigBuilder) newPool(poolName string, subset v1.EndpointSubset) *n.ApplicationGatewayBackendAddressPool { return &n.ApplicationGatewayBackendAddressPool{ Etag: to.StringPtr("*"), Name: &poolName, ID: to.StringPtr(c.appGwIdentifier.AddressPoolID(poolName)), ApplicationGatewayBackendAddressPoolPropertiesFormat: &n.ApplicationGatewayBackendAddressPoolPropertiesFormat{ BackendAddresses: getAddressesForSubset(subset), }, } } func getAddressesForSubset(subset v1.EndpointSubset) *[]n.ApplicationGatewayBackendAddress { // We make separate maps for IP and FQDN to ensure uniqueness within the 2 groups // We cannot use ApplicationGatewayBackendAddress as it contains pointer to strings and the same IP string // at a different address would be 2 unique keys. addrSet := make(map[n.ApplicationGatewayBackendAddress]interface{}) ips := make(map[string]interface{}) fqdns := make(map[string]interface{}) for _, address := range subset.Addresses { // prefer IP address if len(address.IP) != 0 { // address specified by ip ips[address.IP] = nil } else if len(address.Hostname) != 0 { // address specified by hostname fqdns[address.Hostname] = nil } } for _, address := range subset.NotReadyAddresses { // prefer IP address if len(address.IP) != 0 { // address specified by ip ips[address.IP] = nil } else if len(address.Hostname) != 0 { // address specified by hostname fqdns[address.Hostname] = nil } } for ip := range ips { addrSet[n.ApplicationGatewayBackendAddress{IPAddress: to.StringPtr(ip)}] = nil } for fqdn := range fqdns { addrSet[n.ApplicationGatewayBackendAddress{Fqdn: to.StringPtr(fqdn)}] = nil } return getBackendAddressMapKeys(&addrSet) } func getBackendAddressMapKeys(m *map[n.ApplicationGatewayBackendAddress]interface{}) *[]n.ApplicationGatewayBackendAddress { var addresses []n.ApplicationGatewayBackendAddress for addr := range *m { addresses = append(addresses, addr) } sort.Sort(sorter.ByIPFQDN(addresses)) return &addresses }