k8s.io/apiserver@v0.31.1/pkg/server/options/server_run_options_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package options 18 19 import ( 20 "fmt" 21 "strings" 22 "testing" 23 "time" 24 25 utilerrors "k8s.io/apimachinery/pkg/util/errors" 26 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 "k8s.io/apimachinery/pkg/util/version" 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 utilversion "k8s.io/apiserver/pkg/util/version" 30 netutils "k8s.io/utils/net" 31 ) 32 33 func TestServerRunOptionsValidate(t *testing.T) { 34 testRegistry := utilversion.NewComponentGlobalsRegistry() 35 featureGate := utilfeature.DefaultFeatureGate.DeepCopy() 36 effectiveVersion := utilversion.NewEffectiveVersion("1.30") 37 effectiveVersion.SetEmulationVersion(version.MajorMinor(1, 32)) 38 testComponent := "test" 39 utilruntime.Must(testRegistry.Register(testComponent, effectiveVersion, featureGate)) 40 41 testCases := []struct { 42 name string 43 testOptions *ServerRunOptions 44 expectErr string 45 }{ 46 { 47 name: "Test when MaxRequestsInFlight is negative value", 48 testOptions: &ServerRunOptions{ 49 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 50 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 51 MaxRequestsInFlight: -400, 52 MaxMutatingRequestsInFlight: 200, 53 RequestTimeout: time.Duration(2) * time.Minute, 54 MinRequestTimeout: 1800, 55 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 56 MaxRequestBodyBytes: 10 * 1024 * 1024, 57 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 58 }, 59 expectErr: "--max-requests-inflight can not be negative value", 60 }, 61 { 62 name: "Test when MaxMutatingRequestsInFlight is negative value", 63 testOptions: &ServerRunOptions{ 64 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 65 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 66 MaxRequestsInFlight: 400, 67 MaxMutatingRequestsInFlight: -200, 68 RequestTimeout: time.Duration(2) * time.Minute, 69 MinRequestTimeout: 1800, 70 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 71 MaxRequestBodyBytes: 10 * 1024 * 1024, 72 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 73 }, 74 expectErr: "--max-mutating-requests-inflight can not be negative value", 75 }, 76 { 77 name: "Test when RequestTimeout is negative value", 78 testOptions: &ServerRunOptions{ 79 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 80 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 81 MaxRequestsInFlight: 400, 82 MaxMutatingRequestsInFlight: 200, 83 RequestTimeout: -time.Duration(2) * time.Minute, 84 MinRequestTimeout: 1800, 85 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 86 MaxRequestBodyBytes: 10 * 1024 * 1024, 87 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 88 }, 89 expectErr: "--request-timeout can not be negative value", 90 }, 91 { 92 name: "Test when MinRequestTimeout is negative value", 93 testOptions: &ServerRunOptions{ 94 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 95 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 96 MaxRequestsInFlight: 400, 97 MaxMutatingRequestsInFlight: 200, 98 RequestTimeout: time.Duration(2) * time.Minute, 99 MinRequestTimeout: -1800, 100 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 101 MaxRequestBodyBytes: 10 * 1024 * 1024, 102 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 103 }, 104 expectErr: "--min-request-timeout can not be negative value", 105 }, 106 { 107 name: "Test when JSONPatchMaxCopyBytes is negative value", 108 testOptions: &ServerRunOptions{ 109 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 110 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 111 MaxRequestsInFlight: 400, 112 MaxMutatingRequestsInFlight: 200, 113 RequestTimeout: time.Duration(2) * time.Minute, 114 MinRequestTimeout: 1800, 115 JSONPatchMaxCopyBytes: -10 * 1024 * 1024, 116 MaxRequestBodyBytes: 10 * 1024 * 1024, 117 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 118 }, 119 expectErr: "ServerRunOptions.JSONPatchMaxCopyBytes can not be negative value", 120 }, 121 { 122 name: "Test when MaxRequestBodyBytes is negative value", 123 testOptions: &ServerRunOptions{ 124 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 125 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 126 MaxRequestsInFlight: 400, 127 MaxMutatingRequestsInFlight: 200, 128 RequestTimeout: time.Duration(2) * time.Minute, 129 MinRequestTimeout: 1800, 130 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 131 MaxRequestBodyBytes: -10 * 1024 * 1024, 132 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 133 }, 134 expectErr: "ServerRunOptions.MaxRequestBodyBytes can not be negative value", 135 }, 136 { 137 name: "Test when LivezGracePeriod is negative value", 138 testOptions: &ServerRunOptions{ 139 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 140 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 141 MaxRequestsInFlight: 400, 142 MaxMutatingRequestsInFlight: 200, 143 RequestTimeout: time.Duration(2) * time.Minute, 144 MinRequestTimeout: 1800, 145 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 146 MaxRequestBodyBytes: 10 * 1024 * 1024, 147 LivezGracePeriod: -time.Second, 148 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 149 }, 150 expectErr: "--livez-grace-period can not be a negative value", 151 }, 152 { 153 name: "Test when MinimalShutdownDuration is negative value", 154 testOptions: &ServerRunOptions{ 155 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 156 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 157 MaxRequestsInFlight: 400, 158 MaxMutatingRequestsInFlight: 200, 159 RequestTimeout: time.Duration(2) * time.Minute, 160 MinRequestTimeout: 1800, 161 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 162 MaxRequestBodyBytes: 10 * 1024 * 1024, 163 ShutdownDelayDuration: -time.Second, 164 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 165 }, 166 expectErr: "--shutdown-delay-duration can not be negative value", 167 }, 168 { 169 name: "Test when HSTSHeaders is valid", 170 testOptions: &ServerRunOptions{ 171 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 172 CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, 173 HSTSDirectives: []string{"fakevalue", "includeSubDomains", "preload"}, 174 MaxRequestsInFlight: 400, 175 MaxMutatingRequestsInFlight: 200, 176 RequestTimeout: time.Duration(2) * time.Minute, 177 MinRequestTimeout: 1800, 178 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 179 MaxRequestBodyBytes: 10 * 1024 * 1024, 180 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 181 }, 182 expectErr: "--strict-transport-security-directives invalid, allowed values: max-age=expireTime, includeSubDomains, preload. see https://tools.ietf.org/html/rfc6797#section-6.1 for more information", 183 }, 184 { 185 name: "Test when emulation version is invalid", 186 testOptions: &ServerRunOptions{ 187 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 188 CorsAllowedOriginList: []string{"^10.10.10.100$", "^10.10.10.200$"}, 189 HSTSDirectives: []string{"max-age=31536000", "includeSubDomains", "preload"}, 190 MaxRequestsInFlight: 400, 191 MaxMutatingRequestsInFlight: 200, 192 RequestTimeout: time.Duration(2) * time.Minute, 193 MinRequestTimeout: 1800, 194 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 195 MaxRequestBodyBytes: 10 * 1024 * 1024, 196 ComponentName: testComponent, 197 ComponentGlobalsRegistry: testRegistry, 198 }, 199 expectErr: "emulation version 1.32 is not between [1.29, 1.30.0]", 200 }, 201 { 202 name: "Test when ServerRunOptions is valid", 203 testOptions: &ServerRunOptions{ 204 AdvertiseAddress: netutils.ParseIPSloppy("192.168.10.10"), 205 CorsAllowedOriginList: []string{"^10.10.10.100$", "^10.10.10.200$"}, 206 HSTSDirectives: []string{"max-age=31536000", "includeSubDomains", "preload"}, 207 MaxRequestsInFlight: 400, 208 MaxMutatingRequestsInFlight: 200, 209 RequestTimeout: time.Duration(2) * time.Minute, 210 MinRequestTimeout: 1800, 211 JSONPatchMaxCopyBytes: 10 * 1024 * 1024, 212 MaxRequestBodyBytes: 10 * 1024 * 1024, 213 ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry, 214 }, 215 }, 216 } 217 218 for _, testcase := range testCases { 219 t.Run(testcase.name, func(t *testing.T) { 220 errs := testcase.testOptions.Validate() 221 if len(testcase.expectErr) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) { 222 t.Errorf("got err: %v, expected err: %s", errs, testcase.expectErr) 223 } 224 225 if len(testcase.expectErr) == 0 && len(errs) != 0 { 226 t.Errorf("got err: %s, expected err nil", errs) 227 } 228 }) 229 } 230 } 231 232 func TestValidateCorsAllowedOriginList(t *testing.T) { 233 tests := []struct { 234 regexp [][]string 235 errShouldContain string 236 }{ 237 { 238 regexp: [][]string{ 239 {}, // empty list, the cluster operator wants to disable CORS 240 {`^http://foo.com$`}, 241 {`^http://foo.com`}, // valid, because we relaxed the validation 242 {`://foo.com$`}, 243 {`//foo.com$`}, 244 {`^http://foo.com(:|$)`}, 245 {`://foo.com(:|$)`}, 246 {`//foo.com(:|$)`}, 247 {`(^foo.com$)`}, 248 {`^http://foo.com$`, `//bar.com(:|$)`}, 249 }, 250 errShouldContain: "", 251 }, 252 { 253 // empty string, indicates that the cluster operator 254 // specified --cors-allowed-origins="" 255 regexp: [][]string{ 256 {`^http://foo.com$`, ``}, 257 }, 258 errShouldContain: "empty value in --cors-allowed-origins", 259 }, 260 { 261 regexp: [][]string{ 262 {`^foo.com`}, 263 {`//foo.com`}, 264 {`foo.com$`}, 265 {`foo.com(:|$)`}, 266 }, 267 errShouldContain: "regular expression does not pin to start/end of host in the origin header", 268 }, 269 { 270 regexp: [][]string{ 271 {`^http://foo.com$`, `^foo.com`}, // one good followed by a bad one 272 }, 273 errShouldContain: "regular expression does not pin to start/end of host in the origin header", 274 }, 275 } 276 277 for _, test := range tests { 278 for _, regexp := range test.regexp { 279 t.Run(fmt.Sprintf("regexp/%s", regexp), func(t *testing.T) { 280 options := NewServerRunOptions() 281 if errs := options.Validate(); len(errs) != 0 { 282 t.Fatalf("wrong test setup: %#v", errs) 283 } 284 285 options.CorsAllowedOriginList = regexp 286 errsGot := options.Validate() 287 switch { 288 case len(test.errShouldContain) == 0: 289 if len(errsGot) != 0 { 290 t.Errorf("expected no error, but got: %v", errsGot) 291 } 292 default: 293 if len(errsGot) == 0 || 294 !strings.Contains(utilerrors.NewAggregate(errsGot).Error(), test.errShouldContain) { 295 t.Errorf("expected error to contain: %s, but got: %v", test.errShouldContain, errsGot) 296 } 297 } 298 }) 299 } 300 } 301 } 302 303 func TestServerRunOptionsWithShutdownWatchTerminationGracePeriod(t *testing.T) { 304 tests := []struct { 305 name string 306 optionsFn func() *ServerRunOptions 307 errShouldContain string 308 }{ 309 { 310 name: "default should be valid", 311 optionsFn: func() *ServerRunOptions { 312 return NewServerRunOptions() 313 }, 314 }, 315 { 316 name: "negative not allowed", 317 optionsFn: func() *ServerRunOptions { 318 o := NewServerRunOptions() 319 o.ShutdownWatchTerminationGracePeriod = -time.Second 320 return o 321 }, 322 errShouldContain: "shutdown-watch-termination-grace-period, if provided, can not be a negative value", 323 }, 324 } 325 326 for _, test := range tests { 327 t.Run(test.name, func(t *testing.T) { 328 options := test.optionsFn() 329 errsGot := options.Validate() 330 switch { 331 case len(test.errShouldContain) == 0: 332 if len(errsGot) != 0 { 333 t.Errorf("expected no error, but got: %v", errsGot) 334 } 335 default: 336 if len(errsGot) == 0 || 337 !strings.Contains(utilerrors.NewAggregate(errsGot).Error(), test.errShouldContain) { 338 t.Errorf("expected error to contain: %s, but got: %v", test.errShouldContain, errsGot) 339 } 340 } 341 }) 342 } 343 344 t.Run("default should be zero", func(t *testing.T) { 345 options := NewServerRunOptions() 346 if options.ShutdownWatchTerminationGracePeriod != time.Duration(0) { 347 t.Errorf("expected default of ShutdownWatchTerminationGracePeriod to be zero, but got: %s", options.ShutdownWatchTerminationGracePeriod) 348 } 349 }) 350 }