pkg/ingress/kube/annotations/loadbalance.go (172 lines of code) (raw):
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations
import (
"strings"
"github.com/golang/protobuf/ptypes/duration"
networking "istio.io/api/networking/v1alpha3"
)
const (
loadBalanceAnnotation = "load-balance"
upstreamHashBy = "upstream-hash-by"
// affinity in nginx/mse ingress always be cookie
affinity = "affinity"
// affinityMode in mse ingress always be balanced
affinityMode = "affinity-mode"
// affinityCanaryBehavior in mse ingress always be legacy
affinityCanaryBehavior = "affinity-canary-behavior"
sessionCookieName = "session-cookie-name"
sessionCookiePath = "session-cookie-path"
sessionCookieMaxAge = "session-cookie-max-age"
sessionCookieExpires = "session-cookie-expires"
varIndicator = "$"
headerIndicator = "$http_"
queryParamIndicator = "$arg_"
defaultAffinityCookieName = "INGRESSCOOKIE"
defaultAffinityCookiePath = "/"
)
var (
_ Parser = loadBalance{}
_ TrafficPolicyHandler = loadBalance{}
headersMapping = map[string]string{
"$request_uri": ":path",
"$host": ":authority",
"$remote_addr": "x-envoy-external-address",
}
)
type consistentHashByOther struct {
header string
queryParam string
}
type consistentHashByCookie struct {
name string
path string
age *duration.Duration
}
type LoadBalanceConfig struct {
simple networking.LoadBalancerSettings_SimpleLB
other *consistentHashByOther
cookie *consistentHashByCookie
}
type loadBalance struct{}
func (l loadBalance) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
if !needLoadBalanceConfig(annotations) {
return nil
}
loadBalanceConfig := &LoadBalanceConfig{
simple: networking.LoadBalancerSettings_ROUND_ROBIN,
}
defer func() {
config.LoadBalance = loadBalanceConfig
}()
if isCookieAffinity(annotations) {
loadBalanceConfig.cookie = &consistentHashByCookie{
name: defaultAffinityCookieName,
path: defaultAffinityCookiePath,
age: &duration.Duration{},
}
if name, err := annotations.ParseStringASAP(sessionCookieName); err == nil {
loadBalanceConfig.cookie.name = name
}
if path, err := annotations.ParseStringASAP(sessionCookiePath); err == nil {
loadBalanceConfig.cookie.path = path
}
if age, err := annotations.ParseIntASAP(sessionCookieMaxAge); err == nil {
loadBalanceConfig.cookie.age = &duration.Duration{
Seconds: int64(age),
}
} else if age, err = annotations.ParseIntASAP(sessionCookieExpires); err == nil {
loadBalanceConfig.cookie.age = &duration.Duration{
Seconds: int64(age),
}
}
} else if isOtherAffinity(annotations) {
if key, err := annotations.ParseStringASAP(upstreamHashBy); err == nil &&
strings.HasPrefix(key, varIndicator) {
value, exist := headersMapping[key]
if exist {
loadBalanceConfig.other = &consistentHashByOther{
header: value,
}
} else {
if strings.HasPrefix(key, headerIndicator) {
loadBalanceConfig.other = &consistentHashByOther{
header: strings.TrimPrefix(key, headerIndicator),
}
} else if strings.HasPrefix(key, queryParamIndicator) {
loadBalanceConfig.other = &consistentHashByOther{
queryParam: strings.TrimPrefix(key, queryParamIndicator),
}
}
}
}
} else {
if lb, err := annotations.ParseStringASAP(loadBalanceAnnotation); err == nil {
lb = strings.ToUpper(lb)
loadBalanceConfig.simple = networking.LoadBalancerSettings_SimpleLB(networking.LoadBalancerSettings_SimpleLB_value[lb])
}
}
return nil
}
func (l loadBalance) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy, portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
loadBalanceConfig := config.LoadBalance
if loadBalanceConfig == nil {
return
}
var loadBalancer *networking.LoadBalancerSettings
if loadBalanceConfig.cookie != nil {
loadBalancer = &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
Name: loadBalanceConfig.cookie.name,
Path: loadBalanceConfig.cookie.path,
Ttl: loadBalanceConfig.cookie.age,
},
},
},
},
}
} else if loadBalanceConfig.other != nil {
var consistentHash *networking.LoadBalancerSettings_ConsistentHashLB
if loadBalanceConfig.other.header != "" {
consistentHash = &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpHeaderName{
HttpHeaderName: loadBalanceConfig.other.header,
},
}
} else {
consistentHash = &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpQueryParameterName{
HttpQueryParameterName: loadBalanceConfig.other.queryParam,
},
}
}
loadBalancer = &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: consistentHash,
},
}
} else {
loadBalancer = &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_Simple{
Simple: loadBalanceConfig.simple,
},
}
}
if trafficPolicy != nil {
trafficPolicy.LoadBalancer = loadBalancer
}
if portTrafficPolicy != nil {
portTrafficPolicy.LoadBalancer = loadBalancer
}
}
func isCookieAffinity(annotations Annotations) bool {
return annotations.HasASAP(affinity) ||
annotations.HasASAP(sessionCookieName) ||
annotations.HasASAP(sessionCookiePath)
}
func isOtherAffinity(annotations Annotations) bool {
return annotations.HasASAP(upstreamHashBy)
}
func needLoadBalanceConfig(annotations Annotations) bool {
return annotations.HasASAP(loadBalanceAnnotation) ||
isCookieAffinity(annotations) ||
isOtherAffinity(annotations)
}