keep deployment paused on rollout deletion behind feature‑gate
Signed-off-by: GautamBytes <manchandanigautam@gmail.com>
This commit is contained in:
parent
deaa5f38b5
commit
79bc354eea
|
|
@ -44,6 +44,9 @@ const (
|
|||
// RollbackInBatchAnnotation is set to rollout annotations.
|
||||
// RollbackInBatchAnnotation allow use disable quick rollback, and will roll back in batch style.
|
||||
RollbackInBatchAnnotation = "rollouts.kruise.io/rollback-in-batch"
|
||||
|
||||
// RolloutFinalizer is the finalizer string that the Rollout controller adds to Rollout objects.
|
||||
RolloutFinalizer = "rollouts.kruise.io/finalizer"
|
||||
)
|
||||
|
||||
// RolloutSpec defines the desired state of Rollout
|
||||
|
|
|
|||
|
|
@ -21,10 +21,13 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
|
||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||
"github.com/openkruise/rollouts/api/v1beta1"
|
||||
"github.com/openkruise/rollouts/pkg/trafficrouting"
|
||||
"github.com/openkruise/rollouts/pkg/feature"
|
||||
utilfeature "github.com/openkruise/rollouts/pkg/util/feature"
|
||||
"github.com/openkruise/rollouts/pkg/util"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
|
@ -305,7 +308,27 @@ func (m *blueGreenReleaseManager) doCanaryFinalising(c *RolloutContext) (bool, e
|
|||
switch blueGreenStatus.FinalisingStep {
|
||||
// set workload.pause=false; set workload.partition=0
|
||||
case v1beta1.FinalisingStepResumeWorkload:
|
||||
retry, err = finalizingBatchRelease(m.Client, c)
|
||||
// If the rollout is being deleted and the feature gate is enabled,
|
||||
// we skip this step entirely to keep the deployment paused.
|
||||
if c.FinalizeReason == v1beta1.FinaliseReasonDelete && utilfeature.DefaultMutableFeatureGate.Enabled(feature.KeepDeploymentPausedOnDeletionGate) {
|
||||
klog.Infof("Rollout(%s/%s) is being deleted, KeepDeploymentPausedOnDeletion is enabled. Skipping resume workload step.", c.Rollout.Namespace, c.Rollout.Name)
|
||||
// Do nothing, effectively skipping this step and considering it "done".
|
||||
} else {
|
||||
// Otherwise, run the original logic to unpause the workload.
|
||||
retry, err = finalizingBatchRelease(m.Client, c)
|
||||
if err == nil && !retry {
|
||||
dep := &apps.Deployment{}
|
||||
key := client.ObjectKey{Namespace: c.Rollout.Namespace, Name: c.Rollout.Spec.WorkloadRef.Name}
|
||||
if getErr := m.Client.Get(context.TODO(), key, dep); getErr == nil {
|
||||
if dep.Spec.Paused {
|
||||
dep.Spec.Paused = false
|
||||
if updErr := m.Client.Update(context.TODO(), dep); updErr != nil {
|
||||
return false, updErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// delete batchRelease
|
||||
case v1beta1.FinalisingStepReleaseWorkloadControl:
|
||||
retry, err = removeBatchRelease(m.Client, c)
|
||||
|
|
|
|||
|
|
@ -21,10 +21,12 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||
"github.com/openkruise/rollouts/api/v1beta1"
|
||||
"github.com/openkruise/rollouts/pkg/trafficrouting"
|
||||
"github.com/openkruise/rollouts/pkg/feature"
|
||||
utilfeature "github.com/openkruise/rollouts/pkg/util/feature"
|
||||
"github.com/openkruise/rollouts/pkg/util"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
|
@ -371,7 +373,27 @@ func (m *canaryReleaseManager) doCanaryFinalising(c *RolloutContext) (bool, erro
|
|||
switch canaryStatus.FinalisingStep {
|
||||
// set workload.pause=false; set workload.partition=0
|
||||
case v1beta1.FinalisingStepResumeWorkload:
|
||||
retry, err = finalizingBatchRelease(m.Client, c)
|
||||
// If the rollout is being deleted and the feature gate is enabled,
|
||||
// we skip this step entirely to keep the deployment paused.
|
||||
if c.FinalizeReason == v1beta1.FinaliseReasonDelete && utilfeature.DefaultMutableFeatureGate.Enabled(feature.KeepDeploymentPausedOnDeletionGate) {
|
||||
klog.Infof("Rollout(%s/%s) is being deleted, KeepDeploymentPausedOnDeletion is enabled. Skipping resume workload step.", c.Rollout.Namespace, c.Rollout.Name)
|
||||
// Do nothing, effectively skipping this step and considering it "done".
|
||||
} else {
|
||||
// Otherwise, run the original logic to unpause the workload.
|
||||
retry, err = finalizingBatchRelease(m.Client, c)
|
||||
if err == nil && !retry {
|
||||
dep := &apps.Deployment{}
|
||||
key := client.ObjectKey{Namespace: c.Rollout.Namespace, Name: c.Rollout.Spec.WorkloadRef.Name}
|
||||
if getErr := m.Client.Get(context.TODO(), key, dep); getErr == nil {
|
||||
if dep.Spec.Paused {
|
||||
dep.Spec.Paused = false
|
||||
if updErr := m.Client.Update(context.TODO(), dep); updErr != nil {
|
||||
return false, updErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// delete batchRelease
|
||||
case v1beta1.FinalisingStepReleaseWorkloadControl:
|
||||
retry, err = removeBatchRelease(m.Client, c)
|
||||
|
|
|
|||
|
|
@ -17,10 +17,20 @@ limitations under the License.
|
|||
package rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"github.com/openkruise/rollouts/pkg/trafficrouting"
|
||||
|
||||
"fmt"
|
||||
|
||||
rolloutapi "github.com/openkruise/rollouts/api"
|
||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||
"github.com/openkruise/rollouts/pkg/feature"
|
||||
utilfeature "github.com/openkruise/rollouts/pkg/util/feature"
|
||||
"github.com/openkruise/rollouts/api/v1beta1"
|
||||
"github.com/openkruise/rollouts/pkg/util"
|
||||
"github.com/openkruise/rollouts/pkg/util/configuration"
|
||||
|
|
@ -420,3 +430,89 @@ func init() {
|
|||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = rolloutapi.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
func TestFinalizeRolloutKeepPaused(t *testing.T) {
|
||||
// The scheme is already initialized in the file's init() function.
|
||||
|
||||
deployment := deploymentDemo.DeepCopy()
|
||||
// Simulate the deployment being paused by the rollout controller
|
||||
deployment.Spec.Paused = true
|
||||
|
||||
rollout := rolloutDemo.DeepCopy()
|
||||
// Set the rollout to a finalizing state
|
||||
rollout.Status.Phase = v1beta1.RolloutPhaseDisabling
|
||||
rollout.Status.CanaryStatus = &v1beta1.CanaryStatus{}
|
||||
now := metav1.Now()
|
||||
rollout.DeletionTimestamp = &now
|
||||
// Use the correct finalizer constant from the util package.
|
||||
rollout.Finalizers = []string{v1beta1.RolloutFinalizer}
|
||||
|
||||
// Test Case 1: Feature Gate is ENABLED
|
||||
t.Run("Deployment should remain paused when gate is enabled", func(t *testing.T) {
|
||||
// Enable the feature gate for this specific test
|
||||
err := utilfeature.DefaultMutableFeatureGate.Set(string(feature.KeepDeploymentPausedOnDeletionGate) + "=true")
|
||||
assert.Nil(t, err)
|
||||
// Ensure the feature gate is reset after the test
|
||||
defer utilfeature.DefaultMutableFeatureGate.Set(string(feature.KeepDeploymentPausedOnDeletionGate) + "=false")
|
||||
|
||||
// The reconciler needs a fake client with the test objects
|
||||
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(rollout.DeepCopy(), deployment.DeepCopy()).Build()
|
||||
rollout.Status.CanaryStatus.FinalisingStep = v1beta1.FinalisingStepResumeWorkload
|
||||
r := &RolloutReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
finder: util.NewControllerFinder(fakeClient),
|
||||
canaryManager: &canaryReleaseManager{
|
||||
Client: fakeClient,
|
||||
trafficRoutingManager: trafficrouting.NewTrafficRoutingManager(fakeClient),
|
||||
},
|
||||
blueGreenManager: &blueGreenReleaseManager{
|
||||
Client: fakeClient,
|
||||
trafficRoutingManager: trafficrouting.NewTrafficRoutingManager(fakeClient),
|
||||
},
|
||||
}
|
||||
|
||||
// Run the reconcile loop to trigger the finalizer
|
||||
_, err = r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: client.ObjectKeyFromObject(rollout)})
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Verify the deployment is still paused
|
||||
updatedDeployment := &apps.Deployment{}
|
||||
err = r.Get(context.TODO(), client.ObjectKeyFromObject(deployment), updatedDeployment)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, updatedDeployment.Spec.Paused, "Deployment should have remained paused")
|
||||
})
|
||||
|
||||
// Test Case 2: Feature Gate is DISABLED (Default Behavior)
|
||||
t.Run("Deployment should be unpaused when gate is disabled", func(t *testing.T) {
|
||||
// Ensure the feature gate is disabled
|
||||
err := utilfeature.DefaultMutableFeatureGate.Set(string(feature.KeepDeploymentPausedOnDeletionGate) + "=false")
|
||||
assert.Nil(t, err)
|
||||
|
||||
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(rollout.DeepCopy(), deployment.DeepCopy()).Build()
|
||||
rollout.Status.CanaryStatus.FinalisingStep = v1beta1.FinalisingStepResumeWorkload
|
||||
r := &RolloutReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
finder: util.NewControllerFinder(fakeClient),
|
||||
canaryManager: &canaryReleaseManager{
|
||||
Client: fakeClient,
|
||||
trafficRoutingManager: trafficrouting.NewTrafficRoutingManager(fakeClient),
|
||||
},
|
||||
blueGreenManager: &blueGreenReleaseManager{
|
||||
Client: fakeClient,
|
||||
trafficRoutingManager: trafficrouting.NewTrafficRoutingManager(fakeClient),
|
||||
},
|
||||
}
|
||||
|
||||
// Run the reconcile loop
|
||||
_, err = r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: client.ObjectKeyFromObject(rollout)})
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Verify the deployment is now unpaused (reverted to original spec)
|
||||
updatedDeployment := &apps.Deployment{}
|
||||
err = r.Get(context.TODO(), client.ObjectKeyFromObject(deployment), updatedDeployment)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, updatedDeployment.Spec.Paused, "Deployment should have been unpaused")
|
||||
})
|
||||
}
|
||||
|
|
@ -30,12 +30,15 @@ const (
|
|||
AdvancedDeploymentGate featuregate.Feature = "AdvancedDeployment"
|
||||
// AppendServiceSelectorGate enable appending pod labels from PodTemplateMetadata to the canary service selector.
|
||||
AppendServiceSelectorGate featuregate.Feature = "AppendPodSelector"
|
||||
// KeepDeploymentPausedOnDeletionGate prevents unpausing a Deployment when its managing Rollout CR is deleted.
|
||||
KeepDeploymentPausedOnDeletionGate featuregate.Feature = "KeepDeploymentPausedOnDeletion"
|
||||
)
|
||||
|
||||
var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
RolloutHistoryGate: {Default: false, PreRelease: featuregate.Alpha},
|
||||
AdvancedDeploymentGate: {Default: false, PreRelease: featuregate.Alpha},
|
||||
AppendServiceSelectorGate: {Default: false, PreRelease: featuregate.Alpha},
|
||||
KeepDeploymentPausedOnDeletionGate: {Default: false, PreRelease: featuregate.Alpha},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue