switch condition controller check to use a direct etcd write

This commit is contained in:
David Eads 2021-02-25 13:19:32 -05:00
parent 734e9b7dd6
commit 3dbc08288f
2 changed files with 93 additions and 48 deletions

View File

@ -20,9 +20,15 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/pkg/transport"
"google.golang.org/grpc"
"k8s.io/apiextensions-apiserver/pkg/cmd/server/options" "k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
serveroptions "k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
servertesting "k8s.io/apiextensions-apiserver/pkg/cmd/server/testing" servertesting "k8s.io/apiextensions-apiserver/pkg/cmd/server/testing"
@ -103,6 +109,55 @@ func StartDefaultServerWithClients(t servertesting.Logger, extraFlags ...string)
return tearDown, apiExtensionsClient, dynamicClient, nil return tearDown, apiExtensionsClient, dynamicClient, nil
} }
// StartDefaultServerWithClientsAndEtcd starts a test server and returns clients for it.
func StartDefaultServerWithClientsAndEtcd(t servertesting.Logger, extraFlags ...string) (func(), clientset.Interface, dynamic.Interface, *clientv3.Client, string, error) {
tearDown, config, options, err := StartDefaultServer(t, extraFlags...)
if err != nil {
return nil, nil, nil, nil, "", err
}
apiExtensionsClient, err := clientset.NewForConfig(config)
if err != nil {
tearDown()
return nil, nil, nil, nil, "", err
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
tearDown()
return nil, nil, nil, nil, "", err
}
RESTOptionsGetter := serveroptions.NewCRDRESTOptionsGetter(*options.RecommendedOptions.Etcd)
restOptions, err := RESTOptionsGetter.GetRESTOptions(schema.GroupResource{Group: "hopefully-ignored-group", Resource: "hopefully-ignored-resources"})
if err != nil {
return nil, nil, nil, nil, "", err
}
tlsInfo := transport.TLSInfo{
CertFile: restOptions.StorageConfig.Transport.CertFile,
KeyFile: restOptions.StorageConfig.Transport.KeyFile,
TrustedCAFile: restOptions.StorageConfig.Transport.TrustedCAFile,
}
tlsConfig, err := tlsInfo.ClientConfig()
if err != nil {
return nil, nil, nil, nil, "", err
}
etcdConfig := clientv3.Config{
Endpoints: restOptions.StorageConfig.Transport.ServerList,
DialTimeout: 20 * time.Second,
DialOptions: []grpc.DialOption{
grpc.WithBlock(), // block until the underlying connection is up
},
TLS: tlsConfig,
}
etcdclient, err := clientv3.New(etcdConfig)
if err != nil {
return nil, nil, nil, nil, "", err
}
return tearDown, apiExtensionsClient, dynamicClient, etcdclient, restOptions.StorageConfig.Prefix, nil
}
// IntegrationEtcdServers returns etcd server URLs. // IntegrationEtcdServers returns etcd server URLs.
func IntegrationEtcdServers() []string { func IntegrationEtcdServers() []string {
if etcdURL, ok := os.LookupEnv("KUBE_INTEGRATION_ETCD_URL"); ok { if etcdURL, ok := os.LookupEnv("KUBE_INTEGRATION_ETCD_URL"); ok {

View File

@ -19,6 +19,7 @@ package integration
import ( import (
"context" "context"
"fmt" "fmt"
"path"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -27,11 +28,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/util/json"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
clientschema "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" clientschema "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
"k8s.io/apiextensions-apiserver/test/integration/fixtures" "k8s.io/apiextensions-apiserver/test/integration/fixtures"
) )
@ -698,8 +701,7 @@ func TestForbiddenFieldsInSchema(t *testing.T) {
} }
func TestNonStructuralSchemaConditionUpdate(t *testing.T) { func TestNonStructuralSchemaConditionUpdate(t *testing.T) {
t.Skip("non-structural schemas can no longer be created") tearDown, apiExtensionClient, _, etcdclient, etcdStoragePrefix, err := fixtures.StartDefaultServerWithClientsAndEtcd(t)
tearDown, apiExtensionClient, _, err := fixtures.StartDefaultServerWithClients(t)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -735,16 +737,26 @@ spec:
if err != nil { if err != nil {
t.Fatalf("failed decoding of: %v\n\n%s", err, manifest) t.Fatalf("failed decoding of: %v\n\n%s", err, manifest)
} }
crd := obj.(*apiextensionsv1.CustomResourceDefinition) betaCRD := obj.(*apiextensionsv1beta1.CustomResourceDefinition)
name := crd.Name name := betaCRD.Name
// save schema for later // save schema for later
origSchema := crd.Spec.Versions[0].Schema.OpenAPIV3Schema origSchema := &apiextensionsv1.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextensionsv1.JSONSchemaProps{
"a": apiextensionsv1.JSONSchemaProps{
Type: "object",
},
},
}
// create CRDs // create CRDs. We cannot create these in v1, but they can exist in upgraded clusters
t.Logf("Creating CRD %s", crd.Name) t.Logf("Creating CRD %s", betaCRD.Name)
if _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{}); err != nil { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceNone)
t.Fatalf("unexpected create error: %v", err) key := path.Join("/", etcdStoragePrefix, "apiextensions.k8s.io", "customresourcedefinitions/foos.tests.example.com")
val, _ := json.Marshal(betaCRD)
if _, err := etcdclient.Put(ctx, key, string(val)); err != nil {
t.Fatalf("unexpected error: %v", err)
} }
// wait for condition with violations // wait for condition with violations
@ -768,25 +780,23 @@ spec:
t.Fatalf("expected violation %q, but got: %v", v, cond.Message) t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
} }
// remove schema t.Log("fix schema")
t.Log("Remove schema")
for retry := 0; retry < 5; retry++ { for retry := 0; retry < 5; retry++ {
// This patch fixes two fields to resolve crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), name, metav1.GetOptions{})
// 1. property type validation error if err != nil {
// 2. preserveUnknownFields validation error t.Fatal(err)
patch := []byte("[{\"op\":\"add\",\"path\":\"/spec/validation/openAPIV3Schema/properties/a/type\",\"value\":\"int\"}," + }
"{\"op\":\"replace\",\"path\":\"/spec/preserveUnknownFields\",\"value\":false}]") crd.Spec.Versions[0].Schema = fixtures.AllowAllSchema()
if _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(context.TODO(), name, types.JSONPatchType, patch, metav1.PatchOptions{}); apierrors.IsConflict(err) { crd.Spec.PreserveUnknownFields = false
_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
if apierrors.IsConflict(err) {
continue continue
} }
if err != nil { if err != nil {
t.Fatalf("unexpected update error: %v", err) t.Fatal(err)
} }
break break
} }
if err != nil {
t.Fatalf("unexpected update error: %v", err)
}
// wait for condition to go away // wait for condition to go away
t.Log("Wait for condition to disappear") t.Log("Wait for condition to disappear")
@ -805,7 +815,7 @@ spec:
// re-add schema // re-add schema
t.Log("Re-add schema") t.Log("Re-add schema")
for retry := 0; retry < 5; retry++ { for retry := 0; retry < 5; retry++ {
crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), name, metav1.GetOptions{}) crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), name, metav1.GetOptions{})
if err != nil { if err != nil {
t.Fatalf("unexpected get error: %v", err) t.Fatalf("unexpected get error: %v", err)
} }
@ -814,34 +824,14 @@ spec:
if _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{}); apierrors.IsConflict(err) { if _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{}); apierrors.IsConflict(err) {
continue continue
} }
if err != nil { if err == nil {
t.Fatalf("unexpected update error: %v", err) t.Fatalf("missing error")
}
if !strings.Contains(err.Error(), "spec.preserveUnknownFields") {
t.Fatal(err)
} }
break break
} }
if err != nil {
t.Fatalf("unexpected update error: %v", err)
}
// wait for condition with violations
t.Log("Wait for condition to reappear")
err = wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) {
obj, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return false, err
}
cond = findCRDCondition(obj, apiextensionsv1.NonStructuralSchema)
return cond != nil, nil
})
if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
}
if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
}
if v := "spec.preserveUnknownFields: Invalid value: true: must be false"; !strings.Contains(cond.Message, v) {
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
}
} }
func TestNonStructuralSchemaCondition(t *testing.T) { func TestNonStructuralSchemaCondition(t *testing.T) {