Files
kubesphere/pkg/controller/destinationrule/destinationrule_controller_test.go
zryfish ea88c8803d use istio client-go library instead of knative (#1661)
use istio client-go library instead of knative
bump kubernetes dependency version
change code coverage to codecov
2019-12-13 11:26:18 +08:00

364 lines
11 KiB
Go

package destinationrule
import (
"fmt"
apiv1alpha3 "istio.io/api/networking/v1alpha3"
"istio.io/client-go/pkg/apis/networking/v1alpha3"
istiofake "istio.io/client-go/pkg/clientset/versioned/fake"
istioinformers "istio.io/client-go/pkg/informers/externalversions"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubeinformers "k8s.io/client-go/informers"
kubefake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/controller/virtualservice/util"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
"testing"
)
var (
alwaysReady = func() bool { return true }
replicas = int32(2)
)
func newDeployments(service *corev1.Service, version string) *appsv1.Deployment {
lbs := service.Labels
lbs["version"] = version
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%s", service.Name, version),
Namespace: metav1.NamespaceDefault,
Labels: lbs,
Annotations: service.Annotations,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: lbs,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: lbs,
Annotations: service.Annotations,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "c1",
Image: "nginx:latest",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 80,
Protocol: corev1.ProtocolTCP,
},
{
Name: "https",
ContainerPort: 443,
Protocol: corev1.ProtocolTCP,
},
{
Name: "mysql",
ContainerPort: 3306,
Protocol: corev1.ProtocolTCP,
},
},
},
},
},
},
},
Status: appsv1.DeploymentStatus{
AvailableReplicas: replicas,
ReadyReplicas: replicas,
Replicas: replicas,
},
}
return deployment
}
func newDestinationRule(service *corev1.Service, deployments ...*appsv1.Deployment) *v1alpha3.DestinationRule {
dr := &v1alpha3.DestinationRule{
ObjectMeta: metav1.ObjectMeta{
Name: service.Name,
Namespace: service.Namespace,
Labels: service.Labels,
Annotations: make(map[string]string),
},
Spec: apiv1alpha3.DestinationRule{
Host: service.Name,
},
}
dr.Spec.Subsets = []*apiv1alpha3.Subset{}
for _, deployment := range deployments {
subset := &apiv1alpha3.Subset{
Name: util.GetComponentVersion(&deployment.ObjectMeta),
Labels: map[string]string{
"version": util.GetComponentVersion(&deployment.ObjectMeta),
},
}
dr.Spec.Subsets = append(dr.Spec.Subsets, subset)
}
return dr
}
func newService(name string) *corev1.Service {
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{
"app.kubernetes.io/name": "bookinfo",
"app.kubernetes.io/version": "1",
"app": "foo",
},
Annotations: map[string]string{
"servicemesh.kubesphere.io/enabled": "true",
},
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
Protocol: corev1.ProtocolTCP,
},
{
Name: "https",
Port: 443,
Protocol: corev1.ProtocolTCP,
},
{
Name: "mysql",
Port: 3306,
Protocol: corev1.ProtocolTCP,
},
},
Selector: map[string]string{
"app.kubernetes.io/name": "bookinfo",
"app.kubernetes.io/version": "1",
"app": "foo",
},
Type: corev1.ServiceTypeClusterIP,
},
Status: corev1.ServiceStatus{},
}
return service
}
func newServicePolicy(name string, service *corev1.Service, deployments ...*appsv1.Deployment) *v1alpha2.ServicePolicy {
sp := &v1alpha2.ServicePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
Labels: service.Labels,
Annotations: service.Annotations,
},
Spec: v1alpha2.ServicePolicySpec{
Template: v1alpha2.DestinationRuleSpecTemplate{
Spec: apiv1alpha3.DestinationRule{
Host: service.Name,
},
},
},
}
sp.Spec.Template.Spec.Subsets = []*apiv1alpha3.Subset{}
for _, deployment := range deployments {
subset := &apiv1alpha3.Subset{
Name: util.GetComponentVersion(&deployment.ObjectMeta),
Labels: map[string]string{
"version": util.GetComponentVersion(&deployment.ObjectMeta),
},
}
sp.Spec.Template.Spec.Subsets = append(sp.Spec.Template.Spec.Subsets, subset)
}
return sp
}
type fixture struct {
t testing.TB
kubeClient *kubefake.Clientset
istioClient *istiofake.Clientset
servicemeshClient *fake.Clientset
serviceLister []*corev1.Service
deploymentLister []*appsv1.Deployment
drLister []*v1alpha3.DestinationRule
spLister []*v1alpha2.ServicePolicy
kubeObjects []runtime.Object
istioObjects []runtime.Object
servicemeshObjects []runtime.Object
}
func newFixture(t testing.TB) *fixture {
f := &fixture{}
f.t = t
f.kubeObjects = []runtime.Object{}
f.istioObjects = []runtime.Object{}
f.servicemeshObjects = []runtime.Object{}
return f
}
func (f *fixture) newController() (*DestinationRuleController, kubeinformers.SharedInformerFactory, istioinformers.SharedInformerFactory, informers.SharedInformerFactory, error) {
f.kubeClient = kubefake.NewSimpleClientset(f.kubeObjects...)
f.servicemeshClient = fake.NewSimpleClientset(f.servicemeshObjects...)
f.istioClient = istiofake.NewSimpleClientset(f.istioObjects...)
kubeInformers := kubeinformers.NewSharedInformerFactory(f.kubeClient, 0)
istioInformers := istioinformers.NewSharedInformerFactory(f.istioClient, 0)
servicemeshInformers := informers.NewSharedInformerFactory(f.servicemeshClient, 0)
c := NewDestinationRuleController(kubeInformers.Apps().V1().Deployments(),
istioInformers.Networking().V1alpha3().DestinationRules(),
kubeInformers.Core().V1().Services(),
servicemeshInformers.Servicemesh().V1alpha2().ServicePolicies(),
f.kubeClient,
f.istioClient,
f.servicemeshClient)
c.eventRecorder = &record.FakeRecorder{}
c.destinationRuleSynced = alwaysReady
c.deploymentSynced = alwaysReady
c.servicePolicySynced = alwaysReady
c.serviceSynced = alwaysReady
for _, s := range f.serviceLister {
kubeInformers.Core().V1().Services().Informer().GetIndexer().Add(s)
}
for _, d := range f.drLister {
istioInformers.Networking().V1alpha3().DestinationRules().Informer().GetIndexer().Add(d)
}
for _, d := range f.deploymentLister {
kubeInformers.Apps().V1().Deployments().Informer().GetIndexer().Add(d)
}
for _, s := range f.spLister {
servicemeshInformers.Servicemesh().V1alpha2().ServicePolicies().Informer().GetIndexer().Add(s)
}
return c, kubeInformers, istioInformers, servicemeshInformers, nil
}
func (f *fixture) run(service *corev1.Service, expected *v1alpha3.DestinationRule, startInformers bool, expectedError bool) {
c, kubeInformers, istioInformers, servicemeshInformers, err := f.newController()
if err != nil {
f.t.Fatal(err)
}
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
kubeInformers.Start(stopCh)
istioInformers.Start(stopCh)
servicemeshInformers.Start(stopCh)
}
key, err := cache.MetaNamespaceKeyFunc(service)
if err != nil {
f.t.Fatal(err)
}
err = c.syncService(key)
if !expectedError && err != nil {
f.t.Fatalf("error syncing service: %v", err)
} else if expectedError && err == nil {
f.t.Fatal("expected error syncing service, got nil")
}
got, err := c.destinationRuleClient.NetworkingV1alpha3().DestinationRules(service.Namespace).Get(service.Name, metav1.GetOptions{})
if err != nil {
f.t.Fatal(err)
}
if unequals := reflectutils.Equal(got, expected); len(unequals) != 0 {
f.t.Errorf("expected %#v, got %#v, unequal fields:", expected, got)
for _, unequal := range unequals {
f.t.Error(unequal)
}
}
}
func runServicePolicy(t *testing.T, service *corev1.Service, sp *v1alpha2.ServicePolicy, expected *v1alpha3.DestinationRule, expectedError bool, deployments ...*appsv1.Deployment) {
f := newFixture(t)
f.kubeObjects = append(f.kubeObjects, service)
f.serviceLister = append(f.serviceLister, service)
for _, deployment := range deployments {
f.kubeObjects = append(f.kubeObjects, deployment)
f.deploymentLister = append(f.deploymentLister, deployment)
}
if sp != nil {
f.servicemeshObjects = append(f.servicemeshObjects, sp)
f.spLister = append(f.spLister, sp)
}
f.run(service, expected, true, expectedError)
}
func TestServicePolicy(t *testing.T) {
defaultService := newService("foo")
defaultDeploymentV1 := newDeployments(defaultService, "v1")
defaultDeploymentV2 := newDeployments(defaultService, "v2")
defaultServicePolicy := newServicePolicy("foo", defaultService, defaultDeploymentV1, defaultDeploymentV2)
defaultExpected := newDestinationRule(defaultService, defaultDeploymentV1, defaultDeploymentV2)
t.Run("should create default destination rule", func(t *testing.T) {
runServicePolicy(t, defaultService, nil, defaultExpected, false, defaultDeploymentV1, defaultDeploymentV2)
})
t.Run("should create destination rule only to v1", func(t *testing.T) {
deploymentV2 := defaultDeploymentV2.DeepCopy()
deploymentV2.Status.AvailableReplicas = 0
deploymentV2.Status.ReadyReplicas = 0
expected := defaultExpected.DeepCopy()
expected.Spec.Subsets = expected.Spec.Subsets[:1]
runServicePolicy(t, defaultService, nil, expected, false, defaultDeploymentV1, deploymentV2)
})
t.Run("should create destination rule match service policy", func(t *testing.T) {
sp := defaultServicePolicy.DeepCopy()
sp.Spec.Template.Spec.TrafficPolicy = &apiv1alpha3.TrafficPolicy{
LoadBalancer: &apiv1alpha3.LoadBalancerSettings{
LbPolicy: &apiv1alpha3.LoadBalancerSettings_Simple{
Simple: apiv1alpha3.LoadBalancerSettings_ROUND_ROBIN,
},
},
ConnectionPool: &apiv1alpha3.ConnectionPoolSettings{
Http: &apiv1alpha3.ConnectionPoolSettings_HTTPSettings{
Http1MaxPendingRequests: 10,
Http2MaxRequests: 20,
MaxRequestsPerConnection: 5,
MaxRetries: 4,
},
},
OutlierDetection: &apiv1alpha3.OutlierDetection{
ConsecutiveErrors: 5,
MaxEjectionPercent: 10,
MinHealthPercent: 20,
},
}
expected := defaultExpected.DeepCopy()
expected.Spec.TrafficPolicy = sp.Spec.Template.Spec.TrafficPolicy
runServicePolicy(t, defaultService, sp, expected, false, defaultDeploymentV1, defaultDeploymentV2)
})
}