1
16
17 package openapi
18
19 import (
20 "context"
21 "encoding/json"
22 "io"
23 "net/http"
24 "reflect"
25 "strings"
26 "testing"
27 "time"
28
29 openapi_v3 "github.com/google/gnostic-models/openapiv3"
30 "google.golang.org/protobuf/proto"
31 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
32 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
33 "k8s.io/apiextensions-apiserver/test/integration/fixtures"
34 "k8s.io/client-go/dynamic"
35 kubernetes "k8s.io/client-go/kubernetes"
36 restclient "k8s.io/client-go/rest"
37 "k8s.io/kube-openapi/pkg/handler3"
38 "k8s.io/kube-openapi/pkg/spec3"
39 apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
40 "k8s.io/kubernetes/test/integration/framework"
41 "k8s.io/kubernetes/test/utils/ktesting"
42 "sigs.k8s.io/yaml"
43 )
44
45 func TestOpenAPIV3SpecRoundTrip(t *testing.T) {
46 tCtx := ktesting.Init(t)
47 _, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{})
48 defer tearDownFn()
49
50 paths := []string{
51 "/apis/apps/v1",
52 "/apis/authentication.k8s.io/v1",
53 "/apis/policy/v1",
54 "/apis/batch/v1",
55 "/version",
56 }
57 for _, path := range paths {
58 t.Run(path, func(t *testing.T) {
59 rt, err := restclient.TransportFor(kubeConfig)
60 if err != nil {
61 t.Fatal(err)
62 }
63
64 url := kubeConfig.Host + "/openapi/v3" + path
65 req, err := http.NewRequest("GET", url, nil)
66 if err != nil {
67 t.Fatal(err)
68 }
69 resp, err := rt.RoundTrip(req)
70 if err != nil {
71 t.Fatal(err)
72 }
73 defer resp.Body.Close()
74 bs, err := io.ReadAll(resp.Body)
75 if err != nil {
76 t.Fatal(err)
77 }
78 var firstSpec spec3.OpenAPI
79 err = json.Unmarshal(bs, &firstSpec)
80 if err != nil {
81 t.Fatal(err)
82 }
83 specBytes, err := json.Marshal(&firstSpec)
84 if err != nil {
85 t.Fatal(err)
86 }
87 var secondSpec spec3.OpenAPI
88 err = json.Unmarshal(specBytes, &secondSpec)
89 if err != nil {
90 t.Fatal(err)
91 }
92 if !reflect.DeepEqual(firstSpec, secondSpec) {
93 t.Fatal("spec mismatch")
94 }
95 })
96 }
97 }
98
99 func TestAddRemoveGroupVersion(t *testing.T) {
100 server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
101 if err != nil {
102 t.Fatal(err)
103 }
104 defer server.TearDownFn()
105 config := server.ClientConfig
106
107 apiExtensionClient, err := clientset.NewForConfig(config)
108 if err != nil {
109 t.Fatal(err)
110 }
111 dynamicClient, err := dynamic.NewForConfig(config)
112 if err != nil {
113 t.Fatal(err)
114 }
115 clientset, err := kubernetes.NewForConfig(config)
116 if err != nil {
117 t.Fatal(err)
118 }
119
120
121 noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
122 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
123 if err != nil {
124 t.Fatal(err)
125 }
126
127
128 time.Sleep(4 * time.Second)
129
130
131 jsonData, err := clientset.RESTClient().Get().AbsPath("/openapi/v3").Do(context.TODO()).Raw()
132 if err != nil {
133 t.Fatal(err)
134 }
135 openAPIv3GV := handler3.OpenAPIV3Discovery{}
136 err = json.Unmarshal(jsonData, &openAPIv3GV)
137 if err != nil {
138 t.Fatal(err)
139 }
140 foundPath := false
141 for path := range openAPIv3GV.Paths {
142 if strings.Contains(path, "mygroup.example.com/v1beta1") {
143 foundPath = true
144 }
145 }
146 if foundPath == false {
147 t.Fatal("Expected group version mygroup.example.com to be present after CRD applied")
148 }
149
150
151 jsonData, err = clientset.RESTClient().Get().AbsPath("/openapi/v3/apis/mygroup.example.com/v1beta1").Do(context.TODO()).Raw()
152 if err != nil {
153 t.Fatal(err)
154 }
155 var firstSpec spec3.OpenAPI
156 err = json.Unmarshal(jsonData, &firstSpec)
157 if err != nil {
158 t.Fatal(err)
159 }
160
161
162 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
163 t.Fatal(err)
164 }
165 time.Sleep(4 * time.Second)
166
167 jsonData, err = clientset.RESTClient().Get().AbsPath("/openapi/v3").Do(context.TODO()).Raw()
168 if err != nil {
169 t.Fatal(err)
170 }
171 openAPIv3GV = handler3.OpenAPIV3Discovery{}
172 err = json.Unmarshal(jsonData, &openAPIv3GV)
173 if err != nil {
174 t.Fatal(err)
175 }
176
177 for path := range openAPIv3GV.Paths {
178 if strings.Contains(path, "mygroup.example.com") {
179 t.Fatal("Unexpected group version mygroup.example.com in OpenAPI v3 discovery")
180 }
181 }
182 }
183
184 func TestOpenAPIV3ProtoRoundtrip(t *testing.T) {
185
186
187 t.Skip("Skipping OpenAPI V3 Proto roundtrip test")
188
189 tCtx := ktesting.Init(t)
190 _, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{})
191 defer tearDownFn()
192
193 rt, err := restclient.TransportFor(kubeConfig)
194 if err != nil {
195 t.Fatal(err)
196 }
197
198 req, err := http.NewRequest("GET", kubeConfig.Host+"/openapi/v3/apis/apps/v1", nil)
199 if err != nil {
200 t.Fatal(err)
201 }
202 resp, err := rt.RoundTrip(req)
203 if err != nil {
204 t.Fatal(err)
205 }
206 defer resp.Body.Close()
207 bs, err := io.ReadAll(resp.Body)
208 if err != nil {
209 t.Fatal(err)
210 }
211 var firstSpec spec3.OpenAPI
212 err = json.Unmarshal(bs, &firstSpec)
213 if err != nil {
214 t.Fatal(err)
215 }
216
217 protoReq, err := http.NewRequest("GET", kubeConfig.Host+"/openapi/v3/apis/apps/v1", nil)
218 if err != nil {
219 t.Fatal(err)
220 }
221 protoReq.Header.Set("Accept", "application/com.github.proto-openapi.spec.v3@v1.0+protobuf")
222 protoResp, err := rt.RoundTrip(protoReq)
223 if err != nil {
224 t.Fatal(err)
225 }
226 defer protoResp.Body.Close()
227 bs, err = io.ReadAll(protoResp.Body)
228 if err != nil {
229 t.Fatal(err)
230 }
231 var protoDoc openapi_v3.Document
232 err = proto.Unmarshal(bs, &protoDoc)
233 if err != nil {
234 t.Fatal(err)
235 }
236
237 yamlBytes, err := protoDoc.YAMLValue("")
238 if err != nil {
239 t.Fatal(err)
240 }
241 jsonBytes, err := yaml.YAMLToJSON(yamlBytes)
242 if err != nil {
243 t.Fatal(err)
244 }
245 var specFromProto spec3.OpenAPI
246 err = json.Unmarshal(jsonBytes, &specFromProto)
247 if err != nil {
248 t.Fatal(err)
249 }
250
251 if !reflect.DeepEqual(specFromProto, firstSpec) {
252 t.Fatalf("spec mismatch - specFromProto: %s\n", jsonBytes)
253 }
254 }
255
View as plain text