mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Ensure defaulting applies to custom resource items in list response
This commit is contained in:
		@@ -235,6 +235,8 @@ func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc f
 | 
				
			|||||||
		TypeMeta:   metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
 | 
							TypeMeta:   metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
							ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						// our decoder defaults, so we should default our expected object as well
 | 
				
			||||||
 | 
						legacyscheme.Scheme.Default(obj)
 | 
				
			||||||
	buf.Reset()
 | 
						buf.Reset()
 | 
				
			||||||
	printer.PrintObj(obj, buf)
 | 
						printer.PrintObj(obj, buf)
 | 
				
			||||||
	var objOut v1.Pod
 | 
						var objOut v1.Pod
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ limitations under the License.
 | 
				
			|||||||
package integration
 | 
					package integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -29,6 +30,7 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/json"
 | 
						"k8s.io/apimachinery/pkg/util/json"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/watch"
 | 
				
			||||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	utilfeaturetesting "k8s.io/component-base/featuregate/testing"
 | 
						utilfeaturetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
	"k8s.io/utils/pointer"
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
@@ -43,6 +45,18 @@ var defaultingFixture = &apiextensionsv1beta1.CustomResourceDefinition{
 | 
				
			|||||||
	Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
 | 
						Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
 | 
				
			||||||
		Group:   "tests.apiextensions.k8s.io",
 | 
							Group:   "tests.apiextensions.k8s.io",
 | 
				
			||||||
		Version: "v1beta1",
 | 
							Version: "v1beta1",
 | 
				
			||||||
 | 
							Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:    "v1beta1",
 | 
				
			||||||
 | 
									Storage: false,
 | 
				
			||||||
 | 
									Served:  true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:    "v1beta2",
 | 
				
			||||||
 | 
									Storage: true,
 | 
				
			||||||
 | 
									Served:  false,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
 | 
							Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
 | 
				
			||||||
			Plural:   "foos",
 | 
								Plural:   "foos",
 | 
				
			||||||
			Singular: "foo",
 | 
								Singular: "foo",
 | 
				
			||||||
@@ -57,7 +71,7 @@ var defaultingFixture = &apiextensionsv1beta1.CustomResourceDefinition{
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultingFooSchema = `
 | 
					const defaultingFooV1beta1Schema = `
 | 
				
			||||||
type: object
 | 
					type: object
 | 
				
			||||||
properties:
 | 
					properties:
 | 
				
			||||||
  spec:
 | 
					  spec:
 | 
				
			||||||
@@ -69,6 +83,13 @@ properties:
 | 
				
			|||||||
      b:
 | 
					      b:
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
        default: "B"
 | 
					        default: "B"
 | 
				
			||||||
 | 
					      c:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      v1beta1:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "v1beta1"
 | 
				
			||||||
 | 
					      v1beta2:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
  status:
 | 
					  status:
 | 
				
			||||||
    type: object
 | 
					    type: object
 | 
				
			||||||
    properties:
 | 
					    properties:
 | 
				
			||||||
@@ -78,20 +99,76 @@ properties:
 | 
				
			|||||||
      b:
 | 
					      b:
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
        default: "B"
 | 
					        default: "B"
 | 
				
			||||||
 | 
					      c:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      v1beta1:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "v1beta1"
 | 
				
			||||||
 | 
					      v1beta2:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCustomResourceDefaulting(t *testing.T) {
 | 
					const defaultingFooV1beta2Schema = `
 | 
				
			||||||
 | 
					type: object
 | 
				
			||||||
 | 
					properties:
 | 
				
			||||||
 | 
					  spec:
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      a:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "A"
 | 
				
			||||||
 | 
					      b:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "B"
 | 
				
			||||||
 | 
					      c:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      v1beta1:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      v1beta2:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "v1beta2"
 | 
				
			||||||
 | 
					  status:
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      a:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "A"
 | 
				
			||||||
 | 
					      b:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "B"
 | 
				
			||||||
 | 
					      c:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      v1beta1:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      v1beta2:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "v1beta2"
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCustomResourceDefaultingWithWatchCache(t *testing.T) {
 | 
				
			||||||
 | 
						testDefaulting(t, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCustomResourceDefaultingWithoutWatchCache(t *testing.T) {
 | 
				
			||||||
 | 
						testDefaulting(t, false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func testDefaulting(t *testing.T, watchCache bool) {
 | 
				
			||||||
	defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceDefaulting, true)()
 | 
						defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceDefaulting, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
 | 
						tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t, fmt.Sprintf("--watch-cache=%v", watchCache))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer tearDownFn()
 | 
						defer tearDownFn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	crd := defaultingFixture.DeepCopy()
 | 
						crd := defaultingFixture.DeepCopy()
 | 
				
			||||||
	crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
 | 
						crd.Spec.Versions[0].Schema = &apiextensionsv1beta1.CustomResourceValidation{}
 | 
				
			||||||
	if err := yaml.Unmarshal([]byte(defaultingFooSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
 | 
						if err := yaml.Unmarshal([]byte(defaultingFooV1beta1Schema), &crd.Spec.Versions[0].Schema.OpenAPIV3Schema); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						crd.Spec.Versions[1].Schema = &apiextensionsv1beta1.CustomResourceValidation{}
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal([]byte(defaultingFooV1beta2Schema), &crd.Spec.Versions[1].Schema.OpenAPIV3Schema); err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -101,13 +178,15 @@ func TestCustomResourceDefaulting(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mustExist := func(obj map[string]interface{}, pths [][]string) {
 | 
						mustExist := func(obj map[string]interface{}, pths [][]string) {
 | 
				
			||||||
 | 
							t.Helper()
 | 
				
			||||||
		for _, pth := range pths {
 | 
							for _, pth := range pths {
 | 
				
			||||||
			if _, found, _ := unstructured.NestedFieldNoCopy(obj, pth...); !found {
 | 
								if _, found, _ := unstructured.NestedFieldNoCopy(obj, pth...); !found {
 | 
				
			||||||
				t.Errorf("Expected '%s' field exist", strings.Join(pth, "."))
 | 
									t.Errorf("Expected '%s' field was missing", strings.Join(pth, "."))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mustNotExist := func(obj map[string]interface{}, pths [][]string) {
 | 
						mustNotExist := func(obj map[string]interface{}, pths [][]string) {
 | 
				
			||||||
 | 
							t.Helper()
 | 
				
			||||||
		for _, pth := range pths {
 | 
							for _, pth := range pths {
 | 
				
			||||||
			if fld, found, _ := unstructured.NestedFieldNoCopy(obj, pth...); found {
 | 
								if fld, found, _ := unstructured.NestedFieldNoCopy(obj, pth...); found {
 | 
				
			||||||
				t.Errorf("Expected '%s' field to not exist, but it does: %v", strings.Join(pth, "."), fld)
 | 
									t.Errorf("Expected '%s' field to not exist, but it does: %v", strings.Join(pth, "."), fld)
 | 
				
			||||||
@@ -115,6 +194,7 @@ func TestCustomResourceDefaulting(t *testing.T) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	updateCRD := func(update func(*apiextensionsv1beta1.CustomResourceDefinition)) {
 | 
						updateCRD := func(update func(*apiextensionsv1beta1.CustomResourceDefinition)) {
 | 
				
			||||||
 | 
							t.Helper()
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
		for retry := 0; retry < 10; retry++ {
 | 
							for retry := 0; retry < 10; retry++ {
 | 
				
			||||||
			var obj *apiextensionsv1beta1.CustomResourceDefinition
 | 
								var obj *apiextensionsv1beta1.CustomResourceDefinition
 | 
				
			||||||
@@ -136,22 +216,34 @@ func TestCustomResourceDefaulting(t *testing.T) {
 | 
				
			|||||||
			t.Fatal(err)
 | 
								t.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	addDefault := func(key string, value interface{}) {
 | 
						addDefault := func(version string, key string, value interface{}) {
 | 
				
			||||||
 | 
							t.Helper()
 | 
				
			||||||
		updateCRD(func(obj *apiextensionsv1beta1.CustomResourceDefinition) {
 | 
							updateCRD(func(obj *apiextensionsv1beta1.CustomResourceDefinition) {
 | 
				
			||||||
			for _, root := range []string{"spec", "status"} {
 | 
								for _, root := range []string{"spec", "status"} {
 | 
				
			||||||
				obj.Spec.Validation.OpenAPIV3Schema.Properties[root].Properties[key] = apiextensionsv1beta1.JSONSchemaProps{
 | 
									for i := range obj.Spec.Versions {
 | 
				
			||||||
 | 
										if obj.Spec.Versions[i].Name != version {
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										obj.Spec.Versions[i].Schema.OpenAPIV3Schema.Properties[root].Properties[key] = apiextensionsv1beta1.JSONSchemaProps{
 | 
				
			||||||
						Type:    "string",
 | 
											Type:    "string",
 | 
				
			||||||
						Default: jsonPtr(value),
 | 
											Default: jsonPtr(value),
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	removeDefault := func(key string) {
 | 
						removeDefault := func(version string, key string) {
 | 
				
			||||||
 | 
							t.Helper()
 | 
				
			||||||
		updateCRD(func(obj *apiextensionsv1beta1.CustomResourceDefinition) {
 | 
							updateCRD(func(obj *apiextensionsv1beta1.CustomResourceDefinition) {
 | 
				
			||||||
			for _, root := range []string{"spec", "status"} {
 | 
								for _, root := range []string{"spec", "status"} {
 | 
				
			||||||
				props := obj.Spec.Validation.OpenAPIV3Schema.Properties[root].Properties[key]
 | 
									for i := range obj.Spec.Versions {
 | 
				
			||||||
 | 
										if obj.Spec.Versions[i].Name != version {
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										props := obj.Spec.Versions[i].Schema.OpenAPIV3Schema.Properties[root].Properties[key]
 | 
				
			||||||
					props.Default = nil
 | 
										props.Default = nil
 | 
				
			||||||
				obj.Spec.Validation.OpenAPIV3Schema.Properties[root].Properties[key] = props
 | 
										obj.Spec.Versions[i].Schema.OpenAPIV3Schema.Properties[root].Properties[key] = props
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -168,8 +260,12 @@ func TestCustomResourceDefaulting(t *testing.T) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Unable to create CR: %v", err)
 | 
							t.Fatalf("Unable to create CR: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						initialResourceVersion := foo.GetResourceVersion()
 | 
				
			||||||
	t.Logf("CR created: %#v", foo.UnstructuredContent())
 | 
						t.Logf("CR created: %#v", foo.UnstructuredContent())
 | 
				
			||||||
	mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}})
 | 
						// spec.a and spec.b are defaulted in both versions
 | 
				
			||||||
 | 
						// spec.v1beta1 is defaulted when reading the incoming request
 | 
				
			||||||
 | 
						// spec.v1beta2 is defaulted when reading the storage response
 | 
				
			||||||
 | 
						mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "v1beta1"}, {"spec", "v1beta2"}})
 | 
				
			||||||
	mustNotExist(foo.Object, [][]string{{"status"}})
 | 
						mustNotExist(foo.Object, [][]string{{"status"}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Logf("Updating status and expecting 'a' and 'b' to show up.")
 | 
						t.Logf("Updating status and expecting 'a' and 'b' to show up.")
 | 
				
			||||||
@@ -179,8 +275,90 @@ func TestCustomResourceDefaulting(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"status", "a"}, {"status", "b"}})
 | 
						mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"status", "a"}, {"status", "b"}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Logf("Add 'c' default and wait until GET sees it in both status and spec")
 | 
						t.Logf("Add 'c' default to the storage version and wait until GET sees it in both status and spec")
 | 
				
			||||||
	addDefault("c", "C")
 | 
						addDefault("v1beta2", "c", "C")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Logf("wait until GET sees 'c' in both status and spec")
 | 
				
			||||||
 | 
						if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
 | 
				
			||||||
 | 
							obj, err := fooClient.Get(foo.GetName(), metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, found, _ := unstructured.NestedString(obj.Object, "spec", "c"); !found {
 | 
				
			||||||
 | 
								t.Log("will retry, did not find spec.c in the object")
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							foo = obj
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Logf("wait until GET sees 'c' in both status and spec of cached get")
 | 
				
			||||||
 | 
						if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
 | 
				
			||||||
 | 
							obj, err := fooClient.Get(foo.GetName(), metav1.GetOptions{ResourceVersion: "0"})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, found, _ := unstructured.NestedString(obj.Object, "spec", "c"); !found {
 | 
				
			||||||
 | 
								t.Logf("will retry, did not find spec.c in the cached object")
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							foo = obj
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Logf("verify LIST sees 'c' in both status and spec")
 | 
				
			||||||
 | 
						foos, err := fooClient.List(metav1.ListOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, foo := range foos.Items {
 | 
				
			||||||
 | 
							mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Logf("verify LIST from cache sees 'c' in both status and spec")
 | 
				
			||||||
 | 
						foos, err = fooClient.List(metav1.ListOptions{ResourceVersion: "0"})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, foo := range foos.Items {
 | 
				
			||||||
 | 
							mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Omit this test when using the watch cache because changing the CRD resets the watch cache's minimum available resource version.
 | 
				
			||||||
 | 
						// The watch cache is populated by list and watch, which are both tested by this test.
 | 
				
			||||||
 | 
						// The contents of the watch cache are seen by list with rv=0, which is tested by this test.
 | 
				
			||||||
 | 
						if !watchCache {
 | 
				
			||||||
 | 
							t.Logf("verify WATCH sees 'c' in both status and spec")
 | 
				
			||||||
 | 
							w, err := fooClient.Watch(metav1.ListOptions{ResourceVersion: initialResourceVersion})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case event := <-w.ResultChan():
 | 
				
			||||||
 | 
								if event.Type != watch.Modified {
 | 
				
			||||||
 | 
									t.Fatalf("unexpected watch event: %v, %#v", event.Type, event.Object)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if e, a := "v1beta1", event.Object.GetObjectKind().GroupVersionKind().Version; e != a {
 | 
				
			||||||
 | 
									t.Errorf("watch event for v1beta1 API returned %v", a)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								mustExist(
 | 
				
			||||||
 | 
									event.Object.(*unstructured.Unstructured).Object,
 | 
				
			||||||
 | 
									[][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							case <-time.After(wait.ForeverTestTimeout):
 | 
				
			||||||
 | 
								t.Fatal("timed out without getting watch event")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Logf("Add 'c' default to the REST version, remove it from the storage version, and wait until GET no longer sees it in both status and spec")
 | 
				
			||||||
 | 
						addDefault("v1beta1", "c", "C")
 | 
				
			||||||
 | 
						removeDefault("v1beta2", "c")
 | 
				
			||||||
	if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
 | 
						if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
 | 
				
			||||||
		obj, err := fooClient.Get(foo.GetName(), metav1.GetOptions{})
 | 
							obj, err := fooClient.Get(foo.GetName(), metav1.GetOptions{})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -188,22 +366,24 @@ func TestCustomResourceDefaulting(t *testing.T) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		_, found, _ := unstructured.NestedString(obj.Object, "spec", "c")
 | 
							_, found, _ := unstructured.NestedString(obj.Object, "spec", "c")
 | 
				
			||||||
		foo = obj
 | 
							foo = obj
 | 
				
			||||||
		return found, nil
 | 
							return !found, nil
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
						mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"status", "a"}, {"status", "b"}})
 | 
				
			||||||
 | 
						mustNotExist(foo.Object, [][]string{{"spec", "c"}, {"status", "c"}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Logf("Updating status, expecting 'c' to be set in spec and status")
 | 
						t.Logf("Updating status, expecting 'c' to be set in status only")
 | 
				
			||||||
	if foo, err = fooClient.UpdateStatus(foo, metav1.UpdateOptions{}); err != nil {
 | 
						if foo, err = fooClient.UpdateStatus(foo, metav1.UpdateOptions{}); err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"spec", "c"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
						mustExist(foo.Object, [][]string{{"spec", "a"}, {"spec", "b"}, {"status", "a"}, {"status", "b"}, {"status", "c"}})
 | 
				
			||||||
 | 
						mustNotExist(foo.Object, [][]string{{"spec", "c"}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Logf("Removing 'a', 'b' and `c` properties. Expecting that 'c' goes away in spec, but not in status. 'a' and 'b' were peristed.")
 | 
						t.Logf("Removing 'a', 'b' and `c` properties from the REST version. Expecting that 'c' goes away in spec, but not in status. 'a' and 'b' were presisted.")
 | 
				
			||||||
	removeDefault("a")
 | 
						removeDefault("v1beta1", "a")
 | 
				
			||||||
	removeDefault("b")
 | 
						removeDefault("v1beta1", "b")
 | 
				
			||||||
	removeDefault("c")
 | 
						removeDefault("v1beta1", "c")
 | 
				
			||||||
	if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
 | 
						if err := wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
 | 
				
			||||||
		obj, err := fooClient.Get(foo.GetName(), metav1.GetOptions{})
 | 
							obj, err := fooClient.Get(foo.GetName(), metav1.GetOptions{})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,13 +113,6 @@ func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into ru
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// if we specify a target, use generic conversion.
 | 
						// if we specify a target, use generic conversion.
 | 
				
			||||||
	if into != nil {
 | 
						if into != nil {
 | 
				
			||||||
		if into == obj {
 | 
					 | 
				
			||||||
			if isVersioned {
 | 
					 | 
				
			||||||
				return versioned, gvk, nil
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			return into, gvk, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// perform defaulting if requested
 | 
							// perform defaulting if requested
 | 
				
			||||||
		if c.defaulter != nil {
 | 
							if c.defaulter != nil {
 | 
				
			||||||
			// create a copy to ensure defaulting is not applied to the original versioned objects
 | 
								// create a copy to ensure defaulting is not applied to the original versioned objects
 | 
				
			||||||
@@ -133,6 +126,14 @@ func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into ru
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Short-circuit conversion if the into object is same object
 | 
				
			||||||
 | 
							if into == obj {
 | 
				
			||||||
 | 
								if isVersioned {
 | 
				
			||||||
 | 
									return versioned, gvk, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return into, gvk, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil {
 | 
							if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil {
 | 
				
			||||||
			return nil, gvk, err
 | 
								return nil, gvk, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user