mirror of
https://github.com/fluxcd/flagger.git
synced 2026-04-15 06:57:34 +00:00
rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller add unit tests for finalizing introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller run fmt to clean up formatting review changes add kubectl annotation add kubectl annotation support introduction of finalizer introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller add unit tests for finalizing introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller run fmt to clean up formatting review changes introduction of finalizer introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller add unit tests for finalizing introduction of finalizer rebase and squash fix fmt issues revert Dockerfile revert go.mod and go.sum introduction of finalizer introduction of finalizer remove test for finalizer add istio tests fix fmt issues revert go.mod and go.sum revert Dockerfile and main.go fmt deployment controller run fmt to clean up formatting review changes
451 lines
13 KiB
Go
451 lines
13 KiB
Go
package router
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
|
istiov1alpha3 "github.com/weaveworks/flagger/pkg/apis/istio/v1alpha3"
|
|
)
|
|
|
|
func TestIstioRouter_Sync(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
// test insert
|
|
_, err = mocks.meshClient.NetworkingV1alpha3().DestinationRules("default").Get("podinfo-canary", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
_, err = mocks.meshClient.NetworkingV1alpha3().DestinationRules("default").Get("podinfo-primary", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, vs.Spec.Http, 1)
|
|
require.Len(t, vs.Spec.Http[0].Route, 2)
|
|
|
|
// test update
|
|
cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
cdClone := cd.DeepCopy()
|
|
hosts := cdClone.Spec.Service.Hosts
|
|
hosts = append(hosts, "test.example.com")
|
|
cdClone.Spec.Service.Hosts = hosts
|
|
canary, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cdClone)
|
|
require.NoError(t, err)
|
|
|
|
// apply change
|
|
err = router.Reconcile(canary)
|
|
require.NoError(t, err)
|
|
|
|
// verify
|
|
vs, err = mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
assert.Len(t, vs.Spec.Hosts, 2)
|
|
|
|
// test drift
|
|
vsClone := vs.DeepCopy()
|
|
gateways := vsClone.Spec.Gateways
|
|
gateways = append(gateways, "test-gateway.istio-system")
|
|
vsClone.Spec.Gateways = gateways
|
|
totalGateways := len(mocks.canary.Spec.Service.Gateways)
|
|
|
|
vsGateways, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Update(vsClone)
|
|
require.NoError(t, err)
|
|
|
|
totalGateways++
|
|
assert.Len(t, vsGateways.Spec.Gateways, totalGateways)
|
|
|
|
// undo change
|
|
totalGateways--
|
|
err = router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
// verify
|
|
vs, err = mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
assert.Len(t, vs.Spec.Gateways, totalGateways)
|
|
}
|
|
|
|
func TestIstioRouter_SetRoutes(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
pHost := fmt.Sprintf("%s-primary", mocks.canary.Spec.TargetRef.Name)
|
|
cHost := fmt.Sprintf("%s-canary", mocks.canary.Spec.TargetRef.Name)
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
p, c := 60, 40
|
|
err := router.SetRoutes(mocks.canary, p, c, false)
|
|
require.NoError(t, err)
|
|
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
var pRoute, cRoute istiov1alpha3.DestinationWeight
|
|
var mirror *istiov1alpha3.Destination
|
|
for _, http := range vs.Spec.Http {
|
|
for _, route := range http.Route {
|
|
if route.Destination.Host == pHost {
|
|
pRoute = route
|
|
}
|
|
if route.Destination.Host == cHost {
|
|
cRoute = route
|
|
mirror = http.Mirror
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, p, pRoute.Weight)
|
|
assert.Equal(t, c, cRoute.Weight)
|
|
assert.Nil(t, mirror)
|
|
|
|
})
|
|
|
|
t.Run("mirror", func(t *testing.T) {
|
|
for _, w := range []int{0, 10, 50} {
|
|
p, c := 100, 0
|
|
|
|
// set mirror weight
|
|
mocks.canary.Spec.Analysis.MirrorWeight = w
|
|
err := router.SetRoutes(mocks.canary, p, c, true)
|
|
require.NoError(t, err)
|
|
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
var pRoute, cRoute istiov1alpha3.DestinationWeight
|
|
var mirror *istiov1alpha3.Destination
|
|
var mirrorWeight *istiov1alpha3.Percent
|
|
for _, http := range vs.Spec.Http {
|
|
for _, route := range http.Route {
|
|
if route.Destination.Host == pHost {
|
|
pRoute = route
|
|
}
|
|
if route.Destination.Host == cHost {
|
|
cRoute = route
|
|
mirror = http.Mirror
|
|
mirrorWeight = http.MirrorPercentage
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, p, pRoute.Weight)
|
|
assert.Equal(t, c, cRoute.Weight)
|
|
if assert.NotNil(t, mirror) {
|
|
assert.Equal(t, cHost, mirror.Host)
|
|
}
|
|
|
|
if w > 0 && assert.NotNil(t, mirrorWeight) {
|
|
assert.Equal(t, w, int(mirrorWeight.Value))
|
|
} else {
|
|
assert.Nil(t, mirrorWeight)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIstioRouter_GetRoutes(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
p, c, m, err := router.GetRoutes(mocks.canary)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 100, p)
|
|
assert.Equal(t, 0, c)
|
|
assert.False(t, m)
|
|
|
|
mocks.canary = newTestMirror()
|
|
|
|
err = router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
p, c, m, err = router.GetRoutes(mocks.canary)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 100, p)
|
|
assert.Equal(t, 0, c)
|
|
|
|
// A Canary resource with mirror on does not automatically create mirroring
|
|
// in the virtual server (mirroring is activated as a temporary stage).
|
|
assert.False(t, m)
|
|
|
|
// Adjust vs to activate mirroring.
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
cHost := fmt.Sprintf("%s-canary", mocks.canary.Spec.TargetRef.Name)
|
|
for i, http := range vs.Spec.Http {
|
|
for _, route := range http.Route {
|
|
if route.Destination.Host == cHost {
|
|
vs.Spec.Http[i].Mirror = &istiov1alpha3.Destination{
|
|
Host: cHost,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_, err = mocks.meshClient.NetworkingV1alpha3().VirtualServices(mocks.canary.Namespace).Update(vs)
|
|
require.NoError(t, err)
|
|
|
|
p, c, m, err = router.GetRoutes(mocks.canary)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 100, p)
|
|
assert.Equal(t, 0, c)
|
|
assert.True(t, m)
|
|
}
|
|
|
|
func TestIstioRouter_HTTPRequestHeaders(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, vs.Spec.Http, 1)
|
|
assert.Equal(t, "15000", vs.Spec.Http[0].Headers.Request.Add["x-envoy-upstream-rq-timeout-ms"])
|
|
assert.Equal(t, "test", vs.Spec.Http[0].Headers.Request.Remove[0])
|
|
assert.Equal(t, "token", vs.Spec.Http[0].Headers.Response.Remove[0])
|
|
}
|
|
|
|
func TestIstioRouter_CORS(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
require.Len(t, vs.Spec.Http, 1)
|
|
assert.NotNil(t, vs.Spec.Http[0].CorsPolicy)
|
|
assert.Len(t, vs.Spec.Http[0].CorsPolicy.AllowMethods, 2)
|
|
}
|
|
|
|
func TestIstioRouter_ABTest(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.abtest)
|
|
require.NoError(t, err)
|
|
|
|
// test insert
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("abtest", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
assert.Len(t, vs.Spec.Http, 2)
|
|
|
|
p := 0
|
|
c := 100
|
|
m := false
|
|
|
|
err = router.SetRoutes(mocks.abtest, p, c, m)
|
|
require.NoError(t, err)
|
|
|
|
vs, err = mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("abtest", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
pHost := fmt.Sprintf("%s-primary", mocks.abtest.Spec.TargetRef.Name)
|
|
cHost := fmt.Sprintf("%s-canary", mocks.abtest.Spec.TargetRef.Name)
|
|
pRoute := istiov1alpha3.DestinationWeight{}
|
|
cRoute := istiov1alpha3.DestinationWeight{}
|
|
var mirror *istiov1alpha3.Destination
|
|
|
|
for _, http := range vs.Spec.Http {
|
|
for _, route := range http.Route {
|
|
if route.Destination.Host == pHost {
|
|
pRoute = route
|
|
}
|
|
if route.Destination.Host == cHost {
|
|
cRoute = route
|
|
mirror = http.Mirror
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, p, pRoute.Weight)
|
|
assert.Equal(t, c, cRoute.Weight)
|
|
assert.Nil(t, mirror)
|
|
}
|
|
|
|
func TestIstioRouter_GatewayPort(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
err := router.Reconcile(mocks.canary)
|
|
require.NoError(t, err)
|
|
|
|
vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
port := vs.Spec.Http[0].Route[0].Destination.Port.Number
|
|
assert.Equal(t, uint32(mocks.canary.Spec.Service.Port), port)
|
|
}
|
|
|
|
func TestIstioRouter_Finalize(t *testing.T) {
|
|
mocks := newFixture(nil)
|
|
router := &IstioRouter{
|
|
logger: mocks.logger,
|
|
flaggerClient: mocks.flaggerClient,
|
|
istioClient: mocks.meshClient,
|
|
kubeClient: mocks.kubeClient,
|
|
}
|
|
|
|
flaggerSpec := &istiov1alpha3.VirtualServiceSpec{
|
|
Http: []istiov1alpha3.HTTPRoute{
|
|
{
|
|
Match: mocks.canary.Spec.Service.Match,
|
|
Rewrite: mocks.canary.Spec.Service.Rewrite,
|
|
Timeout: mocks.canary.Spec.Service.Timeout,
|
|
Retries: mocks.canary.Spec.Service.Retries,
|
|
CorsPolicy: mocks.canary.Spec.Service.CorsPolicy,
|
|
},
|
|
},
|
|
}
|
|
|
|
kubectlSpec := &istiov1alpha3.VirtualServiceSpec{
|
|
Hosts: []string{"podinfo"},
|
|
Gateways: []string{"ingressgateway.istio-system.svc.cluster.local"},
|
|
Http: []istiov1alpha3.HTTPRoute{
|
|
{
|
|
Match: nil,
|
|
Route: []istiov1alpha3.DestinationWeight{
|
|
{
|
|
Destination: istiov1alpha3.Destination{Host: "podinfo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
tables := []struct {
|
|
router *IstioRouter
|
|
spec *istiov1alpha3.VirtualServiceSpec
|
|
shouldError bool
|
|
createVS bool
|
|
canary *v1beta1.Canary
|
|
callReconcile bool
|
|
annotation string
|
|
}{
|
|
//VS not found
|
|
{router: router, spec: nil, shouldError: true, createVS: false, canary: mocks.canary, callReconcile: false, annotation: ""},
|
|
//No annotation found but still finalizes
|
|
{router: router, spec: nil, shouldError: false, createVS: false, canary: mocks.canary, callReconcile: true, annotation: ""},
|
|
//Spec should match annotation after finalize
|
|
{router: router, spec: flaggerSpec, shouldError: false, createVS: true, canary: mocks.canary, callReconcile: true, annotation: "flagger"},
|
|
//Need to test kubectl annotation
|
|
{router: router, spec: kubectlSpec, shouldError: false, createVS: true, canary: mocks.canary, callReconcile: true, annotation: "kubectl"},
|
|
}
|
|
|
|
for _, table := range tables {
|
|
|
|
var err error
|
|
|
|
if table.createVS {
|
|
vs, err := router.istioClient.NetworkingV1alpha3().VirtualServices(table.canary.Namespace).Get(table.canary.Name, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
if vs.Annotations == nil {
|
|
vs.Annotations = make(map[string]string)
|
|
}
|
|
|
|
if table.annotation == "flagger" {
|
|
b, err := json.Marshal(table.spec)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
vs.Annotations[configAnnotation] = string(b)
|
|
} else if table.annotation == "kubectl" {
|
|
vs.Annotations[kubectlAnnotation] = `{"apiVersion": "networking.istio.io/v1alpha3","kind": "VirtualService","metadata": {"annotations": {},"name": "podinfo","namespace": "test"}, "spec": {"gateways": ["ingressgateway.istio-system.svc.cluster.local"],"hosts": ["podinfo"],"http": [{"route": [{"destination": {"host": "podinfo"}}]}]}}`
|
|
|
|
}
|
|
_, err = router.istioClient.NetworkingV1alpha3().VirtualServices(table.canary.Namespace).Update(vs)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
}
|
|
|
|
if table.callReconcile {
|
|
err = router.Reconcile(table.canary)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
}
|
|
|
|
err = router.Finalize(table.canary)
|
|
|
|
if table.shouldError && err == nil {
|
|
t.Errorf("Expected error from Finalize but error was not returned")
|
|
} else if !table.shouldError && err != nil {
|
|
t.Errorf("Expected no error from Finalize but error was returned %s", err)
|
|
} else if table.spec != nil {
|
|
vs, err := router.istioClient.NetworkingV1alpha3().VirtualServices(table.canary.Namespace).Get(table.canary.Name, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
if cmp.Diff(vs.Spec, *table.spec) != "" {
|
|
|
|
t.Errorf("Expected spec %+v but recieved %+v", table.spec, vs.Spec)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|