Skip to content

Commit

Permalink
Added test for gwctl describe namespace and modified ResourceModel.ad…
Browse files Browse the repository at this point in the history
…dNamespace() function to accept corev1.Namespace
  • Loading branch information
Devaansh-Kumar committed Mar 6, 2024
1 parent d0b3f8f commit b2b8357
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 48 deletions.
5 changes: 3 additions & 2 deletions gwctl/pkg/cmd/describe/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,16 @@ func runDescribe(args []string, params *utils.CmdParams, flags *describeFlags) {
}
backendsPrinter.PrintDescribeView(resourceModel)

case "namespace":
case "namespace", "namespaces":
filter := resourcediscovery.Filter{}
if len(args) > 1 {
filter.Name = args[1]
}

resourceModel, err := discoverer.DiscoverResourcesForNamespace(filter)
if err != nil {
panic(err)
fmt.Fprintf(os.Stderr, "failed to discover Namespace resources: %v\n", err)
os.Exit(1)
}
namespacesPrinter.PrintDescribeView(resourceModel)

Expand Down
12 changes: 7 additions & 5 deletions gwctl/pkg/printer/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package printer
import (
"fmt"
"io"
"os"

"sigs.k8s.io/gateway-api/gwctl/pkg/policymanager"
"sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery"
Expand All @@ -28,14 +29,14 @@ func (nsp *NamespacesPrinter) PrintDescribeView(resourceModel *resourcediscovery

views := []namespaceDescribeView{
{
Name: namespaceNode.NamespaceName,
Name: namespaceNode.Namespace.Name,
},
{
Annotations: namespaceNode.Annotations,
Labels: namespaceNode.Labels,
Annotations: namespaceNode.Namespace.Annotations,
Labels: namespaceNode.Namespace.Labels,
},
{
Status: string(namespaceNode.Status.Phase),
Status: string(namespaceNode.Namespace.Status.Phase),
},
}

Expand All @@ -48,7 +49,8 @@ func (nsp *NamespacesPrinter) PrintDescribeView(resourceModel *resourcediscovery
for _, view := range views {
b, err := yaml.Marshal(view)
if err != nil {
panic(err)
fmt.Fprintf(os.Stderr, "failed to marshal to yaml: %v\n", err)
os.Exit(1)
}
fmt.Fprint(nsp.Out, string(b))
}
Expand Down
172 changes: 172 additions & 0 deletions gwctl/pkg/printer/namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package printer

import (
"bytes"
"testing"

"github.com/google/go-cmp/cmp"
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"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
"sigs.k8s.io/gateway-api/gwctl/pkg/cmd/utils"
"sigs.k8s.io/gateway-api/gwctl/pkg/common"
"sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery"
)

func TestNamespacePrinter_PrintDescribeView(t *testing.T) {
objects := []runtime.Object{
// Defining a Namespace called development
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "development",
Labels: map[string]string{
"type": "test-namespace",
},
Annotations: map[string]string{
"test-annotation": "development-annotation",
},
},
Status: corev1.NamespaceStatus{
Phase: corev1.NamespaceActive,
},
},

// CRD and definition for HealthCheckPolicy attached to development namespace
&apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "healthcheckpolicies.foo.com",
Labels: map[string]string{
gatewayv1alpha2.PolicyLabelKey: "inherited",
},
},
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Scope: apiextensionsv1.ClusterScoped,
Group: "foo.com",
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}},
Names: apiextensionsv1.CustomResourceDefinitionNames{
Plural: "healthcheckpolicies",
Kind: "HealthCheckPolicy",
},
},
},
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "foo.com/v1",
"kind": "HealthCheckPolicy",
"metadata": map[string]interface{}{
"name": "health-check-gatewayclass",
},
"spec": map[string]interface{}{
"override": map[string]interface{}{
"key1": "value-parent-1",
"key3": "value-parent-3",
"key5": "value-parent-5",
},
"default": map[string]interface{}{
"key2": "value-parent-2",
"key4": "value-parent-4",
},
"targetRef": map[string]interface{}{
"kind": "Namespace",
"name": "development",
},
},
},
},

// Defining a Namespace called production
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "production",
Labels: map[string]string{
"type": "production-namespace",
},
},
Status: corev1.NamespaceStatus{
Phase: corev1.NamespaceActive,
},
},
// CRD and definition for TimeoutPolicy attached to default namespace
&apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "timeoutpolicies.bar.com",
Labels: map[string]string{
gatewayv1alpha2.PolicyLabelKey: "direct",
},
},
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-namespace",
},
"spec": map[string]interface{}{
"condition": "path=/abc",
"seconds": int64(30),
"targetRef": map[string]interface{}{
"kind": "Namespace",
"name": "production",
},
},
},
},
}

params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...))
discoverer := resourcediscovery.Discoverer{
K8sClients: params.K8sClients,
PolicyManager: params.PolicyManager,
}
resourceModel, err := discoverer.DiscoverResourcesForNamespace(resourcediscovery.Filter{})
if err != nil {
t.Fatalf("Failed to construct resourceModel: %v", resourceModel)
}

nsp := &NamespacesPrinter{
Out: params.Out,
}
nsp.PrintDescribeView(resourceModel)

got := params.Out.(*bytes.Buffer).String()
// fmt.Println("THIS IS GOT:", got)
want := `
Name: development
Annotations:
test-annotation: development-annotation
Labels:
type: test-namespace
Status: Active
DirectlyAttachedPolicies:
- Group: foo.com
Kind: HealthCheckPolicy
Name: health-check-gatewayclass
Name: production
Labels:
type: production-namespace
Status: Active
DirectlyAttachedPolicies:
- Group: bar.com
Kind: TimeoutPolicy
Name: timeout-policy-namespace
`
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)
}

}
25 changes: 18 additions & 7 deletions gwctl/pkg/resourcediscovery/discoverer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package resourcediscovery

import (
"context"
"fmt"
"os"

"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand Down Expand Up @@ -143,7 +145,7 @@ func (d Discoverer) DiscoverResourcesForNamespace(filter Filter) (*ResourceModel
return resourceModel, err
}

resourceModel.addNamespaceWithLabelsAnnotationsStatus(namespaces...)
resourceModel.addNamespace(namespaces...)

d.discoverPolicies(ctx, resourceModel)

Expand Down Expand Up @@ -244,21 +246,29 @@ func (d Discoverer) discoverHTTPRoutesFromBackends(ctx context.Context, resource
// discoverNamespaces adds Namespaces for resources that exist in the
// resourceModel.
func (d Discoverer) discoverNamespaces(ctx context.Context, resourceModel *ResourceModel) {
namespacesList := &corev1.NamespaceList{}
if err := d.K8sClients.Client.List(ctx, namespacesList, &client.ListOptions{}); err != nil {
fmt.Fprintf(os.Stderr, "failed to fetch list of namespaces: %v\n", err)
os.Exit(1)
}

namespaceMap := make(map[string]corev1.Namespace)
for _, namespace := range namespacesList.Items {
namespaceMap[namespace.Name] = namespace
}

for gatewayID, gatewayNode := range resourceModel.Gateways {
resourceModel.addNamespace(gatewayNode.Gateway.GetNamespace())
resourceModel.addNamespace(namespaceMap[gatewayNode.Gateway.GetNamespace()])
resourceModel.connectGatewayWithNamespace(gatewayID, NamespaceID(gatewayNode.Gateway.GetNamespace()))
}
for httpRouteID, httpRouteNode := range resourceModel.HTTPRoutes {
resourceModel.addNamespace(httpRouteNode.HTTPRoute.GetNamespace())
resourceModel.addNamespace(namespaceMap[httpRouteNode.HTTPRoute.GetNamespace()])
resourceModel.connectHTTPRouteWithNamespace(httpRouteID, NamespaceID(httpRouteNode.HTTPRoute.GetNamespace()))
}
for backendID, backendNode := range resourceModel.Backends {
resourceModel.addNamespace(backendNode.Backend.GetNamespace())
resourceModel.addNamespace(namespaceMap[backendNode.Backend.GetNamespace()])
resourceModel.connectBackendWithNamespace(backendID, NamespaceID(backendNode.Backend.GetNamespace()))
}
// for namespaceID, namespaceNode := range resourceModel.Namespaces {
// fmt.Println("namespace fire:", namespaceID, namespaceNode, "\n")
// }
}

// discoverPolicies adds Policies for resources that exist in the resourceModel.
Expand Down Expand Up @@ -385,6 +395,7 @@ func fetchNamespace(ctx context.Context, k8sClients *common.K8sClients, filter F
namespace := &corev1.Namespace{}
nn := apimachinerytypes.NamespacedName{Name: filter.Name}
err := k8sClients.Client.Get(ctx, nn, namespace)

if err != nil {
return []corev1.Namespace{}, err
}
Expand Down
30 changes: 12 additions & 18 deletions gwctl/pkg/resourcediscovery/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"sigs.k8s.io/gateway-api/gwctl/pkg/policymanager"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/klog/v2"
)
Expand Down Expand Up @@ -250,11 +250,7 @@ func (b *BackendNode) ID() backendID {
// NamespaceNode models the relationships and dependencies of a Namespace.
type NamespaceNode struct {
// NamespaceName identifies the Namespace.
NamespaceName string

Labels map[string]string
Annotations map[string]string
Status corev1.NamespaceStatus
Namespace *corev1.Namespace

// Gateways lists Gateways deployed within the Namespace.
Gateways map[gatewayID]*GatewayNode
Expand All @@ -266,27 +262,25 @@ type NamespaceNode struct {
Policies map[policyID]*PolicyNode
}

func NewNamespaceNode(namespaceName string) *NamespaceNode {
if namespaceName == "" {
namespaceName = metav1.NamespaceDefault
func NewNamespaceNode(namespace corev1.Namespace) *NamespaceNode {
if namespace.Name == "" {
namespace.Name = metav1.NamespaceDefault
}
return &NamespaceNode{
NamespaceName: namespaceName,
Labels: make(map[string]string),
Annotations: make(map[string]string),
Gateways: make(map[gatewayID]*GatewayNode),
HTTPRoutes: make(map[httpRouteID]*HTTPRouteNode),
Backends: make(map[backendID]*BackendNode),
Policies: make(map[policyID]*PolicyNode),
Namespace: &namespace,
Gateways: make(map[gatewayID]*GatewayNode),
HTTPRoutes: make(map[httpRouteID]*HTTPRouteNode),
Backends: make(map[backendID]*BackendNode),
Policies: make(map[policyID]*PolicyNode),
}
}

func (n *NamespaceNode) ID() namespaceID {
if n.NamespaceName == "" {
if n.Namespace.Name == "" {
klog.V(0).ErrorS(nil, "returning empty ID since Namespace is empty")
return namespaceID(resourceID{})
}
return NamespaceID(n.NamespaceName)
return NamespaceID(n.Namespace.Name)
}

// PolicyNode models the relationships and dependencies of a Policy resource
Expand Down
17 changes: 1 addition & 16 deletions gwctl/pkg/resourcediscovery/resourcemodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (rm *ResourceModel) addGatewayClasses(gatewayClasses ...gatewayv1.GatewayCl
}

// addNamespace adds nodes for Namespace.
func (rm *ResourceModel) addNamespace(namespaces ...string) {
func (rm *ResourceModel) addNamespace(namespaces ...corev1.Namespace) {
if rm.Namespaces == nil {
rm.Namespaces = make(map[namespaceID]*NamespaceNode)
}
Expand All @@ -73,21 +73,6 @@ func (rm *ResourceModel) addNamespace(namespaces ...string) {
}
}

func (rm *ResourceModel) addNamespaceWithLabelsAnnotationsStatus(namespaces ...corev1.Namespace) {
if rm.Namespaces == nil {
rm.Namespaces = make(map[namespaceID]*NamespaceNode)
}
for _, namespace := range namespaces {
namespaceNode := NewNamespaceNode(namespace.Name)
namespaceNode.Labels = namespace.GetLabels()
namespaceNode.Annotations = namespace.GetAnnotations()
namespaceNode.Status = namespace.Status
if _, ok := rm.Namespaces[namespaceNode.ID()]; !ok {
rm.Namespaces[namespaceNode.ID()] = namespaceNode
}
}
}

// addGateways adds nodes for Gateways.
func (rm *ResourceModel) addGateways(gateways ...gatewayv1.Gateway) {
if rm.Gateways == nil {
Expand Down

0 comments on commit b2b8357

Please sign in to comment.