diff --git a/pkg/apis/intelligence/v1alpha1/types.go b/pkg/apis/intelligence/v1alpha1/types.go index 388296ef..7cd90d5b 100644 --- a/pkg/apis/intelligence/v1alpha1/types.go +++ b/pkg/apis/intelligence/v1alpha1/types.go @@ -16,16 +16,6 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -const ( - NPRecommendationJobInitial string = "Initial" - NPRecommendationJobSubsequent string = "Subsequent" - NPRecommendationStateNew string = "NEW" - NPRecommendationStateScheduled string = "SCHEDULED" - NPRecommendationStateRunning string = "RUNNING" - NPRecommendationStateCompleted string = "COMPLETED" - NPRecommendationStateFailed string = "FAILED" -) - // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -51,14 +41,15 @@ type NetworkPolicyRecommendation struct { } type NetworkPolicyRecommendationStatus struct { - State string `json:"state,omitempty"` - SparkApplication string `json:"sparkApplication,omitempty"` - CompletedStages int `json:"completedStages,omitempty"` - TotalStages int `json:"totalStages,omitempty"` - RecommendationOutcome string `json:"recommendationOutcome,omitempty"` - CompletionTimestamp metav1.Time `json:"completionTimestamp,omitempty"` - ErrorCode string `json:"errorCode,omitempty"` - ErrorMsg string `json:"errorMsg,omitempty"` + State string `json:"state,omitempty"` + SparkApplication string `json:"sparkApplication,omitempty"` + CompletedStages int `json:"completedStages,omitempty"` + TotalStages int `json:"totalStages,omitempty"` + RecommendedNetworkPolicy string `json:"recommendedNetworkPolicy,omitempty"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorMsg string `json:"errorMsg,omitempty"` + StartTime metav1.Time `json:"startTime,omitempty"` + EndTime metav1.Time `json:"endTime,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/intelligence/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/intelligence/v1alpha1/zz_generated.deepcopy.go index ae94343a..81c20a4e 100644 --- a/pkg/apis/intelligence/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/intelligence/v1alpha1/zz_generated.deepcopy.go @@ -93,7 +93,8 @@ func (in *NetworkPolicyRecommendationList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicyRecommendationStatus) DeepCopyInto(out *NetworkPolicyRecommendationStatus) { *out = *in - in.CompletionTimestamp.DeepCopyInto(&out.CompletionTimestamp) + in.StartTime.DeepCopyInto(&out.StartTime) + in.EndTime.DeepCopyInto(&out.EndTime) return } diff --git a/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest.go b/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest.go index de26a848..ebf7cb40 100644 --- a/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest.go +++ b/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest.go @@ -16,6 +16,7 @@ package networkpolicyrecommendation import ( "context" + "fmt" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -23,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + crdv1alpha1 "antrea.io/theia/pkg/apis/crd/v1alpha1" intelligence "antrea.io/theia/pkg/apis/intelligence/v1alpha1" "antrea.io/theia/pkg/querier" ) @@ -33,11 +35,15 @@ type REST struct { } var ( - _ rest.Scoper = &REST{} - _ rest.Getter = &REST{} - _ rest.Lister = &REST{} + _ rest.Scoper = &REST{} + _ rest.Getter = &REST{} + _ rest.Lister = &REST{} + _ rest.Creater = &REST{} + _ rest.GracefulDeleter = &REST{} ) +const defaultNameSpace = "flow-visibility" + // NewREST returns a REST object that will work against API services. func NewREST(nprq querier.NPRecommendationQuerier) *REST { return &REST{npRecommendationQuerier: nprq} @@ -47,36 +53,14 @@ func (r *REST) New() runtime.Object { return &intelligence.NetworkPolicyRecommendation{} } -func (r *REST) getNetworkPolicyRecommendation(name string) *intelligence.NetworkPolicyRecommendation { - npReco, err := r.npRecommendationQuerier.GetNetworkPolicyRecommendation("flow-visibility", name) - if err != nil { - return nil - } - - job := new(intelligence.NetworkPolicyRecommendation) - job.Name = npReco.Name - job.Type = npReco.Spec.JobType - job.Limit = npReco.Spec.Limit - job.PolicyType = npReco.Spec.PolicyType - job.StartInterval = npReco.Spec.StartInterval - job.EndInterval = npReco.Spec.EndInterval - job.NSAllowList = npReco.Spec.NSAllowList - job.ExcludeLabels = npReco.Spec.ExcludeLabels - job.ToServices = npReco.Spec.ToServices - job.ExecutorInstances = npReco.Spec.ExecutorInstances - job.DriverCoreRequest = npReco.Spec.DriverCoreRequest - job.DriverMemory = npReco.Spec.DriverMemory - job.ExecutorCoreRequest = npReco.Spec.ExecutorCoreRequest - job.ExecutorMemory = npReco.Spec.ExecutorMemory - return job -} - func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - job := r.getNetworkPolicyRecommendation(name) - if job == nil { + npReco, err := r.npRecommendationQuerier.GetNetworkPolicyRecommendation(defaultNameSpace, name) + if err != nil { return nil, errors.NewNotFound(intelligence.Resource("networkpolicyrecommendations"), name) } - return job, nil + intelliNPR := new(intelligence.NetworkPolicyRecommendation) + r.copyNetworkPolicyRecommendation(intelliNPR, npReco) + return intelliNPR, nil } func (r *REST) NewList() runtime.Object { @@ -84,7 +68,17 @@ func (r *REST) NewList() runtime.Object { } func (r *REST) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - list := new(intelligence.NetworkPolicyRecommendationList) + npRecoList, err := r.npRecommendationQuerier.ListNetworkPolicyRecommendation(defaultNameSpace) + if err != nil { + return nil, errors.NewBadRequest(fmt.Sprintf("error when getting NetworkPolicyRecommendationsList: %v", err)) + } + items := make([]intelligence.NetworkPolicyRecommendation, 0, len(npRecoList)) + for _, npReco := range npRecoList { + intelliNPR := new(intelligence.NetworkPolicyRecommendation) + r.copyNetworkPolicyRecommendation(intelliNPR, npReco) + items = append(items, *intelliNPR) + } + list := &intelligence.NetworkPolicyRecommendationList{Items: items} return list, nil } @@ -95,3 +89,74 @@ func (r *REST) NamespaceScoped() bool { func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { return rest.NewDefaultTableConvertor(intelligence.Resource("networkpolicyrecommendations")).ConvertToTable(ctx, obj, tableOptions) } + +func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + npReco, ok := obj.(*intelligence.NetworkPolicyRecommendation) + if !ok { + return nil, errors.NewBadRequest(fmt.Sprintf("not a NetworkPolicyRecommendation object: %T", obj)) + } + existNPReco, _ := r.npRecommendationQuerier.GetNetworkPolicyRecommendation(defaultNameSpace, npReco.Name) + if existNPReco != nil { + return nil, errors.NewBadRequest(fmt.Sprintf("networkPolicyRecommendation job exists, name: %s", npReco.Name)) + } + job := new(crdv1alpha1.NetworkPolicyRecommendation) + job.Name = npReco.Name + job.Spec.JobType = npReco.Type + job.Spec.Limit = npReco.Limit + job.Spec.PolicyType = npReco.PolicyType + job.Spec.StartInterval = npReco.StartInterval + job.Spec.EndInterval = npReco.EndInterval + job.Spec.NSAllowList = npReco.NSAllowList + job.Spec.ExcludeLabels = npReco.ExcludeLabels + job.Spec.ToServices = npReco.ToServices + job.Spec.ExecutorInstances = npReco.ExecutorInstances + job.Spec.DriverCoreRequest = npReco.DriverCoreRequest + job.Spec.DriverMemory = npReco.DriverMemory + job.Spec.ExecutorCoreRequest = npReco.ExecutorCoreRequest + job.Spec.ExecutorMemory = npReco.ExecutorMemory + _, err := r.npRecommendationQuerier.CreateNetworkPolicyRecommendation(defaultNameSpace, job) + if err != nil { + return nil, errors.NewBadRequest(fmt.Sprintf("error when creating NetworkPolicyRecommendation CR: %v", err)) + } + return &metav1.Status{Status: metav1.StatusSuccess}, nil +} + +func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + _, err := r.npRecommendationQuerier.GetNetworkPolicyRecommendation(defaultNameSpace, name) + if err != nil { + return nil, false, errors.NewBadRequest(fmt.Sprintf("NetworkPolicyRecommendation job doesn't exist, name: %s", name)) + } + err = r.npRecommendationQuerier.DeleteNetworkPolicyRecommendation(defaultNameSpace, name) + if err != nil { + return nil, false, err + } + return &metav1.Status{Status: metav1.StatusSuccess}, false, nil +} + +// copyNetworkPolicyRecommendation is used to copy NetworkPolicyRecommendation from crd to intelligence +func (r *REST) copyNetworkPolicyRecommendation(intelli *intelligence.NetworkPolicyRecommendation, crd *crdv1alpha1.NetworkPolicyRecommendation) error { + intelli.Name = crd.Name + intelli.Type = crd.Spec.JobType + intelli.Limit = crd.Spec.Limit + intelli.PolicyType = crd.Spec.PolicyType + intelli.StartInterval = crd.Spec.StartInterval + intelli.EndInterval = crd.Spec.EndInterval + intelli.NSAllowList = crd.Spec.NSAllowList + intelli.ExcludeLabels = crd.Spec.ExcludeLabels + intelli.ToServices = crd.Spec.ToServices + intelli.ExecutorInstances = crd.Spec.ExecutorInstances + intelli.DriverCoreRequest = crd.Spec.DriverCoreRequest + intelli.DriverMemory = crd.Spec.DriverMemory + intelli.ExecutorCoreRequest = crd.Spec.ExecutorCoreRequest + intelli.ExecutorMemory = crd.Spec.ExecutorMemory + intelli.Status.State = crd.Status.State + intelli.Status.SparkApplication = crd.Status.SparkApplication + intelli.Status.CompletedStages = crd.Status.CompletedStages + intelli.Status.TotalStages = crd.Status.TotalStages + intelli.Status.RecommendedNetworkPolicy = crd.Status.RecommendedNP.Spec.Yamls + intelli.Status.ErrorMsg = crd.Status.ErrorMsg + // todo: need to parse the error code + intelli.Status.StartTime = crd.Status.StartTime + intelli.Status.EndTime = crd.Status.EndTime + return nil +} diff --git a/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest_test.go b/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest_test.go new file mode 100644 index 00000000..c4d82dee --- /dev/null +++ b/pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest_test.go @@ -0,0 +1,188 @@ +// Copyright 2020 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 networkpolicyrecommendation + +import ( + "context" + "fmt" + "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/runtime" + + "antrea.io/theia/pkg/apis/crd/v1alpha1" + crdv1alpha1 "antrea.io/theia/pkg/apis/crd/v1alpha1" + intelligence "antrea.io/theia/pkg/apis/intelligence/v1alpha1" +) + +type fakeQuerier struct{} + +func TestREST_Get(t *testing.T) { + tests := []struct { + name string + nprName string + expectErr error + expectResult *intelligence.NetworkPolicyRecommendation + }{ + { + name: "Not Found case", + nprName: "non-existent-npr", + expectErr: errors.NewNotFound(intelligence.Resource("networkpolicyrecommendations"), "non-existent-npr"), + expectResult: nil, + }, + { + name: "Successful Get case", + nprName: "npr-2", + expectErr: nil, + expectResult: &intelligence.NetworkPolicyRecommendation{Type: "NPR", PolicyType: "Allow"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewREST(&fakeQuerier{}) + npr, err := r.Get(context.TODO(), tt.nprName, &v1.GetOptions{}) + assert.Equal(t, err, tt.expectErr) + if npr != nil { + assert.Equal(t, tt.expectResult, npr.(*intelligence.NetworkPolicyRecommendation)) + } else { + assert.Nil(t, tt.expectResult) + } + }) + } +} + +func TestREST_Delete(t *testing.T) { + tests := []struct { + name string + nprName string + expectErr error + }{ + { + name: "Job doesn't exist case", + nprName: "non-existent-npr", + expectErr: errors.NewBadRequest(fmt.Sprintf("NetworkPolicyRecommendation job doesn't exist, name: %s", "non-existent-npr")), + }, + { + name: "Successful Delete case", + nprName: "npr-2", + expectErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewREST(&fakeQuerier{}) + _, _, err := r.Delete(context.TODO(), tt.nprName, nil, &v1.DeleteOptions{}) + assert.Equal(t, err, tt.expectErr) + }) + } +} + +func TestREST_Create(t *testing.T) { + tests := []struct { + name string + obj runtime.Object + expectErr error + expectResult runtime.Object + }{ + { + name: "Wrong object case", + obj: &crdv1alpha1.NetworkPolicyRecommendation{}, + expectErr: errors.NewBadRequest(fmt.Sprintf("not a NetworkPolicyRecommendation object: %T", &crdv1alpha1.NetworkPolicyRecommendation{})), + expectResult: nil, + }, + { + name: "Job already exists case", + obj: &intelligence.NetworkPolicyRecommendation{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{Name: "existent-npr"}, + }, + expectErr: errors.NewBadRequest(fmt.Sprintf("networkPolicyRecommendation job exists, name: %s", "existent-npr")), + expectResult: nil, + }, + { + name: "Successful Create case", + obj: &intelligence.NetworkPolicyRecommendation{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{Name: "non-existent-npr"}, + }, + expectErr: nil, + expectResult: &v1.Status{Status: v1.StatusSuccess}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewREST(&fakeQuerier{}) + result, err := r.Create(context.TODO(), tt.obj, nil, &v1.CreateOptions{}) + assert.Equal(t, err, tt.expectErr) + assert.Equal(t, tt.expectResult, result) + }) + } +} + +func TestREST_List(t *testing.T) { + tests := []struct { + name string + expectResult []intelligence.NetworkPolicyRecommendation + }{ + { + name: "Successful List case", + expectResult: []intelligence.NetworkPolicyRecommendation{ + {ObjectMeta: v1.ObjectMeta{Name: "npr-1"}}, + {ObjectMeta: v1.ObjectMeta{Name: "npr-2"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewREST(&fakeQuerier{}) + itemList, err := r.List(context.TODO(), &internalversion.ListOptions{}) + assert.NoError(t, err) + nprList, ok := itemList.(*intelligence.NetworkPolicyRecommendationList) + assert.True(t, ok) + assert.ElementsMatch(t, tt.expectResult, nprList.Items) + }) + } +} + +func (c *fakeQuerier) GetNetworkPolicyRecommendation(namespace, name string) (*v1alpha1.NetworkPolicyRecommendation, error) { + if name == "non-existent-npr" { + return nil, fmt.Errorf("not found") + } + return &crdv1alpha1.NetworkPolicyRecommendation{ + Spec: crdv1alpha1.NetworkPolicyRecommendationSpec{ + JobType: "NPR", PolicyType: "Allow"}, + Status: crdv1alpha1.NetworkPolicyRecommendationStatus{ + RecommendedNP: &crdv1alpha1.RecommendedNetworkPolicy{}, + }, + }, nil +} + +func (c *fakeQuerier) CreateNetworkPolicyRecommendation(namespace string, networkPolicyRecommendation *v1alpha1.NetworkPolicyRecommendation) (*v1alpha1.NetworkPolicyRecommendation, error) { + return nil, nil +} + +func (c *fakeQuerier) DeleteNetworkPolicyRecommendation(namespace, name string) error { + return nil +} + +func (c *fakeQuerier) ListNetworkPolicyRecommendation(namespace string) ([]*v1alpha1.NetworkPolicyRecommendation, error) { + return []*crdv1alpha1.NetworkPolicyRecommendation{ + {ObjectMeta: v1.ObjectMeta{Name: "npr-1"}, Status: crdv1alpha1.NetworkPolicyRecommendationStatus{RecommendedNP: &crdv1alpha1.RecommendedNetworkPolicy{}}}, + {ObjectMeta: v1.ObjectMeta{Name: "npr-2"}, Status: crdv1alpha1.NetworkPolicyRecommendationStatus{RecommendedNP: &crdv1alpha1.RecommendedNetworkPolicy{}}}, + }, nil +} diff --git a/pkg/controller/networkpolicyrecommendation/controller_test.go b/pkg/controller/networkpolicyrecommendation/controller_test.go index e97eee86..4ade6ffb 100644 --- a/pkg/controller/networkpolicyrecommendation/controller_test.go +++ b/pkg/controller/networkpolicyrecommendation/controller_test.go @@ -28,6 +28,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -37,6 +38,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" k8stesting "k8s.io/client-go/testing" + "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" crdv1alpha1 "antrea.io/theia/pkg/apis/crd/v1alpha1" @@ -46,10 +48,39 @@ import ( "antrea.io/theia/third_party/sparkoperator/v1beta2" ) -const informerDefaultResync = 30 * time.Second +const ( + informerDefaultResync = 30 * time.Second + defaultNameSpace = "flow-visibility" + defaultRetryPeriod = 100 * time.Millisecond + defaultMaxRetryTime = 3 * time.Second +) var ( testNamespace = "controller-test" + npr1 = &crdv1alpha1.NetworkPolicyRecommendation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "npr1", + Namespace: defaultNameSpace, + }, + Spec: crdv1alpha1.NetworkPolicyRecommendationSpec{ + JobType: "Initial", + }, + Status: crdv1alpha1.NetworkPolicyRecommendationStatus{ + State: "Pending", + }, + } + npr2 = &crdv1alpha1.NetworkPolicyRecommendation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "npr2", + Namespace: defaultNameSpace, + }, + Spec: crdv1alpha1.NetworkPolicyRecommendationSpec{ + JobType: "Initial", + }, + Status: crdv1alpha1.NetworkPolicyRecommendationStatus{ + State: "COMPLETE", + }, + } ) type fakeController struct { @@ -663,3 +694,82 @@ func TestGetPolicyRecommendationResult(t *testing.T) { }) } } + +func initTestObjects(t *testing.T, stopCh chan struct{}) *NPRecommendationController { + fakeClient := fakecrd.NewSimpleClientset(npr1) + crdInformerFactory := crdinformers.NewSharedInformerFactory(fakeClient, informerDefaultResync) + npRecommendationInformer := crdInformerFactory.Crd().V1alpha1().NetworkPolicyRecommendations() + + c := &NPRecommendationController{ + crdClient: fakeClient, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(minRetryDelay, maxRetryDelay), "npRecommendation"), + npRecommendationInformer: npRecommendationInformer.Informer(), + npRecommendationLister: npRecommendationInformer.Lister(), + npRecommendationSynced: npRecommendationInformer.Informer().HasSynced, + } + crdInformerFactory.Start(stopCh) + // Wait until npr propagates to the informer + err := waitPropagationToInformer("npr1", c) + require.NoError(t, err) + return c +} + +func waitPropagationToInformer(name string, c *NPRecommendationController) error { + // Wait until npr propagates to the informer + err := wait.PollImmediate(defaultRetryPeriod, defaultMaxRetryTime, func() (bool, error) { + _, err := c.npRecommendationLister.NetworkPolicyRecommendations(defaultNameSpace).Get(name) + if err != nil { + return false, nil + } + return true, nil + }) + return err +} + +func TestGetNetworkPolicyRecommendation(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + c := initTestObjects(t, stopCh) + npr, err := c.GetNetworkPolicyRecommendation(defaultNameSpace, "npr1") + assert.NoError(t, err) + assert.Equal(t, npr1, npr) +} + +func TestCreateNetworkPolicyRecommendation(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + c := initTestObjects(t, stopCh) + npr, err := c.CreateNetworkPolicyRecommendation(defaultNameSpace, npr2) + assert.NoError(t, err) + assert.Equal(t, npr2, npr) + err = waitPropagationToInformer("npr2", c) + assert.NoError(t, err) + npr, err = c.npRecommendationLister.NetworkPolicyRecommendations(defaultNameSpace).Get("npr2") + assert.NoError(t, err) + assert.Equal(t, npr2, npr) +} + +func TestDeleteNetworkPolicyRecommendation(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + c := initTestObjects(t, stopCh) + err := c.DeleteNetworkPolicyRecommendation(defaultNameSpace, "npr1") + assert.NoError(t, err) + err = wait.PollImmediate(defaultRetryPeriod, defaultMaxRetryTime, func() (bool, error) { + _, err = c.npRecommendationLister.NetworkPolicyRecommendations(defaultNameSpace).Get("npr1") + if err != nil { + return true, nil + } + return false, nil + }) + assert.NoError(t, err) +} + +func TestListNetworkPolicyRecommendation(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + c := initTestObjects(t, stopCh) + list, err := c.ListNetworkPolicyRecommendation(defaultNameSpace) + assert.NoError(t, err) + assert.ElementsMatch(t, []*crdv1alpha1.NetworkPolicyRecommendation{npr1}, list) +}