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 <lburgazz...@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)

Reply via email to