Commit Graph
38 Commits
Author SHA1 Message Date
Julian Gutierrez Oschmann c1ab0958f8 Update atenet component to use actor *name* instead of *id*. 2026-07-10 13:06:09 -07:00
Haven Xia 669965a735 Rename actor_id to actor_name in atelet/ateom protos.
Aligns the internal ateletpb/ateompb services with the API style guide's
actor naming (field tags unchanged, so the wire format is compatible).

internal APIs keep dedicated identity fields (not the public ObjectRef).
2026-07-10 12:54:16 -04:00
Julian Gutierrez Oschmann 992a3b3050 Rework Create endpoints to align with API style guide.
* Take resource message in the request.
* Return resource message.
2026-07-09 14:49:50 -07:00
Julian Gutierrez Oschmann f1b69e93a7 Update Delete methods to align with style guide.
* Update return type to return the resource.
* Update request message to use `ObjectRef`.
2026-07-09 11:59:34 -07:00
Julian Gutierrez Oschmann 3f4677c3a4 Update Get methods to align with style guide.
* Return type should be the resource type.
* Requests must use `ObjectRef`.
2026-07-09 11:59:34 -07:00
Julian Gutierrez Oschmann 89f821f487 Introduce ObjectRef message.
Replace existing `ActorRef` with the new generic `ObjectRef` type.
2026-07-09 11:59:34 -07:00
Haven Xia de5d1d93f9 Make worker assignment checks atespace and actor name, also updated locks. (#411)
Current we only check & lock on actor name.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-07-09 09:29:10 -04:00
Julian Gutierrez Oschmann af1f00522c Add ResourceMetadata to both actor and atespace resources.
Replace actor_id and atespace name with `metadata` field which
holds common fields.

This commit only changes the ate-apiserver to support this field, but
intentionally skips upstream / downstream systems (such as kubectl-ate,
atenet, atelet, etc) to keep the scope manageable. This will come in subsequent PRs.
2026-07-08 17:22:03 -07:00
sfunkenhauser 1a40c9d314 controlapi: Add worker pool metadata to workers (#403)
Decouple the actor resumption scheduler from Kubernetes by avoiding
direct queries to WorkerPool CRDs. Instead, cache the worker pool's
labels and sandbox class on the Worker object in Redis during pod
synchronization, and update the scheduler to evaluate worker eligibility
using these cached fields.
2026-07-08 15:35:24 -04:00
Dmitry Berkovich b66b5c2007 refactor: remove redundant SnapshotType enum and use protobuf oneof d… (#370)
refactor: remove redundant SnapshotType enum and use protobuf oneof d…
2026-07-08 10:05:58 -07:00
Zoe Zhao 069ba0840a Detect CRASHED Actors during Checkpoint and Restore (#353)
Related to #292 and #119

Changes:

1. ResumeActor:  
1. During `ResumeActor` we'll put the actor in CRASHED state if the
Resume failed due to any errors related to the snapshot itself, which
includes:
      1. Missing or corrupted snapshot when downloading from GCS  
      2. Runsc command fails.  
2. All other types of errors will not put the Actor in CRASHED state.
2. SuspendActor or PauseActor  
1. During `SuspendActor` or `PauseActor` we'll put the actor in CRASHED
state if we cannot produce a valid snapshot, which includes:
      1. cannot connect to Ateom after retries,  
      2. runsc command fails.  
3. cannot upload the snapshot to GCS (during suspend) ; or cannot save
snapshot locally (during pause).
   2. All other types of errors will not put the Actor in CRASHED state.
2026-07-08 09:26:03 -07:00
Julian Gutierrez Oschmann dce688a224 Propagate (atespace/actor_id) to atelet.
This fixes an issue where atelet/ateom will clash the state
of actors with same ID within different atespaces.

Once substrate resources get UIDs, we should revisit the way
atelet / ateom does the bookkeeping and check in which cases it
should use UID instead of (atespace,actor_id).
2026-07-06 09:50:50 -07:00
Tim Hockin 100532777c Put Worker.actor fields into an "Assignment" message (#363)
This is a cleanup I noticed as I was reviewing other PRs. I tried to
keep the commits clean, so you can see them one by one.
2026-07-01 12:35:58 -04:00
Haven Xia b755ae7388 Remove all the "(tenant)" description from atespace comments (#354)
followup cleaning for #280

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-06-29 22:03:25 -07:00
Haven Xia db97c5ea13 Fold actor name+atespace into ActorRef across actor RPCs
An actor name is only unique within its atespace, so the two always travel together. Add ActorRef{atespace, name} and use it across the Get/Create/Update/Suspend/Pause/Resume/Delete actor requests in place of separate actor_id + atespace fields. The stored Actor object and Redis key layout are unchanged.
2026-06-29 14:57:54 -07:00
Haven Xia b855c3a045 Add Atespace object and CRUD API; require it on CreateActor 2026-06-29 14:57:54 -07:00
Haven Xia 5fdc907307 List actors across all atespaces and show ATESPACE column 2026-06-29 14:57:54 -07:00
Haven Xia 05f7e6a856 Scope actor store and handlers by atespace 2026-06-29 14:57:54 -07:00
Dmitry Berkovich 37b5006d92 DurableDir support (#295)
Implement a layer allowing to take durableDir snapshot only, without
taking snapshot of entire memory. This is a part of the #119 feature.

The "durableDir" support is introduced via adding Volumes support to
ActorTemplate.

## Knowns issues / limitations


1. No MicroVM support for homedir yet. Will be implemented in following
PR.

2. Resume from SNAPSHOT_SCOPE_DATA temporary behavior
Current behavior of resume from durableStorage returns immediately after
gVisor process is started, it means container's bootstrap is not
completed by the time resume API returned to the caller. It causes the
issue that first curl request for suspended actor throws an HTTP
exception, since webserver listening on the port inside container is not
running yet.
PR #330 fixes the issue, for a meantime the workaround is to call `
kubectl ate resume actor` and after it call to the curl itself.

- [X ] Unit tests  pass
- [X ] E2E tests are updated and pass
2026-06-26 20:05:03 -07:00
Dmitry Berkovich 125180ea65 feat: implement container readiness probes with custom HTTP endpoint … (#330)
Extends ActorTemplate.Container spec to allow defining a readyz probe. 
* ateoms (both gVisor & microVM) are updated and wait for readyz signal
if it was enabled on container when creating a new container or resume
from a snapshot.
* actorTemplates controller has been updated not to wait 20 sec during
golden snapshot creation, if readyz is enabled on actor. It assumes,
atelet call returns when all containers passed readyz.

Tested manually both counters: 
* gVisor 
* microvm 

Fixes #316 

- [x] e2e tests pass 
- [X] Appropriate changes to documentation are included in the PR
2026-06-26 18:59:56 -07:00
Julian Gutierrez Oschmann 2b28a6ffec Make WorkerCache periodically relist workers to recover missed events.
If WorkerEvents are missed, the local cache can drift. To recover
from this, the cache periodically executes a full `ListWorkers`.

Set a relist period of 5 minutes for now. There is no much thought
behind this number though.
2026-06-26 17:18:51 -07:00
Mesut Oezdil 9d57db0497 fix: correct stale lock TTL value in comment (#325)
Comment said 7 seconds but the code passes 30 seconds to
acquireActorLock.
2026-06-26 17:35:59 -04:00
Julian Gutierrez Oschmann 2d5764b865 Add an in-memory workers cache.
Remove the need for scanning all workers from ValKey during `ResumeActor`.
Extend store.Interface with a "watcher" interface so that clients
can keep track of workers in memory and do cache invalidation.

For ValKey, the watcher is implemented by publishing events to
a ValKey channel. Clients subscribe to changes using SUBSCRIBE
and receive them via the WorkerWatch channel.
2026-06-25 16:06:53 -07:00
Benjamin Elder fe93d160a1 feat: add SandboxClass support to ActorTemplate (#285)
Big UX improvement for iterating on #123 

Follow-up to https://github.com/agent-substrate/substrate/pull/270
(previously worker pools were directly referenced by actors so you just
made sure to pick an appropriate workerpool, we discussed there)

> It's a good idea to open an issue first for discussion.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-06-22 13:56:54 -07:00
Julian Gutierrez Oschmann 9f8475493b Decouple ActorTemplates from WorkerPools. (#270)
This change removes the hard-link from templates to worker pools,
allowing pools to be selected using k8s labels.

Both actors and actor templates are augmented with a label selector
field.

When an actor is resumed, we resolve the set of *eligible* worker pools
by evaluating the intersection of both actor and template selectors.
Then we find a free worker from one of them. The overall design is
described in [1].

[1]:
https://github.com/agent-substrate/substrate/issues/47#issuecomment-4712334620
2026-06-18 10:58:34 -07:00
Benjamin Elder 00a90a8eda Decouple Sandbox Binaries / Config from ActorTemplate (#261)
Fixes https://github.com/agent-substrate/substrate/issues/253

> It's a good idea to open an issue first for discussion.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

Should make something like
https://github.com/agent-substrate/substrate/pull/239 less awkward, and
also https://github.com/agent-substrate/substrate/issues/255 (we don't
have to cram more fields into actortemplate for the shim binary and
update _all_ of the demo actortemplates to include them).
2026-06-17 09:46:46 -07:00
Julian Gutierrez Oschmann a1ec7e3c5b Use k8s errors package to check AlreadyExists errors. (#260)
Don't rely on string matching.
2026-06-16 18:17:16 -07:00
Benjamin Elder 70beb3020f test: serialize envtest binary setup to fix concurrent-download race (#263)
## Problem

`go test -v ./...` (the `run-tests` job) intermittently fails during
envtest **setup** — not in any test logic:

- `fork/exec .../kubebuilder-envtest/k8s/1.36.0-linux-amd64/etcd: text
file busy`
- `setup-envtest: ... unable to create file kubectl from archive for
version-platform pair 1.36.0-linux-amd64`

Three packages use envtest (`cmd/ateapi/internal/controlapi`,
`cmd/atecontroller/internal/controllers`, `pkg/api/v1alpha1`). `go test
./...` runs them as separate test binaries **in parallel**, and each
`TestMain` shells out to `setup-envtest use`. On a cold cache they race
to download, extract, and exec the **shared** kubebuilder binaries —
hence `text file busy` (exec'ing etcd while it's still being written)
and the archive-extraction error.

Seen on `main` (e.g. the #259 push run) and on PRs. It only reproduces
on a **cold** cache, which is why it's intermittent and doesn't show up
locally once the cache is warm.

## Fix

Add `internal/envtestbins.BinaryAssetsDir()`, which runs `setup-envtest
use` under a **cross-process file lock** (`flock` on a fixed file under
`os.TempDir()`, shared by the sibling test binaries). The first caller
populates the shared cache while the others wait, then they hit a warm
no-op path lookup. The duplicated setup in all three `TestMain`s now
calls this helper.

The lock scope (user/machine-global) matches the resource it guards —
the kubebuilder binary cache is itself user-global, shared even across
repo checkouts.

## Testing

Reproduced locally by moving the warm cache aside and running the three
packages concurrently: it fails without the lock and passes with it.
Full `go test ./...` green.

Test-only change; no production code touched.
2026-06-16 18:11:11 -07:00
Dmitry Berkovich c1b51134e7 Actor Pause/Resume flow (#227)
Implements  `PAUSED` state for issue #119.

The snapshot files are kept locally on node VM in a separate folder. At
resume time, scheduler uses a node VM hint and picks up a worker from
the same node where files were stored at suspend time.

The local file management solution is temporary and will be replaced
once @msau42 introduces a new component that is supposed to manage files
on the node VM.

- [X] Tests pass
- [X] Manual tests with counter demo
```
>kubectl ate create actor my-counter-1 --template ate-demo-counter/counter
>curl -X POST -H "Host: my-counter-1.actors.resources.substrate.ate.dev" http://localhost:8000
hello from: 169.254.17.2 | preserved memory count: 1
>curl -X POST -H "Host: my-counter-1.actors.resources.substrate.ate.dev" http://localhost:8000
hello from: 169.254.17.2 | preserved memory count: 2
>kubectl ate pause actor my-counter-1 -o json
> curl -X POST -H "Host: my-counter-1.actors.resources.substrate.ate.dev" http://localhost:8000
hello from: 169.254.17.2 | preserved memory count: 3
```

### Breaking change
This PR introduces a breaking change in the Actor proto. All existing
actor needs to be recreated, prior testing PAUSE functionality.
2026-06-16 13:15:36 -07:00
Tim Hockin 68bb5efc8f Add more CRD validation (#205)
It's not all done but the fields we are playing with now are mostly
done.
2026-06-09 13:19:25 -04:00
Eitan Yarmush 0076b20c1a Support actortemplate secret env (#20)
A couple of TODOs/Discussions which I think are important to have based
on these changes.
1. Do we need a caching client for this to avoid calling the apiserver
every time?
2. Where/how do we want to resolve secrets generally? I think there are
good arguments to be made for the control-plane OR the "node" component.
In my mind this also related to:
https://github.com/agent-substrate/substrate/issues/18

Fixes #15 

> It's a good idea to open an issue first for discussion.

- [ x ] Tests pass
- [ x ] Appropriate changes to documentation are included in the PR
2026-06-08 16:04:50 -07:00
Grant McCloskey 06dbd13ace Feature/list actors scale w/ pagination (#156)
# Implement Pagination and Pipelining for ListActors

## Overview

**Fixes:** #153 

This PR introduces robust, memory-safe pagination to the `ListActors`
operation and optimizes the Valkey retrieval layer.

Previously, `ListActors` performed an unbounded parallel scatter-gather
across all Valkey cluster shards, pulling all actors into memory at
once. At scale (millions/billions of actors), this would cause OOM
errors and severe latency.

This update replaces the concurrent scatter-gather with a deterministic,
sequential shard scanning approach, adhering to
[AIP-158](https://google.aip.dev/158) pagination standards.

## Changes Included
- **gRPC API (`ateapi.proto`)**: Added `page_size`, `page_token`, and
`next_page_token` fields.
- **Client (`kubectl-ate`)**: Updated to seamlessly loop through
`next_page_token` values until all actors are fetched, presenting a
continuous stream to the user without blowing up memory on a single
request.
- **API Handler (`controlapi`)**: Bounded requests with a max
`page_size` limit of `1000`.
- **Persistence (`ateredis`)**: 
- Implemented `listActorsPageToken` to safely serialize and encode
cross-shard cursors.
- Replaced `ForEachMaster` concurrent execution with deterministic
sequential iteration.
- Implemented Valkey Pipeline batching (`Pipelined`) for `GET` requests
to eliminate N+1 network latency.
- **Testing**: Added `TestListActors_Pagination` at both the integration
level (`functional_test.go`) and data layer (`ateredis_test.go`).
Refactored sorting to use `cmpopts.SortSlices`.

## Design Decisions
During the design phase, several explicit trade-offs were made to
balance performance with API simplicity:
1. **Opaque Tokens (AIP-158):** The `page_token` is strictly an opaque
string at the `ateapi` layer. The client (`kubectl-ate`) is completely
unaware that it contains base64-encoded JSON mapping to `{"shard",
"cursor"}`. This keeps our API database-agnostic.
2. **Empty Pages & Client-Side Looping:** The persistence layer is kept
simple and "dumb". If a shard scan returns 0 keys but the cursor is not
`0`, we return an empty page. We rely on the client `kubectl-ate` to
`for` loop until `next_page_token` is completely exhausted. Update: I
have server side looping across shards until pagesize is met to limit
round trips due to high shard count or low actor count on distributed
shards.
3. **"Soft" Consistency (Missing/Duplicate Data):** `ListActors` is an
infrequent operations command, not on the critical path. Taking a global
cluster lock for iteration would severely degrade performance. We accept
eventual consistency here: if actors migrate shards during an active
scan, they might be duplicated or missed.
4. **Topology Flux Errors:** If a client passes a valid token pointing
to a shard address that no longer exists (due to a cluster resharding
mid-scan), the API simply returns an `Aborted` error. We do not attempt
complex recovery; the client must just restart the `ListActors` command.
5. **Bounded Page Size:** Requests are currently capped at a maximum of
`1000` to prevent single massive payloads. We can revisit/change the max
later.

NOTE: Actor filtering will be implemented later.

## Architecture Flow (Valkey)

Note: If there are future plugin-db models they will need to handle the
cusor/pages at their own implementation layer per the store.go
interface.

The system now seamlessly iterates across shards, handing off cursors
safely between nodes:

```mermaid
sequenceDiagram
    participant CLI as kubectl-ate
    participant API as ateapi
    participant Valkey as Valkey Cluster (Multiple Shards)
    
    CLI->>API: ListActors(page_size=1000, page_token="")
    
    Note over API: Request 1: Empty Token
    API->>API: Decode page_token (empty -> start fresh)
    API->>Valkey: Fetch all Master Nodes
    Valkey-->>API: [Master C, Master A, Master B]
    API->>API: Sort Masters alphabetically by IP/Port
    Note over API: Sorted: [Master A, Master B, Master C]
    
    API->>Valkey: SCAN (Master A, cursor=0, count=1000)
    Valkey-->>API: keys=[k1..k300], next_cursor=45
    
    Note over API: OPTIMIZATION: Pipelining avoids N+1 problem
    API->>Valkey: PIPELINE GET (k1..k300) directly on Master A
    Valkey-->>API: values=[v1..v300]
    
    API->>API: Decode Protobufs
    API->>API: Encode page_token={"shard": "Master A", "cursor": 45}
    API-->>CLI: 300 Actors, next_page_token="base64(eyJzaGFyZ..."
    
    %% SECOND REQUEST
    CLI->>API: ListActors(page_size=1000, page_token="base64(...)")
    
    Note over API: Request 2: Resuming Mid-Shard
    API->>API: Decode page_token -> Shard="Master A", Cursor=45
    API->>API: Fast-forward directly to Master A
    API->>Valkey: SCAN (Master A, cursor=45, count=1000)
    Valkey-->>API: keys=[k301..k400], next_cursor=0
    
    API->>Valkey: PIPELINE GET (k301..k400) directly on Master A
    Valkey-->>API: values=[v301..v400]
    
    Note over API: Shard A is empty. Token points to Shard B!
    API->>API: Encode page_token={"shard": "Master B", "cursor": 0}
    API-->>CLI: 100 Actors, next_page_token="base64(eyJzaGFyZ..."
    
    %% THIRD REQUEST
    CLI->>API: ListActors(page_size=1000, page_token="base64(...)")
    Note over API: Request 3: Starting New Shard
    API->>API: Decode page_token -> Shard="Master B", Cursor=0
    Note over API: Continues scanning Master B...
```


## Testing:

```
> ./bin/kubectl-ate get actors
NAMESPACE          TEMPLATE   ID                                     STATUS             ATEOM POD   ATEOM IP   VERSION
ate-demo-counter   counter    61b478cc-2e44-4ec7-ac6c-1af3f385de86   STATUS_SUSPENDED   <none>                 5
```

```
 ~/go/bin/grpcurl -insecure -d '{"page_size": 2}' localhost:9090 ateapi.Control/ListActors
{
  "actors": [
    {
      "actorId": "test-counter-4",
      "version": "1",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED"
    }
  ],
  "nextPageToken": "eyJzaGFyZCI6IjEwLjI0NC4wLjE5OjYzNzkiLCJjdXJzb3IiOjI1MjU1fQ=="
}
```


<details>

```
bash -c '
export NEXT_TOKEN=""
while :; do
  echo "--- Fetching Page ---"
  
  # Fetch the data and store the response
  RESPONSE=$(~/go/bin/grpcurl -insecure -d "{\"page_size\": 2, \"page_token\": \"$NEXT_TOKEN\"}" localhost:9090 ateapi.Control/ListActors)
  
  # Print the response to the terminal
  echo "$RESPONSE"
  
  # Extract the token without needing jq
  NEXT_TOKEN=$(echo "$RESPONSE" | grep -o "\"nextPageToken\": \"[^\"]*\"" | cut -d"\"" -f4)
  
  # If no token is found, we reached the end
  if [ -z "$NEXT_TOKEN" ]; then
    echo "--- Reached the end! ---"
    break
  fi
  
  echo ""
  read -p "Press Enter to fetch the next page using the new token..."
done
'
```

--- Fetching Page ---
{
  "actors": [
    {
      "actorId": "test-counter-4",
      "version": "1",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED"
    }
  ],
"nextPageToken":
"eyJzaGFyZCI6IjEwLjI0NC4wLjE5OjYzNzkiLCJjdXJzb3IiOjI1MjU1fQ=="
}

Press Enter to fetch the next page using the new token...
--- Fetching Page ---
{
"nextPageToken": "eyJzaGFyZCI6IjEwLjI0NC4wLjIxOjYzNzkiLCJjdXJzb3IiOjB9"
}

Press Enter to fetch the next page using the new token...
--- Fetching Page ---
{
  "actors": [
    {
      "actorId": "test-counter-1",
      "version": "1",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED"
    }
  ],
"nextPageToken":
"eyJzaGFyZCI6IjEwLjI0NC4wLjIxOjYzNzkiLCJjdXJzb3IiOjQ1NjAwfQ=="
}

Press Enter to fetch the next page using the new token...
--- Fetching Page ---
{
  "actors": [
    {
      "actorId": "test-counter-5",
      "version": "1",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED"
    }
  ],
"nextPageToken":
"eyJzaGFyZCI6IjEwLjI0NC4wLjIxOjYzNzkiLCJjdXJzb3IiOjYyMDg2fQ=="
}

Press Enter to fetch the next page using the new token...
--- Fetching Page ---
{
"nextPageToken": "eyJzaGFyZCI6IjEwLjI0NC4wLjI1OjYzNzkiLCJjdXJzb3IiOjB9"
}

Press Enter to fetch the next page using the new token...
--- Fetching Page ---
{
  "actors": [
    {
      "actorId": "test-counter-2",
      "version": "1",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED"
    }
  ],
"nextPageToken":
"eyJzaGFyZCI6IjEwLjI0NC4wLjI1OjYzNzkiLCJjdXJzb3IiOjE2OTkzfQ=="
}

Press Enter to fetch the next page using the new token...
--- Fetching Page ---
{
  "actors": [
    {
      "actorId": "test-counter-3",
      "version": "1",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED"
    },
    {
      "actorId": "61b478cc-2e44-4ec7-ac6c-1af3f385de86",
      "version": "5",
      "actorTemplateNamespace": "ate-demo-counter",
      "actorTemplateName": "counter",
      "status": "STATUS_SUSPENDED",
"lastSnapshot":
"gs://ate-snapshots/ate-demo-counter/61b478cc-2e44-4ec7-ac6c-1af3f385de86/2026-06-03T03:17:34Z-UMYVUZOWW6LDPS5X5FSG3KC4VV"
    }
  ]
}
--- Reached the end! ---

</details>
2026-06-03 15:06:28 -07:00
Benjamin Elder f46c67f0b7 ateom: avoid unix socket path length limits (#100)
Pod names alone can easily exceed the max length (253 > 107).

Add a symlink with the full pod name for debugging.

Follow-up #92 

> It's a good idea to open an issue first for discussion.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

---

TODO: I'm not sure about the symlink. It's annoying mapping pod =>
hash(pod) otherwise though. We could put a file inside the directory
with the pod name instead, but I think we should probably put the full
pod name _somewhere_ in the structure so it's easier to inspect these.
2026-06-03 11:37:52 -07:00
Tim Hockin 1ac5ba32a1 Fix boilerplate in go files 2026-05-31 19:45:36 -07:00
Taahir Ahmed 9291df0b6d ActorTemplate: Enforce that all images must be pinned 2026-05-28 13:25:10 -07:00
Davanum SrinivasandJulian Gutierrez Oschmann 1a852c80e0 ateapi/syncer: release actor when host pod is deleted (#75)
Today, if a worker pod that hosts an active actor is forcibly destroyed,
substrate does not migrate the actor. The actor's AteomPodName still
points at the dead pod and the router times out forwarding to a dead IP.

WorkerPoolSyncer's pod-informer DeleteFunc and soft-delete branch now
check whether the worker being removed is bound to an actor, and if so,
reset that actor to `STATUS_SUSPENDED` before the worker row goes away.

The new helper releaseActorOnDeadWorker reads the actor, only acts if
the actor still claims this worker (a concurrent SuspendActor or
DeleteActor that already advanced state is respected), clears the
pod-binding fields and InProgressSnapshot, preserves LastSnapshot, and
writes via version-checked UpdateActor. On ErrPersistenceRetry we drop
the attempt and let the next informer event retry — no separate lock, no
in-handler retry budget.

`STATUS_SUSPENDED` is reused rather than introducing a new state. The
post-orphan invariant is identical and reusing keeps the proto, printer,
dialer, and router contracts unchanged. The next request through atenet
triggers an implicit resume; findFreeWorker picks any free worker in the
pool; LastSnapshot is restored.

Test plan: `TestSyncer_DeleteBoundWorker_ClearsActor` covers RUNNING
actor → pod delete → SUSPENDED with cleared bind fields,
InProgressSnapshot dropped, LastSnapshot preserved. All existing tests
in `cmd/ateapi/internal/controlapi/` still pass.

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
Co-authored-by: Julian Gutierrez Oschmann <juliangut@google.com>
2026-05-27 16:17:33 -04:00
Davanum Srinivas 55923825c7 chore(hack): add golangci-lint
Adds golangci-lint v2.12.2 using the per-tool-module pattern under
hack/tools/golangci-lint/. The verifier at hack/verify/golangci-lint.sh
is auto-discovered by hack/verify-all.sh, so the existing CI verify
step picks it up with no workflow YAML change.

Ships .golangci.yaml with the standard linter set, a make lint
target, and a verify wrapper that pins GOOS=linux so ateom-gvisor's
netlink references typecheck on macOS the same way they do on CI.

Adjustments to keep the first run clean: two dead-code deletions
((*runsc).cmdList, MemoryPullCache.gcsClient), two SA9003 empty
branches replaced with slog.Error / t.Logf, one S1008 return-bool
simplification, and inline //nolint for one intentional best-effort
ReleaseLock plus four audit-tracked SA1019 deprecations.

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
2026-05-23 16:19:09 -07:00
Julian Gutierrez Oschmann b1fe163fcf Run tools/reorg tool.
This commit was automatically generated by the tools/reorg tool.

```
go run ./tools/reorg
go generate ./...
bash benchmarking/locust/generate_protos.sh
```
2026-05-22 14:43:26 -07:00