mirror of
https://github.com/fluxcd/flagger.git
synced 2026-04-15 06:57:34 +00:00
pkg/canary: add daemonset target controller
This commit is contained in:
@@ -93,16 +93,36 @@ func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*Confi
|
||||
func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]ConfigRef, error) {
|
||||
res := make(map[string]ConfigRef)
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
targetDep, err := ct.KubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return res, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
|
||||
|
||||
var vs []corev1.Volume
|
||||
var cs []corev1.Container
|
||||
switch cd.Spec.TargetRef.Kind {
|
||||
case "Deployment":
|
||||
targetDep, err := ct.KubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return res, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return res, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
return res, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
vs = targetDep.Spec.Template.Spec.Volumes
|
||||
cs = targetDep.Spec.Template.Spec.Containers
|
||||
case "DaemonSet":
|
||||
targetDae, err := ct.KubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return res, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return res, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
vs = targetDae.Spec.Template.Spec.Volumes
|
||||
cs = targetDae.Spec.Template.Spec.Containers
|
||||
default:
|
||||
return nil, fmt.Errorf("TargetRef.Kind invalid: %s", cd.Spec.TargetRef.Kind)
|
||||
}
|
||||
|
||||
// scan volumes
|
||||
for _, volume := range targetDep.Spec.Template.Spec.Volumes {
|
||||
for _, volume := range vs {
|
||||
if cmv := volume.ConfigMap; cmv != nil {
|
||||
config, err := ct.getRefFromConfigMap(cmv.Name, cd.Namespace)
|
||||
if err != nil {
|
||||
@@ -152,7 +172,7 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
|
||||
}
|
||||
}
|
||||
// scan containers
|
||||
for _, container := range targetDep.Spec.Template.Spec.Containers {
|
||||
for _, container := range cs {
|
||||
// scan env
|
||||
for _, env := range container.Env {
|
||||
if env.ValueFrom != nil {
|
||||
|
||||
@@ -7,125 +7,253 @@ import (
|
||||
)
|
||||
|
||||
func TestConfigTracker_ConfigMaps(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
configMap := newTestConfigMap()
|
||||
configMapProjected := newTestConfigProjected()
|
||||
t.Run("deployment", func(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
configMap := newDeploymentControllerTestConfigMap()
|
||||
configMapProjected := newDeploymentControllerTestConfigProjected()
|
||||
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
|
||||
if configPrimaryVolName != "podinfo-config-vol-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary")
|
||||
}
|
||||
configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
|
||||
if configPrimaryVolName != "podinfo-config-vol-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary")
|
||||
}
|
||||
|
||||
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimary.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
|
||||
}
|
||||
if configPrimary.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
|
||||
}
|
||||
|
||||
configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimaryEnv.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"])
|
||||
}
|
||||
if configPrimaryEnv.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"])
|
||||
}
|
||||
|
||||
configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimaryVol.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
|
||||
}
|
||||
if configPrimaryVol.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
|
||||
}
|
||||
|
||||
configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name
|
||||
if configProjectedName != "podinfo-config-projected-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary")
|
||||
}
|
||||
configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name
|
||||
if configProjectedName != "podinfo-config-projected-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary")
|
||||
}
|
||||
|
||||
configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"])
|
||||
}
|
||||
if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("daemonset", func(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
configMap := newDaemonSetControllerTestConfigMap()
|
||||
configMapProjected := newDaemonSetControllerTestConfigProjected()
|
||||
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
|
||||
if configPrimaryVolName != "podinfo-config-vol-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary")
|
||||
}
|
||||
|
||||
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimary.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
|
||||
}
|
||||
|
||||
configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimaryEnv.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"])
|
||||
}
|
||||
|
||||
configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimaryVol.Data["color"] != configMap.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
|
||||
}
|
||||
|
||||
configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name
|
||||
if configProjectedName != "podinfo-config-projected-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary")
|
||||
}
|
||||
|
||||
configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] {
|
||||
t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigTracker_Secrets(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
secret := newTestSecret()
|
||||
secretProjected := newTestSecretProjected()
|
||||
t.Run("deployment", func(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
secret := newDeploymentControllerTestSecret()
|
||||
secretProjected := newDeploymentControllerTestSecretProjected()
|
||||
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName
|
||||
if secretPrimaryVolName != "podinfo-secret-vol-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary")
|
||||
}
|
||||
secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName
|
||||
if secretPrimaryVolName != "podinfo-secret-vol-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary")
|
||||
}
|
||||
|
||||
secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
|
||||
secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
|
||||
secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
|
||||
secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name
|
||||
if secretProjectedName != "podinfo-secret-projected-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary")
|
||||
}
|
||||
secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name
|
||||
if secretProjectedName != "podinfo-secret-projected-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary")
|
||||
}
|
||||
|
||||
secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"])
|
||||
}
|
||||
if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("daemonset", func(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
secret := newDaemonSetControllerTestSecret()
|
||||
secretProjected := newDaemonSetControllerTestSecretProjected()
|
||||
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName
|
||||
if secretPrimaryVolName != "podinfo-secret-vol-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary")
|
||||
}
|
||||
|
||||
secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
|
||||
secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
|
||||
secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
|
||||
}
|
||||
|
||||
secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name
|
||||
if secretProjectedName != "podinfo-secret-projected-primary" {
|
||||
t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary")
|
||||
}
|
||||
|
||||
secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) {
|
||||
t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
var (
|
||||
daemonSetScaleDownNodeSelector = map[string]string{"flux.weave.works/non-exist": "true"}
|
||||
)
|
||||
|
||||
// DaemonSetController is managing the operations for Kubernetes DaemonSet kind
|
||||
type DaemonSetController struct {
|
||||
kubeClient kubernetes.Interface
|
||||
flaggerClient clientset.Interface
|
||||
logger *zap.SugaredLogger
|
||||
configTracker Tracker
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (c *DaemonSetController) Scale(cd *flaggerv1.Canary, v int32) error {
|
||||
// there's no concept `replicas` for DaemonSet
|
||||
if v == 0 {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
dae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
daeCopy := dae.DeepCopy()
|
||||
daeCopy.Spec.Template.Spec.NodeSelector = make(map[string]string,
|
||||
len(dae.Spec.Template.Spec.NodeSelector)+len(daemonSetScaleDownNodeSelector))
|
||||
for k, v := range dae.Spec.Template.Spec.NodeSelector {
|
||||
daeCopy.Spec.Template.Spec.NodeSelector[k] = v
|
||||
}
|
||||
for k, v := range daemonSetScaleDownNodeSelector {
|
||||
daeCopy.Spec.Template.Spec.NodeSelector[k] = v
|
||||
}
|
||||
|
||||
_, err = c.kubeClient.AppsV1().DaemonSets(dae.Namespace).Update(daeCopy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scaling down daemonset %s.%s failed: %v", daeCopy.GetName(), daeCopy.Namespace, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DaemonSetController) ScaleFromZero(cd *flaggerv1.Canary) error {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
dep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
depCopy := dep.DeepCopy()
|
||||
for k := range daemonSetScaleDownNodeSelector {
|
||||
delete(depCopy.Spec.Template.Spec.NodeSelector, k)
|
||||
}
|
||||
|
||||
_, err = c.kubeClient.AppsV1().DaemonSets(dep.Namespace).Update(depCopy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scaling up daemonset %s.%s failed: %v", depCopy.GetName(), depCopy.Namespace, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize creates the primary DaemonSet and
|
||||
// delete the canary DaemonSet and returns the pod selector label and container ports
|
||||
func (c *DaemonSetController) Initialize(cd *flaggerv1.Canary, skipLivenessChecks bool) (err error) {
|
||||
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
|
||||
err = c.createPrimaryDaemonSet(cd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating daemonset %s.%s failed: %v", primaryName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
if cd.Status.Phase == "" || cd.Status.Phase == flaggerv1.CanaryPhaseInitializing {
|
||||
if !skipLivenessChecks && !cd.Spec.SkipAnalysis {
|
||||
_, readyErr := c.IsPrimaryReady(cd)
|
||||
if readyErr != nil {
|
||||
return readyErr
|
||||
}
|
||||
}
|
||||
|
||||
// delete canary daemonset
|
||||
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("Deleting %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
|
||||
if err := c.Scale(cd, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Promote copies the pod spec, secrets and config maps from canary to primary
|
||||
func (c *DaemonSetController) Promote(cd *flaggerv1.Canary) error {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
primaryName := fmt.Sprintf("%s-primary", targetName)
|
||||
|
||||
canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("damonset %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return fmt.Errorf("damonset %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
label, err := c.getSelectorLabel(canary)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
|
||||
targetName, cd.Namespace, targetName)
|
||||
}
|
||||
|
||||
primary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("daemonset %s.%s not found", primaryName, cd.Namespace)
|
||||
}
|
||||
return fmt.Errorf("daemonset %s.%s query error %v", primaryName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
// promote secrets and config maps
|
||||
configRefs, err := c.configTracker.GetTargetConfigs(cd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
primaryCopy := primary.DeepCopy()
|
||||
primaryCopy.Spec.MinReadySeconds = canary.Spec.MinReadySeconds
|
||||
primaryCopy.Spec.RevisionHistoryLimit = canary.Spec.RevisionHistoryLimit
|
||||
primaryCopy.Spec.UpdateStrategy = canary.Spec.UpdateStrategy
|
||||
|
||||
// update spec with primary secrets and config maps
|
||||
primaryCopy.Spec.Template.Spec = c.configTracker.ApplyPrimaryConfigs(canary.Spec.Template.Spec, configRefs)
|
||||
|
||||
// update pod annotations to ensure a rolling update
|
||||
annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
primaryCopy.Spec.Template.Annotations = annotations
|
||||
|
||||
primaryCopy.Spec.Template.Labels = makePrimaryLabels(canary.Spec.Template.Labels, primaryName, label)
|
||||
|
||||
// apply update
|
||||
_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Update(primaryCopy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating deployment %s.%s template spec failed: %v",
|
||||
primaryCopy.GetName(), primaryCopy.Namespace, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasTargetChanged returns true if the canary DaemonSet pod spec has changed
|
||||
func (c *DaemonSetController) HasTargetChanged(cd *flaggerv1.Canary) (bool, error) {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return false, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return false, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
return hasSpecChanged(cd, canary.Spec.Template)
|
||||
}
|
||||
|
||||
// GetMetadata returns the pod label selector and svc ports
|
||||
func (c *DaemonSetController) GetMetadata(cd *flaggerv1.Canary) (string, map[string]int32, error) {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
|
||||
canaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return "", nil, fmt.Errorf("daemonset %s.%s not found, retrying", targetName, cd.Namespace)
|
||||
}
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
label, err := c.getSelectorLabel(canaryDae)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
|
||||
targetName, cd.Namespace, targetName)
|
||||
}
|
||||
|
||||
var ports map[string]int32
|
||||
if cd.Spec.Service.PortDiscovery {
|
||||
p, err := getPorts(cd, canaryDae.Spec.Template.Spec.Containers)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("port discovery failed with error: %v", err)
|
||||
}
|
||||
ports = p
|
||||
}
|
||||
|
||||
return label, ports, nil
|
||||
}
|
||||
|
||||
func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
|
||||
|
||||
canaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("daemonset %s.%s not found, retrying", targetName, cd.Namespace)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if canaryDae.Spec.UpdateStrategy.Type != "" &&
|
||||
canaryDae.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType {
|
||||
return fmt.Errorf("daemonset %s.%s must have RollingUpdate strategy but have %s",
|
||||
targetName, cd.Namespace, canaryDae.Spec.UpdateStrategy.Type)
|
||||
}
|
||||
|
||||
if cd.GetProgressDeadlineSeconds() > 0 {
|
||||
// (@mathetake): should we?
|
||||
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
|
||||
Infof("progressDeadlineSeconds is ignored for DaemonSet", cd.Spec.TargetRef.Name, cd.Namespace)
|
||||
}
|
||||
|
||||
label, err := c.getSelectorLabel(canaryDae)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
|
||||
targetName, cd.Namespace, targetName)
|
||||
}
|
||||
|
||||
primaryDep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
// create primary secrets and config maps
|
||||
configRefs, err := c.configTracker.GetTargetConfigs(cd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
|
||||
return err
|
||||
}
|
||||
annotations, err := makeAnnotations(canaryDae.Spec.Template.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create primary deployment
|
||||
primaryDep = &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: primaryName,
|
||||
Namespace: cd.Namespace,
|
||||
Labels: map[string]string{
|
||||
label: primaryName,
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(cd, schema.GroupVersionKind{
|
||||
Group: flaggerv1.SchemeGroupVersion.Group,
|
||||
Version: flaggerv1.SchemeGroupVersion.Version,
|
||||
Kind: flaggerv1.CanaryKind,
|
||||
}),
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
MinReadySeconds: canaryDae.Spec.MinReadySeconds,
|
||||
RevisionHistoryLimit: canaryDae.Spec.RevisionHistoryLimit,
|
||||
UpdateStrategy: canaryDae.Spec.UpdateStrategy,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
label: primaryName,
|
||||
},
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: makePrimaryLabels(canaryDae.Spec.Template.Labels, primaryName, label),
|
||||
Annotations: annotations,
|
||||
},
|
||||
// update spec with the primary secrets and config maps
|
||||
Spec: c.configTracker.ApplyPrimaryConfigs(canaryDae.Spec.Template.Spec, configRefs),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Create(primaryDep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("DaemonSet %s.%s created", primaryDep.GetName(), cd.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSelectorLabel returns the selector match label
|
||||
func (c *DaemonSetController) getSelectorLabel(daemonSet *appsv1.DaemonSet) (string, error) {
|
||||
for _, l := range c.labels {
|
||||
if _, ok := daemonSet.Spec.Selector.MatchLabels[l]; ok {
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("selector not found")
|
||||
}
|
||||
|
||||
func (c *DaemonSetController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
|
||||
return c.configTracker.HasConfigChanged(cd)
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
)
|
||||
|
||||
func TestDaemonSetController_Sync(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dep := newDaemonSetControllerTestPodInfo()
|
||||
|
||||
primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image
|
||||
sourceImage := dep.Spec.Template.Spec.Containers[0].Image
|
||||
if primaryImage != sourceImage {
|
||||
t.Errorf("Got image %s wanted %s", primaryImage, sourceImage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_Promote(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dep2 := newDaemonSetControllerTestPodInfoV2()
|
||||
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
config2 := newDaemonSetControllerTestConfigMapV2()
|
||||
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(config2)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.controller.Promote(mocks.canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image
|
||||
sourceImage := dep2.Spec.Template.Spec.Containers[0].Image
|
||||
if primaryImage != sourceImage {
|
||||
t.Errorf("Got image %s wanted %s", primaryImage, sourceImage)
|
||||
}
|
||||
|
||||
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if configPrimary.Data["color"] != config2.Data["color"] {
|
||||
t.Errorf("Got primary ConfigMap color %s wanted %s", configPrimary.Data["color"], config2.Data["color"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_NoConfigTracking(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
mocks.controller.configTracker = &NopTracker{}
|
||||
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
|
||||
if !errors.IsNotFound(err) {
|
||||
t.Fatalf("Primary ConfigMap shouldn't have been created")
|
||||
}
|
||||
|
||||
configName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
|
||||
if configName != "podinfo-config-vol" {
|
||||
t.Errorf("Got config name %v wanted %v", configName, "podinfo-config-vol")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_HasTargetChanged(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// save last applied hash
|
||||
canary, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitializing})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// save last promoted hash
|
||||
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = mocks.controller.SetStatusPhase(canary, flaggerv1.CanaryPhaseInitialized)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dep, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depClone := dep.DeepCopy()
|
||||
depClone.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: *resource.NewQuantity(100, resource.DecimalExponent),
|
||||
},
|
||||
}
|
||||
|
||||
// update pod spec
|
||||
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(depClone)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// detect change in last applied spec
|
||||
isNew, err := mocks.controller.HasTargetChanged(canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if !isNew {
|
||||
t.Errorf("Got %v wanted %v", isNew, true)
|
||||
}
|
||||
|
||||
// save hash
|
||||
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dep, err = mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
depClone = dep.DeepCopy()
|
||||
depClone.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: *resource.NewQuantity(1000, resource.DecimalExponent),
|
||||
},
|
||||
}
|
||||
|
||||
// update pod spec
|
||||
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(depClone)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// ignore change as hash should be the same with last promoted
|
||||
isNew, err = mocks.controller.HasTargetChanged(canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if isNew {
|
||||
t.Errorf("Got %v wanted %v", isNew, false)
|
||||
}
|
||||
|
||||
depClone = dep.DeepCopy()
|
||||
depClone.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: *resource.NewQuantity(600, resource.DecimalExponent),
|
||||
},
|
||||
}
|
||||
|
||||
// update pod spec
|
||||
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(depClone)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// detect change
|
||||
isNew, err = mocks.controller.HasTargetChanged(canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if !isNew {
|
||||
t.Errorf("Got %v wanted %v", isNew, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_Scale(t *testing.T) {
|
||||
t.Run("Scale", func(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = mocks.controller.Scale(mocks.canary, 0)
|
||||
c, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
for k := range daemonSetScaleDownNodeSelector {
|
||||
if _, ok := c.Spec.Template.Spec.NodeSelector[k]; !ok {
|
||||
t.Errorf("%s should exist in node selector", k)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("ScaleFromZeo", func(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = mocks.controller.ScaleFromZero(mocks.canary)
|
||||
c, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
for k := range daemonSetScaleDownNodeSelector {
|
||||
if _, ok := c.Spec.Template.Spec.NodeSelector[k]; ok {
|
||||
t.Errorf("%s should not exist in node selector", k)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/flagger/pkg/logger"
|
||||
"go.uber.org/zap"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
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/v1beta1"
|
||||
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
|
||||
fakeFlagger "github.com/weaveworks/flagger/pkg/client/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
type daemonSetControllerFixture struct {
|
||||
canary *flaggerv1.Canary
|
||||
kubeClient kubernetes.Interface
|
||||
flaggerClient clientset.Interface
|
||||
controller DaemonSetController
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func newDaemonSetFixture() daemonSetControllerFixture {
|
||||
// init canary
|
||||
canary := newDaemonSetControllerTestCanary()
|
||||
flaggerClient := fakeFlagger.NewSimpleClientset(canary)
|
||||
|
||||
// init kube clientset and register mock objects
|
||||
kubeClient := fake.NewSimpleClientset(
|
||||
newDaemonSetControllerTestPodInfo(),
|
||||
newDaemonSetControllerTestConfigMap(),
|
||||
newDaemonSetControllerTestConfigMapEnv(),
|
||||
newDaemonSetControllerTestConfigMapVol(),
|
||||
newDaemonSetControllerTestConfigProjected(),
|
||||
newDaemonSetControllerTestSecret(),
|
||||
newDaemonSetControllerTestSecretEnv(),
|
||||
newDaemonSetControllerTestSecretVol(),
|
||||
newDaemonSetControllerTestSecretProjected(),
|
||||
)
|
||||
|
||||
logger, _ := logger.NewLogger("debug")
|
||||
|
||||
ctrl := DaemonSetController{
|
||||
flaggerClient: flaggerClient,
|
||||
kubeClient: kubeClient,
|
||||
logger: logger,
|
||||
labels: []string{"app", "name"},
|
||||
configTracker: &ConfigTracker{
|
||||
Logger: logger,
|
||||
KubeClient: kubeClient,
|
||||
FlaggerClient: flaggerClient,
|
||||
},
|
||||
}
|
||||
|
||||
return daemonSetControllerFixture{
|
||||
canary: canary,
|
||||
controller: ctrl,
|
||||
logger: logger,
|
||||
flaggerClient: flaggerClient,
|
||||
kubeClient: kubeClient,
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestConfigMap() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-config-env",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"color": "red",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestConfigMapV2() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-config-env",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"color": "blue",
|
||||
"output": "console",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestConfigProjected() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-config-projected",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"color": "red",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestConfigMapEnv() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-config-all-env",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"color": "red",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestConfigMapVol() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-config-vol",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"color": "red",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestSecret() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-secret-env",
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"apiKey": []byte("test"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestSecretProjected() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-secret-projected",
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"apiKey": []byte("test"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestSecretEnv() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-secret-all-env",
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"apiKey": []byte("test"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestSecretVol() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo-secret-vol",
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"apiKey": []byte("test"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestCanary() *flaggerv1.Canary {
|
||||
cd := &flaggerv1.Canary{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Spec: flaggerv1.CanarySpec{
|
||||
TargetRef: flaggerv1.CrossNamespaceObjectReference{
|
||||
Name: "podinfo",
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "DaemonSet",
|
||||
},
|
||||
},
|
||||
}
|
||||
return cd
|
||||
}
|
||||
|
||||
/*func newDaemonSetControllerWithout() *appsv1.DaemonSet {
|
||||
n := "nginx-without-node-selector"
|
||||
d := &appsv1.DaemonSet{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: n,
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"name": n,
|
||||
},
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"name": n,
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: n,
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return d
|
||||
}
|
||||
*/
|
||||
func newDaemonSetControllerTestPodInfo() *appsv1.DaemonSet {
|
||||
d := &appsv1.DaemonSet{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"name": "podinfo",
|
||||
},
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"name": "podinfo",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "podinfo",
|
||||
Image: "quay.io/stefanprodan/podinfo:1.2.0",
|
||||
Command: []string{
|
||||
"./podinfo",
|
||||
"--port=9898",
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: *resource.NewQuantity(1000, resource.DecimalExponent),
|
||||
},
|
||||
},
|
||||
Args: nil,
|
||||
WorkingDir: "",
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "http",
|
||||
ContainerPort: 9898,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "PODINFO_UI_COLOR",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-env",
|
||||
},
|
||||
Key: "color",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "API_KEY",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-secret-env",
|
||||
},
|
||||
Key: "apiKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-all-env",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-secret-all-env",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "config",
|
||||
MountPath: "/etc/podinfo/config",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "secret",
|
||||
MountPath: "/etc/podinfo/secret",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-vol",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secret",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "podinfo-secret-vol",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "projected",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
ConfigMap: &corev1.ConfigMapProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-projected",
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "color",
|
||||
Path: "my-group/my-color",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-secret-projected",
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "apiKey",
|
||||
Path: "my-group/my-api-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func newDaemonSetControllerTestPodInfoV2() *appsv1.DaemonSet {
|
||||
d := &appsv1.DaemonSet{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "podinfo",
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"name": "podinfo",
|
||||
},
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"name": "podinfo",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "podinfo",
|
||||
Image: "quay.io/stefanprodan/podinfo:1.2.1",
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "http",
|
||||
ContainerPort: 9898,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"./podinfo",
|
||||
"--port=9898",
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "PODINFO_UI_COLOR",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-env",
|
||||
},
|
||||
Key: "color",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "API_KEY",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-secret-env",
|
||||
},
|
||||
Key: "apiKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-all-env",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "config",
|
||||
MountPath: "/etc/podinfo/config",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "secret",
|
||||
MountPath: "/etc/podinfo/secret",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-vol",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secret",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "podinfo-secret-vol",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "projected",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
ConfigMap: &corev1.ConfigMapProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-config-projected",
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "color",
|
||||
Path: "my-group/my-color",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "podinfo-secret-projected",
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "apiKey",
|
||||
Path: "my-group/my-api-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
)
|
||||
|
||||
// IsPrimaryReady checks the primary daemonset status and returns an error if
|
||||
// the daemonset is in the middle of a rolling update
|
||||
func (c *DaemonSetController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error) {
|
||||
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
|
||||
primary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return true, fmt.Errorf("deployment %s.%s not found", primaryName, cd.Namespace)
|
||||
}
|
||||
return true, fmt.Errorf("deployment %s.%s query error %v", primaryName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
retriable, err := c.isDaemonSetReady(primary)
|
||||
if err != nil {
|
||||
return retriable, fmt.Errorf("halt advancement %s.%s %s", primaryName, cd.Namespace, err.Error())
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsCanaryReady checks the primary daemonset and returns an error if
|
||||
// the daemonset is in the middle of a rolling update
|
||||
func (c *DaemonSetController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error) {
|
||||
targetName := cd.Spec.TargetRef.Name
|
||||
canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return true, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
|
||||
}
|
||||
return true, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
|
||||
}
|
||||
|
||||
retriable, err := c.isDaemonSetReady(canary)
|
||||
if err != nil {
|
||||
return retriable, fmt.Errorf("halt advancement %s.%s %s", targetName, cd.Namespace, err.Error())
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isDaemonSetReady determines if a daemonset is ready by checking the number of old version daemons
|
||||
func (c *DaemonSetController) isDaemonSetReady(daemonSet *appsv1.DaemonSet) (bool, error) {
|
||||
if daemonSet.Generation <= daemonSet.Status.ObservedGeneration {
|
||||
if diff := daemonSet.Status.DesiredNumberScheduled - daemonSet.Status.UpdatedNumberScheduled; diff > 0 {
|
||||
return true, fmt.Errorf("waiting for rollout to finish: %d old daemons not replaced yet", diff)
|
||||
}
|
||||
} else {
|
||||
return true, fmt.Errorf("waiting for rollout to finish: observed daemonset generation less then desired generation")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestDaemonSetController_IsReady(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Error("Expected primary readiness check to fail")
|
||||
}
|
||||
|
||||
_, err = mocks.controller.IsPrimaryReady(mocks.canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = mocks.controller.IsCanaryReady(mocks.canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_isDaemonSetReady(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
_, err := mocks.controller.isDaemonSetReady(&appsv1.DaemonSet{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Generation: 10,
|
||||
},
|
||||
Status: appsv1.DaemonSetStatus{
|
||||
ObservedGeneration: 10,
|
||||
DesiredNumberScheduled: 1,
|
||||
UpdatedNumberScheduled: 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = mocks.controller.isDaemonSetReady(&appsv1.DaemonSet{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Generation: 9,
|
||||
},
|
||||
Status: appsv1.DaemonSetStatus{
|
||||
ObservedGeneration: 10,
|
||||
DesiredNumberScheduled: 2,
|
||||
UpdatedNumberScheduled: 1,
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ex "github.com/pkg/errors"
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// SyncStatus encodes the canary pod spec and updates the canary status
|
||||
func (c *DaemonSetController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
|
||||
dep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("daemonset %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
|
||||
}
|
||||
return ex.Wrap(err, "SyncStatus daemonset query error")
|
||||
}
|
||||
|
||||
configs, err := c.configTracker.GetConfigRefs(cd)
|
||||
if err != nil {
|
||||
return ex.Wrap(err, "SyncStatus configs query error")
|
||||
}
|
||||
|
||||
return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
|
||||
cdCopy.Status.TrackedConfigs = configs
|
||||
})
|
||||
}
|
||||
|
||||
// SetStatusFailedChecks updates the canary failed checks counter
|
||||
func (c *DaemonSetController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusFailedChecks(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
// SetStatusWeight updates the canary status weight value
|
||||
func (c *DaemonSetController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusWeight(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
// SetStatusIterations updates the canary status iterations value
|
||||
func (c *DaemonSetController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusIterations(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
// SetStatusPhase updates the canary status phase
|
||||
func (c *DaemonSetController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
|
||||
return setStatusPhase(c.flaggerClient, cd, phase)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestDaemonSetController_SyncStatus(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
status := flaggerv1.CanaryStatus{
|
||||
Phase: flaggerv1.CanaryPhaseProgressing,
|
||||
FailedChecks: 2,
|
||||
}
|
||||
err = mocks.controller.SyncStatus(mocks.canary, status)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if res.Status.Phase != status.Phase {
|
||||
t.Errorf("Got state %v wanted %v", res.Status.Phase, status.Phase)
|
||||
}
|
||||
|
||||
if res.Status.FailedChecks != status.FailedChecks {
|
||||
t.Errorf("Got failed checks %v wanted %v", res.Status.FailedChecks, status.FailedChecks)
|
||||
}
|
||||
|
||||
if res.Status.TrackedConfigs == nil {
|
||||
t.Fatalf("Status tracking configs are empty")
|
||||
}
|
||||
configs := *res.Status.TrackedConfigs
|
||||
secret := newDaemonSetControllerTestSecret()
|
||||
if _, exists := configs["secret/"+secret.GetName()]; !exists {
|
||||
t.Errorf("Secret %s not found in status", secret.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_SetFailedChecks(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.controller.SetStatusFailedChecks(mocks.canary, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if res.Status.FailedChecks != 1 {
|
||||
t.Errorf("Got %v wanted %v", res.Status.FailedChecks, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetController_SetState(t *testing.T) {
|
||||
mocks := newDaemonSetFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.controller.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if res.Status.Phase != flaggerv1.CanaryPhaseProgressing {
|
||||
t.Errorf("Got %v wanted %v", res.Status.Phase, flaggerv1.CanaryPhaseProgressing)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.uber.org/zap"
|
||||
@@ -13,7 +11,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
@@ -107,7 +104,7 @@ func (c *DeploymentController) Promote(cd *flaggerv1.Canary) error {
|
||||
primaryCopy.Spec.Template.Spec = c.configTracker.ApplyPrimaryConfigs(canary.Spec.Template.Spec, configRefs)
|
||||
|
||||
// update pod annotations to ensure a rolling update
|
||||
annotations, err := c.makeAnnotations(canary.Spec.Template.Annotations)
|
||||
annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -211,7 +208,7 @@ func (c *DeploymentController) GetMetadata(cd *flaggerv1.Canary) (string, map[st
|
||||
|
||||
var ports map[string]int32
|
||||
if cd.Spec.Service.PortDiscovery {
|
||||
p, err := c.getPorts(cd, canaryDep)
|
||||
p, err := getPorts(cd, canaryDep.Spec.Template.Spec.Containers)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("port discovery failed with error: %v", err)
|
||||
}
|
||||
@@ -248,7 +245,7 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err
|
||||
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
|
||||
return err
|
||||
}
|
||||
annotations, err := c.makeAnnotations(canaryDep.Spec.Template.Annotations)
|
||||
annotations, err := makeAnnotations(canaryDep.Spec.Template.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -383,29 +380,6 @@ func (c *DeploymentController) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bo
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeAnnotations appends an unique ID to annotations map
|
||||
func (c *DeploymentController) makeAnnotations(annotations map[string]string) (map[string]string, error) {
|
||||
idKey := "flagger-id"
|
||||
res := make(map[string]string)
|
||||
uuid := make([]byte, 16)
|
||||
n, err := io.ReadFull(rand.Reader, uuid)
|
||||
if n != len(uuid) || err != nil {
|
||||
return res, err
|
||||
}
|
||||
uuid[8] = uuid[8]&^0xc0 | 0x80
|
||||
uuid[6] = uuid[6]&^0xf0 | 0x40
|
||||
id := fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
|
||||
|
||||
for k, v := range annotations {
|
||||
if k != idKey {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
res[idKey] = id
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// getSelectorLabel returns the selector match label
|
||||
func (c *DeploymentController) getSelectorLabel(deployment *appsv1.Deployment) (string, error) {
|
||||
for _, l := range c.labels {
|
||||
@@ -417,74 +391,6 @@ func (c *DeploymentController) getSelectorLabel(deployment *appsv1.Deployment) (
|
||||
return "", fmt.Errorf("selector not found")
|
||||
}
|
||||
|
||||
var sidecars = map[string]bool{
|
||||
"istio-proxy": true,
|
||||
"envoy": true,
|
||||
}
|
||||
|
||||
func (c *DeploymentController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
|
||||
return c.configTracker.HasConfigChanged(cd)
|
||||
}
|
||||
|
||||
// getPorts returns a list of all container ports
|
||||
func (c *DeploymentController) getPorts(cd *flaggerv1.Canary, deployment *appsv1.Deployment) (map[string]int32, error) {
|
||||
ports := make(map[string]int32)
|
||||
|
||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||
// exclude service mesh proxies based on container name
|
||||
if _, ok := sidecars[container.Name]; ok {
|
||||
continue
|
||||
}
|
||||
for i, p := range container.Ports {
|
||||
// exclude canary.service.port or canary.service.targetPort
|
||||
if cd.Spec.Service.TargetPort.String() == "0" {
|
||||
if p.ContainerPort == cd.Spec.Service.Port {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if cd.Spec.Service.TargetPort.Type == intstr.Int {
|
||||
if p.ContainerPort == cd.Spec.Service.TargetPort.IntVal {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if cd.Spec.Service.TargetPort.Type == intstr.String {
|
||||
if p.Name == cd.Spec.Service.TargetPort.StrVal {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
name := fmt.Sprintf("tcp-%s-%v", container.Name, i)
|
||||
if p.Name != "" {
|
||||
name = p.Name
|
||||
}
|
||||
|
||||
ports[name] = p.ContainerPort
|
||||
}
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func makePrimaryLabels(labels map[string]string, primaryName string, label string) map[string]string {
|
||||
res := make(map[string]string)
|
||||
for k, v := range labels {
|
||||
if k != label {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
res[label] = primaryName
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func int32p(i int32) *int32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
func int32Default(i *int32) int32 {
|
||||
if i == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
return *i
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
)
|
||||
|
||||
func TestCanaryDeployer_Sync(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_Sync(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func TestCanaryDeployer_Sync(t *testing.T) {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dep := newTestDeployment()
|
||||
dep := newDeploymentControllerTest()
|
||||
|
||||
primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image
|
||||
sourceImage := dep.Spec.Template.Spec.Containers[0].Image
|
||||
@@ -41,20 +41,20 @@ func TestCanaryDeployer_Sync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_Promote(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_Promote(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dep2 := newTestDeploymentV2()
|
||||
dep2 := newDeploymentControllerTestV2()
|
||||
_, err = mocks.kubeClient.AppsV1().Deployments("default").Update(dep2)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
config2 := NewTestConfigMapV2()
|
||||
config2 := newDeploymentControllerTestConfigMapV2()
|
||||
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(config2)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
@@ -72,7 +72,7 @@ func TestCanaryDeployer_Promote(t *testing.T) {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.deployer.Promote(mocks.canary)
|
||||
err = mocks.controller.Promote(mocks.canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -107,32 +107,32 @@ func TestCanaryDeployer_Promote(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_IsReady(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_IsReady(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Error("Expected primary readiness check to fail")
|
||||
}
|
||||
|
||||
_, err = mocks.deployer.IsPrimaryReady(mocks.canary)
|
||||
_, err = mocks.controller.IsPrimaryReady(mocks.canary)
|
||||
if err == nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = mocks.deployer.IsCanaryReady(mocks.canary)
|
||||
_, err = mocks.controller.IsCanaryReady(mocks.canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_SetFailedChecks(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_SetFailedChecks(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.deployer.SetStatusFailedChecks(mocks.canary, 1)
|
||||
err = mocks.controller.SetStatusFailedChecks(mocks.canary, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -147,14 +147,14 @@ func TestCanaryDeployer_SetFailedChecks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_SetState(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_SetState(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.deployer.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
|
||||
err = mocks.controller.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -169,9 +169,9 @@ func TestCanaryDeployer_SetState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_SyncStatus(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_SyncStatus(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) {
|
||||
Phase: flaggerv1.CanaryPhaseProgressing,
|
||||
FailedChecks: 2,
|
||||
}
|
||||
err = mocks.deployer.SyncStatus(mocks.canary, status)
|
||||
err = mocks.controller.SyncStatus(mocks.canary, status)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -202,20 +202,20 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) {
|
||||
t.Fatalf("Status tracking configs are empty")
|
||||
}
|
||||
configs := *res.Status.TrackedConfigs
|
||||
secret := newTestSecret()
|
||||
secret := newDeploymentControllerTestSecret()
|
||||
if _, exists := configs["secret/"+secret.GetName()]; !exists {
|
||||
t.Errorf("Secret %s not found in status", secret.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_Scale(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_Scale(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
err = mocks.deployer.Scale(mocks.canary, 2)
|
||||
err = mocks.controller.Scale(mocks.canary, 2)
|
||||
|
||||
c, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
@@ -227,11 +227,11 @@ func TestCanaryDeployer_Scale(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_NoConfigTracking(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
mocks.deployer.configTracker = &NopTracker{}
|
||||
func TestDeploymentController_NoConfigTracking(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
mocks.controller.configTracker = &NopTracker{}
|
||||
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -252,9 +252,9 @@ func TestCanaryDeployer_NoConfigTracking(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
mocks := newFixture()
|
||||
err := mocks.deployer.Initialize(mocks.canary, true)
|
||||
func TestDeploymentController_HasTargetChanged(t *testing.T) {
|
||||
mocks := newDeploymentFixture()
|
||||
err := mocks.controller.Initialize(mocks.canary, true)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -264,7 +264,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = mocks.deployer.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitializing})
|
||||
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitializing})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -274,7 +274,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = mocks.deployer.SetStatusPhase(canary, flaggerv1.CanaryPhaseInitialized)
|
||||
err = mocks.controller.SetStatusPhase(canary, flaggerv1.CanaryPhaseInitialized)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
}
|
||||
|
||||
// detect change in last applied spec
|
||||
isNew, err := mocks.deployer.HasTargetChanged(canary)
|
||||
isNew, err := mocks.controller.HasTargetChanged(canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -312,7 +312,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
}
|
||||
|
||||
// save hash
|
||||
err = mocks.deployer.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing})
|
||||
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -341,7 +341,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
}
|
||||
|
||||
// ignore change as hash should be the same with last promoted
|
||||
isNew, err = mocks.deployer.HasTargetChanged(canary)
|
||||
isNew, err = mocks.controller.HasTargetChanged(canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
|
||||
}
|
||||
|
||||
// detect change
|
||||
isNew, err = mocks.deployer.HasTargetChanged(canary)
|
||||
isNew, err = mocks.controller.HasTargetChanged(canary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
@@ -16,36 +16,36 @@ import (
|
||||
fakeFlagger "github.com/weaveworks/flagger/pkg/client/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
type deploymentControllerFixture struct {
|
||||
canary *flaggerv1.Canary
|
||||
kubeClient kubernetes.Interface
|
||||
flaggerClient clientset.Interface
|
||||
deployer DeploymentController
|
||||
controller DeploymentController
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func newFixture() fixture {
|
||||
func newDeploymentFixture() deploymentControllerFixture {
|
||||
// init canary
|
||||
canary := newTestCanary()
|
||||
canary := newDeploymentControllerTestCanary()
|
||||
flaggerClient := fakeFlagger.NewSimpleClientset(canary)
|
||||
|
||||
// init kube clientset and register mock objects
|
||||
kubeClient := fake.NewSimpleClientset(
|
||||
newTestDeployment(),
|
||||
newTestHPA(),
|
||||
newTestConfigMap(),
|
||||
newTestConfigMapEnv(),
|
||||
newTestConfigMapVol(),
|
||||
newTestConfigProjected(),
|
||||
newTestSecret(),
|
||||
newTestSecretEnv(),
|
||||
newTestSecretVol(),
|
||||
newTestSecretProjected(),
|
||||
newDeploymentControllerTest(),
|
||||
newDeploymentControllerTestHPA(),
|
||||
newDeploymentControllerTestConfigMap(),
|
||||
newDeploymentControllerTestConfigMapEnv(),
|
||||
newDeploymentControllerTestConfigMapVol(),
|
||||
newDeploymentControllerTestConfigProjected(),
|
||||
newDeploymentControllerTestSecret(),
|
||||
newDeploymentControllerTestSecretEnv(),
|
||||
newDeploymentControllerTestSecretVol(),
|
||||
newDeploymentControllerTestSecretProjected(),
|
||||
)
|
||||
|
||||
logger, _ := logger.NewLogger("debug")
|
||||
|
||||
deployer := DeploymentController{
|
||||
ctrl := DeploymentController{
|
||||
flaggerClient: flaggerClient,
|
||||
kubeClient: kubeClient,
|
||||
logger: logger,
|
||||
@@ -57,16 +57,16 @@ func newFixture() fixture {
|
||||
},
|
||||
}
|
||||
|
||||
return fixture{
|
||||
return deploymentControllerFixture{
|
||||
canary: canary,
|
||||
deployer: deployer,
|
||||
controller: ctrl,
|
||||
logger: logger,
|
||||
flaggerClient: flaggerClient,
|
||||
kubeClient: kubeClient,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestConfigMap() *corev1.ConfigMap {
|
||||
func newDeploymentControllerTestConfigMap() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -79,7 +79,7 @@ func newTestConfigMap() *corev1.ConfigMap {
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestConfigMapV2() *corev1.ConfigMap {
|
||||
func newDeploymentControllerTestConfigMapV2() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -93,7 +93,7 @@ func NewTestConfigMapV2() *corev1.ConfigMap {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestConfigProjected() *corev1.ConfigMap {
|
||||
func newDeploymentControllerTestConfigProjected() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -106,7 +106,7 @@ func newTestConfigProjected() *corev1.ConfigMap {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestConfigMapEnv() *corev1.ConfigMap {
|
||||
func newDeploymentControllerTestConfigMapEnv() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -119,7 +119,7 @@ func newTestConfigMapEnv() *corev1.ConfigMap {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestConfigMapVol() *corev1.ConfigMap {
|
||||
func newDeploymentControllerTestConfigMapVol() *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -132,7 +132,7 @@ func newTestConfigMapVol() *corev1.ConfigMap {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSecret() *corev1.Secret {
|
||||
func newDeploymentControllerTestSecret() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -146,7 +146,7 @@ func newTestSecret() *corev1.Secret {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSecretProjected() *corev1.Secret {
|
||||
func newDeploymentControllerTestSecretProjected() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -160,7 +160,7 @@ func newTestSecretProjected() *corev1.Secret {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSecretEnv() *corev1.Secret {
|
||||
func newDeploymentControllerTestSecretEnv() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -174,7 +174,7 @@ func newTestSecretEnv() *corev1.Secret {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSecretVol() *corev1.Secret {
|
||||
func newDeploymentControllerTestSecretVol() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -188,7 +188,7 @@ func newTestSecretVol() *corev1.Secret {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestCanary() *flaggerv1.Canary {
|
||||
func newDeploymentControllerTestCanary() *flaggerv1.Canary {
|
||||
cd := &flaggerv1.Canary{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -217,7 +217,7 @@ func newTestCanary() *flaggerv1.Canary {
|
||||
return cd
|
||||
}
|
||||
|
||||
func newTestDeployment() *appsv1.Deployment {
|
||||
func newDeploymentControllerTest() *appsv1.Deployment {
|
||||
d := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -376,7 +376,7 @@ func newTestDeployment() *appsv1.Deployment {
|
||||
return d
|
||||
}
|
||||
|
||||
func newTestDeploymentV2() *appsv1.Deployment {
|
||||
func newDeploymentControllerTestV2() *appsv1.Deployment {
|
||||
d := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -521,7 +521,7 @@ func newTestDeploymentV2() *appsv1.Deployment {
|
||||
return d
|
||||
}
|
||||
|
||||
func newTestHPA() *hpav2.HorizontalPodAutoscaler {
|
||||
func newDeploymentControllerTestHPA() *hpav2.HorizontalPodAutoscaler {
|
||||
h := &hpav2.HorizontalPodAutoscaler{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: hpav2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -36,7 +36,7 @@ func (c *DeploymentController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsCanaryReady checks the primary deployment status and returns an error if
|
||||
// IsCanaryReady checks the canary deployment status and returns an error if
|
||||
// the deployment is in the middle of a rolling update or if the pods are unhealthy
|
||||
// it will return a non retriable error if the rolling update is stuck
|
||||
func (c *DeploymentController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error) {
|
||||
@@ -0,0 +1,51 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ex "github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
)
|
||||
|
||||
// SyncStatus encodes the canary pod spec and updates the canary status
|
||||
func (c *DeploymentController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
|
||||
dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("deployment %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
|
||||
}
|
||||
return ex.Wrap(err, "SyncStatus deployment query error")
|
||||
}
|
||||
|
||||
configs, err := c.configTracker.GetConfigRefs(cd)
|
||||
if err != nil {
|
||||
return ex.Wrap(err, "SyncStatus configs query error")
|
||||
}
|
||||
|
||||
return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
|
||||
cdCopy.Status.TrackedConfigs = configs
|
||||
})
|
||||
}
|
||||
|
||||
// SetStatusFailedChecks updates the canary failed checks counter
|
||||
func (c *DeploymentController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusFailedChecks(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
// SetStatusWeight updates the canary status weight value
|
||||
func (c *DeploymentController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusWeight(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
// SetStatusIterations updates the canary status iterations value
|
||||
func (c *DeploymentController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusIterations(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
// SetStatusPhase updates the canary status phase
|
||||
func (c *DeploymentController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
|
||||
return setStatusPhase(c.flaggerClient, cd, phase)
|
||||
}
|
||||
@@ -37,6 +37,13 @@ func (factory *Factory) Controller(kind string) Controller {
|
||||
labels: factory.labels,
|
||||
configTracker: factory.configTracker,
|
||||
}
|
||||
daemonSetCtrl := &DaemonSetController{
|
||||
logger: factory.logger,
|
||||
kubeClient: factory.kubeClient,
|
||||
flaggerClient: factory.flaggerClient,
|
||||
labels: factory.labels,
|
||||
configTracker: factory.configTracker,
|
||||
}
|
||||
serviceCtrl := &ServiceController{
|
||||
logger: factory.logger,
|
||||
kubeClient: factory.kubeClient,
|
||||
@@ -44,6 +51,8 @@ func (factory *Factory) Controller(kind string) Controller {
|
||||
}
|
||||
|
||||
switch {
|
||||
case kind == "DaemonSet":
|
||||
return daemonSetCtrl
|
||||
case kind == "Deployment":
|
||||
return deploymentCtrl
|
||||
case kind == "Service":
|
||||
|
||||
+8
-49
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
ex "github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/util/retry"
|
||||
|
||||
@@ -14,26 +13,6 @@ import (
|
||||
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
// SyncStatus encodes the canary pod spec and updates the canary status
|
||||
func (c *DeploymentController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
|
||||
dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("deployment %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
|
||||
}
|
||||
return ex.Wrap(err, "SyncStatus deployment query error")
|
||||
}
|
||||
|
||||
configs, err := c.configTracker.GetConfigRefs(cd)
|
||||
if err != nil {
|
||||
return ex.Wrap(err, "SyncStatus configs query error")
|
||||
}
|
||||
|
||||
return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
|
||||
cdCopy.Status.TrackedConfigs = configs
|
||||
})
|
||||
}
|
||||
|
||||
func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, status flaggerv1.CanaryStatus, canaryResource interface{}, setAll func(cdCopy *flaggerv1.Canary)) error {
|
||||
hash := computeHash(canaryResource)
|
||||
|
||||
@@ -56,7 +35,7 @@ func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, s
|
||||
cdCopy.Status.LastTransitionTime = metav1.Now()
|
||||
setAll(cdCopy)
|
||||
|
||||
if ok, conditions := MakeStatusConditions(cd.Status, status.Phase); ok {
|
||||
if ok, conditions := MakeStatusConditions(cd, status.Phase); ok {
|
||||
cdCopy.Status.Conditions = conditions
|
||||
}
|
||||
|
||||
@@ -70,11 +49,6 @@ func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, s
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStatusFailedChecks updates the canary failed checks counter
|
||||
func (c *DeploymentController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusFailedChecks(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
func setStatusFailedChecks(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
|
||||
firstTry := true
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
@@ -99,11 +73,6 @@ func setStatusFailedChecks(flaggerClient clientset.Interface, cd *flaggerv1.Cana
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStatusWeight updates the canary status weight value
|
||||
func (c *DeploymentController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusWeight(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
func setStatusWeight(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
|
||||
firstTry := true
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
@@ -128,11 +97,6 @@ func setStatusWeight(flaggerClient clientset.Interface, cd *flaggerv1.Canary, va
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStatusIterations updates the canary status iterations value
|
||||
func (c *DeploymentController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
|
||||
return setStatusIterations(c.flaggerClient, cd, val)
|
||||
}
|
||||
|
||||
func setStatusIterations(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
|
||||
firstTry := true
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
@@ -159,11 +123,6 @@ func setStatusIterations(flaggerClient clientset.Interface, cd *flaggerv1.Canary
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStatusPhase updates the canary status phase
|
||||
func (c *DeploymentController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
|
||||
return setStatusPhase(c.flaggerClient, cd, phase)
|
||||
}
|
||||
|
||||
func setStatusPhase(flaggerClient clientset.Interface, cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
|
||||
firstTry := true
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
@@ -189,7 +148,7 @@ func setStatusPhase(flaggerClient clientset.Interface, cd *flaggerv1.Canary, pha
|
||||
cdCopy.Status.LastPromotedSpec = cd.Status.LastAppliedSpec
|
||||
}
|
||||
|
||||
if ok, conditions := MakeStatusConditions(cdCopy.Status, phase); ok {
|
||||
if ok, conditions := MakeStatusConditions(cdCopy, phase); ok {
|
||||
cdCopy.Status.Conditions = conditions
|
||||
}
|
||||
|
||||
@@ -215,19 +174,19 @@ func getStatusCondition(status flaggerv1.CanaryStatus, conditionType flaggerv1.C
|
||||
}
|
||||
|
||||
// MakeStatusCondition updates the canary status conditions based on canary phase
|
||||
func MakeStatusConditions(canaryStatus flaggerv1.CanaryStatus,
|
||||
func MakeStatusConditions(cd *flaggerv1.Canary,
|
||||
phase flaggerv1.CanaryPhase) (bool, []flaggerv1.CanaryCondition) {
|
||||
currentCondition := getStatusCondition(canaryStatus, flaggerv1.PromotedType)
|
||||
currentCondition := getStatusCondition(cd.Status, flaggerv1.PromotedType)
|
||||
|
||||
message := "New deployment detected, starting initialization."
|
||||
message := fmt.Sprintf("New %s detected, starting initialization.", cd.Spec.TargetRef.Kind)
|
||||
status := corev1.ConditionUnknown
|
||||
switch phase {
|
||||
case flaggerv1.CanaryPhaseInitializing:
|
||||
status = corev1.ConditionUnknown
|
||||
message = "New deployment detected, starting initialization."
|
||||
message = fmt.Sprintf("New %s detected, starting initialization.", cd.Spec.TargetRef.Kind)
|
||||
case flaggerv1.CanaryPhaseInitialized:
|
||||
status = corev1.ConditionTrue
|
||||
message = "Deployment initialization completed."
|
||||
message = fmt.Sprintf("%s initialization completed.", cd.Spec.TargetRef.Kind)
|
||||
case flaggerv1.CanaryPhaseWaiting:
|
||||
status = corev1.ConditionUnknown
|
||||
message = "Waiting for approval."
|
||||
@@ -245,7 +204,7 @@ func MakeStatusConditions(canaryStatus flaggerv1.CanaryStatus,
|
||||
message = "Canary analysis completed successfully, promotion finished."
|
||||
case flaggerv1.CanaryPhaseFailed:
|
||||
status = corev1.ConditionFalse
|
||||
message = "Canary analysis failed, deployment scaled to zero."
|
||||
message = fmt.Sprintf("Canary analysis failed, %s scaled to zero.", cd.Spec.TargetRef.Kind)
|
||||
}
|
||||
|
||||
newCondition := &flaggerv1.CanaryCondition{
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package canary
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
|
||||
)
|
||||
|
||||
var sidecars = map[string]bool{
|
||||
"istio-proxy": true,
|
||||
"envoy": true,
|
||||
}
|
||||
|
||||
func getPorts(cd *flaggerv1.Canary, cs []corev1.Container) (map[string]int32, error) {
|
||||
ports := make(map[string]int32, len(cs))
|
||||
for _, container := range cs {
|
||||
// exclude service mesh proxies based on container name
|
||||
if _, ok := sidecars[container.Name]; ok {
|
||||
continue
|
||||
}
|
||||
for i, p := range container.Ports {
|
||||
// exclude canary.service.port or canary.service.targetPort
|
||||
if cd.Spec.Service.TargetPort.String() == "0" {
|
||||
if p.ContainerPort == cd.Spec.Service.Port {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if cd.Spec.Service.TargetPort.Type == intstr.Int {
|
||||
if p.ContainerPort == cd.Spec.Service.TargetPort.IntVal {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if cd.Spec.Service.TargetPort.Type == intstr.String {
|
||||
if p.Name == cd.Spec.Service.TargetPort.StrVal {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
name := fmt.Sprintf("tcp-%s-%v", container.Name, i)
|
||||
if p.Name != "" {
|
||||
name = p.Name
|
||||
}
|
||||
|
||||
ports[name] = p.ContainerPort
|
||||
}
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
// makeAnnotations appends an unique ID to annotations map
|
||||
func makeAnnotations(annotations map[string]string) (map[string]string, error) {
|
||||
idKey := "flagger-id"
|
||||
res := make(map[string]string)
|
||||
uuid := make([]byte, 16)
|
||||
n, err := io.ReadFull(rand.Reader, uuid)
|
||||
if n != len(uuid) || err != nil {
|
||||
return res, err
|
||||
}
|
||||
uuid[8] = uuid[8]&^0xc0 | 0x80
|
||||
uuid[6] = uuid[6]&^0xf0 | 0x40
|
||||
id := fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
|
||||
|
||||
for k, v := range annotations {
|
||||
if k != idKey {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
res[idKey] = id
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func makePrimaryLabels(labels map[string]string, primaryName string, label string) map[string]string {
|
||||
res := make(map[string]string)
|
||||
for k, v := range labels {
|
||||
if k != label {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
res[label] = primaryName
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func int32p(i int32) *int32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
func int32Default(i *int32) int32 {
|
||||
if i == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
return *i
|
||||
}
|
||||
Reference in New Issue
Block a user