diff --git a/deployments/common/crds-v1beta1/k8s.nginx.org_transportservers.yaml b/deployments/common/crds-v1beta1/k8s.nginx.org_transportservers.yaml index 7e06c61212..00e326e39a 100644 --- a/deployments/common/crds-v1beta1/k8s.nginx.org_transportservers.yaml +++ b/deployments/common/crds-v1beta1/k8s.nginx.org_transportservers.yaml @@ -77,6 +77,10 @@ spec: description: Upstream defines an upstream. type: object properties: + failTimeout: + type: string + maxFails: + type: integer name: type: string port: diff --git a/deployments/common/crds/k8s.nginx.org_transportservers.yaml b/deployments/common/crds/k8s.nginx.org_transportservers.yaml index 880d4c3bbe..6b7f979fa3 100644 --- a/deployments/common/crds/k8s.nginx.org_transportservers.yaml +++ b/deployments/common/crds/k8s.nginx.org_transportservers.yaml @@ -78,6 +78,10 @@ spec: description: Upstream defines an upstream. type: object properties: + failTimeout: + type: string + maxFails: + type: integer name: type: string port: diff --git a/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml b/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml index 7e06c61212..00e326e39a 100644 --- a/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml +++ b/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml @@ -77,6 +77,10 @@ spec: description: Upstream defines an upstream. type: object properties: + failTimeout: + type: string + maxFails: + type: integer name: type: string port: diff --git a/docs-web/configuration/transportserver-resource.md b/docs-web/configuration/transportserver-resource.md index dbd923f098..5752f6c354 100644 --- a/docs-web/configuration/transportserver-resource.md +++ b/docs-web/configuration/transportserver-resource.md @@ -157,6 +157,8 @@ The upstream defines a destination for the TransportServer. For example: name: secure-app service: secure-app port: 8443 +maxFails: 3 +failTimeout: 30s ``` ```eval_rst @@ -179,6 +181,15 @@ port: 8443 - The port of the service. If the service doesn't define that port, NGINX will assume the service has zero endpoints and close client connections/ignore datagrams. The port must fall into the range ``1..65535``. - ``int`` - Yes + * - ``maxFails`` + - Sets the `number `_ of unsuccessful attempts to communicate with the server that should happen in the duration set by the failTimeout parameter to consider the server unavailable. The default ``1``. + - ``int`` + - No + * - ``failTimeout`` + - Sets the `time `_ during which the specified number of unsuccessful attempts to communicate with the server should happen to consider the server unavailable and the period of time the server will be considered unavailable. The default is ``10s``. + - ``string`` + - No + ``` ### UpstreamParameters diff --git a/internal/configs/transportserver.go b/internal/configs/transportserver.go index 956f0fd6bf..c1d8e7f873 100644 --- a/internal/configs/transportserver.go +++ b/internal/configs/transportserver.go @@ -97,13 +97,12 @@ func generateStreamUpstreams(transportServerEx *TransportServerEx, upstreamNamer var upstreams []version2.StreamUpstream for _, u := range transportServerEx.TransportServer.Spec.Upstreams { - name := upstreamNamer.GetNameForUpstream(u.Name) // subselector is not supported yet in TransportServer upstreams. That's why we pass "nil" here endpointsKey := GenerateEndpointsKey(transportServerEx.TransportServer.Namespace, u.Service, nil, uint16(u.Port)) endpoints := transportServerEx.Endpoints[endpointsKey] - ups := generateStreamUpstream(name, endpoints, isPlus) + ups := generateStreamUpstream(&u, upstreamNamer, endpoints, isPlus) ups.UpstreamLabels.Service = u.Service ups.UpstreamLabels.ResourceType = "transportserver" @@ -116,12 +115,18 @@ func generateStreamUpstreams(transportServerEx *TransportServerEx, upstreamNamer return upstreams } -func generateStreamUpstream(upstreamName string, endpoints []string, isPlus bool) version2.StreamUpstream { +func generateStreamUpstream(upstream *conf_v1alpha1.Upstream, upstreamNamer *upstreamNamer, endpoints []string, isPlus bool) version2.StreamUpstream { var upsServers []version2.StreamUpstreamServer + name := upstreamNamer.GetNameForUpstream(upstream.Name) + maxFails := generateIntFromPointer(upstream.MaxFails, 1) + failTimeout := generateTime(upstream.FailTimeout, "10s") + for _, e := range endpoints { s := version2.StreamUpstreamServer{ - Address: e, + Address: e, + MaxFails: maxFails, + FailTimeout: failTimeout, } upsServers = append(upsServers, s) @@ -129,12 +134,14 @@ func generateStreamUpstream(upstreamName string, endpoints []string, isPlus bool if !isPlus && len(endpoints) == 0 { upsServers = append(upsServers, version2.StreamUpstreamServer{ - Address: nginxNonExistingUnixSocket, + Address: nginxNonExistingUnixSocket, + MaxFails: maxFails, + FailTimeout: failTimeout, }) } return version2.StreamUpstream{ - Name: upstreamName, + Name: name, Servers: upsServers, } } diff --git a/internal/configs/transportserver_test.go b/internal/configs/transportserver_test.go index 9906a9cff4..23ade7b3ca 100644 --- a/internal/configs/transportserver_test.go +++ b/internal/configs/transportserver_test.go @@ -75,9 +75,11 @@ func TestGenerateTransportServerConfigForTCP(t *testing.T) { }, Upstreams: []conf_v1alpha1.Upstream{ { - Name: "tcp-app", - Service: "tcp-app-svc", - Port: 5001, + Name: "tcp-app", + Service: "tcp-app-svc", + Port: 5001, + MaxFails: intPointer(3), + FailTimeout: "40s", }, }, UpstreamParameters: &conf_v1alpha1.UpstreamParameters{ @@ -107,7 +109,9 @@ func TestGenerateTransportServerConfigForTCP(t *testing.T) { Name: "ts_default_tcp-server_tcp-app", Servers: []version2.StreamUpstreamServer{ { - Address: "10.0.0.20:5001", + Address: "10.0.0.20:5001", + MaxFails: 3, + FailTimeout: "40s", }, }, UpstreamLabels: version2.UpstreamLabels{ @@ -186,7 +190,9 @@ func TestGenerateTransportServerConfigForTLSPasstrhough(t *testing.T) { Name: "ts_default_tcp-server_tcp-app", Servers: []version2.StreamUpstreamServer{ { - Address: "10.0.0.20:5001", + Address: "10.0.0.20:5001", + MaxFails: 1, + FailTimeout: "10s", }, }, UpstreamLabels: version2.UpstreamLabels{ @@ -271,7 +277,9 @@ func TestGenerateTransportServerConfigForUDP(t *testing.T) { Name: "ts_default_udp-server_udp-app", Servers: []version2.StreamUpstreamServer{ { - Address: "10.0.0.20:5001", + Address: "10.0.0.20:5001", + MaxFails: 1, + FailTimeout: "10s", }, }, UpstreamLabels: version2.UpstreamLabels{ @@ -336,3 +344,7 @@ func TestGenerateUnixSocket(t *testing.T) { t.Errorf("generateUnixSocket() returned %q but expected %q", result, expected) } } + +func intPointer(value int) *int { + return &value +} diff --git a/internal/configs/version2/nginx-plus.transportserver.tmpl b/internal/configs/version2/nginx-plus.transportserver.tmpl index 013d963e39..286fbf51e4 100644 --- a/internal/configs/version2/nginx-plus.transportserver.tmpl +++ b/internal/configs/version2/nginx-plus.transportserver.tmpl @@ -5,7 +5,7 @@ upstream {{ $u.Name }} { random two least_conn; {{ range $s := $u.Servers }} - server {{ $s.Address }}; + server {{ $s.Address }} max_fails={{ $s.MaxFails }} fail_timeout={{ $s.FailTimeout }}; {{ end }} } {{ end }} diff --git a/internal/configs/version2/nginx.transportserver.tmpl b/internal/configs/version2/nginx.transportserver.tmpl index 2f73d6cb04..65caabda1e 100644 --- a/internal/configs/version2/nginx.transportserver.tmpl +++ b/internal/configs/version2/nginx.transportserver.tmpl @@ -5,7 +5,7 @@ upstream {{ $u.Name }} { random two least_conn; {{ range $s := $u.Servers }} - server {{ $s.Address }}; + server {{ $s.Address }} max_fails={{ $s.MaxFails }} fail_timeout={{ $s.FailTimeout }}; {{ end }} } {{ end }} diff --git a/internal/configs/version2/stream.go b/internal/configs/version2/stream.go index 5bd6bc5888..469fd669d8 100644 --- a/internal/configs/version2/stream.go +++ b/internal/configs/version2/stream.go @@ -15,7 +15,9 @@ type StreamUpstream struct { // StreamUpstreamServer defines a stream upstream server. type StreamUpstreamServer struct { - Address string + Address string + MaxFails int + FailTimeout string } // StreamServer defines a server in the stream module. diff --git a/pkg/apis/configuration/v1alpha1/types.go b/pkg/apis/configuration/v1alpha1/types.go index 16d04e1bd4..b078ae4921 100644 --- a/pkg/apis/configuration/v1alpha1/types.go +++ b/pkg/apis/configuration/v1alpha1/types.go @@ -77,9 +77,11 @@ type TransportServerListener struct { // Upstream defines an upstream. type Upstream struct { - Name string `json:"name"` - Service string `json:"service"` - Port int `json:"port"` + Name string `json:"name"` + Service string `json:"service"` + Port int `json:"port"` + FailTimeout string `json:"failTimeout"` + MaxFails *int `json:"maxFails"` } // UpstreamParameters defines parameters for an upstream. diff --git a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go index d1b7308cdd..0adca67c12 100644 --- a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go @@ -220,7 +220,9 @@ func (in *TransportServerSpec) DeepCopyInto(out *TransportServerSpec) { if in.Upstreams != nil { in, out := &in.Upstreams, &out.Upstreams *out = make([]Upstream, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.UpstreamParameters != nil { in, out := &in.UpstreamParameters, &out.UpstreamParameters @@ -253,6 +255,11 @@ func (in *TransportServerSpec) DeepCopy() *TransportServerSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Upstream) DeepCopyInto(out *Upstream) { *out = *in + if in.MaxFails != nil { + in, out := &in.MaxFails, &out.MaxFails + *out = new(int) + **out = **in + } return } diff --git a/pkg/apis/configuration/validation/transportserver.go b/pkg/apis/configuration/validation/transportserver.go index 163a493ad9..f4065eb462 100644 --- a/pkg/apis/configuration/validation/transportserver.go +++ b/pkg/apis/configuration/validation/transportserver.go @@ -148,6 +148,8 @@ func validateTransportServerUpstreams(upstreams []v1alpha1.Upstream, fieldPath * } allErrs = append(allErrs, validateServiceName(u.Service, idxPath.Child("service"))...) + allErrs = append(allErrs, validatePositiveIntOrZeroFromPointer(u.MaxFails, idxPath.Child("maxFails"))...) + allErrs = append(allErrs, validateTime((u.FailTimeout), idxPath.Child("failTimeout"))...) for _, msg := range validation.IsValidPortNum(u.Port) { allErrs = append(allErrs, field.Invalid(idxPath.Child("port"), u.Port, msg))