Files
mesh-host/internal/apply/replace_image_test.go
T
jschoubben 09c77d57b8
mesh/repo-check head 09c77d57b823 merged into trunk c231d1031fc7 beside mesh-controller#428@d6db3348 — pass: its merge-check.sh passed
mesh/merge-gate head 09c77d57b823 merged into trunk c231d1031fc7 — pass: group fix/1732-held-moves-asked composed: every machine composes with the change a…
mesh/delivery delivered
mesh/delivery-group group fix/1732-held-moves-asked delivered: every member is delivered
Have a replacement container's image before removing the running one, so an unobtainable image leaves the service up (issue 1742)
2026-10-21 19:16:32 +02:00

94 lines
2.8 KiB
Go

package apply
import (
"context"
"errors"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/declaration"
"github.com/novox/mesh-host/internal/store"
)
// Issue 1742: the engine removed a running container and only then found the replacement image
// unobtainable, leaving the service down. The replacement image comes first.
func TestARunningContainerIsKeptWhenItsReplacementImageCannotBeObtained(t *testing.T) {
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"store","type":"container","name":"store","image":"`+pinned+`","env":{"PGDATA":"/data"}}
]}`)
var removed, created bool
run := func(ctx context.Context, name string, args ...string) (string, error) {
args = oldShape(name, args)
switch args[0] {
case "info":
return "27.0\n", nil
case "container":
return "true\tsome-older-spec", nil
case "image":
return "", errors.New("no such image")
case "pull":
return "", errors.New("manifest unknown")
case "rm":
removed = true
return "", nil
case "run":
created = true
return "deadbeef\n", nil
}
return "", nil
}
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
if removed || created {
t.Fatalf("the running container was touched although its image is unobtainable (removed=%v created=%v)", removed, created)
}
if err == nil {
t.Fatal("an unobtainable image failed nothing")
}
if !strings.Contains(err.Error(), pinned) {
t.Errorf("the failure does not name the image %s: %v", pinned, err)
}
}
func TestAReplacementImageIsPresentBeforeTheRunningContainerIsRemoved(t *testing.T) {
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"store","type":"container","name":"store","image":"`+pinned+`","env":{"PGDATA":"/data"}}
]}`)
want := containerSpec(d.Resources[0].(*declaration.Container), inputs{})
var order []string
created := false
run := func(ctx context.Context, name string, args ...string) (string, error) {
args = oldShape(name, args)
switch args[0] {
case "info":
return "27.0\n", nil
case "container":
if created {
return "true\t" + want, nil
}
return "true\tsome-older-spec", nil
case "image":
order = append(order, "inspect")
if len(order) == 1 {
return "", errors.New("no such image")
}
return "", nil
case "pull":
order = append(order, "pull")
case "rm":
order = append(order, "rm")
case "run":
order = append(order, "run")
created = true
return "deadbeef\n", nil
}
return "", nil
}
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil); err != nil {
t.Fatalf("apply failed: %v", err)
}
got := strings.Join(order, ",")
if !strings.HasPrefix(got, "inspect,pull,inspect,rm,run") {
t.Errorf("order of runtime calls = %s, want the image pulled before rm", got)
}
}