apiserver_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package apiserver
  14. import (
  15. "crypto/x509"
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "net"
  20. "net/http"
  21. "os"
  22. "path"
  23. "sync/atomic"
  24. "testing"
  25. "time"
  26. "github.com/stretchr/testify/assert"
  27. apierrors "k8s.io/apimachinery/pkg/api/errors"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/runtime/schema"
  30. "k8s.io/apimachinery/pkg/util/wait"
  31. genericapiserver "k8s.io/apiserver/pkg/server"
  32. genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
  33. discovery "k8s.io/client-go/discovery"
  34. client "k8s.io/client-go/kubernetes"
  35. "k8s.io/client-go/rest"
  36. "k8s.io/client-go/tools/clientcmd"
  37. clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
  38. "k8s.io/client-go/util/cert"
  39. "k8s.io/client-go/util/keyutil"
  40. apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
  41. aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
  42. kubeaggregatorserver "k8s.io/kube-aggregator/pkg/cmd/server"
  43. "k8s.io/kubernetes/cmd/kube-apiserver/app"
  44. "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
  45. "k8s.io/kubernetes/test/integration/framework"
  46. testutil "k8s.io/kubernetes/test/utils"
  47. wardlev1alpha1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1"
  48. wardlev1beta1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1beta1"
  49. sampleserver "k8s.io/sample-apiserver/pkg/cmd/server"
  50. )
  51. func TestAggregatedAPIServer(t *testing.T) {
  52. stopCh := make(chan struct{})
  53. defer close(stopCh)
  54. certDir, _ := ioutil.TempDir("", "test-integration-apiserver")
  55. defer os.RemoveAll(certDir)
  56. _, defaultServiceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24")
  57. proxySigningKey, err := testutil.NewPrivateKey()
  58. if err != nil {
  59. t.Fatal(err)
  60. }
  61. proxySigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "front-proxy-ca"}, proxySigningKey)
  62. if err != nil {
  63. t.Fatal(err)
  64. }
  65. proxyCACertFile, _ := ioutil.TempFile(certDir, "proxy-ca.crt")
  66. if err := ioutil.WriteFile(proxyCACertFile.Name(), testutil.EncodeCertPEM(proxySigningCert), 0644); err != nil {
  67. t.Fatal(err)
  68. }
  69. clientSigningKey, err := testutil.NewPrivateKey()
  70. if err != nil {
  71. t.Fatal(err)
  72. }
  73. clientSigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "client-ca"}, clientSigningKey)
  74. if err != nil {
  75. t.Fatal(err)
  76. }
  77. clientCACertFile, _ := ioutil.TempFile(certDir, "client-ca.crt")
  78. if err := ioutil.WriteFile(clientCACertFile.Name(), testutil.EncodeCertPEM(clientSigningCert), 0644); err != nil {
  79. t.Fatal(err)
  80. }
  81. kubeClientConfigValue := atomic.Value{}
  82. go func() {
  83. listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
  84. if err != nil {
  85. t.Fatal(err)
  86. }
  87. kubeAPIServerOptions := options.NewServerRunOptions()
  88. kubeAPIServerOptions.SecureServing.Listener = listener
  89. kubeAPIServerOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
  90. kubeAPIServerOptions.SecureServing.ServerCert.CertDirectory = certDir
  91. kubeAPIServerOptions.InsecureServing.BindPort = 0
  92. kubeAPIServerOptions.Etcd.StorageConfig.Transport.ServerList = []string{framework.GetEtcdURL()}
  93. kubeAPIServerOptions.ServiceClusterIPRange = *defaultServiceClusterIPRange
  94. kubeAPIServerOptions.Authentication.RequestHeader.UsernameHeaders = []string{"X-Remote-User"}
  95. kubeAPIServerOptions.Authentication.RequestHeader.GroupHeaders = []string{"X-Remote-Group"}
  96. kubeAPIServerOptions.Authentication.RequestHeader.ExtraHeaderPrefixes = []string{"X-Remote-Extra-"}
  97. kubeAPIServerOptions.Authentication.RequestHeader.AllowedNames = []string{"kube-aggregator"}
  98. kubeAPIServerOptions.Authentication.RequestHeader.ClientCAFile = proxyCACertFile.Name()
  99. kubeAPIServerOptions.Authentication.ClientCert.ClientCA = clientCACertFile.Name()
  100. kubeAPIServerOptions.Authorization.Modes = []string{"RBAC"}
  101. completedOptions, err := app.Complete(kubeAPIServerOptions)
  102. if err != nil {
  103. t.Fatal(err)
  104. }
  105. tunneler, proxyTransport, err := app.CreateNodeDialer(completedOptions)
  106. if err != nil {
  107. t.Fatal(err)
  108. }
  109. kubeAPIServerConfig, _, _, _, admissionPostStartHook, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport)
  110. if err != nil {
  111. t.Fatal(err)
  112. }
  113. // Adjust the loopback config for external use (external server name and CA)
  114. kubeAPIServerClientConfig := rest.CopyConfig(kubeAPIServerConfig.GenericConfig.LoopbackClientConfig)
  115. kubeAPIServerClientConfig.CAFile = path.Join(certDir, "apiserver.crt")
  116. kubeAPIServerClientConfig.CAData = nil
  117. kubeAPIServerClientConfig.ServerName = ""
  118. kubeClientConfigValue.Store(kubeAPIServerClientConfig)
  119. kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), admissionPostStartHook)
  120. if err != nil {
  121. t.Fatal(err)
  122. }
  123. if err := kubeAPIServer.GenericAPIServer.PrepareRun().Run(wait.NeverStop); err != nil {
  124. t.Fatal(err)
  125. }
  126. }()
  127. // just use json because everyone speaks it
  128. err = wait.PollImmediate(time.Second, time.Minute, func() (done bool, err error) {
  129. obj := kubeClientConfigValue.Load()
  130. if obj == nil {
  131. return false, nil
  132. }
  133. kubeClientConfig := kubeClientConfigValue.Load().(*rest.Config)
  134. kubeClientConfig.ContentType = ""
  135. kubeClientConfig.AcceptContentTypes = ""
  136. kubeClient, err := client.NewForConfig(kubeClientConfig)
  137. if err != nil {
  138. // this happens because we race the API server start
  139. t.Log(err)
  140. return false, nil
  141. }
  142. healthStatus := 0
  143. kubeClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
  144. if healthStatus != http.StatusOK {
  145. return false, nil
  146. }
  147. return true, nil
  148. })
  149. if err != nil {
  150. t.Fatal(err)
  151. }
  152. // after this point we won't be mutating, so the race detector will be fine
  153. kubeClientConfig := kubeClientConfigValue.Load().(*rest.Config)
  154. // write a kubeconfig out for starting other API servers with delegated auth. remember, no in-cluster config
  155. adminKubeConfig := createKubeConfig(kubeClientConfig)
  156. kubeconfigFile, _ := ioutil.TempFile("", "")
  157. defer os.Remove(kubeconfigFile.Name())
  158. clientcmd.WriteToFile(*adminKubeConfig, kubeconfigFile.Name())
  159. wardleCertDir, _ := ioutil.TempDir("", "test-integration-wardle-server")
  160. defer os.RemoveAll(wardleCertDir)
  161. wardlePort := new(int32)
  162. // start the wardle server to prove we can aggregate it
  163. go func() {
  164. listener, port, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
  165. if err != nil {
  166. t.Fatal(err)
  167. }
  168. atomic.StoreInt32(wardlePort, int32(port))
  169. o := sampleserver.NewWardleServerOptions(os.Stdout, os.Stderr)
  170. o.RecommendedOptions.SecureServing.Listener = listener
  171. o.RecommendedOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
  172. wardleCmd := sampleserver.NewCommandStartWardleServer(o, stopCh)
  173. wardleCmd.SetArgs([]string{
  174. "--requestheader-username-headers=X-Remote-User",
  175. "--requestheader-group-headers=X-Remote-Group",
  176. "--requestheader-extra-headers-prefix=X-Remote-Extra-",
  177. "--requestheader-client-ca-file=" + proxyCACertFile.Name(),
  178. "--requestheader-allowed-names=kube-aggregator",
  179. "--authentication-kubeconfig", kubeconfigFile.Name(),
  180. "--authorization-kubeconfig", kubeconfigFile.Name(),
  181. "--etcd-servers", framework.GetEtcdURL(),
  182. "--cert-dir", wardleCertDir,
  183. "--kubeconfig", kubeconfigFile.Name(),
  184. })
  185. if err := wardleCmd.Execute(); err != nil {
  186. t.Fatal(err)
  187. }
  188. }()
  189. wardleClientConfig := rest.AnonymousClientConfig(kubeClientConfig)
  190. wardleClientConfig.CAFile = path.Join(wardleCertDir, "apiserver.crt")
  191. wardleClientConfig.CAData = nil
  192. wardleClientConfig.ServerName = ""
  193. wardleClientConfig.BearerToken = kubeClientConfig.BearerToken
  194. var wardleClient client.Interface
  195. err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
  196. wardleClientConfig.Host = fmt.Sprintf("https://127.0.0.1:%d", atomic.LoadInt32(wardlePort))
  197. wardleClient, err = client.NewForConfig(wardleClientConfig)
  198. if err != nil {
  199. // this happens because we race the API server start
  200. t.Log(err)
  201. return false, nil
  202. }
  203. healthStatus := 0
  204. wardleClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
  205. if healthStatus != http.StatusOK {
  206. return false, nil
  207. }
  208. return true, nil
  209. })
  210. if err != nil {
  211. t.Fatal(err)
  212. }
  213. // start the aggregator
  214. aggregatorCertDir, _ := ioutil.TempDir("", "test-integration-aggregator")
  215. defer os.RemoveAll(aggregatorCertDir)
  216. proxyClientKey, err := testutil.NewPrivateKey()
  217. if err != nil {
  218. t.Fatal(err)
  219. }
  220. proxyClientCert, err := testutil.NewSignedCert(
  221. &cert.Config{
  222. CommonName: "kube-aggregator",
  223. Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
  224. },
  225. proxyClientKey, proxySigningCert, proxySigningKey,
  226. )
  227. proxyClientCertFile, _ := ioutil.TempFile(aggregatorCertDir, "proxy-client.crt")
  228. proxyClientKeyFile, _ := ioutil.TempFile(aggregatorCertDir, "proxy-client.key")
  229. if err := ioutil.WriteFile(proxyClientCertFile.Name(), testutil.EncodeCertPEM(proxyClientCert), 0600); err != nil {
  230. t.Fatal(err)
  231. }
  232. proxyClientKeyPEM, err := keyutil.MarshalPrivateKeyToPEM(proxyClientKey)
  233. if err != nil {
  234. t.Fatal(err)
  235. }
  236. if err := ioutil.WriteFile(proxyClientKeyFile.Name(), proxyClientKeyPEM, 0644); err != nil {
  237. t.Fatal(err)
  238. }
  239. aggregatorPort := new(int32)
  240. go func() {
  241. listener, port, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
  242. if err != nil {
  243. t.Fatal(err)
  244. }
  245. atomic.StoreInt32(aggregatorPort, int32(port))
  246. o := kubeaggregatorserver.NewDefaultOptions(os.Stdout, os.Stderr)
  247. o.RecommendedOptions.SecureServing.Listener = listener
  248. o.RecommendedOptions.SecureServing.BindAddress = net.ParseIP("127.0.0.1")
  249. aggregatorCmd := kubeaggregatorserver.NewCommandStartAggregator(o, stopCh)
  250. aggregatorCmd.SetArgs([]string{
  251. "--requestheader-username-headers", "",
  252. "--proxy-client-cert-file", proxyClientCertFile.Name(),
  253. "--proxy-client-key-file", proxyClientKeyFile.Name(),
  254. "--kubeconfig", kubeconfigFile.Name(),
  255. "--authentication-kubeconfig", kubeconfigFile.Name(),
  256. "--authorization-kubeconfig", kubeconfigFile.Name(),
  257. "--etcd-servers", framework.GetEtcdURL(),
  258. "--cert-dir", aggregatorCertDir,
  259. })
  260. if err := aggregatorCmd.Execute(); err != nil {
  261. t.Fatal(err)
  262. }
  263. }()
  264. aggregatorClientConfig := rest.AnonymousClientConfig(kubeClientConfig)
  265. aggregatorClientConfig.CAFile = path.Join(aggregatorCertDir, "apiserver.crt")
  266. aggregatorClientConfig.CAData = nil
  267. aggregatorClientConfig.ServerName = ""
  268. aggregatorClientConfig.BearerToken = kubeClientConfig.BearerToken
  269. var aggregatorDiscoveryClient client.Interface
  270. err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
  271. aggregatorClientConfig.Host = fmt.Sprintf("https://127.0.0.1:%d", atomic.LoadInt32(aggregatorPort))
  272. aggregatorDiscoveryClient, err = client.NewForConfig(aggregatorClientConfig)
  273. if err != nil {
  274. // this happens if we race the API server for writing the cert
  275. return false, nil
  276. }
  277. healthStatus := 0
  278. aggregatorDiscoveryClient.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
  279. if healthStatus != http.StatusOK {
  280. return false, nil
  281. }
  282. return true, nil
  283. })
  284. if err != nil {
  285. t.Fatal(err)
  286. }
  287. // now we're finally ready to test. These are what's run by default now
  288. testAPIGroupList(t, wardleClient.Discovery().RESTClient())
  289. testAPIGroup(t, wardleClient.Discovery().RESTClient())
  290. testAPIResourceList(t, wardleClient.Discovery().RESTClient())
  291. wardleCA, err := ioutil.ReadFile(wardleClientConfig.CAFile)
  292. if err != nil {
  293. t.Fatal(err)
  294. }
  295. aggregatorClient := aggregatorclient.NewForConfigOrDie(aggregatorClientConfig)
  296. _, err = aggregatorClient.ApiregistrationV1beta1().APIServices().Create(&apiregistrationv1beta1.APIService{
  297. ObjectMeta: metav1.ObjectMeta{Name: "v1alpha1.wardle.k8s.io"},
  298. Spec: apiregistrationv1beta1.APIServiceSpec{
  299. Service: &apiregistrationv1beta1.ServiceReference{
  300. Namespace: "kube-wardle",
  301. Name: "api",
  302. },
  303. Group: "wardle.k8s.io",
  304. Version: "v1alpha1",
  305. CABundle: wardleCA,
  306. GroupPriorityMinimum: 200,
  307. VersionPriority: 200,
  308. },
  309. })
  310. if err != nil {
  311. t.Fatal(err)
  312. }
  313. // wait for the unavailable API service to be processed with updated status
  314. err = wait.Poll(100*time.Millisecond, 5*time.Second, func() (done bool, err error) {
  315. _, err = aggregatorDiscoveryClient.Discovery().ServerResources()
  316. hasExpectedError := checkWardleUnavailableDiscoveryError(t, err)
  317. return hasExpectedError, nil
  318. })
  319. if err != nil {
  320. t.Fatal(err)
  321. }
  322. _, err = aggregatorClient.ApiregistrationV1beta1().APIServices().Create(&apiregistrationv1beta1.APIService{
  323. ObjectMeta: metav1.ObjectMeta{Name: "v1."},
  324. Spec: apiregistrationv1beta1.APIServiceSpec{
  325. // register this as a local service so it doesn't try to lookup the default kubernetes service
  326. // which will have an unroutable IP address since it's fake.
  327. Group: "",
  328. Version: "v1",
  329. GroupPriorityMinimum: 100,
  330. VersionPriority: 100,
  331. },
  332. })
  333. if err != nil {
  334. t.Fatal(err)
  335. }
  336. // this is ugly, but sleep just a little bit so that the watch is probably observed. Since nothing will actually be added to discovery
  337. // (the service is missing), we don't have an external signal.
  338. time.Sleep(100 * time.Millisecond)
  339. _, err = aggregatorDiscoveryClient.Discovery().ServerResources()
  340. hasExpectedError := checkWardleUnavailableDiscoveryError(t, err)
  341. if !hasExpectedError {
  342. t.Fatalf("Discovery call didn't return expected error: %v", err)
  343. }
  344. // TODO figure out how to turn on enough of services and dns to run more
  345. }
  346. func checkWardleUnavailableDiscoveryError(t *testing.T, err error) bool {
  347. if err == nil {
  348. t.Log("Discovery call expected to return failed unavailable service")
  349. return false
  350. }
  351. if !discovery.IsGroupDiscoveryFailedError(err) {
  352. t.Logf("Unexpected error: %T, %v", err, err)
  353. return false
  354. }
  355. discoveryErr := err.(*discovery.ErrGroupDiscoveryFailed)
  356. if len(discoveryErr.Groups) != 1 {
  357. t.Logf("Unexpected failed groups: %v", err)
  358. return false
  359. }
  360. groupVersion := schema.GroupVersion{Group: "wardle.k8s.io", Version: "v1alpha1"}
  361. groupVersionErr, ok := discoveryErr.Groups[groupVersion]
  362. if !ok {
  363. t.Logf("Unexpected failed group version: %v", err)
  364. return false
  365. }
  366. if !apierrors.IsServiceUnavailable(groupVersionErr) {
  367. t.Logf("Unexpected failed group version error: %v", err)
  368. return false
  369. }
  370. return true
  371. }
  372. func createKubeConfig(clientCfg *rest.Config) *clientcmdapi.Config {
  373. clusterNick := "cluster"
  374. userNick := "user"
  375. contextNick := "context"
  376. config := clientcmdapi.NewConfig()
  377. credentials := clientcmdapi.NewAuthInfo()
  378. credentials.Token = clientCfg.BearerToken
  379. credentials.ClientCertificate = clientCfg.TLSClientConfig.CertFile
  380. if len(credentials.ClientCertificate) == 0 {
  381. credentials.ClientCertificateData = clientCfg.TLSClientConfig.CertData
  382. }
  383. credentials.ClientKey = clientCfg.TLSClientConfig.KeyFile
  384. if len(credentials.ClientKey) == 0 {
  385. credentials.ClientKeyData = clientCfg.TLSClientConfig.KeyData
  386. }
  387. config.AuthInfos[userNick] = credentials
  388. cluster := clientcmdapi.NewCluster()
  389. cluster.Server = clientCfg.Host
  390. cluster.CertificateAuthority = clientCfg.CAFile
  391. if len(cluster.CertificateAuthority) == 0 {
  392. cluster.CertificateAuthorityData = clientCfg.CAData
  393. }
  394. cluster.InsecureSkipTLSVerify = clientCfg.Insecure
  395. config.Clusters[clusterNick] = cluster
  396. context := clientcmdapi.NewContext()
  397. context.Cluster = clusterNick
  398. context.AuthInfo = userNick
  399. config.Contexts[contextNick] = context
  400. config.CurrentContext = contextNick
  401. return config
  402. }
  403. func readResponse(client rest.Interface, location string) ([]byte, error) {
  404. return client.Get().AbsPath(location).DoRaw()
  405. }
  406. func testAPIGroupList(t *testing.T, client rest.Interface) {
  407. contents, err := readResponse(client, "/apis")
  408. if err != nil {
  409. t.Fatalf("%v", err)
  410. }
  411. t.Log(string(contents))
  412. var apiGroupList metav1.APIGroupList
  413. err = json.Unmarshal(contents, &apiGroupList)
  414. if err != nil {
  415. t.Fatalf("Error in unmarshalling response from server %s: %v", "/apis", err)
  416. }
  417. assert.Equal(t, 1, len(apiGroupList.Groups))
  418. assert.Equal(t, wardlev1alpha1.GroupName, apiGroupList.Groups[0].Name)
  419. assert.Equal(t, 2, len(apiGroupList.Groups[0].Versions))
  420. v1alpha1 := metav1.GroupVersionForDiscovery{
  421. GroupVersion: wardlev1alpha1.SchemeGroupVersion.String(),
  422. Version: wardlev1alpha1.SchemeGroupVersion.Version,
  423. }
  424. v1beta1 := metav1.GroupVersionForDiscovery{
  425. GroupVersion: wardlev1beta1.SchemeGroupVersion.String(),
  426. Version: wardlev1beta1.SchemeGroupVersion.Version,
  427. }
  428. assert.Equal(t, v1beta1, apiGroupList.Groups[0].Versions[0])
  429. assert.Equal(t, v1alpha1, apiGroupList.Groups[0].Versions[1])
  430. assert.Equal(t, v1beta1, apiGroupList.Groups[0].PreferredVersion)
  431. }
  432. func testAPIGroup(t *testing.T, client rest.Interface) {
  433. contents, err := readResponse(client, "/apis/wardle.k8s.io")
  434. if err != nil {
  435. t.Fatalf("%v", err)
  436. }
  437. t.Log(string(contents))
  438. var apiGroup metav1.APIGroup
  439. err = json.Unmarshal(contents, &apiGroup)
  440. if err != nil {
  441. t.Fatalf("Error in unmarshalling response from server %s: %v", "/apis/wardle.k8s.io", err)
  442. }
  443. assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.Group, apiGroup.Name)
  444. assert.Equal(t, 2, len(apiGroup.Versions))
  445. assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.String(), apiGroup.Versions[1].GroupVersion)
  446. assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.Version, apiGroup.Versions[1].Version)
  447. assert.Equal(t, apiGroup.PreferredVersion, apiGroup.Versions[0])
  448. }
  449. func testAPIResourceList(t *testing.T, client rest.Interface) {
  450. contents, err := readResponse(client, "/apis/wardle.k8s.io/v1alpha1")
  451. if err != nil {
  452. t.Fatalf("%v", err)
  453. }
  454. t.Log(string(contents))
  455. var apiResourceList metav1.APIResourceList
  456. err = json.Unmarshal(contents, &apiResourceList)
  457. if err != nil {
  458. t.Fatalf("Error in unmarshalling response from server %s: %v", "/apis/wardle.k8s.io/v1alpha1", err)
  459. }
  460. assert.Equal(t, wardlev1alpha1.SchemeGroupVersion.String(), apiResourceList.GroupVersion)
  461. assert.Equal(t, 2, len(apiResourceList.APIResources))
  462. assert.Equal(t, "fischers", apiResourceList.APIResources[0].Name)
  463. assert.False(t, apiResourceList.APIResources[0].Namespaced)
  464. assert.Equal(t, "flunders", apiResourceList.APIResources[1].Name)
  465. assert.True(t, apiResourceList.APIResources[1].Namespaced)
  466. }