blob: 05c37451734df1751959e078dce245b35ccaf886 [file] [log] [blame]
// Copyright Istio 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 mock
import (
"fmt"
"reflect"
"strconv"
"testing"
"time"
)
import (
"go.uber.org/atomic"
networking "istio.io/api/networking/v1alpha3"
authz "istio.io/api/security/v1beta1"
api "istio.io/api/type/v1beta1"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
config2 "github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collection"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
"github.com/apache/dubbo-go-pixiu/pkg/test/config"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/retry"
)
var (
// ExampleVirtualService is an example V2 route rule
ExampleVirtualService = &networking.VirtualService{
Hosts: []string{"prod", "test"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "job",
},
Weight: 80,
},
},
},
},
}
ExampleServiceEntry = &networking.ServiceEntry{
Hosts: []string{"*.google.com"},
Resolution: networking.ServiceEntry_NONE,
Ports: []*networking.Port{
{Number: 80, Name: "http-name", Protocol: "http"},
{Number: 8080, Name: "http2-name", Protocol: "http2"},
},
}
ExampleGateway = &networking.Gateway{
Servers: []*networking.Server{
{
Hosts: []string{"google.com"},
Port: &networking.Port{Name: "http", Protocol: "http", Number: 10080},
},
},
}
// ExampleDestinationRule is an example destination rule
ExampleDestinationRule = &networking.DestinationRule{
Host: "ratings",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: new(networking.LoadBalancerSettings_Simple),
},
},
}
// ExampleAuthorizationPolicy is an example AuthorizationPolicy
ExampleAuthorizationPolicy = &authz.AuthorizationPolicy{
Selector: &api.WorkloadSelector{
MatchLabels: map[string]string{
"app": "httpbin",
"version": "v1",
},
},
}
mockGvk = collections.Mock.Resource().GroupVersionKind()
)
// Make creates a mock config indexed by a number
func Make(namespace string, i int) config2.Config {
name := fmt.Sprintf("%s%d", "mock-config", i)
return config2.Config{
Meta: config2.Meta{
GroupVersionKind: mockGvk,
Name: name,
Namespace: namespace,
Labels: map[string]string{
"key": name,
},
Annotations: map[string]string{
"annotationkey": name,
},
},
Spec: &config.MockConfig{
Key: name,
Pairs: []*config.ConfigPair{
{Key: "key", Value: strconv.Itoa(i)},
},
},
}
}
// Compare checks two configs ignoring revisions and creation time
func Compare(a, b config2.Config) bool {
a.ResourceVersion = ""
b.ResourceVersion = ""
a.CreationTimestamp = time.Time{}
b.CreationTimestamp = time.Time{}
return reflect.DeepEqual(a, b)
}
// CheckMapInvariant validates operational invariants of an empty config registry
func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n int) {
// check that the config descriptor is the mock config descriptor
_, contains := r.Schemas().FindByGroupVersionKind(mockGvk)
if !contains {
t.Fatal("expected config mock types")
}
log.Info("Created mock descriptor")
// create configuration objects
elts := make(map[int]config2.Config)
for i := 0; i < n; i++ {
elts[i] = Make(namespace, i)
}
log.Info("Make mock objects")
// post all elements
for _, elt := range elts {
if _, err := r.Create(elt); err != nil {
t.Error(err)
}
}
log.Info("Created mock objects")
revs := make(map[int]string)
// check that elements are stored
for i, elt := range elts {
v1 := r.Get(mockGvk, elt.Name, elt.Namespace)
if v1 == nil || !Compare(elt, *v1) {
t.Errorf("wanted %v, got %v", elt, v1)
} else {
revs[i] = v1.ResourceVersion
}
}
log.Info("Got stored elements")
if _, err := r.Create(elts[0]); err == nil {
t.Error("expected error posting twice")
}
invalid := config2.Config{
Meta: config2.Meta{
GroupVersionKind: mockGvk,
Name: "invalid",
ResourceVersion: revs[0],
},
Spec: &config.MockConfig{},
}
missing := config2.Config{
Meta: config2.Meta{
GroupVersionKind: mockGvk,
Name: "missing",
ResourceVersion: revs[0],
},
Spec: &config.MockConfig{Key: "missing"},
}
if _, err := r.Create(config2.Config{}); err == nil {
t.Error("expected error posting empty object")
}
if _, err := r.Create(invalid); err == nil {
t.Error("expected error posting invalid object")
}
if _, err := r.Update(config2.Config{}); err == nil {
t.Error("expected error updating empty object")
}
if _, err := r.Update(invalid); err == nil {
t.Error("expected error putting invalid object")
}
if _, err := r.Update(missing); err == nil {
t.Error("expected error putting missing object with a missing key")
}
// check for missing type
if l, _ := r.List(config2.GroupVersionKind{}, namespace); len(l) > 0 {
t.Errorf("unexpected objects for missing type")
}
// check for missing element
if cfg := r.Get(mockGvk, "missing", ""); cfg != nil {
t.Error("unexpected configuration object found")
}
// check for missing element
if cfg := r.Get(config2.GroupVersionKind{}, "missing", ""); cfg != nil {
t.Error("unexpected configuration object found")
}
// delete missing elements
if err := r.Delete(config2.GroupVersionKind{}, "missing", "", nil); err == nil {
t.Error("expected error on deletion of missing type")
}
// delete missing elements
if err := r.Delete(mockGvk, "missing", "", nil); err == nil {
t.Error("expected error on deletion of missing element")
}
if err := r.Delete(mockGvk, "missing", "unknown", nil); err == nil {
t.Error("expected error on deletion of missing element in unknown namespace")
}
// list elements
l, err := r.List(mockGvk, namespace)
if err != nil {
t.Errorf("List error %#v, %v", l, err)
}
if len(l) != n {
t.Errorf("wanted %d element(s), got %d in %v", n, len(l), l)
}
// update all elements
for i := 0; i < n; i++ {
elt := Make(namespace, i)
elt.Spec.(*config.MockConfig).Pairs[0].Value += "(updated)"
elt.ResourceVersion = revs[i]
elts[i] = elt
if _, err = r.Update(elt); err != nil {
t.Error(err)
}
}
// check that elements are stored
for i, elt := range elts {
v1 := r.Get(mockGvk, elts[i].Name, elts[i].Namespace)
if v1 == nil || !Compare(elt, *v1) {
t.Errorf("wanted %v, got %v", elt, v1)
}
}
// delete all elements
for i := range elts {
if err = r.Delete(mockGvk, elts[i].Name, elts[i].Namespace, nil); err != nil {
t.Error(err)
}
}
log.Info("Delete elements")
l, err = r.List(mockGvk, namespace)
if err != nil {
t.Error(err)
}
if len(l) != 0 {
t.Errorf("wanted 0 element(s), got %d in %v", len(l), l)
}
log.Info("Test done, deleting namespace")
}
// CheckIstioConfigTypes validates that an empty store can ingest Istio config objects
func CheckIstioConfigTypes(store model.ConfigStore, namespace string, t *testing.T) {
configName := "example"
// Global scoped policies like MeshPolicy are not isolated, can't be
// run as part of the normal test suites - if needed they should
// be run in separate environment. The test suites are setting cluster
// scoped policies that may interfere and would require serialization
cases := []struct {
name string
configName string
schema collection.Schema
spec config2.Spec
}{
{"VirtualService", configName, collections.IstioNetworkingV1Alpha3Virtualservices, ExampleVirtualService},
{"DestinationRule", configName, collections.IstioNetworkingV1Alpha3Destinationrules, ExampleDestinationRule},
{"ServiceEntry", configName, collections.IstioNetworkingV1Alpha3Serviceentries, ExampleServiceEntry},
{"Gateway", configName, collections.IstioNetworkingV1Alpha3Gateways, ExampleGateway},
{"AuthorizationPolicy", configName, collections.IstioSecurityV1Beta1Authorizationpolicies, ExampleAuthorizationPolicy},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
configMeta := config2.Meta{
GroupVersionKind: c.schema.Resource().GroupVersionKind(),
Name: c.configName,
}
if !c.schema.Resource().IsClusterScoped() {
configMeta.Namespace = namespace
}
if _, err := store.Create(config2.Config{
Meta: configMeta,
Spec: c.spec,
}); err != nil {
t.Errorf("Post(%v) => got %v", c.name, err)
}
})
}
}
// CheckCacheEvents validates operational invariants of a cache
func CheckCacheEvents(store model.ConfigStore, cache model.ConfigStoreController, namespace string, n int, t *testing.T) {
n64 := int64(n)
stop := make(chan struct{})
defer close(stop)
added, deleted := atomic.NewInt64(0), atomic.NewInt64(0)
cache.RegisterEventHandler(mockGvk, func(_, _ config2.Config, ev model.Event) {
switch ev {
case model.EventAdd:
if deleted.Load() != 0 {
t.Errorf("Events are not serialized (add)")
}
added.Inc()
case model.EventDelete:
if added.Load() != n64 {
t.Errorf("Events are not serialized (delete)")
}
deleted.Inc()
}
log.Infof("Added %d, deleted %d", added.Load(), deleted.Load())
})
go cache.Run(stop)
// run map invariant sequence
CheckMapInvariant(store, t, namespace, n)
log.Infof("Waiting till all events are received")
retry.UntilOrFail(t, func() bool {
return added.Load() == n64 && deleted.Load() == n64
}, retry.Message("receive events"), retry.Delay(time.Millisecond*500), retry.Timeout(time.Minute))
}
// CheckCacheFreshness validates operational invariants of a cache
func CheckCacheFreshness(cache model.ConfigStoreController, namespace string, t *testing.T) {
stop := make(chan struct{})
done := make(chan bool)
o := Make(namespace, 0)
// validate cache consistency
cache.RegisterEventHandler(mockGvk, func(_, config config2.Config, ev model.Event) {
elts, _ := cache.List(mockGvk, namespace)
elt := cache.Get(o.GroupVersionKind, o.Name, o.Namespace)
switch ev {
case model.EventAdd:
if len(elts) != 1 {
t.Errorf("Got %#v, expected %d element(s) on Add event", elts, 1)
}
if elt == nil || !reflect.DeepEqual(elt.Spec, o.Spec) {
t.Errorf("Got %#v, expected %#v", elt, o)
}
log.Infof("Calling Update(%s)", config.Key())
revised := Make(namespace, 1)
revised.Meta = elt.Meta
if _, err := cache.Update(revised); err != nil {
t.Error(err)
}
case model.EventUpdate:
if len(elts) != 1 {
t.Errorf("Got %#v, expected %d element(s) on Update event", elts, 1)
}
if elt == nil {
t.Errorf("Got %#v, expected nonempty", elt)
}
log.Infof("Calling Delete(%s)", config.Key())
if err := cache.Delete(mockGvk, config.Name, config.Namespace, nil); err != nil {
t.Error(err)
}
case model.EventDelete:
if len(elts) != 0 {
t.Errorf("Got %#v, expected zero elements on Delete event", elts)
}
log.Infof("Stopping channel for (%#v)", config.Key())
close(stop)
done <- true
}
})
go cache.Run(stop)
// try warm-up with empty Get
if cfg := cache.Get(config2.GroupVersionKind{}, "example", namespace); cfg != nil {
t.Error("unexpected result for unknown type")
}
// add and remove
log.Infof("Calling Create(%#v)", o)
if _, err := cache.Create(o); err != nil {
t.Error(err)
}
timeout := time.After(10 * time.Second)
select {
case <-timeout:
t.Fatalf("timeout waiting to be done")
case <-done:
return
}
}
// CheckCacheSync validates operational invariants of a cache against the
// non-cached client.
func CheckCacheSync(store model.ConfigStore, cache model.ConfigStoreController, namespace string, n int, t *testing.T) {
keys := make(map[int]config2.Config)
// add elements directly through client
for i := 0; i < n; i++ {
keys[i] = Make(namespace, i)
if _, err := store.Create(keys[i]); err != nil {
t.Error(err)
}
}
// check in the controller cache
stop := make(chan struct{})
defer close(stop)
go cache.Run(stop)
retry.UntilOrFail(t, cache.HasSynced, retry.Message("HasSynced"))
os, _ := cache.List(mockGvk, namespace)
if len(os) != n {
t.Errorf("cache.List => Got %d, expected %d", len(os), n)
}
// remove elements directly through client
for i := 0; i < n; i++ {
if err := store.Delete(mockGvk, keys[i].Name, keys[i].Namespace, nil); err != nil {
t.Error(err)
}
}
// check again in the controller cache
retry.UntilOrFail(t, func() bool {
os, _ = cache.List(mockGvk, namespace)
log.Infof("cache.List => Got %d, expected %d", len(os), 0)
return len(os) == 0
}, retry.Message("no elements in cache"))
// now add through the controller
for i := 0; i < n; i++ {
if _, err := cache.Create(Make(namespace, i)); err != nil {
t.Error(err)
}
}
// check directly through the client
retry.UntilOrFail(t, func() bool {
cs, _ := cache.List(mockGvk, namespace)
os, _ := store.List(mockGvk, namespace)
log.Infof("cache.List => Got %d, expected %d", len(cs), n)
log.Infof("store.List => Got %d, expected %d", len(os), n)
return len(os) == n && len(cs) == n
}, retry.Message("cache and backing store match"))
// remove elements directly through the client
for i := 0; i < n; i++ {
if err := store.Delete(mockGvk, keys[i].Name, keys[i].Namespace, nil); err != nil {
t.Error(err)
}
}
}