Skip to content

Commit d6ed77b

Browse files
committed
fix regression and have tests to document expected behavior
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent b6a136b commit d6ed77b

2 files changed

Lines changed: 117 additions & 1 deletion

File tree

pkg/compose/reconcile.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,12 @@ func addCascadingRestartOps(
14521452
}
14531453
}
14541454

1455-
// Start chain: merge with existing start or create new one.
1455+
// Start chain: only when StartContainers is set. Otherwise the start
1456+
// is handled by startService via InDependencyOrder, which respects
1457+
// depends_on conditions and gives services time to become ready.
1458+
if !opts.StartContainers {
1459+
return
1460+
}
14561461
emitStartingID := fmt.Sprintf("emit-starting:%s", ctrName)
14571462
startID := fmt.Sprintf("start-container:%s", ctrName)
14581463
if existingStart, exists := plan.Operations[startID]; exists {

pkg/compose/reconcile_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5226,6 +5226,117 @@ func TestReconcileCascadingRestartOrderingPreservesLogDrivers(t *testing.T) {
52265226
`)
52275227
}
52285228

5229+
// ---------------------------------------------------------------------------
5230+
// Cascading restart — StartContainers flag behavior
5231+
// ---------------------------------------------------------------------------
5232+
5233+
// TestReconcileCascadingRestartWithStartContainers verifies that when
5234+
// StartContainers is true, the cascading restart produces both stop AND start
5235+
// operations for the dependent service in the plan. The start waits for the
5236+
// dependency to be fully recreated.
5237+
func TestReconcileCascadingRestartWithStartContainers(t *testing.T) {
5238+
fluentbit := types.ServiceConfig{Name: "fluentbit", Image: "fluent/fluent-bit:latest"}
5239+
app := types.ServiceConfig{
5240+
Name: "app", Image: "nginx",
5241+
DependsOn: types.DependsOnConfig{
5242+
"fluentbit": {Condition: "service_started", Restart: true},
5243+
},
5244+
}
5245+
appHash, err := ServiceHash(app)
5246+
assert.NilError(t, err)
5247+
5248+
project := &types.Project{
5249+
Name: "tp",
5250+
Services: types.Services{"fluentbit": fluentbit, "app": app},
5251+
}
5252+
observed := &ObservedState{
5253+
ProjectName: "tp",
5254+
Containers: map[string]Containers{
5255+
"fluentbit": {makeContainer("tp", "fluentbit", 1, "stale")},
5256+
"app": {makeContainer("tp", "app", 1, appHash)},
5257+
},
5258+
Networks: map[string]ObservedNetwork{},
5259+
Volumes: map[string]ObservedVolume{},
5260+
Orphans: Containers{},
5261+
}
5262+
5263+
plan, err := Reconcile(project, observed, ReconcileOptions{
5264+
StartContainers: true,
5265+
Recreate: api.RecreateDiverged,
5266+
RecreateDependencies: api.RecreateDiverged,
5267+
})
5268+
assert.NilError(t, err)
5269+
assert.Equal(t, plan.String(), `
5270+
1. emit event tp-app-1 reason: Stopping
5271+
[1] -> 2. stop container tp-app-1 reason: dependency "fluentbit" is being recreated (restart: true)
5272+
[2] -> 3. emit event tp-app-1 reason: Stopped
5273+
[3] -> 4. emit event tp-fluentbit-1 reason: Recreate
5274+
[3] -> 5. emit event tp-app-1 reason: Starting
5275+
[4] -> 6. create container tp-fluentbit_tp-fluentbit-1 reason: config hash changed
5276+
[6] -> 7. stop container tp-fluentbit-1 reason: config hash changed
5277+
[7] -> 8. remove container tp-fluentbit-1 reason: config hash changed
5278+
[8] -> 9. rename container tp-fluentbit-1 reason: config hash changed
5279+
[9] -> 10. start container tp-fluentbit-1 reason: config hash changed
5280+
[10] -> 11. emit event tp-fluentbit-1 reason: Recreated
5281+
[11,5] -> 12. start container tp-app-1 reason: restart after dependency "fluentbit" recreated
5282+
[12] -> 13. emit event tp-app-1 reason: Started
5283+
`)
5284+
}
5285+
5286+
// TestReconcileCascadingRestartWithoutStartContainers verifies that when
5287+
// StartContainers is false (docker compose create, used by up -d), the
5288+
// cascading restart produces only the STOP operations for the dependent.
5289+
// The start is NOT in the plan — it is delegated to startService via
5290+
// InDependencyOrder, which respects depends_on conditions and gives
5291+
// services time to become ready (e.g. fluentd logging driver needs the
5292+
// dependency to be listening before the dependent can start).
5293+
func TestReconcileCascadingRestartWithoutStartContainers(t *testing.T) {
5294+
fluentbit := types.ServiceConfig{Name: "fluentbit", Image: "fluent/fluent-bit:latest"}
5295+
app := types.ServiceConfig{
5296+
Name: "app", Image: "nginx",
5297+
DependsOn: types.DependsOnConfig{
5298+
"fluentbit": {Condition: "service_started", Restart: true},
5299+
},
5300+
}
5301+
appHash, err := ServiceHash(app)
5302+
assert.NilError(t, err)
5303+
5304+
project := &types.Project{
5305+
Name: "tp",
5306+
Services: types.Services{"fluentbit": fluentbit, "app": app},
5307+
}
5308+
observed := &ObservedState{
5309+
ProjectName: "tp",
5310+
Containers: map[string]Containers{
5311+
"fluentbit": {makeContainer("tp", "fluentbit", 1, "stale")},
5312+
"app": {makeContainer("tp", "app", 1, appHash)},
5313+
},
5314+
Networks: map[string]ObservedNetwork{},
5315+
Volumes: map[string]ObservedVolume{},
5316+
Orphans: Containers{},
5317+
}
5318+
5319+
plan, err := Reconcile(project, observed, ReconcileOptions{
5320+
StartContainers: false,
5321+
Recreate: api.RecreateDiverged,
5322+
RecreateDependencies: api.RecreateDiverged,
5323+
})
5324+
assert.NilError(t, err)
5325+
// No start ops for app — only stop + recreate of dependency.
5326+
// The start of app will be handled by startService after ExecutePlan.
5327+
assert.Equal(t, plan.String(), `
5328+
1. emit event tp-app-1 reason: Stopping
5329+
[1] -> 2. stop container tp-app-1 reason: dependency "fluentbit" is being recreated (restart: true)
5330+
[2] -> 3. emit event tp-app-1 reason: Stopped
5331+
[3] -> 4. emit event tp-fluentbit-1 reason: Recreate
5332+
[4] -> 5. create container tp-fluentbit_tp-fluentbit-1 reason: config hash changed
5333+
[5] -> 6. stop container tp-fluentbit-1 reason: config hash changed
5334+
[6] -> 7. remove container tp-fluentbit-1 reason: config hash changed
5335+
[7] -> 8. rename container tp-fluentbit-1 reason: config hash changed
5336+
[8] -> 9. emit event tp-fluentbit-1 reason: Recreated
5337+
`)
5338+
}
5339+
52295340
// ---------------------------------------------------------------------------
52305341
// Test helpers
52315342
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)