From a39416cb14cdecf31f3452f785bc463af36e2285 Mon Sep 17 00:00:00 2001 From: jwcesign Date: Tue, 21 Nov 2023 15:51:27 +0800 Subject: [PATCH] api: define API for MultiClusterService Signed-off-by: jwcesign --- api/openapi-spec/swagger.json | 18 ++++++++++- ...rking.karmada.io_multiclusterservices.yaml | 18 +++++++++-- pkg/apis/networking/v1alpha1/service_types.go | 12 +++++++ .../v1alpha1/zz_generated.deepcopy.go | 10 ++++++ pkg/generated/openapi/zz_generated.openapi.go | 32 ++++++++++++++++++- pkg/webhook/multiclusterservice/validating.go | 15 +++++++-- .../multiclusterservice/validating_test.go | 27 +++++++--------- 7 files changed, 109 insertions(+), 23 deletions(-) diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index b5a97a070..c26fc276c 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -21169,10 +21169,26 @@ } }, "range": { - "description": "Range specifies the ranges where the referencing service should be exposed. Only valid and optional in case of Types contains CrossCluster. If not set and Types contains CrossCluster, all clusters will be selected, that means the referencing service will be exposed across all registered clusters.", + "description": "Range specifies the ranges where the referencing service should be exposed. Only valid and optional in case of Types contains CrossCluster. If not set and Types contains CrossCluster, all clusters will be selected, that means the referencing service will be exposed across all registered clusters. Deprecated: in favor of ServiceProvisionClusters/ServiceConsumptionClusters.", "default": {}, "$ref": "#/definitions/com.github.karmada-io.karmada.pkg.apis.networking.v1alpha1.ExposureRange" }, + "serviceConsumptionClusters": { + "description": "ServiceConsumptionClusters specifies the clusters where the service will be exposed, for clients. If leave it empty, the service will be exposed to all clusters.", + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "serviceProvisionClusters": { + "description": "ServiceProvisionClusters specifies the clusters which will provision the service backend. If leave it empty, we will collect the backend endpoints from all clusters and sync them to the ServiceConsumptionClusters.", + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, "types": { "description": "Types specifies how to expose the service referencing by this MultiClusterService.", "type": "array", diff --git a/charts/karmada/_crds/bases/networking/networking.karmada.io_multiclusterservices.yaml b/charts/karmada/_crds/bases/networking/networking.karmada.io_multiclusterservices.yaml index f23be4b59..ef13571df 100644 --- a/charts/karmada/_crds/bases/networking/networking.karmada.io_multiclusterservices.yaml +++ b/charts/karmada/_crds/bases/networking/networking.karmada.io_multiclusterservices.yaml @@ -66,11 +66,11 @@ spec: type: object type: array range: - description: Range specifies the ranges where the referencing service + description: 'Range specifies the ranges where the referencing service should be exposed. Only valid and optional in case of Types contains CrossCluster. If not set and Types contains CrossCluster, all clusters will be selected, that means the referencing service will be exposed - across all registered clusters. + across all registered clusters. Deprecated: in favor of ServiceProvisionClusters/ServiceConsumptionClusters.' properties: clusterNames: description: ClusterNames is the list of clusters to be selected. @@ -78,6 +78,20 @@ spec: type: string type: array type: object + serviceConsumptionClusters: + description: ServiceConsumptionClusters specifies the clusters where + the service will be exposed, for clients. If leave it empty, the + service will be exposed to all clusters. + items: + type: string + type: array + serviceProvisionClusters: + description: ServiceProvisionClusters specifies the clusters which + will provision the service backend. If leave it empty, we will collect + the backend endpoints from all clusters and sync them to the ServiceConsumptionClusters. + items: + type: string + type: array types: description: Types specifies how to expose the service referencing by this MultiClusterService. diff --git a/pkg/apis/networking/v1alpha1/service_types.go b/pkg/apis/networking/v1alpha1/service_types.go index 8d158ee1c..1b4fab50a 100644 --- a/pkg/apis/networking/v1alpha1/service_types.go +++ b/pkg/apis/networking/v1alpha1/service_types.go @@ -75,8 +75,20 @@ type MultiClusterServiceSpec struct { // If not set and Types contains CrossCluster, all clusters will // be selected, that means the referencing service will be exposed // across all registered clusters. + // Deprecated: in favor of ServiceProvisionClusters/ServiceConsumptionClusters. // +optional Range ExposureRange `json:"range,omitempty"` + + // ServiceProvisionClusters specifies the clusters which will provision the service backend. + // If leave it empty, we will collect the backend endpoints from all clusters and sync + // them to the ServiceConsumptionClusters. + // +optional + ServiceProvisionClusters []string `json:"serviceProvisionClusters,omitempty"` + + // ServiceConsumptionClusters specifies the clusters where the service will be exposed, for clients. + // If leave it empty, the service will be exposed to all clusters. + // +optional + ServiceConsumptionClusters []string `json:"serviceConsumptionClusters,omitempty"` } // ExposureType describes how to expose the service. diff --git a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go index 944d18eae..2efdc6b15 100644 --- a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go @@ -182,6 +182,16 @@ func (in *MultiClusterServiceSpec) DeepCopyInto(out *MultiClusterServiceSpec) { copy(*out, *in) } in.Range.DeepCopyInto(&out.Range) + if in.ServiceProvisionClusters != nil { + in, out := &in.ServiceProvisionClusters, &out.ServiceProvisionClusters + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ServiceConsumptionClusters != nil { + in, out := &in.ServiceConsumptionClusters, &out.ServiceConsumptionClusters + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 2759b9b6b..cef9c1d3d 100755 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -2966,11 +2966,41 @@ func schema_pkg_apis_networking_v1alpha1_MultiClusterServiceSpec(ref common.Refe }, "range": { SchemaProps: spec.SchemaProps{ - Description: "Range specifies the ranges where the referencing service should be exposed. Only valid and optional in case of Types contains CrossCluster. If not set and Types contains CrossCluster, all clusters will be selected, that means the referencing service will be exposed across all registered clusters.", + Description: "Range specifies the ranges where the referencing service should be exposed. Only valid and optional in case of Types contains CrossCluster. If not set and Types contains CrossCluster, all clusters will be selected, that means the referencing service will be exposed across all registered clusters. Deprecated: in favor of ServiceProvisionClusters/ServiceConsumptionClusters.", Default: map[string]interface{}{}, Ref: ref("github.com/karmada-io/karmada/pkg/apis/networking/v1alpha1.ExposureRange"), }, }, + "serviceProvisionClusters": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceProvisionClusters specifies the clusters which will provision the service backend. If leave it empty, we will collect the backend endpoints from all clusters and sync them to the ServiceConsumptionClusters.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "serviceConsumptionClusters": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceConsumptionClusters specifies the clusters where the service will be exposed, for clients. If leave it empty, the service will be exposed to all clusters.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, Required: []string{"types"}, }, diff --git a/pkg/webhook/multiclusterservice/validating.go b/pkg/webhook/multiclusterservice/validating.go index 8aa1a7a23..d7506ae9f 100644 --- a/pkg/webhook/multiclusterservice/validating.go +++ b/pkg/webhook/multiclusterservice/validating.go @@ -103,10 +103,19 @@ func (v *ValidatingAdmission) validateMultiClusterServiceSpec(mcs *networkingv1a exposureType := mcs.Spec.Types[i] allErrs = append(allErrs, v.validateExposureType(&exposureType, typePath)...) } - clusterNamesPath := specPath.Child("range").Child("clusterNames") - for i := range mcs.Spec.Range.ClusterNames { + clusterNamesPath := specPath.Child("range").Child("serviceProvisionClusters") + for i := range mcs.Spec.ServiceProvisionClusters { clusterNamePath := clusterNamesPath.Index(i) - clusterName := mcs.Spec.Range.ClusterNames[i] + clusterName := mcs.Spec.ServiceProvisionClusters[i] + if errMegs := clustervalidation.ValidateClusterName(clusterName); len(errMegs) > 0 { + allErrs = append(allErrs, field.Invalid(clusterNamePath, clusterName, strings.Join(errMegs, ","))) + } + } + + clusterNamesPath = specPath.Child("range").Child("serviceConsumptionClusters") + for i := range mcs.Spec.ServiceConsumptionClusters { + clusterNamePath := clusterNamesPath.Index(i) + clusterName := mcs.Spec.ServiceConsumptionClusters[i] if errMegs := clustervalidation.ValidateClusterName(clusterName); len(errMegs) > 0 { allErrs = append(allErrs, field.Invalid(clusterNamePath, clusterName, strings.Join(errMegs, ","))) } diff --git a/pkg/webhook/multiclusterservice/validating_test.go b/pkg/webhook/multiclusterservice/validating_test.go index 27f21f9c8..d4d17eb5e 100755 --- a/pkg/webhook/multiclusterservice/validating_test.go +++ b/pkg/webhook/multiclusterservice/validating_test.go @@ -53,9 +53,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) { networkingv1alpha1.ExposureTypeLoadBalancer, networkingv1alpha1.ExposureTypeCrossCluster, }, - Range: networkingv1alpha1.ExposureRange{ - ClusterNames: []string{"member1", "member2"}, - }, + ServiceProvisionClusters: []string{"member1", "member2"}, + ServiceConsumptionClusters: []string{"member1", "member2"}, }, }, expectedErr: field.ErrorList{}, @@ -78,9 +77,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) { networkingv1alpha1.ExposureTypeLoadBalancer, networkingv1alpha1.ExposureTypeLoadBalancer, }, - Range: networkingv1alpha1.ExposureRange{ - ClusterNames: []string{"member1"}, - }, + ServiceProvisionClusters: []string{"member1", "member2"}, + ServiceConsumptionClusters: []string{"member1", "member2"}, }, }, expectedErr: field.ErrorList{field.Duplicate(specFld.Child("ports").Index(1).Child("name"), "foo")}, @@ -98,9 +96,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) { Types: []networkingv1alpha1.ExposureType{ networkingv1alpha1.ExposureTypeLoadBalancer, }, - Range: networkingv1alpha1.ExposureRange{ - ClusterNames: []string{"member1"}, - }, + ServiceProvisionClusters: []string{"member1", "member2"}, + ServiceConsumptionClusters: []string{"member1", "member2"}, }, }, expectedErr: field.ErrorList{field.Invalid(specFld.Child("ports").Index(0).Child("port"), int32(163121), validation.InclusiveRangeError(1, 65535))}, @@ -118,9 +115,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) { Types: []networkingv1alpha1.ExposureType{ "", }, - Range: networkingv1alpha1.ExposureRange{ - ClusterNames: []string{"member1"}, - }, + ServiceProvisionClusters: []string{"member1", "member2"}, + ServiceConsumptionClusters: []string{"member1", "member2"}, }, }, expectedErr: field.ErrorList{field.Invalid(specFld.Child("types").Index(0), networkingv1alpha1.ExposureType(""), "ExposureType Error")}, @@ -138,12 +134,11 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) { Types: []networkingv1alpha1.ExposureType{ networkingv1alpha1.ExposureTypeCrossCluster, }, - Range: networkingv1alpha1.ExposureRange{ - ClusterNames: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, - }, + ServiceProvisionClusters: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, + ServiceConsumptionClusters: []string{}, }, }, - expectedErr: field.ErrorList{field.Invalid(specFld.Child("range").Child("clusterNames").Index(0), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "must be no more than 48 characters")}, + expectedErr: field.ErrorList{field.Invalid(specFld.Child("range").Child("serviceProvisionClusters").Index(0), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "must be no more than 48 characters")}, }, } for _, tt := range tests {