Skip to content

Commit

Permalink
Merge pull request #358 from weaveworks/appmesh-gateway
Browse files Browse the repository at this point in the history
Expose canaries on public domains with App Mesh Gateway
  • Loading branch information
stefanprodan authored Nov 6, 2019
2 parents 5901129 + dd272c6 commit 614b7c7
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 5 deletions.
47 changes: 42 additions & 5 deletions pkg/router/appmesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package router

import (
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -80,7 +81,7 @@ func (ar *AppMeshRouter) Reconcile(canary *flaggerv1.Canary) error {
// reconcileVirtualNode creates or updates a virtual node
// the virtual node naming format is name-role-namespace
func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name string, host string) error {
protocol := getProtocol(canary)
protocol := ar.getProtocol(canary)
vnSpec := appmeshv1.VirtualNodeSpec{
MeshName: canary.Spec.Service.MeshName,
Listeners: []appmeshv1.Listener{
Expand Down Expand Up @@ -164,7 +165,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
targetName := canary.Spec.TargetRef.Name
canaryVirtualNode := fmt.Sprintf("%s-canary", targetName)
primaryVirtualNode := fmt.Sprintf("%s-primary", targetName)
protocol := getProtocol(canary)
protocol := ar.getProtocol(canary)

routerName := targetName
if canaryWeight > 0 {
Expand Down Expand Up @@ -212,7 +213,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
Http: &appmeshv1.HttpRoute{
Match: appmeshv1.HttpRouteMatch{
Prefix: routePrefix,
Headers: makeHeaders(canary),
Headers: ar.makeHeaders(canary),
},
RetryPolicy: makeRetryPolicy(canary),
Action: appmeshv1.HttpRouteAction{
Expand Down Expand Up @@ -284,6 +285,15 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
},
Spec: vsSpec,
}

// set App Mesh Gateway annotation on primary virtual service
if canaryWeight == 0 {
a := ar.gatewayAnnotations(canary)
if len(a) > 0 {
virtualService.ObjectMeta.Annotations = a
}
}

_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Create(virtualService)
if err != nil {
return fmt.Errorf("VirtualService %s create error %v", name, err)
Expand All @@ -304,6 +314,14 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
vsClone.Spec = vsSpec
vsClone.Spec.Routes[0].Http.Action = virtualService.Spec.Routes[0].Http.Action

// update App Mesh Gateway annotation on primary virtual service
if canaryWeight == 0 {
a := ar.gatewayAnnotations(canary)
if len(a) > 0 {
vsClone.ObjectMeta.Annotations = a
}
}

_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Update(vsClone)
if err != nil {
return fmt.Errorf("VirtualService %s update error %v", name, err)
Expand Down Expand Up @@ -432,7 +450,7 @@ func makeRetryPolicy(canary *flaggerv1.Canary) *appmeshv1.HttpRetryPolicy {
}

// makeRetryPolicy creates an App Mesh HttpRouteHeader from the Canary.CanaryAnalysis.Match
func makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
func (ar *AppMeshRouter) makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
headers := []appmeshv1.HttpRouteHeader{}

for _, m := range canary.Spec.CanaryAnalysis.Match {
Expand All @@ -453,13 +471,32 @@ func makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
return headers
}

func getProtocol(canary *flaggerv1.Canary) string {
func (ar *AppMeshRouter) getProtocol(canary *flaggerv1.Canary) string {
if strings.Contains(canary.Spec.Service.PortName, "grpc") {
return "grpc"
}
return "http"
}

func (ar *AppMeshRouter) gatewayAnnotations(canary *flaggerv1.Canary) map[string]string {
a := make(map[string]string)
domains := ""
for _, value := range canary.Spec.Service.Hosts {
domains += value + ","
}
if domains != "" {
a["gateway.appmesh.k8s.aws/expose"] = "true"
a["gateway.appmesh.k8s.aws/domain"] = domains
if canary.Spec.Service.Timeout != "" {
a["gateway.appmesh.k8s.aws/timeout"] = canary.Spec.Service.Timeout
}
if canary.Spec.Service.Retries != nil && canary.Spec.Service.Retries.Attempts > 0 {
a["gateway.appmesh.k8s.aws/retries"] = strconv.Itoa(canary.Spec.Service.Retries.Attempts)
}
}
return a
}

func int64p(i int64) *int64 {
return &i
}
Expand Down
44 changes: 44 additions & 0 deletions pkg/router/appmesh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package router

import (
"fmt"
"strconv"
"strings"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -226,3 +228,45 @@ func TestAppmeshRouter_ABTest(t *testing.T) {
t.Errorf("Got http match header exact %v wanted %v", exactMatch, "test")
}
}

func TestAppmeshRouter_Gateway(t *testing.T) {
mocks := setupfakeClients()
router := &AppMeshRouter{
logger: mocks.logger,
flaggerClient: mocks.flaggerClient,
appmeshClient: mocks.meshClient,
kubeClient: mocks.kubeClient,
}

err := router.Reconcile(mocks.appmeshCanary)
if err != nil {
t.Fatal(err.Error())
}

// check virtual service
vsName := fmt.Sprintf("%s.%s", mocks.appmeshCanary.Spec.TargetRef.Name, mocks.appmeshCanary.Namespace)
vs, err := router.appmeshClient.AppmeshV1beta1().VirtualServices("default").Get(vsName, metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}

expose := vs.Annotations["gateway.appmesh.k8s.aws/expose"]
if expose != "true" {
t.Errorf("Got gateway expose annotation %v wanted %v", expose, "true")
}

domain := vs.Annotations["gateway.appmesh.k8s.aws/domain"]
if !strings.Contains(domain, mocks.appmeshCanary.Spec.Service.Hosts[0]) {
t.Errorf("Got gateway domain annotation %v wanted %v", domain, mocks.appmeshCanary.Spec.Service.Hosts[0])
}

timeout := vs.Annotations["gateway.appmesh.k8s.aws/timeout"]
if timeout != mocks.appmeshCanary.Spec.Service.Timeout {
t.Errorf("Got gateway timeout annotation %v wanted %v", timeout, mocks.appmeshCanary.Spec.Service.Timeout)
}

retries := vs.Annotations["gateway.appmesh.k8s.aws/retries"]
if retries != strconv.Itoa(mocks.appmeshCanary.Spec.Service.Retries.Attempts) {
t.Errorf("Got gateway retries annotation %v wanted %v", retries, strconv.Itoa(mocks.appmeshCanary.Spec.Service.Retries.Attempts))
}
}
7 changes: 7 additions & 0 deletions pkg/router/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,14 @@ func newMockCanaryAppMesh() *flaggerv1.Canary {
Service: flaggerv1.CanaryService{
Port: 9898,
MeshName: "global",
Hosts: []string{"*"},
Backends: []string{"backend.default"},
Timeout: "25",
Retries: &istiov1alpha3.HTTPRetry{
Attempts: 5,
PerTryTimeout: "gateway-error",
RetryOn: "5s",
},
}, CanaryAnalysis: flaggerv1.CanaryAnalysis{
Threshold: 10,
StepWeight: 10,
Expand Down

0 comments on commit 614b7c7

Please sign in to comment.