Skip to content

Commit

Permalink
api: define API for MultiClusterService
Browse files Browse the repository at this point in the history
Signed-off-by: jwcesign <[email protected]>
  • Loading branch information
jwcesign committed Nov 23, 2023
1 parent bf1098b commit 6b73315
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 23 deletions.
22 changes: 21 additions & 1 deletion api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -21160,6 +21160,18 @@
"types"
],
"properties": {
"clientLocations": {
"description": "ServiceConsumers specifies the clusters which will request the service. If leave it empty, the service will be exposed to all clusters.",
"type": "array",
"items": {
"type": "string",
"default": ""
}
},
"discoveryStrategy": {
"description": "DiscoveryStrategy specifies how to sync the EndpointSlice from one cluster to another cluster. Default to be RemoteAndLocal, which means syncing the EndpointSlice from one cluster to another cluster from the beginning, the requests to this service will routed to all of them.",
"type": "string"
},
"ports": {
"description": "Ports is the list of ports that are exposed by this MultiClusterService. No specified port will be filtered out during the service exposure and discovery process. All ports in the referencing service will be exposed by default.",
"type": "array",
Expand All @@ -21169,10 +21181,18 @@
}
},
"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 ServiceProviders/ServiceConsumers.",
"default": {},
"$ref": "#/definitions/com.github.karmada-io.karmada.pkg.apis.networking.v1alpha1.ExposureRange"
},
"serverLocations": {
"description": "ServiceProviders specifies the clusters which will provide the service backend. If leave it empty, we will collect the backend endpoints from all clusters and sync them to the ClientLocations.",
"type": "array",
"items": {
"type": "string",
"default": ""
}
},
"types": {
"description": "Types specifies how to expose the service referencing by this MultiClusterService.",
"type": "array",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ spec:
spec:
description: Spec is the desired state of the MultiClusterService.
properties:
clientLocations:
description: ServiceConsumers specifies the clusters which will request
the service. If leave it empty, the service will be exposed to all
clusters.
items:
type: string
type: array
discoveryStrategy:
description: DiscoveryStrategy specifies how to sync the EndpointSlice
from one cluster to another cluster. Default to be RemoteAndLocal,
which means syncing the EndpointSlice from one cluster to another
cluster from the beginning, the requests to this service will routed
to all of them.
type: string
ports:
description: Ports is the list of ports that are exposed by this MultiClusterService.
No specified port will be filtered out during the service exposure
Expand All @@ -66,18 +80,25 @@ 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 ServiceProviders/ServiceConsumers.'
properties:
clusterNames:
description: ClusterNames is the list of clusters to be selected.
items:
type: string
type: array
type: object
serverLocations:
description: ServiceProviders specifies the clusters which will provide
the service backend. If leave it empty, we will collect the backend
endpoints from all clusters and sync them to the ClientLocations.
items:
type: string
type: array
types:
description: Types specifies how to expose the service referencing
by this MultiClusterService.
Expand Down
27 changes: 27 additions & 0 deletions pkg/apis/networking/v1alpha1/service_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,43 @@ type MultiClusterServiceSpec struct {
// +optional
Ports []ExposurePort `json:"ports,omitempty"`

// DiscoveryStrategy specifies how to sync the EndpointSlice from one cluster to another cluster.
// Default to be RemoteAndLocal, which means syncing the EndpointSlice from one cluster
// to another cluster from the beginning, the requests to this service will routed to all of them.
// +optional
DiscoveryStrategy ServiceDiscoveryStrategy `json:"discoveryStrategy,omitempty"`

// 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 ServiceProviders/ServiceConsumers.
// +optional
Range ExposureRange `json:"range,omitempty"`

// ServiceProviders specifies the clusters which will provide the service backend.
// If leave it empty, we will collect the backend endpoints from all clusters and sync
// them to the ClientLocations.
// +optional
ServiceProviders []string `json:"serverLocations,omitempty"`

// ServiceConsumers specifies the clusters which will request the service.
// If leave it empty, the service will be exposed to all clusters.
// +optional
ServiceConsumers []string `json:"clientLocations,omitempty"`
}

// ServiceDiscoverStrategy describes how to sync the EndpointSlice between clusters.
type ServiceDiscoveryStrategy string

const (
// RemoteAndLocal means syncing the EndpointSlice of the service from one cluster
// to another cluster from the beginning, the requests to this service will routed to all of them.
RemoteAndLocal ServiceDiscoveryStrategy = "RemoteAndLocal"
)

// ExposureType describes how to expose the service.
type ExposureType string

Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 38 additions & 1 deletion pkg/generated/openapi/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 12 additions & 3 deletions pkg/webhook/multiclusterservice/validating.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,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("serverLocaltions")
for i := range mcs.Spec.ServiceProviders {
clusterNamePath := clusterNamesPath.Index(i)
clusterName := mcs.Spec.Range.ClusterNames[i]
clusterName := mcs.Spec.ServiceProviders[i]
if errMegs := clustervalidation.ValidateClusterName(clusterName); len(errMegs) > 0 {
allErrs = append(allErrs, field.Invalid(clusterNamePath, clusterName, strings.Join(errMegs, ",")))
}
}

clusterNamesPath = specPath.Child("range").Child("cllientLocaltions")
for i := range mcs.Spec.ServiceConsumers {
clusterNamePath := clusterNamesPath.Index(i)
clusterName := mcs.Spec.ServiceConsumers[i]
if errMegs := clustervalidation.ValidateClusterName(clusterName); len(errMegs) > 0 {
allErrs = append(allErrs, field.Invalid(clusterNamePath, clusterName, strings.Join(errMegs, ",")))
}
Expand Down
27 changes: 11 additions & 16 deletions pkg/webhook/multiclusterservice/validating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) {
networkingv1alpha1.ExposureTypeLoadBalancer,
networkingv1alpha1.ExposureTypeCrossCluster,
},
Range: networkingv1alpha1.ExposureRange{
ClusterNames: []string{"member1", "member2"},
},
ServiceProviders: []string{"member1", "member2"},
ServiceConsumers: []string{"member1", "member2"},
},
},
expectedErr: field.ErrorList{},
Expand All @@ -62,9 +61,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) {
networkingv1alpha1.ExposureTypeLoadBalancer,
networkingv1alpha1.ExposureTypeLoadBalancer,
},
Range: networkingv1alpha1.ExposureRange{
ClusterNames: []string{"member1"},
},
ServiceProviders: []string{"member1", "member2"},
ServiceConsumers: []string{"member1", "member2"},
},
},
expectedErr: field.ErrorList{field.Duplicate(specFld.Child("ports").Index(1).Child("name"), "foo")},
Expand All @@ -82,9 +80,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) {
Types: []networkingv1alpha1.ExposureType{
networkingv1alpha1.ExposureTypeLoadBalancer,
},
Range: networkingv1alpha1.ExposureRange{
ClusterNames: []string{"member1"},
},
ServiceProviders: []string{"member1", "member2"},
ServiceConsumers: []string{"member1", "member2"},
},
},
expectedErr: field.ErrorList{field.Invalid(specFld.Child("ports").Index(0).Child("port"), int32(163121), validation.InclusiveRangeError(1, 65535))},
Expand All @@ -102,9 +99,8 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) {
Types: []networkingv1alpha1.ExposureType{
"",
},
Range: networkingv1alpha1.ExposureRange{
ClusterNames: []string{"member1"},
},
ServiceProviders: []string{"member1", "member2"},
ServiceConsumers: []string{"member1", "member2"},
},
},
expectedErr: field.ErrorList{field.Invalid(specFld.Child("types").Index(0), networkingv1alpha1.ExposureType(""), "ExposureType Error")},
Expand All @@ -122,12 +118,11 @@ func TestValidateMultiClusterServiceSpec(t *testing.T) {
Types: []networkingv1alpha1.ExposureType{
networkingv1alpha1.ExposureTypeCrossCluster,
},
Range: networkingv1alpha1.ExposureRange{
ClusterNames: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
},
ServiceProviders: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
ServiceConsumers: []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("serverLocaltions").Index(0), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "must be no more than 48 characters")},
},
}
for _, tt := range tests {
Expand Down

0 comments on commit 6b73315

Please sign in to comment.