blob: 07933a5540338485ab11765b37063e71a30139f5 [file] [log] [blame]
// Copyright Istio Authors
//
// 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 gateway
import (
"fmt"
"sort"
)
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s "sigs.k8s.io/gateway-api/apis/v1alpha2"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model/kstatus"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
)
func createRouteStatus(gateways []routeParentReference, obj config.Config, current []k8s.RouteParentStatus, routeErr *ConfigError) []k8s.RouteParentStatus {
gws := make([]k8s.RouteParentStatus, 0, len(gateways))
// Fill in all the gateways that are already present but not owned by us. This is non-trivial as there may be multiple
// gateway controllers that are exposing their status on the same route. We need to attempt to manage ours properly (including
// removing gateway references when they are removed), without mangling other Controller's status.
for _, r := range current {
if r.ControllerName != ControllerName {
// We don't own this status, so keep it around
gws = append(gws, r)
}
}
// Collect all of our unique parent references. There may be multiple when we have a route without section name,
// but reference a parent with multiple sections.
seen := map[k8s.ParentReference]routeParentReference{}
failedCount := map[k8s.ParentReference]int{}
for _, gw := range gateways {
// We will append it if it is our first occurrence, or the existing one has an error. This means
// if *any* section has no errors, we will declare Admitted
if gw.DeniedReason != nil {
failedCount[gw.OriginalReference]++
}
if exist, f := seen[gw.OriginalReference]; !f || (exist.DeniedReason != nil && gw.DeniedReason == nil) {
seen[gw.OriginalReference] = gw
}
}
// Now we fill in all the ones we do own
// TODO look into also reporting ResolvedRefs; we should be gracefully dropping invalid backends instead
// of rejecting the whole thing.
for k, gw := range seen {
var condition metav1.Condition
if routeErr != nil {
condition = metav1.Condition{
Type: string(k8s.RouteConditionAccepted),
Status: kstatus.StatusFalse,
ObservedGeneration: obj.Generation,
LastTransitionTime: metav1.Now(),
Reason: routeErr.Reason,
Message: routeErr.Message,
}
} else if gw.DeniedReason != nil {
err := gw.DeniedReason.Error()
if failedCount[k] > 1 {
err = fmt.Sprintf("failed to bind to %d parents, last error: %v", failedCount[k], gw.DeniedReason.Error())
}
condition = metav1.Condition{
Type: string(k8s.RouteConditionAccepted),
Status: kstatus.StatusFalse,
ObservedGeneration: obj.Generation,
LastTransitionTime: metav1.Now(),
Reason: InvalidParentRef,
Message: err,
}
} else {
condition = metav1.Condition{
Type: string(k8s.RouteConditionAccepted),
Status: kstatus.StatusTrue,
ObservedGeneration: obj.Generation,
LastTransitionTime: metav1.Now(),
Reason: "RouteAdmitted",
Message: "Route was valid",
}
}
gws = append(gws, k8s.RouteParentStatus{
ParentRef: gw.OriginalReference,
ControllerName: ControllerName,
Conditions: []metav1.Condition{condition},
})
}
// Ensure output is deterministic.
// TODO: will we fight over other controllers doing similar (but not identical) ordering?
sort.SliceStable(gws, func(i, j int) bool {
return parentRefString(gws[i].ParentRef) > parentRefString(gws[j].ParentRef)
})
return gws
}
type ConfigErrorReason = string
const (
// InvalidDestination indicates an issue with the destination
InvalidDestination ConfigErrorReason = "InvalidDestination"
// InvalidParentRef indicates we could not refer to the parent we request
InvalidParentRef ConfigErrorReason = "InvalidParentReference"
// InvalidFilter indicates an issue with the filters
InvalidFilter ConfigErrorReason = "InvalidFilter"
// InvalidTLS indicates an issue with TLS settings
InvalidTLS ConfigErrorReason = "InvalidTLS"
// InvalidConfiguration indicates a generic error for all other invalid configurations
InvalidConfiguration ConfigErrorReason = "InvalidConfiguration"
)
// ConfigError represents an invalid configuration that will be reported back to the user.
type ConfigError struct {
Reason ConfigErrorReason
Message string
}
type condition struct {
// reason defines the reason to report on success. Ignored if error is set
reason string
// message defines the message to report on success. Ignored if error is set
message string
// status defines the status to report on success. The inverse will be set if error is set
// If not set, will default to StatusTrue
status metav1.ConditionStatus
// error defines an error state; the reason and message will be replaced with that of the error and
// the status inverted
error *ConfigError
// setOnce, if enabled, will only set the condition if it is not yet present or set to this reason
setOnce string
}
// setConditions sets the existingConditions with the new conditions
func setConditions(generation int64, existingConditions []metav1.Condition, conditions map[string]*condition) []metav1.Condition {
// Sort keys for deterministic ordering
condKeys := make([]string, 0, len(conditions))
for k := range conditions {
condKeys = append(condKeys, k)
}
sort.Strings(condKeys)
for _, k := range condKeys {
cond := conditions[k]
setter := kstatus.UpdateConditionIfChanged
if cond.setOnce != "" {
setter = func(conditions []metav1.Condition, condition metav1.Condition) []metav1.Condition {
return kstatus.CreateCondition(conditions, condition, cond.setOnce)
}
}
// A condition can be "negative polarity" (ex: ListenerInvalid) or "positive polarity" (ex:
// ListenerValid), so in order to determine the status we should set each `condition` defines its
// default positive status. When there is an error, we will invert that. Example: If we have
// condition ListenerInvalid, the status will be set to StatusFalse. If an error is reported, it
// will be inverted to StatusTrue to indicate listeners are invalid. See
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
// for more information
if cond.error != nil {
existingConditions = setter(existingConditions, metav1.Condition{
Type: k,
Status: kstatus.InvertStatus(cond.status),
ObservedGeneration: generation,
LastTransitionTime: metav1.Now(),
Reason: cond.error.Reason,
Message: cond.error.Message,
})
} else {
status := cond.status
if status == "" {
status = kstatus.StatusTrue
}
existingConditions = setter(existingConditions, metav1.Condition{
Type: k,
Status: status,
ObservedGeneration: generation,
LastTransitionTime: metav1.Now(),
Reason: cond.reason,
Message: cond.message,
})
}
}
return existingConditions
}
func reportGatewayCondition(obj config.Config, conditions map[string]*condition) {
obj.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
gs := s.(*k8s.GatewayStatus)
gs.Conditions = setConditions(obj.Generation, gs.Conditions, conditions)
return gs
})
}
func reportListenerAttachedRoutes(index int, obj config.Config, i int32) {
obj.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
gs := s.(*k8s.GatewayStatus)
for index >= len(gs.Listeners) {
gs.Listeners = append(gs.Listeners, k8s.ListenerStatus{})
}
status := gs.Listeners[index]
status.AttachedRoutes = i
gs.Listeners[index] = status
return gs
})
}
func reportListenerCondition(index int, l k8s.Listener, obj config.Config, conditions map[string]*condition) {
obj.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
gs := s.(*k8s.GatewayStatus)
for index >= len(gs.Listeners) {
gs.Listeners = append(gs.Listeners, k8s.ListenerStatus{})
}
cond := gs.Listeners[index].Conditions
gs.Listeners[index] = k8s.ListenerStatus{
Name: l.Name,
AttachedRoutes: 0, // this will be reported later
SupportedKinds: generateSupportedKinds(l),
Conditions: setConditions(obj.Generation, cond, conditions),
}
return gs
})
}
func generateSupportedKinds(l k8s.Listener) []k8s.RouteGroupKind {
supported := []k8s.RouteGroupKind{}
switch l.Protocol {
case k8s.HTTPProtocolType, k8s.HTTPSProtocolType:
// Only terminate allowed, so its always HTTP
supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(StrPointer(gvk.HTTPRoute.Group)), Kind: k8s.Kind(gvk.HTTPRoute.Kind)}}
case k8s.TCPProtocolType:
supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(StrPointer(gvk.TCPRoute.Group)), Kind: k8s.Kind(gvk.TCPRoute.Kind)}}
case k8s.TLSProtocolType:
if l.TLS != nil && l.TLS.Mode != nil && *l.TLS.Mode == k8s.TLSModePassthrough {
supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(StrPointer(gvk.TLSRoute.Group)), Kind: k8s.Kind(gvk.TLSRoute.Kind)}}
} else {
supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(StrPointer(gvk.TCPRoute.Group)), Kind: k8s.Kind(gvk.TCPRoute.Kind)}}
}
// UDP route note support
}
if l.AllowedRoutes != nil && len(l.AllowedRoutes.Kinds) > 0 {
// We need to filter down to only ones we actually support
intersection := []k8s.RouteGroupKind{}
for _, s := range supported {
for _, kind := range l.AllowedRoutes.Kinds {
if s == kind {
intersection = append(intersection, s)
break
}
}
}
return intersection
}
return supported
}