mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Use discovery to test apply all status
This commit is contained in:
parent
4bc907f3c9
commit
94cb60e0e8
@ -7,6 +7,7 @@ go_test(
|
|||||||
"apply_crd_test.go",
|
"apply_crd_test.go",
|
||||||
"apply_test.go",
|
"apply_test.go",
|
||||||
"main_test.go",
|
"main_test.go",
|
||||||
|
"status_test.go",
|
||||||
],
|
],
|
||||||
tags = [
|
tags = [
|
||||||
"etcd",
|
"etcd",
|
||||||
@ -32,6 +33,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
||||||
|
"//test/integration/etcd:go_default_library",
|
||||||
"//test/integration/framework:go_default_library",
|
"//test/integration/framework:go_default_library",
|
||||||
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
],
|
],
|
||||||
|
219
test/integration/apiserver/apply/status_test.go
Normal file
219
test/integration/apiserver/apply/status_test.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
"k8s.io/kubernetes/test/integration/etcd"
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// namespace used for all tests, do not change this
|
||||||
|
const testNamespace = "statusnamespace"
|
||||||
|
|
||||||
|
var statusData = map[schema.GroupVersionResource]string{
|
||||||
|
gvr("", "v1", "persistentvolumes"): `{"status": {"message": "hello"}}`,
|
||||||
|
gvr("", "v1", "resourcequotas"): `{"status": {"used": {"cpu": "5M"}}}`,
|
||||||
|
gvr("", "v1", "services"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
|
||||||
|
gvr("extensions", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
|
||||||
|
gvr("networking.k8s.io", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
|
||||||
|
gvr("autoscaling", "v1", "horizontalpodautoscalers"): `{"status": {"currentReplicas": 5}}`,
|
||||||
|
gvr("batch", "v1beta1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
|
||||||
|
gvr("batch", "v2alpha1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
|
||||||
|
gvr("storage.k8s.io", "v1", "volumeattachments"): `{"status": {"attached": true}}`,
|
||||||
|
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
|
||||||
|
gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus"}]}}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"true"}]}}`
|
||||||
|
|
||||||
|
// DO NOT ADD TO THIS LIST.
|
||||||
|
// This list is used to ignore known bugs. We shouldn't introduce new bugs.
|
||||||
|
var ignoreList = map[schema.GroupVersionResource]struct{}{
|
||||||
|
// TODO(#89264): apiservices doesn't work because the openapi is not routed properly.
|
||||||
|
gvr("apiregistration.k8s.io", "v1beta1", "apiservices"): {},
|
||||||
|
gvr("apiregistration.k8s.io", "v1", "apiservices"): {},
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvr(g, v, r string) schema.GroupVersionResource {
|
||||||
|
return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMapping(groupVersion string, resource metav1.APIResource) (*meta.RESTMapping, error) {
|
||||||
|
gv, err := schema.ParseGroupVersion(groupVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resource.Group) > 0 || len(resource.Version) > 0 {
|
||||||
|
gv = schema.GroupVersion{
|
||||||
|
Group: resource.Group,
|
||||||
|
Version: resource.Version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gvk := gv.WithKind(resource.Kind)
|
||||||
|
gvr := gv.WithResource(strings.TrimSuffix(resource.Name, "/status"))
|
||||||
|
scope := meta.RESTScopeRoot
|
||||||
|
if resource.Namespaced {
|
||||||
|
scope = meta.RESTScopeNamespace
|
||||||
|
}
|
||||||
|
return &meta.RESTMapping{
|
||||||
|
Resource: gvr,
|
||||||
|
GroupVersionKind: gvk,
|
||||||
|
Scope: scope,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestApplyStatus makes sure that applying the status works for all known types.
|
||||||
|
func TestApplyStatus(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||||
|
server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), []string{"--disable-admission-plugins", "ServiceAccount,TaintNodesByCondition"}, framework.SharedEtcd())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer server.TearDownFn()
|
||||||
|
|
||||||
|
client, err := kubernetes.NewForConfig(server.ClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
dynamicClient, err := dynamic.NewForConfig(server.ClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create CRDs so we can make sure that custom resources do not get lost
|
||||||
|
etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
|
||||||
|
if _, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createData := etcd.GetEtcdStorageData()
|
||||||
|
|
||||||
|
// gather resources to test
|
||||||
|
_, resourceLists, err := client.Discovery().ServerGroupsAndResources()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, resourceList := range resourceLists {
|
||||||
|
for _, resource := range resourceList.APIResources {
|
||||||
|
if !strings.HasSuffix(resource.Name, "/status") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mapping, err := createMapping(resourceList.GroupVersion, resource)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Run(mapping.Resource.String(), func(t *testing.T) {
|
||||||
|
if _, ok := ignoreList[mapping.Resource]; ok {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
status, ok := statusData[mapping.Resource]
|
||||||
|
if !ok {
|
||||||
|
status = statusDefault
|
||||||
|
}
|
||||||
|
newResource, ok := createData[mapping.Resource]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("no test data for %s. Please add a test for your new type to etcd.GetEtcdStorageData().", mapping.Resource)
|
||||||
|
}
|
||||||
|
newObj := unstructured.Unstructured{}
|
||||||
|
if err := json.Unmarshal([]byte(newResource.Stub), &newObj.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := testNamespace
|
||||||
|
if mapping.Scope == meta.RESTScopeRoot {
|
||||||
|
namespace = ""
|
||||||
|
}
|
||||||
|
name := newObj.GetName()
|
||||||
|
rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace)
|
||||||
|
_, err := rsc.Create(context.TODO(), &newObj, metav1.CreateOptions{FieldManager: "create_test"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusObj := unstructured.Unstructured{}
|
||||||
|
if err := json.Unmarshal([]byte(status), &statusObj.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
statusObj.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String())
|
||||||
|
statusObj.SetKind(mapping.GroupVersionKind.Kind)
|
||||||
|
statusObj.SetName(name)
|
||||||
|
statusYAML, err := yaml.Marshal(statusObj.Object)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
True := true
|
||||||
|
obj, err := dynamicClient.
|
||||||
|
Resource(mapping.Resource).
|
||||||
|
Namespace(namespace).
|
||||||
|
Patch(context.TODO(), name, types.ApplyPatchType, statusYAML, metav1.PatchOptions{FieldManager: "apply_status_test", Force: &True}, "status")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to apply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get meta accessor: %v:\n%v", err, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
managedFields := accessor.GetManagedFields()
|
||||||
|
if managedFields == nil {
|
||||||
|
t.Fatal("Empty managed fields")
|
||||||
|
}
|
||||||
|
if !findManager(managedFields, "apply_status_test") {
|
||||||
|
t.Fatalf("Couldn't find apply_status_test: %v", managedFields)
|
||||||
|
}
|
||||||
|
if !findManager(managedFields, "create_test") {
|
||||||
|
t.Fatalf("Couldn't find create_test: %v", managedFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil {
|
||||||
|
t.Fatalf("deleting final object failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findManager(managedFields []metav1.ManagedFieldsEntry, manager string) bool {
|
||||||
|
for _, entry := range managedFields {
|
||||||
|
if entry.Manager == manager {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user