From 259d611e263b216b5eebc011c31b80e128f39ddc Mon Sep 17 00:00:00 2001 From: Tarun Duhan Date: Sun, 2 Jun 2024 05:07:49 +0530 Subject: [PATCH 1/4] gwctl: Add InheritedPolicies in resource discovery --- gwctl/pkg/resourcediscovery/discoverer.go | 12 ++ gwctl/pkg/resourcediscovery/nodes.go | 9 ++ gwctl/pkg/resourcediscovery/resourcemodel.go | 118 +++++++++++++++++++ 3 files changed, 139 insertions(+) diff --git a/gwctl/pkg/resourcediscovery/discoverer.go b/gwctl/pkg/resourcediscovery/discoverer.go index afa06c7a83..431c276406 100644 --- a/gwctl/pkg/resourcediscovery/discoverer.go +++ b/gwctl/pkg/resourcediscovery/discoverer.go @@ -158,6 +158,10 @@ func (d Discoverer) DiscoverResourcesForGateway(filter Filter) (*ResourceModel, d.discoverNamespaces(ctx, resourceModel) d.discoverPolicies(resourceModel) + if err := resourceModel.calculateInheritedPolicies(); err != nil { + return resourceModel, err + } + if err := resourceModel.calculateEffectivePolicies(); err != nil { return resourceModel, err } @@ -182,6 +186,10 @@ func (d Discoverer) DiscoverResourcesForHTTPRoute(filter Filter) (*ResourceModel d.discoverNamespaces(ctx, resourceModel) d.discoverPolicies(resourceModel) + if err := resourceModel.calculateInheritedPolicies(); err != nil { + return resourceModel, err + } + if err := resourceModel.calculateEffectivePolicies(); err != nil { return resourceModel, err } @@ -207,6 +215,10 @@ func (d Discoverer) DiscoverResourcesForBackend(filter Filter) (*ResourceModel, d.discoverNamespaces(ctx, resourceModel) d.discoverPolicies(resourceModel) + if err := resourceModel.calculateInheritedPolicies(); err != nil { + return resourceModel, err + } + if err := resourceModel.calculateEffectivePolicies(); err != nil { return resourceModel, err } diff --git a/gwctl/pkg/resourcediscovery/nodes.go b/gwctl/pkg/resourcediscovery/nodes.go index 5c6ad896a1..8d8082b031 100644 --- a/gwctl/pkg/resourcediscovery/nodes.go +++ b/gwctl/pkg/resourcediscovery/nodes.go @@ -166,6 +166,8 @@ type GatewayNode struct { HTTPRoutes map[httpRouteID]*HTTPRouteNode // Policies stores Policies directly applied to the Gateway. Policies map[policyID]*PolicyNode + // InheritedPolicies stores policies inherited by this Gateway. + InheritedPolicies map[policyID]*PolicyNode // EffectivePolicies reflects the effective policies applicable to this Gateway, // considering inheritance and hierarchy. EffectivePolicies map[policymanager.PolicyCrdID]policymanager.Policy @@ -178,6 +180,7 @@ func NewGatewayNode(gateway *gatewayv1.Gateway) *GatewayNode { Gateway: gateway, HTTPRoutes: make(map[httpRouteID]*HTTPRouteNode), Policies: make(map[policyID]*PolicyNode), + InheritedPolicies: make(map[policyID]*PolicyNode), EffectivePolicies: make(map[policymanager.PolicyCrdID]policymanager.Policy), Errors: []error{}, } @@ -208,6 +211,8 @@ type HTTPRouteNode struct { Backends map[backendID]*BackendNode // Policies stores Policies directly applied to the HTTPRoute. Policies map[policyID]*PolicyNode + // InheritedPolicies stores policies inherited by this HTTPRoute. + InheritedPolicies map[policyID]*PolicyNode // EffectivePolicies reflects the effective policies applicable to this // HTTPRoute, mapped per Gateway for context-specific enforcement. EffectivePolicies map[gatewayID]map[policymanager.PolicyCrdID]policymanager.Policy @@ -221,6 +226,7 @@ func NewHTTPRouteNode(httpRoute *gatewayv1.HTTPRoute) *HTTPRouteNode { Gateways: make(map[gatewayID]*GatewayNode), Backends: make(map[backendID]*BackendNode), Policies: make(map[policyID]*PolicyNode), + InheritedPolicies: make(map[policyID]*PolicyNode), EffectivePolicies: make(map[gatewayID]map[policymanager.PolicyCrdID]policymanager.Policy), Errors: []error{}, } @@ -252,6 +258,8 @@ type BackendNode struct { Policies map[policyID]*PolicyNode // ReferenceGrants contains ReferenceGrants that expose this Backend. ReferenceGrants map[referenceGrantID]*ReferenceGrantNode + // InheritedPolicies stores policies inherited by this Backend. + InheritedPolicies map[policyID]*PolicyNode // EffectivePolicies reflects the effective policies applicable to this // Backend, mapped per Gateway for context-specific enforcement. EffectivePolicies map[gatewayID]map[policymanager.PolicyCrdID]policymanager.Policy @@ -265,6 +273,7 @@ func NewBackendNode(backend *unstructured.Unstructured) *BackendNode { HTTPRoutes: make(map[httpRouteID]*HTTPRouteNode), Policies: make(map[policyID]*PolicyNode), ReferenceGrants: make(map[referenceGrantID]*ReferenceGrantNode), + InheritedPolicies: make(map[policyID]*PolicyNode), EffectivePolicies: make(map[gatewayID]map[policymanager.PolicyCrdID]policymanager.Policy), Errors: []error{}, } diff --git a/gwctl/pkg/resourcediscovery/resourcemodel.go b/gwctl/pkg/resourcediscovery/resourcemodel.go index ac04cf9ec5..dcd3ee2511 100644 --- a/gwctl/pkg/resourcediscovery/resourcemodel.go +++ b/gwctl/pkg/resourcediscovery/resourcemodel.go @@ -525,3 +525,121 @@ func ConvertPoliciesMapToPolicyRefs(policies map[policyID]*PolicyNode) []common. } return result } + +// calculateInheritedPolicies calculates the inherited polices for all +// Gateways, HTTRoutes, and Backends in ResourceModel. +func (rm *ResourceModel) calculateInheritedPolicies() error { + if err := rm.calculateInheritedPoliciesForGateways(); err != nil { + return err + } + if err := rm.calculateInheritedPoliciesForHTTPRoutes(); err != nil { + return err + } + if err := rm.calculateInheritedPoliciesForBackends(); err != nil { + return err + } + return nil +} + +// calculateInheritedPoliciesForGateways calculates the inherited policies for +// all Gateways present in ResourceModel. +func (rm *ResourceModel) calculateInheritedPoliciesForGateways() error { + for _, gatewayNode := range rm.Gateways { + result := make(map[policyID]*PolicyNode) + + // Policies inherited from Gateway's namespace. + policiesInheritedFromNamespace := extractInheritablePolicies(gatewayNode.Namespace.Policies) + mergePolicyMaps(result, policiesInheritedFromNamespace) + + // Policies inherited from GatewayClass. + if gatewayNode.GatewayClass != nil { + policiesInheritedFromGatewayClass := extractInheritablePolicies(gatewayNode.GatewayClass.Policies) + mergePolicyMaps(result, policiesInheritedFromGatewayClass) + } + + gatewayNode.InheritedPolicies = result + } + return nil +} + +// calculateInheritedPoliciesForHTTPRoutes calculates the inherited policies for +// all HTTPRoutes present in ResourceModel. +func (rm *ResourceModel) calculateInheritedPoliciesForHTTPRoutes() error { + for _, httpRouteNode := range rm.HTTPRoutes { + result := make(map[policyID]*PolicyNode) + + // Policies inherited from HTTPRoute's namespace. + policiesInheritedFromNamespace := extractInheritablePolicies(httpRouteNode.Namespace.Policies) + mergePolicyMaps(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) + + // Add inheritable policies directly applied to GatewayNode. + mergePolicyMaps( + policiesInheritedFromGateways, + extractInheritablePolicies(gatewayNode.Policies), + ) + } + + mergePolicyMaps(result, policiesInheritedFromGateways) + + httpRouteNode.InheritedPolicies = result + } + return nil +} + +// calculateInheritedPoliciesForBackends calculates the inherited policies for +// all Backends present in ResourceModel. +func (rm *ResourceModel) calculateInheritedPoliciesForBackends() error { + for _, backendNode := range rm.Backends { + result := make(map[policyID]*PolicyNode) + + // Policies inherited from Backend's namespace. + policiesInheritedFromNamespace := extractInheritablePolicies(backendNode.Namespace.Policies) + mergePolicyMaps(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) + + // Add inheritable policies directly applied to HTTPRouteNode. + mergePolicyMaps( + policiesInheritedFromHTTPRoutes, + extractInheritablePolicies(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 { + inheritablePolicies := make(map[policyID]*PolicyNode) + + for policyID, policyNode := range policies { + if policyNode.Policy.IsInherited() { + inheritablePolicies[policyID] = policyNode + } + } + + 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 + } +} From d2ab35dcb170899ddbe7c24dbed90763c1a4f65e Mon Sep 17 00:00:00 2001 From: Tarun Duhan Date: Sun, 2 Jun 2024 05:14:49 +0530 Subject: [PATCH 2/4] gwctl: Use new Describer and expand output for HTTPRoute --- gwctl/pkg/printer/httproutes.go | 144 ++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 36 deletions(-) diff --git a/gwctl/pkg/printer/httproutes.go b/gwctl/pkg/printer/httproutes.go index 84b7dac749..56b2f928b4 100644 --- a/gwctl/pkg/printer/httproutes.go +++ b/gwctl/pkg/printer/httproutes.go @@ -19,16 +19,13 @@ package printer import ( "fmt" "io" - "os" + "sort" "strings" "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/util/duration" "k8s.io/utils/clock" - "sigs.k8s.io/yaml" - gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - "sigs.k8s.io/gateway-api/gwctl/pkg/common" "sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery" ) @@ -91,50 +88,125 @@ func (hp *HTTPRoutesPrinter) PrintTable(resourceModel *resourcediscovery.Resourc table.Write(hp, 0) } -type httpRouteDescribeView struct { - Name string `json:",omitempty"` - Namespace string `json:",omitempty"` - Hostnames []gatewayv1.Hostname `json:",omitempty"` - ParentRefs []gatewayv1.ParentReference `json:",omitempty"` - DirectlyAttachedPolicies []common.ObjRef `json:",omitempty"` - EffectivePolicies any `json:",omitempty"` -} - func (hp *HTTPRoutesPrinter) PrintDescribeView(resourceModel *resourcediscovery.ResourceModel) { index := 0 + for _, httpRouteNode := range resourceModel.HTTPRoutes { index++ - views := []httpRouteDescribeView{ - { - Name: httpRouteNode.HTTPRoute.GetName(), - Namespace: httpRouteNode.HTTPRoute.GetNamespace(), - }, - { - Hostnames: httpRouteNode.HTTPRoute.Spec.Hostnames, - ParentRefs: httpRouteNode.HTTPRoute.Spec.ParentRefs, - }, + metadata := httpRouteNode.HTTPRoute.ObjectMeta.DeepCopy() + resetMetadataFields(metadata) + + namespace := handleDefaultNamespace(httpRouteNode.HTTPRoute.Namespace) + + pairs := []*DescriberKV{ + {"Name", httpRouteNode.HTTPRoute.GetName()}, + {"Namespace", namespace}, + {"Label", httpRouteNode.HTTPRoute.Labels}, + {"Annotations", httpRouteNode.HTTPRoute.Annotations}, + {"APIVersion", httpRouteNode.HTTPRoute.APIVersion}, + {"Kind", httpRouteNode.HTTPRoute.Kind}, + {"Metadata", metadata}, + {"Spec", httpRouteNode.HTTPRoute.Spec}, + {"Status", httpRouteNode.HTTPRoute.Status}, } - if policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(httpRouteNode.Policies); len(policyRefs) != 0 { - views = append(views, httpRouteDescribeView{ - DirectlyAttachedPolicies: policyRefs, - }) + + // DirectlyAttachedPolicies + directlyAttachedPolicies := &Table{ + ColumnNames: []string{"Type", "Name"}, + UseSeparator: true, } - if len(httpRouteNode.EffectivePolicies) != 0 { - views = append(views, httpRouteDescribeView{ - EffectivePolicies: httpRouteNode.EffectivePolicies, - }) + + 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}) } - for _, view := range views { - b, err := yaml.Marshal(view) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to marshal to yaml: %v\n", err) - os.Exit(1) + // InheritedPolicies + if len(httpRouteNode.InheritedPolicies) != 0 { + inheritedPolicies := &Table{ + ColumnNames: []string{"Type", "Name", "Target Kind", "Target Name"}, + UseSeparator: true, } - fmt.Fprint(hp, string(b)) + + 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}) + } + + // EffectivePolices + if len(httpRouteNode.EffectivePolicies) != 0 { + pairs = append(pairs, &DescriberKV{Key: "EffectivePolicies", Value: httpRouteNode.EffectivePolicies}) + } + + // Analysis + if len(httpRouteNode.Errors) != 0 { + pairs = append(pairs, &DescriberKV{Key: "Analysis", Value: convertErrorsToString(httpRouteNode.Errors)}) } + // Events + pairs = append(pairs, &DescriberKV{Key: "Events", Value: convertEventsSliceToTable(httpRouteNode.Events, hp.Clock)}) + + Describe(hp, pairs) + if index+1 <= len(resourceModel.HTTPRoutes) { fmt.Fprintf(hp, "\n\n") } From fb4c1657ebc571e6d7fc8fc250b062a8eca39abd Mon Sep 17 00:00:00 2001 From: Tarun Duhan Date: Sun, 2 Jun 2024 05:20:44 +0530 Subject: [PATCH 3/4] gwctl: Update expected value of HTTPRoutePrinter describe view test case --- gwctl/pkg/printer/httproutes_test.go | 31 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/gwctl/pkg/printer/httproutes_test.go b/gwctl/pkg/printer/httproutes_test.go index 20e19159ba..996897c590 100644 --- a/gwctl/pkg/printer/httproutes_test.go +++ b/gwctl/pkg/printer/httproutes_test.go @@ -471,14 +471,30 @@ func TestHTTPRoutesPrinter_PrintDescribeView(t *testing.T) { got := buff.String() want := ` Name: foo-httproute -ParentRefs: -- group: gateway.networking.k8s.io - kind: Gateway - name: foo-gateway +Namespace: default +Label: null +Annotations: null +APIVersion: "" +Kind: "" +Metadata: + creationTimestamp: null + resourceVersion: "999" +Spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: foo-gateway +Status: + parents: null DirectlyAttachedPolicies: -- Group: bar.com - Kind: TimeoutPolicy - Name: timeout-policy-httproute + Type Name + ---- ---- + 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 EffectivePolicies: default/foo-gateway: HealthCheckPolicy.foo.com: @@ -490,6 +506,7 @@ EffectivePolicies: TimeoutPolicy.bar.com: condition: path=/def seconds: 60 +Events: ` if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) From 359efad639aada69a19ccba9bd6f13d6d303958f Mon Sep 17 00:00:00 2001 From: Gaurav Ghildiyal Date: Tue, 2 Jul 2024 21:26:29 -0700 Subject: [PATCH 4/4] 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) + } + }) + } +}