diff --git a/config/crd-base/multicluster.x-k8s.io_serviceexports.yaml b/config/crd-base/multicluster.x-k8s.io_serviceexports.yaml index 5038fdd..3e04c30 100644 --- a/config/crd-base/multicluster.x-k8s.io_serviceexports.yaml +++ b/config/crd-base/multicluster.x-k8s.io_serviceexports.yaml @@ -20,7 +20,7 @@ metadata: # The revision is updated on each CRD change and reset back to 0 on every new version. # It can be used together with the version label when installing those CRDs # and prevent any downgrades. - multicluster.x-k8s.io/crd-schema-revision: "0" + multicluster.x-k8s.io/crd-schema-revision: "1" spec: group: multicluster.x-k8s.io scope: Namespaced diff --git a/config/crd-base/multicluster.x-k8s.io_serviceimports.yaml b/config/crd-base/multicluster.x-k8s.io_serviceimports.yaml index 13c9cb0..97cb272 100644 --- a/config/crd-base/multicluster.x-k8s.io_serviceimports.yaml +++ b/config/crd-base/multicluster.x-k8s.io_serviceimports.yaml @@ -20,7 +20,7 @@ metadata: # The revision is updated on each CRD change and reset back to 0 on every new version. # It can be used together with the version label when installing those CRDs # and prevent any downgrades. - multicluster.x-k8s.io/crd-schema-revision: "0" + multicluster.x-k8s.io/crd-schema-revision: "1" spec: group: multicluster.x-k8s.io scope: Namespaced diff --git a/config/crd/multicluster.x-k8s.io_serviceexports.yaml b/config/crd/multicluster.x-k8s.io_serviceexports.yaml index 44f9a21..6355629 100644 --- a/config/crd/multicluster.x-k8s.io_serviceexports.yaml +++ b/config/crd/multicluster.x-k8s.io_serviceexports.yaml @@ -20,7 +20,7 @@ metadata: # The revision is updated on each CRD change and reset back to 0 on every new version. # It can be used together with the version label when installing those CRDs # and prevent any downgrades. - multicluster.x-k8s.io/crd-schema-revision: "0" + multicluster.x-k8s.io/crd-schema-revision: "1" spec: group: multicluster.x-k8s.io scope: Namespaced diff --git a/config/crd/multicluster.x-k8s.io_serviceimports.yaml b/config/crd/multicluster.x-k8s.io_serviceimports.yaml index 91e3bde..e7c4fbc 100644 --- a/config/crd/multicluster.x-k8s.io_serviceimports.yaml +++ b/config/crd/multicluster.x-k8s.io_serviceimports.yaml @@ -20,7 +20,7 @@ metadata: # The revision is updated on each CRD change and reset back to 0 on every new version. # It can be used together with the version label when installing those CRDs # and prevent any downgrades. - multicluster.x-k8s.io/crd-schema-revision: "0" + multicluster.x-k8s.io/crd-schema-revision: "1" spec: group: multicluster.x-k8s.io scope: Namespaced @@ -78,6 +78,15 @@ spec: - ports - type properties: + internalTrafficPolicy: + description: |- + InternalTrafficPolicy describes how nodes distribute service traffic they + receive on the ClusterIP. If set to "Local", the proxy will assume that pods + only want to talk to endpoints of the service on the same node as the pod, + dropping the traffic if there are no local endpoints. The default value, + "Cluster", uses the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string ipFamilies: description: IPFamilies identifies all the IPFamilies assigned for this ServiceImport. type: array @@ -160,6 +169,15 @@ spec: Default value is 10800(for 3 hours). type: integer format: int32 + trafficDistribution: + description: |- + TrafficDistribution offers a way to express preferences for how traffic + is distributed to Service endpoints. Implementations can use this field + as a hint, but are not required to guarantee strict adherence. If the + field is not set, the implementation will apply its default routing + strategy. If set to "PreferClose", implementations should prioritize + endpoints that are in the same zone. + type: string type: description: |- type defines the type of this service. diff --git a/conformance/service_import.go b/conformance/service_import.go index 03ce91b..53c9d0a 100644 --- a/conformance/service_import.go +++ b/conformance/service_import.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1" ) @@ -179,6 +180,38 @@ func testClusterIPServiceImport() { }) }) + Context("", func() { + BeforeEach(func() { + t.helloService.Spec.InternalTrafficPolicy = ptr.To(corev1.ServiceInternalTrafficPolicyCluster) + }) + Specify("The InternalTrafficPolicy for a ClusterSetIP ServiceImport should match the exported service's InternalTrafficPolicy", + Label(RequiredLabel), func() { + AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#internal-traffic-policy") + + t.awaitServiceImport(&clients[0], helloServiceName, false, func(g Gomega, serviceImport *v1alpha1.ServiceImport) { + g.Expect(serviceImport.Spec.InternalTrafficPolicy).To(Equal(t.helloService.Spec.InternalTrafficPolicy), reportNonConformant( + "The InternalTrafficPolicy of the ServiceImport does not match the exported Service's InternalTrafficPolicy")) + }) + }, + ) + }) + + Context("", func() { + BeforeEach(func() { + t.helloService.Spec.TrafficDistribution = ptr.To(corev1.ServiceTrafficDistributionPreferClose) + }) + Specify("The TrafficDistribution for a ClusterSetIP ServiceImport should match the exported service's TrafficDistribution", + Label(RequiredLabel), func() { + AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#traffic-distribution") + + t.awaitServiceImport(&clients[0], helloServiceName, false, func(g Gomega, serviceImport *v1alpha1.ServiceImport) { + g.Expect(serviceImport.Spec.TrafficDistribution).To(Equal(t.helloService.Spec.TrafficDistribution), reportNonConformant( + "The TrafficDistribution of the ServiceImport does not match the exported Service's TrafficDistribution")) + }) + }, + ) + }) + Specify("An IP should be allocated for a ClusterSetIP ServiceImport", Label(RequiredLabel), func() { AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#clustersetip") diff --git a/hack/verify-crd-bump-revision.sh b/hack/verify-crd-bump-revision.sh index ae81d39..fb004c1 100755 --- a/hack/verify-crd-bump-revision.sh +++ b/hack/verify-crd-bump-revision.sh @@ -20,7 +20,7 @@ BASE_REF="${PULL_BASE_SHA:-master}" crd_changed="$(git diff --name-only "${BASE_REF}" | grep -c "^config/crd/.*\.yaml$")" version_label_changed="$(git diff -U0 "${BASE_REF}" -- "config/crd-base/" | grep -c "multicluster.x-k8s.io/crd-schema-revision")" -if [ "${crd_changed}" -gt 0 ] && [ "${version_label_changed}" -ne 2 ]; then +if [ "${crd_changed}" -gt 0 ] && [ "${version_label_changed}" -ne 4 ]; then echo "❌ CRDs were modified, but the CRD revision labels were not changed in 'config/crd-base/'. Please bump the CRDs revision." exit 1 fi diff --git a/pkg/apis/v1alpha1/serviceexport.go b/pkg/apis/v1alpha1/serviceexport.go index fb79ead..94f0f64 100644 --- a/pkg/apis/v1alpha1/serviceexport.go +++ b/pkg/apis/v1alpha1/serviceexport.go @@ -265,6 +265,14 @@ const ( // annotations. ServiceExportReasonAnnotationsConflict ServiceExportConditionReason = "AnnotationsConflict" + // ServiceExportReasonInternalTrafficPolicyConflict is used with the "Conflict" + // condition when the exported service has a conflict related to internal traffic policy. + ServiceExportReasonInternalTrafficPolicyConflict ServiceExportConditionReason = "InternalTrafficPolicyConflict" + + // ServiceExportReasonTrafficDistributionConflict is used with the "Conflict" + // condition when the exported service has a conflict related to traffic distribution. + ServiceExportReasonTrafficDistributionConflict ServiceExportConditionReason = "TrafficDistributionConflict" + // ServiceExportReasonNoConflicts is used with the "Conflict" condition // when the condition is False. ServiceExportReasonNoConflicts ServiceExportConditionReason = "NoConflicts" diff --git a/pkg/apis/v1alpha1/serviceimport.go b/pkg/apis/v1alpha1/serviceimport.go index 4429664..1cca084 100644 --- a/pkg/apis/v1alpha1/serviceimport.go +++ b/pkg/apis/v1alpha1/serviceimport.go @@ -88,6 +88,24 @@ type ServiceImportSpec struct { // +kubebuilder:validation:MaxItems:=2 // +optional IPFamilies []v1.IPFamily `json:"ipFamilies,omitempty"` + + // InternalTrafficPolicy describes how nodes distribute service traffic they + // receive on the ClusterIP. If set to "Local", the proxy will assume that pods + // only want to talk to endpoints of the service on the same node as the pod, + // dropping the traffic if there are no local endpoints. The default value, + // "Cluster", uses the standard behavior of routing to all endpoints evenly + // (possibly modified by topology and other features). + // +optional + InternalTrafficPolicy *v1.ServiceInternalTrafficPolicy `json:"internalTrafficPolicy,omitempty"` + + // TrafficDistribution offers a way to express preferences for how traffic + // is distributed to Service endpoints. Implementations can use this field + // as a hint, but are not required to guarantee strict adherence. If the + // field is not set, the implementation will apply its default routing + // strategy. If set to "PreferClose", implementations should prioritize + // endpoints that are in the same zone. + // +optional + TrafficDistribution *string `json:"trafficDistribution,omitempty"` } // ServicePort represents the port on which the service is exposed diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go index 1922458..d563dd8 100644 --- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -235,6 +235,16 @@ func (in *ServiceImportSpec) DeepCopyInto(out *ServiceImportSpec) { *out = make([]corev1.IPFamily, len(*in)) copy(*out, *in) } + if in.InternalTrafficPolicy != nil { + in, out := &in.InternalTrafficPolicy, &out.InternalTrafficPolicy + *out = new(corev1.ServiceInternalTrafficPolicy) + **out = **in + } + if in.TrafficDistribution != nil { + in, out := &in.TrafficDistribution, &out.TrafficDistribution + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceImportSpec.