Skip to content

Commit

Permalink
AV-214603 Moving HTTPRoute Validation to status layer
Browse files Browse the repository at this point in the history
  • Loading branch information
pkoshtavmware committed Sep 30, 2024
1 parent 7986542 commit 29038ae
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 260 deletions.
4 changes: 1 addition & 3 deletions ako-gateway-api/k8s/ako_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,7 @@ func (c *GatewayController) FullSyncK8s(sync bool) error {
resVer := meta.GetResourceVersion()
objects.SharedResourceVerInstanceLister().Save(key, resVer)
}
if IsHTTPRouteValid(key, httpRouteObj) {
filteredHTTPRoutes = append(filteredHTTPRoutes, httpRouteObj)
}
filteredHTTPRoutes = append(filteredHTTPRoutes, httpRouteObj)
}
sort.Slice(filteredHTTPRoutes, func(i, j int) bool {
if filteredHTTPRoutes[i].GetCreationTimestamp().Unix() == filteredHTTPRoutes[j].GetCreationTimestamp().Unix() {
Expand Down
6 changes: 0 additions & 6 deletions ako-gateway-api/k8s/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,9 +633,6 @@ func (c *GatewayController) SetupGatewayApiEventHandlers(numWorkers uint32) {
utils.AviLog.Debugf("key: %s, msg: same resource version returning", key)
return
}
if !IsHTTPRouteValid(key, httpRoute) {
return
}
namespace, _, _ := cache.SplitMetaNamespaceKey(utils.ObjKey(httpRoute))
bkt := utils.Bkt(namespace, numWorkers)
c.workqueue[bkt].AddRateLimited(key)
Expand Down Expand Up @@ -674,9 +671,6 @@ func (c *GatewayController) SetupGatewayApiEventHandlers(numWorkers uint32) {
newHTTPRoute := obj.(*gatewayv1.HTTPRoute)
if IsHTTPRouteUpdated(oldHTTPRoute, newHTTPRoute) {
key := lib.HTTPRoute + "/" + utils.ObjKey(newHTTPRoute)
if !IsHTTPRouteValid(key, newHTTPRoute) {
return
}
namespace, _, _ := cache.SplitMetaNamespaceKey(utils.ObjKey(newHTTPRoute))
bkt := utils.Bkt(namespace, numWorkers)
c.workqueue[bkt].AddRateLimited(key)
Expand Down
248 changes: 0 additions & 248 deletions ako-gateway-api/k8s/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,17 @@ package k8s
import (
"context"
"fmt"
"regexp"
"strings"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

"k8s.io/apimachinery/pkg/labels"

akogatewayapilib "github.com/vmware/load-balancer-and-ingress-services-for-kubernetes/ako-gateway-api/lib"
akogatewayapiobjects "github.com/vmware/load-balancer-and-ingress-services-for-kubernetes/ako-gateway-api/objects"
akogatewayapistatus "github.com/vmware/load-balancer-and-ingress-services-for-kubernetes/ako-gateway-api/status"
"github.com/vmware/load-balancer-and-ingress-services-for-kubernetes/internal/lib"
"github.com/vmware/load-balancer-and-ingress-services-for-kubernetes/pkg/utils"
)

func isRegexMatch(stringWithWildCard string, stringToBeMatched string, key string) bool {
// replace the wildcard character with a regex
replacedHostname := strings.Replace(stringWithWildCard, utils.WILDCARD, utils.FQDN_LABEL_REGEX, 1)
// create the expression for pattern matching
pattern := fmt.Sprintf("^%s$", replacedHostname)
expr, err := regexp.Compile(pattern)
if err != nil {
utils.AviLog.Warnf("key: %s, msg: unable to compile wildcard string to regex object. Err: %s", key, err)
}
return expr.MatchString(stringToBeMatched)
}

func IsGatewayClassValid(key string, gatewayClass *gatewayv1.GatewayClass) bool {

controllerName := string(gatewayClass.Spec.ControllerName)
Expand Down Expand Up @@ -337,234 +320,3 @@ func isValidListener(key string, gateway *gatewayv1.Gateway, gatewayStatus *gate
utils.AviLog.Infof("key: %s, msg: Listener %s/%s is valid", key, gateway.Name, listener.Name)
return true
}

func IsHTTPRouteValid(key string, obj *gatewayv1.HTTPRoute) bool {

httpRoute := obj.DeepCopy()
if len(httpRoute.Spec.ParentRefs) == 0 {
utils.AviLog.Errorf("key: %s, msg: Parent Reference is empty for the HTTPRoute %s", key, httpRoute.Name)
return false
}

httpRouteStatus := obj.Status.DeepCopy()
httpRouteStatus.Parents = make([]gatewayv1.RouteParentStatus, 0, len(httpRoute.Spec.ParentRefs))
var invalidParentRefCount int
parentRefIndexInHttpRouteStatus := 0
for parentRefIndexFromSpec := range httpRoute.Spec.ParentRefs {
err := validateParentReference(key, httpRoute, httpRouteStatus, parentRefIndexFromSpec, &parentRefIndexInHttpRouteStatus)
if err != nil {
invalidParentRefCount++
parentRefName := httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].Name
utils.AviLog.Warnf("key: %s, msg: Parent Reference %s of HTTPRoute object %s is not valid, err: %v", key, parentRefName, httpRoute.Name, err)
}
}
akogatewayapistatus.Record(key, httpRoute, &akogatewayapistatus.Status{HTTPRouteStatus: httpRouteStatus})

// No valid attachment, we can't proceed with this HTTPRoute object.
if invalidParentRefCount == len(httpRoute.Spec.ParentRefs) {
utils.AviLog.Errorf("key: %s, msg: HTTPRoute object %s is not valid", key, httpRoute.Name)
akogatewayapilib.AKOControlConfig().EventRecorder().Eventf(httpRoute, corev1.EventTypeWarning,
lib.Detached, "HTTPRoute object %s is not valid", httpRoute.Name)
return false
}
utils.AviLog.Infof("key: %s, msg: HTTPRoute object %s is valid", key, httpRoute.Name)
return true
}

func validateParentReference(key string, httpRoute *gatewayv1.HTTPRoute, httpRouteStatus *gatewayv1.HTTPRouteStatus, parentRefIndexFromSpec int, parentRefIndexInHttpRouteStatus *int) error {

name := string(httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].Name)
namespace := httpRoute.Namespace
if httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].Namespace != nil {
namespace = string(*httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].Namespace)
}
gwNsName := namespace + "/" + name
obj, err := akogatewayapilib.AKOControlConfig().GatewayApiInformers().GatewayInformer.Lister().Gateways(namespace).Get(name)
if err != nil {
utils.AviLog.Errorf("key: %s, msg: unable to get the gateway object. err: %s", key, err)
return err
}
gateway := obj.DeepCopy()

gwClass := string(gateway.Spec.GatewayClassName)
_, isAKOCtrl := akogatewayapiobjects.GatewayApiLister().IsGatewayClassControllerAKO(gwClass)
if !isAKOCtrl {
utils.AviLog.Warnf("key: %s, msg: controller for the parent reference %s of HTTPRoute object %s is not ako", key, name, httpRoute.Name)
return fmt.Errorf("controller for the parent reference %s of HTTPRoute object %s is not ako", name, httpRoute.Name)
}
// creates the Parent status only when the AKO is the gateway controller
httpRouteStatus.Parents = append(httpRouteStatus.Parents, gatewayv1.RouteParentStatus{})
httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].ControllerName = akogatewayapilib.GatewayController
httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].ParentRef.Name = gatewayv1.ObjectName(name)
httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].ParentRef.Namespace = (*gatewayv1.Namespace)(&namespace)
if httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].SectionName != nil {
httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].ParentRef.SectionName = httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].SectionName
}

defaultCondition := akogatewayapistatus.NewCondition().
Type(string(gatewayv1.RouteConditionAccepted)).
Status(metav1.ConditionFalse).
ObservedGeneration(httpRoute.ObjectMeta.Generation)

if len(gateway.Status.Conditions) == 0 {
// Gateway processing by AKO has not started.
utils.AviLog.Errorf("key: %s, msg: AKO is yet to process Gateway %s for parent reference %s.", key, gateway.Name, name)
err := fmt.Errorf("AKO is yet to process Gateway %s for parent reference %s", gateway.Name, name)
defaultCondition.
Reason(string(gatewayv1.RouteReasonPending)).
Message(err.Error()).
SetIn(&httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].Conditions)
*parentRefIndexInHttpRouteStatus = *parentRefIndexInHttpRouteStatus + 1
return err
}

// Attach only when gateway configuration is valid
currentGatewayStatusCondition := gateway.Status.Conditions[0]
if currentGatewayStatusCondition.Status != metav1.ConditionTrue {
// Gateway is not in an expected state.
utils.AviLog.Errorf("key: %s, msg: Gateway %s for parent reference %s is in Invalid State", key, gateway.Name, name)
err := fmt.Errorf("Gateway %s is in Invalid State", gateway.Name)
defaultCondition.
Reason(string(gatewayv1.RouteReasonPending)).
Message(err.Error()).
SetIn(&httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].Conditions)
*parentRefIndexInHttpRouteStatus = *parentRefIndexInHttpRouteStatus + 1
return err
}

//section name is optional
var listenersForRoute []gatewayv1.Listener
if httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].SectionName != nil {
listenerName := *httpRoute.Spec.ParentRefs[parentRefIndexFromSpec].SectionName
i := akogatewayapilib.FindListenerByName(string(listenerName), gateway.Spec.Listeners)
if i == -1 {
// listener is not present in gateway
utils.AviLog.Errorf("key: %s, msg: unable to find the listener from the Section Name %s in Parent Reference %s", key, name, listenerName)
err := fmt.Errorf("Invalid listener name provided")
defaultCondition.
Reason(string(gatewayv1.RouteReasonNoMatchingParent)).
Message(err.Error()).
SetIn(&httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].Conditions)
*parentRefIndexInHttpRouteStatus = *parentRefIndexInHttpRouteStatus + 1
return err
}
listenersForRoute = append(listenersForRoute, gateway.Spec.Listeners[i])
} else {
listenersForRoute = append(listenersForRoute, gateway.Spec.Listeners...)
}

// TODO: Validation for hostname (those being fqdns) need to validate as per the K8 gateway req.
var listenersMatchedToRoute []gatewayv1.Listener
for _, listenerObj := range listenersForRoute {
// check from store
hostInListener := listenerObj.Hostname
isListenerFqdnWildcard := false
matched := false
// TODO:
// Use case to handle for validations of hostname:
// USe case 1: Shouldn't contain mor than 1 *
// USe case 2: * should be at the beginning only
if hostInListener == nil || *hostInListener == "" || *hostInListener == utils.WILDCARD {
matched = true
} else {
// mark listener fqdn if it has *
if strings.HasPrefix(string(*hostInListener), utils.WILDCARD) {
isListenerFqdnWildcard = true
}
for _, host := range httpRoute.Spec.Hostnames {
// casese to consider:
// Case 1: hostname of gateway is wildcard(empty) and hostname from httproute is not wild card
// Case 2: hostname of gateway is not wild card and hostname from httproute is wildcard
// case 3: hostname of gateway is wildcard(empty) and hostname from httproute is wildcard
// case 4: hostname of gateway is not wildcard and hostname from httproute is not wildcard
isHttpRouteHostFqdnWildcard := false
if strings.HasPrefix(string(host), utils.WILDCARD) {
isHttpRouteHostFqdnWildcard = true
}
if isHttpRouteHostFqdnWildcard && isListenerFqdnWildcard {
// both are true. Match nonwildcard part
// Use case: 1. GW: *.avi.internal HttpRoute: *.bar.avi.internal
// USe case: 2. GW: *.bar.avi.internal HttpRoute: *.avi.internal
if utils.CheckSubdomainOverlapping(string(host), string(*hostInListener)) {
matched = true
break
}

} else if !isHttpRouteHostFqdnWildcard && !isListenerFqdnWildcard {
// both are complete fqdn
if string(host) == string(*hostInListener) {
matched = true
break
}
} else {
if isHttpRouteHostFqdnWildcard {
// httpRoute hostFqdn is wildcard
matched = matched || isRegexMatch(string(host), string(*hostInListener), key)
} else if isListenerFqdnWildcard {
// listener fqdn is wildcard
matched = matched || isRegexMatch(string(*hostInListener), string(host), key)
}

}

}
// if there are no hostnames specified, all parent listneres should be matched.
if len(httpRoute.Spec.Hostnames) == 0 {
matched = true
}
}
if !matched {
utils.AviLog.Warnf("key: %s, msg: Gateway object %s don't have any listeners that matches the hostnames in HTTPRoute %s", key, gateway.Name, httpRoute.Name)
continue
}
listenersMatchedToRoute = append(listenersMatchedToRoute, listenerObj)
}
if len(listenersMatchedToRoute) == 0 {
err := fmt.Errorf("Hostname in Gateway Listener doesn't match with any of the hostnames in HTTPRoute")
defaultCondition.
Reason(string(gatewayv1.RouteReasonNoMatchingListenerHostname)).
Message(err.Error()).
SetIn(&httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].Conditions)
*parentRefIndexInHttpRouteStatus = *parentRefIndexInHttpRouteStatus + 1
gwRouteNsName := fmt.Sprintf("%s/%s/%s/%s", gwNsName, lib.HTTPRoute, httpRoute.Namespace, httpRoute.Name)
found, hosts := akogatewayapiobjects.GatewayApiLister().GetGatewayRouteToHostname(gwRouteNsName)
if found {
utils.AviLog.Warnf("key: %s, msg: Hostname in Gateway Listener doesn't match with any of the hostnames in HTTPRoute", key)
utils.AviLog.Debugf("key: %s, msg: %d hosts mapped to the route %s/%s/%s", key, len(hosts), "HTTPRoute", httpRoute.Namespace, httpRoute.Name)
return nil
}
return err
}

//TODO: Add a condition to check whether this route is allowed by the parent gateways allowedroute field and set gatewayv1.RouteReasonNotAllowedByListeners reason while implemenating gateway->listener->allowedRoutes->Selector

gatewayStatus := gateway.Status.DeepCopy()
for _, listenerObj := range listenersMatchedToRoute {
listenerName := listenerObj.Name
// Increment the attached routes of the listener in the Gateway object

i := akogatewayapilib.FindListenerStatusByName(string(listenerName), gatewayStatus.Listeners)
if i == -1 {
utils.AviLog.Errorf("key: %s, msg: Gateway status is missing for the listener with name %s", key, listenerName)
err := fmt.Errorf("Couldn't find the listener %s in the Gateway status", listenerName)
defaultCondition.
Reason(string(gatewayv1.RouteReasonNoMatchingParent)).
Message(err.Error()).
SetIn(&httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].Conditions)
*parentRefIndexInHttpRouteStatus = *parentRefIndexInHttpRouteStatus + 1
return err
}

gatewayStatus.Listeners[i].AttachedRoutes += 1
}
akogatewayapistatus.Record(key, gateway, &akogatewayapistatus.Status{GatewayStatus: gatewayStatus})

defaultCondition.
Reason(string(gatewayv1.RouteReasonAccepted)).
Status(metav1.ConditionTrue).
Message("Parent reference is valid").
SetIn(&httpRouteStatus.Parents[*parentRefIndexInHttpRouteStatus].Conditions)
utils.AviLog.Infof("key: %s, msg: Parent Reference %s of HTTPRoute object %s is valid", key, name, httpRoute.Name)
*parentRefIndexInHttpRouteStatus = *parentRefIndexInHttpRouteStatus + 1
return nil
}
7 changes: 7 additions & 0 deletions ako-gateway-api/nodes/dequeue_ingestion.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ func DequeueIngestion(key string, fullsync bool) {
continue
}

httpRoute, err := akogatewayapilib.AKOControlConfig().GatewayApiInformers().HTTPRouteInformer.Lister().HTTPRoutes(namespace).Get(name)
if err == nil {
utils.AviLog.Debugf("key: %s, msg: Successfully retrieved the HTTPRoute object %s", key, name)
if !IsHTTPRouteValid(key, httpRoute) {
return
}
}
childVSes := make(map[string]struct{}, 0)

switch objType {
Expand Down
2 changes: 1 addition & 1 deletion ako-gateway-api/nodes/gateway_model_rel.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func HTTPRouteChanges(namespace, name, key string) ([]string, bool) {
hrObj, err := akogatewayapilib.AKOControlConfig().GatewayApiInformers().HTTPRouteInformer.Lister().HTTPRoutes(namespace).Get(name)
if err != nil {
if !errors.IsNotFound(err) {
utils.AviLog.Errorf("key: %s, msg: got error while getting gateway: %v", key, err)
utils.AviLog.Errorf("key: %s, msg: got error while getting httproute: %v", key, err)
return []string{}, false
}
// httproute must be deleted so remove mappings
Expand Down
Loading

0 comments on commit 29038ae

Please sign in to comment.