mirror of
https://github.com/alibaba/higress.git
synced 2026-02-27 22:20:57 +08:00
190 lines
5.4 KiB
Go
190 lines
5.4 KiB
Go
// 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 (
|
|
networking "istio.io/api/networking/v1alpha3"
|
|
)
|
|
|
|
const (
|
|
enableCanary = "canary"
|
|
canaryByHeader = "canary-by-header"
|
|
canaryByHeaderValue = "canary-by-header-value"
|
|
canaryByHeaderPattern = "canary-by-header-pattern"
|
|
canaryByCookie = "canary-by-cookie"
|
|
canaryWeight = "canary-weight"
|
|
canaryWeightTotal = "canary-weight-total"
|
|
|
|
defaultCanaryWeightTotal = 100
|
|
)
|
|
|
|
var _ Parser = &canary{}
|
|
|
|
type CanaryConfig struct {
|
|
Enabled bool
|
|
Header string
|
|
HeaderValue string
|
|
HeaderPattern string
|
|
Cookie string
|
|
Weight int
|
|
WeightTotal int
|
|
}
|
|
|
|
type canary struct{}
|
|
|
|
func (c canary) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
|
|
if !needCanaryConfig(annotations) {
|
|
return nil
|
|
}
|
|
|
|
canaryConfig := &CanaryConfig{
|
|
WeightTotal: defaultCanaryWeightTotal,
|
|
}
|
|
|
|
defer func() {
|
|
config.Canary = canaryConfig
|
|
}()
|
|
|
|
canaryConfig.Enabled, _ = annotations.ParseBoolASAP(enableCanary)
|
|
if !canaryConfig.Enabled {
|
|
return nil
|
|
}
|
|
|
|
if header, err := annotations.ParseStringASAP(canaryByHeader); err == nil {
|
|
canaryConfig.Header = header
|
|
}
|
|
|
|
if headerValue, err := annotations.ParseStringASAP(canaryByHeaderValue); err == nil &&
|
|
headerValue != "" {
|
|
canaryConfig.HeaderValue = headerValue
|
|
return nil
|
|
}
|
|
|
|
if headerPattern, err := annotations.ParseStringASAP(canaryByHeaderPattern); err == nil &&
|
|
headerPattern != "" {
|
|
canaryConfig.HeaderPattern = headerPattern
|
|
return nil
|
|
}
|
|
|
|
if cookie, err := annotations.ParseStringASAP(canaryByCookie); err == nil &&
|
|
cookie != "" {
|
|
canaryConfig.Cookie = cookie
|
|
return nil
|
|
}
|
|
|
|
canaryConfig.Weight, _ = annotations.ParseIntASAP(canaryWeight)
|
|
if weightTotal, err := annotations.ParseIntASAP(canaryWeightTotal); err == nil && weightTotal > 0 {
|
|
canaryConfig.WeightTotal = weightTotal
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ApplyByWeight(canary, route *networking.HTTPRoute, canaryIngress *Ingress) {
|
|
if len(route.Route) == 1 {
|
|
// Move route level to destination level
|
|
route.Route[0].Headers = route.Headers
|
|
route.Headers = nil
|
|
}
|
|
|
|
// Modify canary weighted cluster
|
|
canary.Route[0].Weight = int32(canaryIngress.Canary.Weight)
|
|
|
|
// Append canary weight upstream service.
|
|
// We will process total weight in the end.
|
|
route.Route = append(route.Route, canary.Route[0])
|
|
|
|
// canary route use the header control applied on itself.
|
|
headerControl{}.ApplyRoute(canary, canaryIngress)
|
|
// Move route level to destination level
|
|
canary.Route[0].Headers = canary.Headers
|
|
|
|
// First add normal route cluster
|
|
canary.Route[0].FallbackClusters = append(canary.Route[0].FallbackClusters,
|
|
route.Route[0].Destination.DeepCopy())
|
|
// Second add fallback cluster of normal route cluster
|
|
canary.Route[0].FallbackClusters = append(canary.Route[0].FallbackClusters,
|
|
route.Route[0].FallbackClusters...)
|
|
}
|
|
|
|
func ApplyByHeader(canary, route *networking.HTTPRoute, canaryIngress *Ingress) {
|
|
canaryConfig := canaryIngress.Canary
|
|
|
|
// Copy canary http route
|
|
temp := canary.DeepCopy()
|
|
|
|
// Inherit configuration from non-canary rule
|
|
route.DeepCopyInto(canary)
|
|
// Assign temp copied canary route match
|
|
canary.Match = temp.Match
|
|
// Assign temp copied canary route destination
|
|
canary.Route = temp.Route
|
|
|
|
// Modified match base on by header
|
|
if canaryConfig.Header != "" {
|
|
for _, match := range canary.Match {
|
|
match.Headers = map[string]*networking.StringMatch{
|
|
canaryConfig.Header: {
|
|
MatchType: &networking.StringMatch_Exact{
|
|
Exact: "always",
|
|
},
|
|
},
|
|
}
|
|
if canaryConfig.HeaderValue != "" {
|
|
match.Headers = map[string]*networking.StringMatch{
|
|
canaryConfig.Header: {
|
|
MatchType: &networking.StringMatch_Regex{
|
|
Regex: "always|" + canaryConfig.HeaderValue,
|
|
},
|
|
},
|
|
}
|
|
} else if canaryConfig.HeaderPattern != "" {
|
|
match.Headers = map[string]*networking.StringMatch{
|
|
canaryConfig.Header: {
|
|
MatchType: &networking.StringMatch_Regex{
|
|
Regex: canaryConfig.HeaderPattern,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
} else if canaryConfig.Cookie != "" {
|
|
for _, match := range canary.Match {
|
|
match.Headers = map[string]*networking.StringMatch{
|
|
"cookie": {
|
|
MatchType: &networking.StringMatch_Regex{
|
|
Regex: "^(.\\*?;)?(" + canaryConfig.Cookie + "=always)(;.\\*)?$",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
canary.Headers = nil
|
|
// canary route use the header control applied on itself.
|
|
headerControl{}.ApplyRoute(canary, canaryIngress)
|
|
|
|
// First add normal route cluster
|
|
canary.Route[0].FallbackClusters = append(canary.Route[0].FallbackClusters,
|
|
route.Route[0].Destination.DeepCopy())
|
|
// Second add fallback cluster of normal route cluster
|
|
canary.Route[0].FallbackClusters = append(canary.Route[0].FallbackClusters,
|
|
route.Route[0].FallbackClusters...)
|
|
}
|
|
|
|
func needCanaryConfig(annotations Annotations) bool {
|
|
return annotations.HasASAP(enableCanary)
|
|
}
|