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  }