From 588e71a4dc1c66ed9ecd8a305445fa2fee5317f3 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 12 Jun 2023 17:56:29 -0400 Subject: [PATCH] add crds for prioritize by locality --- .changelog/2357.txt | 3 + .../templates/crd-serviceresolvers.yaml | 8 +++ .../api/v1alpha1/serviceresolver_types.go | 59 ++++++++++++++++--- .../v1alpha1/serviceresolver_types_test.go | 28 +++++++++ ...consul.hashicorp.com_serviceresolvers.yaml | 8 +++ 5 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 .changelog/2357.txt diff --git a/.changelog/2357.txt b/.changelog/2357.txt new file mode 100644 index 0000000000..7cc35f595a --- /dev/null +++ b/.changelog/2357.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add the `PrioritizeByLocality` field to the `ServiceResolver` CRD. +``` diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index ed95c15846..f6be435c54 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -227,6 +227,14 @@ spec: type: integer type: object type: object + prioritizeByLocality: + description: PrioritizeByLocality contains the configuration for + locality aware routing. + properties: + mode: + description: Mode specifies the behavior of PrioritizeByLocality + routing. Valid values are "", "none", and "failover". + type: string redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index d00821275d..e5ce3c6fa6 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -79,6 +79,16 @@ type ServiceResolverSpec struct { // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` + // PrioritizeByLocality controls whether the locality of services within the + // local partition will be used to prioritize connectivity. + PrioritizeByLocality *ServiceResolverPrioritizeByLocality `json:"prioritizeByLocality,omitempty"` +} + +type ServiceResolverPrioritizeByLocality struct { + // Mode specifies the type of prioritization that will be performed + // when selecting nodes in the local partition. + // Valid values are: "" (default "none"), "none", and "failover". + Mode string `json:"mode,omitempty"` } type ServiceResolverRedirect struct { @@ -300,15 +310,16 @@ func (in *ServiceResolver) SyncedConditionStatus() corev1.ConditionStatus { // ToConsul converts the entry into its Consul equivalent struct. func (in *ServiceResolver) ToConsul(datacenter string) capi.ConfigEntry { return &capi.ServiceResolverConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - DefaultSubset: in.Spec.DefaultSubset, - Subsets: in.Spec.Subsets.toConsul(), - Redirect: in.Spec.Redirect.toConsul(), - Failover: in.Spec.Failover.toConsul(), - ConnectTimeout: in.Spec.ConnectTimeout.Duration, - LoadBalancer: in.Spec.LoadBalancer.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + DefaultSubset: in.Spec.DefaultSubset, + Subsets: in.Spec.Subsets.toConsul(), + Redirect: in.Spec.Redirect.toConsul(), + Failover: in.Spec.Failover.toConsul(), + ConnectTimeout: in.Spec.ConnectTimeout.Duration, + LoadBalancer: in.Spec.LoadBalancer.toConsul(), + PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), + Meta: meta(datacenter), } } @@ -338,6 +349,7 @@ func (in *ServiceResolver) Validate(consulMeta common.ConsulMeta) error { } errs = append(errs, in.Spec.Redirect.validate(path.Child("redirect"), consulMeta)...) + errs = append(errs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) errs = append(errs, in.Spec.Subsets.validate(path.Child("subsets"))...) errs = append(errs, in.Spec.LoadBalancer.validate(path.Child("loadBalancer"))...) errs = append(errs, in.validateEnterprise(consulMeta)...) @@ -520,6 +532,16 @@ func (in *ServiceResolverFailover) toConsul() *capi.ServiceResolverFailover { } } +func (in *ServiceResolverPrioritizeByLocality) toConsul() *capi.ServiceResolverPrioritizeByLocality { + if in == nil { + return nil + } + + return &capi.ServiceResolverPrioritizeByLocality{ + Mode: in.Mode, + } +} + func (in ServiceResolverFailoverTarget) toConsul() capi.ServiceResolverFailoverTarget { return capi.ServiceResolverFailoverTarget{ Service: in.Service, @@ -629,6 +651,25 @@ func (in *ServiceResolverFailover) isEmpty() bool { return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil && in.SamenessGroup == "" } +func (in *ServiceResolverPrioritizeByLocality) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + + if in == nil { + return nil + } + + switch in.Mode { + case "": + case "none": + case "failover": + default: + asJSON, _ := json.Marshal(in) + errs = append(errs, field.Invalid(path, string(asJSON), + "mode must be one of '', 'none', or 'failover'")) + } + return errs +} + func (in *ServiceResolverFailover) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { var errs field.ErrorList if in.isEmpty() { diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index d09f0809c8..3a3d5a6016 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -66,6 +66,9 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, + PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + Mode: "failover", + }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -147,6 +150,9 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, + PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ + Mode: "failover", + }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -277,6 +283,9 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, + PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + Mode: "none", + }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -358,6 +367,9 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, + PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ + Mode: "none", + }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -882,6 +894,22 @@ func TestServiceResolver_Validate(t *testing.T) { "spec.failover[failB].namespace: Invalid value: \"namespace-b\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, }, + "prioritize by locality none": { + input: &ServiceResolver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: ServiceResolverSpec{ + PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + Mode: "bad", + }, + }, + }, + namespacesEnabled: false, + expectedErrMsgs: []string{ + "mode must be one of '', 'none', or 'failover'", + }, + }, } for name, testCase := range cases { t.Run(name, func(t *testing.T) { diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 3cd3b37324..a275c9b921 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -223,6 +223,14 @@ spec: type: integer type: object type: object + prioritizeByLocality: + description: PrioritizeByLocality contains the configuration for + locality aware routing. + properties: + mode: + description: Mode specifies the behavior of PrioritizeByLocality + routing. Valid values are "", "none", and "failover". + type: string redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied