@@ -33,8 +33,10 @@ import (
33
33
"k8s.io/api/admission/v1beta1"
34
34
admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
35
35
appsv1beta1 "k8s.io/api/apps/v1beta1"
36
+ corev1 "k8s.io/api/core/v1"
36
37
v1 "k8s.io/api/core/v1"
37
38
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
39
+ policyv1beta1 "k8s.io/api/policy/v1beta1"
38
40
"k8s.io/apimachinery/pkg/api/errors"
39
41
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40
42
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -44,6 +46,7 @@ import (
44
46
"k8s.io/apimachinery/pkg/util/sets"
45
47
"k8s.io/apimachinery/pkg/util/wait"
46
48
dynamic "k8s.io/client-go/dynamic"
49
+ "k8s.io/client-go/kubernetes"
47
50
clientset "k8s.io/client-go/kubernetes"
48
51
"k8s.io/client-go/util/retry"
49
52
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
@@ -63,6 +66,8 @@ type testContext struct {
63
66
admissionHolder * holder
64
67
65
68
client dynamic.Interface
69
+ clientset kubernetes.Interface
70
+ verb string
66
71
gvr schema.GroupVersionResource
67
72
resource metav1.APIResource
68
73
resources map [schema.GroupVersionResource ]metav1.APIResource
90
95
91
96
// customTestFuncs holds custom test functions by resource and verb.
92
97
customTestFuncs = map [schema.GroupVersionResource ]map [string ]testFunc {
93
- gvr ("" , "v1" , "namespaces" ): {"delete" : testNamespaceDelete },
98
+ gvr ("" , "v1" , "namespaces" ): {"delete" : testNamespaceDelete },
99
+
94
100
gvr ("apps" , "v1beta1" , "deployments/rollback" ): {"create" : testDeploymentRollback },
95
101
gvr ("extensions" , "v1beta1" , "deployments/rollback" ): {"create" : testDeploymentRollback },
102
+
103
+ gvr ("" , "v1" , "pods/attach" ): {"create" : testPodConnectSubresource },
104
+ gvr ("" , "v1" , "pods/exec" ): {"create" : testPodConnectSubresource },
105
+ gvr ("" , "v1" , "pods/portforward" ): {"create" : testPodConnectSubresource },
106
+
107
+ gvr ("" , "v1" , "bindings" ): {"create" : testPodBindingEviction },
108
+ gvr ("" , "v1" , "pods/binding" ): {"create" : testPodBindingEviction },
109
+ gvr ("" , "v1" , "pods/eviction" ): {"create" : testPodBindingEviction },
110
+
111
+ gvr ("" , "v1" , "nodes/proxy" ): {"*" : testSubresourceProxy },
112
+ gvr ("" , "v1" , "pods/proxy" ): {"*" : testSubresourceProxy },
113
+ gvr ("" , "v1" , "services/proxy" ): {"*" : testSubresourceProxy },
96
114
}
97
115
98
116
// excludedResources lists resources / verb combinations that are not yet tested. this set should trend to zero.
@@ -112,17 +130,6 @@ var (
112
130
// TODO: webhook config objects are not subject to admission, verify CRUD works and webhooks do not observe them
113
131
gvr ("admissionregistration.k8s.io" , "v1beta1" , "mutatingwebhookconfigurations" ): sets .NewString ("*" ),
114
132
gvr ("admissionregistration.k8s.io" , "v1beta1" , "validatingwebhookconfigurations" ): sets .NewString ("*" ),
115
-
116
- // TODO: implement custom subresource tests (requires special states or requests)
117
- gvr ("" , "v1" , "bindings" ): sets .NewString ("create" ),
118
- gvr ("" , "v1" , "nodes/proxy" ): sets .NewString ("*" ),
119
- gvr ("" , "v1" , "pods/attach" ): sets .NewString ("create" ),
120
- gvr ("" , "v1" , "pods/binding" ): sets .NewString ("create" ),
121
- gvr ("" , "v1" , "pods/eviction" ): sets .NewString ("create" ),
122
- gvr ("" , "v1" , "pods/exec" ): sets .NewString ("create" ),
123
- gvr ("" , "v1" , "pods/portforward" ): sets .NewString ("create" ),
124
- gvr ("" , "v1" , "pods/proxy" ): sets .NewString ("*" ),
125
- gvr ("" , "v1" , "services/proxy" ): sets .NewString ("*" ),
126
133
}
127
134
128
135
parentResources = map [schema.GroupVersionResource ]schema.GroupVersionResource {
@@ -393,6 +400,8 @@ func TestWebhookV1beta1(t *testing.T) {
393
400
t : t ,
394
401
admissionHolder : holder ,
395
402
client : dynamicClient ,
403
+ clientset : master .Client ,
404
+ verb : verb ,
396
405
gvr : gvr ,
397
406
resource : resource ,
398
407
resources : resourcesByGVR ,
@@ -735,6 +744,141 @@ func testDeploymentRollback(c *testContext) {
735
744
}
736
745
}
737
746
747
+ // testPodConnectSubresource verifies connect subresources
748
+ func testPodConnectSubresource (c * testContext ) {
749
+ podGVR := gvr ("" , "v1" , "pods" )
750
+ pod , err := createOrGetResource (c .client , podGVR , c .resources [podGVR ])
751
+ if err != nil {
752
+ c .t .Error (err )
753
+ return
754
+ }
755
+
756
+ // check all upgradeable verbs
757
+ for _ , httpMethod := range []string {"GET" , "POST" } {
758
+ c .t .Logf ("verifying %v" , httpMethod )
759
+
760
+ c .admissionHolder .expect (c .gvr , gvk (c .resource .Group , c .resource .Version , c .resource .Kind ), v1beta1 .Connect , pod .GetName (), pod .GetNamespace (), true , false )
761
+ var err error
762
+ switch c .gvr {
763
+ case gvr ("" , "v1" , "pods/exec" ):
764
+ err = c .clientset .CoreV1 ().RESTClient ().Verb (httpMethod ).Namespace (pod .GetNamespace ()).Resource ("pods" ).Name (pod .GetName ()).SubResource ("exec" ).Do ().Error ()
765
+ case gvr ("" , "v1" , "pods/attach" ):
766
+ err = c .clientset .CoreV1 ().RESTClient ().Verb (httpMethod ).Namespace (pod .GetNamespace ()).Resource ("pods" ).Name (pod .GetName ()).SubResource ("attach" ).Do ().Error ()
767
+ case gvr ("" , "v1" , "pods/portforward" ):
768
+ err = c .clientset .CoreV1 ().RESTClient ().Verb (httpMethod ).Namespace (pod .GetNamespace ()).Resource ("pods" ).Name (pod .GetName ()).SubResource ("portforward" ).Do ().Error ()
769
+ default :
770
+ c .t .Errorf ("unknown subresource %#v" , c .gvr )
771
+ return
772
+ }
773
+
774
+ if err != nil {
775
+ c .t .Logf ("debug: result of subresource connect: %v" , err )
776
+ }
777
+ c .admissionHolder .verify (c .t )
778
+
779
+ }
780
+ }
781
+
782
+ // testPodBindingEviction verifies pod binding and eviction admission
783
+ func testPodBindingEviction (c * testContext ) {
784
+ podGVR := gvr ("" , "v1" , "pods" )
785
+ pod , err := createOrGetResource (c .client , podGVR , c .resources [podGVR ])
786
+ if err != nil {
787
+ c .t .Error (err )
788
+ return
789
+ }
790
+
791
+ background := metav1 .DeletePropagationBackground
792
+ zero := int64 (0 )
793
+ forceDelete := & metav1.DeleteOptions {GracePeriodSeconds : & zero , PropagationPolicy : & background }
794
+ defer func () {
795
+ err := c .clientset .CoreV1 ().Pods (pod .GetNamespace ()).Delete (pod .GetName (), forceDelete )
796
+ if err != nil && ! errors .IsNotFound (err ) {
797
+ c .t .Error (err )
798
+ return
799
+ }
800
+ }()
801
+
802
+ c .admissionHolder .expect (c .gvr , gvk (c .resource .Group , c .resource .Version , c .resource .Kind ), v1beta1 .Create , pod .GetName (), pod .GetNamespace (), true , false )
803
+
804
+ switch c .gvr {
805
+ case gvr ("" , "v1" , "bindings" ):
806
+ err = c .clientset .CoreV1 ().RESTClient ().Post ().Namespace (pod .GetNamespace ()).Resource ("bindings" ).Body (& corev1.Binding {
807
+ ObjectMeta : metav1.ObjectMeta {Name : pod .GetName ()},
808
+ Target : corev1.ObjectReference {Name : "foo" , Kind : "Node" , APIVersion : "v1" },
809
+ }).Do ().Error ()
810
+
811
+ case gvr ("" , "v1" , "pods/binding" ):
812
+ err = c .clientset .CoreV1 ().RESTClient ().Post ().Namespace (pod .GetNamespace ()).Resource ("pods" ).Name (pod .GetName ()).SubResource ("binding" ).Body (& corev1.Binding {
813
+ ObjectMeta : metav1.ObjectMeta {Name : pod .GetName ()},
814
+ Target : corev1.ObjectReference {Name : "foo" , Kind : "Node" , APIVersion : "v1" },
815
+ }).Do ().Error ()
816
+
817
+ case gvr ("" , "v1" , "pods/eviction" ):
818
+ err = c .clientset .CoreV1 ().RESTClient ().Post ().Namespace (pod .GetNamespace ()).Resource ("pods" ).Name (pod .GetName ()).SubResource ("eviction" ).Body (& policyv1beta1.Eviction {
819
+ ObjectMeta : metav1.ObjectMeta {Name : pod .GetName ()},
820
+ DeleteOptions : forceDelete ,
821
+ }).Do ().Error ()
822
+
823
+ default :
824
+ c .t .Errorf ("unhandled resource %#v" , c .gvr )
825
+ return
826
+ }
827
+
828
+ if err != nil {
829
+ c .t .Error (err )
830
+ return
831
+ }
832
+ }
833
+
834
+ // testSubresourceProxy verifies proxy subresources
835
+ func testSubresourceProxy (c * testContext ) {
836
+ parentGVR := getParentGVR (c .gvr )
837
+ parentResource := c .resources [parentGVR ]
838
+ obj , err := createOrGetResource (c .client , parentGVR , parentResource )
839
+ if err != nil {
840
+ c .t .Error (err )
841
+ return
842
+ }
843
+
844
+ gvrWithoutSubresources := c .gvr
845
+ gvrWithoutSubresources .Resource = strings .Split (gvrWithoutSubresources .Resource , "/" )[0 ]
846
+ subresources := strings .Split (c .gvr .Resource , "/" )[1 :]
847
+
848
+ verbToHTTPMethods := map [string ][]string {
849
+ "create" : {"POST" , "GET" , "HEAD" , "OPTIONS" }, // also test read-only verbs map to Connect admission
850
+ "update" : {"PUT" },
851
+ "patch" : {"PATCH" },
852
+ "delete" : {"DELETE" },
853
+ }
854
+ httpMethodsToTest , ok := verbToHTTPMethods [c .verb ]
855
+ if ! ok {
856
+ c .t .Errorf ("unknown verb %v" , c .verb )
857
+ return
858
+ }
859
+
860
+ for _ , httpMethod := range httpMethodsToTest {
861
+ c .t .Logf ("testing %v" , httpMethod )
862
+ request := c .clientset .CoreV1 ().RESTClient ().Verb (httpMethod )
863
+
864
+ // add the namespace if required
865
+ if len (obj .GetNamespace ()) > 0 {
866
+ request = request .Namespace (obj .GetNamespace ())
867
+ }
868
+
869
+ // set expectations
870
+ c .admissionHolder .expect (c .gvr , gvk (c .resource .Group , c .resource .Version , c .resource .Kind ), v1beta1 .Connect , obj .GetName (), obj .GetNamespace (), true , false )
871
+ // run the request. we don't actually care if the request is successful, just that admission gets called as expected
872
+ err = request .Resource (gvrWithoutSubresources .Resource ).Name (obj .GetName ()).SubResource (subresources ... ).Do ().Error ()
873
+ if err != nil {
874
+ c .t .Logf ("debug: result of subresource proxy (error expected): %v" , err )
875
+ }
876
+ // verify the result
877
+ c .admissionHolder .verify (c .t )
878
+ }
879
+
880
+ }
881
+
738
882
//
739
883
// utility methods
740
884
//
@@ -808,6 +952,9 @@ func getTestFunc(gvr schema.GroupVersionResource, verb string) testFunc {
808
952
if f , found := customTestFuncs [gvr ][verb ]; found {
809
953
return f
810
954
}
955
+ if f , found := customTestFuncs [gvr ]["*" ]; found {
956
+ return f
957
+ }
811
958
if strings .Contains (gvr .Resource , "/" ) {
812
959
if f , found := defaultSubresourceFuncs [verb ]; found {
813
960
return f
0 commit comments