k8s.io/apiserver@v0.31.1/pkg/endpoints/request/requestinfo_test.go (about) 1 /* 2 Copyright 2016 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 request 18 19 import ( 20 "fmt" 21 "net/http" 22 "reflect" 23 "strings" 24 "testing" 25 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/sets" 28 genericfeatures "k8s.io/apiserver/pkg/features" 29 utilfeature "k8s.io/apiserver/pkg/util/feature" 30 featuregatetesting "k8s.io/component-base/featuregate/testing" 31 ) 32 33 func TestGetAPIRequestInfo(t *testing.T) { 34 namespaceAll := metav1.NamespaceAll 35 successCases := []struct { 36 method string 37 url string 38 expectedVerb string 39 expectedAPIPrefix string 40 expectedAPIGroup string 41 expectedAPIVersion string 42 expectedNamespace string 43 expectedResource string 44 expectedSubresource string 45 expectedName string 46 expectedParts []string 47 }{ 48 49 // resource paths 50 {"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}}, 51 {"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "other", []string{"namespaces", "other"}}, 52 53 {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 54 {"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, 55 {"HEAD", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, 56 {"GET", "/api/v1/pods", "list", "api", "", "v1", namespaceAll, "pods", "", "", []string{"pods"}}, 57 {"HEAD", "/api/v1/pods", "list", "api", "", "v1", namespaceAll, "pods", "", "", []string{"pods"}}, 58 {"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, 59 {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 60 61 // special verbs 62 {"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, 63 {"GET", "/api/v1/proxy/namespaces/other/pods/foo/subpath/not/a/subresource", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}}, 64 {"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", namespaceAll, "pods", "", "", []string{"pods"}}, 65 {"GET", "/api/v1/pods?watch=true", "watch", "api", "", "v1", namespaceAll, "pods", "", "", []string{"pods"}}, 66 {"GET", "/api/v1/pods?watch=false", "list", "api", "", "v1", namespaceAll, "pods", "", "", []string{"pods"}}, 67 {"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 68 {"GET", "/api/v1/namespaces/other/pods?watch=1", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 69 {"GET", "/api/v1/namespaces/other/pods?watch=0", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 70 71 // subresource identification 72 {"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "foo", []string{"pods", "foo", "status"}}, 73 {"GET", "/api/v1/namespaces/other/pods/foo/proxy/subpath", "get", "api", "", "v1", "other", "pods", "proxy", "foo", []string{"pods", "foo", "proxy", "subpath"}}, 74 {"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "namespaces", "finalize", "other", []string{"namespaces", "other", "finalize"}}, 75 {"PUT", "/api/v1/namespaces/other/status", "update", "api", "", "v1", "other", "namespaces", "status", "other", []string{"namespaces", "other", "status"}}, 76 77 // verb identification 78 {"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, 79 {"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, 80 {"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 81 82 // deletecollection verb identification 83 {"DELETE", "/api/v1/nodes", "deletecollection", "api", "", "v1", "", "nodes", "", "", []string{"nodes"}}, 84 {"DELETE", "/api/v1/namespaces", "deletecollection", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}}, 85 {"DELETE", "/api/v1/namespaces/other/pods", "deletecollection", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, 86 {"DELETE", "/apis/extensions/v1/namespaces/other/pods", "deletecollection", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}}, 87 88 // api group identification 89 {"POST", "/apis/extensions/v1/namespaces/other/pods", "create", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}}, 90 91 // api version identification 92 {"POST", "/apis/extensions/v1beta3/namespaces/other/pods", "create", "api", "extensions", "v1beta3", "other", "pods", "", "", []string{"pods"}}, 93 } 94 95 resolver := newTestRequestInfoResolver() 96 97 for _, successCase := range successCases { 98 req, _ := http.NewRequest(successCase.method, successCase.url, nil) 99 100 apiRequestInfo, err := resolver.NewRequestInfo(req) 101 if err != nil { 102 t.Errorf("Unexpected error for url: %s %v", successCase.url, err) 103 } 104 if !apiRequestInfo.IsResourceRequest { 105 t.Errorf("Expected resource request") 106 } 107 if successCase.expectedVerb != apiRequestInfo.Verb { 108 t.Errorf("Unexpected verb for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedVerb, apiRequestInfo.Verb) 109 } 110 if successCase.expectedAPIVersion != apiRequestInfo.APIVersion { 111 t.Errorf("Unexpected apiVersion for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedAPIVersion, apiRequestInfo.APIVersion) 112 } 113 if successCase.expectedNamespace != apiRequestInfo.Namespace { 114 t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace) 115 } 116 if successCase.expectedResource != apiRequestInfo.Resource { 117 t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource) 118 } 119 if successCase.expectedSubresource != apiRequestInfo.Subresource { 120 t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedSubresource, apiRequestInfo.Subresource) 121 } 122 if successCase.expectedName != apiRequestInfo.Name { 123 t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name) 124 } 125 if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) { 126 t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts) 127 } 128 } 129 130 errorCases := map[string]string{ 131 "no resource path": "/", 132 "just apiversion": "/api/version/", 133 "just prefix, group, version": "/apis/group/version/", 134 "apiversion with no resource": "/api/version/", 135 "bad prefix": "/badprefix/version/resource", 136 "missing api group": "/apis/version/resource", 137 } 138 for k, v := range errorCases { 139 req, err := http.NewRequest("GET", v, nil) 140 if err != nil { 141 t.Errorf("Unexpected error %v", err) 142 } 143 apiRequestInfo, err := resolver.NewRequestInfo(req) 144 if err != nil { 145 t.Errorf("%s: Unexpected error %v", k, err) 146 } 147 if apiRequestInfo.IsResourceRequest { 148 t.Errorf("%s: expected non-resource request", k) 149 } 150 } 151 } 152 153 func TestGetNonAPIRequestInfo(t *testing.T) { 154 tests := map[string]struct { 155 url string 156 expected bool 157 }{ 158 "simple groupless": {"/api/version/resource", true}, 159 "simple group": {"/apis/group/version/resource/name/subresource", true}, 160 "more steps": {"/api/version/resource/name/subresource", true}, 161 "group list": {"/apis/batch/v1/job", true}, 162 "group get": {"/apis/batch/v1/job/foo", true}, 163 "group subresource": {"/apis/batch/v1/job/foo/scale", true}, 164 165 "bad root": {"/not-api/version/resource", false}, 166 "group without enough steps": {"/apis/extensions/v1beta1", false}, 167 "group without enough steps 2": {"/apis/extensions/v1beta1/", false}, 168 "not enough steps": {"/api/version", false}, 169 "one step": {"/api", false}, 170 "zero step": {"/", false}, 171 "empty": {"", false}, 172 } 173 174 resolver := newTestRequestInfoResolver() 175 176 for testName, tc := range tests { 177 req, _ := http.NewRequest("GET", tc.url, nil) 178 179 apiRequestInfo, err := resolver.NewRequestInfo(req) 180 if err != nil { 181 t.Errorf("%s: Unexpected error %v", testName, err) 182 } 183 if e, a := tc.expected, apiRequestInfo.IsResourceRequest; e != a { 184 t.Errorf("%s: expected %v, actual %v", testName, e, a) 185 } 186 } 187 } 188 189 func newTestRequestInfoResolver() *RequestInfoFactory { 190 return &RequestInfoFactory{ 191 APIPrefixes: sets.NewString("api", "apis"), 192 GrouplessAPIPrefixes: sets.NewString("api"), 193 } 194 } 195 196 func TestSelectorParsing(t *testing.T) { 197 tests := []struct { 198 name string 199 method string 200 url string 201 expectedName string 202 expectedErr error 203 expectedVerb string 204 expectedFieldSelector string 205 expectedLabelSelector string 206 }{ 207 { 208 name: "no selector", 209 method: "GET", 210 url: "/apis/group/version/resource", 211 expectedVerb: "list", 212 expectedFieldSelector: "", 213 }, 214 { 215 name: "metadata.name selector", 216 method: "GET", 217 url: "/apis/group/version/resource?fieldSelector=metadata.name=name1", 218 expectedName: "name1", 219 expectedVerb: "list", 220 expectedFieldSelector: "metadata.name=name1", 221 }, 222 { 223 name: "metadata.name selector with watch", 224 method: "GET", 225 url: "/apis/group/version/resource?watch=true&fieldSelector=metadata.name=name1", 226 expectedName: "name1", 227 expectedVerb: "watch", 228 expectedFieldSelector: "metadata.name=name1", 229 }, 230 { 231 name: "random selector", 232 method: "GET", 233 url: "/apis/group/version/resource?fieldSelector=foo=bar&labelSelector=baz=qux", 234 expectedName: "", 235 expectedVerb: "list", 236 expectedFieldSelector: "foo=bar", 237 expectedLabelSelector: "baz=qux", 238 }, 239 { 240 name: "invalid selector with metadata.name", 241 method: "GET", 242 url: "/apis/group/version/resource?fieldSelector=metadata.name=name1,foo", 243 expectedName: "", 244 expectedErr: fmt.Errorf("invalid selector"), 245 expectedVerb: "list", 246 expectedFieldSelector: "metadata.name=name1,foo", 247 }, 248 { 249 name: "invalid selector with metadata.name with watch", 250 method: "GET", 251 url: "/apis/group/version/resource?fieldSelector=metadata.name=name1,foo&watch=true", 252 expectedName: "", 253 expectedErr: fmt.Errorf("invalid selector"), 254 expectedVerb: "watch", 255 expectedFieldSelector: "metadata.name=name1,foo", 256 }, 257 { 258 name: "invalid selector with metadata.name with watch false", 259 method: "GET", 260 url: "/apis/group/version/resource?fieldSelector=metadata.name=name1,foo&watch=false", 261 expectedName: "", 262 expectedErr: fmt.Errorf("invalid selector"), 263 expectedVerb: "list", 264 expectedFieldSelector: "metadata.name=name1,foo", 265 }, 266 { 267 name: "selector on deletecollection is honored", 268 method: "DELETE", 269 url: "/apis/group/version/resource?fieldSelector=foo=bar&labelSelector=baz=qux", 270 expectedName: "", 271 expectedVerb: "deletecollection", 272 expectedFieldSelector: "foo=bar", 273 expectedLabelSelector: "baz=qux", 274 }, 275 { 276 name: "selector on repeated param matches parsed param", 277 method: "GET", 278 url: "/apis/group/version/resource?fieldSelector=metadata.name=foo&fieldSelector=metadata.name=bar&labelSelector=foo=bar&labelSelector=foo=baz", 279 expectedName: "foo", 280 expectedVerb: "list", 281 expectedFieldSelector: "metadata.name=foo", 282 expectedLabelSelector: "foo=bar", 283 }, 284 { 285 name: "selector on other verb is ignored", 286 method: "GET", 287 url: "/apis/group/version/resource/name?fieldSelector=foo=bar&labelSelector=foo=bar", 288 expectedName: "name", 289 expectedVerb: "get", 290 expectedFieldSelector: "", 291 expectedLabelSelector: "", 292 }, 293 { 294 name: "selector on deprecated root type watch is not parsed", 295 method: "GET", 296 url: "/apis/group/version/watch/resource?fieldSelector=metadata.name=foo&labelSelector=foo=bar", 297 expectedName: "", 298 expectedVerb: "watch", 299 expectedFieldSelector: "", 300 expectedLabelSelector: "", 301 }, 302 { 303 name: "selector on deprecated root item watch is not parsed", 304 method: "GET", 305 url: "/apis/group/version/watch/resource/name?fieldSelector=metadata.name=foo&labelSelector=foo=bar", 306 expectedName: "name", 307 expectedVerb: "watch", 308 expectedFieldSelector: "", 309 expectedLabelSelector: "", 310 }, 311 } 312 313 resolver := newTestRequestInfoResolver() 314 315 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true) 316 317 for _, tc := range tests { 318 req, _ := http.NewRequest(tc.method, tc.url, nil) 319 320 apiRequestInfo, err := resolver.NewRequestInfo(req) 321 if err != nil { 322 if tc.expectedErr == nil || !strings.Contains(err.Error(), tc.expectedErr.Error()) { 323 t.Errorf("%s: Unexpected error %v", tc.name, err) 324 } 325 } 326 if e, a := tc.expectedName, apiRequestInfo.Name; e != a { 327 t.Errorf("%s: expected %v, actual %v", tc.name, e, a) 328 } 329 if e, a := tc.expectedVerb, apiRequestInfo.Verb; e != a { 330 t.Errorf("%s: expected verb %v, actual %v", tc.name, e, a) 331 } 332 if e, a := tc.expectedFieldSelector, apiRequestInfo.FieldSelector; e != a { 333 t.Errorf("%s: expected fieldSelector %v, actual %v", tc.name, e, a) 334 } 335 if e, a := tc.expectedLabelSelector, apiRequestInfo.LabelSelector; e != a { 336 t.Errorf("%s: expected labelSelector %v, actual %v", tc.name, e, a) 337 } 338 } 339 }