pkg/appgw/istio_settings.go (160 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" n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network" "github.com/Azure/go-autorest/autorest/to" "github.com/knative/pkg/apis/istio/v1alpha3" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/klog/v2" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/controllererrors" ) func istioMatchDestinationIds(cbCtx *ConfigBuilderContext) ([]istioMatchIdentifier, map[istioDestinationIdentifier]interface{}) { matchIDs := make([]istioMatchIdentifier, 0) destinationIDs := make(map[istioDestinationIdentifier]interface{}) for _, virtualService := range cbCtx.IstioVirtualServices { for _, rule := range virtualService.Spec.HTTP { destinations := make([]*v1alpha3.Destination, 0) for _, routeDestination := range rule.Route { if routeDestination.Weight != 0 { destinations = append(destinations, &routeDestination.Destination) /* TODO(rhea): Weights are being ignored for now, since this is not yet supported on App Gateway. Include gates from routeDestination when this is supported */ } destinationID := generateIstioDestinationID(virtualService, &routeDestination.Destination) destinationIDs[destinationID] = nil } for _, match := range rule.Match { if match.URI == nil { klog.V(3).Infof("Skipped match request, no URI field. Other forms of match requests are not supported.") continue } matchID := generateIstioMatchID(virtualService, &rule, &match, destinations) matchIDs = append(matchIDs, matchID) } } } /* TODO(rhea): Filter out destinations for virtual services referencing non-existent Services */ return matchIDs, destinationIDs } func (c *appGwConfigBuilder) getIstioDestinationsAndSettingsMap(cbCtx *ConfigBuilderContext) ([]n.ApplicationGatewayBackendHTTPSettings, map[istioDestinationIdentifier]*n.ApplicationGatewayBackendHTTPSettings, map[istioDestinationIdentifier]serviceBackendPortPair, error) { serviceBackendPairsMap := make(map[istioDestinationIdentifier]map[serviceBackendPortPair]interface{}) backendHTTPSettingsMap := make(map[istioDestinationIdentifier]*n.ApplicationGatewayBackendHTTPSettings) finalServiceBackendPairMap := make(map[istioDestinationIdentifier]serviceBackendPortPair) var unresolvedDestinationID []istioDestinationIdentifier _, destinationIDs := istioMatchDestinationIds(cbCtx) for destinationID := range destinationIDs { resolvedBackendPorts := make(map[serviceBackendPortPair]interface{}) service := c.k8sContext.GetService(destinationID.serviceKey()) destinationPortNum := Port(destinationID.DestinationPort) if service == nil { // Once services are filtered in the istioMatchDestinationIDs function, this should never happen logLine := fmt.Sprintf("Unable to get the service [%s]", destinationID.serviceKey()) klog.Errorf(logLine) // TODO(rhea): add error event pair := serviceBackendPortPair{ ServicePort: Port(destinationPortNum), BackendPort: Port(destinationPortNum), } resolvedBackendPorts[pair] = nil } else { for _, sp := range service.Spec.Ports { // find the backend port number // check if any service ports matches the specified ports if sp.Protocol != v1.ProtocolTCP { // ignore UDP ports continue } // TODO(delqn): implement correctly port lookup by name if Port(sp.Port) == destinationPortNum || sp.TargetPort.String() == fmt.Sprint(destinationPortNum) { // matched a service port with a port from the service if sp.TargetPort.String() == "" { // targetPort is not defined, by default targetPort == port pair := serviceBackendPortPair{ ServicePort: Port(sp.Port), BackendPort: Port(sp.Port), } resolvedBackendPorts[pair] = nil } else { // target port is defined as name or port number if sp.TargetPort.Type == intstr.Int { // port is defined as port number pair := serviceBackendPortPair{ ServicePort: Port(sp.Port), BackendPort: Port(sp.TargetPort.IntVal), } resolvedBackendPorts[pair] = nil } else { // if service port is defined by name, need to resolve targetPortName := sp.TargetPort.StrVal klog.V(1).Infof("resolving port name %s", targetPortName) targetPortsResolved := c.resolveIstioPortName(targetPortName, &destinationID) for targetPort := range targetPortsResolved { pair := serviceBackendPortPair{ ServicePort: Port(sp.Port), BackendPort: Port(targetPort), } resolvedBackendPorts[pair] = nil } } } break } } } if len(resolvedBackendPorts) == 0 { logLine := fmt.Sprintf("Unable to resolve any backend port for service [%s]", destinationID.serviceKey()) klog.Error(logLine) //TODO(rhea): Add error event unresolvedDestinationID = append(unresolvedDestinationID, destinationID) break } // Merge serviceBackendPairsMap[backendID] into resolvedBackendPorts if _, ok := serviceBackendPairsMap[destinationID]; !ok { serviceBackendPairsMap[destinationID] = make(map[serviceBackendPortPair]interface{}) } for portPair := range resolvedBackendPorts { serviceBackendPairsMap[destinationID][portPair] = nil } } if len(unresolvedDestinationID) > 0 { e := controllererrors.NewError( controllererrors.ErrorIstioResolvePortsForServices, "unable to resolve backend port for some services", ) return nil, nil, nil, e } httpSettingsCollection := make(map[string]n.ApplicationGatewayBackendHTTPSettings) for destinationID, serviceBackendPairs := range serviceBackendPairsMap { if len(serviceBackendPairs) > 1 { // more than one possible backend port exposed through ingress backendServicePort := "" if destinationID.DestinationPort != 0 { backendServicePort = fmt.Sprint(destinationID.DestinationPort) } else { // TODO(delqn): implement port lookup by name } //TODO(rhea): add error event recorder e := controllererrors.NewErrorf( controllererrors.ErrorIstioMultipleServiceBackendPortBinding, "service:port [%s:%s] has more than one service-backend port binding", destinationID.serviceKey(), backendServicePort, ) klog.Warning(e.Error()) return nil, nil, nil, e } // At this point there will be only one pair var uniquePair serviceBackendPortPair for k := range serviceBackendPairs { uniquePair = k } finalServiceBackendPairMap[destinationID] = uniquePair httpSettings := c.generateIstioHTTPSettings(destinationID, uniquePair.BackendPort, cbCtx) httpSettingsCollection[*httpSettings.Name] = httpSettings backendHTTPSettingsMap[destinationID] = &httpSettings } httpSettings := make([]n.ApplicationGatewayBackendHTTPSettings, 0, len(httpSettingsCollection)) for _, backend := range httpSettingsCollection { httpSettings = append(httpSettings, backend) } return httpSettings, backendHTTPSettingsMap, finalServiceBackendPairMap, nil } func (c *appGwConfigBuilder) generateIstioHTTPSettings(destinationID istioDestinationIdentifier, port Port, cbCtx *ConfigBuilderContext) n.ApplicationGatewayBackendHTTPSettings { backendServicePort := "" if destinationID.DestinationPort != 0 { backendServicePort = fmt.Sprint(destinationID.DestinationPort) } else { // TODO(delqn): Implement port lookup by name } httpSettingsName := generateHTTPSettingsName(destinationID.serviceFullName(), backendServicePort, port, destinationID.istioVirtualServiceIdentifier.Name) klog.V(3).Infof("Created a new HTTP setting w/ name: %s\n", httpSettingsName) httpSettings := n.ApplicationGatewayBackendHTTPSettings{ Etag: to.StringPtr("*"), Name: &httpSettingsName, ID: to.StringPtr(c.appGwIdentifier.HTTPSettingsID(httpSettingsName)), ApplicationGatewayBackendHTTPSettingsPropertiesFormat: &n.ApplicationGatewayBackendHTTPSettingsPropertiesFormat{ Protocol: n.ApplicationGatewayProtocolHTTP, Port: to.Int32Ptr(int32(port)), }, } //TODO(rhea): check relevant annotations and modify http settings accordingly return httpSettings }