Repository navigation
Conversation
Add map<string, string> annotations to ResourceMetadata for decorative, non-queryable metadata on resources such as Actor and ActorTemplate. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Validate annotation keys as k8s-label-key while rejecting reserved ate.dev domains, cap entries at 16, and bound total key and value size at 4 KiB. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Copy incoming metadata.annotations onto the stored metadata during UpdateActor while keeping server-owned metadata fields intact, and add apivalidation, controlapi, and functionaltest coverage. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Add --annotations flag to kubectl-ate create actor and update actor for passing comma-separated key=value pairs (with --annotations="" clearing annotations on update), add unit tests for CLI flag parsing and ActorTemplate manifest round-trip, and document annotations in the API reference guide. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Benchmark protobuf serialization, update validation, PostgreSQL CRUD (CreateActor, GetActor, UpdateActor, ListActors at 100 and 1000 per page), and the cold ResumeActor/SuspendActor lifecycle with no annotations and with 4 KiB of annotations in four shapes: one or sixteen entries of random base64, which PostgreSQL stores out of line uncompressed, and one or sixteen entries of text with random identifiers, which it stores out of line compressed. The PostgreSQL sub-benchmarks store a ~1.4 KiB actor status so annotations push the row across the 2 KiB TOAST threshold. Each run reports pg_column_size, octet_length, TOAST chunk count and relation size, EXPLAIN (BUFFERS, SERIALIZE) block counts for GetActor, WAL bytes and table growth per UpdateActor and per resume/suspend cycle with the two halves timed separately, and the ListActors response size. Add storetest.Pool so tests can run SQL the store API does not expose, and widen the storetest helpers from *testing.T to testing.TB so benchmarks can provision isolated PostgreSQL test databases. Signed-off-by: Shankar Ganesan <shankgan@google.com>
setCreateMetadata always overwrote metadata.uid and metadata.create_time with fresh values. Keep a uid and create_time the control plane assigned before the insert so it can pass the final metadata to other parties ahead of persisting the row, and only generate them when the caller left them empty. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Define the substrate.hook.v1.LifecycleHook gRPC service that ate-api-server calls on Actor create and delete. OnCreateActor carries the actor with its server-assigned uid and create_time, the resolved ActorTemplate as read-only context, and the authenticated caller principal, and returns an allow/deny decision with annotation mutations. OnDeleteActor carries the actor reference, uid, and caller principal as a notification. The service ships behind a preview gate. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Add the YAML configuration for lifecycle hook targets: a shared maxWaitDuration (default 100ms, at most 250ms) and up to 8 hooks, each with an address, a list of annotation prefixes the hook may write, and TLS settings. A hook with no prefixes can allow or deny a create without mutating annotations. Validation requires TLS 1.3 on every target, exactly one CA trust source (caBundle or caCertFile), a serverName for DNS SAN verification, non-reserved annotation prefixes (rejecting ate.dev and its subdomains), and pairwise disjoint prefixes across hooks so their mutations merge without ordering conflicts. Build gRPC transport credentials from that config using standard Go crypto/tls DNS SAN verification and credbundle.ClientLoader so the client credential reloads per handshake. Signed-off-by: Shankar Ganesan <shankgan@google.com>
Dispatcher dials every configured hook at startup over the TLS credentials from the config and fans OnCreateActor out to all hooks in a single parallel wave under the shared wait budget. Each hook receives its own clone of the request. Responses are merged only after every key a hook returned is checked against that hook's allowed annotation prefixes and Kubernetes qualified-name syntax. An out-of-scope or malformed key fails closed with Internal. A hook blocks a create only by returning allowed=false. A hook that errors or misses the deadline is logged and skipped, and the create proceeds with the other hooks' mutations. OnCreateActor always returns a CreateOutcome populated with every hook that allowed the create and every hook that did not answer, even when OnCreateActor returns an error. Calling Rollback sends a one-shot compensating OnDeleteActor to those hooks so a single caller defer covers both intra-wave failures and later validation or store failures. OnDeleteActor notifies every hook in parallel and returns nothing. An error or timeout is logged and cannot block deletion. Errors map to PermissionDenied for a hook denial and Internal for a prefix or key syntax violation. Signed-off-by: Shankar Ganesan <shankgan@google.com>
…review gate Add the LifecycleHooks preview gate and the --lifecycle-hooks-config flag. When both are set, ate-api-server builds a HookDispatcher and calls it from the control plane: CreateActor acquires the same non-blocking actor lease that Update, Delete, Resume, and Suspend use, confirms the name is free, resolves the template, source tag, and initial external volumes, assigns metadata.uid and create_time, and calls OnCreateActor with the resolved template and the caller principal before the store insert. A single defer calls outcome.Rollback unless the store insert commits, so hooks that allowed the create or timed out are rolled back on any intra-wave or later validation or store error. DeleteActor calls OnDeleteActor inside a CallLifecycleDeleteHooks trace span after the actor's runtime, volumes, and snapshots are released and before the row is removed. The call cannot fail the deletion. Golden actors created and deleted by the template reconciler carry a context marker and skip hooks. With no config file the server behaves as before. Signed-off-by: Shankar Ganesan <shankgan@google.com>
…nd benchmark Add TestLifecycleHooksEndToEnd to exercise the full lifecycle hooks stack over TLS 1.3 and mTLS against real gRPC hook servers, lifecyclehook.LoadConfig, NewDispatcher, RPCService, and PostgreSQL. The test covers parallel annotation merging and pre-assigned metadata fidelity, hook denial and compensating OnDeleteActor rollbacks, post-hook annotation overflow and unpermitted prefix rollbacks, hook timeouts skipped on create and rolled back when a sibling hook denies, golden actor creation and deletion via ActorTemplateReconciler bypassing hooks, and DeleteActor notification resilience when hooks fail or time out. Add BenchmarkCreateActorLifecycleHooks to measure CreateActor throughput and allocations with zero, one, and three parallel mTLS hooks. Signed-off-by: Shankar Ganesan <shankgan@google.com>
shankgan
force-pushed
the
feat/lifecycle-hooks
branch
from
November 14, 2026 06:37
41c765f to
f993a43
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
External control-plane services that integrate with Substrate (such as workload identity authorities, service registries, and admission controllers) need a way to run policy checks, stamp
metadata.annotations, and allocate or clean up external per-actor state when anActoris created or deleted, without pollingListActors.Add opt-in control-plane lifecycle hooks on
CreateActorandDeleteActorbehind theLifecycleHookspreview gate and the--lifecycle-hooks-configflag onate-api-server:atepgpre-assigned metadata (32d1e086): Preserve caller-assignedmetadata.uidandmetadata.create_timeinsetCreateMetadatawhen set by the control plane before insert so hooks observe the sameuidandcreate_timethat get persisted.0775ae5e,api/hook/v1/hook.proto): Definesubstrate.hook.v1.LifecycleHookwith two unary RPCs (OnCreateActorandOnDeleteActor).OnCreateActorreceives theActor, the resolved parentActorTemplateas read-only context, andcaller_principal, and returnsallowed,status_message, andmutated_annotations.OnDeleteActorreceives(atespace, name, uid, caller_principal)as a notification with an empty response.2b0bc6c0,cmd/ateapi/internal/lifecyclehook): Parse--lifecycle-hooks-config(maxWaitDurationdefault100ms, max250ms; up to 8 hook targets with disjoint non-reservedallowedAnnotationPrefixes) and dial each target over TLS 1.3 (caBundleorcaCertFile+serverName, withMTLSclient credentials reloaded viacredbundle.ClientLoaderorTLSOnly).HookDispatcherand compensating rollback (d607f7c2): FanOnCreateActorout to all configured hooks in a single parallel wave undermaxWaitDuration. A hook blocks creation only by returningallowed: false; a hook that errors or times out is logged and skipped. Check returned annotation keys against the hook'sallowedAnnotationPrefixesand Kubernetes qualified-name syntax. Return aCreateOutcomewhoseRollbacksends compensatingOnDeleteActorcalls in parallel to every hook that allowed the create or timed out.controlapiintegration (0563453a): WhenLifecycleHooksis enabled and--lifecycle-hooks-configis set,CreateActoracquires the non-blocking per-actor lease (lease:actor:<atespace>:<name>), verifies the name is free, resolves the template, tag source, and initial external volumes, callsOnCreateActor, re-validates the mutated actor against the16-key /4 KiBannotation bounds, and rolls back via a singledeferif any hook denies, validation fails, orstore.CreateActorfails.DeleteActorcallsOnDeleteActorbefore removing the actor row. Golden actors created and deleted byActorTemplateReconcilerbypass hooks. When--lifecycle-hooks-configis unset,CreateActorandDeleteActorare unchanged.f993a435): AddTestLifecycleHooksEndToEndandBenchmarkCreateActorLifecycleHooksincmd/ateapi/internal/controlapi/functionaltest/lifecycle_hooks_test.goagainst three TLS 1.3 / mTLS gRPC hook servers and PostgreSQL.Benchmark Summary (
BenchmarkCreateActorLifecycleHooks)ns/op)actors/s)B/op)allocs/op)0_NoHooks2,165,702(2.17 ms)455.9119,6304721_SingleMTLSHook6,086,269(6.09 ms)163.4363,7111,0113_ParallelMTLSHooks6,171,949(6.17 ms)161.0660,8401,722Fixes #3819
AI use: AI-assisted, I reviewed and tested it.