Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/2357.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Add the `PrioritizeByLocality` field to the `ServiceResolver` CRD.
```
9 changes: 9 additions & 0 deletions charts/consul/templates/crd-serviceresolvers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ 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
type: object
redirect:
description: Redirect when configured, all attempts to resolve the
service this resolver defines will be substituted for the supplied
Expand Down
59 changes: 50 additions & 9 deletions control-plane/api/v1alpha1/serviceresolver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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)...)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down
28 changes: 28 additions & 0 deletions control-plane/api/v1alpha1/serviceresolver_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ 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
type: object
redirect:
description: Redirect when configured, all attempts to resolve the
service this resolver defines will be substituted for the supplied
Expand Down