From e070ad779c78cd12fa630fa2eb8aeb579350d783 Mon Sep 17 00:00:00 2001 From: changzhen Date: Fri, 20 Jan 2023 17:08:26 +0800 Subject: [PATCH] add ut for cluster registry Signed-off-by: changzhen --- .../app/options/options.go | 7 +- pkg/aggregatedapiserver/apiserver.go | 38 +-- pkg/apis/cluster/scheme/regiser.go | 38 +++ pkg/registry/cluster/strategy_test.go | 323 ++++++++++++++++++ 4 files changed, 368 insertions(+), 38 deletions(-) create mode 100644 pkg/apis/cluster/scheme/regiser.go create mode 100644 pkg/registry/cluster/strategy_test.go diff --git a/cmd/aggregated-apiserver/app/options/options.go b/cmd/aggregated-apiserver/app/options/options.go index c941f4340..9b506ad33 100644 --- a/cmd/aggregated-apiserver/app/options/options.go +++ b/cmd/aggregated-apiserver/app/options/options.go @@ -24,6 +24,7 @@ import ( netutils "k8s.io/utils/net" "github.com/karmada-io/karmada/pkg/aggregatedapiserver" + clusterscheme "github.com/karmada-io/karmada/pkg/apis/cluster/scheme" clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" pkgfeatures "github.com/karmada-io/karmada/pkg/features" clientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned" @@ -54,7 +55,7 @@ func NewOptions() *Options { o := &Options{ RecommendedOptions: genericoptions.NewRecommendedOptions( defaultEtcdPathPrefix, - aggregatedapiserver.Codecs.LegacyCodec(clusterv1alpha1.SchemeGroupVersion)), + clusterscheme.Codecs.LegacyCodec(clusterv1alpha1.SchemeGroupVersion)), } o.RecommendedOptions.Etcd.StorageConfig.EncodeVersioner = runtime.NewMultiGroupVersioner(clusterv1alpha1.SchemeGroupVersion, schema.GroupKind{Group: clusterv1alpha1.GroupName}) return o @@ -126,10 +127,10 @@ func (o *Options) Config() (*aggregatedapiserver.Config, error) { } o.RecommendedOptions.Features = &genericoptions.FeatureOptions{EnableProfiling: false} - serverConfig := genericapiserver.NewRecommendedConfig(aggregatedapiserver.Codecs) + serverConfig := genericapiserver.NewRecommendedConfig(clusterscheme.Codecs) serverConfig.LongRunningFunc = customLongRunningRequestCheck(sets.NewString("watch", "proxy"), sets.NewString("attach", "exec", "proxy", "log", "portforward")) - serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(aggregatedapiserver.Scheme)) + serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(clusterscheme.Scheme)) serverConfig.OpenAPIConfig.Info.Title = "Karmada" if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil { return nil, err diff --git a/pkg/aggregatedapiserver/apiserver.go b/pkg/aggregatedapiserver/apiserver.go index 03913b582..db4db15e9 100644 --- a/pkg/aggregatedapiserver/apiserver.go +++ b/pkg/aggregatedapiserver/apiserver.go @@ -1,10 +1,6 @@ package aggregatedapiserver import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/version" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" @@ -12,38 +8,10 @@ import ( "k8s.io/klog/v2" clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster" - clusterinstall "github.com/karmada-io/karmada/pkg/apis/cluster/install" + clusterscheme "github.com/karmada-io/karmada/pkg/apis/cluster/scheme" clusterstorage "github.com/karmada-io/karmada/pkg/registry/cluster/storage" ) -var ( - // Scheme defines methods for serializing and deserializing API objects. - Scheme = runtime.NewScheme() - // Codecs provides methods for retrieving codecs and serializers for specific - // versions and content types. - Codecs = serializer.NewCodecFactory(Scheme) - // ParameterCodec handles versioning of objects that are converted to query parameters. - ParameterCodec = runtime.NewParameterCodec(Scheme) -) - -func init() { - clusterinstall.Install(Scheme) - - // we need to add the options to empty v1 - // TODO fix the server code to avoid this - metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - - // TODO: keep the generic API server from wanting this - unversioned := schema.GroupVersion{Group: "", Version: "v1"} - Scheme.AddUnversionedTypes(unversioned, - &metav1.Status{}, - &metav1.APIVersions{}, - &metav1.APIGroupList{}, - &metav1.APIGroup{}, - &metav1.APIResourceList{}, - ) -} - // ExtraConfig holds custom apiserver config type ExtraConfig struct { // Add custom config if necessary. @@ -95,9 +63,9 @@ func (c completedConfig) New(kubeClient kubernetes.Interface) (*APIServer, error GenericAPIServer: genericServer, } - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(clusterapis.GroupName, Scheme, ParameterCodec, Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(clusterapis.GroupName, clusterscheme.Scheme, clusterscheme.ParameterCodec, clusterscheme.Codecs) - clusterStorage, err := clusterstorage.NewStorage(Scheme, kubeClient, c.GenericConfig.RESTOptionsGetter) + clusterStorage, err := clusterstorage.NewStorage(clusterscheme.Scheme, kubeClient, c.GenericConfig.RESTOptionsGetter) if err != nil { klog.Errorf("Unable to create REST storage for a resource due to %v, will die", err) return nil, err diff --git a/pkg/apis/cluster/scheme/regiser.go b/pkg/apis/cluster/scheme/regiser.go new file mode 100644 index 000000000..ac454e540 --- /dev/null +++ b/pkg/apis/cluster/scheme/regiser.go @@ -0,0 +1,38 @@ +package scheme + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + + clusterinstall "github.com/karmada-io/karmada/pkg/apis/cluster/install" +) + +var ( + // Scheme defines methods for serializing and deserializing API objects. + Scheme = runtime.NewScheme() + // Codecs provides methods for retrieving codecs and serializers for specific + // versions and content types. + Codecs = serializer.NewCodecFactory(Scheme) + // ParameterCodec handles versioning of objects that are converted to query parameters. + ParameterCodec = runtime.NewParameterCodec(Scheme) +) + +func init() { + clusterinstall.Install(Scheme) + + // we need to add the options to empty v1 + // TODO fix the server code to avoid this + metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + + // TODO: keep the generic API server from wanting this + unversioned := schema.GroupVersion{Group: "", Version: "v1"} + Scheme.AddUnversionedTypes(unversioned, + &metav1.Status{}, + &metav1.APIVersions{}, + &metav1.APIGroupList{}, + &metav1.APIGroup{}, + &metav1.APIResourceList{}, + ) +} diff --git a/pkg/registry/cluster/strategy_test.go b/pkg/registry/cluster/strategy_test.go new file mode 100644 index 000000000..9633c3f42 --- /dev/null +++ b/pkg/registry/cluster/strategy_test.go @@ -0,0 +1,323 @@ +package cluster + +import ( + "fmt" + "math" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + runtimeutil "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" + + clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster" + "github.com/karmada-io/karmada/pkg/apis/cluster/mutation" + clusterscheme "github.com/karmada-io/karmada/pkg/apis/cluster/scheme" + "github.com/karmada-io/karmada/pkg/features" + _ "github.com/karmada-io/karmada/pkg/features" +) + +func getValidCluster(name string) *clusterapis.Cluster { + return &clusterapis.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: clusterapis.ClusterSpec{ + ID: "abc", + APIEndpoint: "http://0.0.0.0:80", + SyncMode: clusterapis.Push, + }, + } +} + +func setFeatureGateDuringTest(tb testing.TB, gate featuregate.FeatureGate, f featuregate.Feature, value bool) func() { + originalValue := gate.Enabled(f) + + if err := gate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", f, value)); err != nil { + tb.Errorf("error setting %s=%v: %v", f, value, err) + } + + return func() { + if err := gate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", f, originalValue)); err != nil { + tb.Errorf("error restoring %s=%v: %v", f, originalValue, err) + } + } +} + +func TestStrategy_NamespaceScoped(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + if clusterStrategy.NamespaceScoped() { + t.Errorf("Cluster must be cluster scoped") + } +} + +func TestStrategy_PrepareForCreate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + ctx := request.NewContext() + + defaultResourceModelCluster := getValidCluster("m1") + mutation.SetDefaultClusterResourceModels(defaultResourceModelCluster) + + standardResourceModelClusterBefore := getValidCluster("m2") + standardResourceModelClusterBefore.Spec.ResourceModels = []clusterapis.ResourceModel{ + { + Grade: 2, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(2, resource.DecimalSI), + Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), + }, + }, + }, + { + Grade: 1, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(0, resource.DecimalSI), + Max: *resource.NewQuantity(2, resource.DecimalSI), + }, + }, + }} + + standardResourceModelClusterAfter := getValidCluster("m2") + standardResourceModelClusterAfter.Spec.ResourceModels = []clusterapis.ResourceModel{ + { + Grade: 1, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(0, resource.DecimalSI), + Max: *resource.NewQuantity(2, resource.DecimalSI), + }, + }, + }, + { + Grade: 2, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(2, resource.DecimalSI), + Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), + }, + }, + }, + } + + tests := []struct { + name string + object runtime.Object + expect runtime.Object + gateFlag bool + }{ + { + name: "featureGate CustomizedClusterResourceModeling is false", + object: getValidCluster("cluster"), + expect: getValidCluster("cluster"), + gateFlag: false, + }, + { + name: "featureGate CustomizedClusterResourceModeling is true and cluster.Spec.ResourceModels is nil", + object: getValidCluster("m1"), + expect: defaultResourceModelCluster, + gateFlag: true, + }, + { + name: "featureGate CustomizedClusterResourceModeling is true and cluster.Spec.ResourceModels is not nil", + object: standardResourceModelClusterBefore, + expect: standardResourceModelClusterAfter, + gateFlag: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + runtimeutil.Must(utilfeature.DefaultMutableFeatureGate.Add(features.DefaultFeatureGates)) + setFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, features.CustomizedClusterResourceModeling, tt.gateFlag) + + clusterStrategy.PrepareForCreate(ctx, tt.object) + if !reflect.DeepEqual(tt.expect, tt.object) { + t.Errorf("Object mismatch! Excepted: \n%#v \ngot: \n%#v", tt.expect, tt.object) + } + }) + } +} + +func TestStrategy_PrepareForUpdate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + ctx := request.NewContext() + + standardResourceModelClusterBefore := getValidCluster("m2") + standardResourceModelClusterBefore.Spec.ResourceModels = []clusterapis.ResourceModel{ + { + Grade: 2, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(2, resource.DecimalSI), + Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), + }, + }, + }, + { + Grade: 1, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(0, resource.DecimalSI), + Max: *resource.NewQuantity(2, resource.DecimalSI), + }, + }, + }} + + standardResourceModelClusterAfter := getValidCluster("m2") + standardResourceModelClusterAfter.Spec.ResourceModels = []clusterapis.ResourceModel{ + { + Grade: 1, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(0, resource.DecimalSI), + Max: *resource.NewQuantity(2, resource.DecimalSI), + }, + }, + }, + { + Grade: 2, + Ranges: []clusterapis.ResourceModelRange{ + { + Name: clusterapis.ResourceCPU, + Min: *resource.NewQuantity(2, resource.DecimalSI), + Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), + }, + }, + }, + } + + tests := []struct { + name string + object runtime.Object + expect runtime.Object + gateFlag bool + }{ + { + name: "featureGate CustomizedClusterResourceModeling is false", + object: getValidCluster("cluster"), + expect: getValidCluster("cluster"), + gateFlag: false, + }, + { + name: "featureGate CustomizedClusterResourceModeling is true and cluster.Spec.ResourceModels is nil", + object: getValidCluster("m1"), + expect: getValidCluster("m1"), + gateFlag: true, + }, + { + name: "featureGate CustomizedClusterResourceModeling is true and cluster.Spec.ResourceModels is not nil", + object: standardResourceModelClusterBefore, + expect: standardResourceModelClusterAfter, + gateFlag: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + runtimeutil.Must(utilfeature.DefaultMutableFeatureGate.Add(features.DefaultFeatureGates)) + setFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, features.CustomizedClusterResourceModeling, tt.gateFlag) + + clusterStrategy.PrepareForUpdate(ctx, tt.object, nil) + if !reflect.DeepEqual(tt.expect, tt.object) { + t.Errorf("Object mismatch! Excepted: \n%#v \ngot: \n%#b", tt.expect, tt.object) + } + }) + } +} + +func TestStrategy_Validate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + ctx := request.NewContext() + cluster := getValidCluster("cluster") + + errs := clusterStrategy.Validate(ctx, cluster) + if len(errs) > 0 { + t.Errorf("Cluster is validate but got errs: %v", errs) + } +} + +func TestStrategy_WarningsOnCreate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + ctx := request.NewContext() + cluster := getValidCluster("cluster") + + wrs := clusterStrategy.WarningsOnCreate(ctx, cluster) + if len(wrs) > 0 { + t.Errorf("Cluster is validate but go warings: %v", wrs) + } +} + +func TestStrategy_AllowCreateOnUpdate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + if clusterStrategy.AllowCreateOnUpdate() { + t.Errorf("Cluster do not allow create on update") + } +} + +func TestStrategy_AllowUnconditionalUpdate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + if !clusterStrategy.AllowUnconditionalUpdate() { + t.Errorf("Cluster can be updated unconditionally on update") + } +} + +func TestStrategy_Canonicalize(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + clusterBefore := getValidCluster("cluster") + clusterAfter := getValidCluster("cluster") + clusterAfter.Spec.Taints = []corev1.Taint{ + { + Key: "foo", + Value: "abc", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "bar", + Effect: corev1.TaintEffectNoExecute, + }, + } + + clusterStrategy.Canonicalize(clusterBefore) + if !reflect.DeepEqual(clusterAfter, clusterAfter) { + t.Errorf("Object mismatch! Excepted: \n%#v \ngot: \n%#v", clusterAfter, clusterAfter) + } +} + +func TestStrategy_ValidateUpdate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + ctx := request.NewContext() + clusterOld := getValidCluster("cluster") + clusterOld.ResourceVersion = "abc" + clusterNew := getValidCluster("cluster") + clusterNew.ResourceVersion = "def" + + errs := clusterStrategy.ValidateUpdate(ctx, clusterOld, clusterNew) + if len(errs) > 0 { + t.Errorf("Clusters old and new are both validate but got errs: %v", errs) + } +} + +func TestStrategy_WarningsOnUpdate(t *testing.T) { + clusterStrategy := NewStrategy(clusterscheme.Scheme) + ctx := request.NewContext() + cluster := getValidCluster("cluster") + + wrs := clusterStrategy.WarningsOnUpdate(ctx, cluster, nil) + if len(wrs) > 0 { + t.Errorf("Cluster is validate but go warings: %v", wrs) + } +}