Merge pull request #63587 from sttts/sttts-crd-test-conflict-errs

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

apiextensions: handle CRD conflict errs in integration tests

In the integration tests we assume that no other party modifies CRDs while the test is updating them repeatedly. Due to semantic changes for CRD conditions (https://github.com/kubernetes/kubernetes/pull/63068) and the introduction of `status.storedVersions` (https://github.com/kubernetes/kubernetes/pull/63518), this assumption will not hold true in the future. This PR prepares the test to handle conflict errors gracefully.
This commit is contained in:
Kubernetes Submit Queue 2018-05-10 03:28:54 -07:00 committed by GitHub
commit e63f25902c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 58 deletions

View File

@ -22,7 +22,6 @@ import (
"sort" "sort"
"strings" "strings"
"testing" "testing"
"time"
autoscaling "k8s.io/api/autoscaling/v1" autoscaling "k8s.io/api/autoscaling/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -30,7 +29,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
@ -396,9 +394,8 @@ func TestValidateOnlyStatus(t *testing.T) {
// UpdateStatus should validate only status // UpdateStatus should validate only status
// 1. create a crd with max value of .spec.num = 10 and .status.num = 10 // 1. create a crd with max value of .spec.num = 10 and .status.num = 10
// 2. create a cr with .spec.num = 10 and .status.num = 10 (valid) // 2. create a cr with .spec.num = 10 and .status.num = 10 (valid)
// 3. update the crd so that max value of .spec.num = 5 and .status.num = 10 // 3. update the spec of the cr with .spec.num = 15 (spec is invalid), expect no error
// 4. update the status of the cr with .status.num = 5 (spec is invalid) // 4. update the spec of the cr with .spec.num = 15 (spec is invalid), expect error
// validation passes becauses spec is not validated
// max value of spec.num = 10 and status.num = 10 // max value of spec.num = 10 and status.num = 10
schema := &apiextensionsv1beta1.JSONSchemaProps{ schema := &apiextensionsv1beta1.JSONSchemaProps{
@ -447,58 +444,31 @@ func TestValidateOnlyStatus(t *testing.T) {
t.Fatalf("unable to create noxu instance: %v", err) t.Fatalf("unable to create noxu instance: %v", err)
} }
gottenCRD, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get("noxus.mygroup.example.com", metav1.GetOptions{}) // update the spec with .spec.num = 15, expecting no error
err = unstructured.SetNestedField(createdNoxuInstance.Object, int64(15), "spec", "num")
if err != nil { if err != nil {
t.Fatal(err) t.Fatalf("unexpected error setting .spec.num: %v", err)
}
createdNoxuInstance, err = noxuStatusResourceClient.Update(createdNoxuInstance)
if err != nil {
t.Errorf("unexpected error: %v", err)
} }
// update the crd so that max value of spec.num = 5 and status.num = 10 // update with .status.num = 15, expecting an error
gottenCRD.Spec.Validation.OpenAPIV3Schema = &apiextensionsv1beta1.JSONSchemaProps{ err = unstructured.SetNestedField(createdNoxuInstance.Object, int64(15), "status", "num")
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
"spec": {
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
"num": {
Type: "integer",
Maximum: float64Ptr(5),
},
},
},
"status": {
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
"num": {
Type: "integer",
Maximum: float64Ptr(10),
},
},
},
},
}
if _, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(gottenCRD); err != nil {
t.Fatal(err)
}
// update the status with .status.num = 5
err = unstructured.SetNestedField(createdNoxuInstance.Object, int64(5), "status", "num")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error setting .status.num: %v", err)
} }
createdNoxuInstance, err = noxuStatusResourceClient.Update(createdNoxuInstance)
// cr is updated even though spec is invalid if err == nil {
err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { t.Fatal("expected error, but got none")
_, err := noxuStatusResourceClient.Update(createdNoxuInstance)
if statusError, isStatus := err.(*apierrors.StatusError); isStatus {
if strings.Contains(statusError.Error(), "is invalid") {
return false, nil
} }
statusError, isStatus := err.(*apierrors.StatusError)
if !isStatus || statusError == nil {
t.Fatalf("expected status error, got %T: %v", err, err)
} }
if err != nil { if !strings.Contains(statusError.Error(), "Invalid value") {
return false, err t.Fatalf("expected 'Invalid value' in error, got: %v", err)
}
return true, nil
})
if err != nil {
t.Fatal(err)
} }
} }

View File

@ -275,6 +275,25 @@ func checkForWatchCachePrimed(crd *apiextensionsv1beta1.CustomResourceDefinition
} }
} }
// UpdateCustomResourceDefinition updates a CRD, retrying up to 5 times on version conflict errors.
func UpdateCustomResourceDefinition(client clientset.Interface, name string, update func(*apiextensionsv1beta1.CustomResourceDefinition)) (*apiextensionsv1beta1.CustomResourceDefinition, error) {
for i := 0; i < 5; i++ {
crd, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get CustomResourceDefinition %q: %v", name, err)
}
update(crd)
crd, err = client.ApiextensionsV1beta1().CustomResourceDefinitions().Update(crd)
if err == nil {
return crd, nil
}
if !errors.IsConflict(err) {
return nil, fmt.Errorf("failed to update CustomResourceDefinition %q: %v", name, err)
}
}
return nil, fmt.Errorf("too many retries after conflicts updating CustomResourceDefinition %q", name)
}
func DeleteCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) error { func DeleteCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) error {
if err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Delete(crd.Name, nil); err != nil { if err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Delete(crd.Name, nil); err != nil {
return err return err

View File

@ -348,14 +348,11 @@ func TestCRValidationOnCRDUpdate(t *testing.T) {
t.Fatalf("unexpected non-error: CR should be rejected") t.Fatalf("unexpected non-error: CR should be rejected")
} }
gottenCRD, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get("noxus.mygroup.example.com", metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
// update the CRD to a less stricter schema // update the CRD to a less stricter schema
gottenCRD.Spec.Validation.OpenAPIV3Schema.Required = []string{"alpha", "beta"} _, err = testserver.UpdateCustomResourceDefinition(apiExtensionClient, "noxus.mygroup.example.com", func(crd *apiextensionsv1beta1.CustomResourceDefinition) {
if _, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(gottenCRD); err != nil { crd.Spec.Validation.OpenAPIV3Schema.Required = []string{"alpha", "beta"}
})
if err != nil {
t.Fatal(err) t.Fatal(err)
} }