diff --git a/pkg/apiserver/registry/controlplane/egressgroup/rest_test.go b/pkg/apiserver/registry/controlplane/egressgroup/rest_test.go index ef793ba8c77..62aa6209094 100644 --- a/pkg/apiserver/registry/controlplane/egressgroup/rest_test.go +++ b/pkg/apiserver/registry/controlplane/egressgroup/rest_test.go @@ -17,18 +17,77 @@ package egressgroup import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "antrea.io/antrea/pkg/apis/controlplane" "antrea.io/antrea/pkg/controller/egress/store" "antrea.io/antrea/pkg/controller/types" ) +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.EgressGroup{}, r.New()) + assert.Equal(t, &controlplane.EgressGroupList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + egressGroups []*types.EgressGroup + objName string + expectedObj runtime.Object + expectedErr error + }{ + { + name: "get existing object", + egressGroups: []*types.EgressGroup{ + { + Name: "foo", + }, + }, + objName: "foo", + expectedObj: &controlplane.EgressGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + }, + }, + { + name: "get non-existing object", + egressGroups: []*types.EgressGroup{ + { + Name: "foo", + }, + }, + objName: "bar", + expectedErr: errors.NewNotFound(controlplane.Resource("egressgroup"), "bar"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewEgressGroupStore() + for _, obj := range tt.egressGroups { + storage.Create(obj) + } + r := NewREST(storage) + actualObj, err := r.Get(context.TODO(), tt.objName, &v1.GetOptions{}) + assert.Equal(t, tt.expectedErr, err) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + func TestRESTList(t *testing.T) { tests := []struct { name string @@ -78,3 +137,66 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTWatch(t *testing.T) { + egressGroups := []*types.EgressGroup{ + { + Name: "egress1", + SpanMeta: types.SpanMeta{NodeNames: sets.NewString("node1")}, + }, + } + tests := []struct { + name string + fieldSelector fields.Selector + expectedEvents []watch.Event + }{ + { + name: "nodeName selecting nothing", + fieldSelector: fields.OneTermEqualSelector("nodeName", "foo"), + expectedEvents: []watch.Event{ + {Type: watch.Bookmark, Object: &controlplane.EgressGroup{}}, + }, + }, + { + name: "nodeName provided", + fieldSelector: fields.OneTermEqualSelector("nodeName", "node1"), + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.EgressGroup{ObjectMeta: v1.ObjectMeta{Name: "egress1"}}}, + {Type: watch.Bookmark, Object: &controlplane.EgressGroup{}}, + }, + }, + { + name: "nodeName not provided", + fieldSelector: nil, + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.EgressGroup{ObjectMeta: v1.ObjectMeta{Name: "egress1"}}}, + {Type: watch.Bookmark, Object: &controlplane.EgressGroup{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewEgressGroupStore() + for _, obj := range egressGroups { + storage.Create(obj) + } + r := NewREST(storage) + watcher, err := r.Watch(context.TODO(), &internalversion.ListOptions{FieldSelector: tt.fieldSelector}) + assert.NoError(t, err) + defer watcher.Stop() + for _, expectedObj := range tt.expectedEvents { + select { + case gotObj := <-watcher.ResultChan(): + assert.Equal(t, expectedObj, gotObj) + case <-time.NewTimer(time.Second).C: + t.Errorf("Failed to get expected object %v from watcher in time", expectedObj) + } + } + select { + case gotObj := <-watcher.ResultChan(): + t.Errorf("Got unexpected object %v from watcher", gotObj) + case <-time.NewTimer(time.Millisecond * 100).C: + } + }) + } +} diff --git a/pkg/apiserver/registry/controlplane/nodestatssummary/rest_test.go b/pkg/apiserver/registry/controlplane/nodestatssummary/rest_test.go new file mode 100644 index 00000000000..1499ea6ccd7 --- /dev/null +++ b/pkg/apiserver/registry/controlplane/nodestatssummary/rest_test.go @@ -0,0 +1,62 @@ +// Copyright 2022 Antrea 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 nodestatssummary + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "antrea.io/antrea/pkg/apis/controlplane" + statsv1alpha1 "antrea.io/antrea/pkg/apis/stats/v1alpha1" +) + +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.NodeStatsSummary{}, r.New()) + assert.False(t, r.NamespaceScoped()) +} + +type fakeCollector struct { + gotSummary *controlplane.NodeStatsSummary +} + +func (f *fakeCollector) Collect(summary *controlplane.NodeStatsSummary) { + f.gotSummary = summary +} + +func TestRESTCreate(t *testing.T) { + collector := &fakeCollector{} + r := NewREST(collector) + + summary := &controlplane.NodeStatsSummary{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + NetworkPolicies: []controlplane.NetworkPolicyStats{ + { + NetworkPolicy: controlplane.NetworkPolicyReference{Type: controlplane.K8sNetworkPolicy, Namespace: "default", Name: "policy1"}, + TrafficStats: statsv1alpha1.TrafficStats{Packets: 10, Sessions: 2, Bytes: 10000}, + }, + }, + } + actualObj, err := r.Create(context.TODO(), summary, nil, &v1.CreateOptions{}) + assert.NoError(t, err) + // Empty struct is returned on success. + assert.Equal(t, &controlplane.NodeStatsSummary{}, actualObj) + assert.Equal(t, summary, collector.gotSummary) +} diff --git a/pkg/apiserver/registry/networkpolicy/addressgroup/rest_test.go b/pkg/apiserver/registry/networkpolicy/addressgroup/rest_test.go index e5c8a33be59..74fc436c95e 100644 --- a/pkg/apiserver/registry/networkpolicy/addressgroup/rest_test.go +++ b/pkg/apiserver/registry/networkpolicy/addressgroup/rest_test.go @@ -17,18 +17,77 @@ package addressgroup import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "antrea.io/antrea/pkg/apis/controlplane" "antrea.io/antrea/pkg/controller/networkpolicy/store" "antrea.io/antrea/pkg/controller/types" ) +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.AddressGroup{}, r.New()) + assert.Equal(t, &controlplane.AddressGroupList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + addressGroups []*types.AddressGroup + objName string + expectedObj runtime.Object + expectedErr error + }{ + { + name: "get existing object", + addressGroups: []*types.AddressGroup{ + { + Name: "foo", + }, + }, + objName: "foo", + expectedObj: &controlplane.AddressGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + }, + }, + { + name: "get non-existing object", + addressGroups: []*types.AddressGroup{ + { + Name: "foo", + }, + }, + objName: "bar", + expectedErr: errors.NewNotFound(controlplane.Resource("addressgroup"), "bar"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewAddressGroupStore() + for _, obj := range tt.addressGroups { + storage.Create(obj) + } + r := NewREST(storage) + actualObj, err := r.Get(context.TODO(), tt.objName, &v1.GetOptions{}) + assert.Equal(t, tt.expectedErr, err) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + func TestRESTList(t *testing.T) { tests := []struct { name string @@ -78,3 +137,66 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTWatch(t *testing.T) { + addressGroups := []*types.AddressGroup{ + { + Name: "addressGroup1", + SpanMeta: types.SpanMeta{NodeNames: sets.NewString("node1")}, + }, + } + tests := []struct { + name string + fieldSelector fields.Selector + expectedEvents []watch.Event + }{ + { + name: "nodeName selecting nothing", + fieldSelector: fields.OneTermEqualSelector("nodeName", "foo"), + expectedEvents: []watch.Event{ + {Type: watch.Bookmark, Object: &controlplane.AddressGroup{}}, + }, + }, + { + name: "nodeName provided", + fieldSelector: fields.OneTermEqualSelector("nodeName", "node1"), + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.AddressGroup{ObjectMeta: v1.ObjectMeta{Name: "addressGroup1"}}}, + {Type: watch.Bookmark, Object: &controlplane.AddressGroup{}}, + }, + }, + { + name: "nodeName not provided", + fieldSelector: nil, + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.AddressGroup{ObjectMeta: v1.ObjectMeta{Name: "addressGroup1"}}}, + {Type: watch.Bookmark, Object: &controlplane.AddressGroup{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewAddressGroupStore() + for _, obj := range addressGroups { + storage.Create(obj) + } + r := NewREST(storage) + watcher, err := r.Watch(context.TODO(), &internalversion.ListOptions{FieldSelector: tt.fieldSelector}) + assert.NoError(t, err) + defer watcher.Stop() + for _, expectedObj := range tt.expectedEvents { + select { + case gotObj := <-watcher.ResultChan(): + assert.Equal(t, expectedObj, gotObj) + case <-time.NewTimer(time.Second).C: + t.Errorf("Failed to get expected object %v from watcher in time", expectedObj) + } + } + select { + case gotObj := <-watcher.ResultChan(): + t.Errorf("Got unexpected object %v from watcher", gotObj) + case <-time.NewTimer(time.Millisecond * 100).C: + } + }) + } +} diff --git a/pkg/apiserver/registry/networkpolicy/appliedtogroup/rest_test.go b/pkg/apiserver/registry/networkpolicy/appliedtogroup/rest_test.go index 79b505fdc3d..8ba555763fe 100644 --- a/pkg/apiserver/registry/networkpolicy/appliedtogroup/rest_test.go +++ b/pkg/apiserver/registry/networkpolicy/appliedtogroup/rest_test.go @@ -17,18 +17,77 @@ package appliedtogroup import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "antrea.io/antrea/pkg/apis/controlplane" "antrea.io/antrea/pkg/controller/networkpolicy/store" "antrea.io/antrea/pkg/controller/types" ) +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.AppliedToGroup{}, r.New()) + assert.Equal(t, &controlplane.AppliedToGroupList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + appliedToGroups []*types.AppliedToGroup + objName string + expectedObj runtime.Object + expectedErr error + }{ + { + name: "get existing object", + appliedToGroups: []*types.AppliedToGroup{ + { + Name: "foo", + }, + }, + objName: "foo", + expectedObj: &controlplane.AppliedToGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + }, + }, + { + name: "get non-existing object", + appliedToGroups: []*types.AppliedToGroup{ + { + Name: "foo", + }, + }, + objName: "bar", + expectedErr: errors.NewNotFound(controlplane.Resource("appliedtogroup"), "bar"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewAppliedToGroupStore() + for _, obj := range tt.appliedToGroups { + storage.Create(obj) + } + r := NewREST(storage) + actualObj, err := r.Get(context.TODO(), tt.objName, &v1.GetOptions{}) + assert.Equal(t, tt.expectedErr, err) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + func TestRESTList(t *testing.T) { tests := []struct { name string @@ -78,3 +137,66 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTWatch(t *testing.T) { + appliedToGroups := []*types.AppliedToGroup{ + { + Name: "appliedToGroup1", + SpanMeta: types.SpanMeta{NodeNames: sets.NewString("node1")}, + }, + } + tests := []struct { + name string + fieldSelector fields.Selector + expectedEvents []watch.Event + }{ + { + name: "nodeName selecting nothing", + fieldSelector: fields.OneTermEqualSelector("nodeName", "foo"), + expectedEvents: []watch.Event{ + {Type: watch.Bookmark, Object: &controlplane.AppliedToGroup{}}, + }, + }, + { + name: "nodeName provided", + fieldSelector: fields.OneTermEqualSelector("nodeName", "node1"), + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.AppliedToGroup{ObjectMeta: v1.ObjectMeta{Name: "appliedToGroup1"}}}, + {Type: watch.Bookmark, Object: &controlplane.AppliedToGroup{}}, + }, + }, + { + name: "nodeName not provided", + fieldSelector: nil, + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.AppliedToGroup{ObjectMeta: v1.ObjectMeta{Name: "appliedToGroup1"}}}, + {Type: watch.Bookmark, Object: &controlplane.AppliedToGroup{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewAppliedToGroupStore() + for _, obj := range appliedToGroups { + storage.Create(obj) + } + r := NewREST(storage) + watcher, err := r.Watch(context.TODO(), &internalversion.ListOptions{FieldSelector: tt.fieldSelector}) + assert.NoError(t, err) + defer watcher.Stop() + for _, expectedObj := range tt.expectedEvents { + select { + case gotObj := <-watcher.ResultChan(): + assert.Equal(t, expectedObj, gotObj) + case <-time.NewTimer(time.Second).C: + t.Errorf("Failed to get expected object %v from watcher in time", expectedObj) + } + } + select { + case gotObj := <-watcher.ResultChan(): + t.Errorf("Got unexpected object %v from watcher", gotObj) + case <-time.NewTimer(time.Millisecond * 100).C: + } + }) + } +} diff --git a/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest_test.go b/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest_test.go index e149796d864..39358ff4d45 100644 --- a/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest_test.go +++ b/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest_test.go @@ -114,6 +114,12 @@ func getTestIPMembers() map[string][]controlplane.IPBlock { } } +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.ClusterGroupMembers{}, r.New()) + assert.False(t, r.NamespaceScoped()) +} + func TestRESTGetBasic(t *testing.T) { tests := []struct { name string diff --git a/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go b/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go index 2a77300dffb..6053b680330 100644 --- a/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go +++ b/pkg/apiserver/registry/networkpolicy/groupassociation/rest_test.go @@ -40,6 +40,12 @@ func (q fakeQuerier) GetAssociatedGroups(name, namespace string) ([]antreatypes. return []antreatypes.Group{}, nil } +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.GroupAssociation{}, r.New()) + assert.True(t, r.NamespaceScoped()) +} + func TestRESTGet(t *testing.T) { groups := map[string][]antreatypes.Group{ "default/podA": { diff --git a/pkg/apiserver/registry/networkpolicy/networkpolicy/rest_test.go b/pkg/apiserver/registry/networkpolicy/networkpolicy/rest_test.go index 61135bf71bf..7a3b87ea0e7 100644 --- a/pkg/apiserver/registry/networkpolicy/networkpolicy/rest_test.go +++ b/pkg/apiserver/registry/networkpolicy/networkpolicy/rest_test.go @@ -17,18 +17,77 @@ package networkpolicy import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "antrea.io/antrea/pkg/apis/controlplane" "antrea.io/antrea/pkg/controller/networkpolicy/store" "antrea.io/antrea/pkg/controller/types" ) +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.NetworkPolicy{}, r.New()) + assert.Equal(t, &controlplane.NetworkPolicyList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + networkPolicies []*types.NetworkPolicy + objName string + expectedObj runtime.Object + expectedErr error + }{ + { + name: "get existing object", + networkPolicies: []*types.NetworkPolicy{ + { + Name: "foo", + }, + }, + objName: "foo", + expectedObj: &controlplane.NetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + }, + }, + { + name: "get non-existing object", + networkPolicies: []*types.NetworkPolicy{ + { + Name: "foo", + }, + }, + objName: "bar", + expectedErr: errors.NewNotFound(controlplane.Resource("networkpolicy"), "bar"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewNetworkPolicyStore() + for _, obj := range tt.networkPolicies { + storage.Create(obj) + } + r := NewREST(storage) + actualObj, err := r.Get(context.TODO(), tt.objName, &v1.GetOptions{}) + assert.Equal(t, tt.expectedErr, err) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + func TestRESTList(t *testing.T) { tests := []struct { name string @@ -78,3 +137,66 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTWatch(t *testing.T) { + networkPolicies := []*types.NetworkPolicy{ + { + Name: "networkPolicy1", + SpanMeta: types.SpanMeta{NodeNames: sets.NewString("node1")}, + }, + } + tests := []struct { + name string + fieldSelector fields.Selector + expectedEvents []watch.Event + }{ + { + name: "nodeName selecting nothing", + fieldSelector: fields.OneTermEqualSelector("nodeName", "foo"), + expectedEvents: []watch.Event{ + {Type: watch.Bookmark, Object: &controlplane.NetworkPolicy{}}, + }, + }, + { + name: "nodeName provided", + fieldSelector: fields.OneTermEqualSelector("nodeName", "node1"), + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.NetworkPolicy{ObjectMeta: v1.ObjectMeta{Name: "networkPolicy1"}}}, + {Type: watch.Bookmark, Object: &controlplane.NetworkPolicy{}}, + }, + }, + { + name: "nodeName not provided", + fieldSelector: nil, + expectedEvents: []watch.Event{ + {Type: watch.Added, Object: &controlplane.NetworkPolicy{ObjectMeta: v1.ObjectMeta{Name: "networkPolicy1"}}}, + {Type: watch.Bookmark, Object: &controlplane.NetworkPolicy{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storage := store.NewNetworkPolicyStore() + for _, obj := range networkPolicies { + storage.Create(obj) + } + r := NewREST(storage) + watcher, err := r.Watch(context.TODO(), &internalversion.ListOptions{FieldSelector: tt.fieldSelector}) + assert.NoError(t, err) + defer watcher.Stop() + for _, expectedObj := range tt.expectedEvents { + select { + case gotObj := <-watcher.ResultChan(): + assert.Equal(t, expectedObj, gotObj) + case <-time.NewTimer(time.Second).C: + t.Errorf("Failed to get expected object %v from watcher in time", expectedObj) + } + } + select { + case gotObj := <-watcher.ResultChan(): + t.Errorf("Got unexpected object %v from watcher", gotObj) + case <-time.NewTimer(time.Millisecond * 100).C: + } + }) + } +} diff --git a/pkg/apiserver/registry/networkpolicy/networkpolicy/subresources_test.go b/pkg/apiserver/registry/networkpolicy/networkpolicy/subresources_test.go new file mode 100644 index 00000000000..6fdfcc10616 --- /dev/null +++ b/pkg/apiserver/registry/networkpolicy/networkpolicy/subresources_test.go @@ -0,0 +1,103 @@ +// Copyright 2022 Antrea 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 networkpolicy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "antrea.io/antrea/pkg/apis/controlplane" +) + +type fakeCollector struct { + gotStatus *controlplane.NetworkPolicyStatus +} + +func (f *fakeCollector) UpdateStatus(status *controlplane.NetworkPolicyStatus) error { + f.gotStatus = status + return nil +} + +func TestStatusREST(t *testing.T) { + r := NewStatusREST(nil) + assert.Equal(t, &controlplane.NetworkPolicyStatus{}, r.New()) +} + +func TestStatusRESTCreate(t *testing.T) { + tests := []struct { + name string + objName string + obj runtime.Object + expectedReturnedObj runtime.Object + expectedErr error + expectedStatus *controlplane.NetworkPolicyStatus + }{ + { + name: "succeed", + objName: "foo", + obj: &controlplane.NetworkPolicyStatus{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + Nodes: []controlplane.NetworkPolicyNodeStatus{ + {NodeName: "node1", Generation: 1}, + }, + }, + expectedReturnedObj: &v1.Status{Status: v1.StatusSuccess}, + expectedStatus: &controlplane.NetworkPolicyStatus{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + Nodes: []controlplane.NetworkPolicyNodeStatus{ + {NodeName: "node1", Generation: 1}, + }, + }, + }, + { + name: "unexpected type", + objName: "foo", + obj: &controlplane.NetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + }, + }, + expectedErr: errors.NewBadRequest("not a NetworkPolicyStatus object: *controlplane.NetworkPolicy"), + }, + { + name: "mismatch name", + objName: "foo", + obj: &controlplane.NetworkPolicyStatus{ + ObjectMeta: v1.ObjectMeta{ + Name: "bar", + }, + }, + expectedErr: errors.NewBadRequest("name in URL does not match name in NetworkPolicyStatus object"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + collector := &fakeCollector{} + r := NewStatusREST(collector) + actualObj, err := r.Create(context.TODO(), tt.objName, tt.obj, nil, &v1.CreateOptions{}) + assert.Equal(t, tt.expectedErr, err) + assert.Equal(t, tt.expectedReturnedObj, actualObj) + }) + } +} diff --git a/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest.go b/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest.go index 98506c7a66c..859f439e2f6 100644 --- a/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest.go +++ b/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest.go @@ -31,6 +31,16 @@ import ( "antrea.io/antrea/pkg/features" ) +var ( + tableColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, + {Name: "Sessions", Type: "integer", Description: "The sessions count hit by the Antrea ClusterNetworkPolicy."}, + {Name: "Packets", Type: "integer", Description: "The packets count hit by the Antrea ClusterNetworkPolicy."}, + {Name: "Bytes", Type: "integer", Description: "The bytes count hit by the Antrea ClusterNetworkPolicy."}, + {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, + } +) + type REST struct { statsProvider statsProvider } @@ -103,13 +113,7 @@ var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { table := &metav1.Table{ - ColumnDefinitions: []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, - {Name: "Sessions", Type: "integer", Description: "The sessions count hit by the Antrea ClusterNetworkPolicy."}, - {Name: "Packets", Type: "integer", Description: "The packets count hit by the Antrea ClusterNetworkPolicy."}, - {Name: "Bytes", Type: "integer", Description: "The bytes count hit by the Antrea ClusterNetworkPolicy."}, - {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, - }, + ColumnDefinitions: tableColumnDefinitions, } if m, err := meta.ListAccessor(obj); err == nil { table.ResourceVersion = m.GetResourceVersion() diff --git a/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest_test.go b/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest_test.go index a268386a33a..83fd84b9b7d 100644 --- a/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest_test.go +++ b/pkg/apiserver/registry/stats/antreaclusternetworkpolicystats/rest_test.go @@ -17,6 +17,7 @@ package antreaclusternetworkpolicystats import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -50,6 +51,13 @@ func (p *fakeStatsProvider) GetAntreaClusterNetworkPolicyStats(name string) (*st return &m, true } +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &statsv1alpha1.AntreaClusterNetworkPolicyStats{}, r.New()) + assert.Equal(t, &statsv1alpha1.AntreaClusterNetworkPolicyStatsList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + func TestRESTGet(t *testing.T) { tests := []struct { name string @@ -256,3 +264,58 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTConvertToTable(t *testing.T) { + stats := &statsv1alpha1.AntreaClusterNetworkPolicyStats{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "bar", + CreationTimestamp: metav1.Time{Time: time.Now()}, + }, + TrafficStats: statsv1alpha1.TrafficStats{ + Packets: 10, + Bytes: 2000, + Sessions: 5, + }, + } + tests := []struct { + name string + object runtime.Object + expectedTable *metav1.Table + }{ + { + name: "one object", + object: stats, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{"bar", int64(5), int64(10), int64(2000), stats.CreationTimestamp.Format("2006-01-02T15:04:05Z")}, + Object: runtime.RawExtension{Object: stats}, + }, + }, + }, + }, + { + name: "multiple objects", + object: &statsv1alpha1.AntreaClusterNetworkPolicyStatsList{Items: []statsv1alpha1.AntreaClusterNetworkPolicyStats{*stats}}, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{"bar", int64(5), int64(10), int64(2000), stats.CreationTimestamp.Format("2006-01-02T15:04:05Z")}, + Object: runtime.RawExtension{Object: stats}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &REST{} + actualTable, err := r.ConvertToTable(context.TODO(), tt.object, &metav1.TableOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.expectedTable, actualTable) + }) + } +} diff --git a/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest.go b/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest.go index 755214568d9..c48761d744a 100644 --- a/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest.go +++ b/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest.go @@ -32,6 +32,16 @@ import ( "antrea.io/antrea/pkg/features" ) +var ( + tableColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, + {Name: "Sessions", Type: "integer", Description: "The sessions count hit by the Antrea NetworkPolicy."}, + {Name: "Packets", Type: "integer", Description: "The packets count hit by the Antrea NetworkPolicy."}, + {Name: "Bytes", Type: "integer", Description: "The bytes count hit by the Antrea NetworkPolicy."}, + {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, + } +) + type REST struct { statsProvider statsProvider } @@ -59,7 +69,7 @@ func (r *REST) New() runtime.Object { } func (r *REST) NewList() runtime.Object { - return &statsv1alpha1.NetworkPolicyStatsList{} + return &statsv1alpha1.AntreaNetworkPolicyStatsList{} } func (r *REST) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { @@ -109,13 +119,7 @@ var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { table := &metav1.Table{ - ColumnDefinitions: []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, - {Name: "Sessions", Type: "integer", Description: "The sessions count hit by the Antrea NetworkPolicy."}, - {Name: "Packets", Type: "integer", Description: "The packets count hit by the Antrea NetworkPolicy."}, - {Name: "Bytes", Type: "integer", Description: "The bytes count hit by the Antrea NetworkPolicy."}, - {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, - }, + ColumnDefinitions: tableColumnDefinitions, } if m, err := meta.ListAccessor(obj); err == nil { table.ResourceVersion = m.GetResourceVersion() diff --git a/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest_test.go b/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest_test.go index 498a8b7bed1..e0d57a00a93 100644 --- a/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest_test.go +++ b/pkg/apiserver/registry/stats/antreanetworkpolicystats/rest_test.go @@ -17,6 +17,7 @@ package antreanetworkpolicystats import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -60,6 +61,13 @@ func (p *fakeStatsProvider) GetAntreaNetworkPolicyStats(namespace, name string) return &m, true } +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &statsv1alpha1.AntreaNetworkPolicyStats{}, r.New()) + assert.Equal(t, &statsv1alpha1.AntreaNetworkPolicyStatsList{}, r.NewList()) + assert.True(t, r.NamespaceScoped()) +} + func TestRESTGet(t *testing.T) { tests := []struct { name string @@ -333,3 +341,58 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTConvertToTable(t *testing.T) { + stats := &statsv1alpha1.AntreaNetworkPolicyStats{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "bar", + CreationTimestamp: metav1.Time{Time: time.Now()}, + }, + TrafficStats: statsv1alpha1.TrafficStats{ + Packets: 10, + Bytes: 2000, + Sessions: 5, + }, + } + tests := []struct { + name string + object runtime.Object + expectedTable *metav1.Table + }{ + { + name: "one object", + object: stats, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{"bar", int64(5), int64(10), int64(2000), stats.CreationTimestamp.Format("2006-01-02T15:04:05Z")}, + Object: runtime.RawExtension{Object: stats}, + }, + }, + }, + }, + { + name: "multiple objects", + object: &statsv1alpha1.AntreaNetworkPolicyStatsList{Items: []statsv1alpha1.AntreaNetworkPolicyStats{*stats}}, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{"bar", int64(5), int64(10), int64(2000), stats.CreationTimestamp.Format("2006-01-02T15:04:05Z")}, + Object: runtime.RawExtension{Object: stats}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &REST{} + actualTable, err := r.ConvertToTable(context.TODO(), tt.object, &metav1.TableOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.expectedTable, actualTable) + }) + } +} diff --git a/pkg/apiserver/registry/stats/multicastgroup/rest.go b/pkg/apiserver/registry/stats/multicastgroup/rest.go index 999b55af8fe..61df6169171 100644 --- a/pkg/apiserver/registry/stats/multicastgroup/rest.go +++ b/pkg/apiserver/registry/stats/multicastgroup/rest.go @@ -32,6 +32,13 @@ import ( "antrea.io/antrea/pkg/util/k8s" ) +var ( + tableColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Group", Type: "string", Format: "name", Description: "IP of multicast group."}, + {Name: "Pods", Type: "string", Description: "List of Pods the has joined the multicast group."}, + } +) + type REST struct { statsProvider statsProvider } @@ -90,10 +97,7 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { table := &metav1.Table{ - ColumnDefinitions: []metav1.TableColumnDefinition{ - {Name: "Group", Type: "string", Format: "name", Description: "IP of multicast group."}, - {Name: "Pods", Type: "string", Description: "List of Pods the has joined the multicast group."}, - }, + ColumnDefinitions: tableColumnDefinitions, } if m, err := meta.ListAccessor(obj); err == nil { table.ResourceVersion = m.GetResourceVersion() diff --git a/pkg/apiserver/registry/stats/multicastgroup/rest_test.go b/pkg/apiserver/registry/stats/multicastgroup/rest_test.go new file mode 100644 index 00000000000..9f31693e54e --- /dev/null +++ b/pkg/apiserver/registry/stats/multicastgroup/rest_test.go @@ -0,0 +1,248 @@ +// Copyright 2022 Antrea 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 multicastgroup + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + featuregatetesting "k8s.io/component-base/featuregate/testing" + + statsv1alpha1 "antrea.io/antrea/pkg/apis/stats/v1alpha1" + "antrea.io/antrea/pkg/features" +) + +var ( + group1 = &statsv1alpha1.MulticastGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "224.0.0.10", + }, + Group: "224.0.0.10", + Pods: []statsv1alpha1.PodReference{ + {Name: "foo", Namespace: "default"}, + {Name: "bar", Namespace: "default"}, + }, + } + group2 = &statsv1alpha1.MulticastGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "224.0.0.11", + }, + Group: "224.0.0.11", + Pods: []statsv1alpha1.PodReference{ + {Name: "foo1", Namespace: "dev"}, + {Name: "foo2", Namespace: "dev"}, + {Name: "bar1", Namespace: "dev"}, + {Name: "bar2", Namespace: "dev"}, + }, + } + group3 = &statsv1alpha1.MulticastGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "224.0.0.12", + }, + Group: "224.0.0.12", + } +) + +type fakeStatsProvider struct { + groups map[string]*statsv1alpha1.MulticastGroup +} + +func (p *fakeStatsProvider) ListMulticastGroups() []statsv1alpha1.MulticastGroup { + var list []statsv1alpha1.MulticastGroup + for _, g := range p.groups { + list = append(list, *g) + } + return list +} + +func (p *fakeStatsProvider) GetMulticastGroup(name string) (*statsv1alpha1.MulticastGroup, bool) { + g, exists := p.groups[name] + return g, exists +} + +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &statsv1alpha1.MulticastGroup{}, r.New()) + assert.Equal(t, &statsv1alpha1.MulticastGroupList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + +func TestRESTList(t *testing.T) { + tests := []struct { + name string + networkPolicyStatsEnabled bool + multicastEnabled bool + stats map[string]*statsv1alpha1.MulticastGroup + groupName string + expectedObj runtime.Object + }{ + { + name: "NetworkPolicyStats feature disabled", + networkPolicyStatsEnabled: false, + groupName: group1.Name, + expectedObj: &statsv1alpha1.MulticastGroupList{}, + }, + { + name: "Multicast feature disabled", + multicastEnabled: false, + groupName: group1.Name, + expectedObj: &statsv1alpha1.MulticastGroupList{}, + }, + { + name: "empty group", + networkPolicyStatsEnabled: true, + multicastEnabled: true, + expectedObj: &statsv1alpha1.MulticastGroupList{}, + }, + { + name: "multiple groups", + networkPolicyStatsEnabled: true, + multicastEnabled: true, + stats: map[string]*statsv1alpha1.MulticastGroup{ + group1.Name: group1, + group2.Name: group2, + }, + expectedObj: &statsv1alpha1.MulticastGroupList{ + Items: []statsv1alpha1.MulticastGroup{*group1, *group2}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.DefaultFeatureGate, features.NetworkPolicyStats, tt.networkPolicyStatsEnabled)() + defer featuregatetesting.SetFeatureGateDuringTest(t, features.DefaultFeatureGate, features.Multicast, tt.multicastEnabled)() + + r := &REST{ + statsProvider: &fakeStatsProvider{groups: tt.stats}, + } + actualObj, err := r.List(context.TODO(), &internalversion.ListOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + networkPolicyStatsEnabled bool + multicastEnabled bool + groups map[string]*statsv1alpha1.MulticastGroup + groupName string + expectedObj runtime.Object + expectedErr error + }{ + { + name: "NetworkPolicyStats feature disabled", + networkPolicyStatsEnabled: false, + groupName: group1.Name, + expectedObj: &statsv1alpha1.MulticastGroup{}, + }, + { + name: "Multicast feature disabled", + multicastEnabled: false, + groupName: group1.Name, + expectedObj: &statsv1alpha1.MulticastGroup{}, + }, + { + name: "group not found", + networkPolicyStatsEnabled: true, + multicastEnabled: true, + groups: nil, + groupName: group1.Name, + expectedErr: errors.NewNotFound(statsv1alpha1.Resource("multicastgroup"), "224.0.0.10"), + }, + { + name: "group found", + networkPolicyStatsEnabled: true, + multicastEnabled: true, + groups: map[string]*statsv1alpha1.MulticastGroup{ + group1.Name: group1, + }, + groupName: group1.Name, + expectedObj: group1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.DefaultFeatureGate, features.NetworkPolicyStats, tt.networkPolicyStatsEnabled)() + defer featuregatetesting.SetFeatureGateDuringTest(t, features.DefaultFeatureGate, features.Multicast, tt.multicastEnabled)() + + r := &REST{ + statsProvider: &fakeStatsProvider{groups: tt.groups}, + } + actualObj, actualErr := r.Get(context.TODO(), tt.groupName, &metav1.GetOptions{}) + assert.Equal(t, tt.expectedErr, actualErr) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + +func TestRESTConvertToTable(t *testing.T) { + tests := []struct { + name string + object runtime.Object + expectedTable *metav1.Table + }{ + { + name: "one object", + object: group1, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{group1.Name, "default/foo,default/bar"}, + Object: runtime.RawExtension{Object: group1}, + }, + }, + }, + }, + { + name: "multiple objects", + object: &statsv1alpha1.MulticastGroupList{Items: []statsv1alpha1.MulticastGroup{*group1, *group2, *group3}}, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{group1.Name, "default/foo,default/bar"}, + Object: runtime.RawExtension{Object: group1}, + }, + { + Cells: []interface{}{group2.Name, "dev/foo1,dev/foo2,dev/bar1 + 1 more..."}, + Object: runtime.RawExtension{Object: group2}, + }, + { + Cells: []interface{}{group3.Name, ""}, + Object: runtime.RawExtension{Object: group3}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &REST{} + actualTable, err := r.ConvertToTable(context.TODO(), tt.object, &metav1.TableOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.expectedTable, actualTable) + }) + } +} diff --git a/pkg/apiserver/registry/stats/networkpolicystats/rest.go b/pkg/apiserver/registry/stats/networkpolicystats/rest.go index 7c0e549b980..829b8ce08a9 100644 --- a/pkg/apiserver/registry/stats/networkpolicystats/rest.go +++ b/pkg/apiserver/registry/stats/networkpolicystats/rest.go @@ -32,6 +32,16 @@ import ( "antrea.io/antrea/pkg/features" ) +var ( + tableColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, + {Name: "Sessions", Type: "integer", Description: "The sessions count hit by the K8s NetworkPolicy."}, + {Name: "Packets", Type: "integer", Description: "The packets count hit by the K8s NetworkPolicy."}, + {Name: "Bytes", Type: "integer", Description: "The bytes count hit by the K8s NetworkPolicy."}, + {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, + } +) + type REST struct { statsProvider statsProvider } @@ -103,13 +113,7 @@ var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { table := &metav1.Table{ - ColumnDefinitions: []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, - {Name: "Sessions", Type: "integer", Description: "The sessions count hit by the K8s NetworkPolicy."}, - {Name: "Packets", Type: "integer", Description: "The packets count hit by the K8s NetworkPolicy."}, - {Name: "Bytes", Type: "integer", Description: "The bytes count hit by the K8s NetworkPolicy."}, - {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, - }, + ColumnDefinitions: tableColumnDefinitions, } if m, err := meta.ListAccessor(obj); err == nil { table.ResourceVersion = m.GetResourceVersion() diff --git a/pkg/apiserver/registry/stats/networkpolicystats/rest_test.go b/pkg/apiserver/registry/stats/networkpolicystats/rest_test.go index 337c8ef185e..40e9f5c4da7 100644 --- a/pkg/apiserver/registry/stats/networkpolicystats/rest_test.go +++ b/pkg/apiserver/registry/stats/networkpolicystats/rest_test.go @@ -17,6 +17,7 @@ package networkpolicystats import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -60,6 +61,13 @@ func (p *fakeStatsProvider) GetNetworkPolicyStats(namespace, name string) (*stat return &m, true } +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &statsv1alpha1.NetworkPolicyStats{}, r.New()) + assert.Equal(t, &statsv1alpha1.NetworkPolicyStatsList{}, r.NewList()) + assert.True(t, r.NamespaceScoped()) +} + func TestRESTGet(t *testing.T) { tests := []struct { name string @@ -304,3 +312,58 @@ func TestRESTList(t *testing.T) { }) } } + +func TestRESTConvertToTable(t *testing.T) { + stats := &statsv1alpha1.NetworkPolicyStats{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "bar", + CreationTimestamp: metav1.Time{Time: time.Now()}, + }, + TrafficStats: statsv1alpha1.TrafficStats{ + Packets: 10, + Bytes: 2000, + Sessions: 5, + }, + } + tests := []struct { + name string + object runtime.Object + expectedTable *metav1.Table + }{ + { + name: "one object", + object: stats, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{"bar", int64(5), int64(10), int64(2000), stats.CreationTimestamp.Format("2006-01-02T15:04:05Z")}, + Object: runtime.RawExtension{Object: stats}, + }, + }, + }, + }, + { + name: "multiple objects", + object: &statsv1alpha1.NetworkPolicyStatsList{Items: []statsv1alpha1.NetworkPolicyStats{*stats}}, + expectedTable: &metav1.Table{ + ColumnDefinitions: tableColumnDefinitions, + Rows: []metav1.TableRow{ + { + Cells: []interface{}{"bar", int64(5), int64(10), int64(2000), stats.CreationTimestamp.Format("2006-01-02T15:04:05Z")}, + Object: runtime.RawExtension{Object: stats}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &REST{} + actualTable, err := r.ConvertToTable(context.TODO(), tt.object, &metav1.TableOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.expectedTable, actualTable) + }) + } +} diff --git a/pkg/apiserver/registry/system/controllerinfo/rest.go b/pkg/apiserver/registry/system/controllerinfo/rest.go index 6478be56e84..6d9b89cbd16 100644 --- a/pkg/apiserver/registry/system/controllerinfo/rest.go +++ b/pkg/apiserver/registry/system/controllerinfo/rest.go @@ -64,7 +64,7 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) info := r.getControllerInfo() // The provided name should match the AntreaControllerInfo.Name. if info.Name != name { - return nil, errors.NewNotFound(system.Resource("clusterinfos"), name) + return nil, errors.NewNotFound(system.Resource("controllerinfos"), name) } return info, nil } @@ -91,5 +91,5 @@ func (r *REST) NamespaceScoped() bool { } func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return rest.NewDefaultTableConvertor(system.Resource("clusterinfos")).ConvertToTable(ctx, obj, tableOptions) + return rest.NewDefaultTableConvertor(system.Resource("controllerinfos")).ConvertToTable(ctx, obj, tableOptions) } diff --git a/pkg/apiserver/registry/system/controllerinfo/rest_test.go b/pkg/apiserver/registry/system/controllerinfo/rest_test.go index fd878e50d97..afbd40b7cee 100644 --- a/pkg/apiserver/registry/system/controllerinfo/rest_test.go +++ b/pkg/apiserver/registry/system/controllerinfo/rest_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -26,6 +27,7 @@ import ( clientset "k8s.io/client-go/kubernetes" "antrea.io/antrea/pkg/apis/crd/v1beta1" + system "antrea.io/antrea/pkg/apis/system/v1beta1" ) type fakeControllerQuerier struct{} @@ -34,6 +36,45 @@ func (q *fakeControllerQuerier) GetControllerInfo(info *v1beta1.AntreaController func (q *fakeControllerQuerier) GetK8sClient() clientset.Interface { return nil } +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &v1beta1.AntreaControllerInfo{}, r.New()) + assert.Equal(t, &v1beta1.AntreaControllerInfoList{}, r.NewList()) + assert.False(t, r.NamespaceScoped()) +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + objName string + expectedObj runtime.Object + expectedErr error + }{ + { + name: "name matches", + objName: ControllerInfoResourceName, + expectedObj: &v1beta1.AntreaControllerInfo{ + ObjectMeta: v1.ObjectMeta{ + Name: ControllerInfoResourceName, + }, + }, + }, + { + name: "name does not match", + objName: "foo", + expectedErr: errors.NewNotFound(system.Resource("controllerinfos"), "foo"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewREST(&fakeControllerQuerier{}) + actualObj, err := r.Get(context.TODO(), tt.objName, &v1.GetOptions{}) + assert.Equal(t, tt.expectedErr, err) + assert.Equal(t, tt.expectedObj, actualObj) + }) + } +} + func TestRESTList(t *testing.T) { tests := []struct { name string diff --git a/pkg/apiserver/registry/system/supportbundle/rest_test.go b/pkg/apiserver/registry/system/supportbundle/rest_test.go index b283f5331ee..5a372ec21e2 100644 --- a/pkg/apiserver/registry/system/supportbundle/rest_test.go +++ b/pkg/apiserver/registry/system/supportbundle/rest_test.go @@ -22,7 +22,10 @@ import ( "time" "github.com/spf13/afero" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/exec" exectesting "k8s.io/utils/exec/testing" @@ -41,6 +44,12 @@ func (te *testExec) Command(cmd string, args ...string) exec.Cmd { return fakeCmd } +func TestREST(t *testing.T) { + r := &supportBundleREST{} + assert.Equal(t, &system.SupportBundle{}, r.New()) + assert.False(t, r.NamespaceScoped()) +} + func TestClean(t *testing.T) { defaultFS = afero.NewMemMapFs() defaultExecutor = new(testExec) @@ -81,3 +90,87 @@ func TestClean(t *testing.T) { }) } } + +func TestCollect(t *testing.T) { + defaultFS = afero.NewMemMapFs() + defaultExecutor = new(testExec) + defer func() { + defaultFS = afero.NewOsFs() + defaultExecutor = exec.New() + }() + + storage := NewControllerStorage() + dumper1Executed := false + dumper1 := func(string) error { + dumper1Executed = true + return nil + } + dumper2Executed := false + dumper2 := func(string) error { + dumper2Executed = true + return nil + } + collectedBundle, err := storage.SupportBundle.collect(context.TODO(), dumper1, dumper2) + require.NoError(t, err) + require.NotEmpty(t, collectedBundle.Filepath) + defer defaultFS.Remove(collectedBundle.Filepath) + assert.Equal(t, system.SupportBundleStatusCollected, collectedBundle.Status) + assert.NotEmpty(t, collectedBundle.Sum) + assert.Greater(t, collectedBundle.Size, uint32(0)) + assert.True(t, dumper1Executed) + assert.True(t, dumper2Executed) + exist, err := afero.Exists(defaultFS, collectedBundle.Filepath) + require.NoError(t, err) + require.True(t, exist) +} + +func TestControllerStorage(t *testing.T) { + defaultFS = afero.NewMemMapFs() + defaultExecutor = new(testExec) + defer func() { + defaultFS = afero.NewOsFs() + defaultExecutor = exec.New() + }() + + storage := NewControllerStorage() + _, err := storage.SupportBundle.Create(context.TODO(), &system.SupportBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: modeController, + }, + Status: system.SupportBundleStatusNone, + Since: "-1h", + }, nil, nil) + require.NoError(t, err) + + var collectedBundle *system.SupportBundle + assert.Eventually(t, func() bool { + object, err := storage.SupportBundle.Get(context.TODO(), modeController, nil) + require.NoError(t, err) + collectedBundle = object.(*system.SupportBundle) + return collectedBundle.Status == system.SupportBundleStatusCollected + }, time.Second*2, time.Millisecond*100) + require.NotEmpty(t, collectedBundle.Filepath) + defer defaultFS.Remove(collectedBundle.Filepath) + assert.NotEmpty(t, collectedBundle.Sum) + assert.Greater(t, collectedBundle.Size, uint32(0)) + exist, err := afero.Exists(defaultFS, collectedBundle.Filepath) + require.NoError(t, err) + require.True(t, exist) + + _, err = storage.SupportBundle.Get(context.TODO(), modeAgent, nil) + assert.Equal(t, errors.NewNotFound(system.Resource("supportBundle"), modeAgent), err) + + _, deleted, err := storage.SupportBundle.Delete(context.TODO(), modeAgent, nil, nil) + assert.Equal(t, errors.NewNotFound(system.Resource("supportBundle"), modeAgent), err) + assert.False(t, deleted) + + _, deleted, err = storage.SupportBundle.Delete(context.TODO(), modeController, nil, nil) + assert.NoError(t, err) + assert.True(t, deleted) + object, err := storage.SupportBundle.Get(context.TODO(), modeController, nil) + assert.NoError(t, err) + assert.Equal(t, &system.SupportBundle{ + ObjectMeta: metav1.ObjectMeta{Name: modeController}, + Status: system.SupportBundleStatusNone, + }, object) +} diff --git a/pkg/support/dump.go b/pkg/support/dump.go index c8c621b7de1..1616c032f5b 100644 --- a/pkg/support/dump.go +++ b/pkg/support/dump.go @@ -175,13 +175,13 @@ func directoryCopy(fs afero.Fs, targetDir string, srcDir string, prefixFilter st targetPath := path.Join(targetDir, info.Name()) targetFile, err := fs.Create(targetPath) if err != nil { - return err + return fmt.Errorf("error when creating target file %s: %w", targetPath, err) } defer targetFile.Close() srcFile, err := fs.Open(filePath) if err != nil { - return err + return fmt.Errorf("error when opening source file %s: %w", filePath, err) } defer srcFile.Close()