mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 12:47:28 +08:00
Move codes to pkg (#46)
This commit is contained in:
133
pkg/ingress/kube/common/controller.go
Normal file
133
pkg/ingress/kube/common/controller.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"istio.io/istio/pkg/config"
|
||||
gatewaytool "istio.io/istio/pkg/config/gateway"
|
||||
listerv1 "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
|
||||
)
|
||||
|
||||
type ServiceKey struct {
|
||||
Namespace string
|
||||
Name string
|
||||
Port int32
|
||||
}
|
||||
|
||||
type WrapperConfig struct {
|
||||
Config *config.Config
|
||||
AnnotationsConfig *annotations.Ingress
|
||||
}
|
||||
|
||||
type WrapperGateway struct {
|
||||
Gateway *networking.Gateway
|
||||
WrapperConfig *WrapperConfig
|
||||
ClusterId string
|
||||
Host string
|
||||
}
|
||||
|
||||
func (w *WrapperGateway) IsHTTPS() bool {
|
||||
if w.Gateway == nil || len(w.Gateway.Servers) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, server := range w.Gateway.Servers {
|
||||
if gatewaytool.IsTLSServer(server) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type WrapperHTTPRoute struct {
|
||||
HTTPRoute *networking.HTTPRoute
|
||||
WrapperConfig *WrapperConfig
|
||||
RawClusterId string
|
||||
ClusterId string
|
||||
ClusterName string
|
||||
Host string
|
||||
OriginPath string
|
||||
OriginPathType PathType
|
||||
WeightTotal int32
|
||||
IsDefaultBackend bool
|
||||
}
|
||||
|
||||
func (w *WrapperHTTPRoute) Meta() string {
|
||||
return strings.Join([]string{w.WrapperConfig.Config.Namespace, w.WrapperConfig.Config.Name}, "-")
|
||||
}
|
||||
|
||||
func (w *WrapperHTTPRoute) BasePathFormat() string {
|
||||
return strings.Join([]string{w.Host, w.OriginPath}, "-")
|
||||
}
|
||||
|
||||
func (w *WrapperHTTPRoute) PathFormat() string {
|
||||
return strings.Join([]string{w.Host, string(w.OriginPathType), w.OriginPath}, "-")
|
||||
}
|
||||
|
||||
type WrapperVirtualService struct {
|
||||
VirtualService *networking.VirtualService
|
||||
WrapperConfig *WrapperConfig
|
||||
ConfiguredDefaultBackend bool
|
||||
AppRoot string
|
||||
}
|
||||
|
||||
type WrapperTrafficPolicy struct {
|
||||
TrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy
|
||||
WrapperConfig *WrapperConfig
|
||||
}
|
||||
|
||||
type WrapperDestinationRule struct {
|
||||
DestinationRule *networking.DestinationRule
|
||||
WrapperConfig *WrapperConfig
|
||||
ServiceKey ServiceKey
|
||||
}
|
||||
|
||||
type IngressController interface {
|
||||
// RegisterEventHandler adds a handler to receive config update events for a
|
||||
// configuration type
|
||||
RegisterEventHandler(kind config.GroupVersionKind, handler model.EventHandler)
|
||||
|
||||
List() []config.Config
|
||||
|
||||
ServiceLister() listerv1.ServiceLister
|
||||
|
||||
SecretLister() listerv1.SecretLister
|
||||
|
||||
ConvertGateway(convertOptions *ConvertOptions, wrapper *WrapperConfig) error
|
||||
|
||||
ConvertHTTPRoute(convertOptions *ConvertOptions, wrapper *WrapperConfig) error
|
||||
|
||||
ApplyDefaultBackend(convertOptions *ConvertOptions, wrapper *WrapperConfig) error
|
||||
|
||||
ApplyCanaryIngress(convertOptions *ConvertOptions, wrapper *WrapperConfig) error
|
||||
|
||||
ConvertTrafficPolicy(convertOptions *ConvertOptions, wrapper *WrapperConfig) error
|
||||
|
||||
// Run until a signal is received
|
||||
Run(stop <-chan struct{})
|
||||
|
||||
SetWatchErrorHandler(func(r *cache.Reflector, err error)) error
|
||||
|
||||
// HasSynced returns true after initial cache synchronization is complete
|
||||
HasSynced() bool
|
||||
}
|
||||
68
pkg/ingress/kube/common/metrics.go
Normal file
68
pkg/ingress/kube/common/metrics.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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 common
|
||||
|
||||
import "istio.io/pkg/monitoring"
|
||||
|
||||
type Event string
|
||||
|
||||
const (
|
||||
Normal Event = "normal"
|
||||
|
||||
Unknown Event = "unknown"
|
||||
|
||||
EmptyRule Event = "empty-rule"
|
||||
|
||||
MissingSecret Event = "missing-secret"
|
||||
|
||||
InvalidBackendService Event = "invalid-backend-service"
|
||||
|
||||
DuplicatedRoute Event = "duplicated-route"
|
||||
|
||||
DuplicatedTls Event = "duplicated-tls"
|
||||
|
||||
PortNameResolveError Event = "port-name-resolve-error"
|
||||
)
|
||||
|
||||
var (
|
||||
clusterTag = monitoring.MustCreateLabel("cluster")
|
||||
invalidType = monitoring.MustCreateLabel("type")
|
||||
|
||||
// totalIngresses tracks the total number of ingress
|
||||
totalIngresses = monitoring.NewGauge(
|
||||
"pilot_total_ingresses",
|
||||
"Total ingresses known to pilot.",
|
||||
monitoring.WithLabels(clusterTag),
|
||||
)
|
||||
|
||||
totalInvalidIngress = monitoring.NewSum(
|
||||
"pilot_total_invalid_ingresses",
|
||||
"Total invalid ingresses known to pilot.",
|
||||
monitoring.WithLabels(clusterTag, invalidType),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
monitoring.MustRegister(totalIngresses)
|
||||
monitoring.MustRegister(totalInvalidIngress)
|
||||
}
|
||||
|
||||
func RecordIngressNumber(cluster string, number int) {
|
||||
totalIngresses.With(clusterTag.Value(cluster)).Record(float64(number))
|
||||
}
|
||||
|
||||
func IncrementInvalidIngress(cluster string, event Event) {
|
||||
totalInvalidIngress.With(clusterTag.Value(cluster), invalidType.Value(string(event))).Increment()
|
||||
}
|
||||
411
pkg/ingress/kube/common/model.go
Normal file
411
pkg/ingress/kube/common/model.go
Normal file
@@ -0,0 +1,411 @@
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"istio.io/istio/pkg/cluster"
|
||||
"istio.io/istio/pkg/config"
|
||||
"istio.io/istio/pkg/config/schema/collection"
|
||||
"istio.io/istio/pkg/config/schema/collections"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
. "github.com/alibaba/higress/pkg/ingress/log"
|
||||
)
|
||||
|
||||
type PathType string
|
||||
|
||||
const (
|
||||
prefixAnnotation = "internal.higress.io/"
|
||||
|
||||
ClusterIdAnnotation = prefixAnnotation + "cluster-id"
|
||||
|
||||
RawClusterIdAnnotation = prefixAnnotation + "raw-cluster-id"
|
||||
|
||||
HostAnnotation = prefixAnnotation + "host"
|
||||
|
||||
// PrefixMatchRegex optionally matches "/..." at the end of a path.
|
||||
// regex taken from https://github.com/projectcontour/contour/blob/2b3376449bedfea7b8cea5fbade99fb64009c0f6/internal/envoy/v3/route.go#L59
|
||||
PrefixMatchRegex = `((\/).*)?`
|
||||
|
||||
DefaultHost = "*"
|
||||
|
||||
DefaultPath = "/"
|
||||
|
||||
// DefaultIngressClass defines the default class used in the nginx ingress controller.
|
||||
// For compatible ingress nginx case, istio controller will watch ingresses whose ingressClass is
|
||||
// nginx, empty string or unset.
|
||||
DefaultIngressClass = "nginx"
|
||||
|
||||
Exact PathType = "exact"
|
||||
|
||||
Prefix PathType = "prefix"
|
||||
|
||||
Regex PathType = "regex"
|
||||
|
||||
DefaultStatusUpdateInterval = 10 * time.Second
|
||||
|
||||
AppKey = "app"
|
||||
AppValue = "higress-gateway"
|
||||
SvcHostNameSuffix = ".multiplenic"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedOp = errors.New("unsupported operation: the ingress config store is a read-only view")
|
||||
|
||||
ErrNotFound = errors.New("item not found")
|
||||
|
||||
Schemas = collection.SchemasFor(
|
||||
collections.IstioNetworkingV1Alpha3Virtualservices,
|
||||
collections.IstioNetworkingV1Alpha3Gateways,
|
||||
collections.IstioNetworkingV1Alpha3Destinationrules,
|
||||
collections.IstioNetworkingV1Alpha3Envoyfilters,
|
||||
)
|
||||
|
||||
clusterPrefix string
|
||||
SvcLabelSelector labels.Selector
|
||||
)
|
||||
|
||||
func init() {
|
||||
set := labels.Set{
|
||||
AppKey: AppValue,
|
||||
}
|
||||
SvcLabelSelector = labels.SelectorFromSet(set)
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Enable bool
|
||||
ClusterId string
|
||||
IngressClass string
|
||||
WatchNamespace string
|
||||
RawClusterId string
|
||||
EnableStatus bool
|
||||
SystemNamespace string
|
||||
GatewaySelectorKey string
|
||||
GatewaySelectorValue string
|
||||
}
|
||||
|
||||
type BasicAuthRules struct {
|
||||
Rules []*Rule `json:"_rules_"`
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Realm string `json:"realm"`
|
||||
MatchRoute []string `json:"_match_route_"`
|
||||
Credentials []string `json:"credentials"`
|
||||
Encrypted bool `json:"encrypted"`
|
||||
}
|
||||
|
||||
type IngressDomainCache struct {
|
||||
// host as key
|
||||
Valid map[string]*IngressDomainBuilder
|
||||
|
||||
Invalid []model.IngressDomain
|
||||
}
|
||||
|
||||
func NewIngressDomainCache() *IngressDomainCache {
|
||||
return &IngressDomainCache{
|
||||
Valid: map[string]*IngressDomainBuilder{},
|
||||
}
|
||||
}
|
||||
|
||||
func (i *IngressDomainCache) Extract() model.IngressDomainCollection {
|
||||
var valid []model.IngressDomain
|
||||
|
||||
for _, builder := range i.Valid {
|
||||
valid = append(valid, builder.Build())
|
||||
}
|
||||
|
||||
return model.IngressDomainCollection{
|
||||
Valid: valid,
|
||||
Invalid: i.Invalid,
|
||||
}
|
||||
}
|
||||
|
||||
type ConvertOptions struct {
|
||||
HostWithRule2Ingress map[string]*config.Config
|
||||
|
||||
HostWithTls2Ingress map[string]*config.Config
|
||||
|
||||
Gateways map[string]*WrapperGateway
|
||||
|
||||
IngressDomainCache *IngressDomainCache
|
||||
|
||||
HostAndPath2Ingress map[string]*config.Config
|
||||
|
||||
// Record valid/invalid routes from ingress
|
||||
IngressRouteCache *IngressRouteCache
|
||||
|
||||
VirtualServices map[string]*WrapperVirtualService
|
||||
|
||||
// host -> routes
|
||||
HTTPRoutes map[string][]*WrapperHTTPRoute
|
||||
|
||||
CanaryIngresses []*WrapperConfig
|
||||
|
||||
Service2TrafficPolicy map[ServiceKey]*WrapperTrafficPolicy
|
||||
|
||||
HasDefaultBackend bool
|
||||
}
|
||||
|
||||
// CreateOptions obtain options from cluster id.
|
||||
// The cluster id format is k8sClusterId ingressClass watchNamespace EnableStatus, delimited by _.
|
||||
func CreateOptions(clusterId cluster.ID) Options {
|
||||
parts := strings.Split(clusterId.String(), "_")
|
||||
// Old cluster key
|
||||
if len(parts) < 3 {
|
||||
out := Options{
|
||||
RawClusterId: clusterId.String(),
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
out.ClusterId = parts[0]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
options := Options{
|
||||
Enable: true,
|
||||
ClusterId: parts[0],
|
||||
IngressClass: parts[1],
|
||||
WatchNamespace: parts[2],
|
||||
RawClusterId: clusterId.String(),
|
||||
// The status switch is enabled by default.
|
||||
EnableStatus: true,
|
||||
}
|
||||
|
||||
if len(parts) == 4 {
|
||||
if enable, err := strconv.ParseBool(parts[3]); err == nil {
|
||||
options.EnableStatus = enable
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
type IngressRouteCache struct {
|
||||
routes map[string]*IngressRouteBuilder
|
||||
invalid []model.IngressRoute
|
||||
}
|
||||
|
||||
func NewIngressRouteCache() *IngressRouteCache {
|
||||
return &IngressRouteCache{
|
||||
routes: map[string]*IngressRouteBuilder{},
|
||||
}
|
||||
}
|
||||
|
||||
func (i *IngressRouteCache) New(route *WrapperHTTPRoute) *IngressRouteBuilder {
|
||||
return &IngressRouteBuilder{
|
||||
ClusterId: route.ClusterId,
|
||||
RouteName: route.HTTPRoute.Name,
|
||||
Path: route.OriginPath,
|
||||
PathType: string(route.OriginPathType),
|
||||
Host: route.Host,
|
||||
Event: Normal,
|
||||
Ingress: route.WrapperConfig.Config,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *IngressRouteCache) NewAndAdd(route *WrapperHTTPRoute) {
|
||||
routeBuilder := &IngressRouteBuilder{
|
||||
ClusterId: route.ClusterId,
|
||||
RouteName: route.HTTPRoute.Name,
|
||||
Path: route.OriginPath,
|
||||
PathType: string(route.OriginPathType),
|
||||
Host: route.Host,
|
||||
Event: Normal,
|
||||
Ingress: route.WrapperConfig.Config,
|
||||
}
|
||||
|
||||
// Only care about the first destination
|
||||
destination := route.HTTPRoute.Route[0].Destination
|
||||
svcName, namespace, _ := SplitServiceFQDN(destination.Host)
|
||||
routeBuilder.ServiceList = []model.BackendService{
|
||||
{
|
||||
Name: svcName,
|
||||
Namespace: namespace,
|
||||
Port: destination.Port.Number,
|
||||
Weight: route.HTTPRoute.Route[0].Weight,
|
||||
},
|
||||
}
|
||||
|
||||
i.routes[route.HTTPRoute.Name] = routeBuilder
|
||||
}
|
||||
|
||||
func (i *IngressRouteCache) Add(builder *IngressRouteBuilder) {
|
||||
if builder.Event != Normal {
|
||||
builder.RouteName = "invalid-route"
|
||||
i.invalid = append(i.invalid, builder.Build())
|
||||
return
|
||||
}
|
||||
|
||||
i.routes[builder.RouteName] = builder
|
||||
}
|
||||
|
||||
func (i *IngressRouteCache) Update(route *WrapperHTTPRoute) {
|
||||
oldBuilder, exist := i.routes[route.HTTPRoute.Name]
|
||||
if !exist {
|
||||
// Never happen
|
||||
IngressLog.Errorf("ingress route builder %s not found.", route.HTTPRoute.Name)
|
||||
return
|
||||
}
|
||||
|
||||
var serviceList []model.BackendService
|
||||
for _, routeDestination := range route.HTTPRoute.Route {
|
||||
serviceList = append(serviceList, ConvertBackendService(routeDestination))
|
||||
}
|
||||
|
||||
oldBuilder.ServiceList = serviceList
|
||||
}
|
||||
|
||||
func (i *IngressRouteCache) Delete(route *WrapperHTTPRoute) {
|
||||
delete(i.routes, route.HTTPRoute.Name)
|
||||
}
|
||||
|
||||
func (i *IngressRouteCache) Extract() model.IngressRouteCollection {
|
||||
var valid []model.IngressRoute
|
||||
|
||||
for _, builder := range i.routes {
|
||||
valid = append(valid, builder.Build())
|
||||
}
|
||||
|
||||
return model.IngressRouteCollection{
|
||||
Valid: valid,
|
||||
Invalid: i.invalid,
|
||||
}
|
||||
}
|
||||
|
||||
type IngressRouteBuilder struct {
|
||||
ClusterId string
|
||||
RouteName string
|
||||
Host string
|
||||
PathType string
|
||||
Path string
|
||||
ServiceList []model.BackendService
|
||||
PortName string
|
||||
Event Event
|
||||
Ingress *config.Config
|
||||
PreIngress *config.Config
|
||||
}
|
||||
|
||||
func (i *IngressRouteBuilder) Build() model.IngressRoute {
|
||||
errorMsg := ""
|
||||
switch i.Event {
|
||||
case DuplicatedRoute:
|
||||
preClusterId := GetClusterId(i.PreIngress.Annotations)
|
||||
errorMsg = fmt.Sprintf("host %s and path %s in ingress %s/%s within cluster %s is already defined in ingress %s/%s within cluster %s",
|
||||
i.Host,
|
||||
i.Path,
|
||||
i.Ingress.Namespace,
|
||||
i.Ingress.Name,
|
||||
i.ClusterId,
|
||||
i.PreIngress.Namespace,
|
||||
i.PreIngress.Name,
|
||||
preClusterId)
|
||||
case InvalidBackendService:
|
||||
errorMsg = fmt.Sprintf("backend service of host %s and path %s is invalid defined in ingress %s/%s within cluster %s",
|
||||
i.Host,
|
||||
i.Path,
|
||||
i.Ingress.Namespace,
|
||||
i.Ingress.Name,
|
||||
i.ClusterId,
|
||||
)
|
||||
case PortNameResolveError:
|
||||
errorMsg = fmt.Sprintf("service port name %s of host %s and path %s resolves error defined in ingress %s/%s within cluster %s",
|
||||
i.PortName,
|
||||
i.Host,
|
||||
i.Path,
|
||||
i.Ingress.Namespace,
|
||||
i.Ingress.Name,
|
||||
i.ClusterId,
|
||||
)
|
||||
}
|
||||
|
||||
ingressRoute := model.IngressRoute{
|
||||
Name: i.RouteName,
|
||||
Host: i.Host,
|
||||
Path: i.Path,
|
||||
PathType: i.PathType,
|
||||
DestinationType: model.Single,
|
||||
ServiceList: i.ServiceList,
|
||||
Error: errorMsg,
|
||||
}
|
||||
|
||||
// backward compatibility
|
||||
if len(ingressRoute.ServiceList) > 0 {
|
||||
ingressRoute.ServiceName = i.ServiceList[0].Name
|
||||
}
|
||||
|
||||
if len(ingressRoute.ServiceList) > 1 {
|
||||
ingressRoute.DestinationType = model.Multiple
|
||||
}
|
||||
|
||||
return ingressRoute
|
||||
}
|
||||
|
||||
type Protocol string
|
||||
|
||||
const (
|
||||
HTTP Protocol = "HTTP"
|
||||
HTTPS Protocol = "HTTPS"
|
||||
)
|
||||
|
||||
type IngressDomainBuilder struct {
|
||||
ClusterId string
|
||||
Host string
|
||||
Protocol Protocol
|
||||
Event Event
|
||||
// format is cluster id/namespace/name
|
||||
SecretName string
|
||||
Ingress *config.Config
|
||||
PreIngress *config.Config
|
||||
}
|
||||
|
||||
func (i *IngressDomainBuilder) Build() model.IngressDomain {
|
||||
errorMsg := ""
|
||||
switch i.Event {
|
||||
case MissingSecret:
|
||||
errorMsg = fmt.Sprintf("tls field of host %s defined in ingress %s/%s within cluster %s misses secret",
|
||||
i.Host,
|
||||
i.Ingress.Namespace,
|
||||
i.Ingress.Name,
|
||||
i.ClusterId,
|
||||
)
|
||||
case DuplicatedTls:
|
||||
preClusterId := GetClusterId(i.PreIngress.Annotations)
|
||||
errorMsg = fmt.Sprintf("tls field of host %s defined in ingress %s/%s within cluster %s "+
|
||||
"is conflicted with ingress %s/%s within cluster %s",
|
||||
i.Host,
|
||||
i.Ingress.Namespace,
|
||||
i.Ingress.Name,
|
||||
i.ClusterId,
|
||||
i.PreIngress.Namespace,
|
||||
i.PreIngress.Name,
|
||||
preClusterId,
|
||||
)
|
||||
}
|
||||
|
||||
return model.IngressDomain{
|
||||
Host: i.Host,
|
||||
Protocol: string(i.Protocol),
|
||||
SecretName: i.SecretName,
|
||||
CreationTime: i.Ingress.CreationTimestamp,
|
||||
Error: errorMsg,
|
||||
}
|
||||
}
|
||||
365
pkg/ingress/kube/common/tool.go
Normal file
365
pkg/ingress/kube/common/tool.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"istio.io/istio/pkg/config"
|
||||
"istio.io/istio/pkg/kube"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
|
||||
. "github.com/alibaba/higress/pkg/ingress/log"
|
||||
)
|
||||
|
||||
// V1Available check if the "networking/v1" Ingress is available.
|
||||
func V1Available(client kube.Client) bool {
|
||||
// check kubernetes version to use new ingress package or not
|
||||
version119, _ := version.ParseGeneric("v1.19.0")
|
||||
|
||||
serverVersion, err := client.GetKubernetesVersion()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
runningVersion, err := version.ParseGeneric(serverVersion.String())
|
||||
if err != nil {
|
||||
IngressLog.Errorf("unexpected error parsing running Kubernetes version: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return runningVersion.AtLeast(version119)
|
||||
}
|
||||
|
||||
// NetworkingIngressAvailable check if the "networking" group Ingress is available.
|
||||
func NetworkingIngressAvailable(client kube.Client) bool {
|
||||
// check kubernetes version to use new ingress package or not
|
||||
version118, _ := version.ParseGeneric("v1.18.0")
|
||||
|
||||
serverVersion, err := client.GetKubernetesVersion()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
runningVersion, err := version.ParseGeneric(serverVersion.String())
|
||||
if err != nil {
|
||||
IngressLog.Errorf("unexpected error parsing running Kubernetes version: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return runningVersion.AtLeast(version118)
|
||||
}
|
||||
|
||||
// SortIngressByCreationTime sorts the list of config objects in ascending order by their creation time (if available).
|
||||
func SortIngressByCreationTime(configs []config.Config) {
|
||||
sort.Slice(configs, func(i, j int) bool {
|
||||
if configs[i].CreationTimestamp == configs[j].CreationTimestamp {
|
||||
in := configs[i].Name + "." + configs[i].Namespace
|
||||
jn := configs[j].Name + "." + configs[j].Namespace
|
||||
return in < jn
|
||||
}
|
||||
return configs[i].CreationTimestamp.Before(configs[j].CreationTimestamp)
|
||||
})
|
||||
}
|
||||
|
||||
func CreateOrUpdateAnnotations(annotations map[string]string, options Options) map[string]string {
|
||||
out := make(map[string]string, len(annotations))
|
||||
|
||||
for key, value := range annotations {
|
||||
out[key] = value
|
||||
}
|
||||
|
||||
out[ClusterIdAnnotation] = options.ClusterId
|
||||
out[RawClusterIdAnnotation] = options.RawClusterId
|
||||
return out
|
||||
}
|
||||
|
||||
func GetClusterId(annotations map[string]string) string {
|
||||
if len(annotations) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if value, exist := annotations[ClusterIdAnnotation]; exist {
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetRawClusterId(annotations map[string]string) string {
|
||||
if len(annotations) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if value, exist := annotations[RawClusterIdAnnotation]; exist {
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetHost(annotations map[string]string) string {
|
||||
if len(annotations) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if value, exist := annotations[HostAnnotation]; exist {
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// CleanHost follow the format of mse-ops for host.
|
||||
func CleanHost(host string) string {
|
||||
if host == "*" {
|
||||
return "global"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(host, "*") {
|
||||
host = strings.ReplaceAll(host, "*", "global-")
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(host, ".", "-")
|
||||
}
|
||||
|
||||
func CreateConvertedName(items ...string) string {
|
||||
for i := len(items) - 1; i >= 0; i-- {
|
||||
if items[i] == "" {
|
||||
items = append(items[:i], items[i+1:]...)
|
||||
}
|
||||
}
|
||||
return strings.Join(items, "-")
|
||||
}
|
||||
|
||||
// SortHTTPRoutes sort routes base on path type and path length
|
||||
func SortHTTPRoutes(routes []*WrapperHTTPRoute) {
|
||||
isDefaultBackend := func(route *WrapperHTTPRoute) bool {
|
||||
return route.IsDefaultBackend
|
||||
}
|
||||
|
||||
isAllCatch := func(route *WrapperHTTPRoute) bool {
|
||||
if route.OriginPathType == Prefix && route.OriginPath == "/" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sort.SliceStable(routes, func(i, j int) bool {
|
||||
// Move default backend to end
|
||||
if isDefaultBackend(routes[i]) {
|
||||
return false
|
||||
}
|
||||
if isDefaultBackend(routes[j]) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Move user specified root path match to end
|
||||
if isAllCatch(routes[i]) {
|
||||
return false
|
||||
}
|
||||
if isAllCatch(routes[j]) {
|
||||
return true
|
||||
}
|
||||
|
||||
if routes[i].OriginPathType == routes[j].OriginPathType {
|
||||
return len(routes[i].OriginPath) > len(routes[j].OriginPath)
|
||||
}
|
||||
|
||||
if routes[i].OriginPathType == Exact {
|
||||
return true
|
||||
}
|
||||
|
||||
if routes[i].OriginPathType != Exact &&
|
||||
routes[j].OriginPathType != Exact {
|
||||
return routes[i].OriginPathType == Prefix
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func constructRouteName(route *WrapperHTTPRoute) string {
|
||||
var builder strings.Builder
|
||||
// host-pathType-path
|
||||
base := route.PathFormat()
|
||||
builder.WriteString(base)
|
||||
|
||||
var mappings []string
|
||||
var headerMappings []string
|
||||
var queryMappings []string
|
||||
if len(route.HTTPRoute.Match) > 0 {
|
||||
match := route.HTTPRoute.Match[0]
|
||||
if len(match.Headers) != 0 {
|
||||
for k, v := range match.Headers {
|
||||
var mapping string
|
||||
switch v.GetMatchType().(type) {
|
||||
case *networking.StringMatch_Exact:
|
||||
mapping = CreateConvertedName("exact", k, v.GetExact())
|
||||
case *networking.StringMatch_Prefix:
|
||||
mapping = CreateConvertedName("prefix", k, v.GetPrefix())
|
||||
case *networking.StringMatch_Regex:
|
||||
mapping = CreateConvertedName("regex", k, v.GetRegex())
|
||||
}
|
||||
|
||||
headerMappings = append(headerMappings, mapping)
|
||||
}
|
||||
|
||||
sort.SliceStable(headerMappings, func(i, j int) bool {
|
||||
return headerMappings[i] < headerMappings[j]
|
||||
})
|
||||
}
|
||||
|
||||
if len(match.QueryParams) != 0 {
|
||||
for k, v := range match.QueryParams {
|
||||
var mapping string
|
||||
switch v.GetMatchType().(type) {
|
||||
case *networking.StringMatch_Exact:
|
||||
mapping = strings.Join([]string{"exact", k, v.GetExact()}, ":")
|
||||
case *networking.StringMatch_Prefix:
|
||||
mapping = strings.Join([]string{"prefix", k, v.GetPrefix()}, ":")
|
||||
case *networking.StringMatch_Regex:
|
||||
mapping = strings.Join([]string{"regex", k, v.GetRegex()}, ":")
|
||||
}
|
||||
|
||||
queryMappings = append(queryMappings, mapping)
|
||||
}
|
||||
|
||||
sort.SliceStable(queryMappings, func(i, j int) bool {
|
||||
return queryMappings[i] < queryMappings[j]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mappings = append(mappings, headerMappings...)
|
||||
mappings = append(mappings, queryMappings...)
|
||||
|
||||
if len(mappings) == 0 {
|
||||
return CreateConvertedName(base)
|
||||
}
|
||||
|
||||
return CreateConvertedName(base, CreateConvertedName(mappings...))
|
||||
}
|
||||
|
||||
func partMd5(raw string) string {
|
||||
hash := md5.Sum([]byte(raw))
|
||||
encoded := hex.EncodeToString(hash[:])
|
||||
return encoded[:4] + encoded[len(encoded)-4:]
|
||||
}
|
||||
|
||||
func GenerateUniqueRouteName(route *WrapperHTTPRoute) string {
|
||||
raw := constructRouteName(route)
|
||||
|
||||
// meta-part-clusterId
|
||||
// meta: ingressNamespace-ingressName
|
||||
meta := route.Meta()
|
||||
// host-pathType-path-header-queryParam, md5, then before 4 char and end 4 char
|
||||
part := partMd5(raw)
|
||||
routeName := CreateConvertedName(meta, part, route.ClusterId)
|
||||
|
||||
if route.WrapperConfig.AnnotationsConfig.IsCanary() {
|
||||
return routeName + "-canary"
|
||||
}
|
||||
|
||||
return routeName
|
||||
}
|
||||
|
||||
func GenerateUniqueRouteNameWithSuffix(route *WrapperHTTPRoute, suffix string) string {
|
||||
raw := constructRouteName(route)
|
||||
|
||||
// meta-part-clusterId
|
||||
// meta: ingressNamespace-ingressName
|
||||
meta := route.Meta()
|
||||
// host-pathType-path-header-queryParam, md5, then before 4 char and end 4 char
|
||||
part := partMd5(raw)
|
||||
return CreateConvertedName(meta, part, route.ClusterId, suffix)
|
||||
}
|
||||
|
||||
func SplitServiceFQDN(fqdn string) (string, string, bool) {
|
||||
parts := strings.Split(fqdn, ".")
|
||||
if len(parts) > 1 {
|
||||
return parts[0], parts[1], true
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
func ConvertBackendService(routeDestination *networking.HTTPRouteDestination) model.BackendService {
|
||||
parts := strings.Split(routeDestination.Destination.Host, ".")
|
||||
return model.BackendService{
|
||||
Namespace: parts[1],
|
||||
Name: parts[0],
|
||||
Port: routeDestination.Destination.Port.Number,
|
||||
Weight: routeDestination.Weight,
|
||||
}
|
||||
}
|
||||
|
||||
func getLoadBalancerIp(svc *v1.Service) []string {
|
||||
var out []string
|
||||
|
||||
for _, ingress := range svc.Status.LoadBalancer.Ingress {
|
||||
if ingress.IP != "" {
|
||||
out = append(out, ingress.IP)
|
||||
}
|
||||
|
||||
if ingress.Hostname != "" {
|
||||
hostName := strings.TrimSuffix(ingress.Hostname, SvcHostNameSuffix)
|
||||
if net.ParseIP(hostName) != nil {
|
||||
out = append(out, hostName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func getSvcIpList(svcList []*v1.Service) []string {
|
||||
var targetSvcList []*v1.Service
|
||||
for _, svc := range svcList {
|
||||
if svc.Spec.Type == v1.ServiceTypeLoadBalancer &&
|
||||
strings.HasPrefix(svc.Name, clusterPrefix) {
|
||||
targetSvcList = append(targetSvcList, svc)
|
||||
}
|
||||
}
|
||||
|
||||
var out []string
|
||||
for _, svc := range targetSvcList {
|
||||
out = append(out, getLoadBalancerIp(svc)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func SortLbIngressList(lbi []v1.LoadBalancerIngress) func(int, int) bool {
|
||||
return func(i int, j int) bool {
|
||||
return lbi[i].IP < lbi[j].IP
|
||||
}
|
||||
}
|
||||
|
||||
func GetLbStatusList(svcList []*v1.Service) []v1.LoadBalancerIngress {
|
||||
svcIpList := getSvcIpList(svcList)
|
||||
lbi := make([]v1.LoadBalancerIngress, 0, len(svcIpList))
|
||||
for _, ep := range svcIpList {
|
||||
lbi = append(lbi, v1.LoadBalancerIngress{IP: ep})
|
||||
}
|
||||
|
||||
sort.SliceStable(lbi, SortLbIngressList(lbi))
|
||||
return lbi
|
||||
}
|
||||
473
pkg/ingress/kube/common/tool_test.go
Normal file
473
pkg/ingress/kube/common/tool_test.go
Normal file
@@ -0,0 +1,473 @@
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pkg/config"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
|
||||
)
|
||||
|
||||
func TestConstructRouteName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input *WrapperHTTPRoute
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "test.com",
|
||||
OriginPathType: Exact,
|
||||
OriginPath: "/test",
|
||||
HTTPRoute: &networking.HTTPRoute{},
|
||||
},
|
||||
expect: "test.com-exact-/test",
|
||||
},
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "*.test.com",
|
||||
OriginPathType: Regex,
|
||||
OriginPath: "/test/(.*)/?[0-9]",
|
||||
HTTPRoute: &networking.HTTPRoute{},
|
||||
},
|
||||
expect: "*.test.com-regex-/test/(.*)/?[0-9]",
|
||||
},
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "test.com",
|
||||
OriginPathType: Exact,
|
||||
OriginPath: "/test",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Match: []*networking.HTTPMatchRequest{
|
||||
{
|
||||
Headers: map[string]*networking.StringMatch{
|
||||
"b": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "a?c.*",
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "hello",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: "test.com-exact-/test-exact-a-hello-regex-b-a?c.*",
|
||||
},
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "test.com",
|
||||
OriginPathType: Prefix,
|
||||
OriginPath: "/test",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Match: []*networking.HTTPMatchRequest{
|
||||
{
|
||||
QueryParams: map[string]*networking.StringMatch{
|
||||
"b": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "a?c.*",
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "hello",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: "test.com-prefix-/test-exact:a:hello-regex:b:a?c.*",
|
||||
},
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "test.com",
|
||||
OriginPathType: Prefix,
|
||||
OriginPath: "/test",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Match: []*networking.HTTPMatchRequest{
|
||||
{
|
||||
Headers: map[string]*networking.StringMatch{
|
||||
"f": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "abc?",
|
||||
},
|
||||
},
|
||||
"e": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "bye",
|
||||
},
|
||||
},
|
||||
},
|
||||
QueryParams: map[string]*networking.StringMatch{
|
||||
"b": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "a?c.*",
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "hello",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: "test.com-prefix-/test-exact-e-bye-regex-f-abc?-exact:a:hello-regex:b:a?c.*",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
out := constructRouteName(c.input)
|
||||
if out != c.expect {
|
||||
t.Fatalf("Expect %s, but is %s", c.expect, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateUniqueRouteName(t *testing.T) {
|
||||
inputWithoutCanary := &WrapperHTTPRoute{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Prefix,
|
||||
OriginPath: "/test",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Match: []*networking.HTTPMatchRequest{
|
||||
{
|
||||
Headers: map[string]*networking.StringMatch{
|
||||
"f": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "abc?",
|
||||
},
|
||||
},
|
||||
"e": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "bye",
|
||||
},
|
||||
},
|
||||
},
|
||||
QueryParams: map[string]*networking.StringMatch{
|
||||
"b": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "a?c.*",
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "hello",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
withoutCanary := GenerateUniqueRouteName(inputWithoutCanary)
|
||||
t.Log(withoutCanary)
|
||||
|
||||
inputWithCanary := &WrapperHTTPRoute{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
Canary: &annotations.CanaryConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Prefix,
|
||||
OriginPath: "/test",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Match: []*networking.HTTPMatchRequest{
|
||||
{
|
||||
Headers: map[string]*networking.StringMatch{
|
||||
"f": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "abc?",
|
||||
},
|
||||
},
|
||||
"e": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "bye",
|
||||
},
|
||||
},
|
||||
},
|
||||
QueryParams: map[string]*networking.StringMatch{
|
||||
"b": {
|
||||
MatchType: &networking.StringMatch_Regex{
|
||||
Regex: "a?c.*",
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
MatchType: &networking.StringMatch_Exact{
|
||||
Exact: "hello",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
withCanary := GenerateUniqueRouteName(inputWithCanary)
|
||||
t.Log(withCanary)
|
||||
|
||||
if withCanary != withoutCanary+"-canary" {
|
||||
t.Fatalf("Expect %s, but actual is %s", withCanary, withoutCanary+"-canary")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLbStatusList(t *testing.T) {
|
||||
clusterPrefix = "gw-123-"
|
||||
svcName := clusterPrefix
|
||||
svcList := []*v1.Service{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: svcName,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeLoadBalancer,
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: []v1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "2.2.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: svcName,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeLoadBalancer,
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: []v1.LoadBalancerIngress{
|
||||
{
|
||||
Hostname: "1.1.1.1" + SvcHostNameSuffix,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: svcName,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeLoadBalancer,
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: []v1.LoadBalancerIngress{
|
||||
{
|
||||
Hostname: "4.4.4.4" + SvcHostNameSuffix,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: svcName,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeLoadBalancer,
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: []v1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "3.3.3.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: svcName,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeClusterIP,
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: []v1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "5.5.5.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
lbiList := GetLbStatusList(svcList)
|
||||
if len(lbiList) != 4 {
|
||||
t.Fatal("len should be 4")
|
||||
}
|
||||
|
||||
if lbiList[0].IP != "1.1.1.1" {
|
||||
t.Fatal("should be 1.1.1.1")
|
||||
}
|
||||
|
||||
if lbiList[3].IP != "4.4.4.4" {
|
||||
t.Fatal("should be 4.4.4.4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortRoutes(t *testing.T) {
|
||||
input := []*WrapperHTTPRoute{
|
||||
{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Prefix,
|
||||
OriginPath: "/",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Name: "test-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Prefix,
|
||||
OriginPath: "/a",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Name: "test-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Name: "test-3",
|
||||
},
|
||||
IsDefaultBackend: true,
|
||||
},
|
||||
{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Exact,
|
||||
OriginPath: "/b",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Name: "test-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
WrapperConfig: &WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Regex,
|
||||
OriginPath: "/d(.*)",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
Name: "test-5",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SortHTTPRoutes(input)
|
||||
if (input[0].HTTPRoute.Name) != "test-4" {
|
||||
t.Fatal("should be test-4")
|
||||
}
|
||||
if (input[1].HTTPRoute.Name) != "test-2" {
|
||||
t.Fatal("should be test-2")
|
||||
}
|
||||
if (input[2].HTTPRoute.Name) != "test-5" {
|
||||
t.Fatal("should be test-5")
|
||||
}
|
||||
if (input[3].HTTPRoute.Name) != "test-1" {
|
||||
t.Fatal("should be test-1")
|
||||
}
|
||||
if (input[4].HTTPRoute.Name) != "test-3" {
|
||||
t.Fatal("should be test-3")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user