pkg/appgw/requestroutingrules.go (471 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"
"strings"
n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network"
"github.com/Azure/go-autorest/autorest/to"
networking "k8s.io/api/networking/v1"
"k8s.io/klog/v2"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/brownfield"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/controllererrors"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/sorter"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/utils"
)
const (
MaxAllowedPriority = 20000
)
func (c *appGwConfigBuilder) RequestRoutingRules(cbCtx *ConfigBuilderContext) error {
requestRoutingRules, pathMaps := c.getRules(cbCtx)
if cbCtx.EnvVariables.EnableBrownfieldDeployment {
c.CleanUpPathRulesAddedByAGIC()
rCtx := brownfield.NewExistingResources(c.appGw, cbCtx.ProhibitedTargets, nil)
{
// PathMaps we obtained from App Gateway - we segment them into ones AGIC is and is not allowed to change.
existingBlacklisted, existingNonBlacklisted := rCtx.GetBlacklistedPathMaps()
brownfield.LogPathMaps(existingBlacklisted, existingNonBlacklisted, pathMaps)
// MergePathMaps would produce unique list of routing rules based on Name. Routing rules, which have the same name
// as a managed rule would be overwritten.
pathMaps = brownfield.MergePathMaps(existingBlacklisted, pathMaps)
}
}
sort.Sort(sorter.ByPathMap(pathMaps))
c.appGw.URLPathMaps = &pathMaps
if cbCtx.EnvVariables.EnableBrownfieldDeployment {
rCtx := brownfield.NewExistingResources(c.appGw, cbCtx.ProhibitedTargets, nil)
{
// RoutingRules we obtained from App Gateway - we segment them into ones AGIC is and is not allowed to change.
existingBlacklisted, existingNonBlacklisted := rCtx.GetBlacklistedRoutingRules()
brownfield.LogRules(existingBlacklisted, existingNonBlacklisted, requestRoutingRules)
// MergeRules would produce unique list of routing rules based on Name. Routing rules, which have the same name
// as a managed rule would be overwritten.
requestRoutingRules = brownfield.MergeRules(&c.appGw, existingBlacklisted, requestRoutingRules)
}
}
sort.Sort(sorter.ByRequestRoutingRuleName(requestRoutingRules))
// Apply rule priority after sorting to come up with stable priority.
requestRoutingRules = c.assignPriorityWhereMissing(requestRoutingRules)
c.appGw.RequestRoutingRules = &requestRoutingRules
return nil
}
func (c *appGwConfigBuilder) getRules(cbCtx *ConfigBuilderContext) ([]n.ApplicationGatewayRequestRoutingRule, []n.ApplicationGatewayURLPathMap) {
if c.mem.routingRules != nil && c.mem.pathMaps != nil {
return *c.mem.routingRules, *c.mem.pathMaps
}
httpListenersMap := c.groupListenersByListenerIdentifier(cbCtx)
pathMap := []n.ApplicationGatewayURLPathMap{}
var requestRoutingRules []n.ApplicationGatewayRequestRoutingRule
urlPathMaps := c.getPathMaps(cbCtx)
priorities := c.getListenerPriorities(cbCtx)
for listenerID, urlPathMap := range urlPathMaps {
routingRuleName := generateRequestRoutingRuleName(listenerID)
httpListener, exists := httpListenersMap[listenerID]
if !exists {
klog.Errorf("Routing rule %s will not be created; listener %+v does not exist", routingRuleName, listenerID)
continue
}
rule := n.ApplicationGatewayRequestRoutingRule{
Etag: to.StringPtr("*"),
Name: to.StringPtr(routingRuleName),
ID: to.StringPtr(c.appGwIdentifier.requestRoutingRuleID(routingRuleName)),
ApplicationGatewayRequestRoutingRulePropertiesFormat: &n.ApplicationGatewayRequestRoutingRulePropertiesFormat{
HTTPListener: &n.SubResource{ID: to.StringPtr(c.appGwIdentifier.listenerID(*httpListener.Name))},
},
}
if urlPathMap.PathRules == nil || len(*urlPathMap.PathRules) == 0 {
// Basic Rule, because we have no path-based rule
rule.RuleType = n.ApplicationGatewayRequestRoutingRuleTypeBasic
rule.RedirectConfiguration = urlPathMap.DefaultRedirectConfiguration
// We setup the default backend address pools and default backend HTTP settings only if
// this rule does not have an `ssl-redirect` configuration.
if rule.RedirectConfiguration == nil {
rule.BackendAddressPool = urlPathMap.DefaultBackendAddressPool
rule.BackendHTTPSettings = urlPathMap.DefaultBackendHTTPSettings
} else {
rule.BackendAddressPool = nil
rule.BackendHTTPSettings = nil
}
rule.RewriteRuleSet = urlPathMap.DefaultRewriteRuleSet
} else {
// Path-based Rule
rule.RuleType = n.ApplicationGatewayRequestRoutingRuleTypePathBasedRouting
rule.URLPathMap = &n.SubResource{ID: to.StringPtr(c.appGwIdentifier.urlPathMapID(*urlPathMap.Name))}
pathMap = append(pathMap, *urlPathMap)
}
if rule.RuleType == n.ApplicationGatewayRequestRoutingRuleTypePathBasedRouting {
klog.V(3).Infof("Bound path-based rule: %s to listener: %s (%s, %d) and url path map %s", *rule.Name, *httpListener.Name, listenerID.HostNames, listenerID.FrontendPort, utils.GetLastChunkOfSlashed(*rule.URLPathMap.ID))
} else {
if rule.RedirectConfiguration != nil {
klog.V(3).Infof("Bound basic rule: %s to listener: %s (%s, %d) and redirect configuration %s", *rule.Name, *httpListener.Name, listenerID.HostNames, listenerID.FrontendPort, utils.GetLastChunkOfSlashed(*rule.RedirectConfiguration.ID))
} else {
klog.V(3).Infof("Bound basic rule: %s to listener: %s (%s, %d) for backend pool %s and backend http settings %s", *rule.Name, *httpListener.Name, listenerID.HostNames, listenerID.FrontendPort, utils.GetLastChunkOfSlashed(*rule.BackendAddressPool.ID), utils.GetLastChunkOfSlashed(*rule.BackendHTTPSettings.ID))
}
}
rule.Priority = priorities[listenerID]
requestRoutingRules = append(requestRoutingRules, rule)
}
c.mem.routingRules = &requestRoutingRules
c.mem.pathMaps = &pathMap
return requestRoutingRules, pathMap
}
func (c *appGwConfigBuilder) noRulesIngress(cbCtx *ConfigBuilderContext, ingress *networking.Ingress, urlPathMaps *map[listenerIdentifier]*n.ApplicationGatewayURLPathMap) {
// There are no Rules. We are dealing with some very rudimentary Ingress definition.
if ingress.Spec.DefaultBackend == nil {
return
}
backendID := generateBackendID(ingress, nil, nil, ingress.Spec.DefaultBackend)
_, _, serviceBackendPairMap, err := c.getBackendsAndSettingsMap(cbCtx)
if err != nil {
klog.Error("Error fetching Backends and Settings: ", err)
}
if serviceBackendPair, exists := serviceBackendPairMap[backendID]; exists {
poolName := generateAddressPoolName(backendID.serviceFullName(), serviceBackendPortToStr(backendID.Backend.Service.Port), serviceBackendPair.BackendPort)
defaultAddressPoolID := c.appGwIdentifier.AddressPoolID(poolName)
defaultHTTPSettingsID := c.appGwIdentifier.HTTPSettingsID(DefaultBackendHTTPSettingsName)
listenerID := defaultFrontendListenerIdentifier(c.appGw, cbCtx.EnvVariables)
pathMapName := generateURLPathMapName(listenerID)
(*urlPathMaps)[listenerID] = &n.ApplicationGatewayURLPathMap{
Etag: to.StringPtr("*"),
Name: to.StringPtr(pathMapName),
ID: to.StringPtr(c.appGwIdentifier.urlPathMapID(pathMapName)),
ApplicationGatewayURLPathMapPropertiesFormat: &n.ApplicationGatewayURLPathMapPropertiesFormat{
DefaultBackendAddressPool: &n.SubResource{ID: &defaultAddressPoolID},
DefaultBackendHTTPSettings: &n.SubResource{ID: &defaultHTTPSettingsID},
PathRules: &[]n.ApplicationGatewayPathRule{},
},
}
}
}
func (c *appGwConfigBuilder) getPathMaps(cbCtx *ConfigBuilderContext) map[listenerIdentifier]*n.ApplicationGatewayURLPathMap {
urlPathMaps := make(map[listenerIdentifier]*n.ApplicationGatewayURLPathMap)
for ingressIdx := range cbCtx.IngressList {
ingress := cbCtx.IngressList[ingressIdx]
if len(ingress.Spec.Rules) == 0 {
c.noRulesIngress(cbCtx, ingress, &urlPathMaps)
}
for ruleIdx := range ingress.Spec.Rules {
rule := &ingress.Spec.Rules[ruleIdx]
// skip no http rule
if rule.HTTP == nil {
continue
}
_, azListenerConfig := c.processIngressRuleWithTLS(rule, ingress, cbCtx.EnvVariables)
for listenerID, listenerAzConfig := range azListenerConfig {
if _, exists := urlPathMaps[listenerID]; !exists {
pathMapName := generateURLPathMapName(listenerID)
urlPathMaps[listenerID] = &n.ApplicationGatewayURLPathMap{
Etag: to.StringPtr("*"),
Name: to.StringPtr(pathMapName),
ID: to.StringPtr(c.appGwIdentifier.urlPathMapID(pathMapName)),
ApplicationGatewayURLPathMapPropertiesFormat: &n.ApplicationGatewayURLPathMapPropertiesFormat{
DefaultBackendAddressPool: &n.SubResource{ID: cbCtx.DefaultAddressPoolID},
DefaultBackendHTTPSettings: &n.SubResource{ID: cbCtx.DefaultHTTPSettingsID},
},
}
}
pathMap := c.getPathMap(cbCtx, listenerID, listenerAzConfig, ingress, rule, ruleIdx)
urlPathMaps[listenerID] = c.mergePathMap(urlPathMaps[listenerID], pathMap, cbCtx)
}
}
}
// if no url pathmaps were created, then add a default path map since this will be translated to
// a basic request routing rule which is needed on Application Gateway to avoid validation error.
if len(urlPathMaps) == 0 {
defaultAddressPoolID := c.appGwIdentifier.AddressPoolID(DefaultBackendAddressPoolName)
defaultHTTPSettingsID := c.appGwIdentifier.HTTPSettingsID(DefaultBackendHTTPSettingsName)
listenerID := defaultFrontendListenerIdentifier(c.appGw, cbCtx.EnvVariables)
pathMapName := generateURLPathMapName(listenerID)
urlPathMaps[listenerID] = &n.ApplicationGatewayURLPathMap{
Etag: to.StringPtr("*"),
Name: to.StringPtr(pathMapName),
ID: to.StringPtr(c.appGwIdentifier.urlPathMapID(pathMapName)),
ApplicationGatewayURLPathMapPropertiesFormat: &n.ApplicationGatewayURLPathMapPropertiesFormat{
DefaultBackendAddressPool: &n.SubResource{ID: &defaultAddressPoolID},
DefaultBackendHTTPSettings: &n.SubResource{ID: &defaultHTTPSettingsID},
PathRules: &[]n.ApplicationGatewayPathRule{},
},
}
}
if cbCtx.EnvVariables.EnableIstioIntegration {
for listenerID, pathMap := range c.getIstioPathMaps(cbCtx) {
if _, exists := urlPathMaps[listenerID]; !exists {
urlPathMaps[listenerID] = pathMap
}
}
}
return urlPathMaps
}
func (c *appGwConfigBuilder) getPathMap(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, listenerAzConfig listenerAzConfig, ingress *networking.Ingress, rule *networking.IngressRule, ruleIdx int) *n.ApplicationGatewayURLPathMap {
// initialize a path map for this listener if doesn't exists
pathMapName := generateURLPathMapName(listenerID)
pathMap := n.ApplicationGatewayURLPathMap{
Etag: to.StringPtr("*"),
Name: to.StringPtr(pathMapName),
ID: to.StringPtr(c.appGwIdentifier.urlPathMapID(pathMapName)),
ApplicationGatewayURLPathMapPropertiesFormat: &n.ApplicationGatewayURLPathMapPropertiesFormat{},
}
// get defaults provided by the rules if any
defaultAddressPoolID, defaultHTTPSettingsID, defaultRedirectConfigurationID, defaultRewriteRuleSetID := c.getDefaultFromRule(cbCtx, listenerID, listenerAzConfig, ingress, rule)
if defaultRedirectConfigurationID != nil {
pathMap.DefaultRedirectConfiguration = resourceRef(*defaultRedirectConfigurationID)
pathMap.DefaultBackendAddressPool = nil
pathMap.DefaultBackendHTTPSettings = nil
} else if defaultAddressPoolID != nil && defaultHTTPSettingsID != nil {
pathMap.DefaultBackendAddressPool = resourceRef(*defaultAddressPoolID)
pathMap.DefaultBackendHTTPSettings = resourceRef(*defaultHTTPSettingsID)
}
if defaultRewriteRuleSetID != nil {
pathMap.DefaultRewriteRuleSet = resourceRef(*defaultRewriteRuleSetID)
}
pathMap.PathRules = c.getPathRules(cbCtx, listenerID, listenerAzConfig, ingress, rule, ruleIdx)
return &pathMap
}
func (c *appGwConfigBuilder) getDefaultFromRule(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, listenerAzConfig listenerAzConfig, ingress *networking.Ingress, rule *networking.IngressRule) (*string, *string, *string, *string) {
if sslRedirect, _ := annotations.IsSslRedirect(ingress); sslRedirect && listenerAzConfig.Protocol == n.ApplicationGatewayProtocolHTTP {
targetListener := listenerID
targetListener.FrontendPort = 443
// We could end up in a situation where we are attempting to attach a redirect, which does not exist.
redirectRef := c.getSslRedirectConfigResourceReference(targetListener)
redirectsSet := *c.groupRedirectsByID(c.getRedirectConfigurations(cbCtx))
if _, exists := redirectsSet[*redirectRef.ID]; exists {
klog.V(3).Infof("Attached default redirection %s to rule %+v", *redirectRef.ID, *rule)
return nil, nil, redirectRef.ID, nil
}
klog.Errorf("Will not attach default redirect to rule; SSL Redirect does not exist: %s", *redirectRef.ID)
}
var defRule *networking.IngressRule
var defPath *networking.HTTPIngressPath
defBackend := ingress.Spec.DefaultBackend
for pathIdx := range rule.HTTP.Paths {
path := &rule.HTTP.Paths[pathIdx]
if isPathCatchAll(path.Path, path.PathType) {
defBackend = &path.Backend
defPath = path
defRule = rule
}
}
backendPools := c.newBackendPoolMap(cbCtx)
_, backendHTTPSettingsMap, _, _ := c.getBackendsAndSettingsMap(cbCtx)
var defaultRewriteRuleSet *string
if defBackend != nil {
// has default backend
defaultBackendID := generateBackendID(ingress, defRule, defPath, defBackend)
defaultHTTPSettings := backendHTTPSettingsMap[defaultBackendID]
defaultAddressPool := backendPools[defaultBackendID]
// check both annotations for rewrite-rule-set, use appropriate one, if both are present - throw error
rewriteRuleSet, err1 := annotations.RewriteRuleSet(ingress)
rewriteRuleSetCR, err2 := annotations.RewriteRuleSetCustomResource(ingress)
if err1 == nil && rewriteRuleSet != "" && err2 == nil && rewriteRuleSetCR != "" {
klog.Errorf("%s and %s both annotations are defined. Please use one.", annotations.RewriteRuleSetKey, annotations.RewriteRuleSetCustomResourceKey)
} else if err1 == nil && rewriteRuleSet != "" {
defaultRewriteRuleSet = to.StringPtr(c.appGwIdentifier.rewriteRuleSetID(rewriteRuleSet))
} else if err2 == nil && rewriteRuleSetCR != "" {
rewriteRuleSetCR = fmt.Sprintf("crd-%s-%s", ingress.Namespace, rewriteRuleSetCR)
defaultRewriteRuleSet = to.StringPtr(c.appGwIdentifier.rewriteRuleSetID(rewriteRuleSetCR))
}
if defaultAddressPool != nil && defaultHTTPSettings != nil {
poolID := to.StringPtr(c.appGwIdentifier.AddressPoolID(*defaultAddressPool.Name))
settID := to.StringPtr(c.appGwIdentifier.HTTPSettingsID(*defaultHTTPSettings.Name))
return poolID, settID, nil, defaultRewriteRuleSet
}
}
return cbCtx.DefaultAddressPoolID, cbCtx.DefaultHTTPSettingsID, nil, defaultRewriteRuleSet
}
func (c *appGwConfigBuilder) getPathRules(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, listenerAzConfig listenerAzConfig, ingress *networking.Ingress, rule *networking.IngressRule, ruleIdx int) *[]n.ApplicationGatewayPathRule {
backendPools := c.newBackendPoolMap(cbCtx)
_, backendHTTPSettingsMap, _, _ := c.getBackendsAndSettingsMap(cbCtx)
pathRules := make([]n.ApplicationGatewayPathRule, 0)
for pathIdx := range rule.HTTP.Paths {
path := &rule.HTTP.Paths[pathIdx]
if isPathCatchAll(path.Path, path.PathType) {
continue
}
pathMapName := generateURLPathMapName(listenerID)
pathRuleName := generatePathRuleName(ingress.Namespace, ingress.Name, ruleIdx, pathIdx)
pathRule := n.ApplicationGatewayPathRule{
Etag: to.StringPtr("*"),
Name: to.StringPtr(pathRuleName),
ID: to.StringPtr(c.appGwIdentifier.pathRuleID(pathMapName, pathRuleName)),
ApplicationGatewayPathRulePropertiesFormat: &n.ApplicationGatewayPathRulePropertiesFormat{
Paths: &[]string{preparePathFromPathType(path.Path, path.PathType)},
},
}
if wafPolicy, err := annotations.WAFPolicy(ingress); err == nil {
pathRule.FirewallPolicy = &n.SubResource{ID: to.StringPtr(string(wafPolicy))}
var paths string
if pathRule.Paths != nil {
paths = strings.Join(*pathRule.Paths, ",")
}
klog.V(3).Infof("Attach Firewall Policy %s to Path Rule %s", wafPolicy, paths)
}
// check both annotations for rewrite-rule-set, use appropriate one, if both are present - throw error
rewriteRuleSet, err1 := annotations.RewriteRuleSet(ingress)
rewriteRuleSetCR, err2 := annotations.RewriteRuleSetCustomResource(ingress)
if err1 == nil && rewriteRuleSet != "" && err2 == nil && rewriteRuleSetCR != "" {
klog.Errorf("%s and %s both annotations are defined. Please use one.", annotations.RewriteRuleSetKey, annotations.RewriteRuleSetCustomResourceKey)
} else if err1 == nil && rewriteRuleSet != "" {
pathRule.RewriteRuleSet = resourceRef(c.appGwIdentifier.rewriteRuleSetID(rewriteRuleSet))
var paths string
if pathRule.Paths != nil {
paths = strings.Join(*pathRule.Paths, ",")
}
klog.V(3).Infof("Attach Rewrite Rule Set %s to Path Rule %s", rewriteRuleSet, paths)
} else if err2 == nil && rewriteRuleSetCR != "" {
rewriteRuleSetCR = fmt.Sprintf("crd-%s-%s", ingress.Namespace, rewriteRuleSetCR)
pathRule.RewriteRuleSet = resourceRef(c.appGwIdentifier.rewriteRuleSetID(rewriteRuleSetCR))
var paths string
if pathRule.Paths != nil {
paths = strings.Join(*pathRule.Paths, ",")
}
klog.V(3).Infof("Attach Rewrite Rule Set %s to Path Rule %s", rewriteRuleSetCR, paths)
}
if sslRedirect, _ := annotations.IsSslRedirect(ingress); sslRedirect && listenerAzConfig.Protocol == n.ApplicationGatewayProtocolHTTP {
targetListener := listenerID
targetListener.FrontendPort = 443
// We could end up in a situation where we are attempting to attach a redirect, which does not exist.
redirectRef := c.getSslRedirectConfigResourceReference(targetListener)
redirectsSet := *c.groupRedirectsByID(c.getRedirectConfigurations(cbCtx))
if _, exists := redirectsSet[*redirectRef.ID]; exists {
// This Path Rule has a SSL Redirect!
// Add it and move on to the next Path Rule; No need to attach Backend Pools and Settings
pathRule.RedirectConfiguration = redirectRef
klog.V(3).Infof("Attached redirection %s to path rule: %s", *redirectRef.ID, *pathRule.Name)
pathRules = append(pathRules, pathRule)
continue
} else {
klog.Errorf("Will not attach redirect to rule; SSL Redirect does not exist: %s", *redirectRef.ID)
}
}
backendID := generateBackendID(ingress, rule, path, &path.Backend)
backendPool := backendPools[backendID]
backendHTTPSettings := backendHTTPSettingsMap[backendID]
if backendPool == nil || backendHTTPSettings == nil {
continue
}
pathRule.BackendAddressPool = &n.SubResource{ID: backendPool.ID}
pathRule.BackendHTTPSettings = &n.SubResource{ID: backendHTTPSettings.ID}
klog.V(3).Infof("Attached pool %s and http setting %s to path rule: %s", *backendPool.Name, *backendHTTPSettings.Name, *pathRule.Name)
pathRules = append(pathRules, pathRule)
}
return &pathRules
}
func (c *appGwConfigBuilder) mergePathMap(existingPathMap *n.ApplicationGatewayURLPathMap, pathMapToMerge *n.ApplicationGatewayURLPathMap, cbCtx *ConfigBuilderContext) *n.ApplicationGatewayURLPathMap {
if pathMapToMerge.DefaultBackendAddressPool != nil && *pathMapToMerge.DefaultBackendAddressPool.ID != *cbCtx.DefaultAddressPoolID {
existingPathMap.DefaultBackendAddressPool = pathMapToMerge.DefaultBackendAddressPool
}
if pathMapToMerge.DefaultBackendHTTPSettings != nil && *pathMapToMerge.DefaultBackendHTTPSettings.ID != *cbCtx.DefaultHTTPSettingsID {
existingPathMap.DefaultBackendHTTPSettings = pathMapToMerge.DefaultBackendHTTPSettings
}
if pathMapToMerge.DefaultRedirectConfiguration != nil {
existingPathMap.DefaultRedirectConfiguration = pathMapToMerge.DefaultRedirectConfiguration
existingPathMap.DefaultBackendAddressPool = nil
existingPathMap.DefaultBackendHTTPSettings = nil
}
if pathMapToMerge.DefaultRewriteRuleSet != nil {
existingPathMap.DefaultRewriteRuleSet = pathMapToMerge.DefaultRewriteRuleSet
}
if pathMapToMerge.PathRules == nil || len(*pathMapToMerge.PathRules) == 0 {
return existingPathMap
}
var mergedPathRules, allPathRules []n.ApplicationGatewayPathRule
if existingPathMap.PathRules == nil {
allPathRules = *pathMapToMerge.PathRules
} else {
allPathRules = append(*existingPathMap.PathRules, *pathMapToMerge.PathRules...)
}
// we want to ensure that there are only unique paths in the url path map
pathMap := make(map[string]n.ApplicationGatewayPathRule)
for _, pathRule := range allPathRules {
addRuleToMergeList := true
for _, path := range *pathRule.Paths {
if _, exists := pathMap[path]; exists {
klog.Errorf("A path-rule with path '%s' already exists'. Existing path rule {%s} and new path rule {%s}.", path, printPathRule(pathMap[path]), printPathRule(pathRule))
addRuleToMergeList = false
} else {
pathMap[path] = pathRule
}
}
if addRuleToMergeList {
mergedPathRules = append(mergedPathRules, pathRule)
}
}
existingPathMap.PathRules = &mergedPathRules
return existingPathMap
}
func (c *appGwConfigBuilder) getListenerPriorities(cbCtx *ConfigBuilderContext) map[listenerIdentifier]*int32 {
prioritySet, priorityNil := false, false
priorityExists := make(map[int32]bool)
allPriorities := make(map[listenerIdentifier]*int32)
for _, ingress := range cbCtx.IngressList {
klog.V(3).Infof("Getting Request Routing Rules Priority for Ingress: %s/%s", ingress.Namespace, ingress.Name)
azListenerConfigs := c.getListenersFromIngress(ingress, cbCtx.EnvVariables)
for listenerID := range azListenerConfigs {
if priority, err := annotations.GetRequestRoutingRulePriority(ingress); err == nil {
klog.V(3).Infof("Request Routing Rules Priority for Ingress: %s/%s is Priority: %d", ingress.Namespace, ingress.Name, *priority)
prioritySet = true
if _, value := priorityExists[*priority]; !value {
priorityExists[*priority] = true
} else {
klog.Errorf(
"Request Routing Rules Priority for Ingress: %s/%s is duplicated. Priority must be unique across all the request routing rules.",
ingress.Namespace,
ingress.Name)
}
allPriorities[listenerID] = priority
} else if controllererrors.IsErrorCode(err, controllererrors.ErrorMissingAnnotation) {
klog.V(9).Infof("Request Routing Rules Priority for Ingress: %s/%s is Priority: nil", ingress.Namespace, ingress.Name)
priorityNil = true
allPriorities[listenerID] = nil
} else if controllererrors.IsErrorCode(err, controllererrors.ErrorInvalidContent) {
klog.Errorf("%s for Ingress: %s/%s", err.Error(), ingress.Namespace, ingress.Name)
}
}
}
if priorityNil && prioritySet {
klog.Error("Either all or no Ingress should have the priority specified.")
}
return allPriorities
}
func printPathRule(pathRule n.ApplicationGatewayPathRule) string {
s := fmt.Sprintf("pathMapName=%s", *pathRule.Name)
if pathRule.BackendAddressPool != nil && pathRule.BackendAddressPool.ID != nil {
s = fmt.Sprintf("%s poolID=%s", s, *pathRule.BackendAddressPool.ID)
}
if pathRule.RedirectConfiguration != nil && pathRule.RedirectConfiguration.ID != nil {
s = fmt.Sprintf("%s redirectID=%s", s, *pathRule.RedirectConfiguration.ID)
}
if pathRule.LoadDistributionPolicy != nil && pathRule.LoadDistributionPolicy.ID != nil {
s = fmt.Sprintf("%s ldpID=%s", s, *pathRule.LoadDistributionPolicy.ID)
}
return s
}
// preparePathFromPathType uses pathType property to modify ingress.Path to append/remove "*"
// if pathType == Prefix, "*" is appended if not provided by the user
// if pathType == Exact, "*" is trimmed
func preparePathFromPathType(path string, pathType *networking.PathType) string {
if pathType != nil {
if *pathType == networking.PathTypeExact {
return strings.TrimSuffix(path, "*")
}
if *pathType == networking.PathTypePrefix && !strings.HasSuffix(path, "*") {
return path + "*"
}
}
return path
}
// Application Gateway doesn't allow exact path for "/"
// Code="ApplicationGatewayPathRulePathShouldHaveNonEmptyMatchValue" Message="Path / should have a nonempty match value followed by '/' in PathRule xxxx
// So, Path "/" with pathType:Exact cannot be added into the path rule. Thus, we don't support it.
// "/" for any path type will be treated as a prefix match.
func isPathCatchAll(path string, pathType *networking.PathType) bool {
return len(path) == 0 || path == "/*" || path == "/"
}
// assignPriorityWhereMissing assigns priority to rules that don't have rule priority assigned
// by the user.
// This logic is similar to how AppGW populates rule priority internally.
// Multisite rule is given higher priority than basic rule.
func (c *appGwConfigBuilder) assignPriorityWhereMissing(rules []n.ApplicationGatewayRequestRoutingRule) []n.ApplicationGatewayRequestRoutingRule {
usedUpPriorities := make(map[int32]interface{})
for _, rule := range rules {
if rule.Priority != nil {
usedUpPriorities[*rule.Priority] = nil
}
}
var lastMultiSiteRulePriority int32 = 19000
var lastBasicRulePriority int32 = 19500
var priorityJump int32 = 5
for _, rule := range rules {
listener := LookupListenerByID(c.appGw.HTTPListeners, rule.HTTPListener.ID)
if rule.Priority != nil {
// rule already has a priority assigned
continue
}
// find priority to assign to the rule
var priority int32
if IsMutliSiteListener(listener) {
priority = findNextFreePriority(usedUpPriorities, lastMultiSiteRulePriority, priorityJump)
lastMultiSiteRulePriority = priority
} else {
priority = findNextFreePriority(usedUpPriorities, lastBasicRulePriority, priorityJump)
lastBasicRulePriority = priority
}
rule.Priority = to.Int32Ptr(priority)
usedUpPriorities[priority] = nil
}
return rules
}
func findNextFreePriority(usedUpPriorities map[int32]interface{}, lastPriority int32, jump int32) int32 {
priority := lastPriority
for {
if _, exists := usedUpPriorities[priority]; !exists {
return priority
}
priority = priority + jump
if priority > MaxAllowedPriority {
return MaxAllowedPriority
}
}
}