Merge pull request #419 from weaveworks/metric-template

Implement metric templates for Prometheus
This commit is contained in:
Stefan Prodan
2020-02-07 12:32:14 +02:00
committed by GitHub
69 changed files with 2850 additions and 888 deletions

View File

@@ -42,6 +42,8 @@ rules:
resources:
- canaries
- canaries/status
- metrictemplates
- metrictemplates/status
verbs: ["*"]
- apiGroups:
- networking.istio.io

View File

@@ -215,18 +215,29 @@ spec:
required: ["name", "threshold"]
properties:
name:
description: Name of the Prometheus metric
description: Name of the metric
type: string
interval:
description: Interval of the promql query
description: Interval of the query
type: string
pattern: "^[0-9]+(m|s)"
threshold:
description: Max scalar value accepted for this metric
description: Max value accepted for this metric
type: number
query:
description: Prometheus query
type: string
templateRef:
description: Metric template reference
type: object
required: ["name"]
properties:
name:
description: Name of this metric template
type: string
namespace:
description: Namespace of this metric template
type: string
webhooks:
description: Webhook list for this canary
type: array
@@ -322,3 +333,62 @@ spec:
type:
description: Type of this condition
type: string
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: metrictemplates.flagger.app
annotations:
helm.sh/resource-policy: keep
spec:
group: flagger.app
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
names:
plural: metrictemplates
singular: metrictemplate
kind: MetricTemplate
categories:
- all
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
properties:
spec:
required:
- provider
- query
properties:
provider:
description: Provider of this metric template
type: object
required:
- type
- address
properties:
type:
description: Type of this provider
type: string
enum:
- prometheus
- influxdb
address:
description: API address of this provider
type: string
secretRef:
description: Kubernetes secret reference containing the provider credentials
type: object
required:
- name
properties:
name:
description: Name of the Kubernetes secret
type: string
query:
description: Query of this metric template
type: string

View File

@@ -78,7 +78,7 @@ spec:
targetRef:
description: Deployment selector
type: object
required: ['apiVersion', 'kind', 'name']
required: ["apiVersion", "kind", "name"]
properties:
apiVersion:
type: string
@@ -91,7 +91,7 @@ spec:
anyOf:
- type: string
- type: object
required: ['apiVersion', 'kind', 'name']
required: ["apiVersion", "kind", "name"]
properties:
apiVersion:
type: string
@@ -104,7 +104,7 @@ spec:
anyOf:
- type: string
- type: object
required: ['apiVersion', 'kind', 'name']
required: ["apiVersion", "kind", "name"]
properties:
apiVersion:
type: string
@@ -114,7 +114,7 @@ spec:
type: string
service:
type: object
required: ['port']
required: ["port"]
properties:
name:
description: Kubernetes service name
@@ -213,21 +213,32 @@ spec:
properties:
items:
type: object
required: ['name', 'threshold']
required: ["name", "threshold"]
properties:
name:
description: Name of the Prometheus metric
description: Name of the metric
type: string
interval:
description: Interval of the promql query
description: Interval of the query
type: string
pattern: "^[0-9]+(m|s)"
threshold:
description: Max scalar value accepted for this metric
description: Max value accepted for this metric
type: number
query:
description: Prometheus query
type: string
templateRef:
description: Metric template reference
type: object
required: ["name"]
properties:
name:
description: Name of this metric template
type: string
namespace:
description: Namespace of this metric template
type: string
webhooks:
description: Webhook list for this canary
type: array
@@ -301,7 +312,7 @@ spec:
properties:
items:
type: object
required: ['type', 'status', 'reason']
required: ["type", "status", "reason"]
properties:
lastTransitionTime:
description: LastTransitionTime of this condition
@@ -323,4 +334,63 @@ spec:
type:
description: Type of this condition
type: string
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: metrictemplates.flagger.app
annotations:
helm.sh/resource-policy: keep
spec:
group: flagger.app
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
names:
plural: metrictemplates
singular: metrictemplate
kind: MetricTemplate
categories:
- all
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
properties:
spec:
required:
- provider
- query
properties:
provider:
description: Provider of this metric template
type: object
required:
- type
- address
properties:
type:
description: Type of this provider
type: string
enum:
- prometheus
- influxdb
address:
description: API address of this provider
type: string
secretRef:
description: Kubernetes secret reference containing the provider credentials
type: object
required:
- name
properties:
name:
description: Name of the Kubernetes secret
type: string
query:
description: Query of this metric template
type: string
{{- end }}

View File

@@ -38,6 +38,8 @@ rules:
resources:
- canaries
- canaries/status
- metrictemplates
- metrictemplates/status
verbs: ["*"]
- apiGroups:
- networking.istio.io

View File

@@ -11,6 +11,7 @@ import (
"github.com/Masterminds/semver/v3"
"go.uber.org/zap"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
@@ -26,7 +27,7 @@ import (
informers "github.com/weaveworks/flagger/pkg/client/informers/externalversions"
"github.com/weaveworks/flagger/pkg/controller"
"github.com/weaveworks/flagger/pkg/logger"
"github.com/weaveworks/flagger/pkg/metrics"
"github.com/weaveworks/flagger/pkg/metrics/observers"
"github.com/weaveworks/flagger/pkg/notifier"
"github.com/weaveworks/flagger/pkg/router"
"github.com/weaveworks/flagger/pkg/server"
@@ -102,6 +103,8 @@ func main() {
stopCh := signals.SetupSignalHandler()
logger.Infof("Starting flagger version %s revision %s mesh provider %s", version.VERSION, version.REVISION, meshProvider)
cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
if err != nil {
logger.Fatalf("Error building kubeconfig: %v", err)
@@ -122,48 +125,19 @@ func main() {
logger.Fatalf("Error building flagger clientset: %s", err.Error())
}
flaggerInformerFactory := informers.NewSharedInformerFactoryWithOptions(flaggerClient, time.Second*30, informers.WithNamespace(namespace))
canaryInformer := flaggerInformerFactory.Flagger().V1alpha3().Canaries()
logger.Infof("Starting flagger version %s revision %s mesh provider %s", version.VERSION, version.REVISION, meshProvider)
ver, err := kubeClient.Discovery().ServerVersion()
if err != nil {
logger.Fatalf("Error calling Kubernetes API: %v", err)
}
k8sVersionConstraint := "^1.11.0"
// We append -alpha.1 to the end of our version constraint so that prebuilds of later versions
// are considered valid for our purposes, as well as some managed solutions like EKS where they provide
// a version like `v1.12.6-eks-d69f1b`. It doesn't matter what the prelease value is here, just that it
// exists in our constraint.
semverConstraint, err := semver.NewConstraint(k8sVersionConstraint + "-alpha.1")
if err != nil {
logger.Fatalf("Error parsing kubernetes version constraint: %v", err)
}
k8sSemver, err := semver.NewVersion(ver.GitVersion)
if err != nil {
logger.Fatalf("Error parsing kubernetes version as a semantic version: %v", err)
}
if !semverConstraint.Check(k8sSemver) {
logger.Fatalf("Unsupported version of kubernetes detected. Expected %s, got %v", k8sVersionConstraint, ver)
}
verifyCRDs(flaggerClient, logger)
verifyKubernetesVersion(kubeClient, logger)
labels := strings.Split(selectorLabels, ",")
if len(labels) < 1 {
logger.Fatalf("At least one selector label is required")
}
logger.Infof("Connected to Kubernetes API %s", ver)
if namespace != "" {
logger.Infof("Watching namespace %s", namespace)
}
observerFactory, err := metrics.NewFactory(metricsServer, 5*time.Second)
observerFactory, err := observers.NewFactory(metricsServer)
if err != nil {
logger.Fatalf("Error building prometheus client: %s", err.Error())
}
@@ -181,6 +155,23 @@ func main() {
// start HTTP server
go server.ListenAndServe(port, 3*time.Second, logger, stopCh)
// start informers
flaggerInformerFactory := informers.NewSharedInformerFactoryWithOptions(flaggerClient, time.Second*30, informers.WithNamespace(namespace))
logger.Info("Waiting for canary informer cache to sync")
canaryInformer := flaggerInformerFactory.Flagger().V1alpha3().Canaries()
go canaryInformer.Informer().Run(stopCh)
if ok := cache.WaitForNamedCacheSync("flagger", stopCh, canaryInformer.Informer().HasSynced); !ok {
logger.Fatalf("failed to wait for cache to sync")
}
logger.Info("Waiting for metric template informer cache to sync")
metricInformer := flaggerInformerFactory.Flagger().V1alpha1().MetricTemplates()
go metricInformer.Informer().Run(stopCh)
if ok := cache.WaitForNamedCacheSync("flagger", stopCh, metricInformer.Informer().HasSynced); !ok {
logger.Fatalf("failed to wait for cache to sync")
}
routerFactory := router.NewFactory(cfg, kubeClient, flaggerClient, ingressAnnotationsPrefix, logger, meshClient)
configTracker := canary.ConfigTracker{
Logger: logger,
@@ -205,17 +196,6 @@ func main() {
eventWebhook,
)
flaggerInformerFactory.Start(stopCh)
logger.Info("Waiting for informer caches to sync")
for _, synced := range []cache.InformerSynced{
canaryInformer.Informer().HasSynced,
} {
if ok := cache.WaitForCacheSync(stopCh, synced); !ok {
logger.Fatalf("Failed to wait for cache sync")
}
}
// leader election context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -322,3 +302,44 @@ func fromEnv(envVar string, defaultVal string) string {
}
return defaultVal
}
func verifyCRDs(flaggerClient clientset.Interface, logger *zap.SugaredLogger) {
_, err := flaggerClient.FlaggerV1alpha3().Canaries(namespace).List(metav1.ListOptions{Limit: 1})
if err != nil {
logger.Fatalf("Canary CRD is not registered %v", err)
}
_, err = flaggerClient.FlaggerV1alpha1().MetricTemplates(namespace).List(metav1.ListOptions{Limit: 1})
if err != nil {
logger.Fatalf("MetricTemplate CRD is not registered %v", err)
}
}
func verifyKubernetesVersion(kubeClient kubernetes.Interface, logger *zap.SugaredLogger) {
ver, err := kubeClient.Discovery().ServerVersion()
if err != nil {
logger.Fatalf("Error calling Kubernetes API: %v", err)
}
k8sVersionConstraint := "^1.11.0"
// We append -alpha.1 to the end of our version constraint so that prebuilds of later versions
// are considered valid for our purposes, as well as some managed solutions like EKS where they provide
// a version like `v1.12.6-eks-d69f1b`. It doesn't matter what the prelease value is here, just that it
// exists in our constraint.
semverConstraint, err := semver.NewConstraint(k8sVersionConstraint + "-alpha.1")
if err != nil {
logger.Fatalf("Error parsing kubernetes version constraint: %v", err)
}
k8sSemver, err := semver.NewVersion(ver.GitVersion)
if err != nil {
logger.Fatalf("Error parsing kubernetes version as a semantic version: %v", err)
}
if !semverConstraint.Check(k8sSemver) {
logger.Fatalf("Unsupported version of kubernetes detected. Expected %s, got %v", k8sVersionConstraint, ver)
}
logger.Infof("Connected to Kubernetes API %s", ver)
}

View File

@@ -30,7 +30,7 @@ chmod +x ${CODEGEN_PKG}/generate-groups.sh
${CODEGEN_PKG}/generate-groups.sh all \
github.com/weaveworks/flagger/pkg/client github.com/weaveworks/flagger/pkg/apis \
"flagger:v1alpha3 appmesh:v1beta1 istio:v1alpha3 smi:v1alpha1 gloo:v1 projectcontour:v1" \
"flagger:v1alpha3 flagger:v1alpha1 appmesh:v1beta1 istio:v1alpha3 smi:v1alpha1 gloo:v1 projectcontour:v1" \
--output-base "${TEMP_DIR}" \
--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt

View File

@@ -215,18 +215,29 @@ spec:
required: ["name", "threshold"]
properties:
name:
description: Name of the Prometheus metric
description: Name of the metric
type: string
interval:
description: Interval of the promql query
description: Interval of the query
type: string
pattern: "^[0-9]+(m|s)"
threshold:
description: Max scalar value accepted for this metric
description: Max value accepted for this metric
type: number
query:
description: Prometheus query
type: string
templateRef:
description: Metric template reference
type: object
required: ["name"]
properties:
name:
description: Name of this metric template
type: string
namespace:
description: Namespace of this metric template
type: string
webhooks:
description: Webhook list for this canary
type: array
@@ -322,3 +333,62 @@ spec:
type:
description: Type of this condition
type: string
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: metrictemplates.flagger.app
annotations:
helm.sh/resource-policy: keep
spec:
group: flagger.app
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
names:
plural: metrictemplates
singular: metrictemplate
kind: MetricTemplate
categories:
- all
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
properties:
spec:
required:
- provider
- query
properties:
provider:
description: Provider of this metric template
type: object
required:
- type
- address
properties:
type:
description: Type of this provider
type: string
enum:
- prometheus
- influxdb
address:
description: API address of this provider
type: string
secretRef:
description: Kubernetes secret reference containing the provider credentials
type: object
required:
- name
properties:
name:
description: Name of the Kubernetes secret
type: string
query:
description: Query of this metric template
type: string

View File

@@ -32,6 +32,8 @@ rules:
resources:
- canaries
- canaries/status
- metrictemplates
- metrictemplates/status
verbs: ["*"]
- apiGroups:
- networking.istio.io

View File

@@ -0,0 +1,21 @@
/*
Copyright The Flagger 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.
*/
// +k8s:deepcopy-gen=package
// Package v1alpha1 is the v1alpha1 version of the API.
// +groupName=flagger.app
package v1alpha1

View File

@@ -0,0 +1,120 @@
/*
Copyright The Flagger 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 (
"text/template"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
MetricTemplateKind = "MetricTemplate"
MetricInterval = "1m"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MetricTemplate is a specification for a canary analysis metric
type MetricTemplate struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MetricTemplateSpec `json:"spec"`
Status MetricTemplateStatus `json:"status"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MetricTemplateList is a list of metric template resources
type MetricTemplateList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []MetricTemplate `json:"items"`
}
// MetricTemplateSpec is the spec for a metric template resource
type MetricTemplateSpec struct {
// Provider of this metric
Provider MetricTemplateProvider `json:"provider,omitempty"`
// Query template for this metric
Query string `json:"query,omitempty"`
}
// MetricProvider is the spec for a MetricProvider resource
type MetricTemplateProvider struct {
// Type of provider
Type string `json:"type,omitempty"`
// HTTP(S) address of this provider
Address string `json:"address,omitempty"`
// Secret reference containing the provider credentials
// +optional
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
}
// MetricTemplateModel is the query template model
type MetricTemplateModel struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Target string `json:"target"`
Service string `json:"service"`
Ingress string `json:"ingress"`
Interval string `json:"interval"`
}
// TemplateFunctions returns a map of functions, one for each model field
func (mtm *MetricTemplateModel) TemplateFunctions() template.FuncMap {
return template.FuncMap{
"name": func() string { return mtm.Name },
"namespace": func() string { return mtm.Namespace },
"target": func() string { return mtm.Target },
"service": func() string { return mtm.Service },
"ingress": func() string { return mtm.Ingress },
"interval": func() string { return mtm.Interval },
}
}
type MetricTemplateStatus struct {
// Conditions of this status
Conditions []MetricTemplateCondition `json:"conditions,omitempty"`
}
type MetricTemplateCondition struct {
// Type of this condition
Type string `json:"type"`
// Status of this condition
Status corev1.ConditionStatus `json:"status"`
// LastUpdateTime of this condition
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
// LastTransitionTime of this condition
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
// Reason for the current status of this condition
Reason string `json:"reason,omitempty"`
// Message associated with this condition
Message string `json:"message,omitempty"`
}

View File

@@ -0,0 +1,53 @@
/*
Copyright The Flagger 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"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/weaveworks/flagger/pkg/apis/flagger"
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: flagger.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 takes an unqualified resource and returns a Group qualified GroupResource
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,
&MetricTemplate{},
&MetricTemplateList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@@ -0,0 +1,182 @@
// +build !ignore_autogenerated
/*
Copyright The Flagger 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 (
v1 "k8s.io/api/core/v1"
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 *MetricTemplate) DeepCopyInto(out *MetricTemplate) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplate.
func (in *MetricTemplate) DeepCopy() *MetricTemplate {
if in == nil {
return nil
}
out := new(MetricTemplate)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MetricTemplate) 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 *MetricTemplateCondition) DeepCopyInto(out *MetricTemplateCondition) {
*out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateCondition.
func (in *MetricTemplateCondition) DeepCopy() *MetricTemplateCondition {
if in == nil {
return nil
}
out := new(MetricTemplateCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MetricTemplateList) DeepCopyInto(out *MetricTemplateList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]MetricTemplate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateList.
func (in *MetricTemplateList) DeepCopy() *MetricTemplateList {
if in == nil {
return nil
}
out := new(MetricTemplateList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MetricTemplateList) 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 *MetricTemplateModel) DeepCopyInto(out *MetricTemplateModel) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateModel.
func (in *MetricTemplateModel) DeepCopy() *MetricTemplateModel {
if in == nil {
return nil
}
out := new(MetricTemplateModel)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MetricTemplateProvider) DeepCopyInto(out *MetricTemplateProvider) {
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(v1.LocalObjectReference)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateProvider.
func (in *MetricTemplateProvider) DeepCopy() *MetricTemplateProvider {
if in == nil {
return nil
}
out := new(MetricTemplateProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MetricTemplateSpec) DeepCopyInto(out *MetricTemplateSpec) {
*out = *in
in.Provider.DeepCopyInto(&out.Provider)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateSpec.
func (in *MetricTemplateSpec) DeepCopy() *MetricTemplateSpec {
if in == nil {
return nil
}
out := new(MetricTemplateSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MetricTemplateStatus) DeepCopyInto(out *MetricTemplateStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]MetricTemplateCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateStatus.
func (in *MetricTemplateStatus) DeepCopy() *MetricTemplateStatus {
if in == nil {
return nil
}
out := new(MetricTemplateStatus)
in.DeepCopyInto(out)
return out
}

View File

@@ -135,6 +135,14 @@ type CanaryMetric struct {
Threshold float64 `json:"threshold"`
// +optional
Query string `json:"query,omitempty"`
// +optional
TemplateRef *MetricTemplateRef `json:"templateRef,omitempty"`
}
type MetricTemplateRef struct {
Name string `json:"name"`
// +optional
Namespace string `json:"namespace,omitempty"`
}
// HookType can be pre, post or during rollout

View File

@@ -60,7 +60,9 @@ func (in *CanaryAnalysis) DeepCopyInto(out *CanaryAnalysis) {
if in.Metrics != nil {
in, out := &in.Metrics, &out.Metrics
*out = make([]CanaryMetric, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Webhooks != nil {
in, out := &in.Webhooks, &out.Webhooks
@@ -143,6 +145,11 @@ func (in *CanaryList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CanaryMetric) DeepCopyInto(out *CanaryMetric) {
*out = *in
if in.TemplateRef != nil {
in, out := &in.TemplateRef, &out.TemplateRef
*out = new(MetricTemplateRef)
**out = **in
}
return
}
@@ -338,3 +345,19 @@ func (in *CanaryWebhookPayload) DeepCopy() *CanaryWebhookPayload {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MetricTemplateRef) DeepCopyInto(out *MetricTemplateRef) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricTemplateRef.
func (in *MetricTemplateRef) DeepCopy() *MetricTemplateRef {
if in == nil {
return nil
}
out := new(MetricTemplateRef)
in.DeepCopyInto(out)
return out
}

View File

@@ -22,6 +22,7 @@ import (
"fmt"
appmeshv1beta1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"
flaggerv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1"
flaggerv1alpha3 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha3"
gloov1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
networkingv1alpha3 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/istio/v1alpha3"
@@ -36,6 +37,7 @@ type Interface interface {
Discovery() discovery.DiscoveryInterface
AppmeshV1beta1() appmeshv1beta1.AppmeshV1beta1Interface
FlaggerV1alpha3() flaggerv1alpha3.FlaggerV1alpha3Interface
FlaggerV1alpha1() flaggerv1alpha1.FlaggerV1alpha1Interface
GlooV1() gloov1.GlooV1Interface
NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3Interface
ProjectcontourV1() projectcontourv1.ProjectcontourV1Interface
@@ -48,6 +50,7 @@ type Clientset struct {
*discovery.DiscoveryClient
appmeshV1beta1 *appmeshv1beta1.AppmeshV1beta1Client
flaggerV1alpha3 *flaggerv1alpha3.FlaggerV1alpha3Client
flaggerV1alpha1 *flaggerv1alpha1.FlaggerV1alpha1Client
glooV1 *gloov1.GlooV1Client
networkingV1alpha3 *networkingv1alpha3.NetworkingV1alpha3Client
projectcontourV1 *projectcontourv1.ProjectcontourV1Client
@@ -64,6 +67,11 @@ func (c *Clientset) FlaggerV1alpha3() flaggerv1alpha3.FlaggerV1alpha3Interface {
return c.flaggerV1alpha3
}
// FlaggerV1alpha1 retrieves the FlaggerV1alpha1Client
func (c *Clientset) FlaggerV1alpha1() flaggerv1alpha1.FlaggerV1alpha1Interface {
return c.flaggerV1alpha1
}
// GlooV1 retrieves the GlooV1Client
func (c *Clientset) GlooV1() gloov1.GlooV1Interface {
return c.glooV1
@@ -113,6 +121,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
if err != nil {
return nil, err
}
cs.flaggerV1alpha1, err = flaggerv1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.glooV1, err = gloov1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
@@ -143,6 +155,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
var cs Clientset
cs.appmeshV1beta1 = appmeshv1beta1.NewForConfigOrDie(c)
cs.flaggerV1alpha3 = flaggerv1alpha3.NewForConfigOrDie(c)
cs.flaggerV1alpha1 = flaggerv1alpha1.NewForConfigOrDie(c)
cs.glooV1 = gloov1.NewForConfigOrDie(c)
cs.networkingV1alpha3 = networkingv1alpha3.NewForConfigOrDie(c)
cs.projectcontourV1 = projectcontourv1.NewForConfigOrDie(c)
@@ -157,6 +170,7 @@ func New(c rest.Interface) *Clientset {
var cs Clientset
cs.appmeshV1beta1 = appmeshv1beta1.New(c)
cs.flaggerV1alpha3 = flaggerv1alpha3.New(c)
cs.flaggerV1alpha1 = flaggerv1alpha1.New(c)
cs.glooV1 = gloov1.New(c)
cs.networkingV1alpha3 = networkingv1alpha3.New(c)
cs.projectcontourV1 = projectcontourv1.New(c)

View File

@@ -22,6 +22,8 @@ import (
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
appmeshv1beta1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1"
fakeappmeshv1beta1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/appmesh/v1beta1/fake"
flaggerv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1"
fakeflaggerv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1/fake"
flaggerv1alpha3 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha3"
fakeflaggerv1alpha3 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha3/fake"
gloov1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/gloo/v1"
@@ -96,6 +98,11 @@ func (c *Clientset) FlaggerV1alpha3() flaggerv1alpha3.FlaggerV1alpha3Interface {
return &fakeflaggerv1alpha3.FakeFlaggerV1alpha3{Fake: &c.Fake}
}
// FlaggerV1alpha1 retrieves the FlaggerV1alpha1Client
func (c *Clientset) FlaggerV1alpha1() flaggerv1alpha1.FlaggerV1alpha1Interface {
return &fakeflaggerv1alpha1.FakeFlaggerV1alpha1{Fake: &c.Fake}
}
// GlooV1 retrieves the GlooV1Client
func (c *Clientset) GlooV1() gloov1.GlooV1Interface {
return &fakegloov1.FakeGlooV1{Fake: &c.Fake}

View File

@@ -20,6 +20,7 @@ package fake
import (
appmeshv1beta1 "github.com/weaveworks/flagger/pkg/apis/appmesh/v1beta1"
flaggerv1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1alpha3 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
gloov1 "github.com/weaveworks/flagger/pkg/apis/gloo/v1"
networkingv1alpha3 "github.com/weaveworks/flagger/pkg/apis/istio/v1alpha3"
@@ -38,6 +39,7 @@ var parameterCodec = runtime.NewParameterCodec(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
appmeshv1beta1.AddToScheme,
flaggerv1alpha3.AddToScheme,
flaggerv1alpha1.AddToScheme,
gloov1.AddToScheme,
networkingv1alpha3.AddToScheme,
projectcontourv1.AddToScheme,

View File

@@ -20,6 +20,7 @@ package scheme
import (
appmeshv1beta1 "github.com/weaveworks/flagger/pkg/apis/appmesh/v1beta1"
flaggerv1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1alpha3 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
gloov1 "github.com/weaveworks/flagger/pkg/apis/gloo/v1"
networkingv1alpha3 "github.com/weaveworks/flagger/pkg/apis/istio/v1alpha3"
@@ -38,6 +39,7 @@ var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
appmeshv1beta1.AddToScheme,
flaggerv1alpha3.AddToScheme,
flaggerv1alpha1.AddToScheme,
gloov1.AddToScheme,
networkingv1alpha3.AddToScheme,
projectcontourv1.AddToScheme,

View File

@@ -0,0 +1,20 @@
/*
Copyright The Flagger 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

View File

@@ -0,0 +1,20 @@
/*
Copyright The Flagger 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

View File

@@ -0,0 +1,40 @@
/*
Copyright The Flagger 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/weaveworks/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeFlaggerV1alpha1 struct {
*testing.Fake
}
func (c *FakeFlaggerV1alpha1) MetricTemplates(namespace string) v1alpha1.MetricTemplateInterface {
return &FakeMetricTemplates{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeFlaggerV1alpha1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}

View File

@@ -0,0 +1,140 @@
/*
Copyright The Flagger 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/weaveworks/flagger/pkg/apis/flagger/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"
)
// FakeMetricTemplates implements MetricTemplateInterface
type FakeMetricTemplates struct {
Fake *FakeFlaggerV1alpha1
ns string
}
var metrictemplatesResource = schema.GroupVersionResource{Group: "flagger.app", Version: "v1alpha1", Resource: "metrictemplates"}
var metrictemplatesKind = schema.GroupVersionKind{Group: "flagger.app", Version: "v1alpha1", Kind: "MetricTemplate"}
// Get takes name of the metricTemplate, and returns the corresponding metricTemplate object, and an error if there is any.
func (c *FakeMetricTemplates) Get(name string, options v1.GetOptions) (result *v1alpha1.MetricTemplate, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(metrictemplatesResource, c.ns, name), &v1alpha1.MetricTemplate{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MetricTemplate), err
}
// List takes label and field selectors, and returns the list of MetricTemplates that match those selectors.
func (c *FakeMetricTemplates) List(opts v1.ListOptions) (result *v1alpha1.MetricTemplateList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(metrictemplatesResource, metrictemplatesKind, c.ns, opts), &v1alpha1.MetricTemplateList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.MetricTemplateList{ListMeta: obj.(*v1alpha1.MetricTemplateList).ListMeta}
for _, item := range obj.(*v1alpha1.MetricTemplateList).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 metricTemplates.
func (c *FakeMetricTemplates) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(metrictemplatesResource, c.ns, opts))
}
// Create takes the representation of a metricTemplate and creates it. Returns the server's representation of the metricTemplate, and an error, if there is any.
func (c *FakeMetricTemplates) Create(metricTemplate *v1alpha1.MetricTemplate) (result *v1alpha1.MetricTemplate, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(metrictemplatesResource, c.ns, metricTemplate), &v1alpha1.MetricTemplate{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MetricTemplate), err
}
// Update takes the representation of a metricTemplate and updates it. Returns the server's representation of the metricTemplate, and an error, if there is any.
func (c *FakeMetricTemplates) Update(metricTemplate *v1alpha1.MetricTemplate) (result *v1alpha1.MetricTemplate, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(metrictemplatesResource, c.ns, metricTemplate), &v1alpha1.MetricTemplate{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MetricTemplate), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeMetricTemplates) UpdateStatus(metricTemplate *v1alpha1.MetricTemplate) (*v1alpha1.MetricTemplate, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(metrictemplatesResource, "status", c.ns, metricTemplate), &v1alpha1.MetricTemplate{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MetricTemplate), err
}
// Delete takes name of the metricTemplate and deletes it. Returns an error if one occurs.
func (c *FakeMetricTemplates) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(metrictemplatesResource, c.ns, name), &v1alpha1.MetricTemplate{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeMetricTemplates) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(metrictemplatesResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.MetricTemplateList{})
return err
}
// Patch applies the patch and returns the patched metricTemplate.
func (c *FakeMetricTemplates) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.MetricTemplate, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(metrictemplatesResource, c.ns, name, pt, data, subresources...), &v1alpha1.MetricTemplate{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MetricTemplate), err
}

View File

@@ -0,0 +1,89 @@
/*
Copyright The Flagger 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 (
v1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type FlaggerV1alpha1Interface interface {
RESTClient() rest.Interface
MetricTemplatesGetter
}
// FlaggerV1alpha1Client is used to interact with features provided by the flagger.app group.
type FlaggerV1alpha1Client struct {
restClient rest.Interface
}
func (c *FlaggerV1alpha1Client) MetricTemplates(namespace string) MetricTemplateInterface {
return newMetricTemplates(c, namespace)
}
// NewForConfig creates a new FlaggerV1alpha1Client for the given config.
func NewForConfig(c *rest.Config) (*FlaggerV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &FlaggerV1alpha1Client{client}, nil
}
// NewForConfigOrDie creates a new FlaggerV1alpha1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *FlaggerV1alpha1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new FlaggerV1alpha1Client for the given RESTClient.
func New(c rest.Interface) *FlaggerV1alpha1Client {
return &FlaggerV1alpha1Client{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 *FlaggerV1alpha1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}

View File

@@ -0,0 +1,21 @@
/*
Copyright The Flagger 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 MetricTemplateExpansion interface{}

View File

@@ -0,0 +1,191 @@
/*
Copyright The Flagger 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 (
"time"
v1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
scheme "github.com/weaveworks/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"
)
// MetricTemplatesGetter has a method to return a MetricTemplateInterface.
// A group's client should implement this interface.
type MetricTemplatesGetter interface {
MetricTemplates(namespace string) MetricTemplateInterface
}
// MetricTemplateInterface has methods to work with MetricTemplate resources.
type MetricTemplateInterface interface {
Create(*v1alpha1.MetricTemplate) (*v1alpha1.MetricTemplate, error)
Update(*v1alpha1.MetricTemplate) (*v1alpha1.MetricTemplate, error)
UpdateStatus(*v1alpha1.MetricTemplate) (*v1alpha1.MetricTemplate, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.MetricTemplate, error)
List(opts v1.ListOptions) (*v1alpha1.MetricTemplateList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.MetricTemplate, err error)
MetricTemplateExpansion
}
// metricTemplates implements MetricTemplateInterface
type metricTemplates struct {
client rest.Interface
ns string
}
// newMetricTemplates returns a MetricTemplates
func newMetricTemplates(c *FlaggerV1alpha1Client, namespace string) *metricTemplates {
return &metricTemplates{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the metricTemplate, and returns the corresponding metricTemplate object, and an error if there is any.
func (c *metricTemplates) Get(name string, options v1.GetOptions) (result *v1alpha1.MetricTemplate, err error) {
result = &v1alpha1.MetricTemplate{}
err = c.client.Get().
Namespace(c.ns).
Resource("metrictemplates").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of MetricTemplates that match those selectors.
func (c *metricTemplates) List(opts v1.ListOptions) (result *v1alpha1.MetricTemplateList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.MetricTemplateList{}
err = c.client.Get().
Namespace(c.ns).
Resource("metrictemplates").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested metricTemplates.
func (c *metricTemplates) Watch(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().
Namespace(c.ns).
Resource("metrictemplates").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a metricTemplate and creates it. Returns the server's representation of the metricTemplate, and an error, if there is any.
func (c *metricTemplates) Create(metricTemplate *v1alpha1.MetricTemplate) (result *v1alpha1.MetricTemplate, err error) {
result = &v1alpha1.MetricTemplate{}
err = c.client.Post().
Namespace(c.ns).
Resource("metrictemplates").
Body(metricTemplate).
Do().
Into(result)
return
}
// Update takes the representation of a metricTemplate and updates it. Returns the server's representation of the metricTemplate, and an error, if there is any.
func (c *metricTemplates) Update(metricTemplate *v1alpha1.MetricTemplate) (result *v1alpha1.MetricTemplate, err error) {
result = &v1alpha1.MetricTemplate{}
err = c.client.Put().
Namespace(c.ns).
Resource("metrictemplates").
Name(metricTemplate.Name).
Body(metricTemplate).
Do().
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *metricTemplates) UpdateStatus(metricTemplate *v1alpha1.MetricTemplate) (result *v1alpha1.MetricTemplate, err error) {
result = &v1alpha1.MetricTemplate{}
err = c.client.Put().
Namespace(c.ns).
Resource("metrictemplates").
Name(metricTemplate.Name).
SubResource("status").
Body(metricTemplate).
Do().
Into(result)
return
}
// Delete takes name of the metricTemplate and deletes it. Returns an error if one occurs.
func (c *metricTemplates) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("metrictemplates").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *metricTemplates) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("metrictemplates").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched metricTemplate.
func (c *metricTemplates) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.MetricTemplate, err error) {
result = &v1alpha1.MetricTemplate{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("metrictemplates").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -19,6 +19,7 @@ limitations under the License.
package flagger
import (
v1alpha1 "github.com/weaveworks/flagger/pkg/client/informers/externalversions/flagger/v1alpha1"
v1alpha3 "github.com/weaveworks/flagger/pkg/client/informers/externalversions/flagger/v1alpha3"
internalinterfaces "github.com/weaveworks/flagger/pkg/client/informers/externalversions/internalinterfaces"
)
@@ -27,6 +28,8 @@ import (
type Interface interface {
// V1alpha3 provides access to shared informers for resources in V1alpha3.
V1alpha3() v1alpha3.Interface
// V1alpha1 provides access to shared informers for resources in V1alpha1.
V1alpha1() v1alpha1.Interface
}
type group struct {
@@ -44,3 +47,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
func (g *group) V1alpha3() v1alpha3.Interface {
return v1alpha3.New(g.factory, g.namespace, g.tweakListOptions)
}
// V1alpha1 returns a new v1alpha1.Interface.
func (g *group) V1alpha1() v1alpha1.Interface {
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
}

View File

@@ -0,0 +1,45 @@
/*
Copyright The Flagger 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/weaveworks/flagger/pkg/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// MetricTemplates returns a MetricTemplateInformer.
MetricTemplates() MetricTemplateInformer
}
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}
}
// MetricTemplates returns a MetricTemplateInformer.
func (v *version) MetricTemplates() MetricTemplateInformer {
return &metricTemplateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}

View File

@@ -0,0 +1,89 @@
/*
Copyright The Flagger 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 (
time "time"
flaggerv1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
versioned "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
internalinterfaces "github.com/weaveworks/flagger/pkg/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/weaveworks/flagger/pkg/client/listers/flagger/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"
)
// MetricTemplateInformer provides access to a shared informer and lister for
// MetricTemplates.
type MetricTemplateInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.MetricTemplateLister
}
type metricTemplateInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewMetricTemplateInformer constructs a new informer for MetricTemplate 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 NewMetricTemplateInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredMetricTemplateInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredMetricTemplateInformer constructs a new informer for MetricTemplate 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 NewFilteredMetricTemplateInformer(client versioned.Interface, namespace string, 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.FlaggerV1alpha1().MetricTemplates(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.FlaggerV1alpha1().MetricTemplates(namespace).Watch(options)
},
},
&flaggerv1alpha1.MetricTemplate{},
resyncPeriod,
indexers,
)
}
func (f *metricTemplateInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredMetricTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *metricTemplateInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&flaggerv1alpha1.MetricTemplate{}, f.defaultInformer)
}
func (f *metricTemplateInformer) Lister() v1alpha1.MetricTemplateLister {
return v1alpha1.NewMetricTemplateLister(f.Informer().GetIndexer())
}

View File

@@ -22,11 +22,12 @@ import (
"fmt"
v1beta1 "github.com/weaveworks/flagger/pkg/apis/appmesh/v1beta1"
v1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
v1alpha3 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
v1 "github.com/weaveworks/flagger/pkg/apis/gloo/v1"
istiov1alpha3 "github.com/weaveworks/flagger/pkg/apis/istio/v1alpha3"
projectcontourv1 "github.com/weaveworks/flagger/pkg/apis/projectcontour/v1"
v1alpha1 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha1"
smiv1alpha1 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha1"
schema "k8s.io/apimachinery/pkg/runtime/schema"
cache "k8s.io/client-go/tools/cache"
)
@@ -65,6 +66,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
case v1beta1.SchemeGroupVersion.WithResource("virtualservices"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Appmesh().V1beta1().VirtualServices().Informer()}, nil
// Group=flagger.app, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("metrictemplates"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Flagger().V1alpha1().MetricTemplates().Informer()}, nil
// Group=flagger.app, Version=v1alpha3
case v1alpha3.SchemeGroupVersion.WithResource("canaries"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Flagger().V1alpha3().Canaries().Informer()}, nil
@@ -84,7 +89,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
}

View File

@@ -0,0 +1,27 @@
/*
Copyright The Flagger 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
// MetricTemplateListerExpansion allows custom methods to be added to
// MetricTemplateLister.
type MetricTemplateListerExpansion interface{}
// MetricTemplateNamespaceListerExpansion allows custom methods to be added to
// MetricTemplateNamespaceLister.
type MetricTemplateNamespaceListerExpansion interface{}

View File

@@ -0,0 +1,94 @@
/*
Copyright The Flagger 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/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// MetricTemplateLister helps list MetricTemplates.
type MetricTemplateLister interface {
// List lists all MetricTemplates in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.MetricTemplate, err error)
// MetricTemplates returns an object that can list and get MetricTemplates.
MetricTemplates(namespace string) MetricTemplateNamespaceLister
MetricTemplateListerExpansion
}
// metricTemplateLister implements the MetricTemplateLister interface.
type metricTemplateLister struct {
indexer cache.Indexer
}
// NewMetricTemplateLister returns a new MetricTemplateLister.
func NewMetricTemplateLister(indexer cache.Indexer) MetricTemplateLister {
return &metricTemplateLister{indexer: indexer}
}
// List lists all MetricTemplates in the indexer.
func (s *metricTemplateLister) List(selector labels.Selector) (ret []*v1alpha1.MetricTemplate, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.MetricTemplate))
})
return ret, err
}
// MetricTemplates returns an object that can list and get MetricTemplates.
func (s *metricTemplateLister) MetricTemplates(namespace string) MetricTemplateNamespaceLister {
return metricTemplateNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// MetricTemplateNamespaceLister helps list and get MetricTemplates.
type MetricTemplateNamespaceLister interface {
// List lists all MetricTemplates in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.MetricTemplate, err error)
// Get retrieves the MetricTemplate from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.MetricTemplate, error)
MetricTemplateNamespaceListerExpansion
}
// metricTemplateNamespaceLister implements the MetricTemplateNamespaceLister
// interface.
type metricTemplateNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all MetricTemplates in the indexer for a given namespace.
func (s metricTemplateNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.MetricTemplate, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.MetricTemplate))
})
return ret, err
}
// Get retrieves the MetricTemplate from the indexer for a given namespace and name.
func (s metricTemplateNamespaceLister) Get(name string) (*v1alpha1.MetricTemplate, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("metrictemplate"), name)
}
return obj.(*v1alpha1.MetricTemplate), nil
}

View File

@@ -26,6 +26,7 @@ import (
flaggerinformers "github.com/weaveworks/flagger/pkg/client/informers/externalversions/flagger/v1alpha3"
flaggerlisters "github.com/weaveworks/flagger/pkg/client/listers/flagger/v1alpha3"
"github.com/weaveworks/flagger/pkg/metrics"
"github.com/weaveworks/flagger/pkg/metrics/observers"
"github.com/weaveworks/flagger/pkg/notifier"
"github.com/weaveworks/flagger/pkg/router"
)
@@ -49,7 +50,7 @@ type Controller struct {
notifier notifier.Interface
canaryFactory *canary.Factory
routerFactory *router.Factory
observerFactory *metrics.Factory
observerFactory *observers.Factory
meshProvider string
eventWebhook string
}
@@ -64,7 +65,7 @@ func NewController(
notifier notifier.Interface,
canaryFactory *canary.Factory,
routerFactory *router.Factory,
observerFactory *metrics.Factory,
observerFactory *observers.Factory,
meshProvider string,
version string,
eventWebhook string,

View File

@@ -1,6 +1,7 @@
package controller
import (
"github.com/weaveworks/flagger/pkg/metrics/observers"
"sync"
"time"
@@ -73,7 +74,7 @@ func SetupMocks(c *flaggerv1.Canary) Mocks {
rf := router.NewFactory(nil, kubeClient, flaggerClient, "annotationsPrefix", logger, flaggerClient)
// init observer
observerFactory, _ := metrics.NewFactory("fake", 5*time.Second)
observerFactory, _ := observers.NewFactory("fake")
// init canary factory
configTracker := canary.ConfigTracker{

View File

@@ -7,9 +7,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
flaggerv1alpha1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1alpha3 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
"github.com/weaveworks/flagger/pkg/canary"
"github.com/weaveworks/flagger/pkg/metrics"
"github.com/weaveworks/flagger/pkg/metrics/observers"
"github.com/weaveworks/flagger/pkg/metrics/providers"
"github.com/weaveworks/flagger/pkg/router"
)
@@ -25,7 +27,7 @@ func (c *Controller) scheduleCanaries() {
stats := make(map[string]int)
c.canaries.Range(func(key interface{}, value interface{}) bool {
canary := value.(*flaggerv1.Canary)
canary := value.(*flaggerv1alpha3.Canary)
// format: <name>.<namespace>
name := key.(string)
@@ -196,8 +198,8 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
// reset status
status := flaggerv1.CanaryStatus{
Phase: flaggerv1.CanaryPhaseProgressing,
status := flaggerv1alpha3.CanaryStatus{
Phase: flaggerv1alpha3.CanaryPhaseProgressing,
CanaryWeight: 0,
FailedChecks: 0,
Iterations: 0,
@@ -225,8 +227,8 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
// check if we should rollback
if cd.Status.Phase == flaggerv1.CanaryPhaseProgressing ||
cd.Status.Phase == flaggerv1.CanaryPhaseWaiting {
if cd.Status.Phase == flaggerv1alpha3.CanaryPhaseProgressing ||
cd.Status.Phase == flaggerv1alpha3.CanaryPhaseWaiting {
if ok := c.runRollbackHooks(cd, cd.Status.Phase); ok {
c.recordEventWarningf(cd, "Rolling back %s.%s manual webhook invoked", cd.Name, cd.Namespace)
c.sendNotification(cd, "Rolling back manual webhook invoked", false, true)
@@ -236,7 +238,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
// route all traffic to primary if analysis has succeeded
if cd.Status.Phase == flaggerv1.CanaryPhasePromoting {
if cd.Status.Phase == flaggerv1alpha3.CanaryPhasePromoting {
if provider != "kubernetes" {
c.recordEventInfof(cd, "Routing all traffic to primary")
if err := meshRouter.SetRoutes(cd, 100, 0, false); err != nil {
@@ -247,7 +249,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
// update status phase
if err := canaryController.SetStatusPhase(cd, flaggerv1.CanaryPhaseFinalising); err != nil {
if err := canaryController.SetStatusPhase(cd, flaggerv1alpha3.CanaryPhaseFinalising); err != nil {
c.recordEventWarningf(cd, "%v", err)
return
}
@@ -256,19 +258,19 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
// scale canary to zero if promotion has finished
if cd.Status.Phase == flaggerv1.CanaryPhaseFinalising {
if cd.Status.Phase == flaggerv1alpha3.CanaryPhaseFinalising {
if err := canaryController.Scale(cd, 0); err != nil {
c.recordEventWarningf(cd, "%v", err)
return
}
// set status to succeeded
if err := canaryController.SetStatusPhase(cd, flaggerv1.CanaryPhaseSucceeded); err != nil {
if err := canaryController.SetStatusPhase(cd, flaggerv1alpha3.CanaryPhaseSucceeded); err != nil {
c.recordEventWarningf(cd, "%v", err)
return
}
c.recorder.SetStatus(cd, flaggerv1.CanaryPhaseSucceeded)
c.runPostRolloutHooks(cd, flaggerv1.CanaryPhaseSucceeded)
c.recorder.SetStatus(cd, flaggerv1alpha3.CanaryPhaseSucceeded)
c.runPostRolloutHooks(cd, flaggerv1alpha3.CanaryPhaseSucceeded)
c.recordEventInfof(cd, "Promotion completed! Scaling down %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
c.sendNotification(cd, "Canary analysis completed successfully, promotion finished.",
false, false)
@@ -276,7 +278,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
// check if the number of failed checks reached the threshold
if cd.Status.Phase == flaggerv1.CanaryPhaseProgressing &&
if cd.Status.Phase == flaggerv1alpha3.CanaryPhaseProgressing &&
(!retriable || cd.Status.FailedChecks >= cd.Spec.CanaryAnalysis.Threshold) {
if !retriable {
c.recordEventWarningf(cd, "Rolling back %s.%s progress deadline exceeded %v",
@@ -349,7 +351,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
}
func (c *Controller) runCanary(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string, mirrored bool, canaryWeight int, primaryWeight int, maxWeight int) {
func (c *Controller) runCanary(canary *flaggerv1alpha3.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string, mirrored bool, canaryWeight int, primaryWeight int, maxWeight int) {
primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
// increase traffic weight
@@ -412,14 +414,14 @@ func (c *Controller) runCanary(canary *flaggerv1.Canary, canaryController canary
}
// update status phase
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhasePromoting); err != nil {
if err := canaryController.SetStatusPhase(canary, flaggerv1alpha3.CanaryPhasePromoting); err != nil {
c.recordEventWarningf(canary, "%v", err)
return
}
}
}
func (c *Controller) runAB(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string) {
func (c *Controller) runAB(canary *flaggerv1alpha3.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string) {
primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
// route traffic to canary and increment iterations
@@ -454,14 +456,14 @@ func (c *Controller) runAB(canary *flaggerv1.Canary, canaryController canary.Con
}
// update status phase
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhasePromoting); err != nil {
if err := canaryController.SetStatusPhase(canary, flaggerv1alpha3.CanaryPhasePromoting); err != nil {
c.recordEventWarningf(canary, "%v", err)
return
}
}
}
func (c *Controller) runBlueGreen(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string, mirrored bool) {
func (c *Controller) runBlueGreen(canary *flaggerv1alpha3.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string, mirrored bool) {
primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
// increment iterations
@@ -522,7 +524,7 @@ func (c *Controller) runBlueGreen(canary *flaggerv1.Canary, canaryController can
}
// update status phase
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhasePromoting); err != nil {
if err := canaryController.SetStatusPhase(canary, flaggerv1alpha3.CanaryPhasePromoting); err != nil {
c.recordEventWarningf(canary, "%v", err)
return
}
@@ -530,7 +532,7 @@ func (c *Controller) runBlueGreen(canary *flaggerv1.Canary, canaryController can
}
func (c *Controller) shouldSkipAnalysis(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, primaryWeight int, canaryWeight int) bool {
func (c *Controller) shouldSkipAnalysis(canary *flaggerv1alpha3.Canary, canaryController canary.Controller, meshRouter router.Interface, primaryWeight int, canaryWeight int) bool {
if !canary.Spec.SkipAnalysis {
return false
}
@@ -559,13 +561,13 @@ func (c *Controller) shouldSkipAnalysis(canary *flaggerv1.Canary, canaryControll
}
// update status phase
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseSucceeded); err != nil {
if err := canaryController.SetStatusPhase(canary, flaggerv1alpha3.CanaryPhaseSucceeded); err != nil {
c.recordEventWarningf(canary, "%v", err)
return false
}
// notify
c.recorder.SetStatus(canary, flaggerv1.CanaryPhaseSucceeded)
c.recorder.SetStatus(canary, flaggerv1alpha3.CanaryPhaseSucceeded)
c.recordEventInfof(canary, "Promotion completed! Canary analysis was skipped for %s.%s",
canary.Spec.TargetRef.Name, canary.Namespace)
c.sendNotification(canary, "Canary analysis was skipped, promotion finished.",
@@ -574,13 +576,13 @@ func (c *Controller) shouldSkipAnalysis(canary *flaggerv1.Canary, canaryControll
return true
}
func (c *Controller) shouldAdvance(canary *flaggerv1.Canary, canaryController canary.Controller) (bool, error) {
func (c *Controller) shouldAdvance(canary *flaggerv1alpha3.Canary, canaryController canary.Controller) (bool, error) {
if canary.Status.LastAppliedSpec == "" ||
canary.Status.Phase == flaggerv1.CanaryPhaseInitializing ||
canary.Status.Phase == flaggerv1.CanaryPhaseProgressing ||
canary.Status.Phase == flaggerv1.CanaryPhaseWaiting ||
canary.Status.Phase == flaggerv1.CanaryPhasePromoting ||
canary.Status.Phase == flaggerv1.CanaryPhaseFinalising {
canary.Status.Phase == flaggerv1alpha3.CanaryPhaseInitializing ||
canary.Status.Phase == flaggerv1alpha3.CanaryPhaseProgressing ||
canary.Status.Phase == flaggerv1alpha3.CanaryPhaseWaiting ||
canary.Status.Phase == flaggerv1alpha3.CanaryPhasePromoting ||
canary.Status.Phase == flaggerv1alpha3.CanaryPhaseFinalising {
return true, nil
}
@@ -601,20 +603,20 @@ func (c *Controller) shouldAdvance(canary *flaggerv1.Canary, canaryController ca
}
func (c *Controller) checkCanaryStatus(canary *flaggerv1.Canary, canaryController canary.Controller, shouldAdvance bool) bool {
func (c *Controller) checkCanaryStatus(canary *flaggerv1alpha3.Canary, canaryController canary.Controller, shouldAdvance bool) bool {
c.recorder.SetStatus(canary, canary.Status.Phase)
if canary.Status.Phase == flaggerv1.CanaryPhaseProgressing ||
canary.Status.Phase == flaggerv1.CanaryPhasePromoting ||
canary.Status.Phase == flaggerv1.CanaryPhaseFinalising {
if canary.Status.Phase == flaggerv1alpha3.CanaryPhaseProgressing ||
canary.Status.Phase == flaggerv1alpha3.CanaryPhasePromoting ||
canary.Status.Phase == flaggerv1alpha3.CanaryPhaseFinalising {
return true
}
if canary.Status.Phase == "" || canary.Status.Phase == flaggerv1.CanaryPhaseInitializing {
if err := canaryController.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitialized}); err != nil {
if canary.Status.Phase == "" || canary.Status.Phase == flaggerv1alpha3.CanaryPhaseInitializing {
if err := canaryController.SyncStatus(canary, flaggerv1alpha3.CanaryStatus{Phase: flaggerv1alpha3.CanaryPhaseInitialized}); err != nil {
c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
return false
}
c.recorder.SetStatus(canary, flaggerv1.CanaryPhaseInitialized)
c.recorder.SetStatus(canary, flaggerv1alpha3.CanaryPhaseInitialized)
c.recordEventInfof(canary, "Initialization done! %s.%s", canary.Name, canary.Namespace)
c.sendNotification(canary, "New deployment detected, initialization completed.",
true, false)
@@ -623,7 +625,7 @@ func (c *Controller) checkCanaryStatus(canary *flaggerv1.Canary, canaryControlle
if shouldAdvance {
canaryPhaseProgressing := canary.DeepCopy()
canaryPhaseProgressing.Status.Phase = flaggerv1.CanaryPhaseProgressing
canaryPhaseProgressing.Status.Phase = flaggerv1alpha3.CanaryPhaseProgressing
c.recordEventInfof(canaryPhaseProgressing, "New revision detected! Scaling up %s.%s", canaryPhaseProgressing.Spec.TargetRef.Name, canaryPhaseProgressing.Namespace)
c.sendNotification(canaryPhaseProgressing, "New revision detected, starting canary analysis.",
true, false)
@@ -632,18 +634,18 @@ func (c *Controller) checkCanaryStatus(canary *flaggerv1.Canary, canaryControlle
c.recordEventErrorf(canary, "%v", err)
return false
}
if err := canaryController.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing}); err != nil {
if err := canaryController.SyncStatus(canary, flaggerv1alpha3.CanaryStatus{Phase: flaggerv1alpha3.CanaryPhaseProgressing}); err != nil {
c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
return false
}
c.recorder.SetStatus(canary, flaggerv1.CanaryPhaseProgressing)
c.recorder.SetStatus(canary, flaggerv1alpha3.CanaryPhaseProgressing)
return false
}
return false
}
func (c *Controller) hasCanaryRevisionChanged(canary *flaggerv1.Canary, canaryController canary.Controller) bool {
if canary.Status.Phase == flaggerv1.CanaryPhaseProgressing {
func (c *Controller) hasCanaryRevisionChanged(canary *flaggerv1alpha3.Canary, canaryController canary.Controller) bool {
if canary.Status.Phase == flaggerv1alpha3.CanaryPhaseProgressing {
if diff, _ := canaryController.HasTargetChanged(canary); diff {
return true
}
@@ -654,13 +656,13 @@ func (c *Controller) hasCanaryRevisionChanged(canary *flaggerv1.Canary, canaryCo
return false
}
func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryController canary.Controller) bool {
func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1alpha3.Canary, canaryController canary.Controller) bool {
for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks {
if webhook.Type == flaggerv1.ConfirmRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
if webhook.Type == flaggerv1alpha3.ConfirmRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1alpha3.CanaryPhaseProgressing, webhook)
if err != nil {
if canary.Status.Phase != flaggerv1.CanaryPhaseWaiting {
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseWaiting); err != nil {
if canary.Status.Phase != flaggerv1alpha3.CanaryPhaseWaiting {
if err := canaryController.SetStatusPhase(canary, flaggerv1alpha3.CanaryPhaseWaiting); err != nil {
c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
}
c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for approval %s",
@@ -669,8 +671,8 @@ func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryCont
}
return false
} else {
if canary.Status.Phase == flaggerv1.CanaryPhaseWaiting {
if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseProgressing); err != nil {
if canary.Status.Phase == flaggerv1alpha3.CanaryPhaseWaiting {
if err := canaryController.SetStatusPhase(canary, flaggerv1alpha3.CanaryPhaseProgressing); err != nil {
c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
return false
}
@@ -683,10 +685,10 @@ func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryCont
return true
}
func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1.Canary) bool {
func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1alpha3.Canary) bool {
for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks {
if webhook.Type == flaggerv1.ConfirmPromotionHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
if webhook.Type == flaggerv1alpha3.ConfirmPromotionHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1alpha3.CanaryPhaseProgressing, webhook)
if err != nil {
c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for promotion approval %s",
canary.Name, canary.Namespace, webhook.Name)
@@ -700,10 +702,10 @@ func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1.Canary) bool {
return true
}
func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool {
func (c *Controller) runPreRolloutHooks(canary *flaggerv1alpha3.Canary) bool {
for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks {
if webhook.Type == flaggerv1.PreRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
if webhook.Type == flaggerv1alpha3.PreRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, flaggerv1alpha3.CanaryPhaseProgressing, webhook)
if err != nil {
c.recordEventWarningf(canary, "Halt %s.%s advancement pre-rollout check %s failed %v",
canary.Name, canary.Namespace, webhook.Name, err)
@@ -716,9 +718,9 @@ func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool {
return true
}
func (c *Controller) runPostRolloutHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
func (c *Controller) runPostRolloutHooks(canary *flaggerv1alpha3.Canary, phase flaggerv1alpha3.CanaryPhase) bool {
for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks {
if webhook.Type == flaggerv1.PostRolloutHook {
if webhook.Type == flaggerv1alpha3.PostRolloutHook {
err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
if err != nil {
c.recordEventWarningf(canary, "Post-rollout hook %s failed %v", webhook.Name, err)
@@ -731,9 +733,9 @@ func (c *Controller) runPostRolloutHooks(canary *flaggerv1.Canary, phase flagger
return true
}
func (c *Controller) runRollbackHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
func (c *Controller) runRollbackHooks(canary *flaggerv1alpha3.Canary, phase flaggerv1alpha3.CanaryPhase) bool {
for _, webhook := range canary.Spec.CanaryAnalysis.Webhooks {
if webhook.Type == flaggerv1.RollbackHook {
if webhook.Type == flaggerv1alpha3.RollbackHook {
err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
if err != nil {
c.recordEventInfof(canary, "Rollback hook %s not signaling a rollback", webhook.Name)
@@ -746,11 +748,11 @@ func (c *Controller) runRollbackHooks(canary *flaggerv1.Canary, phase flaggerv1.
return false
}
func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
func (c *Controller) runAnalysis(r *flaggerv1alpha3.Canary) bool {
// run external checks
for _, webhook := range r.Spec.CanaryAnalysis.Webhooks {
if webhook.Type == "" || webhook.Type == flaggerv1.RolloutHook {
err := CallWebhook(r.Name, r.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
if webhook.Type == "" || webhook.Type == flaggerv1alpha3.RolloutHook {
err := CallWebhook(r.Name, r.Namespace, flaggerv1alpha3.CanaryPhaseProgressing, webhook)
if err != nil {
c.recordEventWarningf(r, "Halt %s.%s advancement external check %s failed %v",
r.Name, r.Namespace, webhook.Name, err)
@@ -759,6 +761,20 @@ func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
}
}
ok := c.runBuiltinMetricChecks(r)
if !ok {
return ok
}
ok = c.runMetricChecks(r)
if !ok {
return ok
}
return true
}
func (c *Controller) runBuiltinMetricChecks(r *flaggerv1alpha3.Canary) bool {
// override the global provider if one is specified in the canary spec
var metricsProvider string
// set the metrics provider to Crossover Prometheus when Crossover is the mesh provider
@@ -786,11 +802,9 @@ func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
observerFactory := c.observerFactory
// override the global metrics server if one is specified in the canary spec
metricsServer := c.observerFactory.Client.GetMetricsServer()
if r.Spec.MetricsServer != "" {
metricsServer = r.Spec.MetricsServer
var err error
observerFactory, err = metrics.NewFactory(metricsServer, 5*time.Second)
observerFactory, err = observers.NewFactory(r.Spec.MetricsServer)
if err != nil {
c.recordEventErrorf(r, "Error building Prometheus client for %s %v", r.Spec.MetricsServer, err)
return false
@@ -805,13 +819,13 @@ func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
}
if metric.Name == "request-success-rate" {
val, err := observer.GetRequestSuccessRate(r.Spec.TargetRef.Name, r.Namespace, metric.Interval)
val, err := observer.GetRequestSuccessRate(toMetricModel(r, metric.Interval))
if err != nil {
if strings.Contains(err.Error(), "no values found") {
c.recordEventWarningf(r, "Halt advancement no values found for %s metric %s probably %s.%s is not receiving traffic",
metricsProvider, metric.Name, r.Spec.TargetRef.Name, r.Namespace)
} else {
c.recordEventErrorf(r, "Metrics server %s query failed: %v", metricsServer, err)
c.recordEventErrorf(r, "Prometheus query failed: %v", err)
}
return false
}
@@ -825,13 +839,13 @@ func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
}
if metric.Name == "request-duration" {
val, err := observer.GetRequestDuration(r.Spec.TargetRef.Name, r.Namespace, metric.Interval)
val, err := observer.GetRequestDuration(toMetricModel(r, metric.Interval))
if err != nil {
if strings.Contains(err.Error(), "no values found") {
c.recordEventWarningf(r, "Halt advancement no values found for %s metric %s probably %s.%s is not receiving traffic",
metricsProvider, metric.Name, r.Spec.TargetRef.Name, r.Namespace)
} else {
c.recordEventErrorf(r, "Metrics server %s query failed: %v", metricsServer, err)
c.recordEventErrorf(r, "Prometheus query failed: %v", err)
}
return false
}
@@ -853,7 +867,7 @@ func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
c.recordEventWarningf(r, "Halt advancement no values found for custom metric: %s",
metric.Name)
} else {
c.recordEventErrorf(r, "Metrics server %s query failed for %s: %v", metricsServer, metric.Name, err)
c.recordEventErrorf(r, "Prometheus query failed for %s: %v", metric.Name, err)
}
return false
}
@@ -868,7 +882,88 @@ func (c *Controller) runAnalysis(r *flaggerv1.Canary) bool {
return true
}
func (c *Controller) rollback(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface) {
func (c *Controller) runMetricChecks(r *flaggerv1alpha3.Canary) bool {
for _, metric := range r.Spec.CanaryAnalysis.Metrics {
if metric.TemplateRef != nil {
namespace := r.Namespace
if metric.TemplateRef.Namespace != "" {
namespace = metric.TemplateRef.Namespace
}
template, err := c.flaggerClient.FlaggerV1alpha1().MetricTemplates(namespace).Get(metric.TemplateRef.Name, metav1.GetOptions{})
if err != nil {
c.recordEventErrorf(r, "Metric template %s.%s error: %v", metric.TemplateRef.Name, namespace, err)
return false
}
var credentials map[string][]byte
if template.Spec.Provider.SecretRef != nil {
secret, err := c.kubeClient.CoreV1().Secrets(namespace).Get(template.Spec.Provider.SecretRef.Name, metav1.GetOptions{})
if err != nil {
c.recordEventErrorf(r, "Metric template %s.%s secret %s error: %v",
metric.TemplateRef.Name, namespace, template.Spec.Provider.SecretRef.Name, err)
return false
}
credentials = secret.Data
}
factory := providers.Factory{}
provider, err := factory.Provider(template.Spec.Provider, credentials)
if err != nil {
c.recordEventErrorf(r, "Metric template %s.%s provider %s error: %v",
metric.TemplateRef.Name, namespace, template.Spec.Provider.Type, err)
return false
}
query, err := observers.RenderQuery(template.Spec.Query, toMetricModel(r, metric.Interval))
if err != nil {
c.recordEventErrorf(r, "Metric template %s.%s query render error: %v",
metric.TemplateRef.Name, namespace, err)
return false
}
val, err := provider.RunQuery(query)
if err != nil {
if strings.Contains(err.Error(), "no values found") {
c.recordEventWarningf(r, "Halt advancement no values found for custom metric: %s",
metric.Name)
} else {
c.recordEventErrorf(r, "Metric query failed for %s: %v", metric.Name, err)
}
return false
}
if val > metric.Threshold {
c.recordEventWarningf(r, "Halt %s.%s advancement %s %.2f > %v",
r.Name, r.Namespace, metric.Name, val, metric.Threshold)
return false
}
}
}
return true
}
func toMetricModel(r *flaggerv1alpha3.Canary, interval string) flaggerv1alpha1.MetricTemplateModel {
service := r.Spec.TargetRef.Name
if r.Spec.Service.Name != "" {
service = r.Spec.Service.Name
}
ingress := r.Spec.TargetRef.Name
if r.Spec.IngressRef != nil {
ingress = r.Spec.IngressRef.Name
}
return flaggerv1alpha1.MetricTemplateModel{
Name: r.Name,
Namespace: r.Namespace,
Target: r.Spec.TargetRef.Name,
Service: service,
Ingress: ingress,
Interval: interval,
}
}
func (c *Controller) rollback(canary *flaggerv1alpha3.Canary, canaryController canary.Controller, meshRouter router.Interface) {
if canary.Status.FailedChecks >= canary.Spec.CanaryAnalysis.Threshold {
c.recordEventWarningf(canary, "Rolling back %s.%s failed checks threshold reached %v",
canary.Name, canary.Namespace, canary.Status.FailedChecks)
@@ -885,7 +980,7 @@ func (c *Controller) rollback(canary *flaggerv1.Canary, canaryController canary.
}
canaryPhaseFailed := canary.DeepCopy()
canaryPhaseFailed.Status.Phase = flaggerv1.CanaryPhaseFailed
canaryPhaseFailed.Status.Phase = flaggerv1alpha3.CanaryPhaseFailed
c.recordEventWarningf(canaryPhaseFailed, "Canary failed! Scaling down %s.%s",
canaryPhaseFailed.Name, canaryPhaseFailed.Namespace)
@@ -898,11 +993,11 @@ func (c *Controller) rollback(canary *flaggerv1.Canary, canaryController canary.
}
// mark canary as failed
if err := canaryController.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseFailed, CanaryWeight: 0}); err != nil {
if err := canaryController.SyncStatus(canary, flaggerv1alpha3.CanaryStatus{Phase: flaggerv1alpha3.CanaryPhaseFailed, CanaryWeight: 0}); err != nil {
c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
return
}
c.recorder.SetStatus(canary, flaggerv1.CanaryPhaseFailed)
c.runPostRolloutHooks(canary, flaggerv1.CanaryPhaseFailed)
c.recorder.SetStatus(canary, flaggerv1alpha3.CanaryPhaseFailed)
c.runPostRolloutHooks(canary, flaggerv1alpha3.CanaryPhaseFailed)
}

View File

@@ -1,73 +0,0 @@
package metrics
import (
"time"
)
var appMeshQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ .Namespace }}",
kubernetes_pod_name=~"{{ .Name }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)",
envoy_response_code!~"5.*"
}[{{ .Interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ .Namespace }}",
kubernetes_pod_name=~"{{ .Name }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
kubernetes_namespace="{{ .Namespace }}",
kubernetes_pod_name=~"{{ .Name }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ .Interval }}]
)
) by (le)
)`,
}
type AppMeshObserver struct {
client *PrometheusClient
}
func (ob *AppMeshObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, appMeshQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *AppMeshObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, appMeshQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,85 +0,0 @@
package metrics
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestPrometheusClient_RunQuery(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}`
w.Write([]byte(json))
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
if err != nil {
t.Fatal(err)
}
query := `
histogram_quantile(0.99,
sum(
rate(
http_request_duration_seconds_bucket{
kubernetes_namespace="test",
kubernetes_pod_name=~"podinfo-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[1m]
)
) by (le)
)`
val, err := client.RunQuery(query)
if err != nil {
t.Fatal(err.Error())
}
if val != 100 {
t.Errorf("Got %v wanted %v", val, 100)
}
}
func TestPrometheusClient_IsOnline(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json := `{"status":"success","data":{"config.file":"/etc/prometheus/prometheus.yml"}}`
w.Write([]byte(json))
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
if err != nil {
t.Fatal(err)
}
ok, err := client.IsOnline()
if err != nil {
t.Fatal(err.Error())
}
if !ok {
t.Errorf("Got %v wanted %v", ok, true)
}
}
func TestPrometheusClient_IsOffline(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
if err != nil {
t.Fatal(err)
}
ok, err := client.IsOnline()
if err == nil {
t.Errorf("Got no error wanted %v", http.StatusBadGateway)
}
if ok {
t.Errorf("Got %v wanted %v", ok, false)
}
}

View File

@@ -1,73 +0,0 @@
package metrics
import (
"time"
)
var crossoverQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ .Namespace }}",
envoy_cluster_name=~"{{ .Name }}-canary",
envoy_response_code!~"5.*"
}[{{ .Interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ .Namespace }}",
envoy_cluster_name=~"{{ .Name }}-canary"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
kubernetes_namespace="{{ .Namespace }}",
envoy_cluster_name=~"{{ .Name }}-canary"
}[{{ .Interval }}]
)
) by (le)
)`,
}
type CrossoverObserver struct {
client *PrometheusClient
}
func (ob *CrossoverObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, crossoverQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *CrossoverObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, crossoverQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,73 +0,0 @@
package metrics
import (
"time"
)
var crossoverServiceQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ .Namespace }}",
envoy_cluster_name="{{ .Name }}-canary",
envoy_response_code!~"5.*"
}[{{ .Interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ .Namespace }}",
envoy_cluster_name="{{ .Name }}-canary"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
kubernetes_namespace="{{ .Namespace }}",
envoy_cluster_name="{{ .Name }}-canary"
}[{{ .Interval }}]
)
) by (le)
)`,
}
type CrossoverServiceObserver struct {
client *PrometheusClient
}
func (ob *CrossoverServiceObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, crossoverServiceQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *CrossoverServiceObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, crossoverServiceQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,71 +0,0 @@
package metrics
import "time"
var httpQueries = map[string]string{
"request-success-rate": `
sum(
rate(
http_request_duration_seconds_count{
kubernetes_namespace="{{ .Namespace }}",
kubernetes_pod_name=~"{{ .Name }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)",
status!~"5.*"
}[{{ .Interval }}]
)
)
/
sum(
rate(
http_request_duration_seconds_count{
kubernetes_namespace="{{ .Namespace }}",
kubernetes_pod_name=~"{{ .Name }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
http_request_duration_seconds_bucket{
kubernetes_namespace="{{ .Namespace }}",
kubernetes_pod_name=~"{{ .Name }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ .Interval }}]
)
) by (le)
)`,
}
type HttpObserver struct {
client *PrometheusClient
}
func (ob *HttpObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, httpQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *HttpObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, httpQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value*1000)) * time.Millisecond
return ms, nil
}

View File

@@ -1,76 +0,0 @@
package metrics
import (
"time"
)
var istioQueries = map[string]string{
"request-success-rate": `
sum(
rate(
istio_requests_total{
reporter="destination",
destination_workload_namespace="{{ .Namespace }}",
destination_workload=~"{{ .Name }}",
response_code!~"5.*"
}[{{ .Interval }}]
)
)
/
sum(
rate(
istio_requests_total{
reporter="destination",
destination_workload_namespace="{{ .Namespace }}",
destination_workload=~"{{ .Name }}"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
istio_request_duration_seconds_bucket{
reporter="destination",
destination_workload_namespace="{{ .Namespace }}",
destination_workload=~"{{ .Name }}"
}[{{ .Interval }}]
)
) by (le)
)`,
}
type IstioObserver struct {
client *PrometheusClient
}
func (ob *IstioObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, istioQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *IstioObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, istioQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value*1000)) * time.Millisecond
return ms, nil
}

View File

@@ -1,76 +0,0 @@
package metrics
import (
"time"
)
var linkerdQueries = map[string]string{
"request-success-rate": `
sum(
rate(
response_total{
namespace="{{ .Namespace }}",
deployment=~"{{ .Name }}",
classification!="failure",
direction="inbound"
}[{{ .Interval }}]
)
)
/
sum(
rate(
response_total{
namespace="{{ .Namespace }}",
deployment=~"{{ .Name }}",
direction="inbound"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
response_latency_ms_bucket{
namespace="{{ .Namespace }}",
deployment=~"{{ .Name }}",
direction="inbound"
}[{{ .Interval }}]
)
) by (le)
)`,
}
type LinkerdObserver struct {
client *PrometheusClient
}
func (ob *LinkerdObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, linkerdQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *LinkerdObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, linkerdQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,80 +0,0 @@
package metrics
import (
"time"
)
var nginxQueries = map[string]string{
"request-success-rate": `
sum(
rate(
nginx_ingress_controller_requests{
namespace="{{ .Namespace }}",
ingress="{{ .Name }}",
status!~"5.*"
}[{{ .Interval }}]
)
)
/
sum(
rate(
nginx_ingress_controller_requests{
namespace="{{ .Namespace }}",
ingress="{{ .Name }}"
}[{{ .Interval }}]
)
)
* 100`,
"request-duration": `
sum(
rate(
nginx_ingress_controller_ingress_upstream_latency_seconds_sum{
namespace="{{ .Namespace }}",
ingress="{{ .Name }}"
}[{{ .Interval }}]
)
)
/
sum(
rate(
nginx_ingress_controller_ingress_upstream_latency_seconds_count{
namespace="{{ .Namespace }}",
ingress="{{ .Name }}"
}[{{ .Interval }}]
)
)
* 1000`,
}
type NginxObserver struct {
client *PrometheusClient
}
func (ob *NginxObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, nginxQueries["request-success-rate"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *NginxObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, nginxQueries["request-duration"])
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +0,0 @@
package metrics
import (
"time"
)
type Interface interface {
GetRequestSuccessRate(name string, namespace string, interval string) (float64, error)
GetRequestDuration(name string, namespace string, interval string) (time.Duration, error)
}

View File

@@ -0,0 +1,76 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var appMeshQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)",
envoy_response_code!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ interval }}]
)
) by (le)
)`,
}
type AppMeshObserver struct {
client providers.Interface
}
func (ob *AppMeshObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(appMeshQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *AppMeshObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(appMeshQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestAppMeshObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestAppMeshObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestAppMeshObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestAppMeshObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestAppMeshObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -1,7 +1,10 @@
package metrics
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
//envoy_cluster_name="test_podinfo-canary_9898"
@@ -11,17 +14,17 @@ var contourQueries = map[string]string{
sum(
rate(
envoy_cluster_upstream_rq{
envoy_cluster_name=~"{{ .Namespace }}_{{ .Name }}-canary_[0-9a-zA-Z-]+",
envoy_cluster_name=~"{{ namespace }}_{{ target }}-canary_[0-9a-zA-Z-]+",
envoy_response_code!~"5.*"
}[{{ .Interval }}]
}[{{ interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
envoy_cluster_name=~"{{ .Namespace }}_{{ .Name }}-canary_[0-9a-zA-Z-]+",
}[{{ .Interval }}]
envoy_cluster_name=~"{{ namespace }}_{{ target }}-canary_[0-9a-zA-Z-]+",
}[{{ interval }}]
)
)
* 100`,
@@ -31,19 +34,19 @@ var contourQueries = map[string]string{
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
envoy_cluster_name=~"{{ .Namespace }}_{{ .Name }}-canary_[0-9a-zA-Z-]+",
}[{{ .Interval }}]
envoy_cluster_name=~"{{ namespace }}_{{ target }}-canary_[0-9a-zA-Z-]+",
}[{{ interval }}]
)
) by (le)
)`,
}
type ContourObserver struct {
client *PrometheusClient
client providers.Interface
}
func (ob *ContourObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, contourQueries["request-success-rate"])
func (ob *ContourObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(contourQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
@@ -56,8 +59,8 @@ func (ob *ContourObserver) GetRequestSuccessRate(name string, namespace string,
return value, nil
}
func (ob *ContourObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, contourQueries["request-duration"])
func (ob *ContourObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(contourQueries["request-duration"], model)
if err != nil {
return 0, err
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestContourObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestContourObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestContourObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestContourObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestContourObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -0,0 +1,76 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var crossoverQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ namespace }}",
envoy_cluster_name=~"{{ target }}-canary",
envoy_response_code!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ namespace }}",
envoy_cluster_name=~"{{ target }}-canary"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
kubernetes_namespace="{{ namespace }}",
envoy_cluster_name=~"{{ target }}-canary"
}[{{ interval }}]
)
) by (le)
)`,
}
type CrossoverObserver struct {
client providers.Interface
}
func (ob *CrossoverObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(crossoverQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *CrossoverObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(crossoverQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -0,0 +1,76 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var crossoverServiceQueries = map[string]string{
"request-success-rate": `
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ namespace }}",
envoy_cluster_name="{{ target }}-canary",
envoy_response_code!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
kubernetes_namespace="{{ namespace }}",
envoy_cluster_name="{{ target }}-canary"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
kubernetes_namespace="{{ namespace }}",
envoy_cluster_name="{{ target }}-canary"
}[{{ interval }}]
)
) by (le)
)`,
}
type CrossoverServiceObserver struct {
client providers.Interface
}
func (ob *CrossoverServiceObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(crossoverServiceQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *CrossoverServiceObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(crossoverServiceQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestCrossoverServiceObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestCrossoverServiceObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestCrossoverServiceObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestCrossoverServiceObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestCrossoverServiceObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestCrossoverObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestCrossoverObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestCrossoverObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestCrossoverObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestCrossoverObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -1,16 +1,22 @@
package metrics
package observers
import (
"strings"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
type Factory struct {
Client *PrometheusClient
Client providers.Interface
}
func NewFactory(metricsServer string, timeout time.Duration) (*Factory, error) {
client, err := NewPrometheusClient(metricsServer, timeout)
func NewFactory(metricsServer string) (*Factory, error) {
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: metricsServer,
SecretRef: nil,
}, nil)
if err != nil {
return nil, err
}

View File

@@ -1,7 +1,10 @@
package metrics
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
//envoy_cluster_name="test-podinfo-primary-9898_gloo-system"
@@ -11,17 +14,17 @@ var glooQueries = map[string]string{
sum(
rate(
envoy_cluster_upstream_rq{
envoy_cluster_name=~"{{ .Namespace }}-{{ .Name }}-canary-[0-9a-zA-Z-]+_[0-9a-zA-Z-]+",
envoy_cluster_name=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+_[0-9a-zA-Z-]+",
envoy_response_code!~"5.*"
}[{{ .Interval }}]
}[{{ interval }}]
)
)
/
sum(
rate(
envoy_cluster_upstream_rq{
envoy_cluster_name=~"{{ .Namespace }}-{{ .Name }}-canary-[0-9a-zA-Z-]+_[0-9a-zA-Z-]+",
}[{{ .Interval }}]
envoy_cluster_name=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+_[0-9a-zA-Z-]+",
}[{{ interval }}]
)
)
* 100`,
@@ -31,19 +34,19 @@ var glooQueries = map[string]string{
sum(
rate(
envoy_cluster_upstream_rq_time_bucket{
envoy_cluster_name=~"{{ .Namespace }}-{{ .Name }}-canary-[0-9a-zA-Z-]+_[0-9a-zA-Z-]+",
}[{{ .Interval }}]
envoy_cluster_name=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+_[0-9a-zA-Z-]+",
}[{{ interval }}]
)
) by (le)
)`,
}
type GlooObserver struct {
client *PrometheusClient
client providers.Interface
}
func (ob *GlooObserver) GetRequestSuccessRate(name string, namespace string, interval string) (float64, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, glooQueries["request-success-rate"])
func (ob *GlooObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(glooQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
@@ -56,8 +59,8 @@ func (ob *GlooObserver) GetRequestSuccessRate(name string, namespace string, int
return value, nil
}
func (ob *GlooObserver) GetRequestDuration(name string, namespace string, interval string) (time.Duration, error) {
query, err := ob.client.RenderQuery(name, namespace, interval, glooQueries["request-duration"])
func (ob *GlooObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(glooQueries["request-duration"], model)
if err != nil {
return 0, err
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestGlooObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestGlooObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestGlooObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestGlooObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestGlooObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -0,0 +1,76 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var httpQueries = map[string]string{
"request-success-rate": `
sum(
rate(
http_request_duration_seconds_count{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)",
status!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
http_request_duration_seconds_count{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
http_request_duration_seconds_bucket{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[{{ interval }}]
)
) by (le)
)`,
}
type HttpObserver struct {
client providers.Interface
}
func (ob *HttpObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(httpQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *HttpObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(httpQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value*1000)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestHttpObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestHttpObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestHttpObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestHttpObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestHttpObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -0,0 +1,79 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var istioQueries = map[string]string{
"request-success-rate": `
sum(
rate(
istio_requests_total{
reporter="destination",
destination_workload_namespace="{{ namespace }}",
destination_workload=~"{{ target }}",
response_code!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
istio_requests_total{
reporter="destination",
destination_workload_namespace="{{ namespace }}",
destination_workload=~"{{ target }}"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
istio_request_duration_seconds_bucket{
reporter="destination",
destination_workload_namespace="{{ namespace }}",
destination_workload=~"{{ target }}"
}[{{ interval }}]
)
) by (le)
)`,
}
type IstioObserver struct {
client providers.Interface
}
func (ob *IstioObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(istioQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *IstioObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(istioQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value*1000)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestIstioObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestIstioObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestIstioObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestIstioObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestIstioObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -0,0 +1,79 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var linkerdQueries = map[string]string{
"request-success-rate": `
sum(
rate(
response_total{
namespace="{{ namespace }}",
deployment=~"{{ target }}",
classification!="failure",
direction="inbound"
}[{{ interval }}]
)
)
/
sum(
rate(
response_total{
namespace="{{ namespace }}",
deployment=~"{{ target }}",
direction="inbound"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
histogram_quantile(
0.99,
sum(
rate(
response_latency_ms_bucket{
namespace="{{ namespace }}",
deployment=~"{{ target }}",
direction="inbound"
}[{{ interval }}]
)
) by (le)
)`,
}
type LinkerdObserver struct {
client providers.Interface
}
func (ob *LinkerdObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(linkerdQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *LinkerdObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(linkerdQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestLinkerdObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestLinkerdObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestLinkerdObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "default", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestLinkerdObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestLinkerdObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "default", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "default",
Target: "podinfo",
Service: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -0,0 +1,83 @@
package observers
import (
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
var nginxQueries = map[string]string{
"request-success-rate": `
sum(
rate(
nginx_ingress_controller_requests{
namespace="{{ namespace }}",
ingress="{{ ingress }}",
status!~"5.*"
}[{{ interval }}]
)
)
/
sum(
rate(
nginx_ingress_controller_requests{
namespace="{{ namespace }}",
ingress="{{ ingress }}"
}[{{ interval }}]
)
)
* 100`,
"request-duration": `
sum(
rate(
nginx_ingress_controller_ingress_upstream_latency_seconds_sum{
namespace="{{ namespace }}",
ingress="{{ ingress }}"
}[{{ interval }}]
)
)
/
sum(
rate(
nginx_ingress_controller_ingress_upstream_latency_seconds_count{
namespace="{{ namespace }}",
ingress="{{ ingress }}"
}[{{ interval }}]
)
)
* 1000`,
}
type NginxObserver struct {
client providers.Interface
}
func (ob *NginxObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
query, err := RenderQuery(nginxQueries["request-success-rate"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
return value, nil
}
func (ob *NginxObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
query, err := RenderQuery(nginxQueries["request-duration"], model)
if err != nil {
return 0, err
}
value, err := ob.client.RunQuery(query)
if err != nil {
return 0, err
}
ms := time.Duration(int64(value)) * time.Millisecond
return ms, nil
}

View File

@@ -1,10 +1,13 @@
package metrics
package observers
import (
"net/http"
"net/http/httptest"
"testing"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"github.com/weaveworks/flagger/pkg/metrics/providers"
)
func TestNginxObserver_GetRequestSuccessRate(t *testing.T) {
@@ -21,7 +24,11 @@ func TestNginxObserver_GetRequestSuccessRate(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,13 @@ func TestNginxObserver_GetRequestSuccessRate(t *testing.T) {
client: client,
}
val, err := observer.GetRequestSuccessRate("podinfo", "nginx", "1m")
val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "nginx",
Target: "podinfo",
Ingress: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}
@@ -54,7 +67,11 @@ func TestNginxObserver_GetRequestDuration(t *testing.T) {
}))
defer ts.Close()
client, err := NewPrometheusClient(ts.URL, time.Second)
client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: ts.URL,
SecretRef: nil,
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -63,7 +80,13 @@ func TestNginxObserver_GetRequestDuration(t *testing.T) {
client: client,
}
val, err := observer.GetRequestDuration("podinfo", "nginx", "1m")
val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{
Name: "podinfo",
Namespace: "nginx",
Target: "podinfo",
Ingress: "podinfo",
Interval: "1m",
})
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -0,0 +1,11 @@
package observers
import (
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
"time"
)
type Interface interface {
GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error)
GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error)
}

View File

@@ -0,0 +1,29 @@
package observers
import (
"bufio"
"bytes"
"text/template"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
)
func RenderQuery(queryTemplate string, model flaggerv1.MetricTemplateModel) (string, error) {
t, err := template.New("tmpl").Funcs(model.TemplateFunctions()).Parse(queryTemplate)
if err != nil {
return "", err
}
var data bytes.Buffer
b := bufio.NewWriter(&data)
if err := t.Execute(b, nil); err != nil {
return "", err
}
err = b.Flush()
if err != nil {
return "", err
}
return data.String(), nil
}

View File

@@ -0,0 +1,15 @@
package providers
import flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
type Factory struct {
}
func (factory Factory) Provider(provider flaggerv1.MetricTemplateProvider, credentials map[string][]byte) (Interface, error) {
switch {
case provider.Type == "prometheus":
return NewPrometheusProvider(provider, credentials)
default:
return NewPrometheusProvider(provider, credentials)
}
}

View File

@@ -1,8 +1,6 @@
package metrics
package providers
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
@@ -12,14 +10,17 @@ import (
"path"
"regexp"
"strconv"
"text/template"
"time"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
)
// PrometheusClient is executing promql queries
type PrometheusClient struct {
timeout time.Duration
url url.URL
// PrometheusProvider executes promQL queries
type PrometheusProvider struct {
timeout time.Duration
url url.URL
username string
password string
}
type prometheusResponse struct {
@@ -33,54 +34,44 @@ type prometheusResponse struct {
}
}
// NewPrometheusClient creates a Prometheus client for the provided URL address
func NewPrometheusClient(address string, timeout time.Duration) (*PrometheusClient, error) {
promURL, err := url.Parse(address)
// NewPrometheusProvider takes a provider spec and the credentials map,
// validates the address, extracts the username and password values if provided and
// returns a Prometheus client ready to execute queries against the API
func NewPrometheusProvider(provider flaggerv1.MetricTemplateProvider, credentials map[string][]byte) (*PrometheusProvider, error) {
promURL, err := url.Parse(provider.Address)
if err != nil {
return nil, err
return nil, fmt.Errorf("%s address %s is not a valid URL", provider.Type, provider.Address)
}
return &PrometheusClient{timeout: timeout, url: *promURL}, nil
prom := PrometheusProvider{
timeout: 5 * time.Second,
url: *promURL,
}
if provider.SecretRef != nil {
if username, ok := credentials["username"]; ok {
prom.username = string(username)
} else {
return nil, fmt.Errorf("%s credentials does not contain a username", provider.Type)
}
if password, ok := credentials["password"]; ok {
prom.password = string(password)
} else {
return nil, fmt.Errorf("%s credentials does not contain a password", provider.Type)
}
}
return &prom, nil
}
// RenderQuery renders the promql query using the provided text template
func (p *PrometheusClient) RenderQuery(name string, namespace string, interval string, tmpl string) (string, error) {
meta := struct {
Name string
Namespace string
Interval string
}{
name,
namespace,
interval,
}
t, err := template.New("tmpl").Parse(tmpl)
if err != nil {
return "", err
}
var data bytes.Buffer
b := bufio.NewWriter(&data)
if err := t.Execute(b, meta); err != nil {
return "", err
}
err = b.Flush()
if err != nil {
return "", err
}
return data.String(), nil
}
// RunQuery executes the promql and converts the result to float64
func (p *PrometheusClient) RunQuery(query string) (float64, error) {
// RunQuery executes the promQL query and returns the the first result as float64
func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
if p.url.Host == "fake" {
return 100, nil
}
query = url.QueryEscape(p.TrimQuery(query))
query = url.QueryEscape(p.trimQuery(query))
u, err := url.Parse(fmt.Sprintf("./api/v1/query?query=%s", query))
if err != nil {
return 0, err
@@ -94,6 +85,10 @@ func (p *PrometheusClient) RunQuery(query string) (float64, error) {
return 0, err
}
if p.username != "" && p.password != "" {
req.SetBasicAuth(p.username, p.password)
}
ctx, cancel := context.WithTimeout(req.Context(), p.timeout)
defer cancel()
@@ -137,14 +132,8 @@ func (p *PrometheusClient) RunQuery(query string) (float64, error) {
return *value, nil
}
// TrimQuery takes a promql query and removes whitespace
func (p *PrometheusClient) TrimQuery(query string) string {
space := regexp.MustCompile(`\s+`)
return space.ReplaceAllString(query, " ")
}
// IsOnline call Prometheus status endpoint and returns an error if the API is unreachable
func (p *PrometheusClient) IsOnline() (bool, error) {
// IsOnline calls the Prometheus status endpoint and returns an error if the API is unreachable
func (p *PrometheusProvider) IsOnline() (bool, error) {
u, err := url.Parse("./api/v1/status/flags")
if err != nil {
return false, err
@@ -158,6 +147,10 @@ func (p *PrometheusClient) IsOnline() (bool, error) {
return false, err
}
if p.username != "" && p.password != "" {
req.SetBasicAuth(p.username, p.password)
}
ctx, cancel := context.WithTimeout(req.Context(), p.timeout)
defer cancel()
@@ -179,6 +172,8 @@ func (p *PrometheusClient) IsOnline() (bool, error) {
return true, nil
}
func (p *PrometheusClient) GetMetricsServer() string {
return p.url.String()
// trimQuery takes a promql query and removes whitespace
func (p *PrometheusProvider) trimQuery(query string) string {
space := regexp.MustCompile(`\s+`)
return space.ReplaceAllString(query, " ")
}

View File

@@ -0,0 +1,170 @@
package providers
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha1"
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
fakeFlagger "github.com/weaveworks/flagger/pkg/client/clientset/versioned/fake"
)
type fakeClients struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
}
func prometheusFake() fakeClients {
provider := flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: "http://prometheus:9090",
SecretRef: &corev1.LocalObjectReference{Name: "prometheus"},
}
template := &flaggerv1.MetricTemplate{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "prometheus",
},
Spec: flaggerv1.MetricTemplateSpec{
Provider: provider,
Query: "sum(envoy_cluster_upstream_rq)",
},
}
flaggerClient := fakeFlagger.NewSimpleClientset(template)
secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "prometheus",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"username": []byte("username"),
"password": []byte("password"),
},
}
kubeClient := fake.NewSimpleClientset(secret)
return fakeClients{
kubeClient: kubeClient,
flaggerClient: flaggerClient,
}
}
func TestNewPrometheusProvider(t *testing.T) {
clients := prometheusFake()
template, err := clients.flaggerClient.FlaggerV1alpha1().MetricTemplates("default").Get("prometheus", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secret, err := clients.kubeClient.CoreV1().Secrets("default").Get("prometheus", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
if err != nil {
t.Fatal(err.Error())
}
if prom.url.String() != "http://prometheus:9090" {
t.Errorf("Got URL %s wanted %s", prom.url.String(), "http://prometheus:9090")
}
if prom.password != "password" {
t.Errorf("Got password %s wanted %s", prom.password, "password")
}
}
func TestPrometheusProvider_RunQueryWithBasicAuth(t *testing.T) {
expected := `sum(envoy_cluster_upstream_rq)`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
promql := r.URL.Query()["query"][0]
if promql != expected {
t.Errorf("\nGot %s \nWanted %s", promql, expected)
}
if header, ok := r.Header["Authorization"]; ok {
if !strings.Contains(header[0], "Basic") {
t.Error("Basic authorization header not found")
}
} else {
t.Error("Authorization header not found")
}
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}`
w.Write([]byte(json))
}))
defer ts.Close()
clients := prometheusFake()
template, err := clients.flaggerClient.FlaggerV1alpha1().MetricTemplates("default").Get("prometheus", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
template.Spec.Provider.Address = ts.URL
secret, err := clients.kubeClient.CoreV1().Secrets("default").Get("prometheus", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
if err != nil {
t.Fatal(err.Error())
}
val, err := prom.RunQuery(template.Spec.Query)
if err != nil {
t.Fatal(err.Error())
}
if val != 100 {
t.Errorf("Got %v wanted %v", val, 100)
}
}
func TestPrometheusProvider_IsOnline(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
}))
defer ts.Close()
clients := prometheusFake()
template, err := clients.flaggerClient.FlaggerV1alpha1().MetricTemplates("default").Get("prometheus", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
template.Spec.Provider.Address = ts.URL
template.Spec.Provider.SecretRef = nil
prom, err := NewPrometheusProvider(template.Spec.Provider, nil)
if err != nil {
t.Fatal(err.Error())
}
ok, err := prom.IsOnline()
if err == nil {
t.Errorf("Got no error wanted %v", http.StatusBadGateway)
}
if ok {
t.Errorf("Got %v wanted %v", ok, false)
}
}

View File

@@ -0,0 +1,9 @@
package providers
type Interface interface {
// RunQuery executes the query and converts the first result to float64
RunQuery(query string) (float64, error)
// IsOnline calls the provider endpoint and returns an error if the API is unreachable
IsOnline() (bool, error)
}

View File

@@ -17,6 +17,31 @@ kubectl -n test rollout status deployment/flagger-loadtester
echo '>>> Initialising canary'
kubectl apply -f ${REPO_ROOT}/test/e2e-workload.yaml
cat <<EOF | kubectl apply -f -
apiVersion: flagger.app/v1alpha1
kind: MetricTemplate
metadata:
name: latency
namespace: linkerd
spec:
provider:
type: prometheus
address: http://linkerd-prometheus.linkerd:9090
query: |
histogram_quantile(
0.99,
sum(
rate(
response_latency_ms_bucket{
namespace="{{ namespace }}",
deployment=~"{{ target }}",
direction="inbound"
}[{{ interval }}]
)
) by (le)
)
EOF
cat <<EOF | kubectl apply -f -
apiVersion: flagger.app/v1alpha3
kind: Canary
@@ -45,6 +70,12 @@ spec:
- name: request-duration
threshold: 500
interval: 30s
- name: latency
templateRef:
name: latency
namespace: linkerd
threshold: 300
interval: 1m
webhooks:
- name: http-acceptance-test
type: pre-rollout