Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

http-route-controller: watch ReferencePolicy lifecycle #156

Merged
merged 47 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c45e435
refpolicy: add ReferencePolicy watch and draft of filtering logic to …
mikemorris Apr 20, 2022
4a9a922
gatewayclient: add GetHTTPRoutes
mikemorris Apr 20, 2022
a70a80a
refpolicy: comment out unused logic, use gatewayclient.GetHTTPRoutes
mikemorris Apr 20, 2022
b34ccc2
gatewayclient: add mock for GetHTTPRoutes
mikemorris Apr 20, 2022
5cfb359
gatewayclient: return []HTTPRoute from GetHTTPRoutes instead of HTTPR…
mikemorris Apr 20, 2022
82e9b75
fixup: use []HTTPRoute instead of HTTPRouteList.Items
mikemorris Apr 20, 2022
44758a0
gatewayclient: add test for GetHTTPRoutes
mikemorris Apr 20, 2022
c907b46
refpolicy: update comment on how events are mapped to watch handler
mikemorris Apr 21, 2022
ba59367
refpolicy: begin adding test for route controller watch
mikemorris Apr 21, 2022
4a52f31
refpolicy: add failing referencePolicyToRouteRequests test
mikemorris Apr 21, 2022
26c3558
refpolicy: start adding logging for referencePolicyToRouteRequests
mikemorris Apr 21, 2022
dbb3f36
refpolicy: add FIXME comment to failing test
mikemorris Apr 21, 2022
af69251
fixup
mikemorris Apr 21, 2022
7aa53be
refpolicy: always return []reconcile.Request from referencePolicyTpRo…
mikemorris Apr 21, 2022
285f531
refpolicy: add FIXMEs to convert to client.MatchingLabels
mikemorris Apr 21, 2022
f3e8b48
refpolicy: get test passing by triggering reconciliation only on HTTP…
mikemorris Apr 21, 2022
be82195
fixup field selector
mikemorris Apr 21, 2022
fae9262
gatewayclient: fixup test for GetHTTPRoutesInNamespace
mikemorris Apr 21, 2022
7340e4c
httproutereconciler: comment out unused toSelectors
mikemorris Apr 21, 2022
7acd89d
Update internal/k8s/controllers/http_route_controller_test.go
mikemorris Apr 21, 2022
286297c
Update internal/k8s/controllers/http_route_controller_test.go
mikemorris Apr 21, 2022
9efb16e
e2e: create two namespaces in stack
mikemorris Apr 22, 2022
d0819fd
e2e: add beginning of ReferencePolicy lifecycle test
mikemorris Apr 22, 2022
acab615
fixup: routeOneName -> routeName
mikemorris Apr 22, 2022
0453a5a
e2e: implement rest of ReferencePolicy lifecycle test
mikemorris Apr 22, 2022
98309ba
fixup: fix syntax errors in createConditionCheck
mikemorris Apr 22, 2022
d92b2be
fixup: fix route namespace for httpRouteStatusCheck
mikemorris Apr 22, 2022
b2528b1
e2e: add FIXME comments to work around controller deleting invalid route
mikemorris Apr 22, 2022
07b7d04
fixup: comment out unused httpRouteStatusCheck
mikemorris Apr 22, 2022
a37cfd1
e2e: add ReferencePolicy to schema known types
mikemorris Apr 22, 2022
ecab6ca
fixup: add ReferencePolicy metadata.name
mikemorris Apr 22, 2022
43c4ab4
cleanup: refactor condition checks to use createConditionCheck
mikemorris Apr 22, 2022
67e05e2
e2e: add debug logging for route creation
mikemorris Apr 22, 2022
46e9ea5
e2e: add gateway namespace to parentRef for route in ReferencePolicy …
mikemorris Apr 22, 2022
42a8756
e2e: clean up debugging, add createConditionsCheck
mikemorris Apr 22, 2022
18f5902
e2e: refactor checks into createConditionsCheck
mikemorris Apr 22, 2022
5f1545e
httproutecontroller: switch reconcile request helper signatures to ta…
mikemorris Apr 22, 2022
5cb3cbc
httproutecontroller: log error in getReferencePolicyObjectsFrom
mikemorris Apr 22, 2022
8eb2e30
httproutecontroller: plumb context through HTTPRouteController struct
mikemorris Apr 22, 2022
59be45d
e2e: revert changes to SetNamespace and Namespace environment helpers
mikemorris Apr 22, 2022
185fdf6
e2e: add HTTPReferencePolicyPort
mikemorris Apr 22, 2022
768af2a
bugfix: check for correct conditionReady, uncovered by fixing conditi…
mikemorris Apr 22, 2022
1742d81
Update internal/k8s/controllers/http_route_controller.go
mikemorris Apr 22, 2022
8836a01
Update internal/k8s/controllers/http_route_controller.go
mikemorris Apr 25, 2022
d778557
httproutecontroller: remove toSelector ReferencePolicy filtering logic
mikemorris Apr 25, 2022
dc8e1cd
httproutecontroller: remove unused groupKindToFieldSelector func
mikemorris Apr 25, 2022
6484db6
changelog: add changelog entry for HTTPRouteController ReferencePolic…
mikemorris Apr 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions internal/k8s/controllers/http_route_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ package controllers
import (
"context"

"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
gateway "sigs.k8s.io/gateway-api/apis/v1alpha2"

"github.com/hashicorp/consul-api-gateway/internal/k8s/gatewayclient"
Expand Down Expand Up @@ -53,5 +60,126 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&gateway.HTTPRoute{}).
Watches(
&source.Kind{Type: &gateway.ReferencePolicy{}},
handler.EnqueueRequestsFromMapFunc(r.referencePolicyToRouteRequests),
).
nathancoleman marked this conversation as resolved.
Show resolved Hide resolved
Complete(gatewayclient.NewRequeueingMiddleware(r.Log, r))
}

// For UpdateEvents which contain both a new and old object, this transformation
// function is run on both objects and both sets of Requests are enqueued.
//
// This is needed to reconcile any objects matched by both current and prior
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
// state in case a ReferencePolicy has been modified to revoke permission from a
// namespace or to a service
//
// It may be possible to improve performance here by filtering Routes by
// BackendRefs selectable by the To fields, but currently we just revalidate
// all Routes allowed in the From Namespaces
func (r *HTTPRouteReconciler) referencePolicyToRouteRequests(object client.Object) []reconcile.Request {
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Is there a safer way I could typecheck this with
// object.GetObjectKind() or something before casting?
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
refPolicy := *object.(*gateway.ReferencePolicy)
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
r.Log.Info("event for ReferencePolicy", "object", refPolicy)

routes := r.getRoutesAffectedByReferencePolicy(refPolicy)
requests := []reconcile.Request{}

for _, route := range routes {
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: route.Name,
Namespace: route.Namespace,
},
})
}

return requests
}

func (r *HTTPRouteReconciler) getRoutesAffectedByReferencePolicy(refPolicy gateway.ReferencePolicy) []gateway.HTTPRoute {
matches := []gateway.HTTPRoute{}

// toSelectors := []fields.Selector{}
// for _, to := range refPolicy.Spec.To {
// // When empty, the Kubernetes core API group is inferred.
// group := "core/v1"
// if to.Group != "" {
// group = string(to.Group)
// }

// toSelectors = append(toSelectors, groupKindToFieldSelector(schema.GroupKind{
// Group: group,
// Kind: string(to.Kind),
// }))
// }
mikemorris marked this conversation as resolved.
Show resolved Hide resolved

routes := r.getReferencePolicyObjectsFrom(refPolicy)

// TODO: match only routes with BackendRefs selectable by a
// ReferencePolicyTo instead of appending all routes. This seems expensive,
// so not sure if it would actually improve performance or not.
matches = append(matches, routes...)

// for _, route := range routes {
// routeMatched := false

// // TODO: should this use reflection to handle xRoute types? seems expensive
// for _, rule := range route.Spec.Rules {
// for range rule.BackendRefs {
// // Check if backendRef.BackendObjectReference is selectable by
// // the requirements in any refPolicy.Spec.To
// for _, selector := range toSelectors {
// if selector.Matches(fields.Set{
// "kind": backendRef.BackendObjectReference.Kind
// "metadata.name": backendRef.BackendObjectReference.Name
// }) {
// routeMatched = true
// matches = append(matches, route)

// // Exit toSelectors loop early if route has already been matched
// if routeMatched {
// break
// }
// }
// }

// // Exit BackendRefs loop early if route has already been matched
// if routeMatched {
// break
// }
// }

// // Exit Rules loop early if route has already been matched
// if routeMatched {
// break
// }
// }
// }

return matches
}

func (r *HTTPRouteReconciler) getReferencePolicyObjectsFrom(refPolicy gateway.ReferencePolicy) []gateway.HTTPRoute {
matches := []gateway.HTTPRoute{}

for _, from := range refPolicy.Spec.From {
// TODO: search by from.Group and from.Kind instead of assuming HTTPRoute
routes, err := r.Client.GetHTTPRoutesInNamespace(context.TODO(), string(from.Namespace))
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
// TODO: is there a better way to handle this error?
return matches
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
}

matches = append(matches, routes...)
}

return matches
}

func groupKindToFieldSelector(gk schema.GroupKind) fields.Selector {
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
return fields.SelectorFromSet(fields.Set{
"kind": gk.Kind,
})
}
111 changes: 111 additions & 0 deletions internal/k8s/controllers/http_route_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import (

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
gw "sigs.k8s.io/gateway-api/apis/v1alpha2"

"github.com/hashicorp/consul-api-gateway/internal/k8s/gatewayclient"
"github.com/hashicorp/consul-api-gateway/internal/k8s/gatewayclient/mocks"
reconcilerMocks "github.com/hashicorp/consul-api-gateway/internal/k8s/reconciler/mocks"
"github.com/hashicorp/go-hclog"

apigwv1alpha1 "github.com/hashicorp/consul-api-gateway/pkg/apis/v1alpha1"
)

var (
Expand Down Expand Up @@ -75,3 +85,104 @@ func TestHTTPRoute(t *testing.T) {
})
}
}

func TestHTTPRouteReferencePolicyToRouteRequests(t *testing.T) {
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()

ctrl := gomock.NewController(t)
defer ctrl.Finish()

serviceNamespace := gw.Namespace("namespace3")

backendObjRef := gw.BackendObjectReference{
Name: gw.ObjectName("service"),
Namespace: &serviceNamespace,
}

httpRouteSpec := gw.HTTPRouteSpec{
Rules: []gw.HTTPRouteRule{{
BackendRefs: []gw.HTTPBackendRef{{
BackendRef: gw.BackendRef{
BackendObjectReference: backendObjRef,
},
}},
}},
}

refPolicy := gw.ReferencePolicy{
TypeMeta: metav1.TypeMeta{Kind: "ReferencePolicy"},
ObjectMeta: metav1.ObjectMeta{Namespace: "namespace3"},
Spec: gw.ReferencePolicySpec{
From: []gw.ReferencePolicyFrom{{
Group: "gateway.networking.k8s.io",
Kind: "HTTPRoute",
Namespace: "namespace1",
}},
To: []gw.ReferencePolicyTo{{
Kind: "Service",
}},
},
}

gatewayclient := NewTestClient(
&gw.HTTPRouteList{
Items: []gw.HTTPRoute{
{
ObjectMeta: metav1.ObjectMeta{
Name: "httproute",
Namespace: "namespace1",
},
Spec: httpRouteSpec,
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "httproute",
Namespace: "namespace2",
},
Spec: httpRouteSpec,
},
},
},
&refPolicy,
)

controller := &HTTPRouteReconciler{
Client: gatewayclient,
Log: hclog.NewNullLogger(),
ControllerName: mockControllerName,
Manager: reconcilerMocks.NewMockReconcileManager(ctrl),
}

requests := controller.referencePolicyToRouteRequests(&refPolicy)

// FIXME: This should only request reconciliation for the one HTTPRoute in the
// allowed namespace
//
// require.Equal(t, 1, len(requests))
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
require.Equal(t, []reconcile.Request{{
NamespacedName: types.NamespacedName{
Name: "httproute",
Namespace: "namespace1",
},
}}, requests)
}

// FIXME: this should be refactored into a test utility package
func NewTestClient(list client.ObjectList, objects ...client.Object) gatewayclient.Client {
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(gw.AddToScheme(scheme))
apigwv1alpha1.RegisterTypes(scheme)

builder := fake.
NewClientBuilder().
WithScheme(scheme)
if list != nil {
builder = builder.WithLists(list)
}
if len(objects) > 0 {
builder = builder.WithObjects(objects...)
}
mikemorris marked this conversation as resolved.
Show resolved Hide resolved

return gatewayclient.New(builder.Build(), scheme, "")
}
10 changes: 10 additions & 0 deletions internal/k8s/gatewayclient/gatewayclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Client interface {
GetSecret(ctx context.Context, key types.NamespacedName) (*core.Secret, error)
GetService(ctx context.Context, key types.NamespacedName) (*core.Service, error)
GetHTTPRoute(ctx context.Context, key types.NamespacedName) (*gateway.HTTPRoute, error)
GetHTTPRoutesInNamespace(ctx context.Context, ns string) ([]gateway.HTTPRoute, error)
GetTCPRoute(ctx context.Context, key types.NamespacedName) (*gateway.TCPRoute, error)
GetMeshService(ctx context.Context, key types.NamespacedName) (*apigwv1alpha1.MeshService, error)
GetNamespace(ctx context.Context, key types.NamespacedName) (*core.Namespace, error)
Expand Down Expand Up @@ -250,6 +251,15 @@ func (g *gatewayClient) GetHTTPRoute(ctx context.Context, key types.NamespacedNa
return route, nil
}

// TODO: Make this generic over Group and Kind, returning []client.Object
func (g *gatewayClient) GetHTTPRoutesInNamespace(ctx context.Context, ns string) ([]gateway.HTTPRoute, error) {
routeList := &gateway.HTTPRouteList{}
if err := g.Client.List(ctx, routeList, client.InNamespace(ns)); err != nil {
return []gateway.HTTPRoute{}, NewK8sError(err)
}
return routeList.Items, nil
}
mikemorris marked this conversation as resolved.
Show resolved Hide resolved

func (g *gatewayClient) GetTCPRoute(ctx context.Context, key types.NamespacedName) (*gateway.TCPRoute, error) {
route := &gateway.TCPRoute{}
if err := g.Client.Get(ctx, key, route); err != nil {
Expand Down
19 changes: 19 additions & 0 deletions internal/k8s/gatewayclient/gatewayclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ func TestGetHTTPRoute(t *testing.T) {
require.NotNil(t, route)
}

func TestGetHTTPRoutesInNamespace(t *testing.T) {
t.Parallel()

gatewayclient := newTestClient(nil, &gateway.HTTPRoute{
ObjectMeta: meta.ObjectMeta{
Name: "httproute",
Namespace: "namespace1",
},
})

routes, err := gatewayclient.GetHTTPRoutesInNamespace(context.Background(), "namespace1")
require.NoError(t, err)
require.Equal(t, len(routes), 1)

routes, err = gatewayclient.GetHTTPRoutesInNamespace(context.Background(), "namespace2")
require.NoError(t, err)
require.Equal(t, len(routes), 0)
}

func TestGetGatewayClass(t *testing.T) {
t.Parallel()

Expand Down
15 changes: 15 additions & 0 deletions internal/k8s/gatewayclient/mocks/gatewayclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.