blob: 61f0ddd3b130eab15cc5f13afe2058715aeed6df [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 model_test
import (
"fmt"
"reflect"
"strconv"
"testing"
)
import (
"github.com/davecgh/go-spew/spew"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
mock_config "github.com/apache/dubbo-go-pixiu/pilot/test/mock"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collection"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/resource"
)
// getByMessageName finds a schema by message name if it is available
// In test setup, we do not have more than one descriptor with the same message type, so this
// function is ok for testing purpose.
func getByMessageName(schemas collection.Schemas, name string) (collection.Schema, bool) {
for _, s := range schemas.All() {
if s.Resource().Proto() == name {
return s, true
}
}
return nil, false
}
func schemaFor(kind, proto string) collection.Schema {
return collection.Builder{
Name: kind,
Resource: resource.Builder{
Kind: kind,
Plural: kind + "s",
Proto: proto,
}.BuildNoValidate(),
}.MustBuild()
}
func TestConfigDescriptor(t *testing.T) {
a := schemaFor("a", "proxy.A")
schemas := collection.SchemasFor(
a,
schemaFor("b", "proxy.B"),
schemaFor("c", "proxy.C"))
want := []string{"a", "b", "c"}
got := schemas.Kinds()
if !reflect.DeepEqual(got, want) {
t.Errorf("descriptor.Types() => got %+vwant %+v", spew.Sdump(got), spew.Sdump(want))
}
aType, aExists := schemas.FindByGroupVersionKind(a.Resource().GroupVersionKind())
if !aExists || !reflect.DeepEqual(aType, a) {
t.Errorf("descriptor.GetByType(a) => got %+v, want %+v", aType, a)
}
if _, exists := schemas.FindByGroupVersionKind(config.GroupVersionKind{Kind: "missing"}); exists {
t.Error("descriptor.GetByType(missing) => got true, want false")
}
aSchema, aSchemaExists := getByMessageName(schemas, a.Resource().Proto())
if !aSchemaExists || !reflect.DeepEqual(aSchema, a) {
t.Errorf("descriptor.GetByMessageName(a) => got %+v, want %+v", aType, a)
}
_, aSchemaNotExist := getByMessageName(schemas, "blah")
if aSchemaNotExist {
t.Errorf("descriptor.GetByMessageName(blah) => got true, want false")
}
}
func TestEventString(t *testing.T) {
cases := []struct {
in model.Event
want string
}{
{model.EventAdd, "add"},
{model.EventUpdate, "update"},
{model.EventDelete, "delete"},
}
for _, c := range cases {
if got := c.in.String(); got != c.want {
t.Errorf("Failed: got %q want %q", got, c.want)
}
}
}
func TestPortList(t *testing.T) {
pl := model.PortList{
{Name: "http", Port: 80, Protocol: protocol.HTTP},
{Name: "http-alt", Port: 8080, Protocol: protocol.HTTP},
}
gotNames := pl.GetNames()
wantNames := []string{"http", "http-alt"}
if !reflect.DeepEqual(gotNames, wantNames) {
t.Errorf("GetNames() failed: got %v want %v", gotNames, wantNames)
}
cases := []struct {
name string
port *model.Port
found bool
}{
{name: pl[0].Name, port: pl[0], found: true},
{name: "foobar", found: false},
}
for _, c := range cases {
gotPort, gotFound := pl.Get(c.name)
if c.found != gotFound || !reflect.DeepEqual(gotPort, c.port) {
t.Errorf("Get() failed: gotFound=%v wantFound=%v\ngot %+vwant %+v",
gotFound, c.found, spew.Sdump(gotPort), spew.Sdump(c.port))
}
}
}
func TestSubsetKey(t *testing.T) {
hostname := host.Name("hostname")
cases := []struct {
hostname host.Name
subset string
port int
want string
}{
{
hostname: "hostname",
subset: "subset",
port: 80,
want: "outbound|80|subset|hostname",
},
{
hostname: "hostname",
subset: "",
port: 80,
want: "outbound|80||hostname",
},
}
for _, c := range cases {
got := model.BuildSubsetKey(model.TrafficDirectionOutbound, c.subset, hostname, c.port)
if got != c.want {
t.Errorf("Failed: got %q want %q", got, c.want)
}
// test parse subset key. ParseSubsetKey is the inverse of BuildSubsetKey
_, s, h, p := model.ParseSubsetKey(got)
if s != c.subset || h != c.hostname || p != c.port {
t.Errorf("Failed: got %s,%s,%d want %s,%s,%d", s, h, p, c.subset, c.hostname, c.port)
}
}
}
func TestLabelsEquals(t *testing.T) {
cases := []struct {
a, b labels.Instance
want bool
}{
{
a: nil,
b: nil,
want: true,
},
{
a: nil,
b: labels.Instance{"a": "b"},
},
{
a: labels.Instance{"a": "b"},
b: nil,
},
{
a: labels.Instance{"a": "b"},
b: labels.Instance{"a": "b"},
want: true,
},
{
a: labels.Instance{"a": "b"},
b: labels.Instance{"a": "b", "c": "d"},
},
{
b: labels.Instance{"a": "b", "c": "d"},
a: labels.Instance{"a": "b"},
},
{
b: labels.Instance{"a": "b", "c": "d"},
a: labels.Instance{"a": "b", "c": "d"},
want: true,
},
}
for _, c := range cases {
if got := c.a.Equals(c.b); got != c.want {
t.Errorf("Failed: got eq=%v want=%v for %q ?= %q", got, c.want, c.a, c.b)
}
}
}
func TestConfigKey(t *testing.T) {
cfg := mock_config.Make("ns", 2)
want := "test.istio.io/v1/MockConfig/ns/mock-config2"
if key := cfg.Meta.Key(); key != want {
t.Fatalf("config.Key() => got %q, want %q", key, want)
}
}
func TestResolveShortnameToFQDN(t *testing.T) {
tests := []struct {
name string
meta config.Meta
out host.Name
}{
{
"*", config.Meta{}, "*",
},
{
"*", config.Meta{Namespace: "default", Domain: "cluster.local"}, "*",
},
{
"foo", config.Meta{Namespace: "default", Domain: "cluster.local"}, "foo.default.svc.cluster.local",
},
{
"foo.bar", config.Meta{Namespace: "default", Domain: "cluster.local"}, "foo.bar",
},
{
"foo", config.Meta{Domain: "cluster.local"}, "foo.svc.cluster.local",
},
{
"foo", config.Meta{Namespace: "default"}, "foo.default",
},
{
"42.185.131.210", config.Meta{Namespace: "default"}, "42.185.131.210",
},
{
"42.185.131.210", config.Meta{Namespace: "cluster.local"}, "42.185.131.210",
},
{
"2a00:4000::614", config.Meta{Namespace: "default"}, "2a00:4000::614",
},
{
"2a00:4000::614", config.Meta{Namespace: "cluster.local"}, "2a00:4000::614",
},
}
for idx, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", idx, tt.out), func(t *testing.T) {
if actual := model.ResolveShortnameToFQDN(tt.name, tt.meta); actual != tt.out {
t.Fatalf("model.ResolveShortnameToFQDN(%q, %v) = %q wanted %q", tt.name, tt.meta, actual, tt.out)
}
})
}
}
func TestMostSpecificHostMatch(t *testing.T) {
tests := []struct {
in []host.Name
needle host.Name
want host.Name
}{
// this has to be a sorted list
{[]host.Name{}, "*", ""},
{[]host.Name{"*.foo.com", "*.com"}, "bar.foo.com", "*.foo.com"},
{[]host.Name{"*.foo.com", "*.com"}, "foo.com", "*.com"},
{[]host.Name{"foo.com", "*.com"}, "*.foo.com", "*.com"},
{[]host.Name{"*.foo.com", "foo.com"}, "foo.com", "foo.com"},
{[]host.Name{"*.foo.com", "foo.com"}, "*.foo.com", "*.foo.com"},
// this passes because we sort alphabetically
{[]host.Name{"bar.com", "foo.com"}, "*.com", ""},
{[]host.Name{"bar.com", "*.foo.com"}, "*foo.com", ""},
{[]host.Name{"foo.com", "*.foo.com"}, "*foo.com", ""},
// should prioritize closest match
{[]host.Name{"*.bar.com", "foo.bar.com"}, "foo.bar.com", "foo.bar.com"},
{[]host.Name{"*.foo.bar.com", "bar.foo.bar.com"}, "bar.foo.bar.com", "bar.foo.bar.com"},
// should not match non-wildcards for wildcard needle
{[]host.Name{"bar.foo.com", "foo.bar.com"}, "*.foo.com", ""},
{[]host.Name{"foo.bar.foo.com", "bar.foo.bar.com"}, "*.bar.foo.com", ""},
}
for idx, tt := range tests {
m := make(map[host.Name]struct{})
for _, h := range tt.in {
m[h] = struct{}{}
}
t.Run(fmt.Sprintf("[%d] %s", idx, tt.needle), func(t *testing.T) {
actual, found := model.MostSpecificHostMatch2(tt.needle, m)
if tt.want != "" && !found {
t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %t; want: %v", tt.needle, tt.in, actual, found, tt.want)
} else if actual != tt.want {
t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %t; want: %v", tt.needle, tt.in, actual, found, tt.want)
}
})
}
}
func BenchmarkMostSpecificHostMatch(b *testing.B) {
benchmarks := []struct {
name string
needle host.Name
baseHost string
hosts []host.Name
hostsMap map[host.Name]struct{}
time int
}{
{"10Exact", host.Name("foo.bar.com.10"), "foo.bar.com", []host.Name{}, nil, 10},
{"50Exact", host.Name("foo.bar.com.50"), "foo.bar.com", []host.Name{}, nil, 50},
{"100Exact", host.Name("foo.bar.com.100"), "foo.bar.com", []host.Name{}, nil, 100},
{"1000Exact", host.Name("foo.bar.com.1000"), "foo.bar.com", []host.Name{}, nil, 1000},
{"5000Exact", host.Name("foo.bar.com.5000"), "foo.bar.com", []host.Name{}, nil, 5000},
{"10DestRuleWildcard", host.Name("foo.bar.com.10"), "*.foo.bar.com", []host.Name{}, nil, 10},
{"50DestRuleWildcard", host.Name("foo.bar.com.50"), "*.foo.bar.com", []host.Name{}, nil, 50},
{"100DestRuleWildcard", host.Name("foo.bar.com.100"), "*.foo.bar.com", []host.Name{}, nil, 100},
{"1000DestRuleWildcard", host.Name("foo.bar.com.1000"), "*.foo.bar.com", []host.Name{}, nil, 1000},
{"5000DestRuleWildcard", host.Name("foo.bar.com.5000"), "*.foo.bar.com", []host.Name{}, nil, 5000},
{"10NeedleWildcard", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, 10},
{"50NeedleWildcard", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, 50},
{"100NeedleWildcard", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, 100},
{"1000NeedleWildcard", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, 1000},
{"5000NeedleWildcard", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, 5000},
}
for _, bm := range benchmarks {
bm.hostsMap = make(map[host.Name]struct{}, bm.time)
for i := 1; i <= bm.time; i++ {
h := host.Name(bm.baseHost + "." + strconv.Itoa(i))
bm.hostsMap[h] = struct{}{}
}
b.Run(bm.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_, _ = model.MostSpecificHostMatch2(bm.needle, bm.hostsMap)
}
})
}
}
func TestConfigsOnlyHaveKind(t *testing.T) {
tests := []struct {
name string
configs map[model.ConfigKey]struct{}
want bool
}{
{
name: "mix",
configs: map[model.ConfigKey]struct{}{
{Kind: gvk.Deployment}: {},
{Kind: gvk.Secret}: {},
},
want: true,
},
{
name: "no secret",
configs: map[model.ConfigKey]struct{}{
{Kind: gvk.Deployment}: {},
},
want: false,
},
{
name: "only secret",
configs: map[model.ConfigKey]struct{}{
{Kind: gvk.Secret}: {},
{Kind: gvk.Secret}: {},
},
want: true,
},
{
name: "empty",
configs: map[model.ConfigKey]struct{}{},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := model.ConfigsHaveKind(tt.configs, gvk.Secret)
if tt.want != got {
t.Errorf("got %v want %v", got, tt.want)
}
})
}
}