diff --git a/functional/fixtures/units/replace-sync.service b/functional/fixtures/units/replace-sync.service new file mode 100644 index 000000000..b81c42683 --- /dev/null +++ b/functional/fixtures/units/replace-sync.service @@ -0,0 +1,5 @@ +[Service] +ExecStartPre=/bin/bash -c "echo 'sync'" +ExecStart=/bin/bash -c "while true; do echo Hello, World!; sleep 1; done" +ExecStop=/bin/bash -c "echo 'stopping'" +ExecStopPost=/bin/bash -c "sleep 3; touch /tmp/fleetSyncFile" diff --git a/functional/unit_action_test.go b/functional/unit_action_test.go index f0bf99d21..7dc4c6612 100644 --- a/functional/unit_action_test.go +++ b/functional/unit_action_test.go @@ -717,3 +717,79 @@ func replaceUnitMultiple(cmd string, n int) error { return nil } + +// TestReplaceSerialization tests if the ExecStartPre of the new version +// of the unit when it replaces the old one is excuted after +// ExecStopPost of the old version. +// This test is to make sure that two versions of the same unit will not +// conflict with each other, that the directives are always serialized, +// and it tries its best to avoid the following scenarios: +// https://github.com/coreos/fleet/issues/1000 +// https://github.com/systemd/systemd/issues/518 +// Now we can't guarantee that that behaviour will not be triggered by +// another external operation, but at least from the Unit replace +// feature context we try to avoid it. +func TestReplaceSerialization(t *testing.T) { + cluster, err := platform.NewNspawnCluster("smoke") + if err != nil { + t.Fatal(err) + } + defer cluster.Destroy() + + m, err := cluster.CreateMember() + if err != nil { + t.Fatal(err) + } + + _, err = cluster.WaitForNMachines(m, 1) + if err != nil { + t.Fatal(err) + } + + tmpSyncFile := "/tmp/fleetSyncReplaceFile" + syncOld := "echo 'sync'" + syncNew := fmt.Sprintf("test -f %s", tmpSyncFile) + tmpSyncService := "/tmp/replace-sync.service" + syncService := "fixtures/units/replace-sync.service" + + stdout, stderr, err := cluster.Fleetctl(m, "start", syncService) + if err != nil { + t.Fatalf("Unable to start unit: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err) + } + + _, err = cluster.WaitForNActiveUnits(m, 1) + if err != nil { + t.Fatal(err) + } + + // replace the unit content, make sure that: + // It shows up and it did 'stat /tmp/fleetSyncFile' correctly + err = util.GenNewFleetService(tmpSyncService, syncService, syncNew, syncOld) + if err != nil { + t.Fatalf("Failed to generate a temp fleet service: %v", err) + } + + stdout, stderr, err = cluster.Fleetctl(m, "start", "--replace", tmpSyncService) + if err != nil { + t.Fatalf("Failed to replace unit: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err) + } + + _, err = cluster.WaitForNActiveUnits(m, 1) + if err != nil { + t.Fatalf("Did not find 1 unit in cluster, unit replace failed: %v", err) + } + + tmpService := path.Base(tmpSyncService) + stdout, _ = cluster.MemberCommand(m, "systemctl", "show", "--property=ActiveState", tmpService) + if strings.TrimSpace(stdout) != "ActiveState=active" { + t.Fatalf("%s unit not reported as active: %s", tmpService, stdout) + } + + stdout, _ = cluster.MemberCommand(m, "systemctl", "show", "--property=Result", tmpService) + if strings.TrimSpace(stdout) != "Result=success" { + t.Fatalf("Result for %s unit not reported as success: %s", tmpService, stdout) + } + + os.Remove(tmpSyncFile) + os.Remove(tmpSyncService) +}