Skip to content

Commit 719bbc8

Browse files
committed
TT-15595, fixed all load targets 0 case
1 parent efd0bb6 commit 719bbc8

3 files changed

Lines changed: 163 additions & 0 deletions

File tree

apidef/validator.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var DefaultValidationRuleSet = ValidationRuleSet{
5858
&RuleValidateIPList{},
5959
&RuleValidateEnforceTimeout{},
6060
&RuleUpstreamAuth{},
61+
&RuleLoadBalancingTargets{},
6162
}
6263

6364
func Validate(definition *APIDefinition, ruleSet ValidationRuleSet) ValidationResult {
@@ -210,6 +211,8 @@ var (
210211
ErrUpstreamOAuthAuthorizationTypeRequired = errors.New("upstream OAuth authorization type is required")
211212
// ErrInvalidUpstreamOAuthAuthorizationType is the error to return when configured OAuth authorization type is invalid.
212213
ErrInvalidUpstreamOAuthAuthorizationType = errors.New("invalid OAuth authorization type")
214+
// ErrAllLoadBalancingTargetsZeroWeight is the error to return when all load balancing targets have weight 0.
215+
ErrAllLoadBalancingTargetsZeroWeight = errors.New("all load balancing targets have weight 0, at least one target must have weight > 0")
213216
)
214217

215218
// RuleUpstreamAuth implements validations for upstream authentication configurations.
@@ -250,3 +253,20 @@ func (r *RuleUpstreamAuth) Validate(apiDef *APIDefinition, validationResult *Val
250253
validationResult.AppendError(ErrInvalidUpstreamOAuthAuthorizationType)
251254
}
252255
}
256+
257+
// RuleLoadBalancingTargets implements validations for load balancing target configurations.
258+
type RuleLoadBalancingTargets struct{}
259+
260+
// Validate validates that when load balancing is enabled, at least one target has weight > 0.
261+
func (r *RuleLoadBalancingTargets) Validate(apiDef *APIDefinition, validationResult *ValidationResult) {
262+
if !apiDef.Proxy.EnableLoadBalancing {
263+
return
264+
}
265+
266+
// In Tyk's internal representation, targets with weight N are repeated N times in Proxy.Targets
267+
// If all weights are 0, the targets list will be empty, which is invalid for load balancing
268+
if len(apiDef.Proxy.Targets) == 0 {
269+
validationResult.IsValid = false
270+
validationResult.AppendError(ErrAllLoadBalancingTargetsZeroWeight)
271+
}
272+
}

apidef/validator_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,3 +508,78 @@ func TestRuleUpstreamAuth_Validate(t *testing.T) {
508508
t.Run(tc.name, runValidationTest(apiDef, ruleSet, tc.result))
509509
}
510510
}
511+
512+
func TestRuleLoadBalancingTargets_Validate(t *testing.T) {
513+
ruleSet := ValidationRuleSet{
514+
&RuleLoadBalancingTargets{},
515+
}
516+
517+
testCases := []struct {
518+
name string
519+
apiDef *APIDefinition
520+
result ValidationResult
521+
}{
522+
{
523+
name: "load balancing disabled",
524+
apiDef: &APIDefinition{
525+
Proxy: ProxyConfig{
526+
EnableLoadBalancing: false,
527+
Targets: []string{},
528+
},
529+
},
530+
result: ValidationResult{
531+
IsValid: true,
532+
Errors: nil,
533+
},
534+
},
535+
{
536+
name: "load balancing enabled with valid targets",
537+
apiDef: &APIDefinition{
538+
Proxy: ProxyConfig{
539+
EnableLoadBalancing: true,
540+
Targets: []string{
541+
"http://target-1",
542+
"http://target-1",
543+
"http://target-2",
544+
},
545+
},
546+
},
547+
result: ValidationResult{
548+
IsValid: true,
549+
Errors: nil,
550+
},
551+
},
552+
{
553+
name: "load balancing enabled with all targets weight 0",
554+
apiDef: &APIDefinition{
555+
Proxy: ProxyConfig{
556+
EnableLoadBalancing: true,
557+
Targets: []string{},
558+
},
559+
},
560+
result: ValidationResult{
561+
IsValid: false,
562+
Errors: []error{
563+
ErrAllLoadBalancingTargetsZeroWeight,
564+
},
565+
},
566+
},
567+
{
568+
name: "load balancing disabled with empty targets",
569+
apiDef: &APIDefinition{
570+
Proxy: ProxyConfig{
571+
EnableLoadBalancing: false,
572+
Targets: []string{},
573+
},
574+
},
575+
result: ValidationResult{
576+
IsValid: true,
577+
Errors: nil,
578+
},
579+
},
580+
}
581+
582+
for _, tc := range testCases {
583+
t.Run(tc.name, runValidationTest(tc.apiDef, ruleSet, tc.result))
584+
}
585+
}

gateway/api_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,6 +2127,74 @@ func TestHandleAddApi(t *testing.T) {
21272127
assert.Equal(t, http.StatusBadRequest, statusCode)
21282128
})
21292129

2130+
t.Run("should return error when load balancing enabled with all targets weight 0", func(t *testing.T) {
2131+
apiDef := apidef.DummyAPI()
2132+
apiDef.APIID = "124"
2133+
apiDef.Proxy.EnableLoadBalancing = true
2134+
apiDef.Proxy.Targets = []string{} // Empty targets list means all weights are 0
2135+
apiDefJson, err := json.Marshal(apiDef)
2136+
require.NoError(t, err)
2137+
2138+
req, err := http.NewRequest(http.MethodPost, "http://gateway", bytes.NewBuffer(apiDefJson))
2139+
require.NoError(t, err)
2140+
2141+
response, statusCode := ts.Gw.handleAddApi(req, testFs, false)
2142+
errorResponse, ok := response.(apiStatusMessage)
2143+
require.True(t, ok)
2144+
2145+
assert.Equal(t, "Validation of API Definition failed. Reason: all load balancing targets have weight 0, at least one target must have weight > 0.", errorResponse.Message)
2146+
assert.Equal(t, http.StatusBadRequest, statusCode)
2147+
})
2148+
2149+
t.Run("should return error when OAS load balancing enabled with all targets weight 0", func(t *testing.T) {
2150+
tykExt := oas.XTykAPIGateway{
2151+
Info: oas.Info{
2152+
Name: "test api",
2153+
State: oas.State{
2154+
Active: true,
2155+
},
2156+
},
2157+
Upstream: oas.Upstream{
2158+
URL: "http://example.com",
2159+
LoadBalancing: &oas.LoadBalancing{
2160+
Enabled: true,
2161+
Targets: []oas.LoadBalancingTarget{
2162+
{URL: "http://target-1", Weight: 0},
2163+
{URL: "http://target-2", Weight: 0},
2164+
},
2165+
},
2166+
},
2167+
Server: oas.Server{
2168+
ListenPath: oas.ListenPath{
2169+
Value: "/test-lb-zero/",
2170+
Strip: false,
2171+
},
2172+
},
2173+
}
2174+
2175+
oasAPI := openapi3.T{
2176+
OpenAPI: "3.0.3",
2177+
Info: &openapi3.Info{
2178+
Title: "test api",
2179+
Version: "1",
2180+
},
2181+
Paths: openapi3.NewPaths(),
2182+
}
2183+
2184+
oasAPI.Extensions = map[string]interface{}{
2185+
oas.ExtensionTykAPIGateway: tykExt,
2186+
}
2187+
2188+
_, _ = ts.Run(t, test.TestCase{
2189+
AdminAuth: true,
2190+
Method: http.MethodPost,
2191+
Path: "/tyk/apis/oas",
2192+
Data: &oasAPI,
2193+
BodyMatch: `all load balancing targets have weight 0`,
2194+
Code: http.StatusBadRequest,
2195+
})
2196+
})
2197+
21302198
t.Run("should return success when no error occurs", func(t *testing.T) {
21312199
apiDef := apidef.DummyAPI()
21322200
apiDef.APIID = "123"

0 commit comments

Comments
 (0)