Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't require container to be down in dcc.Down #26

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ func (d *Daemon) performUpgrade(
return errors.Wrapf(err, "failed to check if service is running")
}

// This check is prone to race conditions, the image could be up at this point
// but exits before dcc.Down is called. However, at this point we are certain
// that upgrade height has been hit, so, it should be safe to Down an exited
// container.
if isRunning {
logger.Info("Executing compose down").Notifyf(ctx, "Shutting down chain to perform upgrade. Current image: %s, new image: %s", currImage, newImage)
if err = d.dcc.Down(ctx, serviceName, compose.DownTimeout); err != nil {
Expand Down
27 changes: 16 additions & 11 deletions internal/pkg/docker/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,26 +182,18 @@ func (dcc *ComposeClient) upgradeImageInComposeFile(ctx context.Context, service
}

func (dcc *ComposeClient) Down(ctx context.Context, serviceName string, timeout time.Duration) error {
isImageContainerRunning, err := dcc.IsServiceRunning(ctx, serviceName, timeout)
if err != nil {
return errors.Wrapf(err, "check for container running failed")
}
if !isImageContainerRunning {
return errors.Wrapf(ErrContainerNotRunning, "expected the container to run before calling docker compose down")
}

// 5 seconds buffer to handle the case when docker timeout (-t)
// takes slightly longer than defined timeout (thus delay context cancellation)
deadline := timeout + 5*time.Second
timeoutSeconds := int(math.Round(timeout.Seconds()))

err = cmd.ExecuteWithDeadlineAndLog(ctx, deadline, []string{}, "docker", "compose", "-f", dcc.composeFile, "down", "--remove-orphans", "-t", strconv.Itoa(timeoutSeconds))
err := cmd.ExecuteWithDeadlineAndLog(ctx, deadline, []string{}, "docker", "compose", "-f", dcc.composeFile, "down", "--remove-orphans", "-t", strconv.Itoa(timeoutSeconds))
if err != nil {
return errors.Wrapf(err, "docker compose down failed")
}

// verify from docker api that the container is down
isImageContainerRunning, err = dcc.IsServiceRunning(ctx, serviceName, timeout)
isImageContainerRunning, err := dcc.IsServiceRunning(ctx, serviceName, timeout)
if err != nil {
return errors.Wrapf(err, "check for container running failed")
}
Expand Down Expand Up @@ -243,7 +235,20 @@ func (dcc *ComposeClient) Up(ctx context.Context, serviceName string, timeout ti
}

func (dcc *ComposeClient) RestartServiceWithHaltHeight(ctx context.Context, composeConfig *config.ComposeCli, serviceName string, upgradeHeight int64) error {
err := dcc.Down(ctx, serviceName, composeConfig.DownTimeout)
isImageContainerRunning, err := dcc.IsServiceRunning(ctx, serviceName, composeConfig.DownTimeout)
if err != nil {
return errors.Wrapf(err, "check for container running failed")
}
if !isImageContainerRunning {
return errors.Wrapf(ErrContainerNotRunning, "expected the container to run before restarting with halt height")
}
// The check above is prone to race conditions and the
// container can exit after the check. That should be super rare
// and it should be safe to Down it anyways, since, we are sure that we want
// to register halt height and restart.
// If the container crashed due to some issue, let it crash again after the restart
// blazar can't do anything about that
err = dcc.Down(ctx, serviceName, composeConfig.DownTimeout)
if err != nil && !errors.Is(err, ErrContainerNotRunning) {
return errors.Wrapf(err, "docker compose down failed")
}
Expand Down
17 changes: 0 additions & 17 deletions internal/pkg/docker/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,23 +272,6 @@ func TestUpDownCompose(t *testing.T) {
require.NoError(t, err)
},
},
{
name: "simulate case where docker compose up didn't start the container",
testFn: func(t *testing.T, _ string) {
// step 1: fake docker binary to simulate docker compose up timeout (container isn't running)
err = os.WriteFile(filepath.Join(tempDir, "docker"), []byte("#!/bin/sh\nexit 0\n"), 0600)
require.NoError(t, err)

oldPath := os.Getenv("PATH")
os.Setenv("PATH", tempDir+":"+oldPath)

// step 2: with docker compose client try to start container and expect it to fail
err = dcc.Down(ctx, "s1", 0)
require.ErrorIs(t, err, ErrContainerNotRunning)

os.Setenv("PATH", oldPath)
},
},
}

for _, test := range tests {
Expand Down
Loading