diff --git a/api/protobuf-spec/github_com_openshift_origin_pkg_route_apis_route_v1.proto b/api/protobuf-spec/github_com_openshift_origin_pkg_route_apis_route_v1.proto index a4bd02dbecd9..dc0a19f6d906 100644 --- a/api/protobuf-spec/github_com_openshift_origin_pkg_route_apis_route_v1.proto +++ b/api/protobuf-spec/github_com_openshift_origin_pkg_route_apis_route_v1.proto @@ -101,9 +101,14 @@ message RoutePort { } // RouteSpec describes the hostname or path the route exposes, any security information, -// and one or more backends the route points to. Weights on each backend can define -// the balance of traffic sent to each backend - if all weights are zero the route will -// be considered to have no backends and return a standard 503 response. +// and one to four backends (services) the route points to. Requests are distributed +// among the backends depending on the weights assigned to each backend. When using +// roundrobin scheduling the portion of requests that go to each backend is the backend +// weight divided by the sum of all of the backend weights. When the backend has more than +// one endpoint the requests that end up on the backend are roundrobin distributed among +// the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests +// to the backend. If all weights are zero the route will be considered to have no backends +// and return a standard 503 response. // // The `tls` field is optional and allows specific certificates or behavior for the // route. Routers typically configure a default certificate on a wildcard domain to @@ -121,13 +126,13 @@ message RouteSpec { optional string path = 2; // to is an object the route should use as the primary backend. Only the Service kind - // is allowed, and it will be defaulted to Service. If the weight field is set to zero, - // no traffic will be sent to this service. + // is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) + // is set to zero, no traffic will be sent to this backend. optional RouteTargetReference to = 3; - // alternateBackends is an extension of the 'to' field. If more than one service needs to be - // pointed to, then use this field. Use the weight field in RouteTargetReference object - // to specify relative preference. If the weight field is zero, the backend is ignored. + // alternateBackends allows up to 3 additional backends to be assigned to the route. + // Only the Service kind is allowed, and it will be defaulted to Service. + // Use the weight field in RouteTargetReference object to specify relative preference. repeated RouteTargetReference alternateBackends = 4; // If specified, the port to be used by the router. Most routers will use all @@ -161,8 +166,8 @@ message RouteTargetReference { // name of the service/target that is being referred to. e.g. name of the service optional string name = 2; - // weight as an integer between 1 and 256 that specifies the target's relative weight - // against other target reference objects + // weight as an integer between 0 and 256, default 1, that specifies the target's relative weight + // against other target reference objects. 0 suppresses requests to this backend. optional int32 weight = 3; } diff --git a/api/swagger-spec/oapi-v1.json b/api/swagger-spec/oapi-v1.json index 4b92aa4b40c4..4ccbf4f04922 100644 --- a/api/swagger-spec/oapi-v1.json +++ b/api/swagger-spec/oapi-v1.json @@ -31412,7 +31412,7 @@ }, "v1.RouteSpec": { "id": "v1.RouteSpec", - "description": "RouteSpec describes the hostname or path the route exposes, any security information, and one or more backends the route points to. Weights on each backend can define the balance of traffic sent to each backend - if all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", + "description": "RouteSpec describes the hostname or path the route exposes, any security information, and one to four backends (services) the route points to. Requests are distributed among the backends depending on the weights assigned to each backend. When using roundrobin scheduling the portion of requests that go to each backend is the backend weight divided by the sum of all of the backend weights. When the backend has more than one endpoint the requests that end up on the backend are roundrobin distributed among the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests to the backend. If all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", "required": [ "host", "to" @@ -31428,14 +31428,14 @@ }, "to": { "$ref": "v1.RouteTargetReference", - "description": "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field is set to zero, no traffic will be sent to this service." + "description": "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) is set to zero, no traffic will be sent to this backend." }, "alternateBackends": { "type": "array", "items": { "$ref": "v1.RouteTargetReference" }, - "description": "alternateBackends is an extension of the 'to' field. If more than one service needs to be pointed to, then use this field. Use the weight field in RouteTargetReference object to specify relative preference. If the weight field is zero, the backend is ignored." + "description": "alternateBackends allows up to 3 additional backends to be assigned to the route. Only the Service kind is allowed, and it will be defaulted to Service. Use the weight field in RouteTargetReference object to specify relative preference." }, "port": { "$ref": "v1.RoutePort", @@ -31471,7 +31471,7 @@ "weight": { "type": "integer", "format": "int32", - "description": "weight as an integer between 1 and 256 that specifies the target's relative weight against other target reference objects" + "description": "weight as an integer between 0 and 256, default 1, that specifies the target's relative weight against other target reference objects. 0 suppresses requests to this backend." } } }, diff --git a/api/swagger-spec/openshift-openapi-spec.json b/api/swagger-spec/openshift-openapi-spec.json index f8d7069fda75..ab36f0ab99dd 100644 --- a/api/swagger-spec/openshift-openapi-spec.json +++ b/api/swagger-spec/openshift-openapi-spec.json @@ -99073,14 +99073,14 @@ } }, "com.github.openshift.origin.pkg.route.apis.route.v1.RouteSpec": { - "description": "RouteSpec describes the hostname or path the route exposes, any security information, and one or more backends the route points to. Weights on each backend can define the balance of traffic sent to each backend - if all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", + "description": "RouteSpec describes the hostname or path the route exposes, any security information, and one to four backends (services) the route points to. Requests are distributed among the backends depending on the weights assigned to each backend. When using roundrobin scheduling the portion of requests that go to each backend is the backend weight divided by the sum of all of the backend weights. When the backend has more than one endpoint the requests that end up on the backend are roundrobin distributed among the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests to the backend. If all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", "required": [ "host", "to" ], "properties": { "alternateBackends": { - "description": "alternateBackends is an extension of the 'to' field. If more than one service needs to be pointed to, then use this field. Use the weight field in RouteTargetReference object to specify relative preference. If the weight field is zero, the backend is ignored.", + "description": "alternateBackends allows up to 3 additional backends to be assigned to the route. Only the Service kind is allowed, and it will be defaulted to Service. Use the weight field in RouteTargetReference object to specify relative preference.", "type": "array", "items": { "$ref": "#/definitions/com.github.openshift.origin.pkg.route.apis.route.v1.RouteTargetReference" @@ -99103,7 +99103,7 @@ "$ref": "#/definitions/com.github.openshift.origin.pkg.route.apis.route.v1.TLSConfig" }, "to": { - "description": "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field is set to zero, no traffic will be sent to this service.", + "description": "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) is set to zero, no traffic will be sent to this backend.", "$ref": "#/definitions/com.github.openshift.origin.pkg.route.apis.route.v1.RouteTargetReference" }, "wildcardPolicy": { @@ -99144,7 +99144,7 @@ "type": "string" }, "weight": { - "description": "weight as an integer between 1 and 256 that specifies the target's relative weight against other target reference objects", + "description": "weight as an integer between 0 and 256, default 1, that specifies the target's relative weight against other target reference objects. 0 suppresses requests to this backend.", "type": "integer", "format": "int32" } diff --git a/pkg/openapi/zz_generated.openapi.go b/pkg/openapi/zz_generated.openapi.go index 3a177c951412..aac9db5e455a 100644 --- a/pkg/openapi/zz_generated.openapi.go +++ b/pkg/openapi/zz_generated.openapi.go @@ -8104,7 +8104,7 @@ func GetOpenAPIDefinitions(ref openapi.ReferenceCallback) map[string]openapi.Ope "github.com/openshift/origin/pkg/route/apis/route/v1.RouteSpec": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "RouteSpec describes the hostname or path the route exposes, any security information, and one or more backends the route points to. Weights on each backend can define the balance of traffic sent to each backend - if all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", + Description: "RouteSpec describes the hostname or path the route exposes, any security information, and one to four backends (services) the route points to. Requests are distributed among the backends depending on the weights assigned to each backend. When using roundrobin scheduling the portion of requests that go to each backend is the backend weight divided by the sum of all of the backend weights. When the backend has more than one endpoint the requests that end up on the backend are roundrobin distributed among the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests to the backend. If all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", Properties: map[string]spec.Schema{ "host": { SchemaProps: spec.SchemaProps{ @@ -8122,13 +8122,13 @@ func GetOpenAPIDefinitions(ref openapi.ReferenceCallback) map[string]openapi.Ope }, "to": { SchemaProps: spec.SchemaProps{ - Description: "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field is set to zero, no traffic will be sent to this service.", + Description: "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) is set to zero, no traffic will be sent to this backend.", Ref: ref("github.com/openshift/origin/pkg/route/apis/route/v1.RouteTargetReference"), }, }, "alternateBackends": { SchemaProps: spec.SchemaProps{ - Description: "alternateBackends is an extension of the 'to' field. If more than one service needs to be pointed to, then use this field. Use the weight field in RouteTargetReference object to specify relative preference. If the weight field is zero, the backend is ignored.", + Description: "alternateBackends allows up to 3 additional backends to be assigned to the route. Only the Service kind is allowed, and it will be defaulted to Service. Use the weight field in RouteTargetReference object to specify relative preference.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -8211,7 +8211,7 @@ func GetOpenAPIDefinitions(ref openapi.ReferenceCallback) map[string]openapi.Ope }, "weight": { SchemaProps: spec.SchemaProps{ - Description: "weight as an integer between 1 and 256 that specifies the target's relative weight against other target reference objects", + Description: "weight as an integer between 0 and 256, default 1, that specifies the target's relative weight against other target reference objects. 0 suppresses requests to this backend.", Type: []string{"integer"}, Format: "int32", }, diff --git a/pkg/route/apis/route/v1/generated.proto b/pkg/route/apis/route/v1/generated.proto index a4bd02dbecd9..dc0a19f6d906 100644 --- a/pkg/route/apis/route/v1/generated.proto +++ b/pkg/route/apis/route/v1/generated.proto @@ -101,9 +101,14 @@ message RoutePort { } // RouteSpec describes the hostname or path the route exposes, any security information, -// and one or more backends the route points to. Weights on each backend can define -// the balance of traffic sent to each backend - if all weights are zero the route will -// be considered to have no backends and return a standard 503 response. +// and one to four backends (services) the route points to. Requests are distributed +// among the backends depending on the weights assigned to each backend. When using +// roundrobin scheduling the portion of requests that go to each backend is the backend +// weight divided by the sum of all of the backend weights. When the backend has more than +// one endpoint the requests that end up on the backend are roundrobin distributed among +// the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests +// to the backend. If all weights are zero the route will be considered to have no backends +// and return a standard 503 response. // // The `tls` field is optional and allows specific certificates or behavior for the // route. Routers typically configure a default certificate on a wildcard domain to @@ -121,13 +126,13 @@ message RouteSpec { optional string path = 2; // to is an object the route should use as the primary backend. Only the Service kind - // is allowed, and it will be defaulted to Service. If the weight field is set to zero, - // no traffic will be sent to this service. + // is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) + // is set to zero, no traffic will be sent to this backend. optional RouteTargetReference to = 3; - // alternateBackends is an extension of the 'to' field. If more than one service needs to be - // pointed to, then use this field. Use the weight field in RouteTargetReference object - // to specify relative preference. If the weight field is zero, the backend is ignored. + // alternateBackends allows up to 3 additional backends to be assigned to the route. + // Only the Service kind is allowed, and it will be defaulted to Service. + // Use the weight field in RouteTargetReference object to specify relative preference. repeated RouteTargetReference alternateBackends = 4; // If specified, the port to be used by the router. Most routers will use all @@ -161,8 +166,8 @@ message RouteTargetReference { // name of the service/target that is being referred to. e.g. name of the service optional string name = 2; - // weight as an integer between 1 and 256 that specifies the target's relative weight - // against other target reference objects + // weight as an integer between 0 and 256, default 1, that specifies the target's relative weight + // against other target reference objects. 0 suppresses requests to this backend. optional int32 weight = 3; } diff --git a/pkg/route/apis/route/v1/swagger_doc.go b/pkg/route/apis/route/v1/swagger_doc.go index 5761763937db..e886a142d8f8 100644 --- a/pkg/route/apis/route/v1/swagger_doc.go +++ b/pkg/route/apis/route/v1/swagger_doc.go @@ -62,11 +62,11 @@ func (RoutePort) SwaggerDoc() map[string]string { } var map_RouteSpec = map[string]string{ - "": "RouteSpec describes the hostname or path the route exposes, any security information, and one or more backends the route points to. Weights on each backend can define the balance of traffic sent to each backend - if all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", + "": "RouteSpec describes the hostname or path the route exposes, any security information, and one to four backends (services) the route points to. Requests are distributed among the backends depending on the weights assigned to each backend. When using roundrobin scheduling the portion of requests that go to each backend is the backend weight divided by the sum of all of the backend weights. When the backend has more than one endpoint the requests that end up on the backend are roundrobin distributed among the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests to the backend. If all weights are zero the route will be considered to have no backends and return a standard 503 response.\n\nThe `tls` field is optional and allows specific certificates or behavior for the route. Routers typically configure a default certificate on a wildcard domain to terminate routes without explicit certificates, but custom hostnames usually must choose passthrough (send traffic directly to the backend via the TLS Server-Name- Indication field) or provide a certificate.", "host": "host is an alias/DNS that points to the service. Optional. If not specified a route name will typically be automatically chosen. Must follow DNS952 subdomain conventions.", "path": "Path that the router watches for, to route traffic for to the service. Optional", - "to": "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field is set to zero, no traffic will be sent to this service.", - "alternateBackends": "alternateBackends is an extension of the 'to' field. If more than one service needs to be pointed to, then use this field. Use the weight field in RouteTargetReference object to specify relative preference. If the weight field is zero, the backend is ignored.", + "to": "to is an object the route should use as the primary backend. Only the Service kind is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) is set to zero, no traffic will be sent to this backend.", + "alternateBackends": "alternateBackends allows up to 3 additional backends to be assigned to the route. Only the Service kind is allowed, and it will be defaulted to Service. Use the weight field in RouteTargetReference object to specify relative preference.", "port": "If specified, the port to be used by the router. Most routers will use all endpoints exposed by the service by default - set this value to instruct routers which port to use.", "tls": "The tls field provides the ability to configure certificates and termination for the route.", "wildcardPolicy": "Wildcard policy if any for the route. Currently only 'Subdomain' or 'None' is allowed.", @@ -89,7 +89,7 @@ var map_RouteTargetReference = map[string]string{ "": "RouteTargetReference specifies the target that resolve into endpoints. Only the 'Service' kind is allowed. Use 'weight' field to emphasize one over others.", "kind": "The kind of target that the route is referring to. Currently, only 'Service' is allowed", "name": "name of the service/target that is being referred to. e.g. name of the service", - "weight": "weight as an integer between 1 and 256 that specifies the target's relative weight against other target reference objects", + "weight": "weight as an integer between 0 and 256, default 1, that specifies the target's relative weight against other target reference objects. 0 suppresses requests to this backend.", } func (RouteTargetReference) SwaggerDoc() map[string]string { diff --git a/pkg/route/apis/route/v1/types.go b/pkg/route/apis/route/v1/types.go index a9cfaefbe54f..3bd2d6161686 100644 --- a/pkg/route/apis/route/v1/types.go +++ b/pkg/route/apis/route/v1/types.go @@ -47,9 +47,14 @@ type RouteList struct { } // RouteSpec describes the hostname or path the route exposes, any security information, -// and one or more backends the route points to. Weights on each backend can define -// the balance of traffic sent to each backend - if all weights are zero the route will -// be considered to have no backends and return a standard 503 response. +// and one to four backends (services) the route points to. Requests are distributed +// among the backends depending on the weights assigned to each backend. When using +// roundrobin scheduling the portion of requests that go to each backend is the backend +// weight divided by the sum of all of the backend weights. When the backend has more than +// one endpoint the requests that end up on the backend are roundrobin distributed among +// the endpoints. Weights are between 0 and 256 with default 1. Weight 0 causes no requests +// to the backend. If all weights are zero the route will be considered to have no backends +// and return a standard 503 response. // // The `tls` field is optional and allows specific certificates or behavior for the // route. Routers typically configure a default certificate on a wildcard domain to @@ -66,13 +71,13 @@ type RouteSpec struct { Path string `json:"path,omitempty" protobuf:"bytes,2,opt,name=path"` // to is an object the route should use as the primary backend. Only the Service kind - // is allowed, and it will be defaulted to Service. If the weight field is set to zero, - // no traffic will be sent to this service. + // is allowed, and it will be defaulted to Service. If the weight field (0-256 default 1) + // is set to zero, no traffic will be sent to this backend. To RouteTargetReference `json:"to" protobuf:"bytes,3,opt,name=to"` - // alternateBackends is an extension of the 'to' field. If more than one service needs to be - // pointed to, then use this field. Use the weight field in RouteTargetReference object - // to specify relative preference. If the weight field is zero, the backend is ignored. + // alternateBackends allows up to 3 additional backends to be assigned to the route. + // Only the Service kind is allowed, and it will be defaulted to Service. + // Use the weight field in RouteTargetReference object to specify relative preference. AlternateBackends []RouteTargetReference `json:"alternateBackends,omitempty" protobuf:"bytes,4,rep,name=alternateBackends"` // If specified, the port to be used by the router. Most routers will use all @@ -97,8 +102,8 @@ type RouteTargetReference struct { // name of the service/target that is being referred to. e.g. name of the service Name string `json:"name" protobuf:"bytes,2,opt,name=name"` - // weight as an integer between 1 and 256 that specifies the target's relative weight - // against other target reference objects + // weight as an integer between 0 and 256, default 1, that specifies the target's relative weight + // against other target reference objects. 0 suppresses requests to this backend. Weight *int32 `json:"weight" protobuf:"varint,3,opt,name=weight"` } diff --git a/pkg/route/apis/route/validation/validation.go b/pkg/route/apis/route/validation/validation.go index 9292eaf438d4..5cc6d5aa9e46 100644 --- a/pkg/route/apis/route/validation/validation.go +++ b/pkg/route/apis/route/validation/validation.go @@ -66,7 +66,7 @@ func ValidateRoute(route *routeapi.Route) field.ErrorList { backendPath := specPath.Child("alternateBackends") if len(route.Spec.AlternateBackends) > 3 { - result = append(result, field.Required(backendPath, "cannot specify more than 3 additional backends")) + result = append(result, field.Required(backendPath, "cannot specify more than 3 alternate backends")) } for i, svc := range route.Spec.AlternateBackends { if len(svc.Name) == 0 { diff --git a/pkg/router/template/plugin_test.go b/pkg/router/template/plugin_test.go index 22c8b83606ab..597924f1e2d5 100644 --- a/pkg/router/template/plugin_test.go +++ b/pkg/router/template/plugin_test.go @@ -178,7 +178,7 @@ func (r *TestRouter) AddRoute(route *routeapi.Route) { config := ServiceAliasConfig{ Host: route.Spec.Host, Path: route.Spec.Path, - ServiceUnitNames: getServiceUnits(route), + ServiceUnitNames: getServiceUnits(r.numberOfEndpoints, route), } for key := range config.ServiceUnitNames { @@ -188,6 +188,11 @@ func (r *TestRouter) AddRoute(route *routeapi.Route) { r.State[routeKey] = config } +func (r *TestRouter) numberOfEndpoints(key string) int32 { + su, _ := r.FindServiceUnit(key) + return int32(len(su.EndpointTable)) +} + // RemoveRoute removes the service alias config for Route func (r *TestRouter) RemoveRoute(route *routeapi.Route) { routeKey := r.routeKey(route) diff --git a/pkg/router/template/router.go b/pkg/router/template/router.go index 35f723af0540..d8fa87ab93bc 100644 --- a/pkg/router/template/router.go +++ b/pkg/router/template/router.go @@ -691,7 +691,7 @@ func (r *templateRouter) createServiceAliasConfig(route *routeapi.Route, routeKe wildcard := r.allowWildcardRoutes && wantsWildcardSupport // Get the service units and count the active ones (with a non-zero weight) - serviceUnits := getServiceUnits(route) + serviceUnits := getServiceUnits(r.numberOfEndpoints, route) activeServiceUnits := 0 for _, weight := range serviceUnits { if weight > 0 { @@ -831,6 +831,19 @@ func (r *templateRouter) removeRouteInternal(route *routeapi.Route) { r.stateChanged = true } +// numberOfEndpoints returns the number of endpoints +func (r *templateRouter) numberOfEndpoints(id string) int32 { + r.lock.Lock() + defer r.lock.Unlock() + + var eps = 0 + svc, ok := r.findMatchingServiceUnit(id) + if ok && len(svc.EndpointTable) > eps { + eps = len(svc.EndpointTable) + } + return int32(eps) +} + // AddEndpoints adds new Endpoints for the given id. func (r *templateRouter) AddEndpoints(id string, endpoints []Endpoint) { r.lock.Lock() @@ -971,23 +984,72 @@ func generateDestCertKey(config *ServiceAliasConfig) string { return config.Host + destCertPostfix } +type endpointsCounter func(key string) int32 + // getServiceUnits returns a map of service keys to their weights. -// Weight suggests the % of traffic that a given service will receive -// compared to other services pointed to by the route. -func getServiceUnits(route *routeapi.Route) map[string]int32 { +// The requests are loadbalanced among the services referenced by the route. +// The weight (0-256, default 1) sets the relative proportions each +// service gets (weight/sum_of_weights fraction of the requests). +// When the weight is 0 no traffic goes to the service. If they are +// all 0 the request is returned with 503 response. +// For each service, the requests are distributed among the endpoints. +// Each endpoint gets weight/numberOfEndpoints portion of the requests. +// The above assumes roundRobin scheduling. +func getServiceUnits(counter endpointsCounter, route *routeapi.Route) map[string]int32 { serviceUnits := make(map[string]int32) key := fmt.Sprintf("%s/%s", route.Namespace, route.Spec.To.Name) + + // find the maximum weight + var maxWeight int32 = 1 + if route.Spec.To.Weight != nil && *route.Spec.To.Weight > maxWeight { + maxWeight = *route.Spec.To.Weight + } + for _, svc := range route.Spec.AlternateBackends { + if svc.Weight != nil && *svc.Weight > maxWeight { + maxWeight = *svc.Weight + } + } + // Scale the weights to near the maximum (256). + // This improves precision when scaling for the endpoints + var scaleWeight int32 = 256 / maxWeight + + // The weight assigned to the service is distributed among the endpoints + // for example the if we have two services "A" with weight 20 and 2 endpoints + // and "B" with weight 10 and 4 endpoints the ultimate weights on + // endpoints would work out as: + // maxWeight = 20, scaleWeight 12 (division truncates 12.8 to 12) + // Scaled "A" is 240, "B" is 120 + // "A" has 2 endpoints: each gets weight 120 (of the available 240) + // "B" has 4 endpoints: each gets weight 30 (of the available 120) + + // serviceUnits[key] is the weigth for each endpoint in the service + // the sum of the weights of the endpoints is the scaled service weight. + + var numEp int32 = counter(key) + if numEp < 1 { + numEp = 1 + } if route.Spec.To.Weight == nil { - serviceUnits[key] = 0 + serviceUnits[key] = scaleWeight / numEp } else { - serviceUnits[key] = *route.Spec.To.Weight + serviceUnits[key] = (*route.Spec.To.Weight * scaleWeight) / numEp + if *route.Spec.To.Weight > 0 && serviceUnits[key] < 1 { + serviceUnits[key] = 1 + } } for _, svc := range route.Spec.AlternateBackends { key = fmt.Sprintf("%s/%s", route.Namespace, svc.Name) + numEp = counter(key) + if numEp < 1 { + numEp = 1 + } if svc.Weight == nil { - serviceUnits[key] = 0 + serviceUnits[key] = scaleWeight / numEp } else { - serviceUnits[key] = *svc.Weight + serviceUnits[key] = (*svc.Weight * scaleWeight) / numEp + if *svc.Weight > 0 && serviceUnits[key] < 1 { + serviceUnits[key] = 1 + } } } return serviceUnits diff --git a/pkg/router/template/router_test.go b/pkg/router/template/router_test.go index d447b04b048b..94b83b67c694 100644 --- a/pkg/router/template/router_test.go +++ b/pkg/router/template/router_test.go @@ -288,7 +288,7 @@ func TestCreateServiceAliasConfig(t *testing.T) { namespace := "foo" serviceName := "TestService" - serviceWeight := int32(30) + serviceWeight := int32(240) route := &routeapi.Route{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/extended/router/weighted.go b/test/extended/router/weighted.go index 2f4d3b84ff6a..dd35afd9923f 100644 --- a/test/extended/router/weighted.go +++ b/test/extended/router/weighted.go @@ -85,7 +85,7 @@ var _ = g.Describe("[Conformance][networking][router] weighted openshift router" err = expectRouteStatusCodeRepeatedExec(ns, execPodName, routerURL, "weighted.example.com", http.StatusOK, times) o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("checking that there are two weighted backends in the router stats")) + g.By(fmt.Sprintf("checking that there are three weighted backends in the router stats")) var trafficValues []string err = wait.PollImmediate(100*time.Millisecond, changeTimeoutSeconds*time.Second, func() (bool, error) { statsURL := fmt.Sprintf("http://%s:1936/;csv", routerIP) @@ -93,7 +93,7 @@ var _ = g.Describe("[Conformance][networking][router] weighted openshift router" o.Expect(err).NotTo(o.HaveOccurred()) trafficValues, err = parseStats(stats, "weightedroute", 7) o.Expect(err).NotTo(o.HaveOccurred()) - return len(trafficValues) == 2, nil + return len(trafficValues) == 3, nil }) o.Expect(err).NotTo(o.HaveOccurred()) diff --git a/test/extended/testdata/bindata.go b/test/extended/testdata/bindata.go index c088b2b77170..7a2cdbc8cc41 100644 --- a/test/extended/testdata/bindata.go +++ b/test/extended/testdata/bindata.go @@ -10758,6 +10758,23 @@ objects: name: http - containerPort: 100 protocol: UDP +- apiVersion: v1 + kind: Pod + metadata: + name: endpoint-3 + labels: + test: weightedrouter2 + endpoints: weightedrouter2 + spec: + terminationGracePeriodSeconds: 1 + containers: + - name: test + image: openshift/hello-openshift + ports: + - containerPort: 8080 + name: http + - containerPort: 100 + protocol: UDP `) func testExtendedTestdataWeightedRouterYamlBytes() ([]byte, error) { diff --git a/test/extended/testdata/weighted-router.yaml b/test/extended/testdata/weighted-router.yaml index 877106c7e6f0..504e5d206112 100644 --- a/test/extended/testdata/weighted-router.yaml +++ b/test/extended/testdata/weighted-router.yaml @@ -145,3 +145,20 @@ objects: name: http - containerPort: 100 protocol: UDP +- apiVersion: v1 + kind: Pod + metadata: + name: endpoint-3 + labels: + test: weightedrouter2 + endpoints: weightedrouter2 + spec: + terminationGracePeriodSeconds: 1 + containers: + - name: test + image: openshift/hello-openshift + ports: + - containerPort: 8080 + name: http + - containerPort: 100 + protocol: UDP