...

Source file src/k8s.io/kubernetes/test/integration/apiserver/apply/status_test.go

Documentation: k8s.io/kubernetes/test/integration/apiserver/apply

     1  /*
     2  Copyright 2020 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 apiserver
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"strings"
    23  	"testing"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    27  	"k8s.io/apimachinery/pkg/api/meta"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/client-go/dynamic"
    32  	"k8s.io/client-go/kubernetes"
    33  	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    34  	"k8s.io/kubernetes/test/integration/etcd"
    35  	"k8s.io/kubernetes/test/integration/framework"
    36  )
    37  
    38  // namespace used for all tests, do not change this
    39  const testNamespace = "statusnamespace"
    40  
    41  var statusData = map[schema.GroupVersionResource]string{
    42  	gvr("", "v1", "persistentvolumes"):                              `{"status": {"message": "hello"}}`,
    43  	gvr("", "v1", "resourcequotas"):                                 `{"status": {"used": {"cpu": "5M"}}}`,
    44  	gvr("", "v1", "services"):                                       `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
    45  	gvr("extensions", "v1beta1", "ingresses"):                       `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
    46  	gvr("networking.k8s.io", "v1beta1", "ingresses"):                `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
    47  	gvr("networking.k8s.io", "v1", "ingresses"):                     `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
    48  	gvr("autoscaling", "v1", "horizontalpodautoscalers"):            `{"status": {"currentReplicas": 5}}`,
    49  	gvr("autoscaling", "v2", "horizontalpodautoscalers"):            `{"status": {"currentReplicas": 5}}`,
    50  	gvr("batch", "v1", "cronjobs"):                                  `{"status": {"lastScheduleTime": null}}`,
    51  	gvr("batch", "v1beta1", "cronjobs"):                             `{"status": {"lastScheduleTime": null}}`,
    52  	gvr("storage.k8s.io", "v1", "volumeattachments"):                `{"status": {"attached": true}}`,
    53  	gvr("policy", "v1", "poddisruptionbudgets"):                     `{"status": {"currentHealthy": 5}}`,
    54  	gvr("policy", "v1beta1", "poddisruptionbudgets"):                `{"status": {"currentHealthy": 5}}`,
    55  	gvr("resource.k8s.io", "v1alpha2", "podschedulingcontexts"):     `{"status": {"resourceClaims": [{"name": "my-claim", "unsuitableNodes": ["node1"]}]}}`,
    56  	gvr("resource.k8s.io", "v1alpha2", "resourceclaims"):            `{"status": {"driverName": "example.com"}}`,
    57  	gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
    58  	// standard for []metav1.Condition
    59  	gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,
    60  	gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"):  `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,
    61  	gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicies"):       `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,
    62  }
    63  
    64  const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"True"}]}}`
    65  
    66  func gvr(g, v, r string) schema.GroupVersionResource {
    67  	return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
    68  }
    69  
    70  func createMapping(groupVersion string, resource metav1.APIResource) (*meta.RESTMapping, error) {
    71  	gv, err := schema.ParseGroupVersion(groupVersion)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	if len(resource.Group) > 0 || len(resource.Version) > 0 {
    76  		gv = schema.GroupVersion{
    77  			Group:   resource.Group,
    78  			Version: resource.Version,
    79  		}
    80  	}
    81  	gvk := gv.WithKind(resource.Kind)
    82  	gvr := gv.WithResource(strings.TrimSuffix(resource.Name, "/status"))
    83  	scope := meta.RESTScopeRoot
    84  	if resource.Namespaced {
    85  		scope = meta.RESTScopeNamespace
    86  	}
    87  	return &meta.RESTMapping{
    88  		Resource:         gvr,
    89  		GroupVersionKind: gvk,
    90  		Scope:            scope,
    91  	}, nil
    92  }
    93  
    94  // TestApplyStatus makes sure that applying the status works for all known types.
    95  func TestApplyStatus(t *testing.T) {
    96  	server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), []string{"--disable-admission-plugins", "ServiceAccount,TaintNodesByCondition"}, framework.SharedEtcd())
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  	defer server.TearDownFn()
   101  
   102  	client, err := kubernetes.NewForConfig(server.ClientConfig)
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	dynamicClient, err := dynamic.NewForConfig(server.ClientConfig)
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	// create CRDs so we can make sure that custom resources do not get lost
   112  	etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
   113  	if _, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}, metav1.CreateOptions{}); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	createData := etcd.GetEtcdStorageData()
   118  
   119  	// gather resources to test
   120  	_, resourceLists, err := client.Discovery().ServerGroupsAndResources()
   121  	if err != nil {
   122  		t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
   123  	}
   124  
   125  	for _, resourceList := range resourceLists {
   126  		for _, resource := range resourceList.APIResources {
   127  			if !strings.HasSuffix(resource.Name, "/status") {
   128  				continue
   129  			}
   130  			mapping, err := createMapping(resourceList.GroupVersion, resource)
   131  			if err != nil {
   132  				t.Fatal(err)
   133  			}
   134  			t.Run(mapping.Resource.String(), func(t *testing.T) {
   135  				// both spec and status get wiped for CSRs,
   136  				// nothing is expected to be managed for it, skip it
   137  				if mapping.Resource.Resource == "certificatesigningrequests" {
   138  					t.Skip()
   139  				}
   140  
   141  				status, ok := statusData[mapping.Resource]
   142  				if !ok {
   143  					status = statusDefault
   144  				}
   145  				newResource, ok := createData[mapping.Resource]
   146  				if !ok {
   147  					t.Fatalf("no test data for %s.  Please add a test for your new type to etcd.GetEtcdStorageData().", mapping.Resource)
   148  				}
   149  				newObj := unstructured.Unstructured{}
   150  				if err := json.Unmarshal([]byte(newResource.Stub), &newObj.Object); err != nil {
   151  					t.Fatal(err)
   152  				}
   153  
   154  				namespace := testNamespace
   155  				if mapping.Scope == meta.RESTScopeRoot {
   156  					namespace = ""
   157  				}
   158  				name := newObj.GetName()
   159  
   160  				// etcd test stub data doesn't contain apiVersion/kind (!), but apply requires it
   161  				newObj.SetGroupVersionKind(mapping.GroupVersionKind)
   162  
   163  				rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace)
   164  				// apply to create
   165  				_, err = rsc.Apply(context.TODO(), name, &newObj, metav1.ApplyOptions{FieldManager: "create_test"})
   166  				if err != nil {
   167  					t.Fatal(err)
   168  				}
   169  
   170  				statusObj := unstructured.Unstructured{}
   171  				if err := json.Unmarshal([]byte(status), &statusObj.Object); err != nil {
   172  					t.Fatal(err)
   173  				}
   174  				statusObj.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String())
   175  				statusObj.SetKind(mapping.GroupVersionKind.Kind)
   176  				statusObj.SetName(name)
   177  
   178  				obj, err := dynamicClient.
   179  					Resource(mapping.Resource).
   180  					Namespace(namespace).
   181  					ApplyStatus(context.TODO(), name, &statusObj, metav1.ApplyOptions{FieldManager: "apply_status_test", Force: true})
   182  				if err != nil {
   183  					t.Fatalf("Failed to apply: %v", err)
   184  				}
   185  
   186  				accessor, err := meta.Accessor(obj)
   187  				if err != nil {
   188  					t.Fatalf("Failed to get meta accessor: %v:\n%v", err, obj)
   189  				}
   190  
   191  				managedFields := accessor.GetManagedFields()
   192  				if managedFields == nil {
   193  					t.Fatal("Empty managed fields")
   194  				}
   195  				if !findManager(managedFields, "apply_status_test") {
   196  					t.Fatalf("Couldn't find apply_status_test: %v", managedFields)
   197  				}
   198  				if !findManager(managedFields, "create_test") {
   199  					t.Fatalf("Couldn't find create_test: %v", managedFields)
   200  				}
   201  
   202  				if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil {
   203  					t.Fatalf("deleting final object failed: %v", err)
   204  				}
   205  			})
   206  		}
   207  	}
   208  }
   209  
   210  func findManager(managedFields []metav1.ManagedFieldsEntry, manager string) bool {
   211  	for _, entry := range managedFields {
   212  		if entry.Manager == manager {
   213  			return true
   214  		}
   215  	}
   216  	return false
   217  }
   218  

View as plain text