...

Source file src/sigs.k8s.io/gateway-api/pkg/admission/server_test.go

Documentation: sigs.k8s.io/gateway-api/pkg/admission

     1  /*
     2  Copyright 2021 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 admission
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"testing"
    26  
    27  	"github.com/lithammer/dedent"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	admission "k8s.io/api/admission/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  )
    33  
    34  var decoder = codecs.UniversalDeserializer()
    35  
    36  func TestServeHTTPInvalidBody(t *testing.T) {
    37  	assert := assert.New(t)
    38  	res := httptest.NewRecorder()
    39  	handler := http.HandlerFunc(ServeHTTP)
    40  	req, err := http.NewRequest("POST", "", nil)
    41  	req = req.WithContext(context.Background())
    42  	assert.Nil(err)
    43  	handler.ServeHTTP(res, req)
    44  	assert.Equal(400, res.Code)
    45  	assert.Equal("admission review object is missing\n",
    46  		res.Body.String())
    47  }
    48  
    49  func TestServeHTTPInvalidMethod(t *testing.T) {
    50  	assert := assert.New(t)
    51  	res := httptest.NewRecorder()
    52  	handler := http.HandlerFunc(ServeHTTP)
    53  	req, err := http.NewRequest("GET", "", nil)
    54  	req = req.WithContext(context.Background())
    55  	assert.Nil(err)
    56  	handler.ServeHTTP(res, req)
    57  	assert.Equal(http.StatusMethodNotAllowed, res.Code)
    58  	assert.Equal("invalid method GET, only POST requests are allowed\n",
    59  		res.Body.String())
    60  }
    61  
    62  func TestServeHTTPSubmissions(t *testing.T) {
    63  	for _, apiVersion := range []string{
    64  		"admission.k8s.io/v1",
    65  		"admission.k8s.io/v1",
    66  	} {
    67  		for _, tt := range []struct {
    68  			name    string
    69  			reqBody string
    70  
    71  			wantRespCode        int
    72  			wantSuccessResponse admission.AdmissionResponse
    73  			wantFailureMessage  string
    74  		}{
    75  			{
    76  				name: "malformed json missing colon at resource",
    77  				reqBody: dedent.Dedent(`{
    78  						"kind": "AdmissionReview",
    79  						"apiVersion": "` + apiVersion + `",
    80  						"request": {
    81  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
    82  							"resource": {
    83  								"group": "networking.x-k8s.io",
    84  								"version": "v1alpha1",
    85  								"resource" "httproutes"
    86  							},
    87  							"object": {
    88  								"apiVersion": "networking.x-k8s.io/v1alpha1",
    89  								"kind": "HTTPRoute"
    90  							},
    91  						"operation": "CREATE"
    92  						}
    93  					}`),
    94  				wantRespCode:       http.StatusBadRequest,
    95  				wantFailureMessage: "invalid character '\"' after object key\n",
    96  			},
    97  			{
    98  				name:               "request with empty body",
    99  				wantRespCode:       http.StatusBadRequest,
   100  				wantFailureMessage: "unexpected end of JSON input\n",
   101  			},
   102  			{
   103  				name: "valid json but not of kind AdmissionReview",
   104  				reqBody: dedent.Dedent(`{
   105  						"kind": "NotReviewYouAreLookingFor",
   106  						"apiVersion": "` + apiVersion + `",
   107  						"request": {
   108  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   109  							"resource": {
   110  								"group": "gateway.networking.k8s.io",
   111  								"version": "v1",
   112  								"resource": "httproutes"
   113  							},
   114  							"object": {
   115  								"apiVersion": "gateway.networking.k8s.io/v1",
   116  								"kind": "HTTPRoute"
   117  							},
   118  						"operation": "CREATE"
   119  						}
   120  					}`),
   121  				wantRespCode:       http.StatusBadRequest,
   122  				wantFailureMessage: "submitted object is not of kind AdmissionReview\n",
   123  			},
   124  			{
   125  				name: "valid v1 Gateway resource",
   126  				reqBody: dedent.Dedent(`{
   127  						"kind": "AdmissionReview",
   128  						"apiVersion": "` + apiVersion + `",
   129  						"request": {
   130  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   131  							"resource": {
   132  								"group": "gateway.networking.k8s.io",
   133  								"version": "v1",
   134  								"resource": "gateways"
   135  							},
   136  							"object": {
   137     								"kind": "Gateway",
   138     								"apiVersion": "gateway.networking.k8s.io/v1",
   139     								"metadata": {
   140     								   "name": "gateway-1",
   141     								   "labels": {
   142     								      "app": "foo"
   143     								   }
   144     								},
   145     								"spec": {
   146  									"gatewayClassName": "contour-class",
   147  									"listeners": [
   148  										{
   149  											"port": 80,
   150  											"protocol": "HTTP",
   151  											"hostname": "foo.com",
   152  											"routes": {
   153  												"group": "gateway.networking.k8s.io",
   154  												"kind": "HTTPRoute",
   155  												"namespaces": {
   156  													"from": "All"
   157  												}
   158  											}
   159  										}
   160  									]
   161     								}
   162  							},
   163  						"operation": "CREATE"
   164  						}
   165  					}`),
   166  				wantRespCode: http.StatusOK,
   167  				wantSuccessResponse: admission.AdmissionResponse{
   168  					UID:     "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   169  					Allowed: true,
   170  					Result:  &metav1.Status{},
   171  				},
   172  			},
   173  			{
   174  				name: "valid v1 HTTPRoute resource",
   175  				reqBody: dedent.Dedent(`{
   176  						"kind": "AdmissionReview",
   177  						"apiVersion": "` + apiVersion + `",
   178  						"request": {
   179  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   180  							"resource": {
   181  								"group": "gateway.networking.k8s.io",
   182  								"version": "v1",
   183  								"resource": "httproutes"
   184  							},
   185  							"object": {
   186     								"kind": "HTTPRoute",
   187     								"apiVersion": "gateway.networking.k8s.io/v1",
   188     								"metadata": {
   189     								   "name": "http-app-1",
   190     								   "labels": {
   191     								      "app": "foo"
   192     								   }
   193     								},
   194     								"spec": {
   195     								   "hostnames": [
   196     								      "foo.com"
   197     								   ],
   198     								   "rules": [
   199     								      {
   200     								         "matches": [
   201     								            {
   202     								               "path": {
   203     								                  "type": "PathPrefix",
   204     								                  "value": "/bar"
   205     								               }
   206     								            }
   207     								         ],
   208     								         "filters": [
   209     								            {
   210     								               "type": "RequestMirror",
   211     								               "requestMirror": {
   212     								                  "serviceName": "my-service1-staging",
   213     								                  "port": 8080
   214     								               }
   215     								            }
   216     								         ],
   217     								         "forwardTo": [
   218     								            {
   219     								               "serviceName": "my-service1",
   220     								               "port": 8080
   221     								            }
   222     								         ]
   223     								      }
   224     								   ]
   225     								}
   226  							},
   227  						"operation": "CREATE"
   228  						}
   229  					}`),
   230  				wantRespCode: http.StatusOK,
   231  				wantSuccessResponse: admission.AdmissionResponse{
   232  					UID:     "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   233  					Allowed: true,
   234  					Result:  &metav1.Status{},
   235  				},
   236  			},
   237  			{
   238  				name: "valid v1 HTTPRoute resource with two request mirror filters",
   239  				reqBody: dedent.Dedent(`{
   240  						"kind": "AdmissionReview",
   241  						"apiVersion": "` + apiVersion + `",
   242  						"request": {
   243  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   244  							"resource": {
   245  								"group": "gateway.networking.k8s.io",
   246  								"version": "v1",
   247  								"resource": "httproutes"
   248  							},
   249  							"object": {
   250     								"kind": "HTTPRoute",
   251     								"apiVersion": "gateway.networking.k8s.io/v1",
   252     								"metadata": {
   253     								   "name": "http-app-1",
   254     								   "labels": {
   255     								      "app": "foo"
   256     								   }
   257     								},
   258     								"spec": {
   259     								   "hostnames": [
   260     								      "foo.com"
   261     								   ],
   262     								   "rules": [
   263     								      {
   264     								         "matches": [
   265     								            {
   266     								               "path": {
   267     								                  "type": "PathPrefix",
   268     								                  "value": "/bar"
   269     								               }
   270     								            }
   271     								         ],
   272     								         "filters": [
   273     								            {
   274     								               "type": "RequestMirror",
   275     								               "requestMirror": {
   276     								                  "serviceName": "my-service1-staging",
   277     								                  "port": 8080
   278     								               }
   279     								            },
   280     								            {
   281     								               "type": "RequestMirror",
   282     								               "requestMirror": {
   283     								                  "serviceName": "my-service2-staging",
   284     								                  "port": 8080
   285     								               }
   286     								            }
   287     								         ],
   288  								         "backendRefs": [
   289     								            {
   290  								               "name": "RequestMirror",
   291     								               "port": 8080
   292     								            }
   293     								         ]
   294     								      }
   295     								   ]
   296     								}
   297  							},
   298  						"operation": "CREATE"
   299  						}
   300  					}`),
   301  				wantRespCode: http.StatusOK,
   302  				wantSuccessResponse: admission.AdmissionResponse{
   303  					UID:     "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   304  					Allowed: true,
   305  					Result:  &metav1.Status{},
   306  				},
   307  			},
   308  			{
   309  				name: "v1a2 GatewayClass create events do not result in an error",
   310  				reqBody: dedent.Dedent(`{
   311  						"kind": "AdmissionReview",
   312  						"apiVersion": "` + apiVersion + `",
   313  						"request": {
   314  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   315  							"resource": {
   316  								"group": "gateway.networking.k8s.io",
   317  								"version": "v1",
   318  								"resource": "gatewayclasses"
   319  							},
   320  							"object": {
   321     								"kind": "GatewayClass",
   322     								"apiVersion": "gateway.networking.k8s.io/v1",
   323     								"metadata": {
   324     								   "name": "gateway-class-1"
   325     								},
   326     								"spec": {
   327     								   "controller": "example.com/foo"
   328     								}
   329  							},
   330  						"operation": "CREATE"
   331  						}
   332  					}`),
   333  				wantRespCode: http.StatusOK,
   334  				wantSuccessResponse: admission.AdmissionResponse{
   335  					UID:     "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   336  					Allowed: true,
   337  					Result:  &metav1.Status{},
   338  				},
   339  			},
   340  			{
   341  				name: "update to v1 GatewayClass parameters field does" +
   342  					" not result in an error",
   343  				reqBody: dedent.Dedent(`{
   344  						"kind": "AdmissionReview",
   345  						"apiVersion": "` + apiVersion + `",
   346  						"request": {
   347  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   348  							"resource": {
   349  								"group": "gateway.networking.k8s.io",
   350  								"version": "v1",
   351  								"resource": "gatewayclasses"
   352  							},
   353  							"object": {
   354     								"kind": "GatewayClass",
   355     								"apiVersion": "gateway.networking.k8s.io/v1",
   356     								"metadata": {
   357     								   "name": "gateway-class-1"
   358     								},
   359     								"spec": {
   360     								   "controllerName": "example.com/foo"
   361     								}
   362  							},
   363  							"oldObject": {
   364     								"kind": "GatewayClass",
   365     								"apiVersion": "gateway.networking.k8s.io/v1",
   366     								"metadata": {
   367     								   "name": "gateway-class-1"
   368     								},
   369     								"spec": {
   370  									"controllerName": "example.com/foo",
   371  									"parametersRef": {
   372  										"name": "foo",
   373  										"namespace": "bar",
   374  										"scope": "Namespace",
   375  										"group": "example.com",
   376  										"kind": "ExampleConfig"
   377  									}
   378     								}
   379  							},
   380  						"operation": "UPDATE"
   381  						}
   382  					}`),
   383  				wantRespCode: http.StatusOK,
   384  				wantSuccessResponse: admission.AdmissionResponse{
   385  					UID:     "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   386  					Allowed: true,
   387  					Result:  &metav1.Status{},
   388  				},
   389  			},
   390  			{
   391  				name: "update to v1 GatewayClass controllerName field" +
   392  					" results in an error ",
   393  				reqBody: dedent.Dedent(`{
   394  						"kind": "AdmissionReview",
   395  						"apiVersion": "` + apiVersion + `",
   396  						"request": {
   397  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   398  							"resource": {
   399  								"group": "gateway.networking.k8s.io",
   400  								"version": "v1",
   401  								"resource": "gatewayclasses"
   402  							},
   403  							"object": {
   404     								"kind": "GatewayClass",
   405     								"apiVersion": "gateway.networking.k8s.io/v1",
   406     								"metadata": {
   407     								   "name": "gateway-class-1"
   408     								},
   409     								"spec": {
   410     								   "controllerName": "example.com/foo"
   411     								}
   412  							},
   413  							"oldObject": {
   414     								"kind": "GatewayClass",
   415     								"apiVersion": "gateway.networking.k8s.io/v1",
   416     								"metadata": {
   417     								   "name": "gateway-class-1"
   418     								},
   419     								"spec": {
   420     								   "controllerName": "example.com/bar"
   421     								}
   422  							},
   423  						"operation": "UPDATE"
   424  						}
   425  					}`),
   426  				wantRespCode: http.StatusOK,
   427  				wantSuccessResponse: admission.AdmissionResponse{
   428  					UID:     "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   429  					Allowed: false,
   430  					Result: &metav1.Status{
   431  						Code:    400,
   432  						Message: `spec.controllerName: Invalid value: "example.com/foo": cannot update an immutable field`,
   433  					},
   434  				},
   435  			},
   436  			{
   437  				name: "unknown resource under networking.x-k8s.io",
   438  				reqBody: dedent.Dedent(`{
   439  						"kind": "AdmissionReview",
   440  						"apiVersion": "` + apiVersion + `",
   441  						"request": {
   442  							"uid": "7313cd05-eddc-4150-b88c-971a0d53b2ab",
   443  							"resource": {
   444  								"group": "gateway.networking.k8s.io",
   445  								"version": "v1",
   446  								"resource": "brokenroutes"
   447  							},
   448  							"object": {
   449  								"apiVersion": "gateway.networking.k8s.io/v1",
   450  								"kind": "HTTPRoute"
   451  							},
   452  						"operation": "CREATE"
   453  						}
   454  					}`),
   455  				wantRespCode:       http.StatusInternalServerError,
   456  				wantFailureMessage: "unknown resource 'brokenroutes'\n",
   457  			},
   458  		} {
   459  			tt := tt
   460  			t.Run(fmt.Sprintf("%s/%s", apiVersion, tt.name), func(t *testing.T) {
   461  				assert := assert.New(t)
   462  				res := httptest.NewRecorder()
   463  				handler := http.HandlerFunc(ServeHTTP)
   464  
   465  				// send request
   466  				req, err := http.NewRequest("POST", "", bytes.NewBuffer([]byte(tt.reqBody)))
   467  				req = req.WithContext(context.Background())
   468  				require.NoError(t, err)
   469  				handler.ServeHTTP(res, req)
   470  
   471  				// check response assertions
   472  				assert.Equal(tt.wantRespCode, res.Code)
   473  				if tt.wantRespCode == http.StatusOK {
   474  					var review admission.AdmissionReview
   475  					_, _, err = decoder.Decode(res.Body.Bytes(), nil, &review)
   476  					require.NoError(t, err)
   477  					assert.EqualValues(&tt.wantSuccessResponse, review.Response)
   478  				} else {
   479  					assert.Equal(res.Body.String(), tt.wantFailureMessage)
   480  				}
   481  			})
   482  		}
   483  	}
   484  }
   485  

View as plain text