print_test.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. Copyright 2018 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. "context"
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "reflect"
  21. "strings"
  22. "testing"
  23. "time"
  24. auditregv1alpha1 "k8s.io/api/auditregistration/v1alpha1"
  25. batchv2alpha1 "k8s.io/api/batch/v2alpha1"
  26. discoveryv1alpha1 "k8s.io/api/discovery/v1alpha1"
  27. discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
  28. extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
  29. flowcontrolv1alpha1 "k8s.io/api/flowcontrol/v1alpha1"
  30. nodev1alpha1 "k8s.io/api/node/v1alpha1"
  31. rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
  32. schedulerapi "k8s.io/api/scheduling/v1"
  33. settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
  34. storagev1alpha1 "k8s.io/api/storage/v1alpha1"
  35. "k8s.io/apimachinery/pkg/api/meta"
  36. metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
  37. "k8s.io/apimachinery/pkg/runtime"
  38. "k8s.io/apimachinery/pkg/runtime/schema"
  39. "k8s.io/cli-runtime/pkg/genericclioptions"
  40. diskcached "k8s.io/client-go/discovery/cached/disk"
  41. "k8s.io/client-go/tools/clientcmd"
  42. clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
  43. "k8s.io/gengo/examples/set-gen/sets"
  44. "k8s.io/kubectl/pkg/cmd/util"
  45. "k8s.io/kubernetes/pkg/api/legacyscheme"
  46. "k8s.io/kubernetes/pkg/printers"
  47. printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
  48. "k8s.io/kubernetes/test/integration/framework"
  49. )
  50. var kindWhiteList = sets.NewString(
  51. // k8s.io/api/core
  52. "APIGroup",
  53. "APIVersions",
  54. "Binding",
  55. "DeleteOptions",
  56. "EphemeralContainers",
  57. "ExportOptions",
  58. "GetOptions",
  59. "ListOptions",
  60. "CreateOptions",
  61. "UpdateOptions",
  62. "PatchOptions",
  63. "NodeProxyOptions",
  64. "PodAttachOptions",
  65. "PodExecOptions",
  66. "PodPortForwardOptions",
  67. "PodLogOptions",
  68. "PodProxyOptions",
  69. "PodStatusResult",
  70. "RangeAllocation",
  71. "ServiceProxyOptions",
  72. "SerializedReference",
  73. // --
  74. // k8s.io/api/admission
  75. "AdmissionReview",
  76. // --
  77. // k8s.io/api/authentication
  78. "TokenRequest",
  79. "TokenReview",
  80. // --
  81. // k8s.io/api/authorization
  82. "LocalSubjectAccessReview",
  83. "SelfSubjectAccessReview",
  84. "SelfSubjectRulesReview",
  85. "SubjectAccessReview",
  86. // --
  87. // k8s.io/api/autoscaling
  88. "Scale",
  89. // --
  90. // k8s.io/api/apps
  91. "DeploymentRollback",
  92. // --
  93. // k8s.io/api/batch
  94. "JobTemplate",
  95. // --
  96. // k8s.io/api/imagepolicy
  97. "ImageReview",
  98. // --
  99. // k8s.io/api/policy
  100. "Eviction",
  101. // --
  102. // k8s.io/apimachinery/pkg/apis/meta
  103. "WatchEvent",
  104. "Status",
  105. // --
  106. )
  107. // TODO (soltysh): this list has to go down to 0!
  108. var missingHanlders = sets.NewString(
  109. "ClusterRole",
  110. "LimitRange",
  111. "ResourceQuota",
  112. "Role",
  113. "PriorityClass",
  114. "PodPreset",
  115. "AuditSink",
  116. )
  117. // known types that are no longer served we should tolerate restmapper errors for
  118. var unservedTypes = map[schema.GroupVersionKind]bool{
  119. {Group: "extensions", Version: "v1beta1", Kind: "ControllerRevision"}: true,
  120. {Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"}: true,
  121. {Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: true,
  122. {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicy"}: true,
  123. {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicy"}: true,
  124. {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"}: true,
  125. {Group: "apps", Version: "v1beta1", Kind: "ControllerRevision"}: true,
  126. {Group: "apps", Version: "v1beta1", Kind: "DaemonSet"}: true,
  127. {Group: "apps", Version: "v1beta1", Kind: "Deployment"}: true,
  128. {Group: "apps", Version: "v1beta1", Kind: "ReplicaSet"}: true,
  129. {Group: "apps", Version: "v1beta1", Kind: "StatefulSet"}: true,
  130. {Group: "apps", Version: "v1beta2", Kind: "ControllerRevision"}: true,
  131. {Group: "apps", Version: "v1beta2", Kind: "DaemonSet"}: true,
  132. {Group: "apps", Version: "v1beta2", Kind: "Deployment"}: true,
  133. {Group: "apps", Version: "v1beta2", Kind: "ReplicaSet"}: true,
  134. {Group: "apps", Version: "v1beta2", Kind: "StatefulSet"}: true,
  135. }
  136. func TestServerSidePrint(t *testing.T) {
  137. s, _, closeFn := setupWithResources(t,
  138. // additional groupversions needed for the test to run
  139. []schema.GroupVersion{
  140. auditregv1alpha1.SchemeGroupVersion,
  141. batchv2alpha1.SchemeGroupVersion,
  142. discoveryv1alpha1.SchemeGroupVersion,
  143. discoveryv1beta1.SchemeGroupVersion,
  144. rbacv1alpha1.SchemeGroupVersion,
  145. settingsv1alpha1.SchemeGroupVersion,
  146. schedulerapi.SchemeGroupVersion,
  147. storagev1alpha1.SchemeGroupVersion,
  148. extensionsv1beta1.SchemeGroupVersion,
  149. nodev1alpha1.SchemeGroupVersion,
  150. flowcontrolv1alpha1.SchemeGroupVersion,
  151. },
  152. []schema.GroupVersionResource{},
  153. )
  154. defer closeFn()
  155. ns := framework.CreateTestingNamespace("server-print", s, t)
  156. defer framework.DeleteTestingNamespace(ns, s, t)
  157. tableParam := fmt.Sprintf("application/json;as=Table;g=%s;v=%s, application/json", metav1beta1.GroupName, metav1beta1.SchemeGroupVersion.Version)
  158. printer := newFakePrinter(printersinternal.AddHandlers)
  159. configFlags := genericclioptions.NewTestConfigFlags().
  160. WithClientConfig(clientcmd.NewDefaultClientConfig(*createKubeConfig(s.URL), &clientcmd.ConfigOverrides{}))
  161. restConfig, err := configFlags.ToRESTConfig()
  162. if err != nil {
  163. t.Errorf("unexpected error: %v", err)
  164. }
  165. cacheDir, err := ioutil.TempDir(os.TempDir(), "test-integration-apiserver-print")
  166. if err != nil {
  167. t.Errorf("unexpected error: %v", err)
  168. }
  169. defer func() {
  170. os.Remove(cacheDir)
  171. }()
  172. cachedClient, err := diskcached.NewCachedDiscoveryClientForConfig(restConfig, cacheDir, "", time.Duration(10*time.Minute))
  173. if err != nil {
  174. t.Errorf("unexpected error: %v", err)
  175. }
  176. configFlags.WithDiscoveryClient(cachedClient)
  177. factory := util.NewFactory(configFlags)
  178. mapper, err := factory.ToRESTMapper()
  179. if err != nil {
  180. t.Errorf("unexpected error getting mapper: %v", err)
  181. return
  182. }
  183. for gvk, apiType := range legacyscheme.Scheme.AllKnownTypes() {
  184. // we do not care about internal objects or lists // TODO make sure this is always true
  185. if gvk.Version == runtime.APIVersionInternal || strings.HasSuffix(apiType.Name(), "List") {
  186. continue
  187. }
  188. if kindWhiteList.Has(gvk.Kind) || missingHanlders.Has(gvk.Kind) {
  189. continue
  190. }
  191. t.Logf("Checking %s", gvk)
  192. // read table definition as returned by the server
  193. mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
  194. if err != nil {
  195. if unservedTypes[gvk] {
  196. continue
  197. }
  198. t.Errorf("unexpected error getting mapping for GVK %s: %v", gvk, err)
  199. continue
  200. }
  201. client, err := factory.ClientForMapping(mapping)
  202. if err != nil {
  203. t.Errorf("unexpected error getting client for GVK %s: %v", gvk, err)
  204. continue
  205. }
  206. req := client.Get()
  207. if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
  208. req = req.Namespace(ns.Name)
  209. }
  210. body, err := req.Resource(mapping.Resource.Resource).SetHeader("Accept", tableParam).Do(context.TODO()).Raw()
  211. if err != nil {
  212. t.Errorf("unexpected error getting %s: %v", gvk, err)
  213. continue
  214. }
  215. actual, err := decodeIntoTable(body)
  216. if err != nil {
  217. t.Errorf("unexpected error decoding %s: %v", gvk, err)
  218. continue
  219. }
  220. // get table definition used in printers
  221. obj, err := legacyscheme.Scheme.New(gvk)
  222. if err != nil {
  223. t.Errorf("unexpected error creating %s: %v", gvk, err)
  224. continue
  225. }
  226. intGV := gvk.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
  227. intObj, err := legacyscheme.Scheme.ConvertToVersion(obj, intGV)
  228. if err != nil {
  229. t.Errorf("unexpected error converting %s to internal: %v", gvk, err)
  230. continue
  231. }
  232. expectedColumnDefinitions, ok := printer.handlers[reflect.TypeOf(intObj)]
  233. if !ok {
  234. t.Errorf("missing handler for type %v", gvk)
  235. continue
  236. }
  237. for _, e := range expectedColumnDefinitions {
  238. for _, a := range actual.ColumnDefinitions {
  239. if a.Name == e.Name && !reflect.DeepEqual(a, e) {
  240. t.Errorf("unexpected difference in column definition %s for %s:\nexpected:\n%#v\nactual:\n%#v\n", e.Name, gvk, e, a)
  241. }
  242. }
  243. }
  244. }
  245. }
  246. type fakePrinter struct {
  247. handlers map[reflect.Type][]metav1beta1.TableColumnDefinition
  248. }
  249. var _ printers.PrintHandler = &fakePrinter{}
  250. func (f *fakePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
  251. return nil
  252. }
  253. func (f *fakePrinter) TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
  254. printFuncValue := reflect.ValueOf(printFunc)
  255. objType := printFuncValue.Type().In(0)
  256. f.handlers[objType] = columns
  257. return nil
  258. }
  259. func (f *fakePrinter) DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
  260. return nil
  261. }
  262. func newFakePrinter(fns ...func(printers.PrintHandler)) *fakePrinter {
  263. handlers := make(map[reflect.Type][]metav1beta1.TableColumnDefinition, len(fns))
  264. p := &fakePrinter{handlers: handlers}
  265. for _, fn := range fns {
  266. fn(p)
  267. }
  268. return p
  269. }
  270. func decodeIntoTable(body []byte) (*metav1beta1.Table, error) {
  271. table := &metav1beta1.Table{}
  272. err := json.Unmarshal(body, table)
  273. if err != nil {
  274. return nil, err
  275. }
  276. return table, nil
  277. }
  278. func createKubeConfig(url string) *clientcmdapi.Config {
  279. clusterNick := "cluster"
  280. userNick := "user"
  281. contextNick := "context"
  282. config := clientcmdapi.NewConfig()
  283. cluster := clientcmdapi.NewCluster()
  284. cluster.Server = url
  285. cluster.InsecureSkipTLSVerify = true
  286. config.Clusters[clusterNick] = cluster
  287. context := clientcmdapi.NewContext()
  288. context.Cluster = clusterNick
  289. context.AuthInfo = userNick
  290. config.Contexts[contextNick] = context
  291. config.CurrentContext = contextNick
  292. return config
  293. }