Files
flagger/pkg/router/contour.go
Tanner Altares 92937a8f48 kubectl annotation support
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
2020-03-20 15:13:51 -05:00

424 lines
12 KiB
Go

package router
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
contourv1 "github.com/weaveworks/flagger/pkg/apis/projectcontour/v1"
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
)
// ContourRouter is managing HTTPProxy objects
type ContourRouter struct {
kubeClient kubernetes.Interface
contourClient clientset.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
}
// Reconcile creates or updates the HTTP proxy
func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
apexName, primaryName, canaryName := canary.GetServiceNames()
newSpec := contourv1.HTTPProxySpec{
Routes: []contourv1.Route{
{
Conditions: []contourv1.Condition{
{
Prefix: cr.makePrefix(canary),
},
},
TimeoutPolicy: cr.makeTimeoutPolicy(canary),
RetryPolicy: cr.makeRetryPolicy(canary),
Services: []contourv1.Service{
{
Name: primaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(100),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, primaryName),
},
},
},
{
Name: canaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(0),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, canaryName),
},
},
},
},
},
},
}
if len(canary.GetAnalysis().Match) > 0 {
newSpec = contourv1.HTTPProxySpec{
Routes: []contourv1.Route{
{
Conditions: cr.makeConditions(canary),
TimeoutPolicy: cr.makeTimeoutPolicy(canary),
RetryPolicy: cr.makeRetryPolicy(canary),
Services: []contourv1.Service{
{
Name: primaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(100),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, primaryName),
},
},
},
{
Name: canaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(0),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, canaryName),
},
},
},
},
},
{
Conditions: []contourv1.Condition{
{
Prefix: cr.makePrefix(canary),
},
},
TimeoutPolicy: cr.makeTimeoutPolicy(canary),
RetryPolicy: cr.makeRetryPolicy(canary),
Services: []contourv1.Service{
{
Name: primaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(100),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, primaryName),
},
},
},
{
Name: canaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(0),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, canaryName),
},
},
},
},
},
},
}
}
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
if errors.IsNotFound(err) {
proxy = &contourv1.HTTPProxy{
ObjectMeta: metav1.ObjectMeta{
Name: apexName,
Namespace: canary.Namespace,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
Group: flaggerv1.SchemeGroupVersion.Group,
Version: flaggerv1.SchemeGroupVersion.Version,
Kind: flaggerv1.CanaryKind,
}),
},
},
Spec: newSpec,
Status: contourv1.Status{
CurrentStatus: "valid",
Description: "valid HTTPProxy",
},
}
_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Create(proxy)
if err != nil {
return fmt.Errorf("HTTPProxy %s.%s create error: %w", apexName, canary.Namespace, err)
}
cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("HTTPProxy %s.%s created", proxy.GetName(), canary.Namespace)
return nil
} else if err != nil {
return fmt.Errorf("HTTPProxy %s.%s get query error: %w", apexName, canary.Namespace, err)
}
// update HTTPProxy but keep the original destination weights
if proxy != nil {
if diff := cmp.Diff(
newSpec,
proxy.Spec,
cmpopts.IgnoreFields(contourv1.Service{}, "Weight"),
); diff != "" {
clone := proxy.DeepCopy()
clone.Spec = newSpec
_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Update(clone)
if err != nil {
return fmt.Errorf("HTTPProxy %s.%s update error: %w", apexName, canary.Namespace, err)
}
cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("HTTPProxy %s.%s updated", proxy.GetName(), canary.Namespace)
}
}
return nil
}
// GetRoutes returns the service weight for primary and canary
func (cr *ContourRouter) GetRoutes(canary *flaggerv1.Canary) (
primaryWeight int,
canaryWeight int,
mirrored bool,
err error,
) {
apexName, primaryName, _ := canary.GetServiceNames()
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
err = fmt.Errorf("HTTPProxy %s.%s get query error %w", apexName, canary.Namespace, err)
return
}
if len(proxy.Spec.Routes) < 1 || len(proxy.Spec.Routes[0].Services) < 2 {
err = fmt.Errorf("HTTPProxy %s.%s services not found", apexName, canary.Namespace)
return
}
for _, dst := range proxy.Spec.Routes[0].Services {
if dst.Name == primaryName {
primaryWeight = int(dst.Weight)
canaryWeight = 100 - primaryWeight
return
}
}
return
}
// SetRoutes updates the service weight for primary and canary
func (cr *ContourRouter) SetRoutes(
canary *flaggerv1.Canary,
primaryWeight int,
canaryWeight int,
_ bool,
) error {
apexName, primaryName, canaryName := canary.GetServiceNames()
if primaryWeight == 0 && canaryWeight == 0 {
return fmt.Errorf("HTTPProxy %s.%s update failed: no valid weights", apexName, canary.Namespace)
}
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("HTTPProxy %s.%s query error: %w", apexName, canary.Namespace, err)
}
proxy.Spec = contourv1.HTTPProxySpec{
Routes: []contourv1.Route{
{
Conditions: []contourv1.Condition{
{
Prefix: cr.makePrefix(canary),
},
},
TimeoutPolicy: cr.makeTimeoutPolicy(canary),
RetryPolicy: cr.makeRetryPolicy(canary),
Services: []contourv1.Service{
{
Name: primaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(primaryWeight),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, primaryName),
},
},
},
{
Name: canaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(canaryWeight),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, canaryName),
},
},
},
}},
},
}
if len(canary.GetAnalysis().Match) > 0 {
proxy.Spec = contourv1.HTTPProxySpec{
Routes: []contourv1.Route{
{
Conditions: cr.makeConditions(canary),
TimeoutPolicy: cr.makeTimeoutPolicy(canary),
RetryPolicy: cr.makeRetryPolicy(canary),
Services: []contourv1.Service{
{
Name: primaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(primaryWeight),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, primaryName),
},
},
},
{
Name: canaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(canaryWeight),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, canaryName),
},
},
},
},
},
{
Conditions: []contourv1.Condition{
{
Prefix: cr.makePrefix(canary),
},
},
TimeoutPolicy: cr.makeTimeoutPolicy(canary),
RetryPolicy: cr.makeRetryPolicy(canary),
Services: []contourv1.Service{
{
Name: primaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(100),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, primaryName),
},
},
},
{
Name: canaryName,
Port: int(canary.Spec.Service.Port),
Weight: uint32(0),
RequestHeadersPolicy: &contourv1.HeadersPolicy{
Set: []contourv1.HeaderValue{
cr.makeLinkerdHeaderValue(canary, canaryName),
},
},
},
},
},
},
}
}
_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Update(proxy)
if err != nil {
return fmt.Errorf("HTTPProxy %s.%s update error: %w", apexName, canary.Namespace, err)
}
return nil
}
func (cr *ContourRouter) makePrefix(canary *flaggerv1.Canary) string {
prefix := "/"
if len(canary.Spec.Service.Match) > 0 &&
canary.Spec.Service.Match[0].Uri != nil &&
canary.Spec.Service.Match[0].Uri.Prefix != "" {
prefix = canary.Spec.Service.Match[0].Uri.Prefix
}
return prefix
}
func (cr *ContourRouter) makeConditions(canary *flaggerv1.Canary) []contourv1.Condition {
list := []contourv1.Condition{}
if len(canary.GetAnalysis().Match) > 0 {
for _, match := range canary.GetAnalysis().Match {
for s, stringMatch := range match.Headers {
h := &contourv1.HeaderCondition{
Name: s,
Exact: stringMatch.Exact,
}
if stringMatch.Suffix != "" {
h = &contourv1.HeaderCondition{
Name: s,
Contains: stringMatch.Suffix,
}
}
if stringMatch.Prefix != "" {
h = &contourv1.HeaderCondition{
Name: s,
Contains: stringMatch.Prefix,
}
}
list = append(list, contourv1.Condition{
Prefix: cr.makePrefix(canary),
Header: h,
})
}
}
} else {
list = []contourv1.Condition{
{
Prefix: cr.makePrefix(canary),
},
}
}
return list
}
func (cr *ContourRouter) makeTimeoutPolicy(canary *flaggerv1.Canary) *contourv1.TimeoutPolicy {
if canary.Spec.Service.Timeout != "" {
return &contourv1.TimeoutPolicy{
Response: canary.Spec.Service.Timeout,
Idle: "5m",
}
}
return nil
}
func (cr *ContourRouter) makeRetryPolicy(canary *flaggerv1.Canary) *contourv1.RetryPolicy {
if canary.Spec.Service.Retries != nil {
return &contourv1.RetryPolicy{
NumRetries: uint32(canary.Spec.Service.Retries.Attempts),
PerTryTimeout: canary.Spec.Service.Retries.PerTryTimeout,
}
}
return nil
}
func (cr *ContourRouter) makeLinkerdHeaderValue(canary *flaggerv1.Canary, serviceName string) contourv1.HeaderValue {
return contourv1.HeaderValue{
Name: "l5d-dst-override",
Value: fmt.Sprintf("%s.%s.svc.cluster.local:%v", serviceName, canary.Namespace, canary.Spec.Service.Port),
}
}
func (cr *ContourRouter) Finalize(canary *flaggerv1.Canary) error {
return nil
}