1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/runner.git synced 2025-09-15 18:57:01 +00:00

add test case for premature termination before health check completes

This commit is contained in:
Mathieu Fenniak 2025-08-05 20:54:44 -06:00
parent 12347b019d
commit aa70cb7d7b
5 changed files with 38 additions and 3 deletions

View file

@ -7,6 +7,7 @@ import (
"code.forgejo.org/forgejo/runner/v9/act/common" "code.forgejo.org/forgejo/runner/v9/act/common"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/pkg/errors"
) )
// NewContainerInput the input for the New function // NewContainerInput the input for the New function
@ -93,3 +94,5 @@ const (
HealthHealthy HealthHealthy
HealthUnHealthy HealthUnHealthy
) )
var ErrContainerNotFound = errors.New("container not found")

View file

@ -18,6 +18,7 @@ import (
"time" "time"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/connhelper" "github.com/docker/cli/cli/connhelper"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -194,6 +195,9 @@ func (cr *containerReference) Remove() common.Executor {
func (cr *containerReference) GetHealth(ctx context.Context) (Health, error) { func (cr *containerReference) GetHealth(ctx context.Context) (Health, error) {
resp, err := cr.cli.ContainerInspect(ctx, cr.id) resp, err := cr.cli.ContainerInspect(ctx, cr.id)
if cerrdefs.IsNotFound(err) {
return HealthUnHealthy, ErrContainerNotFound
}
logger := common.Logger(ctx) logger := common.Logger(ctx)
if err != nil { if err != nil {
return HealthUnHealthy, err return HealthUnHealthy, err

View file

@ -768,10 +768,16 @@ func (rc *RunContext) waitForServiceContainer(c container.ExecutionsEnvironment)
return fmt.Errorf("service container %s: timed out while waiting for healthy or unhealthy status to be reported", c.GetName()) return fmt.Errorf("service container %s: timed out while waiting for healthy or unhealthy status to be reported", c.GetName())
} else if errors.Is(err, context.Canceled) { } else if errors.Is(err, context.Canceled) {
return err return err
} else if errors.Is(err, container.ErrContainerNotFound) || (err == nil && health == container.HealthUnHealthy) {
// Container absent (terminated during health check) and unhealthy are difficult to consistently report
// differently from each other as, in docker, a terminated container will briefly appear unhealthy and
// then start reporting container not found; so, report both the same. Without any detection of the
// ErrContainerNotFound case we would just treat it as a transient failure and timeout, which would be a
// slower error mode that this is working to avoid.
return fmt.Errorf("service container %s: failed health check or terminated before becoming healthy", c.GetName())
} else if err != nil { } else if err != nil {
// assume transient error in the execution environment
logger.Warnf("service container %s: error while checking for health state, will retry: %v", c.GetName(), err) logger.Warnf("service container %s: error while checking for health state, will retry: %v", c.GetName(), err)
} else if health == container.HealthUnHealthy {
return fmt.Errorf("service container %s failed health check", c.GetName())
} else if health == container.HealthHealthy { } else if health == container.HealthHealthy {
return nil return nil
} }

View file

@ -323,7 +323,8 @@ func TestRunner_RunEvent(t *testing.T) {
{workdir, "services", "push", "", platforms, secrets}, {workdir, "services", "push", "", platforms, secrets},
{workdir, "services-with-container", "push", "", platforms, secrets}, {workdir, "services-with-container", "push", "", platforms, secrets},
{workdir, "mysql-service-container-with-health-check", "push", "", platforms, secrets}, {workdir, "mysql-service-container-with-health-check", "push", "", platforms, secrets},
{workdir, "mysql-service-container-failed-health-check", "push", "service container NAME failed health check", platforms, secrets}, {workdir, "mysql-service-container-failed-health-check", "push", "service container NAME: failed health check or terminated before becoming healthy", platforms, secrets},
{workdir, "mysql-service-container-premature-terminate", "push", "service container NAME: failed health check or terminated before becoming healthy", platforms, secrets},
} }
for _, table := range tables { for _, table := range tables {

View file

@ -0,0 +1,21 @@
name: service-container
on: push
jobs:
service-container-test:
runs-on: ubuntu-latest
container: code.forgejo.org/oci/mysql:8.4
services:
maindb:
image: code.forgejo.org/oci/mysql:8.4
# This container should immediately exit due to missing env variable for poassword config. ... [ERROR]
# [Entrypoint]: Database is uninitialized and password option is not specified You need to specify one of the
# following as an environment variable:
# - MYSQL_ROOT_PASSWORD
# - MYSQL_ALLOW_EMPTY_PASSWORD
# - MYSQL_RANDOM_ROOT_PASSWORD
#
# This container should retain the same health check config as the mysql-service-container-with-health-check
# case.
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- run: exit 100 # should never be hit since service will never be healthy