mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 20:57:32 +08:00
Move codes to pkg (#46)
This commit is contained in:
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user