You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by as...@apache.org on 2022/08/04 07:41:02 UTC
[camel-k] 01/02: re-deployment strategies #2256
This is an automated email from the ASF dual-hosted git repository.
astefanutti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 7eee5bc10e060d8776727ee4575d24461dc4c879
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Wed Aug 3 15:56:21 2022 +0200
re-deployment strategies #2256
---
e2e/global/common/traits/deployment_test.go | 98 +++++++++++++++++++++++++++++
pkg/apis/camel/v1/trait/deployment.go | 19 ++++++
pkg/trait/deployment.go | 34 +++++++++-
pkg/trait/deployment_test.go | 88 ++++++++++++++++++++++++++
4 files changed, 238 insertions(+), 1 deletion(-)
diff --git a/e2e/global/common/traits/deployment_test.go b/e2e/global/common/traits/deployment_test.go
new file mode 100644
index 000000000..9d9a23c22
--- /dev/null
+++ b/e2e/global/common/traits/deployment_test.go
@@ -0,0 +1,98 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package traits
+
+import (
+ appsv1 "k8s.io/api/apps/v1"
+ "testing"
+
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gstruct"
+
+ corev1 "k8s.io/api/core/v1"
+
+ . "github.com/apache/camel-k/e2e/support"
+ v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+)
+
+func TestRecreateDeploymentStrategyTrait(t *testing.T) {
+ WithNewTestNamespace(t, func(ns string) {
+ operatorID := "camel-k-trait-jolokia"
+ Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+ t.Run("Run with Recreate Deployment Strategy", func(t *testing.T) {
+ Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+ "-t", "deployment.strategy="+string(appsv1.RecreateDeploymentStrategyType)).
+ Execute()).To(Succeed())
+
+ Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+ Eventually(IntegrationConditionStatus(ns, "java", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+ Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+ Eventually(Deployment(ns, "java"), TestTimeoutMedium).Should(PointTo(MatchFields(IgnoreExtras,
+ Fields{
+ "Spec": MatchFields(IgnoreExtras,
+ Fields{
+ "Strategy": MatchFields(IgnoreExtras,
+ Fields{
+ "Type": Equal(appsv1.RecreateDeploymentStrategyType),
+ }),
+ }),
+ }),
+ ))
+
+ Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+ })
+ })
+}
+
+func TestRollingUpdateDeploymentStrategyTrait(t *testing.T) {
+ WithNewTestNamespace(t, func(ns string) {
+ operatorID := "camel-k-trait-jolokia"
+ Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+ t.Run("Run with RollingUpdate Deployment Strategy", func(t *testing.T) {
+ Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+ "-t", "deployment.strategy="+string(appsv1.RollingUpdateDeploymentStrategyType)).
+ Execute()).To(Succeed())
+
+ Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+ Eventually(IntegrationConditionStatus(ns, "java", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+ Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+ Eventually(Deployment(ns, "java"), TestTimeoutMedium).Should(PointTo(MatchFields(IgnoreExtras,
+ Fields{
+ "Spec": MatchFields(IgnoreExtras,
+ Fields{
+ "Strategy": MatchFields(IgnoreExtras,
+ Fields{
+ "Type": Equal(appsv1.RollingUpdateDeploymentStrategyType),
+ }),
+ }),
+ }),
+ ))
+
+ Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+ })
+ })
+}
diff --git a/pkg/apis/camel/v1/trait/deployment.go b/pkg/apis/camel/v1/trait/deployment.go
index f862c66f9..c670a210f 100644
--- a/pkg/apis/camel/v1/trait/deployment.go
+++ b/pkg/apis/camel/v1/trait/deployment.go
@@ -17,6 +17,10 @@ limitations under the License.
package trait
+import (
+ appsv1 "k8s.io/api/apps/v1"
+)
+
// The Deployment trait is responsible for generating the Kubernetes deployment that will make sure
// the integration will run in the cluster.
//
@@ -26,4 +30,19 @@ type DeploymentTrait struct {
// The maximum time in seconds for the deployment to make progress before it
// is considered to be failed. It defaults to 60s.
ProgressDeadlineSeconds *int32 `property:"progress-deadline-seconds" json:"progressDeadlineSeconds,omitempty"`
+ // The deployment strategy to use to replace existing pods with new ones.
+ Strategy appsv1.DeploymentStrategyType `property:"strategy" json:"strategy,omitempty"`
+ // The maximum number of pods that can be unavailable during the update.
+ // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+ // Absolute number is calculated from percentage by rounding down.
+ // This can not be 0 if MaxSurge is 0.
+ // Defaults to 25%.
+ RollingUpdateMaxUnavailable *int `property:"rolling-update-max-unavailable" json:"rollingUpdateMaxUnavailable,omitempty"`
+ // The maximum number of pods that can be scheduled above the desired number of
+ // pods.
+ // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+ // This can not be 0 if MaxUnavailable is 0.
+ // Absolute number is calculated from percentage by rounding up.
+ // Defaults to 25%.
+ RollingUpdateMaxSurge *int `property:"rolling-update-max-surge" json:"rollingUpdateMaxSurge,omitempty"`
}
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index d7a966d93..35a3eeada 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -20,10 +20,12 @@ package trait
import (
"fmt"
+ "k8s.io/apimachinery/pkg/util/intstr"
+ "k8s.io/utils/pointer"
+
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/utils/pointer"
v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
@@ -165,6 +167,36 @@ func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
},
}
+ switch t.Strategy {
+ case appsv1.RecreateDeploymentStrategyType:
+ deployment.Spec.Strategy = appsv1.DeploymentStrategy{
+ Type: t.Strategy,
+ }
+ case appsv1.RollingUpdateDeploymentStrategyType:
+ deployment.Spec.Strategy = appsv1.DeploymentStrategy{
+ Type: t.Strategy,
+ }
+
+ if t.RollingUpdateMaxSurge != nil || t.RollingUpdateMaxUnavailable != nil {
+ var maxSurge *intstr.IntOrString
+ var maxUnavailable *intstr.IntOrString
+
+ if t.RollingUpdateMaxSurge != nil {
+ v := intstr.FromInt(*t.RollingUpdateMaxSurge)
+ maxSurge = &v
+ }
+ if t.RollingUpdateMaxUnavailable != nil {
+ v := intstr.FromInt(*t.RollingUpdateMaxUnavailable)
+ maxUnavailable = &v
+ }
+
+ deployment.Spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
+ MaxSurge: maxSurge,
+ MaxUnavailable: maxUnavailable,
+ }
+ }
+ }
+
// Reconcile the deployment replicas
replicas := e.Integration.Spec.Replicas
// Deployment replicas defaults to 1, so we avoid forcing
diff --git a/pkg/trait/deployment_test.go b/pkg/trait/deployment_test.go
index 015cbf7de..d0235e5af 100644
--- a/pkg/trait/deployment_test.go
+++ b/pkg/trait/deployment_test.go
@@ -147,6 +147,94 @@ func TestApplyDeploymentTraitWithProgressDeadline(t *testing.T) {
assert.Equal(t, int32(120), *deployment.Spec.ProgressDeadlineSeconds)
}
+func TestApplyDeploymentTraitWitRecresteStrategy(t *testing.T) {
+ deploymentTrait, environment := createNominalDeploymentTest()
+ maxSurge := 10
+
+ deploymentTrait.Strategy = appsv1.RecreateDeploymentStrategyType
+ deploymentTrait.RollingUpdateMaxSurge = &maxSurge
+
+ environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+ err := deploymentTrait.Apply(environment)
+
+ assert.Nil(t, err)
+
+ deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+ assert.NotNil(t, deployment)
+ assert.Equal(t, "integration-name", deployment.Name)
+ assert.Equal(t, appsv1.RecreateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+ assert.Nil(t, deployment.Spec.Strategy.RollingUpdate)
+}
+
+func TestApplyDeploymentTraitWitRollingUpdateStrategy(t *testing.T) {
+
+ t.Run("with defaults", func(t *testing.T) {
+ deploymentTrait, environment := createNominalDeploymentTest()
+
+ deploymentTrait.Strategy = appsv1.RollingUpdateDeploymentStrategyType
+ environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+ err := deploymentTrait.Apply(environment)
+
+ assert.Nil(t, err)
+
+ deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+ assert.NotNil(t, deployment)
+ assert.Equal(t, "integration-name", deployment.Name)
+ assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+ assert.Nil(t, deployment.Spec.Strategy.RollingUpdate)
+ })
+
+ t.Run("with surge", func(t *testing.T) {
+ deploymentTrait, environment := createNominalDeploymentTest()
+
+ maxSurge := 10
+
+ deploymentTrait.Strategy = appsv1.RollingUpdateDeploymentStrategyType
+ deploymentTrait.RollingUpdateMaxSurge = &maxSurge
+
+ environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+ err := deploymentTrait.Apply(environment)
+
+ assert.Nil(t, err)
+
+ deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+ assert.NotNil(t, deployment)
+ assert.Equal(t, "integration-name", deployment.Name)
+ assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+ assert.NotNil(t, deployment.Spec.Strategy.RollingUpdate)
+ assert.Nil(t, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable)
+ assert.Equal(t, maxSurge, deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue())
+ })
+
+ t.Run("with surge and unavailable", func(t *testing.T) {
+ deploymentTrait, environment := createNominalDeploymentTest()
+
+ maxSurge := 10
+ maxUnavailable := 11
+
+ deploymentTrait.Strategy = appsv1.RollingUpdateDeploymentStrategyType
+ deploymentTrait.RollingUpdateMaxSurge = &maxSurge
+ deploymentTrait.RollingUpdateMaxUnavailable = &maxUnavailable
+
+ environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+ err := deploymentTrait.Apply(environment)
+
+ assert.Nil(t, err)
+
+ deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+ assert.NotNil(t, deployment)
+ assert.Equal(t, "integration-name", deployment.Name)
+ assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+ assert.NotNil(t, deployment.Spec.Strategy.RollingUpdate)
+ assert.Equal(t, maxUnavailable, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue())
+ assert.Equal(t, maxSurge, deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue())
+ })
+}
+
func createNominalDeploymentTest() (*deploymentTrait, *Environment) {
trait, _ := newDeploymentTrait().(*deploymentTrait)
trait.Enabled = pointer.Bool(true)