feat: backend executor implement

This commit is contained in:
2026-08-16 17:12:06 +08:00
parent 390e0565d3
commit 7755da72e7
31 changed files with 2762 additions and 184 deletions
+143
View File
@@ -4,12 +4,135 @@ import (
"context"
"encoding/json"
"errors"
"net/url"
"path/filepath"
"sync"
"testing"
"time"
"github.com/ncruces/go-sqlite3/driver"
)
func TestStoreMigratesVersionOneAndCommitsBackendContainerDeployment(t *testing.T) {
t.Parallel()
ctx := context.Background()
databasePath := filepath.Join(t.TempDir(), "transaction.db")
dsn := (&url.URL{Scheme: "file", Path: databasePath}).String()
database, err := driver.Open(dsn)
if err != nil {
t.Fatalf("open version one database: %v", err)
}
if _, err := database.ExecContext(ctx, schemaV1); err != nil {
_ = database.Close()
t.Fatalf("create version one database: %v", err)
}
if err := database.Close(); err != nil {
t.Fatalf("close version one database: %v", err)
}
store, err := OpenStore(ctx, databasePath)
if err != nil {
t.Fatalf("migrate transaction store: %v", err)
}
t.Cleanup(func() { _ = store.Close() })
if _, err := store.BackendContainerDeployment(ctx); !errors.Is(err, ErrNotFound) {
t.Fatalf("unexpected deployment before commit: %v", err)
}
hasHistory, err := store.HasCommittedBackendContainerTransactionHistory(ctx)
if err != nil || hasHistory {
t.Fatalf("unexpected empty backend history: found=%t err=%v", hasHistory, err)
}
nativeRecord, _, err := store.CreateTransaction(ctx, CreateRequest{
ID: "native-backend-transaction",
IdempotencyKey: "backend:native-request",
Source: "test",
Service: "backend",
})
if err != nil {
t.Fatalf("create native backend transaction: %v", err)
}
if _, err := store.Transition(ctx, nativeRecord.ID, StateFailed, "finish native backend transaction"); err != nil {
t.Fatalf("finish native backend transaction: %v", err)
}
hasHistory, err = store.HasCommittedBackendContainerTransactionHistory(ctx)
if err != nil || hasHistory {
t.Fatalf("native backend history was treated as container history: found=%t err=%v", hasHistory, err)
}
record, created, err := store.CreateTransaction(ctx, CreateRequest{
ID: "container-deployment-transaction",
IdempotencyKey: "backend:container:deployment-request",
Source: "test",
Service: "backend",
})
if err != nil || !created {
t.Fatalf("create backend container transaction: record=%+v created=%t err=%v", record, created, err)
}
for _, state := range []State{StateValidating, StatePrepared, StateStarting, StateSwitching, StateVerifying, StateDraining} {
if _, err := store.Transition(ctx, record.ID, state, "test transition"); err != nil {
t.Fatalf("transition backend container transaction to %s: %v", state, err)
}
}
hasHistory, err = store.HasCommittedBackendContainerTransactionHistory(ctx)
if err != nil || hasHistory {
t.Fatalf("unfinished backend container transaction was treated as committed history: found=%t err=%v", hasHistory, err)
}
committed, err := store.CommitBackendContainerDeployment(ctx, record.ID, BackendContainerDeployment{
ActivePort: 8081,
ContainerName: "backend-8081",
ImageDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
ContainerID: "container-backend-8081",
}, "container deployment committed")
if err != nil || committed.State != StateCommitted {
t.Fatalf("commit backend container deployment: record=%+v err=%v", committed, err)
}
hasHistory, err = store.HasCommittedBackendContainerTransactionHistory(ctx)
if err != nil || !hasHistory {
t.Fatalf("committed backend container history was not recorded: found=%t err=%v", hasHistory, err)
}
deployment, err := store.BackendContainerDeployment(ctx)
if err != nil {
t.Fatalf("read backend container deployment: %v", err)
}
if deployment.ActivePort != 8081 || deployment.ContainerName != "backend-8081" || deployment.ContainerID != "container-backend-8081" || deployment.TransactionID != record.ID {
t.Fatalf("unexpected backend container deployment: %+v", deployment)
}
var version int
if err := store.db.QueryRowContext(ctx, "PRAGMA user_version").Scan(&version); err != nil || version != schemaVersion {
t.Fatalf("unexpected migrated schema version: version=%d err=%v", version, err)
}
}
func TestBackendContainerDeploymentCommitRequiresDrainingTransaction(t *testing.T) {
t.Parallel()
ctx := context.Background()
store := openTestStore(t)
record, _, err := store.CreateTransaction(ctx, CreateRequest{
ID: "container-deployment-wrong-state",
IdempotencyKey: "container-deployment-wrong-state-request",
Source: "test",
Service: "backend",
})
if err != nil {
t.Fatalf("create backend transaction: %v", err)
}
_, err = store.CommitBackendContainerDeployment(ctx, record.ID, BackendContainerDeployment{
ActivePort: 8080,
ContainerName: "backend-8080",
ImageDigest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
ContainerID: "container-backend-8080",
}, "must fail")
var transitionErr *TransitionError
if !errors.As(err, &transitionErr) {
t.Fatalf("expected deployment commit transition error, got %v", err)
}
if _, err := store.BackendContainerDeployment(ctx); !errors.Is(err, ErrNotFound) {
t.Fatalf("failed commit wrote backend deployment: %v", err)
}
}
func TestStoreCreateIsIdempotentAndAllowsOnlyOneActiveTransaction(t *testing.T) {
t.Parallel()
ctx := context.Background()
@@ -51,6 +174,26 @@ func TestStoreCreateIsIdempotentAndAllowsOnlyOneActiveTransaction(t *testing.T)
if _, err := store.Transition(ctx, created.ID, StateFailed, "test terminal state"); err != nil {
t.Fatalf("finish first transaction: %v", err)
}
retriedAfterFailure, isNew, err := store.CreateTransaction(ctx, CreateRequest{
ID: "transaction-1-retry",
IdempotencyKey: request.IdempotencyKey,
Source: request.Source,
Service: request.Service,
Request: request.Request,
})
if err != nil || !isNew || retriedAfterFailure.ID != "transaction-1-retry" {
t.Fatalf("retry failed transaction: record=%+v new=%v err=%v", retriedAfterFailure, isNew, err)
}
archived, err := store.Transaction(ctx, created.ID)
if err != nil {
t.Fatalf("read archived failed transaction: %v", err)
}
if archived.IdempotencyKey != request.IdempotencyKey+":terminal:"+created.ID {
t.Fatalf("unexpected archived idempotency key: %q", archived.IdempotencyKey)
}
if _, err := store.Transition(ctx, retriedAfterFailure.ID, StateFailed, "finish retried transaction"); err != nil {
t.Fatalf("finish retried transaction: %v", err)
}
second, isNew, err := store.CreateTransaction(ctx, CreateRequest{
ID: "transaction-2",
IdempotencyKey: "request-2",