Skip to content
This repository was archived by the owner on May 3, 2022. It is now read-only.

Commit fa72fb9

Browse files
Oleg SidorovOleg Sidorov
authored andcommitted
Release StrategyExecutor aborts if successor release is progressing
This release is a fix of a problem with the new implementation of the reconciliation loop involving strategy execution for all release (as opposed to apply it to contender-incumbent pair only in the old implementation). The problem we observed boiled down to the next scenario: 1. A contender runs through the strategy execution loop and bails out with an incomplete state. 2. Incumbent enters it's independent strategy loop and checks it's desired state from the desired state of the contender. This is the point where the things broke: desired state != achieved state. At this moment incumbent can start progressing without waiting for it's successor (contender) to complete. This optimistic triggering was not a planned action and was mainly mistakenly introduced because distinct release generations are being processed independently now. Preventing the strategy loop from progressing is one way to get around this problem where releases are being processed independently. Another alternative to that could be: an incumbent release can be smarter in terms of figuring out it's desired state itself (e.g. if it's successor hasn't achieved it's target step yet, try to reconcile on the last step in it's own strategy). This behavior has pros and cons of course. On the bright side: releases can behave much more independently and this can also challenge the need for incumbent and contender strategy states. On the lowlight, this might provoke some unwanted chatting and flapping in the release squad. Chatty releases might be quite expensive in terms of converging to a stable state (as we now provoke a direct and reverse chain reaction when there is a change to a release object). For now, we settle down with an easy patch which prevents the system from doing unwanted things. This approach might be a subject for change in the future. Signed-off-by: Oleg Sidorov <[email protected]>
1 parent f0b5d31 commit fa72fb9

File tree

6 files changed

+257
-147
lines changed

6 files changed

+257
-147
lines changed

pkg/controller/release/release_controller.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ func (c *Controller) syncOneReleaseHandler(key string) error {
357357
)
358358
diff.Append(releaseutil.SetReleaseCondition(&rel.Status, *condition))
359359

360-
stepAchieved, strategyPatches, trans, err = c.ensureReleaseState(relinfo)
360+
stepAchieved, strategyPatches, trans, err = c.executeReleaseStrategy(relinfo)
361361
if err != nil {
362362
releaseStrategyExecutedCond := releaseutil.NewReleaseCondition(
363363
shipper.ReleaseConditionTypeStrategyExecuted,
@@ -386,8 +386,6 @@ func (c *Controller) syncOneReleaseHandler(key string) error {
386386
previouslyAchievedStep := rel.Status.AchievedStep
387387

388388
if previouslyAchievedStep == nil || targetStep != previouslyAchievedStep.Step {
389-
// we validate that it fits in the len() of
390-
// Strategy.Steps early in the process
391389
targetStepName := strategy.Steps[targetStep].Name
392390
rel.Status.AchievedStep = &shipper.AchievedStep{
393391
Step: targetStep,
@@ -456,7 +454,7 @@ func (c *Controller) applicationReleases(rel *shipper.Release) ([]*shipper.Relea
456454
return releases, nil
457455
}
458456

459-
func (c *Controller) ensureReleaseState(relinfo *releaseInfo) (bool, []StrategyPatch, []ReleaseStrategyStateTransition, error) {
457+
func (c *Controller) executeReleaseStrategy(relinfo *releaseInfo) (bool, []StrategyPatch, []ReleaseStrategyStateTransition, error) {
460458
releases, err := c.applicationReleases(relinfo.release)
461459
if err != nil {
462460
return false, nil, nil, err
@@ -480,13 +478,7 @@ func (c *Controller) ensureReleaseState(relinfo *releaseInfo) (bool, []StrategyP
480478
}
481479
}
482480

483-
//TODO: this flag is deprecated, we keep it here for the sake of gradual
484-
//changes. The flag propagates as deep as strategy conditions
485-
//consolidation functions
486-
// see pkg/util/conditions/strategy.go for more details.
487-
hasIncumbent := len(releases) > 1
488-
489-
executor := NewStrategyExecutor(relinfo, relinfoPrev, relinfoSucc, hasIncumbent)
481+
executor := NewStrategyExecutor(relinfoPrev, relinfo, relinfoSucc)
490482

491483
complete, patches, trans, err := executor.Execute()
492484

pkg/controller/release/release_controller_test.go

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ func (f *fixture) expectReleaseScheduled(release *shipper.Release, clusters []*s
903903
)
904904
}
905905

906-
func (f *fixture) expectCapacityStatusPatch(ct *shipper.CapacityTarget, r *shipper.Release, value uint, totalReplicaCount uint, role role) {
906+
func (f *fixture) expectCapacityStatusPatch(step int32, ct *shipper.CapacityTarget, r *shipper.Release, value uint, totalReplicaCount uint, role role) {
907907
f.filter = f.filter.Extend(actionfilter{
908908
[]string{"patch"},
909909
[]string{"releases", "capacitytargets"},
@@ -921,8 +921,6 @@ func (f *fixture) expectCapacityStatusPatch(ct *shipper.CapacityTarget, r *shipp
921921
action := kubetesting.NewPatchAction(gvr, ct.GetNamespace(), ct.GetName(), types.MergePatchType, patch)
922922
f.actions = append(f.actions, action)
923923

924-
step := r.Spec.TargetStep
925-
926924
var strategyConditions conditions.StrategyConditionsMap
927925

928926
if role == Contender {
@@ -986,7 +984,7 @@ func (f *fixture) expectCapacityStatusPatch(ct *shipper.CapacityTarget, r *shipp
986984
"status": shipper.ReleaseStatus{
987985
Strategy: &shipper.ReleaseStrategyStatus{
988986
Conditions: strategyConditions.AsReleaseStrategyConditions(),
989-
State: strategyConditions.AsReleaseStrategyState(r.Spec.TargetStep, true, false),
987+
State: strategyConditions.AsReleaseStrategyState(step, true, false),
990988
},
991989
},
992990
}
@@ -1004,7 +1002,7 @@ func (f *fixture) expectCapacityStatusPatch(ct *shipper.CapacityTarget, r *shipp
10041002
}
10051003
}
10061004

1007-
func (f *fixture) expectTrafficStatusPatch(tt *shipper.TrafficTarget, r *shipper.Release, value uint32, role role) {
1005+
func (f *fixture) expectTrafficStatusPatch(step int32, tt *shipper.TrafficTarget, r *shipper.Release, value uint32, role role) {
10081006
f.filter = f.filter.Extend(actionfilter{
10091007
[]string{"patch"},
10101008
[]string{"releases", "traffictargets"},
@@ -1022,8 +1020,6 @@ func (f *fixture) expectTrafficStatusPatch(tt *shipper.TrafficTarget, r *shipper
10221020
action := kubetesting.NewPatchAction(gvr, tt.GetNamespace(), tt.GetName(), types.MergePatchType, patch)
10231021
f.actions = append(f.actions, action)
10241022

1025-
step := r.Spec.TargetStep
1026-
10271023
var strategyConditions conditions.StrategyConditionsMap
10281024

10291025
if role == Contender {
@@ -1087,7 +1083,7 @@ func (f *fixture) expectTrafficStatusPatch(tt *shipper.TrafficTarget, r *shipper
10871083
"status": shipper.ReleaseStatus{
10881084
Strategy: &shipper.ReleaseStrategyStatus{
10891085
Conditions: strategyConditions.AsReleaseStrategyConditions(),
1090-
State: strategyConditions.AsReleaseStrategyState(r.Spec.TargetStep, true, false),
1086+
State: strategyConditions.AsReleaseStrategyState(step, true, false),
10911087
},
10921088
},
10931089
}
@@ -1225,7 +1221,6 @@ func (f *fixture) expectCapacityNotReady(relpair releaseInfoPair, targetStep, ac
12251221
})
12261222

12271223
gvr := shipper.SchemeGroupVersion.WithResource("releases")
1228-
rel := relpair.contender.release
12291224

12301225
var newStatus map[string]interface{}
12311226

@@ -1237,6 +1232,7 @@ func (f *fixture) expectCapacityNotReady(relpair releaseInfoPair, targetStep, ac
12371232
// }
12381233
// }
12391234

1235+
var rel *shipper.Release
12401236
if role == Contender {
12411237
newStatus = map[string]interface{}{
12421238
"status": shipper.ReleaseStatus{
@@ -1269,6 +1265,8 @@ func (f *fixture) expectCapacityNotReady(relpair releaseInfoPair, targetStep, ac
12691265
},
12701266
},
12711267
}
1268+
rel = relpair.contender.release
1269+
12721270
} else {
12731271
newStatus = map[string]interface{}{
12741272
"status": shipper.ReleaseStatus{
@@ -1316,6 +1314,8 @@ func (f *fixture) expectCapacityNotReady(relpair releaseInfoPair, targetStep, ac
13161314
},
13171315
},
13181316
}
1317+
1318+
rel = relpair.incumbent.release
13191319
}
13201320

13211321
patch, _ := json.Marshal(newStatus)
@@ -1330,7 +1330,6 @@ func (f *fixture) expectCapacityNotReady(relpair releaseInfoPair, targetStep, ac
13301330

13311331
func (f *fixture) expectTrafficNotReady(relpair releaseInfoPair, targetStep, achievedStepIndex int32, role role, brokenClusterName string) {
13321332
gvr := shipper.SchemeGroupVersion.WithResource("releases")
1333-
rel := relpair.contender.release
13341333
var newStatus map[string]interface{}
13351334

13361335
// var achievedStep *shipper.AchievedStep
@@ -1341,6 +1340,7 @@ func (f *fixture) expectTrafficNotReady(relpair releaseInfoPair, targetStep, ach
13411340
// }
13421341
// }
13431342

1343+
var rel *shipper.Release
13441344
if role == Contender {
13451345
newStatus = map[string]interface{}{
13461346
"status": shipper.ReleaseStatus{
@@ -1378,6 +1378,8 @@ func (f *fixture) expectTrafficNotReady(relpair releaseInfoPair, targetStep, ach
13781378
},
13791379
},
13801380
}
1381+
1382+
rel = relpair.contender.release
13811383
} else {
13821384
newStatus = map[string]interface{}{
13831385
"status": shipper.ReleaseStatus{
@@ -1420,6 +1422,8 @@ func (f *fixture) expectTrafficNotReady(relpair releaseInfoPair, targetStep, ach
14201422
},
14211423
},
14221424
}
1425+
1426+
rel = relpair.incumbent.release
14231427
}
14241428

14251429
patch, _ := json.Marshal(newStatus)
@@ -1680,7 +1684,7 @@ func TestContenderCapacityShouldIncrease(t *testing.T) {
16801684

16811685
ct := contender.capacityTarget.DeepCopy()
16821686
r := contender.release.DeepCopy()
1683-
f.expectCapacityStatusPatch(ct, r, 50, uint(totalReplicaCount), Contender)
1687+
f.expectCapacityStatusPatch(contender.release.Spec.TargetStep, ct, r, 50, uint(totalReplicaCount), Contender)
16841688
f.run()
16851689
}
16861690

@@ -1721,7 +1725,7 @@ func TestContenderCapacityShouldIncreaseWithRolloutBlockOverride(t *testing.T) {
17211725

17221726
ct := contender.capacityTarget.DeepCopy()
17231727
r := contender.release.DeepCopy()
1724-
f.expectCapacityStatusPatch(ct, r, 50, uint(totalReplicaCount), Contender)
1728+
f.expectCapacityStatusPatch(contender.release.Spec.TargetStep, ct, r, 50, uint(totalReplicaCount), Contender)
17251729
overrideEvent := fmt.Sprintf("%s RolloutBlockOverridden %s", corev1.EventTypeNormal, rolloutBlockKey)
17261730
f.expectedEvents = append([]string{overrideEvent}, f.expectedEvents...)
17271731
f.run()
@@ -1808,7 +1812,7 @@ func TestContenderTrafficShouldIncrease(t *testing.T) {
18081812

18091813
tt := contender.trafficTarget.DeepCopy()
18101814
r := contender.release.DeepCopy()
1811-
f.expectTrafficStatusPatch(tt, r, 50, Contender)
1815+
f.expectTrafficStatusPatch(contender.release.Spec.TargetStep, tt, r, 50, Contender)
18121816
f.run()
18131817
}
18141818

@@ -1852,7 +1856,7 @@ func TestContenderTrafficShouldIncreaseWithRolloutBlockOverride(t *testing.T) {
18521856

18531857
tt := contender.trafficTarget.DeepCopy()
18541858
r := contender.release.DeepCopy()
1855-
f.expectTrafficStatusPatch(tt, r, 50, Contender)
1859+
f.expectTrafficStatusPatch(contender.release.Spec.TargetStep, tt, r, 50, Contender)
18561860
overrideEvent := fmt.Sprintf("%s RolloutBlockOverridden %s", corev1.EventTypeNormal, rolloutBlockKey)
18571861
f.expectedEvents = append([]string{overrideEvent}, f.expectedEvents...)
18581862
f.run()
@@ -1921,6 +1925,7 @@ func TestIncumbentTrafficShouldDecrease(t *testing.T) {
19211925
contender.capacityTarget.Spec.Clusters[0].Percent = 50
19221926
contender.capacityTarget.Spec.Clusters[0].TotalReplicaCount = totalReplicaCount
19231927
contender.trafficTarget.Spec.Clusters[0].Weight = 50
1928+
contender.release.Status.AchievedStep = &shipper.AchievedStep{Step: 1}
19241929

19251930
f.addObjects(
19261931
contender.release.DeepCopy(),
@@ -1935,8 +1940,8 @@ func TestIncumbentTrafficShouldDecrease(t *testing.T) {
19351940
)
19361941

19371942
tt := incumbent.trafficTarget.DeepCopy()
1938-
r := contender.release.DeepCopy()
1939-
f.expectTrafficStatusPatch(tt, r, 50, Incumbent)
1943+
r := incumbent.release.DeepCopy()
1944+
f.expectTrafficStatusPatch(contender.release.Spec.TargetStep, tt, r, 50, Incumbent)
19401945
f.run()
19411946
}
19421947

@@ -1976,8 +1981,8 @@ func TestIncumbentTrafficShouldDecreaseWithRolloutBlockOverride(t *testing.T) {
19761981
)
19771982

19781983
tt := incumbent.trafficTarget.DeepCopy()
1979-
r := contender.release.DeepCopy()
1980-
f.expectTrafficStatusPatch(tt, r, 50, Incumbent)
1984+
r := incumbent.release.DeepCopy()
1985+
f.expectTrafficStatusPatch(contender.release.Spec.TargetStep, tt, r, 50, Incumbent)
19811986
overrideEvent := fmt.Sprintf("%s RolloutBlockOverridden %s", corev1.EventTypeNormal, rolloutBlockKey)
19821987
f.expectedEvents = append([]string{overrideEvent}, f.expectedEvents...)
19831988
f.run()
@@ -2047,6 +2052,7 @@ func TestIncumbentCapacityShouldDecrease(t *testing.T) {
20472052
contender.capacityTarget.Spec.Clusters[0].Percent = 50
20482053
contender.capacityTarget.Spec.Clusters[0].TotalReplicaCount = totalReplicaCount
20492054
contender.trafficTarget.Spec.Clusters[0].Weight = 50
2055+
contender.release.Status.AchievedStep = &shipper.AchievedStep{Step: 1}
20502056

20512057
incumbent.trafficTarget.Spec.Clusters[0].Weight = 50
20522058

@@ -2063,8 +2069,8 @@ func TestIncumbentCapacityShouldDecrease(t *testing.T) {
20632069
)
20642070

20652071
tt := incumbent.capacityTarget.DeepCopy()
2066-
r := contender.release.DeepCopy()
2067-
f.expectCapacityStatusPatch(tt, r, 50, uint(totalReplicaCount), Incumbent)
2072+
r := incumbent.release.DeepCopy()
2073+
f.expectCapacityStatusPatch(contender.release.Spec.TargetStep, tt, r, 50, uint(totalReplicaCount), Incumbent)
20682074
f.run()
20692075
}
20702076

@@ -2106,8 +2112,8 @@ func TestIncumbentCapacityShouldDecreaseWithRolloutBlockOverride(t *testing.T) {
21062112
)
21072113

21082114
tt := incumbent.capacityTarget.DeepCopy()
2109-
r := contender.release.DeepCopy()
2110-
f.expectCapacityStatusPatch(tt, r, 50, uint(totalReplicaCount), Incumbent)
2115+
r := incumbent.release.DeepCopy()
2116+
f.expectCapacityStatusPatch(contender.release.Spec.TargetStep, tt, r, 50, uint(totalReplicaCount), Incumbent)
21112117
overrideEvent := fmt.Sprintf("%s RolloutBlockOverridden %s", corev1.EventTypeNormal, rolloutBlockKey)
21122118
f.expectedEvents = append([]string{overrideEvent}, f.expectedEvents...)
21132119
f.run()
@@ -2168,6 +2174,7 @@ func TestContenderReleasePhaseIsWaitingForCommandForFinalStepState(t *testing.T)
21682174
cluster := buildCluster("minikube")
21692175

21702176
f := newFixture(t, app.DeepCopy(), cluster.DeepCopy())
2177+
f.cycles = 1
21712178

21722179
totalReplicaCount := int32(10)
21732180
contender := f.buildContender(namespace, contenderName, totalReplicaCount)
@@ -2204,6 +2211,7 @@ func TestContenderReleaseIsInstalled(t *testing.T) {
22042211
cluster := buildCluster("minikube")
22052212

22062213
f := newFixture(t, app.DeepCopy(), cluster.DeepCopy())
2214+
f.cycles = 1
22072215

22082216
totalReplicaCount := int32(10)
22092217
contender := f.buildContender(namespace, contenderName, totalReplicaCount)
@@ -2322,13 +2330,15 @@ func TestApplicationExposesStrategyFailureSuccessorIndexOutOfBounds(t *testing.T
23222330
cluster := buildCluster("minikube")
23232331

23242332
f := newFixture(t, app.DeepCopy(), cluster.DeepCopy())
2325-
f.cycles = 1
2333+
// we're testing 2 cycles because we expect contender and incumbent patches
2334+
// to be issued independently
2335+
f.cycles = 2
23262336

23272337
totalReplicaCount := int32(1)
23282338
contender := f.buildContender(namespace, contenderName, totalReplicaCount)
23292339
incumbent := f.buildIncumbent(namespace, incumbentName, totalReplicaCount)
23302340

2331-
missingStepMsg := fmt.Sprintf("failed to execute strategy: \"no step 2 in strategy for Release \\\"%s/%s\\\"\"", contender.release.Namespace, contender.release.Name)
2341+
missingStepMsg := fmt.Sprintf("failed to execute strategy: \"no step 2 in strategy for Release \\\"%s/%s\\\"\"", namespace, contenderName)
23322342

23332343
// We define 2 steps and will intentionally set target step index out of this bound
23342344
strategyStaging := shipper.RolloutStrategy{
@@ -2357,8 +2367,24 @@ func TestApplicationExposesStrategyFailureSuccessorIndexOutOfBounds(t *testing.T
23572367
contender.release.Spec.Environment.Strategy = &strategyStaging
23582368
contender.release.Spec.TargetStep = 2 // out of bound index
23592369

2360-
expectedRel := incumbent.release.DeepCopy()
2361-
expectedRel.Status.Conditions = []shipper.ReleaseCondition{
2370+
expectedIncumbent := incumbent.release.DeepCopy()
2371+
expectedIncumbent.Status.Conditions = []shipper.ReleaseCondition{
2372+
{
2373+
Type: shipper.ReleaseConditionTypeBlocked,
2374+
Status: corev1.ConditionFalse,
2375+
},
2376+
{
2377+
Type: shipper.ReleaseConditionTypeScheduled,
2378+
Status: corev1.ConditionTrue,
2379+
},
2380+
{
2381+
Type: shipper.ReleaseConditionTypeStrategyExecuted,
2382+
Status: corev1.ConditionTrue,
2383+
},
2384+
}
2385+
2386+
expectedContender := contender.release.DeepCopy()
2387+
expectedContender.Status.Conditions = []shipper.ReleaseCondition{
23622388
{
23632389
Type: shipper.ReleaseConditionTypeBlocked,
23642390
Status: corev1.ConditionFalse,
@@ -2390,17 +2416,26 @@ func TestApplicationExposesStrategyFailureSuccessorIndexOutOfBounds(t *testing.T
23902416
contender.trafficTarget.DeepCopy(),
23912417
)
23922418

2393-
f.actions = append(f.actions, kubetesting.NewUpdateAction(
2394-
shipper.SchemeGroupVersion.WithResource("releases"),
2395-
namespace,
2396-
expectedRel))
2419+
f.actions = append(f.actions,
2420+
kubetesting.NewUpdateAction(
2421+
shipper.SchemeGroupVersion.WithResource("releases"),
2422+
namespace,
2423+
expectedIncumbent,
2424+
),
2425+
kubetesting.NewUpdateAction(
2426+
shipper.SchemeGroupVersion.WithResource("releases"),
2427+
namespace,
2428+
expectedContender,
2429+
),
2430+
)
23972431

23982432
f.filter = f.filter.Extend(actionfilter{
23992433
[]string{"update"},
24002434
[]string{"releases"},
24012435
})
24022436
f.expectedEvents = append(f.expectedEvents,
2403-
fmt.Sprintf("Normal ReleaseConditionChanged [] -> [Scheduled True], [] -> [StrategyExecuted False StrategyExecutionFailed %s]", missingStepMsg))
2437+
`Normal ReleaseConditionChanged [] -> [Scheduled True], [] -> [StrategyExecuted True]`,
2438+
fmt.Sprintf(`Normal ReleaseConditionChanged [] -> [Scheduled True], [] -> [StrategyExecuted False StrategyExecutionFailed %s]`, missingStepMsg))
24042439

24052440
f.run()
24062441
}

0 commit comments

Comments
 (0)