Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
eguzki committed Oct 9, 2023
1 parent a84fb4d commit 97672db
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 161 deletions.
179 changes: 23 additions & 156 deletions controllers/rate_limiting_wasmplugin_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@ func (r *RateLimitingWASMPluginReconciler) wasmPluginConfig(ctx context.Context,

// TODO(eastizle): Sort RLPs by name for consistent comparison with existing objects??

for _, rlp := range rateLimitPolicies {
for _, policy := range rateLimitPolicies {
rlp := policy.(*kuadrantv1beta2.RateLimitPolicy)
wasmRLP := r.WASMRateLimitPolicy(gatewayAPITopology, rlp, gw)
if wasmRLP == nil {
// no need to add the policy if there are no rules;
// a rlp can return no rules if all its limits fail to match any route rule
// skip this RLP
continue
}

Expand All @@ -207,29 +207,32 @@ func (r *RateLimitingWASMPluginReconciler) wasmPluginConfig(ctx context.Context,
return wasmPlugin, nil
}

func (r *RateLimitingWASMPluginReconciler) WASMRateLimitPolicy(t *common.KuadrantTopology, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1beta1.Gateway) []rlptools.Rule {
gwHostnames := common.TargetHostnames(gw)
func (r *RateLimitingWASMPluginReconciler) WASMRateLimitPolicy(t *common.KuadrantTopology, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1beta1.Gateway) *wasm.RateLimitPolicy {
gwHostnamesTmp := common.TargetHostnames(gw)
gwHostnames := common.Map(gwHostnamesTmp, func(str string) gatewayapiv1beta1.Hostname { return gatewayapiv1beta1.Hostname(str) })

route := r.RouteFromRLP(t, rlp, gw)

rules := rlptools.WasmRules(rlp, route)
if len(rules) == 0 {
// no need to add the policy if there are no rules; a rlp can return no rules if all its limits fail to match any route rule
return nil
}

// narrow the list of hostnames specified in the route so we don't generate wasm rules that only apply to other gateways
// this is a no-op for the gateway rlp
hostnames := common.FilterValidSubdomains(gwHostnames, route.Spec.Hostnames)
if len(hostnames) == 0 { // it should only happen when the route specifies no hostnames
hostnames = gwHostnames
}

// gwHostnames := common.Map(gwHostnamesTmp, func(str string) gatewayapiv1beta1.Hostname { return gatewayapiv1beta1.Hostname(str) })

wasmRLP := &wasm.RateLimitPolicy{
Name: client.ObjectKeyFromObject(rlp).String(),
Domain: rlptools.LimitsNamespaceFromRLP(rlp),
Hostnames: common.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive
Service: common.KuadrantRateLimitClusterName,
Rules: nil,
}


return &wasm.RateLimitPolicy{
Name: client.ObjectKeyFromObject(rlp).String(),
Domain: rlptools.LimitsNamespaceFromRLP(rlp),
Hostnames: common.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive
Service: common.KuadrantRateLimitClusterName,
Rules: rules,
}
}

func (r *RateLimitingWASMPluginReconciler) RouteFromRLP(t *common.KuadrantTopology, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1beta1.Gateway) *gatewayapiv1beta1.HTTPRoute {
Expand All @@ -248,8 +251,11 @@ func (r *RateLimitingWASMPluginReconciler) RouteFromRLP(t *common.KuadrantTopolo
freeRules := make([]gatewayapiv1beta1.HTTPRouteRule, 0)
for idx := range freeRoutes {
freeroute := freeRoutes[idx]
freeRules = append(freeRules, route.Spec.Rules...)
freeRules = append(freeRules, freeroute.Spec.Rules...)
}

gwHostnamesTmp := common.TargetHostnames(gw)
gwHostnames := common.Map(gwHostnamesTmp, func(str string) gatewayapiv1beta1.Hostname { return gatewayapiv1beta1.Hostname(str) })
route = &gatewayapiv1beta1.HTTPRoute{
Spec: gatewayapiv1beta1.HTTPRouteSpec{
Hostnames: gwHostnames,
Expand All @@ -261,145 +267,6 @@ func (r *RateLimitingWASMPluginReconciler) RouteFromRLP(t *common.KuadrantTopolo
return route
}



wasmRules := rlptools.WasmRules(rlp, route)
if len(wasmRules) == 0 {
// no need to add the policy if there are no rules;
// a rlp can return no rules if all its limits fail to match any route rule
continue
}

wasmPlugin.RateLimitPolicies = append(wasmPlugin.RateLimitPolicies, wasm.RateLimitPolicy{
Name: client.ObjectKeyFromObject(rlp).String(),
Domain: rlptools.LimitsNamespaceFromRLP(rlp),
Rules: wasmRules,
Hostnames: common.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive
Service: common.KuadrantRateLimitClusterName,
})

}

return wasmPlugin, nil
}


// SHOULD BE DELETED
func (r *RateLimitingWASMPluginReconciler) wasmPluginConfigTOBEDELETED(ctx context.Context, gw common.GatewayWrapper, rlpRefs []client.ObjectKey) (*wasm.Plugin, error) {
logger, _ := logr.FromContext(ctx)
logger = logger.WithName("wasmPluginConfig").WithValues("gateway", gw.Key())

type store struct {
rlp kuadrantv1beta2.RateLimitPolicy
route gatewayapiv1beta1.HTTPRoute
skip bool
}
rlps := make(map[string]*store, len(rlpRefs))
routeKeys := make(map[string]struct{}, 0)
var gwRLPKey string

// store all rlps and find the one that targets the gateway (if there is one)
for _, rlpKey := range rlpRefs {
rlp := &kuadrantv1beta2.RateLimitPolicy{}
err := r.Client().Get(ctx, rlpKey, rlp)
logger.V(1).Info("get rlp", "ratelimitpolicy", rlpKey, "err", err)
if err != nil {
return nil, err
}

// target ref is a HTTPRoute
if common.IsTargetRefHTTPRoute(rlp.Spec.TargetRef) {
route, err := r.FetchValidHTTPRoute(ctx, rlp.TargetKey())
if err != nil {
return nil, err
}
rlps[rlpKey.String()] = &store{rlp: *rlp, route: *route}
routeKeys[client.ObjectKeyFromObject(route).String()] = struct{}{}
continue
}

// target ref is a Gateway
if rlps[rlpKey.String()] != nil {
return nil, fmt.Errorf("wasmPluginConfig: multiple gateway RLP found and only one expected. rlp keys: %v", rlpRefs)
}
gwRLPKey = rlpKey.String()
rlps[gwRLPKey] = &store{rlp: *rlp}
}

gwHostnames := gw.Hostnames()
if len(gwHostnames) == 0 {
gwHostnames = []gatewayapiv1beta1.Hostname{"*"}
}

// if there is a gateway rlp, fake a single httproute with all rules from all httproutes accepted by the gateway,
// that do not have a rlp of its own, so we can generate wasm rules for those cases
if gwRLPKey != "" {
rules := make([]gatewayapiv1beta1.HTTPRouteRule, 0)
routes := r.FetchAcceptedGatewayHTTPRoutes(ctx, rlps[gwRLPKey].rlp.TargetKey())
for idx := range routes {
route := routes[idx]
// skip routes that have a rlp of its own
if _, found := routeKeys[client.ObjectKeyFromObject(&route).String()]; found {
continue
}
rules = append(rules, route.Spec.Rules...)
}
if len(rules) == 0 {
logger.V(1).Info("no httproutes attached to the targeted gateway, skipping wasm config for the gateway rlp", "ratelimitpolicy", gwRLPKey)
rlps[gwRLPKey].skip = true
} else {
rlps[gwRLPKey].route = gatewayapiv1beta1.HTTPRoute{
Spec: gatewayapiv1beta1.HTTPRouteSpec{
Hostnames: gwHostnames,
Rules: rules,
},
}
}
}

wasmPlugin := &wasm.Plugin{
FailureMode: wasm.FailureModeDeny,
RateLimitPolicies: make([]wasm.RateLimitPolicy, 0),
}

for _, rlpKey := range rlpRefs {
s := rlps[rlpKey.String()]
if s.skip {
continue
}
rlp := s.rlp
route := s.route

// narrow the list of hostnames specified in the route so we don't generate wasm rules that only apply to other gateways
// this is a no-op for the gateway rlp
hostnames := common.FilterValidSubdomains(gwHostnames, route.Spec.Hostnames)
if len(hostnames) == 0 { // it should only happen when the route specifies no hostnames
hostnames = gwHostnames
}
route.Spec.Hostnames = hostnames

rules := rlptools.WasmRules(&rlp, &route)
if len(rules) == 0 {
continue // no need to add the policy if there are no rules; a rlp can return no rules if all its limits fail to match any route rule
}

wasmPlugin.RateLimitPolicies = append(wasmPlugin.RateLimitPolicies, wasm.RateLimitPolicy{
Name: rlpKey.String(),
Domain: rlptools.LimitsNamespaceFromRLP(&rlp),
Rules: rules,
Hostnames: common.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive
Service: common.KuadrantRateLimitClusterName,
})
}

// avoid building a wasm plugin config if there are no rules to apply
if len(wasmPlugin.RateLimitPolicies) == 0 {
return nil, nil
}

return wasmPlugin, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *RateLimitingWASMPluginReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
155 changes: 150 additions & 5 deletions pkg/common/kuadrant_topology.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,173 @@
package common

import (
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)

type KuadrantTopology struct {
// Gateway -> []Policy
gatewayPolicies map[client.ObjectKey][]KuadrantPolicy

// Policy -> HTTPRoute
policyRoute map[client.ObjectKey]*gatewayapiv1beta1.HTTPRoute

// Gateway -> []HTTPRoute (routes not targeted directly by any policy)
freeRoutes map[client.ObjectKey][]*gatewayapiv1beta1.HTTPRoute
}

func NewKuadrantTopology(gateways []*gatewayapiv1beta1.Gateway, routes []*gatewayapiv1beta1.HTTPRoute, policy []KuadrantPolicy) *KuadrantTopology {
// TODO
return &KuadrantTopology{}
func NewKuadrantTopology(gateways []*gatewayapiv1beta1.Gateway, routes []*gatewayapiv1beta1.HTTPRoute, policies []KuadrantPolicy) *KuadrantTopology {
t := NewGatewayAPITopology(gateways, routes)

return &KuadrantTopology{
gatewayPolicies: buildGatewayPoliciesIndex(t, policies),
policyRoute: buildPolicyRouteIndex(t, policies),
freeRoutes: buildFreeRoutesIndex(t, policies),
}
}

func (k *KuadrantTopology) PoliciesFromGateway(gateway *gatewayapiv1beta1.Gateway) []KuadrantPolicy {
return k.gatewayPolicies[client.ObjectKeyFromObject(gateway)]
}

func (k *KuadrantTopology) GetPolicyHTTPRoute(policy KuadrantPolicy) *gatewayapiv1beta1.HTTPRoute {
return k.policyRoute[client.ObjectKeyFromObject(policy)]
}

func (k *KuadrantTopology) GetFreeRoutes(gateway *gatewayapiv1beta1.Gateway) []*gatewayapiv1beta1.HTTPRoute {
return k.freeRoutes[client.ObjectKeyFromObject(gateway)]
}

func buildGatewayPoliciesIndex(t *gatewayAPITopology, policies []KuadrantPolicy) map[client.ObjectKey][]KuadrantPolicy {
// Build Gateway -> []Policy index with all the policies affecting the indexed gateway
index := make(map[client.ObjectKey][]KuadrantPolicy, 0)

for _, policy := range policies {
if gateway := t.GatewayFromPolicy(policy); gateway != nil {
// policy targeting a gateway
policies := index[client.ObjectKeyFromObject(gateway)]
policies = append(policies, policy)
index[client.ObjectKeyFromObject(gateway)] = policies
} else if route := t.RouteFromPolicy(policy); route != nil {
// policy targeting a route
routeParents := t.GetRouteParents(route)
for _, routeParent := range routeParents {
policies := index[client.ObjectKeyFromObject(routeParent)]
policies = append(policies, policy)
index[client.ObjectKeyFromObject(routeParent)] = policies
}
}

// skipping the policy as it does not target neither a valid route nor a valid gateway
}

return index
}

func buildPolicyRouteIndex(t *gatewayAPITopology, policies []KuadrantPolicy) map[client.ObjectKey]*gatewayapiv1beta1.HTTPRoute {
// Build Policy -> HTTPRoute index with the route targeted by the indexed policy
index := make(map[client.ObjectKey]*gatewayapiv1beta1.HTTPRoute, 0)
for _, policy := range policies {
if route := t.RouteFromPolicy(policy); route != nil {
index[client.ObjectKeyFromObject(policy)] = route
}
}

return index
}

func buildFreeRoutesIndex(t *gatewayAPITopology, policies []KuadrantPolicy) map[client.ObjectKey][]*gatewayapiv1beta1.HTTPRoute {
// Build Gateway -> []HTTPRoute index with all the routes not targeted by a policy
index := make(map[client.ObjectKey][]*gatewayapiv1beta1.HTTPRoute, 0)

routesTargetedByPolicy := make(map[client.ObjectKey]bool, 0)
for _, policy := range policies {
if route := t.RouteFromPolicy(policy); route != nil {
routesTargetedByPolicy[client.ObjectKeyFromObject(route)] = true
}
}

for _, gateway := range t.GetGateways() {
var gatewayFreeRoutes []*gatewayapiv1beta1.HTTPRoute
for _, route := range t.GetGatewayRoutes(gateway) {
if !routesTargetedByPolicy[client.ObjectKeyFromObject(route)] {
gatewayFreeRoutes = append(gatewayFreeRoutes, route)
}
}

if len(gatewayFreeRoutes) > 0 {
index[client.ObjectKeyFromObject(gateway)] = gatewayFreeRoutes
}
}

return index
}

type gatewayAPITopology struct {
gatewaysIndex map[client.ObjectKey]*gatewayapiv1beta1.Gateway
routesIndex map[client.ObjectKey]*gatewayapiv1beta1.HTTPRoute
}

func NewGatewayAPITopology(gateways []*gatewayapiv1beta1.Gateway, routes []*gatewayapiv1beta1.HTTPRoute) *gatewayAPITopology {
// First build topology with them all
// TODO Secondly, just valid gateways and accepted HTTPRoutes

gatewaysIndex := make(map[client.ObjectKey]*gatewayapiv1beta1.Gateway, 0)
for _, gateway := range gateways {
gatewaysIndex[client.ObjectKeyFromObject(gateway)] = gateway
}

routesIndex := make(map[client.ObjectKey]*gatewayapiv1beta1.HTTPRoute, 0)
for _, route := range routes {
routesIndex[client.ObjectKeyFromObject(route)] = route
}

return &gatewayAPITopology{
gatewaysIndex: gatewaysIndex,
routesIndex: routesIndex,
}
}

func (g *gatewayAPITopology) GatewayFromPolicy(policy KuadrantPolicy) *gatewayapiv1beta1.Gateway {
// Return gateway targeted by the kuadrant policy

if !IsTargetRefGateway(policy.GetTargetRef()) {
return nil
}

namespace := string(ptr.Deref(policy.GetTargetRef().Namespace, policy.GetWrappedNamespace()))

gwKey := client.ObjectKey{Name: string(policy.GetTargetRef().Name), Namespace: namespace}

return g.gatewaysIndex[gwKey]
}

func (g *gatewayAPITopology) RouteFromPolicy(policy KuadrantPolicy) *gatewayapiv1beta1.HTTPRoute {
// Return route targeted by the kuadrant policy

if !IsTargetRefHTTPRoute(policy.GetTargetRef()) {
return nil
}

namespace := string(ptr.Deref(policy.GetTargetRef().Namespace, policy.GetWrappedNamespace()))

routeKey := client.ObjectKey{Name: string(policy.GetTargetRef().Name), Namespace: namespace}

return g.routesIndex[routeKey]
}

func (g *gatewayAPITopology) GetRouteParents(route *gatewayapiv1beta1.HTTPRoute) []*gatewayapiv1beta1.Gateway {
// TODO
return nil
}

func (k *KuadrantTopology) GetPolicyHTTPRoute(policy KuadrantPolicy) *gatewayapiv1beta1.HTTPRoute {
func (g *gatewayAPITopology) GetGateways() []*gatewayapiv1beta1.Gateway {
// TODO
return nil
}

func (k *KuadrantTopology) GetFreeRoutes(gateway *gatewayapiv1beta1.Gateway) []*gatewayapiv1beta1.HTTPRoute {
func (g *gatewayAPITopology) GetGatewayRoutes(gateway *gatewayapiv1beta1.Gateway) []*gatewayapiv1beta1.HTTPRoute {
// TODO
return nil
}

0 comments on commit 97672db

Please sign in to comment.