From 820119c3a516eb824e158f9ebda17f066f8e7391 Mon Sep 17 00:00:00 2001 From: Yun-Tang Hsu Date: Mon, 12 Sep 2022 18:14:46 -0700 Subject: [PATCH] Add NetworkPolicyRecommnedation restful handler Add unit-test Signed-off-by: Yun-Tang Hsu --- pkg/apis/intelligence/v1alpha1/types.go | 26 +-- .../v1alpha1/zz_generated.deepcopy.go | 3 +- .../networkpolicyrecommendation/rest.go | 126 +++++++++--- .../networkpolicyrecommendation/rest_test.go | 188 ++++++++++++++++++ .../networkpolicyrecommendation/controller.go | 2 +- 5 files changed, 294 insertions(+), 51 deletions(-) create mode 100644 pkg/apiserver/registry/intelligence/networkpolicyrecommendation/rest_test.go diff --git a/pkg/apis/intelligence/v1alpha1/types.go b/pkg/apis/intelligence/v1alpha1/types.go index 388296ef9..eed3c434b 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,14 @@ 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"` + 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 ae94343a0..81c20a4e3 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 de26a8488..846a0b422 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,73 @@ 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 + 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 000000000..c4d82dee4 --- /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.go b/pkg/controller/networkpolicyrecommendation/controller.go index 073c61573..fb409142c 100644 --- a/pkg/controller/networkpolicyrecommendation/controller.go +++ b/pkg/controller/networkpolicyrecommendation/controller.go @@ -497,7 +497,7 @@ func (c *NPRecommendationController) startJob(npReco *crdv1alpha1.NetworkPolicyR return err } err := c.startSparkApplication(npReco) - // Mark the NetworkPolicyRecommendation as failed and not retry if it failed due to illeagel arguments in request + // Mark the NetworkPolicyRecommendation as failed and not retry if it failed due to illegal arguments in request if err != nil && reflect.TypeOf(err) == reflect.TypeOf(IlleagelArguementError{}) { return c.updateNPRecommendationStatus( npReco,