feat: backend executor implement
This commit is contained in:
@@ -59,7 +59,7 @@ func TestExecutorPreservesOpaqueImageTagsAndReachesSwitching(t *testing.T) {
|
||||
engine.mu.Unlock()
|
||||
t.Fatalf("image reference changed: got %q want %q", engine.lastCreateSpec.ImageReference, imageReference)
|
||||
}
|
||||
if !slices.Contains(engine.lastCreateSpec.Environment, "SERVER_PORT=8081") {
|
||||
if !slices.Contains(engine.lastCreateSpec.Environment, "SERVER_PORT=8081") || !slices.Contains(engine.lastCreateSpec.Environment, "SPRING_CONFIG_LOCATION=file:/app/config/yms.yaml") {
|
||||
engine.mu.Unlock()
|
||||
t.Fatalf("missing explicit port environment: %+v", engine.lastCreateSpec.Environment)
|
||||
}
|
||||
@@ -97,6 +97,13 @@ func TestExecutorRecoversRecordedCreateIntentWithoutRepeatingCreate(t *testing.T
|
||||
if _, err := store.CompleteStep(ctx, record.ID, loadStep.Key, transaction.StepSucceeded, []byte(`{"loaded":true}`), ""); err != nil {
|
||||
t.Fatalf("complete image step: %v", err)
|
||||
}
|
||||
removeStep := removeIntent(request)
|
||||
if _, _, err := store.RecordStepIntent(ctx, record.ID, removeStep); err != nil {
|
||||
t.Fatalf("record completed remove intent: %v", err)
|
||||
}
|
||||
if _, err := store.CompleteStep(ctx, record.ID, removeStep.Key, transaction.StepSucceeded, []byte(`{"absent":true}`), ""); err != nil {
|
||||
t.Fatalf("complete remove step: %v", err)
|
||||
}
|
||||
createStep := createIntent(request, engine.loadedImage.ID)
|
||||
if _, _, err := store.RecordStepIntent(ctx, record.ID, createStep); err != nil {
|
||||
t.Fatalf("record crash-window create intent: %v", err)
|
||||
@@ -108,7 +115,7 @@ func TestExecutorRecoversRecordedCreateIntentWithoutRepeatingCreate(t *testing.T
|
||||
engine.mu.Lock()
|
||||
calls := engine.callCounts()
|
||||
engine.mu.Unlock()
|
||||
if calls.load != 0 || calls.create != 0 || calls.start != 1 {
|
||||
if calls.load != 0 || calls.remove != 0 || calls.create != 0 || calls.start != 1 {
|
||||
t.Fatalf("unexpected recovery calls: %+v", calls)
|
||||
}
|
||||
current, err := store.Transaction(ctx, record.ID)
|
||||
@@ -135,7 +142,7 @@ func TestExecutorMarksValidationFailureTerminal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutorKeepsPreparedStateForConflictingContainerInspection(t *testing.T) {
|
||||
func TestExecutorReplacesInactiveContainerWithConflictingImage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store, coordinator := testTransactionKernel(t)
|
||||
request := testRequest(t, testRepository+":20260814-093609-d7ed70f0")
|
||||
@@ -147,14 +154,18 @@ func TestExecutorKeepsPreparedStateForConflictingContainerInspection(t *testing.
|
||||
executor := testExecutor(t, store, coordinator, engine, healthyResponse)
|
||||
record := createTransaction(t, store, "container-conflict")
|
||||
|
||||
err := executor.Run(ctx, record.ID, request)
|
||||
var uncertain *transaction.UncertainStepError
|
||||
if !errors.As(err, &uncertain) {
|
||||
t.Fatalf("expected uncertain container step, got %v", err)
|
||||
if err := executor.Run(ctx, record.ID, request); err != nil {
|
||||
t.Fatalf("replace inactive container: %v", err)
|
||||
}
|
||||
current, readErr := store.Transaction(ctx, record.ID)
|
||||
if readErr != nil || current.State != transaction.StatePrepared {
|
||||
t.Fatalf("conflict did not preserve prepared state: record=%+v err=%v", current, readErr)
|
||||
if readErr != nil || current.State != transaction.StateSwitching {
|
||||
t.Fatalf("replacement did not reach switching: record=%+v err=%v", current, readErr)
|
||||
}
|
||||
engine.mu.Lock()
|
||||
calls := engine.callCounts()
|
||||
engine.mu.Unlock()
|
||||
if calls.remove != 1 || calls.create != 1 {
|
||||
t.Fatalf("inactive replacement calls mismatch: %+v", calls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,17 +233,22 @@ func testRequest(t *testing.T, imageReference string) Request {
|
||||
t.Fatalf("write backend configuration: %v", err)
|
||||
}
|
||||
return Request{
|
||||
ArchivePath: archivePath,
|
||||
ImageReference: imageReference,
|
||||
ExpectedImageDigest: testDigest,
|
||||
Platform: containerengine.Platform{OS: "linux", Architecture: "amd64"},
|
||||
ContainerName: "explicit-backend-8081",
|
||||
Port: 8081,
|
||||
PortEnvironmentKey: "SERVER_PORT",
|
||||
ConfigSource: configPath,
|
||||
ConfigTarget: "/app/config/application.yaml",
|
||||
RestartPolicy: containerengine.RestartPolicy{Name: "unless-stopped"},
|
||||
HealthEndpoint: "http://127.0.0.1:8081/yms/actuator/health",
|
||||
ImageAcquisition: ImageAcquisitionLoad,
|
||||
ArchivePath: archivePath,
|
||||
ImageReference: imageReference,
|
||||
ExpectedImageDigest: testDigest,
|
||||
Platform: containerengine.Platform{OS: "linux", Architecture: "amd64"},
|
||||
ContainerName: "explicit-backend-8081",
|
||||
Port: 8081,
|
||||
PortEnvironmentKey: "SERVER_PORT",
|
||||
ConfigSource: configPath,
|
||||
ConfigTarget: "/app/config/yms.yaml",
|
||||
TmpSource: directory,
|
||||
TmpTarget: "/home/yms/tmp",
|
||||
ConfigEnvironmentKey: "SPRING_CONFIG_LOCATION",
|
||||
ConfigLocation: "file:/app/config/yms.yaml",
|
||||
RestartPolicy: containerengine.RestartPolicy{Name: "unless-stopped"},
|
||||
HealthEndpoint: "http://127.0.0.1:8081/yms/actuator/health",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +363,14 @@ func (e *fakeEngine) LoadImage(_ context.Context, input io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *fakeEngine) PullImage(context.Context, string) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.loadCalls++
|
||||
e.imageAvailable = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *fakeEngine) InspectImage(context.Context, string) (containerengine.Image, error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
@@ -384,6 +408,19 @@ func (e *fakeEngine) StartContainer(_ context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *fakeEngine) StopContainer(_ context.Context, name string) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
record, exists := e.containers[name]
|
||||
if !exists {
|
||||
return containerengine.ErrNotFound
|
||||
}
|
||||
record.Running = false
|
||||
record.Status = "exited"
|
||||
e.containers[name] = record
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *fakeEngine) InspectContainer(_ context.Context, name string) (containerengine.Container, error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
@@ -409,17 +446,19 @@ func (e *fakeEngine) Close() error { return nil }
|
||||
|
||||
func (e *fakeEngine) containerFromSpec(spec containerengine.ContainerSpec, running bool) containerengine.Container {
|
||||
return containerengine.Container{
|
||||
ID: "container-id-" + spec.Name,
|
||||
Name: spec.Name,
|
||||
ImageID: e.loadedImage.ID,
|
||||
ImageReference: spec.ImageReference,
|
||||
Platform: spec.Platform.OS + "/" + spec.Platform.Architecture,
|
||||
Running: running,
|
||||
Status: "created",
|
||||
Environment: append([]string(nil), spec.Environment...),
|
||||
NetworkMode: spec.NetworkMode,
|
||||
RestartPolicy: spec.RestartPolicy,
|
||||
Mounts: append([]containerengine.Mount(nil), spec.Mounts...),
|
||||
ID: "container-id-" + spec.Name,
|
||||
Name: spec.Name,
|
||||
ImageID: e.loadedImage.ID,
|
||||
ImageReference: spec.ImageReference,
|
||||
Platform: spec.Platform.OS + "/" + spec.Platform.Architecture,
|
||||
Running: running,
|
||||
Status: "created",
|
||||
Environment: append([]string(nil), spec.Environment...),
|
||||
NetworkMode: spec.NetworkMode,
|
||||
RestartPolicy: spec.RestartPolicy,
|
||||
Mounts: append([]containerengine.Mount(nil), spec.Mounts...),
|
||||
User: spec.User,
|
||||
StopTimeoutSeconds: spec.StopTimeoutSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user