diff --git a/api/ateapi/v1/ateapi.pb.go b/api/ateapi/v1/ateapi.pb.go index b61e08acf3..44de8a372c 100644 --- a/api/ateapi/v1/ateapi.pb.go +++ b/api/ateapi/v1/ateapi.pb.go @@ -3135,9 +3135,10 @@ type SnapshotConfig struct { // storage_location is the base object-storage URI snapshots of actors on // this version are stored under. If unset, ate-api-server's // --snapshot-location is used. Requires the PerTemplateSnapshotLocation preview - // gate; ignored without it. + // gate. // // +ate:preview=PerTemplateSnapshotLocation + // +k8s:ifDisabled(PerTemplateSnapshotLocation)=+k8s:forbidden // +k8s:optional // +k8s:maxLength=1024 // +k8s:customValidation # Validate URI diff --git a/api/ateapi/v1/ateapi.proto b/api/ateapi/v1/ateapi.proto index fb6c701357..7a59f6a098 100644 --- a/api/ateapi/v1/ateapi.proto +++ b/api/ateapi/v1/ateapi.proto @@ -1279,9 +1279,10 @@ message SnapshotConfig { // storage_location is the base object-storage URI snapshots of actors on // this version are stored under. If unset, ate-api-server's // --snapshot-location is used. Requires the PerTemplateSnapshotLocation preview - // gate; ignored without it. + // gate. // // +ate:preview=PerTemplateSnapshotLocation + // +k8s:ifDisabled(PerTemplateSnapshotLocation)=+k8s:forbidden // +k8s:optional // +k8s:maxLength=1024 // +k8s:customValidation # Validate URI diff --git a/benchmarking/README.md b/benchmarking/README.md index 9c17596d03..803989d54b 100644 --- a/benchmarking/README.md +++ b/benchmarking/README.md @@ -12,12 +12,11 @@ scenario ladder, read [observability.md](observability.md). > [!IMPORTANT] > Source the environment configuration file (e.g., `source .ate-dev-env.sh`) -> first so `PROJECT_ID`, `BUCKET_NAME`, etc. are set. +> first so `PROJECT_ID` etc. are set. > > **For Local Kind Clusters**: Set `KO_DOCKER_REPO` to the local registry so images are pulled without remote cloud credentials: > ```bash > export KO_DOCKER_REPO="localhost:5001" -> export BUCKET_NAME="ate-snapshots" > ``` > [!IMPORTANT] diff --git a/benchmarking/deploy_locust.sh b/benchmarking/deploy_locust.sh index 6d216125cd..d9421a6b7c 100755 --- a/benchmarking/deploy_locust.sh +++ b/benchmarking/deploy_locust.sh @@ -63,7 +63,7 @@ usage() { echo " -h|--help Show this help message" echo "" echo "Environment:" - echo " PROJECT_ID, BUCKET_NAME, etc. are read from .ate-dev-env.sh by the" + echo " PROJECT_ID etc. are read from .ate-dev-env.sh by the" echo " scripts this wrapper invokes." } diff --git a/benchmarking/nighthawk-egress/deploy-traffic-driver.sh b/benchmarking/nighthawk-egress/deploy-traffic-driver.sh index 825cf3dd2c..19d3aeb9ba 100755 --- a/benchmarking/nighthawk-egress/deploy-traffic-driver.sh +++ b/benchmarking/nighthawk-egress/deploy-traffic-driver.sh @@ -35,7 +35,6 @@ EGRESS_POLICY_MANIFEST="${SCRIPT_DIR}/egress-policy.yaml" KUBECTL_ATE_BIN="kubectl-ate" WAIT_TIMEOUT_SECS=300 CONTEXT="${KUBECTL_CONTEXT:-}" -BUCKET_NAME="${BUCKET_NAME:-ate-snapshots}" KO_DOCKER_REPO="${KO_DOCKER_REPO:-localhost:5001}" usage() { @@ -44,7 +43,6 @@ usage() { echo "Options:" echo " --deploy Deploy the ${ATESPACE}/${TEMPLATE_NAME} worker pool, actor template, and ${ACTOR_NAME} actor (default)" echo " --delete Delete the ${ACTOR_NAME} actor, template, atespace, and worker pool" - echo " --bucket-name BUCKET GCS bucket name for actor snapshots (default: \$BUCKET_NAME)" echo " --ko-docker-repo REPO Container registry for ko image builds (default: \$KO_DOCKER_REPO)" echo " --context CONTEXT kubectl context to use (default: \$KUBECTL_CONTEXT or current context)" echo " --wait-timeout SECONDS Timeout waiting for worker pool and golden snapshot readiness (default: ${WAIT_TIMEOUT_SECS})" @@ -60,13 +58,6 @@ while [[ "$#" -gt 0 ]]; do --delete) action="delete" ;; - --bucket-name) - shift - BUCKET_NAME="$1" - ;; - --bucket-name=*) - BUCKET_NAME="${1#*=}" - ;; --ko-docker-repo) shift KO_DOCKER_REPO="$1" @@ -150,10 +141,6 @@ wait_template_ready() { } deploy() { - if [[ -z "${BUCKET_NAME}" ]]; then - echo "Error: BUCKET_NAME is not set. Pass --bucket-name or configure .ate-dev-env.sh." >&2 - exit 1 - fi if [[ -z "${KO_DOCKER_REPO}" ]]; then echo "Error: KO_DOCKER_REPO is not set. Pass --ko-docker-repo or configure .ate-dev-env.sh." >&2 exit 1 @@ -181,8 +168,7 @@ deploy() { run_kubectl_ate delete actor-template "${TEMPLATE_NAME}" -a "${ATESPACE}" >/dev/null 2>&1 || true echo "Creating actor template ${ATESPACE}/${TEMPLATE_NAME} from ${MANIFEST}..." - sed "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" "${MANIFEST}" \ - | run_kubectl_ate create actor-template -f - + run_kubectl_ate create actor-template -f "${MANIFEST}" wait_template_ready diff --git a/benchmarking/nighthawk-egress/traffic-driver-template.yaml.tmpl b/benchmarking/nighthawk-egress/traffic-driver-template.yaml.tmpl index 27c68f984b..0960539262 100644 --- a/benchmarking/nighthawk-egress/traffic-driver-template.yaml.tmpl +++ b/benchmarking/nighthawk-egress/traffic-driver-template.yaml.tmpl @@ -14,7 +14,7 @@ # Substrate ActorTemplate resource (protojson-shaped ateapi.ActorTemplate, # NOT the ate.dev/v1 CRD) for the Nighthawk egress traffic-driver actor. -# Applied with `kubectl-ate create actor-template -f -` by +# Applied with `kubectl-ate create actor-template -f` by # deploy-traffic-driver.sh; the `nighthawk` atespace must exist. # # Runs Envoy's `nighthawk_service` gRPC server on port 80 (the default actor @@ -65,4 +65,3 @@ resources: memoryBytes: 1073741824 # 1Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/nighthawk/traffic-driver/ diff --git a/benchmarking/workloads/deploy.sh b/benchmarking/workloads/deploy.sh index 7881ca2820..b934326ead 100755 --- a/benchmarking/workloads/deploy.sh +++ b/benchmarking/workloads/deploy.sh @@ -23,12 +23,6 @@ if [[ -f .ate-dev-env.sh ]]; then source .ate-dev-env.sh fi -# Ensure BUCKET_NAME is set -if [[ -z "${BUCKET_NAME:-}" ]]; then - echo "Error: BUCKET_NAME environment variable is not set." >&2 - exit 1 -fi - MANIFEST_DIR="benchmarking/workloads/manifests" POOL_MANIFEST="${MANIFEST_DIR}/workloads.yaml.tmpl" # Generated sweperf ActorTemplates; see manifests/sweperf/generate_sweperf_templates.py. @@ -192,8 +186,7 @@ substitute() { if [[ -n "${WORKER_MEMORY}" ]]; then worker_resources="resources: {requests: {memory: \"${WORKER_MEMORY}\"}, limits: {memory: \"${WORKER_MEMORY}\"}}" fi - sed -e "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" \ - -e "s|\${WORKER_COUNT}|${WORKER_COUNT}|g" \ + sed -e "s|\${WORKER_COUNT}|${WORKER_COUNT}|g" \ -e "s|\${SANDBOX_CLASS}|${SANDBOX_CLASS}|g" \ -e "s|\${OTLP_ENDPOINT}|${OTLP_ENDPOINT}|g" \ -e "s|\${ACTOR_MEMORY}|${ACTOR_MEMORY}|g" \ diff --git a/benchmarking/workloads/manifests/ate-env-template.yaml.tmpl b/benchmarking/workloads/manifests/ate-env-template.yaml.tmpl index 64d6791643..1df6a48b65 100644 --- a/benchmarking/workloads/manifests/ate-env-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/ate-env-template.yaml.tmpl @@ -44,4 +44,3 @@ resources: memoryBytes: "${ACTOR_MEMORY_BYTES}" snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/ate-env/ diff --git a/benchmarking/workloads/manifests/glutton-durdir-data-template.yaml.tmpl b/benchmarking/workloads/manifests/glutton-durdir-data-template.yaml.tmpl index d507e18483..67f510453b 100644 --- a/benchmarking/workloads/manifests/glutton-durdir-data-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/glutton-durdir-data-template.yaml.tmpl @@ -50,4 +50,3 @@ resources: memoryBytes: "${ACTOR_MEMORY_BYTES}" snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_VOLUMES - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/glutton-durdir-data/ diff --git a/benchmarking/workloads/manifests/glutton-durdir-full-template.yaml.tmpl b/benchmarking/workloads/manifests/glutton-durdir-full-template.yaml.tmpl index 0cbc965059..f961cbdbe2 100644 --- a/benchmarking/workloads/manifests/glutton-durdir-full-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/glutton-durdir-full-template.yaml.tmpl @@ -47,4 +47,3 @@ resources: memoryBytes: "${ACTOR_MEMORY_BYTES}" snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/glutton-durdir-full/ diff --git a/benchmarking/workloads/manifests/glutton-storage-template.yaml.tmpl b/benchmarking/workloads/manifests/glutton-storage-template.yaml.tmpl index ad7dcb28bb..77fc654a40 100644 --- a/benchmarking/workloads/manifests/glutton-storage-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/glutton-storage-template.yaml.tmpl @@ -57,4 +57,3 @@ resources: memoryBytes: "${ACTOR_MEMORY_BYTES}" snapshotConfig: onCommit: SNAPSHOT_CONTENT_SCOPE_FULL - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/glutton-storage/ diff --git a/benchmarking/workloads/manifests/glutton-template.yaml.tmpl b/benchmarking/workloads/manifests/glutton-template.yaml.tmpl index 7ed6d25d1b..bdec96ac0f 100644 --- a/benchmarking/workloads/manifests/glutton-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/glutton-template.yaml.tmpl @@ -59,4 +59,3 @@ resources: memoryBytes: "${ACTOR_MEMORY_BYTES}" snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/glutton/ diff --git a/benchmarking/workloads/manifests/kernelmem-template.yaml.tmpl b/benchmarking/workloads/manifests/kernelmem-template.yaml.tmpl index 7019662db9..df3f93c811 100644 --- a/benchmarking/workloads/manifests/kernelmem-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/kernelmem-template.yaml.tmpl @@ -37,4 +37,3 @@ resources: memoryBytes: 8589934592 # 8Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/kernelmem/ diff --git a/benchmarking/workloads/manifests/sleep-template.yaml.tmpl b/benchmarking/workloads/manifests/sleep-template.yaml.tmpl index 57c8c5fbef..19c462199e 100644 --- a/benchmarking/workloads/manifests/sleep-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/sleep-template.yaml.tmpl @@ -33,4 +33,3 @@ resources: memoryBytes: "${ACTOR_MEMORY_BYTES}" snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/sleep/ diff --git a/benchmarking/workloads/manifests/sweperf/generated/sweperf-astropy-7336-template.yaml.tmpl b/benchmarking/workloads/manifests/sweperf/generated/sweperf-astropy-7336-template.yaml.tmpl index 33bb0d7738..c641f74f27 100644 --- a/benchmarking/workloads/manifests/sweperf/generated/sweperf-astropy-7336-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/sweperf/generated/sweperf-astropy-7336-template.yaml.tmpl @@ -14,7 +14,7 @@ # Generated by benchmarking/workloads/manifests/sweperf/generate_sweperf_templates.py # from sweperf-images.json and sweperf-template.yaml.j2; edit those, not this -# file. The ${...} values are filled in by benchmarking/workloads/deploy.sh. +# file. metadata: atespace: benchmark-workloads name: sweperf-astropy-7336 @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/sweperf-astropy-7336/ diff --git a/benchmarking/workloads/manifests/sweperf/generated/sweperf-django-11099-template.yaml.tmpl b/benchmarking/workloads/manifests/sweperf/generated/sweperf-django-11099-template.yaml.tmpl index af94623150..4c5a62be26 100644 --- a/benchmarking/workloads/manifests/sweperf/generated/sweperf-django-11099-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/sweperf/generated/sweperf-django-11099-template.yaml.tmpl @@ -14,7 +14,7 @@ # Generated by benchmarking/workloads/manifests/sweperf/generate_sweperf_templates.py # from sweperf-images.json and sweperf-template.yaml.j2; edit those, not this -# file. The ${...} values are filled in by benchmarking/workloads/deploy.sh. +# file. metadata: atespace: benchmark-workloads name: sweperf-django-11099 @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/sweperf-django-11099/ diff --git a/benchmarking/workloads/manifests/sweperf/generated/sweperf-matplotlib-20826-template.yaml.tmpl b/benchmarking/workloads/manifests/sweperf/generated/sweperf-matplotlib-20826-template.yaml.tmpl index 040e0486ff..8751fbde91 100644 --- a/benchmarking/workloads/manifests/sweperf/generated/sweperf-matplotlib-20826-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/sweperf/generated/sweperf-matplotlib-20826-template.yaml.tmpl @@ -14,7 +14,7 @@ # Generated by benchmarking/workloads/manifests/sweperf/generate_sweperf_templates.py # from sweperf-images.json and sweperf-template.yaml.j2; edit those, not this -# file. The ${...} values are filled in by benchmarking/workloads/deploy.sh. +# file. metadata: atespace: benchmark-workloads name: sweperf-matplotlib-20826 @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/sweperf-matplotlib-20826/ diff --git a/benchmarking/workloads/manifests/sweperf/generated/sweperf-sphinx-8638-template.yaml.tmpl b/benchmarking/workloads/manifests/sweperf/generated/sweperf-sphinx-8638-template.yaml.tmpl index 1a8264c447..98c2222fd8 100644 --- a/benchmarking/workloads/manifests/sweperf/generated/sweperf-sphinx-8638-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/sweperf/generated/sweperf-sphinx-8638-template.yaml.tmpl @@ -14,7 +14,7 @@ # Generated by benchmarking/workloads/manifests/sweperf/generate_sweperf_templates.py # from sweperf-images.json and sweperf-template.yaml.j2; edit those, not this -# file. The ${...} values are filled in by benchmarking/workloads/deploy.sh. +# file. metadata: atespace: benchmark-workloads name: sweperf-sphinx-8638 @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/sweperf-sphinx-8638/ diff --git a/benchmarking/workloads/manifests/sweperf/generated/sweperf-sympy-13647-template.yaml.tmpl b/benchmarking/workloads/manifests/sweperf/generated/sweperf-sympy-13647-template.yaml.tmpl index e869b8c2ee..419de76702 100644 --- a/benchmarking/workloads/manifests/sweperf/generated/sweperf-sympy-13647-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/sweperf/generated/sweperf-sympy-13647-template.yaml.tmpl @@ -14,7 +14,7 @@ # Generated by benchmarking/workloads/manifests/sweperf/generate_sweperf_templates.py # from sweperf-images.json and sweperf-template.yaml.j2; edit those, not this -# file. The ${...} values are filled in by benchmarking/workloads/deploy.sh. +# file. metadata: atespace: benchmark-workloads name: sweperf-sympy-13647 @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/sweperf-sympy-13647/ diff --git a/benchmarking/workloads/manifests/sweperf/sweperf-template.yaml.j2 b/benchmarking/workloads/manifests/sweperf/sweperf-template.yaml.j2 index 9fc60b1653..a624026dae 100644 --- a/benchmarking/workloads/manifests/sweperf/sweperf-template.yaml.j2 +++ b/benchmarking/workloads/manifests/sweperf/sweperf-template.yaml.j2 @@ -14,7 +14,7 @@ # Generated by benchmarking/workloads/manifests/sweperf/generate_sweperf_templates.py # from sweperf-images.json and sweperf-template.yaml.j2; edit those, not this -# file. The ${...} values are filled in by benchmarking/workloads/deploy.sh. +# file. metadata: atespace: benchmark-workloads name: {{ name }} @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/{{ name }}/ diff --git a/benchmarking/workloads/manifests/sweperf/test_generate_sweperf_templates.py b/benchmarking/workloads/manifests/sweperf/test_generate_sweperf_templates.py index 2d67908e01..e5c8242c67 100644 --- a/benchmarking/workloads/manifests/sweperf/test_generate_sweperf_templates.py +++ b/benchmarking/workloads/manifests/sweperf/test_generate_sweperf_templates.py @@ -40,8 +40,6 @@ def test_render_fills_task_values(self): self.assertIn("name: sweperf-django-11099", content) self.assertIn(f'image: "{PINNED}"', content) self.assertIn('value: "15"', content) - # deploy.sh fills these, so they must survive rendering. - self.assertIn("${BUCKET_NAME}", content) def test_check_reports_missing_stale_and_orphaned_files(self): rendered = gen.render([{"name": n, "image": PINNED, "steps": 1} diff --git a/benchmarking/workloads/manifests/usermem-template.yaml.tmpl b/benchmarking/workloads/manifests/usermem-template.yaml.tmpl index 5b0bd1fc54..84637bdcb4 100644 --- a/benchmarking/workloads/manifests/usermem-template.yaml.tmpl +++ b/benchmarking/workloads/manifests/usermem-template.yaml.tmpl @@ -35,4 +35,3 @@ resources: memoryBytes: 1610612736 # 1536Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/benchmark-workloads/usermem/ diff --git a/cmd/ate-setup/commands.md b/cmd/ate-setup/commands.md index 28ed5d6fcf..87672d5217 100644 --- a/cmd/ate-setup/commands.md +++ b/cmd/ate-setup/commands.md @@ -192,7 +192,7 @@ is absent); the other two are flags of the benchmark commands. See | `deploy demo sandbox` | An on-demand sandbox actor driven by the sandbox client | | `deploy demo multi-template` | Two ActorTemplates sharing one WorkerPool | | `deploy demo parking` | Actor parking and unparking on a small WorkerPool | -| `deploy demo claude-code-multiplex` | Several Claude Code agents multiplexed onto one WorkerPool (requires `ANTHROPIC_API_KEY`, `BUCKET_NAME`, `KO_DOCKER_REPO`) | +| `deploy demo claude-code-multiplex` | Several Claude Code agents multiplexed onto one WorkerPool (requires `ANTHROPIC_API_KEY`, `KO_DOCKER_REPO`) | Each has a matching `delete demo NAME`. Demo flags bind to the deploy side only; teardown never reads them. diff --git a/cmd/ate-setup/internal/config/config.go b/cmd/ate-setup/internal/config/config.go index bf300d7469..2706b81cad 100644 --- a/cmd/ate-setup/internal/config/config.go +++ b/cmd/ate-setup/internal/config/config.go @@ -129,14 +129,18 @@ type Config struct { // defaults to the in-cluster URL of the install's ate-idp-server Service. ActorJWTIssuer string - // BucketName is the snapshot bucket demos are templated with. + // BucketName is the object-storage bucket (BUCKET_NAME) passed to install + // scripts such as hack/install-microvm-deps.sh. The kind profile also + // derives SnapshotLocation from it. BucketName string // SnapshotLocation is ate-api-server's --snapshot-location // (SNAPSHOT_LOCATION): the cluster-wide object-storage URI actor snapshots // are stored under. Required unless the PerTemplateSnapshotLocation // preview gate is enabled, which also lets a - // template's snapshotConfig.storageLocation override it. + // template's snapshotConfig.storageLocation override it. The demos and + // benchmarks, and so e2e runs, need it even with the gate, since they do + // not set storageLocation. SnapshotLocation string // TransferGSAName is the name of the Google service account snapshot diff --git a/cmd/ate-setup/internal/config/setting.go b/cmd/ate-setup/internal/config/setting.go index 1fdea0effb..0c16e1b897 100644 --- a/cmd/ate-setup/internal/config/setting.go +++ b/cmd/ate-setup/internal/config/setting.go @@ -147,7 +147,7 @@ var Registry = []Setting{ { Key: "storage.bucketName", Env: "BUCKET_NAME", Flag: "storage-bucket-name", Kind: KindString, - Usage: "Snapshot bucket the demos are templated with", + Usage: "Object-storage bucket passed to install scripts; the kind profile derives --snapshot-location from it", }, { Key: "storage.snapshotLocation", Env: "SNAPSHOT_LOCATION", Flag: "snapshot-location", Kind: KindString, diff --git a/cmd/ate-setup/internal/demos/claudemultiplex/claudemultiplex.go b/cmd/ate-setup/internal/demos/claudemultiplex/claudemultiplex.go index f8b7db51b3..b89587f23e 100644 --- a/cmd/ate-setup/internal/demos/claudemultiplex/claudemultiplex.go +++ b/cmd/ate-setup/internal/demos/claudemultiplex/claudemultiplex.go @@ -53,7 +53,7 @@ func init() { d := &demo{} d.sub = demos.Substrate{ DemoName: "demo-claude-code-multiplex", - Short: "Several Claude Code agents multiplexed onto one WorkerPool (requires ANTHROPIC_API_KEY, BUCKET_NAME, KO_DOCKER_REPO)", + Short: "Several Claude Code agents multiplexed onto one WorkerPool (requires ANTHROPIC_API_KEY, KO_DOCKER_REPO)", WorkerPoolManifest: poolManifest, Deployments: []steps.TemplateRef{{Atespace: atespace, Name: "claude-workerpool"}}, Templates: agentTemplates(), @@ -85,9 +85,6 @@ func (d *demo) Deploy(ctx context.Context, e *steps.Env) error { if e.Cfg.AnthropicAPIKey == "" { return fmt.Errorf("ANTHROPIC_API_KEY must be set") } - if e.Cfg.BucketName == "" { - return fmt.Errorf("BUCKET_NAME must be set") - } if e.Cfg.KODockerRepo == "" { return fmt.Errorf("KO_DOCKER_REPO must be set (see hack/ate-dev-env.sh.example)") } diff --git a/cmd/ate-setup/internal/demos/demotest/demotest.go b/cmd/ate-setup/internal/demos/demotest/demotest.go index 5e845ba967..aeef03a372 100644 --- a/cmd/ate-setup/internal/demos/demotest/demotest.go +++ b/cmd/ate-setup/internal/demos/demotest/demotest.go @@ -48,7 +48,7 @@ func Env(t *testing.T) *steps.Env { if err != nil { t.Fatalf("Resolve: %v", err) } - cfg := &config.Config{Root: root, BucketName: "ate-snapshots"} + cfg := &config.Config{Root: root} cfg.SetResolved(r) return &steps.Env{Cfg: cfg} } @@ -85,6 +85,10 @@ func AssertRenderedActorTemplate(t *testing.T, manifest []byte, want resources.A if got := resources.ActorTemplateRefFromActorTemplate(template); got != want { t.Errorf("manifest names template %s, want %s", got, want) } + // The API rejects storageLocation without its preview gate. + if got := template.GetSnapshotConfig().GetStorageLocation(); got != "" { + t.Errorf("manifest sets storageLocation %q, want none", got) + } } // assertNoPlaceholders checks that no non-comment line still contains a diff --git a/cmd/ate-setup/internal/demos/render.go b/cmd/ate-setup/internal/demos/render.go index a755057dfe..2d12137879 100644 --- a/cmd/ate-setup/internal/demos/render.go +++ b/cmd/ate-setup/internal/demos/render.go @@ -19,10 +19,6 @@ import ( "go.ate.dev/substrate/cmd/ate-setup/internal/steps" ) -// bucketNamePlaceholder is substituted into every demo template with the -// snapshot bucket for this environment. -const bucketNamePlaceholder = "BUCKET_NAME" - // ExternalVolumePlaceholders are the optional external-volume hooks in the // counter templates. Every path except // `deploy demo counter --with-external-volume` drops them, which removes the @@ -33,13 +29,7 @@ var ExternalVolumePlaceholders = []string{ "EXTERNAL_VOLUMES", } -// Render expands a demo template with the configured bucket name. -func Render(e *steps.Env, relPath string, extraValues map[string]string, drop []string) ([]byte, error) { - values := map[string]string{ - bucketNamePlaceholder: e.Cfg.BucketName, - } - for k, v := range extraValues { - values[k] = v - } +// Render expands a demo template with values. +func Render(e *steps.Env, relPath string, values map[string]string, drop []string) ([]byte, error) { return render.Template(e.Cfg.Path(relPath), values, drop) } diff --git a/cmd/ate-setup/internal/demos/substrate.go b/cmd/ate-setup/internal/demos/substrate.go index e5812d847b..6278efa2b2 100644 --- a/cmd/ate-setup/internal/demos/substrate.go +++ b/cmd/ate-setup/internal/demos/substrate.go @@ -68,10 +68,10 @@ type Substrate struct { // is a cloud-hypervisor cold boot plus a checkpoint, on nested KVM in CI. GoldenTimeout time.Duration - // RenderValues optionally supplies extra placeholder values at deploy - // time, for demos whose manifests need more than ${BUCKET_NAME} (e.g. a - // freshly built image digest). Delete renders only the pool manifest, so - // deploy-only values may live solely here. + // RenderValues optionally supplies placeholder values at deploy time, for + // demos whose manifests need them (e.g. a freshly built image digest). + // Delete renders only the pool manifest, so deploy-only values may live + // solely here. RenderValues func(ctx context.Context, e *steps.Env) (map[string]string, error) } diff --git a/cmd/ate-setup/internal/steps/apienv.go b/cmd/ate-setup/internal/steps/apienv.go index 1fc1e4e3d0..755b346af0 100644 --- a/cmd/ate-setup/internal/steps/apienv.go +++ b/cmd/ate-setup/internal/steps/apienv.go @@ -187,7 +187,9 @@ func sharedBootstrapOwnerWarning(bootstrapOwners, namespace string) string { // snapshots. It is used whether or not the PerTemplateSnapshotLocation preview // gate is enabled; with the gate, an actor template's // snapshotConfig.storageLocation overrides it. It may be empty only when the -// gate is enabled, in which case every template must set its own. +// gate is enabled. CreateActorTemplate then fails with FailedPrecondition for +// a template without storageLocation, which includes every demo and +// benchmark. func clusterSnapshotLocation(location string, gates []string) (string, error) { if location != "" { if err := resources.ValidateSnapshotLocation(location); err != nil { diff --git a/cmd/ate-setup/internal/steps/substrate_test.go b/cmd/ate-setup/internal/steps/substrate_test.go index d29c0ec174..0f748c8daf 100644 --- a/cmd/ate-setup/internal/steps/substrate_test.go +++ b/cmd/ate-setup/internal/steps/substrate_test.go @@ -40,8 +40,7 @@ func TestActorTemplateFromManifestDemos(t *testing.T) { t.Fatalf("RepoRoot: %v", err) } - // The values benchmarking/workloads/deploy.sh substitutes, beyond the - // ${BUCKET_NAME} every installer provides. + // The values benchmarking/workloads/deploy.sh substitutes. benchmarkValues := map[string]string{ "${ACTOR_CPU_MILLICORES}": "1000", "${ACTOR_MEMORY_BYTES}": "268435456", @@ -113,7 +112,7 @@ func TestActorTemplateFromManifestDemos(t *testing.T) { if err != nil { t.Fatalf("ReadFile: %v", err) } - manifest := strings.ReplaceAll(string(data), "${BUCKET_NAME}", "ate-snapshots") + manifest := string(data) for placeholder, value := range tc.values { manifest = strings.ReplaceAll(manifest, placeholder, value) } @@ -127,6 +126,10 @@ func TestActorTemplateFromManifestDemos(t *testing.T) { if got := resources.ActorTemplateRefFromActorTemplate(template); got != tc.want { t.Errorf("manifest names template %s, want %s", got, tc.want) } + // The API rejects storageLocation without its preview gate. + if got := template.GetSnapshotConfig().GetStorageLocation(); got != "" { + t.Errorf("manifest sets storageLocation %q, want none", got) + } }) } } diff --git a/cmd/ateapi/internal/apivalidation/actor_template.go b/cmd/ateapi/internal/apivalidation/actor_template.go index cad1b5aa34..492af02aae 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template.go +++ b/cmd/ateapi/internal/apivalidation/actor_template.go @@ -16,10 +16,8 @@ package apivalidation import ( "context" - "fmt" "go.ate.dev/substrate/api/ateapi/v1" - "go.ate.dev/substrate/internal/preview" "go.ate.dev/substrate/internal/resources" "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/util/sets" @@ -166,19 +164,9 @@ func ValidateCustom_Container_Image(_ context.Context, _ operation.Operation, fl } // ValidateCustom_SnapshotConfig_StorageLocation ensures an -// ActorTemplate's snapshotConfig.location is a well-formed +// ActorTemplate's snapshotConfig.storageLocation is a well-formed // URI with a bucket, so a bad location fails fast. -// -// The field is ignored while the PerTemplateSnapshotLocation gate is off, so a -// declarative ifDisabled=forbidden would reject the templates that set it -// on servers without the gate. Instead, with the gate on, a request that -// did not opt in to preview features is forbidden from setting it. -func ValidateCustom_SnapshotConfig_StorageLocation(ctx context.Context, _ operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList { - if preview.IsEnabled(preview.GatePerTemplateSnapshotLocation) && preview.IsOptedOut(ctx) { - return field.ErrorList{field.Forbidden(fldPath, fmt.Sprintf( - "requires a client that opts in to preview features (kubectl ate --preview, or the %s header) while the %s preview gate is enabled", - preview.Header, preview.GatePerTemplateSnapshotLocation))} - } +func ValidateCustom_SnapshotConfig_StorageLocation(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList { if err := resources.ValidateSnapshotLocation(*value); err != nil { return field.ErrorList{field.Invalid(fldPath, *value, err.Error())} } diff --git a/cmd/ateapi/internal/apivalidation/actor_template_test.go b/cmd/ateapi/internal/apivalidation/actor_template_test.go index a2d8de5e56..4c111dc51b 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template_test.go +++ b/cmd/ateapi/internal/apivalidation/actor_template_test.go @@ -167,9 +167,9 @@ func TestValidateCreateActorTemplateRequest(t *testing.T) { })}, nil, }, { - "snapshot_config.storage_location is not needed when the preview gate is off", + "valid storage_location", &ateapi.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapi.ActorTemplate) { - tmpl.SnapshotConfig.StorageLocation = "" + tmpl.SnapshotConfig.StorageLocation = "gs://my-bucket/snapshots" })}, nil, }, { @@ -230,6 +230,10 @@ func TestValidateCreateActorTemplateRequest(t *testing.T) { want = field.ErrorList{field.Forbidden(field.NewPath("actor_template", "golden_egress_policy"), "")} wantWithPreview = map[string]field.ErrorList{"GoldenEgressPolicy": tt.want, "ALL": tt.want} } + if tt.req.GetActorTemplate().GetSnapshotConfig().GetStorageLocation() != "" { + want = field.ErrorList{field.Forbidden(field.NewPath("actor_template", "snapshot_config", "storage_location"), "")} + wantWithPreview = map[string]field.ErrorList{"PerTemplateSnapshotLocation": tt.want, "ALL": tt.want} + } assertValidate(t, func() field.ErrorList { return ValidateCreateActorTemplateRequest(context.Background(), tt.req) }, want, wantWithPreview) @@ -241,6 +245,7 @@ func TestValidateCreateActorTemplateRequest_PreviewRestricted(t *testing.T) { setPreviewForTest(t, "ALL") req := &ateapi.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapi.ActorTemplate) { tmpl.Volumes = []*ateapi.Volume{{Name: "data", External: &ateapi.ExternalVolumeSource{Template: &ateapi.ExternalVolumeTemplate{CapacityBytes: 10 << 30, StorageClassName: "fast-ssd"}}}} + tmpl.SnapshotConfig.StorageLocation = "gs://my-bucket/snapshots" })} t.Run("allowed", func(t *testing.T) { @@ -256,9 +261,9 @@ func TestValidateCreateActorTemplateRequest_PreviewRestricted(t *testing.T) { }) } -func TestValidateCreateActorTemplateRequest_PerTemplateSnapshotLocationOptIn(t *testing.T) { +func TestValidateCreateActorTemplateRequest_PerTemplateSnapshotLocation(t *testing.T) { storageLocation := field.NewPath("actor_template", "snapshot_config", "storage_location") - withoutLocation := func(tmpl *ateapi.ActorTemplate) { tmpl.SnapshotConfig.StorageLocation = "" } + withLocation := func(tmpl *ateapi.ActorTemplate) { tmpl.SnapshotConfig.StorageLocation = "gs://my-bucket/snapshots" } tests := []struct { name string gates string @@ -266,29 +271,34 @@ func TestValidateCreateActorTemplateRequest_PerTemplateSnapshotLocationOptIn(t * mutate func(*ateapi.ActorTemplate) want field.ErrorList }{{ + name: "gate off, opted in, location set", + mutate: withLocation, + want: field.ErrorList{field.Forbidden(storageLocation, "")}, + }, { name: "gate off, opted out, location set", optOut: true, + mutate: withLocation, + want: field.ErrorList{field.Forbidden(storageLocation, "")}, }, { name: "gate off, opted out, no location", optOut: true, - mutate: withoutLocation, }, { name: "gate on, opted out, location set", gates: "PerTemplateSnapshotLocation", optOut: true, + mutate: withLocation, want: field.ErrorList{field.Forbidden(storageLocation, "")}, }, { name: "gate on, opted out, no location", gates: "PerTemplateSnapshotLocation", optOut: true, - mutate: withoutLocation, }, { - name: "gate on, opted in, location set", - gates: "PerTemplateSnapshotLocation", - }, { - name: "gate on, opted in, no location", + name: "gate on, opted in, location set", gates: "PerTemplateSnapshotLocation", - mutate: withoutLocation, + mutate: withLocation, + }, { + name: "gate on, opted in, no location", + gates: "PerTemplateSnapshotLocation", }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -471,13 +481,15 @@ func TestValidateActorTemplate(t *testing.T) { mutate: func(tmpl *ateapi.ActorTemplate) { tmpl.SnapshotConfig.StorageLocation = "gs://" + strings.Repeat("x", 1020) }, - want: field.ErrorList{field.TooLong(field.NewPath("snapshot_config", "storage_location"), nil, 1024).WithOrigin("maxLength")}, + want: field.ErrorList{field.Forbidden(field.NewPath("snapshot_config", "storage_location"), "")}, + wantWithPreview: map[string]field.ErrorList{"PerTemplateSnapshotLocation": {field.TooLong(field.NewPath("snapshot_config", "storage_location"), nil, 1024).WithOrigin("maxLength")}}, }, { name: "missing storage_location", mutate: func(tmpl *ateapi.ActorTemplate) { tmpl.SnapshotConfig.StorageLocation = "" }, // Optional with or without the gate: an empty field falls back to // --snapshot-location. - want: nil, + want: nil, + wantWithPreview: map[string]field.ErrorList{"PerTemplateSnapshotLocation": nil}, }, { name: "unspecified snapshot scope", mutate: func(tmpl *ateapi.ActorTemplate) { @@ -962,7 +974,6 @@ func validActorTemplate(mutations ...func(*ateapi.ActorTemplate)) *ateapi.ActorT Metadata: &ateapi.ResourceMetadata{Atespace: "ns1", Name: "tmpl-a"}, Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: "gs://my-bucket/snapshots", PreferredFidelity: ateapi.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, }, Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, diff --git a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go index ab12828f3f..bfe1672f01 100644 --- a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go +++ b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go @@ -8285,6 +8285,13 @@ func Validate_SnapshotConfig( } // call field-attached validations earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "PerTemplateSnapshotLocation", false, validate.ForbiddenValue).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "PerTemplateSnapshotLocation", false, validate.OptionalValue).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { earlyReturn = true } diff --git a/cmd/ateapi/internal/controlapi/actor_template_test.go b/cmd/ateapi/internal/controlapi/actor_template_test.go index 5ff334cbd9..d346b43146 100644 --- a/cmd/ateapi/internal/controlapi/actor_template_test.go +++ b/cmd/ateapi/internal/controlapi/actor_template_test.go @@ -42,7 +42,6 @@ func validActorTemplate(mutations ...func(*ateapi.ActorTemplate)) *ateapi.ActorT Metadata: &ateapi.ResourceMetadata{Atespace: "ns1", Name: "tmpl-a"}, Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: "gs://my-bucket/snapshots", PreferredFidelity: ateapi.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, }, Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, @@ -57,7 +56,9 @@ func validActorTemplate(mutations ...func(*ateapi.ActorTemplate)) *ateapi.ActorT // while the atespace is missing, and succeeds once the atespace exists. func TestCreateActorTemplate(t *testing.T) { persistence := newTestPersistence(t) - s := &RPCService{impl: newServiceImpl(persistence, nil)} + impl := newServiceImpl(persistence, nil) + impl.clusterSnapshotLocation = "gs://cluster-bucket/" + s := &RPCService{impl: impl} ctx := context.Background() req := func(atespace, name string) *ateapi.CreateActorTemplateRequest { return &ateapi.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapi.ActorTemplate) { @@ -83,7 +84,7 @@ func TestCreateActorTemplate(t *testing.T) { // TestCreateActorTemplate_RequiresSnapshotLocation pins that create rejects, // with FailedPrecondition, a template whose actors would have no snapshot -// location: neither an honored storageLocation nor --snapshot-location. +// location: neither storageLocation nor --snapshot-location. func TestCreateActorTemplate_RequiresSnapshotLocation(t *testing.T) { tests := []struct { name string @@ -95,11 +96,9 @@ func TestCreateActorTemplate_RequiresSnapshotLocation(t *testing.T) { }{{ name: "gate off, cluster location set", clusterSnapshotLocation: "gs://cluster-bucket/", - location: "gs://my-bucket/snapshots", wantCode: codes.OK, }, { name: "gate off, no cluster location", - location: "gs://my-bucket/snapshots", wantCode: codes.FailedPrecondition, }, { name: "gate on, location set, no cluster location", @@ -153,7 +152,9 @@ func TestCreateActorTemplate_RequiresSnapshotLocation(t *testing.T) { // the only guard. func TestCreateActorTemplateIgnoresServerOwnedFields(t *testing.T) { persistence := newTestPersistence(t) - s := &RPCService{impl: newServiceImpl(persistence, nil)} + impl := newServiceImpl(persistence, nil) + impl.clusterSnapshotLocation = "gs://cluster-bucket/" + s := &RPCService{impl: impl} ctx := context.Background() if _, err := persistence.CreateAtespace(ctx, &ateapi.Atespace{Metadata: &ateapi.ResourceMetadata{Name: "ns1"}}); err != nil { diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go index e589656a95..c8e6edaff2 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go @@ -42,10 +42,9 @@ func TestActorTemplateCRUD(t *testing.T) { created, err := tc.client.CreateActorTemplate(ctx, &ateapi.CreateActorTemplateRequest{ ActorTemplate: &ateapi.ActorTemplate{ - Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl-a"}, - Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, - SnapshotConfig: &ateapi.SnapshotConfig{StorageLocation: "gs://my-bucket/snapshots"}, - Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 1<<30)}, + Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl-a"}, + Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, + Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 1<<30)}, // Server-owned status on the request is ignored. Status: &ateapi.ActorTemplateStatus{ GoldenSnapshotStatus: &ateapi.GoldenSnapshotStatus{ @@ -61,7 +60,6 @@ func TestActorTemplateCRUD(t *testing.T) { Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl-a", Version: 1}, Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: "gs://my-bucket/snapshots", PreferredFidelity: ateapi.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, GoldenSnapshotConfig: &ateapi.GoldenSnapshotConfig{Mode: ateapi.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, }, @@ -74,10 +72,9 @@ func TestActorTemplateCRUD(t *testing.T) { _, err = tc.client.CreateActorTemplate(ctx, &ateapi.CreateActorTemplateRequest{ ActorTemplate: &ateapi.ActorTemplate{ - Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl-a"}, - Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, - SnapshotConfig: &ateapi.SnapshotConfig{StorageLocation: "gs://my-bucket/snapshots"}, - Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, + Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl-a"}, + Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, + Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, }, }) assertGrpcError(t, err, codes.AlreadyExists, "ActorTemplate "+testAtespace+"/tmpl-a already exists") @@ -236,7 +233,6 @@ func TestActorLifecycleWithoutGoldenSnapshot(t *testing.T) { tmpl, err := tc.client.CreateActorTemplate(ctx, &ateapi.CreateActorTemplateRequest{ActorTemplate: &ateapi.ActorTemplate{ Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "no-golden"}, SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: testStorageLocation, GoldenSnapshotConfig: &ateapi.GoldenSnapshotConfig{Mode: ateapi.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED}, }, Containers: []*ateapi.Container{{Name: "main", Image: "main@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Command: []string{"/main"}}}, @@ -413,8 +409,7 @@ func TestCreateActorTemplate_ExternalVolumesPreview(t *testing.T) { Name: "data", External: &ateapi.ExternalVolumeSource{Template: &ateapi.ExternalVolumeTemplate{StorageClassName: "standard", CapacityBytes: 1 << 30}}, }}, - Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 1<<30)}, - SnapshotConfig: &ateapi.SnapshotConfig{StorageLocation: "gs://my-bucket/snapshots"}, + Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 1<<30)}, }, }) if status.Code(err) != tt.wantCode { @@ -426,9 +421,8 @@ func TestCreateActorTemplate_ExternalVolumesPreview(t *testing.T) { // TestCreateActorTemplate_PerTemplateSnapshotLocationPreview pins how // snapshotConfig.storageLocation combines the PerTemplateSnapshotLocation gate -// with the client's preview opt-in. With the gate off the field is ignored, -// so any client may set it. With the gate on, only a client that opts in may -// set it; a template that leaves it empty uses the server's +// with the client's preview opt-in. Only a client that opts in may set it, and +// only with the gate on. A template that leaves it empty uses the server's // --snapshot-location. func TestCreateActorTemplate_PerTemplateSnapshotLocationPreview(t *testing.T) { ns := namespaceForTest("ns-template-storage-location-preview") @@ -442,8 +436,9 @@ func TestCreateActorTemplate_PerTemplateSnapshotLocationPreview(t *testing.T) { location string wantCode codes.Code }{ - {name: "gate off, no opt-in, location set", optIn: false, location: "gs://my-bucket/snapshots", wantCode: codes.OK}, - {name: "gate off, opt-in, location set", optIn: true, location: "gs://my-bucket/snapshots", wantCode: codes.OK}, + {name: "gate off, no opt-in, location set", optIn: false, location: "gs://my-bucket/snapshots", wantCode: codes.InvalidArgument}, + {name: "gate off, opt-in, location set", optIn: true, location: "gs://my-bucket/snapshots", wantCode: codes.InvalidArgument}, + {name: "gate off, opt-in, no location", optIn: true, wantCode: codes.OK}, {name: "gate on, no opt-in, location set", gates: []string{"PerTemplateSnapshotLocation"}, optIn: false, location: "gs://my-bucket/snapshots", wantCode: codes.InvalidArgument}, {name: "gate on, no opt-in, no location", gates: []string{"PerTemplateSnapshotLocation"}, optIn: false, wantCode: codes.OK}, {name: "gate on, opt-in, location set", gates: []string{"PerTemplateSnapshotLocation"}, optIn: true, location: "gs://my-bucket/snapshots", wantCode: codes.OK}, diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go index bd49b56f88..54e7cfd48f 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go @@ -277,10 +277,9 @@ func TestCreateActor_SubstrateTemplateRef(t *testing.T) { ctx := context.Background() if _, err := tc.client.CreateActorTemplate(ctx, &ateapi.CreateActorTemplateRequest{ ActorTemplate: &ateapi.ActorTemplate{ - Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "sub-tmpl"}, - Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, - SnapshotConfig: &ateapi.SnapshotConfig{StorageLocation: "gs://my-bucket/snapshots"}, - Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, + Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "sub-tmpl"}, + Containers: []*ateapi.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, + Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, }, }); err != nil { t.Fatalf("CreateActorTemplate failed: %v", err) @@ -401,8 +400,7 @@ func TestCreateActor_RejectsSnapshotWithExternalVolumes(t *testing.T) { defer tc.cleanup() template, err := tc.client.CreateActorTemplate(context.Background(), &ateapi.CreateActorTemplateRequest{ ActorTemplate: &ateapi.ActorTemplate{ - Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl1"}, - SnapshotConfig: &ateapi.SnapshotConfig{StorageLocation: "gs://snapshots"}, + Metadata: &ateapi.ResourceMetadata{Atespace: testAtespace, Name: "tmpl1"}, Containers: []*ateapi.Container{{ Name: "main", Image: "main@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", VolumeMounts: []*ateapi.VolumeMount{{Name: "data", MountPath: "/data"}}, }}, @@ -894,9 +892,8 @@ func TestUpdateActor_RepointTemplate(t *testing.T) { Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", VolumeMounts: []*ateapi.VolumeMount{{Name: "data", MountPath: tmpl.mountPath}}, }}, - Volumes: tmpl.volumes, - SnapshotConfig: &ateapi.SnapshotConfig{StorageLocation: "gs://my-bucket/snapshots"}, - Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, + Volumes: tmpl.volumes, + Resources: &ateapi.ResourceRequirements{Limits: resources.CPUMemory(1000, 512<<20)}, }, }); err != nil { t.Fatalf("CreateActorTemplate %s failed: %v", name, err) @@ -3421,7 +3418,6 @@ func createDataFidelityTemplate(t *testing.T, tc *testContext, ns string) *ateap Name: "tmpl1", }, SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: testStorageLocation, PreferredFidelity: ateapi.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, }, Containers: []*ateapi.Container{{ diff --git a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go index d3e0937b97..0bc8ae3206 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go @@ -63,8 +63,8 @@ import ( const ( testAtespace = "test-atespace" testActorID = "id1" - // testStorageLocation is the snapshot_config.storage_location the test - // templates hand out. No object store is wired up behind it. + // testStorageLocation is the --snapshot-location the test server runs + // with. No object store is wired up behind it. testStorageLocation = "gs://fake-fake-fake" testActorJWTIssuer = "https://idp.example.test" @@ -472,9 +472,6 @@ func createTemplateWithContainersAndVolumes(t *testing.T, tc *testContext, ns st Atespace: testAtespace, Name: "tmpl1", }, - SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: testStorageLocation, - }, Containers: containers, Volumes: volumes, WorkerSelector: &ateapi.Selector{ @@ -623,9 +620,6 @@ func createTemplateWithSelector(t *testing.T, tc *testContext, name string, sele Atespace: testAtespace, Name: name, }, - SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: testStorageLocation, - }, Containers: []*ateapi.Container{ {Name: "main", Image: "main@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Command: []string{"/main"}}, }, diff --git a/cmd/kubectl-ate/internal/cmd/actortemplate_test.go b/cmd/kubectl-ate/internal/cmd/actortemplate_test.go index 6ca43a6d9f..b7eeaeca06 100644 --- a/cmd/kubectl-ate/internal/cmd/actortemplate_test.go +++ b/cmd/kubectl-ate/internal/cmd/actortemplate_test.go @@ -158,17 +158,16 @@ func TestActorTemplateFromManifest_DemoManifests(t *testing.T) { if err != nil { t.Fatalf("reading demo manifest: %v", err) } - // ate-setup substitutes the bucket and drops the unused - // optional placeholder lines before applying. - rendered := strings.ReplaceAll(string(data), "${BUCKET_NAME}", "ate-snapshots") + // ate-setup drops the unused optional placeholder lines before + // applying. var kept []string - for _, line := range strings.Split(rendered, "\n") { + for _, line := range strings.Split(string(data), "\n") { if strings.Contains(line, "${") { continue } kept = append(kept, line) } - rendered = strings.Join(kept, "\n") + rendered := strings.Join(kept, "\n") got, err := actorTemplateFromManifest([]byte(rendered)) if err != nil { @@ -178,8 +177,12 @@ func TestActorTemplateFromManifest_DemoManifests(t *testing.T) { t.Errorf("metadata = %s/%s, want %s/%s", got.GetMetadata().GetAtespace(), got.GetMetadata().GetName(), test.atespace, test.name) } - if len(got.GetContainers()) == 0 || got.GetSnapshotConfig().GetStorageLocation() == "" { - t.Errorf("missing required fields: %v", got) + if len(got.GetContainers()) == 0 { + t.Errorf("manifest has no containers: %v", got) + } + // The API rejects storageLocation without its preview gate. + if location := got.GetSnapshotConfig().GetStorageLocation(); location != "" { + t.Errorf("manifest sets storageLocation %q, want none", location) } }) } diff --git a/demos/claude-code-multiplex/README.md b/demos/claude-code-multiplex/README.md index 6c9598b5ad..cc35f3f671 100644 --- a/demos/claude-code-multiplex/README.md +++ b/demos/claude-code-multiplex/README.md @@ -35,7 +35,8 @@ This guide assumes you know Kubernetes and the general shape of agent runtimes ( kubectl port-forward svc/api 8080:443 -n ate-system ``` - An **Anthropic API key** (the agents call Claude). -- The snapshot bucket and image registry (`BUCKET_NAME`, `KO_DOCKER_REPO`) that your Substrate install uses. The deploy step builds and pushes the workload image to that registry with a sha256-pinned reference. +- A snapshot location set at install (`SNAPSHOT_LOCATION`). The agents' snapshots are stored there. +- The image registry (`KO_DOCKER_REPO`) that your Substrate install uses. The deploy step builds and pushes the workload image to that registry with a sha256-pinned reference. - `docker buildx` (the deploy function builds the workload image — a Dockerfile-based Python + Claude Code wrapper, not a Go binary, so `ko` doesn't apply for the workload itself). ## Components @@ -59,7 +60,7 @@ ANTHROPIC_API_KEY=sk-ant-... \ go run ./cmd/ate-setup deploy demo claude-code-multiplex ``` -This creates the `claude-multiplex-demo` namespace and its 2-pod `WorkerPool`, then the `claude-multiplex-demo` atespace and three actor templates in it named `agent-luna`, `agent-mars`, `agent-orion`, and waits for each template's golden snapshot. Under the hood, the deploy function builds the workload image with `docker buildx`, pushes it to `${KO_DOCKER_REPO}/claude-multiplex-demo-workload`, resolves the pushed sha256 digest, and substitutes the digest-pinned reference plus `ANTHROPIC_API_KEY` and `BUCKET_NAME` into the agent templates at apply time. +This creates the `claude-multiplex-demo` namespace and its 2-pod `WorkerPool`, then the `claude-multiplex-demo` atespace and three actor templates in it named `agent-luna`, `agent-mars`, `agent-orion`, and waits for each template's golden snapshot. Under the hood, the deploy function builds the workload image with `docker buildx`, pushes it to `${KO_DOCKER_REPO}/claude-multiplex-demo-workload`, resolves the pushed sha256 digest, and substitutes the digest-pinned reference and `ANTHROPIC_API_KEY` into the agent templates at apply time. ### 2. Start the dashboard diff --git a/demos/claude-code-multiplex/agent-luna-template.yaml.tmpl b/demos/claude-code-multiplex/agent-luna-template.yaml.tmpl index 72bde3b0dc..87a32e15fe 100644 --- a/demos/claude-code-multiplex/agent-luna-template.yaml.tmpl +++ b/demos/claude-code-multiplex/agent-luna-template.yaml.tmpl @@ -48,4 +48,3 @@ resources: memoryBytes: 2147483648 # 2Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/claude-multiplex-demo/ diff --git a/demos/claude-code-multiplex/agent-mars-template.yaml.tmpl b/demos/claude-code-multiplex/agent-mars-template.yaml.tmpl index 96b0ec95de..37c2cb07f6 100644 --- a/demos/claude-code-multiplex/agent-mars-template.yaml.tmpl +++ b/demos/claude-code-multiplex/agent-mars-template.yaml.tmpl @@ -40,4 +40,3 @@ resources: memoryBytes: 2147483648 # 2Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/claude-multiplex-demo/ diff --git a/demos/claude-code-multiplex/agent-orion-template.yaml.tmpl b/demos/claude-code-multiplex/agent-orion-template.yaml.tmpl index ce3240cda2..d1fc8adfea 100644 --- a/demos/claude-code-multiplex/agent-orion-template.yaml.tmpl +++ b/demos/claude-code-multiplex/agent-orion-template.yaml.tmpl @@ -40,4 +40,3 @@ resources: memoryBytes: 2147483648 # 2Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/claude-multiplex-demo/ diff --git a/demos/counter/README.md b/demos/counter/README.md index 604b75dbe5..3f99ba6316 100644 --- a/demos/counter/README.md +++ b/demos/counter/README.md @@ -10,6 +10,7 @@ The demo uses two kinds of resources: the `WorkerPool` is a Kubernetes CRD, whil - A k8s cluster with Agent Substrate installed as in the [Quickstart](../../README.md#quickstart-development) (with `ATE_INSTALL_KIND=true` exported in your shell) or [GKE Quickstart](../../README.md#gke-quickstart-development). +- A snapshot location set at install (`SNAPSHOT_LOCATION`). The demo stores its snapshots there. ## How to Run on Agent Substrate @@ -21,9 +22,8 @@ it, skip to [How to Use](#how-to-use). > [!NOTE] > Do not manually edit `demos/counter/counter.yaml.tmpl` or -> `demos/counter/counter-template.yaml.tmpl`. `ate-setup` -> automatically injects your `${BUCKET_NAME}` environment variable during -> deployment. +> `demos/counter/counter-template.yaml.tmpl`. `ate-setup` fills in their +> `${...}` placeholders during deployment.
Installation on MicroVM diff --git a/demos/counter/counter-microvm-csi-test-template.yaml b/demos/counter/counter-microvm-csi-test-template.yaml index 4b74997769..da6be1bfb3 100644 --- a/demos/counter/counter-microvm-csi-test-template.yaml +++ b/demos/counter/counter-microvm-csi-test-template.yaml @@ -40,7 +40,6 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_VOLUMES - storageLocation: gs://ate-snapshots/ate-demo-counter-microvm-csi/ volumes: - name: data external: diff --git a/demos/counter/counter-microvm-template.yaml.tmpl b/demos/counter/counter-microvm-template.yaml.tmpl index 9e883f6d82..208f96885a 100644 --- a/demos/counter/counter-microvm-template.yaml.tmpl +++ b/demos/counter/counter-microvm-template.yaml.tmpl @@ -62,7 +62,6 @@ snapshotConfig: # demo promises. ROOTFS is also available on this class: it would keep the # file counter and any rootfs writes but cold-boot the memory counter. preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-counter-microvm/ volumes: - name: data durableDir: {} diff --git a/demos/counter/counter-template.yaml.tmpl b/demos/counter/counter-template.yaml.tmpl index b92c17ee2a..52ec23b437 100644 --- a/demos/counter/counter-template.yaml.tmpl +++ b/demos/counter/counter-template.yaml.tmpl @@ -57,7 +57,6 @@ snapshotConfig: # memory cold-boots) is exercised by the e2e demo suite's own # parameterized templates, not by this one. preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-counter/ volumes: - name: data durableDir: {} diff --git a/demos/egress/egress-microvm-template.yaml.tmpl b/demos/egress/egress-microvm-template.yaml.tmpl index 6c21947f10..136c3bf2e9 100644 --- a/demos/egress/egress-microvm-template.yaml.tmpl +++ b/demos/egress/egress-microvm-template.yaml.tmpl @@ -69,4 +69,3 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-egress-microvm/ diff --git a/demos/egress/egress-template.yaml.tmpl b/demos/egress/egress-template.yaml.tmpl index 0815d4aeb4..b6987fc163 100644 --- a/demos/egress/egress-template.yaml.tmpl +++ b/demos/egress/egress-template.yaml.tmpl @@ -61,4 +61,3 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-egress/ diff --git a/demos/jupyter/README.md b/demos/jupyter/README.md index 2fdd7f5faf..99981cf81a 100644 --- a/demos/jupyter/README.md +++ b/demos/jupyter/README.md @@ -6,6 +6,7 @@ This demo runs a Jupyter Notebook on Agent Substrate. It demonstrates how standa - A k8s cluster with Agent Substrate installed as in the [Quickstart](../../README.md#quickstart-development) (with `ATE_INSTALL_KIND=true` exported in your shell) or [GKE Quickstart](../../README.md#gke-quickstart-development). +- A snapshot location set at install (`SNAPSHOT_LOCATION`). The demo stores its snapshots there. ## Deploy the Jupyter Notebook Demo diff --git a/demos/jupyter/jupyter-template.yaml.tmpl b/demos/jupyter/jupyter-template.yaml.tmpl index 239fa4b022..df8cd69396 100644 --- a/demos/jupyter/jupyter-template.yaml.tmpl +++ b/demos/jupyter/jupyter-template.yaml.tmpl @@ -47,4 +47,3 @@ resources: memoryBytes: 4294967296 # 4Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-jupyter/ diff --git a/demos/multi-template/README.md b/demos/multi-template/README.md index e92057b743..0fb0f14185 100644 --- a/demos/multi-template/README.md +++ b/demos/multi-template/README.md @@ -11,16 +11,12 @@ or namespace. - A k8s cluster with Agent Substrate installed as in the [Quickstart](../../README.md#quickstart-development) (with `ATE_INSTALL_KIND=true` exported in your shell) or [GKE Quickstart](../../README.md#gke-quickstart-development). +- A snapshot location set at install (`SNAPSHOT_LOCATION`). The demo stores its snapshots there. ## How to Run on Agent Substrate ### 1. Build and Deploy -> [!NOTE] -> Do not manually edit the `demos/multi-template/*.yaml.tmpl` manifests. -> `ate-setup` automatically injects your `${BUCKET_NAME}` environment -> variable during deployment. - ```bash go run ./cmd/ate-setup deploy demo multi-template ``` diff --git a/demos/multi-template/counter-template.yaml.tmpl b/demos/multi-template/counter-template.yaml.tmpl index e689b1f599..be2a8c6a07 100644 --- a/demos/multi-template/counter-template.yaml.tmpl +++ b/demos/multi-template/counter-template.yaml.tmpl @@ -35,4 +35,3 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-multi-template-counter/ diff --git a/demos/multi-template/fspersist-template.yaml.tmpl b/demos/multi-template/fspersist-template.yaml.tmpl index 1b573243f2..8fffb50648 100644 --- a/demos/multi-template/fspersist-template.yaml.tmpl +++ b/demos/multi-template/fspersist-template.yaml.tmpl @@ -35,4 +35,3 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-multi-template-fspersist/ diff --git a/demos/parking/README.md b/demos/parking/README.md index 74b5c26487..53bf696ffa 100644 --- a/demos/parking/README.md +++ b/demos/parking/README.md @@ -15,16 +15,12 @@ See [docs/request-parking.md](../../docs/request-parking.md) for the design. - A k8s cluster with Agent Substrate installed as in the [Quickstart](../../README.md#quickstart-development) (with `ATE_INSTALL_KIND=true` exported in your shell) or [GKE Quickstart](../../README.md#gke-quickstart-development). +- A snapshot location set at install (`SNAPSHOT_LOCATION`). The demo stores its snapshots there. ## How to Run on Agent Substrate ### 1. Build and Deploy -> [!NOTE] -> Do not manually edit the `demos/parking/*.yaml.tmpl` manifests. `ate-setup` -> automatically injects your `${BUCKET_NAME}` environment variable during -> deployment. - ```bash go run ./cmd/ate-setup deploy demo parking ``` diff --git a/demos/parking/parking-template.yaml.tmpl b/demos/parking/parking-template.yaml.tmpl index 4753f7a02a..f92a6df94d 100644 --- a/demos/parking/parking-template.yaml.tmpl +++ b/demos/parking/parking-template.yaml.tmpl @@ -34,4 +34,3 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-parking/ diff --git a/demos/sandbox/README.md b/demos/sandbox/README.md index b4d87af47c..3a5b289a67 100644 --- a/demos/sandbox/README.md +++ b/demos/sandbox/README.md @@ -16,15 +16,13 @@ It allows you to run arbitrary commands in an sandboxed, isolated container (run - A k8s cluster with Agent Substrate installed as in the [Quickstart](../../README.md#quickstart-development) (with `ATE_INSTALL_KIND=true` exported in your shell) or [GKE Quickstart](../../README.md#gke-quickstart-development). +- A snapshot location set at install (`SNAPSHOT_LOCATION`). The demo stores its snapshots there. - `kubectl-ate` CLI installed (can be installed via `go install ./cmd/kubectl-ate`). ## How to Run on Agent Substrate ### 1. Build and Deploy -> [!NOTE] -> Do not manually edit the `demos/sandbox/*.yaml.tmpl` manifests. `ate-setup` automatically injects your `${BUCKET_NAME}` environment variable during deployment. - Use `ate-setup` to build the image and deploy the demo to your cluster: ```bash diff --git a/demos/sandbox/manual-test-multi-template.yaml b/demos/sandbox/manual-test-multi-template.yaml index 5399ed7ead..2b0489e4a0 100644 --- a/demos/sandbox/manual-test-multi-template.yaml +++ b/demos/sandbox/manual-test-multi-template.yaml @@ -42,7 +42,6 @@ resources: memoryBytes: 536870912 # 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://ate-snapshots/ate-manual-test-multi/ volumes: - name: vol-hostpath external: diff --git a/demos/sandbox/sandbox-template.yaml.tmpl b/demos/sandbox/sandbox-template.yaml.tmpl index 358d32b294..57fb1aa382 100644 --- a/demos/sandbox/sandbox-template.yaml.tmpl +++ b/demos/sandbox/sandbox-template.yaml.tmpl @@ -36,4 +36,3 @@ resources: memoryBytes: 1073741824 # 1Gi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY - storageLocation: gs://${BUCKET_NAME}/ate-demo-sandbox/ diff --git a/docs/api-guide.md b/docs/api-guide.md index bd45a67223..7fba29d68a 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -101,7 +101,7 @@ The `ActorTemplate` defines the code, environment, and state-management policies | :--- | :--- | :--- | | `containers` | `[]Container` | **Required.** The workload definition — see [Container Fields](#container-fields) below. Each container may also declare an optional `wakeupProbe` HTTP probe — see [Container Wakeup Probe](#container-wakeup-probe-wakeupprobe). | | `workerSelector` | `*Selector` | Optional. Gates which `WorkerPool`s actors from this template may use, by matching against each pool's labels (`matchLabels`). If unset, all pools are eligible (subject to the actor's own `worker_selector`). The pool decides the sandbox class, so this is also how a template pins one: if your cluster has pools of more than one sandbox class and the workload needs a particular class, make sure the selector points only at pools of that class. An actor booting with no snapshot and no selector may land on any class, and keeps it from its first checkpoint on. | -| `snapshotConfig` | `SnapshotConfig` | Optional; omitted fields take their defaults. The base object-storage location snapshots are written under (a preview field, ignored without the `PerTemplateSnapshotLocation` gate), the snapshot fidelity (`preferredFidelity`) every Suspend captures, and automated golden snapshot creation (`goldenSnapshotConfig.mode`). See [Snapshot Storage Layout](#snapshot-storage-layout). | +| `snapshotConfig` | `SnapshotConfig` | Optional; omitted fields take their defaults. The base object-storage location snapshots are written under (a preview field that requires the `PerTemplateSnapshotLocation` gate), the snapshot fidelity (`preferredFidelity`) every Suspend captures, and automated golden snapshot creation (`goldenSnapshotConfig.mode`). See [Snapshot Storage Layout](#snapshot-storage-layout). | | `volumes` | `[]Volume` | Optional. Volumes the containers may mount, each a `durableDir`, an `external` CSI volume source (see [CSI Volumes Guide](csi-volumes.md)), or a `systemInfo` volume (see [SystemInfo Volumes](#systeminfo-volumes)). Every declared volume must be mounted by at least one container. A `durableDir` is created `0777`, as Kubernetes creates an emptyDir, so a container running as any user can write it. A snapshot restores it with the mode it had, so the container's own chmod survives suspend and resume. | | `resources` | `*ResourceRequirements` | Required. Declares each actor's compute size via `limits`, which must set both `cpuMillicores` and `memoryBytes` — see [Sandbox Right-Sizing](#sandbox-right-sizing-resources). Immutable, like the rest of the template. | | `goldenEgressPolicy` | `EgressPolicyTemplate` | **Preview.** Optional egress policy for the golden actor during initialization. Requires at least one rule, using the same rule defaults and validation as `EgressPolicy`. See [Golden Egress Policy](#golden-egress-policy-goldenegresspolicy). | @@ -395,8 +395,6 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 1073741824 # 1Gi -snapshotConfig: - storageLocation: gs://my-bucket/secret-agent ``` ### Snapshot Storage Layout @@ -409,13 +407,13 @@ snapshotConfig: ``` > [!NOTE] -> Snapshots are stored under ate-api-server's cluster-wide `--snapshot-location`. `snapshotConfig.storageLocation` is an optional preview field: with the `PerTemplateSnapshotLocation` preview gate (`--preview=PerTemplateSnapshotLocation`), a template that sets it stores its snapshots there instead. Without the gate, the field is ignored. `` above is whichever of the two applies. +> Snapshots are stored under ate-api-server's cluster-wide `--snapshot-location`. `snapshotConfig.storageLocation` is an optional preview field: with the `PerTemplateSnapshotLocation` preview gate (`--preview=PerTemplateSnapshotLocation`), a template that sets it stores its snapshots there instead. `` above is whichever of the two applies. > -> With the gate enabled, only a client that opts in to preview features (`kubectl ate --preview`, or the `Ate-Preview-Allowed: true` header) may set `storageLocation`. `CreateActorTemplate` fails with `FailedPrecondition` when the template leaves it empty and ate-api-server has no `--snapshot-location`. +> Only a client that opts in to preview features (`kubectl ate --preview`, or the `Ate-Preview-Allowed: true` header) may set `storageLocation`, and only with the gate enabled. Otherwise the request fails with `InvalidArgument`. A template stored before the API enforced this keeps the field, and ate-api-server ignores it while the gate is off. `CreateActorTemplate` fails with `FailedPrecondition` when the template leaves it empty and ate-api-server has no `--snapshot-location`. > > Changing the flag or toggling the gate moves `` for every template it affects. Snapshots already taken stay where they are and can still be resumed from. An actor's next suspend writes under the new location and releases its previous snapshot from the old one. Until then, the actor cannot change its `actor_template` (see `UpdateActor` below), and deleting it collects its prefix under the old location. If that release fails, the previous snapshot's objects stay under the old location, and deleting the actor later does not collect them. -The objects of a snapshot (its manifest, memory image, durable-data tar) are named below it. So for the template above, a snapshot of an actor in atespace `team-a` is stored at `gs://my-bucket/secret-agent/atespaces/team-a/actors/3f8b…/snapshots/f47ac10b-…`, and the template's golden snapshot under `gs://my-bucket/secret-agent/atespaces/team-a/tags/`. +The objects of a snapshot (its manifest, memory image, durable-data tar) are named below it. So with `--snapshot-location=gs://my-bucket/`, a snapshot of an actor in atespace `team-a` is stored at `gs://my-bucket/atespaces/team-a/actors/3f8b…/snapshots/f47ac10b-…`, and a golden snapshot under `gs://my-bucket/atespaces/team-a/tags/`. An actor takes a series of snapshots over its life, so it gets a prefix of its own and each snapshot sits below it. A tag holds exactly one, so the tag's @@ -522,7 +520,7 @@ Each entry of `versions` (`SandboxVersionConfig`): | :--- | :--- | :--- | | `name` | `string` | **Required.** Identifies the version within the config. A DNS label (lower-case alphanumerics and `-`, at most 63 characters). | | `state` | `string` | Optional. `Enabled` (default) or `Disabled`. The version named by `defaultVersion` cannot be `Disabled`. | -| `assets` | `map[arch]map[name]AssetFile` | Content-addressed files atelet fetches, keyed by architecture (`amd64`, `arm64`) then asset name. gVisor expects a `gvisor` asset (the release's `gvisor.tar.zstd`), which atelet auto-extracts. A microVM backend expects several. Each `AssetFile` is a `{ url, sha256 }` pair; `url` is at most 2048 bytes. A `ValidatingAdmissionPolicy` enforces each class's required assets on every version. atelet holds no storage credentials: it fetches every URL through the node's object-store plugin (`AssetProvider`), with the plugin's credentials. Which schemes work depends on the plugin; the in-tree one serves only its storage backend's scheme, `gs://` on GCS and `s3://` on S3, as `/`, and rejects other schemes without reading them. A `gs://` URL the plugin cannot serve (it has no such object, rejects the URL, does not implement `AssetProvider`, is unreachable, or fails with an error it does not recognize, such as GCS refusing its credentials for a public bucket) is then read anonymously, which serves public buckets such as `gs://gvisor`; any other refusal by the plugin is final. The plugin returns an object only if its content matches `sha256`, and refuses any URL under a snapshot location (an `ActorTemplate`'s `snapshotConfig.location` followed by `/atespaces/`), so assets must not be stored there. On GKE, setup-gcp lets the plugin read only the asset bucket, `-assets`, so keep assets there. atelet verifies the sha256 again before using an asset. Its logs and errors show an asset URL as `scheme://host/path`. | +| `assets` | `map[arch]map[name]AssetFile` | Content-addressed files atelet fetches, keyed by architecture (`amd64`, `arm64`) then asset name. gVisor expects a `gvisor` asset (the release's `gvisor.tar.zstd`), which atelet auto-extracts. A microVM backend expects several. Each `AssetFile` is a `{ url, sha256 }` pair; `url` is at most 2048 bytes. A `ValidatingAdmissionPolicy` enforces each class's required assets on every version. atelet holds no storage credentials: it fetches every URL through the node's object-store plugin (`AssetProvider`), with the plugin's credentials. Which schemes work depends on the plugin; the in-tree one serves only its storage backend's scheme, `gs://` on GCS and `s3://` on S3, as `/`, and rejects other schemes without reading them. A `gs://` URL the plugin cannot serve (it has no such object, rejects the URL, does not implement `AssetProvider`, is unreachable, or fails with an error it does not recognize, such as GCS refusing its credentials for a public bucket) is then read anonymously, which serves public buckets such as `gs://gvisor`; any other refusal by the plugin is final. The plugin returns an object only if its content matches `sha256`, and refuses any URL under a snapshot location (a snapshot location followed by `/atespaces/`), so assets must not be stored there. On GKE, setup-gcp lets the plugin read only the asset bucket, `-assets`, so keep assets there. atelet verifies the sha256 again before using an asset. Its logs and errors show an asset URL as `scheme://host/path`. | A cluster-wide gVisor `SandboxConfig` (`gvisor-default`, annotated as the class default) is installed with the platform, so gVisor `WorkerPool`s use it automatically without any extra setup. diff --git a/docs/csi-volumes.md b/docs/csi-volumes.md index d44fb43762..932afe8f84 100644 --- a/docs/csi-volumes.md +++ b/docs/csi-volumes.md @@ -192,8 +192,6 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 1073741824 # 1Gi -snapshotConfig: - storageLocation: gs://my-snapshots-bucket/stateful-agent volumes: - name: shared-storage external: diff --git a/docs/operator-install.md b/docs/operator-install.md index 225610d191..2cec3c3054 100644 --- a/docs/operator-install.md +++ b/docs/operator-install.md @@ -325,7 +325,9 @@ ate-api-server stores every actor and tag snapshot under a cluster-wide `--snapshot-location`, an object-storage URI such as `gs://BUCKET/PREFIX/`. Set it with `SNAPSHOT_LOCATION` (or ate-setup's `--snapshot-location`). It has no default, and ate-setup fails before deploying without it unless the -`PerTemplateSnapshotLocation` preview gate is enabled. If you deploy ate-api-server +`PerTemplateSnapshotLocation` preview gate is enabled. Even with the gate, the +demos and benchmarks need it, since they do not set +`snapshotConfig.storageLocation`. So do e2e runs, which deploy the demos. If you deploy ate-api-server some other way, pass the flag yourself; without the gate, the server refuses to start without it. @@ -363,7 +365,7 @@ With the `PerTemplateSnapshotLocation` preview gate, an ActorTemplate may set that leave it empty use `--snapshot-location`. Grant ate-api-server's identity and the snapshot transfer service account object admin and bucket viewer on any other bucket a template names, as setup-gcp does on the snapshot bucket. Without the gate, -ate-api-server ignores `snapshotConfig.storageLocation`. See +ate-api-server rejects templates that set `snapshotConfig.storageLocation`. See [Snapshot Storage Layout](api-guide.md#snapshot-storage-layout). ## Ingress to actors diff --git a/internal/e2e/fixture.go b/internal/e2e/fixture.go index 391b544122..5410bb7c0c 100644 --- a/internal/e2e/fixture.go +++ b/internal/e2e/fixture.go @@ -32,11 +32,10 @@ import ( // ActorTemplate fixture templates carry: the substrate counterpart of // fixtureSubstitutions, whose block fragments are CRD-shaped and now serve // only the pool manifests. The inline placeholders match the pool manifests' -// values so one (bucket, name) pair renders both halves of a fixture. +// values so one name renders both halves of a fixture. // trustBundles names the trust bundles projected into one trust-bundle.pem. -func substrateTemplateSubstitutions(bucket, name string, trustBundles []string) (inline, blocks map[string]string) { +func substrateTemplateSubstitutions(name string, trustBundles []string) (inline, blocks map[string]string) { inline = map[string]string{ - "${BUCKET_NAME}": bucket, "${FIXTURE_SUFFIX}": "-" + name, } blocks = map[string]string{ @@ -134,7 +133,7 @@ type SubstrateFixtureManifests struct { // // Returns the fixture's atespace (the same string that names the k8s // namespace holding the pool) and the created templates. -func DeploySubstrateFixture(t *testing.T, ctx context.Context, clients *Clients, manifests SubstrateFixtureManifests, bucket, name string, trustBundles []string, opts ...FixtureOption) (string, []*ateapi.ActorTemplate) { +func DeploySubstrateFixture(t *testing.T, ctx context.Context, clients *Clients, manifests SubstrateFixtureManifests, name string, trustBundles []string, opts ...FixtureOption) (string, []*ateapi.ActorTemplate) { t.Helper() var cfg fixtureConfig @@ -145,7 +144,7 @@ func DeploySubstrateFixture(t *testing.T, ctx context.Context, clients *Clients, // The pool manifest also carries the namespace. Its cleanup is registered // first so it runs last (t.Cleanup is LIFO), after the templates that // select the pool's workers are gone. - inline, blocks := fixtureSubstitutions(bucket, name) + inline, blocks := fixtureSubstitutions(name) poolManifest := renderManifest(t, manifests.Pool, inline, blocks) koApply(t, poolManifest) t.Cleanup(func() { @@ -156,7 +155,7 @@ func DeploySubstrateFixture(t *testing.T, ctx context.Context, clients *Clients, RunCmd(t, "kubectl", delArgs...) }) - tmplInline, tmplBlocks := substrateTemplateSubstitutions(bucket, name, trustBundles) + tmplInline, tmplBlocks := substrateTemplateSubstitutions(name, trustBundles) maps.Copy(tmplInline, cfg.templateValues) rendered := koResolve(t, renderManifest(t, manifests.Template, tmplInline, tmplBlocks)) templates := decodeSubstrateTemplates(t, rendered) @@ -166,6 +165,13 @@ func DeploySubstrateFixture(t *testing.T, ctx context.Context, clients *Clients, if got := tmpl.GetMetadata().GetAtespace(); got != atespace { t.Fatalf("fixture %s declares templates in different atespaces (%q and %q)", manifests.Template, atespace, got) } + location := SnapshotLocation(t, "ate-e2e-"+tmpl.GetMetadata().GetName()+tmplInline["${FIXTURE_SUFFIX}"]) + if location != "" { + if tmpl.SnapshotConfig == nil { + tmpl.SnapshotConfig = &ateapi.SnapshotConfig{} + } + tmpl.SnapshotConfig.StorageLocation = location + } } if _, err := clients.SubstrateAPI.CreateAtespace(ctx, &ateapi.CreateAtespaceRequest{Atespace: &ateapi.Atespace{Metadata: &ateapi.ResourceMetadata{Name: atespace}}}); err != nil && status.Code(err) != codes.AlreadyExists { diff --git a/internal/e2e/fixtures/capabilities/capabilities-templates.yaml.tmpl b/internal/e2e/fixtures/capabilities/capabilities-templates.yaml.tmpl index 549ec8cdc9..494189840b 100644 --- a/internal/e2e/fixtures/capabilities/capabilities-templates.yaml.tmpl +++ b/internal/e2e/fixtures/capabilities/capabilities-templates.yaml.tmpl @@ -40,8 +40,6 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-caps-default${FIXTURE_SUFFIX}/ --- @@ -70,8 +68,6 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-caps-exact${FIXTURE_SUFFIX}/ --- # No capabilities at all, and the probe still binds :80: both runtimes lift @@ -98,5 +94,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-caps-none${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/probe/probe-bootfetch-template.yaml.tmpl b/internal/e2e/fixtures/probe/probe-bootfetch-template.yaml.tmpl index 576976fe96..81cc2ca883 100644 --- a/internal/e2e/fixtures/probe/probe-bootfetch-template.yaml.tmpl +++ b/internal/e2e/fixtures/probe/probe-bootfetch-template.yaml.tmpl @@ -63,5 +63,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-probe-bootfetch${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/probe/probe-multiactor-template.yaml.tmpl b/internal/e2e/fixtures/probe/probe-multiactor-template.yaml.tmpl index 55138cdd5a..52d7ab1637 100644 --- a/internal/e2e/fixtures/probe/probe-multiactor-template.yaml.tmpl +++ b/internal/e2e/fixtures/probe/probe-multiactor-template.yaml.tmpl @@ -50,5 +50,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 268435456 # 256Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/probe/probe-parking-template.yaml.tmpl b/internal/e2e/fixtures/probe/probe-parking-template.yaml.tmpl index 8b4270db98..c3b1f452a5 100644 --- a/internal/e2e/fixtures/probe/probe-parking-template.yaml.tmpl +++ b/internal/e2e/fixtures/probe/probe-parking-template.yaml.tmpl @@ -48,5 +48,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/probe/probe-sized-template.yaml.tmpl b/internal/e2e/fixtures/probe/probe-sized-template.yaml.tmpl index 3fb12d26c3..c1004a20b6 100644 --- a/internal/e2e/fixtures/probe/probe-sized-template.yaml.tmpl +++ b/internal/e2e/fixtures/probe/probe-sized-template.yaml.tmpl @@ -42,5 +42,3 @@ resources: limits: cpuMillicores: 2000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/probe/probe-template.yaml.tmpl b/internal/e2e/fixtures/probe/probe-template.yaml.tmpl index 6d7c432c6b..93fb703c0a 100644 --- a/internal/e2e/fixtures/probe/probe-template.yaml.tmpl +++ b/internal/e2e/fixtures/probe/probe-template.yaml.tmpl @@ -60,5 +60,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-probe${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/security/atespace-template.yaml.tmpl b/internal/e2e/fixtures/security/atespace-template.yaml.tmpl index d6d6464605..6817e69b3d 100644 --- a/internal/e2e/fixtures/security/atespace-template.yaml.tmpl +++ b/internal/e2e/fixtures/security/atespace-template.yaml.tmpl @@ -74,5 +74,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/testserver/grpcecho-template.yaml.tmpl b/internal/e2e/fixtures/testserver/grpcecho-template.yaml.tmpl index 717868e298..ea97c6da4a 100644 --- a/internal/e2e/fixtures/testserver/grpcecho-template.yaml.tmpl +++ b/internal/e2e/fixtures/testserver/grpcecho-template.yaml.tmpl @@ -49,5 +49,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-grpcecho${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/fixtures/testserver/websocket-template.yaml.tmpl b/internal/e2e/fixtures/testserver/websocket-template.yaml.tmpl index 9663ed2f37..694277c16c 100644 --- a/internal/e2e/fixtures/testserver/websocket-template.yaml.tmpl +++ b/internal/e2e/fixtures/testserver/websocket-template.yaml.tmpl @@ -35,5 +35,3 @@ resources: limits: cpuMillicores: 1000 memoryBytes: 536870912 # 512Mi -snapshotConfig: - storageLocation: gs://${BUCKET_NAME}/ate-e2e-websocket${FIXTURE_SUFFIX}/ diff --git a/internal/e2e/preview.go b/internal/e2e/preview.go index fb997c64c2..21d4be23b2 100644 --- a/internal/e2e/preview.go +++ b/internal/e2e/preview.go @@ -73,6 +73,22 @@ func previewDialOptions() []grpc.DialOption { return nil } +// SnapshotLocation returns gs://$BUCKET_NAME/prefix/ when the install under +// test enabled the PerTemplateSnapshotLocation gate, and "" otherwise. Tests +// set it as a template's storageLocation, which the API rejects without the +// gate, so the gate-off lane stores snapshots under --snapshot-location. +func SnapshotLocation(t testing.TB, prefix string) string { + t.Helper() + if !preview.IsEnabled(preview.GatePerTemplateSnapshotLocation) { + return "" + } + env, err := CheckEnv("BUCKET_NAME") + if err != nil { + t.Fatalf("CheckEnv: %v", err) + } + return "gs://" + env["BUCKET_NAME"] + "/" + prefix + "/" +} + // RequirePreview skips t unless the install under test enabled gate. Tests of // a preview feature call it first, so a run without the gate skips them. func RequirePreview(t testing.TB, gate preview.Gate) { diff --git a/internal/e2e/preview_test.go b/internal/e2e/preview_test.go index 0efb5b7f08..5bab9a6a77 100644 --- a/internal/e2e/preview_test.go +++ b/internal/e2e/preview_test.go @@ -19,8 +19,29 @@ import ( "testing" "k8s.io/utils/ptr" + + "go.ate.dev/substrate/internal/preview" ) +func TestSnapshotLocation(t *testing.T) { + t.Setenv("BUCKET_NAME", "test-bucket") + for _, tc := range []struct { + name string + gates []string + want string + }{ + {name: "gate off"}, + {name: "gate on", gates: []string{string(preview.GatePerTemplateSnapshotLocation)}, want: "gs://test-bucket/ate-e2e-probe/"}, + } { + t.Run(tc.name, func(t *testing.T) { + preview.InitForTest(t, tc.gates...) + if got := SnapshotLocation(t, "ate-e2e-probe"); got != tc.want { + t.Errorf("SnapshotLocation() = %q, want %q", got, tc.want) + } + }) + } +} + func TestPreviewGates(t *testing.T) { for _, tc := range []struct { name string diff --git a/internal/e2e/probe.go b/internal/e2e/probe.go index 256ba0ff7a..ce0f7b6682 100644 --- a/internal/e2e/probe.go +++ b/internal/e2e/probe.go @@ -68,7 +68,7 @@ func WithSystemRoots() ProbeOption { // out from under another running concurrently. It returns the fixture's // atespace (which also names the k8s namespace holding the pool) and the // created ActorTemplate, already golden-snapshotted. -func DeployProbe(t *testing.T, bucket, name string, opts ...ProbeOption) (string, *ateapi.ActorTemplate) { +func DeployProbe(t *testing.T, name string, opts ...ProbeOption) (string, *ateapi.ActorTemplate) { t.Helper() var cfg probeConfig @@ -81,6 +81,6 @@ func DeployProbe(t *testing.T, bucket, name string, opts ...ProbeOption) (string EnsureEgressTrustBundle(t, context.Background(), GetClients()) } - atespace, templates := DeploySubstrateFixture(t, context.Background(), GetClients(), probeManifests, bucket, name, cfg.trustBundles) + atespace, templates := DeploySubstrateFixture(t, context.Background(), GetClients(), probeManifests, name, cfg.trustBundles) return atespace, templates[0] } diff --git a/internal/e2e/probe_test.go b/internal/e2e/probe_test.go index a610e1f37e..c3810ab657 100644 --- a/internal/e2e/probe_test.go +++ b/internal/e2e/probe_test.go @@ -45,7 +45,7 @@ func TestProbeTemplate_TrustBundle(t *testing.T) { // Strict decoding is what proves the fragment landed at the right // depth: misindented, it would fail the protojson decode or parse // as some other field. - inline, blocks := substrateTemplateSubstitutions("test-bucket", "render", cfg.trustBundles) + inline, blocks := substrateTemplateSubstitutions("render", cfg.trustBundles) rendered, err := os.ReadFile(renderManifest(t, probeManifests.Template, inline, blocks)) if err != nil { t.Fatalf("reading rendered manifest: %v", err) diff --git a/internal/e2e/sandbox.go b/internal/e2e/sandbox.go index 387e409058..234be0bf93 100644 --- a/internal/e2e/sandbox.go +++ b/internal/e2e/sandbox.go @@ -166,21 +166,20 @@ func TemplateReadyTimeout(t *testing.T) time.Duration { // One template serves both sandbox classes so the two variants of a fixture // cannot drift apart. See renderManifest for the placeholder kinds a template // can carry. -func RenderFixtureManifest(t *testing.T, relPath, bucket, name string) string { +func RenderFixtureManifest(t *testing.T, relPath, name string) string { t.Helper() - inline, blocks := fixtureSubstitutions(bucket, name) + inline, blocks := fixtureSubstitutions(name) return renderManifest(t, relPath, inline, blocks) } // fixtureSubstitutions is the placeholder set the internal/e2e/fixtures // manifest templates carry, split into the inline and whole-line-block kinds // RenderFixtureManifest treats differently. -func fixtureSubstitutions(bucket, name string) (inline, blocks map[string]string) { +func fixtureSubstitutions(name string) (inline, blocks map[string]string) { inline = map[string]string{ - "${BUCKET_NAME}": bucket, "${WORKER_IMAGE}": "ko://go.ate.dev/substrate/cmd/ateworker-gvisor", // The manifest-side half of FixtureName: it suffixes the fixture's - // namespace, and with it the snapshot prefix underneath. + // namespace. "${FIXTURE_SUFFIX}": "-" + name, } blocks = map[string]string{ diff --git a/internal/e2e/sandbox_test.go b/internal/e2e/sandbox_test.go index 9ac345e2be..427bb5d823 100644 --- a/internal/e2e/sandbox_test.go +++ b/internal/e2e/sandbox_test.go @@ -79,7 +79,7 @@ var substrateFixtures = []struct { // a WorkerPool that never gets a micro-VM worker. func renderPool(t *testing.T, relPath string) *atev1.WorkerPool { t.Helper() - raw, err := os.ReadFile(RenderFixtureManifest(t, relPath, "test-bucket", "render")) + raw, err := os.ReadFile(RenderFixtureManifest(t, relPath, "render")) if err != nil { t.Fatalf("reading the rendered %s: %v", relPath, err) } @@ -115,7 +115,7 @@ func renderPool(t *testing.T, relPath string) *atev1.WorkerPool { // the cluster as literal text. func renderTemplates(t *testing.T, relPath string, values map[string]string) []*ateapi.ActorTemplate { t.Helper() - inline, blocks := substrateTemplateSubstitutions("test-bucket", "render", nil) + inline, blocks := substrateTemplateSubstitutions("render", nil) maps.Copy(inline, values) rendered, err := os.ReadFile(renderManifest(t, relPath, inline, blocks)) if err != nil { @@ -149,14 +149,12 @@ func TestRenderSubstrateFixtures_GVisor(t *testing.T) { } for _, tmpl := range templates { name := tmpl.GetMetadata().GetName() - // An inline placeholder with an empty value must substitute, not - // delete its line: the location is what the golden snapshot needs. - location := tmpl.GetSnapshotConfig().GetStorageLocation() - if want := "gs://test-bucket/"; !strings.HasPrefix(location, want) { - t.Errorf("template %s snapshot location = %q, want it to start with %q", name, location, want) + atespace := tmpl.GetMetadata().GetAtespace() + if want := "-render"; !strings.HasSuffix(atespace, want) { + t.Errorf("template %s atespace = %q, want it to end with %q", name, atespace, want) } - if strings.Contains(location, "-microvm") { - t.Errorf("template %s snapshot location = %q, want no micro-VM suffix", name, location) + if strings.Contains(atespace, "-microvm") { + t.Errorf("template %s atespace = %q, want no micro-VM suffix", name, atespace) } // The selector is what ties the template to its fixture's pool. for k, v := range tmpl.GetWorkerSelector().GetMatchLabels() { @@ -170,7 +168,7 @@ func TestRenderSubstrateFixtures_GVisor(t *testing.T) { } // TestRenderSubstrateFixtures_MicroVM pins the micro-VM rendering: the -// templates carry limits and the snapshots land under their own prefix. +// templates carry limits and land in their own atespace. func TestRenderSubstrateFixtures_MicroVM(t *testing.T) { t.Setenv(sandboxClassEnv, SandboxClassMicroVM) for _, fixture := range substrateFixtures { @@ -193,9 +191,9 @@ func TestRenderSubstrateFixtures_MicroVM(t *testing.T) { if tmpl.GetResources().GetLimits().GetMemoryBytes() == 0 { t.Errorf("template %s declares no memory limit", name) } - location := tmpl.GetSnapshotConfig().GetStorageLocation() - if want := "-microvm-render/"; !strings.HasSuffix(location, want) { - t.Errorf("template %s snapshot location = %q, want it to end with %q", name, location, want) + atespace := tmpl.GetMetadata().GetAtespace() + if want := "-microvm-render"; !strings.HasSuffix(atespace, want) { + t.Errorf("template %s atespace = %q, want it to end with %q", name, atespace, want) } } }) diff --git a/internal/e2e/suites/authz/authz_test.go b/internal/e2e/suites/authz/authz_test.go index f6c9709833..87b914c5bf 100644 --- a/internal/e2e/suites/authz/authz_test.go +++ b/internal/e2e/suites/authz/authz_test.go @@ -34,12 +34,11 @@ import ( // against it. The subtests run in order, not in parallel: each sets the // fixture atespace's AccessPolicy for its own principals. func TestAuthz(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() - f := deployFixture(t, ctx, env["BUCKET_NAME"]) + f := deployFixture(t, ctx) t.Run("BasicFlow", func(t *testing.T) { testBasicFlow(t, ctx, f) }) t.Run("SecurityRegression", func(t *testing.T) { testSecurityRegression(t, ctx, f) }) diff --git a/internal/e2e/suites/authz/helpers_test.go b/internal/e2e/suites/authz/helpers_test.go index f7d3137d28..5c81dbfd30 100644 --- a/internal/e2e/suites/authz/helpers_test.go +++ b/internal/e2e/suites/authz/helpers_test.go @@ -277,10 +277,10 @@ func deleteAtespace(ctx context.Context, clients *e2e.Clients, name string) erro // deployFixture deploys the probe template into a new atespace and creates // one actor from it, all as the bootstrap owner. The actor is never resumed: // the tests only need an actor that exists. -func deployFixture(t *testing.T, ctx context.Context, bucket string) *fixture { +func deployFixture(t *testing.T, ctx context.Context) *fixture { t.Helper() clients := e2e.GetClients() - atespace, _ := e2e.DeployProbe(t, bucket, "authz") + atespace, _ := e2e.DeployProbe(t, "authz") actor := &ateapi.ObjectRef{Atespace: atespace, Name: actorName} template := &ateapi.ObjectRef{Atespace: atespace, Name: e2e.ProbeName} if _, err := clients.SubstrateAPI.CreateActor(ctx, &ateapi.CreateActorRequest{Actor: &ateapi.Actor{ diff --git a/internal/e2e/suites/capabilities/capabilities_test.go b/internal/e2e/suites/capabilities/capabilities_test.go index 0f03febd7f..7c02a1c3a2 100644 --- a/internal/e2e/suites/capabilities/capabilities_test.go +++ b/internal/e2e/suites/capabilities/capabilities_test.go @@ -52,14 +52,13 @@ type capabilitiesResponse struct { // It runs against whichever sandbox class E2E_SANDBOX_CLASS selects, so CI // covers both gvisor and micro-VM from one suite. func TestActorCapabilities(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() clients := e2e.GetClients() - namespace := deployFixture(t, ctx, clients, env["BUCKET_NAME"]) + namespace := deployFixture(t, ctx, clients) tests := []struct { name string @@ -145,12 +144,12 @@ func assertSameCapabilities(t *testing.T, set string, got, want []string) { // container cannot start — for example because a needed capability was // dropped — fails the deploy with the template's error message rather than // timing out per subtest. -func deployFixture(t *testing.T, ctx context.Context, clients *e2e.Clients, bucket string) string { +func deployFixture(t *testing.T, ctx context.Context, clients *e2e.Clients) string { t.Helper() atespace, _ := e2e.DeploySubstrateFixture(t, ctx, clients, e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/capabilities/capabilities.yaml.tmpl", Template: "internal/e2e/fixtures/capabilities/capabilities-templates.yaml.tmpl", - }, bucket, "capabilities", nil) + }, "capabilities", nil) return atespace } diff --git a/internal/e2e/suites/combinedvolumes/combinedvolumes_test.go b/internal/e2e/suites/combinedvolumes/combinedvolumes_test.go index 3c37ea65e5..3149ca2925 100644 --- a/internal/e2e/suites/combinedvolumes/combinedvolumes_test.go +++ b/internal/e2e/suites/combinedvolumes/combinedvolumes_test.go @@ -182,13 +182,8 @@ func storageClassOrEmpty(ctx context.Context, t *testing.T, clients *e2e.Clients func createTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, ns *e2e.Namespace, fixtureImage, storageClass string) *ateapi.ActorTemplate { t.Helper() - env, err := e2e.CheckEnv("BUCKET_NAME") - if err != nil { - t.Fatalf("CheckEnv: %v", err) - } - // The probe supplies this suite's container image and resolved runtime. - probeAtespace, _ := e2e.DeployProbe(t, env["BUCKET_NAME"], "combinedvolumes") + probeAtespace, _ := e2e.DeployProbe(t, "combinedvolumes") src := e2e.SubstrateFixture{ Atespace: probeAtespace, Name: probeName, @@ -206,7 +201,7 @@ func createTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, ns // scheduler cannot hand its workers to another suite's actors. Labels: map[string]string{"combinedvolumes": ns.Name}, SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: fmt.Sprintf("gs://%s/%s/", env["BUCKET_NAME"], ns.Name), + StorageLocation: e2e.SnapshotLocation(t, ns.Name), }, Modify: func(tmpl *ateapi.ActorTemplate) { // Every volume is mounted at two paths, covering the read-only diff --git a/internal/e2e/suites/demo/demo_test.go b/internal/e2e/suites/demo/demo_test.go index 0a30906e50..11d87efafa 100644 --- a/internal/e2e/suites/demo/demo_test.go +++ b/internal/e2e/suites/demo/demo_test.go @@ -1338,11 +1338,6 @@ func deletePausedActor(ctx context.Context, t *testing.T, clients *e2e.Clients, } func createActorTemplateInternal(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, base string, fidelity ateapi.SnapshotFidelity, modifyTemplate func(*ateapi.ActorTemplate)) (*ateapi.ActorTemplate, error) { - env, err := e2e.CheckEnv("BUCKET_NAME") - if err != nil { - t.Fatalf("CheckEnv failed: %v", err) - } - // The whole suite shares demoAtespace, so the per-test suffix keeps // template names unique. name := base + "-" + nsObj.Name @@ -1351,17 +1346,24 @@ func createActorTemplateInternal(ctx context.Context, t *testing.T, clients *e2e Name: name, PoolName: base, // One actor at a time, plus a spare for the worker-deletion tests. - PoolReplicas: 2, - Labels: map[string]string{"demo": nsObj.Name}, - SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: "gs://" + env["BUCKET_NAME"] + "/ate-demo-" + name, - PreferredFidelity: fidelity, - }, - Modify: modifyTemplate, + PoolReplicas: 2, + Labels: map[string]string{"demo": nsObj.Name}, + SnapshotConfig: templateSnapshotConfig(t, fidelity, "ate-demo-"+name), + Modify: modifyTemplate, }) return at, nil } +// templateSnapshotConfig returns a SnapshotConfig that captures at fidelity, +// stored under e2e.SnapshotLocation(prefix). +func templateSnapshotConfig(t *testing.T, fidelity ateapi.SnapshotFidelity, prefix string) *ateapi.SnapshotConfig { + t.Helper() + return &ateapi.SnapshotConfig{ + PreferredFidelity: fidelity, + StorageLocation: e2e.SnapshotLocation(t, prefix), + } +} + func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, fidelity ateapi.SnapshotFidelity) (*ateapi.ActorTemplate, error) { return createActorTemplateInternal(ctx, t, clients, nsObj, "counter", fidelity, nil) } diff --git a/internal/e2e/suites/demo/golden_test.go b/internal/e2e/suites/demo/golden_test.go index c8a7c0acfe..4ff9e3deae 100644 --- a/internal/e2e/suites/demo/golden_test.go +++ b/internal/e2e/suites/demo/golden_test.go @@ -35,10 +35,6 @@ func TestGoldenSnapshotLifecycle(t *testing.T) { ctx := t.Context() clients := e2e.GetClients() ns := e2e.CreateNamespace(t) - env, err := e2e.CheckEnv("BUCKET_NAME") - if err != nil { - t.Fatal(err) - } // A distinct atespace catches placement in a system atespace and // keeps cleanup independent of the suite's shared demo atespace. atespace := "golden-" + ns.Name @@ -49,11 +45,8 @@ func TestGoldenSnapshotLifecycle(t *testing.T) { }) created := e2e.CreateSubstrateCounterTemplate(ctx, t, clients, ns.Name, e2e.SubstrateTemplateOptions{ Atespace: atespace, Name: "counter", PoolName: "counter", PoolReplicas: 1, - Labels: map[string]string{"golden-e2e": ns.Name}, - SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: "gs://" + env["BUCKET_NAME"] + "/" + atespace, - PreferredFidelity: fidelity, - }, + Labels: map[string]string{"golden-e2e": ns.Name}, + SnapshotConfig: templateSnapshotConfig(t, fidelity, atespace), }) templateRef := e2e.TemplateRef(created) diff --git a/internal/e2e/suites/demo/updatetemplate_test.go b/internal/e2e/suites/demo/updatetemplate_test.go index a39d404e9f..a0ae526a70 100644 --- a/internal/e2e/suites/demo/updatetemplate_test.go +++ b/internal/e2e/suites/demo/updatetemplate_test.go @@ -64,18 +64,13 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapi.SnapshotFidelity) { ctx := context.Background() clients := e2e.GetClients() - env, err := e2e.CheckEnv("BUCKET_NAME") - if err != nil { - t.Fatalf("CheckEnv failed: %v", err) - } - // Template A: the plain counter. Template B: the same workload, but its // command additionally validates that the file template A's sprint left // in the durable dir is readable — the response's "file content" both // proves B's spec took effect and re-reads the preserved data. nameA, nameB := "update-a-"+nsObj.Name, "update-b-"+nsObj.Name - createdA := createUpdateTestTemplate(ctx, t, clients, nsObj, nameA, "update-a", env["BUCKET_NAME"], fidelity, nil) - createdB := createUpdateTestTemplate(ctx, t, clients, nsObj, nameB, "update-b", env["BUCKET_NAME"], fidelity, func(tmpl *ateapi.ActorTemplate) { + createdA := createUpdateTestTemplate(ctx, t, clients, nsObj, nameA, "update-a", fidelity, nil) + createdB := createUpdateTestTemplate(ctx, t, clients, nsObj, nameB, "update-b", fidelity, func(tmpl *ateapi.ActorTemplate) { ctr := tmpl.Containers[0] ctr.Command = append(ctr.Command, "--validate-existing-file-path=/home/counter/a.txt") }) @@ -322,19 +317,18 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapi.SnapshotFidelity) { // createUpdateTestTemplate creates a per-test WorkerPool plus a substrate // ActorTemplate copying the deployed counter fixture's resolved runtime, -// capturing at the scope under test on suspend. -func createUpdateTestTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, name, poolName, bucket string, fidelity ateapi.SnapshotFidelity, modify func(*ateapi.ActorTemplate)) *ateapi.ActorTemplate { +// capturing at the scope under test on suspend. Both templates of a test share +// one snapshot location, which UpdateActor requires once the actor has a +// snapshot. +func createUpdateTestTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, name, poolName string, fidelity ateapi.SnapshotFidelity, modify func(*ateapi.ActorTemplate)) *ateapi.ActorTemplate { t.Helper() return e2e.CreateSubstrateCounterTemplate(ctx, t, clients, nsObj.Name, e2e.SubstrateTemplateOptions{ - Atespace: demoAtespace, - Name: name, - PoolName: poolName, - PoolReplicas: 2, - Labels: map[string]string{"demo": nsObj.Name}, - SnapshotConfig: &ateapi.SnapshotConfig{ - StorageLocation: "gs://" + bucket + "/ate-demo-" + nsObj.Name, - PreferredFidelity: fidelity, - }, - Modify: modify, + Atespace: demoAtespace, + Name: name, + PoolName: poolName, + PoolReplicas: 2, + Labels: map[string]string{"demo": nsObj.Name}, + SnapshotConfig: templateSnapshotConfig(t, fidelity, "ate-demo-"+nsObj.Name), + Modify: modify, }) } diff --git a/internal/e2e/suites/egresscredinject/egresscredinject_test.go b/internal/e2e/suites/egresscredinject/egresscredinject_test.go index ae64812a12..f4575f7bf5 100644 --- a/internal/e2e/suites/egresscredinject/egresscredinject_test.go +++ b/internal/e2e/suites/egresscredinject/egresscredinject_test.go @@ -111,8 +111,7 @@ var withPlaceholder = []string{ // hack/setup-e2e-egress-tls-kind.sh // hack/run-e2e-kind.sh ./internal/e2e/suites/egresscredinject -v -args --no-color func TestActorEgressCredentialInjection(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() @@ -124,7 +123,7 @@ func TestActorEgressCredentialInjection(t *testing.T) { e2e.ConfigureCredentialProvider(t) - probeNamespace, _ = e2e.DeployProbe(t, env["BUCKET_NAME"], "egresscredinject", e2e.WithTrustBundle()) + probeNamespace, _ = e2e.DeployProbe(t, "egresscredinject", e2e.WithTrustBundle()) const id = "probe-credinject" createAndResumeActor(t, ctx, clients, id, echoHost) diff --git a/internal/e2e/suites/egressmitm/egressmitm_test.go b/internal/e2e/suites/egressmitm/egressmitm_test.go index 2d6da343b3..47d6b32483 100644 --- a/internal/e2e/suites/egressmitm/egressmitm_test.go +++ b/internal/e2e/suites/egressmitm/egressmitm_test.go @@ -67,8 +67,7 @@ var probeNamespace string // The micro-VM variant additionally needs the micro-VM deps installed // (hack/run-microvm-demo-kind.sh, or hack/install-microvm-deps.sh --install). func TestActorEgressMITMTrust(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() @@ -92,7 +91,7 @@ func TestActorEgressMITMTrust(t *testing.T) { passthrough := e2e.DeployServerPod(t, ctx, passthroughSpec) originRoots := "ca=" + url.QueryEscape(e2e.ServiceDNSTrustBundle(t, ctx, clients)) - probeNamespace, _ = e2e.DeployProbe(t, env["BUCKET_NAME"], "egressmitm", e2e.WithTrustBundle(), e2e.WithSystemRoots()) + probeNamespace, _ = e2e.DeployProbe(t, "egressmitm", e2e.WithTrustBundle(), e2e.WithSystemRoots()) const id = "probe-mitm" createAndResumeActor(t, ctx, clients, id, mitm, passthrough) diff --git a/internal/e2e/suites/egressmitm/goldenboot_test.go b/internal/e2e/suites/egressmitm/goldenboot_test.go index 09215bc123..800ee080e2 100644 --- a/internal/e2e/suites/egressmitm/goldenboot_test.go +++ b/internal/e2e/suites/egressmitm/goldenboot_test.go @@ -45,8 +45,7 @@ var bootFetchManifests = e2e.SubstrateFixtureManifests{ // hack/run-e2e-kind.sh ./internal/e2e/suites/egressmitm -run TestGoldenBootEgress -v -args --no-color --preview=GoldenEgressPolicy func TestGoldenBootEgress(t *testing.T) { e2e.RequirePreview(t, preview.GateGoldenEgressPolicy) - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() @@ -56,7 +55,7 @@ func TestGoldenBootEgress(t *testing.T) { // DeploySubstrateFixture returns once every template has its golden // snapshot, which is the assertion. - atespace, _ := e2e.DeploySubstrateFixture(t, ctx, clients, bootFetchManifests, env["BUCKET_NAME"], "egressmitm-bootfetch", []string{e2e.EgressTrustBundleName}, + atespace, _ := e2e.DeploySubstrateFixture(t, ctx, clients, bootFetchManifests, "egressmitm-bootfetch", []string{e2e.EgressTrustBundleName}, e2e.WithTemplateValue("BOOT_FETCH_HOST", origin.SvcName()), e2e.WithTemplateValue("BOOT_FETCH_URL", "https://"+origin.SvcName()+"/healthz")) diff --git a/internal/e2e/suites/identity/identity_test.go b/internal/e2e/suites/identity/identity_test.go index 60d3bbda0e..e68a18fec7 100644 --- a/internal/e2e/suites/identity/identity_test.go +++ b/internal/e2e/suites/identity/identity_test.go @@ -71,8 +71,7 @@ type whoamiResponse struct { // (e.g. a timestamped-directory symlink swap) would break every held fd of // any actor that ever touched a system-info file. func TestActorIdentity_AfterRestore_IsOwnID_NotGolden(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() @@ -83,7 +82,7 @@ func TestActorIdentity_AfterRestore_IsOwnID_NotGolden(t *testing.T) { // contents. wantTrust := e2e.AddEgressCA(t, ctx, clients, "ate-e2e-probe-trust") var tmpl *ateapi.ActorTemplate - probeNamespace, tmpl = e2e.DeployProbe(t, env["BUCKET_NAME"], "identity", e2e.WithTrustBundle()) + probeNamespace, tmpl = e2e.DeployProbe(t, "identity", e2e.WithTrustBundle()) // The golden actor's id, for the not-golden assertion below. Coupled to // the reconciler's naming: the golden actor is named after the template's // UID (cmd/ateapi template reconciler), so a naming change there weakens diff --git a/internal/e2e/suites/multiactor/multiactor_test.go b/internal/e2e/suites/multiactor/multiactor_test.go index f496c557b3..bb43cffa7c 100644 --- a/internal/e2e/suites/multiactor/multiactor_test.go +++ b/internal/e2e/suites/multiactor/multiactor_test.go @@ -44,15 +44,14 @@ type whoamiResponse struct { func TestTwoActorsShareOneWorker(t *testing.T) { ctx := context.Background() clients := e2e.GetClients() - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatal(err) } e2e.DeploySubstrateFixture(t, ctx, clients, e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/probe/probe-multiactor.yaml.tmpl", Template: "internal/e2e/fixtures/probe/probe-multiactor-template.yaml.tmpl", - }, env["BUCKET_NAME"], "multiactor", nil) + }, "multiactor", nil) names := []string{"ma-first", "ma-second"} pods := map[string]string{} diff --git a/internal/e2e/suites/networking/grpcingress_test.go b/internal/e2e/suites/networking/grpcingress_test.go index b034e457cd..97a5a2f3fb 100644 --- a/internal/e2e/suites/networking/grpcingress_test.go +++ b/internal/e2e/suites/networking/grpcingress_test.go @@ -137,13 +137,12 @@ func TestIngressProtocolDowngrade(t *testing.T) { // deploys as a plain pod, so the two directions cannot disagree about what a // working RPC is. func TestIngressGRPC(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() - fixture := deployGRPCEchoTemplate(t, ctx, env["BUCKET_NAME"]) + fixture := deployGRPCEchoTemplate(t, ctx) _, actorName, _ := createAndResumeSubstrateActor(t, ctx, "grpcingress", fixture) actorRef := resources.ActorRef{Atespace: networkingAtespace, Name: actorName} ctx = metadata.AppendToOutgoingContext(ctx, @@ -262,9 +261,9 @@ func TestIngressGRPC(t *testing.T) { // under test, waits for its golden snapshot and returns it. The suite gets its // own copy of the fixture: suite packages run as concurrent processes, so a // shared one would be deleted out from under another. -func deployGRPCEchoTemplate(t *testing.T, ctx context.Context, bucket string) e2e.SubstrateFixture { +func deployGRPCEchoTemplate(t *testing.T, ctx context.Context) e2e.SubstrateFixture { t.Helper() - atespace, _ := e2e.DeploySubstrateFixture(t, ctx, e2e.GetClients(), grpcEchoFixtureManifests, bucket, "networking", nil) + atespace, _ := e2e.DeploySubstrateFixture(t, ctx, e2e.GetClients(), grpcEchoFixtureManifests, "networking", nil) return e2e.SubstrateFixture{ Atespace: atespace, Name: "grpcecho", diff --git a/internal/e2e/suites/networking/websocketingress_test.go b/internal/e2e/suites/networking/websocketingress_test.go index 5a497badf5..104b3eddee 100644 --- a/internal/e2e/suites/networking/websocketingress_test.go +++ b/internal/e2e/suites/networking/websocketingress_test.go @@ -32,15 +32,14 @@ import ( // under test and waits for its golden snapshot. func deployWebsocketFixture(t *testing.T, ctx context.Context) e2e.SubstrateFixture { t.Helper() - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } atespace, _ := e2e.DeploySubstrateFixture(t, ctx, e2e.GetClients(), e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/testserver/websocket.yaml.tmpl", Template: "internal/e2e/fixtures/testserver/websocket-template.yaml.tmpl", - }, env["BUCKET_NAME"], "websocket", nil) + }, "websocket", nil) return e2e.SubstrateFixture{ Atespace: atespace, diff --git a/internal/e2e/suites/parking/parking_test.go b/internal/e2e/suites/parking/parking_test.go index 76b1d7b1fa..42df00756c 100644 --- a/internal/e2e/suites/parking/parking_test.go +++ b/internal/e2e/suites/parking/parking_test.go @@ -199,14 +199,10 @@ func TestRequestParking(t *testing.T) { // actors (see probe-parking.yaml.tmpl). func deployParkingFixture(t *testing.T, ctx context.Context, clients *e2e.Clients) *ateapi.ActorTemplate { t.Helper() - env, err := e2e.CheckEnv("BUCKET_NAME") - if err != nil { - t.Fatalf("CheckEnv failed: %v", err) - } _, templates := e2e.DeploySubstrateFixture(t, ctx, clients, e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/probe/probe-parking.yaml.tmpl", Template: "internal/e2e/fixtures/probe/probe-parking-template.yaml.tmpl", - }, env["BUCKET_NAME"], "parking", nil) + }, "parking", nil) return templates[0] } diff --git a/internal/e2e/suites/security/crossatespace_test.go b/internal/e2e/suites/security/crossatespace_test.go index 1597fadcaf..ebd2b4b885 100644 --- a/internal/e2e/suites/security/crossatespace_test.go +++ b/internal/e2e/suites/security/crossatespace_test.go @@ -95,14 +95,13 @@ type atespaces struct { // atespace's golden snapshot, which on the micro-VM lane is a cold boot plus a // checkpoint, so per-subtest fixtures would dominate the runtime. func TestCrossAtespaceIsolation(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() clients := e2e.GetClients() - ns := deployAtespaces(t, ctx, clients, env["BUCKET_NAME"]) + ns := deployAtespaces(t, ctx, clients) rc, err := e2e.NewRouterClient(t, ctx) if err != nil { @@ -306,16 +305,16 @@ func testTagScopeBoundary(t *testing.T, ctx context.Context, clients *e2e.Client // deployAtespaces installs both atespaces and returns their names. A goes // first: it brings the only WorkerPool, and B's golden snapshot needs a worker // to boot on. -func deployAtespaces(t *testing.T, ctx context.Context, clients *e2e.Clients, bucket string) atespaces { +func deployAtespaces(t *testing.T, ctx context.Context, clients *e2e.Clients) atespaces { t.Helper() a, _ := e2e.DeploySubstrateFixture(t, ctx, clients, e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/security/atespace-a-pool.yaml.tmpl", Template: "internal/e2e/fixtures/security/atespace-template.yaml.tmpl", - }, bucket, "sec-a", nil) + }, "sec-a", nil) b, _ := e2e.DeploySubstrateFixture(t, ctx, clients, e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/security/atespace-b-namespace.yaml.tmpl", Template: "internal/e2e/fixtures/security/atespace-template.yaml.tmpl", - }, bucket, "sec-b", nil) + }, "sec-b", nil) if a == b { t.Fatalf("both fixtures rendered to atespace %q — the fixture no longer isolates anything", a) } diff --git a/internal/e2e/suites/sizing/sizing_test.go b/internal/e2e/suites/sizing/sizing_test.go index f853cd6d20..3d54e8c96a 100644 --- a/internal/e2e/suites/sizing/sizing_test.go +++ b/internal/e2e/suites/sizing/sizing_test.go @@ -62,14 +62,13 @@ type resourcesResponse struct { // the CPU quota and the cgroup memory limit, while the micro-VM sizes the guest // VM itself (see internal/sizing). func TestActorSizing_SandboxObservesDeclaredLimits(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") - if err != nil { + if _, err := e2e.CheckEnv("KO_DOCKER_REPO"); err != nil { t.Fatalf("CheckEnv failed: %v", err) } ctx := context.Background() clients := e2e.GetClients() - deploySizedProbe(t, ctx, clients, env["BUCKET_NAME"]) + deploySizedProbe(t, ctx, clients) const id = "sized-actor" createAndResumeActor(t, ctx, clients, id) @@ -108,12 +107,12 @@ func TestActorSizing_SandboxObservesDeclaredLimits(t *testing.T) { // deploySizedProbe installs the sized probe fixture for the sandbox class // under test and waits for its golden snapshot. -func deploySizedProbe(t *testing.T, ctx context.Context, clients *e2e.Clients, bucket string) { +func deploySizedProbe(t *testing.T, ctx context.Context, clients *e2e.Clients) { t.Helper() e2e.DeploySubstrateFixture(t, ctx, clients, e2e.SubstrateFixtureManifests{ Pool: "internal/e2e/fixtures/probe/probe-sized.yaml.tmpl", Template: "internal/e2e/fixtures/probe/probe-sized-template.yaml.tmpl", - }, bucket, "sizing", nil) + }, "sizing", nil) } func createAndResumeActor(t *testing.T, ctx context.Context, clients *e2e.Clients, id string) { diff --git a/internal/e2e/suites/snapshotcredentials/snapshotcredentials_test.go b/internal/e2e/suites/snapshotcredentials/snapshotcredentials_test.go index 0b9af6acf8..8460816284 100644 --- a/internal/e2e/suites/snapshotcredentials/snapshotcredentials_test.go +++ b/internal/e2e/suites/snapshotcredentials/snapshotcredentials_test.go @@ -278,16 +278,12 @@ func suspendSync(t *testing.T, ctx context.Context, clients *e2e.Clients, atespa // their own snapshots, it reads its own and not the sibling's. Tokens the // exchange must refuse are checked alongside. func TestExchangeScopesTheTokenToTheActor(t *testing.T) { - env, err := e2e.CheckEnv("BUCKET_NAME") - if err != nil { - t.Fatalf("CheckEnv failed: %v", err) - } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer cancel() clients := e2e.GetClients() requireGCS(t, ctx, clients) - atespace, _ := e2e.DeployProbe(t, env["BUCKET_NAME"], "snapcreds") + atespace, _ := e2e.DeployProbe(t, "snapcreds") sign := newSigner(t, ctx, clients) xchg := dialExchange(t, ctx, clients) stamp := time.Now().UTC().Format("20060102T150405Z") @@ -415,14 +411,14 @@ func TestRepointMovesTheGrant(t *testing.T) { clients := e2e.GetClients() requireGCS(t, ctx, clients) - atespace, probe := e2e.DeployProbe(t, env["BUCKET_NAME"], "snapcreds-repoint") + atespace, probe := e2e.DeployProbe(t, "snapcreds-repoint") sign := newSigner(t, ctx, clients) xchg := dialExchange(t, ctx, clients) stamp := time.Now().UTC().Format("20060102T150405Z") - // Template B is the probe template under another location in the same - // bucket. Its golden is not what the actor resumes from, but the actor's - // next snapshot goes under B's location. + // Template B is the probe template under another location in + // $BUCKET_NAME. B's golden is not what the actor resumes from, but the + // actor's next snapshot goes under B's location. const nameB = "probe-repoint" locationB := "gs://" + env["BUCKET_NAME"] + "/ate-e2e-repoint-" + stamp + "/" templateB := proto.Clone(probe).(*ateapi.ActorTemplate) diff --git a/internal/e2e/template.go b/internal/e2e/template.go index 019cac8762..72355b4568 100644 --- a/internal/e2e/template.go +++ b/internal/e2e/template.go @@ -23,6 +23,7 @@ import ( atev1 "go.ate.dev/substrate/api/infra/k8s/ate/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -45,7 +46,8 @@ type SubstrateTemplateOptions struct { // Labels tie the template's workerSelector to the pool, keeping this // pool's workers invisible to other namespaces' actors. Labels map[string]string - // SnapshotConfig for the new template; nil copies the source's. + // SnapshotConfig for the new template. nil copies the source's, with its + // own SnapshotLocation. SnapshotConfig *ateapi.SnapshotConfig // Modify, when set, edits the template before it is created. Modify func(*ateapi.ActorTemplate) @@ -101,7 +103,11 @@ func CreateSubstrateTemplateFrom(ctx context.Context, t *testing.T, clients *Cli snapshots := opts.SnapshotConfig if snapshots == nil { - snapshots = srcTmpl.GetSnapshotConfig() + snapshots = &ateapi.SnapshotConfig{} + if src := srcTmpl.GetSnapshotConfig(); src != nil { + snapshots = proto.Clone(src).(*ateapi.SnapshotConfig) + } + snapshots.StorageLocation = SnapshotLocation(t, "ate-e2e-"+opts.Atespace+"-"+opts.Name) } tmpl := &ateapi.ActorTemplate{ Metadata: &ateapi.ResourceMetadata{Atespace: opts.Atespace, Name: opts.Name}, diff --git a/internal/preview/preview.go b/internal/preview/preview.go index 8442ff875e..0d3d17c245 100644 --- a/internal/preview/preview.go +++ b/internal/preview/preview.go @@ -45,9 +45,10 @@ const ( // GateGoldenEgressPolicy controls configuring a golden actor's egress policy on its template. GateGoldenEgressPolicy Gate = "GoldenEgressPolicy" - // GatePerTemplateSnapshotLocation controls whether an ActorTemplate's - // snapshotConfig.storage_location is honored. When disabled, snapshots - // are stored under ate-api-server's --snapshot-location. + // GatePerTemplateSnapshotLocation controls whether an ActorTemplate may + // set snapshotConfig.storage_location. When disabled, the API rejects the + // field and snapshots are stored under ate-api-server's + // --snapshot-location. GatePerTemplateSnapshotLocation Gate = "PerTemplateSnapshotLocation" )