mirror of https://github.com/k3s-io/k3s
277 lines
9.4 KiB
Go
277 lines
9.4 KiB
Go
/*
|
|
Copyright 2018 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 dryrun
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
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"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/apiserver/pkg/features"
|
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
|
|
"k8s.io/client-go/dynamic"
|
|
"k8s.io/kubernetes/test/integration/etcd"
|
|
)
|
|
|
|
// Only add kinds to this list when this a virtual resource with get and create verbs that doesn't actually
|
|
// store into it's kind. We've used this downstream for mappings before.
|
|
var kindWhiteList = sets.NewString()
|
|
|
|
// namespace used for all tests, do not change this
|
|
const testNamespace = "dryrunnamespace"
|
|
|
|
func DryRunCreateTest(t *testing.T, rsc dynamic.ResourceInterface, obj *unstructured.Unstructured, gvResource schema.GroupVersionResource) {
|
|
createdObj, err := rsc.Create(obj, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}})
|
|
if err != nil {
|
|
t.Fatalf("failed to dry-run create stub for %s: %#v", gvResource, err)
|
|
}
|
|
if obj.GroupVersionKind() != createdObj.GroupVersionKind() {
|
|
t.Fatalf("created object doesn't have the same gvk as original object: got %v, expected %v",
|
|
createdObj.GroupVersionKind(),
|
|
obj.GroupVersionKind())
|
|
}
|
|
|
|
if _, err := rsc.Get(obj.GetName(), metav1.GetOptions{}); !errors.IsNotFound(err) {
|
|
t.Fatalf("object shouldn't exist: %v", err)
|
|
}
|
|
}
|
|
|
|
func DryRunPatchTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
|
|
patch := []byte(`{"metadata":{"annotations":{"patch": "true"}}}`)
|
|
obj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}})
|
|
if err != nil {
|
|
t.Fatalf("failed to dry-run patch object: %v", err)
|
|
}
|
|
if v := obj.GetAnnotations()["patch"]; v != "true" {
|
|
t.Fatalf("dry-run patched annotations should be returned, got: %v", obj.GetAnnotations())
|
|
}
|
|
obj, err = rsc.Get(obj.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to get object: %v", err)
|
|
}
|
|
if v := obj.GetAnnotations()["patch"]; v == "true" {
|
|
t.Fatalf("dry-run patched annotations should not be persisted, got: %v", obj.GetAnnotations())
|
|
}
|
|
}
|
|
|
|
func getReplicasOrFail(t *testing.T, obj *unstructured.Unstructured) int64 {
|
|
t.Helper()
|
|
replicas, found, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "replicas")
|
|
if err != nil {
|
|
t.Fatalf("failed to get int64 for replicas: %v", err)
|
|
}
|
|
if !found {
|
|
t.Fatal("object doesn't have spec.replicas")
|
|
}
|
|
return replicas
|
|
}
|
|
|
|
func DryRunScalePatchTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
|
|
obj, err := rsc.Get(name, metav1.GetOptions{}, "scale")
|
|
if errors.IsNotFound(err) {
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed to get object: %v", err)
|
|
}
|
|
|
|
replicas := getReplicasOrFail(t, obj)
|
|
patch := []byte(`{"spec":{"replicas":10}}`)
|
|
patchedObj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}}, "scale")
|
|
if err != nil {
|
|
t.Fatalf("failed to dry-run patch object: %v", err)
|
|
}
|
|
if newReplicas := getReplicasOrFail(t, patchedObj); newReplicas != 10 {
|
|
t.Fatalf("dry-run patch to replicas didn't return new value: %v", newReplicas)
|
|
}
|
|
persistedObj, err := rsc.Get(name, metav1.GetOptions{}, "scale")
|
|
if err != nil {
|
|
t.Fatalf("failed to get scale sub-resource")
|
|
}
|
|
if newReplicas := getReplicasOrFail(t, persistedObj); newReplicas != replicas {
|
|
t.Fatalf("number of replicas changed, expected %v, got %v", replicas, newReplicas)
|
|
}
|
|
}
|
|
|
|
func DryRunScaleUpdateTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
|
|
obj, err := rsc.Get(name, metav1.GetOptions{}, "scale")
|
|
if errors.IsNotFound(err) {
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed to get object: %v", err)
|
|
}
|
|
|
|
replicas := getReplicasOrFail(t, obj)
|
|
if err := unstructured.SetNestedField(obj.Object, int64(10), "spec", "replicas"); err != nil {
|
|
t.Fatalf("failed to set spec.replicas: %v", err)
|
|
}
|
|
updatedObj, err := rsc.Update(obj, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}}, "scale")
|
|
if err != nil {
|
|
t.Fatalf("failed to dry-run update scale sub-resource: %v", err)
|
|
}
|
|
if newReplicas := getReplicasOrFail(t, updatedObj); newReplicas != 10 {
|
|
t.Fatalf("dry-run update to replicas didn't return new value: %v", newReplicas)
|
|
}
|
|
persistedObj, err := rsc.Get(name, metav1.GetOptions{}, "scale")
|
|
if err != nil {
|
|
t.Fatalf("failed to get scale sub-resource")
|
|
}
|
|
if newReplicas := getReplicasOrFail(t, persistedObj); newReplicas != replicas {
|
|
t.Fatalf("number of replicas changed, expected %v, got %v", replicas, newReplicas)
|
|
}
|
|
}
|
|
|
|
func DryRunUpdateTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
|
|
var err error
|
|
var obj *unstructured.Unstructured
|
|
for i := 0; i < 3; i++ {
|
|
obj, err = rsc.Get(name, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to retrieve object: %v", err)
|
|
}
|
|
obj.SetAnnotations(map[string]string{"update": "true"})
|
|
obj, err = rsc.Update(obj, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}})
|
|
if err == nil || !errors.IsConflict(err) {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed to dry-run update resource: %v", err)
|
|
}
|
|
if v := obj.GetAnnotations()["update"]; v != "true" {
|
|
t.Fatalf("dry-run updated annotations should be returned, got: %v", obj.GetAnnotations())
|
|
}
|
|
|
|
obj, err = rsc.Get(obj.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to get object: %v", err)
|
|
}
|
|
if v := obj.GetAnnotations()["update"]; v == "true" {
|
|
t.Fatalf("dry-run updated annotations should not be persisted, got: %v", obj.GetAnnotations())
|
|
}
|
|
}
|
|
|
|
func DryRunDeleteCollectionTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
|
|
err := rsc.DeleteCollection(&metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}}, metav1.ListOptions{})
|
|
if err != nil {
|
|
t.Fatalf("dry-run delete collection failed: %v", err)
|
|
}
|
|
obj, err := rsc.Get(name, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to get object: %v", err)
|
|
}
|
|
ts := obj.GetDeletionTimestamp()
|
|
if ts != nil {
|
|
t.Fatalf("object has a deletion timestamp after dry-run delete collection")
|
|
}
|
|
}
|
|
|
|
func DryRunDeleteTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
|
|
err := rsc.Delete(name, &metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}})
|
|
if err != nil {
|
|
t.Fatalf("dry-run delete failed: %v", err)
|
|
}
|
|
obj, err := rsc.Get(name, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("failed to get object: %v", err)
|
|
}
|
|
ts := obj.GetDeletionTimestamp()
|
|
if ts != nil {
|
|
t.Fatalf("object has a deletion timestamp after dry-run delete")
|
|
}
|
|
}
|
|
|
|
// TestDryRun tests dry-run on all types.
|
|
func TestDryRun(t *testing.T) {
|
|
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DryRun, true)()
|
|
|
|
master := etcd.StartRealMasterOrDie(t)
|
|
defer master.Cleanup()
|
|
|
|
if _, err := master.Client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dryrunData := etcd.GetEtcdStorageData()
|
|
|
|
// dry run specific stub overrides
|
|
for resource, stub := range map[schema.GroupVersionResource]string{
|
|
// need to change event's namespace field to match dry run test
|
|
gvr("", "v1", "events"): `{"involvedObject": {"namespace": "dryrunnamespace"}, "message": "some data here", "metadata": {"name": "event1"}}`,
|
|
} {
|
|
data := dryrunData[resource]
|
|
data.Stub = stub
|
|
dryrunData[resource] = data
|
|
}
|
|
|
|
for _, resourceToTest := range master.Resources {
|
|
t.Run(resourceToTest.Mapping.Resource.String(), func(t *testing.T) {
|
|
mapping := resourceToTest.Mapping
|
|
gvk := resourceToTest.Mapping.GroupVersionKind
|
|
gvResource := resourceToTest.Mapping.Resource
|
|
kind := gvk.Kind
|
|
|
|
if kindWhiteList.Has(kind) {
|
|
t.Skip("whitelisted")
|
|
}
|
|
|
|
testData, hasTest := dryrunData[gvResource]
|
|
|
|
if !hasTest {
|
|
t.Fatalf("no test data for %s. Please add a test for your new type to etcd.GetEtcdStorageData().", gvResource)
|
|
}
|
|
|
|
rsc, obj, err := etcd.JSONToUnstructured(testData.Stub, testNamespace, mapping, master.Dynamic)
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal stub (%v): %v", testData.Stub, err)
|
|
}
|
|
|
|
name := obj.GetName()
|
|
|
|
DryRunCreateTest(t, rsc, obj, gvResource)
|
|
|
|
if _, err := rsc.Create(obj, metav1.CreateOptions{}); err != nil {
|
|
t.Fatalf("failed to create stub for %s: %#v", gvResource, err)
|
|
}
|
|
|
|
DryRunUpdateTest(t, rsc, name)
|
|
DryRunPatchTest(t, rsc, name)
|
|
DryRunScalePatchTest(t, rsc, name)
|
|
DryRunScaleUpdateTest(t, rsc, name)
|
|
if resourceToTest.HasDeleteCollection {
|
|
DryRunDeleteCollectionTest(t, rsc, name)
|
|
}
|
|
DryRunDeleteTest(t, rsc, name)
|
|
|
|
if err = rsc.Delete(obj.GetName(), metav1.NewDeleteOptions(0)); err != nil {
|
|
t.Fatalf("deleting final object failed: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func gvr(g, v, r string) schema.GroupVersionResource {
|
|
return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
|
|
}
|