Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update service defaults #502

Merged
merged 4 commits into from
Apr 28, 2021
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
2 changes: 2 additions & 0 deletions api/v1alpha1/proxydefaults_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type ProxyDefaultsSpec struct {
// Expose controls the default expose path configuration for Envoy.
Expose Expose `json:"expose,omitempty"`
// TransparentProxy controls configuration specific to proxies in transparent mode.
// Note: This cannot be set using the CRD and should be set using annotations on the
// services that are part of the mesh.
TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"`
}

Expand Down
183 changes: 183 additions & 0 deletions api/v1alpha1/servicedefaults_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (

const (
ServiceDefaultsKubeKind string = "servicedefaults"
defaultUpstream = "default"
overrideUpstream = "override"
)

func init() {
Expand Down Expand Up @@ -56,7 +58,92 @@ type ServiceDefaultsSpec struct {
// to be changed to a non-connect value when federating with an external system.
ExternalSNI string `json:"externalSNI,omitempty"`
// TransparentProxy controls configuration specific to proxies in transparent mode.
// Note: This cannot be set using the CRD and should be set using annotations on the
// services that are part of the mesh.
TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"`
// Mode can be one of "direct" or "transparent". "transparent" represents that inbound and outbound
// application traffic is being captured and redirected through the proxy. This mode does not
// enable the traffic redirection itself. Instead it signals Consul to configure Envoy as if
// traffic is already being redirected. "direct" represents that the proxy's listeners must be
// dialed directly by the local application and other proxies.
// Note: This cannot be set using the CRD and should be set using annotations on the
// services that are part of the mesh.
Mode *ProxyMode `json:"mode,omitempty"`
// UpstreamConfig controls default configuration settings that apply across all upstreams,
// and per-upstream configuration overrides. Note that per-upstream configuration applies
// across all federated datacenters to the pairing of source and upstream destination services.
UpstreamConfig *Upstreams `json:"upstreamConfig,omitempty"`
}

type Upstreams struct {
// Defaults contains default configuration for all upstreams of a given
// service. The name field must be empty.
Defaults *Upstream `json:"defaults,omitempty"`
// Overrides is a slice of per-service configuration. The name field is
// required.
Overrides []*Upstream `json:"overrides,omitempty"`
}

type Upstream struct {
// Name is only accepted within a service-defaults config entry.
Name string `json:"name,omitempty"`
// Namespace is only accepted within a service-defaults config entry.
Namespace string `json:"namespace,omitempty"`
// EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's
// listener.
// Note: This escape hatch is NOT compatible with the discovery chain and
// will be ignored if a discovery chain is active.
EnvoyListenerJSON string `json:"envoyListenerJSON,omitempty"`
// EnvoyClusterJSON is a complete override ("escape hatch") for the upstream's
// cluster. The Connect client TLS certificate and context will be injected
// overriding any TLS settings present.
// Note: This escape hatch is NOT compatible with the discovery chain and
// will be ignored if a discovery chain is active.
EnvoyClusterJSON string `json:"envoyClusterJSON,omitempty"`
// Protocol describes the upstream's service protocol. Valid values are "tcp",
// "http" and "grpc". Anything else is treated as tcp. This enables protocol
// aware features like per-request metrics and connection pooling, tracing,
// routing etc.
Protocol string `json:"protocol,omitempty"`
// ConnectTimeoutMs is the number of milliseconds to timeout making a new
// connection to this upstream. Defaults to 5000 (5 seconds) if not set.
ConnectTimeoutMs int `json:"connectTimeoutMs,omitempty"`
// Limits are the set of limits that are applied to the proxy for a specific upstream of a
// service instance.
Limits *UpstreamLimits `json:"limits,omitempty"`
// PassiveHealthCheck configuration determines how upstream proxy instances will
// be monitored for removal from the load balancing pool.
PassiveHealthCheck *PassiveHealthCheck `json:"passiveHealthCheck,omitempty"`
// MeshGatewayConfig controls how Mesh Gateways are configured and used.
MeshGateway MeshGateway `json:"meshGateway,omitempty"`
}

// UpstreamLimits describes the limits that are associated with a specific
// upstream of a service instance.
type UpstreamLimits struct {
// MaxConnections is the maximum number of connections the local proxy can
// make to the upstream service.
MaxConnections *int `json:"maxConnections,omitempty"`
// MaxPendingRequests is the maximum number of requests that will be queued
// waiting for an available connection. This is mostly applicable to HTTP/1.1
// clusters since all HTTP/2 requests are streamed over a single
// connection.
MaxPendingRequests *int `json:"maxPendingRequests,omitempty"`
// MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed
// to the upstream cluster at a point in time. This is mostly applicable to HTTP/2
// clusters since all HTTP/1.1 requests are limited by MaxConnections.
MaxConcurrentRequests *int `json:"maxConcurrentRequests,omitempty"`
}

// PassiveHealthCheck configuration determines how upstream proxy instances will
// be monitored for removal from the load balancing pool.
type PassiveHealthCheck struct {
// Interval between health check analysis sweeps. Each sweep may remove
// hosts or return hosts to the pool.
Interval metav1.Duration `json:"interval,omitempty"`
// MaxFailures is the count of consecutive failures that results in a host
// being removed from the pool.
MaxFailures uint32 `json:"maxFailures,omitempty"`
}

func (in *ServiceDefaults) ConsulKind() string {
Expand Down Expand Up @@ -143,6 +230,7 @@ func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry {
Expose: in.Spec.Expose.toConsul(),
ExternalSNI: in.Spec.ExternalSNI,
TransparentProxy: in.Spec.TransparentProxy.toConsul(),
UpstreamConfig: in.Spec.UpstreamConfig.toConsul(),
Meta: meta(datacenter),
}
}
Expand All @@ -153,12 +241,20 @@ func (in *ServiceDefaults) Validate(namespacesEnabled bool) error {
var allErrs field.ErrorList
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should ToConsul function convert UpstreamConfig?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also add a test that sets all possible values set for service defaults and verifies that it gets written to consul.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah..this was definitely a massive oversight by me

path := field.NewPath("spec")

validProtocols := []string{"tcp", "http", "http2", "grpc"}
if in.Spec.Protocol != "" && !sliceContains(validProtocols, in.Spec.Protocol) {
allErrs = append(allErrs, field.Invalid(path.Child("protocol"), in.Spec.Protocol, notInSliceMessage(validProtocols)))
}
ishustava marked this conversation as resolved.
Show resolved Hide resolved
if err := in.Spec.MeshGateway.validate(path.Child("meshGateway")); err != nil {
allErrs = append(allErrs, err)
}
if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil {
allErrs = append(allErrs, err)
}
if err := in.Spec.Mode.validate(path.Child("mode")); err != nil {
allErrs = append(allErrs, err)
}
allErrs = append(allErrs, in.Spec.UpstreamConfig.validate(path.Child("upstreamConfig"))...)
allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...)

if len(allErrs) > 0 {
Expand All @@ -170,6 +266,93 @@ func (in *ServiceDefaults) Validate(namespacesEnabled bool) error {
return nil
}

func (in *Upstreams) validate(path *field.Path) field.ErrorList {
if in == nil {
return nil
}
var errs field.ErrorList
if err := in.Defaults.validate(path.Child("defaults"), defaultUpstream); err != nil {
errs = append(errs, err...)
}
for i, override := range in.Overrides {
if err := override.validate(path.Child("overrides").Index(i), overrideUpstream); err != nil {
errs = append(errs, err...)
}
}
return errs
}

func (in *Upstreams) toConsul() *capi.UpstreamConfiguration {
if in == nil {
return nil
}
upstreams := &capi.UpstreamConfiguration{}
upstreams.Defaults = in.Defaults.toConsul()
for _, override := range in.Overrides {
upstreams.Overrides = append(upstreams.Overrides, override.toConsul())
}
return upstreams
}

func (in *Upstream) validate(path *field.Path, kind string) field.ErrorList {
if in == nil {
return nil
}

var errs field.ErrorList
if kind == defaultUpstream {
if in.Name != "" {
errs = append(errs, field.Invalid(path.Child("name"), in.Name, "upstream.name for a default upstream must be \"\""))
}
} else if kind == overrideUpstream {
if in.Name == "" {
errs = append(errs, field.Invalid(path.Child("name"), in.Name, "upstream.name for an override upstream cannot be \"\""))
}
}
if err := in.MeshGateway.validate(path.Child("meshGateway")); err != nil {
errs = append(errs, err)
}
return errs
}

func (in *Upstream) toConsul() *capi.UpstreamConfig {
if in == nil {
return nil
}
return &capi.UpstreamConfig{
Name: in.Name,
Namespace: in.Namespace,
EnvoyListenerJSON: in.EnvoyListenerJSON,
EnvoyClusterJSON: in.EnvoyClusterJSON,
Protocol: in.Protocol,
ConnectTimeoutMs: in.ConnectTimeoutMs,
Limits: in.Limits.toConsul(),
PassiveHealthCheck: in.PassiveHealthCheck.toConsul(),
MeshGateway: in.MeshGateway.toConsul(),
}
}

func (in *UpstreamLimits) toConsul() *capi.UpstreamLimits {
if in == nil {
return nil
}
return &capi.UpstreamLimits{
MaxConnections: in.MaxConnections,
MaxPendingRequests: in.MaxPendingRequests,
MaxConcurrentRequests: in.MaxConcurrentRequests,
}
}

func (in *PassiveHealthCheck) toConsul() *capi.PassiveHealthCheck {
if in == nil {
return nil
}
return &capi.PassiveHealthCheck{
Interval: in.Interval.Duration,
MaxFailures: in.MaxFailures,
}
}

// DefaultNamespaceFields has no behaviour here as service-defaults have no namespace specific fields.
func (in *ServiceDefaults) DefaultNamespaceFields(_ bool, _ string, _ bool, _ string) {
return
Expand Down
Loading