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 21, 2023
1 parent bf1098b commit 89632da
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 63 deletions.
37 changes: 19 additions & 18 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -20994,20 +20994,6 @@
}
}
},
"com.github.karmada-io.karmada.pkg.apis.networking.v1alpha1.ExposureRange": {
"description": "ExposureRange describes a list of clusters where the service is exposed. Now supports selecting cluster by name, leave the room for extend more methods such as using label selector.",
"type": "object",
"properties": {
"clusterNames": {
"description": "ClusterNames is the list of clusters to be selected.",
"type": "array",
"items": {
"type": "string",
"default": ""
}
}
}
},
"com.github.karmada-io.karmada.pkg.apis.networking.v1alpha1.MultiClusterIngress": {
"description": "MultiClusterIngress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. The structure of MultiClusterIngress is same as Ingress, indicates the Ingress in multi-clusters.",
"type": "object",
Expand Down Expand Up @@ -21160,6 +21146,18 @@
"types"
],
"properties": {
"clientLocations": {
"description": "ClientLocations 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 @@ -21168,10 +21166,13 @@
"$ref": "#/definitions/com.github.karmada-io.karmada.pkg.apis.networking.v1alpha1.ExposurePort"
}
},
"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.",
"default": {},
"$ref": "#/definitions/com.github.karmada-io.karmada.pkg.apis.networking.v1alpha1.ExposureRange"
"serverLocations": {
"description": "ServerLocaltions specifies the clusters which will provide the service backend. If leave it empty, we will collect the backend pods' IP 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.",
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: ClientLocations 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 @@ -65,19 +79,13 @@ spec:
- port
type: object
type: array
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.
properties:
clusterNames:
description: ClusterNames is the list of clusters to be selected.
items:
type: string
type: array
type: object
serverLocations:
description: ServerLocaltions specifies the clusters which will provide
the service backend. If leave it empty, we will collect the backend
pods' IP 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
31 changes: 24 additions & 7 deletions pkg/apis/networking/v1alpha1/service_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,33 @@ type MultiClusterServiceSpec struct {
// +optional
Ports []ExposurePort `json:"ports,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.
// 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
Range ExposureRange `json:"range,omitempty"`
DiscoveryStrategy ServiceDiscoveryStrategy `json:"discoveryStrategy,omitempty"`

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

// ClientLocations specifies the clusters which will request the service.
// If leave it empty, the service will be exposed to all clusters.
// +optional
ClientLocations []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
11 changes: 10 additions & 1 deletion 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.

40 changes: 35 additions & 5 deletions 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.ServerLocaltions {
clusterNamePath := clusterNamesPath.Index(i)
clusterName := mcs.Spec.Range.ClusterNames[i]
clusterName := mcs.Spec.ServerLocaltions[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.ClientLocations {
clusterNamePath := clusterNamesPath.Index(i)
clusterName := mcs.Spec.ClientLocations[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"},
},
ServerLocaltions: []string{"member1", "member2"},
ClientLocations: []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"},
},
ServerLocaltions: []string{"member1", "member2"},
ClientLocations: []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"},
},
ServerLocaltions: []string{"member1", "member2"},
ClientLocations: []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"},
},
ServerLocaltions: []string{"member1", "member2"},
ClientLocations: []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"},
},
ServerLocaltions: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
ClientLocations: []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 89632da

Please sign in to comment.