linkerd2/controller/api/destination/watcher/profile_watcher_test.go

160 lines
3.9 KiB
Go

package watcher
import (
"testing"
"k8s.io/client-go/tools/cache"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/controller/k8s"
logging "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var testServiceProfile = sp.ServiceProfile{
ObjectMeta: metav1.ObjectMeta{
Name: "foobar.ns.svc.cluster.local",
Namespace: "linkerd",
},
Spec: sp.ServiceProfileSpec{
Routes: []*sp.RouteSpec{
{
Condition: &sp.RequestMatch{
PathRegex: "/x/y/z",
},
ResponseClasses: []*sp.ResponseClass{
{
Condition: &sp.ResponseMatch{
Status: &sp.Range{
Min: 500,
},
},
IsFailure: true,
},
},
},
},
},
}
var testServiceProfileResource = `
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: foobar.ns.svc.cluster.local
namespace: linkerd
spec:
routes:
- condition:
pathRegex: "/x/y/z"
responseClasses:
- condition:
status:
min: 500
isFailure: true`
func TestProfileWatcherUpdates(t *testing.T) {
for _, tt := range []struct {
name string
k8sConfigs []string
id ProfileID
expectedProfiles []*sp.ServiceProfileSpec
}{
{
name: "service profile",
k8sConfigs: []string{testServiceProfileResource},
id: ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
expectedProfiles: []*sp.ServiceProfileSpec{
&testServiceProfile.Spec,
},
},
{
name: "service without profile",
k8sConfigs: []string{},
id: ProfileID{Name: "foobar.ns.svc.cluster.local", Namespace: "ns"},
expectedProfiles: []*sp.ServiceProfileSpec{
nil,
},
},
} {
tt := tt // pin
t.Run(tt.name, func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
watcher, err := NewProfileWatcher(k8sAPI, logging.WithField("test", t.Name()))
if err != nil {
t.Fatalf("can't create profile watcher: %s", err)
}
k8sAPI.Sync(nil)
listener := NewBufferingProfileListener()
watcher.Subscribe(tt.id, listener)
actualProfiles := make([]*sp.ServiceProfileSpec, 0)
listener.mu.RLock()
defer listener.mu.RUnlock()
for _, profile := range listener.Profiles {
if profile == nil {
actualProfiles = append(actualProfiles, nil)
} else {
actualProfiles = append(actualProfiles, &profile.Spec)
}
}
testCompare(t, tt.expectedProfiles, actualProfiles)
})
}
}
func TestProfileWatcherDeletes(t *testing.T) {
for _, tt := range []struct {
name string
k8sConfigs []string
id ProfileID
objectToDelete interface{}
}{
{
name: "can delete service profiles",
k8sConfigs: []string{testServiceProfileResource},
id: ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
objectToDelete: &testServiceProfile,
},
{
name: "can delete service profiles wrapped in a DeletedFinalStateUnknown",
k8sConfigs: []string{testServiceProfileResource},
id: ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
objectToDelete: cache.DeletedFinalStateUnknown{Obj: &testServiceProfile},
},
} {
tt := tt // pin
t.Run(tt.name, func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
watcher, err := NewProfileWatcher(k8sAPI, logging.WithField("test", t.Name()))
if err != nil {
t.Fatalf("can't create profile watcher: %s", err)
}
k8sAPI.Sync(nil)
listener := NewDeletingProfileListener()
watcher.Subscribe(tt.id, listener)
watcher.deleteProfile(tt.objectToDelete)
if listener.NumDeletes != 1 {
t.Fatalf("Expected to get 1 deletes but got %v", listener.NumDeletes)
}
})
}
}