From 359efad639aada69a19ccba9bd6f13d6d303958f Mon Sep 17 00:00:00 2001 From: Gaurav Ghildiyal Date: Tue, 2 Jul 2024 21:26:29 -0700 Subject: [PATCH] Add tests and related cleanups --- gwctl/cmd/subcommand.go | 2 +- gwctl/pkg/printer/backends.go | 4 +- gwctl/pkg/printer/common.go | 38 ++- gwctl/pkg/printer/gatewayclasses.go | 4 +- gwctl/pkg/printer/gateways.go | 4 +- gwctl/pkg/printer/httproutes.go | 99 +----- gwctl/pkg/printer/httproutes_test.go | 38 ++- gwctl/pkg/printer/namespace.go | 4 +- gwctl/pkg/resourcediscovery/resourcemodel.go | 67 +---- .../resourcediscovery/resourcemodel_test.go | 283 ++++++++++++++++++ 10 files changed, 379 insertions(+), 164 deletions(-) create mode 100644 gwctl/pkg/resourcediscovery/resourcemodel_test.go diff --git a/gwctl/cmd/subcommand.go b/gwctl/cmd/subcommand.go index 964cdde341..aabb7dc8bc 100644 --- a/gwctl/cmd/subcommand.go +++ b/gwctl/cmd/subcommand.go @@ -314,7 +314,7 @@ func runGetOrDescribeHTTPRoutes(f cmdutils.Factory, o *getOrDescribeOptions) { handleErrOrExitWithMsg(err, "failed to discover HTTPRoute resources") realClock := clock.RealClock{} - httpRoutesPrinter := &printer.HTTPRoutesPrinter{Writer: o.out, Clock: realClock} + httpRoutesPrinter := &printer.HTTPRoutesPrinter{Writer: o.out, Clock: realClock, EventFetcher: discoverer} if o.cmdName == commandNameGet { printer.Print(httpRoutesPrinter, resourceModel, o.outputFormat) } else { diff --git a/gwctl/pkg/printer/backends.go b/gwctl/pkg/printer/backends.go index bb7f380462..02fd407016 100644 --- a/gwctl/pkg/printer/backends.go +++ b/gwctl/pkg/printer/backends.go @@ -134,8 +134,8 @@ func (bp *BackendsPrinter) PrintDescribeView(resourceModel *resourcediscovery.Re pairs = append(pairs, &DescriberKV{Key: "ReferencedByRoutes", Value: routes}) // DirectlyAttachedPolicies - policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(backendNode.Policies) - pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPolicyRefsToTable(policyRefs)}) + policies := SortByString(maps.Values(backendNode.Policies)) + pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPoliciesToRefsTable(policies, false)}) // EffectivePolicies if len(backendNode.EffectivePolicies) != 0 { diff --git a/gwctl/pkg/printer/common.go b/gwctl/pkg/printer/common.go index f8dcdad548..4e9ac26749 100644 --- a/gwctl/pkg/printer/common.go +++ b/gwctl/pkg/printer/common.go @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" - "sigs.k8s.io/gateway-api/gwctl/pkg/common" + "sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery" ) // DescriberKV stores key-value pairs that are used with Describing a resource. @@ -154,20 +154,42 @@ func convertEventsSliceToTable(events []corev1.Event, clock clock.Clock) *Table return table } -func convertPolicyRefsToTable(policyRefs []common.ObjRef) *Table { +func convertPoliciesToRefsTable(policies []*resourcediscovery.PolicyNode, includeTarget bool) *Table { table := &Table{ ColumnNames: []string{"Type", "Name"}, UseSeparator: true, } - for _, policyRef := range policyRefs { - name := policyRef.Name - if policyRef.Namespace != "" { - name = fmt.Sprintf("%v/%v", policyRef.Namespace, name) + if includeTarget { + table.ColumnNames = append(table.ColumnNames, "Target Kind", "Target Name") + } + + for _, policyNode := range policies { + policyType := fmt.Sprintf("%v.%v", policyNode.Policy.Unstructured().GroupVersionKind().Kind, policyNode.Policy.Unstructured().GroupVersionKind().Group) + + policyName := policyNode.Policy.Unstructured().GetName() + if ns := policyNode.Policy.Unstructured().GetNamespace(); ns != "" { + policyName = fmt.Sprintf("%v/%v", ns, policyName) + } + + targetKind := policyNode.Policy.TargetRef().Kind + + targetName := policyNode.Policy.TargetRef().Name + if ns := policyNode.Policy.TargetRef().Namespace; ns != "" { + targetName = fmt.Sprintf("%v/%v", ns, targetName) } + row := []string{ - fmt.Sprintf("%v.%v", policyRef.Kind, policyRef.Group), // Type - name, // Name + policyType, // Type + policyName, // Name } + + if includeTarget { + row = append(row, + targetKind, // Target Kind + targetName, // Target Name + ) + } + table.Rows = append(table.Rows, row) } return table diff --git a/gwctl/pkg/printer/gatewayclasses.go b/gwctl/pkg/printer/gatewayclasses.go index c2c2ae6a0d..9cbc4fbeec 100644 --- a/gwctl/pkg/printer/gatewayclasses.go +++ b/gwctl/pkg/printer/gatewayclasses.go @@ -104,8 +104,8 @@ func (gcp *GatewayClassesPrinter) PrintDescribeView(resourceModel *resourcedisco } // DirectlyAttachedPolicies - policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(gatewayClassNode.Policies) - pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPolicyRefsToTable(policyRefs)}) + policies := SortByString(maps.Values(gatewayClassNode.Policies)) + pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPoliciesToRefsTable(policies, false)}) // Events eventList := gcp.EventFetcher.FetchEventsFor(context.Background(), gatewayClassNode.GatewayClass) diff --git a/gwctl/pkg/printer/gateways.go b/gwctl/pkg/printer/gateways.go index 243c1159e3..71ca591dd6 100644 --- a/gwctl/pkg/printer/gateways.go +++ b/gwctl/pkg/printer/gateways.go @@ -140,8 +140,8 @@ func (gp *GatewaysPrinter) PrintDescribeView(resourceModel *resourcediscovery.Re pairs = append(pairs, &DescriberKV{Key: "AttachedRoutes", Value: attachedRoutes}) // DirectlyAttachedPolicies - policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(gatewayNode.Policies) - pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPolicyRefsToTable(policyRefs)}) + policies := SortByString(maps.Values(gatewayNode.Policies)) + pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPoliciesToRefsTable(policies, false)}) // EffectivePolicies if len(gatewayNode.EffectivePolicies) != 0 { diff --git a/gwctl/pkg/printer/httproutes.go b/gwctl/pkg/printer/httproutes.go index 56b2f928b4..cf8f1c4050 100644 --- a/gwctl/pkg/printer/httproutes.go +++ b/gwctl/pkg/printer/httproutes.go @@ -17,9 +17,9 @@ limitations under the License. package printer import ( + "context" "fmt" "io" - "sort" "strings" "golang.org/x/exp/maps" @@ -33,7 +33,8 @@ var _ Printer = (*HTTPRoutesPrinter)(nil) type HTTPRoutesPrinter struct { io.Writer - Clock clock.Clock + Clock clock.Clock + EventFetcher eventFetcher } func (hp *HTTPRoutesPrinter) GetPrintableNodes(resourceModel *resourcediscovery.ResourceModel) []NodeResource { @@ -95,13 +96,15 @@ func (hp *HTTPRoutesPrinter) PrintDescribeView(resourceModel *resourcediscovery. index++ metadata := httpRouteNode.HTTPRoute.ObjectMeta.DeepCopy() - resetMetadataFields(metadata) - - namespace := handleDefaultNamespace(httpRouteNode.HTTPRoute.Namespace) + metadata.Labels = nil + metadata.Annotations = nil + metadata.Name = "" + metadata.Namespace = "" + metadata.ManagedFields = nil pairs := []*DescriberKV{ {"Name", httpRouteNode.HTTPRoute.GetName()}, - {"Namespace", namespace}, + {"Namespace", httpRouteNode.HTTPRoute.Namespace}, {"Label", httpRouteNode.HTTPRoute.Labels}, {"Annotations", httpRouteNode.HTTPRoute.Annotations}, {"APIVersion", httpRouteNode.HTTPRoute.APIVersion}, @@ -112,85 +115,12 @@ func (hp *HTTPRoutesPrinter) PrintDescribeView(resourceModel *resourcediscovery. } // DirectlyAttachedPolicies - directlyAttachedPolicies := &Table{ - ColumnNames: []string{"Type", "Name"}, - UseSeparator: true, - } - - for _, policyNode := range httpRouteNode.Policies { - if policyNode.Policy.IsDirect() { - policyNamespace := handleDefaultNamespace(policyNode.Policy.TargetRef().Namespace) - - row := []string{ - // Type - fmt.Sprintf("%v.%v", policyNode.Policy.Unstructured().GroupVersionKind().Kind, policyNode.Policy.Unstructured().GroupVersionKind().Group), - // Name - fmt.Sprintf("%v/%v", policyNamespace, policyNode.Policy.Unstructured().GetName()), - } - - directlyAttachedPolicies.Rows = append(directlyAttachedPolicies.Rows, row) - } - } - - if len(directlyAttachedPolicies.Rows) != 0 { - pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: directlyAttachedPolicies}) - } + policies := SortByString(maps.Values(httpRouteNode.Policies)) + pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPoliciesToRefsTable(policies, false)}) // InheritedPolicies - if len(httpRouteNode.InheritedPolicies) != 0 { - inheritedPolicies := &Table{ - ColumnNames: []string{"Type", "Name", "Target Kind", "Target Name"}, - UseSeparator: true, - } - - for _, policyNode := range httpRouteNode.InheritedPolicies { - policyNamespace := handleDefaultNamespace(policyNode.Policy.Unstructured().GetNamespace()) - - row := []string{ - // Type - fmt.Sprintf( - "%v.%v", - policyNode.Policy.Unstructured().GroupVersionKind().Kind, - policyNode.Policy.Unstructured().GroupVersionKind().Group, - ), - // Name - fmt.Sprintf("%v/%v", policyNamespace, policyNode.Policy.Unstructured().GetName()), - // Target Kind - policyNode.Policy.TargetRef().Kind, - } - - // Target Name - switch policyNode.Policy.TargetRef().Kind { - - case "Namespace": - row = append(row, handleDefaultNamespace(policyNode.Policy.TargetRef().Name)) - - case "GatewayClass": - row = append(row, policyNode.Policy.TargetRef().Name) - - default: - // handle namespaced objects - targetRefNamespace := handleDefaultNamespace(policyNode.Policy.TargetRef().Namespace) - name := fmt.Sprintf("%v/%v", targetRefNamespace, policyNode.Policy.TargetRef().Name) - - row = append(row, name) - } - - // Sort inheritedPolices on the basis of Type and Name - sort.Slice(inheritedPolicies.Rows, func(i, j int) bool { - // Compare the Type of inheritedPolicies - if inheritedPolicies.Rows[i][0] != inheritedPolicies.Rows[j][0] { - return inheritedPolicies.Rows[i][0] < inheritedPolicies.Rows[j][0] - } - // If inheritedPolicies are of same Type, compare Names - return inheritedPolicies.Rows[i][1] < inheritedPolicies.Rows[j][1] - }) - - inheritedPolicies.Rows = append(inheritedPolicies.Rows, row) - } - - pairs = append(pairs, &DescriberKV{Key: "InheritedPolicies", Value: inheritedPolicies}) - } + inheritedPolicies := SortByString(maps.Values(httpRouteNode.InheritedPolicies)) + pairs = append(pairs, &DescriberKV{Key: "InheritedPolicies", Value: convertPoliciesToRefsTable(inheritedPolicies, true)}) // EffectivePolices if len(httpRouteNode.EffectivePolicies) != 0 { @@ -203,7 +133,8 @@ func (hp *HTTPRoutesPrinter) PrintDescribeView(resourceModel *resourcediscovery. } // Events - pairs = append(pairs, &DescriberKV{Key: "Events", Value: convertEventsSliceToTable(httpRouteNode.Events, hp.Clock)}) + eventList := hp.EventFetcher.FetchEventsFor(context.Background(), httpRouteNode.HTTPRoute) + pairs = append(pairs, &DescriberKV{Key: "Events", Value: convertEventsSliceToTable(eventList.Items, hp.Clock)}) Describe(hp, pairs) diff --git a/gwctl/pkg/printer/httproutes_test.go b/gwctl/pkg/printer/httproutes_test.go index 996897c590..1d54aaadae 100644 --- a/gwctl/pkg/printer/httproutes_test.go +++ b/gwctl/pkg/printer/httproutes_test.go @@ -295,6 +295,10 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { fakeClock := testingclock.NewFakeClock(time.Now()) objects := []runtime.Object{ &gatewayv1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gatewayv1.GroupVersion.String(), + Kind: "GatewayClass", + }, ObjectMeta: metav1.ObjectMeta{ Name: "foo-gatewayclass", }, @@ -330,6 +334,10 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { }, &gatewayv1.Gateway{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gatewayv1.GroupVersion.String(), + Kind: "Gateway", + }, ObjectMeta: metav1.ObjectMeta{ Name: "foo-gateway", Namespace: "default", @@ -343,7 +351,8 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { "apiVersion": "foo.com/v1", "kind": "HealthCheckPolicy", "metadata": map[string]interface{}{ - "name": "health-check-gateway", + "name": "health-check-gateway", + "namespace": "default", }, "spec": map[string]interface{}{ "override": map[string]interface{}{ @@ -364,8 +373,13 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { }, &gatewayv1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gatewayv1.GroupVersion.String(), + Kind: "HTTPRoute", + }, ObjectMeta: metav1.ObjectMeta{ - Name: "foo-httproute", + Name: "foo-httproute", + Namespace: "default", }, Spec: gatewayv1.HTTPRouteSpec{ CommonRouteSpec: gatewayv1.CommonRouteSpec{ @@ -382,7 +396,8 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { "apiVersion": "bar.com/v1", "kind": "TimeoutPolicy", "metadata": map[string]interface{}{ - "name": "timeout-policy-httproute", + "name": "timeout-policy-httproute", + "namespace": "default", }, "spec": map[string]interface{}{ "condition": "path=/def", @@ -463,8 +478,9 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { } hp := &HTTPRoutesPrinter{ - Writer: buff, - Clock: fakeClock, + Writer: buff, + Clock: fakeClock, + EventFetcher: discoverer, } hp.PrintDescribeView(resourceModel) @@ -474,8 +490,8 @@ Name: foo-httproute Namespace: default Label: null Annotations: null -APIVersion: "" -Kind: "" +APIVersion: gateway.networking.k8s.io/v1 +Kind: HTTPRoute Metadata: creationTimestamp: null resourceVersion: "999" @@ -491,10 +507,10 @@ DirectlyAttachedPolicies: ---- ---- TimeoutPolicy.bar.com default/timeout-policy-httproute InheritedPolicies: - Type Name Target Kind Target Name - ---- ---- ----------- ----------- - HealthCheckPolicy.foo.com default/health-check-gatewayclass GatewayClass foo-gatewayclass - HealthCheckPolicy.foo.com default/health-check-gateway Gateway default/foo-gateway + Type Name Target Kind Target Name + ---- ---- ----------- ----------- + HealthCheckPolicy.foo.com health-check-gatewayclass GatewayClass foo-gatewayclass + HealthCheckPolicy.foo.com default/health-check-gateway Gateway default/foo-gateway EffectivePolicies: default/foo-gateway: HealthCheckPolicy.foo.com: diff --git a/gwctl/pkg/printer/namespace.go b/gwctl/pkg/printer/namespace.go index 50072ab508..78b71188e3 100644 --- a/gwctl/pkg/printer/namespace.go +++ b/gwctl/pkg/printer/namespace.go @@ -92,8 +92,8 @@ func (nsp *NamespacesPrinter) PrintDescribeView(resourceModel *resourcediscovery } // DirectlyAttachedPolicies - policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(namespaceNode.Policies) - pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPolicyRefsToTable(policyRefs)}) + policies := SortByString(maps.Values(namespaceNode.Policies)) + pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPoliciesToRefsTable(policies, false)}) // Events eventList := nsp.EventFetcher.FetchEventsFor(context.Background(), namespaceNode.Namespace) diff --git a/gwctl/pkg/resourcediscovery/resourcemodel.go b/gwctl/pkg/resourcediscovery/resourcemodel.go index dcd3ee2511..831d75bcb8 100644 --- a/gwctl/pkg/resourcediscovery/resourcemodel.go +++ b/gwctl/pkg/resourcediscovery/resourcemodel.go @@ -18,11 +18,11 @@ package resourcediscovery import ( "fmt" + "maps" "sort" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "sigs.k8s.io/gateway-api/gwctl/pkg/common" "sigs.k8s.io/gateway-api/gwctl/pkg/policymanager" corev1 "k8s.io/api/core/v1" @@ -510,22 +510,6 @@ func convertPoliciesMapToSlice(policies map[policyID]*PolicyNode) []policymanage return result } -// ConvertPoliciesMapToPolicyRefs returns the Object references of all given -// policies. Note that these are not the value of targetRef within the Policies -// but rather the reference to the Policy object itself. -func ConvertPoliciesMapToPolicyRefs(policies map[policyID]*PolicyNode) []common.ObjRef { - var result []common.ObjRef - for _, policyNode := range policies { - result = append(result, common.ObjRef{ - Group: policyNode.Policy.Unstructured().GroupVersionKind().Group, - Kind: policyNode.Policy.Unstructured().GroupVersionKind().Kind, - Name: policyNode.Policy.Unstructured().GetName(), - Namespace: policyNode.Policy.Unstructured().GetNamespace(), - }) - } - return result -} - // calculateInheritedPolicies calculates the inherited polices for all // Gateways, HTTRoutes, and Backends in ResourceModel. func (rm *ResourceModel) calculateInheritedPolicies() error { @@ -548,13 +532,13 @@ func (rm *ResourceModel) calculateInheritedPoliciesForGateways() error { result := make(map[policyID]*PolicyNode) // Policies inherited from Gateway's namespace. - policiesInheritedFromNamespace := extractInheritablePolicies(gatewayNode.Namespace.Policies) - mergePolicyMaps(result, policiesInheritedFromNamespace) + policiesInheritedFromNamespace := filterInheritablePolicies(gatewayNode.Namespace.Policies) + maps.Copy(result, policiesInheritedFromNamespace) // Policies inherited from GatewayClass. if gatewayNode.GatewayClass != nil { - policiesInheritedFromGatewayClass := extractInheritablePolicies(gatewayNode.GatewayClass.Policies) - mergePolicyMaps(result, policiesInheritedFromGatewayClass) + policiesInheritedFromGatewayClass := filterInheritablePolicies(gatewayNode.GatewayClass.Policies) + maps.Copy(result, policiesInheritedFromGatewayClass) } gatewayNode.InheritedPolicies = result @@ -569,25 +553,18 @@ func (rm *ResourceModel) calculateInheritedPoliciesForHTTPRoutes() error { result := make(map[policyID]*PolicyNode) // Policies inherited from HTTPRoute's namespace. - policiesInheritedFromNamespace := extractInheritablePolicies(httpRouteNode.Namespace.Policies) - mergePolicyMaps(result, policiesInheritedFromNamespace) + policiesInheritedFromNamespace := filterInheritablePolicies(httpRouteNode.Namespace.Policies) + maps.Copy(result, policiesInheritedFromNamespace) // Policies inherited from Gateways. - policiesInheritedFromGateways := make(map[policyID]*PolicyNode) - for _, gatewayNode := range httpRouteNode.Gateways { // Add policies inherited by GatewayNode. - mergePolicyMaps(policiesInheritedFromGateways, gatewayNode.InheritedPolicies) + maps.Copy(result, gatewayNode.InheritedPolicies) // Add inheritable policies directly applied to GatewayNode. - mergePolicyMaps( - policiesInheritedFromGateways, - extractInheritablePolicies(gatewayNode.Policies), - ) + maps.Copy(result, filterInheritablePolicies(gatewayNode.Policies)) } - mergePolicyMaps(result, policiesInheritedFromGateways) - httpRouteNode.InheritedPolicies = result } return nil @@ -600,32 +577,25 @@ func (rm *ResourceModel) calculateInheritedPoliciesForBackends() error { result := make(map[policyID]*PolicyNode) // Policies inherited from Backend's namespace. - policiesInheritedFromNamespace := extractInheritablePolicies(backendNode.Namespace.Policies) - mergePolicyMaps(result, policiesInheritedFromNamespace) + policiesInheritedFromNamespace := filterInheritablePolicies(backendNode.Namespace.Policies) + maps.Copy(result, policiesInheritedFromNamespace) // Policies inherited from HTTPRoutes. - policiesInheritedFromHTTPRoutes := make(map[policyID]*PolicyNode) - for _, httpRouteNode := range backendNode.HTTPRoutes { // Add policies inherited by HTTPRouteNode. - mergePolicyMaps(policiesInheritedFromHTTPRoutes, httpRouteNode.InheritedPolicies) + maps.Copy(result, httpRouteNode.InheritedPolicies) // Add inheritable policies directly applied to HTTPRouteNode. - mergePolicyMaps( - policiesInheritedFromHTTPRoutes, - extractInheritablePolicies(httpRouteNode.Policies), - ) + maps.Copy(result, filterInheritablePolicies(httpRouteNode.Policies)) } - mergePolicyMaps(result, policiesInheritedFromHTTPRoutes) - backendNode.InheritedPolicies = result } return nil } -// extractInheritablePolicies filters and returns policies which can be inherited. -func extractInheritablePolicies(policies map[policyID]*PolicyNode) map[policyID]*PolicyNode { +// filterInheritablePolicies filters and returns policies which can be inherited. +func filterInheritablePolicies(policies map[policyID]*PolicyNode) map[policyID]*PolicyNode { inheritablePolicies := make(map[policyID]*PolicyNode) for policyID, policyNode := range policies { @@ -636,10 +606,3 @@ func extractInheritablePolicies(policies map[policyID]*PolicyNode) map[policyID] return inheritablePolicies } - -// mergePolicyMaps merges the source map into the destination map. -func mergePolicyMaps(dest, src map[policyID]*PolicyNode) { - for id, node := range src { - dest[id] = node - } -} diff --git a/gwctl/pkg/resourcediscovery/resourcemodel_test.go b/gwctl/pkg/resourcediscovery/resourcemodel_test.go new file mode 100644 index 0000000000..1a7f55ded7 --- /dev/null +++ b/gwctl/pkg/resourcediscovery/resourcemodel_test.go @@ -0,0 +1,283 @@ +/* +Copyright 2024 The Kubernetes 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 resourcediscovery + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + apimachinerytypes "k8s.io/apimachinery/pkg/types" + + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/gwctl/pkg/common" + "sigs.k8s.io/gateway-api/gwctl/pkg/utils" +) + +func TestResourceModel_calculateInheritedPolicies(t *testing.T) { + testcases := []struct { + name string + objects []runtime.Object + + wantInheritedPoliciesForGateways []apimachinerytypes.NamespacedName + wantInheritedPoliciesForHTTPRoutes []apimachinerytypes.NamespacedName + wantInheritedPoliciesForBackends []apimachinerytypes.NamespacedName + }{ + { + name: "normal", + objects: []runtime.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-gatewayclass", + }, + }, + common.NamespaceForTest("default"), + &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-gateway", + Namespace: "default", + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "foo-gatewayclass", + }, + }, + &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-httproute", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Kind: common.PtrTo(gatewayv1.Kind("Gateway")), + Group: common.PtrTo(gatewayv1.Group("gateway.networking.k8s.io")), + Name: "foo-gateway", + }}, + }, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Kind: common.PtrTo(gatewayv1.Kind("Service")), + Name: "foo-svc", + Port: common.PtrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-svc", + Namespace: "default", + }, + }, + + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "timeoutpolicies.bar.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "Inherited", + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.ClusterScoped, + Group: "bar.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "timeoutpolicies", + Kind: "TimeoutPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bar.com/v1", + "kind": "TimeoutPolicy", + "metadata": map[string]interface{}{ + "name": "timeout-policy-on-namespace", + }, + "spec": map[string]interface{}{ + "targetRef": map[string]interface{}{ + "kind": "Namespace", + "name": "default", + }, + }, + }, + }, + + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "healthcheckpolicies.bar.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "Inherited", + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.NamespaceScoped, + Group: "bar.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "healthcheckpolicies", + Kind: "HealthCheckPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bar.com/v1", + "kind": "HealthCheckPolicy", + "metadata": map[string]interface{}{ + "name": "health-check-policy-on-httproute", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "targetRef": map[string]interface{}{ + "group": "gateway.networking.k8s.io", + "kind": "HTTPRoute", + "name": "foo-httproute", + }, + }, + }, + }, + + // Direct Policies should not appear in inherited policies. + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tlspolicies.bar.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "Direct", + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.NamespaceScoped, + Group: "bar.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "tlspolicies", + Kind: "TLSPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bar.com/v1", + "kind": "TLSPolicy", + "metadata": map[string]interface{}{ + "name": "tls-policy-on-httproute", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "targetRef": map[string]interface{}{ + "group": "gateway.networking.k8s.io", + "kind": "HTTPRoute", + "name": "foo-httproute", + }, + }, + }, + }, + }, + wantInheritedPoliciesForGateways: []apimachinerytypes.NamespacedName{ + {Name: "timeout-policy-on-namespace"}, + }, + wantInheritedPoliciesForHTTPRoutes: []apimachinerytypes.NamespacedName{ + {Name: "timeout-policy-on-namespace"}, + }, + wantInheritedPoliciesForBackends: []apimachinerytypes.NamespacedName{ + {Name: "timeout-policy-on-namespace"}, + {Namespace: "default", Name: "health-check-policy-on-httproute"}, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + k8sClients := common.MustClientsForTest(t, tc.objects...) + policyManager := utils.MustPolicyManagerForTest(t, k8sClients) + discoverer := Discoverer{ + K8sClients: k8sClients, + PolicyManager: policyManager, + } + + // TODO: Decouple this test from dependency on + // DiscoverResourcesForBackend() and only invoke the function under + // test viz. calculateInheritedPolicies() + resourceModel, err := discoverer.DiscoverResourcesForBackend(Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + var gotInheritedPoliciesForGateways []apimachinerytypes.NamespacedName + for _, gatewayNode := range resourceModel.Gateways { + for _, policyNode := range gatewayNode.InheritedPolicies { + gotInheritedPoliciesForGateways = append(gotInheritedPoliciesForGateways, apimachinerytypes.NamespacedName{ + Namespace: policyNode.Policy.Unstructured().GetNamespace(), + Name: policyNode.Policy.Unstructured().GetName(), + }) + } + } + var gotInheritedPoliciesForHTTPRoutes []apimachinerytypes.NamespacedName + for _, httpRouteNode := range resourceModel.HTTPRoutes { + for _, policyNode := range httpRouteNode.InheritedPolicies { + gotInheritedPoliciesForHTTPRoutes = append(gotInheritedPoliciesForHTTPRoutes, apimachinerytypes.NamespacedName{ + Namespace: policyNode.Policy.Unstructured().GetNamespace(), + Name: policyNode.Policy.Unstructured().GetName(), + }) + } + } + var gotInheritedPoliciesForBackends []apimachinerytypes.NamespacedName + for _, backendNode := range resourceModel.Backends { + for _, policyNode := range backendNode.InheritedPolicies { + gotInheritedPoliciesForBackends = append(gotInheritedPoliciesForBackends, apimachinerytypes.NamespacedName{ + Namespace: policyNode.Policy.Unstructured().GetNamespace(), + Name: policyNode.Policy.Unstructured().GetName(), + }) + } + } + + lessFunc := func(a, b apimachinerytypes.NamespacedName) bool { + return fmt.Sprintf("%s/%s", a.Namespace, a.Name) < fmt.Sprintf("%s/%s", b.Namespace, b.Name) + } + + if diff := cmp.Diff(tc.wantInheritedPoliciesForGateways, gotInheritedPoliciesForGateways, cmpopts.SortSlices(lessFunc)); diff != "" { + t.Errorf("Unexpected diff in inheritedPoliciesForGateways: (-want, +got):\n%v", diff) + } + if diff := cmp.Diff(tc.wantInheritedPoliciesForHTTPRoutes, gotInheritedPoliciesForHTTPRoutes, cmpopts.SortSlices(lessFunc)); diff != "" { + t.Errorf("Unexpected diff in inheritedPoliciesForHTTPRoutes: (-want, +got):\n%v", diff) + } + if diff := cmp.Diff(tc.wantInheritedPoliciesForBackends, gotInheritedPoliciesForBackends, cmpopts.SortSlices(lessFunc)); diff != "" { + t.Errorf("Unexpected diff in inheritedPoliciesForBackends: (-want, +got):\n%v", diff) + } + }) + } +}