Merge pull request #1085 from johnharris85/add-kuma-support

Add kuma support for progressive traffic shifting canaries
This commit is contained in:
Stefan Prodan
2022-01-11 11:01:14 +02:00
committed by GitHub
48 changed files with 2255 additions and 5 deletions
+1
View File
@@ -14,3 +14,4 @@ redirects:
usage/crossover-progressive-delivery: tutorials/crossover-progressive-delivery.md
usage/traefik-progressive-delivery: tutorials/traefik-progressive-delivery.md
usage/osm-progressive-delivery: tutorials/osm-progressive-delivery.md
usage/kuma-progressive-delivery: tutorials/kuma-progressive-delivery.md
+1
View File
@@ -23,6 +23,7 @@ jobs:
- gloo
- skipper
- osm
- kuma
- kubernetes
steps:
- name: Checkout
+50
View File
@@ -0,0 +1,50 @@
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
annotations:
kuma.io/mesh: default
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 60
service:
port: 9898
targetPort: 9898
apex:
annotations:
9898.service.kuma.io/protocol: "http"
canary:
annotations:
9898.service.kuma.io/protocol: "http"
primary:
annotations:
9898.service.kuma.io/protocol: "http"
analysis:
interval: 15s
threshold: 15
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
threshold: 99
interval: 1m
- name: request-duration
threshold: 500
interval: 30s
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
metadata:
cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"
+13
View File
@@ -195,6 +195,19 @@ rules:
- update
- patch
- delete
- apiGroups:
- kuma.io
resources:
- trafficroutes
- trafficroutes/finalizers
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- nonResourceURLs:
- /version
verbs:
+1 -1
View File
@@ -106,7 +106,7 @@ func init() {
flag.BoolVar(&zapReplaceGlobals, "zap-replace-globals", false, "Whether to change the logging level of the global zap logger.")
flag.StringVar(&zapEncoding, "zap-encoding", "json", "Zap logger encoding.")
flag.StringVar(&namespace, "namespace", "", "Namespace that flagger would watch canary object.")
flag.StringVar(&meshProvider, "mesh-provider", "istio", "Service mesh provider, can be istio, linkerd, appmesh, contour, gloo, nginx, skipper, traefik or osm.")
flag.StringVar(&meshProvider, "mesh-provider", "istio", "Service mesh provider, can be istio, linkerd, appmesh, contour, gloo, nginx, skipper, traefik, osm or kuma.")
flag.StringVar(&selectorLabels, "selector-labels", "app,name,app.kubernetes.io/name", "List of pod labels that Flagger uses to create pod selectors.")
flag.StringVar(&ingressAnnotationsPrefix, "ingress-annotations-prefix", "nginx.ingress.kubernetes.io", "Annotations prefix for NGINX ingresses.")
flag.StringVar(&ingressClass, "ingress-class", "", "Ingress class used for annotating HTTPProxy objects.")
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

+1
View File
@@ -37,6 +37,7 @@ After installing Flagger, you can follow one of these tutorials to get started:
* [Linkerd](tutorials/linkerd-progressive-delivery.md)
* [AWS App Mesh](tutorials/appmesh-progressive-delivery.md)
* [Open Service Mesh](tutorials/osm-progressive-delivery.md)
* [Kuma](tutorials/kuma-progressive-delivery.md)
**Ingress controller tutorials**
+1
View File
@@ -31,6 +31,7 @@
* [Skipper Canary Deployments](tutorials/skipper-progressive-delivery.md)
* [Traefik Canary Deployments](tutorials/traefik-progressive-delivery.md)
* [Open Service Mesh Deployments](tutorials/osm-progressive-delivery.md)
* [Kuma Canary Deployments](tutorials/kuma-progressive-delivery.md)
* [Blue/Green Deployments](tutorials/kubernetes-blue-green.md)
* [Canary analysis with Prometheus Operator](tutorials/prometheus-operator.md)
* [Zero downtime deployments](tutorials/zero-downtime-deployments.md)
@@ -0,0 +1,252 @@
# Kuma Canary Deployments
This guide shows you how to use Kuma and Flagger to automate canary deployments.
![Flagger Kuma Canary](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-kuma-canary.png)
## Prerequisites
Flagger requires a Kubernetes cluster **v1.16** or newer and Kuma **1.3** or newer.
Install Kuma and Prometheus (part of Kuma Metrics):
```bash
kumactl install control-plane | kubectl apply -f -
kumactl install metrics | kubectl apply -f -
```
Install Flagger in the `kuma-system` namespace:
```bash
kubectl apply -k github.com/fluxcd/flagger//kustomize/kuma
```
## Bootstrap
Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),
then creates a series of objects (Kubernetes deployments, ClusterIP services and Kuma `TrafficRoute`).
These objects expose the application inside the mesh and drive the canary analysis and promotion.
Create a test namespace and enable Kuma sidecar injection:
```bash
kubectl create ns test
kubectl annotate namespace test kuma.io/sidecar-injection=enabled
```
Install the load testing service to generate traffic during the canary analysis:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref=main
```
Create a deployment and a horizontal pod autoscaler:
```bash
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/podinfo?ref=main
```
Create a canary custom resource for the `podinfo` deployment:
```yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
annotations:
kuma.io/mesh: default
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 60
service:
port: 9898
targetPort: 9898
apex:
annotations:
9898.service.kuma.io/protocol: "http"
canary:
annotations:
9898.service.kuma.io/protocol: "http"
primary:
annotations:
9898.service.kuma.io/protocol: "http"
analysis:
# schedule interval (default 60s)
interval: 30s
# max number of failed metric checks before rollback
threshold: 5
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 5
metrics:
- name: request-success-rate
threshold: 99
interval: 1m
- name: request-duration
threshold: 500
interval: 30s
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
metadata:
cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"
```
Save the above resource as `podinfo-canary.yaml` and then apply it:
```bash
kubectl apply -f ./podinfo-canary.yaml
```
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary. The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every half a minute.
After a couple of seconds Flagger will create the canary objects:
```bash
# applied
deployment.apps/podinfo
horizontalpodautoscaler.autoscaling/podinfo
ingresses.extensions/podinfo
canary.flagger.app/podinfo
# generated
deployment.apps/podinfo-primary
horizontalpodautoscaler.autoscaling/podinfo-primary
service/podinfo
service/podinfo-canary
service/podinfo-primary
trafficroutes.kuma.io/podinfo
```
After the boostrap, the podinfo deployment will be scaled to zero and the traffic to `podinfo.test` will be routed to the primary pods. During the canary analysis, the `podinfo-canary.test` address can be used to target directly the canary pods.
## Automated canary promotion
Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance indicators like HTTP requests success rate, requests average duration and pod health. Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.
![Flagger Canary Stages](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/diagrams/flagger-canary-steps.png)
Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:3.1.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 0
Phase: Succeeded
Events:
New revision detected! Scaling up podinfo.test
Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Advance podinfo.test canary weight 20
Advance podinfo.test canary weight 25
Waiting for podinfo.test rollout to finish: 1 of 2 updated replicas are available
Advance podinfo.test canary weight 30
Advance podinfo.test canary weight 35
Advance podinfo.test canary weight 40
Advance podinfo.test canary weight 45
Advance podinfo.test canary weight 50
Copying podinfo.test template spec to podinfo-primary.test
Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Promotion completed! Scaling down podinfo.test
```
**Note** that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.
A canary deployment is triggered by changes in any of the following objects:
* Deployment PodSpec \(container image, command, ports, env, resources, etc\)
* ConfigMaps mounted as volumes or mapped to environment variables
* Secrets mounted as volumes or mapped to environment variables
You can monitor all canaries with:
```bash
watch kubectl get canaries --all-namespaces
NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME
test podinfo Progressing 15 2019-06-30T14:05:07Z
prod frontend Succeeded 0 2019-06-30T16:15:07Z
prod backend Failed 0 2019-06-30T17:05:07Z
```
## Automated rollback
During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses and rolls back the faulted version.
Trigger another canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:3.1.2
```
Exec into the load tester pod with:
```bash
kubectl -n test exec -it flagger-loadtester-xx-xx sh
```
Generate HTTP 500 errors:
```bash
watch -n 1 curl http://podinfo-canary.test:9898/status/500
```
Generate latency:
```bash
watch -n 1 curl http://podinfo-canary.test:9898/delay/1
```
When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.
```text
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 10
Phase: Failed
Events:
Starting canary analysis for podinfo.test
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Halt podinfo.test advancement success rate 69.17% < 99%
Halt podinfo.test advancement success rate 61.39% < 99%
Halt podinfo.test advancement success rate 55.06% < 99%
Halt podinfo.test advancement request duration 1.20s > 0.5s
Halt podinfo.test advancement request duration 1.45s > 0.5s
Rolling back podinfo.test failed checks threshold reached 5
Canary failed! Scaling down podinfo.test
```
The above procedures can be extended with [custom metrics](../usage/metrics.md) checks, [webhooks](../usage/webhooks.md), [manual promotion](../usage/webhooks.md#manual-gating) approval and [Slack or MS Teams](../usage/alerting.md) notifications.
+1 -1
View File
@@ -3,7 +3,7 @@
Flagger can run automated application analysis, promotion and rollback for the following deployment strategies:
* **Canary Release** \(progressive traffic shifting\)
* Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo Edge, Traefik, Open Service Mesh
* Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo Edge, Traefik, Open Service Mesh, Kuma
* **A/B Testing** \(HTTP headers and cookies traffic routing\)
* Istio, App Mesh, NGINX, Contour, Gloo Edge
* **Blue/Green** \(traffic switching\)
+1 -1
View File
@@ -30,7 +30,7 @@ chmod +x ${CODEGEN_PKG}/generate-groups.sh
${CODEGEN_PKG}/generate-groups.sh all \
github.com/fluxcd/flagger/pkg/client github.com/fluxcd/flagger/pkg/apis \
"flagger:v1beta1 appmesh:v1beta2 appmesh:v1beta1 istio:v1alpha3 smi:v1alpha1 smi:v1alpha2 smi:v1alpha3 gloo/gloo:v1 gloo/gateway:v1 projectcontour:v1 traefik:v1alpha1" \
"flagger:v1beta1 appmesh:v1beta2 appmesh:v1beta1 istio:v1alpha3 smi:v1alpha1 smi:v1alpha2 smi:v1alpha3 gloo/gloo:v1 gloo/gateway:v1 projectcontour:v1 traefik:v1alpha1 kuma:v1alpha1" \
--output-base "${TEMP_DIR}" \
--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt
+13
View File
@@ -177,6 +177,19 @@ rules:
- update
- patch
- delete
- apiGroups:
- kuma.io
resources:
- trafficroutes
- trafficroutes/finalizers
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- nonResourceURLs:
- /version
verbs:
+5
View File
@@ -0,0 +1,5 @@
namespace: kuma-system
bases:
- ../base/flagger/
patchesStrategicMerge:
- patch.yaml
+14
View File
@@ -0,0 +1,14 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flagger
spec:
template:
spec:
containers:
- name: flagger
args:
- -log-level=info
- -include-label-prefix=app.kubernetes.io
- -mesh-provider=kuma
- -metrics-server=http://prometheus-server.kuma-metrics:80
+1
View File
@@ -12,4 +12,5 @@ const (
SkipperProvider string = "skipper"
TraefikProvider string = "traefik"
OsmProvider string = "osm"
KumaProvider string = "kuma"
)
+5
View File
@@ -0,0 +1,5 @@
package kuma
const (
GroupName = "kuma.io"
)
+5
View File
@@ -0,0 +1,5 @@
// +k8s:deepcopy-gen=package
// Package v1 is the v1 version of the API.
// +groupName=kuma.io
package v1alpha1
+36
View File
@@ -0,0 +1,36 @@
package v1alpha1
import (
"github.com/fluxcd/flagger/pkg/apis/kuma"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeGroupVersion is the GroupVersion for the Kuma API
var SchemeGroupVersion = schema.GroupVersion{Group: kuma.GroupName, Version: "v1alpha1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource gets a Kuma GroupResource for a specified resource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&TrafficRoute{},
&TrafficRouteList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
+82
View File
@@ -0,0 +1,82 @@
/*
Copyright 2019 Kuma authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +genclient:noStatus
// +genclient:nonNamespaced
// TrafficRoute is the Schema for the Traffic Routes API.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TrafficRoute struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Mesh string `json:"mesh,omitempty"`
Spec TrafficRouteSpec `json:"spec,omitempty"`
}
// TrafficRouteList defines a list of TrafficRoute objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TrafficRouteList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []TrafficRoute `json:"items"`
}
// TrafficRouteSpec defines the spec for a TrafficRoute.
type TrafficRouteSpec struct {
// List of selectors to match data plane proxies that are sources of traffic.
Sources []*Selector `json:"sources,omitempty"`
// List of selectors to match services that are destinations of traffic.
//
// Notice the difference between sources and destinations.
// While the source of traffic is always a data plane proxy within a mesh,
// the destination is a service that could be either within or outside
// of a mesh.
Destinations []*Selector `json:"destinations,omitempty"`
// Configuration for the route.
Conf *TrafficRouteConf `json:"conf,omitempty"`
}
// Selector defines the configuration for which Kuma services should be targeted.
type Selector struct {
// Tags to match, can be used for both source and destinations
Match map[string]string `json:"match,omitempty"`
}
// TrafficRouteConf defines the destination configuration.
type TrafficRouteConf struct {
// List of destinations with weights assigned to them.
// When used, "destination" is not allowed.
Split []*TrafficRouteSplit `json:"split,omitempty"`
}
// TrafficRouteSplit defines a destination with a weight assigned to it.
type TrafficRouteSplit struct {
// Weight assigned to that destination.
// Weights are not percentages. For example two destinations with
// weights the same weight "1" will receive both same amount of the traffic.
// 0 means that the destination will be ignored.
Weight uint32 `json:"weight"`
// Selector to match individual endpoints that comprise that destination.
//
// Notice that an endpoint can be either inside or outside the mesh.
// In the former case an endpoint corresponds to a data plane proxy,
// in the latter case an endpoint is an External Service.
Destination map[string]string `json:"destination,omitempty"`
}
@@ -0,0 +1,202 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Selector) DeepCopyInto(out *Selector) {
*out = *in
if in.Match != nil {
in, out := &in.Match, &out.Match
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector.
func (in *Selector) DeepCopy() *Selector {
if in == nil {
return nil
}
out := new(Selector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRoute) DeepCopyInto(out *TrafficRoute) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoute.
func (in *TrafficRoute) DeepCopy() *TrafficRoute {
if in == nil {
return nil
}
out := new(TrafficRoute)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TrafficRoute) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRouteConf) DeepCopyInto(out *TrafficRouteConf) {
*out = *in
if in.Split != nil {
in, out := &in.Split, &out.Split
*out = make([]*TrafficRouteSplit, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(TrafficRouteSplit)
(*in).DeepCopyInto(*out)
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRouteConf.
func (in *TrafficRouteConf) DeepCopy() *TrafficRouteConf {
if in == nil {
return nil
}
out := new(TrafficRouteConf)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRouteList) DeepCopyInto(out *TrafficRouteList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TrafficRoute, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRouteList.
func (in *TrafficRouteList) DeepCopy() *TrafficRouteList {
if in == nil {
return nil
}
out := new(TrafficRouteList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TrafficRouteList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRouteSpec) DeepCopyInto(out *TrafficRouteSpec) {
*out = *in
if in.Sources != nil {
in, out := &in.Sources, &out.Sources
*out = make([]*Selector, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Selector)
(*in).DeepCopyInto(*out)
}
}
}
if in.Destinations != nil {
in, out := &in.Destinations, &out.Destinations
*out = make([]*Selector, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Selector)
(*in).DeepCopyInto(*out)
}
}
}
if in.Conf != nil {
in, out := &in.Conf, &out.Conf
*out = new(TrafficRouteConf)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRouteSpec.
func (in *TrafficRouteSpec) DeepCopy() *TrafficRouteSpec {
if in == nil {
return nil
}
out := new(TrafficRouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRouteSplit) DeepCopyInto(out *TrafficRouteSplit) {
*out = *in
if in.Destination != nil {
in, out := &in.Destination, &out.Destination
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRouteSplit.
func (in *TrafficRouteSplit) DeepCopy() *TrafficRouteSplit {
if in == nil {
return nil
}
out := new(TrafficRouteSplit)
in.DeepCopyInto(out)
return out
}
@@ -28,6 +28,7 @@ import (
gatewayv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gateway/v1"
gloov1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
networkingv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/kuma/v1alpha1"
projectcontourv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/projectcontour/v1"
splitv1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha1"
splitv1alpha2 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha2"
@@ -46,6 +47,7 @@ type Interface interface {
GatewayV1() gatewayv1.GatewayV1Interface
GlooV1() gloov1.GlooV1Interface
NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3Interface
KumaV1alpha1() kumav1alpha1.KumaV1alpha1Interface
ProjectcontourV1() projectcontourv1.ProjectcontourV1Interface
SplitV1alpha1() splitv1alpha1.SplitV1alpha1Interface
SplitV1alpha2() splitv1alpha2.SplitV1alpha2Interface
@@ -63,6 +65,7 @@ type Clientset struct {
gatewayV1 *gatewayv1.GatewayV1Client
glooV1 *gloov1.GlooV1Client
networkingV1alpha3 *networkingv1alpha3.NetworkingV1alpha3Client
kumaV1alpha1 *kumav1alpha1.KumaV1alpha1Client
projectcontourV1 *projectcontourv1.ProjectcontourV1Client
splitV1alpha1 *splitv1alpha1.SplitV1alpha1Client
splitV1alpha2 *splitv1alpha2.SplitV1alpha2Client
@@ -100,6 +103,11 @@ func (c *Clientset) NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3In
return c.networkingV1alpha3
}
// KumaV1alpha1 retrieves the KumaV1alpha1Client
func (c *Clientset) KumaV1alpha1() kumav1alpha1.KumaV1alpha1Interface {
return c.kumaV1alpha1
}
// ProjectcontourV1 retrieves the ProjectcontourV1Client
func (c *Clientset) ProjectcontourV1() projectcontourv1.ProjectcontourV1Interface {
return c.projectcontourV1
@@ -189,6 +197,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
if err != nil {
return nil, err
}
cs.kumaV1alpha1, err = kumav1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.projectcontourV1, err = projectcontourv1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
@@ -236,6 +248,7 @@ func New(c rest.Interface) *Clientset {
cs.gatewayV1 = gatewayv1.New(c)
cs.glooV1 = gloov1.New(c)
cs.networkingV1alpha3 = networkingv1alpha3.New(c)
cs.kumaV1alpha1 = kumav1alpha1.New(c)
cs.projectcontourV1 = projectcontourv1.New(c)
cs.splitV1alpha1 = splitv1alpha1.New(c)
cs.splitV1alpha2 = splitv1alpha2.New(c)
@@ -32,6 +32,8 @@ import (
fakegloov1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/gloo/v1/fake"
networkingv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3"
fakenetworkingv1alpha3 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/kuma/v1alpha1"
fakekumav1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/kuma/v1alpha1/fake"
projectcontourv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/projectcontour/v1"
fakeprojectcontourv1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/projectcontour/v1/fake"
splitv1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha1"
@@ -129,6 +131,11 @@ func (c *Clientset) NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3In
return &fakenetworkingv1alpha3.FakeNetworkingV1alpha3{Fake: &c.Fake}
}
// KumaV1alpha1 retrieves the KumaV1alpha1Client
func (c *Clientset) KumaV1alpha1() kumav1alpha1.KumaV1alpha1Interface {
return &fakekumav1alpha1.FakeKumaV1alpha1{Fake: &c.Fake}
}
// ProjectcontourV1 retrieves the ProjectcontourV1Client
func (c *Clientset) ProjectcontourV1() projectcontourv1.ProjectcontourV1Interface {
return &fakeprojectcontourv1.FakeProjectcontourV1{Fake: &c.Fake}
@@ -25,6 +25,7 @@ import (
gatewayv1 "github.com/fluxcd/flagger/pkg/apis/gloo/gateway/v1"
gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/gloo/v1"
networkingv1alpha3 "github.com/fluxcd/flagger/pkg/apis/istio/v1alpha3"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
projectcontourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1"
splitv1alpha1 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha1"
splitv1alpha2 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha2"
@@ -47,6 +48,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
gatewayv1.AddToScheme,
gloov1.AddToScheme,
networkingv1alpha3.AddToScheme,
kumav1alpha1.AddToScheme,
projectcontourv1.AddToScheme,
splitv1alpha1.AddToScheme,
splitv1alpha2.AddToScheme,
@@ -25,6 +25,7 @@ import (
gatewayv1 "github.com/fluxcd/flagger/pkg/apis/gloo/gateway/v1"
gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/gloo/v1"
networkingv1alpha3 "github.com/fluxcd/flagger/pkg/apis/istio/v1alpha3"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
projectcontourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1"
splitv1alpha1 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha1"
splitv1alpha2 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha2"
@@ -47,6 +48,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
gatewayv1.AddToScheme,
gloov1.AddToScheme,
networkingv1alpha3.AddToScheme,
kumav1alpha1.AddToScheme,
projectcontourv1.AddToScheme,
splitv1alpha1.AddToScheme,
splitv1alpha2.AddToScheme,
@@ -0,0 +1,20 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
// This package has the automatically generated typed clients.
package v1alpha1
@@ -0,0 +1,20 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -0,0 +1,40 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha1 "github.com/fluxcd/flagger/pkg/client/clientset/versioned/typed/kuma/v1alpha1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeKumaV1alpha1 struct {
*testing.Fake
}
func (c *FakeKumaV1alpha1) TrafficRoutes() v1alpha1.TrafficRouteInterface {
return &FakeTrafficRoutes{c}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeKumaV1alpha1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
@@ -0,0 +1,122 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeTrafficRoutes implements TrafficRouteInterface
type FakeTrafficRoutes struct {
Fake *FakeKumaV1alpha1
}
var trafficroutesResource = schema.GroupVersionResource{Group: "kuma.io", Version: "v1alpha1", Resource: "trafficroutes"}
var trafficroutesKind = schema.GroupVersionKind{Group: "kuma.io", Version: "v1alpha1", Kind: "TrafficRoute"}
// Get takes name of the trafficRoute, and returns the corresponding trafficRoute object, and an error if there is any.
func (c *FakeTrafficRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TrafficRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootGetAction(trafficroutesResource, name), &v1alpha1.TrafficRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TrafficRoute), err
}
// List takes label and field selectors, and returns the list of TrafficRoutes that match those selectors.
func (c *FakeTrafficRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TrafficRouteList, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootListAction(trafficroutesResource, trafficroutesKind, opts), &v1alpha1.TrafficRouteList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.TrafficRouteList{ListMeta: obj.(*v1alpha1.TrafficRouteList).ListMeta}
for _, item := range obj.(*v1alpha1.TrafficRouteList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested trafficRoutes.
func (c *FakeTrafficRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchAction(trafficroutesResource, opts))
}
// Create takes the representation of a trafficRoute and creates it. Returns the server's representation of the trafficRoute, and an error, if there is any.
func (c *FakeTrafficRoutes) Create(ctx context.Context, trafficRoute *v1alpha1.TrafficRoute, opts v1.CreateOptions) (result *v1alpha1.TrafficRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(trafficroutesResource, trafficRoute), &v1alpha1.TrafficRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TrafficRoute), err
}
// Update takes the representation of a trafficRoute and updates it. Returns the server's representation of the trafficRoute, and an error, if there is any.
func (c *FakeTrafficRoutes) Update(ctx context.Context, trafficRoute *v1alpha1.TrafficRoute, opts v1.UpdateOptions) (result *v1alpha1.TrafficRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootUpdateAction(trafficroutesResource, trafficRoute), &v1alpha1.TrafficRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TrafficRoute), err
}
// Delete takes name of the trafficRoute and deletes it. Returns an error if one occurs.
func (c *FakeTrafficRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteActionWithOptions(trafficroutesResource, name, opts), &v1alpha1.TrafficRoute{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeTrafficRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewRootDeleteCollectionAction(trafficroutesResource, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.TrafficRouteList{})
return err
}
// Patch applies the patch and returns the patched trafficRoute.
func (c *FakeTrafficRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TrafficRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceAction(trafficroutesResource, name, pt, data, subresources...), &v1alpha1.TrafficRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TrafficRoute), err
}
@@ -0,0 +1,21 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
type TrafficRouteExpansion interface{}
@@ -0,0 +1,107 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"net/http"
v1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
"github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type KumaV1alpha1Interface interface {
RESTClient() rest.Interface
TrafficRoutesGetter
}
// KumaV1alpha1Client is used to interact with features provided by the kuma.io group.
type KumaV1alpha1Client struct {
restClient rest.Interface
}
func (c *KumaV1alpha1Client) TrafficRoutes() TrafficRouteInterface {
return newTrafficRoutes(c)
}
// NewForConfig creates a new KumaV1alpha1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*KumaV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new KumaV1alpha1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*KumaV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &KumaV1alpha1Client{client}, nil
}
// NewForConfigOrDie creates a new KumaV1alpha1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *KumaV1alpha1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new KumaV1alpha1Client for the given RESTClient.
func New(c rest.Interface) *KumaV1alpha1Client {
return &KumaV1alpha1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *KumaV1alpha1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
@@ -0,0 +1,168 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
scheme "github.com/fluxcd/flagger/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// TrafficRoutesGetter has a method to return a TrafficRouteInterface.
// A group's client should implement this interface.
type TrafficRoutesGetter interface {
TrafficRoutes() TrafficRouteInterface
}
// TrafficRouteInterface has methods to work with TrafficRoute resources.
type TrafficRouteInterface interface {
Create(ctx context.Context, trafficRoute *v1alpha1.TrafficRoute, opts v1.CreateOptions) (*v1alpha1.TrafficRoute, error)
Update(ctx context.Context, trafficRoute *v1alpha1.TrafficRoute, opts v1.UpdateOptions) (*v1alpha1.TrafficRoute, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TrafficRoute, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TrafficRouteList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TrafficRoute, err error)
TrafficRouteExpansion
}
// trafficRoutes implements TrafficRouteInterface
type trafficRoutes struct {
client rest.Interface
}
// newTrafficRoutes returns a TrafficRoutes
func newTrafficRoutes(c *KumaV1alpha1Client) *trafficRoutes {
return &trafficRoutes{
client: c.RESTClient(),
}
}
// Get takes name of the trafficRoute, and returns the corresponding trafficRoute object, and an error if there is any.
func (c *trafficRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TrafficRoute, err error) {
result = &v1alpha1.TrafficRoute{}
err = c.client.Get().
Resource("trafficroutes").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of TrafficRoutes that match those selectors.
func (c *trafficRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TrafficRouteList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.TrafficRouteList{}
err = c.client.Get().
Resource("trafficroutes").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested trafficRoutes.
func (c *trafficRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Resource("trafficroutes").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a trafficRoute and creates it. Returns the server's representation of the trafficRoute, and an error, if there is any.
func (c *trafficRoutes) Create(ctx context.Context, trafficRoute *v1alpha1.TrafficRoute, opts v1.CreateOptions) (result *v1alpha1.TrafficRoute, err error) {
result = &v1alpha1.TrafficRoute{}
err = c.client.Post().
Resource("trafficroutes").
VersionedParams(&opts, scheme.ParameterCodec).
Body(trafficRoute).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a trafficRoute and updates it. Returns the server's representation of the trafficRoute, and an error, if there is any.
func (c *trafficRoutes) Update(ctx context.Context, trafficRoute *v1alpha1.TrafficRoute, opts v1.UpdateOptions) (result *v1alpha1.TrafficRoute, err error) {
result = &v1alpha1.TrafficRoute{}
err = c.client.Put().
Resource("trafficroutes").
Name(trafficRoute.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(trafficRoute).
Do(ctx).
Into(result)
return
}
// Delete takes name of the trafficRoute and deletes it. Returns an error if one occurs.
func (c *trafficRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Resource("trafficroutes").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *trafficRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Resource("trafficroutes").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched trafficRoute.
func (c *trafficRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TrafficRoute, err error) {
result = &v1alpha1.TrafficRoute{}
err = c.client.Patch(pt).
Resource("trafficroutes").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
@@ -30,6 +30,7 @@ import (
gloo "github.com/fluxcd/flagger/pkg/client/informers/externalversions/gloo"
internalinterfaces "github.com/fluxcd/flagger/pkg/client/informers/externalversions/internalinterfaces"
istio "github.com/fluxcd/flagger/pkg/client/informers/externalversions/istio"
kuma "github.com/fluxcd/flagger/pkg/client/informers/externalversions/kuma"
projectcontour "github.com/fluxcd/flagger/pkg/client/informers/externalversions/projectcontour"
smi "github.com/fluxcd/flagger/pkg/client/informers/externalversions/smi"
traefik "github.com/fluxcd/flagger/pkg/client/informers/externalversions/traefik"
@@ -184,6 +185,7 @@ type SharedInformerFactory interface {
Gateway() gateway.Interface
Gloo() gloo.Interface
Networking() istio.Interface
Kuma() kuma.Interface
Projectcontour() projectcontour.Interface
Split() smi.Interface
Traefik() traefik.Interface
@@ -209,6 +211,10 @@ func (f *sharedInformerFactory) Networking() istio.Interface {
return istio.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Kuma() kuma.Interface {
return kuma.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Projectcontour() projectcontour.Interface {
return projectcontour.New(f, f.namespace, f.tweakListOptions)
}
@@ -27,8 +27,9 @@ import (
v1 "github.com/fluxcd/flagger/pkg/apis/gloo/gateway/v1"
gloov1 "github.com/fluxcd/flagger/pkg/apis/gloo/gloo/v1"
v1alpha3 "github.com/fluxcd/flagger/pkg/apis/istio/v1alpha3"
v1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
projectcontourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1"
v1alpha1 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha1"
smiv1alpha1 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha1"
v1alpha2 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha2"
smiv1alpha3 "github.com/fluxcd/flagger/pkg/apis/smi/v1alpha3"
traefikv1alpha1 "github.com/fluxcd/flagger/pkg/apis/traefik/v1alpha1"
@@ -94,6 +95,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
case gloov1.SchemeGroupVersion.WithResource("upstreams"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Gloo().V1().Upstreams().Informer()}, nil
// Group=kuma.io, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("trafficroutes"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Kuma().V1alpha1().TrafficRoutes().Informer()}, nil
// Group=networking.istio.io, Version=v1alpha3
case v1alpha3.SchemeGroupVersion.WithResource("destinationrules"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1alpha3().DestinationRules().Informer()}, nil
@@ -105,7 +110,7 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcontour().V1().HTTPProxies().Informer()}, nil
// Group=split.smi-spec.io, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("trafficsplits"):
case smiv1alpha1.SchemeGroupVersion.WithResource("trafficsplits"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Split().V1alpha1().TrafficSplits().Informer()}, nil
// Group=split.smi-spec.io, Version=v1alpha2
@@ -0,0 +1,46 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by informer-gen. DO NOT EDIT.
package kuma
import (
internalinterfaces "github.com/fluxcd/flagger/pkg/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/fluxcd/flagger/pkg/client/informers/externalversions/kuma/v1alpha1"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1alpha1 provides access to shared informers for resources in V1alpha1.
V1alpha1() v1alpha1.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1alpha1 returns a new v1alpha1.Interface.
func (g *group) V1alpha1() v1alpha1.Interface {
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
}
@@ -0,0 +1,45 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
internalinterfaces "github.com/fluxcd/flagger/pkg/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// TrafficRoutes returns a TrafficRouteInformer.
TrafficRoutes() TrafficRouteInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// TrafficRoutes returns a TrafficRouteInformer.
func (v *version) TrafficRoutes() TrafficRouteInformer {
return &trafficRouteInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}
@@ -0,0 +1,89 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
versioned "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
internalinterfaces "github.com/fluxcd/flagger/pkg/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/fluxcd/flagger/pkg/client/listers/kuma/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// TrafficRouteInformer provides access to a shared informer and lister for
// TrafficRoutes.
type TrafficRouteInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.TrafficRouteLister
}
type trafficRouteInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewTrafficRouteInformer constructs a new informer for TrafficRoute type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewTrafficRouteInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredTrafficRouteInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredTrafficRouteInformer constructs a new informer for TrafficRoute type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredTrafficRouteInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.KumaV1alpha1().TrafficRoutes().List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.KumaV1alpha1().TrafficRoutes().Watch(context.TODO(), options)
},
},
&kumav1alpha1.TrafficRoute{},
resyncPeriod,
indexers,
)
}
func (f *trafficRouteInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredTrafficRouteInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *trafficRouteInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&kumav1alpha1.TrafficRoute{}, f.defaultInformer)
}
func (f *trafficRouteInformer) Lister() v1alpha1.TrafficRouteLister {
return v1alpha1.NewTrafficRouteLister(f.Informer().GetIndexer())
}
@@ -0,0 +1,23 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
// TrafficRouteListerExpansion allows custom methods to be added to
// TrafficRouteLister.
type TrafficRouteListerExpansion interface{}
@@ -0,0 +1,68 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// TrafficRouteLister helps list TrafficRoutes.
// All objects returned here must be treated as read-only.
type TrafficRouteLister interface {
// List lists all TrafficRoutes in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.TrafficRoute, err error)
// Get retrieves the TrafficRoute from the index for a given name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.TrafficRoute, error)
TrafficRouteListerExpansion
}
// trafficRouteLister implements the TrafficRouteLister interface.
type trafficRouteLister struct {
indexer cache.Indexer
}
// NewTrafficRouteLister returns a new TrafficRouteLister.
func NewTrafficRouteLister(indexer cache.Indexer) TrafficRouteLister {
return &trafficRouteLister{indexer: indexer}
}
// List lists all TrafficRoutes in the indexer.
func (s *trafficRouteLister) List(selector labels.Selector) (ret []*v1alpha1.TrafficRoute, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.TrafficRoute))
})
return ret, err
}
// Get retrieves the TrafficRoute from the index for a given name.
func (s *trafficRouteLister) Get(name string) (*v1alpha1.TrafficRoute, error) {
obj, exists, err := s.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("trafficroute"), name)
}
return obj.(*v1alpha1.TrafficRoute), nil
}
+4
View File
@@ -84,6 +84,10 @@ func (factory Factory) Observer(provider string) Interface {
return &OsmObserver{
client: factory.Client,
}
case provider == flaggerv1.KumaProvider:
return &KumaObserver{
client: factory.Client,
}
default:
return &IstioObserver{
client: factory.Client,
+94
View File
@@ -0,0 +1,94 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package observers
import (
"fmt"
"time"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
"github.com/fluxcd/flagger/pkg/metrics/providers"
)
// TODO [@johnharris85]: Do we also need to select by mesh here? These could be duplicated (but in different meshes).
// We're currently getting the mesh name from an annotation on the Canary object, but that isn't propagated to the
// MetricTemplateModel.
var kumaQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
envoy_cluster_name=~"{{ target }}-canary_{{ namespace }}_svc_[0-9a-zA-Z-]+",
envoy_response_code!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
envoy_cluster_name=~"{{ target }}-canary_{{ namespace }}_svc_[0-9a-zA-Z-]+",
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
envoy_cluster_name=~"{{ target }}-canary_{{ namespace }}_svc_[0-9a-zA-Z-]+",
}[{{ interval }}]
)
) by (le)
)`,
}
type KumaObserver struct {
client providers.Interface
}
func (ob *KumaObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(kumaQueries["request-success-rate"], model)
if err != nil {
return 0, fmt.Errorf("rendering query failed: %w", err)
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, fmt.Errorf("running query failed: %w", err)
}
return value, nil
}
func (ob *KumaObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(kumaQueries["request-duration"], model)
if err != nil {
return 0, fmt.Errorf("rendering query failed: %w", err)
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, fmt.Errorf("running query failed: %w", err)
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}
+100
View File
@@ -0,0 +1,100 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
"github.com/fluxcd/flagger/pkg/metrics/providers"
)
func TestKumaObserver_GetRequestSuccessRate(t *testing.T) {
expected := ` sum( rate( envoy_cluster_upstream_rq{ envoy_cluster_name=~"podinfo-canary_default_svc_[0-9a-zA-Z-]+", envoy_response_code!~"5.*" }[1m] ) ) / sum( rate( envoy_cluster_upstream_rq{ envoy_cluster_name=~"podinfo-canary_default_svc_[0-9a-zA-Z-]+", }[1m] ) ) * 100`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
promql := r.URL.Query()["query"][0]
assert.Equal(t, expected, promql)
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1,"100"]}]}}`
w.Write([]byte(json))
}))
defer ts.Close()
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
require.NoError(t, err)
observer := &KumaObserver{
client: client,
}
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
require.NoError(t, err)
assert.Equal(t, float64(100), val)
}
func TestKumaObserver_GetRequestDuration(t *testing.T) {
expected := ` histogram_quantile( 0.99, sum( rate( envoy_cluster_upstream_rq_time_bucket{ envoy_cluster_name=~"podinfo-canary_default_svc_[0-9a-zA-Z-]+", }[1m] ) ) by (le) )`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
promql := r.URL.Query()["query"][0]
assert.Equal(t, expected, promql)
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1,"100"]}]}}`
w.Write([]byte(json))
}))
defer ts.Close()
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
require.NoError(t, err)
observer := &KumaObserver{
client: client,
}
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
require.NoError(t, err)
assert.Equal(t, 100*time.Millisecond, val)
}
+7
View File
@@ -170,6 +170,13 @@ func (factory *Factory) MeshRouter(provider string, labelSelector string) Interf
smiClient: factory.meshClient,
targetMesh: flaggerv1.OsmProvider,
}
case provider == flaggerv1.KumaProvider:
return &KumaRouter{
logger: factory.logger,
flaggerClient: factory.flaggerClient,
kubeClient: factory.kubeClient,
kumaClient: factory.meshClient,
}
case provider == flaggerv1.KubernetesProvider:
return &NopRouter{}
default:
+212
View File
@@ -0,0 +1,212 @@
package router
import (
"context"
"fmt"
"strings"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"go.uber.org/zap"
"k8s.io/client-go/kubernetes"
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
)
// KumaRouter is managing TrafficRoute objects
type KumaRouter struct {
kubeClient kubernetes.Interface
kumaClient clientset.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
}
// Reconcile creates or updates the Kuma TrafficRoute
func (kr *KumaRouter) Reconcile(canary *flaggerv1.Canary) error {
apexName, primaryName, canaryName := canary.GetServiceNames()
trSpec := kumav1alpha1.TrafficRouteSpec{
Sources: []*kumav1alpha1.Selector{
{
Match: map[string]string{
"kuma.io/service": "*",
},
},
},
Destinations: []*kumav1alpha1.Selector{
{
Match: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", apexName, canary.Namespace, canary.Spec.Service.Port),
},
},
},
Conf: &kumav1alpha1.TrafficRouteConf{
Split: []*kumav1alpha1.TrafficRouteSplit{
{
Weight: uint32(100),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", primaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
{
Weight: uint32(0),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", canaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
},
},
}
tr, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), apexName, metav1.GetOptions{})
// create TrafficRoute
if errors.IsNotFound(err) {
metadata := canary.Spec.Service.Apex
if metadata == nil {
metadata = &flaggerv1.CustomMetadata{}
}
if metadata.Labels == nil {
metadata.Labels = make(map[string]string)
}
if metadata.Annotations == nil {
metadata.Annotations = make(map[string]string)
metadata.Annotations[fmt.Sprintf("%d.service.kuma.io", canary.Spec.Service.Port)] = "http"
}
meshName, ok := canary.Annotations["kuma.io/mesh"]
if !ok {
meshName = "default"
}
t := &kumav1alpha1.TrafficRoute{
ObjectMeta: metav1.ObjectMeta{
Name: apexName,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
Group: flaggerv1.SchemeGroupVersion.Group,
Version: flaggerv1.SchemeGroupVersion.Version,
Kind: flaggerv1.CanaryKind,
}),
},
Annotations: filterMetadata(metadata.Annotations),
},
Spec: trSpec,
Mesh: meshName,
}
_, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Create(context.TODO(), t, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s create error: %w", apexName, err)
}
kr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("TrafficRoute %s created", t.GetName())
return nil
} else if err != nil {
return fmt.Errorf("TrafficRoute %s get query error: %w", apexName, err)
}
// update TrafficRoute
if diff := cmp.Diff(trSpec, tr.Spec, cmpopts.IgnoreFields(kumav1alpha1.TrafficRouteSplit{}, "Weight")); diff != "" {
trClone := tr.DeepCopy()
trClone.Spec = trSpec
_, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Update(context.TODO(), trClone, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s update error: %w", apexName, err)
}
kr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("TrafficRoute %s.%s updated", apexName, canary.Namespace)
return nil
}
return nil
}
// GetRoutes returns the destinations weight for primary and canary
func (kr *KumaRouter) GetRoutes(canary *flaggerv1.Canary) (
primaryWeight int,
canaryWeight int,
mirrored bool,
err error,
) {
apexName, primaryName, canaryName := canary.GetServiceNames()
tr, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), apexName, metav1.GetOptions{})
if err != nil {
err = fmt.Errorf("TrafficRoute %s get query error %v", apexName, err)
return
}
for _, split := range tr.Spec.Conf.Split {
if strings.Split(split.Destination["kuma.io/service"], "_")[0] == primaryName {
primaryWeight = int(split.Weight)
canaryWeight = 100 - primaryWeight
}
}
if primaryWeight == 0 && canaryWeight == 0 {
err = fmt.Errorf("TrafficRoute %s does not contain routes for %s and %s",
apexName, primaryName, canaryName)
}
mirrored = false
return
}
// SetRoutes updates the destinations weight for primary and canary
func (kr *KumaRouter) SetRoutes(
canary *flaggerv1.Canary,
primaryWeight int,
canaryWeight int,
_ bool,
) error {
apexName, primaryName, canaryName := canary.GetServiceNames()
tr, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), apexName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s get query error %v", apexName, err)
}
conf := &kumav1alpha1.TrafficRouteConf{
Split: []*kumav1alpha1.TrafficRouteSplit{
{
Weight: uint32(primaryWeight),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", primaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
{
Weight: uint32(canaryWeight),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", canaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
},
}
trClone := tr.DeepCopy()
trClone.Spec.Conf = conf
_, err = kr.kumaClient.KumaV1alpha1().TrafficRoutes().Update(context.TODO(), trClone, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s update error %v", apexName, err)
}
return nil
}
func (kr *KumaRouter) Finalize(_ *flaggerv1.Canary) error {
return nil
}
+78
View File
@@ -0,0 +1,78 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestKumaRouter_Reconcile(t *testing.T) {
canary := newTestSMICanary()
mocks := newFixture(canary)
router := &KumaRouter{
logger: mocks.logger,
flaggerClient: mocks.flaggerClient,
kumaClient: mocks.meshClient,
kubeClient: mocks.kubeClient,
}
// init
err := router.Reconcile(canary)
require.NoError(t, err)
// test insert
trafficRoute, err := router.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
splits := trafficRoute.Spec.Conf.Split
require.Len(t, splits, 2)
assert.Equal(t, uint32(100), splits[0].Weight)
assert.Equal(t, uint32(0), splits[1].Weight)
}
func TestKumaRouter_Routes(t *testing.T) {
canary := newTestSMICanary()
mocks := newFixture(canary)
router := &KumaRouter{
logger: mocks.logger,
flaggerClient: mocks.flaggerClient,
kumaClient: mocks.meshClient,
kubeClient: mocks.kubeClient,
}
// init
err := router.Reconcile(canary)
require.NoError(t, err)
// test set routers
err = router.SetRoutes(canary, 50, 50, false)
require.NoError(t, err)
trafficRoute, err := router.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
primary := trafficRoute.Spec.Conf.Split[0]
assert.Equal(t, uint32(50), primary.Weight)
}
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -o errexit
KUMA_VER="1.4.1"
REPO_ROOT=$(git rev-parse --show-toplevel)
mkdir -p ${REPO_ROOT}/bin
echo ">>> Downloading Kuma ${KUMA_VER}"
curl -SsL https://download.konghq.com/mesh-alpine/kuma-${KUMA_VER}-ubuntu-amd64.tar.gz -o kuma-${KUMA_VER}.tar.gz
tar xvzf kuma-${KUMA_VER}.tar.gz
cp kuma-${KUMA_VER}/bin/kumactl ${REPO_ROOT}/bin/kumactl
chmod +x ${REPO_ROOT}/bin/kumactl
echo ">>> Installing Kuma ${KUMA_VER}"
${REPO_ROOT}/bin/kumactl install control-plane | kubectl apply -f -
echo ">>> Installing Kuma Metrics"
${REPO_ROOT}/bin/kumactl install metrics | kubectl apply -f -
echo ">>> Waiting for Kuma Control Plane to be ready"
kubectl wait --for=condition=ready pod -n kuma-system -l app=kuma-control-plane
echo ">>> Configuring Default Kuma Mesh"
cat <<EOF | kubectl apply -f -
apiVersion: kuma.io/v1alpha1
kind: Mesh
metadata:
name: default
spec:
metrics:
enabledBackend: prometheus-1
backends:
- name: prometheus-1
type: prometheus
conf:
skipMTLS: true
port: 5670
path: /metrics
tags:
kuma.io/service: dataplane-metrics
mtls:
enabledBackend: ca-1
backends:
- name: ca-1
type: builtin
mode: PERMISSIVE
dpCert:
rotation:
expiration: 1d
conf:
caCert:
RSAbits: 2048
expiration: 10y
EOF
echo '>>> Installing Flagger'
kubectl apply -k ${REPO_ROOT}/kustomize/kuma
kubectl -n kuma-system set image deployment/flagger flagger=test/flagger:latest
kubectl -n kuma-system rollout status deployment/flagger
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -o errexit
REPO_ROOT=$(git rev-parse --show-toplevel)
DIR="$(cd "$(dirname "$0")" && pwd)"
"$DIR"/install.sh
"$REPO_ROOT"/test/workloads/init.sh
"$DIR"/test-canary.sh
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env bash
# This script runs Kuma e2e tests for Canary initialization, analysis and promotion
set -o errexit
REPO_ROOT=$(git rev-parse --show-toplevel)
cat <<EOF | kubectl apply -f -
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
annotations:
kuma.io/mesh: default
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 60
service:
port: 9898
targetPort: 9898
apex:
annotations:
9898.service.kuma.io/protocol: "http"
canary:
annotations:
9898.service.kuma.io/protocol: "http"
primary:
annotations:
9898.service.kuma.io/protocol: "http"
analysis:
interval: 15s
threshold: 15
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
threshold: 99
interval: 1m
- name: request-duration
threshold: 500
interval: 30s
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
metadata:
cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"
EOF
echo '>>> Waiting for primary to be ready'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test get canary/podinfo | grep 'Initialized' && ok=true || ok=false
sleep 5
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n kuma-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done
echo '✔ Canary initialization test passed'
passed=$(kubectl -n test get svc/podinfo -o jsonpath='{.spec.selector.app}' 2>&1 | { grep podinfo-primary || true; })
if [ -z "$passed" ]; then
echo -e '\u2716 podinfo selector test failed'
exit 1
fi
echo '✔ Canary service custom metadata test passed'
echo '>>> Triggering canary deployment'
kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.1
echo '>>> Waiting for canary promotion'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test describe deployment/podinfo-primary | grep '3.1.1' && ok=true || ok=false
sleep 10
kubectl -n kuma-system logs deployment/flagger --tail 1
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n kuma-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done
echo '>>> Waiting for canary finalization'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test get canary/podinfo | grep 'Succeeded' && ok=true || ok=false
sleep 5
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n kuma-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done
echo '✔ Canary promotion test passed'
cat <<EOF | kubectl apply -f -
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
annotations:
kuma.io/mesh: default
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 60
service:
port: 9898
targetPort: 9898
apex:
annotations:
9898.service.kuma.io/protocol: "http"
canary:
annotations:
9898.service.kuma.io/protocol: "http"
primary:
annotations:
9898.service.kuma.io/protocol: "http"
analysis:
interval: 15s
threshold: 15
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
threshold: 99
interval: 1m
- name: request-duration
threshold: 500
interval: 30s
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/status/500"
EOF
echo '>>> Triggering canary deployment rollback test'
kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.2
echo '>>> Waiting for canary rollback'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test get canary/podinfo | grep 'Failed' && ok=true || ok=false
sleep 10
kubectl -n kuma-system logs deployment/flagger --tail 1
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n kuma-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done
echo '✔ Canary rollback test passed'
+1
View File
@@ -13,6 +13,7 @@ echo '>>> Creating test namespace'
kubectl create namespace test
kubectl label namespace test istio-injection=enabled
kubectl annotate namespace test linkerd.io/inject=enabled
kubectl annotate namespace test kuma.io/sidecar-injection=enabled
echo '>>> Installing the load tester'
kubectl apply -k ${REPO_ROOT}/kustomize/tester