blob: c6c2f6c8d564a2dbbc639ff4976530e1a008687f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 crdclient
import (
"context"
"fmt"
"reflect"
"testing"
"time"
"github.com/apache/dubbo-kubernetes/pkg/core/kubeclient/client"
"github.com/apache/dubbo-kubernetes/pkg/core/model"
"github.com/apache/dubbo-kubernetes/pkg/core/schema/collection"
"github.com/apache/dubbo-kubernetes/pkg/core/schema/collections"
"github.com/apache/dubbo-kubernetes/test/util/retry"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
)
func makeClient(t *testing.T, schemas collection.Schemas) ConfigStoreCache {
fake := client.NewFakeClient()
for _, s := range schemas.All() {
_, err := fake.Ext().ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), &v1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s.%s", s.Resource().Plural(), s.Resource().Group()),
},
}, metav1.CreateOptions{})
if err != nil {
return nil
}
}
stop := make(chan struct{})
config, err := New(fake, "")
if err != nil {
t.Fatal(err)
}
go func() {
err := config.Start(stop)
if err != nil {
t.Error(err)
}
}()
_ = fake.Start(stop)
cache.WaitForCacheSync(stop, config.HasSynced)
t.Cleanup(func() {
close(stop)
})
return config
}
// Ensure that the client can run without CRDs present
func TestClientNoCRDs(t *testing.T) {
schema := collection.NewSchemasBuilder().MustAdd(collections.DubboApacheOrgV1Alpha1ServiceNameMapping).Build()
store := makeClient(t, schema)
retry.UntilOrFail(t, store.HasSynced, retry.Timeout(time.Second))
r := collections.DubboApacheOrgV1Alpha1AuthorizationPolicy.Resource()
configMeta := model.Meta{
Name: "name",
Namespace: "ns",
GroupVersionKind: r.GroupVersionKind(),
}
pb, err := r.NewInstance()
if err != nil {
t.Fatal(err)
}
if _, err := store.Create(model.Config{
Meta: configMeta,
Spec: pb,
}); err != nil {
t.Fatalf("Create => got %v", err)
}
retry.UntilSuccessOrFail(t, func() error {
l, err := store.List(r.GroupVersionKind(), configMeta.Namespace)
// List should actually not return an error in this case; this allows running with missing CRDs
// Instead, we just return an empty list.
if err != nil {
return fmt.Errorf("expected no error, but got %v", err)
}
if len(l) != 0 {
return fmt.Errorf("expected no items returned for unknown CRD")
}
return nil
}, retry.Timeout(time.Second*5), retry.Converge(5))
retry.UntilOrFail(t, func() bool {
return store.Get(r.GroupVersionKind(), configMeta.Namespace, configMeta.Namespace) == nil
}, retry.Message("expected no items returned for unknown CRD"), retry.Timeout(time.Second*5), retry.Converge(5))
}
// CheckDubboConfigTypes validates that an empty store can do CRUD operators on all given types
func TestClient(t *testing.T) {
store := makeClient(t, collections.Rule)
configName := "name"
configNamespace := "namespace"
timeout := retry.Timeout(time.Millisecond * 200)
for _, c := range collections.Rule.All() {
name := c.Resource().Kind()
t.Run(name, func(t *testing.T) {
r := c.Resource()
configMeta := model.Meta{
GroupVersionKind: r.GroupVersionKind(),
Name: configName,
}
if !r.IsClusterScoped() {
configMeta.Namespace = configNamespace
}
pb, err := r.NewInstance()
if err != nil {
t.Fatal(err)
}
if _, err := store.Create(model.Config{
Meta: configMeta,
Spec: pb,
}); err != nil {
t.Fatalf("Create(%v) => got %v", name, err)
}
// Kubernetes is eventually consistent, so we allow a short time to pass before we get
retry.UntilSuccessOrFail(t, func() error {
cfg := store.Get(r.GroupVersionKind(), configName, configMeta.Namespace)
if cfg == nil || !reflect.DeepEqual(cfg.Meta, configMeta) {
return fmt.Errorf("get(%v) => got unexpected object %v", name, cfg)
}
return nil
}, timeout)
// Validate it shows up in list
retry.UntilSuccessOrFail(t, func() error {
cfgs, err := store.List(r.GroupVersionKind(), configNamespace)
if err != nil {
return err
}
if len(cfgs) != 1 {
return fmt.Errorf("expected 1 config, got %v", len(cfgs))
}
for _, cfg := range cfgs {
if !reflect.DeepEqual(cfg.Meta, configMeta) {
return fmt.Errorf("get(%v) => got %v", name, cfg)
}
}
return nil
}, timeout)
// check we can update object metadata
annotations := map[string]string{
"foo": "bar",
}
configMeta.Annotations = annotations
if _, err := store.Update(model.Config{
Meta: configMeta,
Spec: pb,
}); err != nil {
t.Errorf("Unexpected Error in Update -> %v", err)
}
var cfg *model.Config
// validate it is updated
retry.UntilSuccessOrFail(t, func() error {
cfg = store.Get(r.GroupVersionKind(), configName, configMeta.Namespace)
if cfg == nil || !reflect.DeepEqual(cfg.Meta, configMeta) {
return fmt.Errorf("get(%v) => got unexpected object %v", name, cfg)
}
return nil
})
// check we can remove items
if err := store.Delete(r.GroupVersionKind(), configName, configNamespace, nil); err != nil {
t.Fatalf("failed to delete: %v", err)
}
retry.UntilSuccessOrFail(t, func() error {
cfg := store.Get(r.GroupVersionKind(), configName, configNamespace)
if cfg != nil {
return fmt.Errorf("get(%v) => got %v, expected item to be deleted", name, cfg)
}
return nil
}, timeout)
})
}
}