diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index b5a97a07076e..c26fc276cb92 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 f23be4b59594..ef13571dfb51 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 8d158ee1c600..1b4fab50ac7f 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 944d18eae51d..2efdc6b15b0e 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 2759b9b6bfec..cef9c1d3d8b0 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 8aa1a7a23b52..d7506ae9fd35 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 27f21f9c829e..d4d17eb5efc3 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 {