From 68e0cc2585da55863ac5802c46ea46629354c104 Mon Sep 17 00:00:00 2001 From: Taahir Ahmed Date: Sat, 10 Oct 2026 12:05:23 +0000 Subject: [PATCH 1/9] cmd/atenet: Remove the router's "all" mode (#2441) All of our in-tree deployments explicitly deploy the router in either ingress or egress mode. Attempting to one run router instance that handles both ingress and egress seems pretty confusing, so we should get rid of the flag that lets us do so. In the future, we might want to split the ingress router and the egress router into separate atenet subcommands (or even separate binaries). --- cmd/atenet/internal/router/README.md | 9 ++-- cmd/atenet/internal/router/cmd.go | 2 +- cmd/atenet/internal/router/config.go | 13 +++--- cmd/atenet/internal/router/config_test.go | 45 +++++++++---------- cmd/atenet/internal/router/extproc/extproc.go | 7 +-- .../internal/router/extproc/extproc_test.go | 8 ++-- cmd/atenet/internal/router/health_test.go | 14 +++--- cmd/atenet/internal/router/router.go | 10 ++--- cmd/atenet/internal/router/status_test.go | 1 + manifests/ate-install/atenet-egress.yaml | 4 -- 10 files changed, 51 insertions(+), 62 deletions(-) diff --git a/cmd/atenet/internal/router/README.md b/cmd/atenet/internal/router/README.md index 5a02275b4a..fa5cda0200 100644 --- a/cmd/atenet/internal/router/README.md +++ b/cmd/atenet/internal/router/README.md @@ -175,21 +175,20 @@ state rather than a client header. ## modes -One binary serves both directions. `--mode` selects which: +One binary serves both directions, one per instance. `--mode` (required) +selects which: | `--mode` | ext_proc handlers | xDS server | Kubernetes access | | --- | --- | --- | --- | | `ingress` | ingress | yes | yes | | `egress` | egress | no | none | -| `all` (default) | both | yes | yes | The mux refuses a direction this instance was not started to serve (404) rather than falling back to the other handler, which would run the request through the wrong trust model. -Ingress and egress are deployed separately today — `atenet-router` fronts the -ingress dataplane, `atenet-egress` the egress gateway — because the two scale -independently, not because they need separate binaries. +Ingress and egress are deployed separately — `atenet-router` fronts the +ingress dataplane, `atenet-egress` the egress gateway. `--atenet-dataplane` selects the dataplane for both Deployments. Each gateway has its own static configuration because ingress and egress scale independently. diff --git a/cmd/atenet/internal/router/cmd.go b/cmd/atenet/internal/router/cmd.go index 0914a27f15..fa1ba49691 100644 --- a/cmd/atenet/internal/router/cmd.go +++ b/cmd/atenet/internal/router/cmd.go @@ -49,7 +49,7 @@ func NewRouterCmd() *cobra.Command { // bindRouterFlags registers the router's flags on fs, writing into cfg. func bindRouterFlags(fs *pflag.FlagSet, cfg *routerConfig) { - fs.StringVar((*string)(&cfg.Mode), "mode", string(ModeAll), fmt.Sprintf("Traffic direction this instance serves: %q (also runs the ingress control plane — the xDS server — for an Envoy dataplane), %q (ext_proc only, needs no Kubernetes access), or %q for both. The ext_proc mux refuses a direction this instance was not started to serve rather than falling back to the other one", ModeIngress, ModeEgress, ModeAll)) + fs.StringVar((*string)(&cfg.Mode), "mode", "", fmt.Sprintf("Required. Traffic direction this instance serves: %q (also runs the ingress control plane — the xDS server — for an Envoy dataplane) or %q (ext_proc only, needs no Kubernetes access). The ext_proc mux refuses the other direction rather than falling back to it", ModeIngress, ModeEgress)) fs.StringVar(&cfg.LogLevel, "log-level", "info", "Log level: debug, info, warn, error") diff --git a/cmd/atenet/internal/router/config.go b/cmd/atenet/internal/router/config.go index e257eb0048..5ed0d901df 100644 --- a/cmd/atenet/internal/router/config.go +++ b/cmd/atenet/internal/router/config.go @@ -51,18 +51,15 @@ const ( // Nothing else runs: the egress gateway is statically configured, so there // is no xDS server and no Kubernetes client. ModeEgress Mode = "egress" - // ModeAll serves both directions from one instance. This is the default, - // and what a single-gateway or local development setup wants. - ModeAll Mode = "all" ) // ServesIngress reports whether this mode answers ingress requests. It also // gates the ingress control plane: the xDS server that configures the ingress // dataplane (Envoy only). -func (m Mode) ServesIngress() bool { return m != ModeEgress } +func (m Mode) ServesIngress() bool { return m == ModeIngress } // ServesEgress reports whether this mode answers egress CONNECTs. -func (m Mode) ServesEgress() bool { return m != ModeIngress } +func (m Mode) ServesEgress() bool { return m == ModeEgress } // authConfig holds the router's mTLS settings for dialing ateapi. type authConfig struct { @@ -96,7 +93,7 @@ type credentialProviderConfig struct { // routerConfig holds deployment setup and endpoint options for the router node instance. type routerConfig struct { - // Mode restricts the instance to one traffic direction. Empty means ModeAll. + // Mode is the one traffic direction this instance serves. Required. Mode Mode AtenetRouter string Namespace string @@ -254,9 +251,9 @@ func (c routerConfig) validate() error { return fmt.Errorf("--atenet-dataplane must be %q or %q, got %q", atenetRouterEnvoy, atenetRouterAgentgateway, c.AtenetRouter) } switch c.Mode { - case "", ModeIngress, ModeEgress, ModeAll: + case ModeIngress, ModeEgress: default: - return fmt.Errorf("--mode must be one of %q, %q, or %q, got %q", ModeIngress, ModeEgress, ModeAll, c.Mode) + return fmt.Errorf("--mode must be %q or %q, got %q", ModeIngress, ModeEgress, c.Mode) } if err := c.ParkedRequest.Validate(); err != nil { return err diff --git a/cmd/atenet/internal/router/config_test.go b/cmd/atenet/internal/router/config_test.go index 5028d728ca..4754cae1fd 100644 --- a/cmd/atenet/internal/router/config_test.go +++ b/cmd/atenet/internal/router/config_test.go @@ -34,38 +34,38 @@ func TestRouterConfigValidate(t *testing.T) { }{ { name: "defaults are valid (auto breaker, atenet-router defaults to envoy)", - cfg: routerConfig{ExtProcMaxRequests: 0, ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}}, + cfg: routerConfig{Mode: ModeIngress, ExtProcMaxRequests: 0, ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}}, }, { name: "atenet-router set to envoy is valid", - cfg: routerConfig{AtenetRouter: string(atenetRouterEnvoy), ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}}, + cfg: routerConfig{Mode: ModeIngress, AtenetRouter: string(atenetRouterEnvoy), ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}}, }, { name: "atenet-router set to agentgateway is valid", - cfg: routerConfig{AtenetRouter: string(atenetRouterAgentgateway), ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}}, + cfg: routerConfig{Mode: ModeIngress, AtenetRouter: string(atenetRouterAgentgateway), ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}}, }, { name: "unknown router rejected", - cfg: routerConfig{AtenetRouter: "blah"}, + cfg: routerConfig{Mode: ModeIngress, AtenetRouter: "blah"}, wantErr: "--atenet-dataplane must be", }, { name: "negative extproc-max-requests rejected", - cfg: routerConfig{ExtProcMaxRequests: -1, ParkedRequest: ingress.ParkedRequestConfig{Max: 0}}, + cfg: routerConfig{Mode: ModeIngress, ExtProcMaxRequests: -1, ParkedRequest: ingress.ParkedRequestConfig{Max: 0}}, wantErr: "must not be negative", }, { name: "explicit breaker below the lot rejected", - cfg: routerConfig{ExtProcMaxRequests: 512, ParkedRequest: ingress.ParkedRequestConfig{Max: 1024}}, + cfg: routerConfig{Mode: ModeIngress, ExtProcMaxRequests: 512, ParkedRequest: ingress.ParkedRequestConfig{Max: 1024}}, wantErr: "must be >= --parked-request-max", }, { name: "explicit breaker equal to the lot accepted", - cfg: routerConfig{ExtProcMaxRequests: 1024, ParkedRequest: ingress.ParkedRequestConfig{Max: 1024}}, + cfg: routerConfig{Mode: ModeIngress, ExtProcMaxRequests: 1024, ParkedRequest: ingress.ParkedRequestConfig{Max: 1024}}, }, { name: "parking disabled ignores the relation", - cfg: routerConfig{ExtProcMaxRequests: 8, ParkedRequest: ingress.ParkedRequestConfig{Max: 0}}, + cfg: routerConfig{Mode: ModeIngress, ExtProcMaxRequests: 8, ParkedRequest: ingress.ParkedRequestConfig{Max: 0}}, }, { name: "explicit ingress mode accepted", @@ -76,39 +76,40 @@ func TestRouterConfigValidate(t *testing.T) { cfg: routerConfig{Mode: ModeEgress}, }, { - name: "explicit all mode accepted", - cfg: routerConfig{Mode: ModeAll}, + name: "missing mode rejected", + cfg: routerConfig{}, + wantErr: `--mode must be "ingress" or "egress"`, }, { - name: "unknown mode rejected", - cfg: routerConfig{Mode: "both"}, - wantErr: `--mode must be one of`, + name: "all mode rejected", + cfg: routerConfig{Mode: "all"}, + wantErr: `--mode must be "ingress" or "egress"`, }, { name: "drain-timeout below the parking budget rejected", - cfg: routerConfig{ParkedRequest: ingress.ParkedRequestConfig{Budget: 5 * time.Second, Max: 1024}, DrainTimeout: 2 * time.Second}, + cfg: routerConfig{Mode: ModeIngress, ParkedRequest: ingress.ParkedRequestConfig{Budget: 5 * time.Second, Max: 1024}, DrainTimeout: 2 * time.Second}, wantErr: "must be >= --parked-request-budget", }, { name: "drain-timeout equal to the parking budget accepted", - cfg: routerConfig{ParkedRequest: ingress.ParkedRequestConfig{Budget: 5 * time.Second, Max: 1024}, DrainTimeout: 5 * time.Second}, + cfg: routerConfig{Mode: ModeIngress, ParkedRequest: ingress.ParkedRequestConfig{Budget: 5 * time.Second, Max: 1024}, DrainTimeout: 5 * time.Second}, }, { name: "drain-timeout above the parking budget accepted", - cfg: routerConfig{ParkedRequest: ingress.ParkedRequestConfig{Budget: 5 * time.Second, Max: 1024}, DrainTimeout: 30 * time.Second}, + cfg: routerConfig{Mode: ModeIngress, ParkedRequest: ingress.ParkedRequestConfig{Budget: 5 * time.Second, Max: 1024}, DrainTimeout: 30 * time.Second}, }, { name: "short drain-timeout with parking disabled accepted", - cfg: routerConfig{ParkedRequest: ingress.ParkedRequestConfig{Max: 0}, DrainTimeout: time.Second}, + cfg: routerConfig{Mode: ModeIngress, ParkedRequest: ingress.ParkedRequestConfig{Max: 0}, DrainTimeout: time.Second}, }, { name: "negative drain-timeout rejected", - cfg: routerConfig{ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}, DrainTimeout: -time.Second}, + cfg: routerConfig{Mode: ModeIngress, ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}, DrainTimeout: -time.Second}, wantErr: "--drain-timeout must not be negative", }, { name: "negative drain-delay rejected", - cfg: routerConfig{ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}, DrainDelay: -time.Second}, + cfg: routerConfig{Mode: ModeIngress, ParkedRequest: ingress.ParkedRequestConfig{Max: ingress.DefaultParkedRequestMax}, DrainDelay: -time.Second}, wantErr: "--drain-delay must not be negative", }, } @@ -147,16 +148,14 @@ func TestRouterConfigAtenetRouter(t *testing.T) { } } -// The empty mode is what a routerConfig built in code (rather than from flags) -// carries, and it must behave as ModeAll so nothing silently stops serving. +// Each mode serves exactly one direction, and an unset mode serves neither. func TestModeServes(t *testing.T) { tests := []struct { mode Mode wantIngress bool wantEgress bool }{ - {mode: "", wantIngress: true, wantEgress: true}, - {mode: ModeAll, wantIngress: true, wantEgress: true}, + {mode: "", wantIngress: false, wantEgress: false}, {mode: ModeIngress, wantIngress: true, wantEgress: false}, {mode: ModeEgress, wantIngress: false, wantEgress: true}, } diff --git a/cmd/atenet/internal/router/extproc/extproc.go b/cmd/atenet/internal/router/extproc/extproc.go index 05f0f6fe48..49032be95b 100644 --- a/cmd/atenet/internal/router/extproc/extproc.go +++ b/cmd/atenet/internal/router/extproc/extproc.go @@ -130,11 +130,8 @@ func (s *Server) processRequestHeaders( start := time.Now() md := NewRequestMetadata(reqHeaders.GetHeaders().GetHeaders(), req.GetAttributes()) - // One atenet binary serves both directions, as two ext_proc handlers - // selected here. They are deployed separately today — atenet-router fronts - // the ingress dataplane, atenet-egress the egress gateway — because the two - // scale independently, and --mode restricts an instance to the direction - // its deployment fronts. Nothing stops a single instance from serving both. + // Both ingress and egress are built into one binary, but it will only + // handle one traffic direction (egress or ingress) per instance. // // Which handler runs is decided by the filter chain the dataplane says // accepted the request, never by anything in the request itself (see diff --git a/cmd/atenet/internal/router/extproc/extproc_test.go b/cmd/atenet/internal/router/extproc/extproc_test.go index 384b910ccd..e3f27f2630 100644 --- a/cmd/atenet/internal/router/extproc/extproc_test.go +++ b/cmd/atenet/internal/router/extproc/extproc_test.go @@ -49,14 +49,14 @@ func TestProcessRequestHeadersDispatchesByMode(t *testing.T) { wantStatus envoy_type.StatusCode // 0 means "expect success" }{ { - name: "both directions served, egress chain", - registered: []Direction{DirectionIngress, DirectionEgress}, + name: "egress-only instance serves egress traffic", + registered: []Direction{DirectionEgress}, chain: EgressFilterChainName, wantRan: DirectionEgress, }, { - name: "both directions served, ingress chain", - registered: []Direction{DirectionIngress, DirectionEgress}, + name: "ingress-only instance serves ingress traffic", + registered: []Direction{DirectionIngress}, chain: ingressHTTPListener, wantRan: DirectionIngress, }, diff --git a/cmd/atenet/internal/router/health_test.go b/cmd/atenet/internal/router/health_test.go index a44176c67d..a04892514b 100644 --- a/cmd/atenet/internal/router/health_test.go +++ b/cmd/atenet/internal/router/health_test.go @@ -94,7 +94,7 @@ func TestCheckDataplane(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - rh := newRouterHealth(time.Second, nil, nil, routerConfig{AtenetRouter: string(tc.router)}) + rh := newRouterHealth(time.Second, nil, nil, routerConfig{Mode: ModeIngress, AtenetRouter: string(tc.router)}) rh.dataplaneClient = &http.Client{Transport: healthRoundTripFunc(func(req *http.Request) (*http.Response, error) { if req.URL.String() != tc.wantURL { t.Errorf("health URL = %q, want %q", req.URL.String(), tc.wantURL) @@ -138,7 +138,7 @@ func TestCheckK8sTimesOut(t *testing.T) { })) defer server.Close() - rh := newRouterHealth(time.Second, newHealthTestClientset(t, server), nil, routerConfig{}) + rh := newRouterHealth(time.Second, newHealthTestClientset(t, server), nil, routerConfig{Mode: ModeIngress}) startedAt := time.Now() healthy, msg := rh.checkK8s(context.Background()) elapsed := time.Since(startedAt) @@ -155,7 +155,7 @@ func TestCheckK8sTimesOut(t *testing.T) { } func TestCheckK8sWithoutRESTClient(t *testing.T) { - rh := newRouterHealth(time.Second, kubernetesfake.NewSimpleClientset(), nil, routerConfig{}) + rh := newRouterHealth(time.Second, kubernetesfake.NewSimpleClientset(), nil, routerConfig{Mode: ModeIngress}) healthy, msg := rh.checkK8s(context.Background()) if healthy { t.Fatal("checkK8s returned healthy without a discovery REST client") @@ -183,7 +183,7 @@ func TestHealthCheckDoesNotBlockReportOrStatusz(t *testing.T) { })) defer server.Close() - rh := newRouterHealth(time.Second, newHealthTestClientset(t, server), nil, routerConfig{}) + rh := newRouterHealth(time.Second, newHealthTestClientset(t, server), nil, routerConfig{Mode: ModeIngress}) setHealthyDataplaneClient(rh) checkDone := make(chan struct{}) go func() { @@ -208,7 +208,7 @@ func TestHealthCheckDoesNotBlockReportOrStatusz(t *testing.T) { } statusServer := httptest.NewServer(http.HandlerFunc((&RouterServer{ - cfg: routerConfig{}, + cfg: routerConfig{Mode: ModeIngress}, health: rh, }).handleStatusz)) defer statusServer.Close() @@ -272,7 +272,7 @@ func TestHealthChecksRunConcurrently(t *testing.T) { } }, } - rh := newRouterHealth(time.Second, newHealthTestClientset(t, server), apiClient, routerConfig{}) + rh := newRouterHealth(time.Second, newHealthTestClientset(t, server), apiClient, routerConfig{Mode: ModeIngress}) rh.dataplaneClient = &http.Client{Transport: healthRoundTripFunc(func(*http.Request) (*http.Response, error) { started <- "dataplane" <-release @@ -325,7 +325,7 @@ func TestHealthStartStopsWhenK8sCheckIsCanceled(t *testing.T) { })) defer server.Close() - rh := newRouterHealth(time.Hour, newHealthTestClientset(t, server), nil, routerConfig{}) + rh := newRouterHealth(time.Hour, newHealthTestClientset(t, server), nil, routerConfig{Mode: ModeIngress}) setHealthyDataplaneClient(rh) ctx, cancel := context.WithCancel(context.Background()) startDone := make(chan struct{}) diff --git a/cmd/atenet/internal/router/router.go b/cmd/atenet/internal/router/router.go index 7fad480bf1..36ac759664 100644 --- a/cmd/atenet/internal/router/router.go +++ b/cmd/atenet/internal/router/router.go @@ -57,8 +57,8 @@ type RouterServer struct { Cmd *cobra.Command clientset kubernetes.Interface apiClient ateapipb.ControlClient - // extprocSrv is the ext_proc mux. Which handlers it carries — ingress, - // egress, or both — follows cfg.Mode. + // extprocSrv is the ext_proc mux. Which handler it carries, ingress or + // egress, follows cfg.Mode. extprocSrv *extproc.Server // ingressHandler is the ingress handler registered on extprocSrv, kept for // the status page's parking snapshot. Nil in egress-only mode. @@ -190,9 +190,9 @@ func (s *RouterServer) Run(ctx context.Context) error { g, ctx := errgroup.WithContext(ctx) - // Register one handler per direction this instance serves. The mux refuses - // any direction missing from this map, so the mode is enforced here rather - // than merely advertised. + // Register the handler for the direction this instance serves. The mux + // refuses any direction missing from this map, so the mode is enforced here + // rather than merely advertised. handlers := extproc.Handlers{} if s.cfg.Mode.ServesIngress() { parkMetrics, err := ingress.NewParkingMetrics() diff --git a/cmd/atenet/internal/router/status_test.go b/cmd/atenet/internal/router/status_test.go index a1b1280a0f..6fccee90d6 100644 --- a/cmd/atenet/internal/router/status_test.go +++ b/cmd/atenet/internal/router/status_test.go @@ -59,6 +59,7 @@ func TestStatuszEndpoint(t *testing.T) { caPath, clientCertPath := writeTestTLSMaterial(t) cfg := routerConfig{ + Mode: ModeIngress, Namespace: "default", StatusPort: httpPort, HttpPort: 8080, diff --git a/manifests/ate-install/atenet-egress.yaml b/manifests/ate-install/atenet-egress.yaml index ffd6f0cff8..805026ec3e 100644 --- a/manifests/ate-install/atenet-egress.yaml +++ b/manifests/ate-install/atenet-egress.yaml @@ -1379,10 +1379,6 @@ spec: # ext_proc handler (no xDS server, no Kubernetes access). The egress # Envoy calls it over localhost to # authenticate actor identity against the ate API on every CONNECT. - # - # Ingress and egress are separate Deployments because they scale - # independently, not because they need separate binaries: one instance can - # serve both directions with --mode=all. - name: ext-proc image: ko://github.com/agent-substrate/substrate/cmd/atenet args: From 5f9059d779efa2acfcbad7472671b42d8931f068 Mon Sep 17 00:00:00 2001 From: Tim Flannagan Date: Sat, 10 Oct 2026 12:06:28 +0000 Subject: [PATCH 2/9] test: Fix macos unit test failures (#2429) # Description `make test` failed on macOS for two host-specific reasons. The DNS relay referenced Linux-only network namespace APIs from an otherwise portable file, and the object storage tests inherited exported `AWS_PROFILE` and `AWS_DEFAULT_PROFILE` values from the developer's shell. Move only the namespace-serving method behind a Linux build tag so portable DNS tests still run on macOS. Clear both AWS profile selectors in the object storage test so local AWS configuration cannot affect it. - [x] Tests pass - [x] Appropriate changes to documentation are included in the PR --------- Signed-off-by: timflannagan --- internal/ateomnet/dns/relay.go | 40 ------------------ internal/ateomnet/dns/relay_linux.go | 63 ++++++++++++++++++++++++++++ pkg/objectstorage/env_test.go | 2 + 3 files changed, 65 insertions(+), 40 deletions(-) create mode 100644 internal/ateomnet/dns/relay_linux.go diff --git a/internal/ateomnet/dns/relay.go b/internal/ateomnet/dns/relay.go index 81e2854399..e7f421342e 100644 --- a/internal/ateomnet/dns/relay.go +++ b/internal/ateomnet/dns/relay.go @@ -20,10 +20,7 @@ import ( "log/slog" "net" "net/netip" - "strconv" "time" - - "github.com/agent-substrate/substrate/internal/ateomnet/netns" ) const ( @@ -99,43 +96,6 @@ func NewRelayForUpstreams(upstreams []string) (*Relay, error) { }, nil } -// Serve serves UDP and TCP DNS in the sandbox's local gateway namespace. -func (r *Relay) Serve(ctx context.Context, ns netns.Handle) (*Server, error) { - // Bind the wildcard because the microVM tap's gateway address is added later. - address := net.JoinHostPort("0.0.0.0", strconv.Itoa(dnsPort)) - - netC := netConn{ - dialer: *r.dialer, - } - - if err := netns.Do(ctx, ns, func(context.Context) error { - pc, err := net.ListenPacket("udp", address) - if err != nil { - return fmt.Errorf("while opening the actor DNS socket: %w", err) - } - netC.udp = pc - l, err := net.Listen("tcp", address) - if err != nil { - _ = pc.Close() - return fmt.Errorf("while opening the actor DNS listener: %w", err) - } - netC.tcpListener = l - return nil - }); err != nil { - return nil, err - } - - egressUDP, err := net.ListenPacket("udp", ":0") - if err != nil { - _ = netC.udp.Close() - _ = netC.tcpListener.Close() - return nil, fmt.Errorf("while opening the worker DNS egress socket: %w", err) - } - netC.egressUDP = egressUDP - - return r.serveOn(ctx, &netC) -} - // serveOn serves DNS on netC's sockets, which the caller has already bound, // and dials upstreams with netC's dialer. func (r *Relay) serveOn(ctx context.Context, netC *netConn) (*Server, error) { diff --git a/internal/ateomnet/dns/relay_linux.go b/internal/ateomnet/dns/relay_linux.go new file mode 100644 index 0000000000..53f0185b3f --- /dev/null +++ b/internal/ateomnet/dns/relay_linux.go @@ -0,0 +1,63 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dns + +import ( + "context" + "fmt" + "net" + "strconv" + + "github.com/agent-substrate/substrate/internal/ateomnet/netns" +) + +// Serve serves UDP and TCP DNS in the sandbox's local gateway namespace. +func (r *Relay) Serve(ctx context.Context, ns netns.Handle) (*Server, error) { + // Bind the wildcard because the microVM tap's gateway address is added later. + address := net.JoinHostPort("0.0.0.0", strconv.Itoa(dnsPort)) + + netC := netConn{ + dialer: *r.dialer, + } + + if err := netns.Do(ctx, ns, func(context.Context) error { + pc, err := net.ListenPacket("udp", address) + if err != nil { + return fmt.Errorf("while opening the actor DNS socket: %w", err) + } + netC.udp = pc + l, err := net.Listen("tcp", address) + if err != nil { + _ = pc.Close() + return fmt.Errorf("while opening the actor DNS listener: %w", err) + } + netC.tcpListener = l + return nil + }); err != nil { + return nil, err + } + + egressUDP, err := net.ListenPacket("udp", ":0") + if err != nil { + _ = netC.udp.Close() + _ = netC.tcpListener.Close() + return nil, fmt.Errorf("while opening the worker DNS egress socket: %w", err) + } + netC.egressUDP = egressUDP + + return r.serveOn(ctx, &netC) +} diff --git a/pkg/objectstorage/env_test.go b/pkg/objectstorage/env_test.go index 5b9b9e7e4e..ee02d368cc 100644 --- a/pkg/objectstorage/env_test.go +++ b/pkg/objectstorage/env_test.go @@ -41,6 +41,8 @@ func TestNewFromEnv(t *testing.T) { missing := filepath.Join(t.TempDir(), "missing") t.Setenv("AWS_CONFIG_FILE", missing) t.Setenv("AWS_SHARED_CREDENTIALS_FILE", missing) + t.Setenv("AWS_PROFILE", "") + t.Setenv("AWS_DEFAULT_PROFILE", "") t.Setenv("AWS_REGION", "us-east-1") t.Setenv("ATE_STORAGE_BACKEND", tc.backend) t.Setenv("AWS_S3_USE_PATH_STYLE", tc.pathStyle) From d1b2371d203eb9ef9c4047650bb0e3a467e7406e Mon Sep 17 00:00:00 2001 From: cloudonly Date: Sat, 10 Oct 2026 15:23:41 +0000 Subject: [PATCH 3/9] Update x/net to v0.60.0 to fix govulncheck failures (#2401) Both modules in the post-merge govulncheck matrix resolve golang.org/x/net v0.58.0 and fail on five newly published HTTP/2 vulnerabilities. Update x/net to v0.60.0 in the root module and the GCP Secret Manager plugin, include its required transitive dependency updates, and regenerate vendor. Go also updates the required x/crypto, x/sync, x/sys, x/term, and x/text versions. All first-party changes are in the two go.mod/go.sum pairs; the other changes are generated vendor updates. Affected advisories: GO-2026-6603, GO-2026-6610, GO-2026-6611, GO-2026-6612, and [GO-2026-6617](https://pkg.go.dev/vuln/GO-2026-6617). The [latest failing main run](https://github.com/agent-substrate/substrate/actions/runs/37911293778) already selects Go 1.27.2; its remaining failures are all in x/net. The earlier standard-library findings were resolved by the workflow selecting the patched stable toolchain. This change preserves the current Go version requirement. Validation after merging main `59072f7b`: - Linux/amd64 with Go 1.27.1: `go test -race -count=1 ./...` passed with PostgreSQL/envtest integration enabled (`CI=true`). - All checks in `bash hack/verify-all.sh` passed, including generated-code and vendor consistency, and the checkout remained clean. - GCP Secret Manager plugin race tests passed. - govulncheck v1.8.0 with Go 1.27.2: both root and plugin modules passed with exit 0, zero vulnerable imported packages, and zero reachable findings. Both report GO-2026-5932 only at module level in unimported x/crypto code. The merge retains main's direct x/net dependency classification at v0.60.0. Canonical v0.60.0 DNS parser content was restored in vendor after the generation check found that the automatic merge had retained three older statements. Verification ran as an unprivileged user. Root-gated mount tests and real cluster E2E were not run for this head. AI assistance was used to resolve the merge conflicts and run verification. --- go.mod | 12 +- go.sum | 32 +- internal/plugins/gcp-secret-manager/go.mod | 10 +- internal/plugins/gcp-secret-manager/go.sum | 20 +- tools/apitool/go.mod | 8 +- tools/apitool/go.sum | 16 +- .../x/net/dns/dnsmessage/message.go | 15 +- vendor/golang.org/x/net/http2/ascii.go | 14 +- vendor/golang.org/x/net/http2/databuffer.go | 5 +- vendor/golang.org/x/net/http2/flow.go | 103 +- vendor/golang.org/x/net/http2/frame.go | 34 +- vendor/golang.org/x/net/http2/server.go | 110 +- .../golang.org/x/net/http2/server_common.go | 47 + vendor/golang.org/x/net/http2/transport.go | 144 +- .../x/net/http2/transport_common.go | 79 +- .../golang.org/x/net/http2/transport_wrap.go | 216 +- vendor/golang.org/x/net/http2/writesched.go | 7 +- .../x/net/internal/httpcommon/README.md | 7 + .../x/net/internal/httpcommon/gzip.go | 132 ++ .../x/net/internal/httpcommon/request.go | 34 +- vendor/golang.org/x/net/trace/histogram.go | 5 +- .../golang.org/x/sync/semaphore/semaphore.go | 9 +- vendor/golang.org/x/sys/cpu/cpu.go | 21 +- vendor/golang.org/x/sys/cpu/cpu_gc_riscv64.go | 11 + .../golang.org/x/sys/cpu/cpu_linux_ppc64x.go | 4 + .../golang.org/x/sys/cpu/cpu_linux_riscv64.go | 11 + .../golang.org/x/sys/cpu/cpu_netbsd_amd64.go | 47 + vendor/golang.org/x/sys/cpu/cpu_other_x86.go | 2 +- vendor/golang.org/x/sys/cpu/cpu_riscv64.s | 17 + vendor/golang.org/x/sys/cpu/cpu_sparc64.go | 12 + vendor/golang.org/x/sys/unix/ifreq_linux.go | 8 +- vendor/golang.org/x/sys/unix/ioctl_linux.go | 7 + vendor/golang.org/x/sys/unix/mkerrors.sh | 9 +- vendor/golang.org/x/sys/unix/syscall.go | 2 +- vendor/golang.org/x/sys/unix/syscall_bsd.go | 2 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 2 +- vendor/golang.org/x/sys/unix/zerrors_linux.go | 2 + .../x/sys/unix/zerrors_linux_386.go | 1 - .../x/sys/unix/zerrors_linux_amd64.go | 1 - .../x/sys/unix/zerrors_linux_arm.go | 1 - .../x/sys/unix/zerrors_linux_arm64.go | 1 - .../x/sys/unix/zerrors_linux_loong64.go | 1 - .../x/sys/unix/zerrors_linux_mips.go | 1 - .../x/sys/unix/zerrors_linux_mips64.go | 1 - .../x/sys/unix/zerrors_linux_mips64le.go | 1 - .../x/sys/unix/zerrors_linux_mipsle.go | 1 - .../x/sys/unix/zerrors_linux_ppc.go | 1 - .../x/sys/unix/zerrors_linux_ppc64.go | 1 - .../x/sys/unix/zerrors_linux_ppc64le.go | 1 - .../x/sys/unix/zerrors_linux_riscv64.go | 1 - .../x/sys/unix/zerrors_linux_s390x.go | 1 - .../x/sys/unix/zerrors_linux_sparc64.go | 35 +- vendor/golang.org/x/sys/unix/ztypes_linux.go | 86 +- .../golang.org/x/sys/unix/ztypes_linux_386.go | 32 +- .../x/sys/unix/ztypes_linux_amd64.go | 32 +- .../golang.org/x/sys/unix/ztypes_linux_arm.go | 32 +- .../x/sys/unix/ztypes_linux_arm64.go | 32 +- .../x/sys/unix/ztypes_linux_loong64.go | 32 +- .../x/sys/unix/ztypes_linux_mips.go | 32 +- .../x/sys/unix/ztypes_linux_mips64.go | 32 +- .../x/sys/unix/ztypes_linux_mips64le.go | 32 +- .../x/sys/unix/ztypes_linux_mipsle.go | 32 +- .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 32 +- .../x/sys/unix/ztypes_linux_ppc64.go | 32 +- .../x/sys/unix/ztypes_linux_ppc64le.go | 32 +- .../x/sys/unix/ztypes_linux_riscv64.go | 32 +- .../x/sys/unix/ztypes_linux_s390x.go | 32 +- .../x/sys/unix/ztypes_linux_sparc64.go | 32 +- .../golang.org/x/sys/windows/types_windows.go | 1 + vendor/golang.org/x/term/terminal.go | 24 +- vendor/golang.org/x/text/unicode/bidi/core.go | 5 +- .../x/text/unicode/norm/composition.go | 42 +- .../x/text/unicode/norm/forminfo.go | 24 +- .../x/text/unicode/norm/normalize.go | 5 +- .../x/text/unicode/norm/tables15.0.0.go | 1884 ++++++++-------- .../x/text/unicode/norm/tables17.0.0.go | 1924 ++++++++--------- .../x/text/unicode/norm/transform.go | 5 +- vendor/modules.txt | 22 +- 78 files changed, 3513 insertions(+), 2254 deletions(-) create mode 100644 vendor/golang.org/x/net/internal/httpcommon/README.md create mode 100644 vendor/golang.org/x/net/internal/httpcommon/gzip.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gc_riscv64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_riscv64.s create mode 100644 vendor/golang.org/x/sys/cpu/cpu_sparc64.go diff --git a/go.mod b/go.mod index 88db75ee8a..d1b750a752 100644 --- a/go.mod +++ b/go.mod @@ -61,9 +61,9 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.45.0 go.opentelemetry.io/otel/trace v1.45.0 go.opentelemetry.io/proto/otlp v1.11.0 - golang.org/x/net v0.58.0 - golang.org/x/sync v0.22.0 - golang.org/x/sys v0.47.0 + golang.org/x/net v0.60.0 + golang.org/x/sync v0.23.0 + golang.org/x/sys v0.48.0 google.golang.org/api v0.274.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d google.golang.org/grpc v1.83.2 @@ -240,11 +240,11 @@ require ( go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/crypto v0.56.0 // indirect + golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/term v0.46.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.17.0 // indirect diff --git a/go.sum b/go.sum index ed34379a7c..64c57966a0 100644 --- a/go.sum +++ b/go.sum @@ -570,8 +570,8 @@ go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A= golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= @@ -581,8 +581,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -591,8 +591,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -601,8 +601,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -615,14 +615,14 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -634,8 +634,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/plugins/gcp-secret-manager/go.mod b/internal/plugins/gcp-secret-manager/go.mod index 7fcc28ee78..feb77e0d4b 100644 --- a/internal/plugins/gcp-secret-manager/go.mod +++ b/internal/plugins/gcp-secret-manager/go.mod @@ -29,12 +29,12 @@ require ( go.opentelemetry.io/otel/metric v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/crypto v0.56.0 // indirect - golang.org/x/net v0.58.0 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/net v0.60.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.22.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/sync v0.23.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect diff --git a/internal/plugins/gcp-secret-manager/go.sum b/internal/plugins/gcp-secret-manager/go.sum index 901d5e2f42..179110c738 100644 --- a/internal/plugins/gcp-secret-manager/go.sum +++ b/internal/plugins/gcp-secret-manager/go.sum @@ -62,18 +62,18 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= diff --git a/tools/apitool/go.mod b/tools/apitool/go.mod index 3af894b1d1..12aef88bf2 100644 --- a/tools/apitool/go.mod +++ b/tools/apitool/go.mod @@ -15,10 +15,10 @@ require ( require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.10 // indirect - golang.org/x/net v0.58.0 // indirect - golang.org/x/sync v0.22.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/net v0.60.0 // indirect + golang.org/x/sync v0.23.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect google.golang.org/grpc v1.83.2 // indirect ) diff --git a/tools/apitool/go.sum b/tools/apitool/go.sum index 7bb6c62a10..e2ae8a1ed7 100644 --- a/tools/apitool/go.sum +++ b/tools/apitool/go.sum @@ -38,14 +38,14 @@ go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbi go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= diff --git a/vendor/golang.org/x/net/dns/dnsmessage/message.go b/vendor/golang.org/x/net/dns/dnsmessage/message.go index cbe94a38f5..e99f7998bd 100644 --- a/vendor/golang.org/x/net/dns/dnsmessage/message.go +++ b/vendor/golang.org/x/net/dns/dnsmessage/message.go @@ -751,10 +751,7 @@ func (p *Parser) AllAnswers() ([]Resource, error) { // // Pre-allocate up to a certain limit, since p.header is // untrusted data. - n := int(p.header.answers) - if n > 20 { - n = 20 - } + n := min(int(p.header.answers), 20) as := make([]Resource, 0, n) for { a, err := p.Answer() @@ -804,10 +801,7 @@ func (p *Parser) AllAuthorities() ([]Resource, error) { // // Pre-allocate up to a certain limit, since p.header is // untrusted data. - n := int(p.header.authorities) - if n > 10 { - n = 10 - } + n := min(int(p.header.authorities), 10) as := make([]Resource, 0, n) for { a, err := p.Authority() @@ -857,10 +851,7 @@ func (p *Parser) AllAdditionals() ([]Resource, error) { // // Pre-allocate up to a certain limit, since p.header is // untrusted data. - n := int(p.header.additionals) - if n > 10 { - n = 10 - } + n := min(int(p.header.additionals), 10) as := make([]Resource, 0, n) for { a, err := p.Additional() diff --git a/vendor/golang.org/x/net/http2/ascii.go b/vendor/golang.org/x/net/http2/ascii.go index 17caa20586..3a7aed12c5 100644 --- a/vendor/golang.org/x/net/http2/ascii.go +++ b/vendor/golang.org/x/net/http2/ascii.go @@ -4,7 +4,9 @@ package http2 -import "strings" +import ( + "strings" +) // The HTTP protocols are defined in terms of ASCII, not Unicode. This file // contains helper functions which may use Unicode-aware functions which would @@ -43,6 +45,16 @@ func isASCIIPrint(s string) bool { return true } +// isASCII returns whether s is ASCII. +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] > 0x7f { + return false + } + } + return true +} + // asciiToLower returns the lowercase version of s if s is ASCII and printable, // and whether or not it was. func asciiToLower(s string) (lower string, ok bool) { diff --git a/vendor/golang.org/x/net/http2/databuffer.go b/vendor/golang.org/x/net/http2/databuffer.go index e6f55cbd16..9763ab134c 100644 --- a/vendor/golang.org/x/net/http2/databuffer.go +++ b/vendor/golang.org/x/net/http2/databuffer.go @@ -121,10 +121,7 @@ func (b *dataBuffer) Write(p []byte) (int, error) { // If the last chunk is empty, allocate a new chunk. Try to allocate // enough to fully copy p plus any additional bytes we expect to // receive. However, this may allocate less than len(p). - want := int64(len(p)) - if b.expected > want { - want = b.expected - } + want := max(int64(len(p)), b.expected) chunk := b.lastChunkOrAlloc(want) n := copy(chunk[b.w:], p) p = p[n:] diff --git a/vendor/golang.org/x/net/http2/flow.go b/vendor/golang.org/x/net/http2/flow.go index b7dbd18695..5066a39912 100644 --- a/vendor/golang.org/x/net/http2/flow.go +++ b/vendor/golang.org/x/net/http2/flow.go @@ -10,6 +10,9 @@ package http2 // flow control window update. const inflowMinRefresh = 4 << 10 +// maxFlowWindow is the maximum size of a flow control window. +const maxFlowWindow = (1 << 31) - 1 + // inflow accounts for an inbound flow control window. // It tracks both the latest window sent to the peer (used for enforcement) // and the accumulated unsent window. @@ -74,47 +77,99 @@ func takeInflows(f1, f2 *inflow, n uint32) bool { return true } -// outflow is the outbound flow control window's size. +// connOutflow is connection-level outbound flow control. +type connOutflow struct { + initial int32 // SETTINGS_INITIAL_WINDOW_SIZE, changes with settings updates + n int32 // connection-level flow control window + flowErr bool // set when a flow control error is encountered +} + +func (f *connOutflow) init() { + f.initial = initialWindowSize // initial stream window size + f.n = initialWindowSize // current connection window size +} + +func (f *connOutflow) changeInitialWindowSize(size int64) bool { + if size > maxFlowWindow { + f.flowErr = true + return false + } + f.initial = int32(size) + return true +} + +func (f *connOutflow) add(n int32) bool { + sum := int64(f.n) + int64(n) + if sum > maxFlowWindow { + f.flowErr = true + return false + } + f.n += n + return true +} + +// outflow is the stream-level outbound flow control window's size. type outflow struct { _ incomparable - // n is the number of DATA bytes we're allowed to send. - // An outflow is kept both on a conn and a per-stream. - n int32 + // delta is the difference between the stream's flow control window and + // the connection's initial window size (conn.initial). + // + // Another view is that delta is the number of flow control bytes provided to this + // stream in WINDOW_UPDATE frames, less the number of bytes sent on the stream. + delta int32 // conn points to the shared connection-level outflow that is - // shared by all streams on that conn. It is nil for the outflow - // that's on the conn directly. - conn *outflow + // shared by all streams on that conn. + conn *connOutflow } -func (f *outflow) setConnFlow(cf *outflow) { f.conn = cf } - -func (f *outflow) available() int32 { - n := f.n - if f.conn != nil && f.conn.n < n { - n = f.conn.n +func (f *outflow) available() (int32, bool) { + if f.conn == nil { + return maxFlowWindow, true // only happens in tests } - return n + if f.conn.flowErr { + // Block all sending once any stream observes a flow control error. + return 0, false + } + n := int64(f.conn.initial) + int64(f.delta) + if n > maxFlowWindow { + f.conn.flowErr = true + return 0, false + } + return min(int32(n), f.conn.n), true } func (f *outflow) take(n int32) { - if n > f.available() { - panic("internal error: took too much") + if f.conn == nil { + return // only happens in tests } - f.n -= n - if f.conn != nil { - f.conn.n -= n + avail, _ := f.available() + if n > avail { + panic("internal error: took too much") } + f.delta -= n + f.conn.n -= n } // add adds n bytes (positive or negative) to the flow control window. // It returns false if the sum would exceed 2^31-1. func (f *outflow) add(n int32) bool { - sum := f.n + n - if (sum > n) == (f.n > 0) { - f.n = sum - return true + if f.conn == nil { + return true // only happens in tests + } + avail := int64(f.conn.initial) + int64(f.delta) + if avail > maxFlowWindow { + // An earlier change to the initial window pushed this stream over the limit. + // This is a connection-level flow control error. + f.conn.flowErr = true + return false } - return false + if avail+int64(n) > maxFlowWindow { + // This update would push the stream over the limit. + // This is a stream-level flow control error. + return false + } + f.delta += n + return true } diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index be75badcc0..3363bbafc8 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -1711,7 +1711,8 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { mh := &MetaHeadersFrame{ HeadersFrame: hf, } - var remainSize = fr.maxHeaderListSize() + var headersRemainSize = fr.maxHeaderListSize() + var trailersRemainSize = fr.maxHeaderListSize() var sawRegular bool var invalid error // pseudo header field errors @@ -1743,14 +1744,29 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { return } - size := hf.Size() - if size > remainSize { + var remainSize *uint32 + var size uint32 + if hf.Name == "trailer" { + remainSize = &trailersRemainSize + fieldCount := strings.Count(hf.Value, ",") + 1 + // Rather than actually constructing hpack.HeaderField for each + // trailer field and cumulatively adding its Size, just do the math + // manually to avoid unnecessary work. This does make it so + // whitespaces after comma are counted against the budget, but that + // should be innocuous. + size = uint32(len(hf.Value)-fieldCount+1) + uint32(fieldCount)*hpack.HeaderField{}.Size() + } else { + remainSize = &headersRemainSize + size = hf.Size() + } + if size > *remainSize { hdec.SetEmitEnabled(false) mh.Truncated = true - remainSize = 0 + headersRemainSize = 0 + trailersRemainSize = 0 return } - remainSize -= size + *remainSize -= size mh.Fields = append(mh.Fields, hf) }) @@ -1766,10 +1782,10 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { // skip parsing the fragment and close the connection. // // "Too much" is either any CONTINUATION frame after we've already - // exceeded the max header list size (in which case remainSize is 0), - // or a frame whose encoded size is more than twice the remaining - // header list bytes we're willing to accept. - if int64(len(frag)) > int64(2*remainSize) { + // exceeded the max header list size (if so, both budgets are 0), or a + // frame whose encoded size is more than twice the remaining header + // list bytes we're willing to accept. + if int64(len(frag)) > 2*int64(headersRemainSize+trailersRemainSize) { if VerboseLogs { log.Printf("http2: header list too large") } diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index a7d2053b6c..a6cd66226d 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -280,7 +280,6 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon doneServing: make(chan struct{}), clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" advMaxStreams: conf.MaxConcurrentStreams, - initialStreamSendWindowSize: initialWindowSize, initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxFrameSize: initialMaxFrameSize, pingTimeout: conf.PingTimeout, @@ -320,7 +319,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon // These start at the RFC-specified defaults. If there is a higher // configured value for inflow, that will be updated when we send a // WINDOW_UPDATE shortly after sending SETTINGS. - sc.flow.add(initialWindowSize) + sc.flow.init() sc.inflow.init(initialWindowSize) sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) @@ -427,7 +426,7 @@ type serverConn struct { wroteFrameCh chan frameWriteResult // from writeFrameAsync -> serve, tickles more frame writes bodyReadCh chan bodyReadMsg // from handlers -> serve serveMsgCh chan interface{} // misc messages & code to send to / run on the serve loop - flow outflow // conn-wide (not stream-specific) outbound flow control + flow connOutflow // conn-wide (not stream-specific) outbound flow control inflow inflow // conn-wide inbound flow control tlsState *tls.ConnectionState // shared by all handlers, like net/http remoteAddrStr string @@ -441,6 +440,9 @@ type serverConn struct { sawFirstSettings bool // got the initial SETTINGS frame after the preface needToSendSettingsAck bool unackedSettings int // how many SETTINGS have we sent without ACKs? + pendingEncoderTableSize bool // peer changed SETTINGS_HEADER_TABLE_SIZE; apply to hpackEncoder before the next frame write + encoderTableSizeMin uint32 // smallest SETTINGS_HEADER_TABLE_SIZE since the last apply + encoderTableSize uint32 // latest SETTINGS_HEADER_TABLE_SIZE queuedControlFrames int // control frames in the writeSched queue clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit) advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client @@ -451,7 +453,6 @@ type serverConn struct { maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes streams map[uint32]*stream unstartedHandlers []unstartedHandler - initialStreamSendWindowSize int32 initialStreamRecvWindowSize int32 maxFrameSize int32 peerMaxHeaderListSize uint32 // zero means unknown (default) @@ -521,7 +522,8 @@ type stream struct { // immutable: sc *serverConn id uint32 - body *pipe // non-nil if expecting DATA frames + body *pipe // non-nil if expecting DATA frames + reqBody *requestBody cw closeWaiter // closed wait stream transitions to closed state ctx context.Context cancelCtx func() @@ -1172,6 +1174,16 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) { sc.writingFrame = true sc.needsFrameFlush = true + if sc.pendingEncoderTableSize { + // hpackEncoder may be in use by writeFrameAsync, so SETTINGS + // changes to it are deferred until no frame is being written. + // Replaying the smallest size before the latest one keeps the + // encoder's view identical to having applied every change + // (RFC 7541, Section 4.2). + sc.pendingEncoderTableSize = false + sc.hpackEncoder.SetMaxDynamicTableSize(sc.encoderTableSizeMin) + sc.hpackEncoder.SetMaxDynamicTableSize(sc.encoderTableSize) + } if wr.write.staysWithinBuffer(sc.bw.Available()) { sc.writingFrameAsync = false err := wr.write.writeFrame(sc) @@ -1271,6 +1283,11 @@ func (sc *serverConn) scheduleFrameWrite() { } sc.inFrameScheduleLoop = true for !sc.writingFrameAsync { + if sc.flow.flowErr && (!sc.inGoAway || sc.goAwayCode == ErrCodeNo) { + sc.inGoAway = true + sc.needToSendGoAway = true + sc.goAwayCode = ErrCodeFlowControl + } if sc.needToSendGoAway { sc.needToSendGoAway = false sc.startFrameWrite(FrameWriteRequest{ @@ -1294,6 +1311,9 @@ func (sc *serverConn) scheduleFrameWrite() { sc.startFrameWrite(wr) continue } + if sc.flow.flowErr { + continue + } } if sc.needsFrameFlush { sc.startFrameWrite(FrameWriteRequest{write: flushFrameWriter{}}) @@ -1526,6 +1546,10 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error { return nil } if !st.flow.add(int32(f.Increment)) { + if st.flow.conn.flowErr { + // This is a lazily-detected connection-level flow control error. + return sc.countError("bad_flow", ConnectionError(ErrCodeFlowControl)) + } return sc.countError("bad_flow", streamError(f.StreamID, ErrCodeFlowControl)) } default: // connection-level flow control @@ -1584,10 +1608,6 @@ func (sc *serverConn) closeStream(st *stream, err error) { } } if p := st.body; p != nil { - // Return any buffered unread bytes worth of conn-level flow control. - // See golang.org/issue/16481 - sc.sendWindowUpdate(nil, p.Len()) - p.CloseWithError(err) } if e, ok := err.(StreamError); ok { @@ -1641,7 +1661,12 @@ func (sc *serverConn) processSetting(s Setting) error { } switch s.ID { case SettingHeaderTableSize: - sc.hpackEncoder.SetMaxDynamicTableSize(s.Val) + // Applied by startFrameWrite; see comment there. + if !sc.pendingEncoderTableSize || s.Val < sc.encoderTableSizeMin { + sc.encoderTableSizeMin = s.Val + } + sc.encoderTableSize = s.Val + sc.pendingEncoderTableSize = true case SettingEnablePush: sc.pushEnabled = s.Val != 0 case SettingMaxConcurrentStreams: @@ -1672,28 +1697,14 @@ func (sc *serverConn) processSetting(s Setting) error { func (sc *serverConn) processSettingInitialWindowSize(val uint32) error { sc.serveG.check() - // Note: val already validated to be within range by - // processSetting's Valid call. - - // "A SETTINGS frame can alter the initial flow control window - // size for all current streams. When the value of - // SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST - // adjust the size of all stream flow control windows that it - // maintains by the difference between the new value and the - // old value." - old := sc.initialStreamSendWindowSize - sc.initialStreamSendWindowSize = int32(val) - growth := int32(val) - old // may be negative - for _, st := range sc.streams { - if !st.flow.add(growth) { - // 6.9.2 Initial Flow Control Window Size - // "An endpoint MUST treat a change to - // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow - // control window to exceed the maximum size as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR." - return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl)) - } + if !sc.flow.changeInitialWindowSize(int64(val)) { + // 6.9.2 Initial Flow Control Window Size + // "An endpoint MUST treat a change to + // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow + // control window to exceed the maximum size as a + // connection error (Section 5.4.1) of type + // FLOW_CONTROL_ERROR." + return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl)) } return nil } @@ -1966,7 +1977,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { if st.reqTrailer != nil { st.trailer = make(http.Header) } - st.body = req.Body.(*requestBody).pipe // may be nil + st.body = st.reqBody.pipe // may be nil st.declBodyBytes = req.ContentLength handler := sc.handler.ServeHTTP @@ -1989,7 +2000,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) } - return sc.scheduleHandler(id, rw, req, handler) + return sc.scheduleHandler(st, rw, req, handler) } func (sc *serverConn) upgradeRequest(req *http.Request) { @@ -2102,7 +2113,6 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority } st.cw.Init() st.flow.conn = &sc.flow // link to conn-level counter - st.flow.add(sc.initialStreamSendWindowSize) st.inflow.init(sc.initialStreamRecvWindowSize) if sc.hs.WriteTimeout > 0 { st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) @@ -2184,7 +2194,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res } else { req.ContentLength = -1 } - req.Body.(*requestBody).pipe = &pipe{ + st.reqBody.pipe = &pipe{ b: &dataBuffer{expected: req.ContentLength}, } } @@ -2204,7 +2214,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol)) } - body := &requestBody{ + st.reqBody = &requestBody{ conn: sc, stream: st, needsContinue: res.NeedsContinue, @@ -2220,7 +2230,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server ProtoMinor: 0, TLS: tlsState, Host: rp.Authority, - Body: body, + Body: st.reqBody, Trailer: res.Trailer, }).WithContext(st.ctx) rw := sc.newResponseWriter(st, req) @@ -2244,11 +2254,12 @@ type unstartedHandler struct { rw *responseWriter req *http.Request handler func(http.ResponseWriter, *http.Request) + body *pipe } // scheduleHandler starts a handler goroutine, // or schedules one to start as soon as an existing handler finishes. -func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { +func (sc *serverConn) scheduleHandler(st *stream, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { sc.serveG.check() maxHandlers := sc.advMaxStreams if sc.curHandlers < maxHandlers { @@ -2260,10 +2271,11 @@ func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req * return sc.countError("too_many_early_resets", ConnectionError(ErrCodeEnhanceYourCalm)) } sc.unstartedHandlers = append(sc.unstartedHandlers, unstartedHandler{ - streamID: streamID, + streamID: st.id, rw: rw, req: req, handler: handler, + body: st.body, }) return nil } @@ -2277,6 +2289,10 @@ func (sc *serverConn) handlerDone() { u := sc.unstartedHandlers[i] if sc.streams[u.streamID] == nil { // This stream was reset before its goroutine had a chance to start. + if u.body != nil { + u.body.BreakWithError(errClosedBody) + sc.sendWindowUpdate(nil, u.body.Len()) + } continue } if sc.curHandlers >= maxHandlers { @@ -2298,6 +2314,12 @@ func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler didPanic := true defer func() { rw.rws.stream.cancelCtx() + if b := rw.rws.stream.reqBody; b != nil { + // Closing the body refunds flow control credit for any unconsumed data. + // (reqBody is nil for Upgrade: h2c requests, but those do not use flow + // control for the request body.) + b.Close() + } if req.MultipartForm != nil { req.MultipartForm.RemoveAll() } @@ -2396,7 +2418,7 @@ func (sc *serverConn) noteBodyReadFromHandler(st *stream, n int, err error) { func (sc *serverConn) noteBodyRead(st *stream, n int) { sc.serveG.check() sc.sendWindowUpdate(nil, n) // conn-level - if st.state != stateHalfClosedRemote && st.state != stateClosed { + if st != nil && st.state != stateHalfClosedRemote && st.state != stateClosed { // Don't send this WINDOW_UPDATE if the stream is closed // remotely. sc.sendWindowUpdate(st, n) @@ -2444,6 +2466,9 @@ func (b *requestBody) Close() error { b.closeOnce.Do(func() { if b.pipe != nil { b.pipe.BreakWithError(errClosedBody) + if unread := b.pipe.Len(); unread > 0 { + b.conn.noteBodyReadFromHandler(nil, unread, errClosedBody) + } } }) return nil @@ -2461,9 +2486,6 @@ func (b *requestBody) Read(p []byte) (n int, err error) { if err == io.EOF { b.sawEOF = true } - if b.conn == nil { - return - } b.conn.noteBodyReadFromHandler(b.stream, n, err) return } diff --git a/vendor/golang.org/x/net/http2/server_common.go b/vendor/golang.org/x/net/http2/server_common.go index 449538c861..fec369092a 100644 --- a/vendor/golang.org/x/net/http2/server_common.go +++ b/vendor/golang.org/x/net/http2/server_common.go @@ -25,6 +25,8 @@ import ( // // https://golang.org/pkg/net/http/#ResponseWriter // https://golang.org/pkg/net/http/#example_ResponseWriter_trailers +// +// Deprecated: Use [http.TrailerPrefix] instead. const TrailerPrefix = "Trailer:" // Push errors. @@ -38,16 +40,22 @@ var ( // The configuration conf may be nil. // // ConfigureServer must be called before s begins serving. +// +// Deprecated: Set [http.Server.Protocols] instead. func ConfigureServer(s *http.Server, conf *Server) error { return configureServer(s, conf) } // Server is an HTTP/2 server. +// +// Deprecated: Use [http.Server] instead. type Server struct { // MaxHandlers limits the number of http.Handler ServeHTTP goroutines // which may run at a time over all connections. // Negative or zero no limit. // TODO: implement + // + // Deprecated: This field has never had any effect. MaxHandlers int // MaxConcurrentStreams optionally specifies the number of @@ -56,6 +64,9 @@ type Server struct { // which may be active globally, which is MaxHandlers. // If zero, MaxConcurrentStreams defaults to at least 100, per // the HTTP/2 spec's recommendations. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.MaxConcurrentStreams] instead. MaxConcurrentStreams uint32 // MaxDecoderHeaderTableSize optionally specifies the http2 @@ -63,44 +74,67 @@ type Server struct { // informs the remote endpoint of the maximum size of the header compression // table used to decode header blocks, in octets. If zero, the default value // of 4096 is used. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.MaxDecoderHeaderTableSize] instead. MaxDecoderHeaderTableSize uint32 // MaxEncoderHeaderTableSize optionally specifies an upper limit for the // header compression table used for encoding request headers. Received // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero, // the default value of 4096 is used. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.MaxEncoderHeaderTableSize] instead. MaxEncoderHeaderTableSize uint32 // MaxReadFrameSize optionally specifies the largest frame // this server is willing to read. A valid value is between // 16k and 16M, inclusive. If zero or otherwise invalid, a // default value is used. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.MaxReadFrameSize] instead. MaxReadFrameSize uint32 // PermitProhibitedCipherSuites, if true, permits the use of // cipher suites prohibited by the HTTP/2 spec. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.PermitProhibitedCipherSuites] instead. PermitProhibitedCipherSuites bool // IdleTimeout specifies how long until idle clients should be // closed with a GOAWAY frame. PING frames are not considered // activity for the purposes of IdleTimeout. // If zero or negative, there is no timeout. + // + // Deprecated: Use [http.Server.IdleTimeout] instead. IdleTimeout time.Duration // ReadIdleTimeout is the timeout after which a health check using a ping // frame will be carried out if no frame is received on the connection. // If zero, no health check is performed. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.SendPingTimeout] instead. ReadIdleTimeout time.Duration // PingTimeout is the timeout after which the connection will be closed // if a response to a ping is not received. // If zero, a default of 15 seconds is used. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.PingTimeout] instead. PingTimeout time.Duration // WriteByteTimeout is the timeout after which a connection will be // closed if no data can be written to it. The timeout begins when data is // available to write, and is extended whenever any bytes are written. // If zero or negative, there is no timeout. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.WriteByteTimeout] instead. WriteByteTimeout time.Duration // MaxUploadBufferPerConnection is the size of the initial flow @@ -108,12 +142,18 @@ type Server struct { // allow this to be smaller than 65535 or larger than 2^32-1. // If the value is outside this range, a default value will be // used instead. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.MaxReceiveBufferPerConnection] instead. MaxUploadBufferPerConnection int32 // MaxUploadBufferPerStream is the size of the initial flow control // window for each stream. The HTTP/2 spec does not allow this to // be larger than 2^32-1. If the value is zero or larger than the // maximum, a default value will be used instead. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.MaxReceiveBufferPerStream] instead. MaxUploadBufferPerStream int32 // NewWriteScheduler constructs a write scheduler for a connection. @@ -126,6 +166,9 @@ type Server struct { // It's intended to increment a metric for monitoring, such // as an expvar or Prometheus metric. // The errType consists of only ASCII word characters. + // + // Deprecated: Use [http.Server.HTTP2] and + // [http.HTTP2Config.CountError] instead. CountError func(errType string) // Internal state. This is a pointer (rather than embedded directly) @@ -135,6 +178,8 @@ type Server struct { } // ServeConnOpts are options for the Server.ServeConn method. +// +// Deprecated: ServeConnOpts is deprecated. type ServeConnOpts struct { // Context is the base context to use. // If nil, context.Background is used. @@ -178,6 +223,8 @@ type ServeConnOpts struct { // implemented in terms of providing a suitably-behaving net.Conn. // // The opts parameter is optional. If nil, default values are used. +// +// Deprecated: Use [http.Server.Serve] or [http.Server.ServeTLS] instead. func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { if opts == nil { opts = &ServeConnOpts{} diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 08ac409b0e..d894c6019c 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -27,6 +27,7 @@ import ( "net/http" "net/http/httptrace" "net/textproto" + "slices" "strconv" "sync" "sync/atomic" @@ -184,6 +185,8 @@ func (t *Transport) initConnPool() { // ClientConn is the state of a single HTTP/2 client connection to an // HTTP/2 server. +// +// Deprecated: Use [http.ClientConn] instead. type ClientConn struct { t *Transport tconn net.Conn // usually *tls.Conn, except specialized impls @@ -201,7 +204,7 @@ type ClientConn struct { mu sync.Mutex // guards following cond *sync.Cond // hold mu; broadcast on flow/closed changes - flow outflow // our conn-level flow control quota (cs.outflow is per stream) + flow connOutflow // our conn-level flow control quota (cs.outflow is per stream) inflow inflow // peer's conn-level flow control doNotReuse bool // whether conn is marked to not be reused for any future requests closing bool @@ -225,7 +228,6 @@ type ClientConn struct { maxConcurrentStreams uint32 peerMaxHeaderListSize uint64 peerMaxHeaderTableSize uint32 - initialWindowSize uint32 initialStreamRecvWindowSize int32 readIdleTimeout time.Duration pingTimeout time.Duration @@ -470,7 +472,6 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook readerDone: make(chan struct{}), nextStreamID: 1, maxFrameSize: 16 << 10, // spec default - initialWindowSize: 65535, // spec default initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. strictMaxConcurrentStreams: conf.StrictMaxConcurrentRequests, @@ -495,7 +496,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook } cc.cond = sync.NewCond(&cc.mu) - cc.flow.add(int32(initialWindowSize)) + cc.flow.init() // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. @@ -874,6 +875,33 @@ func (cc *ClientConn) sendGoAway() error { return nil } +func (cc *ClientConn) goAwayAndClose(code ErrCode) { + cc.mu.Lock() + closed := cc.closed + cc.closing = true + cc.closed = true + cc.mu.Unlock() + if closed { + return + } + if f := cc.fr.countError; f != nil { + f(fmt.Sprintf("conn_close_error_%s", code.stringToken())) + } + done := make(chan struct{}) + go func() { + defer close(done) + cc.wmu.Lock() + cc.fr.WriteGoAway(0, code, nil) + cc.bw.Flush() + cc.wmu.Unlock() + }() + select { + case <-done: + case <-time.After(250 * time.Millisecond): + } + cc.closeForError(fmt.Errorf("http2: closing connection with %v", code)) +} + // closes the client connection immediately. In-flight requests are interrupted. // err is sent to streams. func (cc *ClientConn) closeForError(err error) { @@ -1317,7 +1345,9 @@ func (cs *clientStream) cleanupWriteRequest(err error) { } if err != nil { cs.abortStream(err) // possibly redundant, but harmless - if cs.sentHeaders { + if ce, ok := err.(ConnectionError); ok { + cc.goAwayAndClose(ErrCode(ce)) + } else if cs.sentHeaders { if se, ok := err.(StreamError); ok { if se.Cause != errFromPeer { cc.writeStreamReset(cs.ID, se.Code, false, err) @@ -1646,8 +1676,12 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) return 0, errRequestCanceled default: } - if a := cs.flow.available(); a > 0 { - take := a + avail, ok := cs.flow.available() + if !ok { + return 0, ConnectionError(ErrCodeFlowControl) + } + if avail > 0 { + take := avail if int(take) > maxBytes { take = int32(maxBytes) // can't truncate int; take is int32 @@ -1708,8 +1742,7 @@ type resAndError struct { // requires cc.mu be held. func (cc *ClientConn) addStreamLocked(cs *clientStream) { - cs.flow.add(int32(cc.initialWindowSize)) - cs.flow.setConnFlow(&cc.flow) + cs.flow.conn = &cc.flow cs.inflow.init(cc.initialStreamRecvWindowSize) cs.ID = cc.nextStreamID cc.nextStreamID += 2 @@ -2093,18 +2126,42 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra return nil, nil } + // Delete various headers that might mess up framing for HTTP/1. This is + // not a problem for HTTP/2, but someone might use HTTP/2 transport as a + // reverse proxy which forwards the response to an HTTP/1 client. Our + // HTTP/1 client transport will properly reject improper headers such as + // multiple conflicting Content-Length headers, but other implementations + // might not. + // TODO: just reject such responses? We deleted them for compatibility + // since this was done in a security fix (go.dev/issue/81115). However, + // rejecting them seems entirely reasonable and relatively safe. + + // Connection-specific header fields must not appear in an HTTP/2 message, + // and any message containing them is malformed. RFC 9113, Section 8.2.2. + for _, k := range connHeaders { + delete(res.Header, k) + } res.ContentLength = -1 - if clens := res.Header["Content-Length"]; len(clens) == 1 { - if cl, err := strconv.ParseUint(clens[0], 10, 63); err == nil { - res.ContentLength = int64(cl) + if clens, ok := res.Header["Content-Length"]; ok { + // Repeated Content-Length values may be collapsed into one only if + // they are identical per RFC 9110 Section 8.6. + // No need to trim whitespace, HTTP/2 header values must not have + // extraneous whitespace per RFC 9113 Section 8.2.1. + // Non-canonical headers are already rejected by our framer at this + // point. + conflicting := slices.ContainsFunc(clens[1:], func(clen string) bool { + return clen != clens[0] + }) + cl, err := strconv.ParseUint(clens[0], 10, 63) + if conflicting || err != nil { + delete(res.Header, "Content-Length") } else { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. + res.Header["Content-Length"] = clens[:1] + res.ContentLength = int64(cl) } - } else if len(clens) > 1 { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } else if f.StreamEnded() && !cs.isHead { + } + + if res.ContentLength < 0 && f.StreamEnded() && !cs.isHead { res.ContentLength = 0 } @@ -2415,6 +2472,10 @@ const ( func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientStream { rl.cc.mu.Lock() defer rl.cc.mu.Unlock() + return rl.streamByIDLocked(id, headerOrData) +} + +func (rl *clientConnReadLoop) streamByIDLocked(id uint32, headerOrData bool) *clientStream { if headerOrData { // Work around an unfortunate gRPC behavior. // See comment on ClientConn.rstStreamPingsBlocked for details. @@ -2497,24 +2558,10 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { case SettingMaxHeaderListSize: cc.peerMaxHeaderListSize = uint64(s.Val) case SettingInitialWindowSize: - // Values above the maximum flow-control - // window size of 2^31-1 MUST be treated as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR. - if s.Val > math.MaxInt32 { + if !cc.flow.changeInitialWindowSize(int64(s.Val)) { return ConnectionError(ErrCodeFlowControl) } - - // Adjust flow control of currently-open - // frames by the difference of the old initial - // window size and this one. - delta := int32(s.Val) - int32(cc.initialWindowSize) - for _, cs := range cc.streams { - cs.flow.add(delta) - } cc.cond.Broadcast() - - cc.initialWindowSize = s.Val case SettingHeaderTableSize: cc.henc.SetMaxDynamicTableSize(s.Val) cc.peerMaxHeaderTableSize = s.Val @@ -2556,29 +2603,30 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { cc := rl.cc - cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame) - if f.StreamID != 0 && cs == nil { - return nil - } - cc.mu.Lock() defer cc.mu.Unlock() - - fl := &cc.flow - if cs != nil { - fl = &cs.flow - } - if !fl.add(int32(f.Increment)) { - // For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR - if cs != nil { + if f.StreamID == 0 { + if !cc.flow.add(int32(f.Increment)) { + return ConnectionError(ErrCodeFlowControl) + } + } else { + cs := rl.streamByIDLocked(f.StreamID, notHeaderOrDataFrame) + if cs == nil { + return nil + } + if !cs.flow.add(int32(f.Increment)) { + if cs.flow.conn.flowErr { + // This is a lazily-detected connection-level flow control error. + return ConnectionError(ErrCodeFlowControl) + } + // For stream, the sender sends RST_STREAM with + // an error code of FLOW_CONTROL_ERROR. rl.endStreamErrorLocked(cs, StreamError{ StreamID: f.StreamID, Code: ErrCodeFlowControl, }) return nil } - - return ConnectionError(ErrCodeFlowControl) } cc.cond.Broadcast() return nil diff --git a/vendor/golang.org/x/net/http2/transport_common.go b/vendor/golang.org/x/net/http2/transport_common.go index b9f52932e9..4f0333a703 100644 --- a/vendor/golang.org/x/net/http2/transport_common.go +++ b/vendor/golang.org/x/net/http2/transport_common.go @@ -24,6 +24,8 @@ import ( // It returns an error if t1 has already been HTTP/2-enabled. // // Use ConfigureTransports instead to configure the HTTP/2 Transport. +// +// Deprecated: Set [http.Transport.Protocols] instead. func ConfigureTransport(t1 *http.Transport) error { return configureTransport(t1) } @@ -31,6 +33,8 @@ func ConfigureTransport(t1 *http.Transport) error { // ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2. // It returns a new HTTP/2 Transport for further configuration. // It returns an error if t1 has already been HTTP/2-enabled. +// +// Deprecated: Set [http.Transport.Protocols] instead. func ConfigureTransports(t1 *http.Transport) (*Transport, error) { return configureTransports(t1) } @@ -39,6 +43,8 @@ func ConfigureTransports(t1 *http.Transport) (*Transport, error) { // // A Transport internally caches connections to servers. It is safe // for concurrent use by multiple goroutines. +// +// Deprecated: Use [http.Transport] instead. type Transport struct { // DialTLSContext specifies an optional dial function with context for // creating TLS connections for requests. @@ -47,6 +53,8 @@ type Transport struct { // // If the returned net.Conn has a ConnectionState method like tls.Conn, // it will be used to set http.Response.TLS. + // + // Deprecated: Use [http.Transport.DialTLSContext] instead. DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) // DialTLS specifies an optional dial function for creating @@ -54,17 +62,21 @@ type Transport struct { // // If DialTLSContext and DialTLS is nil, tls.Dial is used. // - // Deprecated: Use DialTLSContext instead, which allows the transport - // to cancel dials as soon as they are no longer needed. - // If both are set, DialTLSContext takes priority. + // Deprecated: Use [http.Transport.DialTLSContext] instead. DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error) // TLSClientConfig specifies the TLS configuration to use with // tls.Client. If nil, the default configuration is used. + // + // Deprecated: Use [http.Transport.TLSClientConfig] instead. TLSClientConfig *tls.Config // ConnPool optionally specifies an alternate connection pool to use. // If nil, the default is used. + // + // Deprecated: To create a custom connection pool, implement + // [http.RoundTripper]. Use [http.Transport.NewClientConn] + // to create connections for the pool. ConnPool ClientConnPool // DisableCompression, if true, prevents the Transport from @@ -75,10 +87,14 @@ type Transport struct { // decoded in the Response.Body. However, if the user // explicitly requested gzip it is not automatically // uncompressed. + // + // Deprecated: Use [http.Transport.DisableCompression] instead. DisableCompression bool // AllowHTTP, if true, permits HTTP/2 requests using the insecure, // plain-text "http" scheme. Note that this does not enable h2c support. + // + // Deprecated: Use [http.Transport.Protocols] instead. AllowHTTP bool // MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to @@ -88,6 +104,8 @@ type Transport struct { // want to advertise an unlimited value to the peer, Transport // interprets the highest possible value here (0xffffffff or 1<<32-1) // to mean no limit. + // + // Deprecated: Use [http.Transport.MaxResponseHeaderBytes] instead. MaxHeaderListSize uint32 // MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the @@ -97,6 +115,9 @@ type Transport struct { // according to the spec: // https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2. // Values are bounded in the range 16k to 16M. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.MaxReadFrameSize] instead. MaxReadFrameSize uint32 // MaxDecoderHeaderTableSize optionally specifies the http2 @@ -104,12 +125,18 @@ type Transport struct { // informs the remote endpoint of the maximum size of the header compression // table used to decode header blocks, in octets. If zero, the default value // of 4096 is used. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.MaxDecoderHeaderTableSize] instead. MaxDecoderHeaderTableSize uint32 // MaxEncoderHeaderTableSize optionally specifies an upper limit for the // header compression table used for encoding request headers. Received // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero, // the default value of 4096 is used. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.MaxEncoderHeaderTableSize] instead. MaxEncoderHeaderTableSize uint32 // StrictMaxConcurrentStreams controls whether the server's @@ -120,12 +147,17 @@ type Transport struct { // server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as // a global limit and callers of RoundTrip block when needed, // waiting for their turn. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.StrictMaxConcurrentRequests] instead. StrictMaxConcurrentStreams bool // IdleConnTimeout is the maximum amount of time an idle // (keep-alive) connection will remain idle before closing // itself. // Zero means no limit. + // + // Deprecated: Use [http.Transport.IdleConnTimeout] instead. IdleConnTimeout time.Duration // ReadIdleTimeout is the timeout after which a health check using ping @@ -134,22 +166,34 @@ type Transport struct { // there is no other traffic on the connection, the health check will // be performed every ReadIdleTimeout interval. // If zero, no health check is performed. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.SendPingTimeout] instead. ReadIdleTimeout time.Duration // PingTimeout is the timeout after which the connection will be closed // if a response to Ping is not received. // Defaults to 15s. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.PingTimeout] instead. PingTimeout time.Duration // WriteByteTimeout is the timeout after which the connection will be // closed no data can be written to it. The timeout begins when data is // available to write, and is extended whenever any bytes are written. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.WriteByteTimeout] instead. WriteByteTimeout time.Duration // CountError, if non-nil, is called on HTTP/2 transport errors. // It's intended to increment a metric for monitoring, such // as an expvar or Prometheus metric. // The errType consists of only ASCII word characters. + // + // Deprecated: Use [http.Transport.HTTP2] and + // [http.HTTP2Config.CountError] instead. CountError func(errType string) // Internal state, differs between wrapped and non-wrapped implementations. @@ -165,6 +209,10 @@ var ( ) // ClientConnPool manages a pool of HTTP/2 client connections. +// +// Deprecated: To create a custom connection pool, implement +// [http.RoundTripper]. Use [http.Transport.NewClientConn] +// to create connections for the pool. type ClientConnPool interface { // GetClientConn returns a specific HTTP/2 connection (usually // a TLS-TCP connection) to an HTTP/2 server. On success, the @@ -177,6 +225,8 @@ type ClientConnPool interface { } // ClientConnState describes the state of a ClientConn. +// +// Deprecated: Use [http.ClientConn] instead. type ClientConnState struct { // Closed is whether the connection is closed. Closed bool @@ -210,6 +260,8 @@ type ClientConnState struct { } // RoundTripOpt are options for the Transport.RoundTripOpt method. +// +// Deprecated: There are no options to set. type RoundTripOpt struct { // OnlyCachedConn controls whether RoundTripOpt may // create a new TCP connection. If set true and @@ -222,11 +274,14 @@ type RoundTripOpt struct { allowHTTP bool // allow http:// URLs } +// Deprecated: Use [http.Transport.RoundTrip] instead. func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { return t.RoundTripOpt(req, RoundTripOpt{}) } // RoundTripOpt is like RoundTrip, but takes options. +// +// Deprecated: Use [http.Transport.RoundTrip] instead. func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { return t.roundTripOpt(req, opt) } @@ -234,10 +289,15 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res // CloseIdleConnections closes any connections which were previously // connected from previous requests but are now sitting idle. // It does not interrupt any connections currently in use. +// +// Deprecated: Use [http.Transport.CloseIdleConnections] instead. func (t *Transport) CloseIdleConnections() { t.closeIdleConnections() } +// NewClientConn is deprecated. +// +// Deprecated: Use [http.Transport.NewClientConn] instead. func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { return t.newUserClientConn(c) } @@ -256,8 +316,15 @@ func authorityAddr(scheme string, authority string) (addr string) { port = "80" } } - if a, err := idna.ToASCII(host); err == nil { - host = a + // Skip IDNA processing on hosts which are already ASCII. + // This is consistent with net/http and the WHATWG URL Specification. + // (We currently don't follow all of WHATWG, but we're aligned on + // permitting all-ASCII hostnames which fail IDNA validation. + // There are existing, valid domain names which TR #46 processing rejects.) + if !isASCII(host) { + if a, err := idna.Lookup.ToASCII(host); err == nil && a != "" { + host = a + } } // IPv6 address literal, without a port: if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { @@ -414,6 +481,8 @@ func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string // GoAwayError is returned by the Transport when the server closes the // TCP connection after sending a GOAWAY frame. +// +// Deprecated: GoAwayError is deprecated. type GoAwayError struct { LastStreamID uint32 ErrCode ErrCode diff --git a/vendor/golang.org/x/net/http2/transport_wrap.go b/vendor/golang.org/x/net/http2/transport_wrap.go index 741fb97062..eeaa95f03c 100644 --- a/vendor/golang.org/x/net/http2/transport_wrap.go +++ b/vendor/golang.org/x/net/http2/transport_wrap.go @@ -119,33 +119,27 @@ type http2TransportContextKey struct{} // DialFromContext dials a new connection using the http2.Transport's DialTLS/DialTLSContext. func (t transportConfig) DialFromContext(ctx context.Context, network, address string) (net.Conn, error) { - if ctx.Value(http2TransportContextKey{}) == nil { + dial, _ := ctx.Value(http2TransportContextKey{}).(*transportRoundTripState) + if dial == nil { // We're being called from a RoundTrip that did not start with an http2.Transport. // Use the http.Transport's dialer. return nil, errors.ErrUnsupported } - - tlsConf := t.t.TLSClientConfig - if tlsConf == nil { - tlsConf = &tls.Config{} - } else { - tlsConf = tlsConf.Clone() - } - if !slices.Contains(tlsConf.NextProtos, "h2") { - tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...) - } - if tlsConf.ServerName == "" { - host, _, err := net.SplitHostPort(address) - if err == nil { - tlsConf.ServerName = host - } - } - return t.t.dialTLS(ctx, network, address, tlsConf) + return t.t.dialForRoundTrip(ctx, dial, network, address) } type transportInternal struct { initOnce sync.Once lazyt1 *http.Transport + + dialMu sync.Mutex + dials map[string]*transportDialState +} + +type transportDialState struct { + dialc chan struct{} // close when dial returns + dialErr error // dial result, set before dialc is closed + rtdonec chan struct{} // closed when RoundTrip initiating the dial returns } func (t *Transport) init() *http.Transport { @@ -167,6 +161,52 @@ func (t *Transport) configure(t1 *http.Transport) { } } +// transportRoundTripState is the state of the dial for an http2.Transport.RoundTrip. +type transportRoundTripState struct { + mu sync.Mutex + rtdone bool // set when RoundTrip returns + rtdonec chan struct{} // closed when RoundTrip returns + gotconnc chan struct{} // closed when GotConn hook is called +} + +// startDial is called when a dial starts. +// +// It returns done=true if the RoundTrip has already returned, +// in which case we should skip dialing. +func (dial *transportRoundTripState) startDial() (gotconnc chan struct{}, done bool) { + dial.mu.Lock() + defer dial.mu.Unlock() + if dial.rtdone { + return nil, true + } + if dial.rtdonec == nil { + dial.rtdonec = make(chan struct{}) + } + dial.gotconnc = make(chan struct{}) + return dial.gotconnc, false +} + +// gotConn is called when RoundTrip gets a connection. +// This may happen multiple times per RoundTrip, if a request is retried. +func (dial *transportRoundTripState) gotConn() { + dial.mu.Lock() + defer dial.mu.Unlock() + if dial.gotconnc != nil { + close(dial.gotconnc) + dial.gotconnc = nil + } +} + +// roundTripDone is called when RoundTrip returns. +func (dial *transportRoundTripState) roundTripDone() { + dial.mu.Lock() + defer dial.mu.Unlock() + dial.rtdone = true + if dial.rtdonec != nil { + close(dial.rtdonec) + } +} + func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { t1 := t.init() @@ -186,12 +226,146 @@ func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res // Both http.Transport and http2.Transport allow the user to provide a custom // dial function, and historically you only get the dial function from the // Transport you're calling RoundTrip on. - ctx := context.WithValue(req.Context(), http2TransportContextKey{}, t) + // + // In addition, http2.Transport coalesces dials, which http.Transport historically + // has not. + dial := &transportRoundTripState{} + defer dial.roundTripDone() + ctx := context.WithValue(req.Context(), http2TransportContextKey{}, dial) + ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + GotConn: func(httptrace.GotConnInfo) { + // Tell dialForRoundTrip that we have received a connection. + dial.gotConn() + }, + }) req = req.WithContext(ctx) - return t1.RoundTrip(req) } +var errCoalescedDialAbandoned = errors.New("http2: abandoned coalesced dial") + +const coalescedDialRetryTimeout = 50 * time.Millisecond + +// dialForRoundTrip is called (indirectly) by net/http when dialing a new connection +// for a request call which originated as an http2.Transport.RoundTrip call. +// +// It uses the http2.Transport's Dial hooks and coalesces dials. +func (t *Transport) dialForRoundTrip(ctx context.Context, dial *transportRoundTripState, network, address string) (net.Conn, error) { + gotconnc, rtdone := dial.startDial() + if rtdone { + // RoundTrip returned, no need for this dial to proceed. + return nil, errCoalescedDialAbandoned + } + + var state *transportDialState + for { + // The first dial to an address registers itself with t.dials. + // When the dial finishes, it records the outcome and removes itself from t.dials + // so future dials will start a new round of coalescing. + // + // Subsequent dials observe that a dial is in progress, and skip dialing. + t.dialMu.Lock() + if t.dials == nil { + t.dials = make(map[string]*transportDialState) + } + leader := t.dials[address] + if leader == nil { + // No entry in t.dials to coalesce with. Add ourselves as leader. + state = &transportDialState{ + dialc: make(chan struct{}), + rtdonec: dial.rtdonec, + } + t.dials[address] = state + } + t.dialMu.Unlock() + if leader == nil { + // We are the leader, so we should dial for real. + break + } + + // Coalesce with a previous dial. + var ( + done = false + leaderdialc = leader.dialc + leaderrtdonec = leader.rtdonec + timerc <-chan time.Time + ) + for !done { + if leaderdialc == nil && leaderrtdonec == nil && timerc == nil { + // The leader finished dialing, and its RoundTrip finished. + // + // There are several possibilities, which reduce to: + // - The RoundTrip we are dialing for is about to receive + // a connection (possibly the one the leader just dialed), + // but we haven't observed it yet. + // - There's something wrong with the leader's connection, + // such as a TLS handshake failure. + // + // We have no good way to distinguish between these cases. + // + // Wait a short time for the leader's connection (or some + // other usable connection) to be delivered to our RoundTrip. + // If it is not, start a dial of our own. + timerc = time.After(coalescedDialRetryTimeout) + } + select { + case <-leaderdialc: + // The leader's dial completed. + if leader.dialErr != nil { + return nil, leader.dialErr + } + leaderdialc = nil + case <-leaderrtdonec: + // The leader's RoundTrip completed. + leaderrtdonec = nil + case <-timerc: + // The leader's dial and RoundTrip completed, and some time + // has passed. Assume we're not getting a connection and + // restart the dial process. + done = true + case <-dial.rtdonec: + // Our RoundTrip returned. + return nil, errCoalescedDialAbandoned + case <-gotconnc: + // Our RoundTrip got a connection. + return nil, errCoalescedDialAbandoned + case <-ctx.Done(): + // net/http abandoned the dial. + return nil, ctx.Err() + } + } + } + + // Dial for real. + tlsConf := t.TLSClientConfig + if tlsConf == nil { + tlsConf = &tls.Config{} + } else { + tlsConf = tlsConf.Clone() + } + if !slices.Contains(tlsConf.NextProtos, "h2") { + tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...) + } + if tlsConf.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err == nil { + tlsConf.ServerName = host + } + } + nc, err := t.dialTLS(ctx, network, address, tlsConf) + + // Remove this dial from t.dials, so future dial attempts will not coalesce with it. + t.dialMu.Lock() + delete(t.dials, address) + t.dialMu.Unlock() + + // Notify any dial coalesced with this one that we are done. + state.dialErr = err + close(state.dialc) + + return nc, err +} + func (t *Transport) closeIdleConnections() { t1 := t.init() t1.CloseIdleConnections() @@ -216,6 +390,8 @@ func (t *Transport) newUserClientConn(c net.Conn) (*ClientConn, error) { // ClientConn is the state of a single HTTP/2 client connection to an // HTTP/2 server. +// +// Deprecated: Use [http.ClientConn] instead. type ClientConn struct { cc *http.ClientConn tconn net.Conn diff --git a/vendor/golang.org/x/net/http2/writesched.go b/vendor/golang.org/x/net/http2/writesched.go index 36ad5e32b4..f1903a8c66 100644 --- a/vendor/golang.org/x/net/http2/writesched.go +++ b/vendor/golang.org/x/net/http2/writesched.go @@ -79,10 +79,11 @@ func (wr FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteReque } // Might need to split after applying limits. - allowed := wr.stream.flow.available() - if n < allowed { - allowed = n + avail, ok := wr.stream.flow.available() + if !ok { + return empty, empty, 0 } + allowed := min(n, avail) if wr.stream.sc.maxFrameSize < allowed { allowed = wr.stream.sc.maxFrameSize } diff --git a/vendor/golang.org/x/net/internal/httpcommon/README.md b/vendor/golang.org/x/net/internal/httpcommon/README.md new file mode 100644 index 0000000000..951c346c2d --- /dev/null +++ b/vendor/golang.org/x/net/internal/httpcommon/README.md @@ -0,0 +1,7 @@ +These two packages are identical: + +- net/http/internal/httpcommon +- golang.org/x/net/internal/httpcommon + +The two are kept in sync manually. +Any change to one should be made in the other. diff --git a/vendor/golang.org/x/net/internal/httpcommon/gzip.go b/vendor/golang.org/x/net/internal/httpcommon/gzip.go new file mode 100644 index 0000000000..819dd650cb --- /dev/null +++ b/vendor/golang.org/x/net/internal/httpcommon/gzip.go @@ -0,0 +1,132 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import ( + "compress/flate" + "compress/gzip" + "errors" + "io" + "io/fs" + "sync" +) + +var errConcurrentRead = errors.New("http: concurrent read on response body") + +// incomparable is a zero-width, non-comparable type. Adding it to a struct +// makes that struct also non-comparable, and generally doesn't add +// any size (as long as it's first). +type incomparable [0]func() + +// GzipReader wraps a response body so it can lazily +// get gzip.Reader from the pool on the first call to Read. +// After Close is called it puts gzip.Reader to the pool immediately +// if there is no Read in progress or later when Read completes. +type GzipReader struct { + _ incomparable + Body io.ReadCloser // underlying Response.Body + mu sync.Mutex // guards zr and zerr + zr *gzip.Reader // stores gzip reader from the pool between reads + zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close +} + +type eofReader struct{} + +func (eofReader) Read([]byte) (int, error) { return 0, io.EOF } +func (eofReader) ReadByte() (byte, error) { return 0, io.EOF } + +var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} + +// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. +func gzipPoolGet(r io.Reader) (*gzip.Reader, error) { + zr := gzipPool.Get().(*gzip.Reader) + if err := zr.Reset(r); err != nil { + gzipPoolPut(zr) + return nil, err + } + return zr, nil +} + +// gzipPoolPut puts a gzip.Reader back into the pool. +func gzipPoolPut(zr *gzip.Reader) { + // Reset will allocate bufio.Reader if we pass it anything + // other than a flate.Reader, so ensure that it's getting one. + var r flate.Reader = eofReader{} + zr.Reset(r) + gzipPool.Put(zr) +} + +// acquire returns a gzip.Reader for reading response body. +// The reader must be released after use. +func (gz *GzipReader) acquire() (*gzip.Reader, error) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr != nil { + return nil, gz.zerr + } + if gz.zr == nil { + // gzipPoolGet might block indefinitely since it reads the gzip header. + // Therefore, drop mu temporarily when using gzipPoolGet. + // We set zerr to errConcurrentRead to prevent concurrent read + // even when mu is temporarily dropped. + gz.zerr = errConcurrentRead + gz.mu.Unlock() + zr, err := gzipPoolGet(gz.Body) + gz.mu.Lock() + // Guard against Close being called while gzipPoolGet is running. + if gz.zerr != errConcurrentRead { + if zr != nil { + gzipPoolPut(zr) + } + return nil, gz.zerr + } + gz.zr, gz.zerr = zr, err + if gz.zerr != nil { + return nil, gz.zerr + } + } + ret := gz.zr + gz.zr, gz.zerr = nil, errConcurrentRead + return ret, nil +} + +// release returns the gzip.Reader to the pool if Close was called during Read. +func (gz *GzipReader) release(zr *gzip.Reader) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == errConcurrentRead { + gz.zr, gz.zerr = zr, nil + } else { // fs.ErrClosed + gzipPoolPut(zr) + } +} + +// close returns the gzip.Reader to the pool immediately or +// signals release to do so after Read completes. +func (gz *GzipReader) close() { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == nil && gz.zr != nil { + gzipPoolPut(gz.zr) + gz.zr = nil + } + gz.zerr = fs.ErrClosed +} + +func (gz *GzipReader) Read(p []byte) (n int, err error) { + zr, err := gz.acquire() + if err != nil { + return 0, err + } + defer gz.release(zr) + + return zr.Read(p) +} + +func (gz *GzipReader) Close() error { + gz.close() + + return gz.Body.Close() +} diff --git a/vendor/golang.org/x/net/internal/httpcommon/request.go b/vendor/golang.org/x/net/internal/httpcommon/request.go index d94c87429a..5aaca5989b 100644 --- a/vendor/golang.org/x/net/internal/httpcommon/request.go +++ b/vendor/golang.org/x/net/internal/httpcommon/request.go @@ -391,6 +391,7 @@ type ServerRequestParam struct { // ServerRequestResult is the result of NewServerRequest. type ServerRequestResult struct { // Various http.Request fields. + Host string URL *url.URL RequestURI string Trailer map[string][]string @@ -433,20 +434,46 @@ func NewServerRequest(rp ServerRequestParam) ServerRequestResult { } delete(rp.Header, "Trailer") + authority := rp.Authority + if host := rp.Header["Host"]; len(host) == 1 { + // HTTP/2 and HTTP/3 permit the Host header to be present, + // but it must match the :authority pseudo-header. + if authority == "" { + authority = host[0] + } else if host[0] != authority { + return ServerRequestResult{ + InvalidReason: "authority_host_mismatch", + } + } + delete(rp.Header, "Host") + } else if len(host) > 1 { + // HTTP/1.1 rejects any request containing more than one Host header. + // HTTP/2 and HTTP/3 don't use the Host header, but reject multiple anyway. + return ServerRequestResult{ + InvalidReason: "multiple_host_headers", + } + } + // "':authority' MUST NOT include the deprecated userinfo subcomponent // for "http" or "https" schemed URIs." // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8 - if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") { + if strings.IndexByte(authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") { return ServerRequestResult{ InvalidReason: "userinfo_in_authority", } } + if authority != "" && !httpguts.ValidHostHeader(authority) { + return ServerRequestResult{ + InvalidReason: "invalid_authority", + } + } + var url_ *url.URL var requestURI string if rp.Method == "CONNECT" && rp.Protocol == "" { - url_ = &url.URL{Host: rp.Authority} - requestURI = rp.Authority // mimic HTTP/1 server behavior + url_ = &url.URL{Host: authority} + requestURI = authority // mimic HTTP/1 server behavior } else { // "[The :path] pseudo-header field MUST NOT be empty [...]" // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.4.2 @@ -467,6 +494,7 @@ func NewServerRequest(rp ServerRequestParam) ServerRequestResult { } return ServerRequestResult{ + Host: authority, URL: url_, NeedsContinue: needsContinue, RequestURI: requestURI, diff --git a/vendor/golang.org/x/net/trace/histogram.go b/vendor/golang.org/x/net/trace/histogram.go index d6c71101e4..2f78683144 100644 --- a/vendor/golang.org/x/net/trace/histogram.go +++ b/vendor/golang.org/x/net/trace/histogram.go @@ -70,10 +70,7 @@ func log2(i int64) int { } func getBucket(i int64) (index int) { - index = log2(i) - 1 - if index < 0 { - index = 0 - } + index = max(log2(i)-1, 0) if index >= bucketCount { index = bucketCount - 1 } diff --git a/vendor/golang.org/x/sync/semaphore/semaphore.go b/vendor/golang.org/x/sync/semaphore/semaphore.go index 96a035aede..f2a7d3ff49 100644 --- a/vendor/golang.org/x/sync/semaphore/semaphore.go +++ b/vendor/golang.org/x/sync/semaphore/semaphore.go @@ -17,10 +17,13 @@ type waiter struct { } // NewWeighted creates a new weighted semaphore with the given -// maximum combined weight for concurrent access. +// maximum combined weight for concurrent access. NewWeighted panics if n is +// negative. func NewWeighted(n int64) *Weighted { - w := &Weighted{size: n} - return w + if n < 0 { + panic("semaphore: size < 0") + } + return &Weighted{size: n} } // Weighted provides a way to bound concurrent access to a resource. diff --git a/vendor/golang.org/x/sys/cpu/cpu.go b/vendor/golang.org/x/sys/cpu/cpu.go index f1ce515d5c..df8dbd1dbf 100644 --- a/vendor/golang.org/x/sys/cpu/cpu.go +++ b/vendor/golang.org/x/sys/cpu/cpu.go @@ -182,12 +182,13 @@ var MIPS64X struct { // require kernel support to work (DARN, SCV), so there are feature bits for // those as well. The struct is padded to avoid false sharing. var PPC64 struct { - _ CacheLinePad - HasDARN bool // Hardware random number generator (requires kernel enablement) - HasSCV bool // Syscall vectored (requires kernel enablement) - IsPOWER8 bool // ISA v2.07 (POWER8) - IsPOWER9 bool // ISA v3.00 (POWER9), implies IsPOWER8 - _ CacheLinePad + _ CacheLinePad + HasDARN bool // Hardware random number generator (requires kernel enablement) + HasSCV bool // Syscall vectored (requires kernel enablement) + IsPOWER8 bool // ISA v2.07 (POWER8) + IsPOWER9 bool // ISA v3.00 (POWER9), implies IsPOWER8 + IsPOWER10 bool // ISA v3.1 (POWER10 and POWER11; POWER11 did not add a new architected level), implies IsPOWER9 + _ CacheLinePad } // S390X contains the supported CPU features of the current IBM Z @@ -248,13 +249,21 @@ var RISCV64 struct { HasZvks bool // ShangMi Algorithm Suite HasZvksc bool // ShangMi Algorithm Suite with carryless multiplication HasZvksg bool // ShangMi Algorithm Suite with GCM + VLENB uint // Vector register length in bytes, 0 if undetected _ CacheLinePad } +// doDerived, if non-nil, is called after processing GODEBUG to set "derived" +// feature flags. +var doDerived func() + func init() { archInit() initOptions() processOptions() + if doDerived != nil { + doDerived() + } } // options contains the cpu debug options that can be used in GODEBUG. diff --git a/vendor/golang.org/x/sys/cpu/cpu_gc_riscv64.go b/vendor/golang.org/x/sys/cpu/cpu_gc_riscv64.go new file mode 100644 index 0000000000..3d660dd48c --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_gc_riscv64.go @@ -0,0 +1,11 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +package cpu + +// Can only be called when the vector extension is present. +// Implemented in cpu_riscv64.s. +func readVLENB() uint diff --git a/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go b/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go index 197188e67f..70b3dc7dd0 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go +++ b/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go @@ -11,6 +11,7 @@ const ( // ISA Level _PPC_FEATURE2_ARCH_2_07 = 0x80000000 _PPC_FEATURE2_ARCH_3_00 = 0x00800000 + _PPC_FEATURE2_ARCH_3_1 = 0x00040000 // CPU features _PPC_FEATURE2_DARN = 0x00200000 @@ -21,6 +22,9 @@ func doinit() { // HWCAP2 feature bits PPC64.IsPOWER8 = isSet(hwCap2, _PPC_FEATURE2_ARCH_2_07) PPC64.IsPOWER9 = isSet(hwCap2, _PPC_FEATURE2_ARCH_3_00) + // ISA 3.1 covers both POWER10 and POWER11: POWER11 did not introduce a + // new architected HWCAP level, so there is no separate IsPOWER11. + PPC64.IsPOWER10 = isSet(hwCap2, _PPC_FEATURE2_ARCH_3_1) PPC64.HasDARN = isSet(hwCap2, _PPC_FEATURE2_DARN) PPC64.HasSCV = isSet(hwCap2, _PPC_FEATURE2_SCV) } diff --git a/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go b/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go index f4fb52ee96..53e909114b 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go @@ -130,6 +130,9 @@ func doinit() { RISCV64.HasFastMisaligned = v == riscv_HWPROBE_MISALIGNED_FAST } } + if RISCV64.HasV { + RISCV64.VLENB = readVLENB() + } // Let's double check with HWCAP if the C extension does not appear to be supported. // This may happen if we're running on a kernel older than 6.4. @@ -137,6 +140,14 @@ func doinit() { if !RISCV64.HasC { RISCV64.HasC = isSet(hwCap, hwcap_RISCV_ISA_C) } + + doDerived = func() { + // If the vector extension is disabled by GODEBUG, then the VLENB is zero. + if !RISCV64.HasV { + RISCV64.VLENB = 0 + } + } + } func isSet(hwc uint, value uint) bool { diff --git a/vendor/golang.org/x/sys/cpu/cpu_netbsd_amd64.go b/vendor/golang.org/x/sys/cpu/cpu_netbsd_amd64.go new file mode 100644 index 0000000000..4f981b1c8a --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_netbsd_amd64.go @@ -0,0 +1,47 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build netbsd && amd64 && gc + +package cpu + +func doinit() { + // NetBSD corrupts avx registers when receiving signals. + // See issue 80285. + // TODO: when NetBSD fixes the bug, add a version + // check and skip here. + X86.HasAVX = false + X86.HasAVX2 = false + // Set these also, just to be safe + X86.HasAVXVNNI = false + X86.HasAVX512 = false + X86.HasAVX512F = false + X86.HasAVX512CD = false + X86.HasAVX512CD = false + X86.HasAVX512ER = false + X86.HasAVX512PF = false + X86.HasAVX512VL = false + X86.HasAVX512BW = false + X86.HasAVX512DQ = false + X86.HasAVX512IFMA = false + X86.HasAVX512VBMI = false + X86.HasAVX5124VNNIW = false + X86.HasAVX5124FMAPS = false + X86.HasAVX512VPOPCNTDQ = false + X86.HasAVX512VPCLMULQDQ = false + X86.HasAVX512VNNI = false + X86.HasAVX512GFNI = false + X86.HasAVX512VAES = false + X86.HasAVX512VBMI2 = false + X86.HasAVX512BITALG = false + X86.HasAVX512BF16 = false + X86.HasAVXIFMA = false + X86.HasAVXVNNI = false + X86.HasAVXVNNIInt8 = false + +} + +func darwinSupportsAVX512() bool { + panic("only implemented for gc && amd64 && darwin") +} diff --git a/vendor/golang.org/x/sys/cpu/cpu_other_x86.go b/vendor/golang.org/x/sys/cpu/cpu_other_x86.go index a0fd7e2f75..82b7580d0b 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_other_x86.go +++ b/vendor/golang.org/x/sys/cpu/cpu_other_x86.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build 386 || amd64p32 || (amd64 && (!darwin || !gc)) +//go:build 386 || amd64p32 || (amd64 && ((!darwin && !netbsd) || !gc)) package cpu diff --git a/vendor/golang.org/x/sys/cpu/cpu_riscv64.s b/vendor/golang.org/x/sys/cpu/cpu_riscv64.s new file mode 100644 index 0000000000..b092fb2697 --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_riscv64.s @@ -0,0 +1,17 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +#include "textflag.h" + +// Read the vector register length in bytes from the vlenb CSR. +// May only be called when the vector extension is present. +// func readVLENB() uint +TEXT ·readVLENB(SB), NOSPLIT|NOFRAME, $0-8 + // Go 1.25's assembler does not recognize VLENB, so use its raw CSRR encoding. + // Replace WORD with CSRR VLENB, X10 once go.mod requires Go 1.27. + WORD $0xc2202573 + MOV X10, ret+0(FP) + RET diff --git a/vendor/golang.org/x/sys/cpu/cpu_sparc64.go b/vendor/golang.org/x/sys/cpu/cpu_sparc64.go new file mode 100644 index 0000000000..0199ef19ba --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_sparc64.go @@ -0,0 +1,12 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build sparc64 + +package cpu + +// The L1 line is 32 bytes; false sharing is governed by the 64-byte L2 line. +const cacheLineSize = 64 + +func initOptions() {} diff --git a/vendor/golang.org/x/sys/unix/ifreq_linux.go b/vendor/golang.org/x/sys/unix/ifreq_linux.go index 309f5a2b0c..3f7fa76643 100644 --- a/vendor/golang.org/x/sys/unix/ifreq_linux.go +++ b/vendor/golang.org/x/sys/unix/ifreq_linux.go @@ -22,7 +22,13 @@ import ( // fields can be get and set using the following methods: // - Uint16/SetUint16: flags // - Uint32/SetUint32: ifindex, metric, mtu -type Ifreq struct{ raw ifreq } +type Ifreq struct { + // Aligns the union for the accessors below, which cast it in place; + // the generated ifreq is all byte arrays, so its alignment is one. + _ [0]int64 + + raw ifreq +} // NewIfreq creates an Ifreq with the input network interface name after // validating the name does not exceed IFNAMSIZ-1 (trailing NULL required) diff --git a/vendor/golang.org/x/sys/unix/ioctl_linux.go b/vendor/golang.org/x/sys/unix/ioctl_linux.go index 7ca4fa12aa..5589d4f665 100644 --- a/vendor/golang.org/x/sys/unix/ioctl_linux.go +++ b/vendor/golang.org/x/sys/unix/ioctl_linux.go @@ -332,3 +332,10 @@ func IoctlLoopSetStatus64(fd int, value *LoopInfo64) error { func IoctlLoopConfigure(fd int, value *LoopConfig) error { return ioctlPtr(fd, LOOP_CONFIGURE, unsafe.Pointer(value)) } + +// IoctlPidfdInfo fetches information about a pidfd. +// The Mask field of the info argument indicates which +// values to fetch. +func IoctlPidfdInfo(fd int, info *PidfdInfo) error { + return ioctlPtr(fd, PIDFD_GET_INFO, unsafe.Pointer(info)) +} diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index fa74cfe9e8..9e907ac296 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -290,14 +290,7 @@ struct ltchars { #include #include -#if defined(__sparc__) -// On sparc{,64}, the kernel defines struct termios2 itself which clashes with the -// definition in glibc. As only the error constants are needed here, include the -// generic termibits.h (which is included by termbits.h on sparc). -#include -#else #include -#endif #ifndef PTRACE_GETREGS #define PTRACE_GETREGS 0xc @@ -544,7 +537,7 @@ ccflags="$@" $2 ~ /^LO_(KEY|NAME)_SIZE$/ || $2 ~ /^LOOP_(CLR|CTL|GET|SET)_/ || $2 == "LOOP_CONFIGURE" || - $2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ || + $2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|MLOCK|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ || $2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ || $2 ~ /^NFC_.*_(MAX)?SIZE$/ || $2 ~ /^PTP_/ || diff --git a/vendor/golang.org/x/sys/unix/syscall.go b/vendor/golang.org/x/sys/unix/syscall.go index 5ea74da982..8f27e7ecb5 100644 --- a/vendor/golang.org/x/sys/unix/syscall.go +++ b/vendor/golang.org/x/sys/unix/syscall.go @@ -76,7 +76,7 @@ func BytePtrToString(p *byte) string { // Find NUL terminator. n := 0 for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ { - ptr = unsafe.Pointer(uintptr(ptr) + 1) + ptr = unsafe.Add(ptr, 1) } return string(unsafe.Slice(p, n)) diff --git a/vendor/golang.org/x/sys/unix/syscall_bsd.go b/vendor/golang.org/x/sys/unix/syscall_bsd.go index a00c3e5450..035c2cd916 100644 --- a/vendor/golang.org/x/sys/unix/syscall_bsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_bsd.go @@ -188,7 +188,7 @@ func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, _Socklen, error) { } sa.raw.Len = byte(3 + n) // 2 for Family, Len; 1 for NUL sa.raw.Family = AF_UNIX - for i := 0; i < n; i++ { + for i := range n { sa.raw.Path[i] = int8(name[i]) } return unsafe.Pointer(&sa.raw), _Socklen(sa.raw.Len), nil diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 21e2bfa398..6926057e9e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -2363,7 +2363,7 @@ func (fh *FileHandle) Bytes() []byte { if n == 0 { return nil } - return unsafe.Slice((*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&fh.fileHandle.Type))+4)), n) + return unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(&fh.fileHandle.Type), 4)), n) } // NameToHandleAt wraps the name_to_handle_at system call; it obtains diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 5bb51d7ae1..45feb12453 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -2048,6 +2048,7 @@ const ( MINIX3_SUPER_MAGIC = 0x4d5a MINIX_SUPER_MAGIC = 0x137f MINIX_SUPER_MAGIC2 = 0x138f + MLOCK_ONFAULT = 0x1 MNT_DETACH = 0x2 MNT_EXPIRE = 0x4 MNT_FORCE = 0x1 @@ -3862,6 +3863,7 @@ const ( TIOCPKT_NOSTOP = 0x10 TIOCPKT_START = 0x8 TIOCPKT_STOP = 0x4 + TIOCSER_TEMT = 0x1 TIPC_ADDR_ID = 0x3 TIPC_ADDR_MCAST = 0x1 TIPC_ADDR_NAME = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index c0a8ea1de0..6f74d4a0cf 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -483,7 +483,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index ff927c8301..36c1f0b68c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -484,7 +484,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index 55294eda5b..c5512dbea6 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -489,7 +489,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 5dac54c35c..4dabfae071 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -483,7 +483,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 46ac1fcb22..ea97930f1f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -476,7 +476,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index b55483e8a9..0826a929e0 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -481,7 +481,6 @@ const ( TIOCSERGWILD = 0x5489 TIOCSERSETMULTI = 0x5490 TIOCSERSWILD = 0x548a - TIOCSER_TEMT = 0x1 TIOCSETD = 0x7401 TIOCSETN = 0x740a TIOCSETP = 0x7409 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 71890c98a4..139deedd95 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -481,7 +481,6 @@ const ( TIOCSERGWILD = 0x5489 TIOCSERSETMULTI = 0x5490 TIOCSERSWILD = 0x548a - TIOCSER_TEMT = 0x1 TIOCSETD = 0x7401 TIOCSETN = 0x740a TIOCSETP = 0x7409 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index a78b6cc14b..b8018af335 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -481,7 +481,6 @@ const ( TIOCSERGWILD = 0x5489 TIOCSERSETMULTI = 0x5490 TIOCSERSWILD = 0x548a - TIOCSER_TEMT = 0x1 TIOCSETD = 0x7401 TIOCSETN = 0x740a TIOCSETP = 0x7409 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index d0e38ca739..deb5c2e6a9 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -481,7 +481,6 @@ const ( TIOCSERGWILD = 0x5489 TIOCSERSETMULTI = 0x5490 TIOCSERSWILD = 0x548a - TIOCSER_TEMT = 0x1 TIOCSETD = 0x7401 TIOCSETN = 0x740a TIOCSETP = 0x7409 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index c883e14c77..0603bf3a1a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -535,7 +535,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETC = 0x80067411 TIOCSETD = 0x5423 TIOCSETN = 0x8006740a diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 1834273d44..5fa8a5d367 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -539,7 +539,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETC = 0x80067411 TIOCSETD = 0x5423 TIOCSETN = 0x8006740a diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index 39945dd9ae..2733e99934 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -539,7 +539,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETC = 0x80067411 TIOCSETD = 0x5423 TIOCSETN = 0x8006740a diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index bc0f372416..1470172fd9 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -486,7 +486,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 6e87bd6598..1871579c5c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -545,7 +545,6 @@ const ( TIOCSERGWILD = 0x5454 TIOCSERSETMULTI = 0x545b TIOCSERSWILD = 0x5455 - TIOCSER_TEMT = 0x1 TIOCSETD = 0x5423 TIOCSIG = 0x40045436 TIOCSISO7816 = 0xc0285443 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 7e2b2e8a66..10881cf6d3 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -14,21 +14,21 @@ const ( ASI_LEON_DFLUSH = 0x11 ASI_LEON_IFLUSH = 0x10 ASI_LEON_MMUFLUSH = 0x18 - B1000000 = 0x1008 + B1000000 = 0x100c B115200 = 0x1002 - B1152000 = 0x1009 - B1500000 = 0x100a - B2000000 = 0x100b + B1152000 = 0x100d + B1500000 = 0x100e + B153600 = 0x1006 + B2000000 = 0x100f B230400 = 0x1003 - B2500000 = 0x100c - B3000000 = 0x100d - B3500000 = 0x100e - B4000000 = 0x100f + B307200 = 0x1007 B460800 = 0x1004 - B500000 = 0x1005 + B500000 = 0x100a B57600 = 0x1001 - B576000 = 0x1006 - B921600 = 0x1007 + B576000 = 0x100b + B614400 = 0x1008 + B76800 = 0x1005 + B921600 = 0x1009 BLKALIGNOFF = 0x2000127a BLKBSZGET = 0x40081270 BLKBSZSET = 0x80081271 @@ -91,7 +91,7 @@ const ( FFDLY = 0x8000 FICLONE = 0x80049409 FICLONERANGE = 0x8020940d - FLUSHO = 0x1000 + FLUSHO = 0x2000 FS_IOC_ENABLE_VERITY = 0x80806685 FS_IOC_GETFLAGS = 0x40086601 FS_IOC_GET_ENCRYPTION_NONCE = 0x4010661b @@ -563,6 +563,9 @@ const ( TIOCM_CD = 0x40 TIOCM_CTS = 0x20 TIOCM_DSR = 0x100 + TIOCM_LOOP = 0x8000 + TIOCM_OUT1 = 0x2000 + TIOCM_OUT2 = 0x4000 TIOCM_RI = 0x80 TIOCM_RNG = 0x80 TIOCM_SR = 0x10 @@ -641,10 +644,11 @@ const ( UBI_IOCVOLRMBLK = 0x20004f08 UBI_IOCVOLUP = 0x80084f00 VDISCARD = 0xd + VDSUSP = 0xb VEOF = 0x4 - VEOL = 0xb - VEOL2 = 0x10 - VMIN = 0x6 + VEOL = 0x5 + VEOL2 = 0x6 + VMIN = 0x4 VREPRINT = 0xc VSTART = 0x8 VSTOP = 0x9 @@ -664,6 +668,7 @@ const ( WDIOC_KEEPALIVE = 0x40045705 WDIOC_SETOPTIONS = 0x40045704 WORDSIZE = 0x40 + WRAP = 0x20000 XCASE = 0x4 XTABS = 0x1800 _HIDIOCGRAWNAME = 0x40804804 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 526a0d5f43..538a18f27c 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -4264,19 +4264,29 @@ type HwTstampConfig struct { } const ( - HWTSTAMP_FILTER_NONE = 0x0 - HWTSTAMP_FILTER_ALL = 0x1 - HWTSTAMP_FILTER_SOME = 0x2 - HWTSTAMP_FILTER_PTP_V1_L4_EVENT = 0x3 - HWTSTAMP_FILTER_PTP_V2_L4_EVENT = 0x6 - HWTSTAMP_FILTER_PTP_V2_L2_EVENT = 0x9 - HWTSTAMP_FILTER_PTP_V2_EVENT = 0xc + HWTSTAMP_FILTER_NONE = 0x0 + HWTSTAMP_FILTER_ALL = 0x1 + HWTSTAMP_FILTER_SOME = 0x2 + HWTSTAMP_FILTER_PTP_V1_L4_EVENT = 0x3 + HWTSTAMP_FILTER_PTP_V1_L4_SYNC = 0x4 + HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ = 0x5 + HWTSTAMP_FILTER_PTP_V2_L4_EVENT = 0x6 + HWTSTAMP_FILTER_PTP_V2_L4_SYNC = 0x7 + HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ = 0x8 + HWTSTAMP_FILTER_PTP_V2_L2_EVENT = 0x9 + HWTSTAMP_FILTER_PTP_V2_L2_SYNC = 0xa + HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ = 0xb + HWTSTAMP_FILTER_PTP_V2_EVENT = 0xc + HWTSTAMP_FILTER_PTP_V2_SYNC = 0xd + HWTSTAMP_FILTER_PTP_V2_DELAY_REQ = 0xe + HWTSTAMP_FILTER_NTP_ALL = 0xf ) const ( HWTSTAMP_TX_OFF = 0x0 HWTSTAMP_TX_ON = 0x1 HWTSTAMP_TX_ONESTEP_SYNC = 0x2 + HWTSTAMP_TX_ONESTEP_P2P = 0x3 ) type ( @@ -4541,6 +4551,48 @@ const ( LANDLOCK_RULE_PATH_BENEATH = 0x1 ) +const ( + PIDFD_SIGNAL_THREAD = 0x1 + PIDFD_SIGNAL_THREAD_GROUP = 0x2 + PIDFD_SIGNAL_PROCESS_GROUP = 0x4 + PIDFD_SELF_THREAD = -0x2710 + PIDFD_SELF_THREAD_GROUP = -0x2711 + PIDFD_SELF = -0x2710 + PIDFD_SELF_PROCESS = -0x2711 + PIDFD_INFO_PID = 0x1 + PIDFD_INFO_CREDS = 0x2 + PIDFD_INFO_CGROUPID = 0x4 + PIDFD_INFO_EXIT = 0x8 + PIDFD_INFO_COREDUMP = 0x10 + PIDFD_COREDUMPED = 0x1 + PIDFD_COREDUMP_SKIP = 0x2 + PIDFD_COREDUMP_USER = 0x4 + PIDFD_COREDUMP_ROOT = 0x8 + PIDFD_INFO_SIZE_VER0 = 0x40 + PIDFD_GET_INFO = 0xc048ff0b +) + +const SizeofPidfdInfo = 0x48 + +type PidfdInfo struct { + Mask uint64 + Cgroupid uint64 + Pid uint32 + Tgid uint32 + Ppid uint32 + Ruid uint32 + Rgid uint32 + Euid uint32 + Egid uint32 + Suid uint32 + Sgid uint32 + Fsuid uint32 + Fsgid uint32 + Exit_code int32 + Coredump_mask uint32 + _ uint32 +} + const ( IPC_CREAT = 0x200 IPC_EXCL = 0x400 @@ -6473,3 +6525,23 @@ type GPIOV2LineEvent struct { Line_seqno uint32 _ [6]uint32 } + +const ( + IPMI_MAX_ADDR_SIZE = 0x20 + IPMI_SYSTEM_INTERFACE_ADDR_TYPE = 0xc + IPMI_BMC_CHANNEL = 0xf + IPMI_RESPONSE_RECV_TYPE = 0x1 +) + +type IPMISystemInterfaceAddr struct { + Type int32 + Channel int16 + Lun uint8 + _ [1]byte +} +type IPMIMsg struct { + Netfn uint8 + Cmd uint8 + Len uint16 + Data *uint8 +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index aede1de7f2..38658a345b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -680,7 +680,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -715,3 +726,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8014690d + IPMICTL_RECEIVE_MSG = 0xc018690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int32 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int32 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index bb3bc4dc2c..267ffe05f3 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -697,7 +697,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -729,3 +740,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index 1fdf4c5175..ec459d5f3f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -674,7 +674,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -709,3 +720,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8014690d + IPMICTL_RECEIVE_MSG = 0xc018690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int32 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int32 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index 063e6f0b41..fa45ab7d2c 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -676,7 +676,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -708,3 +719,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go index 9cf836c708..3da72ccdf8 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go @@ -677,7 +677,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -709,3 +720,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index 1d222fcb31..78f8c6d555 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -680,7 +680,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x80 + PIDFD_NONBLOCK = 0x80 + PIDFD_THREAD = 0x400 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -714,3 +725,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4014690d + IPMICTL_RECEIVE_MSG = 0xc018690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int32 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int32 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index 912cc4ab63..bbd4c6c2b9 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -679,7 +679,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x80 + PIDFD_NONBLOCK = 0x80 + PIDFD_THREAD = 0x400 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -711,3 +722,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index 1e358ef34f..644d31d6ae 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -679,7 +679,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x80 + PIDFD_NONBLOCK = 0x80 + PIDFD_THREAD = 0x400 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -711,3 +722,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index df59f32f5e..a976f62676 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -680,7 +680,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x80 + PIDFD_NONBLOCK = 0x80 + PIDFD_THREAD = 0x400 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -714,3 +725,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4014690d + IPMICTL_RECEIVE_MSG = 0xc018690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int32 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int32 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go index 29355aa0bf..46324695f6 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go @@ -686,7 +686,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -722,3 +733,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4014690d + IPMICTL_RECEIVE_MSG = 0xc018690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int32 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int32 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index c6083a15d7..cbc303974f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -686,7 +686,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -717,3 +728,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index 6321cc7626..b47f7b4984 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -686,7 +686,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -717,3 +728,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index b44f402feb..cb16add54c 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -704,7 +704,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -796,3 +807,22 @@ const ( const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index b22c795a64..a57ab8fadd 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -700,7 +700,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x800 + PIDFD_NONBLOCK = 0x800 + PIDFD_THREAD = 0x80 + PIDFD_GET_CGROUP_NAMESPACE = 0xff01 + PIDFD_GET_IPC_NAMESPACE = 0xff02 + PIDFD_GET_MNT_NAMESPACE = 0xff03 + PIDFD_GET_NET_NAMESPACE = 0xff04 + PIDFD_GET_PID_NAMESPACE = 0xff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0xff06 + PIDFD_GET_TIME_NAMESPACE = 0xff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0xff08 + PIDFD_GET_USER_NAMESPACE = 0xff09 + PIDFD_GET_UTS_NAMESPACE = 0xff0a ) type SysvIpcPerm struct { @@ -731,3 +742,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x8028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go index 0b18075b53..bf99635d8a 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go @@ -681,7 +681,18 @@ const ( ) const ( - PIDFD_NONBLOCK = 0x4000 + PIDFD_NONBLOCK = 0x4000 + PIDFD_THREAD = 0x800 + PIDFD_GET_CGROUP_NAMESPACE = 0x2000ff01 + PIDFD_GET_IPC_NAMESPACE = 0x2000ff02 + PIDFD_GET_MNT_NAMESPACE = 0x2000ff03 + PIDFD_GET_NET_NAMESPACE = 0x2000ff04 + PIDFD_GET_PID_NAMESPACE = 0x2000ff05 + PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE = 0x2000ff06 + PIDFD_GET_TIME_NAMESPACE = 0x2000ff07 + PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE = 0x2000ff08 + PIDFD_GET_USER_NAMESPACE = 0x2000ff09 + PIDFD_GET_UTS_NAMESPACE = 0x2000ff0a ) type SysvIpcPerm struct { @@ -712,3 +723,22 @@ type SysvShmDesc struct { const ( GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 ) + +const ( + IPMICTL_SEND_COMMAND = 0x4028690d + IPMICTL_RECEIVE_MSG = 0xc030690c +) + +type IPMIReq struct { + Addr *uint8 + Len uint32 + Msgid int64 + Msg IPMIMsg +} +type IPMIRecv struct { + Recv_type int32 + Addr *uint8 + Addr_len uint32 + Msgid int64 + Msg IPMIMsg +} diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 75a50b3165..643737d455 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -1065,6 +1065,7 @@ const ( SO_BROADCAST = 32 SO_LINGER = 128 SO_RCVBUF = 0x1002 + SO_SNDTIMEO = 0x1005 SO_RCVTIMEO = 0x1006 SO_SNDBUF = 0x1001 SO_UPDATE_ACCEPT_CONTEXT = 0x700b diff --git a/vendor/golang.org/x/term/terminal.go b/vendor/golang.org/x/term/terminal.go index 6ec537cdc1..e8327c83fc 100644 --- a/vendor/golang.org/x/term/terminal.go +++ b/vendor/golang.org/x/term/terminal.go @@ -106,6 +106,9 @@ type Terminal struct { // a read. It aliases into inBuf. remainder []byte inBuf [256]byte + // readErr is an error returned by a read that also returned data. It is + // reported after all of that data has been processed. + readErr error // History records and retrieves lines of input read by [ReadLine] which // a user can retrieve and navigate using the up and down arrow keys. @@ -788,7 +791,11 @@ func (t *Terminal) ReadPassword(prompt string) (line string, err error) { return } -// ReadLine returns a line of input from the terminal. +// ReadLine returns a line of input from the terminal, excluding the +// trailing newline. It may return partial data along with an error. +// An [io.EOF] error indicates the end of the stream. For other errors, +// such as [ErrPasteIndicator] in bracketed paste mode, a subsequent +// call may return more data. func (t *Terminal) ReadLine() (line string, err error) { t.lock.Lock() defer t.lock.Unlock() @@ -863,21 +870,24 @@ func (t *Terminal) readLine() (line string, err error) { } return } + if t.readErr != nil { + err = t.readErr + t.readErr = nil + return + } // t.remainder is a slice at the beginning of t.inBuf // containing a partial key sequence readBuf := t.inBuf[len(t.remainder):] - var n int t.lock.Unlock() - n, err = t.c.Read(readBuf) + n, readErr := t.c.Read(readBuf) t.lock.Lock() - if err != nil { - return - } - t.remainder = t.inBuf[:n+len(t.remainder)] + if readErr != nil { + t.readErr = readErr + } } } diff --git a/vendor/golang.org/x/text/unicode/bidi/core.go b/vendor/golang.org/x/text/unicode/bidi/core.go index fb8273236d..e28ac8c8aa 100644 --- a/vendor/golang.org/x/text/unicode/bidi/core.go +++ b/vendor/golang.org/x/text/unicode/bidi/core.go @@ -6,7 +6,6 @@ package bidi import ( "fmt" - "log" ) // This implementation is a port based on the reference implementation found at: @@ -248,7 +247,7 @@ func (p *paragraph) determineParagraphEmbeddingLevel(start, end int) level { } else if t.in(FSI, LRI, RLI) { i = p.matchingPDI[i] // skip over to the matching PDI if i > end { - log.Panic("assert (i <= end)") + panic("assert (i <= end)") } } } @@ -729,7 +728,7 @@ loop: continue loop } } - log.Panicf("invalid bidi code %v present in assertOnly at position %d", t, s.indexes[i]) + panic(fmt.Errorf("invalid bidi code %v present in assertOnly at position %d", t, s.indexes[i])) } } diff --git a/vendor/golang.org/x/text/unicode/norm/composition.go b/vendor/golang.org/x/text/unicode/norm/composition.go index e2087bce52..e024e74b9f 100644 --- a/vendor/golang.org/x/text/unicode/norm/composition.go +++ b/vendor/golang.org/x/text/unicode/norm/composition.go @@ -449,6 +449,16 @@ func (rb *reorderBuffer) combineHangul(s, i, k int) { // ACxx plus 11Ax to LVT rb.assignRune(s, l+v-jamoTBase) default: + // Not a Hangul composition. The segment may still + // contain regular canonical compositions, such as + // combining marks following a Hangul syllable, so + // fall back to the composition table. + if b[i].combinesBackward() { + if combined := combine(l, v); combined != 0 { + rb.assignRune(s, combined) + continue + } + } b[k] = b[i] k++ } @@ -484,25 +494,27 @@ func (rb *reorderBuffer) compose() { return } ii := b[i] + // Track the last starter unconditionally: b[i] must be blocked by + // any starter between it and s, even one that b[i] itself cannot + // combine with, and even if the runes in between never enter the + // combinesBackward branch below. + cccB := b[k-1].ccc + cccC := ii.ccc + blocked := false // b[i] blocked by starter or greater or equal CCC? + if cccB == 0 { + s = k - 1 + } else { + blocked = s != k-1 && cccB >= cccC + } // We can only use combineForward as a filter if we later // get the info for the combined character. This is more // expensive than using the filter. Using combinesBackward() // is safe. - if ii.combinesBackward() { - cccB := b[k-1].ccc - cccC := ii.ccc - blocked := false // b[i] blocked by starter or greater or equal CCC? - if cccB == 0 { - s = k - 1 - } else { - blocked = s != k-1 && cccB >= cccC - } - if !blocked { - combined := combine(rb.runeAt(s), rb.runeAt(i)) - if combined != 0 { - rb.assignRune(s, combined) - continue - } + if ii.combinesBackward() && !blocked { + combined := combine(rb.runeAt(s), rb.runeAt(i)) + if combined != 0 { + rb.assignRune(s, combined) + continue } } b[k] = b[i] diff --git a/vendor/golang.org/x/text/unicode/norm/forminfo.go b/vendor/golang.org/x/text/unicode/norm/forminfo.go index b3cf5d9bde..956708eec0 100644 --- a/vendor/golang.org/x/text/unicode/norm/forminfo.go +++ b/vendor/golang.org/x/text/unicode/norm/forminfo.go @@ -191,30 +191,30 @@ func (p Properties) TrailCCC() uint8 { return ccc[p.tccc] } +// Recomposition +// Each entry of recompMapPacked holds the two runes of the key and the +// composed rune of the value packed into a big-endian uint64 as three +// 21-bit fields, which is wide enough for any rune. +// Note that the recomposition map for NFC and NFKC are identical. + +const recompShift = 21 + func buildRecompMap() { - recompMap = make(map[uint32]rune, len(recompMapPacked)/8) + recompMap = make(map[uint64]rune, len(recompMapPacked)/8) var buf [8]byte for i := 0; i < len(recompMapPacked); i += 8 { copy(buf[:], recompMapPacked[i:i+8]) - key := binary.BigEndian.Uint32(buf[:4]) - val := binary.BigEndian.Uint32(buf[4:]) - recompMap[key] = rune(val) + e := binary.BigEndian.Uint64(buf[:]) + recompMap[e>>recompShift] = rune(e & (1<" + // 0x006C030C: 0x0000013E - "\x00N\x03\x01\x00\x00\x01C" + // 0x004E0301: 0x00000143 - "\x00n\x03\x01\x00\x00\x01D" + // 0x006E0301: 0x00000144 - "\x00N\x03'\x00\x00\x01E" + // 0x004E0327: 0x00000145 - "\x00n\x03'\x00\x00\x01F" + // 0x006E0327: 0x00000146 - "\x00N\x03\f\x00\x00\x01G" + // 0x004E030C: 0x00000147 - "\x00n\x03\f\x00\x00\x01H" + // 0x006E030C: 0x00000148 - "\x00O\x03\x04\x00\x00\x01L" + // 0x004F0304: 0x0000014C - "\x00o\x03\x04\x00\x00\x01M" + // 0x006F0304: 0x0000014D - "\x00O\x03\x06\x00\x00\x01N" + // 0x004F0306: 0x0000014E - "\x00o\x03\x06\x00\x00\x01O" + // 0x006F0306: 0x0000014F - "\x00O\x03\v\x00\x00\x01P" + // 0x004F030B: 0x00000150 - "\x00o\x03\v\x00\x00\x01Q" + // 0x006F030B: 0x00000151 - "\x00R\x03\x01\x00\x00\x01T" + // 0x00520301: 0x00000154 - "\x00r\x03\x01\x00\x00\x01U" + // 0x00720301: 0x00000155 - "\x00R\x03'\x00\x00\x01V" + // 0x00520327: 0x00000156 - "\x00r\x03'\x00\x00\x01W" + // 0x00720327: 0x00000157 - "\x00R\x03\f\x00\x00\x01X" + // 0x0052030C: 0x00000158 - "\x00r\x03\f\x00\x00\x01Y" + // 0x0072030C: 0x00000159 - "\x00S\x03\x01\x00\x00\x01Z" + // 0x00530301: 0x0000015A - "\x00s\x03\x01\x00\x00\x01[" + // 0x00730301: 0x0000015B - "\x00S\x03\x02\x00\x00\x01\\" + // 0x00530302: 0x0000015C - "\x00s\x03\x02\x00\x00\x01]" + // 0x00730302: 0x0000015D - "\x00S\x03'\x00\x00\x01^" + // 0x00530327: 0x0000015E - "\x00s\x03'\x00\x00\x01_" + // 0x00730327: 0x0000015F - "\x00S\x03\f\x00\x00\x01`" + // 0x0053030C: 0x00000160 - "\x00s\x03\f\x00\x00\x01a" + // 0x0073030C: 0x00000161 - "\x00T\x03'\x00\x00\x01b" + // 0x00540327: 0x00000162 - "\x00t\x03'\x00\x00\x01c" + // 0x00740327: 0x00000163 - "\x00T\x03\f\x00\x00\x01d" + // 0x0054030C: 0x00000164 - "\x00t\x03\f\x00\x00\x01e" + // 0x0074030C: 0x00000165 - "\x00U\x03\x03\x00\x00\x01h" + // 0x00550303: 0x00000168 - "\x00u\x03\x03\x00\x00\x01i" + // 0x00750303: 0x00000169 - "\x00U\x03\x04\x00\x00\x01j" + // 0x00550304: 0x0000016A - "\x00u\x03\x04\x00\x00\x01k" + // 0x00750304: 0x0000016B - "\x00U\x03\x06\x00\x00\x01l" + // 0x00550306: 0x0000016C - "\x00u\x03\x06\x00\x00\x01m" + // 0x00750306: 0x0000016D - "\x00U\x03\n\x00\x00\x01n" + // 0x0055030A: 0x0000016E - "\x00u\x03\n\x00\x00\x01o" + // 0x0075030A: 0x0000016F - "\x00U\x03\v\x00\x00\x01p" + // 0x0055030B: 0x00000170 - "\x00u\x03\v\x00\x00\x01q" + // 0x0075030B: 0x00000171 - "\x00U\x03(\x00\x00\x01r" + // 0x00550328: 0x00000172 - "\x00u\x03(\x00\x00\x01s" + // 0x00750328: 0x00000173 - "\x00W\x03\x02\x00\x00\x01t" + // 0x00570302: 0x00000174 - "\x00w\x03\x02\x00\x00\x01u" + // 0x00770302: 0x00000175 - "\x00Y\x03\x02\x00\x00\x01v" + // 0x00590302: 0x00000176 - "\x00y\x03\x02\x00\x00\x01w" + // 0x00790302: 0x00000177 - "\x00Y\x03\b\x00\x00\x01x" + // 0x00590308: 0x00000178 - "\x00Z\x03\x01\x00\x00\x01y" + // 0x005A0301: 0x00000179 - "\x00z\x03\x01\x00\x00\x01z" + // 0x007A0301: 0x0000017A - "\x00Z\x03\a\x00\x00\x01{" + // 0x005A0307: 0x0000017B - "\x00z\x03\a\x00\x00\x01|" + // 0x007A0307: 0x0000017C - "\x00Z\x03\f\x00\x00\x01}" + // 0x005A030C: 0x0000017D - "\x00z\x03\f\x00\x00\x01~" + // 0x007A030C: 0x0000017E - "\x00O\x03\x1b\x00\x00\x01\xa0" + // 0x004F031B: 0x000001A0 - "\x00o\x03\x1b\x00\x00\x01\xa1" + // 0x006F031B: 0x000001A1 - "\x00U\x03\x1b\x00\x00\x01\xaf" + // 0x0055031B: 0x000001AF - "\x00u\x03\x1b\x00\x00\x01\xb0" + // 0x0075031B: 0x000001B0 - "\x00A\x03\f\x00\x00\x01\xcd" + // 0x0041030C: 0x000001CD - "\x00a\x03\f\x00\x00\x01\xce" + // 0x0061030C: 0x000001CE - "\x00I\x03\f\x00\x00\x01\xcf" + // 0x0049030C: 0x000001CF - "\x00i\x03\f\x00\x00\x01\xd0" + // 0x0069030C: 0x000001D0 - "\x00O\x03\f\x00\x00\x01\xd1" + // 0x004F030C: 0x000001D1 - "\x00o\x03\f\x00\x00\x01\xd2" + // 0x006F030C: 0x000001D2 - "\x00U\x03\f\x00\x00\x01\xd3" + // 0x0055030C: 0x000001D3 - "\x00u\x03\f\x00\x00\x01\xd4" + // 0x0075030C: 0x000001D4 - "\x00\xdc\x03\x04\x00\x00\x01\xd5" + // 0x00DC0304: 0x000001D5 - "\x00\xfc\x03\x04\x00\x00\x01\xd6" + // 0x00FC0304: 0x000001D6 - "\x00\xdc\x03\x01\x00\x00\x01\xd7" + // 0x00DC0301: 0x000001D7 - "\x00\xfc\x03\x01\x00\x00\x01\xd8" + // 0x00FC0301: 0x000001D8 - "\x00\xdc\x03\f\x00\x00\x01\xd9" + // 0x00DC030C: 0x000001D9 - "\x00\xfc\x03\f\x00\x00\x01\xda" + // 0x00FC030C: 0x000001DA - "\x00\xdc\x03\x00\x00\x00\x01\xdb" + // 0x00DC0300: 0x000001DB - "\x00\xfc\x03\x00\x00\x00\x01\xdc" + // 0x00FC0300: 0x000001DC - "\x00\xc4\x03\x04\x00\x00\x01\xde" + // 0x00C40304: 0x000001DE - "\x00\xe4\x03\x04\x00\x00\x01\xdf" + // 0x00E40304: 0x000001DF - "\x02&\x03\x04\x00\x00\x01\xe0" + // 0x02260304: 0x000001E0 - "\x02'\x03\x04\x00\x00\x01\xe1" + // 0x02270304: 0x000001E1 - "\x00\xc6\x03\x04\x00\x00\x01\xe2" + // 0x00C60304: 0x000001E2 - "\x00\xe6\x03\x04\x00\x00\x01\xe3" + // 0x00E60304: 0x000001E3 - "\x00G\x03\f\x00\x00\x01\xe6" + // 0x0047030C: 0x000001E6 - "\x00g\x03\f\x00\x00\x01\xe7" + // 0x0067030C: 0x000001E7 - "\x00K\x03\f\x00\x00\x01\xe8" + // 0x004B030C: 0x000001E8 - "\x00k\x03\f\x00\x00\x01\xe9" + // 0x006B030C: 0x000001E9 - "\x00O\x03(\x00\x00\x01\xea" + // 0x004F0328: 0x000001EA - "\x00o\x03(\x00\x00\x01\xeb" + // 0x006F0328: 0x000001EB - "\x01\xea\x03\x04\x00\x00\x01\xec" + // 0x01EA0304: 0x000001EC - "\x01\xeb\x03\x04\x00\x00\x01\xed" + // 0x01EB0304: 0x000001ED - "\x01\xb7\x03\f\x00\x00\x01\xee" + // 0x01B7030C: 0x000001EE - "\x02\x92\x03\f\x00\x00\x01\xef" + // 0x0292030C: 0x000001EF - "\x00j\x03\f\x00\x00\x01\xf0" + // 0x006A030C: 0x000001F0 - "\x00G\x03\x01\x00\x00\x01\xf4" + // 0x00470301: 0x000001F4 - "\x00g\x03\x01\x00\x00\x01\xf5" + // 0x00670301: 0x000001F5 - "\x00N\x03\x00\x00\x00\x01\xf8" + // 0x004E0300: 0x000001F8 - "\x00n\x03\x00\x00\x00\x01\xf9" + // 0x006E0300: 0x000001F9 - "\x00\xc5\x03\x01\x00\x00\x01\xfa" + // 0x00C50301: 0x000001FA - "\x00\xe5\x03\x01\x00\x00\x01\xfb" + // 0x00E50301: 0x000001FB - "\x00\xc6\x03\x01\x00\x00\x01\xfc" + // 0x00C60301: 0x000001FC - "\x00\xe6\x03\x01\x00\x00\x01\xfd" + // 0x00E60301: 0x000001FD - "\x00\xd8\x03\x01\x00\x00\x01\xfe" + // 0x00D80301: 0x000001FE - "\x00\xf8\x03\x01\x00\x00\x01\xff" + // 0x00F80301: 0x000001FF - "\x00A\x03\x0f\x00\x00\x02\x00" + // 0x0041030F: 0x00000200 - "\x00a\x03\x0f\x00\x00\x02\x01" + // 0x0061030F: 0x00000201 - "\x00A\x03\x11\x00\x00\x02\x02" + // 0x00410311: 0x00000202 - "\x00a\x03\x11\x00\x00\x02\x03" + // 0x00610311: 0x00000203 - "\x00E\x03\x0f\x00\x00\x02\x04" + // 0x0045030F: 0x00000204 - "\x00e\x03\x0f\x00\x00\x02\x05" + // 0x0065030F: 0x00000205 - "\x00E\x03\x11\x00\x00\x02\x06" + // 0x00450311: 0x00000206 - "\x00e\x03\x11\x00\x00\x02\a" + // 0x00650311: 0x00000207 - "\x00I\x03\x0f\x00\x00\x02\b" + // 0x0049030F: 0x00000208 - "\x00i\x03\x0f\x00\x00\x02\t" + // 0x0069030F: 0x00000209 - "\x00I\x03\x11\x00\x00\x02\n" + // 0x00490311: 0x0000020A - "\x00i\x03\x11\x00\x00\x02\v" + // 0x00690311: 0x0000020B - "\x00O\x03\x0f\x00\x00\x02\f" + // 0x004F030F: 0x0000020C - "\x00o\x03\x0f\x00\x00\x02\r" + // 0x006F030F: 0x0000020D - "\x00O\x03\x11\x00\x00\x02\x0e" + // 0x004F0311: 0x0000020E - "\x00o\x03\x11\x00\x00\x02\x0f" + // 0x006F0311: 0x0000020F - "\x00R\x03\x0f\x00\x00\x02\x10" + // 0x0052030F: 0x00000210 - "\x00r\x03\x0f\x00\x00\x02\x11" + // 0x0072030F: 0x00000211 - "\x00R\x03\x11\x00\x00\x02\x12" + // 0x00520311: 0x00000212 - "\x00r\x03\x11\x00\x00\x02\x13" + // 0x00720311: 0x00000213 - "\x00U\x03\x0f\x00\x00\x02\x14" + // 0x0055030F: 0x00000214 - "\x00u\x03\x0f\x00\x00\x02\x15" + // 0x0075030F: 0x00000215 - "\x00U\x03\x11\x00\x00\x02\x16" + // 0x00550311: 0x00000216 - "\x00u\x03\x11\x00\x00\x02\x17" + // 0x00750311: 0x00000217 - "\x00S\x03&\x00\x00\x02\x18" + // 0x00530326: 0x00000218 - "\x00s\x03&\x00\x00\x02\x19" + // 0x00730326: 0x00000219 - "\x00T\x03&\x00\x00\x02\x1a" + // 0x00540326: 0x0000021A - "\x00t\x03&\x00\x00\x02\x1b" + // 0x00740326: 0x0000021B - "\x00H\x03\f\x00\x00\x02\x1e" + // 0x0048030C: 0x0000021E - "\x00h\x03\f\x00\x00\x02\x1f" + // 0x0068030C: 0x0000021F - "\x00A\x03\a\x00\x00\x02&" + // 0x00410307: 0x00000226 - "\x00a\x03\a\x00\x00\x02'" + // 0x00610307: 0x00000227 - "\x00E\x03'\x00\x00\x02(" + // 0x00450327: 0x00000228 - "\x00e\x03'\x00\x00\x02)" + // 0x00650327: 0x00000229 - "\x00\xd6\x03\x04\x00\x00\x02*" + // 0x00D60304: 0x0000022A - "\x00\xf6\x03\x04\x00\x00\x02+" + // 0x00F60304: 0x0000022B - "\x00\xd5\x03\x04\x00\x00\x02," + // 0x00D50304: 0x0000022C - "\x00\xf5\x03\x04\x00\x00\x02-" + // 0x00F50304: 0x0000022D - "\x00O\x03\a\x00\x00\x02." + // 0x004F0307: 0x0000022E - "\x00o\x03\a\x00\x00\x02/" + // 0x006F0307: 0x0000022F - "\x02.\x03\x04\x00\x00\x020" + // 0x022E0304: 0x00000230 - "\x02/\x03\x04\x00\x00\x021" + // 0x022F0304: 0x00000231 - "\x00Y\x03\x04\x00\x00\x022" + // 0x00590304: 0x00000232 - "\x00y\x03\x04\x00\x00\x023" + // 0x00790304: 0x00000233 - "\x00\xa8\x03\x01\x00\x00\x03\x85" + // 0x00A80301: 0x00000385 - "\x03\x91\x03\x01\x00\x00\x03\x86" + // 0x03910301: 0x00000386 - "\x03\x95\x03\x01\x00\x00\x03\x88" + // 0x03950301: 0x00000388 - "\x03\x97\x03\x01\x00\x00\x03\x89" + // 0x03970301: 0x00000389 - "\x03\x99\x03\x01\x00\x00\x03\x8a" + // 0x03990301: 0x0000038A - "\x03\x9f\x03\x01\x00\x00\x03\x8c" + // 0x039F0301: 0x0000038C - "\x03\xa5\x03\x01\x00\x00\x03\x8e" + // 0x03A50301: 0x0000038E - "\x03\xa9\x03\x01\x00\x00\x03\x8f" + // 0x03A90301: 0x0000038F - "\x03\xca\x03\x01\x00\x00\x03\x90" + // 0x03CA0301: 0x00000390 - "\x03\x99\x03\b\x00\x00\x03\xaa" + // 0x03990308: 0x000003AA - "\x03\xa5\x03\b\x00\x00\x03\xab" + // 0x03A50308: 0x000003AB - "\x03\xb1\x03\x01\x00\x00\x03\xac" + // 0x03B10301: 0x000003AC - "\x03\xb5\x03\x01\x00\x00\x03\xad" + // 0x03B50301: 0x000003AD - "\x03\xb7\x03\x01\x00\x00\x03\xae" + // 0x03B70301: 0x000003AE - "\x03\xb9\x03\x01\x00\x00\x03\xaf" + // 0x03B90301: 0x000003AF - "\x03\xcb\x03\x01\x00\x00\x03\xb0" + // 0x03CB0301: 0x000003B0 - "\x03\xb9\x03\b\x00\x00\x03\xca" + // 0x03B90308: 0x000003CA - "\x03\xc5\x03\b\x00\x00\x03\xcb" + // 0x03C50308: 0x000003CB - "\x03\xbf\x03\x01\x00\x00\x03\xcc" + // 0x03BF0301: 0x000003CC - "\x03\xc5\x03\x01\x00\x00\x03\xcd" + // 0x03C50301: 0x000003CD - "\x03\xc9\x03\x01\x00\x00\x03\xce" + // 0x03C90301: 0x000003CE - "\x03\xd2\x03\x01\x00\x00\x03\xd3" + // 0x03D20301: 0x000003D3 - "\x03\xd2\x03\b\x00\x00\x03\xd4" + // 0x03D20308: 0x000003D4 - "\x04\x15\x03\x00\x00\x00\x04\x00" + // 0x04150300: 0x00000400 - "\x04\x15\x03\b\x00\x00\x04\x01" + // 0x04150308: 0x00000401 - "\x04\x13\x03\x01\x00\x00\x04\x03" + // 0x04130301: 0x00000403 - "\x04\x06\x03\b\x00\x00\x04\a" + // 0x04060308: 0x00000407 - "\x04\x1a\x03\x01\x00\x00\x04\f" + // 0x041A0301: 0x0000040C - "\x04\x18\x03\x00\x00\x00\x04\r" + // 0x04180300: 0x0000040D - "\x04#\x03\x06\x00\x00\x04\x0e" + // 0x04230306: 0x0000040E - "\x04\x18\x03\x06\x00\x00\x04\x19" + // 0x04180306: 0x00000419 - "\x048\x03\x06\x00\x00\x049" + // 0x04380306: 0x00000439 - "\x045\x03\x00\x00\x00\x04P" + // 0x04350300: 0x00000450 - "\x045\x03\b\x00\x00\x04Q" + // 0x04350308: 0x00000451 - "\x043\x03\x01\x00\x00\x04S" + // 0x04330301: 0x00000453 - "\x04V\x03\b\x00\x00\x04W" + // 0x04560308: 0x00000457 - "\x04:\x03\x01\x00\x00\x04\\" + // 0x043A0301: 0x0000045C - "\x048\x03\x00\x00\x00\x04]" + // 0x04380300: 0x0000045D - "\x04C\x03\x06\x00\x00\x04^" + // 0x04430306: 0x0000045E - "\x04t\x03\x0f\x00\x00\x04v" + // 0x0474030F: 0x00000476 - "\x04u\x03\x0f\x00\x00\x04w" + // 0x0475030F: 0x00000477 - "\x04\x16\x03\x06\x00\x00\x04\xc1" + // 0x04160306: 0x000004C1 - "\x046\x03\x06\x00\x00\x04\xc2" + // 0x04360306: 0x000004C2 - "\x04\x10\x03\x06\x00\x00\x04\xd0" + // 0x04100306: 0x000004D0 - "\x040\x03\x06\x00\x00\x04\xd1" + // 0x04300306: 0x000004D1 - "\x04\x10\x03\b\x00\x00\x04\xd2" + // 0x04100308: 0x000004D2 - "\x040\x03\b\x00\x00\x04\xd3" + // 0x04300308: 0x000004D3 - "\x04\x15\x03\x06\x00\x00\x04\xd6" + // 0x04150306: 0x000004D6 - "\x045\x03\x06\x00\x00\x04\xd7" + // 0x04350306: 0x000004D7 - "\x04\xd8\x03\b\x00\x00\x04\xda" + // 0x04D80308: 0x000004DA - "\x04\xd9\x03\b\x00\x00\x04\xdb" + // 0x04D90308: 0x000004DB - "\x04\x16\x03\b\x00\x00\x04\xdc" + // 0x04160308: 0x000004DC - "\x046\x03\b\x00\x00\x04\xdd" + // 0x04360308: 0x000004DD - "\x04\x17\x03\b\x00\x00\x04\xde" + // 0x04170308: 0x000004DE - "\x047\x03\b\x00\x00\x04\xdf" + // 0x04370308: 0x000004DF - "\x04\x18\x03\x04\x00\x00\x04\xe2" + // 0x04180304: 0x000004E2 - "\x048\x03\x04\x00\x00\x04\xe3" + // 0x04380304: 0x000004E3 - "\x04\x18\x03\b\x00\x00\x04\xe4" + // 0x04180308: 0x000004E4 - "\x048\x03\b\x00\x00\x04\xe5" + // 0x04380308: 0x000004E5 - "\x04\x1e\x03\b\x00\x00\x04\xe6" + // 0x041E0308: 0x000004E6 - "\x04>\x03\b\x00\x00\x04\xe7" + // 0x043E0308: 0x000004E7 - "\x04\xe8\x03\b\x00\x00\x04\xea" + // 0x04E80308: 0x000004EA - "\x04\xe9\x03\b\x00\x00\x04\xeb" + // 0x04E90308: 0x000004EB - "\x04-\x03\b\x00\x00\x04\xec" + // 0x042D0308: 0x000004EC - "\x04M\x03\b\x00\x00\x04\xed" + // 0x044D0308: 0x000004ED - "\x04#\x03\x04\x00\x00\x04\xee" + // 0x04230304: 0x000004EE - "\x04C\x03\x04\x00\x00\x04\xef" + // 0x04430304: 0x000004EF - "\x04#\x03\b\x00\x00\x04\xf0" + // 0x04230308: 0x000004F0 - "\x04C\x03\b\x00\x00\x04\xf1" + // 0x04430308: 0x000004F1 - "\x04#\x03\v\x00\x00\x04\xf2" + // 0x0423030B: 0x000004F2 - "\x04C\x03\v\x00\x00\x04\xf3" + // 0x0443030B: 0x000004F3 - "\x04'\x03\b\x00\x00\x04\xf4" + // 0x04270308: 0x000004F4 - "\x04G\x03\b\x00\x00\x04\xf5" + // 0x04470308: 0x000004F5 - "\x04+\x03\b\x00\x00\x04\xf8" + // 0x042B0308: 0x000004F8 - "\x04K\x03\b\x00\x00\x04\xf9" + // 0x044B0308: 0x000004F9 - "\x06'\x06S\x00\x00\x06\"" + // 0x06270653: 0x00000622 - "\x06'\x06T\x00\x00\x06#" + // 0x06270654: 0x00000623 - "\x06H\x06T\x00\x00\x06$" + // 0x06480654: 0x00000624 - "\x06'\x06U\x00\x00\x06%" + // 0x06270655: 0x00000625 - "\x06J\x06T\x00\x00\x06&" + // 0x064A0654: 0x00000626 - "\x06\xd5\x06T\x00\x00\x06\xc0" + // 0x06D50654: 0x000006C0 - "\x06\xc1\x06T\x00\x00\x06\xc2" + // 0x06C10654: 0x000006C2 - "\x06\xd2\x06T\x00\x00\x06\xd3" + // 0x06D20654: 0x000006D3 - "\t(\t<\x00\x00\t)" + // 0x0928093C: 0x00000929 - "\t0\t<\x00\x00\t1" + // 0x0930093C: 0x00000931 - "\t3\t<\x00\x00\t4" + // 0x0933093C: 0x00000934 - "\t\xc7\t\xbe\x00\x00\t\xcb" + // 0x09C709BE: 0x000009CB - "\t\xc7\t\xd7\x00\x00\t\xcc" + // 0x09C709D7: 0x000009CC - "\vG\vV\x00\x00\vH" + // 0x0B470B56: 0x00000B48 - "\vG\v>\x00\x00\vK" + // 0x0B470B3E: 0x00000B4B - "\vG\vW\x00\x00\vL" + // 0x0B470B57: 0x00000B4C - "\v\x92\v\xd7\x00\x00\v\x94" + // 0x0B920BD7: 0x00000B94 - "\v\xc6\v\xbe\x00\x00\v\xca" + // 0x0BC60BBE: 0x00000BCA - "\v\xc7\v\xbe\x00\x00\v\xcb" + // 0x0BC70BBE: 0x00000BCB - "\v\xc6\v\xd7\x00\x00\v\xcc" + // 0x0BC60BD7: 0x00000BCC - "\fF\fV\x00\x00\fH" + // 0x0C460C56: 0x00000C48 - "\f\xbf\f\xd5\x00\x00\f\xc0" + // 0x0CBF0CD5: 0x00000CC0 - "\f\xc6\f\xd5\x00\x00\f\xc7" + // 0x0CC60CD5: 0x00000CC7 - "\f\xc6\f\xd6\x00\x00\f\xc8" + // 0x0CC60CD6: 0x00000CC8 - "\f\xc6\f\xc2\x00\x00\f\xca" + // 0x0CC60CC2: 0x00000CCA - "\f\xca\f\xd5\x00\x00\f\xcb" + // 0x0CCA0CD5: 0x00000CCB - "\rF\r>\x00\x00\rJ" + // 0x0D460D3E: 0x00000D4A - "\rG\r>\x00\x00\rK" + // 0x0D470D3E: 0x00000D4B - "\rF\rW\x00\x00\rL" + // 0x0D460D57: 0x00000D4C - "\r\xd9\r\xca\x00\x00\r\xda" + // 0x0DD90DCA: 0x00000DDA - "\r\xd9\r\xcf\x00\x00\r\xdc" + // 0x0DD90DCF: 0x00000DDC - "\r\xdc\r\xca\x00\x00\r\xdd" + // 0x0DDC0DCA: 0x00000DDD - "\r\xd9\r\xdf\x00\x00\r\xde" + // 0x0DD90DDF: 0x00000DDE - "\x10%\x10.\x00\x00\x10&" + // 0x1025102E: 0x00001026 - "\x1b\x05\x1b5\x00\x00\x1b\x06" + // 0x1B051B35: 0x00001B06 - "\x1b\a\x1b5\x00\x00\x1b\b" + // 0x1B071B35: 0x00001B08 - "\x1b\t\x1b5\x00\x00\x1b\n" + // 0x1B091B35: 0x00001B0A - "\x1b\v\x1b5\x00\x00\x1b\f" + // 0x1B0B1B35: 0x00001B0C - "\x1b\r\x1b5\x00\x00\x1b\x0e" + // 0x1B0D1B35: 0x00001B0E - "\x1b\x11\x1b5\x00\x00\x1b\x12" + // 0x1B111B35: 0x00001B12 - "\x1b:\x1b5\x00\x00\x1b;" + // 0x1B3A1B35: 0x00001B3B - "\x1b<\x1b5\x00\x00\x1b=" + // 0x1B3C1B35: 0x00001B3D - "\x1b>\x1b5\x00\x00\x1b@" + // 0x1B3E1B35: 0x00001B40 - "\x1b?\x1b5\x00\x00\x1bA" + // 0x1B3F1B35: 0x00001B41 - "\x1bB\x1b5\x00\x00\x1bC" + // 0x1B421B35: 0x00001B43 - "\x00A\x03%\x00\x00\x1e\x00" + // 0x00410325: 0x00001E00 - "\x00a\x03%\x00\x00\x1e\x01" + // 0x00610325: 0x00001E01 - "\x00B\x03\a\x00\x00\x1e\x02" + // 0x00420307: 0x00001E02 - "\x00b\x03\a\x00\x00\x1e\x03" + // 0x00620307: 0x00001E03 - "\x00B\x03#\x00\x00\x1e\x04" + // 0x00420323: 0x00001E04 - "\x00b\x03#\x00\x00\x1e\x05" + // 0x00620323: 0x00001E05 - "\x00B\x031\x00\x00\x1e\x06" + // 0x00420331: 0x00001E06 - "\x00b\x031\x00\x00\x1e\a" + // 0x00620331: 0x00001E07 - "\x00\xc7\x03\x01\x00\x00\x1e\b" + // 0x00C70301: 0x00001E08 - "\x00\xe7\x03\x01\x00\x00\x1e\t" + // 0x00E70301: 0x00001E09 - "\x00D\x03\a\x00\x00\x1e\n" + // 0x00440307: 0x00001E0A - "\x00d\x03\a\x00\x00\x1e\v" + // 0x00640307: 0x00001E0B - "\x00D\x03#\x00\x00\x1e\f" + // 0x00440323: 0x00001E0C - "\x00d\x03#\x00\x00\x1e\r" + // 0x00640323: 0x00001E0D - "\x00D\x031\x00\x00\x1e\x0e" + // 0x00440331: 0x00001E0E - "\x00d\x031\x00\x00\x1e\x0f" + // 0x00640331: 0x00001E0F - "\x00D\x03'\x00\x00\x1e\x10" + // 0x00440327: 0x00001E10 - "\x00d\x03'\x00\x00\x1e\x11" + // 0x00640327: 0x00001E11 - "\x00D\x03-\x00\x00\x1e\x12" + // 0x0044032D: 0x00001E12 - "\x00d\x03-\x00\x00\x1e\x13" + // 0x0064032D: 0x00001E13 - "\x01\x12\x03\x00\x00\x00\x1e\x14" + // 0x01120300: 0x00001E14 - "\x01\x13\x03\x00\x00\x00\x1e\x15" + // 0x01130300: 0x00001E15 - "\x01\x12\x03\x01\x00\x00\x1e\x16" + // 0x01120301: 0x00001E16 - "\x01\x13\x03\x01\x00\x00\x1e\x17" + // 0x01130301: 0x00001E17 - "\x00E\x03-\x00\x00\x1e\x18" + // 0x0045032D: 0x00001E18 - "\x00e\x03-\x00\x00\x1e\x19" + // 0x0065032D: 0x00001E19 - "\x00E\x030\x00\x00\x1e\x1a" + // 0x00450330: 0x00001E1A - "\x00e\x030\x00\x00\x1e\x1b" + // 0x00650330: 0x00001E1B - "\x02(\x03\x06\x00\x00\x1e\x1c" + // 0x02280306: 0x00001E1C - "\x02)\x03\x06\x00\x00\x1e\x1d" + // 0x02290306: 0x00001E1D - "\x00F\x03\a\x00\x00\x1e\x1e" + // 0x00460307: 0x00001E1E - "\x00f\x03\a\x00\x00\x1e\x1f" + // 0x00660307: 0x00001E1F - "\x00G\x03\x04\x00\x00\x1e " + // 0x00470304: 0x00001E20 - "\x00g\x03\x04\x00\x00\x1e!" + // 0x00670304: 0x00001E21 - "\x00H\x03\a\x00\x00\x1e\"" + // 0x00480307: 0x00001E22 - "\x00h\x03\a\x00\x00\x1e#" + // 0x00680307: 0x00001E23 - "\x00H\x03#\x00\x00\x1e$" + // 0x00480323: 0x00001E24 - "\x00h\x03#\x00\x00\x1e%" + // 0x00680323: 0x00001E25 - "\x00H\x03\b\x00\x00\x1e&" + // 0x00480308: 0x00001E26 - "\x00h\x03\b\x00\x00\x1e'" + // 0x00680308: 0x00001E27 - "\x00H\x03'\x00\x00\x1e(" + // 0x00480327: 0x00001E28 - "\x00h\x03'\x00\x00\x1e)" + // 0x00680327: 0x00001E29 - "\x00H\x03.\x00\x00\x1e*" + // 0x0048032E: 0x00001E2A - "\x00h\x03.\x00\x00\x1e+" + // 0x0068032E: 0x00001E2B - "\x00I\x030\x00\x00\x1e," + // 0x00490330: 0x00001E2C - "\x00i\x030\x00\x00\x1e-" + // 0x00690330: 0x00001E2D - "\x00\xcf\x03\x01\x00\x00\x1e." + // 0x00CF0301: 0x00001E2E - "\x00\xef\x03\x01\x00\x00\x1e/" + // 0x00EF0301: 0x00001E2F - "\x00K\x03\x01\x00\x00\x1e0" + // 0x004B0301: 0x00001E30 - "\x00k\x03\x01\x00\x00\x1e1" + // 0x006B0301: 0x00001E31 - "\x00K\x03#\x00\x00\x1e2" + // 0x004B0323: 0x00001E32 - "\x00k\x03#\x00\x00\x1e3" + // 0x006B0323: 0x00001E33 - "\x00K\x031\x00\x00\x1e4" + // 0x004B0331: 0x00001E34 - "\x00k\x031\x00\x00\x1e5" + // 0x006B0331: 0x00001E35 - "\x00L\x03#\x00\x00\x1e6" + // 0x004C0323: 0x00001E36 - "\x00l\x03#\x00\x00\x1e7" + // 0x006C0323: 0x00001E37 - "\x1e6\x03\x04\x00\x00\x1e8" + // 0x1E360304: 0x00001E38 - "\x1e7\x03\x04\x00\x00\x1e9" + // 0x1E370304: 0x00001E39 - "\x00L\x031\x00\x00\x1e:" + // 0x004C0331: 0x00001E3A - "\x00l\x031\x00\x00\x1e;" + // 0x006C0331: 0x00001E3B - "\x00L\x03-\x00\x00\x1e<" + // 0x004C032D: 0x00001E3C - "\x00l\x03-\x00\x00\x1e=" + // 0x006C032D: 0x00001E3D - "\x00M\x03\x01\x00\x00\x1e>" + // 0x004D0301: 0x00001E3E - "\x00m\x03\x01\x00\x00\x1e?" + // 0x006D0301: 0x00001E3F - "\x00M\x03\a\x00\x00\x1e@" + // 0x004D0307: 0x00001E40 - "\x00m\x03\a\x00\x00\x1eA" + // 0x006D0307: 0x00001E41 - "\x00M\x03#\x00\x00\x1eB" + // 0x004D0323: 0x00001E42 - "\x00m\x03#\x00\x00\x1eC" + // 0x006D0323: 0x00001E43 - "\x00N\x03\a\x00\x00\x1eD" + // 0x004E0307: 0x00001E44 - "\x00n\x03\a\x00\x00\x1eE" + // 0x006E0307: 0x00001E45 - "\x00N\x03#\x00\x00\x1eF" + // 0x004E0323: 0x00001E46 - "\x00n\x03#\x00\x00\x1eG" + // 0x006E0323: 0x00001E47 - "\x00N\x031\x00\x00\x1eH" + // 0x004E0331: 0x00001E48 - "\x00n\x031\x00\x00\x1eI" + // 0x006E0331: 0x00001E49 - "\x00N\x03-\x00\x00\x1eJ" + // 0x004E032D: 0x00001E4A - "\x00n\x03-\x00\x00\x1eK" + // 0x006E032D: 0x00001E4B - "\x00\xd5\x03\x01\x00\x00\x1eL" + // 0x00D50301: 0x00001E4C - "\x00\xf5\x03\x01\x00\x00\x1eM" + // 0x00F50301: 0x00001E4D - "\x00\xd5\x03\b\x00\x00\x1eN" + // 0x00D50308: 0x00001E4E - "\x00\xf5\x03\b\x00\x00\x1eO" + // 0x00F50308: 0x00001E4F - "\x01L\x03\x00\x00\x00\x1eP" + // 0x014C0300: 0x00001E50 - "\x01M\x03\x00\x00\x00\x1eQ" + // 0x014D0300: 0x00001E51 - "\x01L\x03\x01\x00\x00\x1eR" + // 0x014C0301: 0x00001E52 - "\x01M\x03\x01\x00\x00\x1eS" + // 0x014D0301: 0x00001E53 - "\x00P\x03\x01\x00\x00\x1eT" + // 0x00500301: 0x00001E54 - "\x00p\x03\x01\x00\x00\x1eU" + // 0x00700301: 0x00001E55 - "\x00P\x03\a\x00\x00\x1eV" + // 0x00500307: 0x00001E56 - "\x00p\x03\a\x00\x00\x1eW" + // 0x00700307: 0x00001E57 - "\x00R\x03\a\x00\x00\x1eX" + // 0x00520307: 0x00001E58 - "\x00r\x03\a\x00\x00\x1eY" + // 0x00720307: 0x00001E59 - "\x00R\x03#\x00\x00\x1eZ" + // 0x00520323: 0x00001E5A - "\x00r\x03#\x00\x00\x1e[" + // 0x00720323: 0x00001E5B - "\x1eZ\x03\x04\x00\x00\x1e\\" + // 0x1E5A0304: 0x00001E5C - "\x1e[\x03\x04\x00\x00\x1e]" + // 0x1E5B0304: 0x00001E5D - "\x00R\x031\x00\x00\x1e^" + // 0x00520331: 0x00001E5E - "\x00r\x031\x00\x00\x1e_" + // 0x00720331: 0x00001E5F - "\x00S\x03\a\x00\x00\x1e`" + // 0x00530307: 0x00001E60 - "\x00s\x03\a\x00\x00\x1ea" + // 0x00730307: 0x00001E61 - "\x00S\x03#\x00\x00\x1eb" + // 0x00530323: 0x00001E62 - "\x00s\x03#\x00\x00\x1ec" + // 0x00730323: 0x00001E63 - "\x01Z\x03\a\x00\x00\x1ed" + // 0x015A0307: 0x00001E64 - "\x01[\x03\a\x00\x00\x1ee" + // 0x015B0307: 0x00001E65 - "\x01`\x03\a\x00\x00\x1ef" + // 0x01600307: 0x00001E66 - "\x01a\x03\a\x00\x00\x1eg" + // 0x01610307: 0x00001E67 - "\x1eb\x03\a\x00\x00\x1eh" + // 0x1E620307: 0x00001E68 - "\x1ec\x03\a\x00\x00\x1ei" + // 0x1E630307: 0x00001E69 - "\x00T\x03\a\x00\x00\x1ej" + // 0x00540307: 0x00001E6A - "\x00t\x03\a\x00\x00\x1ek" + // 0x00740307: 0x00001E6B - "\x00T\x03#\x00\x00\x1el" + // 0x00540323: 0x00001E6C - "\x00t\x03#\x00\x00\x1em" + // 0x00740323: 0x00001E6D - "\x00T\x031\x00\x00\x1en" + // 0x00540331: 0x00001E6E - "\x00t\x031\x00\x00\x1eo" + // 0x00740331: 0x00001E6F - "\x00T\x03-\x00\x00\x1ep" + // 0x0054032D: 0x00001E70 - "\x00t\x03-\x00\x00\x1eq" + // 0x0074032D: 0x00001E71 - "\x00U\x03$\x00\x00\x1er" + // 0x00550324: 0x00001E72 - "\x00u\x03$\x00\x00\x1es" + // 0x00750324: 0x00001E73 - "\x00U\x030\x00\x00\x1et" + // 0x00550330: 0x00001E74 - "\x00u\x030\x00\x00\x1eu" + // 0x00750330: 0x00001E75 - "\x00U\x03-\x00\x00\x1ev" + // 0x0055032D: 0x00001E76 - "\x00u\x03-\x00\x00\x1ew" + // 0x0075032D: 0x00001E77 - "\x01h\x03\x01\x00\x00\x1ex" + // 0x01680301: 0x00001E78 - "\x01i\x03\x01\x00\x00\x1ey" + // 0x01690301: 0x00001E79 - "\x01j\x03\b\x00\x00\x1ez" + // 0x016A0308: 0x00001E7A - "\x01k\x03\b\x00\x00\x1e{" + // 0x016B0308: 0x00001E7B - "\x00V\x03\x03\x00\x00\x1e|" + // 0x00560303: 0x00001E7C - "\x00v\x03\x03\x00\x00\x1e}" + // 0x00760303: 0x00001E7D - "\x00V\x03#\x00\x00\x1e~" + // 0x00560323: 0x00001E7E - "\x00v\x03#\x00\x00\x1e\x7f" + // 0x00760323: 0x00001E7F - "\x00W\x03\x00\x00\x00\x1e\x80" + // 0x00570300: 0x00001E80 - "\x00w\x03\x00\x00\x00\x1e\x81" + // 0x00770300: 0x00001E81 - "\x00W\x03\x01\x00\x00\x1e\x82" + // 0x00570301: 0x00001E82 - "\x00w\x03\x01\x00\x00\x1e\x83" + // 0x00770301: 0x00001E83 - "\x00W\x03\b\x00\x00\x1e\x84" + // 0x00570308: 0x00001E84 - "\x00w\x03\b\x00\x00\x1e\x85" + // 0x00770308: 0x00001E85 - "\x00W\x03\a\x00\x00\x1e\x86" + // 0x00570307: 0x00001E86 - "\x00w\x03\a\x00\x00\x1e\x87" + // 0x00770307: 0x00001E87 - "\x00W\x03#\x00\x00\x1e\x88" + // 0x00570323: 0x00001E88 - "\x00w\x03#\x00\x00\x1e\x89" + // 0x00770323: 0x00001E89 - "\x00X\x03\a\x00\x00\x1e\x8a" + // 0x00580307: 0x00001E8A - "\x00x\x03\a\x00\x00\x1e\x8b" + // 0x00780307: 0x00001E8B - "\x00X\x03\b\x00\x00\x1e\x8c" + // 0x00580308: 0x00001E8C - "\x00x\x03\b\x00\x00\x1e\x8d" + // 0x00780308: 0x00001E8D - "\x00Y\x03\a\x00\x00\x1e\x8e" + // 0x00590307: 0x00001E8E - "\x00y\x03\a\x00\x00\x1e\x8f" + // 0x00790307: 0x00001E8F - "\x00Z\x03\x02\x00\x00\x1e\x90" + // 0x005A0302: 0x00001E90 - "\x00z\x03\x02\x00\x00\x1e\x91" + // 0x007A0302: 0x00001E91 - "\x00Z\x03#\x00\x00\x1e\x92" + // 0x005A0323: 0x00001E92 - "\x00z\x03#\x00\x00\x1e\x93" + // 0x007A0323: 0x00001E93 - "\x00Z\x031\x00\x00\x1e\x94" + // 0x005A0331: 0x00001E94 - "\x00z\x031\x00\x00\x1e\x95" + // 0x007A0331: 0x00001E95 - "\x00h\x031\x00\x00\x1e\x96" + // 0x00680331: 0x00001E96 - "\x00t\x03\b\x00\x00\x1e\x97" + // 0x00740308: 0x00001E97 - "\x00w\x03\n\x00\x00\x1e\x98" + // 0x0077030A: 0x00001E98 - "\x00y\x03\n\x00\x00\x1e\x99" + // 0x0079030A: 0x00001E99 - "\x01\x7f\x03\a\x00\x00\x1e\x9b" + // 0x017F0307: 0x00001E9B - "\x00A\x03#\x00\x00\x1e\xa0" + // 0x00410323: 0x00001EA0 - "\x00a\x03#\x00\x00\x1e\xa1" + // 0x00610323: 0x00001EA1 - "\x00A\x03\t\x00\x00\x1e\xa2" + // 0x00410309: 0x00001EA2 - "\x00a\x03\t\x00\x00\x1e\xa3" + // 0x00610309: 0x00001EA3 - "\x00\xc2\x03\x01\x00\x00\x1e\xa4" + // 0x00C20301: 0x00001EA4 - "\x00\xe2\x03\x01\x00\x00\x1e\xa5" + // 0x00E20301: 0x00001EA5 - "\x00\xc2\x03\x00\x00\x00\x1e\xa6" + // 0x00C20300: 0x00001EA6 - "\x00\xe2\x03\x00\x00\x00\x1e\xa7" + // 0x00E20300: 0x00001EA7 - "\x00\xc2\x03\t\x00\x00\x1e\xa8" + // 0x00C20309: 0x00001EA8 - "\x00\xe2\x03\t\x00\x00\x1e\xa9" + // 0x00E20309: 0x00001EA9 - "\x00\xc2\x03\x03\x00\x00\x1e\xaa" + // 0x00C20303: 0x00001EAA - "\x00\xe2\x03\x03\x00\x00\x1e\xab" + // 0x00E20303: 0x00001EAB - "\x1e\xa0\x03\x02\x00\x00\x1e\xac" + // 0x1EA00302: 0x00001EAC - "\x1e\xa1\x03\x02\x00\x00\x1e\xad" + // 0x1EA10302: 0x00001EAD - "\x01\x02\x03\x01\x00\x00\x1e\xae" + // 0x01020301: 0x00001EAE - "\x01\x03\x03\x01\x00\x00\x1e\xaf" + // 0x01030301: 0x00001EAF - "\x01\x02\x03\x00\x00\x00\x1e\xb0" + // 0x01020300: 0x00001EB0 - "\x01\x03\x03\x00\x00\x00\x1e\xb1" + // 0x01030300: 0x00001EB1 - "\x01\x02\x03\t\x00\x00\x1e\xb2" + // 0x01020309: 0x00001EB2 - "\x01\x03\x03\t\x00\x00\x1e\xb3" + // 0x01030309: 0x00001EB3 - "\x01\x02\x03\x03\x00\x00\x1e\xb4" + // 0x01020303: 0x00001EB4 - "\x01\x03\x03\x03\x00\x00\x1e\xb5" + // 0x01030303: 0x00001EB5 - "\x1e\xa0\x03\x06\x00\x00\x1e\xb6" + // 0x1EA00306: 0x00001EB6 - "\x1e\xa1\x03\x06\x00\x00\x1e\xb7" + // 0x1EA10306: 0x00001EB7 - "\x00E\x03#\x00\x00\x1e\xb8" + // 0x00450323: 0x00001EB8 - "\x00e\x03#\x00\x00\x1e\xb9" + // 0x00650323: 0x00001EB9 - "\x00E\x03\t\x00\x00\x1e\xba" + // 0x00450309: 0x00001EBA - "\x00e\x03\t\x00\x00\x1e\xbb" + // 0x00650309: 0x00001EBB - "\x00E\x03\x03\x00\x00\x1e\xbc" + // 0x00450303: 0x00001EBC - "\x00e\x03\x03\x00\x00\x1e\xbd" + // 0x00650303: 0x00001EBD - "\x00\xca\x03\x01\x00\x00\x1e\xbe" + // 0x00CA0301: 0x00001EBE - "\x00\xea\x03\x01\x00\x00\x1e\xbf" + // 0x00EA0301: 0x00001EBF - "\x00\xca\x03\x00\x00\x00\x1e\xc0" + // 0x00CA0300: 0x00001EC0 - "\x00\xea\x03\x00\x00\x00\x1e\xc1" + // 0x00EA0300: 0x00001EC1 - "\x00\xca\x03\t\x00\x00\x1e\xc2" + // 0x00CA0309: 0x00001EC2 - "\x00\xea\x03\t\x00\x00\x1e\xc3" + // 0x00EA0309: 0x00001EC3 - "\x00\xca\x03\x03\x00\x00\x1e\xc4" + // 0x00CA0303: 0x00001EC4 - "\x00\xea\x03\x03\x00\x00\x1e\xc5" + // 0x00EA0303: 0x00001EC5 - "\x1e\xb8\x03\x02\x00\x00\x1e\xc6" + // 0x1EB80302: 0x00001EC6 - "\x1e\xb9\x03\x02\x00\x00\x1e\xc7" + // 0x1EB90302: 0x00001EC7 - "\x00I\x03\t\x00\x00\x1e\xc8" + // 0x00490309: 0x00001EC8 - "\x00i\x03\t\x00\x00\x1e\xc9" + // 0x00690309: 0x00001EC9 - "\x00I\x03#\x00\x00\x1e\xca" + // 0x00490323: 0x00001ECA - "\x00i\x03#\x00\x00\x1e\xcb" + // 0x00690323: 0x00001ECB - "\x00O\x03#\x00\x00\x1e\xcc" + // 0x004F0323: 0x00001ECC - "\x00o\x03#\x00\x00\x1e\xcd" + // 0x006F0323: 0x00001ECD - "\x00O\x03\t\x00\x00\x1e\xce" + // 0x004F0309: 0x00001ECE - "\x00o\x03\t\x00\x00\x1e\xcf" + // 0x006F0309: 0x00001ECF - "\x00\xd4\x03\x01\x00\x00\x1e\xd0" + // 0x00D40301: 0x00001ED0 - "\x00\xf4\x03\x01\x00\x00\x1e\xd1" + // 0x00F40301: 0x00001ED1 - "\x00\xd4\x03\x00\x00\x00\x1e\xd2" + // 0x00D40300: 0x00001ED2 - "\x00\xf4\x03\x00\x00\x00\x1e\xd3" + // 0x00F40300: 0x00001ED3 - "\x00\xd4\x03\t\x00\x00\x1e\xd4" + // 0x00D40309: 0x00001ED4 - "\x00\xf4\x03\t\x00\x00\x1e\xd5" + // 0x00F40309: 0x00001ED5 - "\x00\xd4\x03\x03\x00\x00\x1e\xd6" + // 0x00D40303: 0x00001ED6 - "\x00\xf4\x03\x03\x00\x00\x1e\xd7" + // 0x00F40303: 0x00001ED7 - "\x1e\xcc\x03\x02\x00\x00\x1e\xd8" + // 0x1ECC0302: 0x00001ED8 - "\x1e\xcd\x03\x02\x00\x00\x1e\xd9" + // 0x1ECD0302: 0x00001ED9 - "\x01\xa0\x03\x01\x00\x00\x1e\xda" + // 0x01A00301: 0x00001EDA - "\x01\xa1\x03\x01\x00\x00\x1e\xdb" + // 0x01A10301: 0x00001EDB - "\x01\xa0\x03\x00\x00\x00\x1e\xdc" + // 0x01A00300: 0x00001EDC - "\x01\xa1\x03\x00\x00\x00\x1e\xdd" + // 0x01A10300: 0x00001EDD - "\x01\xa0\x03\t\x00\x00\x1e\xde" + // 0x01A00309: 0x00001EDE - "\x01\xa1\x03\t\x00\x00\x1e\xdf" + // 0x01A10309: 0x00001EDF - "\x01\xa0\x03\x03\x00\x00\x1e\xe0" + // 0x01A00303: 0x00001EE0 - "\x01\xa1\x03\x03\x00\x00\x1e\xe1" + // 0x01A10303: 0x00001EE1 - "\x01\xa0\x03#\x00\x00\x1e\xe2" + // 0x01A00323: 0x00001EE2 - "\x01\xa1\x03#\x00\x00\x1e\xe3" + // 0x01A10323: 0x00001EE3 - "\x00U\x03#\x00\x00\x1e\xe4" + // 0x00550323: 0x00001EE4 - "\x00u\x03#\x00\x00\x1e\xe5" + // 0x00750323: 0x00001EE5 - "\x00U\x03\t\x00\x00\x1e\xe6" + // 0x00550309: 0x00001EE6 - "\x00u\x03\t\x00\x00\x1e\xe7" + // 0x00750309: 0x00001EE7 - "\x01\xaf\x03\x01\x00\x00\x1e\xe8" + // 0x01AF0301: 0x00001EE8 - "\x01\xb0\x03\x01\x00\x00\x1e\xe9" + // 0x01B00301: 0x00001EE9 - "\x01\xaf\x03\x00\x00\x00\x1e\xea" + // 0x01AF0300: 0x00001EEA - "\x01\xb0\x03\x00\x00\x00\x1e\xeb" + // 0x01B00300: 0x00001EEB - "\x01\xaf\x03\t\x00\x00\x1e\xec" + // 0x01AF0309: 0x00001EEC - "\x01\xb0\x03\t\x00\x00\x1e\xed" + // 0x01B00309: 0x00001EED - "\x01\xaf\x03\x03\x00\x00\x1e\xee" + // 0x01AF0303: 0x00001EEE - "\x01\xb0\x03\x03\x00\x00\x1e\xef" + // 0x01B00303: 0x00001EEF - "\x01\xaf\x03#\x00\x00\x1e\xf0" + // 0x01AF0323: 0x00001EF0 - "\x01\xb0\x03#\x00\x00\x1e\xf1" + // 0x01B00323: 0x00001EF1 - "\x00Y\x03\x00\x00\x00\x1e\xf2" + // 0x00590300: 0x00001EF2 - "\x00y\x03\x00\x00\x00\x1e\xf3" + // 0x00790300: 0x00001EF3 - "\x00Y\x03#\x00\x00\x1e\xf4" + // 0x00590323: 0x00001EF4 - "\x00y\x03#\x00\x00\x1e\xf5" + // 0x00790323: 0x00001EF5 - "\x00Y\x03\t\x00\x00\x1e\xf6" + // 0x00590309: 0x00001EF6 - "\x00y\x03\t\x00\x00\x1e\xf7" + // 0x00790309: 0x00001EF7 - "\x00Y\x03\x03\x00\x00\x1e\xf8" + // 0x00590303: 0x00001EF8 - "\x00y\x03\x03\x00\x00\x1e\xf9" + // 0x00790303: 0x00001EF9 - "\x03\xb1\x03\x13\x00\x00\x1f\x00" + // 0x03B10313: 0x00001F00 - "\x03\xb1\x03\x14\x00\x00\x1f\x01" + // 0x03B10314: 0x00001F01 - "\x1f\x00\x03\x00\x00\x00\x1f\x02" + // 0x1F000300: 0x00001F02 - "\x1f\x01\x03\x00\x00\x00\x1f\x03" + // 0x1F010300: 0x00001F03 - "\x1f\x00\x03\x01\x00\x00\x1f\x04" + // 0x1F000301: 0x00001F04 - "\x1f\x01\x03\x01\x00\x00\x1f\x05" + // 0x1F010301: 0x00001F05 - "\x1f\x00\x03B\x00\x00\x1f\x06" + // 0x1F000342: 0x00001F06 - "\x1f\x01\x03B\x00\x00\x1f\a" + // 0x1F010342: 0x00001F07 - "\x03\x91\x03\x13\x00\x00\x1f\b" + // 0x03910313: 0x00001F08 - "\x03\x91\x03\x14\x00\x00\x1f\t" + // 0x03910314: 0x00001F09 - "\x1f\b\x03\x00\x00\x00\x1f\n" + // 0x1F080300: 0x00001F0A - "\x1f\t\x03\x00\x00\x00\x1f\v" + // 0x1F090300: 0x00001F0B - "\x1f\b\x03\x01\x00\x00\x1f\f" + // 0x1F080301: 0x00001F0C - "\x1f\t\x03\x01\x00\x00\x1f\r" + // 0x1F090301: 0x00001F0D - "\x1f\b\x03B\x00\x00\x1f\x0e" + // 0x1F080342: 0x00001F0E - "\x1f\t\x03B\x00\x00\x1f\x0f" + // 0x1F090342: 0x00001F0F - "\x03\xb5\x03\x13\x00\x00\x1f\x10" + // 0x03B50313: 0x00001F10 - "\x03\xb5\x03\x14\x00\x00\x1f\x11" + // 0x03B50314: 0x00001F11 - "\x1f\x10\x03\x00\x00\x00\x1f\x12" + // 0x1F100300: 0x00001F12 - "\x1f\x11\x03\x00\x00\x00\x1f\x13" + // 0x1F110300: 0x00001F13 - "\x1f\x10\x03\x01\x00\x00\x1f\x14" + // 0x1F100301: 0x00001F14 - "\x1f\x11\x03\x01\x00\x00\x1f\x15" + // 0x1F110301: 0x00001F15 - "\x03\x95\x03\x13\x00\x00\x1f\x18" + // 0x03950313: 0x00001F18 - "\x03\x95\x03\x14\x00\x00\x1f\x19" + // 0x03950314: 0x00001F19 - "\x1f\x18\x03\x00\x00\x00\x1f\x1a" + // 0x1F180300: 0x00001F1A - "\x1f\x19\x03\x00\x00\x00\x1f\x1b" + // 0x1F190300: 0x00001F1B - "\x1f\x18\x03\x01\x00\x00\x1f\x1c" + // 0x1F180301: 0x00001F1C - "\x1f\x19\x03\x01\x00\x00\x1f\x1d" + // 0x1F190301: 0x00001F1D - "\x03\xb7\x03\x13\x00\x00\x1f " + // 0x03B70313: 0x00001F20 - "\x03\xb7\x03\x14\x00\x00\x1f!" + // 0x03B70314: 0x00001F21 - "\x1f \x03\x00\x00\x00\x1f\"" + // 0x1F200300: 0x00001F22 - "\x1f!\x03\x00\x00\x00\x1f#" + // 0x1F210300: 0x00001F23 - "\x1f \x03\x01\x00\x00\x1f$" + // 0x1F200301: 0x00001F24 - "\x1f!\x03\x01\x00\x00\x1f%" + // 0x1F210301: 0x00001F25 - "\x1f \x03B\x00\x00\x1f&" + // 0x1F200342: 0x00001F26 - "\x1f!\x03B\x00\x00\x1f'" + // 0x1F210342: 0x00001F27 - "\x03\x97\x03\x13\x00\x00\x1f(" + // 0x03970313: 0x00001F28 - "\x03\x97\x03\x14\x00\x00\x1f)" + // 0x03970314: 0x00001F29 - "\x1f(\x03\x00\x00\x00\x1f*" + // 0x1F280300: 0x00001F2A - "\x1f)\x03\x00\x00\x00\x1f+" + // 0x1F290300: 0x00001F2B - "\x1f(\x03\x01\x00\x00\x1f," + // 0x1F280301: 0x00001F2C - "\x1f)\x03\x01\x00\x00\x1f-" + // 0x1F290301: 0x00001F2D - "\x1f(\x03B\x00\x00\x1f." + // 0x1F280342: 0x00001F2E - "\x1f)\x03B\x00\x00\x1f/" + // 0x1F290342: 0x00001F2F - "\x03\xb9\x03\x13\x00\x00\x1f0" + // 0x03B90313: 0x00001F30 - "\x03\xb9\x03\x14\x00\x00\x1f1" + // 0x03B90314: 0x00001F31 - "\x1f0\x03\x00\x00\x00\x1f2" + // 0x1F300300: 0x00001F32 - "\x1f1\x03\x00\x00\x00\x1f3" + // 0x1F310300: 0x00001F33 - "\x1f0\x03\x01\x00\x00\x1f4" + // 0x1F300301: 0x00001F34 - "\x1f1\x03\x01\x00\x00\x1f5" + // 0x1F310301: 0x00001F35 - "\x1f0\x03B\x00\x00\x1f6" + // 0x1F300342: 0x00001F36 - "\x1f1\x03B\x00\x00\x1f7" + // 0x1F310342: 0x00001F37 - "\x03\x99\x03\x13\x00\x00\x1f8" + // 0x03990313: 0x00001F38 - "\x03\x99\x03\x14\x00\x00\x1f9" + // 0x03990314: 0x00001F39 - "\x1f8\x03\x00\x00\x00\x1f:" + // 0x1F380300: 0x00001F3A - "\x1f9\x03\x00\x00\x00\x1f;" + // 0x1F390300: 0x00001F3B - "\x1f8\x03\x01\x00\x00\x1f<" + // 0x1F380301: 0x00001F3C - "\x1f9\x03\x01\x00\x00\x1f=" + // 0x1F390301: 0x00001F3D - "\x1f8\x03B\x00\x00\x1f>" + // 0x1F380342: 0x00001F3E - "\x1f9\x03B\x00\x00\x1f?" + // 0x1F390342: 0x00001F3F - "\x03\xbf\x03\x13\x00\x00\x1f@" + // 0x03BF0313: 0x00001F40 - "\x03\xbf\x03\x14\x00\x00\x1fA" + // 0x03BF0314: 0x00001F41 - "\x1f@\x03\x00\x00\x00\x1fB" + // 0x1F400300: 0x00001F42 - "\x1fA\x03\x00\x00\x00\x1fC" + // 0x1F410300: 0x00001F43 - "\x1f@\x03\x01\x00\x00\x1fD" + // 0x1F400301: 0x00001F44 - "\x1fA\x03\x01\x00\x00\x1fE" + // 0x1F410301: 0x00001F45 - "\x03\x9f\x03\x13\x00\x00\x1fH" + // 0x039F0313: 0x00001F48 - "\x03\x9f\x03\x14\x00\x00\x1fI" + // 0x039F0314: 0x00001F49 - "\x1fH\x03\x00\x00\x00\x1fJ" + // 0x1F480300: 0x00001F4A - "\x1fI\x03\x00\x00\x00\x1fK" + // 0x1F490300: 0x00001F4B - "\x1fH\x03\x01\x00\x00\x1fL" + // 0x1F480301: 0x00001F4C - "\x1fI\x03\x01\x00\x00\x1fM" + // 0x1F490301: 0x00001F4D - "\x03\xc5\x03\x13\x00\x00\x1fP" + // 0x03C50313: 0x00001F50 - "\x03\xc5\x03\x14\x00\x00\x1fQ" + // 0x03C50314: 0x00001F51 - "\x1fP\x03\x00\x00\x00\x1fR" + // 0x1F500300: 0x00001F52 - "\x1fQ\x03\x00\x00\x00\x1fS" + // 0x1F510300: 0x00001F53 - "\x1fP\x03\x01\x00\x00\x1fT" + // 0x1F500301: 0x00001F54 - "\x1fQ\x03\x01\x00\x00\x1fU" + // 0x1F510301: 0x00001F55 - "\x1fP\x03B\x00\x00\x1fV" + // 0x1F500342: 0x00001F56 - "\x1fQ\x03B\x00\x00\x1fW" + // 0x1F510342: 0x00001F57 - "\x03\xa5\x03\x14\x00\x00\x1fY" + // 0x03A50314: 0x00001F59 - "\x1fY\x03\x00\x00\x00\x1f[" + // 0x1F590300: 0x00001F5B - "\x1fY\x03\x01\x00\x00\x1f]" + // 0x1F590301: 0x00001F5D - "\x1fY\x03B\x00\x00\x1f_" + // 0x1F590342: 0x00001F5F - "\x03\xc9\x03\x13\x00\x00\x1f`" + // 0x03C90313: 0x00001F60 - "\x03\xc9\x03\x14\x00\x00\x1fa" + // 0x03C90314: 0x00001F61 - "\x1f`\x03\x00\x00\x00\x1fb" + // 0x1F600300: 0x00001F62 - "\x1fa\x03\x00\x00\x00\x1fc" + // 0x1F610300: 0x00001F63 - "\x1f`\x03\x01\x00\x00\x1fd" + // 0x1F600301: 0x00001F64 - "\x1fa\x03\x01\x00\x00\x1fe" + // 0x1F610301: 0x00001F65 - "\x1f`\x03B\x00\x00\x1ff" + // 0x1F600342: 0x00001F66 - "\x1fa\x03B\x00\x00\x1fg" + // 0x1F610342: 0x00001F67 - "\x03\xa9\x03\x13\x00\x00\x1fh" + // 0x03A90313: 0x00001F68 - "\x03\xa9\x03\x14\x00\x00\x1fi" + // 0x03A90314: 0x00001F69 - "\x1fh\x03\x00\x00\x00\x1fj" + // 0x1F680300: 0x00001F6A - "\x1fi\x03\x00\x00\x00\x1fk" + // 0x1F690300: 0x00001F6B - "\x1fh\x03\x01\x00\x00\x1fl" + // 0x1F680301: 0x00001F6C - "\x1fi\x03\x01\x00\x00\x1fm" + // 0x1F690301: 0x00001F6D - "\x1fh\x03B\x00\x00\x1fn" + // 0x1F680342: 0x00001F6E - "\x1fi\x03B\x00\x00\x1fo" + // 0x1F690342: 0x00001F6F - "\x03\xb1\x03\x00\x00\x00\x1fp" + // 0x03B10300: 0x00001F70 - "\x03\xb5\x03\x00\x00\x00\x1fr" + // 0x03B50300: 0x00001F72 - "\x03\xb7\x03\x00\x00\x00\x1ft" + // 0x03B70300: 0x00001F74 - "\x03\xb9\x03\x00\x00\x00\x1fv" + // 0x03B90300: 0x00001F76 - "\x03\xbf\x03\x00\x00\x00\x1fx" + // 0x03BF0300: 0x00001F78 - "\x03\xc5\x03\x00\x00\x00\x1fz" + // 0x03C50300: 0x00001F7A - "\x03\xc9\x03\x00\x00\x00\x1f|" + // 0x03C90300: 0x00001F7C - "\x1f\x00\x03E\x00\x00\x1f\x80" + // 0x1F000345: 0x00001F80 - "\x1f\x01\x03E\x00\x00\x1f\x81" + // 0x1F010345: 0x00001F81 - "\x1f\x02\x03E\x00\x00\x1f\x82" + // 0x1F020345: 0x00001F82 - "\x1f\x03\x03E\x00\x00\x1f\x83" + // 0x1F030345: 0x00001F83 - "\x1f\x04\x03E\x00\x00\x1f\x84" + // 0x1F040345: 0x00001F84 - "\x1f\x05\x03E\x00\x00\x1f\x85" + // 0x1F050345: 0x00001F85 - "\x1f\x06\x03E\x00\x00\x1f\x86" + // 0x1F060345: 0x00001F86 - "\x1f\a\x03E\x00\x00\x1f\x87" + // 0x1F070345: 0x00001F87 - "\x1f\b\x03E\x00\x00\x1f\x88" + // 0x1F080345: 0x00001F88 - "\x1f\t\x03E\x00\x00\x1f\x89" + // 0x1F090345: 0x00001F89 - "\x1f\n\x03E\x00\x00\x1f\x8a" + // 0x1F0A0345: 0x00001F8A - "\x1f\v\x03E\x00\x00\x1f\x8b" + // 0x1F0B0345: 0x00001F8B - "\x1f\f\x03E\x00\x00\x1f\x8c" + // 0x1F0C0345: 0x00001F8C - "\x1f\r\x03E\x00\x00\x1f\x8d" + // 0x1F0D0345: 0x00001F8D - "\x1f\x0e\x03E\x00\x00\x1f\x8e" + // 0x1F0E0345: 0x00001F8E - "\x1f\x0f\x03E\x00\x00\x1f\x8f" + // 0x1F0F0345: 0x00001F8F - "\x1f \x03E\x00\x00\x1f\x90" + // 0x1F200345: 0x00001F90 - "\x1f!\x03E\x00\x00\x1f\x91" + // 0x1F210345: 0x00001F91 - "\x1f\"\x03E\x00\x00\x1f\x92" + // 0x1F220345: 0x00001F92 - "\x1f#\x03E\x00\x00\x1f\x93" + // 0x1F230345: 0x00001F93 - "\x1f$\x03E\x00\x00\x1f\x94" + // 0x1F240345: 0x00001F94 - "\x1f%\x03E\x00\x00\x1f\x95" + // 0x1F250345: 0x00001F95 - "\x1f&\x03E\x00\x00\x1f\x96" + // 0x1F260345: 0x00001F96 - "\x1f'\x03E\x00\x00\x1f\x97" + // 0x1F270345: 0x00001F97 - "\x1f(\x03E\x00\x00\x1f\x98" + // 0x1F280345: 0x00001F98 - "\x1f)\x03E\x00\x00\x1f\x99" + // 0x1F290345: 0x00001F99 - "\x1f*\x03E\x00\x00\x1f\x9a" + // 0x1F2A0345: 0x00001F9A - "\x1f+\x03E\x00\x00\x1f\x9b" + // 0x1F2B0345: 0x00001F9B - "\x1f,\x03E\x00\x00\x1f\x9c" + // 0x1F2C0345: 0x00001F9C - "\x1f-\x03E\x00\x00\x1f\x9d" + // 0x1F2D0345: 0x00001F9D - "\x1f.\x03E\x00\x00\x1f\x9e" + // 0x1F2E0345: 0x00001F9E - "\x1f/\x03E\x00\x00\x1f\x9f" + // 0x1F2F0345: 0x00001F9F - "\x1f`\x03E\x00\x00\x1f\xa0" + // 0x1F600345: 0x00001FA0 - "\x1fa\x03E\x00\x00\x1f\xa1" + // 0x1F610345: 0x00001FA1 - "\x1fb\x03E\x00\x00\x1f\xa2" + // 0x1F620345: 0x00001FA2 - "\x1fc\x03E\x00\x00\x1f\xa3" + // 0x1F630345: 0x00001FA3 - "\x1fd\x03E\x00\x00\x1f\xa4" + // 0x1F640345: 0x00001FA4 - "\x1fe\x03E\x00\x00\x1f\xa5" + // 0x1F650345: 0x00001FA5 - "\x1ff\x03E\x00\x00\x1f\xa6" + // 0x1F660345: 0x00001FA6 - "\x1fg\x03E\x00\x00\x1f\xa7" + // 0x1F670345: 0x00001FA7 - "\x1fh\x03E\x00\x00\x1f\xa8" + // 0x1F680345: 0x00001FA8 - "\x1fi\x03E\x00\x00\x1f\xa9" + // 0x1F690345: 0x00001FA9 - "\x1fj\x03E\x00\x00\x1f\xaa" + // 0x1F6A0345: 0x00001FAA - "\x1fk\x03E\x00\x00\x1f\xab" + // 0x1F6B0345: 0x00001FAB - "\x1fl\x03E\x00\x00\x1f\xac" + // 0x1F6C0345: 0x00001FAC - "\x1fm\x03E\x00\x00\x1f\xad" + // 0x1F6D0345: 0x00001FAD - "\x1fn\x03E\x00\x00\x1f\xae" + // 0x1F6E0345: 0x00001FAE - "\x1fo\x03E\x00\x00\x1f\xaf" + // 0x1F6F0345: 0x00001FAF - "\x03\xb1\x03\x06\x00\x00\x1f\xb0" + // 0x03B10306: 0x00001FB0 - "\x03\xb1\x03\x04\x00\x00\x1f\xb1" + // 0x03B10304: 0x00001FB1 - "\x1fp\x03E\x00\x00\x1f\xb2" + // 0x1F700345: 0x00001FB2 - "\x03\xb1\x03E\x00\x00\x1f\xb3" + // 0x03B10345: 0x00001FB3 - "\x03\xac\x03E\x00\x00\x1f\xb4" + // 0x03AC0345: 0x00001FB4 - "\x03\xb1\x03B\x00\x00\x1f\xb6" + // 0x03B10342: 0x00001FB6 - "\x1f\xb6\x03E\x00\x00\x1f\xb7" + // 0x1FB60345: 0x00001FB7 - "\x03\x91\x03\x06\x00\x00\x1f\xb8" + // 0x03910306: 0x00001FB8 - "\x03\x91\x03\x04\x00\x00\x1f\xb9" + // 0x03910304: 0x00001FB9 - "\x03\x91\x03\x00\x00\x00\x1f\xba" + // 0x03910300: 0x00001FBA - "\x03\x91\x03E\x00\x00\x1f\xbc" + // 0x03910345: 0x00001FBC - "\x00\xa8\x03B\x00\x00\x1f\xc1" + // 0x00A80342: 0x00001FC1 - "\x1ft\x03E\x00\x00\x1f\xc2" + // 0x1F740345: 0x00001FC2 - "\x03\xb7\x03E\x00\x00\x1f\xc3" + // 0x03B70345: 0x00001FC3 - "\x03\xae\x03E\x00\x00\x1f\xc4" + // 0x03AE0345: 0x00001FC4 - "\x03\xb7\x03B\x00\x00\x1f\xc6" + // 0x03B70342: 0x00001FC6 - "\x1f\xc6\x03E\x00\x00\x1f\xc7" + // 0x1FC60345: 0x00001FC7 - "\x03\x95\x03\x00\x00\x00\x1f\xc8" + // 0x03950300: 0x00001FC8 - "\x03\x97\x03\x00\x00\x00\x1f\xca" + // 0x03970300: 0x00001FCA - "\x03\x97\x03E\x00\x00\x1f\xcc" + // 0x03970345: 0x00001FCC - "\x1f\xbf\x03\x00\x00\x00\x1f\xcd" + // 0x1FBF0300: 0x00001FCD - "\x1f\xbf\x03\x01\x00\x00\x1f\xce" + // 0x1FBF0301: 0x00001FCE - "\x1f\xbf\x03B\x00\x00\x1f\xcf" + // 0x1FBF0342: 0x00001FCF - "\x03\xb9\x03\x06\x00\x00\x1f\xd0" + // 0x03B90306: 0x00001FD0 - "\x03\xb9\x03\x04\x00\x00\x1f\xd1" + // 0x03B90304: 0x00001FD1 - "\x03\xca\x03\x00\x00\x00\x1f\xd2" + // 0x03CA0300: 0x00001FD2 - "\x03\xb9\x03B\x00\x00\x1f\xd6" + // 0x03B90342: 0x00001FD6 - "\x03\xca\x03B\x00\x00\x1f\xd7" + // 0x03CA0342: 0x00001FD7 - "\x03\x99\x03\x06\x00\x00\x1f\xd8" + // 0x03990306: 0x00001FD8 - "\x03\x99\x03\x04\x00\x00\x1f\xd9" + // 0x03990304: 0x00001FD9 - "\x03\x99\x03\x00\x00\x00\x1f\xda" + // 0x03990300: 0x00001FDA - "\x1f\xfe\x03\x00\x00\x00\x1f\xdd" + // 0x1FFE0300: 0x00001FDD - "\x1f\xfe\x03\x01\x00\x00\x1f\xde" + // 0x1FFE0301: 0x00001FDE - "\x1f\xfe\x03B\x00\x00\x1f\xdf" + // 0x1FFE0342: 0x00001FDF - "\x03\xc5\x03\x06\x00\x00\x1f\xe0" + // 0x03C50306: 0x00001FE0 - "\x03\xc5\x03\x04\x00\x00\x1f\xe1" + // 0x03C50304: 0x00001FE1 - "\x03\xcb\x03\x00\x00\x00\x1f\xe2" + // 0x03CB0300: 0x00001FE2 - "\x03\xc1\x03\x13\x00\x00\x1f\xe4" + // 0x03C10313: 0x00001FE4 - "\x03\xc1\x03\x14\x00\x00\x1f\xe5" + // 0x03C10314: 0x00001FE5 - "\x03\xc5\x03B\x00\x00\x1f\xe6" + // 0x03C50342: 0x00001FE6 - "\x03\xcb\x03B\x00\x00\x1f\xe7" + // 0x03CB0342: 0x00001FE7 - "\x03\xa5\x03\x06\x00\x00\x1f\xe8" + // 0x03A50306: 0x00001FE8 - "\x03\xa5\x03\x04\x00\x00\x1f\xe9" + // 0x03A50304: 0x00001FE9 - "\x03\xa5\x03\x00\x00\x00\x1f\xea" + // 0x03A50300: 0x00001FEA - "\x03\xa1\x03\x14\x00\x00\x1f\xec" + // 0x03A10314: 0x00001FEC - "\x00\xa8\x03\x00\x00\x00\x1f\xed" + // 0x00A80300: 0x00001FED - "\x1f|\x03E\x00\x00\x1f\xf2" + // 0x1F7C0345: 0x00001FF2 - "\x03\xc9\x03E\x00\x00\x1f\xf3" + // 0x03C90345: 0x00001FF3 - "\x03\xce\x03E\x00\x00\x1f\xf4" + // 0x03CE0345: 0x00001FF4 - "\x03\xc9\x03B\x00\x00\x1f\xf6" + // 0x03C90342: 0x00001FF6 - "\x1f\xf6\x03E\x00\x00\x1f\xf7" + // 0x1FF60345: 0x00001FF7 - "\x03\x9f\x03\x00\x00\x00\x1f\xf8" + // 0x039F0300: 0x00001FF8 - "\x03\xa9\x03\x00\x00\x00\x1f\xfa" + // 0x03A90300: 0x00001FFA - "\x03\xa9\x03E\x00\x00\x1f\xfc" + // 0x03A90345: 0x00001FFC - "!\x90\x038\x00\x00!\x9a" + // 0x21900338: 0x0000219A - "!\x92\x038\x00\x00!\x9b" + // 0x21920338: 0x0000219B - "!\x94\x038\x00\x00!\xae" + // 0x21940338: 0x000021AE - "!\xd0\x038\x00\x00!\xcd" + // 0x21D00338: 0x000021CD - "!\xd4\x038\x00\x00!\xce" + // 0x21D40338: 0x000021CE - "!\xd2\x038\x00\x00!\xcf" + // 0x21D20338: 0x000021CF - "\"\x03\x038\x00\x00\"\x04" + // 0x22030338: 0x00002204 - "\"\b\x038\x00\x00\"\t" + // 0x22080338: 0x00002209 - "\"\v\x038\x00\x00\"\f" + // 0x220B0338: 0x0000220C - "\"#\x038\x00\x00\"$" + // 0x22230338: 0x00002224 - "\"%\x038\x00\x00\"&" + // 0x22250338: 0x00002226 - "\"<\x038\x00\x00\"A" + // 0x223C0338: 0x00002241 - "\"C\x038\x00\x00\"D" + // 0x22430338: 0x00002244 - "\"E\x038\x00\x00\"G" + // 0x22450338: 0x00002247 - "\"H\x038\x00\x00\"I" + // 0x22480338: 0x00002249 - "\x00=\x038\x00\x00\"`" + // 0x003D0338: 0x00002260 - "\"a\x038\x00\x00\"b" + // 0x22610338: 0x00002262 - "\"M\x038\x00\x00\"m" + // 0x224D0338: 0x0000226D - "\x00<\x038\x00\x00\"n" + // 0x003C0338: 0x0000226E - "\x00>\x038\x00\x00\"o" + // 0x003E0338: 0x0000226F - "\"d\x038\x00\x00\"p" + // 0x22640338: 0x00002270 - "\"e\x038\x00\x00\"q" + // 0x22650338: 0x00002271 - "\"r\x038\x00\x00\"t" + // 0x22720338: 0x00002274 - "\"s\x038\x00\x00\"u" + // 0x22730338: 0x00002275 - "\"v\x038\x00\x00\"x" + // 0x22760338: 0x00002278 - "\"w\x038\x00\x00\"y" + // 0x22770338: 0x00002279 - "\"z\x038\x00\x00\"\x80" + // 0x227A0338: 0x00002280 - "\"{\x038\x00\x00\"\x81" + // 0x227B0338: 0x00002281 - "\"\x82\x038\x00\x00\"\x84" + // 0x22820338: 0x00002284 - "\"\x83\x038\x00\x00\"\x85" + // 0x22830338: 0x00002285 - "\"\x86\x038\x00\x00\"\x88" + // 0x22860338: 0x00002288 - "\"\x87\x038\x00\x00\"\x89" + // 0x22870338: 0x00002289 - "\"\xa2\x038\x00\x00\"\xac" + // 0x22A20338: 0x000022AC - "\"\xa8\x038\x00\x00\"\xad" + // 0x22A80338: 0x000022AD - "\"\xa9\x038\x00\x00\"\xae" + // 0x22A90338: 0x000022AE - "\"\xab\x038\x00\x00\"\xaf" + // 0x22AB0338: 0x000022AF - "\"|\x038\x00\x00\"\xe0" + // 0x227C0338: 0x000022E0 - "\"}\x038\x00\x00\"\xe1" + // 0x227D0338: 0x000022E1 - "\"\x91\x038\x00\x00\"\xe2" + // 0x22910338: 0x000022E2 - "\"\x92\x038\x00\x00\"\xe3" + // 0x22920338: 0x000022E3 - "\"\xb2\x038\x00\x00\"\xea" + // 0x22B20338: 0x000022EA - "\"\xb3\x038\x00\x00\"\xeb" + // 0x22B30338: 0x000022EB - "\"\xb4\x038\x00\x00\"\xec" + // 0x22B40338: 0x000022EC - "\"\xb5\x038\x00\x00\"\xed" + // 0x22B50338: 0x000022ED - "0K0\x99\x00\x000L" + // 0x304B3099: 0x0000304C - "0M0\x99\x00\x000N" + // 0x304D3099: 0x0000304E - "0O0\x99\x00\x000P" + // 0x304F3099: 0x00003050 - "0Q0\x99\x00\x000R" + // 0x30513099: 0x00003052 - "0S0\x99\x00\x000T" + // 0x30533099: 0x00003054 - "0U0\x99\x00\x000V" + // 0x30553099: 0x00003056 - "0W0\x99\x00\x000X" + // 0x30573099: 0x00003058 - "0Y0\x99\x00\x000Z" + // 0x30593099: 0x0000305A - "0[0\x99\x00\x000\\" + // 0x305B3099: 0x0000305C - "0]0\x99\x00\x000^" + // 0x305D3099: 0x0000305E - "0_0\x99\x00\x000`" + // 0x305F3099: 0x00003060 - "0a0\x99\x00\x000b" + // 0x30613099: 0x00003062 - "0d0\x99\x00\x000e" + // 0x30643099: 0x00003065 - "0f0\x99\x00\x000g" + // 0x30663099: 0x00003067 - "0h0\x99\x00\x000i" + // 0x30683099: 0x00003069 - "0o0\x99\x00\x000p" + // 0x306F3099: 0x00003070 - "0o0\x9a\x00\x000q" + // 0x306F309A: 0x00003071 - "0r0\x99\x00\x000s" + // 0x30723099: 0x00003073 - "0r0\x9a\x00\x000t" + // 0x3072309A: 0x00003074 - "0u0\x99\x00\x000v" + // 0x30753099: 0x00003076 - "0u0\x9a\x00\x000w" + // 0x3075309A: 0x00003077 - "0x0\x99\x00\x000y" + // 0x30783099: 0x00003079 - "0x0\x9a\x00\x000z" + // 0x3078309A: 0x0000307A - "0{0\x99\x00\x000|" + // 0x307B3099: 0x0000307C - "0{0\x9a\x00\x000}" + // 0x307B309A: 0x0000307D - "0F0\x99\x00\x000\x94" + // 0x30463099: 0x00003094 - "0\x9d0\x99\x00\x000\x9e" + // 0x309D3099: 0x0000309E - "0\xab0\x99\x00\x000\xac" + // 0x30AB3099: 0x000030AC - "0\xad0\x99\x00\x000\xae" + // 0x30AD3099: 0x000030AE - "0\xaf0\x99\x00\x000\xb0" + // 0x30AF3099: 0x000030B0 - "0\xb10\x99\x00\x000\xb2" + // 0x30B13099: 0x000030B2 - "0\xb30\x99\x00\x000\xb4" + // 0x30B33099: 0x000030B4 - "0\xb50\x99\x00\x000\xb6" + // 0x30B53099: 0x000030B6 - "0\xb70\x99\x00\x000\xb8" + // 0x30B73099: 0x000030B8 - "0\xb90\x99\x00\x000\xba" + // 0x30B93099: 0x000030BA - "0\xbb0\x99\x00\x000\xbc" + // 0x30BB3099: 0x000030BC - "0\xbd0\x99\x00\x000\xbe" + // 0x30BD3099: 0x000030BE - "0\xbf0\x99\x00\x000\xc0" + // 0x30BF3099: 0x000030C0 - "0\xc10\x99\x00\x000\xc2" + // 0x30C13099: 0x000030C2 - "0\xc40\x99\x00\x000\xc5" + // 0x30C43099: 0x000030C5 - "0\xc60\x99\x00\x000\xc7" + // 0x30C63099: 0x000030C7 - "0\xc80\x99\x00\x000\xc9" + // 0x30C83099: 0x000030C9 - "0\xcf0\x99\x00\x000\xd0" + // 0x30CF3099: 0x000030D0 - "0\xcf0\x9a\x00\x000\xd1" + // 0x30CF309A: 0x000030D1 - "0\xd20\x99\x00\x000\xd3" + // 0x30D23099: 0x000030D3 - "0\xd20\x9a\x00\x000\xd4" + // 0x30D2309A: 0x000030D4 - "0\xd50\x99\x00\x000\xd6" + // 0x30D53099: 0x000030D6 - "0\xd50\x9a\x00\x000\xd7" + // 0x30D5309A: 0x000030D7 - "0\xd80\x99\x00\x000\xd9" + // 0x30D83099: 0x000030D9 - "0\xd80\x9a\x00\x000\xda" + // 0x30D8309A: 0x000030DA - "0\xdb0\x99\x00\x000\xdc" + // 0x30DB3099: 0x000030DC - "0\xdb0\x9a\x00\x000\xdd" + // 0x30DB309A: 0x000030DD - "0\xa60\x99\x00\x000\xf4" + // 0x30A63099: 0x000030F4 - "0\xef0\x99\x00\x000\xf7" + // 0x30EF3099: 0x000030F7 - "0\xf00\x99\x00\x000\xf8" + // 0x30F03099: 0x000030F8 - "0\xf10\x99\x00\x000\xf9" + // 0x30F13099: 0x000030F9 - "0\xf20\x99\x00\x000\xfa" + // 0x30F23099: 0x000030FA - "0\xfd0\x99\x00\x000\xfe" + // 0x30FD3099: 0x000030FE - "\x10\x99\x10\xba\x00\x01\x10\x9a" + // 0x109910BA: 0x0001109A - "\x10\x9b\x10\xba\x00\x01\x10\x9c" + // 0x109B10BA: 0x0001109C - "\x10\xa5\x10\xba\x00\x01\x10\xab" + // 0x10A510BA: 0x000110AB - "\x111\x11'\x00\x01\x11." + // 0x11311127: 0x0001112E - "\x112\x11'\x00\x01\x11/" + // 0x11321127: 0x0001112F - "\x13G\x13>\x00\x01\x13K" + // 0x1347133E: 0x0001134B - "\x13G\x13W\x00\x01\x13L" + // 0x13471357: 0x0001134C - "\x14\xb9\x14\xba\x00\x01\x14\xbb" + // 0x14B914BA: 0x000114BB - "\x14\xb9\x14\xb0\x00\x01\x14\xbc" + // 0x14B914B0: 0x000114BC - "\x14\xb9\x14\xbd\x00\x01\x14\xbe" + // 0x14B914BD: 0x000114BE - "\x15\xb8\x15\xaf\x00\x01\x15\xba" + // 0x15B815AF: 0x000115BA - "\x15\xb9\x15\xaf\x00\x01\x15\xbb" + // 0x15B915AF: 0x000115BB - "\x195\x190\x00\x01\x198" + // 0x19351930: 0x00011938 + "\x00\x01\x04\x00`\x00\x00\xc0" + // 0x00041 0x00300: 0x000C0 + "\x00\x01\x04\x00` \x00\xc1" + // 0x00041 0x00301: 0x000C1 + "\x00\x01\x04\x00`@\x00\xc2" + // 0x00041 0x00302: 0x000C2 + "\x00\x01\x04\x00``\x00\xc3" + // 0x00041 0x00303: 0x000C3 + "\x00\x01\x04\x00a\x00\x00\xc4" + // 0x00041 0x00308: 0x000C4 + "\x00\x01\x04\x00a@\x00\xc5" + // 0x00041 0x0030A: 0x000C5 + "\x00\x01\f\x00d\xe0\x00\xc7" + // 0x00043 0x00327: 0x000C7 + "\x00\x01\x14\x00`\x00\x00\xc8" + // 0x00045 0x00300: 0x000C8 + "\x00\x01\x14\x00` \x00\xc9" + // 0x00045 0x00301: 0x000C9 + "\x00\x01\x14\x00`@\x00\xca" + // 0x00045 0x00302: 0x000CA + "\x00\x01\x14\x00a\x00\x00\xcb" + // 0x00045 0x00308: 0x000CB + "\x00\x01$\x00`\x00\x00\xcc" + // 0x00049 0x00300: 0x000CC + "\x00\x01$\x00` \x00\xcd" + // 0x00049 0x00301: 0x000CD + "\x00\x01$\x00`@\x00\xce" + // 0x00049 0x00302: 0x000CE + "\x00\x01$\x00a\x00\x00\xcf" + // 0x00049 0x00308: 0x000CF + "\x00\x018\x00``\x00\xd1" + // 0x0004E 0x00303: 0x000D1 + "\x00\x01<\x00`\x00\x00\xd2" + // 0x0004F 0x00300: 0x000D2 + "\x00\x01<\x00` \x00\xd3" + // 0x0004F 0x00301: 0x000D3 + "\x00\x01<\x00`@\x00\xd4" + // 0x0004F 0x00302: 0x000D4 + "\x00\x01<\x00``\x00\xd5" + // 0x0004F 0x00303: 0x000D5 + "\x00\x01<\x00a\x00\x00\xd6" + // 0x0004F 0x00308: 0x000D6 + "\x00\x01T\x00`\x00\x00\xd9" + // 0x00055 0x00300: 0x000D9 + "\x00\x01T\x00` \x00\xda" + // 0x00055 0x00301: 0x000DA + "\x00\x01T\x00`@\x00\xdb" + // 0x00055 0x00302: 0x000DB + "\x00\x01T\x00a\x00\x00\xdc" + // 0x00055 0x00308: 0x000DC + "\x00\x01d\x00` \x00\xdd" + // 0x00059 0x00301: 0x000DD + "\x00\x01\x84\x00`\x00\x00\xe0" + // 0x00061 0x00300: 0x000E0 + "\x00\x01\x84\x00` \x00\xe1" + // 0x00061 0x00301: 0x000E1 + "\x00\x01\x84\x00`@\x00\xe2" + // 0x00061 0x00302: 0x000E2 + "\x00\x01\x84\x00``\x00\xe3" + // 0x00061 0x00303: 0x000E3 + "\x00\x01\x84\x00a\x00\x00\xe4" + // 0x00061 0x00308: 0x000E4 + "\x00\x01\x84\x00a@\x00\xe5" + // 0x00061 0x0030A: 0x000E5 + "\x00\x01\x8c\x00d\xe0\x00\xe7" + // 0x00063 0x00327: 0x000E7 + "\x00\x01\x94\x00`\x00\x00\xe8" + // 0x00065 0x00300: 0x000E8 + "\x00\x01\x94\x00` \x00\xe9" + // 0x00065 0x00301: 0x000E9 + "\x00\x01\x94\x00`@\x00\xea" + // 0x00065 0x00302: 0x000EA + "\x00\x01\x94\x00a\x00\x00\xeb" + // 0x00065 0x00308: 0x000EB + "\x00\x01\xa4\x00`\x00\x00\xec" + // 0x00069 0x00300: 0x000EC + "\x00\x01\xa4\x00` \x00\xed" + // 0x00069 0x00301: 0x000ED + "\x00\x01\xa4\x00`@\x00\xee" + // 0x00069 0x00302: 0x000EE + "\x00\x01\xa4\x00a\x00\x00\xef" + // 0x00069 0x00308: 0x000EF + "\x00\x01\xb8\x00``\x00\xf1" + // 0x0006E 0x00303: 0x000F1 + "\x00\x01\xbc\x00`\x00\x00\xf2" + // 0x0006F 0x00300: 0x000F2 + "\x00\x01\xbc\x00` \x00\xf3" + // 0x0006F 0x00301: 0x000F3 + "\x00\x01\xbc\x00`@\x00\xf4" + // 0x0006F 0x00302: 0x000F4 + "\x00\x01\xbc\x00``\x00\xf5" + // 0x0006F 0x00303: 0x000F5 + "\x00\x01\xbc\x00a\x00\x00\xf6" + // 0x0006F 0x00308: 0x000F6 + "\x00\x01\xd4\x00`\x00\x00\xf9" + // 0x00075 0x00300: 0x000F9 + "\x00\x01\xd4\x00` \x00\xfa" + // 0x00075 0x00301: 0x000FA + "\x00\x01\xd4\x00`@\x00\xfb" + // 0x00075 0x00302: 0x000FB + "\x00\x01\xd4\x00a\x00\x00\xfc" + // 0x00075 0x00308: 0x000FC + "\x00\x01\xe4\x00` \x00\xfd" + // 0x00079 0x00301: 0x000FD + "\x00\x01\xe4\x00a\x00\x00\xff" + // 0x00079 0x00308: 0x000FF + "\x00\x01\x04\x00`\x80\x01\x00" + // 0x00041 0x00304: 0x00100 + "\x00\x01\x84\x00`\x80\x01\x01" + // 0x00061 0x00304: 0x00101 + "\x00\x01\x04\x00`\xc0\x01\x02" + // 0x00041 0x00306: 0x00102 + "\x00\x01\x84\x00`\xc0\x01\x03" + // 0x00061 0x00306: 0x00103 + "\x00\x01\x04\x00e\x00\x01\x04" + // 0x00041 0x00328: 0x00104 + "\x00\x01\x84\x00e\x00\x01\x05" + // 0x00061 0x00328: 0x00105 + "\x00\x01\f\x00` \x01\x06" + // 0x00043 0x00301: 0x00106 + "\x00\x01\x8c\x00` \x01\a" + // 0x00063 0x00301: 0x00107 + "\x00\x01\f\x00`@\x01\b" + // 0x00043 0x00302: 0x00108 + "\x00\x01\x8c\x00`@\x01\t" + // 0x00063 0x00302: 0x00109 + "\x00\x01\f\x00`\xe0\x01\n" + // 0x00043 0x00307: 0x0010A + "\x00\x01\x8c\x00`\xe0\x01\v" + // 0x00063 0x00307: 0x0010B + "\x00\x01\f\x00a\x80\x01\f" + // 0x00043 0x0030C: 0x0010C + "\x00\x01\x8c\x00a\x80\x01\r" + // 0x00063 0x0030C: 0x0010D + "\x00\x01\x10\x00a\x80\x01\x0e" + // 0x00044 0x0030C: 0x0010E + "\x00\x01\x90\x00a\x80\x01\x0f" + // 0x00064 0x0030C: 0x0010F + "\x00\x01\x14\x00`\x80\x01\x12" + // 0x00045 0x00304: 0x00112 + "\x00\x01\x94\x00`\x80\x01\x13" + // 0x00065 0x00304: 0x00113 + "\x00\x01\x14\x00`\xc0\x01\x14" + // 0x00045 0x00306: 0x00114 + "\x00\x01\x94\x00`\xc0\x01\x15" + // 0x00065 0x00306: 0x00115 + "\x00\x01\x14\x00`\xe0\x01\x16" + // 0x00045 0x00307: 0x00116 + "\x00\x01\x94\x00`\xe0\x01\x17" + // 0x00065 0x00307: 0x00117 + "\x00\x01\x14\x00e\x00\x01\x18" + // 0x00045 0x00328: 0x00118 + "\x00\x01\x94\x00e\x00\x01\x19" + // 0x00065 0x00328: 0x00119 + "\x00\x01\x14\x00a\x80\x01\x1a" + // 0x00045 0x0030C: 0x0011A + "\x00\x01\x94\x00a\x80\x01\x1b" + // 0x00065 0x0030C: 0x0011B + "\x00\x01\x1c\x00`@\x01\x1c" + // 0x00047 0x00302: 0x0011C + "\x00\x01\x9c\x00`@\x01\x1d" + // 0x00067 0x00302: 0x0011D + "\x00\x01\x1c\x00`\xc0\x01\x1e" + // 0x00047 0x00306: 0x0011E + "\x00\x01\x9c\x00`\xc0\x01\x1f" + // 0x00067 0x00306: 0x0011F + "\x00\x01\x1c\x00`\xe0\x01 " + // 0x00047 0x00307: 0x00120 + "\x00\x01\x9c\x00`\xe0\x01!" + // 0x00067 0x00307: 0x00121 + "\x00\x01\x1c\x00d\xe0\x01\"" + // 0x00047 0x00327: 0x00122 + "\x00\x01\x9c\x00d\xe0\x01#" + // 0x00067 0x00327: 0x00123 + "\x00\x01 \x00`@\x01$" + // 0x00048 0x00302: 0x00124 + "\x00\x01\xa0\x00`@\x01%" + // 0x00068 0x00302: 0x00125 + "\x00\x01$\x00``\x01(" + // 0x00049 0x00303: 0x00128 + "\x00\x01\xa4\x00``\x01)" + // 0x00069 0x00303: 0x00129 + "\x00\x01$\x00`\x80\x01*" + // 0x00049 0x00304: 0x0012A + "\x00\x01\xa4\x00`\x80\x01+" + // 0x00069 0x00304: 0x0012B + "\x00\x01$\x00`\xc0\x01," + // 0x00049 0x00306: 0x0012C + "\x00\x01\xa4\x00`\xc0\x01-" + // 0x00069 0x00306: 0x0012D + "\x00\x01$\x00e\x00\x01." + // 0x00049 0x00328: 0x0012E + "\x00\x01\xa4\x00e\x00\x01/" + // 0x00069 0x00328: 0x0012F + "\x00\x01$\x00`\xe0\x010" + // 0x00049 0x00307: 0x00130 + "\x00\x01(\x00`@\x014" + // 0x0004A 0x00302: 0x00134 + "\x00\x01\xa8\x00`@\x015" + // 0x0006A 0x00302: 0x00135 + "\x00\x01,\x00d\xe0\x016" + // 0x0004B 0x00327: 0x00136 + "\x00\x01\xac\x00d\xe0\x017" + // 0x0006B 0x00327: 0x00137 + "\x00\x010\x00` \x019" + // 0x0004C 0x00301: 0x00139 + "\x00\x01\xb0\x00` \x01:" + // 0x0006C 0x00301: 0x0013A + "\x00\x010\x00d\xe0\x01;" + // 0x0004C 0x00327: 0x0013B + "\x00\x01\xb0\x00d\xe0\x01<" + // 0x0006C 0x00327: 0x0013C + "\x00\x010\x00a\x80\x01=" + // 0x0004C 0x0030C: 0x0013D + "\x00\x01\xb0\x00a\x80\x01>" + // 0x0006C 0x0030C: 0x0013E + "\x00\x018\x00` \x01C" + // 0x0004E 0x00301: 0x00143 + "\x00\x01\xb8\x00` \x01D" + // 0x0006E 0x00301: 0x00144 + "\x00\x018\x00d\xe0\x01E" + // 0x0004E 0x00327: 0x00145 + "\x00\x01\xb8\x00d\xe0\x01F" + // 0x0006E 0x00327: 0x00146 + "\x00\x018\x00a\x80\x01G" + // 0x0004E 0x0030C: 0x00147 + "\x00\x01\xb8\x00a\x80\x01H" + // 0x0006E 0x0030C: 0x00148 + "\x00\x01<\x00`\x80\x01L" + // 0x0004F 0x00304: 0x0014C + "\x00\x01\xbc\x00`\x80\x01M" + // 0x0006F 0x00304: 0x0014D + "\x00\x01<\x00`\xc0\x01N" + // 0x0004F 0x00306: 0x0014E + "\x00\x01\xbc\x00`\xc0\x01O" + // 0x0006F 0x00306: 0x0014F + "\x00\x01<\x00a`\x01P" + // 0x0004F 0x0030B: 0x00150 + "\x00\x01\xbc\x00a`\x01Q" + // 0x0006F 0x0030B: 0x00151 + "\x00\x01H\x00` \x01T" + // 0x00052 0x00301: 0x00154 + "\x00\x01\xc8\x00` \x01U" + // 0x00072 0x00301: 0x00155 + "\x00\x01H\x00d\xe0\x01V" + // 0x00052 0x00327: 0x00156 + "\x00\x01\xc8\x00d\xe0\x01W" + // 0x00072 0x00327: 0x00157 + "\x00\x01H\x00a\x80\x01X" + // 0x00052 0x0030C: 0x00158 + "\x00\x01\xc8\x00a\x80\x01Y" + // 0x00072 0x0030C: 0x00159 + "\x00\x01L\x00` \x01Z" + // 0x00053 0x00301: 0x0015A + "\x00\x01\xcc\x00` \x01[" + // 0x00073 0x00301: 0x0015B + "\x00\x01L\x00`@\x01\\" + // 0x00053 0x00302: 0x0015C + "\x00\x01\xcc\x00`@\x01]" + // 0x00073 0x00302: 0x0015D + "\x00\x01L\x00d\xe0\x01^" + // 0x00053 0x00327: 0x0015E + "\x00\x01\xcc\x00d\xe0\x01_" + // 0x00073 0x00327: 0x0015F + "\x00\x01L\x00a\x80\x01`" + // 0x00053 0x0030C: 0x00160 + "\x00\x01\xcc\x00a\x80\x01a" + // 0x00073 0x0030C: 0x00161 + "\x00\x01P\x00d\xe0\x01b" + // 0x00054 0x00327: 0x00162 + "\x00\x01\xd0\x00d\xe0\x01c" + // 0x00074 0x00327: 0x00163 + "\x00\x01P\x00a\x80\x01d" + // 0x00054 0x0030C: 0x00164 + "\x00\x01\xd0\x00a\x80\x01e" + // 0x00074 0x0030C: 0x00165 + "\x00\x01T\x00``\x01h" + // 0x00055 0x00303: 0x00168 + "\x00\x01\xd4\x00``\x01i" + // 0x00075 0x00303: 0x00169 + "\x00\x01T\x00`\x80\x01j" + // 0x00055 0x00304: 0x0016A + "\x00\x01\xd4\x00`\x80\x01k" + // 0x00075 0x00304: 0x0016B + "\x00\x01T\x00`\xc0\x01l" + // 0x00055 0x00306: 0x0016C + "\x00\x01\xd4\x00`\xc0\x01m" + // 0x00075 0x00306: 0x0016D + "\x00\x01T\x00a@\x01n" + // 0x00055 0x0030A: 0x0016E + "\x00\x01\xd4\x00a@\x01o" + // 0x00075 0x0030A: 0x0016F + "\x00\x01T\x00a`\x01p" + // 0x00055 0x0030B: 0x00170 + "\x00\x01\xd4\x00a`\x01q" + // 0x00075 0x0030B: 0x00171 + "\x00\x01T\x00e\x00\x01r" + // 0x00055 0x00328: 0x00172 + "\x00\x01\xd4\x00e\x00\x01s" + // 0x00075 0x00328: 0x00173 + "\x00\x01\\\x00`@\x01t" + // 0x00057 0x00302: 0x00174 + "\x00\x01\xdc\x00`@\x01u" + // 0x00077 0x00302: 0x00175 + "\x00\x01d\x00`@\x01v" + // 0x00059 0x00302: 0x00176 + "\x00\x01\xe4\x00`@\x01w" + // 0x00079 0x00302: 0x00177 + "\x00\x01d\x00a\x00\x01x" + // 0x00059 0x00308: 0x00178 + "\x00\x01h\x00` \x01y" + // 0x0005A 0x00301: 0x00179 + "\x00\x01\xe8\x00` \x01z" + // 0x0007A 0x00301: 0x0017A + "\x00\x01h\x00`\xe0\x01{" + // 0x0005A 0x00307: 0x0017B + "\x00\x01\xe8\x00`\xe0\x01|" + // 0x0007A 0x00307: 0x0017C + "\x00\x01h\x00a\x80\x01}" + // 0x0005A 0x0030C: 0x0017D + "\x00\x01\xe8\x00a\x80\x01~" + // 0x0007A 0x0030C: 0x0017E + "\x00\x01<\x00c`\x01\xa0" + // 0x0004F 0x0031B: 0x001A0 + "\x00\x01\xbc\x00c`\x01\xa1" + // 0x0006F 0x0031B: 0x001A1 + "\x00\x01T\x00c`\x01\xaf" + // 0x00055 0x0031B: 0x001AF + "\x00\x01\xd4\x00c`\x01\xb0" + // 0x00075 0x0031B: 0x001B0 + "\x00\x01\x04\x00a\x80\x01\xcd" + // 0x00041 0x0030C: 0x001CD + "\x00\x01\x84\x00a\x80\x01\xce" + // 0x00061 0x0030C: 0x001CE + "\x00\x01$\x00a\x80\x01\xcf" + // 0x00049 0x0030C: 0x001CF + "\x00\x01\xa4\x00a\x80\x01\xd0" + // 0x00069 0x0030C: 0x001D0 + "\x00\x01<\x00a\x80\x01\xd1" + // 0x0004F 0x0030C: 0x001D1 + "\x00\x01\xbc\x00a\x80\x01\xd2" + // 0x0006F 0x0030C: 0x001D2 + "\x00\x01T\x00a\x80\x01\xd3" + // 0x00055 0x0030C: 0x001D3 + "\x00\x01\xd4\x00a\x80\x01\xd4" + // 0x00075 0x0030C: 0x001D4 + "\x00\x03p\x00`\x80\x01\xd5" + // 0x000DC 0x00304: 0x001D5 + "\x00\x03\xf0\x00`\x80\x01\xd6" + // 0x000FC 0x00304: 0x001D6 + "\x00\x03p\x00` \x01\xd7" + // 0x000DC 0x00301: 0x001D7 + "\x00\x03\xf0\x00` \x01\xd8" + // 0x000FC 0x00301: 0x001D8 + "\x00\x03p\x00a\x80\x01\xd9" + // 0x000DC 0x0030C: 0x001D9 + "\x00\x03\xf0\x00a\x80\x01\xda" + // 0x000FC 0x0030C: 0x001DA + "\x00\x03p\x00`\x00\x01\xdb" + // 0x000DC 0x00300: 0x001DB + "\x00\x03\xf0\x00`\x00\x01\xdc" + // 0x000FC 0x00300: 0x001DC + "\x00\x03\x10\x00`\x80\x01\xde" + // 0x000C4 0x00304: 0x001DE + "\x00\x03\x90\x00`\x80\x01\xdf" + // 0x000E4 0x00304: 0x001DF + "\x00\b\x98\x00`\x80\x01\xe0" + // 0x00226 0x00304: 0x001E0 + "\x00\b\x9c\x00`\x80\x01\xe1" + // 0x00227 0x00304: 0x001E1 + "\x00\x03\x18\x00`\x80\x01\xe2" + // 0x000C6 0x00304: 0x001E2 + "\x00\x03\x98\x00`\x80\x01\xe3" + // 0x000E6 0x00304: 0x001E3 + "\x00\x01\x1c\x00a\x80\x01\xe6" + // 0x00047 0x0030C: 0x001E6 + "\x00\x01\x9c\x00a\x80\x01\xe7" + // 0x00067 0x0030C: 0x001E7 + "\x00\x01,\x00a\x80\x01\xe8" + // 0x0004B 0x0030C: 0x001E8 + "\x00\x01\xac\x00a\x80\x01\xe9" + // 0x0006B 0x0030C: 0x001E9 + "\x00\x01<\x00e\x00\x01\xea" + // 0x0004F 0x00328: 0x001EA + "\x00\x01\xbc\x00e\x00\x01\xeb" + // 0x0006F 0x00328: 0x001EB + "\x00\a\xa8\x00`\x80\x01\xec" + // 0x001EA 0x00304: 0x001EC + "\x00\a\xac\x00`\x80\x01\xed" + // 0x001EB 0x00304: 0x001ED + "\x00\x06\xdc\x00a\x80\x01\xee" + // 0x001B7 0x0030C: 0x001EE + "\x00\nH\x00a\x80\x01\xef" + // 0x00292 0x0030C: 0x001EF + "\x00\x01\xa8\x00a\x80\x01\xf0" + // 0x0006A 0x0030C: 0x001F0 + "\x00\x01\x1c\x00` \x01\xf4" + // 0x00047 0x00301: 0x001F4 + "\x00\x01\x9c\x00` \x01\xf5" + // 0x00067 0x00301: 0x001F5 + "\x00\x018\x00`\x00\x01\xf8" + // 0x0004E 0x00300: 0x001F8 + "\x00\x01\xb8\x00`\x00\x01\xf9" + // 0x0006E 0x00300: 0x001F9 + "\x00\x03\x14\x00` \x01\xfa" + // 0x000C5 0x00301: 0x001FA + "\x00\x03\x94\x00` \x01\xfb" + // 0x000E5 0x00301: 0x001FB + "\x00\x03\x18\x00` \x01\xfc" + // 0x000C6 0x00301: 0x001FC + "\x00\x03\x98\x00` \x01\xfd" + // 0x000E6 0x00301: 0x001FD + "\x00\x03`\x00` \x01\xfe" + // 0x000D8 0x00301: 0x001FE + "\x00\x03\xe0\x00` \x01\xff" + // 0x000F8 0x00301: 0x001FF + "\x00\x01\x04\x00a\xe0\x02\x00" + // 0x00041 0x0030F: 0x00200 + "\x00\x01\x84\x00a\xe0\x02\x01" + // 0x00061 0x0030F: 0x00201 + "\x00\x01\x04\x00b \x02\x02" + // 0x00041 0x00311: 0x00202 + "\x00\x01\x84\x00b \x02\x03" + // 0x00061 0x00311: 0x00203 + "\x00\x01\x14\x00a\xe0\x02\x04" + // 0x00045 0x0030F: 0x00204 + "\x00\x01\x94\x00a\xe0\x02\x05" + // 0x00065 0x0030F: 0x00205 + "\x00\x01\x14\x00b \x02\x06" + // 0x00045 0x00311: 0x00206 + "\x00\x01\x94\x00b \x02\a" + // 0x00065 0x00311: 0x00207 + "\x00\x01$\x00a\xe0\x02\b" + // 0x00049 0x0030F: 0x00208 + "\x00\x01\xa4\x00a\xe0\x02\t" + // 0x00069 0x0030F: 0x00209 + "\x00\x01$\x00b \x02\n" + // 0x00049 0x00311: 0x0020A + "\x00\x01\xa4\x00b \x02\v" + // 0x00069 0x00311: 0x0020B + "\x00\x01<\x00a\xe0\x02\f" + // 0x0004F 0x0030F: 0x0020C + "\x00\x01\xbc\x00a\xe0\x02\r" + // 0x0006F 0x0030F: 0x0020D + "\x00\x01<\x00b \x02\x0e" + // 0x0004F 0x00311: 0x0020E + "\x00\x01\xbc\x00b \x02\x0f" + // 0x0006F 0x00311: 0x0020F + "\x00\x01H\x00a\xe0\x02\x10" + // 0x00052 0x0030F: 0x00210 + "\x00\x01\xc8\x00a\xe0\x02\x11" + // 0x00072 0x0030F: 0x00211 + "\x00\x01H\x00b \x02\x12" + // 0x00052 0x00311: 0x00212 + "\x00\x01\xc8\x00b \x02\x13" + // 0x00072 0x00311: 0x00213 + "\x00\x01T\x00a\xe0\x02\x14" + // 0x00055 0x0030F: 0x00214 + "\x00\x01\xd4\x00a\xe0\x02\x15" + // 0x00075 0x0030F: 0x00215 + "\x00\x01T\x00b \x02\x16" + // 0x00055 0x00311: 0x00216 + "\x00\x01\xd4\x00b \x02\x17" + // 0x00075 0x00311: 0x00217 + "\x00\x01L\x00d\xc0\x02\x18" + // 0x00053 0x00326: 0x00218 + "\x00\x01\xcc\x00d\xc0\x02\x19" + // 0x00073 0x00326: 0x00219 + "\x00\x01P\x00d\xc0\x02\x1a" + // 0x00054 0x00326: 0x0021A + "\x00\x01\xd0\x00d\xc0\x02\x1b" + // 0x00074 0x00326: 0x0021B + "\x00\x01 \x00a\x80\x02\x1e" + // 0x00048 0x0030C: 0x0021E + "\x00\x01\xa0\x00a\x80\x02\x1f" + // 0x00068 0x0030C: 0x0021F + "\x00\x01\x04\x00`\xe0\x02&" + // 0x00041 0x00307: 0x00226 + "\x00\x01\x84\x00`\xe0\x02'" + // 0x00061 0x00307: 0x00227 + "\x00\x01\x14\x00d\xe0\x02(" + // 0x00045 0x00327: 0x00228 + "\x00\x01\x94\x00d\xe0\x02)" + // 0x00065 0x00327: 0x00229 + "\x00\x03X\x00`\x80\x02*" + // 0x000D6 0x00304: 0x0022A + "\x00\x03\xd8\x00`\x80\x02+" + // 0x000F6 0x00304: 0x0022B + "\x00\x03T\x00`\x80\x02," + // 0x000D5 0x00304: 0x0022C + "\x00\x03\xd4\x00`\x80\x02-" + // 0x000F5 0x00304: 0x0022D + "\x00\x01<\x00`\xe0\x02." + // 0x0004F 0x00307: 0x0022E + "\x00\x01\xbc\x00`\xe0\x02/" + // 0x0006F 0x00307: 0x0022F + "\x00\b\xb8\x00`\x80\x020" + // 0x0022E 0x00304: 0x00230 + "\x00\b\xbc\x00`\x80\x021" + // 0x0022F 0x00304: 0x00231 + "\x00\x01d\x00`\x80\x022" + // 0x00059 0x00304: 0x00232 + "\x00\x01\xe4\x00`\x80\x023" + // 0x00079 0x00304: 0x00233 + "\x00\x02\xa0\x00` \x03\x85" + // 0x000A8 0x00301: 0x00385 + "\x00\x0eD\x00` \x03\x86" + // 0x00391 0x00301: 0x00386 + "\x00\x0eT\x00` \x03\x88" + // 0x00395 0x00301: 0x00388 + "\x00\x0e\\\x00` \x03\x89" + // 0x00397 0x00301: 0x00389 + "\x00\x0ed\x00` \x03\x8a" + // 0x00399 0x00301: 0x0038A + "\x00\x0e|\x00` \x03\x8c" + // 0x0039F 0x00301: 0x0038C + "\x00\x0e\x94\x00` \x03\x8e" + // 0x003A5 0x00301: 0x0038E + "\x00\x0e\xa4\x00` \x03\x8f" + // 0x003A9 0x00301: 0x0038F + "\x00\x0f(\x00` \x03\x90" + // 0x003CA 0x00301: 0x00390 + "\x00\x0ed\x00a\x00\x03\xaa" + // 0x00399 0x00308: 0x003AA + "\x00\x0e\x94\x00a\x00\x03\xab" + // 0x003A5 0x00308: 0x003AB + "\x00\x0e\xc4\x00` \x03\xac" + // 0x003B1 0x00301: 0x003AC + "\x00\x0e\xd4\x00` \x03\xad" + // 0x003B5 0x00301: 0x003AD + "\x00\x0e\xdc\x00` \x03\xae" + // 0x003B7 0x00301: 0x003AE + "\x00\x0e\xe4\x00` \x03\xaf" + // 0x003B9 0x00301: 0x003AF + "\x00\x0f,\x00` \x03\xb0" + // 0x003CB 0x00301: 0x003B0 + "\x00\x0e\xe4\x00a\x00\x03\xca" + // 0x003B9 0x00308: 0x003CA + "\x00\x0f\x14\x00a\x00\x03\xcb" + // 0x003C5 0x00308: 0x003CB + "\x00\x0e\xfc\x00` \x03\xcc" + // 0x003BF 0x00301: 0x003CC + "\x00\x0f\x14\x00` \x03\xcd" + // 0x003C5 0x00301: 0x003CD + "\x00\x0f$\x00` \x03\xce" + // 0x003C9 0x00301: 0x003CE + "\x00\x0fH\x00` \x03\xd3" + // 0x003D2 0x00301: 0x003D3 + "\x00\x0fH\x00a\x00\x03\xd4" + // 0x003D2 0x00308: 0x003D4 + "\x00\x10T\x00`\x00\x04\x00" + // 0x00415 0x00300: 0x00400 + "\x00\x10T\x00a\x00\x04\x01" + // 0x00415 0x00308: 0x00401 + "\x00\x10L\x00` \x04\x03" + // 0x00413 0x00301: 0x00403 + "\x00\x10\x18\x00a\x00\x04\a" + // 0x00406 0x00308: 0x00407 + "\x00\x10h\x00` \x04\f" + // 0x0041A 0x00301: 0x0040C + "\x00\x10`\x00`\x00\x04\r" + // 0x00418 0x00300: 0x0040D + "\x00\x10\x8c\x00`\xc0\x04\x0e" + // 0x00423 0x00306: 0x0040E + "\x00\x10`\x00`\xc0\x04\x19" + // 0x00418 0x00306: 0x00419 + "\x00\x10\xe0\x00`\xc0\x049" + // 0x00438 0x00306: 0x00439 + "\x00\x10\xd4\x00`\x00\x04P" + // 0x00435 0x00300: 0x00450 + "\x00\x10\xd4\x00a\x00\x04Q" + // 0x00435 0x00308: 0x00451 + "\x00\x10\xcc\x00` \x04S" + // 0x00433 0x00301: 0x00453 + "\x00\x11X\x00a\x00\x04W" + // 0x00456 0x00308: 0x00457 + "\x00\x10\xe8\x00` \x04\\" + // 0x0043A 0x00301: 0x0045C + "\x00\x10\xe0\x00`\x00\x04]" + // 0x00438 0x00300: 0x0045D + "\x00\x11\f\x00`\xc0\x04^" + // 0x00443 0x00306: 0x0045E + "\x00\x11\xd0\x00a\xe0\x04v" + // 0x00474 0x0030F: 0x00476 + "\x00\x11\xd4\x00a\xe0\x04w" + // 0x00475 0x0030F: 0x00477 + "\x00\x10X\x00`\xc0\x04\xc1" + // 0x00416 0x00306: 0x004C1 + "\x00\x10\xd8\x00`\xc0\x04\xc2" + // 0x00436 0x00306: 0x004C2 + "\x00\x10@\x00`\xc0\x04\xd0" + // 0x00410 0x00306: 0x004D0 + "\x00\x10\xc0\x00`\xc0\x04\xd1" + // 0x00430 0x00306: 0x004D1 + "\x00\x10@\x00a\x00\x04\xd2" + // 0x00410 0x00308: 0x004D2 + "\x00\x10\xc0\x00a\x00\x04\xd3" + // 0x00430 0x00308: 0x004D3 + "\x00\x10T\x00`\xc0\x04\xd6" + // 0x00415 0x00306: 0x004D6 + "\x00\x10\xd4\x00`\xc0\x04\xd7" + // 0x00435 0x00306: 0x004D7 + "\x00\x13`\x00a\x00\x04\xda" + // 0x004D8 0x00308: 0x004DA + "\x00\x13d\x00a\x00\x04\xdb" + // 0x004D9 0x00308: 0x004DB + "\x00\x10X\x00a\x00\x04\xdc" + // 0x00416 0x00308: 0x004DC + "\x00\x10\xd8\x00a\x00\x04\xdd" + // 0x00436 0x00308: 0x004DD + "\x00\x10\\\x00a\x00\x04\xde" + // 0x00417 0x00308: 0x004DE + "\x00\x10\xdc\x00a\x00\x04\xdf" + // 0x00437 0x00308: 0x004DF + "\x00\x10`\x00`\x80\x04\xe2" + // 0x00418 0x00304: 0x004E2 + "\x00\x10\xe0\x00`\x80\x04\xe3" + // 0x00438 0x00304: 0x004E3 + "\x00\x10`\x00a\x00\x04\xe4" + // 0x00418 0x00308: 0x004E4 + "\x00\x10\xe0\x00a\x00\x04\xe5" + // 0x00438 0x00308: 0x004E5 + "\x00\x10x\x00a\x00\x04\xe6" + // 0x0041E 0x00308: 0x004E6 + "\x00\x10\xf8\x00a\x00\x04\xe7" + // 0x0043E 0x00308: 0x004E7 + "\x00\x13\xa0\x00a\x00\x04\xea" + // 0x004E8 0x00308: 0x004EA + "\x00\x13\xa4\x00a\x00\x04\xeb" + // 0x004E9 0x00308: 0x004EB + "\x00\x10\xb4\x00a\x00\x04\xec" + // 0x0042D 0x00308: 0x004EC + "\x00\x114\x00a\x00\x04\xed" + // 0x0044D 0x00308: 0x004ED + "\x00\x10\x8c\x00`\x80\x04\xee" + // 0x00423 0x00304: 0x004EE + "\x00\x11\f\x00`\x80\x04\xef" + // 0x00443 0x00304: 0x004EF + "\x00\x10\x8c\x00a\x00\x04\xf0" + // 0x00423 0x00308: 0x004F0 + "\x00\x11\f\x00a\x00\x04\xf1" + // 0x00443 0x00308: 0x004F1 + "\x00\x10\x8c\x00a`\x04\xf2" + // 0x00423 0x0030B: 0x004F2 + "\x00\x11\f\x00a`\x04\xf3" + // 0x00443 0x0030B: 0x004F3 + "\x00\x10\x9c\x00a\x00\x04\xf4" + // 0x00427 0x00308: 0x004F4 + "\x00\x11\x1c\x00a\x00\x04\xf5" + // 0x00447 0x00308: 0x004F5 + "\x00\x10\xac\x00a\x00\x04\xf8" + // 0x0042B 0x00308: 0x004F8 + "\x00\x11,\x00a\x00\x04\xf9" + // 0x0044B 0x00308: 0x004F9 + "\x00\x18\x9c\x00\xca`\x06\"" + // 0x00627 0x00653: 0x00622 + "\x00\x18\x9c\x00ʀ\x06#" + // 0x00627 0x00654: 0x00623 + "\x00\x19 \x00ʀ\x06$" + // 0x00648 0x00654: 0x00624 + "\x00\x18\x9c\x00ʠ\x06%" + // 0x00627 0x00655: 0x00625 + "\x00\x19(\x00ʀ\x06&" + // 0x0064A 0x00654: 0x00626 + "\x00\x1bT\x00ʀ\x06\xc0" + // 0x006D5 0x00654: 0x006C0 + "\x00\x1b\x04\x00ʀ\x06\xc2" + // 0x006C1 0x00654: 0x006C2 + "\x00\x1bH\x00ʀ\x06\xd3" + // 0x006D2 0x00654: 0x006D3 + "\x00$\xa0\x01'\x80\t)" + // 0x00928 0x0093C: 0x00929 + "\x00$\xc0\x01'\x80\t1" + // 0x00930 0x0093C: 0x00931 + "\x00$\xcc\x01'\x80\t4" + // 0x00933 0x0093C: 0x00934 + "\x00'\x1c\x017\xc0\t\xcb" + // 0x009C7 0x009BE: 0x009CB + "\x00'\x1c\x01:\xe0\t\xcc" + // 0x009C7 0x009D7: 0x009CC + "\x00-\x1c\x01j\xc0\vH" + // 0x00B47 0x00B56: 0x00B48 + "\x00-\x1c\x01g\xc0\vK" + // 0x00B47 0x00B3E: 0x00B4B + "\x00-\x1c\x01j\xe0\vL" + // 0x00B47 0x00B57: 0x00B4C + "\x00.H\x01z\xe0\v\x94" + // 0x00B92 0x00BD7: 0x00B94 + "\x00/\x18\x01w\xc0\v\xca" + // 0x00BC6 0x00BBE: 0x00BCA + "\x00/\x1c\x01w\xc0\v\xcb" + // 0x00BC7 0x00BBE: 0x00BCB + "\x00/\x18\x01z\xe0\v\xcc" + // 0x00BC6 0x00BD7: 0x00BCC + "\x001\x18\x01\x8a\xc0\fH" + // 0x00C46 0x00C56: 0x00C48 + "\x002\xfc\x01\x9a\xa0\f\xc0" + // 0x00CBF 0x00CD5: 0x00CC0 + "\x003\x18\x01\x9a\xa0\f\xc7" + // 0x00CC6 0x00CD5: 0x00CC7 + "\x003\x18\x01\x9a\xc0\f\xc8" + // 0x00CC6 0x00CD6: 0x00CC8 + "\x003\x18\x01\x98@\f\xca" + // 0x00CC6 0x00CC2: 0x00CCA + "\x003(\x01\x9a\xa0\f\xcb" + // 0x00CCA 0x00CD5: 0x00CCB + "\x005\x18\x01\xa7\xc0\rJ" + // 0x00D46 0x00D3E: 0x00D4A + "\x005\x1c\x01\xa7\xc0\rK" + // 0x00D47 0x00D3E: 0x00D4B + "\x005\x18\x01\xaa\xe0\rL" + // 0x00D46 0x00D57: 0x00D4C + "\x007d\x01\xb9@\r\xda" + // 0x00DD9 0x00DCA: 0x00DDA + "\x007d\x01\xb9\xe0\r\xdc" + // 0x00DD9 0x00DCF: 0x00DDC + "\x007p\x01\xb9@\r\xdd" + // 0x00DDC 0x00DCA: 0x00DDD + "\x007d\x01\xbb\xe0\r\xde" + // 0x00DD9 0x00DDF: 0x00DDE + "\x00@\x94\x02\x05\xc0\x10&" + // 0x01025 0x0102E: 0x01026 + "\x00l\x14\x03f\xa0\x1b\x06" + // 0x01B05 0x01B35: 0x01B06 + "\x00l\x1c\x03f\xa0\x1b\b" + // 0x01B07 0x01B35: 0x01B08 + "\x00l$\x03f\xa0\x1b\n" + // 0x01B09 0x01B35: 0x01B0A + "\x00l,\x03f\xa0\x1b\f" + // 0x01B0B 0x01B35: 0x01B0C + "\x00l4\x03f\xa0\x1b\x0e" + // 0x01B0D 0x01B35: 0x01B0E + "\x00lD\x03f\xa0\x1b\x12" + // 0x01B11 0x01B35: 0x01B12 + "\x00l\xe8\x03f\xa0\x1b;" + // 0x01B3A 0x01B35: 0x01B3B + "\x00l\xf0\x03f\xa0\x1b=" + // 0x01B3C 0x01B35: 0x01B3D + "\x00l\xf8\x03f\xa0\x1b@" + // 0x01B3E 0x01B35: 0x01B40 + "\x00l\xfc\x03f\xa0\x1bA" + // 0x01B3F 0x01B35: 0x01B41 + "\x00m\b\x03f\xa0\x1bC" + // 0x01B42 0x01B35: 0x01B43 + "\x00\x01\x04\x00d\xa0\x1e\x00" + // 0x00041 0x00325: 0x01E00 + "\x00\x01\x84\x00d\xa0\x1e\x01" + // 0x00061 0x00325: 0x01E01 + "\x00\x01\b\x00`\xe0\x1e\x02" + // 0x00042 0x00307: 0x01E02 + "\x00\x01\x88\x00`\xe0\x1e\x03" + // 0x00062 0x00307: 0x01E03 + "\x00\x01\b\x00d`\x1e\x04" + // 0x00042 0x00323: 0x01E04 + "\x00\x01\x88\x00d`\x1e\x05" + // 0x00062 0x00323: 0x01E05 + "\x00\x01\b\x00f \x1e\x06" + // 0x00042 0x00331: 0x01E06 + "\x00\x01\x88\x00f \x1e\a" + // 0x00062 0x00331: 0x01E07 + "\x00\x03\x1c\x00` \x1e\b" + // 0x000C7 0x00301: 0x01E08 + "\x00\x03\x9c\x00` \x1e\t" + // 0x000E7 0x00301: 0x01E09 + "\x00\x01\x10\x00`\xe0\x1e\n" + // 0x00044 0x00307: 0x01E0A + "\x00\x01\x90\x00`\xe0\x1e\v" + // 0x00064 0x00307: 0x01E0B + "\x00\x01\x10\x00d`\x1e\f" + // 0x00044 0x00323: 0x01E0C + "\x00\x01\x90\x00d`\x1e\r" + // 0x00064 0x00323: 0x01E0D + "\x00\x01\x10\x00f \x1e\x0e" + // 0x00044 0x00331: 0x01E0E + "\x00\x01\x90\x00f \x1e\x0f" + // 0x00064 0x00331: 0x01E0F + "\x00\x01\x10\x00d\xe0\x1e\x10" + // 0x00044 0x00327: 0x01E10 + "\x00\x01\x90\x00d\xe0\x1e\x11" + // 0x00064 0x00327: 0x01E11 + "\x00\x01\x10\x00e\xa0\x1e\x12" + // 0x00044 0x0032D: 0x01E12 + "\x00\x01\x90\x00e\xa0\x1e\x13" + // 0x00064 0x0032D: 0x01E13 + "\x00\x04H\x00`\x00\x1e\x14" + // 0x00112 0x00300: 0x01E14 + "\x00\x04L\x00`\x00\x1e\x15" + // 0x00113 0x00300: 0x01E15 + "\x00\x04H\x00` \x1e\x16" + // 0x00112 0x00301: 0x01E16 + "\x00\x04L\x00` \x1e\x17" + // 0x00113 0x00301: 0x01E17 + "\x00\x01\x14\x00e\xa0\x1e\x18" + // 0x00045 0x0032D: 0x01E18 + "\x00\x01\x94\x00e\xa0\x1e\x19" + // 0x00065 0x0032D: 0x01E19 + "\x00\x01\x14\x00f\x00\x1e\x1a" + // 0x00045 0x00330: 0x01E1A + "\x00\x01\x94\x00f\x00\x1e\x1b" + // 0x00065 0x00330: 0x01E1B + "\x00\b\xa0\x00`\xc0\x1e\x1c" + // 0x00228 0x00306: 0x01E1C + "\x00\b\xa4\x00`\xc0\x1e\x1d" + // 0x00229 0x00306: 0x01E1D + "\x00\x01\x18\x00`\xe0\x1e\x1e" + // 0x00046 0x00307: 0x01E1E + "\x00\x01\x98\x00`\xe0\x1e\x1f" + // 0x00066 0x00307: 0x01E1F + "\x00\x01\x1c\x00`\x80\x1e " + // 0x00047 0x00304: 0x01E20 + "\x00\x01\x9c\x00`\x80\x1e!" + // 0x00067 0x00304: 0x01E21 + "\x00\x01 \x00`\xe0\x1e\"" + // 0x00048 0x00307: 0x01E22 + "\x00\x01\xa0\x00`\xe0\x1e#" + // 0x00068 0x00307: 0x01E23 + "\x00\x01 \x00d`\x1e$" + // 0x00048 0x00323: 0x01E24 + "\x00\x01\xa0\x00d`\x1e%" + // 0x00068 0x00323: 0x01E25 + "\x00\x01 \x00a\x00\x1e&" + // 0x00048 0x00308: 0x01E26 + "\x00\x01\xa0\x00a\x00\x1e'" + // 0x00068 0x00308: 0x01E27 + "\x00\x01 \x00d\xe0\x1e(" + // 0x00048 0x00327: 0x01E28 + "\x00\x01\xa0\x00d\xe0\x1e)" + // 0x00068 0x00327: 0x01E29 + "\x00\x01 \x00e\xc0\x1e*" + // 0x00048 0x0032E: 0x01E2A + "\x00\x01\xa0\x00e\xc0\x1e+" + // 0x00068 0x0032E: 0x01E2B + "\x00\x01$\x00f\x00\x1e," + // 0x00049 0x00330: 0x01E2C + "\x00\x01\xa4\x00f\x00\x1e-" + // 0x00069 0x00330: 0x01E2D + "\x00\x03<\x00` \x1e." + // 0x000CF 0x00301: 0x01E2E + "\x00\x03\xbc\x00` \x1e/" + // 0x000EF 0x00301: 0x01E2F + "\x00\x01,\x00` \x1e0" + // 0x0004B 0x00301: 0x01E30 + "\x00\x01\xac\x00` \x1e1" + // 0x0006B 0x00301: 0x01E31 + "\x00\x01,\x00d`\x1e2" + // 0x0004B 0x00323: 0x01E32 + "\x00\x01\xac\x00d`\x1e3" + // 0x0006B 0x00323: 0x01E33 + "\x00\x01,\x00f \x1e4" + // 0x0004B 0x00331: 0x01E34 + "\x00\x01\xac\x00f \x1e5" + // 0x0006B 0x00331: 0x01E35 + "\x00\x010\x00d`\x1e6" + // 0x0004C 0x00323: 0x01E36 + "\x00\x01\xb0\x00d`\x1e7" + // 0x0006C 0x00323: 0x01E37 + "\x00x\xd8\x00`\x80\x1e8" + // 0x01E36 0x00304: 0x01E38 + "\x00x\xdc\x00`\x80\x1e9" + // 0x01E37 0x00304: 0x01E39 + "\x00\x010\x00f \x1e:" + // 0x0004C 0x00331: 0x01E3A + "\x00\x01\xb0\x00f \x1e;" + // 0x0006C 0x00331: 0x01E3B + "\x00\x010\x00e\xa0\x1e<" + // 0x0004C 0x0032D: 0x01E3C + "\x00\x01\xb0\x00e\xa0\x1e=" + // 0x0006C 0x0032D: 0x01E3D + "\x00\x014\x00` \x1e>" + // 0x0004D 0x00301: 0x01E3E + "\x00\x01\xb4\x00` \x1e?" + // 0x0006D 0x00301: 0x01E3F + "\x00\x014\x00`\xe0\x1e@" + // 0x0004D 0x00307: 0x01E40 + "\x00\x01\xb4\x00`\xe0\x1eA" + // 0x0006D 0x00307: 0x01E41 + "\x00\x014\x00d`\x1eB" + // 0x0004D 0x00323: 0x01E42 + "\x00\x01\xb4\x00d`\x1eC" + // 0x0006D 0x00323: 0x01E43 + "\x00\x018\x00`\xe0\x1eD" + // 0x0004E 0x00307: 0x01E44 + "\x00\x01\xb8\x00`\xe0\x1eE" + // 0x0006E 0x00307: 0x01E45 + "\x00\x018\x00d`\x1eF" + // 0x0004E 0x00323: 0x01E46 + "\x00\x01\xb8\x00d`\x1eG" + // 0x0006E 0x00323: 0x01E47 + "\x00\x018\x00f \x1eH" + // 0x0004E 0x00331: 0x01E48 + "\x00\x01\xb8\x00f \x1eI" + // 0x0006E 0x00331: 0x01E49 + "\x00\x018\x00e\xa0\x1eJ" + // 0x0004E 0x0032D: 0x01E4A + "\x00\x01\xb8\x00e\xa0\x1eK" + // 0x0006E 0x0032D: 0x01E4B + "\x00\x03T\x00` \x1eL" + // 0x000D5 0x00301: 0x01E4C + "\x00\x03\xd4\x00` \x1eM" + // 0x000F5 0x00301: 0x01E4D + "\x00\x03T\x00a\x00\x1eN" + // 0x000D5 0x00308: 0x01E4E + "\x00\x03\xd4\x00a\x00\x1eO" + // 0x000F5 0x00308: 0x01E4F + "\x00\x050\x00`\x00\x1eP" + // 0x0014C 0x00300: 0x01E50 + "\x00\x054\x00`\x00\x1eQ" + // 0x0014D 0x00300: 0x01E51 + "\x00\x050\x00` \x1eR" + // 0x0014C 0x00301: 0x01E52 + "\x00\x054\x00` \x1eS" + // 0x0014D 0x00301: 0x01E53 + "\x00\x01@\x00` \x1eT" + // 0x00050 0x00301: 0x01E54 + "\x00\x01\xc0\x00` \x1eU" + // 0x00070 0x00301: 0x01E55 + "\x00\x01@\x00`\xe0\x1eV" + // 0x00050 0x00307: 0x01E56 + "\x00\x01\xc0\x00`\xe0\x1eW" + // 0x00070 0x00307: 0x01E57 + "\x00\x01H\x00`\xe0\x1eX" + // 0x00052 0x00307: 0x01E58 + "\x00\x01\xc8\x00`\xe0\x1eY" + // 0x00072 0x00307: 0x01E59 + "\x00\x01H\x00d`\x1eZ" + // 0x00052 0x00323: 0x01E5A + "\x00\x01\xc8\x00d`\x1e[" + // 0x00072 0x00323: 0x01E5B + "\x00yh\x00`\x80\x1e\\" + // 0x01E5A 0x00304: 0x01E5C + "\x00yl\x00`\x80\x1e]" + // 0x01E5B 0x00304: 0x01E5D + "\x00\x01H\x00f \x1e^" + // 0x00052 0x00331: 0x01E5E + "\x00\x01\xc8\x00f \x1e_" + // 0x00072 0x00331: 0x01E5F + "\x00\x01L\x00`\xe0\x1e`" + // 0x00053 0x00307: 0x01E60 + "\x00\x01\xcc\x00`\xe0\x1ea" + // 0x00073 0x00307: 0x01E61 + "\x00\x01L\x00d`\x1eb" + // 0x00053 0x00323: 0x01E62 + "\x00\x01\xcc\x00d`\x1ec" + // 0x00073 0x00323: 0x01E63 + "\x00\x05h\x00`\xe0\x1ed" + // 0x0015A 0x00307: 0x01E64 + "\x00\x05l\x00`\xe0\x1ee" + // 0x0015B 0x00307: 0x01E65 + "\x00\x05\x80\x00`\xe0\x1ef" + // 0x00160 0x00307: 0x01E66 + "\x00\x05\x84\x00`\xe0\x1eg" + // 0x00161 0x00307: 0x01E67 + "\x00y\x88\x00`\xe0\x1eh" + // 0x01E62 0x00307: 0x01E68 + "\x00y\x8c\x00`\xe0\x1ei" + // 0x01E63 0x00307: 0x01E69 + "\x00\x01P\x00`\xe0\x1ej" + // 0x00054 0x00307: 0x01E6A + "\x00\x01\xd0\x00`\xe0\x1ek" + // 0x00074 0x00307: 0x01E6B + "\x00\x01P\x00d`\x1el" + // 0x00054 0x00323: 0x01E6C + "\x00\x01\xd0\x00d`\x1em" + // 0x00074 0x00323: 0x01E6D + "\x00\x01P\x00f \x1en" + // 0x00054 0x00331: 0x01E6E + "\x00\x01\xd0\x00f \x1eo" + // 0x00074 0x00331: 0x01E6F + "\x00\x01P\x00e\xa0\x1ep" + // 0x00054 0x0032D: 0x01E70 + "\x00\x01\xd0\x00e\xa0\x1eq" + // 0x00074 0x0032D: 0x01E71 + "\x00\x01T\x00d\x80\x1er" + // 0x00055 0x00324: 0x01E72 + "\x00\x01\xd4\x00d\x80\x1es" + // 0x00075 0x00324: 0x01E73 + "\x00\x01T\x00f\x00\x1et" + // 0x00055 0x00330: 0x01E74 + "\x00\x01\xd4\x00f\x00\x1eu" + // 0x00075 0x00330: 0x01E75 + "\x00\x01T\x00e\xa0\x1ev" + // 0x00055 0x0032D: 0x01E76 + "\x00\x01\xd4\x00e\xa0\x1ew" + // 0x00075 0x0032D: 0x01E77 + "\x00\x05\xa0\x00` \x1ex" + // 0x00168 0x00301: 0x01E78 + "\x00\x05\xa4\x00` \x1ey" + // 0x00169 0x00301: 0x01E79 + "\x00\x05\xa8\x00a\x00\x1ez" + // 0x0016A 0x00308: 0x01E7A + "\x00\x05\xac\x00a\x00\x1e{" + // 0x0016B 0x00308: 0x01E7B + "\x00\x01X\x00``\x1e|" + // 0x00056 0x00303: 0x01E7C + "\x00\x01\xd8\x00``\x1e}" + // 0x00076 0x00303: 0x01E7D + "\x00\x01X\x00d`\x1e~" + // 0x00056 0x00323: 0x01E7E + "\x00\x01\xd8\x00d`\x1e\x7f" + // 0x00076 0x00323: 0x01E7F + "\x00\x01\\\x00`\x00\x1e\x80" + // 0x00057 0x00300: 0x01E80 + "\x00\x01\xdc\x00`\x00\x1e\x81" + // 0x00077 0x00300: 0x01E81 + "\x00\x01\\\x00` \x1e\x82" + // 0x00057 0x00301: 0x01E82 + "\x00\x01\xdc\x00` \x1e\x83" + // 0x00077 0x00301: 0x01E83 + "\x00\x01\\\x00a\x00\x1e\x84" + // 0x00057 0x00308: 0x01E84 + "\x00\x01\xdc\x00a\x00\x1e\x85" + // 0x00077 0x00308: 0x01E85 + "\x00\x01\\\x00`\xe0\x1e\x86" + // 0x00057 0x00307: 0x01E86 + "\x00\x01\xdc\x00`\xe0\x1e\x87" + // 0x00077 0x00307: 0x01E87 + "\x00\x01\\\x00d`\x1e\x88" + // 0x00057 0x00323: 0x01E88 + "\x00\x01\xdc\x00d`\x1e\x89" + // 0x00077 0x00323: 0x01E89 + "\x00\x01`\x00`\xe0\x1e\x8a" + // 0x00058 0x00307: 0x01E8A + "\x00\x01\xe0\x00`\xe0\x1e\x8b" + // 0x00078 0x00307: 0x01E8B + "\x00\x01`\x00a\x00\x1e\x8c" + // 0x00058 0x00308: 0x01E8C + "\x00\x01\xe0\x00a\x00\x1e\x8d" + // 0x00078 0x00308: 0x01E8D + "\x00\x01d\x00`\xe0\x1e\x8e" + // 0x00059 0x00307: 0x01E8E + "\x00\x01\xe4\x00`\xe0\x1e\x8f" + // 0x00079 0x00307: 0x01E8F + "\x00\x01h\x00`@\x1e\x90" + // 0x0005A 0x00302: 0x01E90 + "\x00\x01\xe8\x00`@\x1e\x91" + // 0x0007A 0x00302: 0x01E91 + "\x00\x01h\x00d`\x1e\x92" + // 0x0005A 0x00323: 0x01E92 + "\x00\x01\xe8\x00d`\x1e\x93" + // 0x0007A 0x00323: 0x01E93 + "\x00\x01h\x00f \x1e\x94" + // 0x0005A 0x00331: 0x01E94 + "\x00\x01\xe8\x00f \x1e\x95" + // 0x0007A 0x00331: 0x01E95 + "\x00\x01\xa0\x00f \x1e\x96" + // 0x00068 0x00331: 0x01E96 + "\x00\x01\xd0\x00a\x00\x1e\x97" + // 0x00074 0x00308: 0x01E97 + "\x00\x01\xdc\x00a@\x1e\x98" + // 0x00077 0x0030A: 0x01E98 + "\x00\x01\xe4\x00a@\x1e\x99" + // 0x00079 0x0030A: 0x01E99 + "\x00\x05\xfc\x00`\xe0\x1e\x9b" + // 0x0017F 0x00307: 0x01E9B + "\x00\x01\x04\x00d`\x1e\xa0" + // 0x00041 0x00323: 0x01EA0 + "\x00\x01\x84\x00d`\x1e\xa1" + // 0x00061 0x00323: 0x01EA1 + "\x00\x01\x04\x00a \x1e\xa2" + // 0x00041 0x00309: 0x01EA2 + "\x00\x01\x84\x00a \x1e\xa3" + // 0x00061 0x00309: 0x01EA3 + "\x00\x03\b\x00` \x1e\xa4" + // 0x000C2 0x00301: 0x01EA4 + "\x00\x03\x88\x00` \x1e\xa5" + // 0x000E2 0x00301: 0x01EA5 + "\x00\x03\b\x00`\x00\x1e\xa6" + // 0x000C2 0x00300: 0x01EA6 + "\x00\x03\x88\x00`\x00\x1e\xa7" + // 0x000E2 0x00300: 0x01EA7 + "\x00\x03\b\x00a \x1e\xa8" + // 0x000C2 0x00309: 0x01EA8 + "\x00\x03\x88\x00a \x1e\xa9" + // 0x000E2 0x00309: 0x01EA9 + "\x00\x03\b\x00``\x1e\xaa" + // 0x000C2 0x00303: 0x01EAA + "\x00\x03\x88\x00``\x1e\xab" + // 0x000E2 0x00303: 0x01EAB + "\x00z\x80\x00`@\x1e\xac" + // 0x01EA0 0x00302: 0x01EAC + "\x00z\x84\x00`@\x1e\xad" + // 0x01EA1 0x00302: 0x01EAD + "\x00\x04\b\x00` \x1e\xae" + // 0x00102 0x00301: 0x01EAE + "\x00\x04\f\x00` \x1e\xaf" + // 0x00103 0x00301: 0x01EAF + "\x00\x04\b\x00`\x00\x1e\xb0" + // 0x00102 0x00300: 0x01EB0 + "\x00\x04\f\x00`\x00\x1e\xb1" + // 0x00103 0x00300: 0x01EB1 + "\x00\x04\b\x00a \x1e\xb2" + // 0x00102 0x00309: 0x01EB2 + "\x00\x04\f\x00a \x1e\xb3" + // 0x00103 0x00309: 0x01EB3 + "\x00\x04\b\x00``\x1e\xb4" + // 0x00102 0x00303: 0x01EB4 + "\x00\x04\f\x00``\x1e\xb5" + // 0x00103 0x00303: 0x01EB5 + "\x00z\x80\x00`\xc0\x1e\xb6" + // 0x01EA0 0x00306: 0x01EB6 + "\x00z\x84\x00`\xc0\x1e\xb7" + // 0x01EA1 0x00306: 0x01EB7 + "\x00\x01\x14\x00d`\x1e\xb8" + // 0x00045 0x00323: 0x01EB8 + "\x00\x01\x94\x00d`\x1e\xb9" + // 0x00065 0x00323: 0x01EB9 + "\x00\x01\x14\x00a \x1e\xba" + // 0x00045 0x00309: 0x01EBA + "\x00\x01\x94\x00a \x1e\xbb" + // 0x00065 0x00309: 0x01EBB + "\x00\x01\x14\x00``\x1e\xbc" + // 0x00045 0x00303: 0x01EBC + "\x00\x01\x94\x00``\x1e\xbd" + // 0x00065 0x00303: 0x01EBD + "\x00\x03(\x00` \x1e\xbe" + // 0x000CA 0x00301: 0x01EBE + "\x00\x03\xa8\x00` \x1e\xbf" + // 0x000EA 0x00301: 0x01EBF + "\x00\x03(\x00`\x00\x1e\xc0" + // 0x000CA 0x00300: 0x01EC0 + "\x00\x03\xa8\x00`\x00\x1e\xc1" + // 0x000EA 0x00300: 0x01EC1 + "\x00\x03(\x00a \x1e\xc2" + // 0x000CA 0x00309: 0x01EC2 + "\x00\x03\xa8\x00a \x1e\xc3" + // 0x000EA 0x00309: 0x01EC3 + "\x00\x03(\x00``\x1e\xc4" + // 0x000CA 0x00303: 0x01EC4 + "\x00\x03\xa8\x00``\x1e\xc5" + // 0x000EA 0x00303: 0x01EC5 + "\x00z\xe0\x00`@\x1e\xc6" + // 0x01EB8 0x00302: 0x01EC6 + "\x00z\xe4\x00`@\x1e\xc7" + // 0x01EB9 0x00302: 0x01EC7 + "\x00\x01$\x00a \x1e\xc8" + // 0x00049 0x00309: 0x01EC8 + "\x00\x01\xa4\x00a \x1e\xc9" + // 0x00069 0x00309: 0x01EC9 + "\x00\x01$\x00d`\x1e\xca" + // 0x00049 0x00323: 0x01ECA + "\x00\x01\xa4\x00d`\x1e\xcb" + // 0x00069 0x00323: 0x01ECB + "\x00\x01<\x00d`\x1e\xcc" + // 0x0004F 0x00323: 0x01ECC + "\x00\x01\xbc\x00d`\x1e\xcd" + // 0x0006F 0x00323: 0x01ECD + "\x00\x01<\x00a \x1e\xce" + // 0x0004F 0x00309: 0x01ECE + "\x00\x01\xbc\x00a \x1e\xcf" + // 0x0006F 0x00309: 0x01ECF + "\x00\x03P\x00` \x1e\xd0" + // 0x000D4 0x00301: 0x01ED0 + "\x00\x03\xd0\x00` \x1e\xd1" + // 0x000F4 0x00301: 0x01ED1 + "\x00\x03P\x00`\x00\x1e\xd2" + // 0x000D4 0x00300: 0x01ED2 + "\x00\x03\xd0\x00`\x00\x1e\xd3" + // 0x000F4 0x00300: 0x01ED3 + "\x00\x03P\x00a \x1e\xd4" + // 0x000D4 0x00309: 0x01ED4 + "\x00\x03\xd0\x00a \x1e\xd5" + // 0x000F4 0x00309: 0x01ED5 + "\x00\x03P\x00``\x1e\xd6" + // 0x000D4 0x00303: 0x01ED6 + "\x00\x03\xd0\x00``\x1e\xd7" + // 0x000F4 0x00303: 0x01ED7 + "\x00{0\x00`@\x1e\xd8" + // 0x01ECC 0x00302: 0x01ED8 + "\x00{4\x00`@\x1e\xd9" + // 0x01ECD 0x00302: 0x01ED9 + "\x00\x06\x80\x00` \x1e\xda" + // 0x001A0 0x00301: 0x01EDA + "\x00\x06\x84\x00` \x1e\xdb" + // 0x001A1 0x00301: 0x01EDB + "\x00\x06\x80\x00`\x00\x1e\xdc" + // 0x001A0 0x00300: 0x01EDC + "\x00\x06\x84\x00`\x00\x1e\xdd" + // 0x001A1 0x00300: 0x01EDD + "\x00\x06\x80\x00a \x1e\xde" + // 0x001A0 0x00309: 0x01EDE + "\x00\x06\x84\x00a \x1e\xdf" + // 0x001A1 0x00309: 0x01EDF + "\x00\x06\x80\x00``\x1e\xe0" + // 0x001A0 0x00303: 0x01EE0 + "\x00\x06\x84\x00``\x1e\xe1" + // 0x001A1 0x00303: 0x01EE1 + "\x00\x06\x80\x00d`\x1e\xe2" + // 0x001A0 0x00323: 0x01EE2 + "\x00\x06\x84\x00d`\x1e\xe3" + // 0x001A1 0x00323: 0x01EE3 + "\x00\x01T\x00d`\x1e\xe4" + // 0x00055 0x00323: 0x01EE4 + "\x00\x01\xd4\x00d`\x1e\xe5" + // 0x00075 0x00323: 0x01EE5 + "\x00\x01T\x00a \x1e\xe6" + // 0x00055 0x00309: 0x01EE6 + "\x00\x01\xd4\x00a \x1e\xe7" + // 0x00075 0x00309: 0x01EE7 + "\x00\x06\xbc\x00` \x1e\xe8" + // 0x001AF 0x00301: 0x01EE8 + "\x00\x06\xc0\x00` \x1e\xe9" + // 0x001B0 0x00301: 0x01EE9 + "\x00\x06\xbc\x00`\x00\x1e\xea" + // 0x001AF 0x00300: 0x01EEA + "\x00\x06\xc0\x00`\x00\x1e\xeb" + // 0x001B0 0x00300: 0x01EEB + "\x00\x06\xbc\x00a \x1e\xec" + // 0x001AF 0x00309: 0x01EEC + "\x00\x06\xc0\x00a \x1e\xed" + // 0x001B0 0x00309: 0x01EED + "\x00\x06\xbc\x00``\x1e\xee" + // 0x001AF 0x00303: 0x01EEE + "\x00\x06\xc0\x00``\x1e\xef" + // 0x001B0 0x00303: 0x01EEF + "\x00\x06\xbc\x00d`\x1e\xf0" + // 0x001AF 0x00323: 0x01EF0 + "\x00\x06\xc0\x00d`\x1e\xf1" + // 0x001B0 0x00323: 0x01EF1 + "\x00\x01d\x00`\x00\x1e\xf2" + // 0x00059 0x00300: 0x01EF2 + "\x00\x01\xe4\x00`\x00\x1e\xf3" + // 0x00079 0x00300: 0x01EF3 + "\x00\x01d\x00d`\x1e\xf4" + // 0x00059 0x00323: 0x01EF4 + "\x00\x01\xe4\x00d`\x1e\xf5" + // 0x00079 0x00323: 0x01EF5 + "\x00\x01d\x00a \x1e\xf6" + // 0x00059 0x00309: 0x01EF6 + "\x00\x01\xe4\x00a \x1e\xf7" + // 0x00079 0x00309: 0x01EF7 + "\x00\x01d\x00``\x1e\xf8" + // 0x00059 0x00303: 0x01EF8 + "\x00\x01\xe4\x00``\x1e\xf9" + // 0x00079 0x00303: 0x01EF9 + "\x00\x0e\xc4\x00b`\x1f\x00" + // 0x003B1 0x00313: 0x01F00 + "\x00\x0e\xc4\x00b\x80\x1f\x01" + // 0x003B1 0x00314: 0x01F01 + "\x00|\x00\x00`\x00\x1f\x02" + // 0x01F00 0x00300: 0x01F02 + "\x00|\x04\x00`\x00\x1f\x03" + // 0x01F01 0x00300: 0x01F03 + "\x00|\x00\x00` \x1f\x04" + // 0x01F00 0x00301: 0x01F04 + "\x00|\x04\x00` \x1f\x05" + // 0x01F01 0x00301: 0x01F05 + "\x00|\x00\x00h@\x1f\x06" + // 0x01F00 0x00342: 0x01F06 + "\x00|\x04\x00h@\x1f\a" + // 0x01F01 0x00342: 0x01F07 + "\x00\x0eD\x00b`\x1f\b" + // 0x00391 0x00313: 0x01F08 + "\x00\x0eD\x00b\x80\x1f\t" + // 0x00391 0x00314: 0x01F09 + "\x00| \x00`\x00\x1f\n" + // 0x01F08 0x00300: 0x01F0A + "\x00|$\x00`\x00\x1f\v" + // 0x01F09 0x00300: 0x01F0B + "\x00| \x00` \x1f\f" + // 0x01F08 0x00301: 0x01F0C + "\x00|$\x00` \x1f\r" + // 0x01F09 0x00301: 0x01F0D + "\x00| \x00h@\x1f\x0e" + // 0x01F08 0x00342: 0x01F0E + "\x00|$\x00h@\x1f\x0f" + // 0x01F09 0x00342: 0x01F0F + "\x00\x0e\xd4\x00b`\x1f\x10" + // 0x003B5 0x00313: 0x01F10 + "\x00\x0e\xd4\x00b\x80\x1f\x11" + // 0x003B5 0x00314: 0x01F11 + "\x00|@\x00`\x00\x1f\x12" + // 0x01F10 0x00300: 0x01F12 + "\x00|D\x00`\x00\x1f\x13" + // 0x01F11 0x00300: 0x01F13 + "\x00|@\x00` \x1f\x14" + // 0x01F10 0x00301: 0x01F14 + "\x00|D\x00` \x1f\x15" + // 0x01F11 0x00301: 0x01F15 + "\x00\x0eT\x00b`\x1f\x18" + // 0x00395 0x00313: 0x01F18 + "\x00\x0eT\x00b\x80\x1f\x19" + // 0x00395 0x00314: 0x01F19 + "\x00|`\x00`\x00\x1f\x1a" + // 0x01F18 0x00300: 0x01F1A + "\x00|d\x00`\x00\x1f\x1b" + // 0x01F19 0x00300: 0x01F1B + "\x00|`\x00` \x1f\x1c" + // 0x01F18 0x00301: 0x01F1C + "\x00|d\x00` \x1f\x1d" + // 0x01F19 0x00301: 0x01F1D + "\x00\x0e\xdc\x00b`\x1f " + // 0x003B7 0x00313: 0x01F20 + "\x00\x0e\xdc\x00b\x80\x1f!" + // 0x003B7 0x00314: 0x01F21 + "\x00|\x80\x00`\x00\x1f\"" + // 0x01F20 0x00300: 0x01F22 + "\x00|\x84\x00`\x00\x1f#" + // 0x01F21 0x00300: 0x01F23 + "\x00|\x80\x00` \x1f$" + // 0x01F20 0x00301: 0x01F24 + "\x00|\x84\x00` \x1f%" + // 0x01F21 0x00301: 0x01F25 + "\x00|\x80\x00h@\x1f&" + // 0x01F20 0x00342: 0x01F26 + "\x00|\x84\x00h@\x1f'" + // 0x01F21 0x00342: 0x01F27 + "\x00\x0e\\\x00b`\x1f(" + // 0x00397 0x00313: 0x01F28 + "\x00\x0e\\\x00b\x80\x1f)" + // 0x00397 0x00314: 0x01F29 + "\x00|\xa0\x00`\x00\x1f*" + // 0x01F28 0x00300: 0x01F2A + "\x00|\xa4\x00`\x00\x1f+" + // 0x01F29 0x00300: 0x01F2B + "\x00|\xa0\x00` \x1f," + // 0x01F28 0x00301: 0x01F2C + "\x00|\xa4\x00` \x1f-" + // 0x01F29 0x00301: 0x01F2D + "\x00|\xa0\x00h@\x1f." + // 0x01F28 0x00342: 0x01F2E + "\x00|\xa4\x00h@\x1f/" + // 0x01F29 0x00342: 0x01F2F + "\x00\x0e\xe4\x00b`\x1f0" + // 0x003B9 0x00313: 0x01F30 + "\x00\x0e\xe4\x00b\x80\x1f1" + // 0x003B9 0x00314: 0x01F31 + "\x00|\xc0\x00`\x00\x1f2" + // 0x01F30 0x00300: 0x01F32 + "\x00|\xc4\x00`\x00\x1f3" + // 0x01F31 0x00300: 0x01F33 + "\x00|\xc0\x00` \x1f4" + // 0x01F30 0x00301: 0x01F34 + "\x00|\xc4\x00` \x1f5" + // 0x01F31 0x00301: 0x01F35 + "\x00|\xc0\x00h@\x1f6" + // 0x01F30 0x00342: 0x01F36 + "\x00|\xc4\x00h@\x1f7" + // 0x01F31 0x00342: 0x01F37 + "\x00\x0ed\x00b`\x1f8" + // 0x00399 0x00313: 0x01F38 + "\x00\x0ed\x00b\x80\x1f9" + // 0x00399 0x00314: 0x01F39 + "\x00|\xe0\x00`\x00\x1f:" + // 0x01F38 0x00300: 0x01F3A + "\x00|\xe4\x00`\x00\x1f;" + // 0x01F39 0x00300: 0x01F3B + "\x00|\xe0\x00` \x1f<" + // 0x01F38 0x00301: 0x01F3C + "\x00|\xe4\x00` \x1f=" + // 0x01F39 0x00301: 0x01F3D + "\x00|\xe0\x00h@\x1f>" + // 0x01F38 0x00342: 0x01F3E + "\x00|\xe4\x00h@\x1f?" + // 0x01F39 0x00342: 0x01F3F + "\x00\x0e\xfc\x00b`\x1f@" + // 0x003BF 0x00313: 0x01F40 + "\x00\x0e\xfc\x00b\x80\x1fA" + // 0x003BF 0x00314: 0x01F41 + "\x00}\x00\x00`\x00\x1fB" + // 0x01F40 0x00300: 0x01F42 + "\x00}\x04\x00`\x00\x1fC" + // 0x01F41 0x00300: 0x01F43 + "\x00}\x00\x00` \x1fD" + // 0x01F40 0x00301: 0x01F44 + "\x00}\x04\x00` \x1fE" + // 0x01F41 0x00301: 0x01F45 + "\x00\x0e|\x00b`\x1fH" + // 0x0039F 0x00313: 0x01F48 + "\x00\x0e|\x00b\x80\x1fI" + // 0x0039F 0x00314: 0x01F49 + "\x00} \x00`\x00\x1fJ" + // 0x01F48 0x00300: 0x01F4A + "\x00}$\x00`\x00\x1fK" + // 0x01F49 0x00300: 0x01F4B + "\x00} \x00` \x1fL" + // 0x01F48 0x00301: 0x01F4C + "\x00}$\x00` \x1fM" + // 0x01F49 0x00301: 0x01F4D + "\x00\x0f\x14\x00b`\x1fP" + // 0x003C5 0x00313: 0x01F50 + "\x00\x0f\x14\x00b\x80\x1fQ" + // 0x003C5 0x00314: 0x01F51 + "\x00}@\x00`\x00\x1fR" + // 0x01F50 0x00300: 0x01F52 + "\x00}D\x00`\x00\x1fS" + // 0x01F51 0x00300: 0x01F53 + "\x00}@\x00` \x1fT" + // 0x01F50 0x00301: 0x01F54 + "\x00}D\x00` \x1fU" + // 0x01F51 0x00301: 0x01F55 + "\x00}@\x00h@\x1fV" + // 0x01F50 0x00342: 0x01F56 + "\x00}D\x00h@\x1fW" + // 0x01F51 0x00342: 0x01F57 + "\x00\x0e\x94\x00b\x80\x1fY" + // 0x003A5 0x00314: 0x01F59 + "\x00}d\x00`\x00\x1f[" + // 0x01F59 0x00300: 0x01F5B + "\x00}d\x00` \x1f]" + // 0x01F59 0x00301: 0x01F5D + "\x00}d\x00h@\x1f_" + // 0x01F59 0x00342: 0x01F5F + "\x00\x0f$\x00b`\x1f`" + // 0x003C9 0x00313: 0x01F60 + "\x00\x0f$\x00b\x80\x1fa" + // 0x003C9 0x00314: 0x01F61 + "\x00}\x80\x00`\x00\x1fb" + // 0x01F60 0x00300: 0x01F62 + "\x00}\x84\x00`\x00\x1fc" + // 0x01F61 0x00300: 0x01F63 + "\x00}\x80\x00` \x1fd" + // 0x01F60 0x00301: 0x01F64 + "\x00}\x84\x00` \x1fe" + // 0x01F61 0x00301: 0x01F65 + "\x00}\x80\x00h@\x1ff" + // 0x01F60 0x00342: 0x01F66 + "\x00}\x84\x00h@\x1fg" + // 0x01F61 0x00342: 0x01F67 + "\x00\x0e\xa4\x00b`\x1fh" + // 0x003A9 0x00313: 0x01F68 + "\x00\x0e\xa4\x00b\x80\x1fi" + // 0x003A9 0x00314: 0x01F69 + "\x00}\xa0\x00`\x00\x1fj" + // 0x01F68 0x00300: 0x01F6A + "\x00}\xa4\x00`\x00\x1fk" + // 0x01F69 0x00300: 0x01F6B + "\x00}\xa0\x00` \x1fl" + // 0x01F68 0x00301: 0x01F6C + "\x00}\xa4\x00` \x1fm" + // 0x01F69 0x00301: 0x01F6D + "\x00}\xa0\x00h@\x1fn" + // 0x01F68 0x00342: 0x01F6E + "\x00}\xa4\x00h@\x1fo" + // 0x01F69 0x00342: 0x01F6F + "\x00\x0e\xc4\x00`\x00\x1fp" + // 0x003B1 0x00300: 0x01F70 + "\x00\x0e\xd4\x00`\x00\x1fr" + // 0x003B5 0x00300: 0x01F72 + "\x00\x0e\xdc\x00`\x00\x1ft" + // 0x003B7 0x00300: 0x01F74 + "\x00\x0e\xe4\x00`\x00\x1fv" + // 0x003B9 0x00300: 0x01F76 + "\x00\x0e\xfc\x00`\x00\x1fx" + // 0x003BF 0x00300: 0x01F78 + "\x00\x0f\x14\x00`\x00\x1fz" + // 0x003C5 0x00300: 0x01F7A + "\x00\x0f$\x00`\x00\x1f|" + // 0x003C9 0x00300: 0x01F7C + "\x00|\x00\x00h\xa0\x1f\x80" + // 0x01F00 0x00345: 0x01F80 + "\x00|\x04\x00h\xa0\x1f\x81" + // 0x01F01 0x00345: 0x01F81 + "\x00|\b\x00h\xa0\x1f\x82" + // 0x01F02 0x00345: 0x01F82 + "\x00|\f\x00h\xa0\x1f\x83" + // 0x01F03 0x00345: 0x01F83 + "\x00|\x10\x00h\xa0\x1f\x84" + // 0x01F04 0x00345: 0x01F84 + "\x00|\x14\x00h\xa0\x1f\x85" + // 0x01F05 0x00345: 0x01F85 + "\x00|\x18\x00h\xa0\x1f\x86" + // 0x01F06 0x00345: 0x01F86 + "\x00|\x1c\x00h\xa0\x1f\x87" + // 0x01F07 0x00345: 0x01F87 + "\x00| \x00h\xa0\x1f\x88" + // 0x01F08 0x00345: 0x01F88 + "\x00|$\x00h\xa0\x1f\x89" + // 0x01F09 0x00345: 0x01F89 + "\x00|(\x00h\xa0\x1f\x8a" + // 0x01F0A 0x00345: 0x01F8A + "\x00|,\x00h\xa0\x1f\x8b" + // 0x01F0B 0x00345: 0x01F8B + "\x00|0\x00h\xa0\x1f\x8c" + // 0x01F0C 0x00345: 0x01F8C + "\x00|4\x00h\xa0\x1f\x8d" + // 0x01F0D 0x00345: 0x01F8D + "\x00|8\x00h\xa0\x1f\x8e" + // 0x01F0E 0x00345: 0x01F8E + "\x00|<\x00h\xa0\x1f\x8f" + // 0x01F0F 0x00345: 0x01F8F + "\x00|\x80\x00h\xa0\x1f\x90" + // 0x01F20 0x00345: 0x01F90 + "\x00|\x84\x00h\xa0\x1f\x91" + // 0x01F21 0x00345: 0x01F91 + "\x00|\x88\x00h\xa0\x1f\x92" + // 0x01F22 0x00345: 0x01F92 + "\x00|\x8c\x00h\xa0\x1f\x93" + // 0x01F23 0x00345: 0x01F93 + "\x00|\x90\x00h\xa0\x1f\x94" + // 0x01F24 0x00345: 0x01F94 + "\x00|\x94\x00h\xa0\x1f\x95" + // 0x01F25 0x00345: 0x01F95 + "\x00|\x98\x00h\xa0\x1f\x96" + // 0x01F26 0x00345: 0x01F96 + "\x00|\x9c\x00h\xa0\x1f\x97" + // 0x01F27 0x00345: 0x01F97 + "\x00|\xa0\x00h\xa0\x1f\x98" + // 0x01F28 0x00345: 0x01F98 + "\x00|\xa4\x00h\xa0\x1f\x99" + // 0x01F29 0x00345: 0x01F99 + "\x00|\xa8\x00h\xa0\x1f\x9a" + // 0x01F2A 0x00345: 0x01F9A + "\x00|\xac\x00h\xa0\x1f\x9b" + // 0x01F2B 0x00345: 0x01F9B + "\x00|\xb0\x00h\xa0\x1f\x9c" + // 0x01F2C 0x00345: 0x01F9C + "\x00|\xb4\x00h\xa0\x1f\x9d" + // 0x01F2D 0x00345: 0x01F9D + "\x00|\xb8\x00h\xa0\x1f\x9e" + // 0x01F2E 0x00345: 0x01F9E + "\x00|\xbc\x00h\xa0\x1f\x9f" + // 0x01F2F 0x00345: 0x01F9F + "\x00}\x80\x00h\xa0\x1f\xa0" + // 0x01F60 0x00345: 0x01FA0 + "\x00}\x84\x00h\xa0\x1f\xa1" + // 0x01F61 0x00345: 0x01FA1 + "\x00}\x88\x00h\xa0\x1f\xa2" + // 0x01F62 0x00345: 0x01FA2 + "\x00}\x8c\x00h\xa0\x1f\xa3" + // 0x01F63 0x00345: 0x01FA3 + "\x00}\x90\x00h\xa0\x1f\xa4" + // 0x01F64 0x00345: 0x01FA4 + "\x00}\x94\x00h\xa0\x1f\xa5" + // 0x01F65 0x00345: 0x01FA5 + "\x00}\x98\x00h\xa0\x1f\xa6" + // 0x01F66 0x00345: 0x01FA6 + "\x00}\x9c\x00h\xa0\x1f\xa7" + // 0x01F67 0x00345: 0x01FA7 + "\x00}\xa0\x00h\xa0\x1f\xa8" + // 0x01F68 0x00345: 0x01FA8 + "\x00}\xa4\x00h\xa0\x1f\xa9" + // 0x01F69 0x00345: 0x01FA9 + "\x00}\xa8\x00h\xa0\x1f\xaa" + // 0x01F6A 0x00345: 0x01FAA + "\x00}\xac\x00h\xa0\x1f\xab" + // 0x01F6B 0x00345: 0x01FAB + "\x00}\xb0\x00h\xa0\x1f\xac" + // 0x01F6C 0x00345: 0x01FAC + "\x00}\xb4\x00h\xa0\x1f\xad" + // 0x01F6D 0x00345: 0x01FAD + "\x00}\xb8\x00h\xa0\x1f\xae" + // 0x01F6E 0x00345: 0x01FAE + "\x00}\xbc\x00h\xa0\x1f\xaf" + // 0x01F6F 0x00345: 0x01FAF + "\x00\x0e\xc4\x00`\xc0\x1f\xb0" + // 0x003B1 0x00306: 0x01FB0 + "\x00\x0e\xc4\x00`\x80\x1f\xb1" + // 0x003B1 0x00304: 0x01FB1 + "\x00}\xc0\x00h\xa0\x1f\xb2" + // 0x01F70 0x00345: 0x01FB2 + "\x00\x0e\xc4\x00h\xa0\x1f\xb3" + // 0x003B1 0x00345: 0x01FB3 + "\x00\x0e\xb0\x00h\xa0\x1f\xb4" + // 0x003AC 0x00345: 0x01FB4 + "\x00\x0e\xc4\x00h@\x1f\xb6" + // 0x003B1 0x00342: 0x01FB6 + "\x00~\xd8\x00h\xa0\x1f\xb7" + // 0x01FB6 0x00345: 0x01FB7 + "\x00\x0eD\x00`\xc0\x1f\xb8" + // 0x00391 0x00306: 0x01FB8 + "\x00\x0eD\x00`\x80\x1f\xb9" + // 0x00391 0x00304: 0x01FB9 + "\x00\x0eD\x00`\x00\x1f\xba" + // 0x00391 0x00300: 0x01FBA + "\x00\x0eD\x00h\xa0\x1f\xbc" + // 0x00391 0x00345: 0x01FBC + "\x00\x02\xa0\x00h@\x1f\xc1" + // 0x000A8 0x00342: 0x01FC1 + "\x00}\xd0\x00h\xa0\x1f\xc2" + // 0x01F74 0x00345: 0x01FC2 + "\x00\x0e\xdc\x00h\xa0\x1f\xc3" + // 0x003B7 0x00345: 0x01FC3 + "\x00\x0e\xb8\x00h\xa0\x1f\xc4" + // 0x003AE 0x00345: 0x01FC4 + "\x00\x0e\xdc\x00h@\x1f\xc6" + // 0x003B7 0x00342: 0x01FC6 + "\x00\x7f\x18\x00h\xa0\x1f\xc7" + // 0x01FC6 0x00345: 0x01FC7 + "\x00\x0eT\x00`\x00\x1f\xc8" + // 0x00395 0x00300: 0x01FC8 + "\x00\x0e\\\x00`\x00\x1f\xca" + // 0x00397 0x00300: 0x01FCA + "\x00\x0e\\\x00h\xa0\x1f\xcc" + // 0x00397 0x00345: 0x01FCC + "\x00~\xfc\x00`\x00\x1f\xcd" + // 0x01FBF 0x00300: 0x01FCD + "\x00~\xfc\x00` \x1f\xce" + // 0x01FBF 0x00301: 0x01FCE + "\x00~\xfc\x00h@\x1f\xcf" + // 0x01FBF 0x00342: 0x01FCF + "\x00\x0e\xe4\x00`\xc0\x1f\xd0" + // 0x003B9 0x00306: 0x01FD0 + "\x00\x0e\xe4\x00`\x80\x1f\xd1" + // 0x003B9 0x00304: 0x01FD1 + "\x00\x0f(\x00`\x00\x1f\xd2" + // 0x003CA 0x00300: 0x01FD2 + "\x00\x0e\xe4\x00h@\x1f\xd6" + // 0x003B9 0x00342: 0x01FD6 + "\x00\x0f(\x00h@\x1f\xd7" + // 0x003CA 0x00342: 0x01FD7 + "\x00\x0ed\x00`\xc0\x1f\xd8" + // 0x00399 0x00306: 0x01FD8 + "\x00\x0ed\x00`\x80\x1f\xd9" + // 0x00399 0x00304: 0x01FD9 + "\x00\x0ed\x00`\x00\x1f\xda" + // 0x00399 0x00300: 0x01FDA + "\x00\x7f\xf8\x00`\x00\x1f\xdd" + // 0x01FFE 0x00300: 0x01FDD + "\x00\x7f\xf8\x00` \x1f\xde" + // 0x01FFE 0x00301: 0x01FDE + "\x00\x7f\xf8\x00h@\x1f\xdf" + // 0x01FFE 0x00342: 0x01FDF + "\x00\x0f\x14\x00`\xc0\x1f\xe0" + // 0x003C5 0x00306: 0x01FE0 + "\x00\x0f\x14\x00`\x80\x1f\xe1" + // 0x003C5 0x00304: 0x01FE1 + "\x00\x0f,\x00`\x00\x1f\xe2" + // 0x003CB 0x00300: 0x01FE2 + "\x00\x0f\x04\x00b`\x1f\xe4" + // 0x003C1 0x00313: 0x01FE4 + "\x00\x0f\x04\x00b\x80\x1f\xe5" + // 0x003C1 0x00314: 0x01FE5 + "\x00\x0f\x14\x00h@\x1f\xe6" + // 0x003C5 0x00342: 0x01FE6 + "\x00\x0f,\x00h@\x1f\xe7" + // 0x003CB 0x00342: 0x01FE7 + "\x00\x0e\x94\x00`\xc0\x1f\xe8" + // 0x003A5 0x00306: 0x01FE8 + "\x00\x0e\x94\x00`\x80\x1f\xe9" + // 0x003A5 0x00304: 0x01FE9 + "\x00\x0e\x94\x00`\x00\x1f\xea" + // 0x003A5 0x00300: 0x01FEA + "\x00\x0e\x84\x00b\x80\x1f\xec" + // 0x003A1 0x00314: 0x01FEC + "\x00\x02\xa0\x00`\x00\x1f\xed" + // 0x000A8 0x00300: 0x01FED + "\x00}\xf0\x00h\xa0\x1f\xf2" + // 0x01F7C 0x00345: 0x01FF2 + "\x00\x0f$\x00h\xa0\x1f\xf3" + // 0x003C9 0x00345: 0x01FF3 + "\x00\x0f8\x00h\xa0\x1f\xf4" + // 0x003CE 0x00345: 0x01FF4 + "\x00\x0f$\x00h@\x1f\xf6" + // 0x003C9 0x00342: 0x01FF6 + "\x00\x7f\xd8\x00h\xa0\x1f\xf7" + // 0x01FF6 0x00345: 0x01FF7 + "\x00\x0e|\x00`\x00\x1f\xf8" + // 0x0039F 0x00300: 0x01FF8 + "\x00\x0e\xa4\x00`\x00\x1f\xfa" + // 0x003A9 0x00300: 0x01FFA + "\x00\x0e\xa4\x00h\xa0\x1f\xfc" + // 0x003A9 0x00345: 0x01FFC + "\x00\x86@\x00g\x00!\x9a" + // 0x02190 0x00338: 0x0219A + "\x00\x86H\x00g\x00!\x9b" + // 0x02192 0x00338: 0x0219B + "\x00\x86P\x00g\x00!\xae" + // 0x02194 0x00338: 0x021AE + "\x00\x87@\x00g\x00!\xcd" + // 0x021D0 0x00338: 0x021CD + "\x00\x87P\x00g\x00!\xce" + // 0x021D4 0x00338: 0x021CE + "\x00\x87H\x00g\x00!\xcf" + // 0x021D2 0x00338: 0x021CF + "\x00\x88\f\x00g\x00\"\x04" + // 0x02203 0x00338: 0x02204 + "\x00\x88 \x00g\x00\"\t" + // 0x02208 0x00338: 0x02209 + "\x00\x88,\x00g\x00\"\f" + // 0x0220B 0x00338: 0x0220C + "\x00\x88\x8c\x00g\x00\"$" + // 0x02223 0x00338: 0x02224 + "\x00\x88\x94\x00g\x00\"&" + // 0x02225 0x00338: 0x02226 + "\x00\x88\xf0\x00g\x00\"A" + // 0x0223C 0x00338: 0x02241 + "\x00\x89\f\x00g\x00\"D" + // 0x02243 0x00338: 0x02244 + "\x00\x89\x14\x00g\x00\"G" + // 0x02245 0x00338: 0x02247 + "\x00\x89 \x00g\x00\"I" + // 0x02248 0x00338: 0x02249 + "\x00\x00\xf4\x00g\x00\"`" + // 0x0003D 0x00338: 0x02260 + "\x00\x89\x84\x00g\x00\"b" + // 0x02261 0x00338: 0x02262 + "\x00\x894\x00g\x00\"m" + // 0x0224D 0x00338: 0x0226D + "\x00\x00\xf0\x00g\x00\"n" + // 0x0003C 0x00338: 0x0226E + "\x00\x00\xf8\x00g\x00\"o" + // 0x0003E 0x00338: 0x0226F + "\x00\x89\x90\x00g\x00\"p" + // 0x02264 0x00338: 0x02270 + "\x00\x89\x94\x00g\x00\"q" + // 0x02265 0x00338: 0x02271 + "\x00\x89\xc8\x00g\x00\"t" + // 0x02272 0x00338: 0x02274 + "\x00\x89\xcc\x00g\x00\"u" + // 0x02273 0x00338: 0x02275 + "\x00\x89\xd8\x00g\x00\"x" + // 0x02276 0x00338: 0x02278 + "\x00\x89\xdc\x00g\x00\"y" + // 0x02277 0x00338: 0x02279 + "\x00\x89\xe8\x00g\x00\"\x80" + // 0x0227A 0x00338: 0x02280 + "\x00\x89\xec\x00g\x00\"\x81" + // 0x0227B 0x00338: 0x02281 + "\x00\x8a\b\x00g\x00\"\x84" + // 0x02282 0x00338: 0x02284 + "\x00\x8a\f\x00g\x00\"\x85" + // 0x02283 0x00338: 0x02285 + "\x00\x8a\x18\x00g\x00\"\x88" + // 0x02286 0x00338: 0x02288 + "\x00\x8a\x1c\x00g\x00\"\x89" + // 0x02287 0x00338: 0x02289 + "\x00\x8a\x88\x00g\x00\"\xac" + // 0x022A2 0x00338: 0x022AC + "\x00\x8a\xa0\x00g\x00\"\xad" + // 0x022A8 0x00338: 0x022AD + "\x00\x8a\xa4\x00g\x00\"\xae" + // 0x022A9 0x00338: 0x022AE + "\x00\x8a\xac\x00g\x00\"\xaf" + // 0x022AB 0x00338: 0x022AF + "\x00\x89\xf0\x00g\x00\"\xe0" + // 0x0227C 0x00338: 0x022E0 + "\x00\x89\xf4\x00g\x00\"\xe1" + // 0x0227D 0x00338: 0x022E1 + "\x00\x8aD\x00g\x00\"\xe2" + // 0x02291 0x00338: 0x022E2 + "\x00\x8aH\x00g\x00\"\xe3" + // 0x02292 0x00338: 0x022E3 + "\x00\x8a\xc8\x00g\x00\"\xea" + // 0x022B2 0x00338: 0x022EA + "\x00\x8a\xcc\x00g\x00\"\xeb" + // 0x022B3 0x00338: 0x022EB + "\x00\x8a\xd0\x00g\x00\"\xec" + // 0x022B4 0x00338: 0x022EC + "\x00\x8a\xd4\x00g\x00\"\xed" + // 0x022B5 0x00338: 0x022ED + "\x00\xc1,\x06\x13 0L" + // 0x0304B 0x03099: 0x0304C + "\x00\xc14\x06\x13 0N" + // 0x0304D 0x03099: 0x0304E + "\x00\xc1<\x06\x13 0P" + // 0x0304F 0x03099: 0x03050 + "\x00\xc1D\x06\x13 0R" + // 0x03051 0x03099: 0x03052 + "\x00\xc1L\x06\x13 0T" + // 0x03053 0x03099: 0x03054 + "\x00\xc1T\x06\x13 0V" + // 0x03055 0x03099: 0x03056 + "\x00\xc1\\\x06\x13 0X" + // 0x03057 0x03099: 0x03058 + "\x00\xc1d\x06\x13 0Z" + // 0x03059 0x03099: 0x0305A + "\x00\xc1l\x06\x13 0\\" + // 0x0305B 0x03099: 0x0305C + "\x00\xc1t\x06\x13 0^" + // 0x0305D 0x03099: 0x0305E + "\x00\xc1|\x06\x13 0`" + // 0x0305F 0x03099: 0x03060 + "\x00\xc1\x84\x06\x13 0b" + // 0x03061 0x03099: 0x03062 + "\x00\xc1\x90\x06\x13 0e" + // 0x03064 0x03099: 0x03065 + "\x00\xc1\x98\x06\x13 0g" + // 0x03066 0x03099: 0x03067 + "\x00\xc1\xa0\x06\x13 0i" + // 0x03068 0x03099: 0x03069 + "\x00\xc1\xbc\x06\x13 0p" + // 0x0306F 0x03099: 0x03070 + "\x00\xc1\xbc\x06\x13@0q" + // 0x0306F 0x0309A: 0x03071 + "\x00\xc1\xc8\x06\x13 0s" + // 0x03072 0x03099: 0x03073 + "\x00\xc1\xc8\x06\x13@0t" + // 0x03072 0x0309A: 0x03074 + "\x00\xc1\xd4\x06\x13 0v" + // 0x03075 0x03099: 0x03076 + "\x00\xc1\xd4\x06\x13@0w" + // 0x03075 0x0309A: 0x03077 + "\x00\xc1\xe0\x06\x13 0y" + // 0x03078 0x03099: 0x03079 + "\x00\xc1\xe0\x06\x13@0z" + // 0x03078 0x0309A: 0x0307A + "\x00\xc1\xec\x06\x13 0|" + // 0x0307B 0x03099: 0x0307C + "\x00\xc1\xec\x06\x13@0}" + // 0x0307B 0x0309A: 0x0307D + "\x00\xc1\x18\x06\x13 0\x94" + // 0x03046 0x03099: 0x03094 + "\x00\xc2t\x06\x13 0\x9e" + // 0x0309D 0x03099: 0x0309E + "\x00¬\x06\x13 0\xac" + // 0x030AB 0x03099: 0x030AC + "\x00´\x06\x13 0\xae" + // 0x030AD 0x03099: 0x030AE + "\x00¼\x06\x13 0\xb0" + // 0x030AF 0x03099: 0x030B0 + "\x00\xc2\xc4\x06\x13 0\xb2" + // 0x030B1 0x03099: 0x030B2 + "\x00\xc2\xcc\x06\x13 0\xb4" + // 0x030B3 0x03099: 0x030B4 + "\x00\xc2\xd4\x06\x13 0\xb6" + // 0x030B5 0x03099: 0x030B6 + "\x00\xc2\xdc\x06\x13 0\xb8" + // 0x030B7 0x03099: 0x030B8 + "\x00\xc2\xe4\x06\x13 0\xba" + // 0x030B9 0x03099: 0x030BA + "\x00\xc2\xec\x06\x13 0\xbc" + // 0x030BB 0x03099: 0x030BC + "\x00\xc2\xf4\x06\x13 0\xbe" + // 0x030BD 0x03099: 0x030BE + "\x00\xc2\xfc\x06\x13 0\xc0" + // 0x030BF 0x03099: 0x030C0 + "\x00\xc3\x04\x06\x13 0\xc2" + // 0x030C1 0x03099: 0x030C2 + "\x00\xc3\x10\x06\x13 0\xc5" + // 0x030C4 0x03099: 0x030C5 + "\x00\xc3\x18\x06\x13 0\xc7" + // 0x030C6 0x03099: 0x030C7 + "\x00\xc3 \x06\x13 0\xc9" + // 0x030C8 0x03099: 0x030C9 + "\x00\xc3<\x06\x13 0\xd0" + // 0x030CF 0x03099: 0x030D0 + "\x00\xc3<\x06\x13@0\xd1" + // 0x030CF 0x0309A: 0x030D1 + "\x00\xc3H\x06\x13 0\xd3" + // 0x030D2 0x03099: 0x030D3 + "\x00\xc3H\x06\x13@0\xd4" + // 0x030D2 0x0309A: 0x030D4 + "\x00\xc3T\x06\x13 0\xd6" + // 0x030D5 0x03099: 0x030D6 + "\x00\xc3T\x06\x13@0\xd7" + // 0x030D5 0x0309A: 0x030D7 + "\x00\xc3`\x06\x13 0\xd9" + // 0x030D8 0x03099: 0x030D9 + "\x00\xc3`\x06\x13@0\xda" + // 0x030D8 0x0309A: 0x030DA + "\x00\xc3l\x06\x13 0\xdc" + // 0x030DB 0x03099: 0x030DC + "\x00\xc3l\x06\x13@0\xdd" + // 0x030DB 0x0309A: 0x030DD + "\x00\u0098\x06\x13 0\xf4" + // 0x030A6 0x03099: 0x030F4 + "\x00ü\x06\x13 0\xf7" + // 0x030EF 0x03099: 0x030F7 + "\x00\xc3\xc0\x06\x13 0\xf8" + // 0x030F0 0x03099: 0x030F8 + "\x00\xc3\xc4\x06\x13 0\xf9" + // 0x030F1 0x03099: 0x030F9 + "\x00\xc3\xc8\x06\x13 0\xfa" + // 0x030F2 0x03099: 0x030FA + "\x00\xc3\xf4\x06\x13 0\xfe" + // 0x030FD 0x03099: 0x030FE + "\x04Bd\"\x17A\x10\x9a" + // 0x11099 0x110BA: 0x1109A + "\x04Bl\"\x17A\x10\x9c" + // 0x1109B 0x110BA: 0x1109C + "\x04B\x94\"\x17A\x10\xab" + // 0x110A5 0x110BA: 0x110AB + "\x04D\xc4\"$\xe1\x11." + // 0x11131 0x11127: 0x1112E + "\x04D\xc8\"$\xe1\x11/" + // 0x11132 0x11127: 0x1112F + "\x04M\x1c\"g\xc1\x13K" + // 0x11347 0x1133E: 0x1134B + "\x04M\x1c\"j\xe1\x13L" + // 0x11347 0x11357: 0x1134C + "\x04R\xe4\"\x97A\x14\xbb" + // 0x114B9 0x114BA: 0x114BB + "\x04R\xe4\"\x96\x01\x14\xbc" + // 0x114B9 0x114B0: 0x114BC + "\x04R\xe4\"\x97\xa1\x14\xbe" + // 0x114B9 0x114BD: 0x114BE + "\x04V\xe0\"\xb5\xe1\x15\xba" + // 0x115B8 0x115AF: 0x115BA + "\x04V\xe4\"\xb5\xe1\x15\xbb" + // 0x115B9 0x115AF: 0x115BB + "\x04d\xd4#&\x01\x198" + // 0x11935 0x11930: 0x11938 "" // Total size of tables: 56KB (57068 bytes) diff --git a/vendor/golang.org/x/text/unicode/norm/tables17.0.0.go b/vendor/golang.org/x/text/unicode/norm/tables17.0.0.go index dfd555fc1b..c323189120 100644 --- a/vendor/golang.org/x/text/unicode/norm/tables17.0.0.go +++ b/vendor/golang.org/x/text/unicode/norm/tables17.0.0.go @@ -7135,970 +7135,970 @@ var nfkcSparseValues = [980]valueRange{ } // recompMap: 7688 bytes (entries only) -var recompMap map[uint32]rune +var recompMap map[uint64]rune var recompMapOnce sync.Once const recompMapPacked = "" + - "\x00A\x03\x00\x00\x00\x00\xc0" + // 0x00410300: 0x000000C0 - "\x00A\x03\x01\x00\x00\x00\xc1" + // 0x00410301: 0x000000C1 - "\x00A\x03\x02\x00\x00\x00\xc2" + // 0x00410302: 0x000000C2 - "\x00A\x03\x03\x00\x00\x00\xc3" + // 0x00410303: 0x000000C3 - "\x00A\x03\b\x00\x00\x00\xc4" + // 0x00410308: 0x000000C4 - "\x00A\x03\n\x00\x00\x00\xc5" + // 0x0041030A: 0x000000C5 - "\x00C\x03'\x00\x00\x00\xc7" + // 0x00430327: 0x000000C7 - "\x00E\x03\x00\x00\x00\x00\xc8" + // 0x00450300: 0x000000C8 - "\x00E\x03\x01\x00\x00\x00\xc9" + // 0x00450301: 0x000000C9 - "\x00E\x03\x02\x00\x00\x00\xca" + // 0x00450302: 0x000000CA - "\x00E\x03\b\x00\x00\x00\xcb" + // 0x00450308: 0x000000CB - "\x00I\x03\x00\x00\x00\x00\xcc" + // 0x00490300: 0x000000CC - "\x00I\x03\x01\x00\x00\x00\xcd" + // 0x00490301: 0x000000CD - "\x00I\x03\x02\x00\x00\x00\xce" + // 0x00490302: 0x000000CE - "\x00I\x03\b\x00\x00\x00\xcf" + // 0x00490308: 0x000000CF - "\x00N\x03\x03\x00\x00\x00\xd1" + // 0x004E0303: 0x000000D1 - "\x00O\x03\x00\x00\x00\x00\xd2" + // 0x004F0300: 0x000000D2 - "\x00O\x03\x01\x00\x00\x00\xd3" + // 0x004F0301: 0x000000D3 - "\x00O\x03\x02\x00\x00\x00\xd4" + // 0x004F0302: 0x000000D4 - "\x00O\x03\x03\x00\x00\x00\xd5" + // 0x004F0303: 0x000000D5 - "\x00O\x03\b\x00\x00\x00\xd6" + // 0x004F0308: 0x000000D6 - "\x00U\x03\x00\x00\x00\x00\xd9" + // 0x00550300: 0x000000D9 - "\x00U\x03\x01\x00\x00\x00\xda" + // 0x00550301: 0x000000DA - "\x00U\x03\x02\x00\x00\x00\xdb" + // 0x00550302: 0x000000DB - "\x00U\x03\b\x00\x00\x00\xdc" + // 0x00550308: 0x000000DC - "\x00Y\x03\x01\x00\x00\x00\xdd" + // 0x00590301: 0x000000DD - "\x00a\x03\x00\x00\x00\x00\xe0" + // 0x00610300: 0x000000E0 - "\x00a\x03\x01\x00\x00\x00\xe1" + // 0x00610301: 0x000000E1 - "\x00a\x03\x02\x00\x00\x00\xe2" + // 0x00610302: 0x000000E2 - "\x00a\x03\x03\x00\x00\x00\xe3" + // 0x00610303: 0x000000E3 - "\x00a\x03\b\x00\x00\x00\xe4" + // 0x00610308: 0x000000E4 - "\x00a\x03\n\x00\x00\x00\xe5" + // 0x0061030A: 0x000000E5 - "\x00c\x03'\x00\x00\x00\xe7" + // 0x00630327: 0x000000E7 - "\x00e\x03\x00\x00\x00\x00\xe8" + // 0x00650300: 0x000000E8 - "\x00e\x03\x01\x00\x00\x00\xe9" + // 0x00650301: 0x000000E9 - "\x00e\x03\x02\x00\x00\x00\xea" + // 0x00650302: 0x000000EA - "\x00e\x03\b\x00\x00\x00\xeb" + // 0x00650308: 0x000000EB - "\x00i\x03\x00\x00\x00\x00\xec" + // 0x00690300: 0x000000EC - "\x00i\x03\x01\x00\x00\x00\xed" + // 0x00690301: 0x000000ED - "\x00i\x03\x02\x00\x00\x00\xee" + // 0x00690302: 0x000000EE - "\x00i\x03\b\x00\x00\x00\xef" + // 0x00690308: 0x000000EF - "\x00n\x03\x03\x00\x00\x00\xf1" + // 0x006E0303: 0x000000F1 - "\x00o\x03\x00\x00\x00\x00\xf2" + // 0x006F0300: 0x000000F2 - "\x00o\x03\x01\x00\x00\x00\xf3" + // 0x006F0301: 0x000000F3 - "\x00o\x03\x02\x00\x00\x00\xf4" + // 0x006F0302: 0x000000F4 - "\x00o\x03\x03\x00\x00\x00\xf5" + // 0x006F0303: 0x000000F5 - "\x00o\x03\b\x00\x00\x00\xf6" + // 0x006F0308: 0x000000F6 - "\x00u\x03\x00\x00\x00\x00\xf9" + // 0x00750300: 0x000000F9 - "\x00u\x03\x01\x00\x00\x00\xfa" + // 0x00750301: 0x000000FA - "\x00u\x03\x02\x00\x00\x00\xfb" + // 0x00750302: 0x000000FB - "\x00u\x03\b\x00\x00\x00\xfc" + // 0x00750308: 0x000000FC - "\x00y\x03\x01\x00\x00\x00\xfd" + // 0x00790301: 0x000000FD - "\x00y\x03\b\x00\x00\x00\xff" + // 0x00790308: 0x000000FF - "\x00A\x03\x04\x00\x00\x01\x00" + // 0x00410304: 0x00000100 - "\x00a\x03\x04\x00\x00\x01\x01" + // 0x00610304: 0x00000101 - "\x00A\x03\x06\x00\x00\x01\x02" + // 0x00410306: 0x00000102 - "\x00a\x03\x06\x00\x00\x01\x03" + // 0x00610306: 0x00000103 - "\x00A\x03(\x00\x00\x01\x04" + // 0x00410328: 0x00000104 - "\x00a\x03(\x00\x00\x01\x05" + // 0x00610328: 0x00000105 - "\x00C\x03\x01\x00\x00\x01\x06" + // 0x00430301: 0x00000106 - "\x00c\x03\x01\x00\x00\x01\a" + // 0x00630301: 0x00000107 - "\x00C\x03\x02\x00\x00\x01\b" + // 0x00430302: 0x00000108 - "\x00c\x03\x02\x00\x00\x01\t" + // 0x00630302: 0x00000109 - "\x00C\x03\a\x00\x00\x01\n" + // 0x00430307: 0x0000010A - "\x00c\x03\a\x00\x00\x01\v" + // 0x00630307: 0x0000010B - "\x00C\x03\f\x00\x00\x01\f" + // 0x0043030C: 0x0000010C - "\x00c\x03\f\x00\x00\x01\r" + // 0x0063030C: 0x0000010D - "\x00D\x03\f\x00\x00\x01\x0e" + // 0x0044030C: 0x0000010E - "\x00d\x03\f\x00\x00\x01\x0f" + // 0x0064030C: 0x0000010F - "\x00E\x03\x04\x00\x00\x01\x12" + // 0x00450304: 0x00000112 - "\x00e\x03\x04\x00\x00\x01\x13" + // 0x00650304: 0x00000113 - "\x00E\x03\x06\x00\x00\x01\x14" + // 0x00450306: 0x00000114 - "\x00e\x03\x06\x00\x00\x01\x15" + // 0x00650306: 0x00000115 - "\x00E\x03\a\x00\x00\x01\x16" + // 0x00450307: 0x00000116 - "\x00e\x03\a\x00\x00\x01\x17" + // 0x00650307: 0x00000117 - "\x00E\x03(\x00\x00\x01\x18" + // 0x00450328: 0x00000118 - "\x00e\x03(\x00\x00\x01\x19" + // 0x00650328: 0x00000119 - "\x00E\x03\f\x00\x00\x01\x1a" + // 0x0045030C: 0x0000011A - "\x00e\x03\f\x00\x00\x01\x1b" + // 0x0065030C: 0x0000011B - "\x00G\x03\x02\x00\x00\x01\x1c" + // 0x00470302: 0x0000011C - "\x00g\x03\x02\x00\x00\x01\x1d" + // 0x00670302: 0x0000011D - "\x00G\x03\x06\x00\x00\x01\x1e" + // 0x00470306: 0x0000011E - "\x00g\x03\x06\x00\x00\x01\x1f" + // 0x00670306: 0x0000011F - "\x00G\x03\a\x00\x00\x01 " + // 0x00470307: 0x00000120 - "\x00g\x03\a\x00\x00\x01!" + // 0x00670307: 0x00000121 - "\x00G\x03'\x00\x00\x01\"" + // 0x00470327: 0x00000122 - "\x00g\x03'\x00\x00\x01#" + // 0x00670327: 0x00000123 - "\x00H\x03\x02\x00\x00\x01$" + // 0x00480302: 0x00000124 - "\x00h\x03\x02\x00\x00\x01%" + // 0x00680302: 0x00000125 - "\x00I\x03\x03\x00\x00\x01(" + // 0x00490303: 0x00000128 - "\x00i\x03\x03\x00\x00\x01)" + // 0x00690303: 0x00000129 - "\x00I\x03\x04\x00\x00\x01*" + // 0x00490304: 0x0000012A - "\x00i\x03\x04\x00\x00\x01+" + // 0x00690304: 0x0000012B - "\x00I\x03\x06\x00\x00\x01," + // 0x00490306: 0x0000012C - "\x00i\x03\x06\x00\x00\x01-" + // 0x00690306: 0x0000012D - "\x00I\x03(\x00\x00\x01." + // 0x00490328: 0x0000012E - "\x00i\x03(\x00\x00\x01/" + // 0x00690328: 0x0000012F - "\x00I\x03\a\x00\x00\x010" + // 0x00490307: 0x00000130 - "\x00J\x03\x02\x00\x00\x014" + // 0x004A0302: 0x00000134 - "\x00j\x03\x02\x00\x00\x015" + // 0x006A0302: 0x00000135 - "\x00K\x03'\x00\x00\x016" + // 0x004B0327: 0x00000136 - "\x00k\x03'\x00\x00\x017" + // 0x006B0327: 0x00000137 - "\x00L\x03\x01\x00\x00\x019" + // 0x004C0301: 0x00000139 - "\x00l\x03\x01\x00\x00\x01:" + // 0x006C0301: 0x0000013A - "\x00L\x03'\x00\x00\x01;" + // 0x004C0327: 0x0000013B - "\x00l\x03'\x00\x00\x01<" + // 0x006C0327: 0x0000013C - "\x00L\x03\f\x00\x00\x01=" + // 0x004C030C: 0x0000013D - "\x00l\x03\f\x00\x00\x01>" + // 0x006C030C: 0x0000013E - "\x00N\x03\x01\x00\x00\x01C" + // 0x004E0301: 0x00000143 - "\x00n\x03\x01\x00\x00\x01D" + // 0x006E0301: 0x00000144 - "\x00N\x03'\x00\x00\x01E" + // 0x004E0327: 0x00000145 - "\x00n\x03'\x00\x00\x01F" + // 0x006E0327: 0x00000146 - "\x00N\x03\f\x00\x00\x01G" + // 0x004E030C: 0x00000147 - "\x00n\x03\f\x00\x00\x01H" + // 0x006E030C: 0x00000148 - "\x00O\x03\x04\x00\x00\x01L" + // 0x004F0304: 0x0000014C - "\x00o\x03\x04\x00\x00\x01M" + // 0x006F0304: 0x0000014D - "\x00O\x03\x06\x00\x00\x01N" + // 0x004F0306: 0x0000014E - "\x00o\x03\x06\x00\x00\x01O" + // 0x006F0306: 0x0000014F - "\x00O\x03\v\x00\x00\x01P" + // 0x004F030B: 0x00000150 - "\x00o\x03\v\x00\x00\x01Q" + // 0x006F030B: 0x00000151 - "\x00R\x03\x01\x00\x00\x01T" + // 0x00520301: 0x00000154 - "\x00r\x03\x01\x00\x00\x01U" + // 0x00720301: 0x00000155 - "\x00R\x03'\x00\x00\x01V" + // 0x00520327: 0x00000156 - "\x00r\x03'\x00\x00\x01W" + // 0x00720327: 0x00000157 - "\x00R\x03\f\x00\x00\x01X" + // 0x0052030C: 0x00000158 - "\x00r\x03\f\x00\x00\x01Y" + // 0x0072030C: 0x00000159 - "\x00S\x03\x01\x00\x00\x01Z" + // 0x00530301: 0x0000015A - "\x00s\x03\x01\x00\x00\x01[" + // 0x00730301: 0x0000015B - "\x00S\x03\x02\x00\x00\x01\\" + // 0x00530302: 0x0000015C - "\x00s\x03\x02\x00\x00\x01]" + // 0x00730302: 0x0000015D - "\x00S\x03'\x00\x00\x01^" + // 0x00530327: 0x0000015E - "\x00s\x03'\x00\x00\x01_" + // 0x00730327: 0x0000015F - "\x00S\x03\f\x00\x00\x01`" + // 0x0053030C: 0x00000160 - "\x00s\x03\f\x00\x00\x01a" + // 0x0073030C: 0x00000161 - "\x00T\x03'\x00\x00\x01b" + // 0x00540327: 0x00000162 - "\x00t\x03'\x00\x00\x01c" + // 0x00740327: 0x00000163 - "\x00T\x03\f\x00\x00\x01d" + // 0x0054030C: 0x00000164 - "\x00t\x03\f\x00\x00\x01e" + // 0x0074030C: 0x00000165 - "\x00U\x03\x03\x00\x00\x01h" + // 0x00550303: 0x00000168 - "\x00u\x03\x03\x00\x00\x01i" + // 0x00750303: 0x00000169 - "\x00U\x03\x04\x00\x00\x01j" + // 0x00550304: 0x0000016A - "\x00u\x03\x04\x00\x00\x01k" + // 0x00750304: 0x0000016B - "\x00U\x03\x06\x00\x00\x01l" + // 0x00550306: 0x0000016C - "\x00u\x03\x06\x00\x00\x01m" + // 0x00750306: 0x0000016D - "\x00U\x03\n\x00\x00\x01n" + // 0x0055030A: 0x0000016E - "\x00u\x03\n\x00\x00\x01o" + // 0x0075030A: 0x0000016F - "\x00U\x03\v\x00\x00\x01p" + // 0x0055030B: 0x00000170 - "\x00u\x03\v\x00\x00\x01q" + // 0x0075030B: 0x00000171 - "\x00U\x03(\x00\x00\x01r" + // 0x00550328: 0x00000172 - "\x00u\x03(\x00\x00\x01s" + // 0x00750328: 0x00000173 - "\x00W\x03\x02\x00\x00\x01t" + // 0x00570302: 0x00000174 - "\x00w\x03\x02\x00\x00\x01u" + // 0x00770302: 0x00000175 - "\x00Y\x03\x02\x00\x00\x01v" + // 0x00590302: 0x00000176 - "\x00y\x03\x02\x00\x00\x01w" + // 0x00790302: 0x00000177 - "\x00Y\x03\b\x00\x00\x01x" + // 0x00590308: 0x00000178 - "\x00Z\x03\x01\x00\x00\x01y" + // 0x005A0301: 0x00000179 - "\x00z\x03\x01\x00\x00\x01z" + // 0x007A0301: 0x0000017A - "\x00Z\x03\a\x00\x00\x01{" + // 0x005A0307: 0x0000017B - "\x00z\x03\a\x00\x00\x01|" + // 0x007A0307: 0x0000017C - "\x00Z\x03\f\x00\x00\x01}" + // 0x005A030C: 0x0000017D - "\x00z\x03\f\x00\x00\x01~" + // 0x007A030C: 0x0000017E - "\x00O\x03\x1b\x00\x00\x01\xa0" + // 0x004F031B: 0x000001A0 - "\x00o\x03\x1b\x00\x00\x01\xa1" + // 0x006F031B: 0x000001A1 - "\x00U\x03\x1b\x00\x00\x01\xaf" + // 0x0055031B: 0x000001AF - "\x00u\x03\x1b\x00\x00\x01\xb0" + // 0x0075031B: 0x000001B0 - "\x00A\x03\f\x00\x00\x01\xcd" + // 0x0041030C: 0x000001CD - "\x00a\x03\f\x00\x00\x01\xce" + // 0x0061030C: 0x000001CE - "\x00I\x03\f\x00\x00\x01\xcf" + // 0x0049030C: 0x000001CF - "\x00i\x03\f\x00\x00\x01\xd0" + // 0x0069030C: 0x000001D0 - "\x00O\x03\f\x00\x00\x01\xd1" + // 0x004F030C: 0x000001D1 - "\x00o\x03\f\x00\x00\x01\xd2" + // 0x006F030C: 0x000001D2 - "\x00U\x03\f\x00\x00\x01\xd3" + // 0x0055030C: 0x000001D3 - "\x00u\x03\f\x00\x00\x01\xd4" + // 0x0075030C: 0x000001D4 - "\x00\xdc\x03\x04\x00\x00\x01\xd5" + // 0x00DC0304: 0x000001D5 - "\x00\xfc\x03\x04\x00\x00\x01\xd6" + // 0x00FC0304: 0x000001D6 - "\x00\xdc\x03\x01\x00\x00\x01\xd7" + // 0x00DC0301: 0x000001D7 - "\x00\xfc\x03\x01\x00\x00\x01\xd8" + // 0x00FC0301: 0x000001D8 - "\x00\xdc\x03\f\x00\x00\x01\xd9" + // 0x00DC030C: 0x000001D9 - "\x00\xfc\x03\f\x00\x00\x01\xda" + // 0x00FC030C: 0x000001DA - "\x00\xdc\x03\x00\x00\x00\x01\xdb" + // 0x00DC0300: 0x000001DB - "\x00\xfc\x03\x00\x00\x00\x01\xdc" + // 0x00FC0300: 0x000001DC - "\x00\xc4\x03\x04\x00\x00\x01\xde" + // 0x00C40304: 0x000001DE - "\x00\xe4\x03\x04\x00\x00\x01\xdf" + // 0x00E40304: 0x000001DF - "\x02&\x03\x04\x00\x00\x01\xe0" + // 0x02260304: 0x000001E0 - "\x02'\x03\x04\x00\x00\x01\xe1" + // 0x02270304: 0x000001E1 - "\x00\xc6\x03\x04\x00\x00\x01\xe2" + // 0x00C60304: 0x000001E2 - "\x00\xe6\x03\x04\x00\x00\x01\xe3" + // 0x00E60304: 0x000001E3 - "\x00G\x03\f\x00\x00\x01\xe6" + // 0x0047030C: 0x000001E6 - "\x00g\x03\f\x00\x00\x01\xe7" + // 0x0067030C: 0x000001E7 - "\x00K\x03\f\x00\x00\x01\xe8" + // 0x004B030C: 0x000001E8 - "\x00k\x03\f\x00\x00\x01\xe9" + // 0x006B030C: 0x000001E9 - "\x00O\x03(\x00\x00\x01\xea" + // 0x004F0328: 0x000001EA - "\x00o\x03(\x00\x00\x01\xeb" + // 0x006F0328: 0x000001EB - "\x01\xea\x03\x04\x00\x00\x01\xec" + // 0x01EA0304: 0x000001EC - "\x01\xeb\x03\x04\x00\x00\x01\xed" + // 0x01EB0304: 0x000001ED - "\x01\xb7\x03\f\x00\x00\x01\xee" + // 0x01B7030C: 0x000001EE - "\x02\x92\x03\f\x00\x00\x01\xef" + // 0x0292030C: 0x000001EF - "\x00j\x03\f\x00\x00\x01\xf0" + // 0x006A030C: 0x000001F0 - "\x00G\x03\x01\x00\x00\x01\xf4" + // 0x00470301: 0x000001F4 - "\x00g\x03\x01\x00\x00\x01\xf5" + // 0x00670301: 0x000001F5 - "\x00N\x03\x00\x00\x00\x01\xf8" + // 0x004E0300: 0x000001F8 - "\x00n\x03\x00\x00\x00\x01\xf9" + // 0x006E0300: 0x000001F9 - "\x00\xc5\x03\x01\x00\x00\x01\xfa" + // 0x00C50301: 0x000001FA - "\x00\xe5\x03\x01\x00\x00\x01\xfb" + // 0x00E50301: 0x000001FB - "\x00\xc6\x03\x01\x00\x00\x01\xfc" + // 0x00C60301: 0x000001FC - "\x00\xe6\x03\x01\x00\x00\x01\xfd" + // 0x00E60301: 0x000001FD - "\x00\xd8\x03\x01\x00\x00\x01\xfe" + // 0x00D80301: 0x000001FE - "\x00\xf8\x03\x01\x00\x00\x01\xff" + // 0x00F80301: 0x000001FF - "\x00A\x03\x0f\x00\x00\x02\x00" + // 0x0041030F: 0x00000200 - "\x00a\x03\x0f\x00\x00\x02\x01" + // 0x0061030F: 0x00000201 - "\x00A\x03\x11\x00\x00\x02\x02" + // 0x00410311: 0x00000202 - "\x00a\x03\x11\x00\x00\x02\x03" + // 0x00610311: 0x00000203 - "\x00E\x03\x0f\x00\x00\x02\x04" + // 0x0045030F: 0x00000204 - "\x00e\x03\x0f\x00\x00\x02\x05" + // 0x0065030F: 0x00000205 - "\x00E\x03\x11\x00\x00\x02\x06" + // 0x00450311: 0x00000206 - "\x00e\x03\x11\x00\x00\x02\a" + // 0x00650311: 0x00000207 - "\x00I\x03\x0f\x00\x00\x02\b" + // 0x0049030F: 0x00000208 - "\x00i\x03\x0f\x00\x00\x02\t" + // 0x0069030F: 0x00000209 - "\x00I\x03\x11\x00\x00\x02\n" + // 0x00490311: 0x0000020A - "\x00i\x03\x11\x00\x00\x02\v" + // 0x00690311: 0x0000020B - "\x00O\x03\x0f\x00\x00\x02\f" + // 0x004F030F: 0x0000020C - "\x00o\x03\x0f\x00\x00\x02\r" + // 0x006F030F: 0x0000020D - "\x00O\x03\x11\x00\x00\x02\x0e" + // 0x004F0311: 0x0000020E - "\x00o\x03\x11\x00\x00\x02\x0f" + // 0x006F0311: 0x0000020F - "\x00R\x03\x0f\x00\x00\x02\x10" + // 0x0052030F: 0x00000210 - "\x00r\x03\x0f\x00\x00\x02\x11" + // 0x0072030F: 0x00000211 - "\x00R\x03\x11\x00\x00\x02\x12" + // 0x00520311: 0x00000212 - "\x00r\x03\x11\x00\x00\x02\x13" + // 0x00720311: 0x00000213 - "\x00U\x03\x0f\x00\x00\x02\x14" + // 0x0055030F: 0x00000214 - "\x00u\x03\x0f\x00\x00\x02\x15" + // 0x0075030F: 0x00000215 - "\x00U\x03\x11\x00\x00\x02\x16" + // 0x00550311: 0x00000216 - "\x00u\x03\x11\x00\x00\x02\x17" + // 0x00750311: 0x00000217 - "\x00S\x03&\x00\x00\x02\x18" + // 0x00530326: 0x00000218 - "\x00s\x03&\x00\x00\x02\x19" + // 0x00730326: 0x00000219 - "\x00T\x03&\x00\x00\x02\x1a" + // 0x00540326: 0x0000021A - "\x00t\x03&\x00\x00\x02\x1b" + // 0x00740326: 0x0000021B - "\x00H\x03\f\x00\x00\x02\x1e" + // 0x0048030C: 0x0000021E - "\x00h\x03\f\x00\x00\x02\x1f" + // 0x0068030C: 0x0000021F - "\x00A\x03\a\x00\x00\x02&" + // 0x00410307: 0x00000226 - "\x00a\x03\a\x00\x00\x02'" + // 0x00610307: 0x00000227 - "\x00E\x03'\x00\x00\x02(" + // 0x00450327: 0x00000228 - "\x00e\x03'\x00\x00\x02)" + // 0x00650327: 0x00000229 - "\x00\xd6\x03\x04\x00\x00\x02*" + // 0x00D60304: 0x0000022A - "\x00\xf6\x03\x04\x00\x00\x02+" + // 0x00F60304: 0x0000022B - "\x00\xd5\x03\x04\x00\x00\x02," + // 0x00D50304: 0x0000022C - "\x00\xf5\x03\x04\x00\x00\x02-" + // 0x00F50304: 0x0000022D - "\x00O\x03\a\x00\x00\x02." + // 0x004F0307: 0x0000022E - "\x00o\x03\a\x00\x00\x02/" + // 0x006F0307: 0x0000022F - "\x02.\x03\x04\x00\x00\x020" + // 0x022E0304: 0x00000230 - "\x02/\x03\x04\x00\x00\x021" + // 0x022F0304: 0x00000231 - "\x00Y\x03\x04\x00\x00\x022" + // 0x00590304: 0x00000232 - "\x00y\x03\x04\x00\x00\x023" + // 0x00790304: 0x00000233 - "\x00\xa8\x03\x01\x00\x00\x03\x85" + // 0x00A80301: 0x00000385 - "\x03\x91\x03\x01\x00\x00\x03\x86" + // 0x03910301: 0x00000386 - "\x03\x95\x03\x01\x00\x00\x03\x88" + // 0x03950301: 0x00000388 - "\x03\x97\x03\x01\x00\x00\x03\x89" + // 0x03970301: 0x00000389 - "\x03\x99\x03\x01\x00\x00\x03\x8a" + // 0x03990301: 0x0000038A - "\x03\x9f\x03\x01\x00\x00\x03\x8c" + // 0x039F0301: 0x0000038C - "\x03\xa5\x03\x01\x00\x00\x03\x8e" + // 0x03A50301: 0x0000038E - "\x03\xa9\x03\x01\x00\x00\x03\x8f" + // 0x03A90301: 0x0000038F - "\x03\xca\x03\x01\x00\x00\x03\x90" + // 0x03CA0301: 0x00000390 - "\x03\x99\x03\b\x00\x00\x03\xaa" + // 0x03990308: 0x000003AA - "\x03\xa5\x03\b\x00\x00\x03\xab" + // 0x03A50308: 0x000003AB - "\x03\xb1\x03\x01\x00\x00\x03\xac" + // 0x03B10301: 0x000003AC - "\x03\xb5\x03\x01\x00\x00\x03\xad" + // 0x03B50301: 0x000003AD - "\x03\xb7\x03\x01\x00\x00\x03\xae" + // 0x03B70301: 0x000003AE - "\x03\xb9\x03\x01\x00\x00\x03\xaf" + // 0x03B90301: 0x000003AF - "\x03\xcb\x03\x01\x00\x00\x03\xb0" + // 0x03CB0301: 0x000003B0 - "\x03\xb9\x03\b\x00\x00\x03\xca" + // 0x03B90308: 0x000003CA - "\x03\xc5\x03\b\x00\x00\x03\xcb" + // 0x03C50308: 0x000003CB - "\x03\xbf\x03\x01\x00\x00\x03\xcc" + // 0x03BF0301: 0x000003CC - "\x03\xc5\x03\x01\x00\x00\x03\xcd" + // 0x03C50301: 0x000003CD - "\x03\xc9\x03\x01\x00\x00\x03\xce" + // 0x03C90301: 0x000003CE - "\x03\xd2\x03\x01\x00\x00\x03\xd3" + // 0x03D20301: 0x000003D3 - "\x03\xd2\x03\b\x00\x00\x03\xd4" + // 0x03D20308: 0x000003D4 - "\x04\x15\x03\x00\x00\x00\x04\x00" + // 0x04150300: 0x00000400 - "\x04\x15\x03\b\x00\x00\x04\x01" + // 0x04150308: 0x00000401 - "\x04\x13\x03\x01\x00\x00\x04\x03" + // 0x04130301: 0x00000403 - "\x04\x06\x03\b\x00\x00\x04\a" + // 0x04060308: 0x00000407 - "\x04\x1a\x03\x01\x00\x00\x04\f" + // 0x041A0301: 0x0000040C - "\x04\x18\x03\x00\x00\x00\x04\r" + // 0x04180300: 0x0000040D - "\x04#\x03\x06\x00\x00\x04\x0e" + // 0x04230306: 0x0000040E - "\x04\x18\x03\x06\x00\x00\x04\x19" + // 0x04180306: 0x00000419 - "\x048\x03\x06\x00\x00\x049" + // 0x04380306: 0x00000439 - "\x045\x03\x00\x00\x00\x04P" + // 0x04350300: 0x00000450 - "\x045\x03\b\x00\x00\x04Q" + // 0x04350308: 0x00000451 - "\x043\x03\x01\x00\x00\x04S" + // 0x04330301: 0x00000453 - "\x04V\x03\b\x00\x00\x04W" + // 0x04560308: 0x00000457 - "\x04:\x03\x01\x00\x00\x04\\" + // 0x043A0301: 0x0000045C - "\x048\x03\x00\x00\x00\x04]" + // 0x04380300: 0x0000045D - "\x04C\x03\x06\x00\x00\x04^" + // 0x04430306: 0x0000045E - "\x04t\x03\x0f\x00\x00\x04v" + // 0x0474030F: 0x00000476 - "\x04u\x03\x0f\x00\x00\x04w" + // 0x0475030F: 0x00000477 - "\x04\x16\x03\x06\x00\x00\x04\xc1" + // 0x04160306: 0x000004C1 - "\x046\x03\x06\x00\x00\x04\xc2" + // 0x04360306: 0x000004C2 - "\x04\x10\x03\x06\x00\x00\x04\xd0" + // 0x04100306: 0x000004D0 - "\x040\x03\x06\x00\x00\x04\xd1" + // 0x04300306: 0x000004D1 - "\x04\x10\x03\b\x00\x00\x04\xd2" + // 0x04100308: 0x000004D2 - "\x040\x03\b\x00\x00\x04\xd3" + // 0x04300308: 0x000004D3 - "\x04\x15\x03\x06\x00\x00\x04\xd6" + // 0x04150306: 0x000004D6 - "\x045\x03\x06\x00\x00\x04\xd7" + // 0x04350306: 0x000004D7 - "\x04\xd8\x03\b\x00\x00\x04\xda" + // 0x04D80308: 0x000004DA - "\x04\xd9\x03\b\x00\x00\x04\xdb" + // 0x04D90308: 0x000004DB - "\x04\x16\x03\b\x00\x00\x04\xdc" + // 0x04160308: 0x000004DC - "\x046\x03\b\x00\x00\x04\xdd" + // 0x04360308: 0x000004DD - "\x04\x17\x03\b\x00\x00\x04\xde" + // 0x04170308: 0x000004DE - "\x047\x03\b\x00\x00\x04\xdf" + // 0x04370308: 0x000004DF - "\x04\x18\x03\x04\x00\x00\x04\xe2" + // 0x04180304: 0x000004E2 - "\x048\x03\x04\x00\x00\x04\xe3" + // 0x04380304: 0x000004E3 - "\x04\x18\x03\b\x00\x00\x04\xe4" + // 0x04180308: 0x000004E4 - "\x048\x03\b\x00\x00\x04\xe5" + // 0x04380308: 0x000004E5 - "\x04\x1e\x03\b\x00\x00\x04\xe6" + // 0x041E0308: 0x000004E6 - "\x04>\x03\b\x00\x00\x04\xe7" + // 0x043E0308: 0x000004E7 - "\x04\xe8\x03\b\x00\x00\x04\xea" + // 0x04E80308: 0x000004EA - "\x04\xe9\x03\b\x00\x00\x04\xeb" + // 0x04E90308: 0x000004EB - "\x04-\x03\b\x00\x00\x04\xec" + // 0x042D0308: 0x000004EC - "\x04M\x03\b\x00\x00\x04\xed" + // 0x044D0308: 0x000004ED - "\x04#\x03\x04\x00\x00\x04\xee" + // 0x04230304: 0x000004EE - "\x04C\x03\x04\x00\x00\x04\xef" + // 0x04430304: 0x000004EF - "\x04#\x03\b\x00\x00\x04\xf0" + // 0x04230308: 0x000004F0 - "\x04C\x03\b\x00\x00\x04\xf1" + // 0x04430308: 0x000004F1 - "\x04#\x03\v\x00\x00\x04\xf2" + // 0x0423030B: 0x000004F2 - "\x04C\x03\v\x00\x00\x04\xf3" + // 0x0443030B: 0x000004F3 - "\x04'\x03\b\x00\x00\x04\xf4" + // 0x04270308: 0x000004F4 - "\x04G\x03\b\x00\x00\x04\xf5" + // 0x04470308: 0x000004F5 - "\x04+\x03\b\x00\x00\x04\xf8" + // 0x042B0308: 0x000004F8 - "\x04K\x03\b\x00\x00\x04\xf9" + // 0x044B0308: 0x000004F9 - "\x06'\x06S\x00\x00\x06\"" + // 0x06270653: 0x00000622 - "\x06'\x06T\x00\x00\x06#" + // 0x06270654: 0x00000623 - "\x06H\x06T\x00\x00\x06$" + // 0x06480654: 0x00000624 - "\x06'\x06U\x00\x00\x06%" + // 0x06270655: 0x00000625 - "\x06J\x06T\x00\x00\x06&" + // 0x064A0654: 0x00000626 - "\x06\xd5\x06T\x00\x00\x06\xc0" + // 0x06D50654: 0x000006C0 - "\x06\xc1\x06T\x00\x00\x06\xc2" + // 0x06C10654: 0x000006C2 - "\x06\xd2\x06T\x00\x00\x06\xd3" + // 0x06D20654: 0x000006D3 - "\t(\t<\x00\x00\t)" + // 0x0928093C: 0x00000929 - "\t0\t<\x00\x00\t1" + // 0x0930093C: 0x00000931 - "\t3\t<\x00\x00\t4" + // 0x0933093C: 0x00000934 - "\t\xc7\t\xbe\x00\x00\t\xcb" + // 0x09C709BE: 0x000009CB - "\t\xc7\t\xd7\x00\x00\t\xcc" + // 0x09C709D7: 0x000009CC - "\vG\vV\x00\x00\vH" + // 0x0B470B56: 0x00000B48 - "\vG\v>\x00\x00\vK" + // 0x0B470B3E: 0x00000B4B - "\vG\vW\x00\x00\vL" + // 0x0B470B57: 0x00000B4C - "\v\x92\v\xd7\x00\x00\v\x94" + // 0x0B920BD7: 0x00000B94 - "\v\xc6\v\xbe\x00\x00\v\xca" + // 0x0BC60BBE: 0x00000BCA - "\v\xc7\v\xbe\x00\x00\v\xcb" + // 0x0BC70BBE: 0x00000BCB - "\v\xc6\v\xd7\x00\x00\v\xcc" + // 0x0BC60BD7: 0x00000BCC - "\fF\fV\x00\x00\fH" + // 0x0C460C56: 0x00000C48 - "\f\xbf\f\xd5\x00\x00\f\xc0" + // 0x0CBF0CD5: 0x00000CC0 - "\f\xc6\f\xd5\x00\x00\f\xc7" + // 0x0CC60CD5: 0x00000CC7 - "\f\xc6\f\xd6\x00\x00\f\xc8" + // 0x0CC60CD6: 0x00000CC8 - "\f\xc6\f\xc2\x00\x00\f\xca" + // 0x0CC60CC2: 0x00000CCA - "\f\xca\f\xd5\x00\x00\f\xcb" + // 0x0CCA0CD5: 0x00000CCB - "\rF\r>\x00\x00\rJ" + // 0x0D460D3E: 0x00000D4A - "\rG\r>\x00\x00\rK" + // 0x0D470D3E: 0x00000D4B - "\rF\rW\x00\x00\rL" + // 0x0D460D57: 0x00000D4C - "\r\xd9\r\xca\x00\x00\r\xda" + // 0x0DD90DCA: 0x00000DDA - "\r\xd9\r\xcf\x00\x00\r\xdc" + // 0x0DD90DCF: 0x00000DDC - "\r\xdc\r\xca\x00\x00\r\xdd" + // 0x0DDC0DCA: 0x00000DDD - "\r\xd9\r\xdf\x00\x00\r\xde" + // 0x0DD90DDF: 0x00000DDE - "\x10%\x10.\x00\x00\x10&" + // 0x1025102E: 0x00001026 - "\x1b\x05\x1b5\x00\x00\x1b\x06" + // 0x1B051B35: 0x00001B06 - "\x1b\a\x1b5\x00\x00\x1b\b" + // 0x1B071B35: 0x00001B08 - "\x1b\t\x1b5\x00\x00\x1b\n" + // 0x1B091B35: 0x00001B0A - "\x1b\v\x1b5\x00\x00\x1b\f" + // 0x1B0B1B35: 0x00001B0C - "\x1b\r\x1b5\x00\x00\x1b\x0e" + // 0x1B0D1B35: 0x00001B0E - "\x1b\x11\x1b5\x00\x00\x1b\x12" + // 0x1B111B35: 0x00001B12 - "\x1b:\x1b5\x00\x00\x1b;" + // 0x1B3A1B35: 0x00001B3B - "\x1b<\x1b5\x00\x00\x1b=" + // 0x1B3C1B35: 0x00001B3D - "\x1b>\x1b5\x00\x00\x1b@" + // 0x1B3E1B35: 0x00001B40 - "\x1b?\x1b5\x00\x00\x1bA" + // 0x1B3F1B35: 0x00001B41 - "\x1bB\x1b5\x00\x00\x1bC" + // 0x1B421B35: 0x00001B43 - "\x00A\x03%\x00\x00\x1e\x00" + // 0x00410325: 0x00001E00 - "\x00a\x03%\x00\x00\x1e\x01" + // 0x00610325: 0x00001E01 - "\x00B\x03\a\x00\x00\x1e\x02" + // 0x00420307: 0x00001E02 - "\x00b\x03\a\x00\x00\x1e\x03" + // 0x00620307: 0x00001E03 - "\x00B\x03#\x00\x00\x1e\x04" + // 0x00420323: 0x00001E04 - "\x00b\x03#\x00\x00\x1e\x05" + // 0x00620323: 0x00001E05 - "\x00B\x031\x00\x00\x1e\x06" + // 0x00420331: 0x00001E06 - "\x00b\x031\x00\x00\x1e\a" + // 0x00620331: 0x00001E07 - "\x00\xc7\x03\x01\x00\x00\x1e\b" + // 0x00C70301: 0x00001E08 - "\x00\xe7\x03\x01\x00\x00\x1e\t" + // 0x00E70301: 0x00001E09 - "\x00D\x03\a\x00\x00\x1e\n" + // 0x00440307: 0x00001E0A - "\x00d\x03\a\x00\x00\x1e\v" + // 0x00640307: 0x00001E0B - "\x00D\x03#\x00\x00\x1e\f" + // 0x00440323: 0x00001E0C - "\x00d\x03#\x00\x00\x1e\r" + // 0x00640323: 0x00001E0D - "\x00D\x031\x00\x00\x1e\x0e" + // 0x00440331: 0x00001E0E - "\x00d\x031\x00\x00\x1e\x0f" + // 0x00640331: 0x00001E0F - "\x00D\x03'\x00\x00\x1e\x10" + // 0x00440327: 0x00001E10 - "\x00d\x03'\x00\x00\x1e\x11" + // 0x00640327: 0x00001E11 - "\x00D\x03-\x00\x00\x1e\x12" + // 0x0044032D: 0x00001E12 - "\x00d\x03-\x00\x00\x1e\x13" + // 0x0064032D: 0x00001E13 - "\x01\x12\x03\x00\x00\x00\x1e\x14" + // 0x01120300: 0x00001E14 - "\x01\x13\x03\x00\x00\x00\x1e\x15" + // 0x01130300: 0x00001E15 - "\x01\x12\x03\x01\x00\x00\x1e\x16" + // 0x01120301: 0x00001E16 - "\x01\x13\x03\x01\x00\x00\x1e\x17" + // 0x01130301: 0x00001E17 - "\x00E\x03-\x00\x00\x1e\x18" + // 0x0045032D: 0x00001E18 - "\x00e\x03-\x00\x00\x1e\x19" + // 0x0065032D: 0x00001E19 - "\x00E\x030\x00\x00\x1e\x1a" + // 0x00450330: 0x00001E1A - "\x00e\x030\x00\x00\x1e\x1b" + // 0x00650330: 0x00001E1B - "\x02(\x03\x06\x00\x00\x1e\x1c" + // 0x02280306: 0x00001E1C - "\x02)\x03\x06\x00\x00\x1e\x1d" + // 0x02290306: 0x00001E1D - "\x00F\x03\a\x00\x00\x1e\x1e" + // 0x00460307: 0x00001E1E - "\x00f\x03\a\x00\x00\x1e\x1f" + // 0x00660307: 0x00001E1F - "\x00G\x03\x04\x00\x00\x1e " + // 0x00470304: 0x00001E20 - "\x00g\x03\x04\x00\x00\x1e!" + // 0x00670304: 0x00001E21 - "\x00H\x03\a\x00\x00\x1e\"" + // 0x00480307: 0x00001E22 - "\x00h\x03\a\x00\x00\x1e#" + // 0x00680307: 0x00001E23 - "\x00H\x03#\x00\x00\x1e$" + // 0x00480323: 0x00001E24 - "\x00h\x03#\x00\x00\x1e%" + // 0x00680323: 0x00001E25 - "\x00H\x03\b\x00\x00\x1e&" + // 0x00480308: 0x00001E26 - "\x00h\x03\b\x00\x00\x1e'" + // 0x00680308: 0x00001E27 - "\x00H\x03'\x00\x00\x1e(" + // 0x00480327: 0x00001E28 - "\x00h\x03'\x00\x00\x1e)" + // 0x00680327: 0x00001E29 - "\x00H\x03.\x00\x00\x1e*" + // 0x0048032E: 0x00001E2A - "\x00h\x03.\x00\x00\x1e+" + // 0x0068032E: 0x00001E2B - "\x00I\x030\x00\x00\x1e," + // 0x00490330: 0x00001E2C - "\x00i\x030\x00\x00\x1e-" + // 0x00690330: 0x00001E2D - "\x00\xcf\x03\x01\x00\x00\x1e." + // 0x00CF0301: 0x00001E2E - "\x00\xef\x03\x01\x00\x00\x1e/" + // 0x00EF0301: 0x00001E2F - "\x00K\x03\x01\x00\x00\x1e0" + // 0x004B0301: 0x00001E30 - "\x00k\x03\x01\x00\x00\x1e1" + // 0x006B0301: 0x00001E31 - "\x00K\x03#\x00\x00\x1e2" + // 0x004B0323: 0x00001E32 - "\x00k\x03#\x00\x00\x1e3" + // 0x006B0323: 0x00001E33 - "\x00K\x031\x00\x00\x1e4" + // 0x004B0331: 0x00001E34 - "\x00k\x031\x00\x00\x1e5" + // 0x006B0331: 0x00001E35 - "\x00L\x03#\x00\x00\x1e6" + // 0x004C0323: 0x00001E36 - "\x00l\x03#\x00\x00\x1e7" + // 0x006C0323: 0x00001E37 - "\x1e6\x03\x04\x00\x00\x1e8" + // 0x1E360304: 0x00001E38 - "\x1e7\x03\x04\x00\x00\x1e9" + // 0x1E370304: 0x00001E39 - "\x00L\x031\x00\x00\x1e:" + // 0x004C0331: 0x00001E3A - "\x00l\x031\x00\x00\x1e;" + // 0x006C0331: 0x00001E3B - "\x00L\x03-\x00\x00\x1e<" + // 0x004C032D: 0x00001E3C - "\x00l\x03-\x00\x00\x1e=" + // 0x006C032D: 0x00001E3D - "\x00M\x03\x01\x00\x00\x1e>" + // 0x004D0301: 0x00001E3E - "\x00m\x03\x01\x00\x00\x1e?" + // 0x006D0301: 0x00001E3F - "\x00M\x03\a\x00\x00\x1e@" + // 0x004D0307: 0x00001E40 - "\x00m\x03\a\x00\x00\x1eA" + // 0x006D0307: 0x00001E41 - "\x00M\x03#\x00\x00\x1eB" + // 0x004D0323: 0x00001E42 - "\x00m\x03#\x00\x00\x1eC" + // 0x006D0323: 0x00001E43 - "\x00N\x03\a\x00\x00\x1eD" + // 0x004E0307: 0x00001E44 - "\x00n\x03\a\x00\x00\x1eE" + // 0x006E0307: 0x00001E45 - "\x00N\x03#\x00\x00\x1eF" + // 0x004E0323: 0x00001E46 - "\x00n\x03#\x00\x00\x1eG" + // 0x006E0323: 0x00001E47 - "\x00N\x031\x00\x00\x1eH" + // 0x004E0331: 0x00001E48 - "\x00n\x031\x00\x00\x1eI" + // 0x006E0331: 0x00001E49 - "\x00N\x03-\x00\x00\x1eJ" + // 0x004E032D: 0x00001E4A - "\x00n\x03-\x00\x00\x1eK" + // 0x006E032D: 0x00001E4B - "\x00\xd5\x03\x01\x00\x00\x1eL" + // 0x00D50301: 0x00001E4C - "\x00\xf5\x03\x01\x00\x00\x1eM" + // 0x00F50301: 0x00001E4D - "\x00\xd5\x03\b\x00\x00\x1eN" + // 0x00D50308: 0x00001E4E - "\x00\xf5\x03\b\x00\x00\x1eO" + // 0x00F50308: 0x00001E4F - "\x01L\x03\x00\x00\x00\x1eP" + // 0x014C0300: 0x00001E50 - "\x01M\x03\x00\x00\x00\x1eQ" + // 0x014D0300: 0x00001E51 - "\x01L\x03\x01\x00\x00\x1eR" + // 0x014C0301: 0x00001E52 - "\x01M\x03\x01\x00\x00\x1eS" + // 0x014D0301: 0x00001E53 - "\x00P\x03\x01\x00\x00\x1eT" + // 0x00500301: 0x00001E54 - "\x00p\x03\x01\x00\x00\x1eU" + // 0x00700301: 0x00001E55 - "\x00P\x03\a\x00\x00\x1eV" + // 0x00500307: 0x00001E56 - "\x00p\x03\a\x00\x00\x1eW" + // 0x00700307: 0x00001E57 - "\x00R\x03\a\x00\x00\x1eX" + // 0x00520307: 0x00001E58 - "\x00r\x03\a\x00\x00\x1eY" + // 0x00720307: 0x00001E59 - "\x00R\x03#\x00\x00\x1eZ" + // 0x00520323: 0x00001E5A - "\x00r\x03#\x00\x00\x1e[" + // 0x00720323: 0x00001E5B - "\x1eZ\x03\x04\x00\x00\x1e\\" + // 0x1E5A0304: 0x00001E5C - "\x1e[\x03\x04\x00\x00\x1e]" + // 0x1E5B0304: 0x00001E5D - "\x00R\x031\x00\x00\x1e^" + // 0x00520331: 0x00001E5E - "\x00r\x031\x00\x00\x1e_" + // 0x00720331: 0x00001E5F - "\x00S\x03\a\x00\x00\x1e`" + // 0x00530307: 0x00001E60 - "\x00s\x03\a\x00\x00\x1ea" + // 0x00730307: 0x00001E61 - "\x00S\x03#\x00\x00\x1eb" + // 0x00530323: 0x00001E62 - "\x00s\x03#\x00\x00\x1ec" + // 0x00730323: 0x00001E63 - "\x01Z\x03\a\x00\x00\x1ed" + // 0x015A0307: 0x00001E64 - "\x01[\x03\a\x00\x00\x1ee" + // 0x015B0307: 0x00001E65 - "\x01`\x03\a\x00\x00\x1ef" + // 0x01600307: 0x00001E66 - "\x01a\x03\a\x00\x00\x1eg" + // 0x01610307: 0x00001E67 - "\x1eb\x03\a\x00\x00\x1eh" + // 0x1E620307: 0x00001E68 - "\x1ec\x03\a\x00\x00\x1ei" + // 0x1E630307: 0x00001E69 - "\x00T\x03\a\x00\x00\x1ej" + // 0x00540307: 0x00001E6A - "\x00t\x03\a\x00\x00\x1ek" + // 0x00740307: 0x00001E6B - "\x00T\x03#\x00\x00\x1el" + // 0x00540323: 0x00001E6C - "\x00t\x03#\x00\x00\x1em" + // 0x00740323: 0x00001E6D - "\x00T\x031\x00\x00\x1en" + // 0x00540331: 0x00001E6E - "\x00t\x031\x00\x00\x1eo" + // 0x00740331: 0x00001E6F - "\x00T\x03-\x00\x00\x1ep" + // 0x0054032D: 0x00001E70 - "\x00t\x03-\x00\x00\x1eq" + // 0x0074032D: 0x00001E71 - "\x00U\x03$\x00\x00\x1er" + // 0x00550324: 0x00001E72 - "\x00u\x03$\x00\x00\x1es" + // 0x00750324: 0x00001E73 - "\x00U\x030\x00\x00\x1et" + // 0x00550330: 0x00001E74 - "\x00u\x030\x00\x00\x1eu" + // 0x00750330: 0x00001E75 - "\x00U\x03-\x00\x00\x1ev" + // 0x0055032D: 0x00001E76 - "\x00u\x03-\x00\x00\x1ew" + // 0x0075032D: 0x00001E77 - "\x01h\x03\x01\x00\x00\x1ex" + // 0x01680301: 0x00001E78 - "\x01i\x03\x01\x00\x00\x1ey" + // 0x01690301: 0x00001E79 - "\x01j\x03\b\x00\x00\x1ez" + // 0x016A0308: 0x00001E7A - "\x01k\x03\b\x00\x00\x1e{" + // 0x016B0308: 0x00001E7B - "\x00V\x03\x03\x00\x00\x1e|" + // 0x00560303: 0x00001E7C - "\x00v\x03\x03\x00\x00\x1e}" + // 0x00760303: 0x00001E7D - "\x00V\x03#\x00\x00\x1e~" + // 0x00560323: 0x00001E7E - "\x00v\x03#\x00\x00\x1e\x7f" + // 0x00760323: 0x00001E7F - "\x00W\x03\x00\x00\x00\x1e\x80" + // 0x00570300: 0x00001E80 - "\x00w\x03\x00\x00\x00\x1e\x81" + // 0x00770300: 0x00001E81 - "\x00W\x03\x01\x00\x00\x1e\x82" + // 0x00570301: 0x00001E82 - "\x00w\x03\x01\x00\x00\x1e\x83" + // 0x00770301: 0x00001E83 - "\x00W\x03\b\x00\x00\x1e\x84" + // 0x00570308: 0x00001E84 - "\x00w\x03\b\x00\x00\x1e\x85" + // 0x00770308: 0x00001E85 - "\x00W\x03\a\x00\x00\x1e\x86" + // 0x00570307: 0x00001E86 - "\x00w\x03\a\x00\x00\x1e\x87" + // 0x00770307: 0x00001E87 - "\x00W\x03#\x00\x00\x1e\x88" + // 0x00570323: 0x00001E88 - "\x00w\x03#\x00\x00\x1e\x89" + // 0x00770323: 0x00001E89 - "\x00X\x03\a\x00\x00\x1e\x8a" + // 0x00580307: 0x00001E8A - "\x00x\x03\a\x00\x00\x1e\x8b" + // 0x00780307: 0x00001E8B - "\x00X\x03\b\x00\x00\x1e\x8c" + // 0x00580308: 0x00001E8C - "\x00x\x03\b\x00\x00\x1e\x8d" + // 0x00780308: 0x00001E8D - "\x00Y\x03\a\x00\x00\x1e\x8e" + // 0x00590307: 0x00001E8E - "\x00y\x03\a\x00\x00\x1e\x8f" + // 0x00790307: 0x00001E8F - "\x00Z\x03\x02\x00\x00\x1e\x90" + // 0x005A0302: 0x00001E90 - "\x00z\x03\x02\x00\x00\x1e\x91" + // 0x007A0302: 0x00001E91 - "\x00Z\x03#\x00\x00\x1e\x92" + // 0x005A0323: 0x00001E92 - "\x00z\x03#\x00\x00\x1e\x93" + // 0x007A0323: 0x00001E93 - "\x00Z\x031\x00\x00\x1e\x94" + // 0x005A0331: 0x00001E94 - "\x00z\x031\x00\x00\x1e\x95" + // 0x007A0331: 0x00001E95 - "\x00h\x031\x00\x00\x1e\x96" + // 0x00680331: 0x00001E96 - "\x00t\x03\b\x00\x00\x1e\x97" + // 0x00740308: 0x00001E97 - "\x00w\x03\n\x00\x00\x1e\x98" + // 0x0077030A: 0x00001E98 - "\x00y\x03\n\x00\x00\x1e\x99" + // 0x0079030A: 0x00001E99 - "\x01\x7f\x03\a\x00\x00\x1e\x9b" + // 0x017F0307: 0x00001E9B - "\x00A\x03#\x00\x00\x1e\xa0" + // 0x00410323: 0x00001EA0 - "\x00a\x03#\x00\x00\x1e\xa1" + // 0x00610323: 0x00001EA1 - "\x00A\x03\t\x00\x00\x1e\xa2" + // 0x00410309: 0x00001EA2 - "\x00a\x03\t\x00\x00\x1e\xa3" + // 0x00610309: 0x00001EA3 - "\x00\xc2\x03\x01\x00\x00\x1e\xa4" + // 0x00C20301: 0x00001EA4 - "\x00\xe2\x03\x01\x00\x00\x1e\xa5" + // 0x00E20301: 0x00001EA5 - "\x00\xc2\x03\x00\x00\x00\x1e\xa6" + // 0x00C20300: 0x00001EA6 - "\x00\xe2\x03\x00\x00\x00\x1e\xa7" + // 0x00E20300: 0x00001EA7 - "\x00\xc2\x03\t\x00\x00\x1e\xa8" + // 0x00C20309: 0x00001EA8 - "\x00\xe2\x03\t\x00\x00\x1e\xa9" + // 0x00E20309: 0x00001EA9 - "\x00\xc2\x03\x03\x00\x00\x1e\xaa" + // 0x00C20303: 0x00001EAA - "\x00\xe2\x03\x03\x00\x00\x1e\xab" + // 0x00E20303: 0x00001EAB - "\x1e\xa0\x03\x02\x00\x00\x1e\xac" + // 0x1EA00302: 0x00001EAC - "\x1e\xa1\x03\x02\x00\x00\x1e\xad" + // 0x1EA10302: 0x00001EAD - "\x01\x02\x03\x01\x00\x00\x1e\xae" + // 0x01020301: 0x00001EAE - "\x01\x03\x03\x01\x00\x00\x1e\xaf" + // 0x01030301: 0x00001EAF - "\x01\x02\x03\x00\x00\x00\x1e\xb0" + // 0x01020300: 0x00001EB0 - "\x01\x03\x03\x00\x00\x00\x1e\xb1" + // 0x01030300: 0x00001EB1 - "\x01\x02\x03\t\x00\x00\x1e\xb2" + // 0x01020309: 0x00001EB2 - "\x01\x03\x03\t\x00\x00\x1e\xb3" + // 0x01030309: 0x00001EB3 - "\x01\x02\x03\x03\x00\x00\x1e\xb4" + // 0x01020303: 0x00001EB4 - "\x01\x03\x03\x03\x00\x00\x1e\xb5" + // 0x01030303: 0x00001EB5 - "\x1e\xa0\x03\x06\x00\x00\x1e\xb6" + // 0x1EA00306: 0x00001EB6 - "\x1e\xa1\x03\x06\x00\x00\x1e\xb7" + // 0x1EA10306: 0x00001EB7 - "\x00E\x03#\x00\x00\x1e\xb8" + // 0x00450323: 0x00001EB8 - "\x00e\x03#\x00\x00\x1e\xb9" + // 0x00650323: 0x00001EB9 - "\x00E\x03\t\x00\x00\x1e\xba" + // 0x00450309: 0x00001EBA - "\x00e\x03\t\x00\x00\x1e\xbb" + // 0x00650309: 0x00001EBB - "\x00E\x03\x03\x00\x00\x1e\xbc" + // 0x00450303: 0x00001EBC - "\x00e\x03\x03\x00\x00\x1e\xbd" + // 0x00650303: 0x00001EBD - "\x00\xca\x03\x01\x00\x00\x1e\xbe" + // 0x00CA0301: 0x00001EBE - "\x00\xea\x03\x01\x00\x00\x1e\xbf" + // 0x00EA0301: 0x00001EBF - "\x00\xca\x03\x00\x00\x00\x1e\xc0" + // 0x00CA0300: 0x00001EC0 - "\x00\xea\x03\x00\x00\x00\x1e\xc1" + // 0x00EA0300: 0x00001EC1 - "\x00\xca\x03\t\x00\x00\x1e\xc2" + // 0x00CA0309: 0x00001EC2 - "\x00\xea\x03\t\x00\x00\x1e\xc3" + // 0x00EA0309: 0x00001EC3 - "\x00\xca\x03\x03\x00\x00\x1e\xc4" + // 0x00CA0303: 0x00001EC4 - "\x00\xea\x03\x03\x00\x00\x1e\xc5" + // 0x00EA0303: 0x00001EC5 - "\x1e\xb8\x03\x02\x00\x00\x1e\xc6" + // 0x1EB80302: 0x00001EC6 - "\x1e\xb9\x03\x02\x00\x00\x1e\xc7" + // 0x1EB90302: 0x00001EC7 - "\x00I\x03\t\x00\x00\x1e\xc8" + // 0x00490309: 0x00001EC8 - "\x00i\x03\t\x00\x00\x1e\xc9" + // 0x00690309: 0x00001EC9 - "\x00I\x03#\x00\x00\x1e\xca" + // 0x00490323: 0x00001ECA - "\x00i\x03#\x00\x00\x1e\xcb" + // 0x00690323: 0x00001ECB - "\x00O\x03#\x00\x00\x1e\xcc" + // 0x004F0323: 0x00001ECC - "\x00o\x03#\x00\x00\x1e\xcd" + // 0x006F0323: 0x00001ECD - "\x00O\x03\t\x00\x00\x1e\xce" + // 0x004F0309: 0x00001ECE - "\x00o\x03\t\x00\x00\x1e\xcf" + // 0x006F0309: 0x00001ECF - "\x00\xd4\x03\x01\x00\x00\x1e\xd0" + // 0x00D40301: 0x00001ED0 - "\x00\xf4\x03\x01\x00\x00\x1e\xd1" + // 0x00F40301: 0x00001ED1 - "\x00\xd4\x03\x00\x00\x00\x1e\xd2" + // 0x00D40300: 0x00001ED2 - "\x00\xf4\x03\x00\x00\x00\x1e\xd3" + // 0x00F40300: 0x00001ED3 - "\x00\xd4\x03\t\x00\x00\x1e\xd4" + // 0x00D40309: 0x00001ED4 - "\x00\xf4\x03\t\x00\x00\x1e\xd5" + // 0x00F40309: 0x00001ED5 - "\x00\xd4\x03\x03\x00\x00\x1e\xd6" + // 0x00D40303: 0x00001ED6 - "\x00\xf4\x03\x03\x00\x00\x1e\xd7" + // 0x00F40303: 0x00001ED7 - "\x1e\xcc\x03\x02\x00\x00\x1e\xd8" + // 0x1ECC0302: 0x00001ED8 - "\x1e\xcd\x03\x02\x00\x00\x1e\xd9" + // 0x1ECD0302: 0x00001ED9 - "\x01\xa0\x03\x01\x00\x00\x1e\xda" + // 0x01A00301: 0x00001EDA - "\x01\xa1\x03\x01\x00\x00\x1e\xdb" + // 0x01A10301: 0x00001EDB - "\x01\xa0\x03\x00\x00\x00\x1e\xdc" + // 0x01A00300: 0x00001EDC - "\x01\xa1\x03\x00\x00\x00\x1e\xdd" + // 0x01A10300: 0x00001EDD - "\x01\xa0\x03\t\x00\x00\x1e\xde" + // 0x01A00309: 0x00001EDE - "\x01\xa1\x03\t\x00\x00\x1e\xdf" + // 0x01A10309: 0x00001EDF - "\x01\xa0\x03\x03\x00\x00\x1e\xe0" + // 0x01A00303: 0x00001EE0 - "\x01\xa1\x03\x03\x00\x00\x1e\xe1" + // 0x01A10303: 0x00001EE1 - "\x01\xa0\x03#\x00\x00\x1e\xe2" + // 0x01A00323: 0x00001EE2 - "\x01\xa1\x03#\x00\x00\x1e\xe3" + // 0x01A10323: 0x00001EE3 - "\x00U\x03#\x00\x00\x1e\xe4" + // 0x00550323: 0x00001EE4 - "\x00u\x03#\x00\x00\x1e\xe5" + // 0x00750323: 0x00001EE5 - "\x00U\x03\t\x00\x00\x1e\xe6" + // 0x00550309: 0x00001EE6 - "\x00u\x03\t\x00\x00\x1e\xe7" + // 0x00750309: 0x00001EE7 - "\x01\xaf\x03\x01\x00\x00\x1e\xe8" + // 0x01AF0301: 0x00001EE8 - "\x01\xb0\x03\x01\x00\x00\x1e\xe9" + // 0x01B00301: 0x00001EE9 - "\x01\xaf\x03\x00\x00\x00\x1e\xea" + // 0x01AF0300: 0x00001EEA - "\x01\xb0\x03\x00\x00\x00\x1e\xeb" + // 0x01B00300: 0x00001EEB - "\x01\xaf\x03\t\x00\x00\x1e\xec" + // 0x01AF0309: 0x00001EEC - "\x01\xb0\x03\t\x00\x00\x1e\xed" + // 0x01B00309: 0x00001EED - "\x01\xaf\x03\x03\x00\x00\x1e\xee" + // 0x01AF0303: 0x00001EEE - "\x01\xb0\x03\x03\x00\x00\x1e\xef" + // 0x01B00303: 0x00001EEF - "\x01\xaf\x03#\x00\x00\x1e\xf0" + // 0x01AF0323: 0x00001EF0 - "\x01\xb0\x03#\x00\x00\x1e\xf1" + // 0x01B00323: 0x00001EF1 - "\x00Y\x03\x00\x00\x00\x1e\xf2" + // 0x00590300: 0x00001EF2 - "\x00y\x03\x00\x00\x00\x1e\xf3" + // 0x00790300: 0x00001EF3 - "\x00Y\x03#\x00\x00\x1e\xf4" + // 0x00590323: 0x00001EF4 - "\x00y\x03#\x00\x00\x1e\xf5" + // 0x00790323: 0x00001EF5 - "\x00Y\x03\t\x00\x00\x1e\xf6" + // 0x00590309: 0x00001EF6 - "\x00y\x03\t\x00\x00\x1e\xf7" + // 0x00790309: 0x00001EF7 - "\x00Y\x03\x03\x00\x00\x1e\xf8" + // 0x00590303: 0x00001EF8 - "\x00y\x03\x03\x00\x00\x1e\xf9" + // 0x00790303: 0x00001EF9 - "\x03\xb1\x03\x13\x00\x00\x1f\x00" + // 0x03B10313: 0x00001F00 - "\x03\xb1\x03\x14\x00\x00\x1f\x01" + // 0x03B10314: 0x00001F01 - "\x1f\x00\x03\x00\x00\x00\x1f\x02" + // 0x1F000300: 0x00001F02 - "\x1f\x01\x03\x00\x00\x00\x1f\x03" + // 0x1F010300: 0x00001F03 - "\x1f\x00\x03\x01\x00\x00\x1f\x04" + // 0x1F000301: 0x00001F04 - "\x1f\x01\x03\x01\x00\x00\x1f\x05" + // 0x1F010301: 0x00001F05 - "\x1f\x00\x03B\x00\x00\x1f\x06" + // 0x1F000342: 0x00001F06 - "\x1f\x01\x03B\x00\x00\x1f\a" + // 0x1F010342: 0x00001F07 - "\x03\x91\x03\x13\x00\x00\x1f\b" + // 0x03910313: 0x00001F08 - "\x03\x91\x03\x14\x00\x00\x1f\t" + // 0x03910314: 0x00001F09 - "\x1f\b\x03\x00\x00\x00\x1f\n" + // 0x1F080300: 0x00001F0A - "\x1f\t\x03\x00\x00\x00\x1f\v" + // 0x1F090300: 0x00001F0B - "\x1f\b\x03\x01\x00\x00\x1f\f" + // 0x1F080301: 0x00001F0C - "\x1f\t\x03\x01\x00\x00\x1f\r" + // 0x1F090301: 0x00001F0D - "\x1f\b\x03B\x00\x00\x1f\x0e" + // 0x1F080342: 0x00001F0E - "\x1f\t\x03B\x00\x00\x1f\x0f" + // 0x1F090342: 0x00001F0F - "\x03\xb5\x03\x13\x00\x00\x1f\x10" + // 0x03B50313: 0x00001F10 - "\x03\xb5\x03\x14\x00\x00\x1f\x11" + // 0x03B50314: 0x00001F11 - "\x1f\x10\x03\x00\x00\x00\x1f\x12" + // 0x1F100300: 0x00001F12 - "\x1f\x11\x03\x00\x00\x00\x1f\x13" + // 0x1F110300: 0x00001F13 - "\x1f\x10\x03\x01\x00\x00\x1f\x14" + // 0x1F100301: 0x00001F14 - "\x1f\x11\x03\x01\x00\x00\x1f\x15" + // 0x1F110301: 0x00001F15 - "\x03\x95\x03\x13\x00\x00\x1f\x18" + // 0x03950313: 0x00001F18 - "\x03\x95\x03\x14\x00\x00\x1f\x19" + // 0x03950314: 0x00001F19 - "\x1f\x18\x03\x00\x00\x00\x1f\x1a" + // 0x1F180300: 0x00001F1A - "\x1f\x19\x03\x00\x00\x00\x1f\x1b" + // 0x1F190300: 0x00001F1B - "\x1f\x18\x03\x01\x00\x00\x1f\x1c" + // 0x1F180301: 0x00001F1C - "\x1f\x19\x03\x01\x00\x00\x1f\x1d" + // 0x1F190301: 0x00001F1D - "\x03\xb7\x03\x13\x00\x00\x1f " + // 0x03B70313: 0x00001F20 - "\x03\xb7\x03\x14\x00\x00\x1f!" + // 0x03B70314: 0x00001F21 - "\x1f \x03\x00\x00\x00\x1f\"" + // 0x1F200300: 0x00001F22 - "\x1f!\x03\x00\x00\x00\x1f#" + // 0x1F210300: 0x00001F23 - "\x1f \x03\x01\x00\x00\x1f$" + // 0x1F200301: 0x00001F24 - "\x1f!\x03\x01\x00\x00\x1f%" + // 0x1F210301: 0x00001F25 - "\x1f \x03B\x00\x00\x1f&" + // 0x1F200342: 0x00001F26 - "\x1f!\x03B\x00\x00\x1f'" + // 0x1F210342: 0x00001F27 - "\x03\x97\x03\x13\x00\x00\x1f(" + // 0x03970313: 0x00001F28 - "\x03\x97\x03\x14\x00\x00\x1f)" + // 0x03970314: 0x00001F29 - "\x1f(\x03\x00\x00\x00\x1f*" + // 0x1F280300: 0x00001F2A - "\x1f)\x03\x00\x00\x00\x1f+" + // 0x1F290300: 0x00001F2B - "\x1f(\x03\x01\x00\x00\x1f," + // 0x1F280301: 0x00001F2C - "\x1f)\x03\x01\x00\x00\x1f-" + // 0x1F290301: 0x00001F2D - "\x1f(\x03B\x00\x00\x1f." + // 0x1F280342: 0x00001F2E - "\x1f)\x03B\x00\x00\x1f/" + // 0x1F290342: 0x00001F2F - "\x03\xb9\x03\x13\x00\x00\x1f0" + // 0x03B90313: 0x00001F30 - "\x03\xb9\x03\x14\x00\x00\x1f1" + // 0x03B90314: 0x00001F31 - "\x1f0\x03\x00\x00\x00\x1f2" + // 0x1F300300: 0x00001F32 - "\x1f1\x03\x00\x00\x00\x1f3" + // 0x1F310300: 0x00001F33 - "\x1f0\x03\x01\x00\x00\x1f4" + // 0x1F300301: 0x00001F34 - "\x1f1\x03\x01\x00\x00\x1f5" + // 0x1F310301: 0x00001F35 - "\x1f0\x03B\x00\x00\x1f6" + // 0x1F300342: 0x00001F36 - "\x1f1\x03B\x00\x00\x1f7" + // 0x1F310342: 0x00001F37 - "\x03\x99\x03\x13\x00\x00\x1f8" + // 0x03990313: 0x00001F38 - "\x03\x99\x03\x14\x00\x00\x1f9" + // 0x03990314: 0x00001F39 - "\x1f8\x03\x00\x00\x00\x1f:" + // 0x1F380300: 0x00001F3A - "\x1f9\x03\x00\x00\x00\x1f;" + // 0x1F390300: 0x00001F3B - "\x1f8\x03\x01\x00\x00\x1f<" + // 0x1F380301: 0x00001F3C - "\x1f9\x03\x01\x00\x00\x1f=" + // 0x1F390301: 0x00001F3D - "\x1f8\x03B\x00\x00\x1f>" + // 0x1F380342: 0x00001F3E - "\x1f9\x03B\x00\x00\x1f?" + // 0x1F390342: 0x00001F3F - "\x03\xbf\x03\x13\x00\x00\x1f@" + // 0x03BF0313: 0x00001F40 - "\x03\xbf\x03\x14\x00\x00\x1fA" + // 0x03BF0314: 0x00001F41 - "\x1f@\x03\x00\x00\x00\x1fB" + // 0x1F400300: 0x00001F42 - "\x1fA\x03\x00\x00\x00\x1fC" + // 0x1F410300: 0x00001F43 - "\x1f@\x03\x01\x00\x00\x1fD" + // 0x1F400301: 0x00001F44 - "\x1fA\x03\x01\x00\x00\x1fE" + // 0x1F410301: 0x00001F45 - "\x03\x9f\x03\x13\x00\x00\x1fH" + // 0x039F0313: 0x00001F48 - "\x03\x9f\x03\x14\x00\x00\x1fI" + // 0x039F0314: 0x00001F49 - "\x1fH\x03\x00\x00\x00\x1fJ" + // 0x1F480300: 0x00001F4A - "\x1fI\x03\x00\x00\x00\x1fK" + // 0x1F490300: 0x00001F4B - "\x1fH\x03\x01\x00\x00\x1fL" + // 0x1F480301: 0x00001F4C - "\x1fI\x03\x01\x00\x00\x1fM" + // 0x1F490301: 0x00001F4D - "\x03\xc5\x03\x13\x00\x00\x1fP" + // 0x03C50313: 0x00001F50 - "\x03\xc5\x03\x14\x00\x00\x1fQ" + // 0x03C50314: 0x00001F51 - "\x1fP\x03\x00\x00\x00\x1fR" + // 0x1F500300: 0x00001F52 - "\x1fQ\x03\x00\x00\x00\x1fS" + // 0x1F510300: 0x00001F53 - "\x1fP\x03\x01\x00\x00\x1fT" + // 0x1F500301: 0x00001F54 - "\x1fQ\x03\x01\x00\x00\x1fU" + // 0x1F510301: 0x00001F55 - "\x1fP\x03B\x00\x00\x1fV" + // 0x1F500342: 0x00001F56 - "\x1fQ\x03B\x00\x00\x1fW" + // 0x1F510342: 0x00001F57 - "\x03\xa5\x03\x14\x00\x00\x1fY" + // 0x03A50314: 0x00001F59 - "\x1fY\x03\x00\x00\x00\x1f[" + // 0x1F590300: 0x00001F5B - "\x1fY\x03\x01\x00\x00\x1f]" + // 0x1F590301: 0x00001F5D - "\x1fY\x03B\x00\x00\x1f_" + // 0x1F590342: 0x00001F5F - "\x03\xc9\x03\x13\x00\x00\x1f`" + // 0x03C90313: 0x00001F60 - "\x03\xc9\x03\x14\x00\x00\x1fa" + // 0x03C90314: 0x00001F61 - "\x1f`\x03\x00\x00\x00\x1fb" + // 0x1F600300: 0x00001F62 - "\x1fa\x03\x00\x00\x00\x1fc" + // 0x1F610300: 0x00001F63 - "\x1f`\x03\x01\x00\x00\x1fd" + // 0x1F600301: 0x00001F64 - "\x1fa\x03\x01\x00\x00\x1fe" + // 0x1F610301: 0x00001F65 - "\x1f`\x03B\x00\x00\x1ff" + // 0x1F600342: 0x00001F66 - "\x1fa\x03B\x00\x00\x1fg" + // 0x1F610342: 0x00001F67 - "\x03\xa9\x03\x13\x00\x00\x1fh" + // 0x03A90313: 0x00001F68 - "\x03\xa9\x03\x14\x00\x00\x1fi" + // 0x03A90314: 0x00001F69 - "\x1fh\x03\x00\x00\x00\x1fj" + // 0x1F680300: 0x00001F6A - "\x1fi\x03\x00\x00\x00\x1fk" + // 0x1F690300: 0x00001F6B - "\x1fh\x03\x01\x00\x00\x1fl" + // 0x1F680301: 0x00001F6C - "\x1fi\x03\x01\x00\x00\x1fm" + // 0x1F690301: 0x00001F6D - "\x1fh\x03B\x00\x00\x1fn" + // 0x1F680342: 0x00001F6E - "\x1fi\x03B\x00\x00\x1fo" + // 0x1F690342: 0x00001F6F - "\x03\xb1\x03\x00\x00\x00\x1fp" + // 0x03B10300: 0x00001F70 - "\x03\xb5\x03\x00\x00\x00\x1fr" + // 0x03B50300: 0x00001F72 - "\x03\xb7\x03\x00\x00\x00\x1ft" + // 0x03B70300: 0x00001F74 - "\x03\xb9\x03\x00\x00\x00\x1fv" + // 0x03B90300: 0x00001F76 - "\x03\xbf\x03\x00\x00\x00\x1fx" + // 0x03BF0300: 0x00001F78 - "\x03\xc5\x03\x00\x00\x00\x1fz" + // 0x03C50300: 0x00001F7A - "\x03\xc9\x03\x00\x00\x00\x1f|" + // 0x03C90300: 0x00001F7C - "\x1f\x00\x03E\x00\x00\x1f\x80" + // 0x1F000345: 0x00001F80 - "\x1f\x01\x03E\x00\x00\x1f\x81" + // 0x1F010345: 0x00001F81 - "\x1f\x02\x03E\x00\x00\x1f\x82" + // 0x1F020345: 0x00001F82 - "\x1f\x03\x03E\x00\x00\x1f\x83" + // 0x1F030345: 0x00001F83 - "\x1f\x04\x03E\x00\x00\x1f\x84" + // 0x1F040345: 0x00001F84 - "\x1f\x05\x03E\x00\x00\x1f\x85" + // 0x1F050345: 0x00001F85 - "\x1f\x06\x03E\x00\x00\x1f\x86" + // 0x1F060345: 0x00001F86 - "\x1f\a\x03E\x00\x00\x1f\x87" + // 0x1F070345: 0x00001F87 - "\x1f\b\x03E\x00\x00\x1f\x88" + // 0x1F080345: 0x00001F88 - "\x1f\t\x03E\x00\x00\x1f\x89" + // 0x1F090345: 0x00001F89 - "\x1f\n\x03E\x00\x00\x1f\x8a" + // 0x1F0A0345: 0x00001F8A - "\x1f\v\x03E\x00\x00\x1f\x8b" + // 0x1F0B0345: 0x00001F8B - "\x1f\f\x03E\x00\x00\x1f\x8c" + // 0x1F0C0345: 0x00001F8C - "\x1f\r\x03E\x00\x00\x1f\x8d" + // 0x1F0D0345: 0x00001F8D - "\x1f\x0e\x03E\x00\x00\x1f\x8e" + // 0x1F0E0345: 0x00001F8E - "\x1f\x0f\x03E\x00\x00\x1f\x8f" + // 0x1F0F0345: 0x00001F8F - "\x1f \x03E\x00\x00\x1f\x90" + // 0x1F200345: 0x00001F90 - "\x1f!\x03E\x00\x00\x1f\x91" + // 0x1F210345: 0x00001F91 - "\x1f\"\x03E\x00\x00\x1f\x92" + // 0x1F220345: 0x00001F92 - "\x1f#\x03E\x00\x00\x1f\x93" + // 0x1F230345: 0x00001F93 - "\x1f$\x03E\x00\x00\x1f\x94" + // 0x1F240345: 0x00001F94 - "\x1f%\x03E\x00\x00\x1f\x95" + // 0x1F250345: 0x00001F95 - "\x1f&\x03E\x00\x00\x1f\x96" + // 0x1F260345: 0x00001F96 - "\x1f'\x03E\x00\x00\x1f\x97" + // 0x1F270345: 0x00001F97 - "\x1f(\x03E\x00\x00\x1f\x98" + // 0x1F280345: 0x00001F98 - "\x1f)\x03E\x00\x00\x1f\x99" + // 0x1F290345: 0x00001F99 - "\x1f*\x03E\x00\x00\x1f\x9a" + // 0x1F2A0345: 0x00001F9A - "\x1f+\x03E\x00\x00\x1f\x9b" + // 0x1F2B0345: 0x00001F9B - "\x1f,\x03E\x00\x00\x1f\x9c" + // 0x1F2C0345: 0x00001F9C - "\x1f-\x03E\x00\x00\x1f\x9d" + // 0x1F2D0345: 0x00001F9D - "\x1f.\x03E\x00\x00\x1f\x9e" + // 0x1F2E0345: 0x00001F9E - "\x1f/\x03E\x00\x00\x1f\x9f" + // 0x1F2F0345: 0x00001F9F - "\x1f`\x03E\x00\x00\x1f\xa0" + // 0x1F600345: 0x00001FA0 - "\x1fa\x03E\x00\x00\x1f\xa1" + // 0x1F610345: 0x00001FA1 - "\x1fb\x03E\x00\x00\x1f\xa2" + // 0x1F620345: 0x00001FA2 - "\x1fc\x03E\x00\x00\x1f\xa3" + // 0x1F630345: 0x00001FA3 - "\x1fd\x03E\x00\x00\x1f\xa4" + // 0x1F640345: 0x00001FA4 - "\x1fe\x03E\x00\x00\x1f\xa5" + // 0x1F650345: 0x00001FA5 - "\x1ff\x03E\x00\x00\x1f\xa6" + // 0x1F660345: 0x00001FA6 - "\x1fg\x03E\x00\x00\x1f\xa7" + // 0x1F670345: 0x00001FA7 - "\x1fh\x03E\x00\x00\x1f\xa8" + // 0x1F680345: 0x00001FA8 - "\x1fi\x03E\x00\x00\x1f\xa9" + // 0x1F690345: 0x00001FA9 - "\x1fj\x03E\x00\x00\x1f\xaa" + // 0x1F6A0345: 0x00001FAA - "\x1fk\x03E\x00\x00\x1f\xab" + // 0x1F6B0345: 0x00001FAB - "\x1fl\x03E\x00\x00\x1f\xac" + // 0x1F6C0345: 0x00001FAC - "\x1fm\x03E\x00\x00\x1f\xad" + // 0x1F6D0345: 0x00001FAD - "\x1fn\x03E\x00\x00\x1f\xae" + // 0x1F6E0345: 0x00001FAE - "\x1fo\x03E\x00\x00\x1f\xaf" + // 0x1F6F0345: 0x00001FAF - "\x03\xb1\x03\x06\x00\x00\x1f\xb0" + // 0x03B10306: 0x00001FB0 - "\x03\xb1\x03\x04\x00\x00\x1f\xb1" + // 0x03B10304: 0x00001FB1 - "\x1fp\x03E\x00\x00\x1f\xb2" + // 0x1F700345: 0x00001FB2 - "\x03\xb1\x03E\x00\x00\x1f\xb3" + // 0x03B10345: 0x00001FB3 - "\x03\xac\x03E\x00\x00\x1f\xb4" + // 0x03AC0345: 0x00001FB4 - "\x03\xb1\x03B\x00\x00\x1f\xb6" + // 0x03B10342: 0x00001FB6 - "\x1f\xb6\x03E\x00\x00\x1f\xb7" + // 0x1FB60345: 0x00001FB7 - "\x03\x91\x03\x06\x00\x00\x1f\xb8" + // 0x03910306: 0x00001FB8 - "\x03\x91\x03\x04\x00\x00\x1f\xb9" + // 0x03910304: 0x00001FB9 - "\x03\x91\x03\x00\x00\x00\x1f\xba" + // 0x03910300: 0x00001FBA - "\x03\x91\x03E\x00\x00\x1f\xbc" + // 0x03910345: 0x00001FBC - "\x00\xa8\x03B\x00\x00\x1f\xc1" + // 0x00A80342: 0x00001FC1 - "\x1ft\x03E\x00\x00\x1f\xc2" + // 0x1F740345: 0x00001FC2 - "\x03\xb7\x03E\x00\x00\x1f\xc3" + // 0x03B70345: 0x00001FC3 - "\x03\xae\x03E\x00\x00\x1f\xc4" + // 0x03AE0345: 0x00001FC4 - "\x03\xb7\x03B\x00\x00\x1f\xc6" + // 0x03B70342: 0x00001FC6 - "\x1f\xc6\x03E\x00\x00\x1f\xc7" + // 0x1FC60345: 0x00001FC7 - "\x03\x95\x03\x00\x00\x00\x1f\xc8" + // 0x03950300: 0x00001FC8 - "\x03\x97\x03\x00\x00\x00\x1f\xca" + // 0x03970300: 0x00001FCA - "\x03\x97\x03E\x00\x00\x1f\xcc" + // 0x03970345: 0x00001FCC - "\x1f\xbf\x03\x00\x00\x00\x1f\xcd" + // 0x1FBF0300: 0x00001FCD - "\x1f\xbf\x03\x01\x00\x00\x1f\xce" + // 0x1FBF0301: 0x00001FCE - "\x1f\xbf\x03B\x00\x00\x1f\xcf" + // 0x1FBF0342: 0x00001FCF - "\x03\xb9\x03\x06\x00\x00\x1f\xd0" + // 0x03B90306: 0x00001FD0 - "\x03\xb9\x03\x04\x00\x00\x1f\xd1" + // 0x03B90304: 0x00001FD1 - "\x03\xca\x03\x00\x00\x00\x1f\xd2" + // 0x03CA0300: 0x00001FD2 - "\x03\xb9\x03B\x00\x00\x1f\xd6" + // 0x03B90342: 0x00001FD6 - "\x03\xca\x03B\x00\x00\x1f\xd7" + // 0x03CA0342: 0x00001FD7 - "\x03\x99\x03\x06\x00\x00\x1f\xd8" + // 0x03990306: 0x00001FD8 - "\x03\x99\x03\x04\x00\x00\x1f\xd9" + // 0x03990304: 0x00001FD9 - "\x03\x99\x03\x00\x00\x00\x1f\xda" + // 0x03990300: 0x00001FDA - "\x1f\xfe\x03\x00\x00\x00\x1f\xdd" + // 0x1FFE0300: 0x00001FDD - "\x1f\xfe\x03\x01\x00\x00\x1f\xde" + // 0x1FFE0301: 0x00001FDE - "\x1f\xfe\x03B\x00\x00\x1f\xdf" + // 0x1FFE0342: 0x00001FDF - "\x03\xc5\x03\x06\x00\x00\x1f\xe0" + // 0x03C50306: 0x00001FE0 - "\x03\xc5\x03\x04\x00\x00\x1f\xe1" + // 0x03C50304: 0x00001FE1 - "\x03\xcb\x03\x00\x00\x00\x1f\xe2" + // 0x03CB0300: 0x00001FE2 - "\x03\xc1\x03\x13\x00\x00\x1f\xe4" + // 0x03C10313: 0x00001FE4 - "\x03\xc1\x03\x14\x00\x00\x1f\xe5" + // 0x03C10314: 0x00001FE5 - "\x03\xc5\x03B\x00\x00\x1f\xe6" + // 0x03C50342: 0x00001FE6 - "\x03\xcb\x03B\x00\x00\x1f\xe7" + // 0x03CB0342: 0x00001FE7 - "\x03\xa5\x03\x06\x00\x00\x1f\xe8" + // 0x03A50306: 0x00001FE8 - "\x03\xa5\x03\x04\x00\x00\x1f\xe9" + // 0x03A50304: 0x00001FE9 - "\x03\xa5\x03\x00\x00\x00\x1f\xea" + // 0x03A50300: 0x00001FEA - "\x03\xa1\x03\x14\x00\x00\x1f\xec" + // 0x03A10314: 0x00001FEC - "\x00\xa8\x03\x00\x00\x00\x1f\xed" + // 0x00A80300: 0x00001FED - "\x1f|\x03E\x00\x00\x1f\xf2" + // 0x1F7C0345: 0x00001FF2 - "\x03\xc9\x03E\x00\x00\x1f\xf3" + // 0x03C90345: 0x00001FF3 - "\x03\xce\x03E\x00\x00\x1f\xf4" + // 0x03CE0345: 0x00001FF4 - "\x03\xc9\x03B\x00\x00\x1f\xf6" + // 0x03C90342: 0x00001FF6 - "\x1f\xf6\x03E\x00\x00\x1f\xf7" + // 0x1FF60345: 0x00001FF7 - "\x03\x9f\x03\x00\x00\x00\x1f\xf8" + // 0x039F0300: 0x00001FF8 - "\x03\xa9\x03\x00\x00\x00\x1f\xfa" + // 0x03A90300: 0x00001FFA - "\x03\xa9\x03E\x00\x00\x1f\xfc" + // 0x03A90345: 0x00001FFC - "!\x90\x038\x00\x00!\x9a" + // 0x21900338: 0x0000219A - "!\x92\x038\x00\x00!\x9b" + // 0x21920338: 0x0000219B - "!\x94\x038\x00\x00!\xae" + // 0x21940338: 0x000021AE - "!\xd0\x038\x00\x00!\xcd" + // 0x21D00338: 0x000021CD - "!\xd4\x038\x00\x00!\xce" + // 0x21D40338: 0x000021CE - "!\xd2\x038\x00\x00!\xcf" + // 0x21D20338: 0x000021CF - "\"\x03\x038\x00\x00\"\x04" + // 0x22030338: 0x00002204 - "\"\b\x038\x00\x00\"\t" + // 0x22080338: 0x00002209 - "\"\v\x038\x00\x00\"\f" + // 0x220B0338: 0x0000220C - "\"#\x038\x00\x00\"$" + // 0x22230338: 0x00002224 - "\"%\x038\x00\x00\"&" + // 0x22250338: 0x00002226 - "\"<\x038\x00\x00\"A" + // 0x223C0338: 0x00002241 - "\"C\x038\x00\x00\"D" + // 0x22430338: 0x00002244 - "\"E\x038\x00\x00\"G" + // 0x22450338: 0x00002247 - "\"H\x038\x00\x00\"I" + // 0x22480338: 0x00002249 - "\x00=\x038\x00\x00\"`" + // 0x003D0338: 0x00002260 - "\"a\x038\x00\x00\"b" + // 0x22610338: 0x00002262 - "\"M\x038\x00\x00\"m" + // 0x224D0338: 0x0000226D - "\x00<\x038\x00\x00\"n" + // 0x003C0338: 0x0000226E - "\x00>\x038\x00\x00\"o" + // 0x003E0338: 0x0000226F - "\"d\x038\x00\x00\"p" + // 0x22640338: 0x00002270 - "\"e\x038\x00\x00\"q" + // 0x22650338: 0x00002271 - "\"r\x038\x00\x00\"t" + // 0x22720338: 0x00002274 - "\"s\x038\x00\x00\"u" + // 0x22730338: 0x00002275 - "\"v\x038\x00\x00\"x" + // 0x22760338: 0x00002278 - "\"w\x038\x00\x00\"y" + // 0x22770338: 0x00002279 - "\"z\x038\x00\x00\"\x80" + // 0x227A0338: 0x00002280 - "\"{\x038\x00\x00\"\x81" + // 0x227B0338: 0x00002281 - "\"\x82\x038\x00\x00\"\x84" + // 0x22820338: 0x00002284 - "\"\x83\x038\x00\x00\"\x85" + // 0x22830338: 0x00002285 - "\"\x86\x038\x00\x00\"\x88" + // 0x22860338: 0x00002288 - "\"\x87\x038\x00\x00\"\x89" + // 0x22870338: 0x00002289 - "\"\xa2\x038\x00\x00\"\xac" + // 0x22A20338: 0x000022AC - "\"\xa8\x038\x00\x00\"\xad" + // 0x22A80338: 0x000022AD - "\"\xa9\x038\x00\x00\"\xae" + // 0x22A90338: 0x000022AE - "\"\xab\x038\x00\x00\"\xaf" + // 0x22AB0338: 0x000022AF - "\"|\x038\x00\x00\"\xe0" + // 0x227C0338: 0x000022E0 - "\"}\x038\x00\x00\"\xe1" + // 0x227D0338: 0x000022E1 - "\"\x91\x038\x00\x00\"\xe2" + // 0x22910338: 0x000022E2 - "\"\x92\x038\x00\x00\"\xe3" + // 0x22920338: 0x000022E3 - "\"\xb2\x038\x00\x00\"\xea" + // 0x22B20338: 0x000022EA - "\"\xb3\x038\x00\x00\"\xeb" + // 0x22B30338: 0x000022EB - "\"\xb4\x038\x00\x00\"\xec" + // 0x22B40338: 0x000022EC - "\"\xb5\x038\x00\x00\"\xed" + // 0x22B50338: 0x000022ED - "0K0\x99\x00\x000L" + // 0x304B3099: 0x0000304C - "0M0\x99\x00\x000N" + // 0x304D3099: 0x0000304E - "0O0\x99\x00\x000P" + // 0x304F3099: 0x00003050 - "0Q0\x99\x00\x000R" + // 0x30513099: 0x00003052 - "0S0\x99\x00\x000T" + // 0x30533099: 0x00003054 - "0U0\x99\x00\x000V" + // 0x30553099: 0x00003056 - "0W0\x99\x00\x000X" + // 0x30573099: 0x00003058 - "0Y0\x99\x00\x000Z" + // 0x30593099: 0x0000305A - "0[0\x99\x00\x000\\" + // 0x305B3099: 0x0000305C - "0]0\x99\x00\x000^" + // 0x305D3099: 0x0000305E - "0_0\x99\x00\x000`" + // 0x305F3099: 0x00003060 - "0a0\x99\x00\x000b" + // 0x30613099: 0x00003062 - "0d0\x99\x00\x000e" + // 0x30643099: 0x00003065 - "0f0\x99\x00\x000g" + // 0x30663099: 0x00003067 - "0h0\x99\x00\x000i" + // 0x30683099: 0x00003069 - "0o0\x99\x00\x000p" + // 0x306F3099: 0x00003070 - "0o0\x9a\x00\x000q" + // 0x306F309A: 0x00003071 - "0r0\x99\x00\x000s" + // 0x30723099: 0x00003073 - "0r0\x9a\x00\x000t" + // 0x3072309A: 0x00003074 - "0u0\x99\x00\x000v" + // 0x30753099: 0x00003076 - "0u0\x9a\x00\x000w" + // 0x3075309A: 0x00003077 - "0x0\x99\x00\x000y" + // 0x30783099: 0x00003079 - "0x0\x9a\x00\x000z" + // 0x3078309A: 0x0000307A - "0{0\x99\x00\x000|" + // 0x307B3099: 0x0000307C - "0{0\x9a\x00\x000}" + // 0x307B309A: 0x0000307D - "0F0\x99\x00\x000\x94" + // 0x30463099: 0x00003094 - "0\x9d0\x99\x00\x000\x9e" + // 0x309D3099: 0x0000309E - "0\xab0\x99\x00\x000\xac" + // 0x30AB3099: 0x000030AC - "0\xad0\x99\x00\x000\xae" + // 0x30AD3099: 0x000030AE - "0\xaf0\x99\x00\x000\xb0" + // 0x30AF3099: 0x000030B0 - "0\xb10\x99\x00\x000\xb2" + // 0x30B13099: 0x000030B2 - "0\xb30\x99\x00\x000\xb4" + // 0x30B33099: 0x000030B4 - "0\xb50\x99\x00\x000\xb6" + // 0x30B53099: 0x000030B6 - "0\xb70\x99\x00\x000\xb8" + // 0x30B73099: 0x000030B8 - "0\xb90\x99\x00\x000\xba" + // 0x30B93099: 0x000030BA - "0\xbb0\x99\x00\x000\xbc" + // 0x30BB3099: 0x000030BC - "0\xbd0\x99\x00\x000\xbe" + // 0x30BD3099: 0x000030BE - "0\xbf0\x99\x00\x000\xc0" + // 0x30BF3099: 0x000030C0 - "0\xc10\x99\x00\x000\xc2" + // 0x30C13099: 0x000030C2 - "0\xc40\x99\x00\x000\xc5" + // 0x30C43099: 0x000030C5 - "0\xc60\x99\x00\x000\xc7" + // 0x30C63099: 0x000030C7 - "0\xc80\x99\x00\x000\xc9" + // 0x30C83099: 0x000030C9 - "0\xcf0\x99\x00\x000\xd0" + // 0x30CF3099: 0x000030D0 - "0\xcf0\x9a\x00\x000\xd1" + // 0x30CF309A: 0x000030D1 - "0\xd20\x99\x00\x000\xd3" + // 0x30D23099: 0x000030D3 - "0\xd20\x9a\x00\x000\xd4" + // 0x30D2309A: 0x000030D4 - "0\xd50\x99\x00\x000\xd6" + // 0x30D53099: 0x000030D6 - "0\xd50\x9a\x00\x000\xd7" + // 0x30D5309A: 0x000030D7 - "0\xd80\x99\x00\x000\xd9" + // 0x30D83099: 0x000030D9 - "0\xd80\x9a\x00\x000\xda" + // 0x30D8309A: 0x000030DA - "0\xdb0\x99\x00\x000\xdc" + // 0x30DB3099: 0x000030DC - "0\xdb0\x9a\x00\x000\xdd" + // 0x30DB309A: 0x000030DD - "0\xa60\x99\x00\x000\xf4" + // 0x30A63099: 0x000030F4 - "0\xef0\x99\x00\x000\xf7" + // 0x30EF3099: 0x000030F7 - "0\xf00\x99\x00\x000\xf8" + // 0x30F03099: 0x000030F8 - "0\xf10\x99\x00\x000\xf9" + // 0x30F13099: 0x000030F9 - "0\xf20\x99\x00\x000\xfa" + // 0x30F23099: 0x000030FA - "0\xfd0\x99\x00\x000\xfe" + // 0x30FD3099: 0x000030FE - "\x05\xd2\x03\a\x00\x01\x05\xc9" + // 0x05D20307: 0x000105C9 - "\x05\xda\x03\a\x00\x01\x05\xe4" + // 0x05DA0307: 0x000105E4 - "\x10\x99\x10\xba\x00\x01\x10\x9a" + // 0x109910BA: 0x0001109A - "\x10\x9b\x10\xba\x00\x01\x10\x9c" + // 0x109B10BA: 0x0001109C - "\x10\xa5\x10\xba\x00\x01\x10\xab" + // 0x10A510BA: 0x000110AB - "\x111\x11'\x00\x01\x11." + // 0x11311127: 0x0001112E - "\x112\x11'\x00\x01\x11/" + // 0x11321127: 0x0001112F - "\x13G\x13>\x00\x01\x13K" + // 0x1347133E: 0x0001134B - "\x13G\x13W\x00\x01\x13L" + // 0x13471357: 0x0001134C - "\x13\x82\x13\xc9\x00\x01\x13\x83" + // 0x138213C9: 0x00011383 - "\x13\x84\x13\xbb\x00\x01\x13\x85" + // 0x138413BB: 0x00011385 - "\x13\x8b\x13\xc2\x00\x01\x13\x8e" + // 0x138B13C2: 0x0001138E - "\x13\x90\x13\xc9\x00\x01\x13\x91" + // 0x139013C9: 0x00011391 - "\x13\xc2\x13\xc2\x00\x01\x13\xc5" + // 0x13C213C2: 0x000113C5 - "\x13\xc2\x13\xb8\x00\x01\x13\xc7" + // 0x13C213B8: 0x000113C7 - "\x13\xc2\x13\xc9\x00\x01\x13\xc8" + // 0x13C213C9: 0x000113C8 - "\x14\xb9\x14\xba\x00\x01\x14\xbb" + // 0x14B914BA: 0x000114BB - "\x14\xb9\x14\xb0\x00\x01\x14\xbc" + // 0x14B914B0: 0x000114BC - "\x14\xb9\x14\xbd\x00\x01\x14\xbe" + // 0x14B914BD: 0x000114BE - "\x15\xb8\x15\xaf\x00\x01\x15\xba" + // 0x15B815AF: 0x000115BA - "\x15\xb9\x15\xaf\x00\x01\x15\xbb" + // 0x15B915AF: 0x000115BB - "\x195\x190\x00\x01\x198" + // 0x19351930: 0x00011938 - "a\x1ea\x1e\x00\x01a!" + // 0x611E611E: 0x00016121 - "a\x1ea)\x00\x01a\"" + // 0x611E6129: 0x00016122 - "a\x1ea\x1f\x00\x01a#" + // 0x611E611F: 0x00016123 - "a)a\x1f\x00\x01a$" + // 0x6129611F: 0x00016124 - "a\x1ea \x00\x01a%" + // 0x611E6120: 0x00016125 - "a!a\x1f\x00\x01a&" + // 0x6121611F: 0x00016126 - "a\"a\x1f\x00\x01a'" + // 0x6122611F: 0x00016127 - "a!a \x00\x01a(" + // 0x61216120: 0x00016128 - "mgmg\x00\x01mh" + // 0x6D676D67: 0x00016D68 - "mcmg\x00\x01mi" + // 0x6D636D67: 0x00016D69 - "mimg\x00\x01mj" + // 0x6D696D67: 0x00016D6A + "\x00\x01\x04\x00`\x00\x00\xc0" + // 0x00041 0x00300: 0x000C0 + "\x00\x01\x04\x00` \x00\xc1" + // 0x00041 0x00301: 0x000C1 + "\x00\x01\x04\x00`@\x00\xc2" + // 0x00041 0x00302: 0x000C2 + "\x00\x01\x04\x00``\x00\xc3" + // 0x00041 0x00303: 0x000C3 + "\x00\x01\x04\x00a\x00\x00\xc4" + // 0x00041 0x00308: 0x000C4 + "\x00\x01\x04\x00a@\x00\xc5" + // 0x00041 0x0030A: 0x000C5 + "\x00\x01\f\x00d\xe0\x00\xc7" + // 0x00043 0x00327: 0x000C7 + "\x00\x01\x14\x00`\x00\x00\xc8" + // 0x00045 0x00300: 0x000C8 + "\x00\x01\x14\x00` \x00\xc9" + // 0x00045 0x00301: 0x000C9 + "\x00\x01\x14\x00`@\x00\xca" + // 0x00045 0x00302: 0x000CA + "\x00\x01\x14\x00a\x00\x00\xcb" + // 0x00045 0x00308: 0x000CB + "\x00\x01$\x00`\x00\x00\xcc" + // 0x00049 0x00300: 0x000CC + "\x00\x01$\x00` \x00\xcd" + // 0x00049 0x00301: 0x000CD + "\x00\x01$\x00`@\x00\xce" + // 0x00049 0x00302: 0x000CE + "\x00\x01$\x00a\x00\x00\xcf" + // 0x00049 0x00308: 0x000CF + "\x00\x018\x00``\x00\xd1" + // 0x0004E 0x00303: 0x000D1 + "\x00\x01<\x00`\x00\x00\xd2" + // 0x0004F 0x00300: 0x000D2 + "\x00\x01<\x00` \x00\xd3" + // 0x0004F 0x00301: 0x000D3 + "\x00\x01<\x00`@\x00\xd4" + // 0x0004F 0x00302: 0x000D4 + "\x00\x01<\x00``\x00\xd5" + // 0x0004F 0x00303: 0x000D5 + "\x00\x01<\x00a\x00\x00\xd6" + // 0x0004F 0x00308: 0x000D6 + "\x00\x01T\x00`\x00\x00\xd9" + // 0x00055 0x00300: 0x000D9 + "\x00\x01T\x00` \x00\xda" + // 0x00055 0x00301: 0x000DA + "\x00\x01T\x00`@\x00\xdb" + // 0x00055 0x00302: 0x000DB + "\x00\x01T\x00a\x00\x00\xdc" + // 0x00055 0x00308: 0x000DC + "\x00\x01d\x00` \x00\xdd" + // 0x00059 0x00301: 0x000DD + "\x00\x01\x84\x00`\x00\x00\xe0" + // 0x00061 0x00300: 0x000E0 + "\x00\x01\x84\x00` \x00\xe1" + // 0x00061 0x00301: 0x000E1 + "\x00\x01\x84\x00`@\x00\xe2" + // 0x00061 0x00302: 0x000E2 + "\x00\x01\x84\x00``\x00\xe3" + // 0x00061 0x00303: 0x000E3 + "\x00\x01\x84\x00a\x00\x00\xe4" + // 0x00061 0x00308: 0x000E4 + "\x00\x01\x84\x00a@\x00\xe5" + // 0x00061 0x0030A: 0x000E5 + "\x00\x01\x8c\x00d\xe0\x00\xe7" + // 0x00063 0x00327: 0x000E7 + "\x00\x01\x94\x00`\x00\x00\xe8" + // 0x00065 0x00300: 0x000E8 + "\x00\x01\x94\x00` \x00\xe9" + // 0x00065 0x00301: 0x000E9 + "\x00\x01\x94\x00`@\x00\xea" + // 0x00065 0x00302: 0x000EA + "\x00\x01\x94\x00a\x00\x00\xeb" + // 0x00065 0x00308: 0x000EB + "\x00\x01\xa4\x00`\x00\x00\xec" + // 0x00069 0x00300: 0x000EC + "\x00\x01\xa4\x00` \x00\xed" + // 0x00069 0x00301: 0x000ED + "\x00\x01\xa4\x00`@\x00\xee" + // 0x00069 0x00302: 0x000EE + "\x00\x01\xa4\x00a\x00\x00\xef" + // 0x00069 0x00308: 0x000EF + "\x00\x01\xb8\x00``\x00\xf1" + // 0x0006E 0x00303: 0x000F1 + "\x00\x01\xbc\x00`\x00\x00\xf2" + // 0x0006F 0x00300: 0x000F2 + "\x00\x01\xbc\x00` \x00\xf3" + // 0x0006F 0x00301: 0x000F3 + "\x00\x01\xbc\x00`@\x00\xf4" + // 0x0006F 0x00302: 0x000F4 + "\x00\x01\xbc\x00``\x00\xf5" + // 0x0006F 0x00303: 0x000F5 + "\x00\x01\xbc\x00a\x00\x00\xf6" + // 0x0006F 0x00308: 0x000F6 + "\x00\x01\xd4\x00`\x00\x00\xf9" + // 0x00075 0x00300: 0x000F9 + "\x00\x01\xd4\x00` \x00\xfa" + // 0x00075 0x00301: 0x000FA + "\x00\x01\xd4\x00`@\x00\xfb" + // 0x00075 0x00302: 0x000FB + "\x00\x01\xd4\x00a\x00\x00\xfc" + // 0x00075 0x00308: 0x000FC + "\x00\x01\xe4\x00` \x00\xfd" + // 0x00079 0x00301: 0x000FD + "\x00\x01\xe4\x00a\x00\x00\xff" + // 0x00079 0x00308: 0x000FF + "\x00\x01\x04\x00`\x80\x01\x00" + // 0x00041 0x00304: 0x00100 + "\x00\x01\x84\x00`\x80\x01\x01" + // 0x00061 0x00304: 0x00101 + "\x00\x01\x04\x00`\xc0\x01\x02" + // 0x00041 0x00306: 0x00102 + "\x00\x01\x84\x00`\xc0\x01\x03" + // 0x00061 0x00306: 0x00103 + "\x00\x01\x04\x00e\x00\x01\x04" + // 0x00041 0x00328: 0x00104 + "\x00\x01\x84\x00e\x00\x01\x05" + // 0x00061 0x00328: 0x00105 + "\x00\x01\f\x00` \x01\x06" + // 0x00043 0x00301: 0x00106 + "\x00\x01\x8c\x00` \x01\a" + // 0x00063 0x00301: 0x00107 + "\x00\x01\f\x00`@\x01\b" + // 0x00043 0x00302: 0x00108 + "\x00\x01\x8c\x00`@\x01\t" + // 0x00063 0x00302: 0x00109 + "\x00\x01\f\x00`\xe0\x01\n" + // 0x00043 0x00307: 0x0010A + "\x00\x01\x8c\x00`\xe0\x01\v" + // 0x00063 0x00307: 0x0010B + "\x00\x01\f\x00a\x80\x01\f" + // 0x00043 0x0030C: 0x0010C + "\x00\x01\x8c\x00a\x80\x01\r" + // 0x00063 0x0030C: 0x0010D + "\x00\x01\x10\x00a\x80\x01\x0e" + // 0x00044 0x0030C: 0x0010E + "\x00\x01\x90\x00a\x80\x01\x0f" + // 0x00064 0x0030C: 0x0010F + "\x00\x01\x14\x00`\x80\x01\x12" + // 0x00045 0x00304: 0x00112 + "\x00\x01\x94\x00`\x80\x01\x13" + // 0x00065 0x00304: 0x00113 + "\x00\x01\x14\x00`\xc0\x01\x14" + // 0x00045 0x00306: 0x00114 + "\x00\x01\x94\x00`\xc0\x01\x15" + // 0x00065 0x00306: 0x00115 + "\x00\x01\x14\x00`\xe0\x01\x16" + // 0x00045 0x00307: 0x00116 + "\x00\x01\x94\x00`\xe0\x01\x17" + // 0x00065 0x00307: 0x00117 + "\x00\x01\x14\x00e\x00\x01\x18" + // 0x00045 0x00328: 0x00118 + "\x00\x01\x94\x00e\x00\x01\x19" + // 0x00065 0x00328: 0x00119 + "\x00\x01\x14\x00a\x80\x01\x1a" + // 0x00045 0x0030C: 0x0011A + "\x00\x01\x94\x00a\x80\x01\x1b" + // 0x00065 0x0030C: 0x0011B + "\x00\x01\x1c\x00`@\x01\x1c" + // 0x00047 0x00302: 0x0011C + "\x00\x01\x9c\x00`@\x01\x1d" + // 0x00067 0x00302: 0x0011D + "\x00\x01\x1c\x00`\xc0\x01\x1e" + // 0x00047 0x00306: 0x0011E + "\x00\x01\x9c\x00`\xc0\x01\x1f" + // 0x00067 0x00306: 0x0011F + "\x00\x01\x1c\x00`\xe0\x01 " + // 0x00047 0x00307: 0x00120 + "\x00\x01\x9c\x00`\xe0\x01!" + // 0x00067 0x00307: 0x00121 + "\x00\x01\x1c\x00d\xe0\x01\"" + // 0x00047 0x00327: 0x00122 + "\x00\x01\x9c\x00d\xe0\x01#" + // 0x00067 0x00327: 0x00123 + "\x00\x01 \x00`@\x01$" + // 0x00048 0x00302: 0x00124 + "\x00\x01\xa0\x00`@\x01%" + // 0x00068 0x00302: 0x00125 + "\x00\x01$\x00``\x01(" + // 0x00049 0x00303: 0x00128 + "\x00\x01\xa4\x00``\x01)" + // 0x00069 0x00303: 0x00129 + "\x00\x01$\x00`\x80\x01*" + // 0x00049 0x00304: 0x0012A + "\x00\x01\xa4\x00`\x80\x01+" + // 0x00069 0x00304: 0x0012B + "\x00\x01$\x00`\xc0\x01," + // 0x00049 0x00306: 0x0012C + "\x00\x01\xa4\x00`\xc0\x01-" + // 0x00069 0x00306: 0x0012D + "\x00\x01$\x00e\x00\x01." + // 0x00049 0x00328: 0x0012E + "\x00\x01\xa4\x00e\x00\x01/" + // 0x00069 0x00328: 0x0012F + "\x00\x01$\x00`\xe0\x010" + // 0x00049 0x00307: 0x00130 + "\x00\x01(\x00`@\x014" + // 0x0004A 0x00302: 0x00134 + "\x00\x01\xa8\x00`@\x015" + // 0x0006A 0x00302: 0x00135 + "\x00\x01,\x00d\xe0\x016" + // 0x0004B 0x00327: 0x00136 + "\x00\x01\xac\x00d\xe0\x017" + // 0x0006B 0x00327: 0x00137 + "\x00\x010\x00` \x019" + // 0x0004C 0x00301: 0x00139 + "\x00\x01\xb0\x00` \x01:" + // 0x0006C 0x00301: 0x0013A + "\x00\x010\x00d\xe0\x01;" + // 0x0004C 0x00327: 0x0013B + "\x00\x01\xb0\x00d\xe0\x01<" + // 0x0006C 0x00327: 0x0013C + "\x00\x010\x00a\x80\x01=" + // 0x0004C 0x0030C: 0x0013D + "\x00\x01\xb0\x00a\x80\x01>" + // 0x0006C 0x0030C: 0x0013E + "\x00\x018\x00` \x01C" + // 0x0004E 0x00301: 0x00143 + "\x00\x01\xb8\x00` \x01D" + // 0x0006E 0x00301: 0x00144 + "\x00\x018\x00d\xe0\x01E" + // 0x0004E 0x00327: 0x00145 + "\x00\x01\xb8\x00d\xe0\x01F" + // 0x0006E 0x00327: 0x00146 + "\x00\x018\x00a\x80\x01G" + // 0x0004E 0x0030C: 0x00147 + "\x00\x01\xb8\x00a\x80\x01H" + // 0x0006E 0x0030C: 0x00148 + "\x00\x01<\x00`\x80\x01L" + // 0x0004F 0x00304: 0x0014C + "\x00\x01\xbc\x00`\x80\x01M" + // 0x0006F 0x00304: 0x0014D + "\x00\x01<\x00`\xc0\x01N" + // 0x0004F 0x00306: 0x0014E + "\x00\x01\xbc\x00`\xc0\x01O" + // 0x0006F 0x00306: 0x0014F + "\x00\x01<\x00a`\x01P" + // 0x0004F 0x0030B: 0x00150 + "\x00\x01\xbc\x00a`\x01Q" + // 0x0006F 0x0030B: 0x00151 + "\x00\x01H\x00` \x01T" + // 0x00052 0x00301: 0x00154 + "\x00\x01\xc8\x00` \x01U" + // 0x00072 0x00301: 0x00155 + "\x00\x01H\x00d\xe0\x01V" + // 0x00052 0x00327: 0x00156 + "\x00\x01\xc8\x00d\xe0\x01W" + // 0x00072 0x00327: 0x00157 + "\x00\x01H\x00a\x80\x01X" + // 0x00052 0x0030C: 0x00158 + "\x00\x01\xc8\x00a\x80\x01Y" + // 0x00072 0x0030C: 0x00159 + "\x00\x01L\x00` \x01Z" + // 0x00053 0x00301: 0x0015A + "\x00\x01\xcc\x00` \x01[" + // 0x00073 0x00301: 0x0015B + "\x00\x01L\x00`@\x01\\" + // 0x00053 0x00302: 0x0015C + "\x00\x01\xcc\x00`@\x01]" + // 0x00073 0x00302: 0x0015D + "\x00\x01L\x00d\xe0\x01^" + // 0x00053 0x00327: 0x0015E + "\x00\x01\xcc\x00d\xe0\x01_" + // 0x00073 0x00327: 0x0015F + "\x00\x01L\x00a\x80\x01`" + // 0x00053 0x0030C: 0x00160 + "\x00\x01\xcc\x00a\x80\x01a" + // 0x00073 0x0030C: 0x00161 + "\x00\x01P\x00d\xe0\x01b" + // 0x00054 0x00327: 0x00162 + "\x00\x01\xd0\x00d\xe0\x01c" + // 0x00074 0x00327: 0x00163 + "\x00\x01P\x00a\x80\x01d" + // 0x00054 0x0030C: 0x00164 + "\x00\x01\xd0\x00a\x80\x01e" + // 0x00074 0x0030C: 0x00165 + "\x00\x01T\x00``\x01h" + // 0x00055 0x00303: 0x00168 + "\x00\x01\xd4\x00``\x01i" + // 0x00075 0x00303: 0x00169 + "\x00\x01T\x00`\x80\x01j" + // 0x00055 0x00304: 0x0016A + "\x00\x01\xd4\x00`\x80\x01k" + // 0x00075 0x00304: 0x0016B + "\x00\x01T\x00`\xc0\x01l" + // 0x00055 0x00306: 0x0016C + "\x00\x01\xd4\x00`\xc0\x01m" + // 0x00075 0x00306: 0x0016D + "\x00\x01T\x00a@\x01n" + // 0x00055 0x0030A: 0x0016E + "\x00\x01\xd4\x00a@\x01o" + // 0x00075 0x0030A: 0x0016F + "\x00\x01T\x00a`\x01p" + // 0x00055 0x0030B: 0x00170 + "\x00\x01\xd4\x00a`\x01q" + // 0x00075 0x0030B: 0x00171 + "\x00\x01T\x00e\x00\x01r" + // 0x00055 0x00328: 0x00172 + "\x00\x01\xd4\x00e\x00\x01s" + // 0x00075 0x00328: 0x00173 + "\x00\x01\\\x00`@\x01t" + // 0x00057 0x00302: 0x00174 + "\x00\x01\xdc\x00`@\x01u" + // 0x00077 0x00302: 0x00175 + "\x00\x01d\x00`@\x01v" + // 0x00059 0x00302: 0x00176 + "\x00\x01\xe4\x00`@\x01w" + // 0x00079 0x00302: 0x00177 + "\x00\x01d\x00a\x00\x01x" + // 0x00059 0x00308: 0x00178 + "\x00\x01h\x00` \x01y" + // 0x0005A 0x00301: 0x00179 + "\x00\x01\xe8\x00` \x01z" + // 0x0007A 0x00301: 0x0017A + "\x00\x01h\x00`\xe0\x01{" + // 0x0005A 0x00307: 0x0017B + "\x00\x01\xe8\x00`\xe0\x01|" + // 0x0007A 0x00307: 0x0017C + "\x00\x01h\x00a\x80\x01}" + // 0x0005A 0x0030C: 0x0017D + "\x00\x01\xe8\x00a\x80\x01~" + // 0x0007A 0x0030C: 0x0017E + "\x00\x01<\x00c`\x01\xa0" + // 0x0004F 0x0031B: 0x001A0 + "\x00\x01\xbc\x00c`\x01\xa1" + // 0x0006F 0x0031B: 0x001A1 + "\x00\x01T\x00c`\x01\xaf" + // 0x00055 0x0031B: 0x001AF + "\x00\x01\xd4\x00c`\x01\xb0" + // 0x00075 0x0031B: 0x001B0 + "\x00\x01\x04\x00a\x80\x01\xcd" + // 0x00041 0x0030C: 0x001CD + "\x00\x01\x84\x00a\x80\x01\xce" + // 0x00061 0x0030C: 0x001CE + "\x00\x01$\x00a\x80\x01\xcf" + // 0x00049 0x0030C: 0x001CF + "\x00\x01\xa4\x00a\x80\x01\xd0" + // 0x00069 0x0030C: 0x001D0 + "\x00\x01<\x00a\x80\x01\xd1" + // 0x0004F 0x0030C: 0x001D1 + "\x00\x01\xbc\x00a\x80\x01\xd2" + // 0x0006F 0x0030C: 0x001D2 + "\x00\x01T\x00a\x80\x01\xd3" + // 0x00055 0x0030C: 0x001D3 + "\x00\x01\xd4\x00a\x80\x01\xd4" + // 0x00075 0x0030C: 0x001D4 + "\x00\x03p\x00`\x80\x01\xd5" + // 0x000DC 0x00304: 0x001D5 + "\x00\x03\xf0\x00`\x80\x01\xd6" + // 0x000FC 0x00304: 0x001D6 + "\x00\x03p\x00` \x01\xd7" + // 0x000DC 0x00301: 0x001D7 + "\x00\x03\xf0\x00` \x01\xd8" + // 0x000FC 0x00301: 0x001D8 + "\x00\x03p\x00a\x80\x01\xd9" + // 0x000DC 0x0030C: 0x001D9 + "\x00\x03\xf0\x00a\x80\x01\xda" + // 0x000FC 0x0030C: 0x001DA + "\x00\x03p\x00`\x00\x01\xdb" + // 0x000DC 0x00300: 0x001DB + "\x00\x03\xf0\x00`\x00\x01\xdc" + // 0x000FC 0x00300: 0x001DC + "\x00\x03\x10\x00`\x80\x01\xde" + // 0x000C4 0x00304: 0x001DE + "\x00\x03\x90\x00`\x80\x01\xdf" + // 0x000E4 0x00304: 0x001DF + "\x00\b\x98\x00`\x80\x01\xe0" + // 0x00226 0x00304: 0x001E0 + "\x00\b\x9c\x00`\x80\x01\xe1" + // 0x00227 0x00304: 0x001E1 + "\x00\x03\x18\x00`\x80\x01\xe2" + // 0x000C6 0x00304: 0x001E2 + "\x00\x03\x98\x00`\x80\x01\xe3" + // 0x000E6 0x00304: 0x001E3 + "\x00\x01\x1c\x00a\x80\x01\xe6" + // 0x00047 0x0030C: 0x001E6 + "\x00\x01\x9c\x00a\x80\x01\xe7" + // 0x00067 0x0030C: 0x001E7 + "\x00\x01,\x00a\x80\x01\xe8" + // 0x0004B 0x0030C: 0x001E8 + "\x00\x01\xac\x00a\x80\x01\xe9" + // 0x0006B 0x0030C: 0x001E9 + "\x00\x01<\x00e\x00\x01\xea" + // 0x0004F 0x00328: 0x001EA + "\x00\x01\xbc\x00e\x00\x01\xeb" + // 0x0006F 0x00328: 0x001EB + "\x00\a\xa8\x00`\x80\x01\xec" + // 0x001EA 0x00304: 0x001EC + "\x00\a\xac\x00`\x80\x01\xed" + // 0x001EB 0x00304: 0x001ED + "\x00\x06\xdc\x00a\x80\x01\xee" + // 0x001B7 0x0030C: 0x001EE + "\x00\nH\x00a\x80\x01\xef" + // 0x00292 0x0030C: 0x001EF + "\x00\x01\xa8\x00a\x80\x01\xf0" + // 0x0006A 0x0030C: 0x001F0 + "\x00\x01\x1c\x00` \x01\xf4" + // 0x00047 0x00301: 0x001F4 + "\x00\x01\x9c\x00` \x01\xf5" + // 0x00067 0x00301: 0x001F5 + "\x00\x018\x00`\x00\x01\xf8" + // 0x0004E 0x00300: 0x001F8 + "\x00\x01\xb8\x00`\x00\x01\xf9" + // 0x0006E 0x00300: 0x001F9 + "\x00\x03\x14\x00` \x01\xfa" + // 0x000C5 0x00301: 0x001FA + "\x00\x03\x94\x00` \x01\xfb" + // 0x000E5 0x00301: 0x001FB + "\x00\x03\x18\x00` \x01\xfc" + // 0x000C6 0x00301: 0x001FC + "\x00\x03\x98\x00` \x01\xfd" + // 0x000E6 0x00301: 0x001FD + "\x00\x03`\x00` \x01\xfe" + // 0x000D8 0x00301: 0x001FE + "\x00\x03\xe0\x00` \x01\xff" + // 0x000F8 0x00301: 0x001FF + "\x00\x01\x04\x00a\xe0\x02\x00" + // 0x00041 0x0030F: 0x00200 + "\x00\x01\x84\x00a\xe0\x02\x01" + // 0x00061 0x0030F: 0x00201 + "\x00\x01\x04\x00b \x02\x02" + // 0x00041 0x00311: 0x00202 + "\x00\x01\x84\x00b \x02\x03" + // 0x00061 0x00311: 0x00203 + "\x00\x01\x14\x00a\xe0\x02\x04" + // 0x00045 0x0030F: 0x00204 + "\x00\x01\x94\x00a\xe0\x02\x05" + // 0x00065 0x0030F: 0x00205 + "\x00\x01\x14\x00b \x02\x06" + // 0x00045 0x00311: 0x00206 + "\x00\x01\x94\x00b \x02\a" + // 0x00065 0x00311: 0x00207 + "\x00\x01$\x00a\xe0\x02\b" + // 0x00049 0x0030F: 0x00208 + "\x00\x01\xa4\x00a\xe0\x02\t" + // 0x00069 0x0030F: 0x00209 + "\x00\x01$\x00b \x02\n" + // 0x00049 0x00311: 0x0020A + "\x00\x01\xa4\x00b \x02\v" + // 0x00069 0x00311: 0x0020B + "\x00\x01<\x00a\xe0\x02\f" + // 0x0004F 0x0030F: 0x0020C + "\x00\x01\xbc\x00a\xe0\x02\r" + // 0x0006F 0x0030F: 0x0020D + "\x00\x01<\x00b \x02\x0e" + // 0x0004F 0x00311: 0x0020E + "\x00\x01\xbc\x00b \x02\x0f" + // 0x0006F 0x00311: 0x0020F + "\x00\x01H\x00a\xe0\x02\x10" + // 0x00052 0x0030F: 0x00210 + "\x00\x01\xc8\x00a\xe0\x02\x11" + // 0x00072 0x0030F: 0x00211 + "\x00\x01H\x00b \x02\x12" + // 0x00052 0x00311: 0x00212 + "\x00\x01\xc8\x00b \x02\x13" + // 0x00072 0x00311: 0x00213 + "\x00\x01T\x00a\xe0\x02\x14" + // 0x00055 0x0030F: 0x00214 + "\x00\x01\xd4\x00a\xe0\x02\x15" + // 0x00075 0x0030F: 0x00215 + "\x00\x01T\x00b \x02\x16" + // 0x00055 0x00311: 0x00216 + "\x00\x01\xd4\x00b \x02\x17" + // 0x00075 0x00311: 0x00217 + "\x00\x01L\x00d\xc0\x02\x18" + // 0x00053 0x00326: 0x00218 + "\x00\x01\xcc\x00d\xc0\x02\x19" + // 0x00073 0x00326: 0x00219 + "\x00\x01P\x00d\xc0\x02\x1a" + // 0x00054 0x00326: 0x0021A + "\x00\x01\xd0\x00d\xc0\x02\x1b" + // 0x00074 0x00326: 0x0021B + "\x00\x01 \x00a\x80\x02\x1e" + // 0x00048 0x0030C: 0x0021E + "\x00\x01\xa0\x00a\x80\x02\x1f" + // 0x00068 0x0030C: 0x0021F + "\x00\x01\x04\x00`\xe0\x02&" + // 0x00041 0x00307: 0x00226 + "\x00\x01\x84\x00`\xe0\x02'" + // 0x00061 0x00307: 0x00227 + "\x00\x01\x14\x00d\xe0\x02(" + // 0x00045 0x00327: 0x00228 + "\x00\x01\x94\x00d\xe0\x02)" + // 0x00065 0x00327: 0x00229 + "\x00\x03X\x00`\x80\x02*" + // 0x000D6 0x00304: 0x0022A + "\x00\x03\xd8\x00`\x80\x02+" + // 0x000F6 0x00304: 0x0022B + "\x00\x03T\x00`\x80\x02," + // 0x000D5 0x00304: 0x0022C + "\x00\x03\xd4\x00`\x80\x02-" + // 0x000F5 0x00304: 0x0022D + "\x00\x01<\x00`\xe0\x02." + // 0x0004F 0x00307: 0x0022E + "\x00\x01\xbc\x00`\xe0\x02/" + // 0x0006F 0x00307: 0x0022F + "\x00\b\xb8\x00`\x80\x020" + // 0x0022E 0x00304: 0x00230 + "\x00\b\xbc\x00`\x80\x021" + // 0x0022F 0x00304: 0x00231 + "\x00\x01d\x00`\x80\x022" + // 0x00059 0x00304: 0x00232 + "\x00\x01\xe4\x00`\x80\x023" + // 0x00079 0x00304: 0x00233 + "\x00\x02\xa0\x00` \x03\x85" + // 0x000A8 0x00301: 0x00385 + "\x00\x0eD\x00` \x03\x86" + // 0x00391 0x00301: 0x00386 + "\x00\x0eT\x00` \x03\x88" + // 0x00395 0x00301: 0x00388 + "\x00\x0e\\\x00` \x03\x89" + // 0x00397 0x00301: 0x00389 + "\x00\x0ed\x00` \x03\x8a" + // 0x00399 0x00301: 0x0038A + "\x00\x0e|\x00` \x03\x8c" + // 0x0039F 0x00301: 0x0038C + "\x00\x0e\x94\x00` \x03\x8e" + // 0x003A5 0x00301: 0x0038E + "\x00\x0e\xa4\x00` \x03\x8f" + // 0x003A9 0x00301: 0x0038F + "\x00\x0f(\x00` \x03\x90" + // 0x003CA 0x00301: 0x00390 + "\x00\x0ed\x00a\x00\x03\xaa" + // 0x00399 0x00308: 0x003AA + "\x00\x0e\x94\x00a\x00\x03\xab" + // 0x003A5 0x00308: 0x003AB + "\x00\x0e\xc4\x00` \x03\xac" + // 0x003B1 0x00301: 0x003AC + "\x00\x0e\xd4\x00` \x03\xad" + // 0x003B5 0x00301: 0x003AD + "\x00\x0e\xdc\x00` \x03\xae" + // 0x003B7 0x00301: 0x003AE + "\x00\x0e\xe4\x00` \x03\xaf" + // 0x003B9 0x00301: 0x003AF + "\x00\x0f,\x00` \x03\xb0" + // 0x003CB 0x00301: 0x003B0 + "\x00\x0e\xe4\x00a\x00\x03\xca" + // 0x003B9 0x00308: 0x003CA + "\x00\x0f\x14\x00a\x00\x03\xcb" + // 0x003C5 0x00308: 0x003CB + "\x00\x0e\xfc\x00` \x03\xcc" + // 0x003BF 0x00301: 0x003CC + "\x00\x0f\x14\x00` \x03\xcd" + // 0x003C5 0x00301: 0x003CD + "\x00\x0f$\x00` \x03\xce" + // 0x003C9 0x00301: 0x003CE + "\x00\x0fH\x00` \x03\xd3" + // 0x003D2 0x00301: 0x003D3 + "\x00\x0fH\x00a\x00\x03\xd4" + // 0x003D2 0x00308: 0x003D4 + "\x00\x10T\x00`\x00\x04\x00" + // 0x00415 0x00300: 0x00400 + "\x00\x10T\x00a\x00\x04\x01" + // 0x00415 0x00308: 0x00401 + "\x00\x10L\x00` \x04\x03" + // 0x00413 0x00301: 0x00403 + "\x00\x10\x18\x00a\x00\x04\a" + // 0x00406 0x00308: 0x00407 + "\x00\x10h\x00` \x04\f" + // 0x0041A 0x00301: 0x0040C + "\x00\x10`\x00`\x00\x04\r" + // 0x00418 0x00300: 0x0040D + "\x00\x10\x8c\x00`\xc0\x04\x0e" + // 0x00423 0x00306: 0x0040E + "\x00\x10`\x00`\xc0\x04\x19" + // 0x00418 0x00306: 0x00419 + "\x00\x10\xe0\x00`\xc0\x049" + // 0x00438 0x00306: 0x00439 + "\x00\x10\xd4\x00`\x00\x04P" + // 0x00435 0x00300: 0x00450 + "\x00\x10\xd4\x00a\x00\x04Q" + // 0x00435 0x00308: 0x00451 + "\x00\x10\xcc\x00` \x04S" + // 0x00433 0x00301: 0x00453 + "\x00\x11X\x00a\x00\x04W" + // 0x00456 0x00308: 0x00457 + "\x00\x10\xe8\x00` \x04\\" + // 0x0043A 0x00301: 0x0045C + "\x00\x10\xe0\x00`\x00\x04]" + // 0x00438 0x00300: 0x0045D + "\x00\x11\f\x00`\xc0\x04^" + // 0x00443 0x00306: 0x0045E + "\x00\x11\xd0\x00a\xe0\x04v" + // 0x00474 0x0030F: 0x00476 + "\x00\x11\xd4\x00a\xe0\x04w" + // 0x00475 0x0030F: 0x00477 + "\x00\x10X\x00`\xc0\x04\xc1" + // 0x00416 0x00306: 0x004C1 + "\x00\x10\xd8\x00`\xc0\x04\xc2" + // 0x00436 0x00306: 0x004C2 + "\x00\x10@\x00`\xc0\x04\xd0" + // 0x00410 0x00306: 0x004D0 + "\x00\x10\xc0\x00`\xc0\x04\xd1" + // 0x00430 0x00306: 0x004D1 + "\x00\x10@\x00a\x00\x04\xd2" + // 0x00410 0x00308: 0x004D2 + "\x00\x10\xc0\x00a\x00\x04\xd3" + // 0x00430 0x00308: 0x004D3 + "\x00\x10T\x00`\xc0\x04\xd6" + // 0x00415 0x00306: 0x004D6 + "\x00\x10\xd4\x00`\xc0\x04\xd7" + // 0x00435 0x00306: 0x004D7 + "\x00\x13`\x00a\x00\x04\xda" + // 0x004D8 0x00308: 0x004DA + "\x00\x13d\x00a\x00\x04\xdb" + // 0x004D9 0x00308: 0x004DB + "\x00\x10X\x00a\x00\x04\xdc" + // 0x00416 0x00308: 0x004DC + "\x00\x10\xd8\x00a\x00\x04\xdd" + // 0x00436 0x00308: 0x004DD + "\x00\x10\\\x00a\x00\x04\xde" + // 0x00417 0x00308: 0x004DE + "\x00\x10\xdc\x00a\x00\x04\xdf" + // 0x00437 0x00308: 0x004DF + "\x00\x10`\x00`\x80\x04\xe2" + // 0x00418 0x00304: 0x004E2 + "\x00\x10\xe0\x00`\x80\x04\xe3" + // 0x00438 0x00304: 0x004E3 + "\x00\x10`\x00a\x00\x04\xe4" + // 0x00418 0x00308: 0x004E4 + "\x00\x10\xe0\x00a\x00\x04\xe5" + // 0x00438 0x00308: 0x004E5 + "\x00\x10x\x00a\x00\x04\xe6" + // 0x0041E 0x00308: 0x004E6 + "\x00\x10\xf8\x00a\x00\x04\xe7" + // 0x0043E 0x00308: 0x004E7 + "\x00\x13\xa0\x00a\x00\x04\xea" + // 0x004E8 0x00308: 0x004EA + "\x00\x13\xa4\x00a\x00\x04\xeb" + // 0x004E9 0x00308: 0x004EB + "\x00\x10\xb4\x00a\x00\x04\xec" + // 0x0042D 0x00308: 0x004EC + "\x00\x114\x00a\x00\x04\xed" + // 0x0044D 0x00308: 0x004ED + "\x00\x10\x8c\x00`\x80\x04\xee" + // 0x00423 0x00304: 0x004EE + "\x00\x11\f\x00`\x80\x04\xef" + // 0x00443 0x00304: 0x004EF + "\x00\x10\x8c\x00a\x00\x04\xf0" + // 0x00423 0x00308: 0x004F0 + "\x00\x11\f\x00a\x00\x04\xf1" + // 0x00443 0x00308: 0x004F1 + "\x00\x10\x8c\x00a`\x04\xf2" + // 0x00423 0x0030B: 0x004F2 + "\x00\x11\f\x00a`\x04\xf3" + // 0x00443 0x0030B: 0x004F3 + "\x00\x10\x9c\x00a\x00\x04\xf4" + // 0x00427 0x00308: 0x004F4 + "\x00\x11\x1c\x00a\x00\x04\xf5" + // 0x00447 0x00308: 0x004F5 + "\x00\x10\xac\x00a\x00\x04\xf8" + // 0x0042B 0x00308: 0x004F8 + "\x00\x11,\x00a\x00\x04\xf9" + // 0x0044B 0x00308: 0x004F9 + "\x00\x18\x9c\x00\xca`\x06\"" + // 0x00627 0x00653: 0x00622 + "\x00\x18\x9c\x00ʀ\x06#" + // 0x00627 0x00654: 0x00623 + "\x00\x19 \x00ʀ\x06$" + // 0x00648 0x00654: 0x00624 + "\x00\x18\x9c\x00ʠ\x06%" + // 0x00627 0x00655: 0x00625 + "\x00\x19(\x00ʀ\x06&" + // 0x0064A 0x00654: 0x00626 + "\x00\x1bT\x00ʀ\x06\xc0" + // 0x006D5 0x00654: 0x006C0 + "\x00\x1b\x04\x00ʀ\x06\xc2" + // 0x006C1 0x00654: 0x006C2 + "\x00\x1bH\x00ʀ\x06\xd3" + // 0x006D2 0x00654: 0x006D3 + "\x00$\xa0\x01'\x80\t)" + // 0x00928 0x0093C: 0x00929 + "\x00$\xc0\x01'\x80\t1" + // 0x00930 0x0093C: 0x00931 + "\x00$\xcc\x01'\x80\t4" + // 0x00933 0x0093C: 0x00934 + "\x00'\x1c\x017\xc0\t\xcb" + // 0x009C7 0x009BE: 0x009CB + "\x00'\x1c\x01:\xe0\t\xcc" + // 0x009C7 0x009D7: 0x009CC + "\x00-\x1c\x01j\xc0\vH" + // 0x00B47 0x00B56: 0x00B48 + "\x00-\x1c\x01g\xc0\vK" + // 0x00B47 0x00B3E: 0x00B4B + "\x00-\x1c\x01j\xe0\vL" + // 0x00B47 0x00B57: 0x00B4C + "\x00.H\x01z\xe0\v\x94" + // 0x00B92 0x00BD7: 0x00B94 + "\x00/\x18\x01w\xc0\v\xca" + // 0x00BC6 0x00BBE: 0x00BCA + "\x00/\x1c\x01w\xc0\v\xcb" + // 0x00BC7 0x00BBE: 0x00BCB + "\x00/\x18\x01z\xe0\v\xcc" + // 0x00BC6 0x00BD7: 0x00BCC + "\x001\x18\x01\x8a\xc0\fH" + // 0x00C46 0x00C56: 0x00C48 + "\x002\xfc\x01\x9a\xa0\f\xc0" + // 0x00CBF 0x00CD5: 0x00CC0 + "\x003\x18\x01\x9a\xa0\f\xc7" + // 0x00CC6 0x00CD5: 0x00CC7 + "\x003\x18\x01\x9a\xc0\f\xc8" + // 0x00CC6 0x00CD6: 0x00CC8 + "\x003\x18\x01\x98@\f\xca" + // 0x00CC6 0x00CC2: 0x00CCA + "\x003(\x01\x9a\xa0\f\xcb" + // 0x00CCA 0x00CD5: 0x00CCB + "\x005\x18\x01\xa7\xc0\rJ" + // 0x00D46 0x00D3E: 0x00D4A + "\x005\x1c\x01\xa7\xc0\rK" + // 0x00D47 0x00D3E: 0x00D4B + "\x005\x18\x01\xaa\xe0\rL" + // 0x00D46 0x00D57: 0x00D4C + "\x007d\x01\xb9@\r\xda" + // 0x00DD9 0x00DCA: 0x00DDA + "\x007d\x01\xb9\xe0\r\xdc" + // 0x00DD9 0x00DCF: 0x00DDC + "\x007p\x01\xb9@\r\xdd" + // 0x00DDC 0x00DCA: 0x00DDD + "\x007d\x01\xbb\xe0\r\xde" + // 0x00DD9 0x00DDF: 0x00DDE + "\x00@\x94\x02\x05\xc0\x10&" + // 0x01025 0x0102E: 0x01026 + "\x00l\x14\x03f\xa0\x1b\x06" + // 0x01B05 0x01B35: 0x01B06 + "\x00l\x1c\x03f\xa0\x1b\b" + // 0x01B07 0x01B35: 0x01B08 + "\x00l$\x03f\xa0\x1b\n" + // 0x01B09 0x01B35: 0x01B0A + "\x00l,\x03f\xa0\x1b\f" + // 0x01B0B 0x01B35: 0x01B0C + "\x00l4\x03f\xa0\x1b\x0e" + // 0x01B0D 0x01B35: 0x01B0E + "\x00lD\x03f\xa0\x1b\x12" + // 0x01B11 0x01B35: 0x01B12 + "\x00l\xe8\x03f\xa0\x1b;" + // 0x01B3A 0x01B35: 0x01B3B + "\x00l\xf0\x03f\xa0\x1b=" + // 0x01B3C 0x01B35: 0x01B3D + "\x00l\xf8\x03f\xa0\x1b@" + // 0x01B3E 0x01B35: 0x01B40 + "\x00l\xfc\x03f\xa0\x1bA" + // 0x01B3F 0x01B35: 0x01B41 + "\x00m\b\x03f\xa0\x1bC" + // 0x01B42 0x01B35: 0x01B43 + "\x00\x01\x04\x00d\xa0\x1e\x00" + // 0x00041 0x00325: 0x01E00 + "\x00\x01\x84\x00d\xa0\x1e\x01" + // 0x00061 0x00325: 0x01E01 + "\x00\x01\b\x00`\xe0\x1e\x02" + // 0x00042 0x00307: 0x01E02 + "\x00\x01\x88\x00`\xe0\x1e\x03" + // 0x00062 0x00307: 0x01E03 + "\x00\x01\b\x00d`\x1e\x04" + // 0x00042 0x00323: 0x01E04 + "\x00\x01\x88\x00d`\x1e\x05" + // 0x00062 0x00323: 0x01E05 + "\x00\x01\b\x00f \x1e\x06" + // 0x00042 0x00331: 0x01E06 + "\x00\x01\x88\x00f \x1e\a" + // 0x00062 0x00331: 0x01E07 + "\x00\x03\x1c\x00` \x1e\b" + // 0x000C7 0x00301: 0x01E08 + "\x00\x03\x9c\x00` \x1e\t" + // 0x000E7 0x00301: 0x01E09 + "\x00\x01\x10\x00`\xe0\x1e\n" + // 0x00044 0x00307: 0x01E0A + "\x00\x01\x90\x00`\xe0\x1e\v" + // 0x00064 0x00307: 0x01E0B + "\x00\x01\x10\x00d`\x1e\f" + // 0x00044 0x00323: 0x01E0C + "\x00\x01\x90\x00d`\x1e\r" + // 0x00064 0x00323: 0x01E0D + "\x00\x01\x10\x00f \x1e\x0e" + // 0x00044 0x00331: 0x01E0E + "\x00\x01\x90\x00f \x1e\x0f" + // 0x00064 0x00331: 0x01E0F + "\x00\x01\x10\x00d\xe0\x1e\x10" + // 0x00044 0x00327: 0x01E10 + "\x00\x01\x90\x00d\xe0\x1e\x11" + // 0x00064 0x00327: 0x01E11 + "\x00\x01\x10\x00e\xa0\x1e\x12" + // 0x00044 0x0032D: 0x01E12 + "\x00\x01\x90\x00e\xa0\x1e\x13" + // 0x00064 0x0032D: 0x01E13 + "\x00\x04H\x00`\x00\x1e\x14" + // 0x00112 0x00300: 0x01E14 + "\x00\x04L\x00`\x00\x1e\x15" + // 0x00113 0x00300: 0x01E15 + "\x00\x04H\x00` \x1e\x16" + // 0x00112 0x00301: 0x01E16 + "\x00\x04L\x00` \x1e\x17" + // 0x00113 0x00301: 0x01E17 + "\x00\x01\x14\x00e\xa0\x1e\x18" + // 0x00045 0x0032D: 0x01E18 + "\x00\x01\x94\x00e\xa0\x1e\x19" + // 0x00065 0x0032D: 0x01E19 + "\x00\x01\x14\x00f\x00\x1e\x1a" + // 0x00045 0x00330: 0x01E1A + "\x00\x01\x94\x00f\x00\x1e\x1b" + // 0x00065 0x00330: 0x01E1B + "\x00\b\xa0\x00`\xc0\x1e\x1c" + // 0x00228 0x00306: 0x01E1C + "\x00\b\xa4\x00`\xc0\x1e\x1d" + // 0x00229 0x00306: 0x01E1D + "\x00\x01\x18\x00`\xe0\x1e\x1e" + // 0x00046 0x00307: 0x01E1E + "\x00\x01\x98\x00`\xe0\x1e\x1f" + // 0x00066 0x00307: 0x01E1F + "\x00\x01\x1c\x00`\x80\x1e " + // 0x00047 0x00304: 0x01E20 + "\x00\x01\x9c\x00`\x80\x1e!" + // 0x00067 0x00304: 0x01E21 + "\x00\x01 \x00`\xe0\x1e\"" + // 0x00048 0x00307: 0x01E22 + "\x00\x01\xa0\x00`\xe0\x1e#" + // 0x00068 0x00307: 0x01E23 + "\x00\x01 \x00d`\x1e$" + // 0x00048 0x00323: 0x01E24 + "\x00\x01\xa0\x00d`\x1e%" + // 0x00068 0x00323: 0x01E25 + "\x00\x01 \x00a\x00\x1e&" + // 0x00048 0x00308: 0x01E26 + "\x00\x01\xa0\x00a\x00\x1e'" + // 0x00068 0x00308: 0x01E27 + "\x00\x01 \x00d\xe0\x1e(" + // 0x00048 0x00327: 0x01E28 + "\x00\x01\xa0\x00d\xe0\x1e)" + // 0x00068 0x00327: 0x01E29 + "\x00\x01 \x00e\xc0\x1e*" + // 0x00048 0x0032E: 0x01E2A + "\x00\x01\xa0\x00e\xc0\x1e+" + // 0x00068 0x0032E: 0x01E2B + "\x00\x01$\x00f\x00\x1e," + // 0x00049 0x00330: 0x01E2C + "\x00\x01\xa4\x00f\x00\x1e-" + // 0x00069 0x00330: 0x01E2D + "\x00\x03<\x00` \x1e." + // 0x000CF 0x00301: 0x01E2E + "\x00\x03\xbc\x00` \x1e/" + // 0x000EF 0x00301: 0x01E2F + "\x00\x01,\x00` \x1e0" + // 0x0004B 0x00301: 0x01E30 + "\x00\x01\xac\x00` \x1e1" + // 0x0006B 0x00301: 0x01E31 + "\x00\x01,\x00d`\x1e2" + // 0x0004B 0x00323: 0x01E32 + "\x00\x01\xac\x00d`\x1e3" + // 0x0006B 0x00323: 0x01E33 + "\x00\x01,\x00f \x1e4" + // 0x0004B 0x00331: 0x01E34 + "\x00\x01\xac\x00f \x1e5" + // 0x0006B 0x00331: 0x01E35 + "\x00\x010\x00d`\x1e6" + // 0x0004C 0x00323: 0x01E36 + "\x00\x01\xb0\x00d`\x1e7" + // 0x0006C 0x00323: 0x01E37 + "\x00x\xd8\x00`\x80\x1e8" + // 0x01E36 0x00304: 0x01E38 + "\x00x\xdc\x00`\x80\x1e9" + // 0x01E37 0x00304: 0x01E39 + "\x00\x010\x00f \x1e:" + // 0x0004C 0x00331: 0x01E3A + "\x00\x01\xb0\x00f \x1e;" + // 0x0006C 0x00331: 0x01E3B + "\x00\x010\x00e\xa0\x1e<" + // 0x0004C 0x0032D: 0x01E3C + "\x00\x01\xb0\x00e\xa0\x1e=" + // 0x0006C 0x0032D: 0x01E3D + "\x00\x014\x00` \x1e>" + // 0x0004D 0x00301: 0x01E3E + "\x00\x01\xb4\x00` \x1e?" + // 0x0006D 0x00301: 0x01E3F + "\x00\x014\x00`\xe0\x1e@" + // 0x0004D 0x00307: 0x01E40 + "\x00\x01\xb4\x00`\xe0\x1eA" + // 0x0006D 0x00307: 0x01E41 + "\x00\x014\x00d`\x1eB" + // 0x0004D 0x00323: 0x01E42 + "\x00\x01\xb4\x00d`\x1eC" + // 0x0006D 0x00323: 0x01E43 + "\x00\x018\x00`\xe0\x1eD" + // 0x0004E 0x00307: 0x01E44 + "\x00\x01\xb8\x00`\xe0\x1eE" + // 0x0006E 0x00307: 0x01E45 + "\x00\x018\x00d`\x1eF" + // 0x0004E 0x00323: 0x01E46 + "\x00\x01\xb8\x00d`\x1eG" + // 0x0006E 0x00323: 0x01E47 + "\x00\x018\x00f \x1eH" + // 0x0004E 0x00331: 0x01E48 + "\x00\x01\xb8\x00f \x1eI" + // 0x0006E 0x00331: 0x01E49 + "\x00\x018\x00e\xa0\x1eJ" + // 0x0004E 0x0032D: 0x01E4A + "\x00\x01\xb8\x00e\xa0\x1eK" + // 0x0006E 0x0032D: 0x01E4B + "\x00\x03T\x00` \x1eL" + // 0x000D5 0x00301: 0x01E4C + "\x00\x03\xd4\x00` \x1eM" + // 0x000F5 0x00301: 0x01E4D + "\x00\x03T\x00a\x00\x1eN" + // 0x000D5 0x00308: 0x01E4E + "\x00\x03\xd4\x00a\x00\x1eO" + // 0x000F5 0x00308: 0x01E4F + "\x00\x050\x00`\x00\x1eP" + // 0x0014C 0x00300: 0x01E50 + "\x00\x054\x00`\x00\x1eQ" + // 0x0014D 0x00300: 0x01E51 + "\x00\x050\x00` \x1eR" + // 0x0014C 0x00301: 0x01E52 + "\x00\x054\x00` \x1eS" + // 0x0014D 0x00301: 0x01E53 + "\x00\x01@\x00` \x1eT" + // 0x00050 0x00301: 0x01E54 + "\x00\x01\xc0\x00` \x1eU" + // 0x00070 0x00301: 0x01E55 + "\x00\x01@\x00`\xe0\x1eV" + // 0x00050 0x00307: 0x01E56 + "\x00\x01\xc0\x00`\xe0\x1eW" + // 0x00070 0x00307: 0x01E57 + "\x00\x01H\x00`\xe0\x1eX" + // 0x00052 0x00307: 0x01E58 + "\x00\x01\xc8\x00`\xe0\x1eY" + // 0x00072 0x00307: 0x01E59 + "\x00\x01H\x00d`\x1eZ" + // 0x00052 0x00323: 0x01E5A + "\x00\x01\xc8\x00d`\x1e[" + // 0x00072 0x00323: 0x01E5B + "\x00yh\x00`\x80\x1e\\" + // 0x01E5A 0x00304: 0x01E5C + "\x00yl\x00`\x80\x1e]" + // 0x01E5B 0x00304: 0x01E5D + "\x00\x01H\x00f \x1e^" + // 0x00052 0x00331: 0x01E5E + "\x00\x01\xc8\x00f \x1e_" + // 0x00072 0x00331: 0x01E5F + "\x00\x01L\x00`\xe0\x1e`" + // 0x00053 0x00307: 0x01E60 + "\x00\x01\xcc\x00`\xe0\x1ea" + // 0x00073 0x00307: 0x01E61 + "\x00\x01L\x00d`\x1eb" + // 0x00053 0x00323: 0x01E62 + "\x00\x01\xcc\x00d`\x1ec" + // 0x00073 0x00323: 0x01E63 + "\x00\x05h\x00`\xe0\x1ed" + // 0x0015A 0x00307: 0x01E64 + "\x00\x05l\x00`\xe0\x1ee" + // 0x0015B 0x00307: 0x01E65 + "\x00\x05\x80\x00`\xe0\x1ef" + // 0x00160 0x00307: 0x01E66 + "\x00\x05\x84\x00`\xe0\x1eg" + // 0x00161 0x00307: 0x01E67 + "\x00y\x88\x00`\xe0\x1eh" + // 0x01E62 0x00307: 0x01E68 + "\x00y\x8c\x00`\xe0\x1ei" + // 0x01E63 0x00307: 0x01E69 + "\x00\x01P\x00`\xe0\x1ej" + // 0x00054 0x00307: 0x01E6A + "\x00\x01\xd0\x00`\xe0\x1ek" + // 0x00074 0x00307: 0x01E6B + "\x00\x01P\x00d`\x1el" + // 0x00054 0x00323: 0x01E6C + "\x00\x01\xd0\x00d`\x1em" + // 0x00074 0x00323: 0x01E6D + "\x00\x01P\x00f \x1en" + // 0x00054 0x00331: 0x01E6E + "\x00\x01\xd0\x00f \x1eo" + // 0x00074 0x00331: 0x01E6F + "\x00\x01P\x00e\xa0\x1ep" + // 0x00054 0x0032D: 0x01E70 + "\x00\x01\xd0\x00e\xa0\x1eq" + // 0x00074 0x0032D: 0x01E71 + "\x00\x01T\x00d\x80\x1er" + // 0x00055 0x00324: 0x01E72 + "\x00\x01\xd4\x00d\x80\x1es" + // 0x00075 0x00324: 0x01E73 + "\x00\x01T\x00f\x00\x1et" + // 0x00055 0x00330: 0x01E74 + "\x00\x01\xd4\x00f\x00\x1eu" + // 0x00075 0x00330: 0x01E75 + "\x00\x01T\x00e\xa0\x1ev" + // 0x00055 0x0032D: 0x01E76 + "\x00\x01\xd4\x00e\xa0\x1ew" + // 0x00075 0x0032D: 0x01E77 + "\x00\x05\xa0\x00` \x1ex" + // 0x00168 0x00301: 0x01E78 + "\x00\x05\xa4\x00` \x1ey" + // 0x00169 0x00301: 0x01E79 + "\x00\x05\xa8\x00a\x00\x1ez" + // 0x0016A 0x00308: 0x01E7A + "\x00\x05\xac\x00a\x00\x1e{" + // 0x0016B 0x00308: 0x01E7B + "\x00\x01X\x00``\x1e|" + // 0x00056 0x00303: 0x01E7C + "\x00\x01\xd8\x00``\x1e}" + // 0x00076 0x00303: 0x01E7D + "\x00\x01X\x00d`\x1e~" + // 0x00056 0x00323: 0x01E7E + "\x00\x01\xd8\x00d`\x1e\x7f" + // 0x00076 0x00323: 0x01E7F + "\x00\x01\\\x00`\x00\x1e\x80" + // 0x00057 0x00300: 0x01E80 + "\x00\x01\xdc\x00`\x00\x1e\x81" + // 0x00077 0x00300: 0x01E81 + "\x00\x01\\\x00` \x1e\x82" + // 0x00057 0x00301: 0x01E82 + "\x00\x01\xdc\x00` \x1e\x83" + // 0x00077 0x00301: 0x01E83 + "\x00\x01\\\x00a\x00\x1e\x84" + // 0x00057 0x00308: 0x01E84 + "\x00\x01\xdc\x00a\x00\x1e\x85" + // 0x00077 0x00308: 0x01E85 + "\x00\x01\\\x00`\xe0\x1e\x86" + // 0x00057 0x00307: 0x01E86 + "\x00\x01\xdc\x00`\xe0\x1e\x87" + // 0x00077 0x00307: 0x01E87 + "\x00\x01\\\x00d`\x1e\x88" + // 0x00057 0x00323: 0x01E88 + "\x00\x01\xdc\x00d`\x1e\x89" + // 0x00077 0x00323: 0x01E89 + "\x00\x01`\x00`\xe0\x1e\x8a" + // 0x00058 0x00307: 0x01E8A + "\x00\x01\xe0\x00`\xe0\x1e\x8b" + // 0x00078 0x00307: 0x01E8B + "\x00\x01`\x00a\x00\x1e\x8c" + // 0x00058 0x00308: 0x01E8C + "\x00\x01\xe0\x00a\x00\x1e\x8d" + // 0x00078 0x00308: 0x01E8D + "\x00\x01d\x00`\xe0\x1e\x8e" + // 0x00059 0x00307: 0x01E8E + "\x00\x01\xe4\x00`\xe0\x1e\x8f" + // 0x00079 0x00307: 0x01E8F + "\x00\x01h\x00`@\x1e\x90" + // 0x0005A 0x00302: 0x01E90 + "\x00\x01\xe8\x00`@\x1e\x91" + // 0x0007A 0x00302: 0x01E91 + "\x00\x01h\x00d`\x1e\x92" + // 0x0005A 0x00323: 0x01E92 + "\x00\x01\xe8\x00d`\x1e\x93" + // 0x0007A 0x00323: 0x01E93 + "\x00\x01h\x00f \x1e\x94" + // 0x0005A 0x00331: 0x01E94 + "\x00\x01\xe8\x00f \x1e\x95" + // 0x0007A 0x00331: 0x01E95 + "\x00\x01\xa0\x00f \x1e\x96" + // 0x00068 0x00331: 0x01E96 + "\x00\x01\xd0\x00a\x00\x1e\x97" + // 0x00074 0x00308: 0x01E97 + "\x00\x01\xdc\x00a@\x1e\x98" + // 0x00077 0x0030A: 0x01E98 + "\x00\x01\xe4\x00a@\x1e\x99" + // 0x00079 0x0030A: 0x01E99 + "\x00\x05\xfc\x00`\xe0\x1e\x9b" + // 0x0017F 0x00307: 0x01E9B + "\x00\x01\x04\x00d`\x1e\xa0" + // 0x00041 0x00323: 0x01EA0 + "\x00\x01\x84\x00d`\x1e\xa1" + // 0x00061 0x00323: 0x01EA1 + "\x00\x01\x04\x00a \x1e\xa2" + // 0x00041 0x00309: 0x01EA2 + "\x00\x01\x84\x00a \x1e\xa3" + // 0x00061 0x00309: 0x01EA3 + "\x00\x03\b\x00` \x1e\xa4" + // 0x000C2 0x00301: 0x01EA4 + "\x00\x03\x88\x00` \x1e\xa5" + // 0x000E2 0x00301: 0x01EA5 + "\x00\x03\b\x00`\x00\x1e\xa6" + // 0x000C2 0x00300: 0x01EA6 + "\x00\x03\x88\x00`\x00\x1e\xa7" + // 0x000E2 0x00300: 0x01EA7 + "\x00\x03\b\x00a \x1e\xa8" + // 0x000C2 0x00309: 0x01EA8 + "\x00\x03\x88\x00a \x1e\xa9" + // 0x000E2 0x00309: 0x01EA9 + "\x00\x03\b\x00``\x1e\xaa" + // 0x000C2 0x00303: 0x01EAA + "\x00\x03\x88\x00``\x1e\xab" + // 0x000E2 0x00303: 0x01EAB + "\x00z\x80\x00`@\x1e\xac" + // 0x01EA0 0x00302: 0x01EAC + "\x00z\x84\x00`@\x1e\xad" + // 0x01EA1 0x00302: 0x01EAD + "\x00\x04\b\x00` \x1e\xae" + // 0x00102 0x00301: 0x01EAE + "\x00\x04\f\x00` \x1e\xaf" + // 0x00103 0x00301: 0x01EAF + "\x00\x04\b\x00`\x00\x1e\xb0" + // 0x00102 0x00300: 0x01EB0 + "\x00\x04\f\x00`\x00\x1e\xb1" + // 0x00103 0x00300: 0x01EB1 + "\x00\x04\b\x00a \x1e\xb2" + // 0x00102 0x00309: 0x01EB2 + "\x00\x04\f\x00a \x1e\xb3" + // 0x00103 0x00309: 0x01EB3 + "\x00\x04\b\x00``\x1e\xb4" + // 0x00102 0x00303: 0x01EB4 + "\x00\x04\f\x00``\x1e\xb5" + // 0x00103 0x00303: 0x01EB5 + "\x00z\x80\x00`\xc0\x1e\xb6" + // 0x01EA0 0x00306: 0x01EB6 + "\x00z\x84\x00`\xc0\x1e\xb7" + // 0x01EA1 0x00306: 0x01EB7 + "\x00\x01\x14\x00d`\x1e\xb8" + // 0x00045 0x00323: 0x01EB8 + "\x00\x01\x94\x00d`\x1e\xb9" + // 0x00065 0x00323: 0x01EB9 + "\x00\x01\x14\x00a \x1e\xba" + // 0x00045 0x00309: 0x01EBA + "\x00\x01\x94\x00a \x1e\xbb" + // 0x00065 0x00309: 0x01EBB + "\x00\x01\x14\x00``\x1e\xbc" + // 0x00045 0x00303: 0x01EBC + "\x00\x01\x94\x00``\x1e\xbd" + // 0x00065 0x00303: 0x01EBD + "\x00\x03(\x00` \x1e\xbe" + // 0x000CA 0x00301: 0x01EBE + "\x00\x03\xa8\x00` \x1e\xbf" + // 0x000EA 0x00301: 0x01EBF + "\x00\x03(\x00`\x00\x1e\xc0" + // 0x000CA 0x00300: 0x01EC0 + "\x00\x03\xa8\x00`\x00\x1e\xc1" + // 0x000EA 0x00300: 0x01EC1 + "\x00\x03(\x00a \x1e\xc2" + // 0x000CA 0x00309: 0x01EC2 + "\x00\x03\xa8\x00a \x1e\xc3" + // 0x000EA 0x00309: 0x01EC3 + "\x00\x03(\x00``\x1e\xc4" + // 0x000CA 0x00303: 0x01EC4 + "\x00\x03\xa8\x00``\x1e\xc5" + // 0x000EA 0x00303: 0x01EC5 + "\x00z\xe0\x00`@\x1e\xc6" + // 0x01EB8 0x00302: 0x01EC6 + "\x00z\xe4\x00`@\x1e\xc7" + // 0x01EB9 0x00302: 0x01EC7 + "\x00\x01$\x00a \x1e\xc8" + // 0x00049 0x00309: 0x01EC8 + "\x00\x01\xa4\x00a \x1e\xc9" + // 0x00069 0x00309: 0x01EC9 + "\x00\x01$\x00d`\x1e\xca" + // 0x00049 0x00323: 0x01ECA + "\x00\x01\xa4\x00d`\x1e\xcb" + // 0x00069 0x00323: 0x01ECB + "\x00\x01<\x00d`\x1e\xcc" + // 0x0004F 0x00323: 0x01ECC + "\x00\x01\xbc\x00d`\x1e\xcd" + // 0x0006F 0x00323: 0x01ECD + "\x00\x01<\x00a \x1e\xce" + // 0x0004F 0x00309: 0x01ECE + "\x00\x01\xbc\x00a \x1e\xcf" + // 0x0006F 0x00309: 0x01ECF + "\x00\x03P\x00` \x1e\xd0" + // 0x000D4 0x00301: 0x01ED0 + "\x00\x03\xd0\x00` \x1e\xd1" + // 0x000F4 0x00301: 0x01ED1 + "\x00\x03P\x00`\x00\x1e\xd2" + // 0x000D4 0x00300: 0x01ED2 + "\x00\x03\xd0\x00`\x00\x1e\xd3" + // 0x000F4 0x00300: 0x01ED3 + "\x00\x03P\x00a \x1e\xd4" + // 0x000D4 0x00309: 0x01ED4 + "\x00\x03\xd0\x00a \x1e\xd5" + // 0x000F4 0x00309: 0x01ED5 + "\x00\x03P\x00``\x1e\xd6" + // 0x000D4 0x00303: 0x01ED6 + "\x00\x03\xd0\x00``\x1e\xd7" + // 0x000F4 0x00303: 0x01ED7 + "\x00{0\x00`@\x1e\xd8" + // 0x01ECC 0x00302: 0x01ED8 + "\x00{4\x00`@\x1e\xd9" + // 0x01ECD 0x00302: 0x01ED9 + "\x00\x06\x80\x00` \x1e\xda" + // 0x001A0 0x00301: 0x01EDA + "\x00\x06\x84\x00` \x1e\xdb" + // 0x001A1 0x00301: 0x01EDB + "\x00\x06\x80\x00`\x00\x1e\xdc" + // 0x001A0 0x00300: 0x01EDC + "\x00\x06\x84\x00`\x00\x1e\xdd" + // 0x001A1 0x00300: 0x01EDD + "\x00\x06\x80\x00a \x1e\xde" + // 0x001A0 0x00309: 0x01EDE + "\x00\x06\x84\x00a \x1e\xdf" + // 0x001A1 0x00309: 0x01EDF + "\x00\x06\x80\x00``\x1e\xe0" + // 0x001A0 0x00303: 0x01EE0 + "\x00\x06\x84\x00``\x1e\xe1" + // 0x001A1 0x00303: 0x01EE1 + "\x00\x06\x80\x00d`\x1e\xe2" + // 0x001A0 0x00323: 0x01EE2 + "\x00\x06\x84\x00d`\x1e\xe3" + // 0x001A1 0x00323: 0x01EE3 + "\x00\x01T\x00d`\x1e\xe4" + // 0x00055 0x00323: 0x01EE4 + "\x00\x01\xd4\x00d`\x1e\xe5" + // 0x00075 0x00323: 0x01EE5 + "\x00\x01T\x00a \x1e\xe6" + // 0x00055 0x00309: 0x01EE6 + "\x00\x01\xd4\x00a \x1e\xe7" + // 0x00075 0x00309: 0x01EE7 + "\x00\x06\xbc\x00` \x1e\xe8" + // 0x001AF 0x00301: 0x01EE8 + "\x00\x06\xc0\x00` \x1e\xe9" + // 0x001B0 0x00301: 0x01EE9 + "\x00\x06\xbc\x00`\x00\x1e\xea" + // 0x001AF 0x00300: 0x01EEA + "\x00\x06\xc0\x00`\x00\x1e\xeb" + // 0x001B0 0x00300: 0x01EEB + "\x00\x06\xbc\x00a \x1e\xec" + // 0x001AF 0x00309: 0x01EEC + "\x00\x06\xc0\x00a \x1e\xed" + // 0x001B0 0x00309: 0x01EED + "\x00\x06\xbc\x00``\x1e\xee" + // 0x001AF 0x00303: 0x01EEE + "\x00\x06\xc0\x00``\x1e\xef" + // 0x001B0 0x00303: 0x01EEF + "\x00\x06\xbc\x00d`\x1e\xf0" + // 0x001AF 0x00323: 0x01EF0 + "\x00\x06\xc0\x00d`\x1e\xf1" + // 0x001B0 0x00323: 0x01EF1 + "\x00\x01d\x00`\x00\x1e\xf2" + // 0x00059 0x00300: 0x01EF2 + "\x00\x01\xe4\x00`\x00\x1e\xf3" + // 0x00079 0x00300: 0x01EF3 + "\x00\x01d\x00d`\x1e\xf4" + // 0x00059 0x00323: 0x01EF4 + "\x00\x01\xe4\x00d`\x1e\xf5" + // 0x00079 0x00323: 0x01EF5 + "\x00\x01d\x00a \x1e\xf6" + // 0x00059 0x00309: 0x01EF6 + "\x00\x01\xe4\x00a \x1e\xf7" + // 0x00079 0x00309: 0x01EF7 + "\x00\x01d\x00``\x1e\xf8" + // 0x00059 0x00303: 0x01EF8 + "\x00\x01\xe4\x00``\x1e\xf9" + // 0x00079 0x00303: 0x01EF9 + "\x00\x0e\xc4\x00b`\x1f\x00" + // 0x003B1 0x00313: 0x01F00 + "\x00\x0e\xc4\x00b\x80\x1f\x01" + // 0x003B1 0x00314: 0x01F01 + "\x00|\x00\x00`\x00\x1f\x02" + // 0x01F00 0x00300: 0x01F02 + "\x00|\x04\x00`\x00\x1f\x03" + // 0x01F01 0x00300: 0x01F03 + "\x00|\x00\x00` \x1f\x04" + // 0x01F00 0x00301: 0x01F04 + "\x00|\x04\x00` \x1f\x05" + // 0x01F01 0x00301: 0x01F05 + "\x00|\x00\x00h@\x1f\x06" + // 0x01F00 0x00342: 0x01F06 + "\x00|\x04\x00h@\x1f\a" + // 0x01F01 0x00342: 0x01F07 + "\x00\x0eD\x00b`\x1f\b" + // 0x00391 0x00313: 0x01F08 + "\x00\x0eD\x00b\x80\x1f\t" + // 0x00391 0x00314: 0x01F09 + "\x00| \x00`\x00\x1f\n" + // 0x01F08 0x00300: 0x01F0A + "\x00|$\x00`\x00\x1f\v" + // 0x01F09 0x00300: 0x01F0B + "\x00| \x00` \x1f\f" + // 0x01F08 0x00301: 0x01F0C + "\x00|$\x00` \x1f\r" + // 0x01F09 0x00301: 0x01F0D + "\x00| \x00h@\x1f\x0e" + // 0x01F08 0x00342: 0x01F0E + "\x00|$\x00h@\x1f\x0f" + // 0x01F09 0x00342: 0x01F0F + "\x00\x0e\xd4\x00b`\x1f\x10" + // 0x003B5 0x00313: 0x01F10 + "\x00\x0e\xd4\x00b\x80\x1f\x11" + // 0x003B5 0x00314: 0x01F11 + "\x00|@\x00`\x00\x1f\x12" + // 0x01F10 0x00300: 0x01F12 + "\x00|D\x00`\x00\x1f\x13" + // 0x01F11 0x00300: 0x01F13 + "\x00|@\x00` \x1f\x14" + // 0x01F10 0x00301: 0x01F14 + "\x00|D\x00` \x1f\x15" + // 0x01F11 0x00301: 0x01F15 + "\x00\x0eT\x00b`\x1f\x18" + // 0x00395 0x00313: 0x01F18 + "\x00\x0eT\x00b\x80\x1f\x19" + // 0x00395 0x00314: 0x01F19 + "\x00|`\x00`\x00\x1f\x1a" + // 0x01F18 0x00300: 0x01F1A + "\x00|d\x00`\x00\x1f\x1b" + // 0x01F19 0x00300: 0x01F1B + "\x00|`\x00` \x1f\x1c" + // 0x01F18 0x00301: 0x01F1C + "\x00|d\x00` \x1f\x1d" + // 0x01F19 0x00301: 0x01F1D + "\x00\x0e\xdc\x00b`\x1f " + // 0x003B7 0x00313: 0x01F20 + "\x00\x0e\xdc\x00b\x80\x1f!" + // 0x003B7 0x00314: 0x01F21 + "\x00|\x80\x00`\x00\x1f\"" + // 0x01F20 0x00300: 0x01F22 + "\x00|\x84\x00`\x00\x1f#" + // 0x01F21 0x00300: 0x01F23 + "\x00|\x80\x00` \x1f$" + // 0x01F20 0x00301: 0x01F24 + "\x00|\x84\x00` \x1f%" + // 0x01F21 0x00301: 0x01F25 + "\x00|\x80\x00h@\x1f&" + // 0x01F20 0x00342: 0x01F26 + "\x00|\x84\x00h@\x1f'" + // 0x01F21 0x00342: 0x01F27 + "\x00\x0e\\\x00b`\x1f(" + // 0x00397 0x00313: 0x01F28 + "\x00\x0e\\\x00b\x80\x1f)" + // 0x00397 0x00314: 0x01F29 + "\x00|\xa0\x00`\x00\x1f*" + // 0x01F28 0x00300: 0x01F2A + "\x00|\xa4\x00`\x00\x1f+" + // 0x01F29 0x00300: 0x01F2B + "\x00|\xa0\x00` \x1f," + // 0x01F28 0x00301: 0x01F2C + "\x00|\xa4\x00` \x1f-" + // 0x01F29 0x00301: 0x01F2D + "\x00|\xa0\x00h@\x1f." + // 0x01F28 0x00342: 0x01F2E + "\x00|\xa4\x00h@\x1f/" + // 0x01F29 0x00342: 0x01F2F + "\x00\x0e\xe4\x00b`\x1f0" + // 0x003B9 0x00313: 0x01F30 + "\x00\x0e\xe4\x00b\x80\x1f1" + // 0x003B9 0x00314: 0x01F31 + "\x00|\xc0\x00`\x00\x1f2" + // 0x01F30 0x00300: 0x01F32 + "\x00|\xc4\x00`\x00\x1f3" + // 0x01F31 0x00300: 0x01F33 + "\x00|\xc0\x00` \x1f4" + // 0x01F30 0x00301: 0x01F34 + "\x00|\xc4\x00` \x1f5" + // 0x01F31 0x00301: 0x01F35 + "\x00|\xc0\x00h@\x1f6" + // 0x01F30 0x00342: 0x01F36 + "\x00|\xc4\x00h@\x1f7" + // 0x01F31 0x00342: 0x01F37 + "\x00\x0ed\x00b`\x1f8" + // 0x00399 0x00313: 0x01F38 + "\x00\x0ed\x00b\x80\x1f9" + // 0x00399 0x00314: 0x01F39 + "\x00|\xe0\x00`\x00\x1f:" + // 0x01F38 0x00300: 0x01F3A + "\x00|\xe4\x00`\x00\x1f;" + // 0x01F39 0x00300: 0x01F3B + "\x00|\xe0\x00` \x1f<" + // 0x01F38 0x00301: 0x01F3C + "\x00|\xe4\x00` \x1f=" + // 0x01F39 0x00301: 0x01F3D + "\x00|\xe0\x00h@\x1f>" + // 0x01F38 0x00342: 0x01F3E + "\x00|\xe4\x00h@\x1f?" + // 0x01F39 0x00342: 0x01F3F + "\x00\x0e\xfc\x00b`\x1f@" + // 0x003BF 0x00313: 0x01F40 + "\x00\x0e\xfc\x00b\x80\x1fA" + // 0x003BF 0x00314: 0x01F41 + "\x00}\x00\x00`\x00\x1fB" + // 0x01F40 0x00300: 0x01F42 + "\x00}\x04\x00`\x00\x1fC" + // 0x01F41 0x00300: 0x01F43 + "\x00}\x00\x00` \x1fD" + // 0x01F40 0x00301: 0x01F44 + "\x00}\x04\x00` \x1fE" + // 0x01F41 0x00301: 0x01F45 + "\x00\x0e|\x00b`\x1fH" + // 0x0039F 0x00313: 0x01F48 + "\x00\x0e|\x00b\x80\x1fI" + // 0x0039F 0x00314: 0x01F49 + "\x00} \x00`\x00\x1fJ" + // 0x01F48 0x00300: 0x01F4A + "\x00}$\x00`\x00\x1fK" + // 0x01F49 0x00300: 0x01F4B + "\x00} \x00` \x1fL" + // 0x01F48 0x00301: 0x01F4C + "\x00}$\x00` \x1fM" + // 0x01F49 0x00301: 0x01F4D + "\x00\x0f\x14\x00b`\x1fP" + // 0x003C5 0x00313: 0x01F50 + "\x00\x0f\x14\x00b\x80\x1fQ" + // 0x003C5 0x00314: 0x01F51 + "\x00}@\x00`\x00\x1fR" + // 0x01F50 0x00300: 0x01F52 + "\x00}D\x00`\x00\x1fS" + // 0x01F51 0x00300: 0x01F53 + "\x00}@\x00` \x1fT" + // 0x01F50 0x00301: 0x01F54 + "\x00}D\x00` \x1fU" + // 0x01F51 0x00301: 0x01F55 + "\x00}@\x00h@\x1fV" + // 0x01F50 0x00342: 0x01F56 + "\x00}D\x00h@\x1fW" + // 0x01F51 0x00342: 0x01F57 + "\x00\x0e\x94\x00b\x80\x1fY" + // 0x003A5 0x00314: 0x01F59 + "\x00}d\x00`\x00\x1f[" + // 0x01F59 0x00300: 0x01F5B + "\x00}d\x00` \x1f]" + // 0x01F59 0x00301: 0x01F5D + "\x00}d\x00h@\x1f_" + // 0x01F59 0x00342: 0x01F5F + "\x00\x0f$\x00b`\x1f`" + // 0x003C9 0x00313: 0x01F60 + "\x00\x0f$\x00b\x80\x1fa" + // 0x003C9 0x00314: 0x01F61 + "\x00}\x80\x00`\x00\x1fb" + // 0x01F60 0x00300: 0x01F62 + "\x00}\x84\x00`\x00\x1fc" + // 0x01F61 0x00300: 0x01F63 + "\x00}\x80\x00` \x1fd" + // 0x01F60 0x00301: 0x01F64 + "\x00}\x84\x00` \x1fe" + // 0x01F61 0x00301: 0x01F65 + "\x00}\x80\x00h@\x1ff" + // 0x01F60 0x00342: 0x01F66 + "\x00}\x84\x00h@\x1fg" + // 0x01F61 0x00342: 0x01F67 + "\x00\x0e\xa4\x00b`\x1fh" + // 0x003A9 0x00313: 0x01F68 + "\x00\x0e\xa4\x00b\x80\x1fi" + // 0x003A9 0x00314: 0x01F69 + "\x00}\xa0\x00`\x00\x1fj" + // 0x01F68 0x00300: 0x01F6A + "\x00}\xa4\x00`\x00\x1fk" + // 0x01F69 0x00300: 0x01F6B + "\x00}\xa0\x00` \x1fl" + // 0x01F68 0x00301: 0x01F6C + "\x00}\xa4\x00` \x1fm" + // 0x01F69 0x00301: 0x01F6D + "\x00}\xa0\x00h@\x1fn" + // 0x01F68 0x00342: 0x01F6E + "\x00}\xa4\x00h@\x1fo" + // 0x01F69 0x00342: 0x01F6F + "\x00\x0e\xc4\x00`\x00\x1fp" + // 0x003B1 0x00300: 0x01F70 + "\x00\x0e\xd4\x00`\x00\x1fr" + // 0x003B5 0x00300: 0x01F72 + "\x00\x0e\xdc\x00`\x00\x1ft" + // 0x003B7 0x00300: 0x01F74 + "\x00\x0e\xe4\x00`\x00\x1fv" + // 0x003B9 0x00300: 0x01F76 + "\x00\x0e\xfc\x00`\x00\x1fx" + // 0x003BF 0x00300: 0x01F78 + "\x00\x0f\x14\x00`\x00\x1fz" + // 0x003C5 0x00300: 0x01F7A + "\x00\x0f$\x00`\x00\x1f|" + // 0x003C9 0x00300: 0x01F7C + "\x00|\x00\x00h\xa0\x1f\x80" + // 0x01F00 0x00345: 0x01F80 + "\x00|\x04\x00h\xa0\x1f\x81" + // 0x01F01 0x00345: 0x01F81 + "\x00|\b\x00h\xa0\x1f\x82" + // 0x01F02 0x00345: 0x01F82 + "\x00|\f\x00h\xa0\x1f\x83" + // 0x01F03 0x00345: 0x01F83 + "\x00|\x10\x00h\xa0\x1f\x84" + // 0x01F04 0x00345: 0x01F84 + "\x00|\x14\x00h\xa0\x1f\x85" + // 0x01F05 0x00345: 0x01F85 + "\x00|\x18\x00h\xa0\x1f\x86" + // 0x01F06 0x00345: 0x01F86 + "\x00|\x1c\x00h\xa0\x1f\x87" + // 0x01F07 0x00345: 0x01F87 + "\x00| \x00h\xa0\x1f\x88" + // 0x01F08 0x00345: 0x01F88 + "\x00|$\x00h\xa0\x1f\x89" + // 0x01F09 0x00345: 0x01F89 + "\x00|(\x00h\xa0\x1f\x8a" + // 0x01F0A 0x00345: 0x01F8A + "\x00|,\x00h\xa0\x1f\x8b" + // 0x01F0B 0x00345: 0x01F8B + "\x00|0\x00h\xa0\x1f\x8c" + // 0x01F0C 0x00345: 0x01F8C + "\x00|4\x00h\xa0\x1f\x8d" + // 0x01F0D 0x00345: 0x01F8D + "\x00|8\x00h\xa0\x1f\x8e" + // 0x01F0E 0x00345: 0x01F8E + "\x00|<\x00h\xa0\x1f\x8f" + // 0x01F0F 0x00345: 0x01F8F + "\x00|\x80\x00h\xa0\x1f\x90" + // 0x01F20 0x00345: 0x01F90 + "\x00|\x84\x00h\xa0\x1f\x91" + // 0x01F21 0x00345: 0x01F91 + "\x00|\x88\x00h\xa0\x1f\x92" + // 0x01F22 0x00345: 0x01F92 + "\x00|\x8c\x00h\xa0\x1f\x93" + // 0x01F23 0x00345: 0x01F93 + "\x00|\x90\x00h\xa0\x1f\x94" + // 0x01F24 0x00345: 0x01F94 + "\x00|\x94\x00h\xa0\x1f\x95" + // 0x01F25 0x00345: 0x01F95 + "\x00|\x98\x00h\xa0\x1f\x96" + // 0x01F26 0x00345: 0x01F96 + "\x00|\x9c\x00h\xa0\x1f\x97" + // 0x01F27 0x00345: 0x01F97 + "\x00|\xa0\x00h\xa0\x1f\x98" + // 0x01F28 0x00345: 0x01F98 + "\x00|\xa4\x00h\xa0\x1f\x99" + // 0x01F29 0x00345: 0x01F99 + "\x00|\xa8\x00h\xa0\x1f\x9a" + // 0x01F2A 0x00345: 0x01F9A + "\x00|\xac\x00h\xa0\x1f\x9b" + // 0x01F2B 0x00345: 0x01F9B + "\x00|\xb0\x00h\xa0\x1f\x9c" + // 0x01F2C 0x00345: 0x01F9C + "\x00|\xb4\x00h\xa0\x1f\x9d" + // 0x01F2D 0x00345: 0x01F9D + "\x00|\xb8\x00h\xa0\x1f\x9e" + // 0x01F2E 0x00345: 0x01F9E + "\x00|\xbc\x00h\xa0\x1f\x9f" + // 0x01F2F 0x00345: 0x01F9F + "\x00}\x80\x00h\xa0\x1f\xa0" + // 0x01F60 0x00345: 0x01FA0 + "\x00}\x84\x00h\xa0\x1f\xa1" + // 0x01F61 0x00345: 0x01FA1 + "\x00}\x88\x00h\xa0\x1f\xa2" + // 0x01F62 0x00345: 0x01FA2 + "\x00}\x8c\x00h\xa0\x1f\xa3" + // 0x01F63 0x00345: 0x01FA3 + "\x00}\x90\x00h\xa0\x1f\xa4" + // 0x01F64 0x00345: 0x01FA4 + "\x00}\x94\x00h\xa0\x1f\xa5" + // 0x01F65 0x00345: 0x01FA5 + "\x00}\x98\x00h\xa0\x1f\xa6" + // 0x01F66 0x00345: 0x01FA6 + "\x00}\x9c\x00h\xa0\x1f\xa7" + // 0x01F67 0x00345: 0x01FA7 + "\x00}\xa0\x00h\xa0\x1f\xa8" + // 0x01F68 0x00345: 0x01FA8 + "\x00}\xa4\x00h\xa0\x1f\xa9" + // 0x01F69 0x00345: 0x01FA9 + "\x00}\xa8\x00h\xa0\x1f\xaa" + // 0x01F6A 0x00345: 0x01FAA + "\x00}\xac\x00h\xa0\x1f\xab" + // 0x01F6B 0x00345: 0x01FAB + "\x00}\xb0\x00h\xa0\x1f\xac" + // 0x01F6C 0x00345: 0x01FAC + "\x00}\xb4\x00h\xa0\x1f\xad" + // 0x01F6D 0x00345: 0x01FAD + "\x00}\xb8\x00h\xa0\x1f\xae" + // 0x01F6E 0x00345: 0x01FAE + "\x00}\xbc\x00h\xa0\x1f\xaf" + // 0x01F6F 0x00345: 0x01FAF + "\x00\x0e\xc4\x00`\xc0\x1f\xb0" + // 0x003B1 0x00306: 0x01FB0 + "\x00\x0e\xc4\x00`\x80\x1f\xb1" + // 0x003B1 0x00304: 0x01FB1 + "\x00}\xc0\x00h\xa0\x1f\xb2" + // 0x01F70 0x00345: 0x01FB2 + "\x00\x0e\xc4\x00h\xa0\x1f\xb3" + // 0x003B1 0x00345: 0x01FB3 + "\x00\x0e\xb0\x00h\xa0\x1f\xb4" + // 0x003AC 0x00345: 0x01FB4 + "\x00\x0e\xc4\x00h@\x1f\xb6" + // 0x003B1 0x00342: 0x01FB6 + "\x00~\xd8\x00h\xa0\x1f\xb7" + // 0x01FB6 0x00345: 0x01FB7 + "\x00\x0eD\x00`\xc0\x1f\xb8" + // 0x00391 0x00306: 0x01FB8 + "\x00\x0eD\x00`\x80\x1f\xb9" + // 0x00391 0x00304: 0x01FB9 + "\x00\x0eD\x00`\x00\x1f\xba" + // 0x00391 0x00300: 0x01FBA + "\x00\x0eD\x00h\xa0\x1f\xbc" + // 0x00391 0x00345: 0x01FBC + "\x00\x02\xa0\x00h@\x1f\xc1" + // 0x000A8 0x00342: 0x01FC1 + "\x00}\xd0\x00h\xa0\x1f\xc2" + // 0x01F74 0x00345: 0x01FC2 + "\x00\x0e\xdc\x00h\xa0\x1f\xc3" + // 0x003B7 0x00345: 0x01FC3 + "\x00\x0e\xb8\x00h\xa0\x1f\xc4" + // 0x003AE 0x00345: 0x01FC4 + "\x00\x0e\xdc\x00h@\x1f\xc6" + // 0x003B7 0x00342: 0x01FC6 + "\x00\x7f\x18\x00h\xa0\x1f\xc7" + // 0x01FC6 0x00345: 0x01FC7 + "\x00\x0eT\x00`\x00\x1f\xc8" + // 0x00395 0x00300: 0x01FC8 + "\x00\x0e\\\x00`\x00\x1f\xca" + // 0x00397 0x00300: 0x01FCA + "\x00\x0e\\\x00h\xa0\x1f\xcc" + // 0x00397 0x00345: 0x01FCC + "\x00~\xfc\x00`\x00\x1f\xcd" + // 0x01FBF 0x00300: 0x01FCD + "\x00~\xfc\x00` \x1f\xce" + // 0x01FBF 0x00301: 0x01FCE + "\x00~\xfc\x00h@\x1f\xcf" + // 0x01FBF 0x00342: 0x01FCF + "\x00\x0e\xe4\x00`\xc0\x1f\xd0" + // 0x003B9 0x00306: 0x01FD0 + "\x00\x0e\xe4\x00`\x80\x1f\xd1" + // 0x003B9 0x00304: 0x01FD1 + "\x00\x0f(\x00`\x00\x1f\xd2" + // 0x003CA 0x00300: 0x01FD2 + "\x00\x0e\xe4\x00h@\x1f\xd6" + // 0x003B9 0x00342: 0x01FD6 + "\x00\x0f(\x00h@\x1f\xd7" + // 0x003CA 0x00342: 0x01FD7 + "\x00\x0ed\x00`\xc0\x1f\xd8" + // 0x00399 0x00306: 0x01FD8 + "\x00\x0ed\x00`\x80\x1f\xd9" + // 0x00399 0x00304: 0x01FD9 + "\x00\x0ed\x00`\x00\x1f\xda" + // 0x00399 0x00300: 0x01FDA + "\x00\x7f\xf8\x00`\x00\x1f\xdd" + // 0x01FFE 0x00300: 0x01FDD + "\x00\x7f\xf8\x00` \x1f\xde" + // 0x01FFE 0x00301: 0x01FDE + "\x00\x7f\xf8\x00h@\x1f\xdf" + // 0x01FFE 0x00342: 0x01FDF + "\x00\x0f\x14\x00`\xc0\x1f\xe0" + // 0x003C5 0x00306: 0x01FE0 + "\x00\x0f\x14\x00`\x80\x1f\xe1" + // 0x003C5 0x00304: 0x01FE1 + "\x00\x0f,\x00`\x00\x1f\xe2" + // 0x003CB 0x00300: 0x01FE2 + "\x00\x0f\x04\x00b`\x1f\xe4" + // 0x003C1 0x00313: 0x01FE4 + "\x00\x0f\x04\x00b\x80\x1f\xe5" + // 0x003C1 0x00314: 0x01FE5 + "\x00\x0f\x14\x00h@\x1f\xe6" + // 0x003C5 0x00342: 0x01FE6 + "\x00\x0f,\x00h@\x1f\xe7" + // 0x003CB 0x00342: 0x01FE7 + "\x00\x0e\x94\x00`\xc0\x1f\xe8" + // 0x003A5 0x00306: 0x01FE8 + "\x00\x0e\x94\x00`\x80\x1f\xe9" + // 0x003A5 0x00304: 0x01FE9 + "\x00\x0e\x94\x00`\x00\x1f\xea" + // 0x003A5 0x00300: 0x01FEA + "\x00\x0e\x84\x00b\x80\x1f\xec" + // 0x003A1 0x00314: 0x01FEC + "\x00\x02\xa0\x00`\x00\x1f\xed" + // 0x000A8 0x00300: 0x01FED + "\x00}\xf0\x00h\xa0\x1f\xf2" + // 0x01F7C 0x00345: 0x01FF2 + "\x00\x0f$\x00h\xa0\x1f\xf3" + // 0x003C9 0x00345: 0x01FF3 + "\x00\x0f8\x00h\xa0\x1f\xf4" + // 0x003CE 0x00345: 0x01FF4 + "\x00\x0f$\x00h@\x1f\xf6" + // 0x003C9 0x00342: 0x01FF6 + "\x00\x7f\xd8\x00h\xa0\x1f\xf7" + // 0x01FF6 0x00345: 0x01FF7 + "\x00\x0e|\x00`\x00\x1f\xf8" + // 0x0039F 0x00300: 0x01FF8 + "\x00\x0e\xa4\x00`\x00\x1f\xfa" + // 0x003A9 0x00300: 0x01FFA + "\x00\x0e\xa4\x00h\xa0\x1f\xfc" + // 0x003A9 0x00345: 0x01FFC + "\x00\x86@\x00g\x00!\x9a" + // 0x02190 0x00338: 0x0219A + "\x00\x86H\x00g\x00!\x9b" + // 0x02192 0x00338: 0x0219B + "\x00\x86P\x00g\x00!\xae" + // 0x02194 0x00338: 0x021AE + "\x00\x87@\x00g\x00!\xcd" + // 0x021D0 0x00338: 0x021CD + "\x00\x87P\x00g\x00!\xce" + // 0x021D4 0x00338: 0x021CE + "\x00\x87H\x00g\x00!\xcf" + // 0x021D2 0x00338: 0x021CF + "\x00\x88\f\x00g\x00\"\x04" + // 0x02203 0x00338: 0x02204 + "\x00\x88 \x00g\x00\"\t" + // 0x02208 0x00338: 0x02209 + "\x00\x88,\x00g\x00\"\f" + // 0x0220B 0x00338: 0x0220C + "\x00\x88\x8c\x00g\x00\"$" + // 0x02223 0x00338: 0x02224 + "\x00\x88\x94\x00g\x00\"&" + // 0x02225 0x00338: 0x02226 + "\x00\x88\xf0\x00g\x00\"A" + // 0x0223C 0x00338: 0x02241 + "\x00\x89\f\x00g\x00\"D" + // 0x02243 0x00338: 0x02244 + "\x00\x89\x14\x00g\x00\"G" + // 0x02245 0x00338: 0x02247 + "\x00\x89 \x00g\x00\"I" + // 0x02248 0x00338: 0x02249 + "\x00\x00\xf4\x00g\x00\"`" + // 0x0003D 0x00338: 0x02260 + "\x00\x89\x84\x00g\x00\"b" + // 0x02261 0x00338: 0x02262 + "\x00\x894\x00g\x00\"m" + // 0x0224D 0x00338: 0x0226D + "\x00\x00\xf0\x00g\x00\"n" + // 0x0003C 0x00338: 0x0226E + "\x00\x00\xf8\x00g\x00\"o" + // 0x0003E 0x00338: 0x0226F + "\x00\x89\x90\x00g\x00\"p" + // 0x02264 0x00338: 0x02270 + "\x00\x89\x94\x00g\x00\"q" + // 0x02265 0x00338: 0x02271 + "\x00\x89\xc8\x00g\x00\"t" + // 0x02272 0x00338: 0x02274 + "\x00\x89\xcc\x00g\x00\"u" + // 0x02273 0x00338: 0x02275 + "\x00\x89\xd8\x00g\x00\"x" + // 0x02276 0x00338: 0x02278 + "\x00\x89\xdc\x00g\x00\"y" + // 0x02277 0x00338: 0x02279 + "\x00\x89\xe8\x00g\x00\"\x80" + // 0x0227A 0x00338: 0x02280 + "\x00\x89\xec\x00g\x00\"\x81" + // 0x0227B 0x00338: 0x02281 + "\x00\x8a\b\x00g\x00\"\x84" + // 0x02282 0x00338: 0x02284 + "\x00\x8a\f\x00g\x00\"\x85" + // 0x02283 0x00338: 0x02285 + "\x00\x8a\x18\x00g\x00\"\x88" + // 0x02286 0x00338: 0x02288 + "\x00\x8a\x1c\x00g\x00\"\x89" + // 0x02287 0x00338: 0x02289 + "\x00\x8a\x88\x00g\x00\"\xac" + // 0x022A2 0x00338: 0x022AC + "\x00\x8a\xa0\x00g\x00\"\xad" + // 0x022A8 0x00338: 0x022AD + "\x00\x8a\xa4\x00g\x00\"\xae" + // 0x022A9 0x00338: 0x022AE + "\x00\x8a\xac\x00g\x00\"\xaf" + // 0x022AB 0x00338: 0x022AF + "\x00\x89\xf0\x00g\x00\"\xe0" + // 0x0227C 0x00338: 0x022E0 + "\x00\x89\xf4\x00g\x00\"\xe1" + // 0x0227D 0x00338: 0x022E1 + "\x00\x8aD\x00g\x00\"\xe2" + // 0x02291 0x00338: 0x022E2 + "\x00\x8aH\x00g\x00\"\xe3" + // 0x02292 0x00338: 0x022E3 + "\x00\x8a\xc8\x00g\x00\"\xea" + // 0x022B2 0x00338: 0x022EA + "\x00\x8a\xcc\x00g\x00\"\xeb" + // 0x022B3 0x00338: 0x022EB + "\x00\x8a\xd0\x00g\x00\"\xec" + // 0x022B4 0x00338: 0x022EC + "\x00\x8a\xd4\x00g\x00\"\xed" + // 0x022B5 0x00338: 0x022ED + "\x00\xc1,\x06\x13 0L" + // 0x0304B 0x03099: 0x0304C + "\x00\xc14\x06\x13 0N" + // 0x0304D 0x03099: 0x0304E + "\x00\xc1<\x06\x13 0P" + // 0x0304F 0x03099: 0x03050 + "\x00\xc1D\x06\x13 0R" + // 0x03051 0x03099: 0x03052 + "\x00\xc1L\x06\x13 0T" + // 0x03053 0x03099: 0x03054 + "\x00\xc1T\x06\x13 0V" + // 0x03055 0x03099: 0x03056 + "\x00\xc1\\\x06\x13 0X" + // 0x03057 0x03099: 0x03058 + "\x00\xc1d\x06\x13 0Z" + // 0x03059 0x03099: 0x0305A + "\x00\xc1l\x06\x13 0\\" + // 0x0305B 0x03099: 0x0305C + "\x00\xc1t\x06\x13 0^" + // 0x0305D 0x03099: 0x0305E + "\x00\xc1|\x06\x13 0`" + // 0x0305F 0x03099: 0x03060 + "\x00\xc1\x84\x06\x13 0b" + // 0x03061 0x03099: 0x03062 + "\x00\xc1\x90\x06\x13 0e" + // 0x03064 0x03099: 0x03065 + "\x00\xc1\x98\x06\x13 0g" + // 0x03066 0x03099: 0x03067 + "\x00\xc1\xa0\x06\x13 0i" + // 0x03068 0x03099: 0x03069 + "\x00\xc1\xbc\x06\x13 0p" + // 0x0306F 0x03099: 0x03070 + "\x00\xc1\xbc\x06\x13@0q" + // 0x0306F 0x0309A: 0x03071 + "\x00\xc1\xc8\x06\x13 0s" + // 0x03072 0x03099: 0x03073 + "\x00\xc1\xc8\x06\x13@0t" + // 0x03072 0x0309A: 0x03074 + "\x00\xc1\xd4\x06\x13 0v" + // 0x03075 0x03099: 0x03076 + "\x00\xc1\xd4\x06\x13@0w" + // 0x03075 0x0309A: 0x03077 + "\x00\xc1\xe0\x06\x13 0y" + // 0x03078 0x03099: 0x03079 + "\x00\xc1\xe0\x06\x13@0z" + // 0x03078 0x0309A: 0x0307A + "\x00\xc1\xec\x06\x13 0|" + // 0x0307B 0x03099: 0x0307C + "\x00\xc1\xec\x06\x13@0}" + // 0x0307B 0x0309A: 0x0307D + "\x00\xc1\x18\x06\x13 0\x94" + // 0x03046 0x03099: 0x03094 + "\x00\xc2t\x06\x13 0\x9e" + // 0x0309D 0x03099: 0x0309E + "\x00¬\x06\x13 0\xac" + // 0x030AB 0x03099: 0x030AC + "\x00´\x06\x13 0\xae" + // 0x030AD 0x03099: 0x030AE + "\x00¼\x06\x13 0\xb0" + // 0x030AF 0x03099: 0x030B0 + "\x00\xc2\xc4\x06\x13 0\xb2" + // 0x030B1 0x03099: 0x030B2 + "\x00\xc2\xcc\x06\x13 0\xb4" + // 0x030B3 0x03099: 0x030B4 + "\x00\xc2\xd4\x06\x13 0\xb6" + // 0x030B5 0x03099: 0x030B6 + "\x00\xc2\xdc\x06\x13 0\xb8" + // 0x030B7 0x03099: 0x030B8 + "\x00\xc2\xe4\x06\x13 0\xba" + // 0x030B9 0x03099: 0x030BA + "\x00\xc2\xec\x06\x13 0\xbc" + // 0x030BB 0x03099: 0x030BC + "\x00\xc2\xf4\x06\x13 0\xbe" + // 0x030BD 0x03099: 0x030BE + "\x00\xc2\xfc\x06\x13 0\xc0" + // 0x030BF 0x03099: 0x030C0 + "\x00\xc3\x04\x06\x13 0\xc2" + // 0x030C1 0x03099: 0x030C2 + "\x00\xc3\x10\x06\x13 0\xc5" + // 0x030C4 0x03099: 0x030C5 + "\x00\xc3\x18\x06\x13 0\xc7" + // 0x030C6 0x03099: 0x030C7 + "\x00\xc3 \x06\x13 0\xc9" + // 0x030C8 0x03099: 0x030C9 + "\x00\xc3<\x06\x13 0\xd0" + // 0x030CF 0x03099: 0x030D0 + "\x00\xc3<\x06\x13@0\xd1" + // 0x030CF 0x0309A: 0x030D1 + "\x00\xc3H\x06\x13 0\xd3" + // 0x030D2 0x03099: 0x030D3 + "\x00\xc3H\x06\x13@0\xd4" + // 0x030D2 0x0309A: 0x030D4 + "\x00\xc3T\x06\x13 0\xd6" + // 0x030D5 0x03099: 0x030D6 + "\x00\xc3T\x06\x13@0\xd7" + // 0x030D5 0x0309A: 0x030D7 + "\x00\xc3`\x06\x13 0\xd9" + // 0x030D8 0x03099: 0x030D9 + "\x00\xc3`\x06\x13@0\xda" + // 0x030D8 0x0309A: 0x030DA + "\x00\xc3l\x06\x13 0\xdc" + // 0x030DB 0x03099: 0x030DC + "\x00\xc3l\x06\x13@0\xdd" + // 0x030DB 0x0309A: 0x030DD + "\x00\u0098\x06\x13 0\xf4" + // 0x030A6 0x03099: 0x030F4 + "\x00ü\x06\x13 0\xf7" + // 0x030EF 0x03099: 0x030F7 + "\x00\xc3\xc0\x06\x13 0\xf8" + // 0x030F0 0x03099: 0x030F8 + "\x00\xc3\xc4\x06\x13 0\xf9" + // 0x030F1 0x03099: 0x030F9 + "\x00\xc3\xc8\x06\x13 0\xfa" + // 0x030F2 0x03099: 0x030FA + "\x00\xc3\xf4\x06\x13 0\xfe" + // 0x030FD 0x03099: 0x030FE + "\x04\x17H\x00`\xe1\x05\xc9" + // 0x105D2 0x00307: 0x105C9 + "\x04\x17h\x00`\xe1\x05\xe4" + // 0x105DA 0x00307: 0x105E4 + "\x04Bd\"\x17A\x10\x9a" + // 0x11099 0x110BA: 0x1109A + "\x04Bl\"\x17A\x10\x9c" + // 0x1109B 0x110BA: 0x1109C + "\x04B\x94\"\x17A\x10\xab" + // 0x110A5 0x110BA: 0x110AB + "\x04D\xc4\"$\xe1\x11." + // 0x11131 0x11127: 0x1112E + "\x04D\xc8\"$\xe1\x11/" + // 0x11132 0x11127: 0x1112F + "\x04M\x1c\"g\xc1\x13K" + // 0x11347 0x1133E: 0x1134B + "\x04M\x1c\"j\xe1\x13L" + // 0x11347 0x11357: 0x1134C + "\x04N\b\"y!\x13\x83" + // 0x11382 0x113C9: 0x11383 + "\x04N\x10\"wa\x13\x85" + // 0x11384 0x113BB: 0x11385 + "\x04N,\"xA\x13\x8e" + // 0x1138B 0x113C2: 0x1138E + "\x04N@\"y!\x13\x91" + // 0x11390 0x113C9: 0x11391 + "\x04O\b\"xA\x13\xc5" + // 0x113C2 0x113C2: 0x113C5 + "\x04O\b\"w\x01\x13\xc7" + // 0x113C2 0x113B8: 0x113C7 + "\x04O\b\"y!\x13\xc8" + // 0x113C2 0x113C9: 0x113C8 + "\x04R\xe4\"\x97A\x14\xbb" + // 0x114B9 0x114BA: 0x114BB + "\x04R\xe4\"\x96\x01\x14\xbc" + // 0x114B9 0x114B0: 0x114BC + "\x04R\xe4\"\x97\xa1\x14\xbe" + // 0x114B9 0x114BD: 0x114BE + "\x04V\xe0\"\xb5\xe1\x15\xba" + // 0x115B8 0x115AF: 0x115BA + "\x04V\xe4\"\xb5\xe1\x15\xbb" + // 0x115B9 0x115AF: 0x115BB + "\x04d\xd4#&\x01\x198" + // 0x11935 0x11930: 0x11938 + "\x05\x84x,#\xc1a!" + // 0x1611E 0x1611E: 0x16121 + "\x05\x84x,%!a\"" + // 0x1611E 0x16129: 0x16122 + "\x05\x84x,#\xe1a#" + // 0x1611E 0x1611F: 0x16123 + "\x05\x84\xa4,#\xe1a$" + // 0x16129 0x1611F: 0x16124 + "\x05\x84x,$\x01a%" + // 0x1611E 0x16120: 0x16125 + "\x05\x84\x84,#\xe1a&" + // 0x16121 0x1611F: 0x16126 + "\x05\x84\x88,#\xe1a'" + // 0x16122 0x1611F: 0x16127 + "\x05\x84\x84,$\x01a(" + // 0x16121 0x16120: 0x16128 + "\x05\xb5\x9c-\xac\xe1mh" + // 0x16D67 0x16D67: 0x16D68 + "\x05\xb5\x8c-\xac\xe1mi" + // 0x16D63 0x16D67: 0x16D69 + "\x05\xb5\xa4-\xac\xe1mj" + // 0x16D69 0x16D67: 0x16D6A "" // Total size of tables: 57KB (58086 bytes) diff --git a/vendor/golang.org/x/text/unicode/norm/transform.go b/vendor/golang.org/x/text/unicode/norm/transform.go index a1d366ae48..583230d4df 100644 --- a/vendor/golang.org/x/text/unicode/norm/transform.go +++ b/vendor/golang.org/x/text/unicode/norm/transform.go @@ -33,7 +33,7 @@ func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) return nDst + n, nSrc + n, err } - if err == nil && n < len(src) && !atEOF { + if err == nil && n < len(src) { err = transform.ErrShortSrc } return n, n, err @@ -67,6 +67,7 @@ func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) nSrc = end // Next quickSpan. + var err error end = rb.nsrc eof := atEOF if n := nSrc + len(dst) - nDst; n < end { @@ -79,7 +80,7 @@ func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) nSrc += n nDst += n if ok { - if err == nil && n < rb.nsrc && !atEOF { + if err == nil && nSrc < rb.nsrc { err = transform.ErrShortSrc } return nDst, nSrc, err diff --git a/vendor/modules.txt b/vendor/modules.txt index 2af767a13b..c8a5f93e16 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1252,7 +1252,7 @@ go.yaml.in/yaml/v2 # go.yaml.in/yaml/v3 v3.0.5 ## explicit; go 1.16 go.yaml.in/yaml/v3 -# golang.org/x/crypto v0.56.0 +# golang.org/x/crypto v0.57.0 ## explicit; go 1.26.0 golang.org/x/crypto/blowfish golang.org/x/crypto/chacha20 @@ -1268,8 +1268,8 @@ golang.org/x/crypto/ssh/internal/bcrypt_pbkdf # golang.org/x/exp v0.0.0-20260718201538-764159d718ef ## explicit; go 1.25.0 golang.org/x/exp/slices -# golang.org/x/net v0.58.0 -## explicit; go 1.25.0 +# golang.org/x/net v0.60.0 +## explicit; go 1.26.0 golang.org/x/net/bpf golang.org/x/net/dns/dnsmessage golang.org/x/net/http/httpguts @@ -1295,23 +1295,23 @@ golang.org/x/oauth2/google/internal/stsexchange golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt -# golang.org/x/sync v0.22.0 -## explicit; go 1.25.0 +# golang.org/x/sync v0.23.0 +## explicit; go 1.26.0 golang.org/x/sync/errgroup golang.org/x/sync/semaphore golang.org/x/sync/singleflight -# golang.org/x/sys v0.47.0 -## explicit; go 1.25.0 +# golang.org/x/sys v0.48.0 +## explicit; go 1.26.0 golang.org/x/sys/cpu golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry -# golang.org/x/term v0.45.0 -## explicit; go 1.25.0 +# golang.org/x/term v0.46.0 +## explicit; go 1.26.0 golang.org/x/term -# golang.org/x/text v0.41.0 -## explicit; go 1.25.0 +# golang.org/x/text v0.42.0 +## explicit; go 1.26.0 golang.org/x/text/cases golang.org/x/text/encoding golang.org/x/text/encoding/internal From b73ee7f91c84ffe0ec3f153ecb5f56e8f76fbc21 Mon Sep 17 00:00:00 2001 From: Dmitry Berkovich <54346214+dberkov@users.noreply.github.com> Date: Sun, 11 Oct 2026 05:52:11 +0000 Subject: [PATCH 4/9] ROOTFS snapshot fidelity: micro-VM capture and restore, API opened for micro-VM templates (#2448) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step III of #2102, tracked in #2447. Step I (#2309) removed `on_pause`; step II (#2364) introduced the `SnapshotFidelity` ladder with ROOTFS defined but rejected everywhere. **ROOTFS becomes real on micro-VM.** A ROOTFS snapshot holds the durable volumes plus each container's root-filesystem changes, without guest memory: the actor cold-boots on resume but keeps what it wrote to its rootfs. gVisor keeps rootfs changes inside its memory checkpoint and cannot serve it yet; it now declares that explicitly and a gVisor template that asks for ROOTFS is rejected at admission. Four commits, meant to be read in order. Each builds and passes tests on its own, and ROOTFS stays unreachable from the public API until the third. ## 1. atelet: forward ROOTFS and stop narrowing paused uploads Atelet accepts every level of the ladder and forwards it; which levels a runtime serves is the runtime's knowledge. The paused-upload path still converted a MEMORY pause into a VOLUMES upload, a leftover from the days of separate pause and commit scopes. Pause and suspend now capture one fidelity, a paused actor's template cannot change, and golden actors are never paused, so captured and requested fidelity are always equal; the conversion is replaced by an equality check that refuses any mismatch as drift. ## 2. ateom-microvm: capture and restore ROOTFS Every piece already existed: MEMORY checkpoints tar the host-side rootfs upper per container alongside the Cloud Hypervisor snapshot, and MEMORY restores untar those uppers before assembling the overlays. ROOTFS is the same checkpoint without the VM snapshot, and the upper restore followed by the VOLUMES cold-boot path with a new `keepRootfsUpper` boot parameter so the cold boot does not wipe the restored upper (safe across its retry). Only a MEMORY restore resumes the guest's CPU counters; ROOTFS counts from zero like VOLUMES. `resources.ValidateSnapshotFidelity` now takes the runtime's supported set: micro-VM passes all three, gVisor passes VOLUMES and MEMORY and rejects ROOTFS before taking the actor lock, naming the levels it serves. ## 3. ateapi: allow ROOTFS on micro-VM templates The field-level rejection moves to the template-level cross-field hook: ROOTFS requires `sandbox_config.sandbox_class: SANDBOX_CLASS_MICROVM`. An actor can only be repointed to a template with an identical sandbox config, so a ROOTFS template never ends up under a gVisor actor. The control plane needs no logic change: a ROOTFS snapshot restores at ROOTFS, and a repointed actor drops to VOLUMES as it does for MEMORY, since a rootfs upper is only valid over the image it was written on. ## 4. e2e: prove it on the counter demo The counter app gains a third counter in a file on its root filesystem, printed in the response, so a ROOTFS snapshot is distinguishable from VOLUMES. All lifecycle rows state their rootfs expectation (MEMORY and ROOTFS keep it, VOLUMES resets it). New micro-VM-only ROOTFS rows cover pause, suspend, suspend from PAUSED, and the template repoint and revert. On the gVisor CI lane the ROOTFS rows skip; the new rootfs assertions on MEMORY and VOLUMES rows run there as a side check. ## Behavior changes - **API**: `SNAPSHOT_FIDELITY_ROOTFS` is accepted on micro-VM templates and rejected on gVisor ones with a message naming the requirement. No field or wire changes; the `preferred_fidelity` field-level custom validation tag is removed. - **Atelet**: a paused-upload whose desired fidelity differs from the captured one is now `FailedPrecondition`; previously a MEMORY capture could be downgraded to VOLUMES. Unreachable in practice, as explained in commit 1. - Unchanged: MEMORY and VOLUMES capture and restore, the golden-actor MEMORY override, metric labels (`rootfs` was already a member). - No backward compatibility is kept, per the pre-v1.0 policy. ## Not in this PR `required_fidelity`, `ResumeActorResponse.resumed_fidelity`, the resume fallback rule, gVisor ROOTFS (pending the gVisor team's separable rootfs capture), and restoring a MEMORY snapshot as a ROOTFS cold boot (the artifacts already allow it; atelet would need the rootfs file subset in the manifest to skip the memory image). ## Verification - Unit tests for atelet, ateom-microvm, ateom-gvisor, resources, ateattr, ateomphaselog, ateapi (validation, controlapi, functional), kubectl-ate, and apitool pass; the ateom tests are linux-tagged and were type-checked for linux locally. - golangci-lint, kube-api-linter, python-protos, boilerplate pass; codegen shows no drift. - The micro-VM counter demo was run manually on a GKE cluster with a nested-virtualization node pool and behaved as expected. The ROOTFS e2e rows need a micro-VM lane; the gVisor lane runs the rest. - [x] Breaking change - [x] Not backward compatible 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../internal/apivalidation/actor_template.go | 25 ++-- .../apivalidation/actor_template_test.go | 13 +- .../apivalidation/zz_generated.validation.go | 4 - .../controlapi/workflow_pause_test.go | 5 +- .../controlapi/workflow_resume_test.go | 24 ++++ cmd/atelet/lifecycle_test.go | 35 +++++- cmd/atelet/main.go | 54 +++------ cmd/atelet/main_test.go | 112 ++++++++++-------- cmd/atelet/sandbox_assets.go | 4 +- cmd/ateom-gvisor/fidelity_test.go | 87 ++++++++++++++ cmd/ateom-gvisor/main.go | 10 +- cmd/ateom-microvm/checkpoint.go | 62 +++++----- cmd/ateom-microvm/fidelity.go | 38 ++++++ cmd/ateom-microvm/fidelity_test.go | 84 +++++++++++++ cmd/ateom-microvm/main.go | 10 +- cmd/ateom-microvm/restore.go | 29 ++++- cmd/ateom-microvm/rootfsupper.go | 29 +++-- cmd/ateom-microvm/rootfsupper_test.go | 47 ++++++++ cmd/ateom-microvm/run.go | 43 +++++-- demos/counter/README.md | 4 + .../counter-microvm-template.yaml.tmpl | 11 +- demos/counter/counter.go | 4 +- docs/api-guide.md | 2 +- docs/architecture.md | 2 +- docs/glossary.md | 6 +- internal/e2e/suites/demo/demo_test.go | 87 +++++++++++++- .../e2e/suites/demo/updatetemplate_test.go | 25 +++- internal/proto/ateletpb/atelet.pb.go | 13 +- internal/proto/ateletpb/atelet.proto | 13 +- internal/proto/ateompb/ateom.pb.go | 4 +- internal/proto/ateompb/ateom.proto | 4 +- internal/resources/validate.go | 26 ++-- internal/resources/validate_test.go | 25 ++-- pkg/proto/ateapipb/ateapi.pb.go | 13 +- pkg/proto/ateapipb/ateapi.proto | 13 +- 35 files changed, 725 insertions(+), 242 deletions(-) create mode 100644 cmd/ateom-gvisor/fidelity_test.go create mode 100644 cmd/ateom-microvm/fidelity.go create mode 100644 cmd/ateom-microvm/fidelity_test.go diff --git a/cmd/ateapi/internal/apivalidation/actor_template.go b/cmd/ateapi/internal/apivalidation/actor_template.go index 3366dd272f..4df9c84a41 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template.go +++ b/cmd/ateapi/internal/apivalidation/actor_template.go @@ -61,14 +61,24 @@ func ValidateActorTemplateUpdate(ctx context.Context, fldPath *field.Path, newVa return Validate_ActorTemplate(ctx, op, fldPath, newVal, oldVal) } -// ValidateCustom_CreateActorTemplateRequest_ActorTemplate rejects container -// volume mounts that reference volumes the template does not declare. +// ValidateCustom_CreateActorTemplateRequest_ActorTemplate holds the rules +// that span more than one field of a template: container volume mounts must +// reference volumes the template declares, and a ROOTFS preferred fidelity +// needs a sandbox class that can capture rootfs changes without memory, which +// today is the micro-VM class alone. func ValidateCustom_CreateActorTemplateRequest_ActorTemplate(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *ateapipb.ActorTemplate) field.ErrorList { + var errs field.ErrorList + if value.GetSnapshotConfig().GetPreferredFidelity() == ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS && + value.GetSandboxConfig().GetSandboxClass() != ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM { + errs = append(errs, field.Invalid( + fldPath.Child("snapshot_config", "preferred_fidelity"), + value.GetSnapshotConfig().GetPreferredFidelity().String(), + "ROOTFS fidelity requires sandbox_config.sandbox_class SANDBOX_CLASS_MICROVM")) + } declared := make(map[string]bool, len(value.GetVolumes())) for _, vol := range value.GetVolumes() { declared[vol.GetName()] = true } - var errs field.ErrorList for i, ctr := range value.GetContainers() { for j, mount := range ctr.GetVolumeMounts() { name := mount.GetName() @@ -237,15 +247,6 @@ func ValidateCustom_SnapshotConfig_StorageLocation(_ context.Context, _ operatio return nil } -// ValidateCustom_SnapshotConfig_PreferredFidelity rejects ROOTFS until a -// sandbox runtime can capture root filesystem changes without memory. -func ValidateCustom_SnapshotConfig_PreferredFidelity(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *ateapipb.SnapshotFidelity) field.ErrorList { - if *value == ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS { - return field.ErrorList{field.Invalid(fldPath, value.String(), "ROOTFS fidelity is not supported yet")} - } - return nil -} - // envVarNameRE constrains env var names to any printable ASCII character // except '='. var envVarNameRE = regexp.MustCompile(`^[ -<>-~]+$`) diff --git a/cmd/ateapi/internal/apivalidation/actor_template_test.go b/cmd/ateapi/internal/apivalidation/actor_template_test.go index d04127cbaa..c57a3060b5 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template_test.go +++ b/cmd/ateapi/internal/apivalidation/actor_template_test.go @@ -69,13 +69,20 @@ func TestValidateCreateActorTemplateRequest(t *testing.T) { })}, nil, }, { - // ROOTFS is in the enum for the API's sake but no runtime captures it - // yet, so templates may not ask for it. - "rootfs fidelity not supported", + // Only the micro-VM runtime captures rootfs changes without memory; + // the fixture is a gVisor template. + "rootfs fidelity on gvisor rejected", &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS })}, field.ErrorList{field.Invalid(field.NewPath("actor_template", "snapshot_config", "preferred_fidelity"), "SNAPSHOT_FIDELITY_ROOTFS", "")}, + }, { + "rootfs fidelity on microvm accepted", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS + tmpl.SandboxConfig = &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM, ConfigName: "microvm-default"} + })}, + nil, }, { "invalid worker_selector label key", &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { diff --git a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go index f754fa9d6c..43363ee262 100644 --- a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go +++ b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go @@ -7275,10 +7275,6 @@ func Validate_SnapshotConfig( if earlyReturn { return // do not proceed } - // custom validation - if e := ValidateCustom_SnapshotConfig_PreferredFidelity(ctx, op, fldPath, obj, oldObj); len(e) != 0 { - errs = append(errs, e...) - } if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { errs = append(errs, e...) } diff --git a/cmd/ateapi/internal/controlapi/workflow_pause_test.go b/cmd/ateapi/internal/controlapi/workflow_pause_test.go index d8725d34d7..b8ae87a248 100644 --- a/cmd/ateapi/internal/controlapi/workflow_pause_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_pause_test.go @@ -163,8 +163,9 @@ func TestEnsurePausedFinalized_RecordsFidelity(t *testing.T) { fidelity ateapipb.SnapshotFidelity want ateapipb.SnapshotFidelity }{ - {"data", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES}, - {"full", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, + {"volumes", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES}, + {"rootfs", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS}, + {"memory", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { diff --git a/cmd/ateapi/internal/controlapi/workflow_resume_test.go b/cmd/ateapi/internal/controlapi/workflow_resume_test.go index 02a6030fa5..1d5a7e13a0 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume_test.go @@ -1138,6 +1138,7 @@ func TestResumeActor_AteletWireRequest(t *testing.T) { goldenURI := someActorSnapshotURI(t, "gs://bucket/golden-root", "ate-golden", "golden-1") fullScope := ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY + rootfsScope := ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS dataScope := ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES unspecScope := ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED @@ -1258,6 +1259,29 @@ func TestResumeActor_AteletWireRequest(t *testing.T) { scope: ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, }, }, + { + name: "08a Rootfs durable snapshot restores as Rootfs", + actor: actorSeed{externalSnapshot: extSnap(actorURI, rootfsScope)}, + want: restoreWant{ + checkpointType: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL, + snapshotURI: actorURI, + scope: ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + }, + }, + { + // A rootfs upper is only valid over the image it was written on, + // so a repointed actor drops to Data like a Full snapshot does. + name: "08b repointed actor's Rootfs durable snapshot drops to Data", + actor: actorSeed{ + externalSnapshot: extSnap(actorURI, rootfsScope), + tmplUID: "mismatch", + }, + want: restoreWant{ + checkpointType: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL, + snapshotURI: actorURI, + scope: ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, + }, + }, { name: "09 Data durable snapshot restores as Data", actor: actorSeed{externalSnapshot: extSnap(actorURI, dataScope)}, diff --git a/cmd/atelet/lifecycle_test.go b/cmd/atelet/lifecycle_test.go index 80c332c0fe..c592bfb734 100644 --- a/cmd/atelet/lifecycle_test.go +++ b/cmd/atelet/lifecycle_test.go @@ -66,6 +66,16 @@ type fakeAteom struct { // preserveRestoreDir records the PreserveRestoreDir flag from the most // recent RestoreWorkload request. preserveRestoreDir bool + // fidelities records the fidelity each checkpoint or restore arrived + // with, by RPC name. + fidelities map[string]ateompb.SnapshotFidelity +} + +func (f *fakeAteom) recordFidelity(rpc string, fidelity ateompb.SnapshotFidelity) { + if f.fidelities == nil { + f.fidelities = map[string]ateompb.SnapshotFidelity{} + } + f.fidelities[rpc] = fidelity } func (f *fakeAteom) recordActorDirs(rpc string, actorDirs *ateompb.ActorDirs) { @@ -82,6 +92,7 @@ func (f *fakeAteom) RunWorkload(_ context.Context, req *ateompb.RunWorkloadReque func (f *fakeAteom) CheckpointWorkload(_ context.Context, req *ateompb.CheckpointWorkloadRequest) (*ateompb.CheckpointWorkloadResponse, error) { f.recordActorDirs("CheckpointWorkload", req.GetActorDirs()) + f.recordFidelity("CheckpointWorkload", req.GetFidelity()) dir := req.GetActorDirs().GetCheckpointDir() names := make([]string, 0, len(f.snapshotFiles)) for name, body := range f.snapshotFiles { @@ -95,6 +106,7 @@ func (f *fakeAteom) CheckpointWorkload(_ context.Context, req *ateompb.Checkpoin func (f *fakeAteom) RestoreWorkload(_ context.Context, req *ateompb.RestoreWorkloadRequest) (*ateompb.RestoreWorkloadResponse, error) { f.recordActorDirs("RestoreWorkload", req.GetActorDirs()) + f.recordFidelity("RestoreWorkload", req.GetFidelity()) f.preserveRestoreDir = req.GetPreserveRestoreDir() dir := req.GetActorDirs().GetRestoreDir() f.restored = map[string]string{} @@ -140,8 +152,20 @@ func serveFakeAteom(t *testing.T, f *fakeAteom) { // TestLocalSnapshotGC walks an actor through // run -> pause -> resume -> terminate over atelet's RPC surface and ensures that -// the local snapshot is garbage collected after the actor is terminated. +// the local snapshot is garbage collected after the actor is terminated. It +// runs at every fidelity atelet forwards, pinning that the pause and the +// resume hand ateom the fidelity the control plane asked for, unchanged. func TestLocalSnapshotGC(t *testing.T) { + for _, fidelity := range []ateletpb.SnapshotFidelity{ + ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, + ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + } { + t.Run(fidelity.String(), func(t *testing.T) { runLocalSnapshotGC(t, fidelity) }) + } +} + +func runLocalSnapshotGC(t *testing.T, fidelity ateletpb.SnapshotFidelity) { useTempNodeDirs(t) ctx := t.Context() @@ -207,7 +231,7 @@ func TestLocalSnapshotGC(t *testing.T) { ActorTemplateName: "counter", WorkerPodUid: workerPodUID, Spec: spec, - Fidelity: ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + Fidelity: fidelity, Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL, Config: &ateletpb.CheckpointRequest_LocalConfig{ LocalConfig: &ateletpb.LocalCheckpointConfiguration{SnapshotName: snapshotName}, @@ -230,7 +254,7 @@ func TestLocalSnapshotGC(t *testing.T) { WorkerPodUid: workerPodUID, SandboxAssets: sandboxAssets, Spec: spec, - Fidelity: ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + Fidelity: fidelity, Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL, Config: &ateletpb.RestoreRequest_LocalConfig{ LocalConfig: &ateletpb.LocalCheckpointConfiguration{SnapshotName: snapshotName}, @@ -241,6 +265,11 @@ func TestLocalSnapshotGC(t *testing.T) { if got := ateom.restored["checkpoint.img"]; got != "guest-memory" { t.Fatalf("restore staged %q for ateom, want the pause snapshot's %q", got, "guest-memory") } + for _, rpc := range []string{"CheckpointWorkload", "RestoreWorkload"} { + if got := ateom.fidelities[rpc]; got != toAteomFidelity(fidelity) { + t.Errorf("%s carried fidelity %v, want %v", rpc, got, toAteomFidelity(fidelity)) + } + } if !ateom.preserveRestoreDir { t.Errorf("RestoreWorkload preserve_restore_dir = false, want true for pure-local restore") } diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index 244c3cc595..09ab84adff 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -1003,18 +1003,12 @@ func (s *AteomHerder) uploadLocalCheckpointDir(ctx context.Context, req *ateletp if capturedFidelity == "" { return rec.SandboxClass, apierror.FailedPrecondition("local snapshot %q has no fidelity recorded in its manifest; resume and pause the actor again before suspending it", req.GetLocalSnapshotName()) } - desiredFidelity := ateattr.SnapshotFidelityValue(req.GetDesiredFidelity()) - - switch { - case capturedFidelity == desiredFidelity: - case capturedFidelity == ateattr.SnapshotFidelityVolumes && desiredFidelity == ateattr.SnapshotFidelityMemory: - // The control plane rejects this before marking SUSPENDING; reaching - // it here means the template changed mid-flight or store state drifted. - return rec.SandboxClass, apierror.FailedPrecondition("pause snapshot captured %s; cannot upload it as %s (memory was never captured)", capturedFidelity, desiredFidelity) - default: // captured MEMORY, VOLUMES wanted - if err := narrowMemoryCaptureToVolumes(rec); err != nil { - return rec.SandboxClass, err - } + // Pause and suspend capture the template's one preferred fidelity, and a + // paused actor's template cannot change, so the upload is always of what + // the pause captured. A mismatch means the store or the control plane + // drifted; refuse rather than guess. + if desiredFidelity := ateattr.SnapshotFidelityValue(req.GetDesiredFidelity()); capturedFidelity != desiredFidelity { + return rec.SandboxClass, apierror.FailedPrecondition("pause snapshot captured %s; upload asks for %s", capturedFidelity, desiredFidelity) } // The local snapshot stays until the upload succeeds, so a retry can @@ -1031,19 +1025,6 @@ func readSnapshotManifest(dir string) ([]byte, error) { return root.ReadFile(sandboxManifestName) } -// narrowMemoryCaptureToVolumes rewrites rec so a MEMORY capture uploads as a -// VOLUMES snapshot holding only the volume files ateom reported at checkpoint. -func narrowMemoryCaptureToVolumes(rec *sandboxAssetsRecord) error { - if len(rec.DataSnapshotFiles) == 0 { - // Either no durable-dir volumes were attached at pause, or the - // manifest predates the list. Neither is retryable. - return apierror.FailedPrecondition("memory capture lists no volume files; the actor has no durable data to upload as %s", ateattr.SnapshotFidelityVolumes) - } - rec.SnapshotFiles = rec.DataSnapshotFiles - rec.Fidelity = ateattr.SnapshotFidelityVolumes - return nil -} - func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) (resp *ateletpb.RestoreResponse, err error) { if err := validateRestoreRequest(req); err != nil { return nil, apierror.InvalidArgument("%v", err) @@ -1819,18 +1800,17 @@ func validateTerminateRequest(req *ateletpb.TerminateRequest) error { return resources.ValidateContainerNames(names) } -// validateFidelity rejects a fidelity no runtime can serve. ROOTFS is in the -// enum but not captured by any sandbox runtime yet, so it is refused here as -// well as at template admission. +// validateFidelity accepts every level of the ladder. Which levels a sandbox +// runtime can actually serve is the runtime's call: atelet forwards the +// fidelity and ateom rejects what it cannot capture. func validateFidelity(fidelity ateletpb.SnapshotFidelity) error { switch fidelity { - case ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES: + case ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, + ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY: return nil case ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED: return fmt.Errorf("snapshot fidelity must be non-zero") - case ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS: - return fmt.Errorf("ROOTFS fidelity is not supported yet") default: return fmt.Errorf("invalid snapshot fidelity: %v", fidelity) } @@ -1850,14 +1830,8 @@ func validateUploadPausedCheckpointRequest(req *ateletpb.UploadPausedCheckpointR if _, err := resources.ParseSnapshotURI(req.GetDestinationSnapshotUri()); err != nil { errs = append(errs, field.Invalid(field.NewPath("destination_snapshot_uri"), req.GetDestinationSnapshotUri(), err.Error())) } - // Uploads only ever produce MEMORY or VOLUMES snapshots. - switch req.GetDesiredFidelity() { - case ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES: - case ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS: - errs = append(errs, field.Invalid(field.NewPath("desired_fidelity"), req.GetDesiredFidelity().String(), "ROOTFS fidelity is not supported yet")) - default: - errs = append(errs, field.NotSupported(field.NewPath("desired_fidelity"), req.GetDesiredFidelity(), - []string{ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY.String(), ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES.String()})) + if err := validateFidelity(req.GetDesiredFidelity()); err != nil { + errs = append(errs, field.Invalid(field.NewPath("desired_fidelity"), req.GetDesiredFidelity().String(), err.Error())) } return errs.ToAggregate() } diff --git a/cmd/atelet/main_test.go b/cmd/atelet/main_test.go index 921267e907..757de4581f 100644 --- a/cmd/atelet/main_test.go +++ b/cmd/atelet/main_test.go @@ -673,9 +673,9 @@ func TestValidateCheckpointRequest(t *testing.T) { r.Fidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED }), true}, {"invalid snapshot fidelity", makeReq(func(r *ateletpb.CheckpointRequest) { r.Fidelity = ateletpb.SnapshotFidelity(23) }), true}, - {"rootfs fidelity not supported yet", makeReq(func(r *ateletpb.CheckpointRequest) { + {"rootfs fidelity", makeReq(func(r *ateletpb.CheckpointRequest) { r.Fidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS - }), true}, + }), false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -734,9 +734,9 @@ func TestValidateRestoreRequest(t *testing.T) { {"unspecified snapshot type", makeReq(func(r *ateletpb.RestoreRequest) { r.Type = ateletpb.CheckpointType_CHECKPOINT_TYPE_UNSPECIFIED }), true}, {"unspecified snapshot fidelity", makeReq(func(r *ateletpb.RestoreRequest) { r.Fidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED }), true}, {"invalid snapshot fidelity", makeReq(func(r *ateletpb.RestoreRequest) { r.Fidelity = ateletpb.SnapshotFidelity(23) }), true}, - {"rootfs fidelity not supported yet", makeReq(func(r *ateletpb.RestoreRequest) { + {"rootfs fidelity", makeReq(func(r *ateletpb.RestoreRequest) { r.Fidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS - }), true}, + }), false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -1574,71 +1574,69 @@ func TestUploadLocalCheckpointDir(t *testing.T) { } }) - t.Run("full capture uploads the reported data files alone as data", func(t *testing.T) { + t.Run("matching rootfs fidelity uploads all files", func(t *testing.T) { store := &recordingObjectStorage{} s := newPluginHerder(t, store) dir := filepath.Join(t.TempDir(), "pause-snap-1") - writeLocalSnapshot(t, dir, fullRec("microvm"), map[string]string{ - "config.json": "cfg", "memory-ranges": "mem", "data.tar": "data", - }) + writeLocalSnapshot(t, dir, sandboxAssetsRecord{ + SandboxClass: "microvm", + PauseImage: testPauseImage, + SnapshotFiles: []string{"rootfs-upper-app.tar", "data.tar"}, + DataSnapshotFiles: []string{"data.tar"}, + Fidelity: ateattr.SnapshotFidelityRootfs, + }, map[string]string{"rootfs-upper-app.tar": "upper", "data.tar": "data"}) req := validUploadPausedCheckpointRequest() - req.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + req.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS if _, err := s.uploadLocalCheckpointDir(ctx, req, dir, uri); err != nil { t.Fatalf("uploadLocalCheckpointDir: %v", err) } want := []string{ pausedSnapshotPath + "/data.tar.zstd", pausedSnapshotPath + "/manifest.json", + pausedSnapshotPath + "/rootfs-upper-app.tar.zstd", } if got := store.keys(); !slices.Equal(got, want) { t.Errorf("uploaded objects = %v, want %v", got, want) } - rec := remoteManifest(t, store) - if rec.Fidelity != ateattr.SnapshotFidelityVolumes { - t.Errorf("uploaded manifest fidelity = %q, want %q", rec.Fidelity, ateattr.SnapshotFidelityVolumes) - } - if want := []string{"data.tar"}; !slices.Equal(rec.SnapshotFiles, want) { - t.Errorf("uploaded manifest files = %v, want %v", rec.SnapshotFiles, want) + if rec := remoteManifest(t, store); rec.Fidelity != ateattr.SnapshotFidelityRootfs { + t.Errorf("uploaded manifest fidelity = %q, want %q", rec.Fidelity, ateattr.SnapshotFidelityRootfs) } }) - t.Run("full capture listing no data files is rejected", func(t *testing.T) { - store := &recordingObjectStorage{} - s := newPluginHerder(t, store) - dir := filepath.Join(t.TempDir(), "pause-snap-1") - rec := fullRec("microvm") - rec.DataSnapshotFiles = nil - writeLocalSnapshot(t, dir, rec, map[string]string{ - "config.json": "cfg", "memory-ranges": "mem", "data.tar": "data", - }) - - req := validUploadPausedCheckpointRequest() - req.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES - _, err := s.uploadLocalCheckpointDir(ctx, req, dir, uri) - if got := apierror.Code(err); got != codes.FailedPrecondition { - t.Fatalf("status.Code = %v (err %v), want FailedPrecondition", got, err) - } - if len(store.keys()) != 0 { - t.Errorf("objects uploaded despite rejection: %v", store.keys()) - } - }) - - t.Run("data capture cannot become full", func(t *testing.T) { - s := newPluginHerder(t, &recordingObjectStorage{}) - dir := filepath.Join(t.TempDir(), "pause-snap-1") - writeLocalSnapshot(t, dir, sandboxAssetsRecord{ - SandboxClass: "microvm", - PauseImage: testPauseImage, - SnapshotFiles: []string{"data.tar"}, - Fidelity: ateattr.SnapshotFidelityVolumes, - }, map[string]string{"data.tar": "data"}) + // The capture and the upload always carry the template's one fidelity; + // a mismatch in either direction is drift, never a conversion. + for _, tc := range []struct { + name string + captured string + desired ateletpb.SnapshotFidelity + }{ + {"volumes capture cannot become memory", ateattr.SnapshotFidelityVolumes, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, + {"memory capture cannot become volumes", ateattr.SnapshotFidelityMemory, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES}, + {"memory capture cannot become rootfs", ateattr.SnapshotFidelityMemory, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS}, + {"rootfs capture cannot become memory", ateattr.SnapshotFidelityRootfs, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, + } { + t.Run(tc.name, func(t *testing.T) { + store := &recordingObjectStorage{} + s := newPluginHerder(t, store) + dir := filepath.Join(t.TempDir(), "pause-snap-1") + rec := fullRec("microvm") + rec.Fidelity = tc.captured + writeLocalSnapshot(t, dir, rec, map[string]string{ + "config.json": "cfg", "memory-ranges": "mem", "data.tar": "data", + }) - _, err := s.uploadLocalCheckpointDir(ctx, validUploadPausedCheckpointRequest(), dir, uri) - if got := apierror.Code(err); got != codes.FailedPrecondition { - t.Fatalf("status.Code = %v (err %v), want FailedPrecondition", got, err) - } - }) + req := validUploadPausedCheckpointRequest() + req.DesiredFidelity = tc.desired + _, err := s.uploadLocalCheckpointDir(ctx, req, dir, uri) + if got := apierror.Code(err); got != codes.FailedPrecondition { + t.Fatalf("status.Code = %v (err %v), want FailedPrecondition", got, err) + } + if len(store.keys()) != 0 { + t.Errorf("objects uploaded despite rejection: %v", store.keys()) + } + }) + } t.Run("manifest without scope is rejected", func(t *testing.T) { store := &recordingObjectStorage{} @@ -1708,6 +1706,9 @@ func TestValidateUploadPausedCheckpointRequest(t *testing.T) { {"valid volumes fidelity", func(r *ateletpb.UploadPausedCheckpointRequest) { r.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES }, false}, + {"valid rootfs fidelity", func(r *ateletpb.UploadPausedCheckpointRequest) { + r.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS + }, false}, {"invalid atespace", func(r *ateletpb.UploadPausedCheckpointRequest) { r.Atespace = "../escape" }, true}, {"golden atespace rejected", func(r *ateletpb.UploadPausedCheckpointRequest) { r.Atespace = resources.GoldenActorAtespace }, true}, {"invalid actor name", func(r *ateletpb.UploadPausedCheckpointRequest) { r.ActorName = "UPPER" }, true}, @@ -1721,8 +1722,8 @@ func TestValidateUploadPausedCheckpointRequest(t *testing.T) { {"unspecified fidelity", func(r *ateletpb.UploadPausedCheckpointRequest) { r.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED }, true}, - {"rootfs fidelity not supported yet", func(r *ateletpb.UploadPausedCheckpointRequest) { - r.DesiredFidelity = ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS + {"invalid fidelity", func(r *ateletpb.UploadPausedCheckpointRequest) { + r.DesiredFidelity = ateletpb.SnapshotFidelity(23) }, true}, } for _, tc := range tests { @@ -1749,6 +1750,13 @@ func TestShouldHaveSnapshots(t *testing.T) { }, want: true, }, + { + name: "rootfs fidelity always expects snapshots", + req: &ateletpb.CheckpointRequest{ + Fidelity: ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + }, + want: true, + }, { name: "data scope with durable volumes expects snapshots", req: &ateletpb.CheckpointRequest{ diff --git a/cmd/atelet/sandbox_assets.go b/cmd/atelet/sandbox_assets.go index 0e59dd5852..0deb76889f 100644 --- a/cmd/atelet/sandbox_assets.go +++ b/cmd/atelet/sandbox_assets.go @@ -105,8 +105,8 @@ type sandboxAssetsRecord struct { // Empty when the capture holds no durable data. DataSnapshotFiles []string `json:"dataSnapshotFiles,omitempty"` // Fidelity is the snapshot fidelity the checkpoint captured, as the shared - // ateattr label ("memory" or "volumes"), so a snapshot's content is - // knowable from the manifest alone. Empty in the on-node record written at + // ateattr label ("volumes", "rootfs", or "memory"), so a snapshot's content + // is knowable from the manifest alone. Empty in the on-node record written at // Run/Restore. Fidelity string `json:"fidelity,omitempty"` } diff --git a/cmd/ateom-gvisor/fidelity_test.go b/cmd/ateom-gvisor/fidelity_test.go new file mode 100644 index 0000000000..6e24890f4b --- /dev/null +++ b/cmd/ateom-gvisor/fidelity_test.go @@ -0,0 +1,87 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "strings" + "testing" + + "google.golang.org/grpc/codes" + + "github.com/agent-substrate/substrate/internal/apierror" + "github.com/agent-substrate/substrate/internal/proto/ateompb" +) + +// TestValidateFidelity pins what this runtime serves. gVisor keeps rootfs +// changes inside its memory checkpoint, so ROOTFS is refused until it can +// capture them on their own; the error names the levels it does serve. +func TestValidateFidelity(t *testing.T) { + for _, tc := range []struct { + name string + fidelity ateompb.SnapshotFidelity + wantErr bool + }{ + {"volumes", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, false}, + {"memory", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, false}, + {"rootfs is not served", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, true}, + {"unspecified", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, true}, + {"outside the enum", ateompb.SnapshotFidelity(99), true}, + } { + t.Run(tc.name, func(t *testing.T) { + err := validateFidelity(tc.fidelity) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Fatalf("validateFidelity(%v) = %v, wantErr %t", tc.fidelity, err, tc.wantErr) + } + if err != nil && apierror.Code(err) != codes.InvalidArgument { + t.Errorf("code = %v, want InvalidArgument", apierror.Code(err)) + } + }) + } + err := validateFidelity(ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS) + for _, want := range []string{"SNAPSHOT_FIDELITY_VOLUMES", "SNAPSHOT_FIDELITY_MEMORY"} { + if !strings.Contains(err.Error(), want) { + t.Errorf("rootfs rejection %q does not list %s as supported", err, want) + } + } +} + +// TestRPCsRejectRootfsBeforeTouchingTheSandbox pins that a ROOTFS checkpoint +// or restore fails validation up front, before the actor lock or any file +// system work, so a misrouted request cannot leave the actor half-handled. +func TestRPCsRejectRootfsBeforeTouchingTheSandbox(t *testing.T) { + s := &AteomService{} + dirs := &ateompb.ActorDirs{ + RootDir: "/node/actors/actor-a", + OciBundleDir: "/node/actors/actor-a/bundle", + CheckpointDir: "/node/actors/actor-a/checkpoint-state", + RestoreDir: "/node/actors/actor-a/restore-state", + DurableDirVolumeMountsDir: "/node/actors/actor-a/durable-dirs", + SystemInfoVolumeRootsDir: "/node/actors/actor-a/system-info", + VolumesDir: "/node/actors/actor-a/volumes", + } + rootfs := ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS + + _, err := s.CheckpointWorkload(context.Background(), &ateompb.CheckpointWorkloadRequest{ActorUid: "actor-a", ActorDirs: dirs, Fidelity: rootfs}) + if apierror.Code(err) != codes.InvalidArgument || !strings.Contains(err.Error(), "fidelity") { + t.Errorf("CheckpointWorkload(ROOTFS) = %v, want InvalidArgument naming the fidelity", err) + } + _, err = s.RestoreWorkload(context.Background(), &ateompb.RestoreWorkloadRequest{ActorUid: "actor-a", ActorDirs: dirs, Fidelity: rootfs}) + if apierror.Code(err) != codes.InvalidArgument || !strings.Contains(err.Error(), "fidelity") { + t.Errorf("RestoreWorkload(ROOTFS) = %v, want InvalidArgument naming the fidelity", err) + } +} diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index 3e21122300..531e65ed47 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -547,10 +547,18 @@ func validateActorDirs(actorDirs *ateompb.ActorDirs) error { return nil } +// supportedFidelities are the snapshot fidelities this runtime serves. gVisor +// keeps rootfs changes inside its memory checkpoint, so it cannot capture +// ROOTFS without MEMORY. +var supportedFidelities = []ateompb.SnapshotFidelity{ + ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, + ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, +} + // validateFidelity rejects a checkpoint or restore request whose fidelity // this runtime cannot serve. func validateFidelity(fidelity ateompb.SnapshotFidelity) error { - if errs := resources.ValidateSnapshotFidelity(fidelity, field.NewPath("fidelity")); len(errs) > 0 { + if errs := resources.ValidateSnapshotFidelity(fidelity, supportedFidelities, field.NewPath("fidelity")); len(errs) > 0 { return apierror.InvalidArgument("%v", errs.ToAggregate()) } return nil diff --git a/cmd/ateom-microvm/checkpoint.go b/cmd/ateom-microvm/checkpoint.go index a282d2df34..bd04c97d85 100644 --- a/cmd/ateom-microvm/checkpoint.go +++ b/cmd/ateom-microvm/checkpoint.go @@ -42,19 +42,24 @@ import ( // Contract with atelet: after we return, atelet uploads the checkpoint dir to object // storage, then tears down bundles and resets the actor dir. // -// What the snapshot holds depends on the requested scope: +// What the snapshot holds depends on the requested fidelity; each level +// includes the ones below it: // -// - MEMORY: the whole guest. ateom drives the CH REST api-socket: pause -> snapshot -// file:// (config.json + state.json + sparse memory-ranges) -// -> tear the VMM down. Each container's rootfs is overlay(virtio-fs RO lower + -// disk-backed upper): the upper is host-backed like the durable-dir volumes and -// ships alongside as its own tar (see rootfsupper.go); process memory persists -// via the memory snapshot. The RO lower is reconstructed from the OCI image at -// restore, so it never ships. Durable-dir volumes ship alongside as per-volume tars. -// - VOLUMES: the durable-dir volumes only, as those same tars. The guest is discarded, so -// the actor cold-starts on restore with its volumes re-materialized. +// - MEMORY: the whole guest. ateom drives the CH REST api-socket: pause -> +// snapshot file:// (config.json + state.json + sparse +// memory-ranges) -> tear the VMM down. Process memory persists via the +// memory snapshot; the volume and upper tars ship alongside as for ROOTFS. +// - ROOTFS: the volumes plus each container's rootfs upper. Each container's +// rootfs is overlay(virtio-fs RO lower + disk-backed upper): the upper is +// host-backed like the durable-dir volumes and ships as its own tar (see +// rootfsupper.go). The RO lower is reconstructed from the OCI image at +// restore, so it never ships. The guest is discarded; the actor cold-starts +// on restore with its rootfs writes back in place. +// - VOLUMES: the durable-dir volumes, as per-volume tars. The guest is +// discarded, so the actor cold-starts on restore with its volumes +// re-materialized. // -// Either way the guest is paused first, which is what makes the tar coherent: the +// Whatever the fidelity the guest is paused first, which is what makes the tar coherent: the // durable share is served write-through, so every completed guest write is already on // the host and no further ones can arrive. // @@ -112,12 +117,13 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // suspended mid-flight for a call that could never have succeeded. // // Durable-dir volumes are host-backed, so they are captured the same way - // under either fidelity — and are the ONLY thing a VOLUMES snapshot - // captures. + // under every fidelity — and are the ONLY thing a VOLUMES snapshot + // captures. ROOTFS and MEMORY always have something to capture: every + // container has a rootfs upper. durable := hasDurableVolumes(req.GetSpec().GetContainers()) csi := hasCsiVolumes(req.GetSpec().GetContainers()) switch scope { - case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY: + case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS: case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES: // TODO: Revisit handling for CSI volumes since snapshots are currently quietly ignored. if !durable && !csi { @@ -171,15 +177,16 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // window costs the slowest of them rather than their sum (the tars scale // with the actor's data; suspend latency is the metric that matters). // - // - CH snapshot (MEMORY only): the guest memory + VM state. A VOLUMES snapshot - // deliberately captures no VM state — no memory image, and no base-id, - // since nothing will reattach to the frozen virtio-fs lower: at restore - // the actor cold-boots from the OCI image. - // - Durable-dir tars (any scope, when declared): host-backed, so pausing - // the write-through share makes the tars coherent. - // - Rootfs upper tars (MEMORY only): host-backed like the durable volumes — - // the memory snapshot does not carry rootfs writes. Under VOLUMES the - // workload cold-starts on restore, discarding rootfs state. + // - CH snapshot (MEMORY only): the guest memory + VM state. The lower + // fidelities deliberately capture no VM state — no memory image, and no + // base-id, since nothing will reattach to the frozen virtio-fs lower: at + // restore the actor cold-boots from the OCI image. + // - Durable-dir tars (any fidelity, when declared): host-backed, so + // pausing the write-through share makes the tars coherent. + // - Rootfs upper tars (ROOTFS and MEMORY): host-backed like the durable + // volumes — the memory snapshot does not carry rootfs writes. Under + // VOLUMES the workload cold-starts on restore, discarding rootfs state. + shipsRootfs := includesRootfs(scope) var durableFiles []string g, gctx := errgroup.WithContext(ctx) if scope == ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY { @@ -202,7 +209,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec return err }) } - if scope == ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY { + if shipsRootfs { g.Go(func() error { t := time.Now() err := tarRootfsUpper(gctx, rootfsUpperDir(actorDirs), checkpointDir, containerNames(req.GetSpec().GetContainers())) @@ -214,9 +221,10 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec return nil, err } - // Report exactly the files we wrote so atelet ships precisely this snapshot: for - // MEMORY, the CH snapshot (config.json + state.json + memory-ranges + base-id) - // plus any durable-dir tars; for VOLUMES, those tars alone. + // Report exactly the files we wrote so atelet ships precisely this snapshot: + // for MEMORY, the CH snapshot (config.json + state.json + memory-ranges + + // base-id) plus the upper and durable-dir tars; for ROOTFS, the upper and + // durable-dir tars; for VOLUMES, the durable-dir tars alone. snapshotFiles, err := listFiles(checkpointDir) if err != nil { return nil, fmt.Errorf("while listing snapshot files: %w", err) diff --git a/cmd/ateom-microvm/fidelity.go b/cmd/ateom-microvm/fidelity.go new file mode 100644 index 0000000000..981fd94d18 --- /dev/null +++ b/cmd/ateom-microvm/fidelity.go @@ -0,0 +1,38 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "github.com/agent-substrate/substrate/internal/proto/ateompb" + +// The fidelity ladder, lowest to highest, each level including the ones +// below it: VOLUMES holds the durable volumes, ROOTFS adds each container's +// rootfs upper, MEMORY adds the guest. These two predicates are the only +// place the runtime spells out which levels carry which layer. + +// includesRootfs reports whether snapshots at this fidelity carry the +// containers' rootfs uppers, so a checkpoint tars them and a restore puts +// them back. +func includesRootfs(f ateompb.SnapshotFidelity) bool { + return f == ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS || f == ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY +} + +// includesMemory reports whether snapshots at this fidelity carry the guest +// itself, so a restore resumes it (and its CPU counters) rather than +// cold-booting a fresh one. +func includesMemory(f ateompb.SnapshotFidelity) bool { + return f == ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY +} diff --git a/cmd/ateom-microvm/fidelity_test.go b/cmd/ateom-microvm/fidelity_test.go new file mode 100644 index 0000000000..cb65fa75e7 --- /dev/null +++ b/cmd/ateom-microvm/fidelity_test.go @@ -0,0 +1,84 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "strings" + "testing" + + "google.golang.org/grpc/codes" + + "github.com/agent-substrate/substrate/internal/apierror" + "github.com/agent-substrate/substrate/internal/proto/ateompb" +) + +// TestFidelityLadder pins which layers each level carries: the checkpoint's +// capture set and the restore's resume-versus-cold-boot decision both key off +// these two predicates. +func TestFidelityLadder(t *testing.T) { + for _, tc := range []struct { + fidelity ateompb.SnapshotFidelity + wantRootfs, wantMemory bool + }{ + {ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, false, false}, + {ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, false, false}, + {ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, true, false}, + {ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, true, true}, + } { + t.Run(tc.fidelity.String(), func(t *testing.T) { + if got := includesRootfs(tc.fidelity); got != tc.wantRootfs { + t.Errorf("includesRootfs = %t, want %t", got, tc.wantRootfs) + } + if got := includesMemory(tc.fidelity); got != tc.wantMemory { + t.Errorf("includesMemory = %t, want %t", got, tc.wantMemory) + } + }) + } +} + +// TestValidateFidelity pins what this runtime serves: every level of the +// ladder, ROOTFS included, since the rootfs upper is host-backed and ships +// with or without guest memory. Anything else is refused before the request +// touches the guest. +func TestValidateFidelity(t *testing.T) { + for _, tc := range []struct { + name string + fidelity ateompb.SnapshotFidelity + wantErr bool + }{ + {"volumes", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, false}, + {"rootfs", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, false}, + {"memory", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, false}, + {"unspecified", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, true}, + {"outside the enum", ateompb.SnapshotFidelity(99), true}, + } { + t.Run(tc.name, func(t *testing.T) { + err := validateFidelity(tc.fidelity) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Fatalf("validateFidelity(%v) = %v, wantErr %t", tc.fidelity, err, tc.wantErr) + } + if err != nil { + if apierror.Code(err) != codes.InvalidArgument { + t.Errorf("code = %v, want InvalidArgument", apierror.Code(err)) + } + if !strings.Contains(err.Error(), "fidelity") { + t.Errorf("error %q does not name the fidelity field", err) + } + } + }) + } +} diff --git a/cmd/ateom-microvm/main.go b/cmd/ateom-microvm/main.go index 443754be0c..36bde56802 100644 --- a/cmd/ateom-microvm/main.go +++ b/cmd/ateom-microvm/main.go @@ -419,10 +419,18 @@ func validateActorDirs(actorDirs *ateompb.ActorDirs) error { return nil } +// supportedFidelities are the snapshot fidelities this runtime serves: the +// rootfs upper is host-backed, so it ships with or without guest memory. +var supportedFidelities = []ateompb.SnapshotFidelity{ + ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, + ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, +} + // validateFidelity rejects a checkpoint or restore request whose fidelity // this runtime cannot serve. func validateFidelity(fidelity ateompb.SnapshotFidelity) error { - if errs := resources.ValidateSnapshotFidelity(fidelity, field.NewPath("fidelity")); len(errs) > 0 { + if errs := resources.ValidateSnapshotFidelity(fidelity, supportedFidelities, field.NewPath("fidelity")); len(errs) > 0 { return apierror.InvalidArgument("%v", errs.ToAggregate()) } return nil diff --git a/cmd/ateom-microvm/restore.go b/cmd/ateom-microvm/restore.go index ca1dbf642a..caae2478bf 100644 --- a/cmd/ateom-microvm/restore.go +++ b/cmd/ateom-microvm/restore.go @@ -95,6 +95,9 @@ func newReseedNonce() ([]byte, error) { // // - MEMORY: relaunch cloud-hypervisor from the snapshot and resume the guest // (restoreMemoryFidelity). +// - ROOTFS: as VOLUMES, but each container's rootfs upper is re-materialized +// from its tar first, so the cold boot sees the rootfs writes the actor had +// made. // - VOLUMES: there is no guest to resume — re-materialize the durable-dir // volumes and cold-boot the actor, which starts its containers afresh from // the OCI image. @@ -151,10 +154,10 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore return nil, err } // Publish attribution before restore, so a stats read during it is - // attributed. A VOLUMES restore cold-boots, so its CPU counts from zero; the other - // scopes resume the guest's counters, so theirs counts from the first - // reading. - resumesGuest := req.GetFidelity() != ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + // attributed. Only a MEMORY restore resumes the guest and its CPU counters, + // so only it counts from the first reading; the lower fidelities cold-boot + // a fresh guest whose CPU counts from zero. + resumesGuest := includesMemory(req.GetFidelity()) if _, err := s.hostActor(ctx, attribution, resumesGuest); err != nil { return nil, err } @@ -168,8 +171,8 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore }() // Restore the durable-dir volumes before anything can observe them: for MEMORY - // that means before the share's virtiofsd starts, for VOLUMES before the workload - // cold-starts. + // that means before the share's virtiofsd starts, for the lower fidelities + // before the workload cold-starts. if hasDurableVolumes(p.containers) { if err := untarDurableVolumes(durableDir, restoreDir, durableVolumeNames(p.containers)); err != nil { return nil, err @@ -181,6 +184,20 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore if err := s.restoreMemoryFidelity(ctx, p, scope, restoreDir, req.GetPreserveRestoreDir(), tStart); err != nil { return nil, err } + case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS: + // A ROOTFS snapshot holds no guest state either, but it carries each + // container's rootfs upper. The cold boot re-materializes the uppers + // from the snapshot instead of starting from a pristine dir, overlapped + // with its bundle preparation like the MEMORY path does. + p.rootfsUpperSnapshotDir = restoreDir + if err := s.coldBootActorRetrying(ctx, p); err != nil { + return nil, err + } + dTotal := time.Since(tStart) + slog.InfoContext(ctx, "Actor restored (rootfs upper + durable-dir volumes, cold boot)", + slog.String("id", p.actorUID), slog.Duration("total", dTotal)) + ateomphaselog.LogSnapshotPhases(ctx, "Restore timing breakdown", attribution, scope, + ateomphaselog.RestoreDurationKey, nil, []ateomphaselog.Phase{{Name: phaseTotal, D: dTotal}}) case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES: // A VOLUMES snapshot holds no guest state, so this is a cold boot that // happens to start with the volumes already populated. wakeup probe gating comes diff --git a/cmd/ateom-microvm/rootfsupper.go b/cmd/ateom-microvm/rootfsupper.go index 11b0bbaf01..7a5c9bce2f 100644 --- a/cmd/ateom-microvm/rootfsupper.go +++ b/cmd/ateom-microvm/rootfsupper.go @@ -36,14 +36,15 @@ package main // removed at teardown — after CleanupSandboxState has dropped the overlay // mounts that use it. // -// Snapshots: the upper does not ride in guest memory, so a MEMORY snapshot -// ships it as one tar per container (rootfsUpperTarFile), taken while the -// guest is paused (the share is write-through, so a paused guest's completed -// writes are already in the upper). Each tar holds only the contents of that -// container's upperdir: ateom creates the directory layout itself on restore, -// so nothing in the snapshot decides a path ateom later mounts or wipes. A -// VOLUMES snapshot deliberately excludes rootfs state: the workload cold-starts -// on restore. +// Snapshots: the upper does not ride in guest memory, so ROOTFS and MEMORY +// snapshots ship it as one tar per container (rootfsUpperTarFile), taken while +// the guest is paused (the share is write-through, so a paused guest's +// completed writes are already in the upper). Each tar holds only the contents +// of that container's upperdir: ateom creates the directory layout itself on +// restore, so nothing in the snapshot decides a path ateom later mounts or +// wipes. A ROOTFS restore puts the upper back and cold-boots on it; a VOLUMES +// snapshot deliberately excludes rootfs state, so the workload cold-starts on a +// pristine upper. import ( "context" @@ -79,6 +80,18 @@ func containerNames(containers []*ateompb.Container) []string { return names } +// stageRootfsUpper prepares the host upper dir for a cold boot: pristine when +// snapshotDir is empty, otherwise re-materialized from the per-container tars +// a ROOTFS or MEMORY snapshot left in snapshotDir. Either way any previous +// activation's contents are gone first. Must run after CleanupSandboxState +// has dropped the overlay mounts that used the dir. +func stageRootfsUpper(actorDirs *ateompb.ActorDirs, snapshotDir string, containers []string) error { + if snapshotDir == "" { + return resetRootfsUpperDir(actorDirs) + } + return untarRootfsUpper(rootfsUpperDir(actorDirs), snapshotDir, containers) +} + // resetRootfsUpperDir gives a cold boot a pristine upper directory: a cold // boot must start from the bare image, and atelet's actor-dir reset does not // know about this directory, so ateom wipes any previous activation's contents diff --git a/cmd/ateom-microvm/rootfsupper_test.go b/cmd/ateom-microvm/rootfsupper_test.go index 79aa2b3f21..c715711367 100644 --- a/cmd/ateom-microvm/rootfsupper_test.go +++ b/cmd/ateom-microvm/rootfsupper_test.go @@ -23,6 +23,8 @@ import ( "testing" "github.com/agent-substrate/substrate/internal/tarutil" + + "github.com/agent-substrate/substrate/internal/proto/ateompb" ) // upperDirWith returns a rootfs upper directory laid out the way the host @@ -96,6 +98,51 @@ func TestRootfsUpperRoundTrip(t *testing.T) { } } +// TestStageRootfsUpper pins the one decision a cold boot makes about the +// host upper dir: without a snapshot dir it starts pristine, wiping whatever +// a previous activation left; with one it is re-materialized from the tars. +func TestStageRootfsUpper(t *testing.T) { + containers := []string{"app_ovl"} + src := upperDirWith(t, map[string]string{"app_ovl/fs/home/agent/notes.txt": "rootfs write"}) + snapshotDir := t.TempDir() + if err := tarRootfsUpper(t.Context(), src, snapshotDir, containers); err != nil { + t.Fatalf("tarRootfsUpper: %v", err) + } + + t.Run("pristine without a snapshot", func(t *testing.T) { + stale := upperDirWith(t, map[string]string{"app_ovl/fs/stale.txt": "stale"}) + dirs := &ateompb.ActorDirs{RootDir: filepath.Dir(stale)} + if err := os.Rename(stale, rootfsUpperDir(dirs)); err != nil { + t.Fatal(err) + } + if err := stageRootfsUpper(dirs, "", containers); err != nil { + t.Fatalf("stageRootfsUpper: %v", err) + } + entries, err := os.ReadDir(rootfsUpperDir(dirs)) + if err != nil || len(entries) != 0 { + t.Errorf("upper dir after pristine staging: ReadDir = %v, %v; want an empty directory", entries, err) + } + }) + + t.Run("re-materialized from a snapshot", func(t *testing.T) { + stale := upperDirWith(t, map[string]string{"app_ovl/fs/stale.txt": "stale"}) + dirs := &ateompb.ActorDirs{RootDir: filepath.Dir(stale)} + if err := os.Rename(stale, rootfsUpperDir(dirs)); err != nil { + t.Fatal(err) + } + if err := stageRootfsUpper(dirs, snapshotDir, containers); err != nil { + t.Fatalf("stageRootfsUpper: %v", err) + } + got, err := os.ReadFile(filepath.Join(rootfsUpperDir(dirs), "app_ovl/fs/home/agent/notes.txt")) + if err != nil || string(got) != "rootfs write" { + t.Errorf("restored upper content = %q, %v; want the snapshot's write", got, err) + } + if _, err := os.Stat(filepath.Join(rootfsUpperDir(dirs), "app_ovl/fs/stale.txt")); !os.IsNotExist(err) { + t.Errorf("stale content survived staging from a snapshot (stat err = %v), want it wiped", err) + } + }) +} + // The snapshot is untrusted, so it must not decide any directory above the // upperdirs: a symlink there would point the overlay mount, or the workdir // wipe in kata.StageMergedRootfs, outside the actor's directory. diff --git a/cmd/ateom-microvm/run.go b/cmd/ateom-microvm/run.go index 1500968454..1bdbfe186f 100644 --- a/cmd/ateom-microvm/run.go +++ b/cmd/ateom-microvm/run.go @@ -312,6 +312,11 @@ type actorBootParams struct { // memory); a container's own cgroup limit comes from its declared resources. // Zero fields keep the kata defaults. size sizing.SandboxSize + // rootfsUpperSnapshotDir, when set, names a snapshot directory holding the + // per-container rootfs upper tars; the cold boot re-materializes the uppers + // from it instead of starting from a pristine dir. A ROOTFS restore sets it + // to the restore dir. + rootfsUpperSnapshotDir string } func (p actorBootParams) attribution() resources.ActorAttribution { @@ -416,6 +421,29 @@ func (s *AteomService) coldBootActor(ctx context.Context, p actorBootParams) (re } }() + // Clean stale per-sandbox state before anything touches the host upper dir. + // Also create the runtime dir for the sockets. + + s.cleanupSandboxState(ctx, actorUID) + if err := os.MkdirAll(kata.VMDir(actorUID), 0o700); err != nil { + return fmt.Errorf("while creating VM dir: %w", err) + } + + // Stage the host upper dir in the background (pristine, or from a ROOTFS + // snapshot's tars) and join it right before the overlay mounts need it, + // hiding it behind the bundle preparation below. Every return before the + // join must drain the goroutine, or a retry's staging would race it. + upperDone := make(chan error, 1) + upperJoined := false + go func() { + upperDone <- stageRootfsUpper(p.actorDirs, p.rootfsUpperSnapshotDir, containerNames(containers)) + }() + defer func() { + if !upperJoined { + <-upperDone + } + }() + // Guest sizing + agent kernel params. memMiB, vcpus, kparams := s.guestConfig() @@ -456,15 +484,9 @@ func (s *AteomService) coldBootActor(ctx context.Context, p actorBootParams) (re return err } - // Clean stale per-sandbox state + create the runtime dir for the sockets. - s.cleanupSandboxState(ctx, actorUID) - if err := os.MkdirAll(kata.VMDir(actorUID), 0o700); err != nil { - return fmt.Errorf("while creating VM dir: %w", err) - } - - // A cold boot starts from the bare image: give it a pristine host upper dir - // (atelet's actor-dir reset does not know this directory; see rootfsupper.go). - if err := resetRootfsUpperDir(p.actorDirs); err != nil { + // The overlay mounts below need the upper dir in its final state. + upperJoined = true + if err := <-upperDone; err != nil { return err } @@ -666,8 +688,7 @@ func (s *AteomService) buildActorContainers(actorUID string, actorDirs *ateompb. // CSI volumes, and system-info volumes (if any) under SharedDir(id)/durable, // SharedDir(id)/csi, and SharedDir(id)/system-info, // then starts the ONE virtiofsd that serves them all. Must run AFTER CleanupSandboxState (which -// wipes SharedDir) and resetRootfsUpperDir/untarRootfsUpper (which own the -// upper contents). The returned virtiofsd cmd outlives this call (CH +// wipes SharedDir) and stageRootfsUpper (which owns the upper contents). The returned virtiofsd cmd outlives this call (CH // demand-pages from it); the caller owns it (tracked on runningActor, killed // in teardownActor). func (s *AteomService) stageMergedRootfs(ctx context.Context, rr resolvedRuntime, id string, actorDirs *ateompb.ActorDirs, ctrs []actorContainer, containers []*ateompb.Container, procAttr *syscall.SysProcAttr) (*exec.Cmd, error) { diff --git a/demos/counter/README.md b/demos/counter/README.md index 87696e5fd3..f69d21ac05 100644 --- a/demos/counter/README.md +++ b/demos/counter/README.md @@ -142,6 +142,10 @@ prioritizing it, not a fundamental limitation of the design. The same in-RAM-counter suspend/resume-continuity demo also runs on the micro-VM sandbox class (`ateom-microvm`: a Kata guest on Cloud Hypervisor), proving that the guest-memory snapshot round-trips just as gVisor's process snapshot does. +The micro-VM class is also the one that serves `SNAPSHOT_FIDELITY_ROOTFS`, a +snapshot of the volumes and each container's rootfs writes without guest +memory; this template keeps `MEMORY` because the in-RAM counter is the point of +the demo. - [`demos/counter/counter-microvm.yaml.tmpl`](counter-microvm.yaml.tmpl) — the `WorkerPool` for the micro-VM sandbox class. diff --git a/demos/counter/counter-microvm-template.yaml.tmpl b/demos/counter/counter-microvm-template.yaml.tmpl index ad8f5ee949..3442643da1 100644 --- a/demos/counter/counter-microvm-template.yaml.tmpl +++ b/demos/counter/counter-microvm-template.yaml.tmpl @@ -45,8 +45,8 @@ containers: timeoutSeconds: 120 # The counter writes its file counter here. With the durableDir volume # below it lands on a host-backed virtio-fs share instead of the guest-RAM - # rootfs upper, so it survives a DATA-scope snapshot (which keeps no guest - # memory). + # rootfs upper, so it survives a VOLUMES snapshot (which keeps neither guest + # memory nor rootfs writes). volumeMounts: - name: data mountPath: /home/counter @@ -60,9 +60,10 @@ resources: - name: memory quantity: 512Mi snapshotConfig: - # MEMORY, matching the gVisor variant: a suspend captures - # guest memory, so the in-RAM count continues from where it left off — the - # round-trip this demo promises. + # MEMORY, matching the gVisor variant: a suspend captures guest memory, so + # the in-RAM count continues from where it left off — the round-trip this + # 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/ sandboxConfig: diff --git a/demos/counter/counter.go b/demos/counter/counter.go index 1850550dee..5720f3d1c7 100644 --- a/demos/counter/counter.go +++ b/demos/counter/counter.go @@ -67,6 +67,7 @@ func main() { sigtermSleepDurationSecs.Store(15) fileCounterDirectory := pflag.String("file-counter-directory", "/home/counter", "Directory for file counter") secondFileCounterDirectory := pflag.String("second-file-counter-directory", "", "Directory for a second file counter; empty disables it. Used to exercise an Actor with more than one durable volume") + rootfsCounterFile := pflag.String("rootfs-counter-file", "/rootfs-counter.txt", "File on the container's root filesystem holding a third counter, so a snapshot's handling of rootfs writes is observable: ROOTFS and MEMORY fidelities preserve it, VOLUMES resets it") validateExistingFilePath := pflag.String("validate-existing-file-path", "", "Path to existing file to validate reading") extraPort := pflag.Int("extra-port", 0, "Additional port to listen on, for exercising atenet-router's arbitrary-port ingress support; 0 disables it") tcpPort := pflag.Int("tcp-port", 0, "Plain TCP echo port for exercising atunnel CONNECT ingress; 0 disables it") @@ -89,6 +90,7 @@ func main() { defaultMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fileCounter := incrementFileCounter(filepath.Join(*fileCounterDirectory, "a.txt")) + rootfsCounter := incrementFileCounter(*rootfsCounterFile) memoryCounter := requestCount.Add(1) currentIP := getCurrentIP() @@ -112,7 +114,7 @@ func main() { secondFileCounterStr = fmt.Sprintf(" | preserved second file counter: %d", secondFileCounter) } - response := fmt.Sprintf("hello from: %s | preserved memory count: %d | preserved file counter: %d%s%s\n", currentIP, memoryCounter, fileCounter, secondFileCounterStr, fileContentStr) + response := fmt.Sprintf("hello from: %s | preserved memory count: %d | preserved file counter: %d | preserved rootfs counter: %d%s%s\n", currentIP, memoryCounter, fileCounter, rootfsCounter, secondFileCounterStr, fileContentStr) slog.InfoContext(ctx, "Handled request", slog.String("response", response)) w.WriteHeader(http.StatusOK) diff --git a/docs/api-guide.md b/docs/api-guide.md index b2f6d956bc..c060d86d8b 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -123,7 +123,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). | | `sandboxConfig` | `SandboxConfig` | **Required.** The sandbox runtime selection: `sandboxClass` (**required**, `SANDBOX_CLASS_GVISOR` or `SANDBOX_CLASS_MICROVM`) picks the runtime family this template's actors require — only `WorkerPool`s whose `sandboxClasses` entry matches are eligible — and `configName` (**required**) names the cluster-scoped [`SandboxConfig`](#3-sandboxconfig-the-sandbox-itself) object supplying the sandbox binaries. It must reference an existing config of the matching class; `CreateActorTemplate` rejects the template otherwise. | | `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`). | -| `snapshotConfig` | `SnapshotConfig` | **Required.** The base object-storage location snapshots are written under, plus the snapshot fidelity (`preferredFidelity`) every Pause and Suspend captures. See [Snapshot Storage Layout](#snapshot-storage-layout). | +| `snapshotConfig` | `SnapshotConfig` | **Required.** The base object-storage location snapshots are written under, plus the snapshot fidelity (`preferredFidelity`) every Pause and Suspend captures; `SNAPSHOT_FIDELITY_ROOTFS` requires `sandboxClass: SANDBOX_CLASS_MICROVM`. See [Snapshot Storage Layout](#snapshot-storage-layout). | | `volumes` | `[]Volume` | Optional. Volumes the containers may mount, each a `durableDir`, an `externalVolumeTemplate` (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 `microvm` template may declare several `durableDir` volumes; a `gvisor` template is limited to one. | | `resources` | `*ResourceRequirements` | Optional. Declares each actor's compute size via `limits` — see [Sandbox Right-Sizing](#sandbox-right-sizing-resources). Immutable, like the rest of the template. | diff --git a/docs/architecture.md b/docs/architecture.md index 45593ab924..49886b63ec 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -335,7 +335,7 @@ A `WorkerPool` selects a **sandbox class** (`spec.sandboxClasses[].name`), and e * **gVisor** (`ateom-gvisor`, the default): Runs the workload under `runsc` for kernel-level sandboxing. Suspend and resume leverage gVisor's native checkpoint/restore of the sandboxed process tree. - * **micro-VM** (`ateom-microvm`): Runs the workload inside a [Kata Containers](https://katacontainers.io/) guest on the [Cloud Hypervisor](https://www.cloudhypervisor.org/) VMM. Suspend and resume capture a memory-only VM snapshot and restore it on-demand using `userfaultfd` memory demand-paging. Container rootfs writes are host-backed: the overlay is assembled on the host (read-only OCI image lower plus a per-actor writable upper) and served to the guest over the single virtio-fs share, so they cost reclaimable host page cache rather than guest RAM, and a `MEMORY` snapshot ships the upper as its own tar. `DurableDir` volumes travel over that same share and are likewise shipped as a tar, so a `VOLUMES` snapshot can capture them without any guest memory. Each volume is a subdirectory of the share, so an actor can have several at no extra cost in devices, which is why the micro-VM class lifts the single-`DurableDir` limit that still applies to gVisor. + * **micro-VM** (`ateom-microvm`): Runs the workload inside a [Kata Containers](https://katacontainers.io/) guest on the [Cloud Hypervisor](https://www.cloudhypervisor.org/) VMM. Suspend and resume capture a memory-only VM snapshot and restore it on-demand using `userfaultfd` memory demand-paging. Container rootfs writes are host-backed: the overlay is assembled on the host (read-only OCI image lower plus a per-actor writable upper) and served to the guest over the single virtio-fs share, so they cost reclaimable host page cache rather than guest RAM, and `ROOTFS` and `MEMORY` snapshots ship the upper as its own tar (a `ROOTFS` restore puts it back and cold-boots on it). `DurableDir` volumes travel over that same share and are likewise shipped as a tar, so a `VOLUMES` snapshot can capture them without any guest memory. Each volume is a subdirectory of the share, so an actor can have several at no extra cost in devices, which is why the micro-VM class lifts the single-`DurableDir` limit that still applies to gVisor. ### Networking Stack (`atenet` + `atunnel`) diff --git a/docs/glossary.md b/docs/glossary.md index ca6a17fe15..cb1f88ab0e 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -116,8 +116,10 @@ for etcd. without the cost of a memory image. On Resume the containers start afresh from the OCI image with the `DurableDir` contents restored. - **`ROOTFS`**: volumes plus the root filesystem changes made since boot. - Defined in the API but not supported by any sandbox runtime yet; - templates that request it are rejected. + Process memory is discarded; on Resume the containers cold-boot from the + OCI image with their rootfs writes back in place. Served by the micro-VM + sandbox class only: gVisor keeps rootfs changes inside its memory + checkpoint, so a gVisor template that requests it is rejected. - **`MEMORY`**: volumes, rootfs changes, and process memory. Used to capture everything needed to resume hot. diff --git a/internal/e2e/suites/demo/demo_test.go b/internal/e2e/suites/demo/demo_test.go index 2900689c6c..1b2ea0dae4 100644 --- a/internal/e2e/suites/demo/demo_test.go +++ b/internal/e2e/suites/demo/demo_test.go @@ -220,54 +220,93 @@ func TestDurableDirLifecycle(t *testing.T) { fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, wantMemoryAfterPause: 2, wantFileAfterPause: 2, + wantRootfsAfterPause: 2, wantMemoryAfterSuspend: 3, wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 3, }, }, { - // Pause captures the same Data scope as suspend, so memory - // cold-boots on every resume. + // Pause captures the same VOLUMES fidelity as suspend, so memory + // and rootfs writes cold-boot on every resume. name: "preferredFidelity:VOLUMES", tc: actorLifecycleTestCase{ fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, wantMemoryAfterPause: 1, wantFileAfterPause: 2, + wantRootfsAfterPause: 1, wantMemoryAfterSuspend: 1, wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 1, }, }, { - // Suspend from PAUSED uploads the Full local snapshot. + // ROOTFS keeps the rootfs writes across a cold boot: the memory + // counter restarts while the rootfs counter continues. + name: "preferredFidelity:ROOTFS", + tc: actorLifecycleTestCase{ + fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + wantMemoryAfterPause: 1, + wantFileAfterPause: 2, + wantRootfsAfterPause: 2, + wantMemoryAfterSuspend: 1, + wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 3, + microVMOnly: true, + }, + }, + { + // Suspend from PAUSED uploads the MEMORY local snapshot. name: "preferredFidelity:MEMORY, suspend from PAUSED", tc: actorLifecycleTestCase{ fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, wantMemoryAfterPause: 2, wantFileAfterPause: 2, + wantRootfsAfterPause: 2, wantMemoryAfterSuspend: 3, wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 3, wantSnapshotFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, suspendWhilePaused: true, }, }, { - // Suspend from PAUSED uploads the Data local snapshot. + // Suspend from PAUSED uploads the VOLUMES local snapshot. name: "preferredFidelity:VOLUMES, suspend from PAUSED", tc: actorLifecycleTestCase{ fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, wantMemoryAfterPause: 1, wantFileAfterPause: 2, + wantRootfsAfterPause: 1, wantMemoryAfterSuspend: 1, wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 1, wantSnapshotFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, suspendWhilePaused: true, }, }, + { + // Suspend from PAUSED uploads the ROOTFS local snapshot. + name: "preferredFidelity:ROOTFS, suspend from PAUSED", + tc: actorLifecycleTestCase{ + fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + wantMemoryAfterPause: 1, + wantFileAfterPause: 2, + wantRootfsAfterPause: 2, + wantMemoryAfterSuspend: 1, + wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 3, + wantSnapshotFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + suspendWhilePaused: true, + microVMOnly: true, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.tc.microVMOnly && !e2e.IsMicroVM() { - t.Skipf("Skipping %s: micro-VM-only case (durable-data extraction from a Full capture)", test.name) + t.Skipf("Skipping %s: only the micro-VM runtime serves this fidelity", test.name) } t.Parallel() runActorLifecycleTestCase(t, "durabledir-lifecycle", createActorTemplate, test.tc) @@ -307,6 +346,20 @@ func TestMultipleDurableDirLifecycle(t *testing.T) { checkSecondFileCounter: true, }, }, + { + name: "preferredFidelity:ROOTFS", + tc: actorLifecycleTestCase{ + fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + wantMemoryAfterPause: 1, + wantFileAfterPause: 2, + wantRootfsAfterPause: 2, + wantMemoryAfterSuspend: 1, + wantFileAfterSuspend: 3, + wantRootfsAfterSuspend: 3, + checkSecondFileCounter: true, + microVMOnly: true, + }, + }, } for _, test := range tests { @@ -514,6 +567,12 @@ type actorLifecycleTestCase struct { wantMemoryAfterSuspend int wantFileAfterSuspend int + // wantRootfsAfterPause and wantRootfsAfterSuspend assert the counter the + // workload keeps in a file on its root filesystem: ROOTFS and MEMORY + // snapshots carry it, VOLUMES resets it. Zero skips the check. + wantRootfsAfterPause int + wantRootfsAfterSuspend int + // checkSecondFileCounter also asserts the counter kept in a SECOND durable // volume. Both volumes are written on every request, so it must track the // first counter exactly — if one volume were dropped or restored into the @@ -585,6 +644,9 @@ func runActorLifecycleTestCase(t *testing.T, prefix string, createTemplate func( if tc.checkSecondFileCounter { validateSecondFileCounter(t, resp, "after creation", 1) } + if tc.wantRootfsAfterPause != 0 { + validateRootfsCounter(t, resp, "after creation", 1) + } // // Pausing the actor @@ -614,6 +676,9 @@ func runActorLifecycleTestCase(t *testing.T, prefix string, createTemplate func( if tc.checkSecondFileCounter { validateSecondFileCounter(t, resp, "after pause", tc.wantFileAfterPause) } + if tc.wantRootfsAfterPause != 0 { + validateRootfsCounter(t, resp, "after pause", tc.wantRootfsAfterPause) + } // // Suspending the actor @@ -670,6 +735,9 @@ func runActorLifecycleTestCase(t *testing.T, prefix string, createTemplate func( if tc.checkSecondFileCounter { validateSecondFileCounter(t, resp, "after suspend", tc.wantFileAfterSuspend) } + if tc.wantRootfsAfterSuspend != 0 { + validateRootfsCounter(t, resp, "after suspend", tc.wantRootfsAfterSuspend) + } } // validateSnapshotFidelity asserts the content scope recorded on the @@ -750,6 +818,15 @@ func validateSecondFileCounter(t *testing.T, resp string, stage string, want int } } +// validateRootfsCounter asserts the counter the workload keeps in a file on +// its root filesystem, the one layer that tells ROOTFS apart from VOLUMES. +func validateRootfsCounter(t *testing.T, resp string, stage string, want int) { + t.Helper() + if !strings.Contains(resp, fmt.Sprintf("preserved rootfs counter: %d", want)) { + t.Errorf("[%s] expected rootfs counter %d, got response: %s", stage, want, resp) + } +} + func validateCounterResponse(t *testing.T, resp string, stage string, wantMemory, wantFile int) { memoryCounterPrefix := "preserved memory count: " fileCounterPrefix := "preserved file counter: " diff --git a/internal/e2e/suites/demo/updatetemplate_test.go b/internal/e2e/suites/demo/updatetemplate_test.go index b689189043..bd5978bfd0 100644 --- a/internal/e2e/suites/demo/updatetemplate_test.go +++ b/internal/e2e/suites/demo/updatetemplate_test.go @@ -46,9 +46,16 @@ func TestUpdateTemplateLifecycle(t *testing.T) { name: "preferredFidelity:MEMORY", fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, }, + { + name: "preferredFidelity:ROOTFS", + fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if test.fidelity == ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS && !e2e.IsMicroVM() { + t.Skipf("Skipping %s: only the micro-VM runtime serves this fidelity", test.name) + } t.Parallel() runUpdateTemplateTestCase(t, test.fidelity) }) @@ -118,6 +125,7 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapipb.SnapshotFidelity) t.Fatalf("failed to call actor (call %d): %v", i, err) } validateCounterResponse(t, resp, "under template A", i, i) + validateRootfsCounter(t, resp, "under template A", i) } // @@ -184,6 +192,9 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapipb.SnapshotFidelity) t.Fatalf("failed to call actor after template update: %v", err) } validateCounterResponse(t, resp, "after template update", 1, 3) + // The repoint drops the restore to VOLUMES whatever the snapshot holds: + // a rootfs upper is only valid over the image it was written on. + validateRootfsCounter(t, resp, "after template update", 1) if want := "file content: 3"; !strings.Contains(resp, want) { t.Errorf("[after template update] expected %q (template B validating the preserved file), got response: %s", want, resp) } @@ -192,7 +203,7 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapipb.SnapshotFidelity) // committed snapshot from template A. Resuming from PAUSED restores the // local checkpoint (which was captured under template B, since templates // can only be updated while SUSPENDED) and preserves the in-memory counter - // when preferredFidelity is FULL. + // when preferredFidelity is MEMORY. t.Logf("Pausing Actor %q under template B...", actorID) if _, err := clients.SubstrateAPI.PauseActor(ctx, &ateapipb.PauseActorRequest{ Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, @@ -223,11 +234,18 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapipb.SnapshotFidelity) if err != nil { t.Fatalf("failed to call actor after pause/resume under template B: %v", err) } - wantMemAfterPause := 2 - if fidelity == ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES { + // The pause snapshot was captured under B, so it restores at its own + // fidelity: MEMORY keeps both counters, ROOTFS keeps the rootfs one, and + // VOLUMES neither. + wantMemAfterPause, wantRootfsAfterPause := 2, 2 + switch fidelity { + case ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES: + wantMemAfterPause, wantRootfsAfterPause = 1, 1 + case ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS: wantMemAfterPause = 1 } validateCounterResponse(t, resp, "after pause/resume under template B", wantMemAfterPause, 4) + validateRootfsCounter(t, resp, "after pause/resume under template B", wantRootfsAfterPause) // Revert while running under template B: the actor goes back to SUSPENDED // at the external snapshot it still holds, which is template A's. The spec @@ -278,6 +296,7 @@ func runUpdateTemplateTestCase(t *testing.T, fidelity ateapipb.SnapshotFidelity) // and this call takes it to 3 -- the same value the first resume under B // produced, now reached a second time from the same snapshot. validateCounterResponse(t, resp, "after revert under template B", 1, 3) + validateRootfsCounter(t, resp, "after revert under template B", 1) if want := "file content: 3"; !strings.Contains(resp, want) { t.Errorf("[after revert under template B] expected %q (template B reading the rewound file), got response: %s", want, resp) } diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index e11f9cc298..9e318ed685 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -151,8 +151,8 @@ const ( // (currently DurableDir-typed volumes). Memory and the rest of rootfs are // excluded. SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES SnapshotFidelity = 1 - // Volumes plus the root filesystem changes made since boot. No runtime - // captures this yet; requests carrying it are rejected. + // Volumes plus the root filesystem changes made since boot. Served by the + // micro-VM runtime only; other runtimes reject requests carrying it. SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS SnapshotFidelity = 2 // Capture process memory plus the full filesystem delta on top of the OCI // image (including any attached DurableDir volumes). @@ -2879,11 +2879,10 @@ type UploadPausedCheckpointRequest struct { LocalSnapshotName string `protobuf:"bytes,6,opt,name=local_snapshot_name,json=localSnapshotName,proto3" json:"local_snapshot_name,omitempty"` // Destination object-storage URI (the actor's in-progress snapshot URI). DestinationSnapshotUri string `protobuf:"bytes,7,opt,name=destination_snapshot_uri,json=destinationSnapshotUri,proto3" json:"destination_snapshot_uri,omitempty"` - // Fidelity the uploaded snapshot must have (MEMORY or VOLUMES). The - // fidelity the pause checkpoint captured is not sent: atelet reads it from - // the local snapshot's own manifest, which is authoritative. When they - // differ, atelet converts where possible (a MEMORY capture to a VOLUMES - // upload of the volume files ateom reported) and rejects otherwise. + // Fidelity the uploaded snapshot must have. The fidelity the pause + // checkpoint captured is not sent: atelet reads it from the local + // snapshot's own manifest, which is authoritative, and rejects the upload + // if the two differ. DesiredFidelity SnapshotFidelity `protobuf:"varint,8,opt,name=desired_fidelity,json=desiredFidelity,proto3,enum=atelet.SnapshotFidelity" json:"desired_fidelity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index 034e7e03dc..35cf61db1f 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -518,8 +518,8 @@ enum SnapshotFidelity { // (currently DurableDir-typed volumes). Memory and the rest of rootfs are // excluded. SNAPSHOT_FIDELITY_VOLUMES = 1; - // Volumes plus the root filesystem changes made since boot. No runtime - // captures this yet; requests carrying it are rejected. + // Volumes plus the root filesystem changes made since boot. Served by the + // micro-VM runtime only; other runtimes reject requests carrying it. SNAPSHOT_FIDELITY_ROOTFS = 2; // Capture process memory plus the full filesystem delta on top of the OCI // image (including any attached DurableDir volumes). @@ -582,11 +582,10 @@ message UploadPausedCheckpointRequest { // Destination object-storage URI (the actor's in-progress snapshot URI). string destination_snapshot_uri = 7; - // Fidelity the uploaded snapshot must have (MEMORY or VOLUMES). The - // fidelity the pause checkpoint captured is not sent: atelet reads it from - // the local snapshot's own manifest, which is authoritative. When they - // differ, atelet converts where possible (a MEMORY capture to a VOLUMES - // upload of the volume files ateom reported) and rejects otherwise. + // Fidelity the uploaded snapshot must have. The fidelity the pause + // checkpoint captured is not sent: atelet reads it from the local + // snapshot's own manifest, which is authoritative, and rejects the upload + // if the two differ. SnapshotFidelity desired_fidelity = 8; } diff --git a/internal/proto/ateompb/ateom.pb.go b/internal/proto/ateompb/ateom.pb.go index 23f5a590c2..87fde50518 100644 --- a/internal/proto/ateompb/ateom.pb.go +++ b/internal/proto/ateompb/ateom.pb.go @@ -46,8 +46,8 @@ const ( // (currently DurableDir-typed volumes). Memory and the rest of rootfs are // excluded. SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES SnapshotFidelity = 1 - // Volumes plus the root filesystem changes made since boot. No runtime - // captures this yet; requests carrying it are rejected. + // Volumes plus the root filesystem changes made since boot. Served by the + // micro-VM runtime only; other runtimes reject requests carrying it. SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS SnapshotFidelity = 2 // Capture process memory plus the full filesystem delta on top of the OCI // image (including any attached DurableDir volumes). diff --git a/internal/proto/ateompb/ateom.proto b/internal/proto/ateompb/ateom.proto index 89755de037..557cfa803b 100644 --- a/internal/proto/ateompb/ateom.proto +++ b/internal/proto/ateompb/ateom.proto @@ -294,8 +294,8 @@ enum SnapshotFidelity { // (currently DurableDir-typed volumes). Memory and the rest of rootfs are // excluded. SNAPSHOT_FIDELITY_VOLUMES = 1; - // Volumes plus the root filesystem changes made since boot. No runtime - // captures this yet; requests carrying it are rejected. + // Volumes plus the root filesystem changes made since boot. Served by the + // micro-VM runtime only; other runtimes reject requests carrying it. SNAPSHOT_FIDELITY_ROOTFS = 2; // Capture process memory plus the full filesystem delta on top of the OCI // image (including any attached DurableDir volumes). diff --git a/internal/resources/validate.go b/internal/resources/validate.go index 104f043435..ff3d411a65 100644 --- a/internal/resources/validate.go +++ b/internal/resources/validate.go @@ -121,21 +121,21 @@ func ValidateActorDirs(actorDirs *ateompb.ActorDirs, fldPath *field.Path) field. } // ValidateSnapshotFidelity rejects a checkpoint or restore request whose -// fidelity no runtime can serve. ROOTFS is defined in the enum but no sandbox -// runtime captures rootfs changes without memory yet, so it is refused here -// as well as at template admission. -func ValidateSnapshotFidelity(fidelity ateompb.SnapshotFidelity, fldPath *field.Path) field.ErrorList { - switch fidelity { - case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY: - return nil - case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED: +// fidelity the calling sandbox runtime cannot serve. Each runtime passes the +// levels it implements; anything else, including an unset fidelity, is +// refused before the request touches the guest. +func ValidateSnapshotFidelity(fidelity ateompb.SnapshotFidelity, supported []ateompb.SnapshotFidelity, fldPath *field.Path) field.ErrorList { + if fidelity == ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED { return field.ErrorList{field.Required(fldPath, "")} - case ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS: - return field.ErrorList{field.Invalid(fldPath, fidelity.String(), "ROOTFS fidelity is not supported yet")} - default: - return field.ErrorList{field.NotSupported(fldPath, fidelity, - []string{ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES.String(), ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY.String()})} } + names := make([]string, 0, len(supported)) + for _, f := range supported { + if f == fidelity { + return nil + } + names = append(names, f.String()) + } + return field.ErrorList{field.NotSupported(fldPath, fidelity, names)} } func validateAbsDir(dir string, fldPath *field.Path) field.ErrorList { diff --git a/internal/resources/validate_test.go b/internal/resources/validate_test.go index cec0e0864a..7db4c4997a 100644 --- a/internal/resources/validate_test.go +++ b/internal/resources/validate_test.go @@ -325,19 +325,26 @@ func testActorDirs() *ateompb.ActorDirs { } func TestValidateSnapshotFidelity(t *testing.T) { + volumes := ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + rootfs := ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS + memory := ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY + all := []ateompb.SnapshotFidelity{volumes, rootfs, memory} + noRootfs := []ateompb.SnapshotFidelity{volumes, memory} for _, tc := range []struct { - name string - fidelity ateompb.SnapshotFidelity - wantErr bool + name string + fidelity ateompb.SnapshotFidelity + supported []ateompb.SnapshotFidelity + wantErr bool }{ - {"volumes", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, false}, - {"memory", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, false}, - {"unspecified", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, true}, - {"rootfs not supported yet", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, true}, - {"outside the enum", ateompb.SnapshotFidelity(99), true}, + {"volumes", volumes, all, false}, + {"memory", memory, all, false}, + {"rootfs where supported", rootfs, all, false}, + {"rootfs where unsupported", rootfs, noRootfs, true}, + {"unspecified", ateompb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, all, true}, + {"outside the enum", ateompb.SnapshotFidelity(99), all, true}, } { t.Run(tc.name, func(t *testing.T) { - errs := ValidateSnapshotFidelity(tc.fidelity, field.NewPath("fidelity")) + errs := ValidateSnapshotFidelity(tc.fidelity, tc.supported, field.NewPath("fidelity")) if gotErr := len(errs) != 0; gotErr != tc.wantErr { t.Fatalf("ValidateSnapshotFidelity(%v) = %v, wantErr %t", tc.fidelity, errs, tc.wantErr) } diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index 6f4145c96b..95d1935c45 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -204,8 +204,9 @@ const ( // Durable volumes only. The actor resumes by cold-booting its containers // from the OCI image with the volumes restored. SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES SnapshotFidelity = 1 - // Volumes plus the root filesystem changes made since boot. Not yet - // supported by any sandbox runtime; templates may not request it. + // Volumes plus the root filesystem changes made since boot. The actor + // cold-boots on resume with its rootfs writes back in place. Served by the + // micro-VM sandbox class only. SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS SnapshotFidelity = 2 // Volumes, root filesystem changes, and process memory. The actor resumes // where it left off. @@ -3080,13 +3081,13 @@ type SnapshotConfig struct { // more than this level, and it may package less when a layer cannot be // captured or is too expensive to capture at the time, for example memory // under host pressure. A process resumed in place on its worker keeps its - // memory regardless of this setting. ROOTFS is not supported yet and is - // rejected. Defaults to MEMORY when unset. + // memory regardless of this setting. ROOTFS requires + // sandbox_config.sandbox_class SANDBOX_CLASS_MICROVM. Defaults to MEMORY + // when unset. // // +k8s:required // +k8s:minimum=1 // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum - // +k8s:customValidation # ROOTFS is not supported yet PreferredFidelity SnapshotFidelity `protobuf:"varint,2,opt,name=preferred_fidelity,json=preferredFidelity,proto3,enum=ateapi.SnapshotFidelity" json:"preferred_fidelity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -4504,7 +4505,7 @@ type CreateActorTemplateRequest struct { // version, timestamps) is ignored, as are the status fields. // // +k8s:required - // +k8s:customValidation # volume_mounts must reference declared volumes + // +k8s:customValidation # volume_mounts must reference declared volumes; ROOTFS fidelity requires the micro-VM sandbox class ActorTemplate *ActorTemplate `protobuf:"bytes,1,opt,name=actor_template,json=actorTemplate,proto3" json:"actor_template,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index 138cb6967c..3d0cb6b7b9 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -304,8 +304,9 @@ enum SnapshotFidelity { // Durable volumes only. The actor resumes by cold-booting its containers // from the OCI image with the volumes restored. SNAPSHOT_FIDELITY_VOLUMES = 1; - // Volumes plus the root filesystem changes made since boot. Not yet - // supported by any sandbox runtime; templates may not request it. + // Volumes plus the root filesystem changes made since boot. The actor + // cold-boots on resume with its rootfs writes back in place. Served by the + // micro-VM sandbox class only. SNAPSHOT_FIDELITY_ROOTFS = 2; // Volumes, root filesystem changes, and process memory. The actor resumes // where it left off. @@ -1145,13 +1146,13 @@ message SnapshotConfig { // more than this level, and it may package less when a layer cannot be // captured or is too expensive to capture at the time, for example memory // under host pressure. A process resumed in place on its worker keeps its - // memory regardless of this setting. ROOTFS is not supported yet and is - // rejected. Defaults to MEMORY when unset. + // memory regardless of this setting. ROOTFS requires + // sandbox_config.sandbox_class SANDBOX_CLASS_MICROVM. Defaults to MEMORY + // when unset. // // +k8s:required // +k8s:minimum=1 // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum - // +k8s:customValidation # ROOTFS is not supported yet SnapshotFidelity preferred_fidelity = 2; } @@ -1556,7 +1557,7 @@ message CreateActorTemplateRequest { // version, timestamps) is ignored, as are the status fields. // // +k8s:required - // +k8s:customValidation # volume_mounts must reference declared volumes + // +k8s:customValidation # volume_mounts must reference declared volumes; ROOTFS fidelity requires the micro-VM sandbox class ActorTemplate actor_template = 1; } From 019ee9ab74fe8c7e691dbae150d9ebd3a9c78301 Mon Sep 17 00:00:00 2001 From: ktsoator Date: Sun, 11 Oct 2026 17:12:58 +0000 Subject: [PATCH 5/9] fix(kubectl-ate): validate atespace flags before connecting (#2464) `get actors` and `get actor-template` now reject missing or conflicting atespace flags before loading kubeconfig or connecting to the API. Previously, a kubeconfig or connection error could hide the argument error. The tests use a missing kubeconfig to check that invalid flags are rejected first and valid queries still reach client initialization. Refs #2463 - [x] Tests pass: `go test -race ./cmd/kubectl-ate/...` - [x] Full Go suite passes: `env -u NO_COLOR go test -race -p 1 ./...` - [x] Lint passes: `hack/verify/golangci-lint.sh` The first full-suite run failed in the CPU timing test and in color-output tests with `NO_COLOR=1`. The serial run above passed. The Python license and proto checks could not finish because `pypi.org` DNS lookup failed. The remaining repository verifiers passed; metrics and shellcheck used local binaries at the versions pinned by the scripts. --- cmd/kubectl-ate/internal/cmd/actor.go | 35 +++--- cmd/kubectl-ate/internal/cmd/actortemplate.go | 35 +++--- cmd/kubectl-ate/internal/cmd/get_test.go | 103 ++++++++++++++++++ 3 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 cmd/kubectl-ate/internal/cmd/get_test.go diff --git a/cmd/kubectl-ate/internal/cmd/actor.go b/cmd/kubectl-ate/internal/cmd/actor.go index ea74e334f6..ab993b6638 100644 --- a/cmd/kubectl-ate/internal/cmd/actor.go +++ b/cmd/kubectl-ate/internal/cmd/actor.go @@ -63,14 +63,6 @@ var getActorsCmd = &cobra.Command{ Aliases: []string{"actor"}, Short: "List all actors or get one or more actors", RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - - apiClient, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled) - if err != nil { - return fmt.Errorf("failed to connect to ate-api-server: %w", err) - } - defer apiClient.Close() - if len(args) > 0 { // An actor is addressed by (atespace, name), so the atespace is // mandatory and "all atespaces" is meaningless here. @@ -80,7 +72,25 @@ var getActorsCmd = &cobra.Command{ if getActorAtespaceFlag == "" { return fmt.Errorf("--atespace is required when getting actors") } + } else { + // Listing requires exactly one of --atespace (one atespace) or -A (all + // atespaces). There is no default atespace to fall back on. + if getActorAllAtespacesFlag && getActorAtespaceFlag != "" { + return fmt.Errorf("--atespace and -A/--all-atespaces are mutually exclusive") + } + if !getActorAllAtespacesFlag && getActorAtespaceFlag == "" { + return fmt.Errorf("specify --atespace to list one atespace, or -A/--all-atespaces for all") + } + } + ctx := cmd.Context() + apiClient, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled) + if err != nil { + return fmt.Errorf("failed to connect to ate-api-server: %w", err) + } + defer apiClient.Close() + + if len(args) > 0 { actors := make([]*ateapipb.Actor, 0, len(args)) for _, actorName := range args { actorRef := resources.ActorRef{Atespace: getActorAtespaceFlag, Name: actorName} @@ -96,15 +106,6 @@ var getActorsCmd = &cobra.Command{ return printer.PrintActorsTo(cmd.OutOrStdout(), actors, outputFmt) } - // Listing requires exactly one of --atespace (one atespace) or -A (all - // atespaces). There is no default atespace to fall back on. - if getActorAllAtespacesFlag && getActorAtespaceFlag != "" { - return fmt.Errorf("--atespace and -A/--all-atespaces are mutually exclusive") - } - if !getActorAllAtespacesFlag && getActorAtespaceFlag == "" { - return fmt.Errorf("specify --atespace to list one atespace, or -A/--all-atespaces for all") - } - var allActors []*ateapipb.Actor pageToken := "" for { diff --git a/cmd/kubectl-ate/internal/cmd/actortemplate.go b/cmd/kubectl-ate/internal/cmd/actortemplate.go index db17459a7c..98b19f76d7 100644 --- a/cmd/kubectl-ate/internal/cmd/actortemplate.go +++ b/cmd/kubectl-ate/internal/cmd/actortemplate.go @@ -40,14 +40,6 @@ var getActorTemplatesCmd = &cobra.Command{ Short: "List all actor templates or get one or more actor templates", Long: "List or get actor templates.", RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - - apiClient, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled) - if err != nil { - return fmt.Errorf("failed to connect to ate-api-server: %w", err) - } - defer apiClient.Close() - if len(args) > 0 { // A template is addressed by (atespace, name), so the atespace is // mandatory and "all atespaces" is meaningless here. @@ -57,7 +49,25 @@ var getActorTemplatesCmd = &cobra.Command{ if getActorTemplateAtespaceFlag == "" { return fmt.Errorf("--atespace is required when getting actor templates") } + } else { + // Listing requires exactly one of --atespace (one atespace) or -A (all + // atespaces). There is no default atespace to fall back on. + if getActorTemplateAllAtespacesFlag && getActorTemplateAtespaceFlag != "" { + return fmt.Errorf("--atespace and -A/--all-atespaces are mutually exclusive") + } + if !getActorTemplateAllAtespacesFlag && getActorTemplateAtespaceFlag == "" { + return fmt.Errorf("specify --atespace to list one atespace, or -A/--all-atespaces for all") + } + } + ctx := cmd.Context() + apiClient, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled) + if err != nil { + return fmt.Errorf("failed to connect to ate-api-server: %w", err) + } + defer apiClient.Close() + + if len(args) > 0 { templates := make([]*ateapipb.ActorTemplate, 0, len(args)) for _, name := range args { resp, err := apiClient.GetActorTemplate(ctx, &ateapipb.GetActorTemplateRequest{ @@ -74,15 +84,6 @@ var getActorTemplatesCmd = &cobra.Command{ return printer.PrintActorTemplatesTo(cmd.OutOrStdout(), templates, outputFmt) } - // Listing requires exactly one of --atespace (one atespace) or -A (all - // atespaces). There is no default atespace to fall back on. - if getActorTemplateAllAtespacesFlag && getActorTemplateAtespaceFlag != "" { - return fmt.Errorf("--atespace and -A/--all-atespaces are mutually exclusive") - } - if !getActorTemplateAllAtespacesFlag && getActorTemplateAtespaceFlag == "" { - return fmt.Errorf("specify --atespace to list one atespace, or -A/--all-atespaces for all") - } - var allTemplates []*ateapipb.ActorTemplate pageToken := "" for { diff --git a/cmd/kubectl-ate/internal/cmd/get_test.go b/cmd/kubectl-ate/internal/cmd/get_test.go new file mode 100644 index 0000000000..1b5aac6655 --- /dev/null +++ b/cmd/kubectl-ate/internal/cmd/get_test.go @@ -0,0 +1,103 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func TestGetScopeBeforeConnecting(t *testing.T) { + previousKubeconfig := kubeconfig + kubeconfig = filepath.Join(t.TempDir(), "missing-kubeconfig") + t.Cleanup(func() { kubeconfig = previousKubeconfig }) + + for _, resource := range []struct { + name string + command *cobra.Command + atespace *string + all *bool + }{ + {"actors", getActorsCmd, &getActorAtespaceFlag, &getActorAllAtespacesFlag}, + {"actor templates", getActorTemplatesCmd, &getActorTemplateAtespaceFlag, &getActorTemplateAllAtespacesFlag}, + } { + t.Run(resource.name, func(t *testing.T) { + previousAtespace, previousAll := *resource.atespace, *resource.all + previousContext := resource.command.Context() + resource.command.SetContext(t.Context()) + t.Cleanup(func() { + *resource.atespace, *resource.all = previousAtespace, previousAll + resource.command.SetContext(previousContext) + }) + + for _, test := range []struct { + name string + args []string + atespace string + all bool + wantErr string + }{ + { + name: "get without atespace", args: []string{"counter"}, + wantErr: "--atespace is required when getting", + }, + { + name: "get with all atespaces", args: []string{"counter"}, all: true, + wantErr: "-A/--all-atespaces cannot be used when getting", + }, + { + name: "get with both scopes", args: []string{"counter"}, atespace: "demo", all: true, + wantErr: "-A/--all-atespaces cannot be used when getting", + }, + { + name: "list without scope", + wantErr: "specify --atespace to list one atespace, or -A/--all-atespaces for all", + }, + { + name: "list with both scopes", atespace: "demo", all: true, + wantErr: "--atespace and -A/--all-atespaces are mutually exclusive", + }, + // Valid scopes reach client initialization, where the missing kubeconfig fails. + { + name: "get in atespace", args: []string{"counter"}, atespace: "demo", + wantErr: "failed to load kubeconfig", + }, + { + name: "get multiple in atespace", args: []string{"counter", "other"}, atespace: "demo", + wantErr: "failed to load kubeconfig", + }, + { + name: "list in atespace", atespace: "demo", + wantErr: "failed to load kubeconfig", + }, + { + name: "list all atespaces", all: true, + wantErr: "failed to load kubeconfig", + }, + } { + t.Run(test.name, func(t *testing.T) { + *resource.atespace, *resource.all = test.atespace, test.all + err := resource.command.RunE(resource.command, test.args) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("RunE() error = %v, want error containing %q", err, test.wantErr) + } + }) + } + }) + } +} From 29c049fc867509e4f279e64fae8175911c139c1b Mon Sep 17 00:00:00 2001 From: cloudonly Date: Sun, 11 Oct 2026 17:51:25 +0000 Subject: [PATCH 6/9] Allow ActorTemplates to disable golden snapshot creation (#2328) ActorTemplates can opt out of automated golden snapshot creation with `snapshotConfig.goldenSnapshotConfig.mode: GOLDEN_SNAPSHOT_MODE_DISABLED`, avoiding unused golden actors and snapshots. Omitting the configuration or leaving the mode `UNSPECIFIED` defaults to `ENABLED`. The mode is an enum with declarative validation, following the existing snapshot fidelity pattern. Disabled templates are skipped during queue resync and reconciliation. Their regular actors can cold boot and suspend/resume using their own snapshots. `kubectl ate get actor-templates` displays `DISABLED` in the `GOLDEN TAG` column. The API guide, field table, glossary, and CLI documentation describe the opt-out. The functional regression starts a real `ActorTemplateReconciler`. An enabled control template proves it is making progress while the disabled template's golden actor and tag both remain absent. The test also verifies the disabled template's actor cold-boots, creates its own durable snapshot on suspend, and restores that snapshot on resume. The disabled reconciliation case is part of the existing table-driven unit test. Implements the golden snapshot opt-out feature discussed in #2068. AI assistance: Codex helped implement and test these changes. ### Validation - macOS arm64: race and coverage tests passed for defaulting, API validation, control-plane unit tests, and CLI printing. - The CLI table regression failed before adding the disabled state and passed after the change. - Linux amd64, Go 1.27.1: `go test -race ./...` passed for all 147 packages with tests. - With `CI=true`, fresh race tests for control-plane functional tests and PostgreSQL storage passed: 612 tests/subtests, zero failures and zero skips. - Removing both disabled-mode guards made the lifecycle regression fail because it found the disabled template's golden actor. The complete implementation passed. - `hack/verify-all.sh`: all 14 verifiers passed with unmodified settings. The first attempt hit a linter package-loading timeout; a longer standalone lint run reported `0 issues`, and the standard verifier run then passed after the cache warmed. - Linux builds of `ateapi` and `kubectl-ate` passed. Functional tests use real PostgreSQL and an envtest Kubernetes API with a fake atelet. Real gVisor E2E was not run. - [x] Appropriate changes to documentation are included in the PR. - [x] Race tests, repository verifiers, and affected binaries were checked. --------- Co-authored-by: cloudonly <28681487+cloudonly@users.noreply.github.com> --- cmd/ate-setup/internal/steps/substrate.go | 8 +- .../internal/steps/substrate_test.go | 25 + .../apivalidation/actor_template_test.go | 28 + .../apivalidation/zz_generated.validation.go | 74 + .../controlapi/actor_template_test.go | 1 + .../functionaltest/actor_template_test.go | 110 +- .../controlapi/template_reconciler.go | 7 + .../controlapi/template_reconciler_test.go | 32 +- cmd/ateapi/internal/defaults/defaults.go | 6 + cmd/ateapi/internal/defaults/defaults_test.go | 47 +- .../store/atepg/actor_template_test.go | 3 +- cmd/kubectl-ate/README.md | 2 +- cmd/kubectl-ate/internal/printer/printer.go | 6 +- .../internal/printer/printer_test.go | 12 + docs/api-guide.md | 19 +- docs/glossary.md | 12 +- internal/e2e/template.go | 10 +- internal/e2e/template_test.go | 48 + pkg/proto/ateapipb/ateapi.pb.go | 1410 +++++++++-------- pkg/proto/ateapipb/ateapi.proto | 24 + 20 files changed, 1219 insertions(+), 665 deletions(-) create mode 100644 internal/e2e/template_test.go diff --git a/cmd/ate-setup/internal/steps/substrate.go b/cmd/ate-setup/internal/steps/substrate.go index 9ca2acd117..76bd0b4f55 100644 --- a/cmd/ate-setup/internal/steps/substrate.go +++ b/cmd/ate-setup/internal/steps/substrate.go @@ -85,7 +85,8 @@ func CreateActorTemplate(ctx context.Context, client *ateclient.Client, template } // WaitActorTemplateGolden blocks until the template's golden snapshot is -// built. It fails fast when the template reconciler reports an error. +// built. It fails fast when the template reconciler reports an error, and +// returns immediately for a template that opted out of the golden snapshot. func WaitActorTemplateGolden(ctx context.Context, client *ateclient.Client, ref resources.ActorTemplateRef, timeout time.Duration) error { deadline := time.Now().Add(timeout) // The last Get error rides along in the timeout error: a wait that spent @@ -96,6 +97,11 @@ func WaitActorTemplateGolden(ctx context.Context, client *ateclient.Client, ref template, err := client.GetActorTemplate(ctx, &ateapipb.GetActorTemplateRequest{ActorTemplate: ref.ToObjectRef()}) lastErr = err if err == nil { + if template.GetSnapshotConfig().GetGoldenSnapshotConfig().GetMode() == ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED { + // The template opted out: no golden snapshot will ever appear, + // and none is needed. + return nil + } goldenStatus := template.GetStatus().GetGoldenSnapshotStatus() if goldenStatus.GetGoldenTag().GetName() != "" { return nil diff --git a/cmd/ate-setup/internal/steps/substrate_test.go b/cmd/ate-setup/internal/steps/substrate_test.go index e240884325..2f192e8122 100644 --- a/cmd/ate-setup/internal/steps/substrate_test.go +++ b/cmd/ate-setup/internal/steps/substrate_test.go @@ -165,3 +165,28 @@ func TestWaitActorTemplateGoldenTimeoutIncludesLastError(t *testing.T) { t.Errorf("WaitActorTemplateGolden error = %v, want it to wrap %v", err, getErr) } } + +// disabledTemplateClient serves one ActorTemplate that opted out of the +// golden snapshot and has no golden status. +type disabledTemplateClient struct { + ateapipb.ControlClient +} + +func (disabledTemplateClient) GetActorTemplate(_ context.Context, in *ateapipb.GetActorTemplateRequest, _ ...grpc.CallOption) (*ateapipb.ActorTemplate, error) { + return &ateapipb.ActorTemplate{ + Metadata: &ateapipb.ResourceMetadata{Atespace: in.GetActorTemplate().GetAtespace(), Name: in.GetActorTemplate().GetName()}, + SnapshotConfig: &ateapipb.SnapshotConfig{GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED}}, + }, nil +} + +// TestWaitActorTemplateGoldenDisabledTemplateReady: a template that opted out +// never gets a golden tag or an error, so the wait must treat it as ready +// instead of timing out. +func TestWaitActorTemplateGoldenDisabledTemplateReady(t *testing.T) { + client := &ateclient.Client{ControlClient: disabledTemplateClient{}} + ref := resources.ActorTemplateRef{Atespace: "ate-demo", Name: "counter"} + + if err := WaitActorTemplateGolden(context.Background(), client, ref, 0); err != nil { + t.Fatalf("WaitActorTemplateGolden on a disabled template = %v, want nil", err) + } +} diff --git a/cmd/ateapi/internal/apivalidation/actor_template_test.go b/cmd/ateapi/internal/apivalidation/actor_template_test.go index c57a3060b5..12d979784d 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template_test.go +++ b/cmd/ateapi/internal/apivalidation/actor_template_test.go @@ -287,6 +287,34 @@ func TestValidateActorTemplate(t *testing.T) { want field.ErrorList }{{ name: "valid", + }, { + name: "golden snapshot enabled without wakeup probe", + mutate: func(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED} + }, + }, { + name: "golden snapshot disabled", + mutate: func(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED} + }, + }, { + name: "golden snapshot mode unspecified", + mutate: func(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{} + }, + want: field.ErrorList{field.Required(field.NewPath("snapshot_config", "golden_snapshot_config", "mode"), "")}, + }, { + name: "golden snapshot mode negative", + mutate: func(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode(-1)} + }, + want: field.ErrorList{field.Invalid(field.NewPath("snapshot_config", "golden_snapshot_config", "mode"), nil, "").WithOrigin("minimum")}, + }, { + name: "golden snapshot mode unknown", + mutate: func(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode(3)} + }, + want: field.ErrorList{field.Invalid(field.NewPath("snapshot_config", "golden_snapshot_config", "mode"), nil, "").WithOrigin("maximum")}, }, { name: "missing metadata", mutate: func(tmpl *ateapipb.ActorTemplate) { tmpl.Metadata = nil }, diff --git a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go index 43363ee262..b780f5ab62 100644 --- a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go +++ b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go @@ -4302,6 +4302,50 @@ func Validate_GetWorkerRequest( return errs } +// Validate_GoldenSnapshotConfig validates an instance of GoldenSnapshotConfig according +// to declarative validation rules in the API schema. +func Validate_GoldenSnapshotConfig( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ateapipb.GoldenSnapshotConfig) (errs field.ErrorList) { + + { // field ateapipb.GoldenSnapshotConfig.Mode + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.GoldenSnapshotMode, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.GoldenSnapshotConfig) *ateapipb.GoldenSnapshotMode { + return &oldObj.Mode + }) + errs = append(errs, fn(fldPath.Child("mode"), &obj.Mode, oldVal, oldObj != nil)...) + } + + return errs +} + // Validate_GoldenSnapshotStatus validates an instance of GoldenSnapshotStatus according // to declarative validation rules in the API schema. func Validate_GoldenSnapshotStatus( @@ -7290,6 +7334,36 @@ func Validate_SnapshotConfig( errs = append(errs, fn(fldPath.Child("preferred_fidelity"), &obj.PreferredFidelity, oldVal, oldObj != nil)...) } + { // field ateapipb.SnapshotConfig.GoldenSnapshotConfig + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.GoldenSnapshotConfig, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if ateDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_GoldenSnapshotConfig(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.SnapshotConfig) *ateapipb.GoldenSnapshotConfig { + return oldObj.GoldenSnapshotConfig + }) + errs = append(errs, fn(fldPath.Child("golden_snapshot_config"), obj.GoldenSnapshotConfig, oldVal, oldObj != nil)...) + } + return errs } diff --git a/cmd/ateapi/internal/controlapi/actor_template_test.go b/cmd/ateapi/internal/controlapi/actor_template_test.go index 07937d0abe..ceb4b2f4b3 100644 --- a/cmd/ateapi/internal/controlapi/actor_template_test.go +++ b/cmd/ateapi/internal/controlapi/actor_template_test.go @@ -181,6 +181,7 @@ func TestCreateActorTemplateIgnoresServerOwnedFields(t *testing.T) { want := validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { tmpl.Metadata.Version = 1 + tmpl.SnapshotConfig.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED} tmpl.WorkerSelector = in.GetWorkerSelector() tmpl.Containers = in.GetContainers() tmpl.Resources = in.GetResources() diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go index ee694af879..1d83e56437 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go @@ -16,6 +16,7 @@ package functionaltest import ( "context" + "fmt" "testing" "time" @@ -62,8 +63,9 @@ func TestActorTemplateCRUD(t *testing.T) { Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "tmpl-a", Version: 1}, Containers: []*ateapipb.Container{{Name: "main", Image: "example.com/app:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}}, SnapshotConfig: &ateapipb.SnapshotConfig{ - StorageLocation: "gs://my-bucket/snapshots", - PreferredFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + StorageLocation: "gs://my-bucket/snapshots", + PreferredFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, }, SandboxConfig: &ateapipb.SandboxConfig{ SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR, @@ -235,6 +237,110 @@ func TestGoldenTagLifecycle(t *testing.T) { } } +func TestActorLifecycleWithoutGoldenSnapshot(t *testing.T) { + ns := namespaceForTest("no-golden") + tc := setupTest(t, ns) + defer tc.cleanup() + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + ensureDefaultGvisorSandboxConfig(t, tc) + createWorkerPool(t, tc, ns, "pool1", map[string]string{poolLabelKey: ns}) + tmpl, err := tc.client.CreateActorTemplate(ctx, &ateapipb.CreateActorTemplateRequest{ActorTemplate: &ateapipb.ActorTemplate{ + Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "no-golden"}, + SnapshotConfig: &ateapipb.SnapshotConfig{ + StorageLocation: testStorageLocation, + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED}, + }, + SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR, ConfigName: "gvisor-default"}, + Containers: []*ateapipb.Container{{Name: "main", Image: "main@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Command: []string{"/main"}}}, + WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{poolLabelKey: ns}}, + }}) + if err != nil { + t.Fatal(err) + } + if tmpl.GetSnapshotConfig().GetGoldenSnapshotConfig().GetMode() != ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED { + t.Fatal("CreateActorTemplate did not preserve the golden snapshot opt-out") + } + workerName := createWorkerPod(t, tc, ns, "worker-1", "node1", "pool1") + controlTemplate := createTemplateWithSelector(t, tc, "golden-control", &ateapipb.Selector{MatchLabels: map[string]string{poolLabelKey: ns}}) + goldenRef := &ateapipb.ObjectRef{Atespace: resources.GoldenActorAtespace, Name: tmpl.GetMetadata().GetUid()} + assertNoGolden := func(ctx context.Context) error { + if _, err := tc.client.GetActor(ctx, &ateapipb.GetActorRequest{Actor: goldenRef}); status.Code(err) != codes.NotFound { + return fmt.Errorf("disabled template's golden actor: got %v, want NotFound", err) + } + if _, err := tc.client.GetTag(ctx, &ateapipb.GetTagRequest{Tag: goldenRef}); status.Code(err) != codes.NotFound { + return fmt.Errorf("disabled template's golden tag: got %v, want NotFound", err) + } + return nil + } + controlapi.NewActorTemplateReconciler(tc.persistence, tc.service, 7*time.Second).Start(ctx) + // The enabled template proves the same reconciler is making progress. + err = wait.PollUntilContextTimeout(ctx, 50*time.Millisecond, 30*time.Second, true, func(ctx context.Context) (bool, error) { + if err := assertNoGolden(ctx); err != nil { + return false, err + } + current, err := tc.client.GetActorTemplate(ctx, &ateapipb.GetActorTemplateRequest{ + ActorTemplate: resources.ActorTemplateRefFromActorTemplate(controlTemplate).ToObjectRef(), + }) + return current.GetStatus().GetGoldenSnapshotStatus().GetGoldenTag() != nil, err + }) + if err != nil { + t.Fatalf("waiting for enabled template's golden tag: %v", err) + } + waitForWorkerAvailable(t, tc, workerName) + actor, err := tc.client.CreateActor(ctx, &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "actor"}, + ActorTemplate: resources.ActorTemplateRefFromActorTemplate(tmpl).ToObjectRef(), + }}) + if err != nil { + t.Fatal(err) + } + if durableSnapshot(actor.GetStatus()) != nil { + t.Fatal("new actor unexpectedly inherited a snapshot") + } + ref := resources.ActorRefFromActor(actor).ToObjectRef() + tc.fakeAtelet.Lock.Lock() + tc.fakeAtelet.RunCalled, tc.fakeAtelet.RestoreCalled = false, false + tc.fakeAtelet.Lock.Unlock() + if _, err := tc.client.ResumeActor(ctx, &ateapipb.ResumeActorRequest{Actor: ref}); err != nil { + t.Fatal(err) + } + tc.fakeAtelet.Lock.Lock() + ran, restored := tc.fakeAtelet.RunCalled, tc.fakeAtelet.RestoreCalled + tc.fakeAtelet.Lock.Unlock() + if !ran || restored { + t.Fatal("first resume did not cold boot") + } + suspended, err := tc.client.SuspendActor(ctx, &ateapipb.SuspendActorRequest{Actor: ref}) + if err != nil { + t.Fatal(err) + } + snapshotURI := durableSnapshotURI(suspended.GetActor().GetStatus()) + if snapshotURI == "" { + t.Fatal("suspend did not produce the actor's own snapshot") + } + assertSnapshotPresent(t, tc, snapshotURI) + waitForWorkerAvailable(t, tc, workerName) + if _, err := tc.client.ResumeActor(ctx, &ateapipb.ResumeActorRequest{Actor: ref}); err != nil { + t.Fatal(err) + } + if got := tc.fakeAtelet.lastRestoreRequest().GetExternalConfig().GetSnapshotUri(); got != snapshotURI { + t.Fatalf("restored snapshot = %q, want actor's snapshot %q", got, snapshotURI) + } + if err := assertNoGolden(ctx); err != nil { + t.Fatal(err) + } + if _, err := tc.client.DeleteActor(ctx, &ateapipb.DeleteActorRequest{Actor: ref, AnyState: true}); err != nil { + t.Fatal(err) + } + if _, err := tc.client.DeleteActorTemplate(ctx, &ateapipb.DeleteActorTemplateRequest{ActorTemplate: resources.ActorTemplateRefFromActorTemplate(tmpl).ToObjectRef()}); err != nil { + t.Fatal(err) + } + if _, err := tc.client.DeleteActorTemplate(ctx, &ateapipb.DeleteActorTemplateRequest{ActorTemplate: resources.ActorTemplateRefFromActorTemplate(controlTemplate).ToObjectRef()}); err != nil { + t.Fatal(err) + } +} + func TestListActorTemplates_InvalidPageToken(t *testing.T) { ns := namespaceForTest("ns-template-invalid-token") tc := setupTest(t, ns) diff --git a/cmd/ateapi/internal/controlapi/template_reconciler.go b/cmd/ateapi/internal/controlapi/template_reconciler.go index 03ef425127..a550395c35 100644 --- a/cmd/ateapi/internal/controlapi/template_reconciler.go +++ b/cmd/ateapi/internal/controlapi/template_reconciler.go @@ -121,6 +121,9 @@ func (r *ActorTemplateReconciler) resync(ctx context.Context) { return } for _, tmpl := range page.Items { + if tmpl.GetSnapshotConfig().GetGoldenSnapshotConfig().GetMode() == ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED { + continue + } ref := resources.ActorTemplateRefFromActorTemplate(tmpl) if goldenSnapshotDone(tmpl.GetStatus().GetGoldenSnapshotStatus()) { slog.DebugContext(ctx, "Skipping actor template with terminal golden snapshot status", slog.String("ActorTemplate", ref.String())) @@ -190,6 +193,10 @@ func (r *ActorTemplateReconciler) reconcileOne(ctx context.Context, ref resource return 0, err } + if tmpl.GetSnapshotConfig().GetGoldenSnapshotConfig().GetMode() == ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED { + return 0, nil + } + goldenActorRef := &ateapipb.ObjectRef{ // Golden actors live in the reserved ate-golden atespace, because // the suspend workflow relies on the ate-golden system atespace to diff --git a/cmd/ateapi/internal/controlapi/template_reconciler_test.go b/cmd/ateapi/internal/controlapi/template_reconciler_test.go index 7effb0c55b..14cc4facf3 100644 --- a/cmd/ateapi/internal/controlapi/template_reconciler_test.go +++ b/cmd/ateapi/internal/controlapi/template_reconciler_test.go @@ -326,6 +326,9 @@ func testTemplate(opts ...func(*ateapipb.ActorTemplate)) *ateapipb.ActorTemplate Containers: []*ateapipb.Container{ {Name: "main", Image: "img", WakeupProbe: &ateapipb.ContainerWakeupProbe{}}, }, + SnapshotConfig: &ateapipb.SnapshotConfig{ + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, + }, Status: &ateapipb.ActorTemplateStatus{}, } for _, opt := range opts { @@ -340,6 +343,10 @@ func withoutWakeupProbe(tmpl *ateapipb.ActorTemplate) { } } +func withoutGoldenSnapshot(tmpl *ateapipb.ActorTemplate) { + tmpl.SnapshotConfig.GoldenSnapshotConfig.Mode = ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED +} + // seededGoldenStatus returns the template's golden snapshot status, allocating // it so the with* options below can mutate it. func seededGoldenStatus(tmpl *ateapipb.ActorTemplate) *ateapipb.GoldenSnapshotStatus { @@ -412,11 +419,18 @@ func TestReconcileOne(t *testing.T) { // wantTag indicates that the golden tag should be recorded. wantTag bool // wantDeadline asserts whether take_golden_snapshot_at is set. - wantDeadline bool - wantCreates int - wantResumes int - wantSuspends int + wantDeadline bool + wantCreates int + wantResumes int + wantSuspends int + wantUnchanged bool }{ + { + name: "golden snapshot disabled is a noop", + template: testTemplate(withoutGoldenSnapshot), + control: &fakeGoldenControl{}, + wantUnchanged: true, + }, { name: "happy path creates, resumes, and snapshots the golden actor", template: testTemplate(), @@ -592,6 +606,15 @@ func TestReconcileOne(t *testing.T) { if tt.template == nil { return } + if tt.wantUnchanged { + got, err := st.GetActorTemplate(t.Context(), testTemplateRef) + if err != nil { + t.Fatal(err) + } + if !proto.Equal(tt.template, got) { + t.Fatalf("template was modified: %v", got) + } + } snapshotStatus := st.storedStatus(t, testTemplateRef).GetGoldenSnapshotStatus() errorMessage := snapshotStatus.GetErrorMessage() if tt.wantFailedReason == "" && tt.wantMessage == "" { @@ -815,6 +838,7 @@ func TestResync_QueuesOnlyActionableTemplates(t *testing.T) { wantQueued bool }{ {"empty status", nil, true}, + {"golden snapshot disabled", []func(*ateapipb.ActorTemplate){withoutGoldenSnapshot}, false}, {"mid warmup", []func(*ateapipb.ActorTemplate){withSnapshotDeadline(time.Now().Add(time.Hour))}, true}, {"golden snapshot taken", []func(*ateapipb.ActorTemplate){withGoldenTag()}, false}, {"failed", []func(*ateapipb.ActorTemplate){withFailed(reasonGoldenActorCrashed)}, false}, diff --git a/cmd/ateapi/internal/defaults/defaults.go b/cmd/ateapi/internal/defaults/defaults.go index 76c8bb2c7b..ff31f792d4 100644 --- a/cmd/ateapi/internal/defaults/defaults.go +++ b/cmd/ateapi/internal/defaults/defaults.go @@ -64,6 +64,12 @@ func applySnapshotConfigDefaults(sc *ateapipb.SnapshotConfig) { if sc.PreferredFidelity == ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED { sc.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY } + if sc.GoldenSnapshotConfig == nil { + sc.GoldenSnapshotConfig = &ateapipb.GoldenSnapshotConfig{} + } + if sc.GoldenSnapshotConfig.Mode == ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_UNSPECIFIED { + sc.GoldenSnapshotConfig.Mode = ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED + } } func applyContainerDefaults(c *ateapipb.Container) { diff --git a/cmd/ateapi/internal/defaults/defaults_test.go b/cmd/ateapi/internal/defaults/defaults_test.go index ca8082713b..5a4c90042c 100644 --- a/cmd/ateapi/internal/defaults/defaults_test.go +++ b/cmd/ateapi/internal/defaults/defaults_test.go @@ -19,6 +19,7 @@ import ( "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" ) @@ -44,7 +45,8 @@ func TestApply(t *testing.T) { name: "empty snapshot_config gets every default", in: &ateapipb.ActorTemplate{SnapshotConfig: &ateapipb.SnapshotConfig{}}, want: &ateapipb.ActorTemplate{SnapshotConfig: &ateapipb.SnapshotConfig{ - PreferredFidelity: scopeFull, + PreferredFidelity: scopeFull, + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, }}, }, { name: "set scopes are kept", @@ -52,7 +54,8 @@ func TestApply(t *testing.T) { PreferredFidelity: scopeData, }}, want: &ateapipb.ActorTemplate{SnapshotConfig: &ateapipb.SnapshotConfig{ - PreferredFidelity: scopeData, + PreferredFidelity: scopeData, + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, }}, }, { name: "container without wakeup probe stays without one", @@ -151,3 +154,43 @@ func TestApply(t *testing.T) { }) } } + +func TestGoldenSnapshotDefaults(t *testing.T) { + for _, tt := range []struct { + name string + config string + mode ateapipb.GoldenSnapshotMode + }{ + {"omitted config", `{}`, ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, + {"omitted mode", `{"goldenSnapshotConfig": {}}`, ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, + {"unspecified mode", `{"goldenSnapshotConfig": {"mode": "GOLDEN_SNAPSHOT_MODE_UNSPECIFIED"}}`, ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, + {"explicit zero", `{"goldenSnapshotConfig": {"mode": 0}}`, ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, + {"enabled", `{"goldenSnapshotConfig": {"mode": "GOLDEN_SNAPSHOT_MODE_ENABLED"}}`, ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, + {"disabled", `{"goldenSnapshotConfig": {"mode": "GOLDEN_SNAPSHOT_MODE_DISABLED"}}`, ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED}, + } { + t.Run(tt.name, func(t *testing.T) { + tmpl := &ateapipb.ActorTemplate{} + if err := protojson.Unmarshal([]byte(`{"snapshotConfig": `+tt.config+`}`), tmpl); err != nil { + t.Fatal(err) + } + // JSON and gRPC use the enum value rather than field presence. + data, err := proto.Marshal(tmpl) + if err != nil { + t.Fatal(err) + } + if err := proto.Unmarshal(data, tmpl); err != nil { + t.Fatal(err) + } + Apply(tmpl) + want := &ateapipb.GoldenSnapshotConfig{Mode: tt.mode} + if diff := cmp.Diff(want, tmpl.GetSnapshotConfig().GetGoldenSnapshotConfig(), protocmp.Transform()); diff != "" { + t.Fatalf("golden snapshot default mismatch (-want +got):\n%s", diff) + } + again := proto.Clone(tmpl) + Apply(again) + if !proto.Equal(tmpl, again) { + t.Fatal("golden snapshot defaulting is not idempotent") + } + }) + } +} diff --git a/cmd/ateapi/internal/store/atepg/actor_template_test.go b/cmd/ateapi/internal/store/atepg/actor_template_test.go index 0e04e5b6c3..6560c1d699 100644 --- a/cmd/ateapi/internal/store/atepg/actor_template_test.go +++ b/cmd/ateapi/internal/store/atepg/actor_template_test.go @@ -97,7 +97,8 @@ func TestGetActorTemplate_BackfillsDefaults(t *testing.T) { t.Fatalf("GetActorTemplate failed: %v", err) } want := &ateapipb.SnapshotConfig{ - PreferredFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + PreferredFidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED}, } if diff := cmp.Diff(want, got.GetSnapshotConfig(), protocmp.Transform()); diff != "" { t.Errorf("GetActorTemplate did not backfill snapshot_config defaults (-want +got):\n%s", diff) diff --git a/cmd/kubectl-ate/README.md b/cmd/kubectl-ate/README.md index 6b3a0f2df5..f021248030 100644 --- a/cmd/kubectl-ate/README.md +++ b/cmd/kubectl-ate/README.md @@ -184,7 +184,7 @@ for a complete manifest example. | `ATESPACE` | The atespace the template belongs to. | | `NAME` | The template's name. | | `SANDBOX CLASS` | The sandbox runtime family (`SANDBOX_CLASS_GVISOR` or `SANDBOX_CLASS_MICROVM`). | -| `GOLDEN TAG` | The golden tag's name once it exists; empty while the golden build is still running. | +| `GOLDEN TAG` | The golden tag's name once it exists; `DISABLED` when the template opts out of golden snapshot creation; empty while the golden build is still running. | | `ERROR` | `ERROR` when the golden build failed; `-o yaml` shows the full message. | | `AGE` | Time elapsed since the template was created. | diff --git a/cmd/kubectl-ate/internal/printer/printer.go b/cmd/kubectl-ate/internal/printer/printer.go index 59c3e309fb..a5e9056c26 100644 --- a/cmd/kubectl-ate/internal/printer/printer.go +++ b/cmd/kubectl-ate/internal/printer/printer.go @@ -295,6 +295,10 @@ func PrintActorTemplatesTo(out io.Writer, templates []*ateapipb.ActorTemplate, f fmt.Fprintln(w, "ATESPACE\tNAME\tSANDBOX CLASS\tGOLDEN TAG\tERROR\tAGE") for _, t := range templates { gss := t.GetStatus().GetGoldenSnapshotStatus() + goldenTag := gss.GetGoldenTag().GetName() + if t.GetSnapshotConfig().GetGoldenSnapshotConfig().GetMode() == ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED { + goldenTag = "DISABLED" + } // Error messages are too long for a table cell. errFlag := "" if gss.GetErrorMessage() != "" { @@ -303,7 +307,7 @@ func PrintActorTemplatesTo(out io.Writer, templates []*ateapipb.ActorTemplate, f fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", t.GetMetadata().GetAtespace(), t.GetMetadata().GetName(), t.GetSandboxConfig().GetSandboxClass(), - gss.GetGoldenTag().GetName(), errFlag, + goldenTag, errFlag, formatAge(t.GetMetadata().GetCreateTime())) } return w.Flush() diff --git a/cmd/kubectl-ate/internal/printer/printer_test.go b/cmd/kubectl-ate/internal/printer/printer_test.go index 70612bdeaa..6d7b53cdad 100644 --- a/cmd/kubectl-ate/internal/printer/printer_test.go +++ b/cmd/kubectl-ate/internal/printer/printer_test.go @@ -473,6 +473,17 @@ func TestPrintActorTemplatesTo_Table(t *testing.T) { ConfigName: "gvisor-default", }, }, + { + Metadata: &ateapipb.ResourceMetadata{ + Atespace: "ate-demo-counter-substrate", + Name: "counter-3", + CreateTime: timestamppb.New(now.Add(-15 * time.Minute)), + }, + SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR}, + SnapshotConfig: &ateapipb.SnapshotConfig{ + GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED}, + }, + }, } if err := PrintActorTemplatesTo(&buf, templates, "table"); err != nil { @@ -484,6 +495,7 @@ func TestPrintActorTemplatesTo_Table(t *testing.T) { expected := `ATESPACE NAME SANDBOX CLASS GOLDEN TAG ERROR AGE ate-demo-counter-substrate counter SANDBOX_CLASS_GVISOR golden-tag 5m ate-demo-counter-substrate counter-2 SANDBOX_CLASS_GVISOR 3d +ate-demo-counter-substrate counter-3 SANDBOX_CLASS_GVISOR DISABLED 15m ate-demo-counter-substrate-microvm counter-microvm SANDBOX_CLASS_MICROVM ERROR 5h ` if diff := cmp.Diff(expected, buf.String()); diff != "" { diff --git a/docs/api-guide.md b/docs/api-guide.md index c060d86d8b..96b69b07c8 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -114,7 +114,7 @@ worker pod on a GPU node and reserves the device, and nothing else reads it. ## 2. ActorTemplate: The Workload Blueprint -The `ActorTemplate` defines the code, environment, and state-management policies for a specific type of agent. It is used to generate the "Golden Snapshot" from which all actors of this type are derived. +The `ActorTemplate` defines the code, environment, and state-management policies for a specific type of agent. By default, it is used to generate a "Golden Snapshot" that new actors can start from. ### Specification (`ActorTemplate`) @@ -123,7 +123,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). | | `sandboxConfig` | `SandboxConfig` | **Required.** The sandbox runtime selection: `sandboxClass` (**required**, `SANDBOX_CLASS_GVISOR` or `SANDBOX_CLASS_MICROVM`) picks the runtime family this template's actors require — only `WorkerPool`s whose `sandboxClasses` entry matches are eligible — and `configName` (**required**) names the cluster-scoped [`SandboxConfig`](#3-sandboxconfig-the-sandbox-itself) object supplying the sandbox binaries. It must reference an existing config of the matching class; `CreateActorTemplate` rejects the template otherwise. | | `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`). | -| `snapshotConfig` | `SnapshotConfig` | **Required.** The base object-storage location snapshots are written under, plus the snapshot fidelity (`preferredFidelity`) every Pause and Suspend captures; `SNAPSHOT_FIDELITY_ROOTFS` requires `sandboxClass: SANDBOX_CLASS_MICROVM`. See [Snapshot Storage Layout](#snapshot-storage-layout). | +| `snapshotConfig` | `SnapshotConfig` | **Required.** The base object-storage location snapshots are written under, the snapshot fidelity (`preferredFidelity`) every Pause and Suspend captures (`SNAPSHOT_FIDELITY_ROOTFS` requires `sandboxClass: SANDBOX_CLASS_MICROVM`), 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 `externalVolumeTemplate` (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 `microvm` template may declare several `durableDir` volumes; a `gvisor` template is limited to one. | | `resources` | `*ResourceRequirements` | Optional. Declares each actor's compute size via `limits` — see [Sandbox Right-Sizing](#sandbox-right-sizing-resources). Immutable, like the rest of the template. | @@ -457,7 +457,18 @@ See [`hack/microvm-assets/`](../hack/microvm-assets/) for scripts that assemble ## 4. Operational Workflow ### The Golden Snapshot -When an `ActorTemplate` is created: + +Golden snapshot creation is enabled by default. To disable it, set +`snapshotConfig.goldenSnapshotConfig.mode: GOLDEN_SNAPSHOT_MODE_DISABLED` on the +template. Omitting the config or leaving `mode` unspecified defaults to +`GOLDEN_SNAPSHOT_MODE_ENABLED`. A disabled template creates no +golden actor or tag. Actors created without a `sourceTag` cold-boot and can still +pause, suspend, and resume using their own snapshots. + +`kubectl ate get actor-templates` shows `DISABLED` in the `GOLDEN TAG` column +for these templates. + +When an `ActorTemplate` is created with golden snapshots enabled: 1. Substrate creates and resumes a temporary golden actor in `ate-golden`. 2. It waits for readiness (or the warm-up interval), then suspends the actor. 3. It creates a published tag named after the template UID, copying the snapshot into tag-owned storage. @@ -488,7 +499,7 @@ The node can cut the 30 minutes short. GKE node upgrades and cluster autoscaler --- ## 5. Best Practices -* **Startup Logic:** Place expensive initialization (loading large models, establishing baseline connections) in your application's entry point. These will be captured in the Golden Snapshot and won't need to be repeated on every resumption. +* **Startup Logic:** Place expensive initialization (loading large models, establishing baseline connections) in your application's entry point. When golden snapshots are enabled, this initialization can be captured in the Golden Snapshot. Actors that cold-boot run initialization again. * **Placement:** Ensure your `ActorTemplate`'s `sandboxClass` matches your `WorkerPool`'s `sandboxClasses[].name`, and use the template's `workerSelector` to target specific pools — pool selection is by label match, not by namespace or RBAC. * **Version Management:** When updating code, create a new `ActorTemplate` (e.g. `v2`). Substrate treats each template as an immutable state root. * **Eviction:** Give every actor a `SIGTERM` handler that does not exit, and have the actor suspended within 30 minutes of it. See [Eviction](#eviction). diff --git a/docs/glossary.md b/docs/glossary.md index cb1f88ab0e..5eab2b4df5 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -9,7 +9,9 @@ For how the pieces fit together, see the [Architecture](architecture.md) and - **ActorTemplate** (ate API resource): the definition of an actor "class": the container image(s) and snapshot configuration. Creating an - `ActorTemplate` triggers creation of a [Golden Snapshot](#snapshots). It is + `ActorTemplate` triggers creation of a [Golden Snapshot](#snapshots) by + default. Set `snapshotConfig.goldenSnapshotConfig.mode` to + `GOLDEN_SNAPSHOT_MODE_DISABLED` to skip it. The template is treated as immutable: you create a new template for a new version rather than editing an existing one. It is analogous to a Pod template, but for a checkpointable workload. ActorTemplates are created and managed through the @@ -131,10 +133,12 @@ for etcd. less when a layer cannot be captured or is too expensive to capture at the time. A process resumed in place keeps its memory regardless. -- **Golden Snapshot**: the initial checkpoint captured once, when an +- **Golden Snapshot**: the initial checkpoint normally captured once, when an `ActorTemplate` is created, from a temporary "golden" boot of the workload. - By default an Actor of that template is first restored from this shared - snapshot. It is always a `MEMORY` capture. + Templates with golden snapshot creation disabled do not create one. By + default an Actor of that template is first restored from this shared + snapshot when it is available; otherwise it cold-boots. It is always a + `MEMORY` capture. - **Last Snapshot**: the most recent per-Actor snapshot, written on Suspend and used to restore that specific Actor on the next Resume. diff --git a/internal/e2e/template.go b/internal/e2e/template.go index 9f90201a3a..4bc24dc076 100644 --- a/internal/e2e/template.go +++ b/internal/e2e/template.go @@ -146,8 +146,9 @@ func CreateSubstrateTemplateFrom(ctx context.Context, t *testing.T, clients *Cli } // WaitForSubstrateTemplateReady blocks until the substrate ActorTemplate's -// golden snapshot exists. The timeout follows the sandbox class under test -// (see TemplateReadyTimeout). +// golden snapshot exists, returning immediately for a template that opted +// out of the golden snapshot. The timeout follows the sandbox class under +// test (see TemplateReadyTimeout). func WaitForSubstrateTemplateReady(ctx context.Context, t *testing.T, clients *Clients, atespace, name string) { t.Helper() @@ -161,6 +162,11 @@ func WaitForSubstrateTemplateReady(ctx context.Context, t *testing.T, clients *C ActorTemplate: &ateapipb.ObjectRef{Atespace: atespace, Name: name}, }) if err == nil { + if at.GetSnapshotConfig().GetGoldenSnapshotConfig().GetMode() == ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED { + // The template opted out: no golden snapshot will ever appear, + // and none is needed. + return + } lastStatus = at.GetStatus().GetGoldenSnapshotStatus() if lastStatus.GetGoldenTag().GetName() != "" { return diff --git a/internal/e2e/template_test.go b/internal/e2e/template_test.go new file mode 100644 index 0000000000..d870add40b --- /dev/null +++ b/internal/e2e/template_test.go @@ -0,0 +1,48 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "context" + "testing" + + "github.com/agent-substrate/substrate/internal/ateclient" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "google.golang.org/grpc" +) + +// disabledGoldenTemplateClient serves one ActorTemplate that opted out of the +// golden snapshot and has no golden status. +type disabledGoldenTemplateClient struct { + ateapipb.ControlClient +} + +func (disabledGoldenTemplateClient) GetActorTemplate(_ context.Context, in *ateapipb.GetActorTemplateRequest, _ ...grpc.CallOption) (*ateapipb.ActorTemplate, error) { + return &ateapipb.ActorTemplate{ + Metadata: &ateapipb.ResourceMetadata{Atespace: in.GetActorTemplate().GetAtespace(), Name: in.GetActorTemplate().GetName()}, + SnapshotConfig: &ateapipb.SnapshotConfig{GoldenSnapshotConfig: &ateapipb.GoldenSnapshotConfig{Mode: ateapipb.GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED}}, + }, nil +} + +// TestWaitForSubstrateTemplateReadyDisabledTemplate: a template that opted out +// never gets a golden tag or an error, so the helper must return instead of +// riding its timeout. The short E2E_TEMPLATE_READY_TIMEOUT keeps a regression +// from stalling the test for minutes. +func TestWaitForSubstrateTemplateReadyDisabledTemplate(t *testing.T) { + t.Setenv("E2E_TEMPLATE_READY_TIMEOUT", "2s") + clients := &Clients{SubstrateAPI: &ateclient.Client{ControlClient: disabledGoldenTemplateClient{}}} + + WaitForSubstrateTemplateReady(context.Background(), t, clients, "ate-demo", "counter") +} diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index 95d1935c45..4633400824 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -430,6 +430,56 @@ func (SandboxClass) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{6} } +// GoldenSnapshotMode selects whether a template builds a golden snapshot. +type GoldenSnapshotMode int32 + +const ( + GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_UNSPECIFIED GoldenSnapshotMode = 0 + GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_ENABLED GoldenSnapshotMode = 1 + GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_DISABLED GoldenSnapshotMode = 2 +) + +// Enum value maps for GoldenSnapshotMode. +var ( + GoldenSnapshotMode_name = map[int32]string{ + 0: "GOLDEN_SNAPSHOT_MODE_UNSPECIFIED", + 1: "GOLDEN_SNAPSHOT_MODE_ENABLED", + 2: "GOLDEN_SNAPSHOT_MODE_DISABLED", + } + GoldenSnapshotMode_value = map[string]int32{ + "GOLDEN_SNAPSHOT_MODE_UNSPECIFIED": 0, + "GOLDEN_SNAPSHOT_MODE_ENABLED": 1, + "GOLDEN_SNAPSHOT_MODE_DISABLED": 2, + } +) + +func (x GoldenSnapshotMode) Enum() *GoldenSnapshotMode { + p := new(GoldenSnapshotMode) + *p = x + return p +} + +func (x GoldenSnapshotMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (GoldenSnapshotMode) Descriptor() protoreflect.EnumDescriptor { + return file_ateapi_proto_enumTypes[7].Descriptor() +} + +func (GoldenSnapshotMode) Type() protoreflect.EnumType { + return &file_ateapi_proto_enumTypes[7] +} + +func (x GoldenSnapshotMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use GoldenSnapshotMode.Descriptor instead. +func (GoldenSnapshotMode) EnumDescriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{7} +} + // ActorMetadataField selects one identity field of the actor. type ActorMetadataField int32 @@ -467,11 +517,11 @@ func (x ActorMetadataField) String() string { } func (ActorMetadataField) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[7].Descriptor() + return file_ateapi_proto_enumTypes[8].Descriptor() } func (ActorMetadataField) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[7] + return &file_ateapi_proto_enumTypes[8] } func (x ActorMetadataField) Number() protoreflect.EnumNumber { @@ -480,7 +530,7 @@ func (x ActorMetadataField) Number() protoreflect.EnumNumber { // Deprecated: Use ActorMetadataField.Descriptor instead. func (ActorMetadataField) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{7} + return file_ateapi_proto_rawDescGZIP(), []int{8} } type WorkerState int32 @@ -518,11 +568,11 @@ func (x WorkerState) String() string { } func (WorkerState) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[8].Descriptor() + return file_ateapi_proto_enumTypes[9].Descriptor() } func (WorkerState) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[8] + return &file_ateapi_proto_enumTypes[9] } func (x WorkerState) Number() protoreflect.EnumNumber { @@ -531,7 +581,7 @@ func (x WorkerState) Number() protoreflect.EnumNumber { // Deprecated: Use WorkerState.Descriptor instead. func (WorkerState) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{8} + return file_ateapi_proto_rawDescGZIP(), []int{9} } type ExternalVolume_Status int32 @@ -573,11 +623,11 @@ func (x ExternalVolume_Status) String() string { } func (ExternalVolume_Status) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[9].Descriptor() + return file_ateapi_proto_enumTypes[10].Descriptor() } func (ExternalVolume_Status) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[9] + return &file_ateapi_proto_enumTypes[10] } func (x ExternalVolume_Status) Number() protoreflect.EnumNumber { @@ -3089,8 +3139,13 @@ type SnapshotConfig struct { // +k8s:minimum=1 // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum PreferredFidelity SnapshotFidelity `protobuf:"varint,2,opt,name=preferred_fidelity,json=preferredFidelity,proto3,enum=ateapi.SnapshotFidelity" json:"preferred_fidelity,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // golden_snapshot_config configures automated golden snapshot creation. + // Golden snapshots are enabled when this config is omitted. + // + // +k8s:optional + GoldenSnapshotConfig *GoldenSnapshotConfig `protobuf:"bytes,5,opt,name=golden_snapshot_config,json=goldenSnapshotConfig,proto3" json:"golden_snapshot_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SnapshotConfig) Reset() { @@ -3137,6 +3192,64 @@ func (x *SnapshotConfig) GetPreferredFidelity() SnapshotFidelity { return SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED } +func (x *SnapshotConfig) GetGoldenSnapshotConfig() *GoldenSnapshotConfig { + if x != nil { + return x.GoldenSnapshotConfig + } + return nil +} + +// GoldenSnapshotConfig controls automated golden snapshot creation for a template. +type GoldenSnapshotConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // mode defaults to ENABLED when unspecified. DISABLED skips creating the + // template's golden actor and tag. + // + // +k8s:required + // +k8s:minimum=1 + // +k8s:maximum=2 # keep this in sync with the GoldenSnapshotMode enum + Mode GoldenSnapshotMode `protobuf:"varint,1,opt,name=mode,proto3,enum=ateapi.GoldenSnapshotMode" json:"mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GoldenSnapshotConfig) Reset() { + *x = GoldenSnapshotConfig{} + mi := &file_ateapi_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GoldenSnapshotConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GoldenSnapshotConfig) ProtoMessage() {} + +func (x *GoldenSnapshotConfig) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GoldenSnapshotConfig.ProtoReflect.Descriptor instead. +func (*GoldenSnapshotConfig) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{32} +} + +func (x *GoldenSnapshotConfig) GetMode() GoldenSnapshotMode { + if x != nil { + return x.Mode + } + return GoldenSnapshotMode_GOLDEN_SNAPSHOT_MODE_UNSPECIFIED +} + // Container is a single application container of an ActorTemplate. type Container struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -3207,7 +3320,7 @@ type Container struct { func (x *Container) Reset() { *x = Container{} - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3219,7 +3332,7 @@ func (x *Container) String() string { func (*Container) ProtoMessage() {} func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3232,7 +3345,7 @@ func (x *Container) ProtoReflect() protoreflect.Message { // Deprecated: Use Container.ProtoReflect.Descriptor instead. func (*Container) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{32} + return file_ateapi_proto_rawDescGZIP(), []int{33} } func (x *Container) GetName() string { @@ -3312,7 +3425,7 @@ type SecurityContext struct { func (x *SecurityContext) Reset() { *x = SecurityContext{} - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3324,7 +3437,7 @@ func (x *SecurityContext) String() string { func (*SecurityContext) ProtoMessage() {} func (x *SecurityContext) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3337,7 +3450,7 @@ func (x *SecurityContext) ProtoReflect() protoreflect.Message { // Deprecated: Use SecurityContext.ProtoReflect.Descriptor instead. func (*SecurityContext) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{33} + return file_ateapi_proto_rawDescGZIP(), []int{34} } func (x *SecurityContext) GetCapabilities() *Capabilities { @@ -3375,7 +3488,7 @@ type Capabilities struct { func (x *Capabilities) Reset() { *x = Capabilities{} - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3387,7 +3500,7 @@ func (x *Capabilities) String() string { func (*Capabilities) ProtoMessage() {} func (x *Capabilities) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3400,7 +3513,7 @@ func (x *Capabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use Capabilities.ProtoReflect.Descriptor instead. func (*Capabilities) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{34} + return file_ateapi_proto_rawDescGZIP(), []int{35} } func (x *Capabilities) GetAdd() []string { @@ -3438,7 +3551,7 @@ type EnvVar struct { func (x *EnvVar) Reset() { *x = EnvVar{} - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3450,7 +3563,7 @@ func (x *EnvVar) String() string { func (*EnvVar) ProtoMessage() {} func (x *EnvVar) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3463,7 +3576,7 @@ func (x *EnvVar) ProtoReflect() protoreflect.Message { // Deprecated: Use EnvVar.ProtoReflect.Descriptor instead. func (*EnvVar) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{35} + return file_ateapi_proto_rawDescGZIP(), []int{36} } func (x *EnvVar) GetName() string { @@ -3503,7 +3616,7 @@ type ContainerWakeupProbe struct { func (x *ContainerWakeupProbe) Reset() { *x = ContainerWakeupProbe{} - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3515,7 +3628,7 @@ func (x *ContainerWakeupProbe) String() string { func (*ContainerWakeupProbe) ProtoMessage() {} func (x *ContainerWakeupProbe) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3528,7 +3641,7 @@ func (x *ContainerWakeupProbe) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerWakeupProbe.ProtoReflect.Descriptor instead. func (*ContainerWakeupProbe) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{36} + return file_ateapi_proto_rawDescGZIP(), []int{37} } func (x *ContainerWakeupProbe) GetHttpGet() *HTTPGetAction { @@ -3568,7 +3681,7 @@ type HTTPGetAction struct { func (x *HTTPGetAction) Reset() { *x = HTTPGetAction{} - mi := &file_ateapi_proto_msgTypes[37] + mi := &file_ateapi_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3580,7 +3693,7 @@ func (x *HTTPGetAction) String() string { func (*HTTPGetAction) ProtoMessage() {} func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[37] + mi := &file_ateapi_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3593,7 +3706,7 @@ func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPGetAction.ProtoReflect.Descriptor instead. func (*HTTPGetAction) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{37} + return file_ateapi_proto_rawDescGZIP(), []int{38} } func (x *HTTPGetAction) GetPath() string { @@ -3640,7 +3753,7 @@ type Volume struct { func (x *Volume) Reset() { *x = Volume{} - mi := &file_ateapi_proto_msgTypes[38] + mi := &file_ateapi_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3652,7 +3765,7 @@ func (x *Volume) String() string { func (*Volume) ProtoMessage() {} func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[38] + mi := &file_ateapi_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3665,7 +3778,7 @@ func (x *Volume) ProtoReflect() protoreflect.Message { // Deprecated: Use Volume.ProtoReflect.Descriptor instead. func (*Volume) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{38} + return file_ateapi_proto_rawDescGZIP(), []int{39} } func (x *Volume) GetName() string { @@ -3721,7 +3834,7 @@ type ImageVolumeSource struct { func (x *ImageVolumeSource) Reset() { *x = ImageVolumeSource{} - mi := &file_ateapi_proto_msgTypes[39] + mi := &file_ateapi_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3733,7 +3846,7 @@ func (x *ImageVolumeSource) String() string { func (*ImageVolumeSource) ProtoMessage() {} func (x *ImageVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[39] + mi := &file_ateapi_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3746,7 +3859,7 @@ func (x *ImageVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ImageVolumeSource.ProtoReflect.Descriptor instead. func (*ImageVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{39} + return file_ateapi_proto_rawDescGZIP(), []int{40} } func (x *ImageVolumeSource) GetReference() string { @@ -3766,7 +3879,7 @@ type DurableDirVolumeSource struct { func (x *DurableDirVolumeSource) Reset() { *x = DurableDirVolumeSource{} - mi := &file_ateapi_proto_msgTypes[40] + mi := &file_ateapi_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3778,7 +3891,7 @@ func (x *DurableDirVolumeSource) String() string { func (*DurableDirVolumeSource) ProtoMessage() {} func (x *DurableDirVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[40] + mi := &file_ateapi_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3791,7 +3904,7 @@ func (x *DurableDirVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use DurableDirVolumeSource.ProtoReflect.Descriptor instead. func (*DurableDirVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{40} + return file_ateapi_proto_rawDescGZIP(), []int{41} } // ExternalVolumeTemplate provisions an external volume per actor; the volume @@ -3817,7 +3930,7 @@ type ExternalVolumeTemplate struct { func (x *ExternalVolumeTemplate) Reset() { *x = ExternalVolumeTemplate{} - mi := &file_ateapi_proto_msgTypes[41] + mi := &file_ateapi_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3829,7 +3942,7 @@ func (x *ExternalVolumeTemplate) String() string { func (*ExternalVolumeTemplate) ProtoMessage() {} func (x *ExternalVolumeTemplate) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[41] + mi := &file_ateapi_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3842,7 +3955,7 @@ func (x *ExternalVolumeTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalVolumeTemplate.ProtoReflect.Descriptor instead. func (*ExternalVolumeTemplate) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{41} + return file_ateapi_proto_rawDescGZIP(), []int{42} } func (x *ExternalVolumeTemplate) GetCapacity() string { @@ -3879,7 +3992,7 @@ type SystemInfoVolumeSource struct { func (x *SystemInfoVolumeSource) Reset() { *x = SystemInfoVolumeSource{} - mi := &file_ateapi_proto_msgTypes[42] + mi := &file_ateapi_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3891,7 +4004,7 @@ func (x *SystemInfoVolumeSource) String() string { func (*SystemInfoVolumeSource) ProtoMessage() {} func (x *SystemInfoVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[42] + mi := &file_ateapi_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3904,7 +4017,7 @@ func (x *SystemInfoVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfoVolumeSource.ProtoReflect.Descriptor instead. func (*SystemInfoVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{42} + return file_ateapi_proto_rawDescGZIP(), []int{43} } func (x *SystemInfoVolumeSource) GetDataSources() []*SystemInfoDataSource { @@ -3930,7 +4043,7 @@ type SystemInfoDataSource struct { func (x *SystemInfoDataSource) Reset() { *x = SystemInfoDataSource{} - mi := &file_ateapi_proto_msgTypes[43] + mi := &file_ateapi_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3942,7 +4055,7 @@ func (x *SystemInfoDataSource) String() string { func (*SystemInfoDataSource) ProtoMessage() {} func (x *SystemInfoDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[43] + mi := &file_ateapi_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3955,7 +4068,7 @@ func (x *SystemInfoDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfoDataSource.ProtoReflect.Descriptor instead. func (*SystemInfoDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{43} + return file_ateapi_proto_rawDescGZIP(), []int{44} } func (x *SystemInfoDataSource) GetActorMetadata() *ActorMetadataDataSource { @@ -3992,7 +4105,7 @@ type ActorMetadataDataSource struct { func (x *ActorMetadataDataSource) Reset() { *x = ActorMetadataDataSource{} - mi := &file_ateapi_proto_msgTypes[44] + mi := &file_ateapi_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4004,7 +4117,7 @@ func (x *ActorMetadataDataSource) String() string { func (*ActorMetadataDataSource) ProtoMessage() {} func (x *ActorMetadataDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[44] + mi := &file_ateapi_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4017,7 +4130,7 @@ func (x *ActorMetadataDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorMetadataDataSource.ProtoReflect.Descriptor instead. func (*ActorMetadataDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{44} + return file_ateapi_proto_rawDescGZIP(), []int{45} } func (x *ActorMetadataDataSource) GetItems() []*ActorMetadataItem { @@ -4049,7 +4162,7 @@ type ActorMetadataItem struct { func (x *ActorMetadataItem) Reset() { *x = ActorMetadataItem{} - mi := &file_ateapi_proto_msgTypes[45] + mi := &file_ateapi_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4061,7 +4174,7 @@ func (x *ActorMetadataItem) String() string { func (*ActorMetadataItem) ProtoMessage() {} func (x *ActorMetadataItem) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[45] + mi := &file_ateapi_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4074,7 +4187,7 @@ func (x *ActorMetadataItem) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorMetadataItem.ProtoReflect.Descriptor instead. func (*ActorMetadataItem) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{45} + return file_ateapi_proto_rawDescGZIP(), []int{46} } func (x *ActorMetadataItem) GetField() ActorMetadataField { @@ -4126,7 +4239,7 @@ type TrustBundleDataSource struct { func (x *TrustBundleDataSource) Reset() { *x = TrustBundleDataSource{} - mi := &file_ateapi_proto_msgTypes[46] + mi := &file_ateapi_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4138,7 +4251,7 @@ func (x *TrustBundleDataSource) String() string { func (*TrustBundleDataSource) ProtoMessage() {} func (x *TrustBundleDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[46] + mi := &file_ateapi_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4151,7 +4264,7 @@ func (x *TrustBundleDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleDataSource.ProtoReflect.Descriptor instead. func (*TrustBundleDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{46} + return file_ateapi_proto_rawDescGZIP(), []int{47} } func (x *TrustBundleDataSource) GetNames() []string { @@ -4190,7 +4303,7 @@ type VolumeMount struct { func (x *VolumeMount) Reset() { *x = VolumeMount{} - mi := &file_ateapi_proto_msgTypes[47] + mi := &file_ateapi_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4202,7 +4315,7 @@ func (x *VolumeMount) String() string { func (*VolumeMount) ProtoMessage() {} func (x *VolumeMount) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[47] + mi := &file_ateapi_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4215,7 +4328,7 @@ func (x *VolumeMount) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeMount.ProtoReflect.Descriptor instead. func (*VolumeMount) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{47} + return file_ateapi_proto_rawDescGZIP(), []int{48} } func (x *VolumeMount) GetName() string { @@ -4244,7 +4357,7 @@ type CreateAtespaceRequest struct { func (x *CreateAtespaceRequest) Reset() { *x = CreateAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[48] + mi := &file_ateapi_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4256,7 +4369,7 @@ func (x *CreateAtespaceRequest) String() string { func (*CreateAtespaceRequest) ProtoMessage() {} func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[48] + mi := &file_ateapi_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4269,7 +4382,7 @@ func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAtespaceRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{48} + return file_ateapi_proto_rawDescGZIP(), []int{49} } func (x *CreateAtespaceRequest) GetAtespace() *Atespace { @@ -4290,7 +4403,7 @@ type GetAtespaceRequest struct { func (x *GetAtespaceRequest) Reset() { *x = GetAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[49] + mi := &file_ateapi_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4302,7 +4415,7 @@ func (x *GetAtespaceRequest) String() string { func (*GetAtespaceRequest) ProtoMessage() {} func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[49] + mi := &file_ateapi_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4315,7 +4428,7 @@ func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{49} + return file_ateapi_proto_rawDescGZIP(), []int{50} } func (x *GetAtespaceRequest) GetAtespace() *ObjectRef { @@ -4346,7 +4459,7 @@ type ListAtespacesRequest struct { func (x *ListAtespacesRequest) Reset() { *x = ListAtespacesRequest{} - mi := &file_ateapi_proto_msgTypes[50] + mi := &file_ateapi_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4358,7 +4471,7 @@ func (x *ListAtespacesRequest) String() string { func (*ListAtespacesRequest) ProtoMessage() {} func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[50] + mi := &file_ateapi_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4371,7 +4484,7 @@ func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesRequest.ProtoReflect.Descriptor instead. func (*ListAtespacesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{50} + return file_ateapi_proto_rawDescGZIP(), []int{51} } func (x *ListAtespacesRequest) GetPageSize() int32 { @@ -4400,7 +4513,7 @@ type ListAtespacesResponse struct { func (x *ListAtespacesResponse) Reset() { *x = ListAtespacesResponse{} - mi := &file_ateapi_proto_msgTypes[51] + mi := &file_ateapi_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4412,7 +4525,7 @@ func (x *ListAtespacesResponse) String() string { func (*ListAtespacesResponse) ProtoMessage() {} func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[51] + mi := &file_ateapi_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4425,7 +4538,7 @@ func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesResponse.ProtoReflect.Descriptor instead. func (*ListAtespacesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{51} + return file_ateapi_proto_rawDescGZIP(), []int{52} } func (x *ListAtespacesResponse) GetAtespaces() []*Atespace { @@ -4457,7 +4570,7 @@ type DeleteAtespaceRequest struct { func (x *DeleteAtespaceRequest) Reset() { *x = DeleteAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[52] + mi := &file_ateapi_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4469,7 +4582,7 @@ func (x *DeleteAtespaceRequest) String() string { func (*DeleteAtespaceRequest) ProtoMessage() {} func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[52] + mi := &file_ateapi_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4482,7 +4595,7 @@ func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAtespaceRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{52} + return file_ateapi_proto_rawDescGZIP(), []int{53} } func (x *DeleteAtespaceRequest) GetAtespace() *ObjectRef { @@ -4513,7 +4626,7 @@ type CreateActorTemplateRequest struct { func (x *CreateActorTemplateRequest) Reset() { *x = CreateActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[53] + mi := &file_ateapi_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4525,7 +4638,7 @@ func (x *CreateActorTemplateRequest) String() string { func (*CreateActorTemplateRequest) ProtoMessage() {} func (x *CreateActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[53] + mi := &file_ateapi_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4538,7 +4651,7 @@ func (x *CreateActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorTemplateRequest.ProtoReflect.Descriptor instead. func (*CreateActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{53} + return file_ateapi_proto_rawDescGZIP(), []int{54} } func (x *CreateActorTemplateRequest) GetActorTemplate() *ActorTemplate { @@ -4559,7 +4672,7 @@ type GetActorTemplateRequest struct { func (x *GetActorTemplateRequest) Reset() { *x = GetActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[54] + mi := &file_ateapi_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4571,7 +4684,7 @@ func (x *GetActorTemplateRequest) String() string { func (*GetActorTemplateRequest) ProtoMessage() {} func (x *GetActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[54] + mi := &file_ateapi_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4584,7 +4697,7 @@ func (x *GetActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorTemplateRequest.ProtoReflect.Descriptor instead. func (*GetActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{54} + return file_ateapi_proto_rawDescGZIP(), []int{55} } func (x *GetActorTemplateRequest) GetActorTemplate() *ObjectRef { @@ -4621,7 +4734,7 @@ type ListActorTemplatesRequest struct { func (x *ListActorTemplatesRequest) Reset() { *x = ListActorTemplatesRequest{} - mi := &file_ateapi_proto_msgTypes[55] + mi := &file_ateapi_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4633,7 +4746,7 @@ func (x *ListActorTemplatesRequest) String() string { func (*ListActorTemplatesRequest) ProtoMessage() {} func (x *ListActorTemplatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[55] + mi := &file_ateapi_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4646,7 +4759,7 @@ func (x *ListActorTemplatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorTemplatesRequest.ProtoReflect.Descriptor instead. func (*ListActorTemplatesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{55} + return file_ateapi_proto_rawDescGZIP(), []int{56} } func (x *ListActorTemplatesRequest) GetAtespace() string { @@ -4683,7 +4796,7 @@ type ListActorTemplatesResponse struct { func (x *ListActorTemplatesResponse) Reset() { *x = ListActorTemplatesResponse{} - mi := &file_ateapi_proto_msgTypes[56] + mi := &file_ateapi_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4695,7 +4808,7 @@ func (x *ListActorTemplatesResponse) String() string { func (*ListActorTemplatesResponse) ProtoMessage() {} func (x *ListActorTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[56] + mi := &file_ateapi_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4708,7 +4821,7 @@ func (x *ListActorTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorTemplatesResponse.ProtoReflect.Descriptor instead. func (*ListActorTemplatesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{56} + return file_ateapi_proto_rawDescGZIP(), []int{57} } func (x *ListActorTemplatesResponse) GetActorTemplates() []*ActorTemplate { @@ -4740,7 +4853,7 @@ type DeleteActorTemplateRequest struct { func (x *DeleteActorTemplateRequest) Reset() { *x = DeleteActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[57] + mi := &file_ateapi_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4752,7 +4865,7 @@ func (x *DeleteActorTemplateRequest) String() string { func (*DeleteActorTemplateRequest) ProtoMessage() {} func (x *DeleteActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[57] + mi := &file_ateapi_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4765,7 +4878,7 @@ func (x *DeleteActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorTemplateRequest.ProtoReflect.Descriptor instead. func (*DeleteActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{57} + return file_ateapi_proto_rawDescGZIP(), []int{58} } func (x *DeleteActorTemplateRequest) GetActorTemplate() *ObjectRef { @@ -4793,7 +4906,7 @@ type GetActorRequest struct { func (x *GetActorRequest) Reset() { *x = GetActorRequest{} - mi := &file_ateapi_proto_msgTypes[58] + mi := &file_ateapi_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4805,7 +4918,7 @@ func (x *GetActorRequest) String() string { func (*GetActorRequest) ProtoMessage() {} func (x *GetActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[58] + mi := &file_ateapi_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4818,7 +4931,7 @@ func (x *GetActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorRequest.ProtoReflect.Descriptor instead. func (*GetActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{58} + return file_ateapi_proto_rawDescGZIP(), []int{59} } func (x *GetActorRequest) GetActor() *ObjectRef { @@ -4841,7 +4954,7 @@ type CreateActorRequest struct { func (x *CreateActorRequest) Reset() { *x = CreateActorRequest{} - mi := &file_ateapi_proto_msgTypes[59] + mi := &file_ateapi_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4853,7 +4966,7 @@ func (x *CreateActorRequest) String() string { func (*CreateActorRequest) ProtoMessage() {} func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[59] + mi := &file_ateapi_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4866,7 +4979,7 @@ func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorRequest.ProtoReflect.Descriptor instead. func (*CreateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{59} + return file_ateapi_proto_rawDescGZIP(), []int{60} } func (x *CreateActorRequest) GetActor() *Actor { @@ -4897,7 +5010,7 @@ type UpdateActorRequest struct { func (x *UpdateActorRequest) Reset() { *x = UpdateActorRequest{} - mi := &file_ateapi_proto_msgTypes[60] + mi := &file_ateapi_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4909,7 +5022,7 @@ func (x *UpdateActorRequest) String() string { func (*UpdateActorRequest) ProtoMessage() {} func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[60] + mi := &file_ateapi_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4922,7 +5035,7 @@ func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorRequest.ProtoReflect.Descriptor instead. func (*UpdateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{60} + return file_ateapi_proto_rawDescGZIP(), []int{61} } func (x *UpdateActorRequest) GetActor() *Actor { @@ -4943,7 +5056,7 @@ type SuspendActorRequest struct { func (x *SuspendActorRequest) Reset() { *x = SuspendActorRequest{} - mi := &file_ateapi_proto_msgTypes[61] + mi := &file_ateapi_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4955,7 +5068,7 @@ func (x *SuspendActorRequest) String() string { func (*SuspendActorRequest) ProtoMessage() {} func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[61] + mi := &file_ateapi_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4968,7 +5081,7 @@ func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorRequest.ProtoReflect.Descriptor instead. func (*SuspendActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{61} + return file_ateapi_proto_rawDescGZIP(), []int{62} } func (x *SuspendActorRequest) GetActor() *ObjectRef { @@ -4987,7 +5100,7 @@ type SuspendActorResponse struct { func (x *SuspendActorResponse) Reset() { *x = SuspendActorResponse{} - mi := &file_ateapi_proto_msgTypes[62] + mi := &file_ateapi_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4999,7 +5112,7 @@ func (x *SuspendActorResponse) String() string { func (*SuspendActorResponse) ProtoMessage() {} func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[62] + mi := &file_ateapi_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5012,7 +5125,7 @@ func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorResponse.ProtoReflect.Descriptor instead. func (*SuspendActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{62} + return file_ateapi_proto_rawDescGZIP(), []int{63} } func (x *SuspendActorResponse) GetActor() *Actor { @@ -5033,7 +5146,7 @@ type PauseActorRequest struct { func (x *PauseActorRequest) Reset() { *x = PauseActorRequest{} - mi := &file_ateapi_proto_msgTypes[63] + mi := &file_ateapi_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5045,7 +5158,7 @@ func (x *PauseActorRequest) String() string { func (*PauseActorRequest) ProtoMessage() {} func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[63] + mi := &file_ateapi_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5058,7 +5171,7 @@ func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorRequest.ProtoReflect.Descriptor instead. func (*PauseActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{63} + return file_ateapi_proto_rawDescGZIP(), []int{64} } func (x *PauseActorRequest) GetActor() *ObjectRef { @@ -5077,7 +5190,7 @@ type PauseActorResponse struct { func (x *PauseActorResponse) Reset() { *x = PauseActorResponse{} - mi := &file_ateapi_proto_msgTypes[64] + mi := &file_ateapi_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5089,7 +5202,7 @@ func (x *PauseActorResponse) String() string { func (*PauseActorResponse) ProtoMessage() {} func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[64] + mi := &file_ateapi_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5102,7 +5215,7 @@ func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorResponse.ProtoReflect.Descriptor instead. func (*PauseActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{64} + return file_ateapi_proto_rawDescGZIP(), []int{65} } func (x *PauseActorResponse) GetActor() *Actor { @@ -5123,7 +5236,7 @@ type ResumeActorRequest struct { func (x *ResumeActorRequest) Reset() { *x = ResumeActorRequest{} - mi := &file_ateapi_proto_msgTypes[65] + mi := &file_ateapi_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5135,7 +5248,7 @@ func (x *ResumeActorRequest) String() string { func (*ResumeActorRequest) ProtoMessage() {} func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[65] + mi := &file_ateapi_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5148,7 +5261,7 @@ func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorRequest.ProtoReflect.Descriptor instead. func (*ResumeActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{65} + return file_ateapi_proto_rawDescGZIP(), []int{66} } func (x *ResumeActorRequest) GetActor() *ObjectRef { @@ -5170,7 +5283,7 @@ type ResumeActorResponse struct { func (x *ResumeActorResponse) Reset() { *x = ResumeActorResponse{} - mi := &file_ateapi_proto_msgTypes[66] + mi := &file_ateapi_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5182,7 +5295,7 @@ func (x *ResumeActorResponse) String() string { func (*ResumeActorResponse) ProtoMessage() {} func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[66] + mi := &file_ateapi_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5195,7 +5308,7 @@ func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorResponse.ProtoReflect.Descriptor instead. func (*ResumeActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{66} + return file_ateapi_proto_rawDescGZIP(), []int{67} } func (x *ResumeActorResponse) GetActor() *Actor { @@ -5225,7 +5338,7 @@ type RevertActorRequest struct { func (x *RevertActorRequest) Reset() { *x = RevertActorRequest{} - mi := &file_ateapi_proto_msgTypes[67] + mi := &file_ateapi_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5237,7 +5350,7 @@ func (x *RevertActorRequest) String() string { func (*RevertActorRequest) ProtoMessage() {} func (x *RevertActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[67] + mi := &file_ateapi_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5250,7 +5363,7 @@ func (x *RevertActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertActorRequest.ProtoReflect.Descriptor instead. func (*RevertActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{67} + return file_ateapi_proto_rawDescGZIP(), []int{68} } func (x *RevertActorRequest) GetActor() *ObjectRef { @@ -5272,7 +5385,7 @@ type RevertActorResponse struct { func (x *RevertActorResponse) Reset() { *x = RevertActorResponse{} - mi := &file_ateapi_proto_msgTypes[68] + mi := &file_ateapi_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5284,7 +5397,7 @@ func (x *RevertActorResponse) String() string { func (*RevertActorResponse) ProtoMessage() {} func (x *RevertActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[68] + mi := &file_ateapi_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5297,7 +5410,7 @@ func (x *RevertActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertActorResponse.ProtoReflect.Descriptor instead. func (*RevertActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{68} + return file_ateapi_proto_rawDescGZIP(), []int{69} } func (x *RevertActorResponse) GetActor() *Actor { @@ -5326,7 +5439,7 @@ type DeleteActorRequest struct { func (x *DeleteActorRequest) Reset() { *x = DeleteActorRequest{} - mi := &file_ateapi_proto_msgTypes[69] + mi := &file_ateapi_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5338,7 +5451,7 @@ func (x *DeleteActorRequest) String() string { func (*DeleteActorRequest) ProtoMessage() {} func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[69] + mi := &file_ateapi_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5351,7 +5464,7 @@ func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorRequest.ProtoReflect.Descriptor instead. func (*DeleteActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{69} + return file_ateapi_proto_rawDescGZIP(), []int{70} } func (x *DeleteActorRequest) GetActor() *ObjectRef { @@ -5389,7 +5502,7 @@ type GetActorEgressPolicyRequest struct { func (x *GetActorEgressPolicyRequest) Reset() { *x = GetActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[70] + mi := &file_ateapi_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5401,7 +5514,7 @@ func (x *GetActorEgressPolicyRequest) String() string { func (*GetActorEgressPolicyRequest) ProtoMessage() {} func (x *GetActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[70] + mi := &file_ateapi_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5414,7 +5527,7 @@ func (x *GetActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*GetActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{70} + return file_ateapi_proto_rawDescGZIP(), []int{71} } func (x *GetActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5445,7 +5558,7 @@ type CreateActorEgressPolicyRequest struct { func (x *CreateActorEgressPolicyRequest) Reset() { *x = CreateActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5457,7 +5570,7 @@ func (x *CreateActorEgressPolicyRequest) String() string { func (*CreateActorEgressPolicyRequest) ProtoMessage() {} func (x *CreateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5470,7 +5583,7 @@ func (x *CreateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{71} + return file_ateapi_proto_rawDescGZIP(), []int{72} } func (x *CreateActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5508,7 +5621,7 @@ type UpdateActorEgressPolicyRequest struct { func (x *UpdateActorEgressPolicyRequest) Reset() { *x = UpdateActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5520,7 +5633,7 @@ func (x *UpdateActorEgressPolicyRequest) String() string { func (*UpdateActorEgressPolicyRequest) ProtoMessage() {} func (x *UpdateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5533,7 +5646,7 @@ func (x *UpdateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{72} + return file_ateapi_proto_rawDescGZIP(), []int{73} } func (x *UpdateActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5568,7 +5681,7 @@ type DeleteActorEgressPolicyRequest struct { func (x *DeleteActorEgressPolicyRequest) Reset() { *x = DeleteActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5580,7 +5693,7 @@ func (x *DeleteActorEgressPolicyRequest) String() string { func (*DeleteActorEgressPolicyRequest) ProtoMessage() {} func (x *DeleteActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5593,7 +5706,7 @@ func (x *DeleteActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{73} + return file_ateapi_proto_rawDescGZIP(), []int{74} } func (x *DeleteActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5621,7 +5734,7 @@ type GetTagRequest struct { func (x *GetTagRequest) Reset() { *x = GetTagRequest{} - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5633,7 +5746,7 @@ func (x *GetTagRequest) String() string { func (*GetTagRequest) ProtoMessage() {} func (x *GetTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5646,7 +5759,7 @@ func (x *GetTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTagRequest.ProtoReflect.Descriptor instead. func (*GetTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{74} + return file_ateapi_proto_rawDescGZIP(), []int{75} } func (x *GetTagRequest) GetTag() *ObjectRef { @@ -5685,7 +5798,7 @@ type MintActorJWTRequest struct { func (x *MintActorJWTRequest) Reset() { *x = MintActorJWTRequest{} - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5697,7 +5810,7 @@ func (x *MintActorJWTRequest) String() string { func (*MintActorJWTRequest) ProtoMessage() {} func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5710,7 +5823,7 @@ func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorJWTRequest.ProtoReflect.Descriptor instead. func (*MintActorJWTRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{75} + return file_ateapi_proto_rawDescGZIP(), []int{76} } func (x *MintActorJWTRequest) GetActor() *ObjectRef { @@ -5760,7 +5873,7 @@ type MintActorJWTResponse struct { func (x *MintActorJWTResponse) Reset() { *x = MintActorJWTResponse{} - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5772,7 +5885,7 @@ func (x *MintActorJWTResponse) String() string { func (*MintActorJWTResponse) ProtoMessage() {} func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5785,7 +5898,7 @@ func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorJWTResponse.ProtoReflect.Descriptor instead. func (*MintActorJWTResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{76} + return file_ateapi_proto_rawDescGZIP(), []int{77} } func (x *MintActorJWTResponse) GetActorJwt() string { @@ -5830,7 +5943,7 @@ type MintActorCertificateRequest struct { func (x *MintActorCertificateRequest) Reset() { *x = MintActorCertificateRequest{} - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5842,7 +5955,7 @@ func (x *MintActorCertificateRequest) String() string { func (*MintActorCertificateRequest) ProtoMessage() {} func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5855,7 +5968,7 @@ func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{77} + return file_ateapi_proto_rawDescGZIP(), []int{78} } func (x *MintActorCertificateRequest) GetActor() *ObjectRef { @@ -5892,7 +6005,7 @@ type MintActorCertificateResponse struct { func (x *MintActorCertificateResponse) Reset() { *x = MintActorCertificateResponse{} - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5904,7 +6017,7 @@ func (x *MintActorCertificateResponse) String() string { func (*MintActorCertificateResponse) ProtoMessage() {} func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5917,7 +6030,7 @@ func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{78} + return file_ateapi_proto_rawDescGZIP(), []int{79} } func (x *MintActorCertificateResponse) GetActorCertificates() [][]byte { @@ -5953,7 +6066,7 @@ type ListTagsRequest struct { func (x *ListTagsRequest) Reset() { *x = ListTagsRequest{} - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5965,7 +6078,7 @@ func (x *ListTagsRequest) String() string { func (*ListTagsRequest) ProtoMessage() {} func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5978,7 +6091,7 @@ func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTagsRequest.ProtoReflect.Descriptor instead. func (*ListTagsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{79} + return file_ateapi_proto_rawDescGZIP(), []int{80} } func (x *ListTagsRequest) GetAtespace() string { @@ -6012,7 +6125,7 @@ type ListTagsResponse struct { func (x *ListTagsResponse) Reset() { *x = ListTagsResponse{} - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6024,7 +6137,7 @@ func (x *ListTagsResponse) String() string { func (*ListTagsResponse) ProtoMessage() {} func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6037,7 +6150,7 @@ func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead. func (*ListTagsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{80} + return file_ateapi_proto_rawDescGZIP(), []int{81} } func (x *ListTagsResponse) GetTags() []*Tag { @@ -6082,7 +6195,7 @@ type CreateTagRequest struct { func (x *CreateTagRequest) Reset() { *x = CreateTagRequest{} - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6094,7 +6207,7 @@ func (x *CreateTagRequest) String() string { func (*CreateTagRequest) ProtoMessage() {} func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6107,7 +6220,7 @@ func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead. func (*CreateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{81} + return file_ateapi_proto_rawDescGZIP(), []int{82} } func (x *CreateTagRequest) GetTag() *Tag { @@ -6137,7 +6250,7 @@ type UpdateTagRequest struct { func (x *UpdateTagRequest) Reset() { *x = UpdateTagRequest{} - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6149,7 +6262,7 @@ func (x *UpdateTagRequest) String() string { func (*UpdateTagRequest) ProtoMessage() {} func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6162,7 +6275,7 @@ func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateTagRequest.ProtoReflect.Descriptor instead. func (*UpdateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{82} + return file_ateapi_proto_rawDescGZIP(), []int{83} } func (x *UpdateTagRequest) GetTag() *Tag { @@ -6187,7 +6300,7 @@ type DeleteTagRequest struct { func (x *DeleteTagRequest) Reset() { *x = DeleteTagRequest{} - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6199,7 +6312,7 @@ func (x *DeleteTagRequest) String() string { func (*DeleteTagRequest) ProtoMessage() {} func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6212,7 +6325,7 @@ func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteTagRequest.ProtoReflect.Descriptor instead. func (*DeleteTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{83} + return file_ateapi_proto_rawDescGZIP(), []int{84} } func (x *DeleteTagRequest) GetTag() *ObjectRef { @@ -6251,7 +6364,7 @@ type DeleteOptions struct { func (x *DeleteOptions) Reset() { *x = DeleteOptions{} - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6263,7 +6376,7 @@ func (x *DeleteOptions) String() string { func (*DeleteOptions) ProtoMessage() {} func (x *DeleteOptions) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6276,7 +6389,7 @@ func (x *DeleteOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteOptions.ProtoReflect.Descriptor instead. func (*DeleteOptions) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{84} + return file_ateapi_proto_rawDescGZIP(), []int{85} } func (x *DeleteOptions) GetVersion() int64 { @@ -6320,7 +6433,7 @@ type ListWorkerActorAssignmentsRequest struct { func (x *ListWorkerActorAssignmentsRequest) Reset() { *x = ListWorkerActorAssignmentsRequest{} - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6332,7 +6445,7 @@ func (x *ListWorkerActorAssignmentsRequest) String() string { func (*ListWorkerActorAssignmentsRequest) ProtoMessage() {} func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6345,7 +6458,7 @@ func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsRequest.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{85} + return file_ateapi_proto_rawDescGZIP(), []int{86} } func (x *ListWorkerActorAssignmentsRequest) GetWorker() *ObjectRef { @@ -6382,7 +6495,7 @@ type ListWorkerActorAssignmentsResponse struct { func (x *ListWorkerActorAssignmentsResponse) Reset() { *x = ListWorkerActorAssignmentsResponse{} - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6394,7 +6507,7 @@ func (x *ListWorkerActorAssignmentsResponse) String() string { func (*ListWorkerActorAssignmentsResponse) ProtoMessage() {} func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6407,7 +6520,7 @@ func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsResponse.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{86} + return file_ateapi_proto_rawDescGZIP(), []int{87} } func (x *ListWorkerActorAssignmentsResponse) GetActorAssignments() []*ActorAssignment { @@ -6445,7 +6558,7 @@ type ListWorkersRequest struct { func (x *ListWorkersRequest) Reset() { *x = ListWorkersRequest{} - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6457,7 +6570,7 @@ func (x *ListWorkersRequest) String() string { func (*ListWorkersRequest) ProtoMessage() {} func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6470,7 +6583,7 @@ func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersRequest.ProtoReflect.Descriptor instead. func (*ListWorkersRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{87} + return file_ateapi_proto_rawDescGZIP(), []int{88} } func (x *ListWorkersRequest) GetPageSize() int32 { @@ -6499,7 +6612,7 @@ type ListWorkersResponse struct { func (x *ListWorkersResponse) Reset() { *x = ListWorkersResponse{} - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6511,7 +6624,7 @@ func (x *ListWorkersResponse) String() string { func (*ListWorkersResponse) ProtoMessage() {} func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6524,7 +6637,7 @@ func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersResponse.ProtoReflect.Descriptor instead. func (*ListWorkersResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{88} + return file_ateapi_proto_rawDescGZIP(), []int{89} } func (x *ListWorkersResponse) GetWorkers() []*Worker { @@ -6554,7 +6667,7 @@ type GetWorkerRequest struct { func (x *GetWorkerRequest) Reset() { *x = GetWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6566,7 +6679,7 @@ func (x *GetWorkerRequest) String() string { func (*GetWorkerRequest) ProtoMessage() {} func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6579,7 +6692,7 @@ func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkerRequest.ProtoReflect.Descriptor instead. func (*GetWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{89} + return file_ateapi_proto_rawDescGZIP(), []int{90} } func (x *GetWorkerRequest) GetWorker() *ObjectRef { @@ -6601,7 +6714,7 @@ type CreateWorkerRequest struct { func (x *CreateWorkerRequest) Reset() { *x = CreateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[90] + mi := &file_ateapi_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6613,7 +6726,7 @@ func (x *CreateWorkerRequest) String() string { func (*CreateWorkerRequest) ProtoMessage() {} func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[90] + mi := &file_ateapi_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6626,7 +6739,7 @@ func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkerRequest.ProtoReflect.Descriptor instead. func (*CreateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{90} + return file_ateapi_proto_rawDescGZIP(), []int{91} } func (x *CreateWorkerRequest) GetWorker() *Worker { @@ -6661,7 +6774,7 @@ type UpdateWorkerRequest struct { func (x *UpdateWorkerRequest) Reset() { *x = UpdateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[91] + mi := &file_ateapi_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6673,7 +6786,7 @@ func (x *UpdateWorkerRequest) String() string { func (*UpdateWorkerRequest) ProtoMessage() {} func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[91] + mi := &file_ateapi_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6686,7 +6799,7 @@ func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkerRequest.ProtoReflect.Descriptor instead. func (*UpdateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{91} + return file_ateapi_proto_rawDescGZIP(), []int{92} } func (x *UpdateWorkerRequest) GetWorker() *Worker { @@ -6713,7 +6826,7 @@ type DeleteWorkerRequest struct { func (x *DeleteWorkerRequest) Reset() { *x = DeleteWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[92] + mi := &file_ateapi_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6725,7 +6838,7 @@ func (x *DeleteWorkerRequest) String() string { func (*DeleteWorkerRequest) ProtoMessage() {} func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[92] + mi := &file_ateapi_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6738,7 +6851,7 @@ func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkerRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{92} + return file_ateapi_proto_rawDescGZIP(), []int{93} } func (x *DeleteWorkerRequest) GetWorker() *ObjectRef { @@ -6768,7 +6881,7 @@ type DrainWorkerRequest struct { func (x *DrainWorkerRequest) Reset() { *x = DrainWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[93] + mi := &file_ateapi_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6780,7 +6893,7 @@ func (x *DrainWorkerRequest) String() string { func (*DrainWorkerRequest) ProtoMessage() {} func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[93] + mi := &file_ateapi_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6793,7 +6906,7 @@ func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DrainWorkerRequest.ProtoReflect.Descriptor instead. func (*DrainWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{93} + return file_ateapi_proto_rawDescGZIP(), []int{94} } func (x *DrainWorkerRequest) GetWorker() *ObjectRef { @@ -6831,7 +6944,7 @@ type ListActorsRequest struct { func (x *ListActorsRequest) Reset() { *x = ListActorsRequest{} - mi := &file_ateapi_proto_msgTypes[94] + mi := &file_ateapi_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6843,7 +6956,7 @@ func (x *ListActorsRequest) String() string { func (*ListActorsRequest) ProtoMessage() {} func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[94] + mi := &file_ateapi_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6856,7 +6969,7 @@ func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsRequest.ProtoReflect.Descriptor instead. func (*ListActorsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{94} + return file_ateapi_proto_rawDescGZIP(), []int{95} } func (x *ListActorsRequest) GetAtespace() string { @@ -6892,7 +7005,7 @@ type ListActorsResponse struct { func (x *ListActorsResponse) Reset() { *x = ListActorsResponse{} - mi := &file_ateapi_proto_msgTypes[95] + mi := &file_ateapi_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6904,7 +7017,7 @@ func (x *ListActorsResponse) String() string { func (*ListActorsResponse) ProtoMessage() {} func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[95] + mi := &file_ateapi_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6917,7 +7030,7 @@ func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsResponse.ProtoReflect.Descriptor instead. func (*ListActorsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{95} + return file_ateapi_proto_rawDescGZIP(), []int{96} } func (x *ListActorsResponse) GetActors() []*Actor { @@ -7024,7 +7137,7 @@ type Worker struct { func (x *Worker) Reset() { *x = Worker{} - mi := &file_ateapi_proto_msgTypes[96] + mi := &file_ateapi_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7036,7 +7149,7 @@ func (x *Worker) String() string { func (*Worker) ProtoMessage() {} func (x *Worker) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[96] + mi := &file_ateapi_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7049,7 +7162,7 @@ func (x *Worker) ProtoReflect() protoreflect.Message { // Deprecated: Use Worker.ProtoReflect.Descriptor instead. func (*Worker) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{96} + return file_ateapi_proto_rawDescGZIP(), []int{97} } func (x *Worker) GetMetadata() *ResourceMetadata { @@ -7178,7 +7291,7 @@ type WorkerStatus struct { func (x *WorkerStatus) Reset() { *x = WorkerStatus{} - mi := &file_ateapi_proto_msgTypes[97] + mi := &file_ateapi_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7190,7 +7303,7 @@ func (x *WorkerStatus) String() string { func (*WorkerStatus) ProtoMessage() {} func (x *WorkerStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[97] + mi := &file_ateapi_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7203,7 +7316,7 @@ func (x *WorkerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerStatus.ProtoReflect.Descriptor instead. func (*WorkerStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{97} + return file_ateapi_proto_rawDescGZIP(), []int{98} } func (x *WorkerStatus) GetState() WorkerState { @@ -7270,7 +7383,7 @@ type SandboxRuntime struct { func (x *SandboxRuntime) Reset() { *x = SandboxRuntime{} - mi := &file_ateapi_proto_msgTypes[98] + mi := &file_ateapi_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7282,7 +7395,7 @@ func (x *SandboxRuntime) String() string { func (*SandboxRuntime) ProtoMessage() {} func (x *SandboxRuntime) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[98] + mi := &file_ateapi_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7295,7 +7408,7 @@ func (x *SandboxRuntime) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxRuntime.ProtoReflect.Descriptor instead. func (*SandboxRuntime) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{98} + return file_ateapi_proto_rawDescGZIP(), []int{99} } func (x *SandboxRuntime) GetSandboxClass() string { @@ -7343,7 +7456,7 @@ type VersionedSandboxCompat struct { func (x *VersionedSandboxCompat) Reset() { *x = VersionedSandboxCompat{} - mi := &file_ateapi_proto_msgTypes[99] + mi := &file_ateapi_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7355,7 +7468,7 @@ func (x *VersionedSandboxCompat) String() string { func (*VersionedSandboxCompat) ProtoMessage() {} func (x *VersionedSandboxCompat) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[99] + mi := &file_ateapi_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7368,7 +7481,7 @@ func (x *VersionedSandboxCompat) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionedSandboxCompat.ProtoReflect.Descriptor instead. func (*VersionedSandboxCompat) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{99} + return file_ateapi_proto_rawDescGZIP(), []int{100} } func (x *VersionedSandboxCompat) GetSchemaVersion() string { @@ -7404,7 +7517,7 @@ type AttributeEntry struct { func (x *AttributeEntry) Reset() { *x = AttributeEntry{} - mi := &file_ateapi_proto_msgTypes[100] + mi := &file_ateapi_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7416,7 +7529,7 @@ func (x *AttributeEntry) String() string { func (*AttributeEntry) ProtoMessage() {} func (x *AttributeEntry) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[100] + mi := &file_ateapi_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7429,7 +7542,7 @@ func (x *AttributeEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use AttributeEntry.ProtoReflect.Descriptor instead. func (*AttributeEntry) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{100} + return file_ateapi_proto_rawDescGZIP(), []int{101} } func (x *AttributeEntry) GetKey() string { @@ -7468,7 +7581,7 @@ type WorkerResources struct { func (x *WorkerResources) Reset() { *x = WorkerResources{} - mi := &file_ateapi_proto_msgTypes[101] + mi := &file_ateapi_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7480,7 +7593,7 @@ func (x *WorkerResources) String() string { func (*WorkerResources) ProtoMessage() {} func (x *WorkerResources) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[101] + mi := &file_ateapi_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7493,7 +7606,7 @@ func (x *WorkerResources) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerResources.ProtoReflect.Descriptor instead. func (*WorkerResources) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{101} + return file_ateapi_proto_rawDescGZIP(), []int{102} } func (x *WorkerResources) GetResources() *Resources { @@ -7546,7 +7659,7 @@ type ActorAssignment struct { func (x *ActorAssignment) Reset() { *x = ActorAssignment{} - mi := &file_ateapi_proto_msgTypes[102] + mi := &file_ateapi_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7558,7 +7671,7 @@ func (x *ActorAssignment) String() string { func (*ActorAssignment) ProtoMessage() {} func (x *ActorAssignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[102] + mi := &file_ateapi_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7571,7 +7684,7 @@ func (x *ActorAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorAssignment.ProtoReflect.Descriptor instead. func (*ActorAssignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{102} + return file_ateapi_proto_rawDescGZIP(), []int{103} } func (x *ActorAssignment) GetMetadata() *ResourceMetadata { @@ -7642,7 +7755,7 @@ type RegisterWorkerRequest struct { func (x *RegisterWorkerRequest) Reset() { *x = RegisterWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[103] + mi := &file_ateapi_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7654,7 +7767,7 @@ func (x *RegisterWorkerRequest) String() string { func (*RegisterWorkerRequest) ProtoMessage() {} func (x *RegisterWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[103] + mi := &file_ateapi_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7667,7 +7780,7 @@ func (x *RegisterWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterWorkerRequest.ProtoReflect.Descriptor instead. func (*RegisterWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{103} + return file_ateapi_proto_rawDescGZIP(), []int{104} } func (x *RegisterWorkerRequest) GetWorker() *ObjectRef { @@ -7708,7 +7821,7 @@ type RegisterWorkerResponse struct { func (x *RegisterWorkerResponse) Reset() { *x = RegisterWorkerResponse{} - mi := &file_ateapi_proto_msgTypes[104] + mi := &file_ateapi_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7720,7 +7833,7 @@ func (x *RegisterWorkerResponse) String() string { func (*RegisterWorkerResponse) ProtoMessage() {} func (x *RegisterWorkerResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[104] + mi := &file_ateapi_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7733,7 +7846,7 @@ func (x *RegisterWorkerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterWorkerResponse.ProtoReflect.Descriptor instead. func (*RegisterWorkerResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{104} + return file_ateapi_proto_rawDescGZIP(), []int{105} } func (x *RegisterWorkerResponse) GetWorker() *Worker { @@ -7772,7 +7885,7 @@ type MintAteomActorCertificateRequest struct { func (x *MintAteomActorCertificateRequest) Reset() { *x = MintAteomActorCertificateRequest{} - mi := &file_ateapi_proto_msgTypes[105] + mi := &file_ateapi_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7784,7 +7897,7 @@ func (x *MintAteomActorCertificateRequest) String() string { func (*MintAteomActorCertificateRequest) ProtoMessage() {} func (x *MintAteomActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[105] + mi := &file_ateapi_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7797,7 +7910,7 @@ func (x *MintAteomActorCertificateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintAteomActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintAteomActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{105} + return file_ateapi_proto_rawDescGZIP(), []int{106} } func (x *MintAteomActorCertificateRequest) GetActor() *ObjectRef { @@ -7834,7 +7947,7 @@ type MintAteomActorCertificateResponse struct { func (x *MintAteomActorCertificateResponse) Reset() { *x = MintAteomActorCertificateResponse{} - mi := &file_ateapi_proto_msgTypes[106] + mi := &file_ateapi_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7846,7 +7959,7 @@ func (x *MintAteomActorCertificateResponse) String() string { func (*MintAteomActorCertificateResponse) ProtoMessage() {} func (x *MintAteomActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[106] + mi := &file_ateapi_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7859,7 +7972,7 @@ func (x *MintAteomActorCertificateResponse) ProtoReflect() protoreflect.Message // Deprecated: Use MintAteomActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintAteomActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{106} + return file_ateapi_proto_rawDescGZIP(), []int{107} } func (x *MintAteomActorCertificateResponse) GetActorCertificates() [][]byte { @@ -7897,7 +8010,7 @@ type RequestActorSuspendRequest struct { func (x *RequestActorSuspendRequest) Reset() { *x = RequestActorSuspendRequest{} - mi := &file_ateapi_proto_msgTypes[107] + mi := &file_ateapi_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7909,7 +8022,7 @@ func (x *RequestActorSuspendRequest) String() string { func (*RequestActorSuspendRequest) ProtoMessage() {} func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[107] + mi := &file_ateapi_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7922,7 +8035,7 @@ func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendRequest.ProtoReflect.Descriptor instead. func (*RequestActorSuspendRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{107} + return file_ateapi_proto_rawDescGZIP(), []int{108} } func (x *RequestActorSuspendRequest) GetWorker() *ObjectRef { @@ -7956,7 +8069,7 @@ type RequestActorSuspendResponse struct { func (x *RequestActorSuspendResponse) Reset() { *x = RequestActorSuspendResponse{} - mi := &file_ateapi_proto_msgTypes[108] + mi := &file_ateapi_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7968,7 +8081,7 @@ func (x *RequestActorSuspendResponse) String() string { func (*RequestActorSuspendResponse) ProtoMessage() {} func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[108] + mi := &file_ateapi_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7981,7 +8094,7 @@ func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendResponse.ProtoReflect.Descriptor instead. func (*RequestActorSuspendResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{108} + return file_ateapi_proto_rawDescGZIP(), []int{109} } func (x *RequestActorSuspendResponse) GetActor() *Actor { @@ -8016,7 +8129,7 @@ type AccessPolicy struct { func (x *AccessPolicy) Reset() { *x = AccessPolicy{} - mi := &file_ateapi_proto_msgTypes[109] + mi := &file_ateapi_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8028,7 +8141,7 @@ func (x *AccessPolicy) String() string { func (*AccessPolicy) ProtoMessage() {} func (x *AccessPolicy) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[109] + mi := &file_ateapi_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8041,7 +8154,7 @@ func (x *AccessPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessPolicy.ProtoReflect.Descriptor instead. func (*AccessPolicy) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{109} + return file_ateapi_proto_rawDescGZIP(), []int{110} } func (x *AccessPolicy) GetMetadata() *ResourceMetadata { @@ -8083,7 +8196,7 @@ type Binding struct { func (x *Binding) Reset() { *x = Binding{} - mi := &file_ateapi_proto_msgTypes[110] + mi := &file_ateapi_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8095,7 +8208,7 @@ func (x *Binding) String() string { func (*Binding) ProtoMessage() {} func (x *Binding) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[110] + mi := &file_ateapi_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8108,7 +8221,7 @@ func (x *Binding) ProtoReflect() protoreflect.Message { // Deprecated: Use Binding.ProtoReflect.Descriptor instead. func (*Binding) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{110} + return file_ateapi_proto_rawDescGZIP(), []int{111} } func (x *Binding) GetRole() string { @@ -8134,7 +8247,7 @@ type GetGlobalAccessPolicyRequest struct { func (x *GetGlobalAccessPolicyRequest) Reset() { *x = GetGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[111] + mi := &file_ateapi_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8146,7 +8259,7 @@ func (x *GetGlobalAccessPolicyRequest) String() string { func (*GetGlobalAccessPolicyRequest) ProtoMessage() {} func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[111] + mi := &file_ateapi_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8159,7 +8272,7 @@ func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{111} + return file_ateapi_proto_rawDescGZIP(), []int{112} } // CreateGlobalAccessPolicyRequest creates the deployment-wide global access policy singleton. @@ -8176,7 +8289,7 @@ type CreateGlobalAccessPolicyRequest struct { func (x *CreateGlobalAccessPolicyRequest) Reset() { *x = CreateGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[112] + mi := &file_ateapi_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8188,7 +8301,7 @@ func (x *CreateGlobalAccessPolicyRequest) String() string { func (*CreateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[112] + mi := &file_ateapi_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8201,7 +8314,7 @@ func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{112} + return file_ateapi_proto_rawDescGZIP(), []int{113} } func (x *CreateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -8225,7 +8338,7 @@ type UpdateGlobalAccessPolicyRequest struct { func (x *UpdateGlobalAccessPolicyRequest) Reset() { *x = UpdateGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[113] + mi := &file_ateapi_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8237,7 +8350,7 @@ func (x *UpdateGlobalAccessPolicyRequest) String() string { func (*UpdateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[113] + mi := &file_ateapi_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8250,7 +8363,7 @@ func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{113} + return file_ateapi_proto_rawDescGZIP(), []int{114} } func (x *UpdateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -8274,7 +8387,7 @@ type GetAtespaceAccessPolicyRequest struct { func (x *GetAtespaceAccessPolicyRequest) Reset() { *x = GetAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[114] + mi := &file_ateapi_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8286,7 +8399,7 @@ func (x *GetAtespaceAccessPolicyRequest) String() string { func (*GetAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[114] + mi := &file_ateapi_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8299,7 +8412,7 @@ func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{114} + return file_ateapi_proto_rawDescGZIP(), []int{115} } func (x *GetAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8328,7 +8441,7 @@ type CreateAtespaceAccessPolicyRequest struct { func (x *CreateAtespaceAccessPolicyRequest) Reset() { *x = CreateAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[115] + mi := &file_ateapi_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8340,7 +8453,7 @@ func (x *CreateAtespaceAccessPolicyRequest) String() string { func (*CreateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[115] + mi := &file_ateapi_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8353,7 +8466,7 @@ func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{115} + return file_ateapi_proto_rawDescGZIP(), []int{116} } func (x *CreateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8389,7 +8502,7 @@ type UpdateAtespaceAccessPolicyRequest struct { func (x *UpdateAtespaceAccessPolicyRequest) Reset() { *x = UpdateAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[116] + mi := &file_ateapi_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8401,7 +8514,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) String() string { func (*UpdateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[116] + mi := &file_ateapi_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8414,7 +8527,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{116} + return file_ateapi_proto_rawDescGZIP(), []int{117} } func (x *UpdateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8449,7 +8562,7 @@ type DeleteAtespaceAccessPolicyRequest struct { func (x *DeleteAtespaceAccessPolicyRequest) Reset() { *x = DeleteAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[117] + mi := &file_ateapi_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8461,7 +8574,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) String() string { func (*DeleteAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[117] + mi := &file_ateapi_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8474,7 +8587,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{117} + return file_ateapi_proto_rawDescGZIP(), []int{118} } func (x *DeleteAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8649,10 +8762,13 @@ const file_ateapi_proto_rawDesc = "" + "\rSandboxConfig\x129\n" + "\rsandbox_class\x18\x01 \x01(\x0e2\x14.ateapi.SandboxClassR\fsandboxClass\x12\x1f\n" + "\vconfig_name\x18\x02 \x01(\tR\n" + - "configName\"\x84\x01\n" + + "configName\"\xd8\x01\n" + "\x0eSnapshotConfig\x12)\n" + "\x10storage_location\x18\x01 \x01(\tR\x0fstorageLocation\x12G\n" + - "\x12preferred_fidelity\x18\x02 \x01(\x0e2\x18.ateapi.SnapshotFidelityR\x11preferredFidelity\"\xf5\x02\n" + + "\x12preferred_fidelity\x18\x02 \x01(\x0e2\x18.ateapi.SnapshotFidelityR\x11preferredFidelity\x12R\n" + + "\x16golden_snapshot_config\x18\x05 \x01(\v2\x1c.ateapi.GoldenSnapshotConfigR\x14goldenSnapshotConfig\"F\n" + + "\x14GoldenSnapshotConfig\x12.\n" + + "\x04mode\x18\x01 \x01(\x0e2\x1a.ateapi.GoldenSnapshotModeR\x04mode\"\xf5\x02\n" + "\tContainer\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05image\x18\x02 \x01(\tR\x05image\x12\x18\n" + @@ -8964,7 +9080,11 @@ const file_ateapi_proto_rawDesc = "" + "\fSandboxClass\x12\x1d\n" + "\x19SANDBOX_CLASS_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14SANDBOX_CLASS_GVISOR\x10\x01\x12\x19\n" + - "\x15SANDBOX_CLASS_MICROVM\x10\x02*\x9a\x01\n" + + "\x15SANDBOX_CLASS_MICROVM\x10\x02*\x7f\n" + + "\x12GoldenSnapshotMode\x12$\n" + + " GOLDEN_SNAPSHOT_MODE_UNSPECIFIED\x10\x00\x12 \n" + + "\x1cGOLDEN_SNAPSHOT_MODE_ENABLED\x10\x01\x12!\n" + + "\x1dGOLDEN_SNAPSHOT_MODE_DISABLED\x10\x02*\x9a\x01\n" + "\x12ActorMetadataField\x12$\n" + " ACTOR_METADATA_FIELD_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19ACTOR_METADATA_FIELD_NAME\x10\x01\x12!\n" + @@ -9036,8 +9156,8 @@ func file_ateapi_proto_rawDescGZIP() []byte { return file_ateapi_proto_rawDescData } -var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 10) -var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 121) +var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 11) +var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 122) var file_ateapi_proto_goTypes = []any{ (SnapshotOwner)(0), // 0: ateapi.SnapshotOwner (SnapshotDurability)(0), // 1: ateapi.SnapshotDurability @@ -9046,384 +9166,388 @@ var file_ateapi_proto_goTypes = []any{ (TagScope)(0), // 4: ateapi.TagScope (ActorState)(0), // 5: ateapi.ActorState (SandboxClass)(0), // 6: ateapi.SandboxClass - (ActorMetadataField)(0), // 7: ateapi.ActorMetadataField - (WorkerState)(0), // 8: ateapi.WorkerState - (ExternalVolume_Status)(0), // 9: ateapi.ExternalVolume.Status - (*ObjectSnapshot)(nil), // 10: ateapi.ObjectSnapshot - (*LocalSnapshot)(nil), // 11: ateapi.LocalSnapshot - (*SnapshotStorage)(nil), // 12: ateapi.SnapshotStorage - (*Snapshot)(nil), // 13: ateapi.Snapshot - (*Selector)(nil), // 14: ateapi.Selector - (*ResourceMetadata)(nil), // 15: ateapi.ResourceMetadata - (*ExternalVolume)(nil), // 16: ateapi.ExternalVolume - (*Actor)(nil), // 17: ateapi.Actor - (*EgressPolicy)(nil), // 18: ateapi.EgressPolicy - (*EgressRule)(nil), // 19: ateapi.EgressRule - (*HTTPRule)(nil), // 20: ateapi.HTTPRule - (*HTTPSRule)(nil), // 21: ateapi.HTTPSRule - (*TLSPassthroughRule)(nil), // 22: ateapi.TLSPassthroughRule - (*Ports)(nil), // 23: ateapi.Ports - (*AllPorts)(nil), // 24: ateapi.AllPorts - (*HttpRuleEffects)(nil), // 25: ateapi.HttpRuleEffects - (*CredentialHeader)(nil), // 26: ateapi.CredentialHeader - (*ActorJWTSource)(nil), // 27: ateapi.ActorJWTSource - (*ActorStatus)(nil), // 28: ateapi.ActorStatus - (*ActorCrash)(nil), // 29: ateapi.ActorCrash - (*WorkerAssignment)(nil), // 30: ateapi.WorkerAssignment - (*TagStatus)(nil), // 31: ateapi.TagStatus - (*Tag)(nil), // 32: ateapi.Tag - (*Atespace)(nil), // 33: ateapi.Atespace - (*ObjectRef)(nil), // 34: ateapi.ObjectRef - (*ActorTemplate)(nil), // 35: ateapi.ActorTemplate - (*Resources)(nil), // 36: ateapi.Resources - (*Limits)(nil), // 37: ateapi.Limits - (*GoldenSnapshotStatus)(nil), // 38: ateapi.GoldenSnapshotStatus - (*ActorTemplateStatus)(nil), // 39: ateapi.ActorTemplateStatus - (*SandboxConfig)(nil), // 40: ateapi.SandboxConfig - (*SnapshotConfig)(nil), // 41: ateapi.SnapshotConfig - (*Container)(nil), // 42: ateapi.Container - (*SecurityContext)(nil), // 43: ateapi.SecurityContext - (*Capabilities)(nil), // 44: ateapi.Capabilities - (*EnvVar)(nil), // 45: ateapi.EnvVar - (*ContainerWakeupProbe)(nil), // 46: ateapi.ContainerWakeupProbe - (*HTTPGetAction)(nil), // 47: ateapi.HTTPGetAction - (*Volume)(nil), // 48: ateapi.Volume - (*ImageVolumeSource)(nil), // 49: ateapi.ImageVolumeSource - (*DurableDirVolumeSource)(nil), // 50: ateapi.DurableDirVolumeSource - (*ExternalVolumeTemplate)(nil), // 51: ateapi.ExternalVolumeTemplate - (*SystemInfoVolumeSource)(nil), // 52: ateapi.SystemInfoVolumeSource - (*SystemInfoDataSource)(nil), // 53: ateapi.SystemInfoDataSource - (*ActorMetadataDataSource)(nil), // 54: ateapi.ActorMetadataDataSource - (*ActorMetadataItem)(nil), // 55: ateapi.ActorMetadataItem - (*TrustBundleDataSource)(nil), // 56: ateapi.TrustBundleDataSource - (*VolumeMount)(nil), // 57: ateapi.VolumeMount - (*CreateAtespaceRequest)(nil), // 58: ateapi.CreateAtespaceRequest - (*GetAtespaceRequest)(nil), // 59: ateapi.GetAtespaceRequest - (*ListAtespacesRequest)(nil), // 60: ateapi.ListAtespacesRequest - (*ListAtespacesResponse)(nil), // 61: ateapi.ListAtespacesResponse - (*DeleteAtespaceRequest)(nil), // 62: ateapi.DeleteAtespaceRequest - (*CreateActorTemplateRequest)(nil), // 63: ateapi.CreateActorTemplateRequest - (*GetActorTemplateRequest)(nil), // 64: ateapi.GetActorTemplateRequest - (*ListActorTemplatesRequest)(nil), // 65: ateapi.ListActorTemplatesRequest - (*ListActorTemplatesResponse)(nil), // 66: ateapi.ListActorTemplatesResponse - (*DeleteActorTemplateRequest)(nil), // 67: ateapi.DeleteActorTemplateRequest - (*GetActorRequest)(nil), // 68: ateapi.GetActorRequest - (*CreateActorRequest)(nil), // 69: ateapi.CreateActorRequest - (*UpdateActorRequest)(nil), // 70: ateapi.UpdateActorRequest - (*SuspendActorRequest)(nil), // 71: ateapi.SuspendActorRequest - (*SuspendActorResponse)(nil), // 72: ateapi.SuspendActorResponse - (*PauseActorRequest)(nil), // 73: ateapi.PauseActorRequest - (*PauseActorResponse)(nil), // 74: ateapi.PauseActorResponse - (*ResumeActorRequest)(nil), // 75: ateapi.ResumeActorRequest - (*ResumeActorResponse)(nil), // 76: ateapi.ResumeActorResponse - (*RevertActorRequest)(nil), // 77: ateapi.RevertActorRequest - (*RevertActorResponse)(nil), // 78: ateapi.RevertActorResponse - (*DeleteActorRequest)(nil), // 79: ateapi.DeleteActorRequest - (*GetActorEgressPolicyRequest)(nil), // 80: ateapi.GetActorEgressPolicyRequest - (*CreateActorEgressPolicyRequest)(nil), // 81: ateapi.CreateActorEgressPolicyRequest - (*UpdateActorEgressPolicyRequest)(nil), // 82: ateapi.UpdateActorEgressPolicyRequest - (*DeleteActorEgressPolicyRequest)(nil), // 83: ateapi.DeleteActorEgressPolicyRequest - (*GetTagRequest)(nil), // 84: ateapi.GetTagRequest - (*MintActorJWTRequest)(nil), // 85: ateapi.MintActorJWTRequest - (*MintActorJWTResponse)(nil), // 86: ateapi.MintActorJWTResponse - (*MintActorCertificateRequest)(nil), // 87: ateapi.MintActorCertificateRequest - (*MintActorCertificateResponse)(nil), // 88: ateapi.MintActorCertificateResponse - (*ListTagsRequest)(nil), // 89: ateapi.ListTagsRequest - (*ListTagsResponse)(nil), // 90: ateapi.ListTagsResponse - (*CreateTagRequest)(nil), // 91: ateapi.CreateTagRequest - (*UpdateTagRequest)(nil), // 92: ateapi.UpdateTagRequest - (*DeleteTagRequest)(nil), // 93: ateapi.DeleteTagRequest - (*DeleteOptions)(nil), // 94: ateapi.DeleteOptions - (*ListWorkerActorAssignmentsRequest)(nil), // 95: ateapi.ListWorkerActorAssignmentsRequest - (*ListWorkerActorAssignmentsResponse)(nil), // 96: ateapi.ListWorkerActorAssignmentsResponse - (*ListWorkersRequest)(nil), // 97: ateapi.ListWorkersRequest - (*ListWorkersResponse)(nil), // 98: ateapi.ListWorkersResponse - (*GetWorkerRequest)(nil), // 99: ateapi.GetWorkerRequest - (*CreateWorkerRequest)(nil), // 100: ateapi.CreateWorkerRequest - (*UpdateWorkerRequest)(nil), // 101: ateapi.UpdateWorkerRequest - (*DeleteWorkerRequest)(nil), // 102: ateapi.DeleteWorkerRequest - (*DrainWorkerRequest)(nil), // 103: ateapi.DrainWorkerRequest - (*ListActorsRequest)(nil), // 104: ateapi.ListActorsRequest - (*ListActorsResponse)(nil), // 105: ateapi.ListActorsResponse - (*Worker)(nil), // 106: ateapi.Worker - (*WorkerStatus)(nil), // 107: ateapi.WorkerStatus - (*SandboxRuntime)(nil), // 108: ateapi.SandboxRuntime - (*VersionedSandboxCompat)(nil), // 109: ateapi.VersionedSandboxCompat - (*AttributeEntry)(nil), // 110: ateapi.AttributeEntry - (*WorkerResources)(nil), // 111: ateapi.WorkerResources - (*ActorAssignment)(nil), // 112: ateapi.ActorAssignment - (*RegisterWorkerRequest)(nil), // 113: ateapi.RegisterWorkerRequest - (*RegisterWorkerResponse)(nil), // 114: ateapi.RegisterWorkerResponse - (*MintAteomActorCertificateRequest)(nil), // 115: ateapi.MintAteomActorCertificateRequest - (*MintAteomActorCertificateResponse)(nil), // 116: ateapi.MintAteomActorCertificateResponse - (*RequestActorSuspendRequest)(nil), // 117: ateapi.RequestActorSuspendRequest - (*RequestActorSuspendResponse)(nil), // 118: ateapi.RequestActorSuspendResponse - (*AccessPolicy)(nil), // 119: ateapi.AccessPolicy - (*Binding)(nil), // 120: ateapi.Binding - (*GetGlobalAccessPolicyRequest)(nil), // 121: ateapi.GetGlobalAccessPolicyRequest - (*CreateGlobalAccessPolicyRequest)(nil), // 122: ateapi.CreateGlobalAccessPolicyRequest - (*UpdateGlobalAccessPolicyRequest)(nil), // 123: ateapi.UpdateGlobalAccessPolicyRequest - (*GetAtespaceAccessPolicyRequest)(nil), // 124: ateapi.GetAtespaceAccessPolicyRequest - (*CreateAtespaceAccessPolicyRequest)(nil), // 125: ateapi.CreateAtespaceAccessPolicyRequest - (*UpdateAtespaceAccessPolicyRequest)(nil), // 126: ateapi.UpdateAtespaceAccessPolicyRequest - (*DeleteAtespaceAccessPolicyRequest)(nil), // 127: ateapi.DeleteAtespaceAccessPolicyRequest - nil, // 128: ateapi.Selector.MatchLabelsEntry - nil, // 129: ateapi.ExternalVolume.VolumeContextEntry - nil, // 130: ateapi.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 131: google.protobuf.Timestamp + (GoldenSnapshotMode)(0), // 7: ateapi.GoldenSnapshotMode + (ActorMetadataField)(0), // 8: ateapi.ActorMetadataField + (WorkerState)(0), // 9: ateapi.WorkerState + (ExternalVolume_Status)(0), // 10: ateapi.ExternalVolume.Status + (*ObjectSnapshot)(nil), // 11: ateapi.ObjectSnapshot + (*LocalSnapshot)(nil), // 12: ateapi.LocalSnapshot + (*SnapshotStorage)(nil), // 13: ateapi.SnapshotStorage + (*Snapshot)(nil), // 14: ateapi.Snapshot + (*Selector)(nil), // 15: ateapi.Selector + (*ResourceMetadata)(nil), // 16: ateapi.ResourceMetadata + (*ExternalVolume)(nil), // 17: ateapi.ExternalVolume + (*Actor)(nil), // 18: ateapi.Actor + (*EgressPolicy)(nil), // 19: ateapi.EgressPolicy + (*EgressRule)(nil), // 20: ateapi.EgressRule + (*HTTPRule)(nil), // 21: ateapi.HTTPRule + (*HTTPSRule)(nil), // 22: ateapi.HTTPSRule + (*TLSPassthroughRule)(nil), // 23: ateapi.TLSPassthroughRule + (*Ports)(nil), // 24: ateapi.Ports + (*AllPorts)(nil), // 25: ateapi.AllPorts + (*HttpRuleEffects)(nil), // 26: ateapi.HttpRuleEffects + (*CredentialHeader)(nil), // 27: ateapi.CredentialHeader + (*ActorJWTSource)(nil), // 28: ateapi.ActorJWTSource + (*ActorStatus)(nil), // 29: ateapi.ActorStatus + (*ActorCrash)(nil), // 30: ateapi.ActorCrash + (*WorkerAssignment)(nil), // 31: ateapi.WorkerAssignment + (*TagStatus)(nil), // 32: ateapi.TagStatus + (*Tag)(nil), // 33: ateapi.Tag + (*Atespace)(nil), // 34: ateapi.Atespace + (*ObjectRef)(nil), // 35: ateapi.ObjectRef + (*ActorTemplate)(nil), // 36: ateapi.ActorTemplate + (*Resources)(nil), // 37: ateapi.Resources + (*Limits)(nil), // 38: ateapi.Limits + (*GoldenSnapshotStatus)(nil), // 39: ateapi.GoldenSnapshotStatus + (*ActorTemplateStatus)(nil), // 40: ateapi.ActorTemplateStatus + (*SandboxConfig)(nil), // 41: ateapi.SandboxConfig + (*SnapshotConfig)(nil), // 42: ateapi.SnapshotConfig + (*GoldenSnapshotConfig)(nil), // 43: ateapi.GoldenSnapshotConfig + (*Container)(nil), // 44: ateapi.Container + (*SecurityContext)(nil), // 45: ateapi.SecurityContext + (*Capabilities)(nil), // 46: ateapi.Capabilities + (*EnvVar)(nil), // 47: ateapi.EnvVar + (*ContainerWakeupProbe)(nil), // 48: ateapi.ContainerWakeupProbe + (*HTTPGetAction)(nil), // 49: ateapi.HTTPGetAction + (*Volume)(nil), // 50: ateapi.Volume + (*ImageVolumeSource)(nil), // 51: ateapi.ImageVolumeSource + (*DurableDirVolumeSource)(nil), // 52: ateapi.DurableDirVolumeSource + (*ExternalVolumeTemplate)(nil), // 53: ateapi.ExternalVolumeTemplate + (*SystemInfoVolumeSource)(nil), // 54: ateapi.SystemInfoVolumeSource + (*SystemInfoDataSource)(nil), // 55: ateapi.SystemInfoDataSource + (*ActorMetadataDataSource)(nil), // 56: ateapi.ActorMetadataDataSource + (*ActorMetadataItem)(nil), // 57: ateapi.ActorMetadataItem + (*TrustBundleDataSource)(nil), // 58: ateapi.TrustBundleDataSource + (*VolumeMount)(nil), // 59: ateapi.VolumeMount + (*CreateAtespaceRequest)(nil), // 60: ateapi.CreateAtespaceRequest + (*GetAtespaceRequest)(nil), // 61: ateapi.GetAtespaceRequest + (*ListAtespacesRequest)(nil), // 62: ateapi.ListAtespacesRequest + (*ListAtespacesResponse)(nil), // 63: ateapi.ListAtespacesResponse + (*DeleteAtespaceRequest)(nil), // 64: ateapi.DeleteAtespaceRequest + (*CreateActorTemplateRequest)(nil), // 65: ateapi.CreateActorTemplateRequest + (*GetActorTemplateRequest)(nil), // 66: ateapi.GetActorTemplateRequest + (*ListActorTemplatesRequest)(nil), // 67: ateapi.ListActorTemplatesRequest + (*ListActorTemplatesResponse)(nil), // 68: ateapi.ListActorTemplatesResponse + (*DeleteActorTemplateRequest)(nil), // 69: ateapi.DeleteActorTemplateRequest + (*GetActorRequest)(nil), // 70: ateapi.GetActorRequest + (*CreateActorRequest)(nil), // 71: ateapi.CreateActorRequest + (*UpdateActorRequest)(nil), // 72: ateapi.UpdateActorRequest + (*SuspendActorRequest)(nil), // 73: ateapi.SuspendActorRequest + (*SuspendActorResponse)(nil), // 74: ateapi.SuspendActorResponse + (*PauseActorRequest)(nil), // 75: ateapi.PauseActorRequest + (*PauseActorResponse)(nil), // 76: ateapi.PauseActorResponse + (*ResumeActorRequest)(nil), // 77: ateapi.ResumeActorRequest + (*ResumeActorResponse)(nil), // 78: ateapi.ResumeActorResponse + (*RevertActorRequest)(nil), // 79: ateapi.RevertActorRequest + (*RevertActorResponse)(nil), // 80: ateapi.RevertActorResponse + (*DeleteActorRequest)(nil), // 81: ateapi.DeleteActorRequest + (*GetActorEgressPolicyRequest)(nil), // 82: ateapi.GetActorEgressPolicyRequest + (*CreateActorEgressPolicyRequest)(nil), // 83: ateapi.CreateActorEgressPolicyRequest + (*UpdateActorEgressPolicyRequest)(nil), // 84: ateapi.UpdateActorEgressPolicyRequest + (*DeleteActorEgressPolicyRequest)(nil), // 85: ateapi.DeleteActorEgressPolicyRequest + (*GetTagRequest)(nil), // 86: ateapi.GetTagRequest + (*MintActorJWTRequest)(nil), // 87: ateapi.MintActorJWTRequest + (*MintActorJWTResponse)(nil), // 88: ateapi.MintActorJWTResponse + (*MintActorCertificateRequest)(nil), // 89: ateapi.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 90: ateapi.MintActorCertificateResponse + (*ListTagsRequest)(nil), // 91: ateapi.ListTagsRequest + (*ListTagsResponse)(nil), // 92: ateapi.ListTagsResponse + (*CreateTagRequest)(nil), // 93: ateapi.CreateTagRequest + (*UpdateTagRequest)(nil), // 94: ateapi.UpdateTagRequest + (*DeleteTagRequest)(nil), // 95: ateapi.DeleteTagRequest + (*DeleteOptions)(nil), // 96: ateapi.DeleteOptions + (*ListWorkerActorAssignmentsRequest)(nil), // 97: ateapi.ListWorkerActorAssignmentsRequest + (*ListWorkerActorAssignmentsResponse)(nil), // 98: ateapi.ListWorkerActorAssignmentsResponse + (*ListWorkersRequest)(nil), // 99: ateapi.ListWorkersRequest + (*ListWorkersResponse)(nil), // 100: ateapi.ListWorkersResponse + (*GetWorkerRequest)(nil), // 101: ateapi.GetWorkerRequest + (*CreateWorkerRequest)(nil), // 102: ateapi.CreateWorkerRequest + (*UpdateWorkerRequest)(nil), // 103: ateapi.UpdateWorkerRequest + (*DeleteWorkerRequest)(nil), // 104: ateapi.DeleteWorkerRequest + (*DrainWorkerRequest)(nil), // 105: ateapi.DrainWorkerRequest + (*ListActorsRequest)(nil), // 106: ateapi.ListActorsRequest + (*ListActorsResponse)(nil), // 107: ateapi.ListActorsResponse + (*Worker)(nil), // 108: ateapi.Worker + (*WorkerStatus)(nil), // 109: ateapi.WorkerStatus + (*SandboxRuntime)(nil), // 110: ateapi.SandboxRuntime + (*VersionedSandboxCompat)(nil), // 111: ateapi.VersionedSandboxCompat + (*AttributeEntry)(nil), // 112: ateapi.AttributeEntry + (*WorkerResources)(nil), // 113: ateapi.WorkerResources + (*ActorAssignment)(nil), // 114: ateapi.ActorAssignment + (*RegisterWorkerRequest)(nil), // 115: ateapi.RegisterWorkerRequest + (*RegisterWorkerResponse)(nil), // 116: ateapi.RegisterWorkerResponse + (*MintAteomActorCertificateRequest)(nil), // 117: ateapi.MintAteomActorCertificateRequest + (*MintAteomActorCertificateResponse)(nil), // 118: ateapi.MintAteomActorCertificateResponse + (*RequestActorSuspendRequest)(nil), // 119: ateapi.RequestActorSuspendRequest + (*RequestActorSuspendResponse)(nil), // 120: ateapi.RequestActorSuspendResponse + (*AccessPolicy)(nil), // 121: ateapi.AccessPolicy + (*Binding)(nil), // 122: ateapi.Binding + (*GetGlobalAccessPolicyRequest)(nil), // 123: ateapi.GetGlobalAccessPolicyRequest + (*CreateGlobalAccessPolicyRequest)(nil), // 124: ateapi.CreateGlobalAccessPolicyRequest + (*UpdateGlobalAccessPolicyRequest)(nil), // 125: ateapi.UpdateGlobalAccessPolicyRequest + (*GetAtespaceAccessPolicyRequest)(nil), // 126: ateapi.GetAtespaceAccessPolicyRequest + (*CreateAtespaceAccessPolicyRequest)(nil), // 127: ateapi.CreateAtespaceAccessPolicyRequest + (*UpdateAtespaceAccessPolicyRequest)(nil), // 128: ateapi.UpdateAtespaceAccessPolicyRequest + (*DeleteAtespaceAccessPolicyRequest)(nil), // 129: ateapi.DeleteAtespaceAccessPolicyRequest + nil, // 130: ateapi.Selector.MatchLabelsEntry + nil, // 131: ateapi.ExternalVolume.VolumeContextEntry + nil, // 132: ateapi.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 133: google.protobuf.Timestamp } var file_ateapi_proto_depIdxs = []int32{ 1, // 0: ateapi.SnapshotStorage.durability:type_name -> ateapi.SnapshotDurability 2, // 1: ateapi.SnapshotStorage.status:type_name -> ateapi.SnapshotStorageStatus 3, // 2: ateapi.SnapshotStorage.fidelity:type_name -> ateapi.SnapshotFidelity - 11, // 3: ateapi.SnapshotStorage.local:type_name -> ateapi.LocalSnapshot - 10, // 4: ateapi.SnapshotStorage.object:type_name -> ateapi.ObjectSnapshot + 12, // 3: ateapi.SnapshotStorage.local:type_name -> ateapi.LocalSnapshot + 11, // 4: ateapi.SnapshotStorage.object:type_name -> ateapi.ObjectSnapshot 0, // 5: ateapi.Snapshot.owner:type_name -> ateapi.SnapshotOwner - 12, // 6: ateapi.Snapshot.storage:type_name -> ateapi.SnapshotStorage - 128, // 7: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry - 131, // 8: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 131, // 9: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp - 9, // 10: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status - 129, // 11: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry - 15, // 12: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata - 34, // 13: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef - 14, // 14: ateapi.Actor.worker_selector:type_name -> ateapi.Selector - 34, // 15: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef - 28, // 16: ateapi.Actor.status:type_name -> ateapi.ActorStatus - 15, // 17: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata - 19, // 18: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule - 20, // 19: ateapi.EgressRule.http:type_name -> ateapi.HTTPRule - 21, // 20: ateapi.EgressRule.https:type_name -> ateapi.HTTPSRule - 22, // 21: ateapi.EgressRule.tls_passthrough:type_name -> ateapi.TLSPassthroughRule - 23, // 22: ateapi.HTTPRule.ports:type_name -> ateapi.Ports - 25, // 23: ateapi.HTTPRule.effects:type_name -> ateapi.HttpRuleEffects - 23, // 24: ateapi.HTTPSRule.ports:type_name -> ateapi.Ports - 25, // 25: ateapi.HTTPSRule.effects:type_name -> ateapi.HttpRuleEffects - 23, // 26: ateapi.TLSPassthroughRule.ports:type_name -> ateapi.Ports - 24, // 27: ateapi.Ports.all:type_name -> ateapi.AllPorts - 26, // 28: ateapi.HttpRuleEffects.replace_headers:type_name -> ateapi.CredentialHeader - 27, // 29: ateapi.CredentialHeader.actor_jwt:type_name -> ateapi.ActorJWTSource + 13, // 6: ateapi.Snapshot.storage:type_name -> ateapi.SnapshotStorage + 130, // 7: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry + 133, // 8: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 133, // 9: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 10, // 10: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status + 131, // 11: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry + 16, // 12: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata + 35, // 13: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef + 15, // 14: ateapi.Actor.worker_selector:type_name -> ateapi.Selector + 35, // 15: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef + 29, // 16: ateapi.Actor.status:type_name -> ateapi.ActorStatus + 16, // 17: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata + 20, // 18: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule + 21, // 19: ateapi.EgressRule.http:type_name -> ateapi.HTTPRule + 22, // 20: ateapi.EgressRule.https:type_name -> ateapi.HTTPSRule + 23, // 21: ateapi.EgressRule.tls_passthrough:type_name -> ateapi.TLSPassthroughRule + 24, // 22: ateapi.HTTPRule.ports:type_name -> ateapi.Ports + 26, // 23: ateapi.HTTPRule.effects:type_name -> ateapi.HttpRuleEffects + 24, // 24: ateapi.HTTPSRule.ports:type_name -> ateapi.Ports + 26, // 25: ateapi.HTTPSRule.effects:type_name -> ateapi.HttpRuleEffects + 24, // 26: ateapi.TLSPassthroughRule.ports:type_name -> ateapi.Ports + 25, // 27: ateapi.Ports.all:type_name -> ateapi.AllPorts + 27, // 28: ateapi.HttpRuleEffects.replace_headers:type_name -> ateapi.CredentialHeader + 28, // 29: ateapi.CredentialHeader.actor_jwt:type_name -> ateapi.ActorJWTSource 5, // 30: ateapi.ActorStatus.state:type_name -> ateapi.ActorState - 30, // 31: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment - 16, // 32: ateapi.ActorStatus.external_volumes:type_name -> ateapi.ExternalVolume - 29, // 33: ateapi.ActorStatus.crash:type_name -> ateapi.ActorCrash - 13, // 34: ateapi.ActorStatus.snapshots:type_name -> ateapi.Snapshot - 131, // 35: ateapi.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp - 34, // 36: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef - 13, // 37: ateapi.TagStatus.snapshot:type_name -> ateapi.Snapshot - 15, // 38: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata - 31, // 39: ateapi.Tag.status:type_name -> ateapi.TagStatus + 31, // 31: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment + 17, // 32: ateapi.ActorStatus.external_volumes:type_name -> ateapi.ExternalVolume + 30, // 33: ateapi.ActorStatus.crash:type_name -> ateapi.ActorCrash + 14, // 34: ateapi.ActorStatus.snapshots:type_name -> ateapi.Snapshot + 133, // 35: ateapi.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp + 35, // 36: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef + 14, // 37: ateapi.TagStatus.snapshot:type_name -> ateapi.Snapshot + 16, // 38: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata + 32, // 39: ateapi.Tag.status:type_name -> ateapi.TagStatus 4, // 40: ateapi.Tag.scope:type_name -> ateapi.TagScope - 34, // 41: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef - 15, // 42: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata - 15, // 43: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata - 14, // 44: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector - 42, // 45: ateapi.ActorTemplate.containers:type_name -> ateapi.Container - 48, // 46: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume - 41, // 47: ateapi.ActorTemplate.snapshot_config:type_name -> ateapi.SnapshotConfig - 40, // 48: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig - 36, // 49: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources - 39, // 50: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus - 37, // 51: ateapi.Resources.limits:type_name -> ateapi.Limits - 34, // 52: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef - 131, // 53: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp - 38, // 54: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus + 35, // 41: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef + 16, // 42: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata + 16, // 43: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata + 15, // 44: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector + 44, // 45: ateapi.ActorTemplate.containers:type_name -> ateapi.Container + 50, // 46: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume + 42, // 47: ateapi.ActorTemplate.snapshot_config:type_name -> ateapi.SnapshotConfig + 41, // 48: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig + 37, // 49: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources + 40, // 50: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus + 38, // 51: ateapi.Resources.limits:type_name -> ateapi.Limits + 35, // 52: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef + 133, // 53: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp + 39, // 54: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus 6, // 55: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass 3, // 56: ateapi.SnapshotConfig.preferred_fidelity:type_name -> ateapi.SnapshotFidelity - 45, // 57: ateapi.Container.env:type_name -> ateapi.EnvVar - 46, // 58: ateapi.Container.wakeup_probe:type_name -> ateapi.ContainerWakeupProbe - 57, // 59: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount - 43, // 60: ateapi.Container.security_context:type_name -> ateapi.SecurityContext - 36, // 61: ateapi.Container.resources:type_name -> ateapi.Resources - 44, // 62: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities - 47, // 63: ateapi.ContainerWakeupProbe.http_get:type_name -> ateapi.HTTPGetAction - 50, // 64: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource - 51, // 65: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate - 52, // 66: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource - 49, // 67: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource - 53, // 68: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource - 54, // 69: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource - 56, // 70: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource - 55, // 71: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem - 7, // 72: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField - 33, // 73: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace - 34, // 74: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 33, // 75: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace - 34, // 76: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 94, // 77: ateapi.DeleteAtespaceRequest.options:type_name -> ateapi.DeleteOptions - 35, // 78: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate - 34, // 79: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 35, // 80: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate - 34, // 81: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 94, // 82: ateapi.DeleteActorTemplateRequest.options:type_name -> ateapi.DeleteOptions - 34, // 83: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef - 17, // 84: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor - 17, // 85: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor - 34, // 86: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef - 17, // 87: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor - 34, // 88: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef - 17, // 89: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor - 34, // 90: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef - 17, // 91: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor - 34, // 92: ateapi.RevertActorRequest.actor:type_name -> ateapi.ObjectRef - 17, // 93: ateapi.RevertActorResponse.actor:type_name -> ateapi.Actor - 34, // 94: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef - 94, // 95: ateapi.DeleteActorRequest.options:type_name -> ateapi.DeleteOptions - 34, // 96: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 34, // 97: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 18, // 98: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 34, // 99: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 18, // 100: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 34, // 101: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 94, // 102: ateapi.DeleteActorEgressPolicyRequest.options:type_name -> ateapi.DeleteOptions - 34, // 103: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef - 34, // 104: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef - 131, // 105: ateapi.MintActorJWTResponse.expires_at:type_name -> google.protobuf.Timestamp - 34, // 106: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 32, // 107: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag - 32, // 108: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag - 32, // 109: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag - 34, // 110: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef - 94, // 111: ateapi.DeleteTagRequest.options:type_name -> ateapi.DeleteOptions - 34, // 112: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef - 112, // 113: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment - 106, // 114: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 34, // 115: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef - 106, // 116: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker - 106, // 117: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker - 34, // 118: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef - 94, // 119: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions - 34, // 120: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef - 17, // 121: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 15, // 122: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata - 130, // 123: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 107, // 124: ateapi.Worker.status:type_name -> ateapi.WorkerStatus - 8, // 125: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState - 111, // 126: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources - 111, // 127: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources - 108, // 128: ateapi.WorkerStatus.default_runtime:type_name -> ateapi.SandboxRuntime - 108, // 129: ateapi.WorkerStatus.restorable_runtimes:type_name -> ateapi.SandboxRuntime - 109, // 130: ateapi.SandboxRuntime.version:type_name -> ateapi.VersionedSandboxCompat - 110, // 131: ateapi.VersionedSandboxCompat.attributes:type_name -> ateapi.AttributeEntry - 36, // 132: ateapi.WorkerResources.resources:type_name -> ateapi.Resources - 15, // 133: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata - 34, // 134: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef - 36, // 135: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources - 34, // 136: ateapi.RegisterWorkerRequest.worker:type_name -> ateapi.ObjectRef - 111, // 137: ateapi.RegisterWorkerRequest.capacity:type_name -> ateapi.WorkerResources - 108, // 138: ateapi.RegisterWorkerRequest.default_runtime:type_name -> ateapi.SandboxRuntime - 108, // 139: ateapi.RegisterWorkerRequest.restorable_runtimes:type_name -> ateapi.SandboxRuntime - 106, // 140: ateapi.RegisterWorkerResponse.worker:type_name -> ateapi.Worker - 34, // 141: ateapi.MintAteomActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 34, // 142: ateapi.RequestActorSuspendRequest.worker:type_name -> ateapi.ObjectRef - 34, // 143: ateapi.RequestActorSuspendRequest.actor:type_name -> ateapi.ObjectRef - 17, // 144: ateapi.RequestActorSuspendResponse.actor:type_name -> ateapi.Actor - 15, // 145: ateapi.AccessPolicy.metadata:type_name -> ateapi.ResourceMetadata - 120, // 146: ateapi.AccessPolicy.bindings:type_name -> ateapi.Binding - 119, // 147: ateapi.CreateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 119, // 148: ateapi.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 34, // 149: ateapi.GetAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 34, // 150: ateapi.CreateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 119, // 151: ateapi.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 34, // 152: ateapi.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 119, // 153: ateapi.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 34, // 154: ateapi.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 94, // 155: ateapi.DeleteAtespaceAccessPolicyRequest.options:type_name -> ateapi.DeleteOptions - 68, // 156: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 69, // 157: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 70, // 158: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 71, // 159: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 73, // 160: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 75, // 161: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 77, // 162: ateapi.Control.RevertActor:input_type -> ateapi.RevertActorRequest - 79, // 163: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 80, // 164: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest - 81, // 165: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest - 82, // 166: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest - 83, // 167: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest - 85, // 168: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest - 87, // 169: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest - 91, // 170: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest - 84, // 171: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest - 89, // 172: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest - 92, // 173: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest - 93, // 174: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest - 97, // 175: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 99, // 176: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest - 100, // 177: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest - 101, // 178: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest - 102, // 179: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest - 103, // 180: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest - 95, // 181: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest - 104, // 182: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 58, // 183: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 59, // 184: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 60, // 185: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 62, // 186: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 63, // 187: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest - 64, // 188: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest - 65, // 189: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest - 67, // 190: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest - 121, // 191: ateapi.Control.GetGlobalAccessPolicy:input_type -> ateapi.GetGlobalAccessPolicyRequest - 122, // 192: ateapi.Control.CreateGlobalAccessPolicy:input_type -> ateapi.CreateGlobalAccessPolicyRequest - 123, // 193: ateapi.Control.UpdateGlobalAccessPolicy:input_type -> ateapi.UpdateGlobalAccessPolicyRequest - 124, // 194: ateapi.Control.GetAtespaceAccessPolicy:input_type -> ateapi.GetAtespaceAccessPolicyRequest - 125, // 195: ateapi.Control.CreateAtespaceAccessPolicy:input_type -> ateapi.CreateAtespaceAccessPolicyRequest - 126, // 196: ateapi.Control.UpdateAtespaceAccessPolicy:input_type -> ateapi.UpdateAtespaceAccessPolicyRequest - 127, // 197: ateapi.Control.DeleteAtespaceAccessPolicy:input_type -> ateapi.DeleteAtespaceAccessPolicyRequest - 113, // 198: ateapi.WorkerService.RegisterWorker:input_type -> ateapi.RegisterWorkerRequest - 115, // 199: ateapi.WorkerService.MintAteomActorCertificate:input_type -> ateapi.MintAteomActorCertificateRequest - 117, // 200: ateapi.WorkerService.RequestActorSuspend:input_type -> ateapi.RequestActorSuspendRequest - 17, // 201: ateapi.Control.GetActor:output_type -> ateapi.Actor - 17, // 202: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 17, // 203: ateapi.Control.UpdateActor:output_type -> ateapi.Actor - 72, // 204: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 74, // 205: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 76, // 206: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 78, // 207: ateapi.Control.RevertActor:output_type -> ateapi.RevertActorResponse - 17, // 208: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 18, // 209: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy - 18, // 210: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 18, // 211: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 18, // 212: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy - 86, // 213: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse - 88, // 214: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse - 32, // 215: ateapi.Control.CreateTag:output_type -> ateapi.Tag - 32, // 216: ateapi.Control.GetTag:output_type -> ateapi.Tag - 90, // 217: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse - 32, // 218: ateapi.Control.UpdateTag:output_type -> ateapi.Tag - 32, // 219: ateapi.Control.DeleteTag:output_type -> ateapi.Tag - 98, // 220: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 106, // 221: ateapi.Control.GetWorker:output_type -> ateapi.Worker - 106, // 222: ateapi.Control.CreateWorker:output_type -> ateapi.Worker - 106, // 223: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker - 106, // 224: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker - 106, // 225: ateapi.Control.DrainWorker:output_type -> ateapi.Worker - 96, // 226: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse - 105, // 227: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 33, // 228: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 33, // 229: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 61, // 230: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 33, // 231: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 35, // 232: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate - 35, // 233: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate - 66, // 234: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse - 35, // 235: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate - 119, // 236: ateapi.Control.GetGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 119, // 237: ateapi.Control.CreateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 119, // 238: ateapi.Control.UpdateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 119, // 239: ateapi.Control.GetAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 119, // 240: ateapi.Control.CreateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 119, // 241: ateapi.Control.UpdateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 119, // 242: ateapi.Control.DeleteAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 114, // 243: ateapi.WorkerService.RegisterWorker:output_type -> ateapi.RegisterWorkerResponse - 116, // 244: ateapi.WorkerService.MintAteomActorCertificate:output_type -> ateapi.MintAteomActorCertificateResponse - 118, // 245: ateapi.WorkerService.RequestActorSuspend:output_type -> ateapi.RequestActorSuspendResponse - 201, // [201:246] is the sub-list for method output_type - 156, // [156:201] is the sub-list for method input_type - 156, // [156:156] is the sub-list for extension type_name - 156, // [156:156] is the sub-list for extension extendee - 0, // [0:156] is the sub-list for field type_name + 43, // 57: ateapi.SnapshotConfig.golden_snapshot_config:type_name -> ateapi.GoldenSnapshotConfig + 7, // 58: ateapi.GoldenSnapshotConfig.mode:type_name -> ateapi.GoldenSnapshotMode + 47, // 59: ateapi.Container.env:type_name -> ateapi.EnvVar + 48, // 60: ateapi.Container.wakeup_probe:type_name -> ateapi.ContainerWakeupProbe + 59, // 61: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount + 45, // 62: ateapi.Container.security_context:type_name -> ateapi.SecurityContext + 37, // 63: ateapi.Container.resources:type_name -> ateapi.Resources + 46, // 64: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities + 49, // 65: ateapi.ContainerWakeupProbe.http_get:type_name -> ateapi.HTTPGetAction + 52, // 66: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource + 53, // 67: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate + 54, // 68: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource + 51, // 69: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource + 55, // 70: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource + 56, // 71: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource + 58, // 72: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource + 57, // 73: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem + 8, // 74: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField + 34, // 75: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace + 35, // 76: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 34, // 77: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace + 35, // 78: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 96, // 79: ateapi.DeleteAtespaceRequest.options:type_name -> ateapi.DeleteOptions + 36, // 80: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate + 35, // 81: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 36, // 82: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate + 35, // 83: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 96, // 84: ateapi.DeleteActorTemplateRequest.options:type_name -> ateapi.DeleteOptions + 35, // 85: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef + 18, // 86: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor + 18, // 87: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor + 35, // 88: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef + 18, // 89: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor + 35, // 90: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef + 18, // 91: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor + 35, // 92: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef + 18, // 93: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor + 35, // 94: ateapi.RevertActorRequest.actor:type_name -> ateapi.ObjectRef + 18, // 95: ateapi.RevertActorResponse.actor:type_name -> ateapi.Actor + 35, // 96: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef + 96, // 97: ateapi.DeleteActorRequest.options:type_name -> ateapi.DeleteOptions + 35, // 98: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 35, // 99: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 19, // 100: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 35, // 101: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 19, // 102: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 35, // 103: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 96, // 104: ateapi.DeleteActorEgressPolicyRequest.options:type_name -> ateapi.DeleteOptions + 35, // 105: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef + 35, // 106: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef + 133, // 107: ateapi.MintActorJWTResponse.expires_at:type_name -> google.protobuf.Timestamp + 35, // 108: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 33, // 109: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag + 33, // 110: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag + 33, // 111: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag + 35, // 112: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef + 96, // 113: ateapi.DeleteTagRequest.options:type_name -> ateapi.DeleteOptions + 35, // 114: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef + 114, // 115: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment + 108, // 116: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 35, // 117: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef + 108, // 118: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker + 108, // 119: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker + 35, // 120: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef + 96, // 121: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions + 35, // 122: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef + 18, // 123: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 16, // 124: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata + 132, // 125: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 109, // 126: ateapi.Worker.status:type_name -> ateapi.WorkerStatus + 9, // 127: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState + 113, // 128: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources + 113, // 129: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources + 110, // 130: ateapi.WorkerStatus.default_runtime:type_name -> ateapi.SandboxRuntime + 110, // 131: ateapi.WorkerStatus.restorable_runtimes:type_name -> ateapi.SandboxRuntime + 111, // 132: ateapi.SandboxRuntime.version:type_name -> ateapi.VersionedSandboxCompat + 112, // 133: ateapi.VersionedSandboxCompat.attributes:type_name -> ateapi.AttributeEntry + 37, // 134: ateapi.WorkerResources.resources:type_name -> ateapi.Resources + 16, // 135: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata + 35, // 136: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef + 37, // 137: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources + 35, // 138: ateapi.RegisterWorkerRequest.worker:type_name -> ateapi.ObjectRef + 113, // 139: ateapi.RegisterWorkerRequest.capacity:type_name -> ateapi.WorkerResources + 110, // 140: ateapi.RegisterWorkerRequest.default_runtime:type_name -> ateapi.SandboxRuntime + 110, // 141: ateapi.RegisterWorkerRequest.restorable_runtimes:type_name -> ateapi.SandboxRuntime + 108, // 142: ateapi.RegisterWorkerResponse.worker:type_name -> ateapi.Worker + 35, // 143: ateapi.MintAteomActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 35, // 144: ateapi.RequestActorSuspendRequest.worker:type_name -> ateapi.ObjectRef + 35, // 145: ateapi.RequestActorSuspendRequest.actor:type_name -> ateapi.ObjectRef + 18, // 146: ateapi.RequestActorSuspendResponse.actor:type_name -> ateapi.Actor + 16, // 147: ateapi.AccessPolicy.metadata:type_name -> ateapi.ResourceMetadata + 122, // 148: ateapi.AccessPolicy.bindings:type_name -> ateapi.Binding + 121, // 149: ateapi.CreateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 121, // 150: ateapi.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 35, // 151: ateapi.GetAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 35, // 152: ateapi.CreateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 121, // 153: ateapi.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 35, // 154: ateapi.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 121, // 155: ateapi.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 35, // 156: ateapi.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 96, // 157: ateapi.DeleteAtespaceAccessPolicyRequest.options:type_name -> ateapi.DeleteOptions + 70, // 158: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 71, // 159: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 72, // 160: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 73, // 161: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 75, // 162: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 77, // 163: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 79, // 164: ateapi.Control.RevertActor:input_type -> ateapi.RevertActorRequest + 81, // 165: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 82, // 166: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest + 83, // 167: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest + 84, // 168: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest + 85, // 169: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest + 87, // 170: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest + 89, // 171: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest + 93, // 172: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest + 86, // 173: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest + 91, // 174: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest + 94, // 175: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest + 95, // 176: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest + 99, // 177: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 101, // 178: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest + 102, // 179: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest + 103, // 180: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest + 104, // 181: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest + 105, // 182: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest + 97, // 183: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest + 106, // 184: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 60, // 185: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 61, // 186: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 62, // 187: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 64, // 188: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 65, // 189: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest + 66, // 190: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest + 67, // 191: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest + 69, // 192: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest + 123, // 193: ateapi.Control.GetGlobalAccessPolicy:input_type -> ateapi.GetGlobalAccessPolicyRequest + 124, // 194: ateapi.Control.CreateGlobalAccessPolicy:input_type -> ateapi.CreateGlobalAccessPolicyRequest + 125, // 195: ateapi.Control.UpdateGlobalAccessPolicy:input_type -> ateapi.UpdateGlobalAccessPolicyRequest + 126, // 196: ateapi.Control.GetAtespaceAccessPolicy:input_type -> ateapi.GetAtespaceAccessPolicyRequest + 127, // 197: ateapi.Control.CreateAtespaceAccessPolicy:input_type -> ateapi.CreateAtespaceAccessPolicyRequest + 128, // 198: ateapi.Control.UpdateAtespaceAccessPolicy:input_type -> ateapi.UpdateAtespaceAccessPolicyRequest + 129, // 199: ateapi.Control.DeleteAtespaceAccessPolicy:input_type -> ateapi.DeleteAtespaceAccessPolicyRequest + 115, // 200: ateapi.WorkerService.RegisterWorker:input_type -> ateapi.RegisterWorkerRequest + 117, // 201: ateapi.WorkerService.MintAteomActorCertificate:input_type -> ateapi.MintAteomActorCertificateRequest + 119, // 202: ateapi.WorkerService.RequestActorSuspend:input_type -> ateapi.RequestActorSuspendRequest + 18, // 203: ateapi.Control.GetActor:output_type -> ateapi.Actor + 18, // 204: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 18, // 205: ateapi.Control.UpdateActor:output_type -> ateapi.Actor + 74, // 206: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 76, // 207: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 78, // 208: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 80, // 209: ateapi.Control.RevertActor:output_type -> ateapi.RevertActorResponse + 18, // 210: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 19, // 211: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy + 19, // 212: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 19, // 213: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 19, // 214: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy + 88, // 215: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse + 90, // 216: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse + 33, // 217: ateapi.Control.CreateTag:output_type -> ateapi.Tag + 33, // 218: ateapi.Control.GetTag:output_type -> ateapi.Tag + 92, // 219: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse + 33, // 220: ateapi.Control.UpdateTag:output_type -> ateapi.Tag + 33, // 221: ateapi.Control.DeleteTag:output_type -> ateapi.Tag + 100, // 222: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 108, // 223: ateapi.Control.GetWorker:output_type -> ateapi.Worker + 108, // 224: ateapi.Control.CreateWorker:output_type -> ateapi.Worker + 108, // 225: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker + 108, // 226: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker + 108, // 227: ateapi.Control.DrainWorker:output_type -> ateapi.Worker + 98, // 228: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse + 107, // 229: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 34, // 230: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 34, // 231: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 63, // 232: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 34, // 233: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 36, // 234: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate + 36, // 235: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate + 68, // 236: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse + 36, // 237: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate + 121, // 238: ateapi.Control.GetGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 121, // 239: ateapi.Control.CreateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 121, // 240: ateapi.Control.UpdateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 121, // 241: ateapi.Control.GetAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 121, // 242: ateapi.Control.CreateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 121, // 243: ateapi.Control.UpdateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 121, // 244: ateapi.Control.DeleteAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 116, // 245: ateapi.WorkerService.RegisterWorker:output_type -> ateapi.RegisterWorkerResponse + 118, // 246: ateapi.WorkerService.MintAteomActorCertificate:output_type -> ateapi.MintAteomActorCertificateResponse + 120, // 247: ateapi.WorkerService.RequestActorSuspend:output_type -> ateapi.RequestActorSuspendResponse + 203, // [203:248] is the sub-list for method output_type + 158, // [158:203] is the sub-list for method input_type + 158, // [158:158] is the sub-list for extension type_name + 158, // [158:158] is the sub-list for extension extendee + 0, // [0:158] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } @@ -9436,8 +9560,8 @@ func file_ateapi_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ateapi_proto_rawDesc), len(file_ateapi_proto_rawDesc)), - NumEnums: 10, - NumMessages: 121, + NumEnums: 11, + NumMessages: 122, NumExtensions: 0, NumServices: 2, }, diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index 3d0cb6b7b9..3bba6da970 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -1154,6 +1154,30 @@ message SnapshotConfig { // +k8s:minimum=1 // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum SnapshotFidelity preferred_fidelity = 2; + + // golden_snapshot_config configures automated golden snapshot creation. + // Golden snapshots are enabled when this config is omitted. + // + // +k8s:optional + GoldenSnapshotConfig golden_snapshot_config = 5; +} + +// GoldenSnapshotMode selects whether a template builds a golden snapshot. +enum GoldenSnapshotMode { + GOLDEN_SNAPSHOT_MODE_UNSPECIFIED = 0; + GOLDEN_SNAPSHOT_MODE_ENABLED = 1; + GOLDEN_SNAPSHOT_MODE_DISABLED = 2; +} + +// GoldenSnapshotConfig controls automated golden snapshot creation for a template. +message GoldenSnapshotConfig { + // mode defaults to ENABLED when unspecified. DISABLED skips creating the + // template's golden actor and tag. + // + // +k8s:required + // +k8s:minimum=1 + // +k8s:maximum=2 # keep this in sync with the GoldenSnapshotMode enum + GoldenSnapshotMode mode = 1; } // Container is a single application container of an ActorTemplate. From 656dfdc009a2431370a53ef2684d08e2345ab974 Mon Sep 17 00:00:00 2001 From: Pancho Trujillo Date: Mon, 12 Oct 2026 03:25:45 +0000 Subject: [PATCH 7/9] atunnel: strip X-Ate-Assignment-Stale from upstream actor responses (#2410) ### Motivation `atunnel` defines `StaleAssignmentHeader = "X-Ate-Assignment-Stale"` to distinguish an `atunnel` infrastructure-level routing rejection (HTTP 421 `Misdirected Request`, returned when an actor is no longer active on that worker) from an HTTP 421 returned by the guest actor application itself (`internal/atunnel/ingress.go#L45-L47`). While `newActorProxy` already strips internal Substrate headers (`ate-target-port`, `ate-target-actor`) on the request leg into the sandbox, it lacked response sanitization on the return leg. As a result, an untrusted actor workload running inside the container could return `X-Ate-Assignment-Stale: true` and spoof an infrastructure routing rejection to downstream routers or clients. ### Changes 1. **`internal/atunnel/ingress.go`**: Added a `ModifyResponse` hook to `newActorProxy()` that removes `X-Ate-Assignment-Stale` from any response received from the actor upstream. 2. **`internal/atunnel/ingress_test.go`**: Added unit test `TestServeHTTPStripsStaleAssignmentHeaderFromActor` asserting that even if the actor application returns `X-Ate-Assignment-Stale: true`, the header is stripped before the response reaches the client. ### Testing - `go test -race -count=1 ./internal/atunnel/...` - `./hack/verify/boilerplate.sh` - `./hack/verify/gofmt.sh` - `./hack/verify/golangci-lint.sh` > The PR title becomes the release note. Write it for users: what changed for them, not how the code changed. AI was used to assist in the development of these changes. - [x] Tests pass - [x] Appropriate changes to documentation are included in the PR --- internal/atunnel/ingress.go | 6 ++++++ internal/atunnel/ingress_test.go | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/internal/atunnel/ingress.go b/internal/atunnel/ingress.go index 2a135a8025..6bcbe20d51 100644 --- a/internal/atunnel/ingress.go +++ b/internal/atunnel/ingress.go @@ -188,6 +188,12 @@ func newActorProxy(upstream *url.URL, dial DialFunc) *httputil.ReverseProxy { } pr.SetXForwarded() }, + // Prevent untrusted actor responses from spoofing routing rejections + // to trigger cache evictions and stream restarts in the ingress router. + ModifyResponse: func(resp *http.Response) error { + resp.Header.Del(StaleAssignmentHeader) + return nil + }, Transport: newProtocolMirrorTransport(dial), ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { slog.WarnContext(r.Context(), "atunnel upstream request failed", slog.Any("err", err)) diff --git a/internal/atunnel/ingress_test.go b/internal/atunnel/ingress_test.go index ffeeb3889e..72664cce87 100644 --- a/internal/atunnel/ingress_test.go +++ b/internal/atunnel/ingress_test.go @@ -357,6 +357,40 @@ func TestServerRejectsDuplicateOrCommaTargetActorHeaders(t *testing.T) { } } +func TestServeHTTPStripsStaleAssignmentHeaderFromActor(t *testing.T) { + upstreamURL, err := url.Parse("http://actor.internal:80") + if err != nil { + t.Fatal(err) + } + + s := newTestServer(t, upstreamURL) + actorTransport := roundTripFunc(func(r *http.Request) (*http.Response, error) { + h := make(http.Header) + h.Set(StaleAssignmentHeader, "true") + return &http.Response{ + StatusCode: http.StatusOK, + Header: h, + Body: http.NoBody, + }, nil + }) + if err := s.Activate("team-a", "actor-1", "uid-actor-1", testDial); err != nil { + t.Fatal(err) + } + setActorTransport(t, s, "team-a", "actor-1", actorTransport) + + req := httptest.NewRequest(http.MethodGet, "https://worker/hello", nil) + req.Header.Set(atenet.TargetActorHeader, "team-a/actor-1") + rec := httptest.NewRecorder() + s.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) + } + if got := rec.Header().Get(StaleAssignmentHeader); got != "" { + t.Errorf("expected %s to be stripped from actor response, got %q", StaleAssignmentHeader, got) + } +} + func TestServeHTTPHonorsTargetPortHeader(t *testing.T) { upstreamURL, err := url.Parse("http://actor.internal:80") if err != nil { From 9b3f86116dd04b353c4f82dcb4f01487d6b73643 Mon Sep 17 00:00:00 2001 From: Zoe Zhao <35669350+zoez7@users.noreply.github.com> Date: Mon, 12 Oct 2026 04:41:07 +0000 Subject: [PATCH 8/9] WorkerPools resolve their SandboxConfig and protect it from deletion (#2393) Part of #587. Step 3 of the SandboxConfig management design, after #2332 and #2333. Each WorkerPool now resolves one SandboxConfig per sandbox class, reports it in status, and protects it from being deleted while in use. Workers do not read the resolved config yet; that is the next step. ## How to review The PR is three commits. Each one builds and passes its tests on its own, so they can be reviewed in order. 1. **Resolve each WorkerPool's SandboxConfig into status.** The API change, the resolution rules, and the `Resolved` condition. No events, no finalizer. 2. **Protect SandboxConfigs in use with a finalizer.** The finalizer, the reconciler that releases it, the two-step status write for a switch, and the `ate-setup` and `install-microvm-deps.sh` changes that deal with the finalizer. 3. **Emit events for a WorkerPool's SandboxConfig and add e2e tests.** The Warning and `SandboxConfigSwitched` events, and an e2e suite that walks a pool through every move. ## Resolution Without `configRef`: the pool uses the class default. ```yaml apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: counter-pool namespace: ate-demo generation: 1 spec: replicas: 2 workerImage: ateom-gvisor:v1 sandboxClasses: - name: gvisor # no configRef status: replicas: 2 readyReplicas: 2 selector: ate.dev/worker-pool=counter-pool sandboxClasses: - name: gvisor configRef: name: gvisor-default # newest gvisor config annotated is-class-default conditions: - type: Resolved status: "True" reason: Resolved message: resolved to SandboxConfig "gvisor-default" observedGeneration: 1 ``` With `configRef` pointing at a config that does not exist: the pool keeps the config it resolved last time. ```yaml apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: pinned-pool namespace: ate-demo generation: 2 spec: replicas: 2 workerImage: ateom-gvisor:v1 sandboxClasses: - name: gvisor configRef: name: gvisor-release2 # does not exist status: replicas: 2 readyReplicas: 2 selector: ate.dev/worker-pool=pinned-pool sandboxClasses: - name: gvisor configRef: name: gvisor-release1 # still in use, still protected conditions: - type: Resolved status: "False" reason: SandboxConfigNotFound message: SandboxConfig "gvisor-release2" not found; keeping "gvisor-release1" observedGeneration: 2 ``` - With `configRef`: that config is used. It must exist, belong to the same class, and not be in the process of being deleted. - Without `configRef`: the newest config of the class that is annotated `sandboxconfig.ate.dev/is-class-default: "true"` and is not being deleted is used. - If no config can be resolved, the status `configRef` keeps the config resolved last time, and the `Resolved` condition is `False` with reason `SandboxConfigNotFound`, `SandboxClassMismatch`, `SandboxConfigDeleting`, or `DefaultConfigNotFound`. A Warning event is emitted when the reason changes. - `kubectl get workerpool` gains `SANDBOXCONFIG` and `RESOLVED` columns. - The counter demo no longer pins its pool to `gvisor-default`. It follows the class default. ## Switching configs When a class switches to a different config, the controller writes status twice. First it records the new config in `status.sandboxClasses[].pendingConfigRef`, which protects the new config while the current one stays in `configRef`. Then it reads the new config from the API server. If the config exists, belongs to the class, and is not being deleted, `configRef` moves to it. Otherwise the class keeps the current config and `Resolved` becomes `False`. This prevents a pool from recording a config that was deleted between the cache read and the status write. ## Events Each time a class's `configRef` changes, the controller emits a Normal event with reason `SandboxConfigSwitched`. The event is emitted after the status write that records the change, so each switch shows up once. A reconcile that leaves the config unchanged emits nothing, so the pool's events show the configs it has used, in order. A class that fails to resolve emits a Warning event with the condition reason. To see the history for one pool: ``` kubectl -n ate-demo events --for workerpool/counter-pool ``` Example for the counter pool, which has no `configRef` and so follows the class default. It first resolved to `gvisor-default`. Then a newer config `gvisor-default-v2` annotated `is-class-default: "true"` was created, and the pool switched to it on its own. Then `gvisor-default-v2` lost the annotation, got it back, and was finally deleted. A config that is being deleted is no longer a class default, so the pool moved back to `gvisor-default`, and the finalizer on `gvisor-default-v2` was released once no pool used it. ``` $ kubectl -n ate-demo-counter events --for workerpool/counter LAST SEEN TYPE REASON OBJECT MESSAGE 61s Normal SandboxConfigSwitched WorkerPool/counter sandbox class "gvisor" uses SandboxConfig "gvisor-default" 53s Normal SandboxConfigSwitched WorkerPool/counter sandbox class "gvisor" switched from SandboxConfig "gvisor-default" to "gvisor-default-v2" 46s Normal SandboxConfigSwitched WorkerPool/counter sandbox class "gvisor" switched from SandboxConfig "gvisor-default-v2" to "gvisor-default" 39s Normal SandboxConfigSwitched WorkerPool/counter sandbox class "gvisor" switched from SandboxConfig "gvisor-default" to "gvisor-default-v2" 33s Normal SandboxConfigSwitched WorkerPool/counter sandbox class "gvisor" switched from SandboxConfig "gvisor-default-v2" to "gvisor-default" ``` ## Deletion protection - Before recording a config in status, the WorkerPool controller adds the finalizer `sandboxconfig.ate.dev/workerpool-protection` to it. - A new `SandboxConfigProtectionReconciler` removes the finalizer once no WorkerPool status references the config. It checks the cache first, then confirms with a live list from the API server before removing it. - A SandboxConfig that is being deleted is never resolved for a new WorkerPool and is no longer treated as the class default. WorkerPools that already use it keep using it, and because of the finalizer the config is not actually deleted until every such WorkerPool has moved to another config or has been deleted. This is what the bundled `gvisor-default` looks like once a WorkerPool has resolved it. Nothing else in the config changes. ``` kubectl get sandboxconfig gvisor-default -o yaml ``` ```yaml apiVersion: ate.dev/v1alpha1 kind: SandboxConfig metadata: name: gvisor-default annotations: sandboxconfig.ate.dev/is-class-default: "true" finalizers: - sandboxconfig.ate.dev/workerpool-protection # added by the WorkerPool controller spec: sandboxClass: gvisor defaultVersion: v1 versions: - name: v1 state: Enabled ... ``` After `kubectl delete sandboxconfig gvisor-default` while a WorkerPool still uses it, the delete does not finish. The config gets a `deletionTimestamp` and stays in the cluster with the finalizer until the last WorkerPool moves off it: ```yaml apiVersion: ate.dev/v1alpha1 kind: SandboxConfig metadata: name: gvisor-default annotations: sandboxconfig.ate.dev/is-class-default: "true" deletionTimestamp: "2026-10-11T18:42:07Z" finalizers: - sandboxconfig.ate.dev/workerpool-protection # kept while a WorkerPool status references the config spec: ... ``` To find out which WorkerPools are holding it: ``` kubectl get workerpool -A -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,SANDBOXCONFIG:.status.sandboxClasses[*].configRef.name ``` ## Testing - Unit tests cover resolution, the status write, the finalizer, the two-step switch, and the events, including that a switch is reported once when the first status write is rejected. - The new e2e suite `internal/e2e/suites/sandboxconfig` creates a pool with no `configRef` and no workers and walks it through the sequence in the events example above. Run it with `hack/run-e2e.sh ./internal/e2e/suites/sandboxconfig/...`. ## Breaking changes - A new WorkerPool gets no Deployment until every sandbox class has a resolved config. Before upgrading, make sure each class has a config annotated `is-class-default: "true"`, or set `configRef` on the pool. The bundled `gvisor-default` and the micro-VM config are already annotated. Existing pools keep their Deployment. - `spec.sandboxClasses[].name` is now immutable. To change the class, create a new pool. - Rolling atecontroller back to a version before this PR leaves the finalizer on SandboxConfigs. Remove it with `kubectl patch sandboxconfig --type=merge -p '{"metadata":{"finalizers":null}}'`. - [x] Tests pass - [x] Appropriate changes to documentation are included in the PR --- cmd/ate-setup/internal/steps/benchmarks.go | 6 +- cmd/ate-setup/internal/steps/delete.go | 75 +- cmd/ate-setup/internal/steps/delete_test.go | 272 ++ .../sandboxconfig_protection_controller.go | 163 ++ ...andboxconfig_protection_controller_test.go | 392 +++ .../controllers/workerpool_controller.go | 277 +- .../controllers/workerpool_controller_test.go | 21 +- .../controllers/workerpool_sandboxconfig.go | 273 ++ .../workerpool_sandboxconfig_test.go | 2552 +++++++++++++++++ cmd/atecontroller/main.go | 10 + .../isolate/fake-workersync/controller.go | 3 +- .../fake-workersync/controller_test.go | 3 +- demos/counter/counter.yaml.tmpl | 5 +- docs/api-guide.md | 27 +- hack/install-microvm-deps.sh | 38 +- .../suites/sandboxconfig/classdefault_test.go | 333 +++ .../e2e/suites/sandboxconfig/testmain_test.go | 24 + .../generated/ate.dev_workerpools.yaml | 132 +- manifests/ate-install/generated/role.yaml | 16 + pkg/api/v1alpha1/sandboxconfig_types.go | 22 + .../v1alpha1/sandboxconfig_validation_test.go | 11 +- pkg/api/v1alpha1/workerpool_types.go | 77 +- .../v1alpha1/workerpool_validation_test.go | 76 + pkg/api/v1alpha1/zz_generated.deepcopy.go | 42 +- 24 files changed, 4807 insertions(+), 43 deletions(-) create mode 100644 cmd/ate-setup/internal/steps/delete_test.go create mode 100644 cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller.go create mode 100644 cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller_test.go create mode 100644 cmd/atecontroller/internal/controllers/workerpool_sandboxconfig.go create mode 100644 cmd/atecontroller/internal/controllers/workerpool_sandboxconfig_test.go create mode 100644 internal/e2e/suites/sandboxconfig/classdefault_test.go create mode 100644 internal/e2e/suites/sandboxconfig/testmain_test.go diff --git a/cmd/ate-setup/internal/steps/benchmarks.go b/cmd/ate-setup/internal/steps/benchmarks.go index eb3556e353..6244460b40 100644 --- a/cmd/ate-setup/internal/steps/benchmarks.go +++ b/cmd/ate-setup/internal/steps/benchmarks.go @@ -110,8 +110,10 @@ func (e *Env) DeleteBenchmarks(ctx context.Context, opts BenchmarkOptions) error if err := e.runScript(ctx, deployLocustScript, "--delete"); err != nil { return err } - // Only tear down the microvm SandboxConfig if the caller opted into - // microvm: it is cluster-wide and may be in use by something else. + // The microvm SandboxConfig is cluster-wide. It is only torn down when + // the caller opted into microvm, and only after the benchmark pool is + // gone. The script leaves it in place if another pool still uses it. + // `delete all` tries again once every demo is gone. if opts.SandboxClass == config.SandboxClassMicrovm { return e.runScript(ctx, installMicrovmDepScript, "--delete") } diff --git a/cmd/ate-setup/internal/steps/delete.go b/cmd/ate-setup/internal/steps/delete.go index 88f799ea36..92c59f3c1c 100644 --- a/cmd/ate-setup/internal/steps/delete.go +++ b/cmd/ate-setup/internal/steps/delete.go @@ -17,11 +17,16 @@ package steps import ( "context" "fmt" + "slices" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/util/retry" "github.com/agent-substrate/substrate/cmd/ate-setup/internal/log" + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" ) // DeleteAteSystem removes the control plane. @@ -74,9 +79,51 @@ func (e *Env) DeleteAteSystem(ctx context.Context) error { if err := e.Kube.WaitDeleted(ctx, schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}, "", e.Namespace(), e.Cfg.WaitTimeout(BootstrapTimeout)); err != nil { return err } + // Removing the protection finalizer by hand is safe here. atecontroller, + // which would remove it, went away with the namespace. Deleting the CRDs + // above deleted every WorkerPool, so no pool uses a config anymore. + if err := releaseSandboxConfigs(ctx, e.Kube.Dynamic.Resource(atev1alpha1.GroupVersion.WithResource("sandboxconfigs"))); err != nil { + return err + } return e.UnlabelNodesSubstrateVersion(ctx) } +// releaseSandboxConfigs removes the WorkerPool protection finalizer from +// every SandboxConfig. Only atecontroller adds and removes this finalizer, +// and atecontroller is gone once its namespace is. Without this step no +// SandboxConfig could finish deleting, and deleting the CRD would hang. +func releaseSandboxConfigs(ctx context.Context, res dynamic.ResourceInterface) error { + list, err := res.List(ctx, metav1.ListOptions{}) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return fmt.Errorf("while listing SandboxConfigs: %w", err) + } + for _, item := range list.Items { + name := item.GetName() + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + sc, err := res.Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return err + } + finalizers := slices.DeleteFunc(sc.GetFinalizers(), func(f string) bool { + return f == atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer + }) + if len(finalizers) == len(sc.GetFinalizers()) { + return nil + } + sc.SetFinalizers(finalizers) + _, err = res.Update(ctx, sc, metav1.UpdateOptions{}) + return err + }) + if err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("while releasing SandboxConfig %s: %w", name, err) + } + } + return nil +} + // DeleteAtenet removes the atenet dataplane and the bundled credential // provider, deleting the provider first so that a later failure cannot leave // its ClusterRole and binding behind. The provider's policy ConfigMap stays: @@ -108,14 +155,38 @@ type Deleter interface { Delete(ctx context.Context, e *Env) error } -// DeleteAll removes every registered demo and then the control plane. +// DeleteAll removes every demo, then the microvm SandboxConfig, then the +// control plane. func (e *Env) DeleteAll(ctx context.Context, demos []Deleter) error { log.Step("delete_all") + if err := e.deleteDemos(ctx, demos); err != nil { + return err + } + return e.DeleteAteSystem(ctx) +} + +// deleteDemos removes every demo, then tries to delete the microvm +// SandboxConfig. An earlier `delete benchmarks` or `delete demo` may have +// left the config behind because a pool still used it. After the demos, no +// pool does. +// +// The delete is best effort. The script skips a config that is still in use, +// and a failure is only logged, because DeleteAteSystem deletes the config +// with the CRD anyway. +// +// The gvisor config needs no such step. It is installed and deleted with the +// control plane, and DeleteAteSystem strips its finalizer itself. The microvm +// config is managed by hack/install-microvm-deps.sh, whose `kubectl delete` +// would block while a pool still uses it. +func (e *Env) deleteDemos(ctx context.Context, demos []Deleter) error { for _, demo := range demos { if err := demo.Delete(ctx, e); err != nil { return err } } - return e.DeleteAteSystem(ctx) + if err := e.runScript(ctx, installMicrovmDepScript, "--delete"); err != nil { + log.Warnf("while deleting the microvm SandboxConfig: %v", err) + } + return nil } diff --git a/cmd/ate-setup/internal/steps/delete_test.go b/cmd/ate-setup/internal/steps/delete_test.go new file mode 100644 index 0000000000..f516ed111b --- /dev/null +++ b/cmd/ate-setup/internal/steps/delete_test.go @@ -0,0 +1,272 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package steps + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/util/retry" + + "github.com/agent-substrate/substrate/cmd/ate-setup/internal/config" + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" +) + +// fakeSandboxConfigs is an in-memory dynamic.ResourceInterface implementing +// the calls releaseSandboxConfigs makes. Any other call panics. +type fakeSandboxConfigs struct { + dynamic.ResourceInterface + objs map[string]*unstructured.Unstructured + // listErr, when set, is returned by List instead of objs. + listErr error + // gone are names List returns that Get then reports NotFound, as if the + // SandboxConfig was deleted between the two calls. + gone []string + // conflicts is the number of Update calls to reject with Conflict before + // accepting the write. + conflicts int + updates int +} + +var sandboxConfigsGR = atev1alpha1.GroupVersion.WithResource("sandboxconfigs").GroupResource() + +func (f *fakeSandboxConfigs) List(context.Context, metav1.ListOptions) (*unstructured.UnstructuredList, error) { + if f.listErr != nil { + return nil, f.listErr + } + list := &unstructured.UnstructuredList{} + for _, o := range f.objs { + list.Items = append(list.Items, *o.DeepCopy()) + } + for _, name := range f.gone { + list.Items = append(list.Items, *newSandboxConfig(name, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer)) + } + return list, nil +} + +func (f *fakeSandboxConfigs) Get(_ context.Context, name string, _ metav1.GetOptions, _ ...string) (*unstructured.Unstructured, error) { + o, ok := f.objs[name] + if !ok { + return nil, apierrors.NewNotFound(sandboxConfigsGR, name) + } + return o.DeepCopy(), nil +} + +func (f *fakeSandboxConfigs) Update(_ context.Context, obj *unstructured.Unstructured, _ metav1.UpdateOptions, _ ...string) (*unstructured.Unstructured, error) { + f.updates++ + if f.conflicts > 0 { + f.conflicts-- + return nil, apierrors.NewConflict(sandboxConfigsGR, obj.GetName(), errors.New("stale resourceVersion")) + } + f.objs[obj.GetName()] = obj.DeepCopy() + return obj, nil +} + +func newSandboxConfig(name string, finalizers ...string) *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(atev1alpha1.GroupVersion.WithKind("SandboxConfig")) + u.SetName(name) + u.SetFinalizers(finalizers) + return u +} + +func TestReleaseSandboxConfigs(t *testing.T) { + const inUse = atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer + tests := []struct { + name string + res *fakeSandboxConfigs + // wantFinalizers is the expected finalizers of every object left in + // res.objs after the call. + wantFinalizers map[string][]string + wantUpdates int + wantErr bool + }{ + { + name: "strips only the protection finalizer and skips configs without it", + res: &fakeSandboxConfigs{objs: map[string]*unstructured.Unstructured{ + "in-use": newSandboxConfig("in-use", inUse, "example.com/other"), + "unused": newSandboxConfig("unused", "example.com/other"), + }}, + wantFinalizers: map[string][]string{ + "in-use": {"example.com/other"}, + "unused": {"example.com/other"}, + }, + wantUpdates: 1, + }, + { + name: "List NotFound means the CRD is gone and is not an error", + res: &fakeSandboxConfigs{ + listErr: apierrors.NewNotFound(sandboxConfigsGR, ""), + }, + }, + { + name: "List failure other than NotFound is returned", + res: &fakeSandboxConfigs{ + listErr: apierrors.NewServiceUnavailable("apiserver down"), + }, + wantErr: true, + }, + { + name: "config deleted between List and Get is skipped and the rest are released", + res: &fakeSandboxConfigs{ + objs: map[string]*unstructured.Unstructured{ + "in-use": newSandboxConfig("in-use", inUse), + }, + gone: []string{"vanished"}, + }, + wantFinalizers: map[string][]string{"in-use": {}}, + wantUpdates: 1, + }, + { + name: "Update conflict is retried until the write lands", + res: &fakeSandboxConfigs{ + objs: map[string]*unstructured.Unstructured{ + "in-use": newSandboxConfig("in-use", inUse, "example.com/other"), + }, + conflicts: 2, + }, + wantFinalizers: map[string][]string{"in-use": {"example.com/other"}}, + wantUpdates: 3, + }, + { + name: "Update conflicts beyond the retry budget are returned", + res: &fakeSandboxConfigs{ + objs: map[string]*unstructured.Unstructured{ + "in-use": newSandboxConfig("in-use", inUse), + }, + conflicts: retry.DefaultRetry.Steps, + }, + wantFinalizers: map[string][]string{"in-use": {inUse}}, + wantUpdates: retry.DefaultRetry.Steps, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := releaseSandboxConfigs(t.Context(), tt.res) + if (err != nil) != tt.wantErr { + t.Fatalf("releaseSandboxConfigs() error = %v, wantErr %v", err, tt.wantErr) + } + for name, want := range tt.wantFinalizers { + obj, ok := tt.res.objs[name] + if !ok { + t.Fatalf("SandboxConfig %s missing from fake", name) + } + if got := obj.GetFinalizers(); !slices.Equal(got, want) { + t.Errorf("SandboxConfig %s finalizers = %v, want %v", name, got, want) + } + } + if tt.res.updates != tt.wantUpdates { + t.Errorf("updates = %d, want %d", tt.res.updates, tt.wantUpdates) + } + }) + } +} + +// recordingDeleter is a Deleter that appends its name to a log file shared +// with the fake microvm script, so a test can read back the order of calls. +type recordingDeleter struct { + name string + log string + err error +} + +func (d recordingDeleter) Delete(context.Context, *Env) error { + appendLine(d.log, d.name) + return d.err +} + +func appendLine(path, line string) { + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + panic(err) + } + defer f.Close() + fmt.Fprintln(f, line) +} + +func TestDeleteDemos(t *testing.T) { + demoErr := errors.New("demo failed") + tests := []struct { + name string + // demos are the deleters in order. A nil entry's err is nil. + demos []recordingDeleter + // scriptExit is the exit code of the fake install-microvm-deps.sh. + scriptExit int + wantCalls []string + wantErr error + }{ + { + name: "microvm SandboxConfig is deleted after every demo", + demos: []recordingDeleter{{name: "demo-a"}, {name: "demo-b"}}, + wantCalls: []string{"demo-a", "demo-b", "script --delete"}, + }, + { + name: "script failure is logged, not returned", + demos: []recordingDeleter{{name: "demo-a"}}, + scriptExit: 1, + wantCalls: []string{"demo-a", "script --delete"}, + }, + { + name: "a failing demo stops before the script", + demos: []recordingDeleter{{name: "demo-a", err: demoErr}, {name: "demo-b"}}, + wantCalls: []string{"demo-a"}, + wantErr: demoErr, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + root := t.TempDir() + calls := filepath.Join(root, "calls.log") + script := filepath.Join(root, installMicrovmDepScript) + if err := os.MkdirAll(filepath.Dir(script), 0o755); err != nil { + t.Fatal(err) + } + body := fmt.Sprintf("#!/bin/sh\necho \"script $*\" >> %q\nexit %d\n", calls, tt.scriptExit) + if err := os.WriteFile(script, []byte(body), 0o755); err != nil { + t.Fatal(err) + } + demos := make([]Deleter, 0, len(tt.demos)) + for _, d := range tt.demos { + d.log = calls + demos = append(demos, d) + } + e := &Env{Cfg: &config.Config{Root: root}} + + err := e.deleteDemos(t.Context(), demos) + if !errors.Is(err, tt.wantErr) { + t.Fatalf("deleteDemos() error = %v, want %v", err, tt.wantErr) + } + got, _ := os.ReadFile(calls) + gotCalls := strings.Fields(strings.ReplaceAll(strings.TrimSpace(string(got)), "script --delete", "script_--delete")) + for i := range gotCalls { + gotCalls[i] = strings.ReplaceAll(gotCalls[i], "script_--delete", "script --delete") + } + if !slices.Equal(gotCalls, tt.wantCalls) { + t.Errorf("calls = %q, want %q", gotCalls, tt.wantCalls) + } + }) + } +} diff --git a/cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller.go b/cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller.go new file mode 100644 index 0000000000..ac0f937b39 --- /dev/null +++ b/cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller.go @@ -0,0 +1,163 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "context" + "fmt" + "slices" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" +) + +// workerPoolSandboxConfigIndex names the cache index from a SandboxConfig's +// name to the WorkerPools whose status uses it, in configRef or +// pendingConfigRef. See sandboxConfigsInUse. +const workerPoolSandboxConfigIndex = "status.sandboxClasses.sandboxConfig" + +// SandboxConfigProtectionReconciler removes the protection finalizer from a +// SandboxConfig that is being deleted, once no WorkerPool uses it. +type SandboxConfigProtectionReconciler struct { + client.Client + // APIReader lists WorkerPools straight from the API server. The cache + // can lag behind a pool that just started using a config, so a finalizer + // is only removed after a live list finds no user. + APIReader client.Reader +} + +//+kubebuilder:rbac:groups=ate.dev,resources=sandboxconfigs,verbs=get;list;watch;patch +//+kubebuilder:rbac:groups=ate.dev,resources=workerpools,verbs=get;list;watch + +// Reconcile decides whether one SandboxConfig can be released. +// +// - A config that is not being deleted needs nothing. +// - A config that is being deleted and still used by a WorkerPool keeps +// the finalizer, so it stays Terminating. +// - A config that is being deleted and used by no WorkerPool loses the +// finalizer. The API server then finishes the delete. +// +// A config that is still in use is not requeued. It is reconciled again when +// a WorkerPool that uses it changes. Every SandboxConfig is also reconciled +// when atecontroller starts, which releases a config whose last user went +// away while atecontroller was down. +func (r *SandboxConfigProtectionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + sc := &atev1alpha1.SandboxConfig{} + if err := r.Get(ctx, req.NamespacedName, sc); err != nil { + // Already deleted. Nothing to release. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + if sc.DeletionTimestamp.IsZero() || !controllerutil.ContainsFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) { + // Not being deleted, or already released. Other finalizers, if + // any, decide the rest. + return ctrl.Result{}, nil + } + inUse, err := r.usedByWorkerPool(ctx, sc.Name) + if err != nil || inUse { + // Still in use. Keep the finalizer, so it stays Terminating. + return ctrl.Result{}, err + } + // No WorkerPool uses it. Release it. + return ctrl.Result{}, removeProtectionFinalizer(ctx, r.Client, sc) +} + +// usedByWorkerPool reports whether any WorkerPool's status names the +// SandboxConfig. It checks the cache first. Only when the cache finds no user +// does it list from the API server. That is rare, because a config is deleted +// far less often than a pool reconciles. +func (r *SandboxConfigProtectionReconciler) usedByWorkerPool(ctx context.Context, name string) (bool, error) { + var cached atev1alpha1.WorkerPoolList + if err := r.List(ctx, &cached, client.MatchingFields{workerPoolSandboxConfigIndex: name}); err != nil { + return false, fmt.Errorf("failed to list WorkerPools using SandboxConfig %q: %w", name, err) + } + if len(cached.Items) > 0 { + return true, nil + } + var pools atev1alpha1.WorkerPoolList + if err := r.APIReader.List(ctx, &pools); err != nil { + return false, fmt.Errorf("failed to list WorkerPools: %w", err) + } + for i := range pools.Items { + if slices.Contains(sandboxConfigsInUse(&pools.Items[i]), name) { + return true, nil + } + } + return false, nil +} + +// removeProtectionFinalizer removes the protection finalizer from sc. The +// patch carries sc's resourceVersion, so a concurrent change to the config +// fails with a conflict and the reconcile is retried. +func removeProtectionFinalizer(ctx context.Context, c client.Client, sc *atev1alpha1.SandboxConfig) error { + if !controllerutil.ContainsFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) { + return nil + } + orig := sc.DeepCopy() + controllerutil.RemoveFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + if err := c.Patch(ctx, sc, client.MergeFromWithOptions(orig, client.MergeFromWithOptimisticLock{})); err != nil { + return fmt.Errorf("failed to update finalizers of SandboxConfig %q: %w", sc.Name, err) + } + return nil +} + +// SetupWithManager registers this reconciler with mgr. The manager is the +// controller-runtime object that runs every controller in atecontroller. It +// keeps one shared cache of the Kubernetes objects the controllers watch, and +// it calls a controller's Reconcile when one of those objects changes. +// +// This reconciler asks the manager for two things: +// +// - A cache index from a SandboxConfig's name to the WorkerPools whose +// status uses it. usedByWorkerPool looks up users through the index +// instead of listing every WorkerPool. +// - Reconcile calls for a SandboxConfig when it changes, which includes +// being marked for deletion, and for every SandboxConfig a WorkerPool's +// status names when that WorkerPool changes. +func (r *SandboxConfigProtectionReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &atev1alpha1.WorkerPool{}, workerPoolSandboxConfigIndex, + func(obj client.Object) []string { + wp, ok := obj.(*atev1alpha1.WorkerPool) + if !ok { + return nil + } + return sandboxConfigsInUse(wp) + }); err != nil { + return fmt.Errorf("failed to index WorkerPools by SandboxConfig: %w", err) + } + return ctrl.NewControllerManagedBy(mgr). + Named("sandboxconfig-protection"). + For(&atev1alpha1.SandboxConfig{}). + // A config that is being deleted waits for the last pool that uses + // it to go away. For an update, this runs for the old and the new + // object, so the config a pool switches away from is reconciled too. + Watches(&atev1alpha1.WorkerPool{}, handler.EnqueueRequestsFromMapFunc( + func(_ context.Context, obj client.Object) []reconcile.Request { + wp, ok := obj.(*atev1alpha1.WorkerPool) + if !ok { + return nil + } + var reqs []reconcile.Request + for _, name := range sandboxConfigsInUse(wp) { + reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKey{Name: name}}) + } + return reqs + })). + Complete(r) +} diff --git a/cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller_test.go b/cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller_test.go new file mode 100644 index 0000000000..fd65d90f23 --- /dev/null +++ b/cmd/atecontroller/internal/controllers/sandboxconfig_protection_controller_test.go @@ -0,0 +1,392 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "context" + "errors" + "testing" + + k8errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" +) + +// TestSandboxConfigProtectionReconcile checks that the reconciler never adds +// the protection finalizer, that a deleting SandboxConfig loses it only when +// neither the cache nor a live read finds a WorkerPool naming it, that it +// keeps it when either list fails, and that other finalizers are untouched. +func TestSandboxConfigProtectionReconcile(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + now := metav1.Now() + // using returns a pool whose status has one sandbox class per name, each + // using the named config, or no config when the name is "". + using := func(names ...string) *atev1alpha1.WorkerPool { + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + for _, name := range names { + s := atev1alpha1.WorkerPoolSandboxClassStatus{Name: atev1alpha1.SandboxClassGvisor} + if name != "" { + s.ConfigRef = &atev1alpha1.SandboxConfigReference{Name: name} + } + wp.Status.SandboxClasses = append(wp.Status.SandboxClasses, s) + } + return wp + } + // claiming returns a pool whose one sandbox class uses "other" and is + // switching to the named config. + claiming := func(name string) *atev1alpha1.WorkerPool { + wp := using("other") + wp.Status.SandboxClasses[0].PendingConfigRef = &atev1alpha1.SandboxConfigReference{Name: name} + return wp + } + // config builds a SandboxConfig that has the protection finalizer if + // protected. A deleting one carries another finalizer too, as the fake + // client, like the API server, keeps no deleting object without one. + config := func(protected, deleting bool) *atev1alpha1.SandboxConfig { + sc := makeSandboxConfig("sc", atev1alpha1.SandboxClassGvisor, true) + if protected { + controllerutil.AddFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + } + if deleting { + controllerutil.AddFinalizer(sc, testHoldFinalizer) + sc.DeletionTimestamp = &now + } + return sc + } + // newClient returns a client whose List fails with listErr, if set. + newClient := func(listErr error, objs ...client.Object) client.Client { + return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...). + WithIndex(&atev1alpha1.WorkerPool{}, workerPoolSandboxConfigIndex, func(obj client.Object) []string { + return sandboxConfigsInUse(obj.(*atev1alpha1.WorkerPool)) + }). + WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, c client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + if listErr != nil { + return listErr + } + return c.List(ctx, list, opts...) + }, + }).Build() + } + errList := errors.New("list failed") + + tests := []struct { + name string + sc *atev1alpha1.SandboxConfig + cached []client.Object + live []client.Object + // cachedErr and liveErr fail the cached and the live WorkerPool list. + cachedErr error + liveErr error + want bool + // wantErr is the error Reconcile must return, or nil. + wantErr error + }{ + { + name: "live and unused: left alone", + sc: config(false, false), + want: false, + }, + { + name: "live and used but unprotected: left alone, adding is the WorkerPool controller's job", + sc: config(false, false), + cached: []client.Object{using("sc")}, + want: false, + }, + { + name: "live and protected: keep", + sc: config(true, false), + want: true, + }, + { + name: "deleting, used in cache: keep", + sc: config(true, true), + cached: []client.Object{using("sc")}, + want: true, + }, + { + name: "deleting, unused in cache but used live: keep", + sc: config(true, true), + live: []client.Object{using("sc")}, + want: true, + }, + { + name: "deleting, used by a later sandbox class in cache: keep", + sc: config(true, true), + cached: []client.Object{using("other", "sc")}, + want: true, + }, + { + name: "deleting, used by a later sandbox class live: keep", + sc: config(true, true), + live: []client.Object{using("other", "sc")}, + want: true, + }, + { + name: "deleting, claimed in cache: keep", + sc: config(true, true), + cached: []client.Object{claiming("sc")}, + want: true, + }, + { + name: "deleting, unclaimed in cache but claimed live: keep", + sc: config(true, true), + live: []client.Object{claiming("sc")}, + want: true, + }, + { + name: "deleting and unused: remove", + sc: config(true, true), + cached: []client.Object{using("other")}, + live: []client.Object{using("other")}, + want: false, + }, + { + name: "deleting, only a pool that has no config in use: remove", + sc: config(true, true), + cached: []client.Object{using("")}, + live: []client.Object{using("")}, + want: false, + }, + { + name: "deleting without the finalizer: nothing to do", + sc: config(false, true), + want: false, + }, + { + name: "deleting, cached list fails: keep and return the error", + sc: config(true, true), + cachedErr: errList, + want: true, + wantErr: errList, + }, + { + name: "deleting, unused in cache, live list fails: keep and return the error", + sc: config(true, true), + cached: []client.Object{using("other")}, + liveErr: errList, + want: true, + wantErr: errList, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + c := newClient(tc.cachedErr, append([]client.Object{tc.sc}, tc.cached...)...) + r := &SandboxConfigProtectionReconciler{Client: c, APIReader: newClient(tc.liveErr, tc.live...)} + _, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: types.NamespacedName{Name: tc.sc.Name}}) + if !errors.Is(err, tc.wantErr) { + t.Fatalf("Reconcile error = %v, want %v", err, tc.wantErr) + } + got := &atev1alpha1.SandboxConfig{} + if err := c.Get(t.Context(), types.NamespacedName{Name: tc.sc.Name}, got); err != nil { + t.Fatalf("get SandboxConfig: %v", err) + } + if has := controllerutil.ContainsFinalizer(got, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer); has != tc.want { + t.Errorf("has protection finalizer = %v, want %v", has, tc.want) + } + if controllerutil.ContainsFinalizer(tc.sc, testHoldFinalizer) && !controllerutil.ContainsFinalizer(got, testHoldFinalizer) { + t.Errorf("lost %s", testHoldFinalizer) + } + }) + } +} + +// TestSandboxConfigProtectionReconcileGet checks Reconcile when the +// SandboxConfig cannot be read: a config that is gone needs nothing, and any +// other read error is returned so the request is retried. +func TestSandboxConfigProtectionReconcileGet(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + errGet := errors.New("get failed") + + tests := []struct { + name string + // getErr fails every Get, if set. + getErr error + wantErr error + }{ + { + name: "not found: nothing to release", + }, + { + name: "get fails: return the error", + getErr: errGet, + wantErr: errGet, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + c := fake.NewClientBuilder().WithScheme(scheme). + WithInterceptorFuncs(interceptor.Funcs{ + Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if tc.getErr != nil { + return tc.getErr + } + return c.Get(ctx, key, obj, opts...) + }, + }).Build() + r := &SandboxConfigProtectionReconciler{Client: c, APIReader: c} + res, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "missing"}}) + if !errors.Is(err, tc.wantErr) { + t.Fatalf("Reconcile error = %v, want %v", err, tc.wantErr) + } + if !res.IsZero() { + t.Errorf("Reconcile result = %+v, want zero", res) + } + }) + } +} + +// protectionFinalizerCase is one case of a test that applies +// addProtectionFinalizer or removeProtectionFinalizer to a SandboxConfig. +type protectionFinalizerCase struct { + name string + op func(context.Context, client.Client, *atev1alpha1.SandboxConfig) error + sc *atev1alpha1.SandboxConfig + // stale changes the stored config after it is read, so the patch + // carries an outdated resourceVersion. + stale bool + wantPatches int + want bool + wantErr func(error) bool +} + +// protectedSandboxConfig builds a SandboxConfig for a protectionFinalizerCase. +// It always carries testHoldFinalizer, so it stays readable while deleting, +// and the protection finalizer if protected. +func protectedSandboxConfig(protected, deleting bool) *atev1alpha1.SandboxConfig { + sc := makeSandboxConfig("sc", atev1alpha1.SandboxClassGvisor, false) + controllerutil.AddFinalizer(sc, testHoldFinalizer) + if protected { + controllerutil.AddFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + } + if deleting { + now := metav1.Now() + sc.DeletionTimestamp = &now + } + return sc +} + +// runProtectionFinalizerCases applies each case's op to its config and +// checks the patches made, the error, the protection finalizer, and that +// testHoldFinalizer is still there. +func runProtectionFinalizerCases(t *testing.T, tests []protectionFinalizerCase) { + t.Helper() + scheme := runtime.NewScheme() + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + patches := 0 + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tc.sc). + WithInterceptorFuncs(interceptor.Funcs{ + Patch: func(ctx context.Context, c client.WithWatch, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + patches++ + return c.Patch(ctx, obj, patch, opts...) + }, + }).Build() + key := types.NamespacedName{Name: tc.sc.Name} + read := &atev1alpha1.SandboxConfig{} + if err := c.Get(t.Context(), key, read); err != nil { + t.Fatalf("get SandboxConfig: %v", err) + } + if tc.stale { + stored := read.DeepCopy() + stored.Labels = map[string]string{"changed": "true"} + if err := c.Update(t.Context(), stored); err != nil { + t.Fatalf("update SandboxConfig: %v", err) + } + } + + err := tc.op(t.Context(), c, read) + if tc.wantErr == nil && err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tc.wantErr != nil && !tc.wantErr(err) { + t.Fatalf("error = %v, want a conflict", err) + } + if patches != tc.wantPatches { + t.Errorf("patches = %d, want %d", patches, tc.wantPatches) + } + got := &atev1alpha1.SandboxConfig{} + if err := c.Get(t.Context(), key, got); err != nil { + t.Fatalf("get SandboxConfig: %v", err) + } + if has := controllerutil.ContainsFinalizer(got, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer); has != tc.want { + t.Errorf("has protection finalizer = %v, want %v", has, tc.want) + } + if !controllerutil.ContainsFinalizer(got, testHoldFinalizer) { + t.Errorf("lost %s", testHoldFinalizer) + } + }) + } +} + +// TestRemoveProtectionFinalizer checks that removeProtectionFinalizer patches +// only when the finalizer is present, leaves other finalizers alone, and fails +// with a conflict when the config changed since it was read. +func TestRemoveProtectionFinalizer(t *testing.T) { + t.Parallel() + runProtectionFinalizerCases(t, []protectionFinalizerCase{ + { + name: "remove from protected config", + op: removeProtectionFinalizer, + sc: protectedSandboxConfig(true, false), + wantPatches: 1, + want: false, + }, + { + name: "remove from deleting config", + op: removeProtectionFinalizer, + sc: protectedSandboxConfig(true, true), + wantPatches: 1, + want: false, + }, + { + name: "remove from unprotected config: no patch", + op: removeProtectionFinalizer, + sc: protectedSandboxConfig(false, false), + want: false, + }, + { + name: "remove from stale config: conflict", + op: removeProtectionFinalizer, + sc: protectedSandboxConfig(true, false), + stale: true, + wantPatches: 1, + want: true, + wantErr: k8errors.IsConflict, + }, + }) +} diff --git a/cmd/atecontroller/internal/controllers/workerpool_controller.go b/cmd/atecontroller/internal/controllers/workerpool_controller.go index 4806729e4b..f6b6637f79 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_controller.go +++ b/cmd/atecontroller/internal/controllers/workerpool_controller.go @@ -17,24 +17,39 @@ package controllers import ( "context" "fmt" + "slices" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" k8errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/events" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/agent-substrate/substrate/internal/ateattr" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" ) -const workerPoolFieldOwner = "workerpool-controller" +const ( + workerPoolFieldOwner = "workerpool-controller" + // eventReasonSandboxConfigSwitched is the reason of the Normal event a + // pool emits each time a class's status configRef changes. + eventReasonSandboxConfigSwitched = "SandboxConfigSwitched" + // eventActionResolveSandboxConfig is the action of every event the pool + // emits while resolving its SandboxConfigs. + eventActionResolveSandboxConfig = "ResolveSandboxConfig" +) type WorkerPoolReconciler struct { client.Client @@ -64,6 +79,13 @@ type WorkerPoolReconciler struct { SystemNamespace string AteletServiceAccount string RouterServiceAccount string + // Recorder emits a Normal event each time a pool's SandboxConfig + // changes, and a Warning event when a class stops resolving. + Recorder events.EventRecorder + // APIReader reads a SandboxConfig straight from the API server, not from + // the cache. It confirms a config the pool switches to, once status has + // claimed it. + APIReader client.Reader desiredWorkers metric.Int64ObservableUpDownCounter readyWorkers metric.Int64ObservableUpDownCounter @@ -73,6 +95,8 @@ type WorkerPoolReconciler struct { //+kubebuilder:rbac:groups=ate.dev,resources=workerpools/status,verbs=get;update;patch //+kubebuilder:rbac:groups=ate.dev,resources=workerpools/finalizers,verbs=update //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=ate.dev,resources=sandboxconfigs,verbs=get;list;watch;patch +//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -102,23 +126,192 @@ func (r *WorkerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } +// reconcileWorkerPool applies the worker Deployment, resolves the pool's +// SandboxConfigs, and records them in status. +// +// A class that switches to a new SandboxConfig does so in two status writes. +// This makes sure status never names a config after its protection finalizer +// was released. +// +// 1. Resolve every class from the cache, and add the protection finalizer +// to each config a class resolved to. +// 2. Write status with the new config in pendingConfigRef and the config in +// use still in configRef. Both are protected from now on. +// 3. Read the new config from the API server. If it is live, configRef +// moves to it. If it is gone or being deleted, the class keeps the config +// in use. +// 4. Write status again, and emit events for what changed. +// +// The read in step 3 has to come after the write in step 2. The protection +// reconciler only releases a config that is already being deleted, and only +// after it listed the WorkerPools and found none using the config. If it +// released this config, it listed the pools before step 2 wrote the claim. +// The config was already being deleted then, so the read in step 3 sees the +// deletion and the class does not switch. func (r *WorkerPoolReconciler) reconcileWorkerPool(ctx context.Context, wp *atev1alpha1.WorkerPool) error { log := log.FromContext(ctx) log.Info("Reconciling worker pool") - if err := r.applyDeployment(ctx, wp); err != nil { + existing := &appsv1.Deployment{} + if err := r.Get(ctx, types.NamespacedName{Name: wp.Name, Namespace: wp.Namespace}, existing); err != nil { + if !k8errors.IsNotFound(err) { + return fmt.Errorf("failed to get deployment: %w", err) + } + existing = nil + } + // Worker pods take nothing from the SandboxConfig, so an existing + // Deployment is applied first. A SandboxConfig error never holds back a + // scale. + if existing != nil { + if err := r.applyDeployment(ctx, wp); err != nil { + return err + } + } + + var list atev1alpha1.SandboxConfigList + if err := r.List(ctx, &list); err != nil { + return fmt.Errorf("failed to list SandboxConfigs: %w", err) + } + configs := list.Items + + classes, _ := resolveSandboxClasses(wp, configs) + if err := protectSandboxConfigs(ctx, r.Client, classes, configs); err != nil { + return err + } + // prev is the status before this reconcile, for the events below. + // syncStatus overwrites wp.Status. + prev := wp.Status.DeepCopy().SandboxClasses + if claim := claimSandboxConfigs(wp, classes); claim != nil { + if err := r.syncStatus(ctx, wp, existing, claim); err != nil { + return err + } + if err := r.confirmSandboxConfigSwitches(ctx, wp, classes, configs); err != nil { + return err + } + } + if err := r.syncStatus(ctx, wp, existing, classes); err != nil { return err } + r.recordSandboxConfigEvents(wp, prev, classes) + // A pool without a Deployment gets one only once every sandbox class has + // a config. + if existing == nil && everyClassHasConfig(classes) { + return r.applyDeployment(ctx, wp) + } + return nil +} - dep := &appsv1.Deployment{} - if err := r.Get(ctx, types.NamespacedName{Name: wp.Name, Namespace: wp.Namespace}, dep); err != nil { - if k8errors.IsNotFound(err) { - return nil +// everyClassHasConfig reports whether every class in classes has a +// SandboxConfig, either resolved now or kept from before. +func everyClassHasConfig(classes []atev1alpha1.WorkerPoolSandboxClassStatus) bool { + for _, c := range classes { + if c.ConfigRef == nil { + return false } - return fmt.Errorf("failed to get deployment: %w", err) } + return true +} - return r.syncStatus(ctx, wp, dep) +// protectSandboxConfigs adds the protection finalizer to every SandboxConfig +// that classes resolved to. It runs before status names the config. +// +// This is the only place the finalizer is added. A config that no pool has +// ever used has no finalizer and is deleted right away. A config that was +// deleted after the cached read is caught later, when reconcileWorkerPool +// confirms it against the API server. +func protectSandboxConfigs(ctx context.Context, c client.Client, classes []atev1alpha1.WorkerPoolSandboxClassStatus, configs []atev1alpha1.SandboxConfig) error { + for _, class := range classes { + if class.ConfigRef == nil { + continue + } + i := slices.IndexFunc(configs, func(sc atev1alpha1.SandboxConfig) bool { return sc.Name == class.ConfigRef.Name }) + if i < 0 { + return fmt.Errorf("resolved SandboxConfig %q is not in the list it was resolved from", class.ConfigRef.Name) + } + if err := addProtectionFinalizer(ctx, c, &configs[i]); err != nil { + return err + } + } + return nil +} + +// addProtectionFinalizer adds the protection finalizer to sc. The API server +// rejects a new finalizer on an object that is being deleted, so a deleting +// config is left alone. The patch carries sc's resourceVersion, so a +// concurrent change to the config fails with a conflict and the reconcile is +// retried. +func addProtectionFinalizer(ctx context.Context, c client.Client, sc *atev1alpha1.SandboxConfig) error { + if controllerutil.ContainsFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) || !sc.DeletionTimestamp.IsZero() { + return nil + } + orig := sc.DeepCopy() + controllerutil.AddFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + if err := c.Patch(ctx, sc, client.MergeFromWithOptions(orig, client.MergeFromWithOptimisticLock{})); err != nil { + return fmt.Errorf("failed to update finalizers of SandboxConfig %q: %w", sc.Name, err) + } + return nil +} + +// confirmSandboxConfigSwitches reads every config that a class in classes +// switches to from the API server. A class whose new config is gone, belongs +// to another class, or is being deleted gets an entry that keeps the config in +// use instead. wp's status must already claim the new configs. See +// reconcileWorkerPool. +func (r *WorkerPoolReconciler) confirmSandboxConfigSwitches(ctx context.Context, wp *atev1alpha1.WorkerPool, classes []atev1alpha1.WorkerPoolSandboxClassStatus, configs []atev1alpha1.SandboxConfig) error { + for i, class := range classes { + if !switchesSandboxConfig(wp, class) { + continue + } + live := &atev1alpha1.SandboxConfig{} + if err := r.APIReader.Get(ctx, client.ObjectKey{Name: class.ConfigRef.Name}, live); err != nil { + if !k8errors.IsNotFound(err) { + return fmt.Errorf("failed to get SandboxConfig %q: %w", class.ConfigRef.Name, err) + } + live = nil + } + classes[i] = confirmSandboxConfigSwitch(wp, class, live, configs) + } + return nil +} + +// recordSandboxConfigEvents emits an event for every class in classes whose +// state differs from prev, the pool's status before this reconcile. +// +// - A class whose configRef changed gets a Normal event that names the old +// and the new config. The pool's events are then the history of its +// recent configs. The API server drops an event after its TTL, one hour +// by default, so the full trail lives in whatever stores the events. +// - A class whose Resolved condition is False gets a Warning event, unless +// prev already reports the same failure. A pool that stays unresolved +// does not repeat the event on every reconcile. +// +// It runs after classes is written to status. A reconcile that read a stale +// copy of the pool sees the switch a second time. The reconcile that a claim +// write triggers often does. Its status write fails on the resourceVersion +// before it gets here, so the switch is reported once. +func (r *WorkerPoolReconciler) recordSandboxConfigEvents(wp *atev1alpha1.WorkerPool, prev, classes []atev1alpha1.WorkerPoolSandboxClassStatus) { + for _, class := range classes { + if switchesSandboxConfigFrom(prev, class) { + if prev := findSandboxClassStatus(prev, class.Name); prev != nil && prev.ConfigRef != nil { + r.Recorder.Eventf(wp, nil, corev1.EventTypeNormal, eventReasonSandboxConfigSwitched, eventActionResolveSandboxConfig, + "sandbox class %q switched from SandboxConfig %q to %q", class.Name, prev.ConfigRef.Name, class.ConfigRef.Name) + } else { + r.Recorder.Eventf(wp, nil, corev1.EventTypeNormal, eventReasonSandboxConfigSwitched, eventActionResolveSandboxConfig, + "sandbox class %q uses SandboxConfig %q", class.Name, class.ConfigRef.Name) + } + } + cond := meta.FindStatusCondition(class.Conditions, atev1alpha1.WorkerPoolSandboxClassConditionResolved) + if cond == nil || cond.Status == metav1.ConditionTrue { + continue + } + if prev := findSandboxClassStatus(prev, class.Name); prev != nil { + p := meta.FindStatusCondition(prev.Conditions, cond.Type) + if p != nil && p.Status == cond.Status && p.Reason == cond.Reason && p.Message == cond.Message { + continue + } + } + r.Recorder.Eventf(wp, nil, corev1.EventTypeWarning, cond.Reason, eventActionResolveSandboxConfig, "%s", cond.Message) + } } func (r *WorkerPoolReconciler) applyDeployment(ctx context.Context, wp *atev1alpha1.WorkerPool) error { @@ -136,22 +329,25 @@ func (r *WorkerPoolReconciler) applyDeployment(ctx context.Context, wp *atev1alp return nil } -func (r *WorkerPoolReconciler) syncStatus(ctx context.Context, wp *atev1alpha1.WorkerPool, dep *appsv1.Deployment) error { - selector, err := metav1.LabelSelectorAsSelector(dep.Spec.Selector) - if err != nil { - return fmt.Errorf("failed to convert Deployment selector: %w", err) - } - - want := atev1alpha1.WorkerPoolStatus{ - Replicas: dep.Status.Replicas, - ReadyReplicas: dep.Status.ReadyReplicas, - Selector: selector.String(), +// syncStatus writes wp's status: replica counts and selector from dep (left +// as they are when dep is nil) and sandboxClasses from classes. +func (r *WorkerPoolReconciler) syncStatus(ctx context.Context, wp *atev1alpha1.WorkerPool, dep *appsv1.Deployment, classes []atev1alpha1.WorkerPoolSandboxClassStatus) error { + want := wp.Status.DeepCopy() + if dep != nil { + selector, err := metav1.LabelSelectorAsSelector(dep.Spec.Selector) + if err != nil { + return fmt.Errorf("failed to convert Deployment selector: %w", err) + } + want.Replicas = dep.Status.Replicas + want.ReadyReplicas = dep.Status.ReadyReplicas + want.Selector = selector.String() } - if equality.Semantic.DeepEqual(wp.Status, want) { + want.SandboxClasses = classes + if equality.Semantic.DeepEqual(wp.Status, *want) { return nil } - wp.Status = want + wp.Status = *want if err := r.Status().Update(ctx, wp); err != nil { return fmt.Errorf("failed to update WorkerPool status: %w", err) } @@ -209,7 +405,12 @@ func (r *WorkerPoolReconciler) InitMetrics(meter metric.Meter) error { return nil } -// SetupWithManager sets up the controller with the Manager. +// SetupWithManager registers this reconciler with mgr. The manager is the +// controller-runtime object that runs every controller in atecontroller. It +// keeps one shared cache of the Kubernetes objects the controllers watch, and +// it calls a controller's Reconcile when one of those objects changes. A +// WorkerPool is reconciled when it changes, when its Deployment changes, and +// when a SandboxConfig that can change its resolution changes. func (r *WorkerPoolReconciler) SetupWithManager(mgr ctrl.Manager) error { if err := r.InitMetrics(otel.Meter("atecontroller")); err != nil { return fmt.Errorf("failed to initialize workerpool metrics: %w", err) @@ -217,5 +418,39 @@ func (r *WorkerPoolReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&atev1alpha1.WorkerPool{}). Owns(&appsv1.Deployment{}). + Watches(&atev1alpha1.SandboxConfig{}, handler.EnqueueRequestsFromMapFunc(r.workerPoolsForSandboxConfig)). Complete(r) } + +// workerPoolsForSandboxConfig maps a SandboxConfig event to the WorkerPools +// whose resolution it can change. Those are the pools that run its sandbox +// class, because the config may become or stop being their default, and the +// pools whose spec or status names it. +func (r *WorkerPoolReconciler) workerPoolsForSandboxConfig(ctx context.Context, obj client.Object) []reconcile.Request { + sc, ok := obj.(*atev1alpha1.SandboxConfig) + if !ok { + return nil + } + var pools atev1alpha1.WorkerPoolList + if err := r.List(ctx, &pools); err != nil { + log.FromContext(ctx).Error(err, "failed to list worker pools for SandboxConfig", "sandboxConfig", sc.Name) + return nil + } + var reqs []reconcile.Request + for i := range pools.Items { + wp := &pools.Items[i] + if workerPoolMayUseSandboxConfig(wp, sc) { + reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(wp)}) + } + } + return reqs +} + +func workerPoolMayUseSandboxConfig(wp *atev1alpha1.WorkerPool, sc *atev1alpha1.SandboxConfig) bool { + for _, entry := range wp.Spec.SandboxClasses { + if entry.Name == sc.Spec.SandboxClass || (entry.ConfigRef != nil && entry.ConfigRef.Name == sc.Name) { + return true + } + } + return slices.Contains(sandboxConfigsInUse(wp), sc.Name) +} diff --git a/cmd/atecontroller/internal/controllers/workerpool_controller_test.go b/cmd/atecontroller/internal/controllers/workerpool_controller_test.go index 784f3de2f1..44b8d51795 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_controller_test.go +++ b/cmd/atecontroller/internal/controllers/workerpool_controller_test.go @@ -80,13 +80,30 @@ func TestMain(m *testing.M) { } if err := (&WorkerPoolReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorder("workerpool-controller"), + APIReader: mgr.GetAPIReader(), }).SetupWithManager(mgr); err != nil { fmt.Fprintf(os.Stderr, "controller setup failed: %v\n", err) os.Exit(1) } + if err := (&SandboxConfigProtectionReconciler{ + Client: mgr.GetClient(), + APIReader: mgr.GetAPIReader(), + }).SetupWithManager(mgr); err != nil { + fmt.Fprintf(os.Stderr, "sandboxconfig protection controller setup failed: %v\n", err) + os.Exit(1) + } + + // makeWorkerPool omits configRef, so every gvisor pool resolves to this + // class default. Tests that need to control the class default use microvm. + if err := k8sClient.Create(context.Background(), makeSandboxConfig(testGvisorDefault, atev1alpha1.SandboxClassGvisor, true)); err != nil { + fmt.Fprintf(os.Stderr, "create default SandboxConfig: %v\n", err) + os.Exit(1) + } + if err := (&NetworkPolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/cmd/atecontroller/internal/controllers/workerpool_sandboxconfig.go b/cmd/atecontroller/internal/controllers/workerpool_sandboxconfig.go new file mode 100644 index 0000000000..e9752713f9 --- /dev/null +++ b/cmd/atecontroller/internal/controllers/workerpool_sandboxconfig.go @@ -0,0 +1,273 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "fmt" + "slices" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" +) + +// resolveLatestSandboxConfig picks the SandboxConfig the spec of one sandbox +// class entry asks for. It does not fall back to the config the pool already +// uses. On failure it returns nil plus the condition reason and message. +// +// - With configRef, that config is used. It must exist, belong to the same +// class, and not be being deleted. +// - Without configRef, the newest config of the class that is annotated as +// the class default and is not being deleted is used. If two were created +// at the same time, the name that sorts first wins. +func resolveLatestSandboxConfig(entry atev1alpha1.WorkerPoolSandboxClass, configs []atev1alpha1.SandboxConfig) (*atev1alpha1.SandboxConfig, string, string) { + if entry.ConfigRef != nil { + name := entry.ConfigRef.Name + i := slices.IndexFunc(configs, func(sc atev1alpha1.SandboxConfig) bool { return sc.Name == name }) + if i < 0 { + return nil, atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + fmt.Sprintf("SandboxConfig %q not found", name) + } + sc := &configs[i] + if sc.Spec.SandboxClass != entry.Name { + return nil, atev1alpha1.WorkerPoolReasonSandboxClassMismatch, + fmt.Sprintf("SandboxConfig %q is for sandbox class %q, not %q", name, sc.Spec.SandboxClass, entry.Name) + } + if !sc.DeletionTimestamp.IsZero() { + return nil, atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + fmt.Sprintf("SandboxConfig %q is being deleted", name) + } + return sc, "", "" + } + + isDefault := func(sc atev1alpha1.SandboxConfig) bool { + return sc.Spec.SandboxClass == entry.Name && + sc.Annotations[atev1alpha1.SandboxConfigClassDefaultAnnotation] == "true" + } + var best *atev1alpha1.SandboxConfig + for i := range configs { + sc := &configs[i] + if isDefault(*sc) && sc.DeletionTimestamp.IsZero() && (best == nil || newerSandboxConfig(sc, best)) { + best = sc + } + } + if best != nil { + return best, "", "" + } + isDeletingDefault := func(sc atev1alpha1.SandboxConfig) bool { + return isDefault(sc) && !sc.DeletionTimestamp.IsZero() + } + if slices.ContainsFunc(configs, isDeletingDefault) { + return nil, atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + fmt.Sprintf("the default SandboxConfig for sandbox class %q is being deleted", entry.Name) + } + return nil, atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + fmt.Sprintf("no SandboxConfig of sandbox class %q is annotated %s=true", entry.Name, atev1alpha1.SandboxConfigClassDefaultAnnotation) +} + +// resolveSandboxConfig returns the SandboxConfig one sandbox class entry uses +// and its Resolved condition. inUse is the config the pool's status already +// uses for this class, or "". When the spec does not resolve, the condition is +// False and the pool keeps inUse. See keepSandboxConfig. +func resolveSandboxConfig(entry atev1alpha1.WorkerPoolSandboxClass, inUse string, configs []atev1alpha1.SandboxConfig) (*atev1alpha1.SandboxConfig, metav1.Condition) { + sc, reason, message := resolveLatestSandboxConfig(entry, configs) + if sc != nil { + return sc, metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionTrue, + Reason: atev1alpha1.WorkerPoolReasonResolved, + Message: fmt.Sprintf("resolved to SandboxConfig %q", sc.Name), + } + } + return keepSandboxConfig(entry.Name, inUse, configs, reason, message) +} + +// keepSandboxConfig returns the config a class keeps when its spec does not +// resolve, and a False Resolved condition with reason and message. +// +// The class keeps inUse, so a running pool never loses its config. The result +// is nil only when inUse is "" or no longer names a config of the class. A +// config in use that is being deleted carries the protection finalizer while +// any pool's status names it. One without the finalizer is about to +// disappear, so it is not kept. +func keepSandboxConfig(class atev1alpha1.SandboxClass, inUse string, configs []atev1alpha1.SandboxConfig, reason, message string) (*atev1alpha1.SandboxConfig, metav1.Condition) { + var kept *atev1alpha1.SandboxConfig + if inUse != "" { + i := slices.IndexFunc(configs, func(sc atev1alpha1.SandboxConfig) bool { return sc.Name == inUse }) + if i >= 0 && configs[i].Spec.SandboxClass == class && + (configs[i].DeletionTimestamp.IsZero() || controllerutil.ContainsFinalizer(&configs[i], atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer)) { + kept = &configs[i] + message = fmt.Sprintf("%s; keeping %q", message, kept.Name) + } + } + return kept, metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionFalse, + Reason: reason, + Message: message, + } +} + +// newerSandboxConfig reports whether a takes precedence over b as a class +// default. +func newerSandboxConfig(a, b *atev1alpha1.SandboxConfig) bool { + if !a.CreationTimestamp.Equal(&b.CreationTimestamp) { + return b.CreationTimestamp.Before(&a.CreationTimestamp) + } + return a.Name < b.Name +} + +// resolveSandboxClasses resolves every entry of wp.Spec.SandboxClasses. It +// returns one status entry per class, and whether every class resolved. Each +// entry carries the config the class uses, nil if it has none, and its +// Resolved condition. +func resolveSandboxClasses(wp *atev1alpha1.WorkerPool, configs []atev1alpha1.SandboxConfig) ([]atev1alpha1.WorkerPoolSandboxClassStatus, bool) { + statuses := make([]atev1alpha1.WorkerPoolSandboxClassStatus, 0, len(wp.Spec.SandboxClasses)) + resolved := true + for _, entry := range wp.Spec.SandboxClasses { + prev := sandboxClassStatus(wp, entry.Name) + inUse := "" + if prev != nil && prev.ConfigRef != nil { + inUse = prev.ConfigRef.Name + } + sc, cond := resolveSandboxConfig(entry, inUse, configs) + if cond.Status != metav1.ConditionTrue { + resolved = false + } + statuses = append(statuses, sandboxClassStatusFor(wp, entry.Name, sc, cond)) + } + return statuses, resolved +} + +// sandboxClassStatusFor builds the status entry for class from the config it +// uses, nil if none, and its Resolved condition. The entry starts from the +// class's previous conditions in wp, so lastTransitionTime only moves when the +// condition's status does. +func sandboxClassStatusFor(wp *atev1alpha1.WorkerPool, class atev1alpha1.SandboxClass, sc *atev1alpha1.SandboxConfig, cond metav1.Condition) atev1alpha1.WorkerPoolSandboxClassStatus { + cond.ObservedGeneration = wp.Generation + status := atev1alpha1.WorkerPoolSandboxClassStatus{Name: class} + if prev := sandboxClassStatus(wp, class); prev != nil { + status.Conditions = slices.Clone(prev.Conditions) + } + meta.SetStatusCondition(&status.Conditions, cond) + if sc != nil { + status.ConfigRef = &atev1alpha1.SandboxConfigReference{Name: sc.Name} + } + return status +} + +// switchesSandboxConfig reports whether class, as resolved, uses a config +// other than the one wp's status uses for it. +func switchesSandboxConfig(wp *atev1alpha1.WorkerPool, class atev1alpha1.WorkerPoolSandboxClassStatus) bool { + return switchesSandboxConfigFrom(wp.Status.SandboxClasses, class) +} + +// switchesSandboxConfigFrom reports whether class, as resolved, uses a config +// other than the one its entry in prev uses. +func switchesSandboxConfigFrom(prev []atev1alpha1.WorkerPoolSandboxClassStatus, class atev1alpha1.WorkerPoolSandboxClassStatus) bool { + if class.ConfigRef == nil { + return false + } + p := findSandboxClassStatus(prev, class.Name) + return p == nil || p.ConfigRef == nil || p.ConfigRef.Name != class.ConfigRef.Name +} + +// claimSandboxConfigs returns the status that claims every config a class in +// classes switches to, or nil if no class switches. The claim keeps each +// class's status in wp as it is and names the new config in pendingConfigRef. +// That protects the new config like configRef does while +// confirmSandboxConfigSwitch checks it. +func claimSandboxConfigs(wp *atev1alpha1.WorkerPool, classes []atev1alpha1.WorkerPoolSandboxClassStatus) []atev1alpha1.WorkerPoolSandboxClassStatus { + claim := make([]atev1alpha1.WorkerPoolSandboxClassStatus, 0, len(classes)) + claimed := false + for _, class := range classes { + status := atev1alpha1.WorkerPoolSandboxClassStatus{Name: class.Name} + if prev := sandboxClassStatus(wp, class.Name); prev != nil { + status = *prev.DeepCopy() + status.PendingConfigRef = nil + } + if switchesSandboxConfig(wp, class) { + status.PendingConfigRef = class.ConfigRef.DeepCopy() + claimed = true + } + claim = append(claim, status) + } + if !claimed { + return nil + } + return claim +} + +// confirmSandboxConfigSwitch returns the status entry for a class that +// switches to a new config. live is that config as read from the API server +// after wp's status claimed it, or nil if it does not exist. The switch stands +// if the config belongs to the class and is not being deleted. Otherwise the +// class keeps the config wp's status uses, with a False Resolved condition. +// configs is the cached list keepSandboxConfig checks that config against. +func confirmSandboxConfigSwitch(wp *atev1alpha1.WorkerPool, class atev1alpha1.WorkerPoolSandboxClassStatus, live *atev1alpha1.SandboxConfig, configs []atev1alpha1.SandboxConfig) atev1alpha1.WorkerPoolSandboxClassStatus { + name := class.ConfigRef.Name + var reason, message string + switch { + case live == nil: + reason, message = atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + fmt.Sprintf("SandboxConfig %q not found", name) + case live.Spec.SandboxClass != class.Name: + reason, message = atev1alpha1.WorkerPoolReasonSandboxClassMismatch, + fmt.Sprintf("SandboxConfig %q is for sandbox class %q, not %q", name, live.Spec.SandboxClass, class.Name) + case !live.DeletionTimestamp.IsZero(): + reason, message = atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + fmt.Sprintf("SandboxConfig %q is being deleted", name) + default: + return class + } + inUse := "" + if prev := sandboxClassStatus(wp, class.Name); prev != nil && prev.ConfigRef != nil { + inUse = prev.ConfigRef.Name + } + sc, cond := keepSandboxConfig(class.Name, inUse, configs, reason, message) + return sandboxClassStatusFor(wp, class.Name, sc, cond) +} + +// sandboxClassStatus returns the status entry wp records for class, or nil. +func sandboxClassStatus(wp *atev1alpha1.WorkerPool, class atev1alpha1.SandboxClass) *atev1alpha1.WorkerPoolSandboxClassStatus { + return findSandboxClassStatus(wp.Status.SandboxClasses, class) +} + +// findSandboxClassStatus returns the entry of class in statuses, or nil. +func findSandboxClassStatus(statuses []atev1alpha1.WorkerPoolSandboxClassStatus, class atev1alpha1.SandboxClass) *atev1alpha1.WorkerPoolSandboxClassStatus { + for i := range statuses { + if statuses[i].Name == class { + return &statuses[i] + } + } + return nil +} + +// sandboxConfigsInUse returns the names of the SandboxConfigs wp's status +// records as in use. That is every configRef and every pendingConfigRef. +func sandboxConfigsInUse(wp *atev1alpha1.WorkerPool) []string { + names := make([]string, 0, len(wp.Status.SandboxClasses)) + for _, s := range wp.Status.SandboxClasses { + if s.ConfigRef != nil { + names = append(names, s.ConfigRef.Name) + } + if s.PendingConfigRef != nil { + names = append(names, s.PendingConfigRef.Name) + } + } + return names +} diff --git a/cmd/atecontroller/internal/controllers/workerpool_sandboxconfig_test.go b/cmd/atecontroller/internal/controllers/workerpool_sandboxconfig_test.go new file mode 100644 index 0000000000..46e54e02f9 --- /dev/null +++ b/cmd/atecontroller/internal/controllers/workerpool_sandboxconfig_test.go @@ -0,0 +1,2552 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "cmp" + "context" + "errors" + "fmt" + "slices" + "testing" + "time" + + appsv1 "k8s.io/api/apps/v1" + eventsv1 "k8s.io/api/events/v1" + "k8s.io/apimachinery/pkg/api/equality" + k8errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/events" + "k8s.io/client-go/util/retry" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" +) + +// testGvisorDefault is the gvisor class default TestMain creates. +const testGvisorDefault = "gvisor-default-test" + +const testPauseImage = "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4" + +// testHoldFinalizer keeps a SandboxConfig on the API server after it is +// deleted, so a test can observe how a config that is being deleted is treated. +const testHoldFinalizer = "test.ate.dev/hold" + +func TestResolveSandboxConfig(t *testing.T) { + t.Parallel() + t0 := metav1.NewTime(time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)) + t1 := metav1.NewTime(t0.Add(time.Minute)) + sc := func(name string, class atev1alpha1.SandboxClass, isDefault bool, created metav1.Time, mutate ...func(*atev1alpha1.SandboxConfig)) atev1alpha1.SandboxConfig { + c := *makeSandboxConfig(name, class, isDefault) + c.CreationTimestamp = created + for _, m := range mutate { + m(&c) + } + return c + } + deleting := func(c *atev1alpha1.SandboxConfig) { c.DeletionTimestamp = &t1 } + protected := func(c *atev1alpha1.SandboxConfig) { + controllerutil.AddFinalizer(c, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + } + gvisor := func(ref string) atev1alpha1.WorkerPoolSandboxClass { + e := atev1alpha1.WorkerPoolSandboxClass{Name: atev1alpha1.SandboxClassGvisor} + if ref != "" { + e.ConfigRef = &atev1alpha1.SandboxConfigReference{Name: ref} + } + return e + } + + gv := atev1alpha1.SandboxClassGvisor + + tests := []struct { + name string + entry atev1alpha1.WorkerPoolSandboxClass + inUse string + configs []atev1alpha1.SandboxConfig + // want is the config the class uses, or "" for none. + want string + // wantReason is the False condition's reason, or "" for True. + wantReason string + wantMessage string + }{ + { + name: "explicit ref resolves even when another config is the default", + entry: gvisor("custom"), + configs: []atev1alpha1.SandboxConfig{sc("custom", gv, false, t0), sc("def", gv, true, t1)}, + want: "custom", + wantMessage: `resolved to SandboxConfig "custom"`, + }, + { + name: "explicit ref not found", + entry: gvisor("missing"), + configs: []atev1alpha1.SandboxConfig{sc("def", gv, true, t0)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + wantMessage: `SandboxConfig "missing" not found`, + }, + { + name: "explicit ref not found keeps the config in use", + entry: gvisor("missing"), + inUse: "old", + configs: []atev1alpha1.SandboxConfig{sc("old", gv, false, t0)}, + want: "old", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + wantMessage: `SandboxConfig "missing" not found; keeping "old"`, + }, + { + name: "explicit ref of another class", + entry: gvisor("vm"), + configs: []atev1alpha1.SandboxConfig{sc("vm", atev1alpha1.SandboxClassMicroVM, false, t0)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxClassMismatch, + wantMessage: `SandboxConfig "vm" is for sandbox class "microvm", not "gvisor"`, + }, + { + name: "explicit ref of another class keeps the config in use", + entry: gvisor("vm"), + inUse: "old", + configs: []atev1alpha1.SandboxConfig{sc("vm", atev1alpha1.SandboxClassMicroVM, false, t0), sc("old", gv, false, t0)}, + want: "old", + wantReason: atev1alpha1.WorkerPoolReasonSandboxClassMismatch, + wantMessage: `SandboxConfig "vm" is for sandbox class "microvm", not "gvisor"; keeping "old"`, + }, + { + name: "explicit ref being deleted takes no new users", + entry: gvisor("dying"), + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, false, t0, deleting)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `SandboxConfig "dying" is being deleted`, + }, + { + name: "explicit ref being deleted is kept but reported by a pool already using it", + entry: gvisor("dying"), + inUse: "dying", + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, false, t0, deleting, protected)}, + want: "dying", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `SandboxConfig "dying" is being deleted; keeping "dying"`, + }, + { + name: "explicit ref being deleted and released is not kept by a pool using it", + entry: gvisor("dying"), + inUse: "dying", + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, false, t0, deleting)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `SandboxConfig "dying" is being deleted`, + }, + { + name: "explicit ref being deleted keeps the previous config in use", + entry: gvisor("dying"), + inUse: "old", + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, false, t0, deleting), sc("old", gv, false, t0)}, + want: "old", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `SandboxConfig "dying" is being deleted; keeping "old"`, + }, + { + name: "no default", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("plain", gv, false, t0)}, + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + wantMessage: `no SandboxConfig of sandbox class "gvisor" is annotated sandboxconfig.ate.dev/is-class-default=true`, + }, + { + name: "default of another class is ignored", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("vm", atev1alpha1.SandboxClassMicroVM, true, t0)}, + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + wantMessage: `no SandboxConfig of sandbox class "gvisor" is annotated sandboxconfig.ate.dev/is-class-default=true`, + }, + { + name: "annotation must be exactly true", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("no", gv, false, t0, func(c *atev1alpha1.SandboxConfig) { + c.Annotations = map[string]string{atev1alpha1.SandboxConfigClassDefaultAnnotation: "True"} + })}, + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + wantMessage: `no SandboxConfig of sandbox class "gvisor" is annotated sandboxconfig.ate.dev/is-class-default=true`, + }, + { + name: "default being deleted takes no new users", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, true, t0, deleting)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `the default SandboxConfig for sandbox class "gvisor" is being deleted`, + }, + { + name: "several defaults being deleted are reported", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("dying-new", gv, true, t1, deleting), sc("dying-old", gv, true, t0, deleting)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `the default SandboxConfig for sandbox class "gvisor" is being deleted`, + }, + { + name: "default being deleted is kept but reported by a pool already using it", + entry: gvisor(""), + inUse: "dying", + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, true, t0, deleting, protected)}, + want: "dying", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `the default SandboxConfig for sandbox class "gvisor" is being deleted; keeping "dying"`, + }, + { + name: "default being deleted and released is not kept by a pool using it", + entry: gvisor(""), + inUse: "dying", + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, true, t0, deleting)}, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `the default SandboxConfig for sandbox class "gvisor" is being deleted`, + }, + { + name: "a live default replaces the deleting one in use, even when older", + entry: gvisor(""), + inUse: "dying", + configs: []atev1alpha1.SandboxConfig{sc("dying", gv, true, t1, deleting), sc("old", gv, true, t0)}, + want: "old", + wantMessage: `resolved to SandboxConfig "old"`, + }, + { + name: "config in use that lost the default annotation is kept but reported", + entry: gvisor(""), + inUse: "plain", + configs: []atev1alpha1.SandboxConfig{sc("plain", gv, false, t0)}, + want: "plain", + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + wantMessage: `no SandboxConfig of sandbox class "gvisor" is annotated sandboxconfig.ate.dev/is-class-default=true; keeping "plain"`, + }, + { + name: "config in use that no longer exists is not kept", + entry: gvisor(""), + inUse: "gone", + configs: []atev1alpha1.SandboxConfig{sc("plain", gv, false, t0)}, + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + wantMessage: `no SandboxConfig of sandbox class "gvisor" is annotated sandboxconfig.ate.dev/is-class-default=true`, + }, + { + name: "config in use of another class is not kept", + entry: gvisor(""), + inUse: "vm", + configs: []atev1alpha1.SandboxConfig{sc("vm", atev1alpha1.SandboxClassMicroVM, true, t0)}, + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + wantMessage: `no SandboxConfig of sandbox class "gvisor" is annotated sandboxconfig.ate.dev/is-class-default=true`, + }, + { + name: "newest default wins", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("a-old", gv, true, t0), sc("z-new", gv, true, t1), sc("plain", gv, false, t1)}, + want: "z-new", + wantMessage: `resolved to SandboxConfig "z-new"`, + }, + { + name: "newest default wins regardless of list order", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("z-new", gv, true, t1), sc("a-old", gv, true, t0)}, + want: "z-new", + wantMessage: `resolved to SandboxConfig "z-new"`, + }, + { + name: "creation time tie goes to the first name", + entry: gvisor(""), + configs: []atev1alpha1.SandboxConfig{sc("b", gv, true, t0), sc("a", gv, true, t0)}, + want: "a", + wantMessage: `resolved to SandboxConfig "a"`, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got, cond := resolveSandboxConfig(tc.entry, tc.inUse, tc.configs) + gotName := "" + if got != nil { + gotName = got.Name + } + want := metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionTrue, + Reason: atev1alpha1.WorkerPoolReasonResolved, + Message: tc.wantMessage, + } + if tc.wantReason != "" { + want.Status = metav1.ConditionFalse + want.Reason = tc.wantReason + } + if gotName != tc.want || cond != want { + t.Fatalf("resolveSandboxConfig = (%q, %+v), want (%q, %+v)", gotName, cond, tc.want, want) + } + }) + } +} + +// TestReconcileProtectsBeforeRecording verifies that the WorkerPool +// controller puts the protection finalizer on a config it resolved, and does +// so before its status names the config. It is the only writer that adds the +// finalizer, so a fake client without the protection reconciler is the real +// situation, not a simplification. +func TestReconcileProtectsBeforeRecording(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add client-go scheme: %v", err) + } + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + protected := func(sc *atev1alpha1.SandboxConfig) *atev1alpha1.SandboxConfig { + controllerutil.AddFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + return sc + } + tests := []struct { + name string + config *atev1alpha1.SandboxConfig + // ref is the pool's configRef. "" follows the class default. + ref string + // wantResolved is whether the pool must end up using config. + wantResolved bool + // wantProtected is whether config must carry the finalizer afterwards. + wantProtected bool + }{ + { + name: "unprotected class default", + config: makeSandboxConfig("fresh-default", atev1alpha1.SandboxClassGvisor, true), + wantResolved: true, + wantProtected: true, + }, + { + name: "unprotected configRef target", + config: makeSandboxConfig("fresh-ref", atev1alpha1.SandboxClassGvisor, false), + ref: "fresh-ref", + wantResolved: true, + wantProtected: true, + }, + { + name: "already protected config", + config: protected(makeSandboxConfig("held", atev1alpha1.SandboxClassGvisor, true)), + wantResolved: true, + wantProtected: true, + }, + { + name: "unresolved pool protects nothing", + config: makeSandboxConfig("other-class", atev1alpha1.SandboxClassMicroVM, false), + ref: "other-class", + wantResolved: false, + wantProtected: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + if tc.ref != "" { + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: tc.ref} + } + // protectedAtWrite records whether config carried the finalizer + // at the moment the pool's status was written. + var wroteStatus, protectedAtWrite bool + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(wp, tc.config).WithStatusSubresource(wp). + WithInterceptorFuncs(interceptor.Funcs{ + SubResourceUpdate: func(ctx context.Context, c client.Client, sub string, obj client.Object, opts ...client.SubResourceUpdateOption) error { + sc := &atev1alpha1.SandboxConfig{} + if err := c.Get(ctx, types.NamespacedName{Name: tc.config.Name}, sc); err != nil { + return err + } + wroteStatus = true + protectedAtWrite = controllerutil.ContainsFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + return c.SubResource(sub).Update(ctx, obj, opts...) + }, + }).Build() + r := &WorkerPoolReconciler{Client: c, APIReader: c, Recorder: events.NewFakeRecorder(1)} + if _, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: client.ObjectKeyFromObject(wp)}); err != nil { + t.Fatalf("Reconcile: %v", err) + } + + sc := &atev1alpha1.SandboxConfig{} + if err := c.Get(t.Context(), types.NamespacedName{Name: tc.config.Name}, sc); err != nil { + t.Fatalf("get SandboxConfig: %v", err) + } + if got := controllerutil.ContainsFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer); got != tc.wantProtected { + t.Errorf("config protected = %v, want %v", got, tc.wantProtected) + } + got := &atev1alpha1.WorkerPool{} + if err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), got); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if resolved := statusConfig(got, atev1alpha1.SandboxClassGvisor) == tc.config.Name; resolved != tc.wantResolved { + t.Errorf("status names config = %v, want %v (status %+v)", resolved, tc.wantResolved, got.Status) + } + if !wroteStatus { + t.Fatalf("status was never written") + } + if tc.wantResolved && !protectedAtWrite { + t.Errorf("status named the config before it carried the protection finalizer") + } + }) + } +} + +// TestReconcileConfigDeletedBeforeClaim verifies that a config deleted after +// the cached read the pool resolved from, but before the status write that +// claims it, never becomes the config in use: the protection reconciler runs +// while the claim is being written, as it can on a real cluster, and cannot +// see the pool yet. The read that confirms the switch finds the config gone +// or being deleted, so the class keeps the config it already uses, and the +// next reconcile, which the SandboxConfig watch triggers, settles on the +// older default. +func TestReconcileConfigDeletedBeforeClaim(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + t0 := metav1.NewTime(time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)) + // newDefault is the newest gvisor default, so the pool resolves to it + // first. oldDefault is the one it must fall back to. + config := func(name string, created metav1.Time, finalizers ...string) *atev1alpha1.SandboxConfig { + sc := makeSandboxConfig(name, atev1alpha1.SandboxClassGvisor, true) + sc.CreationTimestamp = created + sc.Finalizers = finalizers + return sc + } + using := func(name string) []atev1alpha1.WorkerPoolSandboxClassStatus { + return []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: name}, + }} + } + + tests := []struct { + name string + // newDefault is the config the pool resolves to from the cache. + newDefault *atev1alpha1.SandboxConfig + // pinned sets the pool's configRef to newDefault. + pinned bool + // status is the pool's status before the reconcile. + status []atev1alpha1.WorkerPoolSandboxClassStatus + // deletedBefore marks newDefault for deletion before the reconcile. + deletedBefore bool + // deleteDuringWrite deletes newDefault just before the first status + // write. release then runs the protection reconciler on it. + deleteDuringWrite bool + release bool + // wantFirst and want are the configs status must name after the + // first and the second reconcile. + wantFirst string + want string + }{ + { + name: "not deleted: switched", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute))), + wantFirst: "new-default", + want: "new-default", + }, + { + name: "deleted and released: never named, falls back on the next reconcile", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute))), + deleteDuringWrite: true, + release: true, + wantFirst: "", + want: "old-default", + }, + { + name: "deleted, released, held by another finalizer: never named, falls back on the next reconcile", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute)), testHoldFinalizer), + deleteDuringWrite: true, + release: true, + wantFirst: "", + want: "old-default", + }, + { + name: "deleted, not yet released: never named, falls back on the next reconcile", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute))), + deleteDuringWrite: true, + wantFirst: "", + want: "old-default", + }, + { + name: "deleted and released while switching away from a config: keeps it", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute))), + status: using("old-default"), + deleteDuringWrite: true, + release: true, + wantFirst: "old-default", + want: "old-default", + }, + { + name: "deleted, not yet released, while switching away from a config: keeps it", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute))), + status: using("old-default"), + deleteDuringWrite: true, + wantFirst: "old-default", + want: "old-default", + }, + { + name: "pinned, named in status but released: dropped", + newDefault: config("new-default", metav1.NewTime(t0.Add(time.Minute)), testHoldFinalizer), + pinned: true, + status: using("new-default"), + deletedBefore: true, + wantFirst: "", + want: "", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Status.SandboxClasses = tc.status + if tc.pinned { + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: tc.newDefault.Name} + } + newDefault := tc.newDefault.DeepCopy() + if tc.deletedBefore { + newDefault.DeletionTimestamp = &t0 + } + deleted := false + c := fake.NewClientBuilder().WithScheme(scheme). + WithObjects(wp, newDefault, config("old-default", t0)). + WithStatusSubresource(wp). + WithIndex(&atev1alpha1.WorkerPool{}, workerPoolSandboxConfigIndex, func(obj client.Object) []string { + return sandboxConfigsInUse(obj.(*atev1alpha1.WorkerPool)) + }). + WithInterceptorFuncs(interceptor.Funcs{ + SubResourceUpdate: func(ctx context.Context, c client.Client, sub string, obj client.Object, opts ...client.SubResourceUpdateOption) error { + if tc.deleteDuringWrite && !deleted { + deleted = true + if err := c.Delete(ctx, newDefault.DeepCopy()); err != nil { + return err + } + if tc.release { + p := &SandboxConfigProtectionReconciler{Client: c, APIReader: c} + if _, err := p.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(newDefault)}); err != nil { + return err + } + } + } + return c.SubResource(sub).Update(ctx, obj, opts...) + }, + }).Build() + r := &WorkerPoolReconciler{Client: c, APIReader: c, Recorder: events.NewFakeRecorder(10)} + for i, want := range []string{tc.wantFirst, tc.want} { + if _, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: client.ObjectKeyFromObject(wp)}); err != nil { + t.Fatalf("Reconcile %d: %v", i+1, err) + } + if tc.deleteDuringWrite && !deleted { + t.Fatalf("status was never written, so nothing was deleted") + } + got := &atev1alpha1.WorkerPool{} + if err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), got); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if name := statusConfig(got, atev1alpha1.SandboxClassGvisor); name != want { + t.Errorf("after reconcile %d, status names %q, want %q", i+1, name, want) + } + for _, class := range got.Status.SandboxClasses { + if class.PendingConfigRef != nil { + t.Errorf("after reconcile %d, class %q still claims %q", i+1, class.Name, class.PendingConfigRef.Name) + } + } + } + }) + } +} + +// TestSandboxConfigReleasedWhenUnused verifies that a SandboxConfig no pool +// uses deletes without waiting: one no pool ever used carries no protection +// finalizer at all, and one a pool switched away from is released. The pool +// resolves to another config first, which shows both controllers have been +// running while the released config existed. +func TestSandboxConfigReleasedWhenUnused(t *testing.T) { + t.Parallel() + tests := []struct { + name string + // configs are created before the pool. + configs []string + pool string + // ref is the pool's first configRef. + ref string + // switchTo, if set, is the configRef the pool moves to. + switchTo string + // release is the config that must delete at once. + release string + }{ + { + name: "never used", + configs: []string{"unused-sc", "unused-sc-other"}, + pool: "test-unused-sc", + ref: "unused-sc-other", + release: "unused-sc", + }, + { + name: "pool switched away", + configs: []string{"switch-from", "switch-to"}, + pool: "test-switch-ref", + ref: "switch-from", + switchTo: "switch-to", + release: "switch-from", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + for _, name := range tc.configs { + createSandboxConfig(t, makeSandboxConfig(name, atev1alpha1.SandboxClassGvisor, false)) + } + wp := makeWorkerPool(tc.pool, "default", 1, "ateom:v1") + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: tc.ref} + createWorkerPool(t, wp) + waitResolvedAndDeployed(t, wp, tc.ref) + + if tc.switchTo != "" { + updateWorkerPoolSpec(t, t.Context(), wp, "switch configRef", func(wp *atev1alpha1.WorkerPool) { + wp.Spec.SandboxClasses[0].ConfigRef.Name = tc.switchTo + }) + waitResolved(t, wp, tc.switchTo) + waitProtectionFinalizer(t, tc.switchTo, true) + } + if tc.release != tc.ref { + // Never used, so never protected. + got := &atev1alpha1.SandboxConfig{} + if err := k8sClient.Get(t.Context(), types.NamespacedName{Name: tc.release}, got); err != nil { + t.Fatalf("get SandboxConfig: %v", err) + } + if controllerutil.ContainsFinalizer(got, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) { + t.Fatalf("unused SandboxConfig %s carries the protection finalizer", tc.release) + } + } + deleteAndWait(t, &atev1alpha1.SandboxConfig{ObjectMeta: metav1.ObjectMeta{Name: tc.release}}) + }) + } +} + +// TestWorkerPoolConfigRef covers a pool with an explicit configRef. It +// resolves only when the named SandboxConfig exists, is of the pool's class, +// and is not being deleted. It then protects the config and gets its +// Deployment. Otherwise it reports why, records no config in status, gets no +// Deployment, and emits a Warning event. +func TestWorkerPoolConfigRef(t *testing.T) { + t.Parallel() + tests := []struct { + name string + // config is created before the pool. nil creates none. + config *atev1alpha1.SandboxConfig + // deleting marks config for deletion before the pool is created. + deleting bool + ref string + // wantReason is the Resolved=False reason to expect, or + // "" when the pool must resolve. + wantReason string + // late is created once the pool reports wantReason. The pool must + // then resolve to it. + late *atev1alpha1.SandboxConfig + }{ + { + name: "existing config of the same class", + config: makeSandboxConfig("ref-ok", atev1alpha1.SandboxClassGvisor, false), + ref: "ref-ok", + }, + { + name: "missing config, created later", + ref: "ref-late", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + late: makeSandboxConfig("ref-late", atev1alpha1.SandboxClassGvisor, false), + }, + { + name: "config of another class", + config: makeSandboxConfig("ref-vm", atev1alpha1.SandboxClassMicroVM, false), + ref: "ref-vm", + wantReason: atev1alpha1.WorkerPoolReasonSandboxClassMismatch, + }, + { + name: "config being deleted", + config: makeSandboxConfig("ref-dying", atev1alpha1.SandboxClassGvisor, false), + deleting: true, + ref: "ref-dying", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if tc.config != nil { + if tc.deleting { + controllerutil.AddFinalizer(tc.config, testHoldFinalizer) + } + createSandboxConfig(t, tc.config) + if tc.deleting { + t.Cleanup(func() { removeFinalizer(t, tc.config.Name, testHoldFinalizer) }) + if err := k8sClient.Delete(t.Context(), tc.config); err != nil { + t.Fatalf("delete SandboxConfig: %v", err) + } + } + } + wp := makeWorkerPool("test-configref-"+tc.ref, "default", 1, "ateom:v1") + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: tc.ref} + createWorkerPool(t, wp) + + if tc.wantReason == "" { + waitResolvedAndDeployed(t, wp, tc.ref) + return + } + current := waitUnresolved(t, wp, tc.wantReason) + if got := statusConfig(current, atev1alpha1.SandboxClassGvisor); got != "" { + t.Errorf("status.sandboxClasses names %q, want no config for a pool that never resolved", got) + } + assertNoDeployment(t, wp) + waitWarningEvent(t, wp, tc.wantReason) + if tc.late != nil { + createSandboxConfig(t, tc.late) + waitResolvedAndDeployed(t, wp, tc.ref) + } + }) + } +} + +// waitResolvedAndDeployed waits for wp to resolve to config, for config to +// carry the protection finalizer, and for wp's Deployment to exist. +func waitResolvedAndDeployed(t *testing.T, wp *atev1alpha1.WorkerPool, config string) { + t.Helper() + waitResolved(t, wp, config) + waitProtectionFinalizer(t, config, true) + eventually(t, func(ctx context.Context) (bool, error) { + _, err := getDeployment(ctx, wp) + return err == nil, nil + }) +} + +// waitWarningEvent waits for a Warning event with reason about wp. +func waitWarningEvent(t *testing.T, wp *atev1alpha1.WorkerPool, reason string) { + t.Helper() + eventually(t, func(ctx context.Context) (bool, error) { + var evs eventsv1.EventList + if err := k8sClient.List(ctx, &evs, client.InNamespace(wp.Namespace)); err != nil { + return false, err + } + for _, ev := range evs.Items { + if ev.Regarding.Name == wp.Name && ev.Type == "Warning" && ev.Reason == reason { + return true, nil + } + } + return false, nil + }) +} + +// TestWorkerPoolUnresolvedKeepsConfigInUse verifies that a pool whose sandbox +// class stops resolving keeps the config it uses: it reports why with a +// Warning event, keeps the config in status and protected, and the controller +// keeps updating its Deployment. +func TestWorkerPoolUnresolvedKeepsConfigInUse(t *testing.T) { + t.Parallel() + tests := []struct { + name string + pool string + // config is created with testHoldFinalizer and named by the pool's + // configRef. nil leaves the pool on the class default. + config *atev1alpha1.SandboxConfig + // missingRef, if set, breaks resolution by pointing configRef at a + // config that does not exist, in the same update that scales the + // pool. Otherwise config is deleted and the pool scaled afterwards. + missingRef string + replicas int32 + wantReason string + }{ + { + name: "spec points at a missing config", + pool: "test-unresolved-keeps", + missingRef: "never-created", + replicas: 5, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + }, + { + name: "config in use marked for deletion", + pool: "test-ref-deleting-in-use", + config: makeSandboxConfig("dying-in-use", atev1alpha1.SandboxClassGvisor, false), + replicas: 3, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + wp := makeWorkerPool(tc.pool, "default", 1, "ateom:v1") + inUse := testGvisorDefault + if tc.config != nil { + controllerutil.AddFinalizer(tc.config, testHoldFinalizer) + createSandboxConfig(t, tc.config) + t.Cleanup(func() { removeFinalizer(t, tc.config.Name, testHoldFinalizer) }) + inUse = tc.config.Name + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: inUse} + } + createWorkerPool(t, wp) + waitResolvedAndDeployed(t, wp, inUse) + + if tc.missingRef != "" { + updateWorkerPoolSpec(t, ctx, wp, "point WorkerPool at a missing SandboxConfig and scale it", func(wp *atev1alpha1.WorkerPool) { + wp.Spec.Replicas = tc.replicas + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: tc.missingRef} + }) + } else if err := k8sClient.Delete(ctx, tc.config); err != nil { + t.Fatalf("delete SandboxConfig: %v", err) + } + current := waitUnresolved(t, wp, tc.wantReason) + if got := statusConfig(current, atev1alpha1.SandboxClassGvisor); got != inUse { + t.Errorf("status.sandboxClasses names %q, want the config in use %q", got, inUse) + } + waitWarningEvent(t, wp, tc.wantReason) + waitProtectionFinalizer(t, inUse, true) + + if tc.missingRef == "" { + updateWorkerPoolSpec(t, ctx, wp, "scale WorkerPool", func(wp *atev1alpha1.WorkerPool) { + wp.Spec.Replicas = tc.replicas + }) + } + waitDeploymentReplicas(t, wp, tc.replicas) + }) + } +} + +// waitDeploymentReplicas waits for wp's Deployment to ask for replicas pods. +func waitDeploymentReplicas(t *testing.T, wp *atev1alpha1.WorkerPool, replicas int32) { + t.Helper() + eventually(t, func(ctx context.Context) (bool, error) { + dep, err := getDeployment(ctx, wp) + if err != nil { + return false, nil + } + return dep.Spec.Replicas != nil && *dep.Spec.Replicas == replicas, nil + }) +} + +// TestSandboxConfigInUseProtection verifies that a SandboxConfig shared by +// several pools survives a delete until all of them are gone, and that the +// pools themselves delete without waiting. +// +// A sentinel pool uses a sentinel config that is deleted at the same time as +// the shared one. The pool deletions reach the protection reconciler through +// the same WorkerPool watch, in order, and it reconciles one config at a +// time, so once the sentinel is gone the shared config has been reconciled +// for every user deleted before the sentinel pool and found still in use. +func TestSandboxConfigInUseProtection(t *testing.T) { + t.Parallel() + tests := []struct { + name string + config string + // users are the pools sharing config, deleted in order. + users []string + sentinel string + sentinelPool string + }{ + { + name: "two pools", + config: "shared-sc", + users: []string{"test-shared-a", "test-shared-b"}, + sentinel: "shared-sentinel", + sentinelPool: "test-shared-sentinel", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + sc := makeSandboxConfig(tc.config, atev1alpha1.SandboxClassGvisor, false) + createSandboxConfig(t, sc) + sentinel := makeSandboxConfig(tc.sentinel, atev1alpha1.SandboxClassGvisor, false) + createSandboxConfig(t, sentinel) + newPool := func(name, config string) *atev1alpha1.WorkerPool { + wp := makeWorkerPool(name, "default", 1, "ateom:v1") + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: config} + createWorkerPool(t, wp) + waitResolved(t, wp, config) + return wp + } + var users []*atev1alpha1.WorkerPool + for _, name := range tc.users { + users = append(users, newPool(name, sc.Name)) + } + sentinelPool := newPool(tc.sentinelPool, sentinel.Name) + waitProtectionFinalizer(t, sc.Name, true) + waitProtectionFinalizer(t, sentinel.Name, true) + + for _, c := range []*atev1alpha1.SandboxConfig{sc, sentinel} { + if err := k8sClient.Delete(ctx, c); err != nil { + t.Fatalf("delete SandboxConfig %s: %v", c.Name, err) + } + } + last := users[len(users)-1] + for _, wp := range users[:len(users)-1] { + deleteAndWait(t, wp) + } + deleteAndWait(t, sentinelPool) + waitGone(t, sentinel) + // The shared config has been reconciled for every other user's + // deletion. It must stay for the last one. + got := &atev1alpha1.SandboxConfig{} + if err := k8sClient.Get(ctx, types.NamespacedName{Name: sc.Name}, got); err != nil { + t.Fatalf("SandboxConfig still used by %s: get: %v", last.Name, err) + } + if got.DeletionTimestamp.IsZero() { + t.Fatalf("SandboxConfig has no deletionTimestamp after delete") + } + + deleteAndWait(t, last) + waitGone(t, sc) + }) + } +} + +// TestWorkerPoolDefaultNewestWins verifies that a pool without a configRef +// follows the newest class default, and keeps the config it uses, while +// reporting why, once no default is left. Each step changes the class +// defaults and checks the pool's resolution afterwards. It is not parallel +// because it owns the microvm class default. +func TestWorkerPoolDefaultNewestWins(t *testing.T) { + mvdefA := makeSandboxConfig("mvdef-a", atev1alpha1.SandboxClassMicroVM, true) + mvdefB := makeSandboxConfig("mvdef-b", atev1alpha1.SandboxClassMicroVM, true) + // The steps create the configs, but they outlive each step. + deleteOnCleanup(t, mvdefA) + deleteOnCleanup(t, mvdefB) + create := func(t *testing.T, sc *atev1alpha1.SandboxConfig) { + t.Helper() + if err := k8sClient.Create(t.Context(), sc); err != nil { + t.Fatalf("create SandboxConfig %s: %v", sc.Name, err) + } + } + wp := makeWorkerPool("test-default-newest", "default", 1, "ateom:v1") + wp.Spec.SandboxClasses[0].Name = atev1alpha1.SandboxClassMicroVM + createWorkerPool(t, wp) + + steps := []struct { + name string + // act runs before the checks. nil checks the pool as created. + act func(t *testing.T) + // wantConfig is the config the pool's status names, or "" for none. + wantConfig string + // wantReason is the Resolved=False reason to expect, or "" when the + // pool must resolve to wantConfig and get its Deployment. + wantReason string + }{ + { + name: "no class default", + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + }, + { + name: "a class default appears", + act: func(t *testing.T) { create(t, mvdefA) }, + wantConfig: "mvdef-a", + }, + { + name: "a newer class default wins", + act: func(t *testing.T) { + // creationTimestamp has one-second resolution, and the name + // tie-break would pick mvdef-a, so only a later creation time + // can make mvdef-b win. + time.Sleep(1100 * time.Millisecond) + create(t, mvdefB) + }, + wantConfig: "mvdef-b", + }, + { + name: "the default no longer used deletes at once", + act: func(t *testing.T) { deleteAndWait(t, mvdefA) }, + wantConfig: "mvdef-b", + }, + { + name: "the only default being deleted is kept, held by its finalizer", + act: func(t *testing.T) { + if err := k8sClient.Delete(t.Context(), mvdefB); err != nil { + t.Fatalf("delete SandboxConfig mvdef-b: %v", err) + } + }, + wantConfig: "mvdef-b", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + }, + { + name: "no default left: the config in use is kept", + act: func(t *testing.T) { + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + sc := &atev1alpha1.SandboxConfig{} + if err := k8sClient.Get(t.Context(), types.NamespacedName{Name: "mvdef-b"}, sc); err != nil { + return err + } + delete(sc.Annotations, atev1alpha1.SandboxConfigClassDefaultAnnotation) + return k8sClient.Update(t.Context(), sc) + }) + if err != nil { + t.Fatalf("remove class default annotation from mvdef-b: %v", err) + } + }, + wantConfig: "mvdef-b", + wantReason: atev1alpha1.WorkerPoolReasonDefaultConfigNotFound, + }, + } + for _, step := range steps { + // Each step builds on the previous one. + if !t.Run(step.name, func(t *testing.T) { + if step.act != nil { + step.act(t) + } + if step.wantReason == "" { + waitResolvedAndDeployed(t, wp, step.wantConfig) + return + } + current := waitUnresolved(t, wp, step.wantReason) + if got := statusConfig(current, atev1alpha1.SandboxClassMicroVM); got != step.wantConfig { + t.Errorf("status.sandboxClasses names %q, want %q", got, step.wantConfig) + } + waitWarningEvent(t, wp, step.wantReason) + if step.wantConfig == "" { + assertNoDeployment(t, wp) + } + }) { + return + } + } + + // Once the pool is gone, nothing holds mvdef-b. + deleteAndWait(t, wp) + waitGone(t, mvdefB) +} + +// TestWorkerPoolMayUseSandboxConfig covers the ways a SandboxConfig +// event can concern a WorkerPool. +func TestWorkerPoolMayUseSandboxConfig(t *testing.T) { + t.Parallel() + sc := makeSandboxConfig("sc", atev1alpha1.SandboxClassGvisor, false) + gvisor := atev1alpha1.WorkerPoolSandboxClass{Name: atev1alpha1.SandboxClassGvisor} + microvm := atev1alpha1.WorkerPoolSandboxClass{Name: atev1alpha1.SandboxClassMicroVM} + withRef := func(e atev1alpha1.WorkerPoolSandboxClass, name string) atev1alpha1.WorkerPoolSandboxClass { + e.ConfigRef = &atev1alpha1.SandboxConfigReference{Name: name} + return e + } + using := func(name string) atev1alpha1.WorkerPoolSandboxClassStatus { + return atev1alpha1.WorkerPoolSandboxClassStatus{Name: atev1alpha1.SandboxClassMicroVM, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: name}} + } + + tests := []struct { + name string + spec []atev1alpha1.WorkerPoolSandboxClass + status []atev1alpha1.WorkerPoolSandboxClassStatus + want bool + }{ + { + name: "runs the config's class", + spec: []atev1alpha1.WorkerPoolSandboxClass{gvisor}, + want: true, + }, + { + name: "runs the config's class with another ref", + spec: []atev1alpha1.WorkerPoolSandboxClass{withRef(gvisor, "other")}, + want: true, + }, + { + name: "spec names the config", + spec: []atev1alpha1.WorkerPoolSandboxClass{withRef(microvm, "sc")}, + want: true, + }, + { + name: "status names the config", + spec: []atev1alpha1.WorkerPoolSandboxClass{microvm}, + status: []atev1alpha1.WorkerPoolSandboxClassStatus{using("sc")}, + want: true, + }, + { + name: "status claims the config", + spec: []atev1alpha1.WorkerPoolSandboxClass{microvm}, + status: []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassMicroVM, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "other"}, + PendingConfigRef: &atev1alpha1.SandboxConfigReference{Name: "sc"}, + }}, + want: true, + }, + { + name: "another class, another config", + spec: []atev1alpha1.WorkerPoolSandboxClass{withRef(microvm, "other")}, + status: []atev1alpha1.WorkerPoolSandboxClassStatus{using("other")}, + want: false, + }, + { + name: "another class, no config in use", + spec: []atev1alpha1.WorkerPoolSandboxClass{withRef(microvm, "other")}, + status: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassMicroVM}}, + want: false, + }, + { + name: "no sandbox classes", + want: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPoolClasses("default", "pool", tc.spec, tc.status) + if got := workerPoolMayUseSandboxConfig(wp, sc); got != tc.want { + t.Errorf("workerPoolMayUseSandboxConfig = %v, want %v", got, tc.want) + } + }) + } +} + +// TestWorkerPoolsForSandboxConfig verifies the SandboxConfig watch maps an +// event to every pool that may use the config, across namespaces, and to +// nothing for an object of another kind or when listing pools fails. +func TestWorkerPoolsForSandboxConfig(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + gvisor := []atev1alpha1.WorkerPoolSandboxClass{{Name: atev1alpha1.SandboxClassGvisor}} + microvm := []atev1alpha1.WorkerPoolSandboxClass{{Name: atev1alpha1.SandboxClassMicroVM}} + microvmRef := []atev1alpha1.WorkerPoolSandboxClass{{Name: atev1alpha1.SandboxClassMicroVM, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "sc"}}} + usingSC := []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassMicroVM, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "sc"}}} + // newReconciler returns a reconciler whose List fails with listErr, if set. + newReconciler := func(listErr error) *WorkerPoolReconciler { + return &WorkerPoolReconciler{Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( + makeWorkerPoolClasses("a", "gv", gvisor, nil), + makeWorkerPoolClasses("b", "vm", microvm, nil), + makeWorkerPoolClasses("b", "vm-ref", microvmRef, nil), + makeWorkerPoolClasses("b", "vm-status", microvm, usingSC), + ).WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, c client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + if listErr != nil { + return listErr + } + return c.List(ctx, list, opts...) + }, + }).Build()} + } + req := func(ns, name string) reconcile.Request { + return reconcile.Request{NamespacedName: types.NamespacedName{Namespace: ns, Name: name}} + } + + tests := []struct { + name string + obj client.Object + // listErr fails the WorkerPool list. + listErr error + want []reconcile.Request + }{ + { + name: "gvisor config: its class and the pools naming it", + obj: makeSandboxConfig("sc", atev1alpha1.SandboxClassGvisor, false), + want: []reconcile.Request{req("a", "gv"), req("b", "vm-ref"), req("b", "vm-status")}, + }, + { + name: "microvm config: every microvm pool", + obj: makeSandboxConfig("other", atev1alpha1.SandboxClassMicroVM, false), + want: []reconcile.Request{req("b", "vm"), req("b", "vm-ref"), req("b", "vm-status")}, + }, + { + name: "not a SandboxConfig", + obj: makeWorkerPoolClasses("a", "gv", gvisor, nil), + }, + { + name: "list fails: nothing", + obj: makeSandboxConfig("sc", atev1alpha1.SandboxClassGvisor, false), + listErr: errors.New("list failed"), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := newReconciler(tc.listErr).workerPoolsForSandboxConfig(t.Context(), tc.obj) + slices.SortFunc(got, func(a, b reconcile.Request) int { return cmp.Compare(a.String(), b.String()) }) + if !slices.Equal(got, tc.want) { + t.Errorf("workerPoolsForSandboxConfig = %v, want %v", got, tc.want) + } + }) + } +} + +// TestRecordSandboxConfigEvents verifies the events a reconcile emits per +// class: a Normal event each time the config in use changes, naming the old +// and new config, and a Warning event for a failure, reported once and again +// only when its reason or message changes or the class resolved in between. +// A class whose config and failure are as before emits nothing. +func TestRecordSandboxConfigEvents(t *testing.T) { + t.Parallel() + failed := metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionFalse, + Reason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + Message: `SandboxConfig "x" not found`, + } + resolved := func(config string) metav1.Condition { + return metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionTrue, + Reason: atev1alpha1.WorkerPoolReasonResolved, + Message: fmt.Sprintf("resolved to SandboxConfig %q", config), + } + } + // class builds a gvisor status entry using config ("" for none) with + // cond. + class := func(config string, cond metav1.Condition) []atev1alpha1.WorkerPoolSandboxClassStatus { + status := atev1alpha1.WorkerPoolSandboxClassStatus{Name: atev1alpha1.SandboxClassGvisor, Conditions: []metav1.Condition{cond}} + if config != "" { + status.ConfigRef = &atev1alpha1.SandboxConfigReference{Name: config} + } + return []atev1alpha1.WorkerPoolSandboxClassStatus{status} + } + with := func(reason, message string) metav1.Condition { + c := failed + c.Reason = reason + c.Message = message + return c + } + warning := "Warning " + failed.Reason + " " + failed.Message + + tests := []struct { + name string + // prev is the pool's status before the reconcile. + prev []atev1alpha1.WorkerPoolSandboxClassStatus + classes []atev1alpha1.WorkerPoolSandboxClassStatus + want []string + }{ + { + name: "first resolve", + classes: class("x", resolved("x")), + want: []string{`Normal SandboxConfigSwitched sandbox class "gvisor" uses SandboxConfig "x"`}, + }, + { + name: "first resolve after a failure", + prev: class("", failed), + classes: class("x", resolved("x")), + want: []string{`Normal SandboxConfigSwitched sandbox class "gvisor" uses SandboxConfig "x"`}, + }, + { + name: "switch", + prev: class("x", resolved("x")), + classes: class("y", resolved("y")), + want: []string{`Normal SandboxConfigSwitched sandbox class "gvisor" switched from SandboxConfig "x" to "y"`}, + }, + { + name: "same config: nothing", + prev: class("x", resolved("x")), + classes: class("x", resolved("x")), + }, + { + name: "resolves again to the kept config: nothing", + prev: class("x", failed), + classes: class("x", resolved("x")), + }, + { + name: "first failure", + classes: class("", failed), + want: []string{warning}, + }, + { + name: "failure keeping the config: warning only", + prev: class("x", resolved("x")), + classes: class("x", failed), + want: []string{warning}, + }, + { + name: "same failure already reported", + prev: class("", failed), + classes: class("", failed), + }, + { + name: "another reason", + prev: class("", with(atev1alpha1.WorkerPoolReasonSandboxClassMismatch, failed.Message)), + classes: class("", failed), + want: []string{warning}, + }, + { + name: "another message", + prev: class("", with(failed.Reason, `SandboxConfig "y" not found`)), + classes: class("", failed), + want: []string{warning}, + }, + { + name: "resolved in between", + prev: class("", resolved("x")), + classes: class("", failed), + want: []string{warning}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + rec := events.NewFakeRecorder(2 * len(tc.classes)) + r := &WorkerPoolReconciler{Recorder: rec} + r.recordSandboxConfigEvents(wp, tc.prev, tc.classes) + var got []string + for len(rec.Events) > 0 { + got = append(got, <-rec.Events) + } + if !slices.Equal(got, tc.want) { + t.Errorf("events = %q, want %q", got, tc.want) + } + }) + } +} + +// TestReconcileReportsSwitchOnceWritten verifies that a switch's +// SandboxConfigSwitched event follows the status write that records it: a +// reconcile whose write is rejected emits nothing, and the switch is +// reported once when a later reconcile writes it. +func TestReconcileReportsSwitchOnceWritten(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add client-go scheme: %v", err) + } + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + tests := []struct { + name string + // rejected is how many status writes moving configRef to "y" are + // rejected with a Conflict before one goes through. + rejected int + }{ + {name: "written at once"}, + {name: "written on the second try", rejected: 1}, + {name: "written on the third try", rejected: 2}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPoolClasses("default", "pool", + []atev1alpha1.WorkerPoolSandboxClass{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "y"}}}, + []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "x"}}}) + objs := []client.Object{wp, + makeSandboxConfig("x", atev1alpha1.SandboxClassGvisor, false), + makeSandboxConfig("y", atev1alpha1.SandboxClassGvisor, false), + } + rejected := 0 + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).WithStatusSubresource(wp). + WithInterceptorFuncs(interceptor.Funcs{ + SubResourceUpdate: func(ctx context.Context, c client.Client, sub string, obj client.Object, opts ...client.SubResourceUpdateOption) error { + if pool, ok := obj.(*atev1alpha1.WorkerPool); ok && rejected < tc.rejected && statusConfig(pool, atev1alpha1.SandboxClassGvisor) == "y" { + rejected++ + return k8errors.NewConflict(atev1alpha1.GroupVersion.WithResource("workerpools").GroupResource(), pool.Name, errors.New("stale")) + } + return c.SubResource(sub).Update(ctx, obj, opts...) + }, + }).Build() + rec := events.NewFakeRecorder(10) + r := &WorkerPoolReconciler{Client: c, APIReader: c, Recorder: rec} + req := ctrl.Request{NamespacedName: client.ObjectKeyFromObject(wp)} + + for i := 0; i < tc.rejected; i++ { + if _, err := r.Reconcile(t.Context(), req); !k8errors.IsConflict(err) { + t.Fatalf("Reconcile %d error = %v, want Conflict", i+1, err) + } + if len(rec.Events) != 0 { + t.Fatalf("Reconcile %d emitted %q before status was written", i+1, <-rec.Events) + } + } + if _, err := r.Reconcile(t.Context(), req); err != nil { + t.Fatalf("Reconcile: %v", err) + } + var got []string + for len(rec.Events) > 0 { + got = append(got, <-rec.Events) + } + want := []string{`Normal SandboxConfigSwitched sandbox class "gvisor" switched from SandboxConfig "x" to "y"`} + if !slices.Equal(got, want) { + t.Errorf("events = %q, want %q", got, want) + } + current := &atev1alpha1.WorkerPool{} + if err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), current); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if name := statusConfig(current, atev1alpha1.SandboxClassGvisor); name != "y" { + t.Errorf("status names %q, want %q", name, "y") + } + }) + } +} + +// TestResolveSandboxClasses verifies the status entries built for a pool: +// the config each class uses, a condition stamped with the pool's generation +// whose lastTransitionTime moves only when its status does, and whether +// every class resolved. +func TestResolveSandboxClasses(t *testing.T) { + t.Parallel() + const generation = 3 + earlier := metav1.NewTime(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)) + configs := []atev1alpha1.SandboxConfig{*makeSandboxConfig("def", atev1alpha1.SandboxClassGvisor, true)} + ref := func(name string) *atev1alpha1.SandboxConfigReference { + if name == "" { + return nil + } + return &atev1alpha1.SandboxConfigReference{Name: name} + } + cond := func(status metav1.ConditionStatus, reason, message string, gen int64, at metav1.Time) metav1.Condition { + return metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: status, + Reason: reason, + Message: message, + ObservedGeneration: gen, + LastTransitionTime: at, + } + } + resolvedDef := func(gen int64, at metav1.Time) metav1.Condition { + return cond(metav1.ConditionTrue, atev1alpha1.WorkerPoolReasonResolved, `resolved to SandboxConfig "def"`, gen, at) + } + notFound := func(message string, gen int64, at metav1.Time) metav1.Condition { + return cond(metav1.ConditionFalse, atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, message, gen, at) + } + // now stands for a lastTransitionTime set during the call. + var now metav1.Time + + tests := []struct { + name string + // specRef is the pool's configRef. "" follows the class default. + specRef string + prev []atev1alpha1.WorkerPoolSandboxClassStatus + want []atev1alpha1.WorkerPoolSandboxClassStatus + // wantResolved is the second return value. + wantResolved bool + }{ + { + name: "first resolve", + want: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: ref("def"), Conditions: []metav1.Condition{resolvedDef(generation, now)}}}, + wantResolved: true, + }, + { + name: "still resolved keeps lastTransitionTime", + prev: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: ref("def"), Conditions: []metav1.Condition{resolvedDef(generation-1, earlier)}}}, + want: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: ref("def"), Conditions: []metav1.Condition{resolvedDef(generation, earlier)}}}, + wantResolved: true, + }, + { + name: "never resolved records no config", + specRef: "missing", + want: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, Conditions: []metav1.Condition{notFound(`SandboxConfig "missing" not found`, generation, now)}}}, + }, + { + name: "stops resolving keeps the config and moves lastTransitionTime", + specRef: "missing", + prev: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: ref("def"), Conditions: []metav1.Condition{resolvedDef(generation-1, earlier)}}}, + want: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: ref("def"), Conditions: []metav1.Condition{notFound(`SandboxConfig "missing" not found; keeping "def"`, generation, now)}}}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Generation = generation + wp.Spec.SandboxClasses[0].ConfigRef = ref(tc.specRef) + wp.Status.SandboxClasses = tc.prev + before := metav1.Now().Rfc3339Copy() + got, resolved := resolveSandboxClasses(wp, configs) + if resolved != tc.wantResolved { + t.Errorf("resolved = %v, want %v", resolved, tc.wantResolved) + } + // Replace a lastTransitionTime set during the call with now. + for i := range got { + for j := range got[i].Conditions { + if c := &got[i].Conditions[j]; !c.LastTransitionTime.Before(&before) { + c.LastTransitionTime = now + } + } + } + if !equality.Semantic.DeepEqual(got, tc.want) { + t.Errorf("resolveSandboxClasses = %+v, want %+v", got, tc.want) + } + }) + } +} + +// TestClaimSandboxConfigs verifies the status written to claim the configs a +// pool switches to: each class's status stays as it was, a switching class +// names its new config in pendingConfigRef, and nothing is written when no +// class switches. +func TestClaimSandboxConfigs(t *testing.T) { + t.Parallel() + ref := func(name string) *atev1alpha1.SandboxConfigReference { + if name == "" { + return nil + } + return &atev1alpha1.SandboxConfigReference{Name: name} + } + resolved := metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionTrue, + Reason: atev1alpha1.WorkerPoolReasonResolved, + } + // status builds the pool's single gvisor status entry. + status := func(configRef, pending string, conds ...metav1.Condition) []atev1alpha1.WorkerPoolSandboxClassStatus { + return []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: ref(configRef), + PendingConfigRef: ref(pending), + Conditions: conds, + }} + } + + tests := []struct { + name string + prev []atev1alpha1.WorkerPoolSandboxClassStatus + // classes is the status resolveSandboxClasses built. + classes []atev1alpha1.WorkerPoolSandboxClassStatus + want []atev1alpha1.WorkerPoolSandboxClassStatus + }{ + { + name: "same config: no claim", + prev: status("x", "", resolved), + classes: status("x", ""), + want: nil, + }, + { + name: "unresolved and nothing kept: no claim", + classes: status("", ""), + want: nil, + }, + { + name: "new pool: claims, no config in use", + classes: status("x", ""), + want: status("", "x"), + }, + { + name: "switch: claims and keeps the config in use", + prev: status("y", "", resolved), + classes: status("x", ""), + want: status("y", "x", resolved), + }, + { + name: "earlier claim for the same config: claimed again", + prev: status("y", "x", resolved), + classes: status("x", ""), + want: status("y", "x", resolved), + }, + { + name: "earlier claim for another config: replaced", + prev: status("y", "x", resolved), + classes: status("z", ""), + want: status("y", "z", resolved), + }, + { + name: "earlier claim, back to the config in use: no claim", + prev: status("y", "x", resolved), + classes: status("y", ""), + want: nil, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Status.SandboxClasses = tc.prev + if got := claimSandboxConfigs(wp, tc.classes); !equality.Semantic.DeepEqual(got, tc.want) { + t.Errorf("claimSandboxConfigs = %+v, want %+v", got, tc.want) + } + }) + } +} + +// TestConfirmSandboxConfigSwitch verifies how the config a class switches to, +// as read from the API server, settles the class: a live config of the class +// stands, and anything else keeps the config in use with a False Resolved +// condition. +func TestConfirmSandboxConfigSwitch(t *testing.T) { + t.Parallel() + const generation = 3 + now := metav1.Now() + resolvedX := metav1.Condition{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionTrue, + Reason: atev1alpha1.WorkerPoolReasonResolved, + Message: `resolved to SandboxConfig "x"`, + ObservedGeneration: generation, + } + // class is the entry resolveSandboxClasses built: switching to x. + class := atev1alpha1.WorkerPoolSandboxClassStatus{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "x"}, + Conditions: []metav1.Condition{resolvedX}, + } + onY := []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "y"}, + PendingConfigRef: &atev1alpha1.SandboxConfigReference{Name: "x"}, + }} + x := makeSandboxConfig("x", atev1alpha1.SandboxClassGvisor, false) + deletingX := x.DeepCopy() + deletingX.DeletionTimestamp = &now + configs := []atev1alpha1.SandboxConfig{*makeSandboxConfig("y", atev1alpha1.SandboxClassGvisor, false), *x} + + tests := []struct { + name string + prev []atev1alpha1.WorkerPoolSandboxClassStatus + // live is x as read from the API server. nil means not found. + live *atev1alpha1.SandboxConfig + // wantConfig is the config the class must end up using. + wantConfig string + wantReason string + wantMessage string + }{ + { + name: "live: switches", + prev: onY, + live: x, + wantConfig: "x", + wantReason: atev1alpha1.WorkerPoolReasonResolved, + wantMessage: `resolved to SandboxConfig "x"`, + }, + { + name: "not found: keeps the config in use", + prev: onY, + wantConfig: "y", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + wantMessage: `SandboxConfig "x" not found; keeping "y"`, + }, + { + name: "being deleted: keeps the config in use", + prev: onY, + live: deletingX, + wantConfig: "y", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `SandboxConfig "x" is being deleted; keeping "y"`, + }, + { + name: "now of another class: keeps the config in use", + prev: onY, + live: makeSandboxConfig("x", atev1alpha1.SandboxClassMicroVM, false), + wantConfig: "y", + wantReason: atev1alpha1.WorkerPoolReasonSandboxClassMismatch, + wantMessage: `SandboxConfig "x" is for sandbox class "microvm", not "gvisor"; keeping "y"`, + }, + { + name: "being deleted, new pool: no config", + live: deletingX, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantMessage: `SandboxConfig "x" is being deleted`, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Generation = generation + wp.Status.SandboxClasses = tc.prev + got := confirmSandboxConfigSwitch(wp, *class.DeepCopy(), tc.live, configs) + if name := ptr.Deref(got.ConfigRef, atev1alpha1.SandboxConfigReference{}).Name; name != tc.wantConfig { + t.Errorf("configRef = %q, want %q", name, tc.wantConfig) + } + if got.PendingConfigRef != nil { + t.Errorf("pendingConfigRef = %q, want none", got.PendingConfigRef.Name) + } + cond := meta.FindStatusCondition(got.Conditions, atev1alpha1.WorkerPoolSandboxClassConditionResolved) + if cond == nil { + t.Fatalf("no Resolved condition in %+v", got.Conditions) + } + if cond.Reason != tc.wantReason || cond.Message != tc.wantMessage || cond.ObservedGeneration != generation { + t.Errorf("condition = %s %q (generation %d), want %s %q (generation %d)", + cond.Reason, cond.Message, cond.ObservedGeneration, tc.wantReason, tc.wantMessage, generation) + } + }) + } +} + +// TestReconcileConfirmsSwitch verifies a switch against a cache that lags +// the API server: the cache shows the new config live, while the API server +// has it gone or being deleted. The pool's status must claim the new config +// before it is read from the API server, and the switch only stands if that +// read finds it live. Otherwise the pool keeps the config it uses. +func TestReconcileConfirmsSwitch(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add client-go scheme: %v", err) + } + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + now := metav1.Now() + protected := func(sc *atev1alpha1.SandboxConfig) *atev1alpha1.SandboxConfig { + controllerutil.AddFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + return sc + } + deleting := func(sc *atev1alpha1.SandboxConfig) *atev1alpha1.SandboxConfig { + sc.DeletionTimestamp = &now + return sc + } + x := func() *atev1alpha1.SandboxConfig { + return protected(makeSandboxConfig("x", atev1alpha1.SandboxClassGvisor, false)) + } + + tests := []struct { + name string + // inUse is the config the pool's status uses before the reconcile. + // "" for a new pool. + inUse string + // liveX is x on the API server. nil means it is gone. + liveX *atev1alpha1.SandboxConfig + // wantRead is whether x must be read from the API server. + wantRead bool + wantConfig string + wantReason string + wantDeployment bool + }{ + { + name: "live: switches", + inUse: "y", + liveX: x(), + wantRead: true, + wantConfig: "x", + wantReason: atev1alpha1.WorkerPoolReasonResolved, + wantDeployment: true, + }, + { + name: "being deleted: keeps the config in use", + inUse: "y", + liveX: deleting(x()), + wantRead: true, + wantConfig: "y", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + wantDeployment: true, + }, + { + name: "gone: keeps the config in use", + inUse: "y", + wantRead: true, + wantConfig: "y", + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigNotFound, + wantDeployment: true, + }, + { + name: "live, new pool: switches and gets a Deployment", + liveX: x(), + wantRead: true, + wantConfig: "x", + wantReason: atev1alpha1.WorkerPoolReasonResolved, + wantDeployment: true, + }, + { + name: "being deleted, new pool: no config, no Deployment", + liveX: deleting(x()), + wantRead: true, + wantReason: atev1alpha1.WorkerPoolReasonSandboxConfigDeleting, + }, + { + name: "already in use: not read again", + inUse: "x", + liveX: deleting(x()), + wantConfig: "x", + wantReason: atev1alpha1.WorkerPoolReasonResolved, + wantDeployment: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: "x"} + objs := []client.Object{wp, x(), protected(makeSandboxConfig("y", atev1alpha1.SandboxClassGvisor, false))} + if tc.inUse != "" { + wp.Status.SandboxClasses = []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: tc.inUse}, + }} + objs = append(objs, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: wp.Name, Namespace: wp.Namespace}}) + } + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).WithStatusSubresource(wp).Build() + + var liveObjs []client.Object + if tc.liveX != nil { + liveObjs = append(liveObjs, tc.liveX) + } + // claimedAtRead records whether the pool's status claimed x + // when x was read from the API server. + var read, claimedAtRead bool + live := fake.NewClientBuilder().WithScheme(scheme).WithObjects(liveObjs...). + WithInterceptorFuncs(interceptor.Funcs{ + Get: func(ctx context.Context, lc client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + read = true + pool := &atev1alpha1.WorkerPool{} + if err := c.Get(ctx, client.ObjectKeyFromObject(wp), pool); err != nil { + return err + } + claimedAtRead = slices.Contains(sandboxConfigsInUse(pool), key.Name) + return lc.Get(ctx, key, obj, opts...) + }, + }).Build() + + r := &WorkerPoolReconciler{Client: c, APIReader: live, Recorder: events.NewFakeRecorder(1)} + if _, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: client.ObjectKeyFromObject(wp)}); err != nil { + t.Fatalf("Reconcile: %v", err) + } + + if read != tc.wantRead { + t.Errorf("read x from the API server = %v, want %v", read, tc.wantRead) + } + if read && !claimedAtRead { + t.Errorf("x was read from the API server before the pool's status claimed it") + } + got := &atev1alpha1.WorkerPool{} + if err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), got); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if name := statusConfig(got, atev1alpha1.SandboxClassGvisor); name != tc.wantConfig { + t.Errorf("status names %q, want %q", name, tc.wantConfig) + } + class := sandboxClassStatus(got, atev1alpha1.SandboxClassGvisor) + if class == nil { + t.Fatalf("no gvisor status entry in %+v", got.Status) + } + if class.PendingConfigRef != nil { + t.Errorf("pendingConfigRef = %q, want none", class.PendingConfigRef.Name) + } + if cond := meta.FindStatusCondition(class.Conditions, atev1alpha1.WorkerPoolSandboxClassConditionResolved); cond == nil || cond.Reason != tc.wantReason { + t.Errorf("Resolved condition = %+v, want reason %s", cond, tc.wantReason) + } + err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), &appsv1.Deployment{}) + if hasDeployment := err == nil; hasDeployment != tc.wantDeployment { + t.Errorf("has Deployment = %v (get error %v), want %v", hasDeployment, err, tc.wantDeployment) + } + }) + } +} + +// TestSwitchInterleavings runs a pool's switch from config y to config x with +// a deletion and a protection reconcile interleaved at each point of the +// switch, as they can be on a real cluster: before the claim is written, +// after it, after x is read from the API server, and after the final status +// write. Throughout, the config status names in configRef must exist, and +// the deleted config must outlive its last claim: the protection reconciler +// may release it only while no pool's status names it. +func TestSwitchInterleavings(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add client-go scheme: %v", err) + } + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + // The points in a switch where the deletion and the protection reconcile + // run. + const ( + beforeClaim = "before claim" + afterClaim = "after claim" + afterRead = "after read" + afterFinal = "after final write" + ) + + tests := []struct { + name string + // deleted is the config deleted at the point at. + deleted string + at string + // unprotected starts x without the protection finalizer, as a config + // no pool has used yet. + unprotected bool + // wantHeldAtPoint is whether the deleted config still exists after + // the protection reconcile at the point. + wantHeldAtPoint bool + // wantFirst is the config status names after the reconcile, and + // wantSettled after the protection and pool reconciles that the + // deletion triggers have run. + wantFirst string + wantSettled string + // wantGone is whether the deleted config is gone once settled. + wantGone bool + }{ + { + name: "x deleted and released before the claim: switch rejected", + deleted: "x", + at: beforeClaim, + wantFirst: "y", + wantSettled: "y", + wantGone: true, + }, + { + name: "unused x deleted and released before the claim: switch rejected", + deleted: "x", + at: beforeClaim, + unprotected: true, + wantFirst: "y", + wantSettled: "y", + wantGone: true, + }, + { + name: "x deleted after the claim: held by the claim, switch rejected, then released", + deleted: "x", + at: afterClaim, + wantHeldAtPoint: true, + wantFirst: "y", + wantSettled: "y", + wantGone: true, + }, + { + name: "x deleted after the read: held by the claim, switched, kept while in use", + deleted: "x", + at: afterRead, + wantHeldAtPoint: true, + wantFirst: "x", + wantSettled: "x", + }, + { + name: "x deleted after the final write: held, kept while in use", + deleted: "x", + at: afterFinal, + wantHeldAtPoint: true, + wantFirst: "x", + wantSettled: "x", + }, + { + name: "y deleted before the claim: held while named, released after the switch", + deleted: "y", + at: beforeClaim, + wantHeldAtPoint: true, + wantFirst: "x", + wantSettled: "x", + wantGone: true, + }, + { + name: "y deleted after the claim: held while named, released after the switch", + deleted: "y", + at: afterClaim, + wantHeldAtPoint: true, + wantFirst: "x", + wantSettled: "x", + wantGone: true, + }, + { + name: "y deleted after the read: held while named, released after the switch", + deleted: "y", + at: afterRead, + wantHeldAtPoint: true, + wantFirst: "x", + wantSettled: "x", + wantGone: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Spec.SandboxClasses[0].ConfigRef = &atev1alpha1.SandboxConfigReference{Name: "x"} + wp.Status.SandboxClasses = []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "y"}, + }} + x := makeSandboxConfig("x", atev1alpha1.SandboxClassGvisor, false) + if !tc.unprotected { + controllerutil.AddFinalizer(x, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + } + y := makeSandboxConfig("y", atev1alpha1.SandboxClassGvisor, false) + controllerutil.AddFinalizer(y, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) + // base is the cluster itself: the hooks and the protection + // reconciler use it, so only the pool's reconcile is intercepted. + base := fake.NewClientBuilder().WithScheme(scheme).WithObjects(wp, x, y).WithStatusSubresource(wp). + WithIndex(&atev1alpha1.WorkerPool{}, workerPoolSandboxConfigIndex, func(obj client.Object) []string { + return sandboxConfigsInUse(obj.(*atev1alpha1.WorkerPool)) + }).Build() + protection := &SandboxConfigProtectionReconciler{Client: base, APIReader: base} + protect := func(ctx context.Context) error { + for _, name := range []string{"x", "y"} { + if _, err := protection.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: name}}); err != nil { + return fmt.Errorf("protection reconcile of %q: %w", name, err) + } + } + return nil + } + exists := func(ctx context.Context, name string) (bool, error) { + err := base.Get(ctx, types.NamespacedName{Name: name}, &atev1alpha1.SandboxConfig{}) + if k8errors.IsNotFound(err) { + return false, nil + } + return err == nil, err + } + // checkConfigRef fails the test if status names a config in + // configRef that does not exist. + checkConfigRef := func(ctx context.Context, when string) error { + pool := &atev1alpha1.WorkerPool{} + if err := base.Get(ctx, client.ObjectKeyFromObject(wp), pool); err != nil { + return err + } + for _, class := range pool.Status.SandboxClasses { + if class.ConfigRef == nil { + continue + } + ok, err := exists(ctx, class.ConfigRef.Name) + if err != nil { + return err + } + if !ok { + t.Errorf("%s: status names %q, which no longer exists", when, class.ConfigRef.Name) + } + } + return nil + } + + armed, hit := true, false + // interleave deletes the config and runs the protection + // reconciler, if at is the point the test interleaves at. + interleave := func(ctx context.Context, at string) error { + if !armed || at != tc.at { + return nil + } + hit = true + sc := &atev1alpha1.SandboxConfig{} + if err := base.Get(ctx, types.NamespacedName{Name: tc.deleted}, sc); err != nil { + return err + } + if err := base.Delete(ctx, sc); err != nil { + return err + } + if err := protect(ctx); err != nil { + return err + } + held, err := exists(ctx, tc.deleted) + if err != nil { + return err + } + if held != tc.wantHeldAtPoint { + t.Errorf("%s: %q exists after the protection reconcile = %v, want %v", at, tc.deleted, held, tc.wantHeldAtPoint) + } + return checkConfigRef(ctx, at) + } + statusWrites := 0 + c := interceptor.NewClient(base, interceptor.Funcs{ + SubResourceUpdate: func(ctx context.Context, c client.Client, sub string, obj client.Object, opts ...client.SubResourceUpdateOption) error { + statusWrites++ + claim := statusWrites == 1 + if claim { + if err := interleave(ctx, beforeClaim); err != nil { + return err + } + } + if err := c.SubResource(sub).Update(ctx, obj, opts...); err != nil { + return err + } + if claim { + return interleave(ctx, afterClaim) + } + return interleave(ctx, afterFinal) + }, + Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if err := c.Get(ctx, key, obj, opts...); err != nil { + return err + } + if _, ok := obj.(*atev1alpha1.SandboxConfig); ok { + return interleave(ctx, afterRead) + } + return nil + }, + }) + + r := &WorkerPoolReconciler{Client: c, APIReader: c, Recorder: events.NewFakeRecorder(10)} + req := ctrl.Request{NamespacedName: client.ObjectKeyFromObject(wp)} + if _, err := r.Reconcile(t.Context(), req); err != nil { + t.Fatalf("Reconcile: %v", err) + } + armed = false + if !hit { + t.Fatalf("the reconcile never reached %q", tc.at) + } + if statusWrites != 2 { + t.Errorf("status writes = %d, want 2: the claim and the final write", statusWrites) + } + got := &atev1alpha1.WorkerPool{} + if err := base.Get(t.Context(), req.NamespacedName, got); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if name := statusConfig(got, atev1alpha1.SandboxClassGvisor); name != tc.wantFirst { + t.Errorf("after the reconcile, status names %q, want %q", name, tc.wantFirst) + } + if err := checkConfigRef(t.Context(), "after the reconcile"); err != nil { + t.Fatal(err) + } + + // Settle: the deletion and the status writes trigger the + // protection reconciler and another pool reconcile. + if err := protect(t.Context()); err != nil { + t.Fatal(err) + } + if _, err := r.Reconcile(t.Context(), req); err != nil { + t.Fatalf("second Reconcile: %v", err) + } + if err := protect(t.Context()); err != nil { + t.Fatal(err) + } + if err := base.Get(t.Context(), req.NamespacedName, got); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if name := statusConfig(got, atev1alpha1.SandboxClassGvisor); name != tc.wantSettled { + t.Errorf("once settled, status names %q, want %q", name, tc.wantSettled) + } + if err := checkConfigRef(t.Context(), "once settled"); err != nil { + t.Fatal(err) + } + ok, err := exists(t.Context(), tc.deleted) + if err != nil { + t.Fatal(err) + } + if gone := !ok; gone != tc.wantGone { + t.Errorf("once settled, %q gone = %v, want %v", tc.deleted, gone, tc.wantGone) + } + }) + } +} + +// TestEveryClassHasConfig covers when a pool without a worker Deployment +// gets one. +func TestEveryClassHasConfig(t *testing.T) { + t.Parallel() + tests := []struct { + name string + classes []atev1alpha1.WorkerPoolSandboxClassStatus + want bool + }{ + { + name: "every class has a config", + classes: []atev1alpha1.WorkerPoolSandboxClassStatus{{Name: atev1alpha1.SandboxClassGvisor, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "def"}}}, + want: true, + }, + { + name: "one class has no config", + classes: []atev1alpha1.WorkerPoolSandboxClassStatus{ + {Name: atev1alpha1.SandboxClassGvisor, ConfigRef: &atev1alpha1.SandboxConfigReference{Name: "def"}}, + {Name: atev1alpha1.SandboxClassMicroVM}, + }, + want: false, + }, + { + name: "no classes", + want: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := everyClassHasConfig(tc.classes); got != tc.want { + t.Errorf("everyClassHasConfig = %v, want %v", got, tc.want) + } + }) + } +} + +// TestReconcileAppliesExistingDeploymentFirst verifies that a pool with a +// worker Deployment gets it applied even when a SandboxConfig step of the +// same reconcile fails, and that a pool without one gets none when those +// steps fail. +func TestReconcileAppliesExistingDeploymentFirst(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add client-go scheme: %v", err) + } + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + errInjected := errors.New("injected") + failList := interceptor.Funcs{ + List: func(ctx context.Context, c client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + if _, ok := list.(*atev1alpha1.SandboxConfigList); ok { + return errInjected + } + return c.List(ctx, list, opts...) + }, + } + failProtect := interceptor.Funcs{ + Patch: func(ctx context.Context, c client.WithWatch, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + if _, ok := obj.(*atev1alpha1.SandboxConfig); ok { + return errInjected + } + return c.Patch(ctx, obj, patch, opts...) + }, + } + failStatus := interceptor.Funcs{ + SubResourceUpdate: func(context.Context, client.Client, string, client.Object, ...client.SubResourceUpdateOption) error { + return errInjected + }, + } + tests := []struct { + name string + funcs interceptor.Funcs + // hasDeployment is whether the pool has a Deployment, with 1 replica, + // before the reconcile. + hasDeployment bool + // wantReplicas is the Deployment's replicas afterwards. 0 means no + // Deployment. + wantReplicas int32 + }{ + { + name: "list fails, Deployment exists", + funcs: failList, + hasDeployment: true, + wantReplicas: 3, + }, + { + name: "protection fails, Deployment exists", + funcs: failProtect, + hasDeployment: true, + wantReplicas: 3, + }, + { + name: "status write fails, Deployment exists", + funcs: failStatus, + hasDeployment: true, + wantReplicas: 3, + }, + { + name: "list fails, no Deployment", + funcs: failList, + }, + { + name: "protection fails, no Deployment", + funcs: failProtect, + }, + { + name: "status write fails, no Deployment", + funcs: failStatus, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 3, "ateom:v1") + objs := []client.Object{wp, makeSandboxConfig("def", atev1alpha1.SandboxClassGvisor, true)} + if tc.hasDeployment { + objs = append(objs, &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: wp.Name, Namespace: wp.Namespace}, + Spec: appsv1.DeploymentSpec{Replicas: ptr.To[int32](1)}, + }) + } + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).WithStatusSubresource(wp). + WithInterceptorFuncs(tc.funcs).Build() + r := &WorkerPoolReconciler{Client: c, APIReader: c, Recorder: events.NewFakeRecorder(1)} + if _, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: client.ObjectKeyFromObject(wp)}); !errors.Is(err, errInjected) { + t.Fatalf("Reconcile error = %v, want %v", err, errInjected) + } + + dep := &appsv1.Deployment{} + err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), dep) + if tc.wantReplicas == 0 { + if !k8errors.IsNotFound(err) { + t.Errorf("get Deployment error = %v, want NotFound", err) + } + return + } + if err != nil { + t.Fatalf("get Deployment: %v", err) + } + if got := ptr.Deref(dep.Spec.Replicas, 0); got != tc.wantReplicas { + t.Errorf("Deployment replicas = %d, want %d", got, tc.wantReplicas) + } + }) + } +} + +func TestSyncStatus(t *testing.T) { + t.Parallel() + scheme := runtime.NewScheme() + if err := atev1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + classes := func(config string) []atev1alpha1.WorkerPoolSandboxClassStatus { + return []atev1alpha1.WorkerPoolSandboxClassStatus{{ + Name: atev1alpha1.SandboxClassGvisor, + ConfigRef: &atev1alpha1.SandboxConfigReference{Name: config}, + Conditions: []metav1.Condition{{ + Type: atev1alpha1.WorkerPoolSandboxClassConditionResolved, + Status: metav1.ConditionTrue, + Reason: atev1alpha1.WorkerPoolReasonResolved, + Message: "resolved to SandboxConfig " + config, + ObservedGeneration: 2, + LastTransitionTime: metav1.NewTime(time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)), + }}, + }} + } + before := atev1alpha1.WorkerPoolStatus{ + Replicas: 3, + ReadyReplicas: 2, + Selector: "app=old", + SandboxClasses: classes("old"), + } + dep := &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "new"}}}, + Status: appsv1.DeploymentStatus{Replicas: 5, ReadyReplicas: 4}, + } + + tests := []struct { + name string + dep *appsv1.Deployment + classes []atev1alpha1.WorkerPoolSandboxClassStatus + want atev1alpha1.WorkerPoolStatus + wantWrite bool + }{ + { + name: "nil Deployment keeps the replica fields", + classes: classes("new"), + want: atev1alpha1.WorkerPoolStatus{ + Replicas: 3, ReadyReplicas: 2, Selector: "app=old", + SandboxClasses: classes("new"), + }, + wantWrite: true, + }, + { + name: "Deployment replaces the replica fields", + dep: dep, + classes: classes("new"), + want: atev1alpha1.WorkerPoolStatus{ + Replicas: 5, ReadyReplicas: 4, Selector: "app=new", + SandboxClasses: classes("new"), + }, + wantWrite: true, + }, + { + name: "nothing changed writes nothing", + classes: classes("old"), + want: before, + wantWrite: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + wp := makeWorkerPool("pool", "default", 1, "ateom:v1") + wp.Status = *before.DeepCopy() + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(wp).WithStatusSubresource(wp).Build() + stored := &atev1alpha1.WorkerPool{} + if err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), stored); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + // Update refreshes stored's resourceVersion in place, so keep + // the one from before the call. + before := stored.ResourceVersion + r := &WorkerPoolReconciler{Client: c} + if err := r.syncStatus(t.Context(), stored, tc.dep, tc.classes); err != nil { + t.Fatalf("syncStatus: %v", err) + } + got := &atev1alpha1.WorkerPool{} + if err := c.Get(t.Context(), client.ObjectKeyFromObject(wp), got); err != nil { + t.Fatalf("get WorkerPool: %v", err) + } + if !equality.Semantic.DeepEqual(got.Status, tc.want) { + t.Errorf("status = %+v, want %+v", got.Status, tc.want) + } + if wrote := got.ResourceVersion != before; wrote != tc.wantWrite { + t.Errorf("wrote status = %v, want %v", wrote, tc.wantWrite) + } + }) + } +} + +// TestAddProtectionFinalizer checks that addProtectionFinalizer patches only +// when the finalizer is missing, leaves other finalizers alone, never adds to +// a deleting config, and fails with a conflict when the config changed since +// it was read. +func TestAddProtectionFinalizer(t *testing.T) { + t.Parallel() + runProtectionFinalizerCases(t, []protectionFinalizerCase{ + { + name: "add to unprotected config", + op: addProtectionFinalizer, + sc: protectedSandboxConfig(false, false), + wantPatches: 1, + want: true, + }, + { + name: "add to protected config: no patch", + op: addProtectionFinalizer, + sc: protectedSandboxConfig(true, false), + want: true, + }, + { + name: "add to deleting config: no patch", + op: addProtectionFinalizer, + sc: protectedSandboxConfig(false, true), + want: false, + }, + { + name: "add to stale config: conflict", + op: addProtectionFinalizer, + sc: protectedSandboxConfig(false, false), + stale: true, + wantPatches: 1, + want: false, + wantErr: k8errors.IsConflict, + }, + }) +} + +// --- helpers --- + +func makeSandboxConfig(name string, class atev1alpha1.SandboxClass, isDefault bool) *atev1alpha1.SandboxConfig { + v := atev1alpha1.SandboxVersionConfig{Name: "v1"} + if class == atev1alpha1.SandboxClassGvisor { + v.PauseImage = testPauseImage + } + sc := &atev1alpha1.SandboxConfig{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: atev1alpha1.SandboxConfigSpec{ + SandboxClass: class, + DefaultVersion: v.Name, + Versions: []atev1alpha1.SandboxVersionConfig{v}, + }, + } + if isDefault { + sc.Annotations = map[string]string{atev1alpha1.SandboxConfigClassDefaultAnnotation: "true"} + } + return sc +} + +func createSandboxConfig(t *testing.T, sc *atev1alpha1.SandboxConfig) { + t.Helper() + if err := k8sClient.Create(t.Context(), sc); err != nil { + t.Fatalf("create SandboxConfig %s: %v", sc.Name, err) + } + deleteOnCleanup(t, sc) +} + +func createWorkerPool(t *testing.T, wp *atev1alpha1.WorkerPool) { + t.Helper() + if err := k8sClient.Create(t.Context(), wp); err != nil { + t.Fatalf("create WorkerPool %s: %v", wp.Name, err) + } + deleteOnCleanup(t, wp) +} + +// waitResolved waits for wp's sandbox class to report Resolved=True for its +// current generation and to use config. +func waitResolved(t *testing.T, wp *atev1alpha1.WorkerPool, config string) *atev1alpha1.WorkerPool { + t.Helper() + return waitCondition(t, wp, func(current *atev1alpha1.WorkerPool, cond *metav1.Condition) bool { + return cond.Status == metav1.ConditionTrue && + statusConfig(current, current.Spec.DefaultSandboxClass()) == config + }) +} + +// waitUnresolved waits for wp's sandbox class to report Resolved=False with +// reason for its current generation. +func waitUnresolved(t *testing.T, wp *atev1alpha1.WorkerPool, reason string) *atev1alpha1.WorkerPool { + t.Helper() + return waitCondition(t, wp, func(_ *atev1alpha1.WorkerPool, cond *metav1.Condition) bool { + return cond.Status == metav1.ConditionFalse && cond.Reason == reason + }) +} + +func waitCondition(t *testing.T, wp *atev1alpha1.WorkerPool, ok func(*atev1alpha1.WorkerPool, *metav1.Condition) bool) *atev1alpha1.WorkerPool { + t.Helper() + current := &atev1alpha1.WorkerPool{} + eventually(t, func(ctx context.Context) (bool, error) { + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(wp), current); err != nil { + return false, nil + } + status := sandboxClassStatus(current, current.Spec.DefaultSandboxClass()) + if status == nil { + return false, nil + } + cond := meta.FindStatusCondition(status.Conditions, atev1alpha1.WorkerPoolSandboxClassConditionResolved) + return cond != nil && cond.ObservedGeneration == current.Generation && ok(current, cond), nil + }) + return current +} + +// waitProtectionFinalizer waits for the SandboxConfig name to carry, or not +// carry, the protection finalizer. +func waitProtectionFinalizer(t *testing.T, name string, want bool) { + t.Helper() + eventually(t, func(ctx context.Context) (bool, error) { + sc := &atev1alpha1.SandboxConfig{} + if err := k8sClient.Get(ctx, types.NamespacedName{Name: name}, sc); err != nil { + return false, nil + } + return controllerutil.ContainsFinalizer(sc, atev1alpha1.SandboxConfigWorkerPoolProtectionFinalizer) == want, nil + }) +} + +// waitGone waits for obj to no longer exist. +func waitGone(t *testing.T, obj client.Object) { + t.Helper() + eventually(t, func(ctx context.Context) (bool, error) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(obj), obj.DeepCopyObject().(client.Object)) + return k8errors.IsNotFound(err), nil + }) +} + +// assertNoDeployment fails if a Deployment controlled by wp exists. envtest +// runs no garbage collector, so a same-named Deployment left by a pool from an +// earlier -count iteration does not count. +func assertNoDeployment(t *testing.T, wp *atev1alpha1.WorkerPool) { + t.Helper() + dep, err := getDeployment(t.Context(), wp) + if k8errors.IsNotFound(err) { + return + } + if err != nil { + t.Fatalf("get Deployment: %v", err) + } + if metav1.IsControlledBy(dep, wp) { + t.Fatalf("unresolved WorkerPool %s has a Deployment", wp.Name) + } +} + +func deleteAndWait(t *testing.T, obj client.Object) { + t.Helper() + if err := k8sClient.Delete(t.Context(), obj); err != nil { + t.Fatalf("delete %s: %v", obj.GetName(), err) + } + waitGone(t, obj) +} + +// removeFinalizer removes finalizer from the named SandboxConfig, if it still +// exists. It uses its own context because it runs as a cleanup. +func removeFinalizer(t *testing.T, name, finalizer string) { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + sc := &atev1alpha1.SandboxConfig{} + if err := k8sClient.Get(ctx, types.NamespacedName{Name: name}, sc); err != nil { + return client.IgnoreNotFound(err) + } + if !controllerutil.RemoveFinalizer(sc, finalizer) { + return nil + } + return k8sClient.Update(ctx, sc) + }) + if err != nil { + t.Errorf("cleanup: remove finalizer %s from SandboxConfig %s: %v", finalizer, name, err) + } +} + +// makeWorkerPoolClasses returns a pool in ns with the given spec and status +// sandbox classes. +func makeWorkerPoolClasses(ns, name string, spec []atev1alpha1.WorkerPoolSandboxClass, status []atev1alpha1.WorkerPoolSandboxClassStatus) *atev1alpha1.WorkerPool { + wp := makeWorkerPool(name, ns, 1, "ateom:v1") + wp.Spec.SandboxClasses = spec + wp.Status.SandboxClasses = status + return wp +} + +// statusConfig returns the SandboxConfig wp's status uses for class, or "". +func statusConfig(wp *atev1alpha1.WorkerPool, class atev1alpha1.SandboxClass) string { + if s := sandboxClassStatus(wp, class); s != nil && s.ConfigRef != nil { + return s.ConfigRef.Name + } + return "" +} diff --git a/cmd/atecontroller/main.go b/cmd/atecontroller/main.go index 8faead0ed3..f624bbec90 100644 --- a/cmd/atecontroller/main.go +++ b/cmd/atecontroller/main.go @@ -208,11 +208,21 @@ func main() { SystemNamespace: systemNamespace, AteletServiceAccount: *ateletServiceAccount, RouterServiceAccount: *routerServiceAccount, + Recorder: mgr.GetEventRecorder("workerpool-controller"), + APIReader: mgr.GetAPIReader(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "WorkerPool") os.Exit(1) } + if err = (&controllers.SandboxConfigProtectionReconciler{ + Client: mgr.GetClient(), + APIReader: mgr.GetAPIReader(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "SandboxConfigProtection") + os.Exit(1) + } + if err = (&controllers.NetworkPolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/cmd/benchmarking/isolate/fake-workersync/controller.go b/cmd/benchmarking/isolate/fake-workersync/controller.go index 8920072373..7726105b07 100644 --- a/cmd/benchmarking/isolate/fake-workersync/controller.go +++ b/cmd/benchmarking/isolate/fake-workersync/controller.go @@ -39,6 +39,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/labels" ) @@ -514,7 +515,7 @@ func (c *controller) syncStatus(ctx context.Context, wp *atev1alpha1.WorkerPool, } } c.mu.Unlock() - if wp.Status == want { + if equality.Semantic.DeepEqual(wp.Status, want) { return nil } updated := wp.DeepCopy() diff --git a/cmd/benchmarking/isolate/fake-workersync/controller_test.go b/cmd/benchmarking/isolate/fake-workersync/controller_test.go index 8d43bebaf2..a28dff6f56 100644 --- a/cmd/benchmarking/isolate/fake-workersync/controller_test.go +++ b/cmd/benchmarking/isolate/fake-workersync/controller_test.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "maps" + "reflect" "slices" "sync" "sync/atomic" @@ -411,7 +412,7 @@ func TestReconcileHonorsReplicasAndLimits(t *testing.T) { if got := rel.runtimes[ctl.uid(name)]; !proto.Equal(got, wantRuntime) { t.Errorf("default runtime %v, want %v: the pool's class on this host, which ate-api-server requires", got, wantRuntime) } - if got, want := cl.statuses["bench"], (atev1alpha1.WorkerPoolStatus{Replicas: 3, ReadyReplicas: 3, Selector: "ate.dev/worker-pool=bench"}); got != want { + if got, want := cl.statuses["bench"], (atev1alpha1.WorkerPoolStatus{Replicas: 3, ReadyReplicas: 3, Selector: "ate.dev/worker-pool=bench"}); !reflect.DeepEqual(got, want) { t.Errorf("status = %+v, want %+v", got, want) } } diff --git a/demos/counter/counter.yaml.tmpl b/demos/counter/counter.yaml.tmpl index 61bf5e0ae3..dea8cb2541 100644 --- a/demos/counter/counter.yaml.tmpl +++ b/demos/counter/counter.yaml.tmpl @@ -38,10 +38,11 @@ spec: # deploy blocks on a rollout that never completes. replicas: 3 workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor + # No configRef: the pool follows the class default, the newest SandboxConfig + # of the class annotated sandboxconfig.ate.dev/is-class-default=true + # (gvisor-default as installed), and moves to a new default when one appears. sandboxClasses: - name: gvisor - configRef: - name: gvisor-default template: nodeSelector: ate.dev/substrate-version: "${SUBSTRATE_VERSION}" diff --git a/docs/api-guide.md b/docs/api-guide.md index 96b69b07c8..d7bc87255d 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -12,7 +12,7 @@ The `WorkerPool` defines the pool of physical "warm" compute capacity. It manage | :--- | :--- | :--- | | `replicas` | `int32` | **Required.** Number of physical standby pods to maintain in the cluster. | | `workerImage` | `string` | **Required.** The container image for the `ateom` herder process (e.g. `ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor`). | -| `sandboxClasses` | `[]WorkerPoolSandboxClass` | **Required.** The sandbox runtime families the pool runs; exactly one entry today. Each entry has `name` (**required**, `gvisor` or `microvm`), which drives the worker pod shape (e.g. KVM device mounts, node placement), and `configRef.name` (optional), which names a cluster-scoped [`SandboxConfig`](#3-sandboxconfig-the-sandbox-itself) of the same class. `configRef` is not consumed yet: the sandbox binaries still come from the `SandboxConfig` each `ActorTemplate` selects. | +| `sandboxClasses` | `[]WorkerPoolSandboxClass` | **Required.** The sandbox runtime families the pool runs; exactly one entry today. Each entry has `name` (**required**, `gvisor` or `microvm`, immutable), which drives the worker pod shape (e.g. KVM device mounts, node placement), and `configRef.name` (optional), which names a cluster-scoped [`SandboxConfig`](#3-sandboxconfig-the-sandbox-itself) of the same class. Without `configRef`, the pool uses the class default (see [Class default](#class-default)). The controller records the result in `status.sandboxClasses`. The sandbox binaries an actor boots with still come from the `SandboxConfig` its `ActorTemplate` selects. | | `template` | `WorkerPoolPodTemplate` | **Optional.** Metadata, scheduling, and resource settings for worker workloads. | #### `WorkerPoolPodTemplate` (`spec.template`) @@ -109,6 +109,7 @@ worker pod on a GPU node and reserves the device, and nothing else reads it. | :--- | :--- | :--- | | `replicas` | `int32` | Total number of worker pods, mirrored from the managed Deployment. | | `selector` | `string` | Label selector for the worker pods. | +| `sandboxClasses` | `[]WorkerPoolSandboxClassStatus` | One entry per sandbox class, with `name`, `configRef.name` (the `SandboxConfig` in use), `pendingConfigRef.name` (transient, the config the class is switching to; see [Deletion protection](#deletion-protection)), and `conditions`. The `Resolved` condition is `True` (reason `Resolved`) when the class's spec resolves. Otherwise it is `False` with reason `SandboxConfigNotFound`, `SandboxConfigDeleting`, `SandboxClassMismatch`, or `DefaultConfigNotFound`, the controller emits a Warning event, and `configRef` keeps the last resolved config. `configRef` is omitted if the class has never resolved. Each time `configRef` changes, the controller emits a Normal `SandboxConfigSwitched` event naming the old and new config, so `kubectl events --for workerpool/NAME` shows the configs the pool used recently. The API server keeps an event for one hour by default, so the full history is only in a logging backend that stores events. The controller creates the worker Deployment only once every class has a config, and keeps updating an existing Deployment while a class does not resolve. | --- @@ -419,6 +420,30 @@ Each entry of `versions` (`SandboxVersionConfig`): A cluster-wide gVisor `SandboxConfig` (`gvisor-default`) is installed with the platform, so gVisor templates can name it via `sandboxConfig.configName` without any extra setup. +### Class default + +A `SandboxConfig` annotated `sandboxconfig.ate.dev/is-class-default: "true"` is a default for its `sandboxClass`. A `WorkerPool` entry without `configRef` uses the newest such config of its class that is not being deleted. Among configs created in the same second, the name that sorts first wins. More than one config of a class may carry the annotation, so a new default can be rolled out by creating it, and pools move to it without restarting their workers. + +While no default is available, the class reports `Resolved=False` with reason `DefaultConfigNotFound`, or `SandboxConfigDeleting` if the only defaults are being deleted. A pool that already uses a config keeps it meanwhile, even if that config is being deleted or has lost the annotation. + +### Deletion protection + +atecontroller keeps a `SandboxConfig` from being deleted while a `WorkerPool` uses it. + +- When a `WorkerPool` resolves a `SandboxConfig`, atecontroller puts the finalizer `sandboxconfig.ate.dev/workerpool-protection` on the config before it records the config in the pool's `status.sandboxClasses`. +- A pool that switches to another config first records the new config in `pendingConfigRef` and keeps its current config in `configRef`. It then reads the new config from the API server, and moves `configRef` only if the config is not being deleted. Otherwise it keeps its current config. +- The finalizer stays until the config is deleted. Deleting the config marks it for deletion. atecontroller removes the finalizer once no `WorkerPool`'s `status.sandboxClasses` names the config. It confirms that against the API server, not only its cache. +- A config that pools stopped using is gone within a reconcile. A config that pools still use stays `Terminating` until they move or are deleted. A config no pool has ever used carries no finalizer and is deleted at once. +- A config that is being deleted takes no new pools. A `configRef` to it reports `SandboxConfigDeleting`, and it stops being a class default. A pool already on it keeps it and reports `Resolved=False`. A pinned pool does so until its `configRef` changes, an unpinned pool until another default exists. So a `Terminating` config only loses users. +- `WorkerPool`s carry no finalizer and delete without waiting. + +A delete cannot be undone, and an object that is being deleted cannot be recreated under the same name. Applying its manifest again only updates the terminating object. To recover from deleting a class default that pools still use: + +1. Create a replacement under a different name with the `is-class-default` annotation. Pools move to it on their own, the finalizer is released, and the old config disappears. +2. Once the old config is gone, the next `ate-setup` upgrade recreates the shipped config under its original name. Pools move back to it, because it is then the newest default. + +atecontroller alone adds and releases the finalizer. While it is down, `kubectl delete sandboxconfig` of a config that carries the finalizer hangs until atecontroller is back. `ate-setup` strips the finalizer from every config when it deletes the control plane. + ### Example ```yaml diff --git a/hack/install-microvm-deps.sh b/hack/install-microvm-deps.sh index 13774ffd85..c9786a6d11 100755 --- a/hack/install-microvm-deps.sh +++ b/hack/install-microvm-deps.sh @@ -31,9 +31,13 @@ # sandboxConfig.configName: microvm. This avoids a dirty teardown silently # binding new templates to a stale config. # -# On --delete: removes the SandboxConfig from the cluster. Bucket contents -# are left alone (they're inert until a SandboxConfig points at them, and -# re-staging is cheap on next install). +# On --delete: removes the SandboxConfig from the cluster. The delete is best +# effort. A SandboxConfig that a WorkerPool uses carries a protection +# finalizer, which atecontroller releases only once no pool's status names +# the config. So the script leaves the config in place if any WorkerPool +# still uses it. Otherwise it waits a bounded time for the finalizer to go. +# Bucket contents are left alone (they're inert until a SandboxConfig points +# at them, and re-staging is cheap on next install). # # Like the other hack scripts, this sources .ate-dev-env.sh for the cluster / # registry / bucket settings unless NO_DEV_ENV is set. @@ -117,10 +121,34 @@ run_kubectl() { MANIFEST_TEMPLATE="manifests/microvm/sandboxconfig-microvm.yaml.tmpl" if [[ "${action}" == "delete" ]]; then + # A WorkerPool that uses the config holds it. The delete would only mark + # the config Terminating, and `kubectl delete` would wait for it without + # end. A Terminating config also cannot be recreated under its name. So no + # delete is issued at all. The config is left in place and the pools are + # named instead. A cluster without the WorkerPool CRD has no users. + users="$(run_kubectl get workerpools --all-namespaces \ + -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name} {.status.sandboxClasses[*].configRef.name}{"\n"}{end}' 2>/dev/null \ + | awk '{ for (i = 2; i <= NF; i++) if ($i == "microvm") { print $1; break } }' || true)" + if [[ -n "${users}" ]]; then + echo "Warning: the microvm SandboxConfig is left in place; these WorkerPools" >&2 + echo " still use it:" >&2 + while IFS= read -r pool; do echo " ${pool}" >&2; done <<<"${users}" + echo " Delete them, or point their spec.sandboxClasses[].configRef at" >&2 + echo " another microvm SandboxConfig, and run this again." >&2 + exit 0 + fi log "Deleting microvm SandboxConfig..." # Delete by name rather than by manifest so a missing/edited template file - # doesn't block cleanup on an older cluster. - run_kubectl delete --ignore-not-found sandboxconfig microvm + # doesn't block cleanup on an older cluster. atecontroller releases the + # protection finalizer within a reconcile. The timeout turns a missing + # atecontroller into an error instead of a hang. + if ! run_kubectl delete --ignore-not-found --timeout=60s sandboxconfig microvm; then + echo "Error: the microvm SandboxConfig did not finish deleting. It stays" >&2 + echo " Terminating until atecontroller releases its protection" >&2 + echo " finalizer; check that atecontroller is running, then run this" >&2 + echo " again." >&2 + exit 1 + fi log "Done. (Bucket assets at gs://${BUCKET_NAME}/kata-assets/ left in place.)" exit 0 fi diff --git a/internal/e2e/suites/sandboxconfig/classdefault_test.go b/internal/e2e/suites/sandboxconfig/classdefault_test.go new file mode 100644 index 0000000000..1d97bb8f5e --- /dev/null +++ b/internal/e2e/suites/sandboxconfig/classdefault_test.go @@ -0,0 +1,333 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package sandboxconfig holds the end-to-end tests for how a WorkerPool +// resolves its SandboxConfig: which config atecontroller picks, how the pick +// shows up in the pool's status and events, and how the protection finalizer +// holds a config until no pool names it. +// +// The pools here run no workers (replicas is 0). Worker pods take nothing +// from the SandboxConfig, so the resolution is all there is to test, and a +// pool without pods costs the cluster nothing. +// +// The class defaults the tests create are cluster-scoped, so every other pool +// of the class without a configRef follows them too, and back. Each is a copy +// of the installed default's spec under another name, so nothing those pools +// run changes. +package sandboxconfig + +import ( + "context" + "encoding/json" + "fmt" + "slices" + "strings" + "testing" + "time" + + "github.com/agent-substrate/substrate/internal/e2e" + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + eventsv1 "k8s.io/api/events/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + // eventReasonSwitched is the reason of the Normal event atecontroller + // emits each time a pool's SandboxConfig changes. + eventReasonSwitched = "SandboxConfigSwitched" + + // stepTimeout bounds each wait on the controller. A switch takes a few + // reconciles. The deletion of a config in use also waits for the + // protection reconciler to release it once every pool has moved off it. + stepTimeout = 60 * time.Second +) + +// TestWorkerPoolFollowsClassDefault covers a pool with no configRef, which +// follows its class default: the newest SandboxConfig of its class annotated +// sandboxconfig.ate.dev/is-class-default=true. Starting from the default the +// cluster installs, a newer default is created, un-annotated, re-annotated, +// and finally deleted while the pool uses it. At each step the pool's status +// must follow, and at the end its events must record every move exactly once. +func TestWorkerPoolFollowsClassDefault(t *testing.T) { + ctx := t.Context() + clients := e2e.GetClients() + + class := atev1alpha1.SandboxClassGvisor + if e2e.IsMicroVM() { + class = atev1alpha1.SandboxClassMicroVM + } + base := newestClassDefault(t, ctx, clients, class) + t.Logf("Class %q default as installed: %s", class, base.Name) + + switched := func(from, to string) string { + return fmt.Sprintf("sandbox class %q switched from SandboxConfig %q to %q", class, from, to) + } + + // 1. A pool with no configRef resolves to the installed default. + ns := e2e.CreateNamespace(t).Name + pool, err := clients.SubstrateK8s.ApiV1alpha1().WorkerPools(ns).Create(ctx, &atev1alpha1.WorkerPool{ + ObjectMeta: metav1.ObjectMeta{Name: "follows-default", Namespace: ns}, + Spec: atev1alpha1.WorkerPoolSpec{ + Replicas: 0, + // Never pulled: the pool runs no workers. + WorkerImage: "ateom:unused", + SandboxClasses: []atev1alpha1.WorkerPoolSandboxClass{{Name: class}}, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create WorkerPool: %v", err) + } + waitForSandboxConfig(t, ctx, clients, pool, base.Name) + waitForEvent(t, ctx, clients, pool, fmt.Sprintf("sandbox class %q uses SandboxConfig %q", class, base.Name)) + + // 2. A newer class default appears: the pool moves to it. The name sorts + // before the installed default's, which is the tie-break should both + // carry the same creation second. + v2Name := fmt.Sprintf("e2e-%s-default-%s", class, ns) + if _, err := clients.SubstrateK8s.ApiV1alpha1().SandboxConfigs().Create(ctx, &atev1alpha1.SandboxConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: v2Name, + Annotations: map[string]string{atev1alpha1.SandboxConfigClassDefaultAnnotation: "true"}, + }, + Spec: *base.Spec.DeepCopy(), + }, metav1.CreateOptions{}); err != nil { + t.Fatalf("failed to create SandboxConfig %s: %v", v2Name, err) + } + // Always, pass or fail: a class default left behind moves every pool of + // the class in the cluster. Not t.Context: it is canceled before cleanups. + t.Cleanup(func() { + cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + if err := clients.SubstrateK8s.ApiV1alpha1().SandboxConfigs().Delete(cleanupCtx, v2Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { + t.Errorf("failed to delete SandboxConfig %s: %v", v2Name, err) + } + }) + waitForSandboxConfig(t, ctx, clients, pool, v2Name) + waitForEvent(t, ctx, clients, pool, switched(base.Name, v2Name)) + + // 3. The newer config stops being a default: the pool moves back. + setClassDefault(t, ctx, clients, v2Name, false) + waitForSandboxConfig(t, ctx, clients, pool, base.Name) + waitForEvent(t, ctx, clients, pool, switched(v2Name, base.Name)) + + // 4. It becomes a default again, and is deleted while the pool uses it. + // A default being deleted is never picked, so the pool moves back, and + // only then does the protection finalizer let the config go. + setClassDefault(t, ctx, clients, v2Name, true) + waitForSandboxConfig(t, ctx, clients, pool, v2Name) + waitForEvent(t, ctx, clients, pool, switched(base.Name, v2Name)) + + if err := clients.SubstrateK8s.ApiV1alpha1().SandboxConfigs().Delete(ctx, v2Name, metav1.DeleteOptions{}); err != nil { + t.Fatalf("failed to delete SandboxConfig %s: %v", v2Name, err) + } + waitForSandboxConfig(t, ctx, clients, pool, base.Name) + waitForSandboxConfigGone(t, ctx, clients, v2Name) + waitForEvent(t, ctx, clients, pool, switched(v2Name, base.Name)) + + // 5. The pool's events are the history of its configs, each move once. + // Two moves to the same config give two events, in order. A move + // reported twice within one reconcile would show up as a series instead. + want := []string{ + fmt.Sprintf("sandbox class %q uses SandboxConfig %q", class, base.Name), + switched(base.Name, v2Name), + switched(v2Name, base.Name), + switched(base.Name, v2Name), + switched(v2Name, base.Name), + } + events, err := poolEvents(ctx, clients, pool) + if err != nil { + t.Fatalf("failed to list events: %v", err) + } + var got []string + for _, ev := range events { + got = append(got, ev.Note) + if ev.Type != corev1.EventTypeNormal || ev.Reason != eventReasonSwitched { + t.Errorf("unexpected %s %s event: %s", ev.Type, ev.Reason, ev.Note) + } + if ev.Series != nil { + t.Errorf("event recorded %d times, want once: %s", ev.Series.Count, ev.Note) + } + } + if !slices.Equal(got, want) { + t.Errorf("events regarding the pool, oldest first:\n got:\n %s\n want:\n %s", + strings.Join(got, "\n "), strings.Join(want, "\n ")) + } +} + +// newestClassDefault returns the SandboxConfig a pool of class with no +// configRef resolves to when the test starts: the newest of the class +// annotated as its default, by creation time and then by name, the same rule +// atecontroller applies. The test cannot start without one. +func newestClassDefault(t *testing.T, ctx context.Context, clients *e2e.Clients, class atev1alpha1.SandboxClass) *atev1alpha1.SandboxConfig { + t.Helper() + list, err := clients.SubstrateK8s.ApiV1alpha1().SandboxConfigs().List(ctx, metav1.ListOptions{}) + if err != nil { + t.Fatalf("failed to list SandboxConfigs: %v", err) + } + var best *atev1alpha1.SandboxConfig + for i := range list.Items { + sc := &list.Items[i] + if sc.Spec.SandboxClass != class || sc.Annotations[atev1alpha1.SandboxConfigClassDefaultAnnotation] != "true" || !sc.DeletionTimestamp.IsZero() { + continue + } + if best == nil || best.CreationTimestamp.Before(&sc.CreationTimestamp) || + (best.CreationTimestamp.Equal(&sc.CreationTimestamp) && sc.Name < best.Name) { + best = sc + } + } + if best == nil { + t.Fatalf("no SandboxConfig of class %q is annotated %s=true; the install ships one, see manifests/ate-install", class, atev1alpha1.SandboxConfigClassDefaultAnnotation) + } + return best +} + +// setClassDefault sets or removes the class-default annotation on the +// SandboxConfig named name. +func setClassDefault(t *testing.T, ctx context.Context, clients *e2e.Clients, name string, isDefault bool) { + t.Helper() + var value any + if isDefault { + value = "true" + } + patch, err := json.Marshal(map[string]any{"metadata": map[string]any{"annotations": map[string]any{ + atev1alpha1.SandboxConfigClassDefaultAnnotation: value, + }}}) + if err != nil { + t.Fatalf("failed to marshal patch: %v", err) + } + if _, err := clients.SubstrateK8s.ApiV1alpha1().SandboxConfigs().Patch(ctx, name, types.MergePatchType, patch, metav1.PatchOptions{}); err != nil { + t.Fatalf("failed to patch SandboxConfig %s: %v", name, err) + } + t.Logf("SandboxConfig %s: %s=%v", name, atev1alpha1.SandboxConfigClassDefaultAnnotation, value) +} + +// waitForSandboxConfig blocks until pool's status reports its sandbox class +// Resolved, using the SandboxConfig named want, with no switch pending. +func waitForSandboxConfig(t *testing.T, ctx context.Context, clients *e2e.Clients, pool *atev1alpha1.WorkerPool, want string) { + t.Helper() + ctx, cancel := context.WithTimeout(ctx, stepTimeout) + defer cancel() + + class := pool.Spec.DefaultSandboxClass() + var last *atev1alpha1.WorkerPoolSandboxClassStatus + for { + wp, err := clients.SubstrateK8s.ApiV1alpha1().WorkerPools(pool.Namespace).Get(ctx, pool.Name, metav1.GetOptions{}) + if err == nil { + last = nil + for i := range wp.Status.SandboxClasses { + if wp.Status.SandboxClasses[i].Name == class { + last = &wp.Status.SandboxClasses[i] + } + } + if last != nil && last.ConfigRef != nil && last.ConfigRef.Name == want && last.PendingConfigRef == nil && + meta.IsStatusConditionTrue(last.Conditions, atev1alpha1.WorkerPoolSandboxClassConditionResolved) { + t.Logf("WorkerPool %s/%s uses SandboxConfig %s", pool.Namespace, pool.Name, want) + return + } + } + select { + case <-ctx.Done(): + status, _ := json.Marshal(last) + t.Fatalf("timed out after %v waiting for WorkerPool %s/%s to use SandboxConfig %q (last status %s, err %v)", stepTimeout, pool.Namespace, pool.Name, want, status, err) + case <-time.After(500 * time.Millisecond): + } + } +} + +// waitForSandboxConfigGone blocks until the SandboxConfig named name no +// longer exists, which for a config a pool used means its protection +// finalizer was released. +func waitForSandboxConfigGone(t *testing.T, ctx context.Context, clients *e2e.Clients, name string) { + t.Helper() + ctx, cancel := context.WithTimeout(ctx, stepTimeout) + defer cancel() + + for { + sc, err := clients.SubstrateK8s.ApiV1alpha1().SandboxConfigs().Get(ctx, name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + t.Logf("SandboxConfig %s is gone", name) + return + } + select { + case <-ctx.Done(): + var state string + if err == nil { + state = fmt.Sprintf("deletionTimestamp %v, finalizers %v", sc.DeletionTimestamp, sc.Finalizers) + } else { + state = err.Error() + } + t.Fatalf("timed out after %v waiting for SandboxConfig %s to be deleted (%s)", stepTimeout, name, state) + case <-time.After(500 * time.Millisecond): + } + } +} + +// waitForEvent blocks until an event regarding pool carries note. Events are +// recorded asynchronously, so one can trail the status write it reports. +func waitForEvent(t *testing.T, ctx context.Context, clients *e2e.Clients, pool *atev1alpha1.WorkerPool, note string) { + t.Helper() + ctx, cancel := context.WithTimeout(ctx, stepTimeout) + defer cancel() + + var seen []string + for { + events, err := poolEvents(ctx, clients, pool) + if err == nil { + seen = seen[:0] + for _, ev := range events { + if ev.Note == note { + t.Logf("Event: %s %s %s", ev.Type, ev.Reason, ev.Note) + return + } + seen = append(seen, fmt.Sprintf("%s %s %s", ev.Type, ev.Reason, ev.Note)) + } + } + select { + case <-ctx.Done(): + t.Fatalf("timed out after %v waiting for event %q on WorkerPool %s/%s (seen: %q, err %v)", stepTimeout, note, pool.Namespace, pool.Name, seen, err) + case <-time.After(500 * time.Millisecond): + } + } +} + +// poolEvents returns the events regarding pool, oldest first. +func poolEvents(ctx context.Context, clients *e2e.Clients, pool *atev1alpha1.WorkerPool) ([]eventsv1.Event, error) { + list, err := clients.K8s.EventsV1().Events(pool.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + var events []eventsv1.Event + for _, ev := range list.Items { + if ev.Regarding.UID == pool.UID { + events = append(events, ev) + } + } + slices.SortFunc(events, func(a, b eventsv1.Event) int { + return eventTime(a).Compare(eventTime(b)) + }) + return events, nil +} + +// eventTime is when an event was emitted: eventTime for one recorded through +// events.k8s.io, else its creation time. +func eventTime(ev eventsv1.Event) time.Time { + if !ev.EventTime.IsZero() { + return ev.EventTime.Time + } + return ev.CreationTimestamp.Time +} diff --git a/internal/e2e/suites/sandboxconfig/testmain_test.go b/internal/e2e/suites/sandboxconfig/testmain_test.go new file mode 100644 index 0000000000..e61c70574a --- /dev/null +++ b/internal/e2e/suites/sandboxconfig/testmain_test.go @@ -0,0 +1,24 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sandboxconfig + +import ( + "os" + "testing" + + "github.com/agent-substrate/substrate/internal/e2e" +) + +func TestMain(m *testing.M) { os.Exit(e2e.RunTestMain(m)) } diff --git a/manifests/ate-install/generated/ate.dev_workerpools.yaml b/manifests/ate-install/generated/ate.dev_workerpools.yaml index cad0eb1319..428862e728 100644 --- a/manifests/ate-install/generated/ate.dev_workerpools.yaml +++ b/manifests/ate-install/generated/ate.dev_workerpools.yaml @@ -40,6 +40,12 @@ spec: - jsonPath: .status.readyReplicas name: Ready type: integer + - jsonPath: .status.sandboxClasses[*].configRef.name + name: SandboxConfig + type: string + - jsonPath: .status.sandboxClasses[*].conditions[?(@.type=="Resolved")].status + name: Resolved + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -76,7 +82,7 @@ spec: sandboxClasses: description: |- SandboxClasses lists the sandbox runtime families this pool runs. Exactly - one entry is allowed today. + one entry is allowed today. The names cannot change after creation. items: description: WorkerPoolSandboxClass is one sandbox runtime family a WorkerPool runs. @@ -84,10 +90,14 @@ spec: configRef: description: |- ConfigRef names a cluster-scoped SandboxConfig for this sandbox class. - The referenced config's SandboxClass must match Name. Not consumed yet. + The referenced config's SandboxClass must match Name. When omitted, the + pool uses the newest SandboxConfig of this class annotated + sandboxconfig.ate.dev/is-class-default: "true". The controller reports + the resolved config in status.sandboxClasses. properties: name: description: Name is the SandboxConfig's metadata.name. + maxLength: 253 minLength: 1 type: string required: @@ -114,6 +124,9 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + x-kubernetes-validations: + - message: sandboxClasses names are immutable + rule: self.map(c, c.name) == oldSelf.map(c, c.name) template: description: Template holds optional metadata, scheduling, and resource settings for worker workloads. @@ -492,6 +505,121 @@ spec: format: int32 minimum: 0 type: integer + sandboxClasses: + description: |- + SandboxClasses reports the SandboxConfig each sandbox class of the pool + uses and whether the class's spec resolves. + items: + description: |- + WorkerPoolSandboxClassStatus reports the SandboxConfig and resolution state + of one sandbox class of a WorkerPool. + properties: + conditions: + description: Conditions describe the current state of this sandbox + class. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + default: "" + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configRef: + description: |- + ConfigRef names the cluster-scoped SandboxConfig in use. That is the + config spec.sandboxClasses[].configRef names, or the class default, or + the last resolved config while the spec does not resolve. Omitted when + the class has never resolved. + properties: + name: + description: Name is the SandboxConfig's metadata.name. + maxLength: 253 + minLength: 1 + type: string + required: + - name + type: object + name: + description: Name is the sandbox runtime family. + enum: + - gvisor + - microvm + type: string + pendingConfigRef: + description: |- + PendingConfigRef names the SandboxConfig the class is switching to + while the controller confirms it against the API server. It protects + the config from deletion like configRef does. The same reconcile + clears it, and configRef either moves to it or stays where it was. + properties: + name: + description: Name is the SandboxConfig's metadata.name. + maxLength: 253 + minLength: 1 + type: string + required: + - name + type: object + required: + - name + type: object + maxItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map selector: description: Selector is the label selector for the worker pods. type: string diff --git a/manifests/ate-install/generated/role.yaml b/manifests/ate-install/generated/role.yaml index ed3ad1a674..7614dac1f4 100644 --- a/manifests/ate-install/generated/role.yaml +++ b/manifests/ate-install/generated/role.yaml @@ -47,6 +47,15 @@ rules: - patch - update - watch +- apiGroups: + - ate.dev + resources: + - sandboxconfigs + verbs: + - get + - list + - patch + - watch - apiGroups: - ate.dev resources: @@ -93,6 +102,13 @@ rules: - signers verbs: - attest +- apiGroups: + - events.k8s.io + resources: + - events + verbs: + - create + - patch - apiGroups: - networking.k8s.io resources: diff --git a/pkg/api/v1alpha1/sandboxconfig_types.go b/pkg/api/v1alpha1/sandboxconfig_types.go index f88c4b7b3e..6234d3c271 100644 --- a/pkg/api/v1alpha1/sandboxconfig_types.go +++ b/pkg/api/v1alpha1/sandboxconfig_types.go @@ -54,6 +54,28 @@ type AssetFile struct { // class may carry it; the newest one wins. const SandboxConfigClassDefaultAnnotation = "sandboxconfig.ate.dev/is-class-default" +// SandboxConfigWorkerPoolProtectionFinalizer is put on a SandboxConfig by +// atecontroller before any WorkerPool's status records it. It stays until the +// config is deleted. Deleting a config that carries it goes in three steps: +// +// 1. The delete only sets deletionTimestamp, because the finalizer list is +// not empty. The config stays, Terminating. +// 2. atecontroller checks whether any WorkerPool's status.sandboxClasses +// names the config, in configRef or pendingConfigRef. If one does, it +// leaves the finalizer and checks again whenever a WorkerPool changes. +// Once none does, it removes the finalizer. +// 3. With the finalizer list empty, the API server deletes the config. +// +// Without atecontroller, step 2 never happens and the config stays +// Terminating until someone clears the finalizer by hand. A config no pool +// has ever used carries no finalizer and deletes at once. +// +// A pool that switches to a config names it in pendingConfigRef first. Then +// atecontroller reads the config from the API server, and moves configRef to +// it only if that read finds it is not being deleted. So a config is never +// released while a pool's configRef names it. +const SandboxConfigWorkerPoolProtectionFinalizer = "sandboxconfig.ate.dev/workerpool-protection" + // SandboxVersionState is whether a SandboxConfig version may be used. // // +kubebuilder:validation:Enum=Enabled;Disabled diff --git a/pkg/api/v1alpha1/sandboxconfig_validation_test.go b/pkg/api/v1alpha1/sandboxconfig_validation_test.go index 5e819755c8..d7f98b0c08 100644 --- a/pkg/api/v1alpha1/sandboxconfig_validation_test.go +++ b/pkg/api/v1alpha1/sandboxconfig_validation_test.go @@ -399,7 +399,16 @@ func TestShippedSandboxConfigManifests(t *testing.T) { if err := k8sClient.Create(ctx, sc); err != nil { t.Fatalf("Create() unexpected error: %v", err) } - t.Cleanup(func() { _ = k8sClient.Delete(ctx, sc) }) + // No controller runs here, so the object never gains the + // protection finalizer and a plain delete completes. t.Context() + // is already canceled when cleanups run, hence a fresh one. + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + if err := client.IgnoreNotFound(k8sClient.Delete(ctx, sc)); err != nil { + t.Errorf("cleanup: delete SandboxConfig %s: %v", sc.Name, err) + } + }) }) } } diff --git a/pkg/api/v1alpha1/workerpool_types.go b/pkg/api/v1alpha1/workerpool_types.go index 65d7686231..d31080749f 100644 --- a/pkg/api/v1alpha1/workerpool_types.go +++ b/pkg/api/v1alpha1/workerpool_types.go @@ -102,11 +102,12 @@ type WorkerPoolSpec struct { Template *WorkerPoolPodTemplate `json:"template,omitempty"` // SandboxClasses lists the sandbox runtime families this pool runs. Exactly - // one entry is allowed today. + // one entry is allowed today. The names cannot change after creation. // // +required // +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:MaxItems=1 + // +kubebuilder:validation:XValidation:rule="self.map(c, c.name) == oldSelf.map(c, c.name)",message="sandboxClasses names are immutable" // +listType=map // +listMapKey=name SandboxClasses []WorkerPoolSandboxClass `json:"sandboxClasses,omitempty"` @@ -126,7 +127,10 @@ type WorkerPoolSandboxClass struct { Name SandboxClass `json:"name,omitempty"` // ConfigRef names a cluster-scoped SandboxConfig for this sandbox class. - // The referenced config's SandboxClass must match Name. Not consumed yet. + // The referenced config's SandboxClass must match Name. When omitted, the + // pool uses the newest SandboxConfig of this class annotated + // sandboxconfig.ate.dev/is-class-default: "true". The controller reports + // the resolved config in status.sandboxClasses. // // +optional ConfigRef *SandboxConfigReference `json:"configRef,omitempty"` @@ -138,6 +142,7 @@ type SandboxConfigReference struct { // // +required // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 Name string `json:"name,omitempty"` } @@ -165,8 +170,74 @@ type WorkerPoolStatus struct { // Selector is the label selector for the worker pods. // +optional Selector string `json:"selector,omitempty"` + + // SandboxClasses reports the SandboxConfig each sandbox class of the pool + // uses and whether the class's spec resolves. + // + // +optional + // +kubebuilder:validation:MaxItems=1 + // +listType=map + // +listMapKey=name + SandboxClasses []WorkerPoolSandboxClassStatus `json:"sandboxClasses,omitempty"` +} + +// WorkerPoolSandboxClassStatus reports the SandboxConfig and resolution state +// of one sandbox class of a WorkerPool. +type WorkerPoolSandboxClassStatus struct { + // Name is the sandbox runtime family. + // + // +required + // +kubebuilder:validation:Enum=gvisor;microvm + Name SandboxClass `json:"name,omitempty"` + + // ConfigRef names the cluster-scoped SandboxConfig in use. That is the + // config spec.sandboxClasses[].configRef names, or the class default, or + // the last resolved config while the spec does not resolve. Omitted when + // the class has never resolved. + // + // +optional + ConfigRef *SandboxConfigReference `json:"configRef,omitempty"` + + // PendingConfigRef names the SandboxConfig the class is switching to + // while the controller confirms it against the API server. It protects + // the config from deletion like configRef does. The same reconcile + // clears it, and configRef either moves to it or stays where it was. + // + // +optional + PendingConfigRef *SandboxConfigReference `json:"pendingConfigRef,omitempty"` + + // Conditions describe the current state of this sandbox class. + // + // +optional + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty"` } +const ( + // WorkerPoolSandboxClassConditionResolved is True when the sandbox + // class's spec resolves to a usable SandboxConfig. While it is False for + // any class, the controller applies the worker Deployment only if one + // already exists or every class has a config in use. + WorkerPoolSandboxClassConditionResolved = "Resolved" + + // WorkerPoolReasonResolved: the sandbox class resolved. + WorkerPoolReasonResolved = "Resolved" + // WorkerPoolReasonSandboxConfigNotFound: configRef names a SandboxConfig + // that does not exist. + WorkerPoolReasonSandboxConfigNotFound = "SandboxConfigNotFound" + // WorkerPoolReasonSandboxConfigDeleting: the SandboxConfig configRef + // names, or the only class default, is being deleted. This is reported + // whether or not the pool already uses it. + WorkerPoolReasonSandboxConfigDeleting = "SandboxConfigDeleting" + // WorkerPoolReasonSandboxClassMismatch: configRef names a SandboxConfig + // of another sandbox class. + WorkerPoolReasonSandboxClassMismatch = "SandboxClassMismatch" + // WorkerPoolReasonDefaultConfigNotFound: configRef is omitted and no + // SandboxConfig of the class is annotated as the class default. + WorkerPoolReasonDefaultConfigNotFound = "DefaultConfigNotFound" +) + // WorkerPool is the Schema for the workerpools API // +genclient // +kubebuilder:object:generate=true @@ -177,6 +248,8 @@ type WorkerPoolStatus struct { // +kubebuilder:printcolumn:name="Desired",type=integer,JSONPath=`.spec.replicas` // +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=`.status.replicas` // +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=`.status.readyReplicas` +// +kubebuilder:printcolumn:name="SandboxConfig",type=string,JSONPath=`.status.sandboxClasses[*].configRef.name` +// +kubebuilder:printcolumn:name="Resolved",type=string,JSONPath=`.status.sandboxClasses[*].conditions[?(@.type=="Resolved")].status` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` type WorkerPool struct { metav1.TypeMeta `json:",inline"` diff --git a/pkg/api/v1alpha1/workerpool_validation_test.go b/pkg/api/v1alpha1/workerpool_validation_test.go index 99d47c889c..e5700c4c62 100644 --- a/pkg/api/v1alpha1/workerpool_validation_test.go +++ b/pkg/api/v1alpha1/workerpool_validation_test.go @@ -125,6 +125,25 @@ func TestWorkerPoolValidation(t *testing.T) { }, wantErr: true, errMsg: "spec.sandboxClasses[0].configRef.name", + }, { + name: "configRef name of 253 characters", + mutate: func(wp *WorkerPool) { + wp.Spec.SandboxClasses = []WorkerPoolSandboxClass{{ + Name: SandboxClassGvisor, + ConfigRef: &SandboxConfigReference{Name: strings.Repeat("a", 253)}, + }} + }, + wantErr: false, + }, { + name: "configRef name of 254 characters", + mutate: func(wp *WorkerPool) { + wp.Spec.SandboxClasses = []WorkerPoolSandboxClass{{ + Name: SandboxClassGvisor, + ConfigRef: &SandboxConfigReference{Name: strings.Repeat("a", 254)}, + }} + }, + wantErr: true, + errMsg: "spec.sandboxClasses[0].configRef.name", }, { name: "valid template", mutate: func(wp *WorkerPool) { @@ -303,3 +322,60 @@ func TestWorkerPoolReservedMetadataUpdate(t *testing.T) { t.Errorf("wrong error:\n wanted: %q\n got: %q", want, err.Error()) } } + +// TestWorkerPoolSandboxClassesUpdate verifies that the names in +// spec.sandboxClasses cannot change after creation, while the rest of an +// entry can. +func TestWorkerPoolSandboxClassesUpdate(t *testing.T) { + ctx := context.Background() + tests := []struct { + name string + mutate func(*WorkerPool) + wantErr string + }{{ + name: "change the sandbox class", + mutate: func(wp *WorkerPool) { + wp.Spec.SandboxClasses[0].Name = SandboxClassMicroVM + }, + wantErr: "sandboxClasses names are immutable", + }, { + name: "set configRef", + mutate: func(wp *WorkerPool) { + wp.Spec.SandboxClasses[0].ConfigRef = &SandboxConfigReference{Name: "gvisor-release1"} + }, + }} + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wp := &WorkerPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("test-sandbox-classes-update-%d", i), + Namespace: "default", + }, + Spec: WorkerPoolSpec{ + Replicas: 1, + WorkerImage: "example.com/ateom:latest", + SandboxClasses: []WorkerPoolSandboxClass{{Name: SandboxClassGvisor}}, + }, + } + if err := k8sClient.Create(ctx, wp); err != nil { + t.Fatalf("create WorkerPool: %v", err) + } + t.Cleanup(func() { _ = k8sClient.Delete(ctx, wp) }) + + tt.mutate(wp) + err := k8sClient.Update(ctx, wp) + if tt.wantErr == "" { + if err != nil { + t.Fatalf("update: %v", err) + } + return + } + if err == nil { + t.Fatalf("update unexpectedly accepted, want %q", tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("wrong error:\n wanted: %q\n got: %q", tt.wantErr, err.Error()) + } + }) + } +} diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 3cacd34349..394977bbf3 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -20,6 +20,7 @@ package v1alpha1 import ( "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -270,7 +271,7 @@ func (in *WorkerPool) DeepCopyInto(out *WorkerPool) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkerPool. @@ -401,6 +402,38 @@ func (in *WorkerPoolSandboxClass) DeepCopy() *WorkerPoolSandboxClass { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkerPoolSandboxClassStatus) DeepCopyInto(out *WorkerPoolSandboxClassStatus) { + *out = *in + if in.ConfigRef != nil { + in, out := &in.ConfigRef, &out.ConfigRef + *out = new(SandboxConfigReference) + **out = **in + } + if in.PendingConfigRef != nil { + in, out := &in.PendingConfigRef, &out.PendingConfigRef + *out = new(SandboxConfigReference) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkerPoolSandboxClassStatus. +func (in *WorkerPoolSandboxClassStatus) DeepCopy() *WorkerPoolSandboxClassStatus { + if in == nil { + return nil + } + out := new(WorkerPoolSandboxClassStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkerPoolSpec) DeepCopyInto(out *WorkerPoolSpec) { *out = *in @@ -431,6 +464,13 @@ func (in *WorkerPoolSpec) DeepCopy() *WorkerPoolSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkerPoolStatus) DeepCopyInto(out *WorkerPoolStatus) { *out = *in + if in.SandboxClasses != nil { + in, out := &in.SandboxClasses, &out.SandboxClasses + *out = make([]WorkerPoolSandboxClassStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkerPoolStatus. From c4129c94bca0b66462d79d3a398af4ce77548f79 Mon Sep 17 00:00:00 2001 From: Michelle Au Date: Mon, 12 Oct 2026 06:09:13 +0000 Subject: [PATCH 9/9] Simplify Snapshot API for Actor Lifecycle v2 (#2449) The initial API developed in #2289 had a repeated SnasphotStorage structure keyed by Durability (RESIDENT, LOCAL and DURABLE). However this was overly descriptive. RESIDENT and LOCAL storage details are local to atelet, and the promotion of snapshots from RESIDENT -> LOCAL -> DURABLE is also driven by atelet. Putting the information in Snapshot API meant that every action triggered by atelet has to invoke ateapi first to record the transition. Instead, this PR simplifies the sturcture by having a single SnapshotStorage to contain the durable storage details. Locality details will not be recorded in the Snapshot API. We only record that a snapshot also has locality on a certain node. #### Breaking change LocalSnapshot is removed and replaced by a Snapshot.locality field. Part of #2069 - [x] Tests pass - [x] Appropriate changes to documentation are included in the PR --- .../internal/apivalidation/actor_test.go | 91 +- .../internal/apivalidation/validation_test.go | 158 +- .../apivalidation/zz_generated.validation.go | 301 ++- cmd/ateapi/internal/controlapi/actor.go | 4 +- .../controlapi/actor_template_test.go | 2 +- cmd/ateapi/internal/controlapi/actor_test.go | 14 +- cmd/ateapi/internal/controlapi/crash.go | 14 + cmd/ateapi/internal/controlapi/crash_test.go | 51 +- .../functionaltest/actor_template_test.go | 4 +- .../controlapi/functionaltest/actor_test.go | 72 +- .../controlapi/functionaltest/common_test.go | 81 +- .../controlapi/functionaltest/tag_test.go | 13 +- .../internal/controlapi/snapshot_helpers.go | 156 +- .../controlapi/snapshot_helpers_test.go | 215 +- cmd/ateapi/internal/controlapi/tag_test.go | 12 +- .../controlapi/template_reconciler.go | 4 +- .../controlapi/template_reconciler_test.go | 6 +- .../internal/controlapi/workflow_delete.go | 10 +- .../controlapi/workflow_delete_test.go | 16 +- .../internal/controlapi/workflow_pause.go | 95 +- .../controlapi/workflow_pause_test.go | 275 ++- .../workflow_reconcile_assignments_test.go | 6 +- .../internal/controlapi/workflow_resume.go | 16 +- .../controlapi/workflow_resume_test.go | 51 +- .../internal/controlapi/workflow_revert.go | 14 +- .../controlapi/workflow_revert_test.go | 30 +- .../internal/controlapi/workflow_suspend.go | 131 +- .../controlapi/workflow_suspend_test.go | 246 ++- .../internal/controlapi/workflow_tag.go | 117 +- .../internal/controlapi/workflow_tag_test.go | 81 +- .../internal/controlapi/workflow_test.go | 2 +- .../controlapi/workflow_testutil_test.go | 47 + .../controlapi/workflow_worker_delete_test.go | 20 +- cmd/ateapi/internal/store/atepg/tag.go | 8 +- cmd/ateapi/internal/store/atepg/tag_test.go | 11 +- .../internal/store/storecontract/contract.go | 100 +- cmd/kubectl-ate/internal/cmd/tag_test.go | 11 +- cmd/kubectl-ate/internal/printer/printer.go | 15 +- .../internal/printer/printer_test.go | 33 +- docs/api-guide.md | 41 +- internal/e2e/suites/demo/demo_test.go | 35 +- .../e2e/suites/security/crossatespace_test.go | 8 +- internal/proto/ateletpb/atelet.pb.go | 4 +- internal/proto/ateletpb/atelet.proto | 4 +- pkg/proto/ateapipb/ateapi.pb.go | 1821 ++++++++--------- pkg/proto/ateapipb/ateapi.proto | 121 +- 46 files changed, 2423 insertions(+), 2144 deletions(-) diff --git a/cmd/ateapi/internal/apivalidation/actor_test.go b/cmd/ateapi/internal/apivalidation/actor_test.go index 6b4cfcc627..57d6e36892 100644 --- a/cmd/ateapi/internal/apivalidation/actor_test.go +++ b/cmd/ateapi/internal/apivalidation/actor_test.go @@ -524,121 +524,88 @@ func TestValidateActorUpdate(t *testing.T) { })), field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("actor_template_uid"), nil, "").WithOrigin("format=k8s-uuid")}, }, { - "valid actor.status.snapshots local snapshot_name", + "valid actor.status.snapshots uuid and locality", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage = []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Local: &ateapipb.LocalSnapshot{SnapshotName: "snap-1"}, - }} + snap.Uuid = "snap-1" + snap.Locality = "node-1" })} })), nil, }, { - "invalid actor.status.snapshots local snapshot_name", + "missing actor.status.snapshots.uuid", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage = []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Local: &ateapipb.LocalSnapshot{SnapshotName: "SNAP 1"}, - }} + snap.Uuid = "" })} })), - field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("storage").Index(0).Child("local", "snapshot_name"), nil, "").WithOrigin("format=k8s-short-name")}, + field.ErrorList{field.Required(field.NewPath("status", "snapshots").Index(0).Child("uuid"), "")}, }, { - "duplicate actor.status.snapshots generation", - validInput(), - validOutput(withStatus(func(s *ateapipb.ActorStatus) { - s.Snapshots = []*ateapipb.Snapshot{ - validSnapshot(func(snap *ateapipb.Snapshot) { snap.Generation = 1 }), - validSnapshot(func(snap *ateapipb.Snapshot) { snap.Generation = 1 }), - } - })), - field.ErrorList{field.Duplicate(field.NewPath("status", "snapshots").Index(1), nil)}, - }, { - "duplicate actor.status.snapshots.storage durability", + "invalid actor.status.snapshots.uuid", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage = []*ateapipb.SnapshotStorage{ - validSnapshotStorage(), - validSnapshotStorage(), - } + snap.Uuid = "SNAP 1" })} })), - field.ErrorList{field.Duplicate(field.NewPath("status", "snapshots").Index(0).Child("storage").Index(1), nil)}, + field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("uuid"), nil, "").WithOrigin("format=k8s-short-name")}, }, { - "too many actor.status.snapshots.storage entries", + "invalid actor.status.snapshots.locality", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage = []*ateapipb.SnapshotStorage{ - validSnapshotStorage(func(ss *ateapipb.SnapshotStorage) { - ss.Durability = ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL - ss.Object = nil - ss.Local = &ateapipb.LocalSnapshot{SnapshotName: "snap-1"} - }), - validSnapshotStorage(func(ss *ateapipb.SnapshotStorage) { - ss.Durability = ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE - }), - validSnapshotStorage(func(ss *ateapipb.SnapshotStorage) { - ss.Durability = ateapipb.SnapshotDurability(3) - }), - } + snap.Locality = "NODE 1" })} })), - field.ErrorList{field.TooMany(field.NewPath("status", "snapshots").Index(0).Child("storage"), 3, 2).WithOrigin("maxItems")}, + field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("locality"), nil, "").WithOrigin("format=k8s-long-name")}, }, { - "invalid actor.status.snapshots.storage.durability", + "duplicate actor.status.snapshots generation", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { - s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage[0].Durability = ateapipb.SnapshotDurability(3) - })} + s.Snapshots = []*ateapipb.Snapshot{ + validSnapshot(func(snap *ateapipb.Snapshot) { snap.Generation = 1 }), + validSnapshot(func(snap *ateapipb.Snapshot) { snap.Generation = 1 }), + } })), - field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("storage").Index(0).Child("durability"), nil, "").WithOrigin("maximum")}, + field.ErrorList{field.Duplicate(field.NewPath("status", "snapshots").Index(1), nil)}, }, { - "valid actor.status.snapshots.storage.fidelity", + "valid actor.status.snapshots.fidelity", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + snap.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES })} })), nil, }, { - "missing actor.status.snapshots.storage.fidelity", + "missing actor.status.snapshots.fidelity", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED + snap.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED })} })), - field.ErrorList{field.Required(field.NewPath("status", "snapshots").Index(0).Child("storage").Index(0).Child("fidelity"), "")}, + field.ErrorList{field.Required(field.NewPath("status", "snapshots").Index(0).Child("fidelity"), "")}, }, { - "negative actor.status.snapshots.storage.fidelity", + "negative actor.status.snapshots.fidelity", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage[0].Fidelity = ateapipb.SnapshotFidelity(-1) + snap.Fidelity = ateapipb.SnapshotFidelity(-1) })} })), - field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("storage").Index(0).Child("fidelity"), nil, "").WithOrigin("minimum")}, + field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("fidelity"), nil, "").WithOrigin("minimum")}, }, { - "invalid actor.status.snapshots.storage.fidelity", + "invalid actor.status.snapshots.fidelity", validInput(), validOutput(withStatus(func(s *ateapipb.ActorStatus) { s.Snapshots = []*ateapipb.Snapshot{validSnapshot(func(snap *ateapipb.Snapshot) { - snap.Storage[0].Fidelity = ateapipb.SnapshotFidelity(4) + snap.Fidelity = ateapipb.SnapshotFidelity(4) })} })), - field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("storage").Index(0).Child("fidelity"), nil, "").WithOrigin("maximum")}, + field.ErrorList{field.Invalid(field.NewPath("status", "snapshots").Index(0).Child("fidelity"), nil, "").WithOrigin("maximum")}, }, { "too many external_volumes", validInput(), diff --git a/cmd/ateapi/internal/apivalidation/validation_test.go b/cmd/ateapi/internal/apivalidation/validation_test.go index f5ed650b13..a8dd874f67 100644 --- a/cmd/ateapi/internal/apivalidation/validation_test.go +++ b/cmd/ateapi/internal/apivalidation/validation_test.go @@ -1154,10 +1154,8 @@ func validObjectSnapshot(mutate ...func(*ateapipb.ObjectSnapshot)) *ateapipb.Obj func validSnapshotStorage(mutate ...func(*ateapipb.SnapshotStorage)) *ateapipb.SnapshotStorage { ss := &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: validObjectSnapshot(), + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: validObjectSnapshot(), } for _, m := range mutate { m(ss) @@ -1167,9 +1165,12 @@ func validSnapshotStorage(mutate ...func(*ateapipb.SnapshotStorage)) *ateapipb.S func validSnapshot(mutate ...func(*ateapipb.Snapshot)) *ateapipb.Snapshot { s := &ateapipb.Snapshot{ - Generation: 1, - Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, - Storage: []*ateapipb.SnapshotStorage{validSnapshotStorage()}, + Uuid: "snap-1", + Generation: 1, + Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + Locality: "node-1", + DurableSnapshot: validSnapshotStorage(), } for _, m := range mutate { m(s) @@ -1181,23 +1182,26 @@ func validSnapshot(mutate ...func(*ateapipb.Snapshot)) *ateapipb.Snapshot { // containing type reaches every field of it. func badSnapshot(mutate ...func(*ateapipb.Snapshot)) *ateapipb.Snapshot { breakIt := func(s *ateapipb.Snapshot) { - s.Storage = []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity(4), - Object: &ateapipb.ObjectSnapshot{SnapshotUri: ""}, - }} + s.Fidelity = ateapipb.SnapshotFidelity(4) + s.DurableSnapshot = &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: ""}, + } } return validSnapshot(append([]func(*ateapipb.Snapshot){breakIt}, mutate...)...) } func TestValidateSnapshot(t *testing.T) { valid := validSnapshot + uuidPath := field.NewPath("uuid") genPath := field.NewPath("generation") ownerPath := field.NewPath("owner") - storagePath := field.NewPath("storage") - fidelityPath := storagePath.Index(0).Child("fidelity") - uriPath := storagePath.Index(0).Child("object", "snapshot_uri") + fidelityPath := field.NewPath("fidelity") + localityPath := field.NewPath("locality") + durableSnapshotPath := field.NewPath("durable_snapshot") + statusPath := durableSnapshotPath.Child("status") + objectPath := durableSnapshotPath.Child("object") + uriPath := objectPath.Child("snapshot_uri") tests := []struct { name string @@ -1208,6 +1212,27 @@ func TestValidateSnapshot(t *testing.T) { name: "valid", obj: valid(), }, + { + name: "valid optional fields unset", + obj: valid(func(s *ateapipb.Snapshot) { + s.Locality = "" + }), + }, + { + name: "missing uuid", + obj: valid(func(s *ateapipb.Snapshot) { s.Uuid = "" }), + want: field.ErrorList{field.Required(uuidPath, "")}, + }, + { + name: "invalid uuid", + obj: valid(func(s *ateapipb.Snapshot) { s.Uuid = "SNAP 1" }), + want: field.ErrorList{field.Invalid(uuidPath, nil, "").WithOrigin("format=k8s-short-name")}, + }, + { + name: "invalid locality", + obj: valid(func(s *ateapipb.Snapshot) { s.Locality = "NODE 1" }), + want: field.ErrorList{field.Invalid(localityPath, nil, "").WithOrigin("format=k8s-long-name")}, + }, { name: "missing generation", obj: valid(func(s *ateapipb.Snapshot) { s.Generation = 0 }), @@ -1231,96 +1256,68 @@ func TestValidateSnapshot(t *testing.T) { { name: "valid fidelity: volumes", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + s.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES }), }, { name: "missing fidelity", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED + s.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED }), want: field.ErrorList{field.Required(fidelityPath, "")}, }, { - name: "missing storage", - obj: valid(func(s *ateapipb.Snapshot) { s.Storage = nil }), - want: field.ErrorList{field.Required(storagePath, "")}, + name: "valid without durable_snapshot", + obj: valid(func(s *ateapipb.Snapshot) { s.DurableSnapshot = nil }), }, { - name: "too many storage entries", + name: "missing durable_snapshot.status", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage = []*ateapipb.SnapshotStorage{ - validSnapshotStorage(func(ss *ateapipb.SnapshotStorage) { - ss.Durability = ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL - ss.Object = nil - ss.Local = &ateapipb.LocalSnapshot{SnapshotName: "snap-1"} - }), - validSnapshotStorage(func(ss *ateapipb.SnapshotStorage) { - ss.Durability = ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE - }), - validSnapshotStorage(func(ss *ateapipb.SnapshotStorage) { - ss.Durability = ateapipb.SnapshotDurability(3) - }), - } + s.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED }), - want: field.ErrorList{field.TooMany(storagePath, 3, 2).WithOrigin("maxItems")}, + want: field.ErrorList{field.Required(statusPath, "")}, }, { - name: "missing storage durability", + name: "negative durable_snapshot.status", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Durability = ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_UNSPECIFIED + s.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus(-1) }), - want: field.ErrorList{field.Required(storagePath.Index(0).Child("durability"), "")}, + want: field.ErrorList{field.Invalid(statusPath, nil, "").WithOrigin("minimum")}, }, { - name: "negative storage durability", + name: "durable_snapshot.status above the enum", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Durability = ateapipb.SnapshotDurability(-1) + s.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus(3) }), - want: field.ErrorList{field.Invalid(storagePath.Index(0).Child("durability"), nil, "").WithOrigin("minimum")}, + want: field.ErrorList{field.Invalid(statusPath, nil, "").WithOrigin("maximum")}, }, { - name: "storage durability above the enum", + name: "missing durable_snapshot.object", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Durability = ateapipb.SnapshotDurability(3) + s.DurableSnapshot.Object = nil }), - want: field.ErrorList{field.Invalid(storagePath.Index(0).Child("durability"), nil, "").WithOrigin("maximum")}, - }, - { - name: "storage union missing both local and object", - obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Object = nil - s.Storage[0].Local = nil - }), - want: field.ErrorList{field.Invalid(storagePath.Index(0), nil, "one of").WithOrigin("union")}, - }, - { - name: "storage union has both local and object", - obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Local = &ateapipb.LocalSnapshot{SnapshotName: "snap-1"} - }), - want: field.ErrorList{field.Invalid(storagePath.Index(0), nil, "one of").WithOrigin("union")}, + want: field.ErrorList{field.Required(objectPath, "")}, }, { name: "missing snapshot_uri", - obj: valid(func(s *ateapipb.Snapshot) { s.Storage[0].Object.SnapshotUri = "" }), + obj: valid(func(s *ateapipb.Snapshot) { s.DurableSnapshot.Object.SnapshotUri = "" }), want: field.ErrorList{field.Required(uriPath, "")}, }, { name: "snapshot_uri too long", obj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Object.SnapshotUri = "gs://" + strings.Repeat("x", 2044) + s.DurableSnapshot.Object.SnapshotUri = "gs://" + strings.Repeat("x", 2044) }), want: field.ErrorList{field.TooLong(uriPath, nil, 2048).WithOrigin("maxLength")}, }, { name: "fidelity above the enum", - obj: valid(func(s *ateapipb.Snapshot) { s.Storage[0].Fidelity = ateapipb.SnapshotFidelity(4) }), + obj: valid(func(s *ateapipb.Snapshot) { s.Fidelity = ateapipb.SnapshotFidelity(4) }), want: field.ErrorList{field.Invalid(fidelityPath, nil, "").WithOrigin("maximum")}, }, { name: "negative fidelity", - obj: valid(func(s *ateapipb.Snapshot) { s.Storage[0].Fidelity = ateapipb.SnapshotFidelity(-1) }), + obj: valid(func(s *ateapipb.Snapshot) { s.Fidelity = ateapipb.SnapshotFidelity(-1) }), want: field.ErrorList{field.Invalid(fidelityPath, nil, "").WithOrigin("minimum")}, }, { @@ -1342,8 +1339,8 @@ func TestValidateSnapshot(t *testing.T) { func TestValidateSnapshotUpdate(t *testing.T) { valid := validSnapshot - fidelityPath := field.NewPath("storage").Index(0).Child("fidelity") - uriPath := field.NewPath("storage").Index(0).Child("object", "snapshot_uri") + fidelityPath := field.NewPath("fidelity") + uriPath := field.NewPath("durable_snapshot", "object", "snapshot_uri") tests := []struct { name string @@ -1368,27 +1365,27 @@ func TestValidateSnapshotUpdate(t *testing.T) { name: "fidelity changed to a valid value", oldObj: valid(), newObj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + s.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES }), }, { name: "fidelity cleared", oldObj: valid(), newObj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED + s.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED }), want: field.ErrorList{field.Required(fidelityPath, "")}, }, { name: "fidelity changed to a value outside the enum", oldObj: valid(), - newObj: valid(func(s *ateapipb.Snapshot) { s.Storage[0].Fidelity = ateapipb.SnapshotFidelity(4) }), + newObj: valid(func(s *ateapipb.Snapshot) { s.Fidelity = ateapipb.SnapshotFidelity(4) }), want: field.ErrorList{field.Invalid(fidelityPath, nil, "").WithOrigin("maximum")}, }, { name: "snapshot_uri cleared", oldObj: valid(), - newObj: valid(func(s *ateapipb.Snapshot) { s.Storage[0].Object.SnapshotUri = "" }), + newObj: valid(func(s *ateapipb.Snapshot) { s.DurableSnapshot.Object.SnapshotUri = "" }), want: field.ErrorList{field.Required(uriPath, "")}, }, { @@ -1397,21 +1394,21 @@ func TestValidateSnapshotUpdate(t *testing.T) { name: "fidelity repaired", oldObj: badSnapshot(), newObj: badSnapshot(func(s *ateapipb.Snapshot) { - s.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY + s.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY }), }, { name: "snapshot_uri changed to a value that is too long", oldObj: valid(), newObj: valid(func(s *ateapipb.Snapshot) { - s.Storage[0].Object.SnapshotUri = "gs://" + strings.Repeat("x", 2044) + s.DurableSnapshot.Object.SnapshotUri = "gs://" + strings.Repeat("x", 2044) }), want: field.ErrorList{field.TooLong(uriPath, nil, 2048).WithOrigin("maxLength")}, }, { name: "snapshot_uri repaired", oldObj: badSnapshot(), - newObj: badSnapshot(func(s *ateapipb.Snapshot) { s.Storage[0].Object.SnapshotUri = validObjectSnapshot().SnapshotUri }), + newObj: badSnapshot(func(s *ateapipb.Snapshot) { s.DurableSnapshot.Object.SnapshotUri = validObjectSnapshot().SnapshotUri }), }, { name: "every field repaired", @@ -1466,8 +1463,8 @@ func TestValidateNestedSnapshot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { want := field.ErrorList{ - field.Required(tt.path.Child("storage").Index(0).Child("object", "snapshot_uri"), ""), - field.Invalid(tt.path.Child("storage").Index(0).Child("fidelity"), nil, "").WithOrigin("maximum"), + field.Required(tt.path.Child("durable_snapshot", "object", "snapshot_uri"), ""), + field.Invalid(tt.path.Child("fidelity"), nil, "").WithOrigin("maximum"), } assertValidateErr(t, tt.validate(context.Background()), want) }) @@ -1571,9 +1568,14 @@ func TestValidateTag(t *testing.T) { want: field.ErrorList{field.Invalid(statusPath.Child("actor_template_uid"), nil, "").WithOrigin("format=k8s-uuid")}, }, { + // A reserved tag has no snapshot until the tag workflow records it. name: "missing status.snapshot", obj: valid(func(tag *ateapipb.Tag) { tag.Status.Snapshot = nil }), - want: field.ErrorList{field.Required(statusPath.Child("snapshot"), "")}, + }, + { + name: "missing status.snapshot.durable_snapshot", + obj: valid(func(tag *ateapipb.Tag) { tag.Status.Snapshot.DurableSnapshot = nil }), + want: field.ErrorList{field.Required(statusPath.Child("snapshot", "durable_snapshot"), "")}, }, } for _, tt := range tests { @@ -1666,8 +1668,8 @@ func TestValidateTagRequestPayloads(t *testing.T) { return Validate_CreateTagRequest(ctx, op, nil, req, nil) }, want: field.ErrorList{ - field.Required(tagPath.Child("status", "snapshot", "storage").Index(0).Child("object", "snapshot_uri"), ""), - field.Invalid(tagPath.Child("status", "snapshot", "storage").Index(0).Child("fidelity"), nil, "").WithOrigin("maximum"), + field.Required(tagPath.Child("status", "snapshot", "durable_snapshot", "object", "snapshot_uri"), ""), + field.Invalid(tagPath.Child("status", "snapshot", "fidelity"), nil, "").WithOrigin("maximum"), }, }, { diff --git a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go index b780f5ab62..25b7bee5f0 100644 --- a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go +++ b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go @@ -5511,47 +5511,6 @@ func Validate_ListWorkersRequest( return errs } -// Validate_LocalSnapshot validates an instance of LocalSnapshot according -// to declarative validation rules in the API schema. -func Validate_LocalSnapshot( - ctx context.Context, op operation.Operation, fldPath *field.Path, - obj, oldObj *ateapipb.LocalSnapshot) (errs field.ErrorList) { - - { // field ateapipb.LocalSnapshot.SnapshotName - fn := func( - fldPath *field.Path, - obj, oldObj *string, - oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update { - if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { - return nil - } - } - // call field-attached validations - earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) - earlyReturn = true - } - if earlyReturn { - return // do not proceed - } - if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { - errs = append(errs, e...) - } - return - } - oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.LocalSnapshot) *string { - return &oldObj.SnapshotName - }) - errs = append(errs, fn(fldPath.Child("snapshot_name"), &obj.SnapshotName, oldVal, oldObj != nil)...) - } - - return errs -} - // Validate_MintActorCertificateRequest validates an instance of MintActorCertificateRequest according // to declarative validation rules in the API schema. func Validate_MintActorCertificateRequest( @@ -7105,10 +7064,10 @@ func Validate_Snapshot( ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ateapipb.Snapshot) (errs field.ErrorList) { - { // field ateapipb.Snapshot.Generation + { // field ateapipb.Snapshot.Uuid fn := func( fldPath *field.Path, - obj, oldObj *int32, + obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7125,22 +7084,22 @@ func Validate_Snapshot( if earlyReturn { return // do not proceed } - if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.Snapshot) *int32 { - return &oldObj.Generation + func(oldObj *ateapipb.Snapshot) *string { + return &oldObj.Uuid }) - errs = append(errs, fn(fldPath.Child("generation"), &obj.Generation, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("uuid"), &obj.Uuid, oldVal, oldObj != nil)...) } - { // field ateapipb.Snapshot.Owner + { // field ateapipb.Snapshot.Generation fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotOwner, + obj, oldObj *int32, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7157,25 +7116,22 @@ func Validate_Snapshot( if earlyReturn { return // do not proceed } - if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { - errs = append(errs, e...) - } if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.Snapshot) *ateapipb.SnapshotOwner { - return &oldObj.Owner + func(oldObj *ateapipb.Snapshot) *int32 { + return &oldObj.Generation }) - errs = append(errs, fn(fldPath.Child("owner"), &obj.Owner, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("generation"), &obj.Generation, oldVal, oldObj != nil)...) } - { // field ateapipb.Snapshot.ActorTemplateUid + { // field ateapipb.Snapshot.Owner fn := func( fldPath *field.Path, - obj, oldObj *string, + obj, oldObj *ateapipb.SnapshotOwner, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7185,85 +7141,64 @@ func Validate_Snapshot( } // call field-attached validations earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.Snapshot) *string { - return &oldObj.ActorTemplateUid + func(oldObj *ateapipb.Snapshot) *ateapipb.SnapshotOwner { + return &oldObj.Owner }) - errs = append(errs, fn(fldPath.Child("actor_template_uid"), &obj.ActorTemplateUid, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("owner"), &obj.Owner, oldVal, oldObj != nil)...) } - { // field ateapipb.Snapshot.Storage + { // field ateapipb.Snapshot.Fidelity fn := func( fldPath *field.Path, - obj, oldObj []*ateapipb.SnapshotStorage, + obj, oldObj *ateapipb.SnapshotFidelity, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { - if ateDeepEqual(obj, oldObj) { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { return nil } } // call field-attached validations earlyReturn := false - if e := validate.PtrSliceNoNils[ateapipb.SnapshotStorage](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) - earlyReturn = true - } - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) - earlyReturn = true - } - if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - // lists with map semantics require unique keys - if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, - func(a *ateapipb.SnapshotStorage, b *ateapipb.SnapshotStorage) bool { - return a.Durability == b.Durability - }); len(e) != 0 { + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { errs = append(errs, e...) } - // iterate the list and call the type's validation function - if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, - func(a *ateapipb.SnapshotStorage, b *ateapipb.SnapshotStorage) bool { - return a.Durability == b.Durability - }, ateDeepEqual, Validate_SnapshotStorage); len(e) != 0 { + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.Snapshot) []*ateapipb.SnapshotStorage { - return oldObj.Storage + func(oldObj *ateapipb.Snapshot) *ateapipb.SnapshotFidelity { + return &oldObj.Fidelity }) - errs = append(errs, fn(fldPath.Child("storage"), obj.Storage, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("fidelity"), &obj.Fidelity, oldVal, oldObj != nil)...) } - return errs -} - -// Validate_SnapshotConfig validates an instance of SnapshotConfig according -// to declarative validation rules in the API schema. -func Validate_SnapshotConfig( - ctx context.Context, op operation.Operation, fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotConfig) (errs field.ErrorList) { - - { // field ateapipb.SnapshotConfig.StorageLocation + { // field ateapipb.Snapshot.ActorTemplateUid fn := func( fldPath *field.Path, obj, oldObj *string, @@ -7276,33 +7211,28 @@ func Validate_SnapshotConfig( } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - // custom validation - if e := ValidateCustom_SnapshotConfig_StorageLocation(ctx, op, fldPath, obj, oldObj); len(e) != 0 { - errs = append(errs, e...) - } - if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 1024); len(e) != 0 { + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotConfig) *string { - return &oldObj.StorageLocation + func(oldObj *ateapipb.Snapshot) *string { + return &oldObj.ActorTemplateUid }) - errs = append(errs, fn(fldPath.Child("storage_location"), &obj.StorageLocation, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("actor_template_uid"), &obj.ActorTemplateUid, oldVal, oldObj != nil)...) } - { // field ateapipb.SnapshotConfig.PreferredFidelity + { // field ateapipb.Snapshot.Locality fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotFidelity, + obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7312,32 +7242,28 @@ func Validate_SnapshotConfig( } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { - errs = append(errs, e...) - } - if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + if e := validate.LongName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotConfig) *ateapipb.SnapshotFidelity { - return &oldObj.PreferredFidelity + func(oldObj *ateapipb.Snapshot) *string { + return &oldObj.Locality }) - errs = append(errs, fn(fldPath.Child("preferred_fidelity"), &obj.PreferredFidelity, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("locality"), &obj.Locality, oldVal, oldObj != nil)...) } - { // field ateapipb.SnapshotConfig.GoldenSnapshotConfig + { // field ateapipb.Snapshot.DurableSnapshot fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.GoldenSnapshotConfig, + obj, oldObj *ateapipb.SnapshotStorage, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7354,47 +7280,29 @@ func Validate_SnapshotConfig( return // do not proceed } // call the type's validation function - errs = append(errs, Validate_GoldenSnapshotConfig(ctx, op, fldPath, obj, oldObj)...) + errs = append(errs, Validate_SnapshotStorage(ctx, op, fldPath, obj, oldObj)...) return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotConfig) *ateapipb.GoldenSnapshotConfig { - return oldObj.GoldenSnapshotConfig + func(oldObj *ateapipb.Snapshot) *ateapipb.SnapshotStorage { + return oldObj.DurableSnapshot }) - errs = append(errs, fn(fldPath.Child("golden_snapshot_config"), obj.GoldenSnapshotConfig, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("durable_snapshot"), obj.DurableSnapshot, oldVal, oldObj != nil)...) } return errs } -var unionMembershipFor_github_com_agent_substrate_substrate_pkg_proto_ateapipb_SnapshotStorage_ = validate.NewUnionMembership(validate.NewUnionMember("local"), validate.NewUnionMember("object")) - -// Validate_SnapshotStorage validates an instance of SnapshotStorage according +// Validate_SnapshotConfig validates an instance of SnapshotConfig according // to declarative validation rules in the API schema. -func Validate_SnapshotStorage( +func Validate_SnapshotConfig( ctx context.Context, op operation.Operation, fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotStorage) (errs field.ErrorList) { - - if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_github_com_agent_substrate_substrate_pkg_proto_ateapipb_SnapshotStorage_, - func(obj *ateapipb.SnapshotStorage) bool { - if obj == nil { - return false - } - return obj.Local != nil - }, - func(obj *ateapipb.SnapshotStorage) bool { - if obj == nil { - return false - } - return obj.Object != nil - }); len(e) != 0 { - errs = append(errs, e...) - } + obj, oldObj *ateapipb.SnapshotConfig) (errs field.ErrorList) { - { // field ateapipb.SnapshotStorage.Durability + { // field ateapipb.SnapshotConfig.StorageLocation fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotDurability, + obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7411,25 +7319,26 @@ func Validate_SnapshotStorage( if earlyReturn { return // do not proceed } - if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { + // custom validation + if e := ValidateCustom_SnapshotConfig_StorageLocation(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } - if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 1024); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotStorage) *ateapipb.SnapshotDurability { - return &oldObj.Durability + func(oldObj *ateapipb.SnapshotConfig) *string { + return &oldObj.StorageLocation }) - errs = append(errs, fn(fldPath.Child("durability"), &obj.Durability, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("storage_location"), &obj.StorageLocation, oldVal, oldObj != nil)...) } - { // field ateapipb.SnapshotStorage.Status + { // field ateapipb.SnapshotConfig.PreferredFidelity fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotStorageStatus, + obj, oldObj *ateapipb.SnapshotFidelity, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -7446,7 +7355,7 @@ func Validate_SnapshotStorage( if earlyReturn { return // do not proceed } - if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { errs = append(errs, e...) } if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { @@ -7455,75 +7364,84 @@ func Validate_SnapshotStorage( return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotStorage) *ateapipb.SnapshotStorageStatus { - return &oldObj.Status + func(oldObj *ateapipb.SnapshotConfig) *ateapipb.SnapshotFidelity { + return &oldObj.PreferredFidelity }) - errs = append(errs, fn(fldPath.Child("status"), &obj.Status, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("preferred_fidelity"), &obj.PreferredFidelity, oldVal, oldObj != nil)...) } - { // field ateapipb.SnapshotStorage.Fidelity + { // field ateapipb.SnapshotConfig.GoldenSnapshotConfig fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.SnapshotFidelity, + obj, oldObj *ateapipb.GoldenSnapshotConfig, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { - if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + if ateDeepEqual(obj, oldObj) { return nil } } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { - errs = append(errs, e...) - } - if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { - errs = append(errs, e...) - } + // call the type's validation function + errs = append(errs, Validate_GoldenSnapshotConfig(ctx, op, fldPath, obj, oldObj)...) return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotStorage) *ateapipb.SnapshotFidelity { - return &oldObj.Fidelity + func(oldObj *ateapipb.SnapshotConfig) *ateapipb.GoldenSnapshotConfig { + return oldObj.GoldenSnapshotConfig }) - errs = append(errs, fn(fldPath.Child("fidelity"), &obj.Fidelity, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("golden_snapshot_config"), obj.GoldenSnapshotConfig, oldVal, oldObj != nil)...) } - { // field ateapipb.SnapshotStorage.Local + return errs +} + +// Validate_SnapshotStorage validates an instance of SnapshotStorage according +// to declarative validation rules in the API schema. +func Validate_SnapshotStorage( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ateapipb.SnapshotStorage) (errs field.ErrorList) { + + { // field ateapipb.SnapshotStorage.Status fn := func( fldPath *field.Path, - obj, oldObj *ateapipb.LocalSnapshot, + obj, oldObj *ateapipb.SnapshotStorageStatus, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { - if ateDeepEqual(obj, oldObj) { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { return nil } } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - // call the type's validation function - errs = append(errs, Validate_LocalSnapshot(ctx, op, fldPath, obj, oldObj)...) + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.SnapshotStorage) *ateapipb.LocalSnapshot { - return oldObj.Local + func(oldObj *ateapipb.SnapshotStorage) *ateapipb.SnapshotStorageStatus { + return &oldObj.Status }) - errs = append(errs, fn(fldPath.Child("local"), obj.Local, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("status"), &obj.Status, oldVal, oldObj != nil)...) } { // field ateapipb.SnapshotStorage.Object @@ -7539,7 +7457,8 @@ func Validate_SnapshotStorage( } // call field-attached validations earlyReturn := false - if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) earlyReturn = true } if earlyReturn { @@ -8028,13 +7947,27 @@ func Validate_TagStatus( } // call field-attached validations earlyReturn := false - if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } + func() { // cohort = "durable_snapshot" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "durable_snapshot", + func(o *ateapipb.Snapshot) *ateapipb.SnapshotStorage { return o.DurableSnapshot }, ateDeepEqual, validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "durable_snapshot", + func(o *ateapipb.Snapshot) *ateapipb.SnapshotStorage { return o.DurableSnapshot }, ateDeepEqual, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + }() // call the type's validation function errs = append(errs, Validate_Snapshot(ctx, op, fldPath, obj, oldObj)...) return diff --git a/cmd/ateapi/internal/controlapi/actor.go b/cmd/ateapi/internal/controlapi/actor.go index c6ca082f38..19740224df 100644 --- a/cmd/ateapi/internal/controlapi/actor.go +++ b/cmd/ateapi/internal/controlapi/actor.go @@ -394,8 +394,8 @@ func validateTemplateVolumesUnchanged(oldTemplate, newTemplate *ateapipb.ActorTe // Deleting an actor collects everything under its external snapshot prefix. If // the location prefix ever changes, we risk leaking the snapshots under the old prefix. func validateSnapshotLocationUnchanged(actor *ateapipb.Actor, newTemplate *ateapipb.ActorTemplate) error { - _, st := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - currentSnapshotURI := st.GetObject().GetSnapshotUri() + snap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + currentSnapshotURI := snap.GetDurableSnapshot().GetObject().GetSnapshotUri() if currentSnapshotURI == "" { return nil } diff --git a/cmd/ateapi/internal/controlapi/actor_template_test.go b/cmd/ateapi/internal/controlapi/actor_template_test.go index ceb4b2f4b3..6e0f28385a 100644 --- a/cmd/ateapi/internal/controlapi/actor_template_test.go +++ b/cmd/ateapi/internal/controlapi/actor_template_test.go @@ -233,7 +233,7 @@ func TestDeleteActorTemplate(t *testing.T) { actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), actorURI.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), actorURI.Name(), actorURI.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) var tag *ateapipb.Tag diff --git a/cmd/ateapi/internal/controlapi/actor_test.go b/cmd/ateapi/internal/controlapi/actor_test.go index de1e64f5f1..f5377c4169 100644 --- a/cmd/ateapi/internal/controlapi/actor_test.go +++ b/cmd/ateapi/internal/controlapi/actor_test.go @@ -511,7 +511,7 @@ func TestUpdateActor_RepointTemplateStorageLocation(t *testing.T) { actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", uri, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snapshot", uri, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) } @@ -817,7 +817,7 @@ func TestCreateActor_GoldenTagDefault(t *testing.T) { Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED, Status: &ateapipb.TagStatus{ ActorTemplateUid: tmpl.GetMetadata().GetUid(), - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), "gs://bucket/atespaces/ate-golden/tags/"+someActorUID, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), someActorUID, "gs://bucket/atespaces/ate-golden/tags/"+someActorUID, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), }, } wantCode := codes.OK @@ -831,7 +831,7 @@ func TestCreateActor_GoldenTagDefault(t *testing.T) { tag.Status.ActorTemplateUid = "other" wantCode = codes.FailedPrecondition case "data scope": - tag.Status.Snapshot.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + tag.Status.Snapshot.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES wantCode = codes.FailedPrecondition } if scenario != "missing" { @@ -848,7 +848,7 @@ func TestCreateActor_GoldenTagDefault(t *testing.T) { actor := &ateapipb.Actor{Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor"}, ActorTemplate: resources.ActorTemplateRefFromActorTemplate(tmpl).ToObjectRef()} if scenario == "explicit tag" { tag.Metadata.Name = "explicit" - tag.Status.Snapshot = newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), "gs://bucket/atespaces/ate-golden/tags/explicit", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + tag.Status.Snapshot = newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), "explicit", "gs://bucket/atespaces/ate-golden/tags/explicit", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) if _, err := persistence.CreateTag(ctx, tag); err != nil { t.Fatal(err) } @@ -862,8 +862,8 @@ func TestCreateActor_GoldenTagDefault(t *testing.T) { if err != nil { return } - gotSnap, gotSt := findLatestSnapshotStorage(created.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if gotSt.GetObject().GetSnapshotUri() != tagDurableSnapshotURI(tag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) || gotSnap.GetActorTemplateUid() != tmpl.GetMetadata().GetUid() { + gotSnap := findLatestDurableSnapshot(created.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != tagDurableSnapshotURI(tag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) || gotSnap.GetActorTemplateUid() != tmpl.GetMetadata().GetUid() { t.Fatalf("incorrect initial status: %v", created.GetStatus()) } if scenario == "own snapshot" { @@ -874,7 +874,7 @@ func TestCreateActor_GoldenTagDefault(t *testing.T) { if _, err := persistence.UpdateActor(ctx, resources.ActorRefFromActor(created), store.PreconditionFrom(created), func(db *ateapipb.Actor) error { db.Status.LastAssignedGeneration = 1 db.Status.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), uri.Name(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } return nil }); err != nil { diff --git a/cmd/ateapi/internal/controlapi/crash.go b/cmd/ateapi/internal/controlapi/crash.go index d25745e6d1..ee3bfc0988 100644 --- a/cmd/ateapi/internal/controlapi/crash.go +++ b/cmd/ateapi/internal/controlapi/crash.go @@ -37,6 +37,8 @@ import ( const ( crashMessageWorkerAssignmentMissing = "actor has no worker assignment in a state that requires one" crashMessageLocalSnapshotNodeUnknown = "node holding the actor's local snapshot is unknown" + crashMessageLocalSnapshotMissing = "actor has no local snapshot" + crashMessageDurableSnapshotMissing = "actor has no durable snapshot" crashMessageWorkerGone = "assigned worker no longer exists" crashMessageWorkerDraining = "assigned worker is draining" crashMessageWorkerReassigned = "assigned worker no longer hosts the actor" @@ -104,6 +106,18 @@ func handleAteletError(ctx context.Context, st crashActorStore, actorRef resourc return apierror.Internal("while calling atelet %s: %w", rpc, err) } +// crashActorAndFail crashes the actor for a workflow step that found its +// record in a state no retry can recover from, and returns the error the step +// fails with: FailedPrecondition once the crash is committed, since retrying +// the operation is pointless, or crashActor's own error if the crash did not +// commit, so the caller retries and the retry crashes the actor again. +func crashActorAndFail(ctx context.Context, st crashActorStore, actorRef resources.ActorRef, opName, message string) error { + if err := crashActor(ctx, st, actorRef, opName, message); err != nil { + return err + } + return apierror.FailedPrecondition("actor %s is CRASHED: %s", actorRef, message) +} + // crashActor moves the actor to CRASHED state and frees the worker it was // assigned to, if any, so the worker can host other actors. message is // recorded in the actor's status. diff --git a/cmd/ateapi/internal/controlapi/crash_test.go b/cmd/ateapi/internal/controlapi/crash_test.go index 7cf02bcece..67a06958c2 100644 --- a/cmd/ateapi/internal/controlapi/crash_test.go +++ b/cmd/ateapi/internal/controlapi/crash_test.go @@ -65,7 +65,7 @@ func seedActor(t *testing.T, ctx context.Context, st store.Interface, actorRef r }, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "gs://bucket/atespaces/as/actors/uid/snapshots/reserved-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "reserved-snapshot", "gs://bucket/atespaces/as/actors/uid/snapshots/reserved-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, }, }) @@ -112,7 +112,7 @@ func seedUnboundActor(t *testing.T, ctx context.Context, st store.Interface, act State: ateapipb.ActorState_ACTOR_STATE_RUNNING, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "gs://bucket/atespaces/as/actors/uid/snapshots/reserved-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "reserved-snapshot", "gs://bucket/atespaces/as/actors/uid/snapshots/reserved-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, }, }) @@ -130,7 +130,7 @@ func assertCrashed(t *testing.T, ctx context.Context, st store.Interface, actorR t.Errorf("status = %v, want %v", got.GetStatus().GetState(), ateapipb.ActorState_ACTOR_STATE_CRASHED) } // Keep the snapshot uri for debugging. - if _, st := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); st == nil { + if findLatestDurableSnapshot(got.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) == nil { t.Error(`in-progress durable snapshot URI = "", want preserved`) } if got.GetStatus().GetWorkerAssignment() != nil { @@ -722,6 +722,51 @@ func TestCrashActorReleaseFailureLeavesWorkerReclaimable(t *testing.T) { } } +// TestCrashActorAndFail verifies the error a workflow step fails with when it +// crashes the actor: FailedPrecondition once the crash commits, since no retry +// can finish the step, but the crash's own error when it does not, leaving the +// actor as it was so the caller retries and the retry crashes it. +func TestCrashActorAndFail(t *testing.T) { + ctx := context.Background() + actorRef := resources.ActorRef{Atespace: "team-a", Name: "actor-1"} + + t.Run("committed crash is FailedPrecondition", func(t *testing.T) { + st, cleanup := storetest.SetupTestStore(t) + defer cleanup() + seedActor(t, ctx, st, actorRef) + seedWorker(t, ctx, st, actorRef) + + err := crashActorAndFail(ctx, st, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotMissing) + if got := apierror.Code(err); got != codes.FailedPrecondition { + t.Fatalf("apierror.Code(err) = %v, want %v (err: %v)", got, codes.FailedPrecondition, err) + } + assertCrashed(t, ctx, st, actorRef) + }) + + t.Run("failed crash returns its error", func(t *testing.T) { + st, cleanup := storetest.SetupTestStore(t) + defer cleanup() + seedActor(t, ctx, st, actorRef) + seedWorker(t, ctx, st, actorRef) + + releaseErr := errors.New("state store unavailable") + err := crashActorAndFail(ctx, failingReleaseStore{Interface: st, err: releaseErr}, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotMissing) + if !errors.Is(err, releaseErr) { + t.Fatalf("crashActorAndFail() = %v, want it to wrap %v", err, releaseErr) + } + if got := apierror.Code(err); got == codes.FailedPrecondition { + t.Errorf("apierror.Code(err) = %v for a crash that did not commit, want a code the caller retries", got) + } + got, gerr := st.GetActor(ctx, actorRef) + if gerr != nil { + t.Fatalf("GetActor() = %v, want nil", gerr) + } + if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_RUNNING { + t.Errorf("status = %v, want %v (the crash did not commit)", got.GetStatus().GetState(), ateapipb.ActorState_ACTOR_STATE_RUNNING) + } + }) +} + // crashRecords captures the "Actor crashed" records a crash emits, so a test can // assert the identity that ate.actor.crashes is barred from carrying. crashActor // logs through the slog default, so this swaps it and the caller cannot be parallel. diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go index 1d83e56437..697107cd24 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_template_test.go @@ -181,8 +181,8 @@ func TestGoldenTagLifecycle(t *testing.T) { if err != nil { t.Fatal(err) } - if !parsed.OwnedBy(resources.TagSnapshotOwner(resources.GoldenActorAtespace, parsed.Name())) { - t.Fatal("golden snapshot is not tag-owned") + if !parsed.OwnedBy(resources.TagSnapshotOwner(resources.GoldenActorAtespace, golden.GetMetadata().GetUid())) { + t.Fatalf("golden snapshot %q is not under the golden tag's own prefix", uri) } assertSnapshotPresent(t, tc, uri) _, err = tc.client.GetActor(ctx, &ateapipb.GetActorRequest{Actor: goldenRef}) diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go index 4e56ab6816..49f4f64b4d 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go @@ -81,7 +81,7 @@ func TestCreateActor_Success(t *testing.T) { State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotURI(t)), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), }, }, WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "free"}}, @@ -297,7 +297,7 @@ func TestCreateActor_RejectsDifferentTemplateForDataSnapshot(t *testing.T) { createTemplateWithSelector(t, tc, "tmpl2", nil) seedTag(t, tc, "data-source", "data-snapshot", func(tag *ateapipb.Tag) { - tag.Status.Snapshot.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + tag.Status.Snapshot.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES tag.Status.ActorTemplateUid = tmpl.GetMetadata().GetUid() }) @@ -370,7 +370,7 @@ func TestCreateActor_PendingTag(t *testing.T) { // Simulates a tag creation that failed while writing the snapshot to // external storage. pending := seedTag(t, tc, "pending-source", "pending", func(tag *ateapipb.Tag) { - tag.Status.Snapshot.Storage[0].Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS + tag.Status.Snapshot.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING tag.Status.ActorTemplateUid = tmpl.GetMetadata().GetUid() }) tagRef := &ateapipb.ObjectRef{Atespace: testAtespace, Name: "pending"} @@ -384,11 +384,11 @@ func TestCreateActor_PendingTag(t *testing.T) { assertGrpcError(t, err, codes.FailedPrecondition, "source Tag is still being created or failed creation") // Finishing the tag creation, so now the tag is qualified to be a tag source. - snapshotURI := pending.GetStatus().GetSnapshot().GetStorage()[0].GetObject().GetSnapshotUri() + snapshotURI := pending.GetStatus().GetSnapshot().GetDurableSnapshot().GetObject().GetSnapshotUri() if _, err := tc.persistence.UpdateTag(ctx, resources.TagRefFromTag(pending), store.PreconditionFrom(pending), func(toUpdate *ateapipb.Tag) error { - toUpdate.Status.Snapshot.Storage[0].Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED + toUpdate.Status.Snapshot.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED return nil }); err != nil { t.Fatalf("finalizing the tag: %v", err) @@ -698,7 +698,7 @@ func TestUpdateActor_Success(t *testing.T) { State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotURI(t)), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), }, }, WorkerSelector: &ateapipb.Selector{ @@ -847,7 +847,7 @@ func TestUpdateActor(t *testing.T) { State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotURI(t)), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), }, }, WorkerSelector: &ateapipb.Selector{ @@ -1119,11 +1119,11 @@ func TestDeleteActor_MissingSnapshotBucket(t *testing.T) { } if _, err := tc.persistence.UpdateActor(ctx, resources.ActorRefFromActor(actor), store.PreconditionFrom(actor), func(toUpdate *ateapipb.Actor) error { toUpdate.Status.State = state - snap := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", uri.String()) + snap := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", uri.Name(), uri.String()) if state != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { // A failed upload leaves only the in-progress URI, even // when a prior deletion already marked the actor DELETING. - snap.Storage[0].Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS + snap.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING } toUpdate.Status.Snapshots = []*ateapipb.Snapshot{snap} return nil @@ -2911,7 +2911,7 @@ func TestResumeActor(t *testing.T) { AssignedNode: "node1", LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotURI(t)), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), }, WorkerAssignment: &ateapipb.WorkerAssignment{ Worker: &ateapipb.ObjectRef{Name: podUID}, @@ -3068,7 +3068,7 @@ func createDataFidelityTemplate(t *testing.T, tc *testContext, ns string) *ateap SourceActor: &ateapipb.ObjectRef{Atespace: resources.GoldenActorAtespace, Name: created.GetMetadata().GetUid()}, Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED, Status: &ateapipb.TagStatus{ - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, created.GetMetadata().GetUid(), goldenSnapshotURI(t)), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, created.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), ActorTemplateUid: created.GetMetadata().GetUid(), }, }) @@ -3640,12 +3640,13 @@ func TestSuspendActor(t *testing.T) { assertSnapshotPresent(t, tc, snapshotURI) assertSnapshotPresent(t, tc, taggedSnapshotURI) + wantTagSnap := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, durableSnapshot(sourceActor.GetStatus()).GetFidelity(), "", snapshotName(t, taggedSnapshotURI), taggedSnapshotURI) wantTag := &ateapipb.Tag{ Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName}, Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE, SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: name}, Status: &ateapipb.TagStatus{ - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, durableSnapshotStorage(sourceActor.GetStatus()).GetFidelity(), "", taggedSnapshotURI), + Snapshot: wantTagSnap, ActorTemplateUid: tmpl.GetMetadata().GetUid(), }, } @@ -3743,6 +3744,7 @@ func TestSuspendActor(t *testing.T) { if err != nil { t.Fatalf("GetActor failed: %v", err) } + wantActorSnap := newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, durableSnapshot(sourceActor.GetStatus()).GetFidelity(), tmpl.GetMetadata().GetUid(), snapshotName(t, snapshotURI), snapshotURI) want := &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Name: name, Atespace: testAtespace}, ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"}, @@ -3750,7 +3752,7 @@ func TestSuspendActor(t *testing.T) { State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED, LastAssignedGeneration: 2, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, durableSnapshotStorage(sourceActor.GetStatus()).GetFidelity(), tmpl.GetMetadata().GetUid(), snapshotURI), + wantActorSnap, }, }, } @@ -4034,6 +4036,10 @@ func TestPauseActor(t *testing.T) { if err != nil { t.Fatalf("GetActor failed: %v", err) } + ls := localSnapshot(getResp.GetStatus()) + if ls.GetUuid() == "" { + t.Error("Snapshot.Uuid is empty, want the name the pause checkpointed under") + } want := &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Name: name, Atespace: testAtespace}, ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"}, @@ -4042,8 +4048,8 @@ func TestPauseActor(t *testing.T) { AssignedNode: "node1", LastAssignedGeneration: 2, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotURI(t)), - newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), "ignored"), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), + newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmpl.GetMetadata().GetUid(), "ignored", "node1"), }, }, } @@ -4053,13 +4059,10 @@ func TestPauseActor(t *testing.T) { ignoreUID, ignoreVersion, ignoreTimestamps, - protocmp.IgnoreFields(&ateapipb.LocalSnapshot{}, "snapshot_name"), + protocmp.IgnoreFields(&ateapipb.Snapshot{}, "uuid"), ); diff != "" { t.Errorf("GetActor response mismatch (-want +got):\n%s", diff) } - if _, ls := localSnapshot(getResp.GetStatus()); ls.GetSnapshotName() == "" { - t.Error("LocalSnapshot.SnapshotName is empty, want the name the pause checkpointed under") - } } // TestResumeActor_PausedLocalSnapshotMissing_Crashes tests that if the local checkpoint @@ -4106,8 +4109,8 @@ func TestResumeActor_PausedLocalSnapshotMissing_Crashes(t *testing.T) { if getResp.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_PAUSED { t.Fatalf("actor state = %v, want ACTOR_STATE_PAUSED", getResp.GetStatus().GetState()) } - if _, ls := localSnapshot(getResp.GetStatus()); ls == nil { - t.Fatal("expected LocalSnapshot to be present on paused actor") + if ls := localSnapshot(getResp.GetStatus()); ls == nil { + t.Fatal("expected local snapshot to be present on paused actor") } waitForWorkerAvailable(t, tc, workerName) @@ -4731,10 +4734,16 @@ func TestSuspendActor_FromPaused(t *testing.T) { t.Error("atelet Checkpoint called for a paused actor; there is no workload to checkpoint") } upload := tc.fakeAtelet.UploadRequest - _, pausedLS := localSnapshot(paused.GetStatus()) - if got, want := upload.GetLocalSnapshotName(), pausedLS.GetSnapshotName(); got != want { + pausedLS := localSnapshot(paused.GetStatus()) + if pausedLS == nil { + t.Fatal("paused actor has no local snapshot") + } + if got, want := upload.GetLocalSnapshotName(), pausedLS.GetUuid(); got != want { t.Errorf("upload local_snapshot_name = %q, want the pause snapshot %q", got, want) } + if got, want := snapshotName(t, upload.GetDestinationSnapshotUri()), pausedLS.GetUuid(); got != want { + t.Errorf("upload destination snapshot name = %q, want the pause snapshot uuid %q", got, want) + } if got, want := upload.GetAtespace(), testAtespace; got != want { t.Errorf("upload atespace = %q, want %q", got, want) } @@ -4746,13 +4755,18 @@ func TestSuspendActor_FromPaused(t *testing.T) { if actor.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { t.Errorf("state = %v, want SUSPENDED", actor.GetStatus().GetState()) } - if _, ls := localSnapshot(actor.GetStatus()); ls != nil { - t.Errorf("LocalSnapshot = %v, want cleared (node pinning must not survive suspend)", ls) + if ls := localSnapshot(actor.GetStatus()); ls != nil { + t.Errorf("local snapshot = %v, want cleared (node pinning must not survive suspend)", ls) } if got, want := durableSnapshotURI(actor.GetStatus()), upload.GetDestinationSnapshotUri(); got != want { t.Errorf("snapshot URI = %q, want the upload destination %q", got, want) } - if got := durableSnapshotStorage(actor.GetStatus()).GetFidelity(); got != ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY { + // The suspend promotes the pause snapshot rather than taking a new one. + durable := durableSnapshot(actor.GetStatus()) + if durable.GetGeneration() != pausedLS.GetGeneration() || durable.GetUuid() != pausedLS.GetUuid() { + t.Errorf("durable snapshot = generation %d, uuid %q; want the pause snapshot's generation %d, uuid %q", durable.GetGeneration(), durable.GetUuid(), pausedLS.GetGeneration(), pausedLS.GetUuid()) + } + if got := durable.GetFidelity(); got != ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY { t.Errorf("snapshot Fidelity = %v, want FULL", got) } } @@ -4985,8 +4999,8 @@ func TestResumeActor_RelocatesAfterSuspendFromPaused(t *testing.T) { if got := suspended.GetActor().GetStatus().GetState(); got != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { t.Fatalf("state after suspend = %v, want SUSPENDED", got) } - if _, ls := localSnapshot(suspended.GetActor().GetStatus()); ls != nil { - t.Fatalf("LocalSnapshot = %v, want cleared so the actor can be scheduled anywhere", ls) + if ls := localSnapshot(suspended.GetActor().GetStatus()); ls != nil { + t.Fatalf("local snapshot = %v, want cleared so the actor can be scheduled anywhere", ls) } // Resume should succeed now and the actor scheduled on node2. @@ -5394,7 +5408,7 @@ func TestRevertActor_FromPaused(t *testing.T) { t.Errorf("external snapshot = %q, want it untouched at %q", uri, snapshotURI) } assertSnapshotPresent(t, tc, snapshotURI) - if _, ls := localSnapshot(got); ls != nil { + if ls := localSnapshot(got); ls != nil { t.Errorf("local snapshot info = %v, want nil", ls) } if got.GetWorkerAssignment() != nil { diff --git a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go index 398e9a05f6..df476a3a90 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go @@ -341,10 +341,11 @@ func assertSnapshotCollected(t *testing.T, tc *testContext, snapshotURI string) } } +const goldenSnapshotName = "9c2f7b41-6d05-4e83-a1f7-3b8c0d5e2a94" + // goldenSnapshotURI is the snapshot owned by the test template's golden tag. func goldenSnapshotURI(t *testing.T) string { t.Helper() - const goldenSnapshotName = "9c2f7b41-6d05-4e83-a1f7-3b8c0d5e2a94" uri, err := resources.NewTagSnapshotURI(testStorageLocation, resources.GoldenActorAtespace, goldenSnapshotName) if err != nil { t.Fatalf("NewTagSnapshotURI: %v", err) @@ -374,6 +375,17 @@ func assertSnapshotOwnedByActor(t *testing.T, actor *ateapipb.Actor, snapshotURI } } +// snapshotName returns the name snapshotURI gives its snapshot in object +// storage, which the Snapshot's uuid must match. +func snapshotName(t *testing.T, snapshotURI string) string { + t.Helper() + uri, err := resources.ParseSnapshotURI(snapshotURI) + if err != nil { + t.Fatalf("ParseSnapshotURI(%q) = %v", snapshotURI, err) + } + return uri.Name() +} + func namespaceForTest(baseName string) string { return fmt.Sprintf("%s-%d", baseName, time.Now().UnixNano()) } @@ -459,7 +471,7 @@ func createTemplateWithContainersAndVolumes(t *testing.T, tc *testContext, ns st SourceActor: &ateapipb.ObjectRef{Atespace: resources.GoldenActorAtespace, Name: created.GetMetadata().GetUid()}, Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED, Status: &ateapipb.TagStatus{ - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, created.GetMetadata().GetUid(), goldenSnapshotURI(t)), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, created.GetMetadata().GetUid(), goldenSnapshotName, goldenSnapshotURI(t)), ActorTemplateUid: created.GetMetadata().GetUid(), }, }) @@ -486,44 +498,39 @@ func createTemplateWithContainersAndVolumes(t *testing.T, tc *testContext, ns st return updated } -func newDurableSnapshot(gen int32, owner ateapipb.SnapshotOwner, fidelity ateapipb.SnapshotFidelity, templateUID, uri string) *ateapipb.Snapshot { +func newDurableSnapshot(gen int32, owner ateapipb.SnapshotOwner, fidelity ateapipb.SnapshotFidelity, templateUID, uuid, uri string) *ateapipb.Snapshot { return &ateapipb.Snapshot{ + Uuid: uuid, Generation: gen, Owner: owner, + Fidelity: fidelity, ActorTemplateUid: templateUID, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: fidelity, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri}, - }}, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri}, + }, } } -func newLocalSnapshot(gen int32, fidelity ateapipb.SnapshotFidelity, templateUID, name string) *ateapipb.Snapshot { +func newLocalSnapshot(gen int32, fidelity ateapipb.SnapshotFidelity, templateUID, uuid, locality string) *ateapipb.Snapshot { return &ateapipb.Snapshot{ + Uuid: uuid, Generation: gen, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, + Fidelity: fidelity, ActorTemplateUid: templateUID, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: fidelity, - Local: &ateapipb.LocalSnapshot{SnapshotName: name}, - }}, + Locality: locality, } } func durableSnapshot(status *ateapipb.ActorStatus) *ateapipb.Snapshot { var best *ateapipb.Snapshot for _, snap := range status.GetSnapshots() { - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE && - st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && - st.GetObject().GetSnapshotUri() != "" { - if best == nil || snap.GetGeneration() > best.GetGeneration() { - best = snap - } + st := snap.GetDurableSnapshot() + if st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && + st.GetObject().GetSnapshotUri() != "" { + if best == nil || snap.GetGeneration() > best.GetGeneration() { + best = snap } } } @@ -531,39 +538,27 @@ func durableSnapshot(status *ateapipb.ActorStatus) *ateapipb.Snapshot { } func durableSnapshotStorage(status *ateapipb.ActorStatus) *ateapipb.SnapshotStorage { - snap := durableSnapshot(status) - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE { - return st - } - } - return nil + return durableSnapshot(status).GetDurableSnapshot() } func durableSnapshotURI(status *ateapipb.ActorStatus) string { return durableSnapshotStorage(status).GetObject().GetSnapshotUri() } -func localSnapshot(status *ateapipb.ActorStatus) (*ateapipb.Snapshot, *ateapipb.LocalSnapshot) { +func localSnapshot(status *ateapipb.ActorStatus) *ateapipb.Snapshot { + var best *ateapipb.Snapshot for _, snap := range status.GetSnapshots() { - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL && - st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && - st.GetLocal().GetSnapshotName() != "" { - return snap, st.GetLocal() + if snap.GetLocality() != "" && snap.GetUuid() != "" { + if best == nil || snap.GetGeneration() > best.GetGeneration() { + best = snap } } } - return nil, nil + return best } func tagSnapshotURI(tag *ateapipb.Tag) string { - for _, st := range tag.GetStatus().GetSnapshot().GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE { - return st.GetObject().GetSnapshotUri() - } - } - return "" + return tag.GetStatus().GetSnapshot().GetDurableSnapshot().GetObject().GetSnapshotUri() } // testPauseImage is the pause image the default test SandboxConfig carries; diff --git a/cmd/ateapi/internal/controlapi/functionaltest/tag_test.go b/cmd/ateapi/internal/controlapi/functionaltest/tag_test.go index 205a022cb0..54a268a0d8 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/tag_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/tag_test.go @@ -49,7 +49,7 @@ func seedTag(t *testing.T, tc *testContext, actorName, tagName string, opts ...f actor, err = tc.persistence.UpdateActor(ctx, resources.ActorRefFromActor(actor), store.PreconditionFrom(actor), func(toUpdate *ateapipb.Actor) error { toUpdate.Status.LastAssignedGeneration = 1 toUpdate.Status.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", actorSnapshotURI.String()), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", actorSnapshotURI.Name(), actorSnapshotURI.String()), } return nil }) @@ -65,16 +65,7 @@ func seedTag(t *testing.T, tc *testContext, actorName, tagName string, opts ...f Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE, SourceActor: resources.ActorRefFromActor(actor).ToObjectRef(), Status: &ateapipb.TagStatus{ - Snapshot: &ateapipb.Snapshot{ - Generation: 1, - Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: durableSnapshotStorage(actor.GetStatus()).GetFidelity(), - Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri.String()}, - }}, - }, + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, durableSnapshot(actor.GetStatus()).GetFidelity(), "", uri.Name(), uri.String()), }, } for _, opt := range opts { diff --git a/cmd/ateapi/internal/controlapi/snapshot_helpers.go b/cmd/ateapi/internal/controlapi/snapshot_helpers.go index e493914b49..362f08bede 100644 --- a/cmd/ateapi/internal/controlapi/snapshot_helpers.go +++ b/cmd/ateapi/internal/controlapi/snapshot_helpers.go @@ -15,8 +15,6 @@ package controlapi import ( - "math" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" ) @@ -40,137 +38,107 @@ func snapshotAtLatestGeneration(status *ateapipb.ActorStatus) *ateapipb.Snapshot return findSnapshotByGeneration(status.GetSnapshots(), status.GetLastAssignedGeneration()) } -// findSnapshotStorage returns the SnapshotStorage entry on snap with the given -// durability, or nil if none exists. -func findSnapshotStorage(snap *ateapipb.Snapshot, durability ateapipb.SnapshotDurability) *ateapipb.SnapshotStorage { - for _, st := range snap.GetStorage() { - if st.GetDurability() == durability { - return st +// latestSnapshot returns the highest-generation Snapshot on status that +// matches, or nil if none does. +func latestSnapshot(status *ateapipb.ActorStatus, matches func(*ateapipb.Snapshot) bool) *ateapipb.Snapshot { + var latest *ateapipb.Snapshot + for _, snap := range status.GetSnapshots() { + if matches(snap) && (latest == nil || snap.GetGeneration() >= latest.GetGeneration()) { + latest = snap } } - return nil + return latest } -// setSnapshotStorage replaces an existing SnapshotStorage entry on snap with -// the same durability, or appends entry if none exists. -func setSnapshotStorage(snap *ateapipb.Snapshot, entry *ateapipb.SnapshotStorage) { - for i, st := range snap.Storage { - if st.GetDurability() == entry.GetDurability() { - snap.Storage[i] = entry - return - } - } - snap.Storage = append(snap.Storage, entry) +// findLatestDurableSnapshot returns the highest-generation Snapshot on status +// whose DurableSnapshot matches storageStatus, or nil if none exists. +func findLatestDurableSnapshot(status *ateapipb.ActorStatus, storageStatus ateapipb.SnapshotStorageStatus) *ateapipb.Snapshot { + return latestSnapshot(status, func(snap *ateapipb.Snapshot) bool { + return snap.GetDurableSnapshot().GetStatus() == storageStatus + }) } -// removeSnapshotStorage removes any SnapshotStorage entry on snap with the -// given durability. -func removeSnapshotStorage(snap *ateapipb.Snapshot, durability ateapipb.SnapshotDurability) { - if snap == nil { - return - } - filtered := snap.Storage[:0] - for _, st := range snap.Storage { - if st.GetDurability() != durability { - filtered = append(filtered, st) - } - } - snap.Storage = filtered +// findLatestLocalSnapshot returns the highest-generation Snapshot on status +// that holds a completed local checkpoint (Locality != ""), or nil if none +// exists. +func findLatestLocalSnapshot(status *ateapipb.ActorStatus) *ateapipb.Snapshot { + return latestSnapshot(status, func(snap *ateapipb.Snapshot) bool { + return snap.GetLocality() != "" + }) } -// findLatestSnapshotStorage returns the highest-generation Snapshot on status -// that holds a SnapshotStorage entry matching durability and storageStatus, -// along with that SnapshotStorage entry, or (nil, nil) if none exists. -func findLatestSnapshotStorage(status *ateapipb.ActorStatus, durability ateapipb.SnapshotDurability, storageStatus ateapipb.SnapshotStorageStatus) (*ateapipb.Snapshot, *ateapipb.SnapshotStorage) { - var bestSnap *ateapipb.Snapshot - var bestStorage *ateapipb.SnapshotStorage +// clearLocalSnapshots clears Locality across all Snapshots in status.Snapshots +// without dropping PENDING durable snapshots (so DeleteActor can still collect +// an in-flight durable snapshot after releasing the worker). +func clearLocalSnapshots(status *ateapipb.ActorStatus) { for _, snap := range status.GetSnapshots() { - st := findSnapshotStorage(snap, durability) - if st.GetStatus() != storageStatus { - continue - } - if bestSnap == nil || snap.GetGeneration() >= bestSnap.GetGeneration() { - bestSnap = snap - bestStorage = st - } + snap.Locality = "" } - return bestSnap, bestStorage } -// removeSnapshotStorageEntries removes SnapshotStorage entries matching -// durability (and *storageStatus, if non-nil) from status.Snapshots, dropping -// any Snapshot left with no storage entries. +// pruneSnapshots retains only local, the Snapshot whose local checkpoint the +// actor keeps (nil if it keeps none), and the latest COMPLETED durable +// snapshot, dropping all other (stale PENDING or superseded COMPLETED) +// Snapshots. It clears Locality on every Snapshot other than local: it runs +// in finalize steps that follow an atelet call that deleted the actor's other +// local checkpoints from the node. Any older COMPLETED durable snapshots are +// expected to have already been garbage collected (or captured for cleanup) +// beforehand. // -// Called from ensureWorkerReleased during DeleteActor, ensureRevertedFinalized -// during revert, and ensureSuspendedFinalized during suspend. -func removeSnapshotStorageEntries(status *ateapipb.ActorStatus, durability ateapipb.SnapshotDurability, storageStatus *ateapipb.SnapshotStorageStatus) { - removeOlderSnapshotStorageEntries(status, durability, storageStatus, math.MaxInt32) -} - -// removeOlderSnapshotStorageEntries removes SnapshotStorage entries matching -// durability (and *storageStatus, if non-nil) from Snapshots in -// status.Snapshots with generation older than keepGen, dropping any Snapshot -// left with no storage entries. +// Dropping PENDING durable snapshots is safe because: +// - On pause finalization, pause is only reachable from RUNNING (a failed +// suspend cannot resume without reverting first, which discards any +// in-flight upload), so no uncleaned PENDING durable snapshot exists. +// - On revert finalization, ensureInProgressSnapshotDiscarded has already +// deleted any in-flight durable snapshot objects. +// - On suspend finalization, the latest snapshot has already been marked +// COMPLETED. // // status.LastAssignedGeneration is not decreased so that we can keep track of // stale snapshots that need to be cleaned up and discarded generation numbers // are never reused. -// -// Called from ensurePausedFinalized on pause success and -// ensureSuspendedFinalized during suspend. -func removeOlderSnapshotStorageEntries(status *ateapipb.ActorStatus, durability ateapipb.SnapshotDurability, storageStatus *ateapipb.SnapshotStorageStatus, keepGen int32) { +func pruneSnapshots(status *ateapipb.ActorStatus, local *ateapipb.Snapshot) { if status == nil { return } + latestDurable := findLatestDurableSnapshot(status, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) kept := status.Snapshots[:0] for _, snap := range status.Snapshots { - if snap.GetGeneration() < keepGen { - storage := snap.Storage[:0] - for _, st := range snap.Storage { - if st.GetDurability() == durability && - (storageStatus == nil || st.GetStatus() == *storageStatus) { - continue - } - storage = append(storage, st) - } - snap.Storage = storage + if snap != local { + snap.Locality = "" } - if len(snap.GetStorage()) > 0 { + if snap == local || snap == latestDurable { kept = append(kept, snap) } } status.Snapshots = kept } -// newDurableSnapshot constructs a Snapshot with a single DURABLE ObjectSnapshot -// storage entry. -func newDurableSnapshot(gen int32, owner ateapipb.SnapshotOwner, fidelity ateapipb.SnapshotFidelity, templateUID, uri string, storageStatus ateapipb.SnapshotStorageStatus) *ateapipb.Snapshot { +// newDurableSnapshot constructs a Snapshot with DurableSnapshot populated. +func newDurableSnapshot(gen int32, owner ateapipb.SnapshotOwner, fidelity ateapipb.SnapshotFidelity, templateUID, uuid, uri string, storageStatus ateapipb.SnapshotStorageStatus) *ateapipb.Snapshot { return &ateapipb.Snapshot{ + Uuid: uuid, Generation: gen, Owner: owner, + Fidelity: fidelity, ActorTemplateUid: templateUID, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: storageStatus, - Fidelity: fidelity, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri}, - }}, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: storageStatus, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri}, + }, } } -// newLocalSnapshot constructs an actor Snapshot with a single LOCAL -// LocalSnapshot storage entry. -func newLocalSnapshot(gen int32, fidelity ateapipb.SnapshotFidelity, templateUID, snapshotName string, storageStatus ateapipb.SnapshotStorageStatus) *ateapipb.Snapshot { +// newLocalSnapshot constructs an actor Snapshot for a local pause checkpoint +// with Uuid, Fidelity, and Locality. +func newLocalSnapshot(gen int32, fidelity ateapipb.SnapshotFidelity, templateUID, uuid, locality string) *ateapipb.Snapshot { return &ateapipb.Snapshot{ + Uuid: uuid, Generation: gen, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, + Fidelity: fidelity, ActorTemplateUid: templateUID, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, - Status: storageStatus, - Fidelity: fidelity, - Local: &ateapipb.LocalSnapshot{SnapshotName: snapshotName}, - }}, + Locality: locality, } } @@ -178,7 +146,7 @@ func newLocalSnapshot(gen int32, fidelity ateapipb.SnapshotFidelity, templateUID // storageStatus (or any status if storageStatus is UNSPECIFIED), or "" if none // exists. func tagDurableSnapshotURI(tag *ateapipb.Tag, storageStatus ateapipb.SnapshotStorageStatus) string { - st := findSnapshotStorage(tag.GetStatus().GetSnapshot(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE) + st := tag.GetStatus().GetSnapshot().GetDurableSnapshot() if storageStatus != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED && st.GetStatus() != storageStatus { return "" } diff --git a/cmd/ateapi/internal/controlapi/snapshot_helpers_test.go b/cmd/ateapi/internal/controlapi/snapshot_helpers_test.go index 0c00dc49ca..1a0a38d959 100644 --- a/cmd/ateapi/internal/controlapi/snapshot_helpers_test.go +++ b/cmd/ateapi/internal/controlapi/snapshot_helpers_test.go @@ -30,8 +30,8 @@ func TestFindSnapshotByGenerationAndSnapshotAtLatestGeneration(t *testing.T) { t.Errorf("findSnapshotByGeneration(nil, 1) = %v, want nil", got) } - s1 := newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - s2 := newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + s1 := newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-1", "node-1") + s2 := newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-2", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) status := &ateapipb.ActorStatus{ LastAssignedGeneration: 2, Snapshots: []*ateapipb.Snapshot{s1, s2}, @@ -48,159 +48,150 @@ func TestFindSnapshotByGenerationAndSnapshotAtLatestGeneration(t *testing.T) { } } -func TestSnapshotStorageAccessors(t *testing.T) { - if got := findSnapshotStorage(nil, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL); got != nil { - t.Errorf("findSnapshotStorage(nil) = %v, want nil", got) +func TestFindLatestDurableAndLocalSnapshots(t *testing.T) { + if snap := findLatestDurableSnapshot(nil, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); snap != nil { + t.Errorf("findLatestDurableSnapshot(nil) = %v, want nil", snap) + } + if snap := findLatestLocalSnapshot(nil); snap != nil { + t.Errorf("findLatestLocalSnapshot(nil) = %v, want nil", snap) } - // Should not panic on nil snapshot. - removeSnapshotStorage(nil, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL) - snap := newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-in-progress", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) + s1 := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + s2 := newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-2", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + s3 := newDurableSnapshot(3, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-3", "gs://b/snap-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + s4 := newLocalSnapshot(4, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-4", "node-1") - // Replace existing LOCAL entry. - completedLocal := &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Local: &ateapipb.LocalSnapshot{SnapshotName: "local-completed"}, - } - setSnapshotStorage(snap, completedLocal) - if len(snap.GetStorage()) != 1 { - t.Fatalf("len(snap.Storage) = %d, want 1", len(snap.GetStorage())) - } - if got := findSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL); got != completedLocal { - t.Errorf("findSnapshotStorage(LOCAL) = %v, want %v", got, completedLocal) + // Place s2 before s1 to verify generation comparison rather than slice order. + status := &ateapipb.ActorStatus{ + Snapshots: []*ateapipb.Snapshot{s2, s1, s3, s4}, } - // Append DURABLE entry alongside LOCAL. - durableEntry := &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://b/snap-1"}, - } - setSnapshotStorage(snap, durableEntry) - if len(snap.GetStorage()) != 2 { - t.Fatalf("len(snap.Storage) = %d, want 2", len(snap.GetStorage())) - } - if got := findSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE); got != durableEntry { - t.Errorf("findSnapshotStorage(DURABLE) = %v, want %v", got, durableEntry) + gotSnap := findLatestDurableSnapshot(status, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if gotSnap != s2 || gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != "gs://b/snap-2" { + t.Errorf("findLatestDurableSnapshot(COMPLETED) = %v, want gen 2 (gs://b/snap-2)", gotSnap) } - // Remove LOCAL and verify DURABLE remains. - removeSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL) - if got := findSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL); got != nil { - t.Errorf("findSnapshotStorage(LOCAL) after remove = %v, want nil", got) + gotSnap = findLatestDurableSnapshot(status, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + if gotSnap != s3 || gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != "gs://b/snap-3" { + t.Errorf("findLatestDurableSnapshot(PENDING) = %v, want gen 3 (gs://b/snap-3)", gotSnap) } - if got := findSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE); got != durableEntry { - t.Errorf("findSnapshotStorage(DURABLE) after removing LOCAL = %v, want %v", got, durableEntry) + + gotLocal := findLatestLocalSnapshot(status) + if gotLocal != s4 || gotLocal.GetUuid() != "local-4" || gotLocal.GetLocality() != "node-1" { + t.Errorf("findLatestLocalSnapshot() = %v, want %v", gotLocal, s4) } } -func TestFindLatestSnapshotStorage(t *testing.T) { - if snap, st := findLatestSnapshotStorage(nil, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); snap != nil || st != nil { - t.Errorf("findLatestSnapshotStorage(nil) = (%v, %v), want (nil, nil)", snap, st) - } +func TestSnapshotPruningHelpers(t *testing.T) { + // Should not panic on nil status. + clearLocalSnapshots(nil) + pruneSnapshots(nil, nil) - s1 := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - s2 := newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - s3 := newDurableSnapshot(3, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - s4 := newLocalSnapshot(4, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-4", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + t.Run("clearLocalSnapshots clears locality without dropping snapshots", func(t *testing.T) { + localGen1 := newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-1", "node-1") + localGen2 := newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", "node-2") + status := &ateapipb.ActorStatus{ + Snapshots: []*ateapipb.Snapshot{localGen1, localGen2}, + } - // Place s2 before s1 to verify generation comparison rather than slice order. - status := &ateapipb.ActorStatus{ - Snapshots: []*ateapipb.Snapshot{s2, s1, s3, s4}, - } + clearLocalSnapshots(status) - gotSnap, gotSt := findLatestSnapshotStorage(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if gotSnap != s2 || gotSt.GetObject().GetSnapshotUri() != "gs://b/snap-2" { - t.Errorf("findLatestSnapshotStorage(DURABLE, COMPLETED) = (gen %d, %v), want (gen 2, gs://b/snap-2)", gotSnap.GetGeneration(), gotSt) - } + want := []*ateapipb.Snapshot{ + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-1", ""), + newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", ""), + } + if diff := cmp.Diff(want, status.GetSnapshots(), protocmp.Transform()); diff != "" { + t.Errorf("status.Snapshots mismatch (-want +got):\n%s", diff) + } + }) - gotSnap, gotSt = findLatestSnapshotStorage(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - if gotSnap != s3 || gotSt.GetObject().GetSnapshotUri() != "gs://b/snap-3" { - t.Errorf("findLatestSnapshotStorage(DURABLE, IN_PROGRESS) = (gen %d, %v), want (gen 3, gs://b/snap-3)", gotSnap.GetGeneration(), gotSt) - } + t.Run("pause flow: pruneSnapshots keeps the latest COMPLETED durable and the new local snapshot", func(t *testing.T) { + completedGen1 := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + completedGen1.Locality = "node-1" + localGen2 := newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", "node-2") + localGen3 := newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", "") - gotSnap, gotSt = findLatestSnapshotStorage(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if gotSnap != s4 || gotSt.GetLocal().GetSnapshotName() != "local-4" { - t.Errorf("findLatestSnapshotStorage(LOCAL, COMPLETED) = (gen %d, %v), want (gen 4, local-4)", gotSnap.GetGeneration(), gotSt) - } + status := &ateapipb.ActorStatus{ + LastAssignedGeneration: 3, + Snapshots: []*ateapipb.Snapshot{completedGen1, localGen2, localGen3}, + } - gotSnap, gotSt = findLatestSnapshotStorage(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - if gotSnap != nil || gotSt != nil { - t.Errorf("findLatestSnapshotStorage(LOCAL, IN_PROGRESS) = (%v, %v), want (nil, nil)", gotSnap, gotSt) - } -} + localGen3.Locality = "node-3" + pruneSnapshots(status, localGen3) -func TestRemoveSnapshotStorageEntries(t *testing.T) { - // Should not panic on nil status. - removeSnapshotStorageEntries(nil, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil) - removeOlderSnapshotStorageEntries(nil, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil, 3) - - t.Run("removes older local snapshots while preserving keepGen, newer generations, and durable storage", func(t *testing.T) { - bothGen1 := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - setSnapshotStorage(bothGen1, &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Local: &ateapipb.LocalSnapshot{SnapshotName: "local-1"}, - }) - localGen2 := newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - localGen3 := newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - localGen4 := newLocalSnapshot(4, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-4", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) + want := []*ateapipb.Snapshot{ + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", "node-3"), + } + if diff := cmp.Diff(want, status.GetSnapshots(), protocmp.Transform()); diff != "" { + t.Errorf("status.Snapshots mismatch (-want +got):\n%s", diff) + } + if status.GetLastAssignedGeneration() != 3 { + t.Errorf("LastAssignedGeneration = %d, want 3", status.GetLastAssignedGeneration()) + } + }) + t.Run("pruneSnapshots drops a PENDING snapshot that still names a node", func(t *testing.T) { + pendingWithLocality := newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-2", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + pendingWithLocality.Locality = "node-1" + local := newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", "node-1") status := &ateapipb.ActorStatus{ - LastAssignedGeneration: 4, - Snapshots: []*ateapipb.Snapshot{bothGen1, localGen2, localGen3, localGen4}, + Snapshots: []*ateapipb.Snapshot{ + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + pendingWithLocality, + local, + }, } - removeOlderSnapshotStorageEntries(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil, 3) + pruneSnapshots(status, local) want := []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newLocalSnapshot(4, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-4", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", "node-1"), } if diff := cmp.Diff(want, status.GetSnapshots(), protocmp.Transform()); diff != "" { t.Errorf("status.Snapshots mismatch (-want +got):\n%s", diff) } - if status.GetLastAssignedGeneration() != 4 { - t.Errorf("LastAssignedGeneration = %d, want 4", status.GetLastAssignedGeneration()) + if pendingWithLocality.GetLocality() != "" { + t.Errorf("dropped PENDING snapshot Locality = %q, want cleared", pendingWithLocality.GetLocality()) } }) - t.Run("removes all matching snapshot storage entries across all generations", func(t *testing.T) { + t.Run("revert flow: pruneSnapshots drops all PENDING snapshots and retains latest COMPLETED", func(t *testing.T) { + completedWithLocality := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + completedWithLocality.Locality = "node-1" status := &ateapipb.ActorStatus{ Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + completedWithLocality, + newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", "node-2"), + newDurableSnapshot(3, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-3", "gs://b/snap-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, } - removeSnapshotStorageEntries(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil) + pruneSnapshots(status, nil) want := []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } if diff := cmp.Diff(want, status.GetSnapshots(), protocmp.Transform()); diff != "" { t.Errorf("status.Snapshots mismatch (-want +got):\n%s", diff) } }) - t.Run("filters by specific storageStatus", func(t *testing.T) { + t.Run("suspend flow: pruneSnapshots drops older COMPLETED and PENDING snapshots", func(t *testing.T) { status := &ateapipb.ActorStatus{ Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "local-2", "node-1"), + newDurableSnapshot(3, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-3", "gs://b/snap-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), }, } + status.Snapshots[2].Locality = "node-1" - removeSnapshotStorageEntries(status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, new(ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS)) + pruneSnapshots(status, nil) want := []*ateapipb.Snapshot{ - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/snap-2", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(3, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-3", "gs://b/snap-3", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } if diff := cmp.Diff(want, status.GetSnapshots(), protocmp.Transform()); diff != "" { t.Errorf("status.Snapshots mismatch (-want +got):\n%s", diff) @@ -213,18 +204,18 @@ func TestTagDurableSnapshotURI(t *testing.T) { t.Errorf("tagDurableSnapshotURI(nil) = %q, want empty", got) } - inProgressTag := &ateapipb.Tag{ + pendingTag := &ateapipb.Tag{ Status: &ateapipb.TagStatus{ - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "gs://b/tag-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-1", "uuid-1", "gs://b/tag-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, } - if got := tagDurableSnapshotURI(inProgressTag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); got != "" { - t.Errorf("tagDurableSnapshotURI(inProgressTag, COMPLETED) = %q, want empty", got) + if got := tagDurableSnapshotURI(pendingTag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); got != "" { + t.Errorf("tagDurableSnapshotURI(pendingTag, COMPLETED) = %q, want empty", got) } - if got := tagDurableSnapshotURI(inProgressTag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); got != "gs://b/tag-1" { - t.Errorf("tagDurableSnapshotURI(inProgressTag, IN_PROGRESS) = %q, want gs://b/tag-1", got) + if got := tagDurableSnapshotURI(pendingTag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); got != "gs://b/tag-1" { + t.Errorf("tagDurableSnapshotURI(pendingTag, PENDING) = %q, want gs://b/tag-1", got) } - if got := tagDurableSnapshotURI(inProgressTag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED); got != "gs://b/tag-1" { - t.Errorf("tagDurableSnapshotURI(inProgressTag, UNSPECIFIED) = %q, want gs://b/tag-1", got) + if got := tagDurableSnapshotURI(pendingTag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED); got != "gs://b/tag-1" { + t.Errorf("tagDurableSnapshotURI(pendingTag, UNSPECIFIED) = %q, want gs://b/tag-1", got) } } diff --git a/cmd/ateapi/internal/controlapi/tag_test.go b/cmd/ateapi/internal/controlapi/tag_test.go index 90b3779578..73cca12212 100644 --- a/cmd/ateapi/internal/controlapi/tag_test.go +++ b/cmd/ateapi/internal/controlapi/tag_test.go @@ -135,7 +135,7 @@ func TestUpdateTag(t *testing.T) { req: &ateapipb.Tag{ Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED, Status: &ateapipb.TagStatus{ - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, "", "gs://attacker/elsewhere", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, "", "elsewhere", "gs://attacker/elsewhere", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), ActorTemplateUid: "other-template-uid", }, }, @@ -251,7 +251,7 @@ func newTestSuspendedActor(t *testing.T, ctx context.Context, st store.Interface return mustUpdateActorStatus(t, ctx, st, actor, func(status *ateapipb.ActorStatus) { status.LastAssignedGeneration = 1 status.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", uri.Name(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) } @@ -266,24 +266,24 @@ func newTestTag(t *testing.T, name string, actor *ateapipb.Actor) *ateapipb.Tag if err != nil { t.Fatalf("NewTagSnapshotURI: %v", err) } - actorSnap, actorSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + actorSnap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) return &ateapipb.Tag{ Metadata: &ateapipb.ResourceMetadata{Atespace: atespace, Name: name}, Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE, SourceActor: resources.ActorRefFromActor(actor).ToObjectRef(), Status: &ateapipb.TagStatus{ - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, actorSt.GetFidelity(), actorSnap.GetActorTemplateUid(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, actorSnap.GetFidelity(), actorSnap.GetActorTemplateUid(), uri.Name(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), }, } } // newPendingTestTag builds the tag a create leaves behind when it dies between // reserving the name and finishing the copy: the row names the prefix it was -// writing into as IN_PROGRESS. +// writing into as PENDING. func newPendingTestTag(t *testing.T, name string, actor *ateapipb.Actor) *ateapipb.Tag { t.Helper() tag := newTestTag(t, name, actor) - tag.Status.Snapshot.Storage[0].Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS + tag.Status.Snapshot.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING return tag } diff --git a/cmd/ateapi/internal/controlapi/template_reconciler.go b/cmd/ateapi/internal/controlapi/template_reconciler.go index a550395c35..a1857d0f4d 100644 --- a/cmd/ateapi/internal/controlapi/template_reconciler.go +++ b/cmd/ateapi/internal/controlapi/template_reconciler.go @@ -291,7 +291,7 @@ func (r *ActorTemplateReconciler) reconcileOne(ctx context.Context, ref resource ateapipb.ActorState_ACTOR_STATE_SUSPENDED: // The golden actor was never resumed, or a previous resume didn't // finish; ResumeActor is reentrant from both. - if _, st := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); st != nil { + if findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) != nil { // Golden actors never start from a source snapshot, so an // existing snapshot means an earlier suspend completed // without being recorded. @@ -326,7 +326,7 @@ func (r *ActorTemplateReconciler) suspendActor(ctx context.Context, goldenRef *a // A crash during suspend is observed as CRASHED on the retry. return fmt.Errorf("while suspending golden actor: %w", err) } - if _, st := findLatestSnapshotStorage(resp.GetActor().GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); st == nil { + if findLatestDurableSnapshot(resp.GetActor().GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) == nil { return fmt.Errorf("suspending golden actor produced no external snapshot") } return nil diff --git a/cmd/ateapi/internal/controlapi/template_reconciler_test.go b/cmd/ateapi/internal/controlapi/template_reconciler_test.go index 14cc4facf3..531248b099 100644 --- a/cmd/ateapi/internal/controlapi/template_reconciler_test.go +++ b/cmd/ateapi/internal/controlapi/template_reconciler_test.go @@ -187,7 +187,7 @@ func (c *fakeGoldenControl) goldenActorStatus() *ateapipb.ActorStatus { if c.goldenSnapshot != "" { st.LastAssignedGeneration = 1 st.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, testTemplateUID, c.goldenSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, testTemplateUID, "golden-snapshot", c.goldenSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } } return st @@ -274,7 +274,7 @@ func (c *fakeGoldenControl) CreateTag(_ context.Context, req *ateapipb.CreateTag c.tag = proto.CloneOf(req.GetTag()) c.tag.Status = &ateapipb.TagStatus{ ActorTemplateUid: testTemplateUID, - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, testTemplateUID, c.goldenSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, testTemplateUID, "golden-snapshot", c.goldenSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } return proto.CloneOf(c.tag), nil } @@ -896,7 +896,7 @@ func TestReconcileOne_GoldenTagRecovery(t *testing.T) { Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED, Status: &ateapipb.TagStatus{ ActorTemplateUid: testTemplateUID, - Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, testTemplateUID, "gs://bucket/tag-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + Snapshot: newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, testTemplateUID, "tag-snapshot", "gs://bucket/tag-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), }, } incomplete := proto.CloneOf(completed) diff --git a/cmd/ateapi/internal/controlapi/workflow_delete.go b/cmd/ateapi/internal/controlapi/workflow_delete.go index 5a4337fdc5..ac787425c2 100644 --- a/cmd/ateapi/internal/controlapi/workflow_delete.go +++ b/cmd/ateapi/internal/controlapi/workflow_delete.go @@ -289,7 +289,7 @@ func (w *ActorWorkflow) ensureWorkerReleased(ctx context.Context, actorRef resou updatedActor, err := w.store.UpdateActor(ctx, actorRef, store.PreconditionFrom(latestActor), func(dbActor *ateapipb.Actor) error { if dbActor.Status != nil { - removeSnapshotStorageEntries(dbActor.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil) + clearLocalSnapshots(dbActor.Status) dbActor.Status.AssignedNode = "" dbActor.Status.WorkerAssignment = nil } @@ -397,8 +397,8 @@ func (w *ActorWorkflow) ensureExternalSnapshotsReleased(ctx context.Context, act // wrote anything. func actorSnapshotStoragePrefix(actor *ateapipb.Actor) (resources.StoragePrefix, error) { actorOwner := actorSnapshotOwner(actor) - if _, st := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); st != nil { - snapshotURI := st.GetObject().GetSnapshotUri() + if snap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); snap != nil { + snapshotURI := snap.GetDurableSnapshot().GetObject().GetSnapshotUri() uri, err := resources.ParseSnapshotURI(snapshotURI) if err != nil { return resources.StoragePrefix{}, fmt.Errorf("while parsing the external snapshot %q: %w", snapshotURI, err) @@ -413,8 +413,8 @@ func actorSnapshotStoragePrefix(actor *ateapipb.Actor) (resources.StoragePrefix, // Nothing of the actor's own is recorded. Unless a suspend died partway, // nothing was ever written under its prefix: the in-progress URI is // recorded before atelet uploads the first object. - _, inProgressSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - inProgress := inProgressSt.GetObject().GetSnapshotUri() + inProgressSnap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + inProgress := inProgressSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() if inProgress == "" { return resources.StoragePrefix{}, nil } diff --git a/cmd/ateapi/internal/controlapi/workflow_delete_test.go b/cmd/ateapi/internal/controlapi/workflow_delete_test.go index 214f0b3965..4b84fa6fa5 100644 --- a/cmd/ateapi/internal/controlapi/workflow_delete_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_delete_test.go @@ -276,8 +276,8 @@ func TestEnsureExternalSnapshotsReleased(t *testing.T) { actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 2 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, currentType, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", current.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", inFlight.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, currentType, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", current.Name(), current.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", inFlight.Name(), inFlight.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), } }) @@ -325,7 +325,7 @@ func TestEnsureExternalSnapshotsReleased_CollectsStrandedSnapshots(t *testing.T) actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", currentSnapshot.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", currentSnapshot.Name(), currentSnapshot.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) @@ -365,7 +365,7 @@ func TestDeleteActor_CollectsInFlightSnapshotWithoutTemplate(t *testing.T) { mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", inFlight.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", inFlight.Name(), inFlight.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), } }) @@ -398,7 +398,7 @@ func TestEnsureExternalSnapshotsReleased_DeletePrefixFailure(t *testing.T) { actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", current.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", current.Name(), current.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) @@ -495,7 +495,7 @@ func TestDeleteActor_CollectsSnapshotsAfterWorkerDelete(t *testing.T) { actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.Name(), previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) } @@ -507,8 +507,8 @@ func TestDeleteActor_CollectsSnapshotsAfterWorkerDelete(t *testing.T) { if err != nil { t.Fatalf("ensureMarkedSuspending: %v", err) } - _, freshSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - fresh := mustParseSnapshotURI(t, freshSt.GetObject().GetSnapshotUri()) + freshSnap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + fresh := mustParseSnapshotURI(t, freshSnap.GetDurableSnapshot().GetObject().GetSnapshotUri()) objects.PutSnapshot(t, fresh, "manifest.json") // The worker's pod goes away with the commit still outstanding, so diff --git a/cmd/ateapi/internal/controlapi/workflow_pause.go b/cmd/ateapi/internal/controlapi/workflow_pause.go index bac9a6e5ce..f48e68106b 100644 --- a/cmd/ateapi/internal/controlapi/workflow_pause.go +++ b/cmd/ateapi/internal/controlapi/workflow_pause.go @@ -130,7 +130,7 @@ func (w *ActorWorkflow) ensureMarkedPausing(ctx context.Context, actorRef resour return nil, apierror.FailedPrecondition("actors in atespace %q are golden actors, which cannot be paused", actorRef.Atespace) } - snapshotName := resources.NewSnapshotName() + snapshotUUID := resources.NewSnapshotName() storedActor, err := w.store.UpdateActor(ctx, actorRef, store.PreconditionFrom(actor), func(toUpdate *ateapipb.Actor) error { toUpdate.Status.State = ateapipb.ActorState_ACTOR_STATE_PAUSING // Increment last_assigned_generation for the new Pause request. @@ -141,8 +141,8 @@ func (w *ActorWorkflow) ensureMarkedPausing(ctx context.Context, actorRef resour gen, actorTemplate.GetSnapshotConfig().GetPreferredFidelity(), actorTemplate.GetMetadata().GetUid(), - snapshotName, - ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, + snapshotUUID, + "", )) return nil }) @@ -169,10 +169,7 @@ func (w *ActorWorkflow) ensureAteletPaused(ctx context.Context, actorRef resourc assignment := actor.GetStatus().GetWorkerAssignment() if assignment == nil { // Missing active worker pod reference in PAUSING state indicates corrupted store state. - if err := crashActor(ctx, w.store, actorRef, ateattr.OperationPause, crashMessageWorkerAssignmentMissing); err != nil { - slog.ErrorContext(ctx, "Failed to crash actor", slog.String("err", err.Error())) - } - return "", apierror.FailedPrecondition("CallAteletPause prerequisite not met for Actor: %s. No worker assignment", actorRef) + return "", crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationPause, crashMessageWorkerAssignmentMissing) } ateletConn, err := w.dialer.DialForAteletOnNode(assignment.GetNodeName()) @@ -189,7 +186,7 @@ func (w *ActorWorkflow) ensureAteletPaused(ctx context.Context, actorRef resourc // Checkpoint does not carry the sandbox config: atelet uses the version the // actor is currently running (recorded on-node at Run/Restore) and pins it // into the snapshot manifest. - _, inProgressLocal := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) + inProgressSnap := snapshotAtLatestGeneration(actor.GetStatus()) req := &ateletpb.CheckpointRequest{ WorkerPodUid: assignment.GetWorkerPodUid(), Atespace: actor.GetMetadata().GetAtespace(), @@ -200,7 +197,7 @@ func (w *ActorWorkflow) ensureAteletPaused(ctx context.Context, actorRef resourc Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL, Config: &ateletpb.CheckpointRequest_LocalConfig{ LocalConfig: &ateletpb.LocalCheckpointConfiguration{ - SnapshotName: inProgressLocal.GetLocal().GetSnapshotName(), + SnapshotName: inProgressSnap.GetUuid(), }, }, Fidelity: fidelityToAtelet(actorTemplate.GetSnapshotConfig().GetPreferredFidelity()), @@ -216,11 +213,13 @@ func (w *ActorWorkflow) ensureAteletPaused(ctx context.Context, actorRef resourc // ensurePausedFinalized releases the actor's worker (only when it is still // owned by this actor), records where the local snapshot lives, and commits -// PAUSED with the assignment cleared in a single update — or CRASHED when the -// worker's node name was lost, since a local snapshot on an unknown node can -// never be resumed. It re-reads the actor first so an out-of-band transition -// (e.g. the syncer crashing the actor after its worker died) is not -// overwritten: with no assignment left there is nothing to finalize. +// PAUSED with the assignment cleared in a single update. When the worker's +// node name was lost or the latest local snapshot entry is missing, the actor +// can never be resumed from local state, so it commits CRASHED instead and +// fails the step with FailedPrecondition. It re-reads the actor first so an +// out-of-band transition (e.g. the syncer crashing the actor after its worker +// died) is not overwritten: with no assignment left there is nothing to +// finalize. func (w *ActorWorkflow) ensurePausedFinalized(ctx context.Context, actorRef resources.ActorRef) (_ *ateapipb.Actor, err error) { ctx, done := stepSpan(ctx, "FinalizePaused") defer func() { err = done(err) }() @@ -232,6 +231,17 @@ func (w *ActorWorkflow) ensurePausedFinalized(ctx context.Context, actorRef reso // 1. Free the worker (if it hasn't been freed yet) if assignment := latestActor.GetStatus().GetWorkerAssignment(); assignment != nil { + if latestActor.GetStatus().GetAssignedNode() == "" { + // Without a node name we cannot record where the local snapshot lives, + // so the actor can never be resumed (the scheduler would search for a + // worker on an unknown node forever). Crash it instead of leaving it + // stuck in PAUSED. + return nil, crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationPause, crashMessageLocalSnapshotNodeUnknown) + } + if snapshotAtLatestGeneration(latestActor.GetStatus()) == nil { + return nil, crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationPause, crashMessageLocalSnapshotMissing) + } + worker, err := w.store.GetWorker(ctx, assignment.GetWorker().GetName()) if err != nil { if !errors.Is(err, store.ErrNotFound) { @@ -250,64 +260,25 @@ func (w *ActorWorkflow) ensurePausedFinalized(ctx context.Context, actorRef reso } } - // 2. Clear the actor's assignment, now that the worker is freed - latestActor, err = w.store.GetActor(ctx, actorRef) - if err != nil { - return nil, err - } - wasAlreadyCrashed := latestActor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_CRASHED - newState := ateapipb.ActorState_ACTOR_STATE_PAUSED - var crashStatus *ateapipb.ActorCrash - if latestActor.GetStatus().GetAssignedNode() == "" { - // Without a node name we cannot record where the local snapshot lives, - // so the actor can never be resumed (the scheduler would search for a - // worker on an unknown node forever). Crash it instead of leaving it - // stuck in PAUSED. - slog.LogAttrs(ctx, slog.LevelError, "Node name not found during finalize pause, crashing actor", - ateattr.ActorRefLogAttrs(actorRef)...) - newState = ateapipb.ActorState_ACTOR_STATE_CRASHED - crashStatus = newActorCrash(ateattr.OperationPause, crashMessageLocalSnapshotNodeUnknown) - } - sandboxClass := "" - if worker != nil { - sandboxClass = worker.GetSandboxClass() - } - // Snapshot crash attributes before pod and pool pointers are cleared below. - latestActor.Status.State = newState - crashAttrs := ateattr.ActorMetricAttributes(latestActor, sandboxClass, ateattr.OperationPause) - + // 2. Clear the actor's assignment, now that the worker is freed. The + // precondition pins the version checked above, so an out-of-band write + // since then fails the update instead of being overwritten. storedActor, err := w.store.UpdateActor(ctx, actorRef, store.PreconditionFrom(latestActor), func(toUpdate *ateapipb.Actor) error { - toUpdate.Status.State = newState - if newState == ateapipb.ActorState_ACTOR_STATE_CRASHED && !wasAlreadyCrashed { - toUpdate.Status.Crash = crashStatus - } - if newState != ateapipb.ActorState_ACTOR_STATE_CRASHED { - // Finalize the snapshot and move it from IN_PROGRESS to COMPLETED. - snap := snapshotAtLatestGeneration(toUpdate.Status) - if snap == nil { - return fmt.Errorf("actor %s has no latest snapshot", actorRef) - } - if inProgressSt := findSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL); inProgressSt.GetLocal().GetSnapshotName() != "" { - inProgressSt.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED - removeOlderSnapshotStorageEntries(toUpdate.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil, snap.GetGeneration()) - } - } + toUpdate.Status.State = ateapipb.ActorState_ACTOR_STATE_PAUSED + // Record the node locality of the local checkpoint and clear older local checkpoints. + snap := snapshotAtLatestGeneration(toUpdate.Status) + snap.Locality = toUpdate.GetStatus().GetAssignedNode() + pruneSnapshots(toUpdate.Status, snap) toUpdate.Status.WorkerAssignment = nil return nil }) - if err == nil && storedActor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_CRASHED && !wasAlreadyCrashed { - logActorCrashed(ctx, latestActor, ateattr.OperationPause) - recordActorCrash(ctx, crashAttrs) - } - if err == nil && storedActor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_PAUSED { - logActorStateChanged(ctx, storedActor, ateattr.OperationPause) - } if err != nil { if errors.Is(err, store.ErrVersionConflict) { return nil, apierror.Aborted("concurrent update conflict, please retry") } return nil, err } + logActorStateChanged(ctx, storedActor, ateattr.OperationPause) latestActor = storedActor } diff --git a/cmd/ateapi/internal/controlapi/workflow_pause_test.go b/cmd/ateapi/internal/controlapi/workflow_pause_test.go index b8ae87a248..1c182dd46b 100644 --- a/cmd/ateapi/internal/controlapi/workflow_pause_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_pause_test.go @@ -21,6 +21,7 @@ import ( "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest" "github.com/agent-substrate/substrate/internal/apierror" "github.com/agent-substrate/substrate/internal/ateattr" + "github.com/agent-substrate/substrate/internal/proto/ateletpb" "github.com/agent-substrate/substrate/internal/resources" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "google.golang.org/grpc/codes" @@ -32,7 +33,8 @@ import ( // // Current behavior: AssignedNode is left empty, and the actor is crashed // instead of left PAUSED, since a local snapshot with an unknown node can -// never be safely resumed. +// never be safely resumed. The step fails with FailedPrecondition rather than +// reporting the pause as done. func TestEnsurePausedFinalized_WorkerGone(t *testing.T) { st, cleanup := storetest.SetupTestStore(t) defer cleanup() @@ -52,7 +54,7 @@ func TestEnsurePausedFinalized_WorkerGone(t *testing.T) { }, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-snap-1", ""), }, }, } @@ -60,9 +62,8 @@ func TestEnsurePausedFinalized_WorkerGone(t *testing.T) { // Intentionally NOT creating the worker in store, simulates worker already gone. w := &ActorWorkflow{store: st} - finalized, err := w.ensurePausedFinalized(ctx, actorRef) - if err != nil { - t.Fatalf("ensurePausedFinalized: %v", err) + if _, err := w.ensurePausedFinalized(ctx, actorRef); apierror.Code(err) != codes.FailedPrecondition { + t.Fatalf("ensurePausedFinalized error = %v, want FailedPrecondition for the actor it crashed", err) } got, err := st.GetActor(ctx, actorRef) @@ -79,15 +80,11 @@ func TestEnsurePausedFinalized_WorkerGone(t *testing.T) { if got.GetStatus().GetAssignedNode() != "" { t.Errorf("AssignedNode = %q, want empty", got.GetStatus().GetAssignedNode()) } - if _, gotSt := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetLocal().GetSnapshotName() != "local-snap-1" { - t.Errorf("in-progress local snapshot name = %q, want %q preserved on crash", gotSt.GetLocal().GetSnapshotName(), "local-snap-1") - } - - if finalized.GetStatus().GetWorkerAssignment() != nil { - t.Error("returned actor still has a worker assignment, want it cleared") + if gotSnap := snapshotAtLatestGeneration(got.GetStatus()); gotSnap.GetUuid() != "local-snap-1" { + t.Errorf("in-progress local snapshot uuid = %q, want %q preserved on crash", gotSnap.GetUuid(), "local-snap-1") } - if finalized.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { - t.Errorf("returned state = %v, want CRASHED", finalized.GetStatus().GetState()) + if got.GetStatus().GetWorkerAssignment() != nil { + t.Error("stored actor still has a worker assignment, want it cleared") } // This site records the crash counter, so it must write the record too, or @@ -102,8 +99,8 @@ func TestEnsurePausedFinalized_WorkerGone(t *testing.T) { // TestEnsurePausedFinalized_AlreadyCrashed verifies that if the actor was // already crashed out-of-band when ensurePausedFinalized runs with no -// AssignedNode, its existing Crash status and snapshots are preserved and no -// duplicate crash log record is emitted. +// AssignedNode, its existing Crash status and snapshots are preserved, no +// duplicate crash log record is emitted, and the step still fails. func TestEnsurePausedFinalized_AlreadyCrashed(t *testing.T) { st, cleanup := storetest.SetupTestStore(t) defer cleanup() @@ -125,48 +122,101 @@ func TestEnsurePausedFinalized_AlreadyCrashed(t *testing.T) { }, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-snap-1", ""), }, }, } storetest.MustCreateActor(t, ctx, st, actor) w := &ActorWorkflow{store: st} - finalized, err := w.ensurePausedFinalized(ctx, actorRef) + if _, err := w.ensurePausedFinalized(ctx, actorRef); apierror.Code(err) != codes.FailedPrecondition { + t.Fatalf("ensurePausedFinalized error = %v, want FailedPrecondition for a CRASHED actor", err) + } + got, err := st.GetActor(ctx, actorRef) if err != nil { - t.Fatalf("ensurePausedFinalized: %v", err) + t.Fatalf("GetActor: %v", err) } - if finalized.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { - t.Errorf("state = %v, want CRASHED", finalized.GetStatus().GetState()) + if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { + t.Errorf("state = %v, want CRASHED", got.GetStatus().GetState()) } - if got, want := finalized.GetStatus().GetCrash().GetMessage(), originalCrash.GetMessage(); got != want { - t.Errorf("crash message = %q, want original %q preserved", got, want) + if msg, want := got.GetStatus().GetCrash().GetMessage(), originalCrash.GetMessage(); msg != want { + t.Errorf("crash message = %q, want original %q preserved", msg, want) } - if _, gotSt := findLatestSnapshotStorage(finalized.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetLocal().GetSnapshotName() != "local-snap-1" { - t.Errorf("in-progress local snapshot name = %q, want %q preserved", gotSt.GetLocal().GetSnapshotName(), "local-snap-1") + if gotSnap := snapshotAtLatestGeneration(got.GetStatus()); gotSnap.GetUuid() != "local-snap-1" { + t.Errorf("in-progress local snapshot uuid = %q, want %q preserved", gotSnap.GetUuid(), "local-snap-1") } - if finalized.GetStatus().GetWorkerAssignment() != nil { - t.Errorf("WorkerAssignment = %v, want nil", finalized.GetStatus().GetWorkerAssignment()) + if got.GetStatus().GetWorkerAssignment() != nil { + t.Errorf("WorkerAssignment = %v, want nil", got.GetStatus().GetWorkerAssignment()) } if len(*records) != 0 { t.Errorf("got %d crash records, want 0 for an actor that was already crashed", len(*records)) } } -// TestEnsurePausedFinalized_RecordsFidelity verifies pause marks the -// scope the pause checkpoint captures (the template's preferredFidelity) in -// SnapshotStorage and finalization promotes it to COMPLETED, so a later suspend -// or resume of the PAUSED actor knows what the local snapshot contains. +// TestEnsurePausedFinalized_KeepsCrashFromWorkerDelete verifies that a crash +// the DeleteWorker workflow commits while ensurePausedFinalized is freeing the +// worker is kept: the step's update is pinned to the actor version it checked, +// so it fails Aborted instead of committing PAUSED over the crash. +func TestEnsurePausedFinalized_KeepsCrashFromWorkerDelete(t *testing.T) { + st, cleanup := storetest.SetupTestStore(t) + defer cleanup() + ctx := context.Background() + actorRef := resources.ActorRef{Atespace: "team-a", Name: "actor-1"} + + workerName := testWorkerUID("worker-pod-1") + created := storetest.MustCreateActor(t, ctx, st, &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_RUNNING, + AssignedNode: "node1", + WorkerAssignment: &ateapipb.WorkerAssignment{ + Worker: &ateapipb.ObjectRef{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + }, + }, + }) + if _, err := st.CreateWorker(ctx, &ateapipb.Worker{ + Metadata: &ateapipb.ResourceMetadata{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + NodeName: "node1", + Status: &ateapipb.WorkerStatus{}, + }); err != nil { + t.Fatalf("CreateWorker: %v", err) + } + seedAssignment(t, st, workerName, &ateapipb.ActorAssignment{ + Actor: &ateapipb.ObjectRef{Atespace: actorRef.Atespace, Name: actorRef.Name}, + ActorUid: created.GetMetadata().GetUid(), + }) + if _, err := (&ActorWorkflow{store: st}).ensureMarkedPausing(ctx, actorRef, created, &ateapipb.ActorTemplate{}); err != nil { + t.Fatalf("ensureMarkedPausing: %v", err) + } + + w := &ActorWorkflow{store: workerDeletedDuringReleaseStore{Interface: st}} + _, err := w.ensurePausedFinalized(ctx, actorRef) + assertConcurrentCrashKept(t, ctx, st, actorRef, err, "pause failed: "+crashMessageWorkerPodGone) +} + +// TestEnsurePausedFinalized_RecordsFidelity verifies pause records the +// template's preferred_fidelity on the local Snapshot alongside Locality and +// leaves DurableSnapshot unset until the actor is suspended. func TestEnsurePausedFinalized_RecordsFidelity(t *testing.T) { tests := []struct { - name string - fidelity ateapipb.SnapshotFidelity - want ateapipb.SnapshotFidelity + name string + preferred ateapipb.SnapshotFidelity + want ateapipb.SnapshotFidelity }{ - {"volumes", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES}, - {"rootfs", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS}, - {"memory", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, + {"VOLUMES", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES}, + {"ROOTFS", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_ROOTFS}, + {"MEMORY", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, + {"UNSPECIFIED recorded as UNSPECIFIED", ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED}, } + for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { st, cleanup := storetest.SetupTestStore(t) @@ -207,7 +257,9 @@ func TestEnsurePausedFinalized_RecordsFidelity(t *testing.T) { w := &ActorWorkflow{store: st} tmpl := &ateapipb.ActorTemplate{ - SnapshotConfig: &ateapipb.SnapshotConfig{PreferredFidelity: tc.fidelity}, + SnapshotConfig: &ateapipb.SnapshotConfig{ + PreferredFidelity: tc.preferred, + }, } if _, err := w.ensureMarkedPausing(ctx, actorRef, created, tmpl); err != nil { t.Fatalf("ensureMarkedPausing: %v", err) @@ -220,9 +272,15 @@ func TestEnsurePausedFinalized_RecordsFidelity(t *testing.T) { if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_PAUSED { t.Fatalf("state = %v, want PAUSED", got.GetStatus().GetState()) } - _, localSt := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if scope := localSt.GetFidelity(); scope != tc.want { - t.Errorf("local SnapshotStorage.Fidelity = %v, want %v", scope, tc.want) + localSnap := findLatestLocalSnapshot(got.GetStatus()) + if localSnap.GetFidelity() != tc.want { + t.Errorf("local Snapshot.Fidelity = %v, want %v", localSnap.GetFidelity(), tc.want) + } + if localSnap.GetDurableSnapshot() != nil { + t.Errorf("local DurableSnapshot = %v, want nil", localSnap.GetDurableSnapshot()) + } + if localSnap.GetLocality() != "node1" { + t.Errorf("local Snapshot.Locality = %q, want %q", localSnap.GetLocality(), "node1") } if got.GetStatus().GetAssignedNode() != "node1" { t.Errorf("AssignedNode = %q, want %q", got.GetStatus().GetAssignedNode(), "node1") @@ -231,6 +289,72 @@ func TestEnsurePausedFinalized_RecordsFidelity(t *testing.T) { } } +// TestEnsurePausedFinalized_MissingLocalSnapshotClearsAssignment verifies that +// if the latest generation snapshot entry is missing when ensurePausedFinalized +// runs, the actor transitions to CRASHED and clears WorkerAssignment after +// releasing the worker rather than getting stuck in PAUSING, and the step +// fails. +func TestEnsurePausedFinalized_MissingLocalSnapshotClearsAssignment(t *testing.T) { + st, cleanup := storetest.SetupTestStore(t) + defer cleanup() + ctx := context.Background() + actorRef := resources.ActorRef{Atespace: "team-a", Name: "actor-1"} + records := crashRecords(t) + + workerName := testWorkerUID("worker-pod-1") + created := storetest.MustCreateActor(t, ctx, st, &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_PAUSING, + AssignedNode: "node1", + WorkerAssignment: &ateapipb.WorkerAssignment{ + Worker: &ateapipb.ObjectRef{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + }, + LastAssignedGeneration: 1, + }, + }) + if _, err := st.CreateWorker(ctx, &ateapipb.Worker{ + Metadata: &ateapipb.ResourceMetadata{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + NodeName: "node1", + Status: &ateapipb.WorkerStatus{}, + }); err != nil { + t.Fatalf("CreateWorker: %v", err) + } + seedAssignment(t, st, workerName, &ateapipb.ActorAssignment{ + Actor: &ateapipb.ObjectRef{Atespace: actorRef.Atespace, Name: actorRef.Name}, + ActorUid: created.GetMetadata().GetUid(), + }) + + w := &ActorWorkflow{store: st} + if _, err := w.ensurePausedFinalized(ctx, actorRef); apierror.Code(err) != codes.FailedPrecondition { + t.Fatalf("ensurePausedFinalized error = %v, want FailedPrecondition for the actor it crashed", err) + } + got, err := st.GetActor(ctx, actorRef) + if err != nil { + t.Fatalf("GetActor: %v", err) + } + if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { + t.Errorf("state = %v, want CRASHED", got.GetStatus().GetState()) + } + if msg, want := got.GetStatus().GetCrash().GetMessage(), "pause failed: "+crashMessageLocalSnapshotMissing; msg != want { + t.Errorf("crash message = %q, want %q", msg, want) + } + if got.GetStatus().GetWorkerAssignment() != nil { + t.Errorf("WorkerAssignment = %v, want nil", got.GetStatus().GetWorkerAssignment()) + } + if len(*records) != 1 { + t.Fatalf("got %d crash records, want 1", len(*records)) + } +} + // TestPauseActorWorkflow_RejectedAndIdempotentPaths covers the two // short-circuit paths of the pause workflow: rejection of the pause edge for // a non-RUNNING actor and the idempotent fast-forward for a PAUSED one. @@ -346,9 +470,9 @@ func TestEnsureAteletPaused_DialFailureLeavesActorRetryable(t *testing.T) { var snapshots []*ateapipb.Snapshot if tt.prevSnapshot != "" { - snapshots = append(snapshots, newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", tt.prevSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED)) + snapshots = append(snapshots, newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "prev-snapshot", tt.prevSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED)) } - snapshots = append(snapshots, newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "actor-1-never-written", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS)) + snapshots = append(snapshots, newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "actor-1-never-written", "")) actor := &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"}, @@ -378,11 +502,11 @@ func TestEnsureAteletPaused_DialFailureLeavesActorRetryable(t *testing.T) { if stored.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_PAUSING { t.Errorf("state = %v, want unchanged PAUSING", stored.GetStatus().GetState()) } - if _, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetLocal().GetSnapshotName() != "actor-1-never-written" { - t.Errorf("in-progress local snapshot name = %q, want preserved for debugging", gotSt.GetLocal().GetSnapshotName()) + if gotSnap := snapshotAtLatestGeneration(stored.GetStatus()); gotSnap.GetUuid() != "actor-1-never-written" { + t.Errorf("in-progress local snapshot uuid = %q, want preserved for debugging", gotSnap.GetUuid()) } - _, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if got := gotSt.GetObject().GetSnapshotUri(); got != tt.prevSnapshot { + gotSnap := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != tt.prevSnapshot { t.Errorf("SnapshotUri = %q, want %q", got, tt.prevSnapshot) } }) @@ -433,3 +557,62 @@ func TestEnsureMarkedPausing_GoldenAtespaceRejected(t *testing.T) { t.Fatalf("apierror.Code = %v (err %v), want FailedPrecondition", got, err) } } + +// TestEnsureAteletPaused_ForwardsPreferredFidelity verifies that local pause +// checkpoints forward the template's PreferredFidelity to atelet. +func TestEnsureAteletPaused_ForwardsPreferredFidelity(t *testing.T) { + tests := []struct { + preferred ateapipb.SnapshotFidelity + want ateletpb.SnapshotFidelity + }{ + {ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY}, + {ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES}, + {ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED, ateletpb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED}, + } + for _, tc := range tests { + t.Run(tc.preferred.String(), func(t *testing.T) { + ctx := context.Background() + persistence := newTestPersistence(t) + w, atelet := newWireCaptureWorkflow(t, persistence) + + actorRef := resources.ActorRef{Atespace: "team-a", Name: "actor-1"} + created := storetest.MustCreateActor(t, ctx, persistence, &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name}, + ActorTemplate: &ateapipb.ObjectRef{Atespace: "ns", Name: "tmpl1"}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_PAUSING, + AssignedNode: "node-1", + WorkerAssignment: wireTestAssignment(), + LastAssignedGeneration: 1, + Snapshots: []*ateapipb.Snapshot{ + newLocalSnapshot(1, tc.preferred, "", "local-snap-1", ""), + }, + }, + }) + tmpl := &ateapipb.ActorTemplate{ + Metadata: &ateapipb.ResourceMetadata{Atespace: "ns", Name: "tmpl1"}, + SnapshotConfig: &ateapipb.SnapshotConfig{ + PreferredFidelity: tc.preferred, + }, + } + + wireFidelity, err := w.ensureAteletPaused(ctx, actorRef, created, tmpl) + if err != nil { + t.Fatalf("ensureAteletPaused: %v", err) + } + if want := ateattr.SnapshotFidelityValue(tc.want); wireFidelity != want { + t.Errorf("wireFidelity = %q, want %q", wireFidelity, want) + } + req := atelet.checkpointRequest() + if req == nil { + t.Fatal("expected Checkpoint RPC to be called") + } + if got := req.GetFidelity(); got != tc.want { + t.Errorf("CheckpointRequest.Fidelity = %v, want %v", got, tc.want) + } + if got := req.GetLocalConfig().GetSnapshotName(); got != "local-snap-1" { + t.Errorf("CheckpointRequest.LocalConfig.SnapshotName = %q, want %q", got, "local-snap-1") + } + }) + } +} diff --git a/cmd/ateapi/internal/controlapi/workflow_reconcile_assignments_test.go b/cmd/ateapi/internal/controlapi/workflow_reconcile_assignments_test.go index 5a59ab01e0..c25d81d086 100644 --- a/cmd/ateapi/internal/controlapi/workflow_reconcile_assignments_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_reconcile_assignments_test.go @@ -179,7 +179,7 @@ func TestReconcileAssignments_CrashesEarlierActors(t *testing.T) { actor := seedAPIActor(t, ctx, persistence, ateapipb.ActorState_ACTOR_STATE_RUNNING, func(a *ateapipb.Actor) { a.Status.LastAssignedGeneration = 1 a.Status.Snapshots = []*ateapipb.Snapshot{ - newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "partial-local-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "partial-local-snapshot", ""), } }) assignAPIWorker(t, ctx, persistence, apiWorkerName, actor.GetMetadata().GetUid()) @@ -207,8 +207,8 @@ func TestReconcileAssignments_CrashesEarlierActors(t *testing.T) { if got.GetStatus().GetWorkerAssignment() != nil { t.Errorf("actor worker assignment = %v, want it cleared", got.GetStatus().GetWorkerAssignment()) } - if _, gotSt := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetLocal().GetSnapshotName() != "partial-local-snapshot" { - t.Errorf("in-progress local checkpoint = %q, want %q preserved", gotSt.GetLocal().GetSnapshotName(), "partial-local-snapshot") + if gotSnap := snapshotAtLatestGeneration(got.GetStatus()); gotSnap.GetUuid() != "partial-local-snapshot" { + t.Errorf("in-progress local checkpoint = %q, want %q preserved", gotSnap.GetUuid(), "partial-local-snapshot") } if a := firstAssignment(t, persistence, apiWorkerName); a != nil { t.Errorf("worker still hosts %v, want the assignment released", a) diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index b9bfbff09f..3ee70ac6c3 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -138,11 +138,10 @@ func (w *ActorWorkflow) ResumeActor(ctx context.Context, actorRef resources.Acto // whose record drifted — surface a clear error instead of shipping a restore // request atelet would reject (or that would boot an empty guest). func validateGoldenSnapshotFidelity(snapshot *ateapipb.Snapshot) error { - st := findSnapshotStorage(snapshot, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE) - if fidelity := st.GetFidelity(); fidelity != ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY { + if fidelity := snapshot.GetFidelity(); fidelity != ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY { return apierror.FailedPrecondition( "ActorTemplate golden snapshot %q was taken with fidelity %s, not MEMORY; regenerate the golden snapshot", - st.GetObject().GetSnapshotUri(), fidelity) + snapshot.GetDurableSnapshot().GetObject().GetSnapshotUri(), fidelity) } return nil } @@ -173,11 +172,12 @@ func (w *ActorWorkflow) loadActorForResume(ctx context.Context, actorRef resourc if err != nil { return nil, nil, src, err } - if snap, st := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); st != nil { + if snap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); snap != nil { + st := snap.GetDurableSnapshot() if src.SnapshotURI, err = resources.ParseSnapshotURI(st.GetObject().GetSnapshotUri()); err != nil { return nil, nil, src, apierror.DataLoss("Actor %s external snapshot: %v", actorRef, err) } - src.Fidelity = st.GetFidelity() + src.Fidelity = snap.GetFidelity() capturedUnder := snap.GetActorTemplateUid() src.TemplateReplaced = capturedUnder != "" && capturedUnder != actorTemplate.GetMetadata().GetUid() } @@ -695,7 +695,7 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou return tele, fmt.Errorf("while resolving sandbox assets: %w", err) } - if _, localSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); localSt != nil { + if localSnap := findLatestLocalSnapshot(actor.GetStatus()); localSnap != nil { slog.InfoContext(ctx, "Actor has snapshot; Restoring from snapshot") tele.SnapshotKind = ateattr.SnapshotKindLocal @@ -714,9 +714,9 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou } req.Type = ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL req.Config = &ateletpb.RestoreRequest_LocalConfig{ - LocalConfig: &ateletpb.LocalCheckpointConfiguration{SnapshotName: localSt.GetLocal().GetSnapshotName()}, + LocalConfig: &ateletpb.LocalCheckpointConfiguration{SnapshotName: localSnap.GetUuid()}, } - req.Fidelity = fidelityToAtelet(localSt.GetFidelity()) + req.Fidelity = fidelityToAtelet(localSnap.GetFidelity()) tele.WireFidelity = ateattr.SnapshotFidelityValue(req.Fidelity) if _, err = client.Restore(ctx, req); err != nil { diff --git a/cmd/ateapi/internal/controlapi/workflow_resume_test.go b/cmd/ateapi/internal/controlapi/workflow_resume_test.go index 1d5a7e13a0..0c4f00bfdc 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume_test.go @@ -112,14 +112,23 @@ func TestResumeActor_RunningFastPathDoesNotAcquireLease(t *testing.T) { } // TestFinalizeRunning_CommitsRunning verifies the last step of a resume moves -// the actor out of RESUMING against a freshly read version. +// the actor out of RESUMING against a freshly read version, and keeps the +// locality of the local snapshot it restored from: resume does not delete the +// snapshot's files from the node. func TestFinalizeRunning_CommitsRunning(t *testing.T) { ctx := context.Background() persistence := newTestPersistence(t) storetest.MustCreateActor(t, ctx, persistence, &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "id1"}, ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "tmpl-2"}, - Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RESUMING}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_RESUMING, + AssignedNode: "node-1", + LastAssignedGeneration: 1, + Snapshots: []*ateapipb.Snapshot{ + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-1", "node-1"), + }, + }, }) w := &ActorWorkflow{store: persistence} @@ -130,6 +139,9 @@ func TestFinalizeRunning_CommitsRunning(t *testing.T) { if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_RUNNING { t.Errorf("state = %v, want RUNNING", got.GetStatus().GetState()) } + if locality := findLatestLocalSnapshot(got.GetStatus()).GetLocality(); locality != "node-1" { + t.Errorf("local snapshot locality = %q, want %q kept after resume", locality, "node-1") + } } // bindErrorStore fails every claim, standing in for a worker that moved or @@ -971,7 +983,7 @@ func TestLoadActorForResume_TemplateReplaced(t *testing.T) { seedOpts = append(seedOpts, func(a *ateapipb.Actor) { a.Status.LastAssignedGeneration = 1 a.Status.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, resolve(tt.snapshotUID), someActorSnapshotURI(t, testStorageLocation, actorRef.Atespace, "snap-1"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, resolve(tt.snapshotUID), "snap-1", someActorSnapshotURI(t, testStorageLocation, actorRef.Atespace, "snap-1"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) } @@ -1021,9 +1033,10 @@ func TestLoadActorForResume_RunningActorShortCircuits(t *testing.T) { type capturingAtelet struct { ateletpb.UnimplementedAteomHerderServer - mu sync.Mutex - restore *ateletpb.RestoreRequest - run *ateletpb.RunRequest + mu sync.Mutex + restore *ateletpb.RestoreRequest + run *ateletpb.RunRequest + checkpoint *ateletpb.CheckpointRequest } func (f *capturingAtelet) Restore(ctx context.Context, req *ateletpb.RestoreRequest) (*ateletpb.RestoreResponse, error) { @@ -1040,6 +1053,22 @@ func (f *capturingAtelet) Run(ctx context.Context, req *ateletpb.RunRequest) (*a return &ateletpb.RunResponse{}, nil } +func (f *capturingAtelet) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRequest) (*ateletpb.CheckpointResponse, error) { + f.mu.Lock() + defer f.mu.Unlock() + f.checkpoint = proto.Clone(req).(*ateletpb.CheckpointRequest) + return &ateletpb.CheckpointResponse{}, nil +} + +func (f *capturingAtelet) checkpointRequest() *ateletpb.CheckpointRequest { + f.mu.Lock() + defer f.mu.Unlock() + if f.checkpoint == nil { + return nil + } + return proto.Clone(f.checkpoint).(*ateletpb.CheckpointRequest) +} + // requests returns the recorded Restore and Run requests, nil for an RPC that // was never called. func (f *capturingAtelet) requests() (*ateletpb.RestoreRequest, *ateletpb.RunRequest) { @@ -1143,10 +1172,10 @@ func TestResumeActor_AteletWireRequest(t *testing.T) { unspecScope := ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED extSnap := func(uri string, fidelity ateapipb.SnapshotFidelity) *ateapipb.Snapshot { - return newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, fidelity, "", uri, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + return newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, fidelity, "", "snap-1", uri, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) } localSnap := func(name string, fidelity ateapipb.SnapshotFidelity) *ateapipb.Snapshot { - return newLocalSnapshot(1, fidelity, "", name, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + return newLocalSnapshot(1, fidelity, "", name, "node-1") } // actorSeed is the actor status a row persists before resuming. @@ -1372,8 +1401,8 @@ func TestResumeActor_AteletWireRequest(t *testing.T) { }, }, { - // The local snapshot takes precedence at restore, and its recorded - // scope wins over the durable snapshot's. + // The local snapshot takes precedence at restore, carrying its own + // scope rather than the durable snapshot's. name: "17 local snapshot wins over a Full durable snapshot", actor: actorSeed{ localSnapshot: localSnap(localSnapshotName, dataScope), @@ -1412,7 +1441,7 @@ func TestResumeActor_AteletWireRequest(t *testing.T) { }, { name: "20 local snapshot restore with a missing SandboxConfig is rejected", - actor: actorSeed{localSnapshot: localSnap(localSnapshotName, unspecScope)}, + actor: actorSeed{localSnapshot: localSnap(localSnapshotName, fullScope)}, tmpl: templateSeed{configName: "missing"}, want: restoreWant{code: codes.FailedPrecondition}, }, diff --git a/cmd/ateapi/internal/controlapi/workflow_revert.go b/cmd/ateapi/internal/controlapi/workflow_revert.go index 2f4b3c6e2f..00da969f91 100644 --- a/cmd/ateapi/internal/controlapi/workflow_revert.go +++ b/cmd/ateapi/internal/controlapi/workflow_revert.go @@ -199,8 +199,8 @@ func (w *ActorWorkflow) ensureInProgressSnapshotDiscarded(ctx context.Context, a ctx, done := stepSpan(ctx, "DiscardInProgressSnapshot") defer func() { err = done(err) }() - _, inProgressSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - inProgress := inProgressSt.GetObject().GetSnapshotUri() + inProgressSnap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + inProgress := inProgressSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() switch { case w.snapshotPlugin == nil: markSkipped(ctx, "no object store configured") @@ -246,13 +246,13 @@ func (w *ActorWorkflow) ensureRevertedFinalized(ctx context.Context, actorRef re toUpdate.Status.State = ateapipb.ActorState_ACTOR_STATE_SUSPENDED toUpdate.Status.WorkerAssignment = nil toUpdate.Status.AssignedNode = "" - // We revert back to the latest durable snapshot, so all local snapshots become invalid. - // We already cleaned up the node before this in ensureWorkerDiscarded. + // We revert back to the latest durable snapshot, so all local snapshots + // and pending durable snapshots are removed. + // We already cleaned up the node before this in ensureWorkerDiscarded, + // and any in-progress durable snapshot in ensureInProgressSnapshotDiscarded. // Even during revert, we do not decrease LastAssignedGeneration so that we // can keep track of stale snapshots that need to be cleaned up. - removeSnapshotStorageEntries(toUpdate.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil) - // We already cleaned up the inprogress durable snapshot in ensureInProgressSnapshotDiscarded - removeSnapshotStorageEntries(toUpdate.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, new(ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS)) + pruneSnapshots(toUpdate.Status, nil) toUpdate.Status.Crash = nil return nil }) diff --git a/cmd/ateapi/internal/controlapi/workflow_revert_test.go b/cmd/ateapi/internal/controlapi/workflow_revert_test.go index acec0029e5..f6b78d9503 100644 --- a/cmd/ateapi/internal/controlapi/workflow_revert_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_revert_test.go @@ -99,9 +99,9 @@ func TestRevertActor_ReturnsActorToItsSnapshot(t *testing.T) { mustUpdateActorStatus(t, ctx, st, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 3 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", keptURI, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-in-progress", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "kept", keptURI, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(2, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-1", "node-1"), + newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "local-in-progress", ""), } }) @@ -120,24 +120,24 @@ func TestRevertActor_ReturnsActorToItsSnapshot(t *testing.T) { if got := gotStatus.GetLastAssignedGeneration(); got != 3 { t.Errorf("last_assigned_generation = %d, want 3", got) } - gotSnap, gotSt := findLatestSnapshotStorage(gotStatus, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + gotSnap := findLatestDurableSnapshot(gotStatus, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) if got := gotSnap.GetGeneration(); got != 1 { t.Errorf("retained snapshot generation = %d, want 1", got) } - if got := gotSt.GetObject().GetSnapshotUri(); got != keptURI { + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != keptURI { t.Errorf("external snapshot = %q, want it untouched at %q", got, keptURI) } // Resume prefers a local snapshot over the external one, so a // surviving LocalSnapshot would restore the execution this // revert just discarded. - if _, got := findLatestSnapshotStorage(gotStatus, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); got != nil { + if got := findLatestLocalSnapshot(gotStatus); got != nil { t.Errorf("local snapshot info = %v, want nil", got) } - if _, got := findLatestSnapshotStorage(gotStatus, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); got != nil { + if got := findLatestDurableSnapshot(gotStatus, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); got != nil { t.Errorf("in-progress durable snapshot = %v, want nil", got) } - if _, got := findLatestSnapshotStorage(gotStatus, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); got != nil { - t.Errorf("in-progress local snapshot = %v, want nil", got) + if got := len(gotStatus.GetSnapshots()); got != 1 { + t.Errorf("len(snapshots) = %d, want 1 (only the completed durable snapshot should remain)", got) } if got := gotStatus.GetAssignedNode(); got != "" { t.Errorf("assigned node = %q, want empty", got) @@ -251,8 +251,8 @@ func TestRevertActor_NoSnapshotToRevertTo(t *testing.T) { if got := reverted.GetStatus().GetCrash(); got != nil { t.Errorf("Crash = %v, want cleared by the revert", got) } - _, gotSt := findLatestSnapshotStorage(reverted.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if got := gotSt.GetObject().GetSnapshotUri(); got != "" { + gotSnap := findLatestDurableSnapshot(reverted.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != "" { t.Errorf("external snapshot = %q, want none", got) } } @@ -332,11 +332,11 @@ func TestEnsureInProgressSnapshotDiscarded(t *testing.T) { actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, currentType, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", current.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, currentType, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", current.Name(), current.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } if tt.inFlight != "" { s.LastAssignedGeneration = 2 - s.Snapshots = append(s.Snapshots, newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", inFlight.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS)) + s.Snapshots = append(s.Snapshots, newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", inFlight.Name(), inFlight.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING)) } }) @@ -385,7 +385,7 @@ func TestEnsureRevertedFinalized_NoObjectStore(t *testing.T) { AssignedNode: "node-1", LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", someActorSnapshotURI(t, testStorageLocation, "team-a", "abandoned"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "abandoned", someActorSnapshotURI(t, testStorageLocation, "team-a", "abandoned"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, }, }) @@ -405,7 +405,7 @@ func TestEnsureRevertedFinalized_NoObjectStore(t *testing.T) { if got := finalized.GetStatus().GetAssignedNode(); got != "" { t.Errorf("assigned node = %q, want empty", got) } - if _, got := findLatestSnapshotStorage(finalized.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); got != nil { + if got := findLatestDurableSnapshot(finalized.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); got != nil { t.Errorf("in-progress durable snapshot = %v, want nil", got) } } diff --git a/cmd/ateapi/internal/controlapi/workflow_suspend.go b/cmd/ateapi/internal/controlapi/workflow_suspend.go index 28f3893cd7..c4cf2aabd8 100644 --- a/cmd/ateapi/internal/controlapi/workflow_suspend.go +++ b/cmd/ateapi/internal/controlapi/workflow_suspend.go @@ -131,19 +131,22 @@ func (w *ActorWorkflow) ensureMarkedSuspending(ctx context.Context, actorRef res if got := actor.GetStatus().GetState(); got != ateapipb.ActorState_ACTOR_STATE_RUNNING && got != ateapipb.ActorState_ACTOR_STATE_PAUSED { return nil, apierror.FailedPrecondition("MarkSuspending prerequisite not met for Actor: %s (got: %v, want %s or %s)", actorRef, got, ateapipb.ActorState_ACTOR_STATE_RUNNING, ateapipb.ActorState_ACTOR_STATE_PAUSED) } + // A paused actor's durable snapshot is an upload of its local checkpoint, + // so it keeps that checkpoint's name; a running actor's is a new one. + var snapshotName string if actor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_PAUSED { - localSnap, _ := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + localSnap := findLatestLocalSnapshot(actor.GetStatus()) if localSnap == nil { - if err := crashActor(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotNodeUnknown); err != nil { - slog.ErrorContext(ctx, "Failed to crash actor", slog.String("err", err.Error())) - } - return nil, fmt.Errorf("actor is CRASHED because it was in PAUSED state with no completed local snapshot") + return nil, crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotMissing) } + snapshotName = localSnap.GetUuid() + } else { + snapshotName = resources.NewSnapshotName() } // Fail here rather than at checkpoint time if the template's location // cannot produce a usable URI: nothing has been written yet. - uri, err := newInProgressSnapshotURI(actorTemplate, actor) + uri, err := newInProgressSnapshotURI(actorTemplate, actor, snapshotName) if err != nil { return nil, err } @@ -151,23 +154,21 @@ func (w *ActorWorkflow) ensureMarkedSuspending(ctx context.Context, actorRef res wasPaused := toUpdate.Status.State == ateapipb.ActorState_ACTOR_STATE_PAUSED toUpdate.Status.State = ateapipb.ActorState_ACTOR_STATE_SUSPENDING if wasPaused { - // Add a new in progress durable snapshot to the current generation - snap, _ := findLatestSnapshotStorage(toUpdate.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + // Promote the current generation's local snapshot to a pending durable snapshot. + snap := findLatestLocalSnapshot(toUpdate.Status) if snap == nil { return fmt.Errorf("actor %s is PAUSED but has no completed local snapshot", actorRef) } snap.ActorTemplateUid = actorTemplate.GetMetadata().GetUid() - durableInProgress := &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, - Fidelity: preferredFidelity(actorRef.Atespace, actorTemplate), - Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri.String()}, + snap.Fidelity = preferredFidelity(actorRef.Atespace, actorTemplate) + snap.DurableSnapshot = &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: uri.String()}, } - setSnapshotStorage(snap, durableInProgress) return nil } // Increment last_assigned_generation for the new Suspend request and create - // an in-progress snapshot for that generation. + // a pending snapshot for that generation. gen := toUpdate.Status.LastAssignedGeneration + 1 toUpdate.Status.LastAssignedGeneration = gen toUpdate.Status.Snapshots = append(toUpdate.Status.Snapshots, @@ -176,8 +177,9 @@ func (w *ActorWorkflow) ensureMarkedSuspending(ctx context.Context, actorRef res ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, preferredFidelity(actorRef.Atespace, actorTemplate), actorTemplate.GetMetadata().GetUid(), + uri.Name(), uri.String(), - ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, + ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING, )) return nil }) @@ -204,18 +206,18 @@ func preferredFidelity(atespace string, tmpl *ateapipb.ActorTemplate) ateapipb.S // isPausedOriginSuspend reports whether the suspend must upload a PAUSED // actor's node-local snapshot instead of checkpointing a running workload. -// A LocalSnapshot alone does not mean paused-origin: resume never clears -// it, so a RUNNING actor resumed from pause still carries a stale one. The -// nil worker assignment disambiguates — running-origin suspends keep their -// assignment until finalize, paused actors never have one. +// A local snapshot alone does not mean paused-origin: resume restores from it +// without deleting its files, so a RUNNING actor resumed from pause still +// carries one until its next checkpoint. The nil worker assignment +// disambiguates — running-origin suspends keep their assignment until +// finalize, paused actors never have one. func isPausedOriginSuspend(actor *ateapipb.Actor) bool { if actor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_PAUSED { return true } if actor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_SUSPENDING && actor.GetStatus().GetWorkerAssignment() == nil { - _, localSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - return localSt != nil + return findLatestLocalSnapshot(actor.GetStatus()) != nil } return false } @@ -233,10 +235,7 @@ func (w *ActorWorkflow) ensureAteletSuspended(ctx context.Context, actorRef reso assignment := actor.GetStatus().GetWorkerAssignment() if assignment == nil { // Missing active worker pod reference in SUSPENDING state indicates corrupted store state. - if err := crashActor(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageWorkerAssignmentMissing); err != nil { - slog.ErrorContext(ctx, "Failed to crash actor", slog.String("err", err.Error())) - } - return "", fmt.Errorf("actor is CRASHED because it was in SUSPENDING state but has no active worker") + return "", crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageWorkerAssignmentMissing) } ateletConn, err := w.dialer.DialForAteletOnNode(assignment.GetNodeName()) @@ -253,7 +252,7 @@ func (w *ActorWorkflow) ensureAteletSuspended(ctx context.Context, actorRef reso // Checkpoint does not carry the sandbox config: atelet uses the version the // actor is currently running (recorded on-node at Run/Restore) and pins it // into the snapshot manifest. - _, inProgressDurable := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) + inProgressSnap := snapshotAtLatestGeneration(actor.GetStatus()) req := &ateletpb.CheckpointRequest{ WorkerPodUid: assignment.GetWorkerPodUid(), Atespace: actor.GetMetadata().GetAtespace(), @@ -264,7 +263,7 @@ func (w *ActorWorkflow) ensureAteletSuspended(ctx context.Context, actorRef reso Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL, Config: &ateletpb.CheckpointRequest_ExternalConfig{ ExternalConfig: &ateletpb.ExternalCheckpointConfiguration{ - SnapshotUri: inProgressDurable.GetObject().GetSnapshotUri(), + SnapshotUri: inProgressSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(), }, }, Fidelity: fidelityToAtelet(preferredFidelity(actor.GetMetadata().GetAtespace(), actorTemplate)), @@ -288,15 +287,15 @@ func (w *ActorWorkflow) ensurePausedSnapshotUploaded(ctx context.Context, actorR ctx, done := stepSpan(ctx, "UploadPausedCheckpoint") defer func() { err = done(err) }() - _, localSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + localSnap := findLatestLocalSnapshot(actor.GetStatus()) + if localSnap == nil { + return "", crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotMissing) + } nodeName := actor.GetStatus().GetAssignedNode() - if localSt == nil || nodeName == "" { + if nodeName == "" { // Without the node the snapshot can never be found (mirrors // FinalizePaused, which crashes rather than record an unknown node). - if err := crashActor(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotNodeUnknown); err != nil { - slog.ErrorContext(ctx, "Failed to crash actor", slog.String("err", err.Error())) - } - return "", fmt.Errorf("actor is CRASHED because it was suspending a paused snapshot with no node recorded") + return "", crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageLocalSnapshotNodeUnknown) } ateletConn, err := w.dialer.DialForAteletOnNode(nodeName) @@ -308,15 +307,15 @@ func (w *ActorWorkflow) ensurePausedSnapshotUploaded(ctx context.Context, actorR } client := ateletpb.NewAteomHerderClient(ateletConn) - _, inProgressDurable := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) + inProgressSnap := snapshotAtLatestGeneration(actor.GetStatus()) req := &ateletpb.UploadPausedCheckpointRequest{ Atespace: actor.GetMetadata().GetAtespace(), ActorName: actor.GetMetadata().GetName(), ActorUid: actor.GetMetadata().GetUid(), ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(), ActorTemplateName: actor.GetActorTemplate().GetName(), - LocalSnapshotName: localSt.GetLocal().GetSnapshotName(), - DestinationSnapshotUri: inProgressDurable.GetObject().GetSnapshotUri(), + LocalSnapshotName: localSnap.GetUuid(), + DestinationSnapshotUri: inProgressSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(), // The commit scope, like a running-origin suspend; atelet converts // from the captured scope in the snapshot's manifest where possible. DesiredFidelity: fidelityToAtelet(preferredFidelity(actor.GetMetadata().GetAtespace(), actorTemplate)), @@ -331,9 +330,9 @@ func (w *ActorWorkflow) ensurePausedSnapshotUploaded(ctx context.Context, actorR // newInProgressSnapshotURI is where the snapshot an actor is currently taking is // written: under the actor's own prefix, so the objects name their owner. -func newInProgressSnapshotURI(actorTemplate *ateapipb.ActorTemplate, actor *ateapipb.Actor) (resources.SnapshotURI, error) { +func newInProgressSnapshotURI(actorTemplate *ateapipb.ActorTemplate, actor *ateapipb.Actor, snapshotName string) (resources.SnapshotURI, error) { atespace := actor.GetMetadata().GetAtespace() - uri, err := resources.NewActorSnapshotURI(actorTemplate.GetSnapshotConfig().GetStorageLocation(), atespace, actor.GetMetadata().GetUid(), resources.NewSnapshotName()) + uri, err := resources.NewActorSnapshotURI(actorTemplate.GetSnapshotConfig().GetStorageLocation(), atespace, actor.GetMetadata().GetUid(), snapshotName) if err != nil { return resources.SnapshotURI{}, fmt.Errorf("while building the snapshot URI for actor %s/%s: %w", atespace, actor.GetMetadata().GetName(), err) } @@ -356,6 +355,8 @@ func (w *ActorWorkflow) ensureVolumesDetached(ctx context.Context, actor *ateapi // owned by this actor), records the in-progress snapshot as the actor's // durable snapshot, and commits SUSPENDED with the assignment cleared in a // single update, then releases the durable snapshot that update replaced. +// When the latest snapshot has no durable snapshot to record, it commits +// CRASHED instead and fails the step. // It re-reads the actor first so an out-of-band transition (e.g. the syncer // crashing the actor after its worker died) is not overwritten: with no // assignment left there is nothing to finalize. @@ -368,14 +369,13 @@ func (w *ActorWorkflow) ensureSuspendedFinalized(ctx context.Context, actorRef r // that stalls and then fails still reports where the time went; steps not // reached (or skipped) log zero. start := time.Now() - var dGetActor, dReleaseWorker, dRefetchActor, dUpdateActor, dReleaseSnapshot time.Duration + var dGetActor, dReleaseWorker, dUpdateActor, dReleaseSnapshot time.Duration defer func() { slog.InfoContext(ctx, "FinalizeSuspended store call durations", slog.Any("actor", actorRef), slog.Duration("total", time.Since(start)), slog.Duration("get_actor", dGetActor), slog.Duration("release_worker", dReleaseWorker), - slog.Duration("refetch_actor", dRefetchActor), slog.Duration("update_actor", dUpdateActor), slog.Duration("release_snapshot", dReleaseSnapshot)) }() @@ -386,6 +386,9 @@ func (w *ActorWorkflow) ensureSuspendedFinalized(ctx context.Context, actorRef r if err != nil { return nil, err } + if snapshotAtLatestGeneration(latestActor.GetStatus()).GetDurableSnapshot() == nil { + return nil, crashActorAndFail(ctx, w.store, actorRef, ateattr.OperationSuspend, crashMessageDurableSnapshotMissing) + } // 1. Free the worker (if it hasn't been freed yet) if latestActor.GetStatus().GetWorkerAssignment() != nil { @@ -395,38 +398,22 @@ func (w *ActorWorkflow) ensureSuspendedFinalized(ctx context.Context, actorRef r if err != nil { return nil, err } - - // Re-fetch the actor now that the worker is freed. - t = time.Now() - latestActor, err = w.store.GetActor(ctx, actorRef) - dRefetchActor = time.Since(t) - if err != nil { - return nil, err - } } // 2. Finalize the actor: record its new durable snapshot and mark it SUSPENDED. This // must run even with no worker assignment (nothing to free), or the actor // would be left SUSPENDING forever with the workflow reporting success. - _, prevSt := findLatestSnapshotStorage(latestActor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - - // 3. Commit the actor. + // The precondition pins the version checked above, so an out-of-band write + // since then fails the update instead of being overwritten. t = time.Now() storedActor, err := w.store.UpdateActor(ctx, actorRef, store.PreconditionFrom(latestActor), func(toUpdate *ateapipb.Actor) error { toUpdate.Status.State = ateapipb.ActorState_ACTOR_STATE_SUSPENDED - snap := snapshotAtLatestGeneration(toUpdate.Status) - if snap == nil { - return fmt.Errorf("actor %s has no latest snapshot", actorRef) - } - if inProgressSt := findSnapshotStorage(snap, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE); inProgressSt.GetObject().GetSnapshotUri() != "" { - inProgressSt.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED - } + inProgressSt := snapshotAtLatestGeneration(toUpdate.Status).GetDurableSnapshot() + inProgressSt.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED // Remove all local snapshots since they are superseded by the new durable snapshot. // TODO: This will change when we merge pause and suspend and allow for both local // and durable snapshots to be kept. - removeSnapshotStorageEntries(toUpdate.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, nil) - // Remove all durable snapshots older than the new durable snapshot. - removeOlderSnapshotStorageEntries(toUpdate.Status, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, new(ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), snap.GetGeneration()) + pruneSnapshots(toUpdate.Status, nil) toUpdate.Status.WorkerAssignment = nil toUpdate.Status.AssignedNode = "" return nil @@ -440,34 +427,34 @@ func (w *ActorWorkflow) ensureSuspendedFinalized(ctx context.Context, actorRef r } logActorStateChanged(ctx, storedActor, ateattr.OperationSuspend) - // 4. Release the external snapshot this suspend replaced. Best-effort: the + // 3. Release the external snapshot this suspend replaced. Best-effort: the // new snapshot is committed and the worker is gone, so failing the // workflow here would only strand the actor. A failure leaves the old // snapshot's objects in storage until the actor is deleted, which removes // its whole prefix. t = time.Now() - _, nextSt := findLatestSnapshotStorage(storedActor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - releaseErr := w.releaseReplacedSnapshot(ctx, latestActor, nextSt) + replaced := findLatestDurableSnapshot(latestActor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED).GetDurableSnapshot().GetObject().GetSnapshotUri() + current := findLatestDurableSnapshot(storedActor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED).GetDurableSnapshot().GetObject().GetSnapshotUri() + releaseErr := w.releaseReplacedSnapshot(ctx, latestActor, replaced, current) dReleaseSnapshot = time.Since(t) if releaseErr != nil { slog.WarnContext(ctx, "Failed to release the external snapshot a suspend replaced; its objects are left in storage until the actor is deleted", slog.Any("actor", actorRef), - slog.String("snapshot_uri", prevSt.GetObject().GetSnapshotUri()), + slog.String("snapshot_uri", replaced), slog.String("err", releaseErr.Error())) } return storedActor, nil } -// releaseReplacedSnapshot releases the external snapshot the actor held before -// this suspend. actor is the record as it was before the suspend committed. +// releaseReplacedSnapshot releases previous, the URI of the external snapshot +// the actor held before this suspend, unless it is still current. actor is +// the record as it was before the suspend committed. // An actor that borrowed its current snapshot from a tag releases nothing — // the snapshot lives under the tag's prefix, and the tag outlives the actor. -func (w *ActorWorkflow) releaseReplacedSnapshot(ctx context.Context, actor *ateapipb.Actor, nextSnapshot *ateapipb.SnapshotStorage) (err error) { +func (w *ActorWorkflow) releaseReplacedSnapshot(ctx context.Context, actor *ateapipb.Actor, previous, current string) (err error) { ctx, done := stepSpan(ctx, "ReleaseReplacedSnapshot") defer func() { err = done(err) }() - _, prevSt := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - previous := prevSt.GetObject().GetSnapshotUri() switch { case w.snapshotPlugin == nil: markSkipped(ctx, "no object store configured") @@ -475,7 +462,7 @@ func (w *ActorWorkflow) releaseReplacedSnapshot(ctx context.Context, actor *atea case previous == "": markSkipped(ctx, "the actor held no external snapshot") return nil - case previous == nextSnapshot.GetObject().GetSnapshotUri(): + case previous == current: markSkipped(ctx, "the actor's external snapshot is unchanged") return nil } diff --git a/cmd/ateapi/internal/controlapi/workflow_suspend_test.go b/cmd/ateapi/internal/controlapi/workflow_suspend_test.go index 845283878b..33e7125443 100644 --- a/cmd/ateapi/internal/controlapi/workflow_suspend_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_suspend_test.go @@ -48,11 +48,14 @@ func TestEnsureMarkedSuspending_SnapshotURI(t *testing.T) { t.Fatalf("ensureMarkedSuspending: %v", err) } - _, inProgressSt := findLatestSnapshotStorage(marked.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - uri := mustParseSnapshotURI(t, inProgressSt.GetObject().GetSnapshotUri()) + inProgressSnap := findLatestDurableSnapshot(marked.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + uri := mustParseSnapshotURI(t, inProgressSnap.GetDurableSnapshot().GetObject().GetSnapshotUri()) if !resources.IsValidResourceName(uri.Name()) { t.Errorf("in-progress snapshot name = %q, want a valid resource name", uri.Name()) } + if got := inProgressSnap.GetUuid(); got != uri.Name() { + t.Errorf("Snapshot.Uuid = %q, want the snapshot name in its URI, %q", got, uri.Name()) + } want := "gs://bucket/root/atespaces/team-a/actors/" + marked.GetMetadata().GetUid() + "/snapshots/" + uri.Name() if uri.String() != want { t.Errorf("snapshot URI = %q, want %q", uri, want) @@ -73,7 +76,7 @@ func TestEnsureMarkedSuspending_ReentryKeepsPersistedSnapshotLocation(t *testing State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", firstAttempt, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "first-attempt", firstAttempt, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, }, }) @@ -82,8 +85,8 @@ func TestEnsureMarkedSuspending_ReentryKeepsPersistedSnapshotLocation(t *testing if err != nil { t.Fatalf("ensureMarkedSuspending: %v", err) } - if _, gotSt := findLatestSnapshotStorage(marked.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetObject().GetSnapshotUri() != firstAttempt { - t.Errorf("in-progress durable snapshot URI = %q, want the first attempt's location", gotSt.GetObject().GetSnapshotUri()) + if gotSnap := findLatestDurableSnapshot(marked.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != firstAttempt { + t.Errorf("in-progress durable snapshot URI = %q, want the first attempt's location", gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri()) } } @@ -165,7 +168,7 @@ func TestEnsureMarkedSuspending_StateMatrix(t *testing.T) { if seedState == ateapipb.ActorState_ACTOR_STATE_PAUSED { actorStatus.LastAssignedGeneration = 1 actorStatus.Snapshots = []*ateapipb.Snapshot{ - newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap-1", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap-1", "node-1"), } } actor := storetest.MustCreateActor(t, ctx, persistence, &ateapipb.Actor{ @@ -181,12 +184,32 @@ func TestEnsureMarkedSuspending_StateMatrix(t *testing.T) { if err == nil && marked.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_SUSPENDING { t.Errorf("state %v: ensureMarkedSuspending returned actor in %v, want SUSPENDING", seedState, marked.GetStatus().GetState()) } + if seedState == ateapipb.ActorState_ACTOR_STATE_PAUSED && err == nil { + wantURI, err := resources.NewActorSnapshotURI("gs://snapshots", actorRef.Atespace, actor.GetMetadata().GetUid(), "snap-1") + if err != nil { + t.Fatalf("NewActorSnapshotURI: %v", err) + } + pending := findLatestDurableSnapshot(marked.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + gotURI := pending.GetDurableSnapshot().GetObject().GetSnapshotUri() + if gotURI != wantURI.String() { + t.Errorf("paused-origin in-progress SnapshotUri = %q, want %q (derived from local snapshot uuid)", gotURI, wantURI.String()) + } + // The upload promotes the pause snapshot in place rather than + // minting a new generation. + if pending.GetGeneration() != 1 || pending.GetUuid() != "snap-1" { + t.Errorf("paused-origin pending snapshot = generation %d, uuid %q; want the pause snapshot (generation 1, uuid %q)", pending.GetGeneration(), pending.GetUuid(), "snap-1") + } + if got := marked.GetStatus().GetLastAssignedGeneration(); got != 1 { + t.Errorf("paused-origin LastAssignedGeneration = %d, want 1", got) + } + } } } // TestSuspendActor_CrashesWhenSuspendingActorMissingWorkerPod verifies that a // SUSPENDING actor with no worker pod recorded is moved to CRASHED by -// CallAteletSuspendStep's prerequisite check and the suspend fails. +// CallAteletSuspendStep's prerequisite check and the suspend fails with +// FailedPrecondition. func TestSuspendActor_CrashesWhenSuspendingActorMissingWorkerPod(t *testing.T) { ctx := context.Background() st, cleanup := storetest.SetupTestStore(t) @@ -195,8 +218,9 @@ func TestSuspendActor_CrashesWhenSuspendingActorMissingWorkerPod(t *testing.T) { seedWorkflowActor(t, ctx, st, resources.ActorRef{Atespace: "team-a", Name: "id1"}, "ns", "tmpl1", ateapipb.ActorState_ACTOR_STATE_SUSPENDING) - if _, err := w.SuspendActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}); err == nil { - t.Fatal("SuspendActor succeeded, want error for SUSPENDING actor with no worker pod") + _, err := w.SuspendActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}) + if got := apierror.Code(err); got != codes.FailedPrecondition { + t.Fatalf("apierror.Code(err) = %v, want %v (err: %v)", got, codes.FailedPrecondition, err) } got, err := st.GetActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}) @@ -250,9 +274,9 @@ func TestEnsureAteletSuspended_DialFailureLeavesActorRetryable(t *testing.T) { persistence := newTestPersistence(t) var snapshots []*ateapipb.Snapshot if tt.prevSnapshot != "" { - snapshots = append(snapshots, newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", tt.prevSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED)) + snapshots = append(snapshots, newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "prev-snapshot", tt.prevSnapshot, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED)) } - snapshots = append(snapshots, newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", neverWritten, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS)) + snapshots = append(snapshots, newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "never-written", neverWritten, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING)) actor := &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"}, @@ -285,11 +309,11 @@ func TestEnsureAteletSuspended_DialFailureLeavesActorRetryable(t *testing.T) { if stored.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_SUSPENDING { t.Errorf("state = %v, want unchanged SUSPENDING", stored.GetStatus().GetState()) } - if _, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetObject().GetSnapshotUri() != neverWritten { - t.Errorf("in-progress durable snapshot URI = %q, want preserved for debugging", gotSt.GetObject().GetSnapshotUri()) + if gotSnap := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != neverWritten { + t.Errorf("in-progress durable snapshot URI = %q, want preserved for debugging", gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri()) } - _, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if got := gotSt.GetObject().GetSnapshotUri(); got != tt.prevSnapshot { + gotSnap := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != tt.prevSnapshot { t.Errorf("SnapshotUri = %q, want %q", got, tt.prevSnapshot) } }) @@ -307,13 +331,8 @@ func TestEnsureSuspendedFinalized_NoAssignment(t *testing.T) { persistence := newTestPersistence(t) snapshotURI := someActorSnapshotURI(t, testStorageLocation, "team-a", "2026-01-01t00-00-00z-abc") - snap := newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-uid-1", "actor-1-pause-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - snap.Storage = append(snap.Storage, &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: snapshotURI}, - }) + snap := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "tmpl-uid-1", "actor-1-pause-snapshot", snapshotURI, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + snap.Locality = "node1" actor := &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"}, Status: &ateapipb.ActorStatus{ @@ -334,8 +353,8 @@ func TestEnsureSuspendedFinalized_NoAssignment(t *testing.T) { if stored.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { t.Errorf("state = %v, want SUSPENDED", stored.GetStatus().GetState()) } - gotSnap, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if got := gotSt.GetObject().GetSnapshotUri(); got != snapshotURI { + gotSnap := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != snapshotURI { t.Errorf("SnapshotUri = %q, want %q", got, snapshotURI) } // The snapshot carries the template it was captured under, so a later @@ -343,10 +362,10 @@ func TestEnsureSuspendedFinalized_NoAssignment(t *testing.T) { if got := gotSnap.GetActorTemplateUid(); got != "tmpl-uid-1" { t.Errorf("Snapshot.ActorTemplateUid = %q, want %q", got, "tmpl-uid-1") } - if _, got := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); got != nil { + if got := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); got != nil { t.Errorf("in-progress durable snapshot = %v, want cleared", got) } - if _, local := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED); local != nil { + if local := findLatestLocalSnapshot(stored.GetStatus()); local != nil { t.Errorf("LocalSnapshot = %v, want cleared", local) } if got := stored.GetStatus().GetAssignedNode(); got != "" { @@ -409,8 +428,8 @@ func TestEnsureSuspendedFinalized_ReleasesReplacedSnapshot(t *testing.T) { mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 2 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, prevType, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", fresh.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, prevType, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.Name(), previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", fresh.Name(), fresh.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), } }) @@ -420,8 +439,8 @@ func TestEnsureSuspendedFinalized_ReleasesReplacedSnapshot(t *testing.T) { } // Whichever way the previous snapshot went, the actor now owns the // one it just wrote and is no longer borrowing. - _, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if got := gotSt.GetObject().GetSnapshotUri(); got != fresh.String() { + gotSnap := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != fresh.String() { t.Errorf("external snapshot = %q, want the one this suspend wrote, %q", got, fresh) } if released := len(objects.Snapshot(t, previous)) == 0; released != tt.wantReleased { @@ -471,8 +490,8 @@ func TestEnsureSuspendedFinalized_CommitsDespiteObjectStoreFailure(t *testing.T) mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 2 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", fresh.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.Name(), previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", fresh.Name(), fresh.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), } }) @@ -484,11 +503,11 @@ func TestEnsureSuspendedFinalized_CommitsDespiteObjectStoreFailure(t *testing.T) if got := stored.GetStatus().GetState(); got != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { t.Errorf("state = %v, want SUSPENDED", got) } - _, gotSt := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if got := gotSt.GetObject().GetSnapshotUri(); got != fresh.String() { + gotSnap := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if got := gotSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(); got != fresh.String() { t.Errorf("actor snapshot uri = %q, want the one this suspend wrote, %q", got, fresh) } - if _, got := findLatestSnapshotStorage(stored.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); got != nil { + if got := findLatestDurableSnapshot(stored.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING); got != nil { t.Errorf("in-progress durable snapshot = %v, want cleared", got) } if len(objects.Snapshot(t, previous)) == 0 { @@ -523,8 +542,8 @@ func TestEnsureSuspendedFinalized_KeepsReplacedSnapshotOnConflict(t *testing.T) mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 2 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", fresh.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", previous.Name(), previous.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", fresh.Name(), fresh.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), } }) @@ -547,6 +566,56 @@ func (conflictingUpdateStore) UpdateActor(context.Context, resources.ActorRef, s return nil, store.ErrVersionConflict } +// TestEnsureSuspendedFinalized_KeepsCrashFromWorkerDelete verifies that a +// crash the DeleteWorker workflow commits while ensureSuspendedFinalized is +// freeing the worker is kept: the step's update is pinned to the actor version +// it checked, so it fails Aborted instead of committing SUSPENDED over the +// crash. +func TestEnsureSuspendedFinalized_KeepsCrashFromWorkerDelete(t *testing.T) { + ctx := context.Background() + persistence := newTestPersistence(t) + actorRef := resources.ActorRef{Atespace: "team-a", Name: "actor-1"} + + workerName := testWorkerUID("worker-pod-1") + created := storetest.MustCreateActor(t, ctx, persistence, &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, + AssignedNode: "node1", + WorkerAssignment: &ateapipb.WorkerAssignment{ + Worker: &ateapipb.ObjectRef{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + }, + LastAssignedGeneration: 1, + Snapshots: []*ateapipb.Snapshot{ + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snapshot-1", someActorSnapshotURI(t, testStorageLocation, "team-a", "snapshot-1"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), + }, + }, + }) + if _, err := persistence.CreateWorker(ctx, &ateapipb.Worker{ + Metadata: &ateapipb.ResourceMetadata{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + NodeName: "node1", + Status: &ateapipb.WorkerStatus{}, + }); err != nil { + t.Fatalf("CreateWorker: %v", err) + } + seedAssignment(t, persistence, workerName, &ateapipb.ActorAssignment{ + Actor: &ateapipb.ObjectRef{Atespace: actorRef.Atespace, Name: actorRef.Name}, + ActorUid: created.GetMetadata().GetUid(), + }) + + w := &ActorWorkflow{store: workerDeletedDuringReleaseStore{Interface: persistence}} + _, err := w.ensureSuspendedFinalized(ctx, actorRef) + assertConcurrentCrashKept(t, ctx, persistence, actorRef, err, "suspend failed: "+crashMessageWorkerPodGone) +} + func TestEnsureSuspendedFinalized_ReleasesOnlyOwnWorker(t *testing.T) { tests := []struct { name string @@ -589,7 +658,7 @@ func TestEnsureSuspendedFinalized_ReleasesOnlyOwnWorker(t *testing.T) { }, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", someActorSnapshotURI(t, testStorageLocation, "team-a", "snapshot-1"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snapshot-1", someActorSnapshotURI(t, testStorageLocation, "team-a", "snapshot-1"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), }, }, } @@ -659,14 +728,14 @@ func TestPreferredFidelity(t *testing.T) { } } -// TestIsPausedOriginSuspend pins the paused-origin discriminator: -// LocalSnapshot alone must not select the paused path, because resume -// leaves it stale on RUNNING actors; only PAUSED state, or SUSPENDING with +// TestIsPausedOriginSuspend pins the paused-origin discriminator: a local +// snapshot alone must not select the paused path, because a RUNNING actor +// resumed from pause still carries one; only PAUSED state, or SUSPENDING with // no worker assignment, means the suspend uploads a local snapshot. func TestIsPausedOriginSuspend(t *testing.T) { assignment := &ateapipb.WorkerAssignment{WorkerNamespace: "ns", WorkerPool: "pool", WorkerPod: "pod-1"} localSnaps := []*ateapipb.Snapshot{ - newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap", "node1"), } tests := []struct { name string @@ -675,8 +744,8 @@ func TestIsPausedOriginSuspend(t *testing.T) { }{ {"paused actor", &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_PAUSED, AssignedNode: "node1", Snapshots: localSnaps}}, true}, {"suspending retry of a paused-origin suspend", &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, AssignedNode: "node1", Snapshots: localSnaps}}, true}, - {"running actor with stale local snapshot info", &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RUNNING, Snapshots: localSnaps, WorkerAssignment: assignment}}, false}, - {"suspending retry of a running-origin suspend with stale local snapshot info", &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, Snapshots: localSnaps, WorkerAssignment: assignment}}, false}, + {"running actor resumed from pause", &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RUNNING, Snapshots: localSnaps, WorkerAssignment: assignment}}, false}, + {"suspending retry of a running-origin suspend after a resume from pause", &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, Snapshots: localSnaps, WorkerAssignment: assignment}}, false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -703,13 +772,14 @@ func TestEnsurePausedSnapshotUploaded_Preconditions(t *testing.T) { State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, LastAssignedGeneration: 1, Snapshots: []*ateapipb.Snapshot{ - newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap", "node1"), }, }, }) - if _, err := w.ensurePausedSnapshotUploaded(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, &ateapipb.ActorTemplate{}); err == nil { - t.Fatal("ensurePausedSnapshotUploaded = nil, want error for missing node record") + _, err := w.ensurePausedSnapshotUploaded(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, &ateapipb.ActorTemplate{}) + if got := apierror.Code(err); got != codes.FailedPrecondition { + t.Fatalf("apierror.Code(err) = %v, want %v for a missing node record (err: %v)", got, codes.FailedPrecondition, err) } stored, err := persistence.GetActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}) @@ -726,13 +796,8 @@ func TestEnsurePausedSnapshotUploaded_Preconditions(t *testing.T) { persistence := newTestPersistence(t) w := &ActorWorkflow{store: persistence, dialer: newDanglingDialer()} - snap := newLocalSnapshot(1, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - snap.Storage = append(snap.Storage, &ateapipb.SnapshotStorage{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: someActorSnapshotURI(t, testStorageLocation, "team-a", "snap-dest")}, - }) + snap := newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "snap", someActorSnapshotURI(t, testStorageLocation, "team-a", "snap-dest"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + snap.Locality = "node1" created := storetest.MustCreateActor(t, ctx, persistence, &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"}, Status: &ateapipb.ActorStatus{ @@ -775,8 +840,9 @@ func TestSuspendActor_PausedWithoutLocalSnapshotCrashes(t *testing.T) { seedWorkflowActor(t, ctx, st, resources.ActorRef{Atespace: "team-a", Name: "id1"}, "ns", "tmpl1", ateapipb.ActorState_ACTOR_STATE_PAUSED) - if _, err := w.SuspendActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}); err == nil { - t.Fatal("SuspendActor succeeded, want error for PAUSED actor with no local snapshot record") + _, err := w.SuspendActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}) + if got := apierror.Code(err); got != codes.FailedPrecondition { + t.Fatalf("apierror.Code(err) = %v, want %v for PAUSED actor with no local snapshot record (err: %v)", got, codes.FailedPrecondition, err) } got, err := st.GetActor(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}) @@ -786,7 +852,73 @@ func TestSuspendActor_PausedWithoutLocalSnapshotCrashes(t *testing.T) { if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { t.Errorf("stored state = %v, want %v", got.GetStatus().GetState(), ateapipb.ActorState_ACTOR_STATE_CRASHED) } - if msg, want := got.GetStatus().GetCrash().GetMessage(), "suspend failed: "+crashMessageLocalSnapshotNodeUnknown; msg != want { + if msg, want := got.GetStatus().GetCrash().GetMessage(), "suspend failed: "+crashMessageLocalSnapshotMissing; msg != want { t.Errorf("crash message = %q, want %q", msg, want) } } + +// TestEnsureSuspendedFinalized_MissingDurableSnapshotClearsAssignment verifies +// that if the latest generation snapshot entry is missing when +// ensureSuspendedFinalized runs, the actor transitions to CRASHED and clears +// WorkerAssignment after releasing the worker rather than getting stuck in +// SUSPENDING, and the step fails. +func TestEnsureSuspendedFinalized_MissingDurableSnapshotClearsAssignment(t *testing.T) { + st, cleanup := storetest.SetupTestStore(t) + defer cleanup() + ctx := context.Background() + actorRef := resources.ActorRef{Atespace: "team-a", Name: "actor-1"} + records := crashRecords(t) + + workerName := testWorkerUID("worker-pod-1") + created := storetest.MustCreateActor(t, ctx, st, &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, + AssignedNode: "node1", + WorkerAssignment: &ateapipb.WorkerAssignment{ + Worker: &ateapipb.ObjectRef{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + }, + LastAssignedGeneration: 1, + }, + }) + if _, err := st.CreateWorker(ctx, &ateapipb.Worker{ + Metadata: &ateapipb.ResourceMetadata{Name: workerName}, + WorkerNamespace: "default", + WorkerPool: "pool1", + WorkerPod: "worker-pod-1", + WorkerPodUid: workerName, + NodeName: "node1", + Status: &ateapipb.WorkerStatus{}, + }); err != nil { + t.Fatalf("CreateWorker: %v", err) + } + seedAssignment(t, st, workerName, &ateapipb.ActorAssignment{ + Actor: &ateapipb.ObjectRef{Atespace: actorRef.Atespace, Name: actorRef.Name}, + ActorUid: created.GetMetadata().GetUid(), + }) + + w := &ActorWorkflow{store: st} + if _, err := w.ensureSuspendedFinalized(ctx, actorRef); apierror.Code(err) != codes.FailedPrecondition { + t.Fatalf("ensureSuspendedFinalized error = %v, want FailedPrecondition for the actor it crashed", err) + } + got, err := st.GetActor(ctx, actorRef) + if err != nil { + t.Fatalf("GetActor: %v", err) + } + if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { + t.Errorf("state = %v, want CRASHED", got.GetStatus().GetState()) + } + if msg, want := got.GetStatus().GetCrash().GetMessage(), "suspend failed: "+crashMessageDurableSnapshotMissing; msg != want { + t.Errorf("crash message = %q, want %q", msg, want) + } + if got.GetStatus().GetWorkerAssignment() != nil { + t.Errorf("WorkerAssignment = %v, want nil", got.GetStatus().GetWorkerAssignment()) + } + if len(*records) != 1 { + t.Fatalf("got %d crash records, want 1", len(*records)) + } +} diff --git a/cmd/ateapi/internal/controlapi/workflow_tag.go b/cmd/ateapi/internal/controlapi/workflow_tag.go index 634f781ce9..742fcfe74f 100644 --- a/cmd/ateapi/internal/controlapi/workflow_tag.go +++ b/cmd/ateapi/internal/controlapi/workflow_tag.go @@ -30,10 +30,12 @@ import ( // The tag is given its own copy of that snapshot, so suspending the actor again // or deleting the actor does not garbage collect the tag's snapshot. // -// The tag is built in 3 phases: -// 1. Reserve the tag and record its storage location. -// 2. Copy the snapshot under the reserved tag's UID. -// 3. Finalize: write the completed snapshot object to the tag. +// The tag is built in 4 phases: +// 1. Reserve the tag's name; the store assigns its UID. +// 2. Record the snapshot's URI, under the tag's UID, before anything is +// copied there. +// 3. Copy the snapshot to that URI. +// 4. Finalize: mark the tag's snapshot COMPLETED. // // The tag captures whichever snapshot the actor holds when the workflow runs. // An actor keeps no snapshot history, so a suspend that lands first moves what @@ -67,16 +69,20 @@ func (w *ActorWorkflow) TagActorSnapshot(ctx context.Context, tag *ateapipb.Tag) if err != nil { return nil, err } - snapshot, _ := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + snapshot := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - reserved, dst, err := w.ensureTagReserved(leaseCtx, tagRef, actor, actorTemplate, tag) + reserved, err := w.ensureTagReserved(leaseCtx, tagRef, actor, actorTemplate, snapshot, tag) if err != nil { return nil, err } - if err := w.ensureTagSnapshotCopied(leaseCtx, reserved, snapshot, dst); err != nil { + recorded, dst, err := w.ensureTagSnapshotRecorded(leaseCtx, reserved, actorTemplate, snapshot) + if err != nil { + return nil, err + } + if err := w.ensureTagSnapshotCopied(leaseCtx, recorded, snapshot, dst); err != nil { return nil, err } - return w.ensureTagFinalized(leaseCtx, reserved, snapshot, dst) + return w.ensureTagFinalized(leaseCtx, recorded, snapshot, dst) } // DeleteTag releases the external snapshot the tag owns and then removes the @@ -145,7 +151,9 @@ func (w *ActorWorkflow) loadTagForDelete(ctx context.Context, tagRef resources.T // ensureTagSnapshotReleased deletes the objects the tag's external snapshot is // made of. It tolerates a partly-collected snapshot, so a retry finishes -// cleanly. It collects the in-progress snapshot of a pending tag too. +// cleanly. It collects the in-progress snapshot of a pending tag too, and +// skips a tag whose create died before recording its snapshot: nothing is +// copied until the URI is recorded, so such a tag stranded nothing. func (w *ActorWorkflow) ensureTagSnapshotReleased(ctx context.Context, tag *ateapipb.Tag) (err error) { ctx, done := stepSpan(ctx, "ReleaseTagSnapshot") defer func() { err = done(err) }() @@ -155,7 +163,12 @@ func (w *ActorWorkflow) ensureTagSnapshotReleased(ctx context.Context, tag *atea return nil } tagRef := resources.TagRefFromTag(tag) - uri, err := resources.ParseSnapshotURI(tagDurableSnapshotURI(tag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED)) + snapshotURI := tagDurableSnapshotURI(tag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED) + if snapshotURI == "" { + markSkipped(ctx, "the tag records no snapshot") + return nil + } + uri, err := resources.ParseSnapshotURI(snapshotURI) if err != nil { return fmt.Errorf("while resolving the external snapshot of tag %s: %w", tagRef, err) } @@ -201,8 +214,8 @@ func (w *ActorWorkflow) loadActorForTag(ctx context.Context, actorRef resources. if got := actor.GetStatus().GetState(); got != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { return nil, nil, apierror.FailedPrecondition("Actor %s must be %s to be tagged (got: %v)", actorRef, ateapipb.ActorState_ACTOR_STATE_SUSPENDED, got) } - snap, st := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if st == nil { + snap := findLatestDurableSnapshot(actor.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if snap == nil { return nil, nil, apierror.FailedPrecondition("Actor %s holds no external snapshot to tag", actorRef) } // Every way an Actor comes to hold an external snapshot records the @@ -220,26 +233,24 @@ func (w *ActorWorkflow) loadActorForTag(ctx context.Context, actorRef resources. return actor, actorTemplate, nil } -// ensureTagReserved takes the tag's name and records the in-progress snapshot. +// ensureTagReserved takes the tag's name. The store assigns the tag's UID on +// create, and the UID keys the prefix the snapshot is copied to, so the +// snapshot is recorded by the next step. // // A name already taken is AlreadyExists, whether the tag holding it is finished // or was left pending by a create that died. Resuming a pending row would mean // deciding whether the objects under it still belong to the snapshot being // tagged, and the row may not even be this actor's; deleting the tag collects // them and frees the name, so a retry is a delete followed by a create. -func (w *ActorWorkflow) ensureTagReserved(ctx context.Context, tagRef resources.TagRef, actor *ateapipb.Actor, actorTemplate *ateapipb.ActorTemplate, tag *ateapipb.Tag) (_ *ateapipb.Tag, _ resources.SnapshotURI, err error) { +func (w *ActorWorkflow) ensureTagReserved(ctx context.Context, tagRef resources.TagRef, actor *ateapipb.Actor, actorTemplate *ateapipb.ActorTemplate, snapshot *ateapipb.Snapshot, tag *ateapipb.Tag) (_ *ateapipb.Tag, err error) { ctx, done := stepSpan(ctx, "ReserveTag") defer func() { err = done(err) }() - location := actorTemplate.GetSnapshotConfig().GetStorageLocation() - if err := resources.ValidateSnapshotLocation(location); err != nil { - return nil, resources.SnapshotURI{}, fmt.Errorf("invalid storage location for tag %s: %w", tagRef, err) - } - dst, err := resources.NewTagSnapshotURI(location, tagRef.Atespace, resources.NewSnapshotName()) - if err != nil { - return nil, resources.SnapshotURI{}, fmt.Errorf("while building the snapshot URI for tag %s: %w", tagRef, err) + // Checked before the name is taken, so an unusable location fails the + // create without leaving a pending tag behind. + if err := resources.ValidateSnapshotLocation(actorTemplate.GetSnapshotConfig().GetStorageLocation()); err != nil { + return nil, fmt.Errorf("invalid storage location for tag %s: %w", tagRef, err) } - snap, st := findLatestSnapshotStorage(actor.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) tagToCreate := &ateapipb.Tag{ Metadata: &ateapipb.ResourceMetadata{Atespace: tagRef.Atespace, Name: tagRef.Name}, Scope: tag.GetScope(), @@ -250,28 +261,58 @@ func (w *ActorWorkflow) ensureTagReserved(ctx context.Context, tagRef resources. // and a tag that claimed the new template would hand clones the old template's // memory under the new one's identity, past the data-only downgrade a resume of // the actor itself would take. - ActorTemplateUid: snap.GetActorTemplateUid(), - Snapshot: newDurableSnapshot( - 1, - ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, - st.GetFidelity(), - "", - dst.String(), - ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, - ), + ActorTemplateUid: snapshot.GetActorTemplateUid(), }, } stored, err := w.store.CreateTag(ctx, tagToCreate) switch { case err == nil: - return stored, dst, nil + return stored, nil case errors.Is(err, store.ErrFailedPrecondition): - return nil, resources.SnapshotURI{}, apierror.FailedPrecondition("Atespace %s not found", tagRef.Atespace) + return nil, apierror.FailedPrecondition("Atespace %s not found", tagRef.Atespace) case errors.Is(err, store.ErrAlreadyExists): - return nil, resources.SnapshotURI{}, apierror.AlreadyExists("Tag %s already exists; delete it and create it again to retry", tagRef) + return nil, apierror.AlreadyExists("Tag %s already exists; delete it and create it again to retry", tagRef) + } + return nil, fmt.Errorf("while reserving tag %s: %w", tagRef, err) +} + +// ensureTagSnapshotRecorded records the snapshot the tag will hold as PENDING: +// a copy of snapshot under the tag's own prefix, keyed on the UID the store +// assigned when the tag was reserved. It lands before anything is copied, so a +// tag whose create fails later still names the objects it stranded. +func (w *ActorWorkflow) ensureTagSnapshotRecorded(ctx context.Context, tag *ateapipb.Tag, actorTemplate *ateapipb.ActorTemplate, snapshot *ateapipb.Snapshot) (_ *ateapipb.Tag, _ resources.SnapshotURI, err error) { + ctx, done := stepSpan(ctx, "RecordTagSnapshot") + defer func() { err = done(err) }() + + tagRef := resources.TagRefFromTag(tag) + dst, err := resources.NewTagSnapshotURI(actorTemplate.GetSnapshotConfig().GetStorageLocation(), tagRef.Atespace, tag.GetMetadata().GetUid()) + if err != nil { + return nil, resources.SnapshotURI{}, fmt.Errorf("while building the snapshot URI for tag %s: %w", tagRef, err) + } + pending := newDurableSnapshot( + 1, + ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, + snapshot.GetFidelity(), + "", + dst.Name(), + dst.String(), + ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING, + ) + stored, err := w.store.UpdateTag(ctx, tagRef, store.PreconditionFrom(tag), func(toUpdate *ateapipb.Tag) error { + toUpdate.Status.Snapshot = pending + return nil + }) + if err != nil { + if errors.Is(err, store.ErrVersionConflict) { + return nil, resources.SnapshotURI{}, apierror.Aborted("concurrent update conflict, please retry") + } + if errors.Is(err, store.ErrNotFound) || errors.Is(err, store.ErrUIDConflict) { + return nil, resources.SnapshotURI{}, apierror.Aborted("Tag %s was deleted while it was being created, please retry", tagRef) + } + return nil, resources.SnapshotURI{}, fmt.Errorf("while recording the snapshot of tag %s: %w", tagRef, err) } - return nil, resources.SnapshotURI{}, fmt.Errorf("while reserving tag %s: %w", tagRef, err) + return stored, dst, nil } // ensureTagSnapshotCopied copies the actor's external snapshot to the tag's own @@ -286,7 +327,7 @@ func (w *ActorWorkflow) ensureTagSnapshotCopied(ctx context.Context, tag *ateapi return nil } tagRef := resources.TagRefFromTag(tag) - srcURI := findSnapshotStorage(snapshot, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE).GetObject().GetSnapshotUri() + srcURI := snapshot.GetDurableSnapshot().GetObject().GetSnapshotUri() src, err := resources.ParseSnapshotURI(srcURI) if err != nil { return fmt.Errorf("while parsing the external snapshot %q of the source actor: %w", srcURI, err) @@ -306,12 +347,12 @@ func (w *ActorWorkflow) ensureTagFinalized(ctx context.Context, tag *ateapipb.Ta tagRef := resources.TagRefFromTag(tag) // The copy is byte-identical to the source, so it carries the same content. - srcSt := findSnapshotStorage(snapshot, ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE) finalSnapshot := newDurableSnapshot( 1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, - srcSt.GetFidelity(), + snapshot.GetFidelity(), "", + dst.Name(), dst.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, ) diff --git a/cmd/ateapi/internal/controlapi/workflow_tag_test.go b/cmd/ateapi/internal/controlapi/workflow_tag_test.go index 1e7ee697b3..bbfaf5fe97 100644 --- a/cmd/ateapi/internal/controlapi/workflow_tag_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_tag_test.go @@ -46,7 +46,7 @@ func seedTagSource(t *testing.T, ctx context.Context, persistence store.Interfac actor = mustUpdateActorStatus(t, ctx, persistence, actor, func(s *ateapipb.ActorStatus) { s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, template.GetMetadata().GetUid(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, template.GetMetadata().GetUid(), uri.Name(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) return actor, uri @@ -93,13 +93,16 @@ func TestTagActorSnapshot(t *testing.T) { if tagSnapshot == "" || tagSnapshot == actorSnapshot.String() { t.Fatalf("tag snapshot uri = %q, want a copy of its own rather than the actor's %q", tagSnapshot, actorSnapshot) } - if want := mustReservedTagSnapshotURI(t, tag).String(); tagSnapshot != want { + if want := mustTagSnapshotURI(t, template, "team-a", tag.GetMetadata().GetUid()).String(); tagSnapshot != want { t.Errorf("tag snapshot uri = %q, want UID-based URI %q", tagSnapshot, want) } + if got, want := tag.GetStatus().GetSnapshot().GetUuid(), tag.GetMetadata().GetUid(); got != want { + t.Errorf("tag snapshot uuid = %q, want the tag's UID %q", got, want) + } if got, want := tag.GetStatus().GetActorTemplateUid(), template.GetMetadata().GetUid(); got != want { t.Errorf("actor template uid = %q, want %q", got, want) } - if got, want := findSnapshotStorage(tag.GetStatus().GetSnapshot(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE).GetFidelity(), ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY; got != want { + if got, want := tag.GetStatus().GetSnapshot().GetFidelity(), ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY; got != want { t.Errorf("fidelity = %v, want the source's %v", got, want) } @@ -203,7 +206,7 @@ func TestTagActorSnapshot_Preconditions(t *testing.T) { } s.LastAssignedGeneration = 1 s.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmplUID, uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, tmplUID, uri.Name(), uri.String(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), } }) } @@ -294,7 +297,7 @@ func TestTagActorSnapshot_RecreateAfterCopyFailure(t *testing.T) { if recreated == "" || recreated == stranded { t.Fatalf("snapshot uri after the delete = %q, want a fresh prefix rather than the stranded %q", recreated, stranded) } - if want := mustReservedTagSnapshotURI(t, tag).String(); recreated != want { + if want := mustTagSnapshotURI(t, template, "team-a", tag.GetMetadata().GetUid()).String(); recreated != want { t.Errorf("recreated tag snapshot uri = %q, want UID-based URI %q", recreated, want) } if diff := cmp.Diff([]string{"manifest.json", "memory.zst"}, objects.Snapshot(t, mustParseSnapshotURI(t, recreated))); diff != "" { @@ -308,6 +311,74 @@ func TestTagActorSnapshot_RecreateAfterCopyFailure(t *testing.T) { } } +// recordTagErrorStore fails every UpdateTag while err is set, standing in for +// a create that dies between reserving the tag and recording its snapshot. +type recordTagErrorStore struct { + store.Interface + err error +} + +func (s *recordTagErrorStore) UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition store.Precondition, mutate func(*ateapipb.Tag) error) (*ateapipb.Tag, error) { + if s.err != nil { + return nil, s.err + } + return s.Interface.UpdateTag(ctx, tagRef, precondition, mutate) +} + +// TestTagActorSnapshot_RecreateAfterRecordFailure verifies what a create that +// dies after reserving the name, before recording the snapshot, leaves behind: +// a tag with no snapshot and nothing in object storage, since nothing is +// copied until the snapshot's URI is recorded. Deleting that tag frees the +// name without touching object storage, and the create then runs again. +func TestTagActorSnapshot_RecreateAfterRecordFailure(t *testing.T) { + ctx := context.Background() + persistence := newTestPersistence(t) + template := seedSubstrateTemplate(t, ctx, persistence, "sub-tmpl") + failing := &recordTagErrorStore{Interface: persistence, err: errors.New("store unavailable")} + w, objects := newFinalizeWorkflow(failing) + + actor, _ := seedTagSource(t, ctx, persistence, objects, template, "actor-1", "manifest.json", "memory.zst") + actorRef := resources.ActorRefFromActor(actor) + tagRef := resources.TagRef{Atespace: "team-a", Name: "v1"} + + if _, err := w.TagActorSnapshot(ctx, tagToCreate(actorRef, "v1")); !errors.Is(err, failing.err) { + t.Fatalf("TagActorSnapshot = %v, want an error wrapping %v", err, failing.err) + } + failing.err = nil + + reserved, err := persistence.GetTag(ctx, tagRef) + if err != nil { + t.Fatalf("GetTag after the failed record: %v", err) + } + if snap := reserved.GetStatus().GetSnapshot(); snap != nil { + t.Errorf("snapshot after the failed record = %v, want unset", snap) + } + if got := objects.Snapshot(t, mustTagSnapshotURI(t, template, "team-a", reserved.GetMetadata().GetUid())); len(got) != 0 { + t.Errorf("the reserved tag's prefix holds %v, want nothing copied before the snapshot is recorded", got) + } + + // The delete has nothing to collect, so it must not reach object storage. + objects.OnList = func(_, prefix string) error { + t.Errorf("DeleteTag listed %q for a tag that records no snapshot", prefix) + return nil + } + if _, err := w.DeleteTag(ctx, tagRef, store.DeletePreconditions{}); err != nil { + t.Fatalf("DeleteTag on the reserved tag: %v", err) + } + objects.OnList = nil + if _, err := persistence.GetTag(ctx, tagRef); !errors.Is(err, store.ErrNotFound) { + t.Errorf("GetTag after the delete = %v, want ErrNotFound", err) + } + + tag, err := w.TagActorSnapshot(ctx, tagToCreate(actorRef, "v1")) + if err != nil { + t.Fatalf("TagActorSnapshot after the delete: %v", err) + } + if got, want := tagDurableSnapshotURI(tag, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), mustTagSnapshotURI(t, template, "team-a", tag.GetMetadata().GetUid()).String(); got != want { + t.Errorf("recreated tag snapshot uri = %q, want UID-based URI %q", got, want) + } +} + // TestTagActorSnapshot_RacesDelete verifies that deleting a tag that's // still being created is aborted. func TestTagActorSnapshot_RacesDelete(t *testing.T) { diff --git a/cmd/ateapi/internal/controlapi/workflow_test.go b/cmd/ateapi/internal/controlapi/workflow_test.go index c89f43f9aa..0b5f79d982 100644 --- a/cmd/ateapi/internal/controlapi/workflow_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_test.go @@ -260,7 +260,7 @@ func TestActorStateChangeRecordSkippedOnConflict(t *testing.T) { if _, err := persistence.UpdateActor(ctx, actorRef, store.PreconditionFrom(stale), func(toUpdate *ateapipb.Actor) error { toUpdate.Status.LastAssignedGeneration = 1 toUpdate.Status.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", someActorSnapshotURI(t, testStorageLocation, "team-a", "someone-else"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "someone-else", someActorSnapshotURI(t, testStorageLocation, "team-a", "someone-else"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), } return nil }); err != nil { diff --git a/cmd/ateapi/internal/controlapi/workflow_testutil_test.go b/cmd/ateapi/internal/controlapi/workflow_testutil_test.go index d2e78bd3cb..2f7bbc4aa6 100644 --- a/cmd/ateapi/internal/controlapi/workflow_testutil_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_testutil_test.go @@ -181,3 +181,50 @@ func assertPrerequisiteResult(t *testing.T, st ateapipb.ActorState, err error, w t.Errorf("state %v: status.Code = %v, want %v", st, got, codes.FailedPrecondition) } } + +// workerDeletedDuringReleaseStore runs the DeleteWorker workflow's release of +// an actor right after a finalize step frees the actor's worker. That workflow +// takes no actor lease, so its crash can land between the step's read of the +// actor and its write. +type workerDeletedDuringReleaseStore struct { + store.Interface +} + +func (s workerDeletedDuringReleaseStore) ReleaseActorFromWorker(ctx context.Context, workerName, actorUID string) (*ateapipb.Worker, error) { + worker, err := s.Interface.GetWorker(ctx, workerName) + if err != nil { + return nil, err + } + assignment, err := s.Interface.GetWorkerAssignment(ctx, workerName, actorUID) + if err != nil { + return nil, err + } + released, err := s.Interface.ReleaseActorFromWorker(ctx, workerName, actorUID) + if err != nil { + return nil, err + } + if err := NewWorkerWorkflow(s.Interface).releaseBoundActor(ctx, worker, assignment); err != nil { + return nil, err + } + return released, nil +} + +// assertConcurrentCrashKept verifies that a finalize step racing +// workerDeletedDuringReleaseStore failed as Aborted and left the actor CRASHED +// with the crash DeleteWorker recorded, rather than committing over it. +func assertConcurrentCrashKept(t *testing.T, ctx context.Context, st store.Interface, actorRef resources.ActorRef, finalizeErr error, wantCrash string) { + t.Helper() + if got := apierror.Code(finalizeErr); got != codes.Aborted { + t.Errorf("finalize error = %v, want code %v", finalizeErr, codes.Aborted) + } + got, err := st.GetActor(ctx, actorRef) + if err != nil { + t.Fatalf("GetActor: %v", err) + } + if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_CRASHED { + t.Errorf("state = %v, want %v", got.GetStatus().GetState(), ateapipb.ActorState_ACTOR_STATE_CRASHED) + } + if msg := got.GetStatus().GetCrash().GetMessage(); msg != wantCrash { + t.Errorf("crash message = %q, want %q", msg, wantCrash) + } +} diff --git a/cmd/ateapi/internal/controlapi/workflow_worker_delete_test.go b/cmd/ateapi/internal/controlapi/workflow_worker_delete_test.go index 15ebdc91ba..7f77931523 100644 --- a/cmd/ateapi/internal/controlapi/workflow_worker_delete_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_worker_delete_test.go @@ -111,9 +111,9 @@ func TestDeleteWorkerWorkflow_ReleasesBoundActor(t *testing.T) { // shared crash path, which cannot know which workflow was in flight. a.Status.LastAssignedGeneration = 3 a.Status.Snapshots = []*ateapipb.Snapshot{ - newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "last"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), - newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "partial-snapshot"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), - newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "partial-local-snapshot", ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS), + newDurableSnapshot(1, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "last", someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "last"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED), + newDurableSnapshot(2, ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "partial-snapshot", someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "partial-snapshot"), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING), + newLocalSnapshot(3, ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, "", "partial-local-snapshot", ""), } }) assignAPIWorker(t, ctx, persistence, apiWorkerName, actor.GetMetadata().GetUid()) @@ -137,19 +137,19 @@ func TestDeleteWorkerWorkflow_ReleasesBoundActor(t *testing.T) { } // In-progress checkpoints are kept so delete or revert can clean them up // while assigned_node or the object prefix is still recorded. - if _, gotSt := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS); gotSt.GetLocal().GetSnapshotName() != "partial-local-snapshot" { - t.Errorf("in-progress local checkpoint = %q, want %q preserved", gotSt.GetLocal().GetSnapshotName(), "partial-local-snapshot") + if gotSnap := snapshotAtLatestGeneration(got.GetStatus()); gotSnap.GetUuid() != "partial-local-snapshot" { + t.Errorf("in-progress local checkpoint = %q, want %q preserved", gotSnap.GetUuid(), "partial-local-snapshot") } // The durable one is kept: it names the prefix whatever atelet already // uploaded lives under, which delete or revert needs to collect it. - _, gotInProgressSt := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS) - if want := someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "partial-snapshot"); gotInProgressSt.GetObject().GetSnapshotUri() != want { + gotInProgressSnap := findLatestDurableSnapshot(got.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING) + if want := someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "partial-snapshot"); gotInProgressSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != want { t.Errorf("in-progress external checkpoint not preserved: %v", got.GetStatus()) } // The last completed snapshot is what makes the actor resumable, so it stays. - _, gotCompletedSt := findLatestSnapshotStorage(got.GetStatus(), ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) - if want := someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "last"); gotCompletedSt.GetObject().GetSnapshotUri() != want { - t.Errorf("external snapshot = %q, want it preserved as %q", gotCompletedSt.GetObject().GetSnapshotUri(), want) + gotCompletedSnap := findLatestDurableSnapshot(got.GetStatus(), ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED) + if want := someActorSnapshotURI(t, testStorageLocation, apiActorRef.Atespace, "last"); gotCompletedSnap.GetDurableSnapshot().GetObject().GetSnapshotUri() != want { + t.Errorf("external snapshot = %q, want it preserved as %q", gotCompletedSnap.GetDurableSnapshot().GetObject().GetSnapshotUri(), want) } } diff --git a/cmd/ateapi/internal/store/atepg/tag.go b/cmd/ateapi/internal/store/atepg/tag.go index 543e1fa15a..2a42f7fe8e 100644 --- a/cmd/ateapi/internal/store/atepg/tag.go +++ b/cmd/ateapi/internal/store/atepg/tag.go @@ -189,15 +189,15 @@ func isAllowedTagSnapshotTransition(stored, mutated *ateapipb.Snapshot) bool { if proto.Equal(stored, mutated) || stored == nil { return true } - if mutated == nil || len(stored.GetStorage()) != 1 || len(mutated.GetStorage()) != 1 { + if mutated == nil || stored.GetDurableSnapshot() == nil || mutated.GetDurableSnapshot() == nil { return false } - if stored.GetStorage()[0].GetStatus() != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS || - mutated.GetStorage()[0].GetStatus() != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED { + if stored.GetDurableSnapshot().GetStatus() != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING || + mutated.GetDurableSnapshot().GetStatus() != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED { return false } expected := proto.CloneOf(stored) - expected.Storage[0].Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED + expected.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED return proto.Equal(expected, mutated) } diff --git a/cmd/ateapi/internal/store/atepg/tag_test.go b/cmd/ateapi/internal/store/atepg/tag_test.go index 3d51f7a6eb..9d4682dfc2 100644 --- a/cmd/ateapi/internal/store/atepg/tag_test.go +++ b/cmd/ateapi/internal/store/atepg/tag_test.go @@ -38,12 +38,11 @@ func createTestTag(t *testing.T, s *Persistence, tagAtespace, tagName string) *a Snapshot: &ateapipb.Snapshot{ Generation: 1, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://bucket/atespaces/" + tagAtespace + "/tags/" + tagName}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://bucket/atespaces/" + tagAtespace + "/tags/" + tagName}, + }, }, }, }) diff --git a/cmd/ateapi/internal/store/storecontract/contract.go b/cmd/ateapi/internal/store/storecontract/contract.go index 3589e6849c..f356967998 100644 --- a/cmd/ateapi/internal/store/storecontract/contract.go +++ b/cmd/ateapi/internal/store/storecontract/contract.go @@ -870,12 +870,11 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac Snapshots: []*ateapipb.Snapshot{{ Generation: 1, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: testActorSnapshotURI("gs://bucket", testAtespace, "snapshot-1")}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: testActorSnapshotURI("gs://bucket", testAtespace, "snapshot-1")}, + }, }}, }, } @@ -888,12 +887,11 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac Snapshots: []*ateapipb.Snapshot{{ Generation: 1, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: testActorSnapshotURI("gs://bucket", testAtespace, "snapshot-2")}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: testActorSnapshotURI("gs://bucket", testAtespace, "snapshot-2")}, + }, }}, }, } @@ -1169,19 +1167,18 @@ func newTestSuspendedActor(atespace, name string) *ateapipb.Actor { Snapshots: []*ateapipb.Snapshot{{ Generation: 1, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_ACTOR, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: testActorSnapshotURI("gs://private", atespace, name)}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: testActorSnapshotURI("gs://private", atespace, name)}, + }, }}, }, } } // newTestInProgressTag builds the row CreateTag reserves for a -// tag of actor: ATESPACE-scoped, with an IN_PROGRESS snapshot naming where the +// tag of actor: ATESPACE-scoped, with a PENDING snapshot naming where the // copy is going. func newTestInProgressTag(name string, actor *ateapipb.Actor) *ateapipb.Tag { atespace := actor.GetMetadata().GetAtespace() @@ -1193,14 +1190,13 @@ func newTestInProgressTag(name string, actor *ateapipb.Actor) *ateapipb.Tag { Snapshot: &ateapipb.Snapshot{ Generation: 1, Owner: ateapipb.SnapshotOwner_SNAPSHOT_OWNER_TAG, - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING, Object: &ateapipb.ObjectSnapshot{ SnapshotUri: testTagSnapshotURI("gs://private", atespace, name), }, - }}, + }, }, }, } @@ -1242,10 +1238,10 @@ func runTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) if err != nil { t.Fatalf("GetTag failed: %v", err) } - // A reserved tag has an IN_PROGRESS snapshot storage entry and is not + // A reserved tag has a PENDING snapshot storage entry and is not // usable until the copy lands and finalize marks it COMPLETED. - if got := tag.GetStatus().GetSnapshot().GetStorage()[0].GetStatus(); got != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS { - t.Errorf("reserved tag snapshot storage status = %v, want IN_PROGRESS", got) + if got := tag.GetStatus().GetSnapshot().GetDurableSnapshot().GetStatus(); got != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING { + t.Errorf("reserved tag snapshot storage status = %v, want PENDING", got) } if diff := cmp.Diff(inProgressTag, tag, protocmp.Transform(), ignoreUID, ignoreVersion, ignoreTimestamps); diff != "" { t.Errorf("stored tag mismatch (-want +got):\n%s", diff) @@ -1262,14 +1258,11 @@ func runTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) t.Fatalf("finalizing tag failed: %v", err) } wantURI := testTagSnapshotURI("gs://private", "team-a", "production") - var gotURI string - if len(ready.GetStatus().GetSnapshot().GetStorage()) > 0 { - gotURI = ready.GetStatus().GetSnapshot().GetStorage()[0].GetObject().GetSnapshotUri() - } + gotURI := ready.GetStatus().GetSnapshot().GetDurableSnapshot().GetObject().GetSnapshotUri() if gotURI != wantURI { t.Errorf("finalized tag snapshot uri = %q, want %q", gotURI, wantURI) } - if got := ready.GetStatus().GetSnapshot().GetStorage()[0].GetStatus(); got != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED { + if got := ready.GetStatus().GetSnapshot().GetDurableSnapshot().GetStatus(); got != ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED { t.Errorf("finalized tag snapshot storage status = %v, want COMPLETED", got) } @@ -1374,13 +1367,13 @@ func runTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) { name: "snapshot uri", mutate: func(toUpdate *ateapipb.Tag) { - toUpdate.Status.Snapshot.Storage[0].Object.SnapshotUri = "gs://private/elsewhere" + toUpdate.Status.Snapshot.DurableSnapshot.Object.SnapshotUri = "gs://private/elsewhere" }, }, { name: "snapshot content scope", mutate: func(toUpdate *ateapipb.Tag) { - toUpdate.Status.Snapshot.Storage[0].Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES + toUpdate.Status.Snapshot.Fidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES }, }, { @@ -1408,6 +1401,41 @@ func runTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) } }) + t.Run("Tag_RecordSnapshotAfterReserve", func(t *testing.T) { + s := setup(t) + ctx := context.Background() + actor := seedSuspendedActor(t, s, "team-a", "actor-1") + tagRef := resources.TagRef{Atespace: "team-a", Name: "production"} + + // A tag's snapshot is keyed on the UID the store assigns, so the tag + // is reserved without one and the snapshot is recorded by an update. + input := newTestInProgressTag(tagRef.Name, actor) + pending := input.Status.Snapshot + input.Status.Snapshot = nil + reserved, err := s.CreateTag(ctx, input) + if err != nil { + t.Fatalf("CreateTag without a snapshot failed: %v", err) + } + recorded, err := s.UpdateTag(ctx, tagRef, store.PreconditionFrom(reserved), func(toUpdate *ateapipb.Tag) error { + toUpdate.Status.Snapshot = proto.CloneOf(pending) + return nil + }) + if err != nil { + t.Fatalf("recording the snapshot of a reserved tag failed: %v", err) + } + if diff := cmp.Diff(pending, recorded.GetStatus().GetSnapshot(), protocmp.Transform()); diff != "" { + t.Errorf("recorded snapshot mismatch (-want +got):\n%s", diff) + } + + // Once recorded, the snapshot is fixed like any other. + if _, err := s.UpdateTag(ctx, tagRef, store.PreconditionFrom(recorded), func(toUpdate *ateapipb.Tag) error { + toUpdate.Status.Snapshot.DurableSnapshot.Object.SnapshotUri = "gs://private/elsewhere" + return nil + }); !errors.Is(err, store.ErrImmutableField) { + t.Errorf("rewriting a recorded snapshot = %v, want one matching store.ErrImmutableField", err) + } + }) + t.Run("CreateTag_ReusedTagName", func(t *testing.T) { s := setup(t) ctx := context.Background() @@ -1526,9 +1554,9 @@ func runTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) } // finalizeTag mutates a reserved tag the way the tag workflow's second -// transaction does: it marks the in-progress snapshot storage COMPLETED. +// transaction does: it marks the pending snapshot storage COMPLETED. func finalizeTag(toUpdate *ateapipb.Tag) error { - toUpdate.Status.Snapshot.Storage[0].Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED + toUpdate.Status.Snapshot.DurableSnapshot.Status = ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED return nil } diff --git a/cmd/kubectl-ate/internal/cmd/tag_test.go b/cmd/kubectl-ate/internal/cmd/tag_test.go index 1f9b769433..fe63752ea5 100644 --- a/cmd/kubectl-ate/internal/cmd/tag_test.go +++ b/cmd/kubectl-ate/internal/cmd/tag_test.go @@ -61,12 +61,11 @@ func TestUpdateTagScope(t *testing.T) { Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE, Status: &ateapipb.TagStatus{ Snapshot: &ateapipb.Snapshot{ - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/space-1/tags/tag-1"}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/space-1/tags/tag-1"}, + }, }, }, } diff --git a/cmd/kubectl-ate/internal/printer/printer.go b/cmd/kubectl-ate/internal/printer/printer.go index a5e9056c26..f77cbcecf6 100644 --- a/cmd/kubectl-ate/internal/printer/printer.go +++ b/cmd/kubectl-ate/internal/printer/printer.go @@ -344,9 +344,10 @@ func PrintTagsTo(out io.Writer, tags []*ateapipb.Tag, format string) error { // A pending tag has no snapshot yet, so neither its URI nor its // fidelity says anything. snapshotURI, fidelity := "", "" - if st := durableSnapshotStorage(tag.GetStatus().GetSnapshot()); st != nil { + snap := tag.GetStatus().GetSnapshot() + if st := durableSnapshotStorage(snap); st != nil { snapshotURI = st.GetObject().GetSnapshotUri() - fidelity = st.GetFidelity().String() + fidelity = snap.GetFidelity().String() } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", tag.GetMetadata().GetAtespace(), tag.GetMetadata().GetName(), tag.GetScope(), @@ -360,12 +361,10 @@ func PrintTagsTo(out io.Writer, tags []*ateapipb.Tag, format string) error { } func durableSnapshotStorage(snapshot *ateapipb.Snapshot) *ateapipb.SnapshotStorage { - for _, st := range snapshot.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE && - st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && - st.GetObject().GetSnapshotUri() != "" { - return st - } + st := snapshot.GetDurableSnapshot() + if st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && + st.GetObject().GetSnapshotUri() != "" { + return st } return nil } diff --git a/cmd/kubectl-ate/internal/printer/printer_test.go b/cmd/kubectl-ate/internal/printer/printer_test.go index 6d7b53cdad..eded1c9b5a 100644 --- a/cmd/kubectl-ate/internal/printer/printer_test.go +++ b/cmd/kubectl-ate/internal/printer/printer_test.go @@ -612,12 +612,11 @@ func TestPrintTagsTo_Table(t *testing.T) { Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED, Status: &ateapipb.TagStatus{ Snapshot: &ateapipb.Snapshot{ - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v2"}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v2"}, + }, }, }, }, @@ -630,12 +629,11 @@ func TestPrintTagsTo_Table(t *testing.T) { Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE, Status: &ateapipb.TagStatus{ Snapshot: &ateapipb.Snapshot{ - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v1"}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v1"}, + }, }, }, }, @@ -650,12 +648,11 @@ func TestPrintTagsTo_Table(t *testing.T) { Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE, Status: &ateapipb.TagStatus{ Snapshot: &ateapipb.Snapshot{ - Storage: []*ateapipb.SnapshotStorage{{ - Durability: ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE, - Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS, - Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, - Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v3"}, - }}, + Fidelity: ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_MEMORY, + DurableSnapshot: &ateapipb.SnapshotStorage{ + Status: ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING, + Object: &ateapipb.ObjectSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v3"}, + }, }, }, }, diff --git a/docs/api-guide.md b/docs/api-guide.md index d7bc87255d..cdf0766025 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -372,11 +372,48 @@ snapshotConfig: 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 — the golden tag lives in the reserved `ate-golden` atespace — under `gs://my-bucket/secret-agent/atespaces/ate-golden/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 prefix *is* its snapshot's. Both owners are keyed on their UID, so recreating an actor or tag under the same name never inherits its predecessor's objects. A pending tag records its base location in `status.storageLocation`; together with its atespace and UID, this identifies any partial copy to collect if creation fails. +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 +prefix *is* its snapshot's. Both owners are keyed on their UID, so recreating an +actor or tag under the same name never inherits its predecessor's objects. A tag +records its snapshot's URI in `status.snapshot` right after it is reserved, +before anything is copied, so a tag whose creation failed still names any +partial copy to collect. An owner is collected by deleting everything under its prefix, and it can delete nothing else. That is what makes a borrowed snapshot safe: an actor created from a tag points at a URI under `tags/`, which its own prefix does not cover. See [Snapshot lifetime](#snapshot-lifetime). -An `Actor` reports its snapshots in the server-managed `status.snapshots` (and `status.lastAssignedGeneration`) and a `Tag` in `status.snapshot`, each a `Snapshot` carrying `generation`, `owner`, `actorTemplateUid`, and `storage`. The URI and `fidelity` are recorded in a `DURABLE` `SnapshotStorage` entry (`object.snapshotUri` and `fidelity`) when the snapshot is written. `actorTemplateUid` records the `ActorTemplate` whose sandbox the guest state was captured from, which is not always the template the actor points at now: an actor may be repointed while `SUSPENDED`, and the snapshot on disk still came from the old one. A resume that finds the two disagree restores the durable data only and boots the guest fresh, because memory captured under one sandbox image cannot be resumed under another. An `ActorTemplate` references its golden tag with the `ObjectRef` in `status.goldenSnapshotStatus.goldenTag`. These status fields are server-owned and ignored on input. Parse a URI only against the scheme above. +An `Actor` reports its snapshots in the server-managed `status.snapshots` (and +`status.lastAssignedGeneration`) and a `Tag` in `status.snapshot`. Each entry is +a `Snapshot` with the following fields: + +- `uuid`: The snapshot's server-generated name. It names the node-local + checkpoint and forms the last segment of the snapshot's URI in object storage + (so a tag's snapshot is named by the tag's UID). +- `generation`: The monotonically increasing snapshot generation on the owner. +- `owner`: The resource kind that owns the snapshot (`SNAPSHOT_OWNER_ACTOR` or + `SNAPSHOT_OWNER_TAG`). +- `fidelity`: The state layers the snapshot holds (`SNAPSHOT_FIDELITY_VOLUMES`, + `SNAPSHOT_FIDELITY_ROOTFS`, or `SNAPSHOT_FIDELITY_MEMORY`). +- `actorTemplateUid`: The UID of the `ActorTemplate` whose sandbox the guest + state was captured from. This is not always the template the actor points at + now: an actor may be repointed while `SUSPENDED`, while the snapshot on disk + still came from the old one. A resume that finds the two disagree restores the + durable data only and boots the guest fresh, because memory captured under one + sandbox image cannot be resumed under another. +- `locality`: The node that holds the snapshot's local files. It is set when a + pause completes and cleared once those files are deleted from the node (when + the actor is next paused or suspended, or is reverted). A resume restores from + the files without deleting them, so an actor resumed from pause still reports + where they are. +- `durableSnapshot`: The copy in object storage, with its URI in + `object.snapshotUri` and a `status` that is `PENDING` while the copy is being + written and `COMPLETED` once it can be restored from. A snapshot that exists + only on a node has no `durableSnapshot`. A tag's snapshot always has one, and + the tag can seed actors only once it is `COMPLETED`. + +An `ActorTemplate` references its golden tag with the `ObjectRef` in +`status.goldenSnapshotStatus.goldenTag`. These status fields are server-owned +and ignored on input. Parse a URI only against the scheme above. An `ActorTemplate` belongs to one atespace, but one `storageLocation` still holds snapshots for many atespaces: the golden actor lives in the reserved `ate-golden` atespace, and a `PUBLISHED` snapshot may be cloned from other atespaces. The `` level exists so that access can be granted per tenant: an object-storage policy can only condition on an **object-name prefix**, and cannot read the identity recorded inside a snapshot's manifest. Binding a per-atespace grant on GCS looks like: diff --git a/internal/e2e/suites/demo/demo_test.go b/internal/e2e/suites/demo/demo_test.go index 1b2ea0dae4..661615b57a 100644 --- a/internal/e2e/suites/demo/demo_test.go +++ b/internal/e2e/suites/demo/demo_test.go @@ -753,7 +753,7 @@ func validateSnapshotFidelity(ctx context.Context, t *testing.T, clients *e2e.Cl if durableSnapshotURI(actor.GetStatus()) == "" { t.Fatal("suspended Actor has no external snapshot") } - if got := durableSnapshotStorage(actor.GetStatus()).GetFidelity(); got != want { + if got := durableSnapshot(actor.GetStatus()).GetFidelity(); got != want { t.Errorf("snapshot %q content scope = %v, want %v", durableSnapshotURI(actor.GetStatus()), got, want) } } @@ -761,13 +761,11 @@ func validateSnapshotFidelity(ctx context.Context, t *testing.T, clients *e2e.Cl func durableSnapshot(status *ateapipb.ActorStatus) *ateapipb.Snapshot { var best *ateapipb.Snapshot for _, snap := range status.GetSnapshots() { - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE && - st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && - st.GetObject().GetSnapshotUri() != "" { - if best == nil || snap.GetGeneration() > best.GetGeneration() { - best = snap - } + st := snap.GetDurableSnapshot() + if st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED && + st.GetObject().GetSnapshotUri() != "" { + if best == nil || snap.GetGeneration() > best.GetGeneration() { + best = snap } } } @@ -775,13 +773,7 @@ func durableSnapshot(status *ateapipb.ActorStatus) *ateapipb.Snapshot { } func durableSnapshotStorage(status *ateapipb.ActorStatus) *ateapipb.SnapshotStorage { - snap := durableSnapshot(status) - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE { - return st - } - } - return nil + return durableSnapshot(status).GetDurableSnapshot() } func durableSnapshotURI(status *ateapipb.ActorStatus) string { @@ -790,22 +782,15 @@ func durableSnapshotURI(status *ateapipb.ActorStatus) string { func hasLocalSnapshot(status *ateapipb.ActorStatus) bool { for _, snap := range status.GetSnapshots() { - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL { - return true - } + if snap.GetLocality() != "" && snap.GetUuid() != "" { + return true } } return false } func tagSnapshotURI(tag *ateapipb.Tag) string { - for _, st := range tag.GetStatus().GetSnapshot().GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE { - return st.GetObject().GetSnapshotUri() - } - } - return "" + return tag.GetStatus().GetSnapshot().GetDurableSnapshot().GetObject().GetSnapshotUri() } // validateSecondFileCounter checks the counter the workload keeps in its second diff --git a/internal/e2e/suites/security/crossatespace_test.go b/internal/e2e/suites/security/crossatespace_test.go index 355229d310..104d0b47d8 100644 --- a/internal/e2e/suites/security/crossatespace_test.go +++ b/internal/e2e/suites/security/crossatespace_test.go @@ -465,11 +465,9 @@ func durableSnapshotURI(status *ateapipb.ActorStatus) string { } func snapshotURI(snap *ateapipb.Snapshot) string { - for _, st := range snap.GetStorage() { - if st.GetDurability() == ateapipb.SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE && - st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED { - return st.GetObject().GetSnapshotUri() - } + st := snap.GetDurableSnapshot() + if st.GetStatus() == ateapipb.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED { + return st.GetObject().GetSnapshotUri() } return "" } diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index 9e318ed685..fbf2536a06 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -2874,8 +2874,8 @@ type UploadPausedCheckpointRequest struct { // For metrics attribution, like on CheckpointRequest. ActorTemplateAtespace string `protobuf:"bytes,4,opt,name=actor_template_atespace,json=actorTemplateAtespace,proto3" json:"actor_template_atespace,omitempty"` ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - // The local checkpoint to upload: LocalSnapshot.snapshot_name recorded - // at pause time. + // The local checkpoint to upload: the uuid of the actor's pause snapshot + // (ateapi Snapshot.uuid). LocalSnapshotName string `protobuf:"bytes,6,opt,name=local_snapshot_name,json=localSnapshotName,proto3" json:"local_snapshot_name,omitempty"` // Destination object-storage URI (the actor's in-progress snapshot URI). DestinationSnapshotUri string `protobuf:"bytes,7,opt,name=destination_snapshot_uri,json=destinationSnapshotUri,proto3" json:"destination_snapshot_uri,omitempty"` diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index 35cf61db1f..32446bced8 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -575,8 +575,8 @@ message UploadPausedCheckpointRequest { string actor_template_atespace = 4; string actor_template_name = 5; - // The local checkpoint to upload: LocalSnapshot.snapshot_name recorded - // at pause time. + // The local checkpoint to upload: the uuid of the actor's pause snapshot + // (ateapi Snapshot.uuid). string local_snapshot_name = 6; // Destination object-storage URI (the actor's in-progress snapshot URI). diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index 4633400824..597cbfaef2 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -88,69 +88,14 @@ func (SnapshotOwner) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{0} } -// SnapshotDurability indicates the durability level of the snapshot. -// TODO: potentially rename this to survivability level. Is it at the actor or snapshot level? -type SnapshotDurability int32 - -const ( - SnapshotDurability_SNAPSHOT_DURABILITY_UNSPECIFIED SnapshotDurability = 0 - // Node-local checkpoint on the worker node - // Survives node reboot. - SnapshotDurability_SNAPSHOT_DURABILITY_LOCAL SnapshotDurability = 1 - // Durable snapshot in external storage. - // Survives node crash and deletion. - SnapshotDurability_SNAPSHOT_DURABILITY_DURABLE SnapshotDurability = 2 // Keep this in sync with SnapshotStorage.durability's maximum. -) - -// Enum value maps for SnapshotDurability. -var ( - SnapshotDurability_name = map[int32]string{ - 0: "SNAPSHOT_DURABILITY_UNSPECIFIED", - 1: "SNAPSHOT_DURABILITY_LOCAL", - 2: "SNAPSHOT_DURABILITY_DURABLE", - } - SnapshotDurability_value = map[string]int32{ - "SNAPSHOT_DURABILITY_UNSPECIFIED": 0, - "SNAPSHOT_DURABILITY_LOCAL": 1, - "SNAPSHOT_DURABILITY_DURABLE": 2, - } -) - -func (x SnapshotDurability) Enum() *SnapshotDurability { - p := new(SnapshotDurability) - *p = x - return p -} - -func (x SnapshotDurability) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (SnapshotDurability) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[1].Descriptor() -} - -func (SnapshotDurability) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[1] -} - -func (x SnapshotDurability) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use SnapshotDurability.Descriptor instead. -func (SnapshotDurability) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{1} -} - -// SnapshotStorageStatus indicates the lifecycle stage of a snapshot storage entry. +// SnapshotStorageStatus indicates the lifecycle stage of a durable snapshot. type SnapshotStorageStatus int32 const ( SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED SnapshotStorageStatus = 0 - // Snapshot creation or promotion is in progress. - SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_IN_PROGRESS SnapshotStorageStatus = 1 - // Snapshot is complete and ready for use. + // Snapshot creation in durable storage is in progress. + SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_PENDING SnapshotStorageStatus = 1 + // Snapshot is complete and ready for use in durable storage. SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED SnapshotStorageStatus = 2 // Keep this in sync with SnapshotStorage.status's maximum. ) @@ -158,12 +103,12 @@ const ( var ( SnapshotStorageStatus_name = map[int32]string{ 0: "SNAPSHOT_STORAGE_STATUS_UNSPECIFIED", - 1: "SNAPSHOT_STORAGE_STATUS_IN_PROGRESS", + 1: "SNAPSHOT_STORAGE_STATUS_PENDING", 2: "SNAPSHOT_STORAGE_STATUS_COMPLETED", } SnapshotStorageStatus_value = map[string]int32{ "SNAPSHOT_STORAGE_STATUS_UNSPECIFIED": 0, - "SNAPSHOT_STORAGE_STATUS_IN_PROGRESS": 1, + "SNAPSHOT_STORAGE_STATUS_PENDING": 1, "SNAPSHOT_STORAGE_STATUS_COMPLETED": 2, } ) @@ -179,11 +124,11 @@ func (x SnapshotStorageStatus) String() string { } func (SnapshotStorageStatus) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[2].Descriptor() + return file_ateapi_proto_enumTypes[1].Descriptor() } func (SnapshotStorageStatus) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[2] + return &file_ateapi_proto_enumTypes[1] } func (x SnapshotStorageStatus) Number() protoreflect.EnumNumber { @@ -192,7 +137,7 @@ func (x SnapshotStorageStatus) Number() protoreflect.EnumNumber { // Deprecated: Use SnapshotStorageStatus.Descriptor instead. func (SnapshotStorageStatus) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{2} + return file_ateapi_proto_rawDescGZIP(), []int{1} } // SnapshotFidelity orders the state layers a snapshot can hold, lowest to @@ -240,11 +185,11 @@ func (x SnapshotFidelity) String() string { } func (SnapshotFidelity) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[3].Descriptor() + return file_ateapi_proto_enumTypes[2].Descriptor() } func (SnapshotFidelity) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[3] + return &file_ateapi_proto_enumTypes[2] } func (x SnapshotFidelity) Number() protoreflect.EnumNumber { @@ -253,7 +198,7 @@ func (x SnapshotFidelity) Number() protoreflect.EnumNumber { // Deprecated: Use SnapshotFidelity.Descriptor instead. func (SnapshotFidelity) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{3} + return file_ateapi_proto_rawDescGZIP(), []int{2} } type TagScope int32 @@ -293,11 +238,11 @@ func (x TagScope) String() string { } func (TagScope) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[4].Descriptor() + return file_ateapi_proto_enumTypes[3].Descriptor() } func (TagScope) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[4] + return &file_ateapi_proto_enumTypes[3] } func (x TagScope) Number() protoreflect.EnumNumber { @@ -306,7 +251,7 @@ func (x TagScope) Number() protoreflect.EnumNumber { // Deprecated: Use TagScope.Descriptor instead. func (TagScope) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{4} + return file_ateapi_proto_rawDescGZIP(), []int{3} } type ActorState int32 @@ -363,11 +308,11 @@ func (x ActorState) String() string { } func (ActorState) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[5].Descriptor() + return file_ateapi_proto_enumTypes[4].Descriptor() } func (ActorState) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[5] + return &file_ateapi_proto_enumTypes[4] } func (x ActorState) Number() protoreflect.EnumNumber { @@ -376,7 +321,7 @@ func (x ActorState) Number() protoreflect.EnumNumber { // Deprecated: Use ActorState.Descriptor instead. func (ActorState) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{5} + return file_ateapi_proto_rawDescGZIP(), []int{4} } // SandboxClass selects the sandbox runtime family. Snapshots are not portable @@ -414,11 +359,11 @@ func (x SandboxClass) String() string { } func (SandboxClass) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[6].Descriptor() + return file_ateapi_proto_enumTypes[5].Descriptor() } func (SandboxClass) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[6] + return &file_ateapi_proto_enumTypes[5] } func (x SandboxClass) Number() protoreflect.EnumNumber { @@ -427,7 +372,7 @@ func (x SandboxClass) Number() protoreflect.EnumNumber { // Deprecated: Use SandboxClass.Descriptor instead. func (SandboxClass) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{6} + return file_ateapi_proto_rawDescGZIP(), []int{5} } // GoldenSnapshotMode selects whether a template builds a golden snapshot. @@ -464,11 +409,11 @@ func (x GoldenSnapshotMode) String() string { } func (GoldenSnapshotMode) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[7].Descriptor() + return file_ateapi_proto_enumTypes[6].Descriptor() } func (GoldenSnapshotMode) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[7] + return &file_ateapi_proto_enumTypes[6] } func (x GoldenSnapshotMode) Number() protoreflect.EnumNumber { @@ -477,7 +422,7 @@ func (x GoldenSnapshotMode) Number() protoreflect.EnumNumber { // Deprecated: Use GoldenSnapshotMode.Descriptor instead. func (GoldenSnapshotMode) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{7} + return file_ateapi_proto_rawDescGZIP(), []int{6} } // ActorMetadataField selects one identity field of the actor. @@ -517,11 +462,11 @@ func (x ActorMetadataField) String() string { } func (ActorMetadataField) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[8].Descriptor() + return file_ateapi_proto_enumTypes[7].Descriptor() } func (ActorMetadataField) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[8] + return &file_ateapi_proto_enumTypes[7] } func (x ActorMetadataField) Number() protoreflect.EnumNumber { @@ -530,7 +475,7 @@ func (x ActorMetadataField) Number() protoreflect.EnumNumber { // Deprecated: Use ActorMetadataField.Descriptor instead. func (ActorMetadataField) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{8} + return file_ateapi_proto_rawDescGZIP(), []int{7} } type WorkerState int32 @@ -568,11 +513,11 @@ func (x WorkerState) String() string { } func (WorkerState) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[9].Descriptor() + return file_ateapi_proto_enumTypes[8].Descriptor() } func (WorkerState) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[9] + return &file_ateapi_proto_enumTypes[8] } func (x WorkerState) Number() protoreflect.EnumNumber { @@ -581,7 +526,7 @@ func (x WorkerState) Number() protoreflect.EnumNumber { // Deprecated: Use WorkerState.Descriptor instead. func (WorkerState) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{9} + return file_ateapi_proto_rawDescGZIP(), []int{8} } type ExternalVolume_Status int32 @@ -623,11 +568,11 @@ func (x ExternalVolume_Status) String() string { } func (ExternalVolume_Status) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[10].Descriptor() + return file_ateapi_proto_enumTypes[9].Descriptor() } func (ExternalVolume_Status) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[10] + return &file_ateapi_proto_enumTypes[9] } func (x ExternalVolume_Status) Number() protoreflect.EnumNumber { @@ -636,7 +581,7 @@ func (x ExternalVolume_Status) Number() protoreflect.EnumNumber { // Deprecated: Use ExternalVolume_Status.Descriptor instead. func (ExternalVolume_Status) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{6, 0} + return file_ateapi_proto_rawDescGZIP(), []int{5, 0} } // ObjectSnapshot addresses a snapshot held in object storage. @@ -688,99 +633,26 @@ func (x *ObjectSnapshot) GetSnapshotUri() string { return "" } -// LocalSnapshot records information about a node-local snapshot. -type LocalSnapshot struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The name of the local checkpoint on the node. Checkpoint names are - // server-generated UUIDs, but any resource name is valid here. - // - // TODO: consider removing this since it's the same as the snapshot_id - // - // +k8s:required - // +k8s:format=k8s-short-name - SnapshotName string `protobuf:"bytes,1,opt,name=snapshot_name,json=snapshotName,proto3" json:"snapshot_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *LocalSnapshot) Reset() { - *x = LocalSnapshot{} - mi := &file_ateapi_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *LocalSnapshot) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LocalSnapshot) ProtoMessage() {} - -func (x *LocalSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LocalSnapshot.ProtoReflect.Descriptor instead. -func (*LocalSnapshot) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{1} -} - -func (x *LocalSnapshot) GetSnapshotName() string { - if x != nil { - return x.SnapshotName - } - return "" -} - -// SnapshotStorage records the physical storage representation of a Snapshot. +// SnapshotStorage records the durable storage representation of a Snapshot. type SnapshotStorage struct { state protoimpl.MessageState `protogen:"open.v1"` - // durability is the durability tier of this storage entry. Only - // SNAPSHOT_DURABILITY_LOCAL and SNAPSHOT_DURABILITY_DURABLE are allowed. - // - // +k8s:required - // +k8s:minimum=1 - // +k8s:maximum=2 # keep this in sync with the SnapshotDurability enum - Durability SnapshotDurability `protobuf:"varint,1,opt,name=durability,proto3,enum=ateapi.SnapshotDurability" json:"durability,omitempty"` - // status is the lifecycle status of this storage representation. + // status is the lifecycle status of the durable snapshot. // // +k8s:required // +k8s:minimum=1 // +k8s:maximum=2 # keep this in sync with the SnapshotStorageStatus enum - Status SnapshotStorageStatus `protobuf:"varint,2,opt,name=status,proto3,enum=ateapi.SnapshotStorageStatus" json:"status,omitempty"` - // fidelity is what the snapshot holds. - // - // +k8s:required - // +k8s:minimum=1 - // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum - Fidelity SnapshotFidelity `protobuf:"varint,3,opt,name=fidelity,proto3,enum=ateapi.SnapshotFidelity" json:"fidelity,omitempty"` - // Exactly one of local or object must be set. - // - // local snapshot is stored on the node where the actor was suspended. - // - // +k8s:optional - // +k8s:unionMember - Local *LocalSnapshot `protobuf:"bytes,4,opt,name=local,proto3" json:"local,omitempty"` + Status SnapshotStorageStatus `protobuf:"varint,1,opt,name=status,proto3,enum=ateapi.SnapshotStorageStatus" json:"status,omitempty"` // object snapshot is stored in object storage. // - // +k8s:optional - // +k8s:unionMember - Object *ObjectSnapshot `protobuf:"bytes,5,opt,name=object,proto3" json:"object,omitempty"` + // +k8s:required + Object *ObjectSnapshot `protobuf:"bytes,2,opt,name=object,proto3" json:"object,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SnapshotStorage) Reset() { *x = SnapshotStorage{} - mi := &file_ateapi_proto_msgTypes[2] + mi := &file_ateapi_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -792,7 +664,7 @@ func (x *SnapshotStorage) String() string { func (*SnapshotStorage) ProtoMessage() {} func (x *SnapshotStorage) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[2] + mi := &file_ateapi_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -805,14 +677,7 @@ func (x *SnapshotStorage) ProtoReflect() protoreflect.Message { // Deprecated: Use SnapshotStorage.ProtoReflect.Descriptor instead. func (*SnapshotStorage) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{2} -} - -func (x *SnapshotStorage) GetDurability() SnapshotDurability { - if x != nil { - return x.Durability - } - return SnapshotDurability_SNAPSHOT_DURABILITY_UNSPECIFIED + return file_ateapi_proto_rawDescGZIP(), []int{1} } func (x *SnapshotStorage) GetStatus() SnapshotStorageStatus { @@ -822,20 +687,6 @@ func (x *SnapshotStorage) GetStatus() SnapshotStorageStatus { return SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_UNSPECIFIED } -func (x *SnapshotStorage) GetFidelity() SnapshotFidelity { - if x != nil { - return x.Fidelity - } - return SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED -} - -func (x *SnapshotStorage) GetLocal() *LocalSnapshot { - if x != nil { - return x.Local - } - return nil -} - func (x *SnapshotStorage) GetObject() *ObjectSnapshot { if x != nil { return x.Object @@ -846,39 +697,57 @@ func (x *SnapshotStorage) GetObject() *ObjectSnapshot { // Snapshot represents one generation of an Actor's or Tag's snapshot state. type Snapshot struct { state protoimpl.MessageState `protogen:"open.v1"` + // uuid is the server-generated unique identifier for this snapshot, passed to + // atelet to identify the local checkpoint on disk and used as the snapshot + // name in object storage. + // + // +k8s:required + // +k8s:format=k8s-short-name + Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // generation is the monotonically increasing generation number of this // snapshot, starting at 1. // // +k8s:required // +k8s:minimum=1 - Generation int32 `protobuf:"varint,1,opt,name=generation,proto3" json:"generation,omitempty"` + Generation int32 `protobuf:"varint,2,opt,name=generation,proto3" json:"generation,omitempty"` // owner identifies the origin and ownership of this snapshot. // // +k8s:required // +k8s:minimum=1 // +k8s:maximum=2 # keep this in sync with the SnapshotOwner enum - Owner SnapshotOwner `protobuf:"varint,2,opt,name=owner,proto3,enum=ateapi.SnapshotOwner" json:"owner,omitempty"` + Owner SnapshotOwner `protobuf:"varint,3,opt,name=owner,proto3,enum=ateapi.SnapshotOwner" json:"owner,omitempty"` + // fidelity is what this snapshot holds. + // + // +k8s:required + // +k8s:minimum=1 + // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum + Fidelity SnapshotFidelity `protobuf:"varint,4,opt,name=fidelity,proto3,enum=ateapi.SnapshotFidelity" json:"fidelity,omitempty"` // UID of the ActorTemplate whose sandbox this snapshot's guest state was // captured from. // // +k8s:optional // +k8s:format=k8s-uuid - ActorTemplateUid string `protobuf:"bytes,3,opt,name=actor_template_uid,json=actorTemplateUid,proto3" json:"actor_template_uid,omitempty"` - // storage lists the storage representations of this snapshot by durability. - // Only SNAPSHOT_DURABILITY_LOCAL and SNAPSHOT_DURABILITY_DURABLE are allowed. + ActorTemplateUid string `protobuf:"bytes,5,opt,name=actor_template_uid,json=actorTemplateUid,proto3" json:"actor_template_uid,omitempty"` + // locality identifies the node holding this snapshot's files (currently the + // Kubernetes node name). Set when a local pause checkpoint completes, and + // cleared once the files are deleted from that node; a resume does not + // delete them. // - // +k8s:required - // +k8s:maxItems=2 - // +k8s:listType=map - // +k8s:listMapKey=durability - Storage []*SnapshotStorage `protobuf:"bytes,4,rep,name=storage,proto3" json:"storage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // +k8s:optional + // +k8s:format=k8s-long-name + Locality string `protobuf:"bytes,6,opt,name=locality,proto3" json:"locality,omitempty"` + // durable_snapshot holds the durable storage representation of this snapshot. + // Unset when the snapshot only exists locally. + // + // +k8s:optional + DurableSnapshot *SnapshotStorage `protobuf:"bytes,7,opt,name=durable_snapshot,json=durableSnapshot,proto3" json:"durable_snapshot,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Snapshot) Reset() { *x = Snapshot{} - mi := &file_ateapi_proto_msgTypes[3] + mi := &file_ateapi_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -890,7 +759,7 @@ func (x *Snapshot) String() string { func (*Snapshot) ProtoMessage() {} func (x *Snapshot) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[3] + mi := &file_ateapi_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -903,7 +772,14 @@ func (x *Snapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use Snapshot.ProtoReflect.Descriptor instead. func (*Snapshot) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{3} + return file_ateapi_proto_rawDescGZIP(), []int{2} +} + +func (x *Snapshot) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" } func (x *Snapshot) GetGeneration() int32 { @@ -920,6 +796,13 @@ func (x *Snapshot) GetOwner() SnapshotOwner { return SnapshotOwner_SNAPSHOT_OWNER_UNSPECIFIED } +func (x *Snapshot) GetFidelity() SnapshotFidelity { + if x != nil { + return x.Fidelity + } + return SnapshotFidelity_SNAPSHOT_FIDELITY_UNSPECIFIED +} + func (x *Snapshot) GetActorTemplateUid() string { if x != nil { return x.ActorTemplateUid @@ -927,9 +810,16 @@ func (x *Snapshot) GetActorTemplateUid() string { return "" } -func (x *Snapshot) GetStorage() []*SnapshotStorage { +func (x *Snapshot) GetLocality() string { if x != nil { - return x.Storage + return x.Locality + } + return "" +} + +func (x *Snapshot) GetDurableSnapshot() *SnapshotStorage { + if x != nil { + return x.DurableSnapshot } return nil } @@ -953,7 +843,7 @@ type Selector struct { func (x *Selector) Reset() { *x = Selector{} - mi := &file_ateapi_proto_msgTypes[4] + mi := &file_ateapi_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -965,7 +855,7 @@ func (x *Selector) String() string { func (*Selector) ProtoMessage() {} func (x *Selector) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[4] + mi := &file_ateapi_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -978,7 +868,7 @@ func (x *Selector) ProtoReflect() protoreflect.Message { // Deprecated: Use Selector.ProtoReflect.Descriptor instead. func (*Selector) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{4} + return file_ateapi_proto_rawDescGZIP(), []int{3} } func (x *Selector) GetMatchLabels() map[string]string { @@ -1049,7 +939,7 @@ type ResourceMetadata struct { func (x *ResourceMetadata) Reset() { *x = ResourceMetadata{} - mi := &file_ateapi_proto_msgTypes[5] + mi := &file_ateapi_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1061,7 +951,7 @@ func (x *ResourceMetadata) String() string { func (*ResourceMetadata) ProtoMessage() {} func (x *ResourceMetadata) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[5] + mi := &file_ateapi_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1074,7 +964,7 @@ func (x *ResourceMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceMetadata.ProtoReflect.Descriptor instead. func (*ResourceMetadata) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{5} + return file_ateapi_proto_rawDescGZIP(), []int{4} } func (x *ResourceMetadata) GetAtespace() string { @@ -1173,7 +1063,7 @@ type ExternalVolume struct { func (x *ExternalVolume) Reset() { *x = ExternalVolume{} - mi := &file_ateapi_proto_msgTypes[6] + mi := &file_ateapi_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1185,7 +1075,7 @@ func (x *ExternalVolume) String() string { func (*ExternalVolume) ProtoMessage() {} func (x *ExternalVolume) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[6] + mi := &file_ateapi_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1198,7 +1088,7 @@ func (x *ExternalVolume) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalVolume.ProtoReflect.Descriptor instead. func (*ExternalVolume) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{6} + return file_ateapi_proto_rawDescGZIP(), []int{5} } func (x *ExternalVolume) GetName() string { @@ -1274,7 +1164,7 @@ type Actor struct { func (x *Actor) Reset() { *x = Actor{} - mi := &file_ateapi_proto_msgTypes[7] + mi := &file_ateapi_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1286,7 +1176,7 @@ func (x *Actor) String() string { func (*Actor) ProtoMessage() {} func (x *Actor) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[7] + mi := &file_ateapi_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1299,7 +1189,7 @@ func (x *Actor) ProtoReflect() protoreflect.Message { // Deprecated: Use Actor.ProtoReflect.Descriptor instead. func (*Actor) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{7} + return file_ateapi_proto_rawDescGZIP(), []int{6} } func (x *Actor) GetMetadata() *ResourceMetadata { @@ -1377,7 +1267,7 @@ type EgressPolicy struct { func (x *EgressPolicy) Reset() { *x = EgressPolicy{} - mi := &file_ateapi_proto_msgTypes[8] + mi := &file_ateapi_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1389,7 +1279,7 @@ func (x *EgressPolicy) String() string { func (*EgressPolicy) ProtoMessage() {} func (x *EgressPolicy) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[8] + mi := &file_ateapi_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1402,7 +1292,7 @@ func (x *EgressPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressPolicy.ProtoReflect.Descriptor instead. func (*EgressPolicy) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{8} + return file_ateapi_proto_rawDescGZIP(), []int{7} } func (x *EgressPolicy) GetMetadata() *ResourceMetadata { @@ -1452,7 +1342,7 @@ type EgressRule struct { func (x *EgressRule) Reset() { *x = EgressRule{} - mi := &file_ateapi_proto_msgTypes[9] + mi := &file_ateapi_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1464,7 +1354,7 @@ func (x *EgressRule) String() string { func (*EgressRule) ProtoMessage() {} func (x *EgressRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[9] + mi := &file_ateapi_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1477,7 +1367,7 @@ func (x *EgressRule) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressRule.ProtoReflect.Descriptor instead. func (*EgressRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{9} + return file_ateapi_proto_rawDescGZIP(), []int{8} } func (x *EgressRule) GetHttp() *HTTPRule { @@ -1545,7 +1435,7 @@ type HTTPRule struct { func (x *HTTPRule) Reset() { *x = HTTPRule{} - mi := &file_ateapi_proto_msgTypes[10] + mi := &file_ateapi_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1557,7 +1447,7 @@ func (x *HTTPRule) String() string { func (*HTTPRule) ProtoMessage() {} func (x *HTTPRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[10] + mi := &file_ateapi_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1570,7 +1460,7 @@ func (x *HTTPRule) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPRule.ProtoReflect.Descriptor instead. func (*HTTPRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{10} + return file_ateapi_proto_rawDescGZIP(), []int{9} } func (x *HTTPRule) GetHostnames() []string { @@ -1630,7 +1520,7 @@ type HTTPSRule struct { func (x *HTTPSRule) Reset() { *x = HTTPSRule{} - mi := &file_ateapi_proto_msgTypes[11] + mi := &file_ateapi_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1642,7 +1532,7 @@ func (x *HTTPSRule) String() string { func (*HTTPSRule) ProtoMessage() {} func (x *HTTPSRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[11] + mi := &file_ateapi_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1655,7 +1545,7 @@ func (x *HTTPSRule) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPSRule.ProtoReflect.Descriptor instead. func (*HTTPSRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{11} + return file_ateapi_proto_rawDescGZIP(), []int{10} } func (x *HTTPSRule) GetHostnames() []string { @@ -1706,7 +1596,7 @@ type TLSPassthroughRule struct { func (x *TLSPassthroughRule) Reset() { *x = TLSPassthroughRule{} - mi := &file_ateapi_proto_msgTypes[12] + mi := &file_ateapi_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1718,7 +1608,7 @@ func (x *TLSPassthroughRule) String() string { func (*TLSPassthroughRule) ProtoMessage() {} func (x *TLSPassthroughRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[12] + mi := &file_ateapi_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1731,7 +1621,7 @@ func (x *TLSPassthroughRule) ProtoReflect() protoreflect.Message { // Deprecated: Use TLSPassthroughRule.ProtoReflect.Descriptor instead. func (*TLSPassthroughRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{12} + return file_ateapi_proto_rawDescGZIP(), []int{11} } func (x *TLSPassthroughRule) GetHostnames() []string { @@ -1772,7 +1662,7 @@ type Ports struct { func (x *Ports) Reset() { *x = Ports{} - mi := &file_ateapi_proto_msgTypes[13] + mi := &file_ateapi_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1784,7 +1674,7 @@ func (x *Ports) String() string { func (*Ports) ProtoMessage() {} func (x *Ports) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[13] + mi := &file_ateapi_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1797,7 +1687,7 @@ func (x *Ports) ProtoReflect() protoreflect.Message { // Deprecated: Use Ports.ProtoReflect.Descriptor instead. func (*Ports) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{13} + return file_ateapi_proto_rawDescGZIP(), []int{12} } func (x *Ports) GetAll() *AllPorts { @@ -1823,7 +1713,7 @@ type AllPorts struct { func (x *AllPorts) Reset() { *x = AllPorts{} - mi := &file_ateapi_proto_msgTypes[14] + mi := &file_ateapi_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1835,7 +1725,7 @@ func (x *AllPorts) String() string { func (*AllPorts) ProtoMessage() {} func (x *AllPorts) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[14] + mi := &file_ateapi_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1848,7 +1738,7 @@ func (x *AllPorts) ProtoReflect() protoreflect.Message { // Deprecated: Use AllPorts.ProtoReflect.Descriptor instead. func (*AllPorts) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{14} + return file_ateapi_proto_rawDescGZIP(), []int{13} } // HttpRuleEffects contains effects applied by a matching HTTP or HTTPS rule. @@ -1882,7 +1772,7 @@ type HttpRuleEffects struct { func (x *HttpRuleEffects) Reset() { *x = HttpRuleEffects{} - mi := &file_ateapi_proto_msgTypes[15] + mi := &file_ateapi_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1894,7 +1784,7 @@ func (x *HttpRuleEffects) String() string { func (*HttpRuleEffects) ProtoMessage() {} func (x *HttpRuleEffects) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[15] + mi := &file_ateapi_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1907,7 +1797,7 @@ func (x *HttpRuleEffects) ProtoReflect() protoreflect.Message { // Deprecated: Use HttpRuleEffects.ProtoReflect.Descriptor instead. func (*HttpRuleEffects) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{15} + return file_ateapi_proto_rawDescGZIP(), []int{14} } func (x *HttpRuleEffects) GetReplaceHeaders() []*CredentialHeader { @@ -1957,7 +1847,7 @@ type CredentialHeader struct { func (x *CredentialHeader) Reset() { *x = CredentialHeader{} - mi := &file_ateapi_proto_msgTypes[16] + mi := &file_ateapi_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1969,7 +1859,7 @@ func (x *CredentialHeader) String() string { func (*CredentialHeader) ProtoMessage() {} func (x *CredentialHeader) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[16] + mi := &file_ateapi_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1982,7 +1872,7 @@ func (x *CredentialHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialHeader.ProtoReflect.Descriptor instead. func (*CredentialHeader) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{16} + return file_ateapi_proto_rawDescGZIP(), []int{15} } func (x *CredentialHeader) GetHeader() string { @@ -2039,7 +1929,7 @@ type ActorJWTSource struct { func (x *ActorJWTSource) Reset() { *x = ActorJWTSource{} - mi := &file_ateapi_proto_msgTypes[17] + mi := &file_ateapi_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2051,7 +1941,7 @@ func (x *ActorJWTSource) String() string { func (*ActorJWTSource) ProtoMessage() {} func (x *ActorJWTSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[17] + mi := &file_ateapi_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2064,7 +1954,7 @@ func (x *ActorJWTSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorJWTSource.ProtoReflect.Descriptor instead. func (*ActorJWTSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{17} + return file_ateapi_proto_rawDescGZIP(), []int{16} } func (x *ActorJWTSource) GetAudiences() []string { @@ -2140,7 +2030,7 @@ type ActorStatus struct { func (x *ActorStatus) Reset() { *x = ActorStatus{} - mi := &file_ateapi_proto_msgTypes[18] + mi := &file_ateapi_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2152,7 +2042,7 @@ func (x *ActorStatus) String() string { func (*ActorStatus) ProtoMessage() {} func (x *ActorStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[18] + mi := &file_ateapi_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2165,7 +2055,7 @@ func (x *ActorStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorStatus.ProtoReflect.Descriptor instead. func (*ActorStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{18} + return file_ateapi_proto_rawDescGZIP(), []int{17} } func (x *ActorStatus) GetState() ActorState { @@ -2235,7 +2125,7 @@ type ActorCrash struct { func (x *ActorCrash) Reset() { *x = ActorCrash{} - mi := &file_ateapi_proto_msgTypes[19] + mi := &file_ateapi_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2247,7 +2137,7 @@ func (x *ActorCrash) String() string { func (*ActorCrash) ProtoMessage() {} func (x *ActorCrash) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[19] + mi := &file_ateapi_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2260,7 +2150,7 @@ func (x *ActorCrash) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorCrash.ProtoReflect.Descriptor instead. func (*ActorCrash) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{19} + return file_ateapi_proto_rawDescGZIP(), []int{18} } func (x *ActorCrash) GetMessage() string { @@ -2341,7 +2231,7 @@ type WorkerAssignment struct { func (x *WorkerAssignment) Reset() { *x = WorkerAssignment{} - mi := &file_ateapi_proto_msgTypes[20] + mi := &file_ateapi_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2353,7 +2243,7 @@ func (x *WorkerAssignment) String() string { func (*WorkerAssignment) ProtoMessage() {} func (x *WorkerAssignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[20] + mi := &file_ateapi_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2366,7 +2256,7 @@ func (x *WorkerAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerAssignment.ProtoReflect.Descriptor instead. func (*WorkerAssignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{20} + return file_ateapi_proto_rawDescGZIP(), []int{19} } func (x *WorkerAssignment) GetWorker() *ObjectRef { @@ -2430,12 +2320,15 @@ func (x *WorkerAssignment) GetWorkerEpoch() int64 { // the rest is immutable provenance. type TagStatus struct { state protoimpl.MessageState `protogen:"open.v1"` - // snapshot is the external snapshot this tag points at. Its storage entry is - // IN_PROGRESS while the tag is being created and transitions to COMPLETED - // once the copy finishes: a tag never moves between snapshots and is usable - // only once COMPLETED. + // snapshot is the external snapshot this tag points at: the tag's own copy, + // stored under a prefix keyed on the tag's UID. It is recorded with its + // durable_snapshot PENDING right after the tag is reserved, before anything + // is copied, and transitions to COMPLETED once the copy finishes: a tag never + // moves between snapshots and is usable only once COMPLETED. Unset until it + // is recorded; a tag whose creation failed first never gets one. // - // +k8s:required + // +k8s:optional + // +k8s:subfield(durable_snapshot)=+k8s:required Snapshot *Snapshot `protobuf:"bytes,1,opt,name=snapshot,proto3" json:"snapshot,omitempty"` // UID of the ActorTemplate the snapshot was taken under. Actors can only be // seeded from a tag under the same template. @@ -2449,7 +2342,7 @@ type TagStatus struct { func (x *TagStatus) Reset() { *x = TagStatus{} - mi := &file_ateapi_proto_msgTypes[21] + mi := &file_ateapi_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2461,7 +2354,7 @@ func (x *TagStatus) String() string { func (*TagStatus) ProtoMessage() {} func (x *TagStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[21] + mi := &file_ateapi_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2474,7 +2367,7 @@ func (x *TagStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use TagStatus.ProtoReflect.Descriptor instead. func (*TagStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{21} + return file_ateapi_proto_rawDescGZIP(), []int{20} } func (x *TagStatus) GetSnapshot() *Snapshot { @@ -2526,7 +2419,7 @@ type Tag struct { func (x *Tag) Reset() { *x = Tag{} - mi := &file_ateapi_proto_msgTypes[22] + mi := &file_ateapi_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2538,7 +2431,7 @@ func (x *Tag) String() string { func (*Tag) ProtoMessage() {} func (x *Tag) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[22] + mi := &file_ateapi_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2551,7 +2444,7 @@ func (x *Tag) ProtoReflect() protoreflect.Message { // Deprecated: Use Tag.ProtoReflect.Descriptor instead. func (*Tag) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{22} + return file_ateapi_proto_rawDescGZIP(), []int{21} } func (x *Tag) GetMetadata() *ResourceMetadata { @@ -2597,7 +2490,7 @@ type Atespace struct { func (x *Atespace) Reset() { *x = Atespace{} - mi := &file_ateapi_proto_msgTypes[23] + mi := &file_ateapi_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2609,7 +2502,7 @@ func (x *Atespace) String() string { func (*Atespace) ProtoMessage() {} func (x *Atespace) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[23] + mi := &file_ateapi_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2622,7 +2515,7 @@ func (x *Atespace) ProtoReflect() protoreflect.Message { // Deprecated: Use Atespace.ProtoReflect.Descriptor instead. func (*Atespace) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{23} + return file_ateapi_proto_rawDescGZIP(), []int{22} } func (x *Atespace) GetMetadata() *ResourceMetadata { @@ -2655,7 +2548,7 @@ type ObjectRef struct { func (x *ObjectRef) Reset() { *x = ObjectRef{} - mi := &file_ateapi_proto_msgTypes[24] + mi := &file_ateapi_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2667,7 +2560,7 @@ func (x *ObjectRef) String() string { func (*ObjectRef) ProtoMessage() {} func (x *ObjectRef) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[24] + mi := &file_ateapi_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2680,7 +2573,7 @@ func (x *ObjectRef) ProtoReflect() protoreflect.Message { // Deprecated: Use ObjectRef.ProtoReflect.Descriptor instead. func (*ObjectRef) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{24} + return file_ateapi_proto_rawDescGZIP(), []int{23} } func (x *ObjectRef) GetAtespace() string { @@ -2737,7 +2630,7 @@ type ActorTemplate struct { func (x *ActorTemplate) Reset() { *x = ActorTemplate{} - mi := &file_ateapi_proto_msgTypes[25] + mi := &file_ateapi_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2749,7 +2642,7 @@ func (x *ActorTemplate) String() string { func (*ActorTemplate) ProtoMessage() {} func (x *ActorTemplate) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[25] + mi := &file_ateapi_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2762,7 +2655,7 @@ func (x *ActorTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorTemplate.ProtoReflect.Descriptor instead. func (*ActorTemplate) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{25} + return file_ateapi_proto_rawDescGZIP(), []int{24} } func (x *ActorTemplate) GetMetadata() *ResourceMetadata { @@ -2838,7 +2731,7 @@ type Resources struct { func (x *Resources) Reset() { *x = Resources{} - mi := &file_ateapi_proto_msgTypes[26] + mi := &file_ateapi_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2850,7 +2743,7 @@ func (x *Resources) String() string { func (*Resources) ProtoMessage() {} func (x *Resources) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[26] + mi := &file_ateapi_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2863,7 +2756,7 @@ func (x *Resources) ProtoReflect() protoreflect.Message { // Deprecated: Use Resources.ProtoReflect.Descriptor instead. func (*Resources) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{26} + return file_ateapi_proto_rawDescGZIP(), []int{25} } func (x *Resources) GetLimits() []*Limits { @@ -2891,7 +2784,7 @@ type Limits struct { func (x *Limits) Reset() { *x = Limits{} - mi := &file_ateapi_proto_msgTypes[27] + mi := &file_ateapi_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2903,7 +2796,7 @@ func (x *Limits) String() string { func (*Limits) ProtoMessage() {} func (x *Limits) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[27] + mi := &file_ateapi_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2916,7 +2809,7 @@ func (x *Limits) ProtoReflect() protoreflect.Message { // Deprecated: Use Limits.ProtoReflect.Descriptor instead. func (*Limits) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{27} + return file_ateapi_proto_rawDescGZIP(), []int{26} } func (x *Limits) GetName() string { @@ -2957,7 +2850,7 @@ type GoldenSnapshotStatus struct { func (x *GoldenSnapshotStatus) Reset() { *x = GoldenSnapshotStatus{} - mi := &file_ateapi_proto_msgTypes[28] + mi := &file_ateapi_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2969,7 +2862,7 @@ func (x *GoldenSnapshotStatus) String() string { func (*GoldenSnapshotStatus) ProtoMessage() {} func (x *GoldenSnapshotStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[28] + mi := &file_ateapi_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2982,7 +2875,7 @@ func (x *GoldenSnapshotStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use GoldenSnapshotStatus.ProtoReflect.Descriptor instead. func (*GoldenSnapshotStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{28} + return file_ateapi_proto_rawDescGZIP(), []int{27} } func (x *GoldenSnapshotStatus) GetGoldenTag() *ObjectRef { @@ -3016,7 +2909,7 @@ type ActorTemplateStatus struct { func (x *ActorTemplateStatus) Reset() { *x = ActorTemplateStatus{} - mi := &file_ateapi_proto_msgTypes[29] + mi := &file_ateapi_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3028,7 +2921,7 @@ func (x *ActorTemplateStatus) String() string { func (*ActorTemplateStatus) ProtoMessage() {} func (x *ActorTemplateStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[29] + mi := &file_ateapi_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3041,7 +2934,7 @@ func (x *ActorTemplateStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorTemplateStatus.ProtoReflect.Descriptor instead. func (*ActorTemplateStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{29} + return file_ateapi_proto_rawDescGZIP(), []int{28} } func (x *ActorTemplateStatus) GetGoldenSnapshotStatus() *GoldenSnapshotStatus { @@ -3072,7 +2965,7 @@ type SandboxConfig struct { func (x *SandboxConfig) Reset() { *x = SandboxConfig{} - mi := &file_ateapi_proto_msgTypes[30] + mi := &file_ateapi_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3084,7 +2977,7 @@ func (x *SandboxConfig) String() string { func (*SandboxConfig) ProtoMessage() {} func (x *SandboxConfig) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[30] + mi := &file_ateapi_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3097,7 +2990,7 @@ func (x *SandboxConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfig.ProtoReflect.Descriptor instead. func (*SandboxConfig) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{30} + return file_ateapi_proto_rawDescGZIP(), []int{29} } func (x *SandboxConfig) GetSandboxClass() SandboxClass { @@ -3150,7 +3043,7 @@ type SnapshotConfig struct { func (x *SnapshotConfig) Reset() { *x = SnapshotConfig{} - mi := &file_ateapi_proto_msgTypes[31] + mi := &file_ateapi_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3162,7 +3055,7 @@ func (x *SnapshotConfig) String() string { func (*SnapshotConfig) ProtoMessage() {} func (x *SnapshotConfig) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[31] + mi := &file_ateapi_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3175,7 +3068,7 @@ func (x *SnapshotConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SnapshotConfig.ProtoReflect.Descriptor instead. func (*SnapshotConfig) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{31} + return file_ateapi_proto_rawDescGZIP(), []int{30} } func (x *SnapshotConfig) GetStorageLocation() string { @@ -3215,7 +3108,7 @@ type GoldenSnapshotConfig struct { func (x *GoldenSnapshotConfig) Reset() { *x = GoldenSnapshotConfig{} - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3227,7 +3120,7 @@ func (x *GoldenSnapshotConfig) String() string { func (*GoldenSnapshotConfig) ProtoMessage() {} func (x *GoldenSnapshotConfig) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3240,7 +3133,7 @@ func (x *GoldenSnapshotConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GoldenSnapshotConfig.ProtoReflect.Descriptor instead. func (*GoldenSnapshotConfig) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{32} + return file_ateapi_proto_rawDescGZIP(), []int{31} } func (x *GoldenSnapshotConfig) GetMode() GoldenSnapshotMode { @@ -3320,7 +3213,7 @@ type Container struct { func (x *Container) Reset() { *x = Container{} - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3332,7 +3225,7 @@ func (x *Container) String() string { func (*Container) ProtoMessage() {} func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3345,7 +3238,7 @@ func (x *Container) ProtoReflect() protoreflect.Message { // Deprecated: Use Container.ProtoReflect.Descriptor instead. func (*Container) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{33} + return file_ateapi_proto_rawDescGZIP(), []int{32} } func (x *Container) GetName() string { @@ -3425,7 +3318,7 @@ type SecurityContext struct { func (x *SecurityContext) Reset() { *x = SecurityContext{} - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3437,7 +3330,7 @@ func (x *SecurityContext) String() string { func (*SecurityContext) ProtoMessage() {} func (x *SecurityContext) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3450,7 +3343,7 @@ func (x *SecurityContext) ProtoReflect() protoreflect.Message { // Deprecated: Use SecurityContext.ProtoReflect.Descriptor instead. func (*SecurityContext) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{34} + return file_ateapi_proto_rawDescGZIP(), []int{33} } func (x *SecurityContext) GetCapabilities() *Capabilities { @@ -3488,7 +3381,7 @@ type Capabilities struct { func (x *Capabilities) Reset() { *x = Capabilities{} - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3500,7 +3393,7 @@ func (x *Capabilities) String() string { func (*Capabilities) ProtoMessage() {} func (x *Capabilities) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3513,7 +3406,7 @@ func (x *Capabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use Capabilities.ProtoReflect.Descriptor instead. func (*Capabilities) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{35} + return file_ateapi_proto_rawDescGZIP(), []int{34} } func (x *Capabilities) GetAdd() []string { @@ -3551,7 +3444,7 @@ type EnvVar struct { func (x *EnvVar) Reset() { *x = EnvVar{} - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3563,7 +3456,7 @@ func (x *EnvVar) String() string { func (*EnvVar) ProtoMessage() {} func (x *EnvVar) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3576,7 +3469,7 @@ func (x *EnvVar) ProtoReflect() protoreflect.Message { // Deprecated: Use EnvVar.ProtoReflect.Descriptor instead. func (*EnvVar) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{36} + return file_ateapi_proto_rawDescGZIP(), []int{35} } func (x *EnvVar) GetName() string { @@ -3616,7 +3509,7 @@ type ContainerWakeupProbe struct { func (x *ContainerWakeupProbe) Reset() { *x = ContainerWakeupProbe{} - mi := &file_ateapi_proto_msgTypes[37] + mi := &file_ateapi_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3628,7 +3521,7 @@ func (x *ContainerWakeupProbe) String() string { func (*ContainerWakeupProbe) ProtoMessage() {} func (x *ContainerWakeupProbe) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[37] + mi := &file_ateapi_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3641,7 +3534,7 @@ func (x *ContainerWakeupProbe) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerWakeupProbe.ProtoReflect.Descriptor instead. func (*ContainerWakeupProbe) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{37} + return file_ateapi_proto_rawDescGZIP(), []int{36} } func (x *ContainerWakeupProbe) GetHttpGet() *HTTPGetAction { @@ -3681,7 +3574,7 @@ type HTTPGetAction struct { func (x *HTTPGetAction) Reset() { *x = HTTPGetAction{} - mi := &file_ateapi_proto_msgTypes[38] + mi := &file_ateapi_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3693,7 +3586,7 @@ func (x *HTTPGetAction) String() string { func (*HTTPGetAction) ProtoMessage() {} func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[38] + mi := &file_ateapi_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3706,7 +3599,7 @@ func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPGetAction.ProtoReflect.Descriptor instead. func (*HTTPGetAction) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{38} + return file_ateapi_proto_rawDescGZIP(), []int{37} } func (x *HTTPGetAction) GetPath() string { @@ -3753,7 +3646,7 @@ type Volume struct { func (x *Volume) Reset() { *x = Volume{} - mi := &file_ateapi_proto_msgTypes[39] + mi := &file_ateapi_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3765,7 +3658,7 @@ func (x *Volume) String() string { func (*Volume) ProtoMessage() {} func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[39] + mi := &file_ateapi_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3778,7 +3671,7 @@ func (x *Volume) ProtoReflect() protoreflect.Message { // Deprecated: Use Volume.ProtoReflect.Descriptor instead. func (*Volume) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{39} + return file_ateapi_proto_rawDescGZIP(), []int{38} } func (x *Volume) GetName() string { @@ -3834,7 +3727,7 @@ type ImageVolumeSource struct { func (x *ImageVolumeSource) Reset() { *x = ImageVolumeSource{} - mi := &file_ateapi_proto_msgTypes[40] + mi := &file_ateapi_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3846,7 +3739,7 @@ func (x *ImageVolumeSource) String() string { func (*ImageVolumeSource) ProtoMessage() {} func (x *ImageVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[40] + mi := &file_ateapi_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3859,7 +3752,7 @@ func (x *ImageVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ImageVolumeSource.ProtoReflect.Descriptor instead. func (*ImageVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{40} + return file_ateapi_proto_rawDescGZIP(), []int{39} } func (x *ImageVolumeSource) GetReference() string { @@ -3879,7 +3772,7 @@ type DurableDirVolumeSource struct { func (x *DurableDirVolumeSource) Reset() { *x = DurableDirVolumeSource{} - mi := &file_ateapi_proto_msgTypes[41] + mi := &file_ateapi_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3891,7 +3784,7 @@ func (x *DurableDirVolumeSource) String() string { func (*DurableDirVolumeSource) ProtoMessage() {} func (x *DurableDirVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[41] + mi := &file_ateapi_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3904,7 +3797,7 @@ func (x *DurableDirVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use DurableDirVolumeSource.ProtoReflect.Descriptor instead. func (*DurableDirVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{41} + return file_ateapi_proto_rawDescGZIP(), []int{40} } // ExternalVolumeTemplate provisions an external volume per actor; the volume @@ -3930,7 +3823,7 @@ type ExternalVolumeTemplate struct { func (x *ExternalVolumeTemplate) Reset() { *x = ExternalVolumeTemplate{} - mi := &file_ateapi_proto_msgTypes[42] + mi := &file_ateapi_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3942,7 +3835,7 @@ func (x *ExternalVolumeTemplate) String() string { func (*ExternalVolumeTemplate) ProtoMessage() {} func (x *ExternalVolumeTemplate) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[42] + mi := &file_ateapi_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3955,7 +3848,7 @@ func (x *ExternalVolumeTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalVolumeTemplate.ProtoReflect.Descriptor instead. func (*ExternalVolumeTemplate) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{42} + return file_ateapi_proto_rawDescGZIP(), []int{41} } func (x *ExternalVolumeTemplate) GetCapacity() string { @@ -3992,7 +3885,7 @@ type SystemInfoVolumeSource struct { func (x *SystemInfoVolumeSource) Reset() { *x = SystemInfoVolumeSource{} - mi := &file_ateapi_proto_msgTypes[43] + mi := &file_ateapi_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4004,7 +3897,7 @@ func (x *SystemInfoVolumeSource) String() string { func (*SystemInfoVolumeSource) ProtoMessage() {} func (x *SystemInfoVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[43] + mi := &file_ateapi_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4017,7 +3910,7 @@ func (x *SystemInfoVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfoVolumeSource.ProtoReflect.Descriptor instead. func (*SystemInfoVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{43} + return file_ateapi_proto_rawDescGZIP(), []int{42} } func (x *SystemInfoVolumeSource) GetDataSources() []*SystemInfoDataSource { @@ -4043,7 +3936,7 @@ type SystemInfoDataSource struct { func (x *SystemInfoDataSource) Reset() { *x = SystemInfoDataSource{} - mi := &file_ateapi_proto_msgTypes[44] + mi := &file_ateapi_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4055,7 +3948,7 @@ func (x *SystemInfoDataSource) String() string { func (*SystemInfoDataSource) ProtoMessage() {} func (x *SystemInfoDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[44] + mi := &file_ateapi_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4068,7 +3961,7 @@ func (x *SystemInfoDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfoDataSource.ProtoReflect.Descriptor instead. func (*SystemInfoDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{44} + return file_ateapi_proto_rawDescGZIP(), []int{43} } func (x *SystemInfoDataSource) GetActorMetadata() *ActorMetadataDataSource { @@ -4105,7 +3998,7 @@ type ActorMetadataDataSource struct { func (x *ActorMetadataDataSource) Reset() { *x = ActorMetadataDataSource{} - mi := &file_ateapi_proto_msgTypes[45] + mi := &file_ateapi_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4117,7 +4010,7 @@ func (x *ActorMetadataDataSource) String() string { func (*ActorMetadataDataSource) ProtoMessage() {} func (x *ActorMetadataDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[45] + mi := &file_ateapi_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4130,7 +4023,7 @@ func (x *ActorMetadataDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorMetadataDataSource.ProtoReflect.Descriptor instead. func (*ActorMetadataDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{45} + return file_ateapi_proto_rawDescGZIP(), []int{44} } func (x *ActorMetadataDataSource) GetItems() []*ActorMetadataItem { @@ -4162,7 +4055,7 @@ type ActorMetadataItem struct { func (x *ActorMetadataItem) Reset() { *x = ActorMetadataItem{} - mi := &file_ateapi_proto_msgTypes[46] + mi := &file_ateapi_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4174,7 +4067,7 @@ func (x *ActorMetadataItem) String() string { func (*ActorMetadataItem) ProtoMessage() {} func (x *ActorMetadataItem) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[46] + mi := &file_ateapi_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4187,7 +4080,7 @@ func (x *ActorMetadataItem) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorMetadataItem.ProtoReflect.Descriptor instead. func (*ActorMetadataItem) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{46} + return file_ateapi_proto_rawDescGZIP(), []int{45} } func (x *ActorMetadataItem) GetField() ActorMetadataField { @@ -4239,7 +4132,7 @@ type TrustBundleDataSource struct { func (x *TrustBundleDataSource) Reset() { *x = TrustBundleDataSource{} - mi := &file_ateapi_proto_msgTypes[47] + mi := &file_ateapi_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4251,7 +4144,7 @@ func (x *TrustBundleDataSource) String() string { func (*TrustBundleDataSource) ProtoMessage() {} func (x *TrustBundleDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[47] + mi := &file_ateapi_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4264,7 +4157,7 @@ func (x *TrustBundleDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleDataSource.ProtoReflect.Descriptor instead. func (*TrustBundleDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{47} + return file_ateapi_proto_rawDescGZIP(), []int{46} } func (x *TrustBundleDataSource) GetNames() []string { @@ -4303,7 +4196,7 @@ type VolumeMount struct { func (x *VolumeMount) Reset() { *x = VolumeMount{} - mi := &file_ateapi_proto_msgTypes[48] + mi := &file_ateapi_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4315,7 +4208,7 @@ func (x *VolumeMount) String() string { func (*VolumeMount) ProtoMessage() {} func (x *VolumeMount) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[48] + mi := &file_ateapi_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4328,7 +4221,7 @@ func (x *VolumeMount) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeMount.ProtoReflect.Descriptor instead. func (*VolumeMount) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{48} + return file_ateapi_proto_rawDescGZIP(), []int{47} } func (x *VolumeMount) GetName() string { @@ -4357,7 +4250,7 @@ type CreateAtespaceRequest struct { func (x *CreateAtespaceRequest) Reset() { *x = CreateAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[49] + mi := &file_ateapi_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4369,7 +4262,7 @@ func (x *CreateAtespaceRequest) String() string { func (*CreateAtespaceRequest) ProtoMessage() {} func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[49] + mi := &file_ateapi_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4382,7 +4275,7 @@ func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAtespaceRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{49} + return file_ateapi_proto_rawDescGZIP(), []int{48} } func (x *CreateAtespaceRequest) GetAtespace() *Atespace { @@ -4403,7 +4296,7 @@ type GetAtespaceRequest struct { func (x *GetAtespaceRequest) Reset() { *x = GetAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[50] + mi := &file_ateapi_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4415,7 +4308,7 @@ func (x *GetAtespaceRequest) String() string { func (*GetAtespaceRequest) ProtoMessage() {} func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[50] + mi := &file_ateapi_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4428,7 +4321,7 @@ func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{50} + return file_ateapi_proto_rawDescGZIP(), []int{49} } func (x *GetAtespaceRequest) GetAtespace() *ObjectRef { @@ -4459,7 +4352,7 @@ type ListAtespacesRequest struct { func (x *ListAtespacesRequest) Reset() { *x = ListAtespacesRequest{} - mi := &file_ateapi_proto_msgTypes[51] + mi := &file_ateapi_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4471,7 +4364,7 @@ func (x *ListAtespacesRequest) String() string { func (*ListAtespacesRequest) ProtoMessage() {} func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[51] + mi := &file_ateapi_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4484,7 +4377,7 @@ func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesRequest.ProtoReflect.Descriptor instead. func (*ListAtespacesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{51} + return file_ateapi_proto_rawDescGZIP(), []int{50} } func (x *ListAtespacesRequest) GetPageSize() int32 { @@ -4513,7 +4406,7 @@ type ListAtespacesResponse struct { func (x *ListAtespacesResponse) Reset() { *x = ListAtespacesResponse{} - mi := &file_ateapi_proto_msgTypes[52] + mi := &file_ateapi_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4525,7 +4418,7 @@ func (x *ListAtespacesResponse) String() string { func (*ListAtespacesResponse) ProtoMessage() {} func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[52] + mi := &file_ateapi_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4538,7 +4431,7 @@ func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesResponse.ProtoReflect.Descriptor instead. func (*ListAtespacesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{52} + return file_ateapi_proto_rawDescGZIP(), []int{51} } func (x *ListAtespacesResponse) GetAtespaces() []*Atespace { @@ -4570,7 +4463,7 @@ type DeleteAtespaceRequest struct { func (x *DeleteAtespaceRequest) Reset() { *x = DeleteAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[53] + mi := &file_ateapi_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4582,7 +4475,7 @@ func (x *DeleteAtespaceRequest) String() string { func (*DeleteAtespaceRequest) ProtoMessage() {} func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[53] + mi := &file_ateapi_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4595,7 +4488,7 @@ func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAtespaceRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{53} + return file_ateapi_proto_rawDescGZIP(), []int{52} } func (x *DeleteAtespaceRequest) GetAtespace() *ObjectRef { @@ -4626,7 +4519,7 @@ type CreateActorTemplateRequest struct { func (x *CreateActorTemplateRequest) Reset() { *x = CreateActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[54] + mi := &file_ateapi_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4638,7 +4531,7 @@ func (x *CreateActorTemplateRequest) String() string { func (*CreateActorTemplateRequest) ProtoMessage() {} func (x *CreateActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[54] + mi := &file_ateapi_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4651,7 +4544,7 @@ func (x *CreateActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorTemplateRequest.ProtoReflect.Descriptor instead. func (*CreateActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{54} + return file_ateapi_proto_rawDescGZIP(), []int{53} } func (x *CreateActorTemplateRequest) GetActorTemplate() *ActorTemplate { @@ -4672,7 +4565,7 @@ type GetActorTemplateRequest struct { func (x *GetActorTemplateRequest) Reset() { *x = GetActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[55] + mi := &file_ateapi_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4684,7 +4577,7 @@ func (x *GetActorTemplateRequest) String() string { func (*GetActorTemplateRequest) ProtoMessage() {} func (x *GetActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[55] + mi := &file_ateapi_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4697,7 +4590,7 @@ func (x *GetActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorTemplateRequest.ProtoReflect.Descriptor instead. func (*GetActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{55} + return file_ateapi_proto_rawDescGZIP(), []int{54} } func (x *GetActorTemplateRequest) GetActorTemplate() *ObjectRef { @@ -4734,7 +4627,7 @@ type ListActorTemplatesRequest struct { func (x *ListActorTemplatesRequest) Reset() { *x = ListActorTemplatesRequest{} - mi := &file_ateapi_proto_msgTypes[56] + mi := &file_ateapi_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4746,7 +4639,7 @@ func (x *ListActorTemplatesRequest) String() string { func (*ListActorTemplatesRequest) ProtoMessage() {} func (x *ListActorTemplatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[56] + mi := &file_ateapi_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4759,7 +4652,7 @@ func (x *ListActorTemplatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorTemplatesRequest.ProtoReflect.Descriptor instead. func (*ListActorTemplatesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{56} + return file_ateapi_proto_rawDescGZIP(), []int{55} } func (x *ListActorTemplatesRequest) GetAtespace() string { @@ -4796,7 +4689,7 @@ type ListActorTemplatesResponse struct { func (x *ListActorTemplatesResponse) Reset() { *x = ListActorTemplatesResponse{} - mi := &file_ateapi_proto_msgTypes[57] + mi := &file_ateapi_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4808,7 +4701,7 @@ func (x *ListActorTemplatesResponse) String() string { func (*ListActorTemplatesResponse) ProtoMessage() {} func (x *ListActorTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[57] + mi := &file_ateapi_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4821,7 +4714,7 @@ func (x *ListActorTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorTemplatesResponse.ProtoReflect.Descriptor instead. func (*ListActorTemplatesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{57} + return file_ateapi_proto_rawDescGZIP(), []int{56} } func (x *ListActorTemplatesResponse) GetActorTemplates() []*ActorTemplate { @@ -4853,7 +4746,7 @@ type DeleteActorTemplateRequest struct { func (x *DeleteActorTemplateRequest) Reset() { *x = DeleteActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[58] + mi := &file_ateapi_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4865,7 +4758,7 @@ func (x *DeleteActorTemplateRequest) String() string { func (*DeleteActorTemplateRequest) ProtoMessage() {} func (x *DeleteActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[58] + mi := &file_ateapi_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4878,7 +4771,7 @@ func (x *DeleteActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorTemplateRequest.ProtoReflect.Descriptor instead. func (*DeleteActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{58} + return file_ateapi_proto_rawDescGZIP(), []int{57} } func (x *DeleteActorTemplateRequest) GetActorTemplate() *ObjectRef { @@ -4906,7 +4799,7 @@ type GetActorRequest struct { func (x *GetActorRequest) Reset() { *x = GetActorRequest{} - mi := &file_ateapi_proto_msgTypes[59] + mi := &file_ateapi_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4918,7 +4811,7 @@ func (x *GetActorRequest) String() string { func (*GetActorRequest) ProtoMessage() {} func (x *GetActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[59] + mi := &file_ateapi_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4931,7 +4824,7 @@ func (x *GetActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorRequest.ProtoReflect.Descriptor instead. func (*GetActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{59} + return file_ateapi_proto_rawDescGZIP(), []int{58} } func (x *GetActorRequest) GetActor() *ObjectRef { @@ -4954,7 +4847,7 @@ type CreateActorRequest struct { func (x *CreateActorRequest) Reset() { *x = CreateActorRequest{} - mi := &file_ateapi_proto_msgTypes[60] + mi := &file_ateapi_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4966,7 +4859,7 @@ func (x *CreateActorRequest) String() string { func (*CreateActorRequest) ProtoMessage() {} func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[60] + mi := &file_ateapi_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4979,7 +4872,7 @@ func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorRequest.ProtoReflect.Descriptor instead. func (*CreateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{60} + return file_ateapi_proto_rawDescGZIP(), []int{59} } func (x *CreateActorRequest) GetActor() *Actor { @@ -5010,7 +4903,7 @@ type UpdateActorRequest struct { func (x *UpdateActorRequest) Reset() { *x = UpdateActorRequest{} - mi := &file_ateapi_proto_msgTypes[61] + mi := &file_ateapi_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5022,7 +4915,7 @@ func (x *UpdateActorRequest) String() string { func (*UpdateActorRequest) ProtoMessage() {} func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[61] + mi := &file_ateapi_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5035,7 +4928,7 @@ func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorRequest.ProtoReflect.Descriptor instead. func (*UpdateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{61} + return file_ateapi_proto_rawDescGZIP(), []int{60} } func (x *UpdateActorRequest) GetActor() *Actor { @@ -5056,7 +4949,7 @@ type SuspendActorRequest struct { func (x *SuspendActorRequest) Reset() { *x = SuspendActorRequest{} - mi := &file_ateapi_proto_msgTypes[62] + mi := &file_ateapi_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5068,7 +4961,7 @@ func (x *SuspendActorRequest) String() string { func (*SuspendActorRequest) ProtoMessage() {} func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[62] + mi := &file_ateapi_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5081,7 +4974,7 @@ func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorRequest.ProtoReflect.Descriptor instead. func (*SuspendActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{62} + return file_ateapi_proto_rawDescGZIP(), []int{61} } func (x *SuspendActorRequest) GetActor() *ObjectRef { @@ -5100,7 +4993,7 @@ type SuspendActorResponse struct { func (x *SuspendActorResponse) Reset() { *x = SuspendActorResponse{} - mi := &file_ateapi_proto_msgTypes[63] + mi := &file_ateapi_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5112,7 +5005,7 @@ func (x *SuspendActorResponse) String() string { func (*SuspendActorResponse) ProtoMessage() {} func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[63] + mi := &file_ateapi_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5125,7 +5018,7 @@ func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorResponse.ProtoReflect.Descriptor instead. func (*SuspendActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{63} + return file_ateapi_proto_rawDescGZIP(), []int{62} } func (x *SuspendActorResponse) GetActor() *Actor { @@ -5146,7 +5039,7 @@ type PauseActorRequest struct { func (x *PauseActorRequest) Reset() { *x = PauseActorRequest{} - mi := &file_ateapi_proto_msgTypes[64] + mi := &file_ateapi_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5158,7 +5051,7 @@ func (x *PauseActorRequest) String() string { func (*PauseActorRequest) ProtoMessage() {} func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[64] + mi := &file_ateapi_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5171,7 +5064,7 @@ func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorRequest.ProtoReflect.Descriptor instead. func (*PauseActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{64} + return file_ateapi_proto_rawDescGZIP(), []int{63} } func (x *PauseActorRequest) GetActor() *ObjectRef { @@ -5190,7 +5083,7 @@ type PauseActorResponse struct { func (x *PauseActorResponse) Reset() { *x = PauseActorResponse{} - mi := &file_ateapi_proto_msgTypes[65] + mi := &file_ateapi_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5202,7 +5095,7 @@ func (x *PauseActorResponse) String() string { func (*PauseActorResponse) ProtoMessage() {} func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[65] + mi := &file_ateapi_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5215,7 +5108,7 @@ func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorResponse.ProtoReflect.Descriptor instead. func (*PauseActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{65} + return file_ateapi_proto_rawDescGZIP(), []int{64} } func (x *PauseActorResponse) GetActor() *Actor { @@ -5236,7 +5129,7 @@ type ResumeActorRequest struct { func (x *ResumeActorRequest) Reset() { *x = ResumeActorRequest{} - mi := &file_ateapi_proto_msgTypes[66] + mi := &file_ateapi_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5248,7 +5141,7 @@ func (x *ResumeActorRequest) String() string { func (*ResumeActorRequest) ProtoMessage() {} func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[66] + mi := &file_ateapi_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5261,7 +5154,7 @@ func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorRequest.ProtoReflect.Descriptor instead. func (*ResumeActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{66} + return file_ateapi_proto_rawDescGZIP(), []int{65} } func (x *ResumeActorRequest) GetActor() *ObjectRef { @@ -5283,7 +5176,7 @@ type ResumeActorResponse struct { func (x *ResumeActorResponse) Reset() { *x = ResumeActorResponse{} - mi := &file_ateapi_proto_msgTypes[67] + mi := &file_ateapi_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5295,7 +5188,7 @@ func (x *ResumeActorResponse) String() string { func (*ResumeActorResponse) ProtoMessage() {} func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[67] + mi := &file_ateapi_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5308,7 +5201,7 @@ func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorResponse.ProtoReflect.Descriptor instead. func (*ResumeActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{67} + return file_ateapi_proto_rawDescGZIP(), []int{66} } func (x *ResumeActorResponse) GetActor() *Actor { @@ -5338,7 +5231,7 @@ type RevertActorRequest struct { func (x *RevertActorRequest) Reset() { *x = RevertActorRequest{} - mi := &file_ateapi_proto_msgTypes[68] + mi := &file_ateapi_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5350,7 +5243,7 @@ func (x *RevertActorRequest) String() string { func (*RevertActorRequest) ProtoMessage() {} func (x *RevertActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[68] + mi := &file_ateapi_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5363,7 +5256,7 @@ func (x *RevertActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertActorRequest.ProtoReflect.Descriptor instead. func (*RevertActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{68} + return file_ateapi_proto_rawDescGZIP(), []int{67} } func (x *RevertActorRequest) GetActor() *ObjectRef { @@ -5385,7 +5278,7 @@ type RevertActorResponse struct { func (x *RevertActorResponse) Reset() { *x = RevertActorResponse{} - mi := &file_ateapi_proto_msgTypes[69] + mi := &file_ateapi_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5397,7 +5290,7 @@ func (x *RevertActorResponse) String() string { func (*RevertActorResponse) ProtoMessage() {} func (x *RevertActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[69] + mi := &file_ateapi_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5410,7 +5303,7 @@ func (x *RevertActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertActorResponse.ProtoReflect.Descriptor instead. func (*RevertActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{69} + return file_ateapi_proto_rawDescGZIP(), []int{68} } func (x *RevertActorResponse) GetActor() *Actor { @@ -5439,7 +5332,7 @@ type DeleteActorRequest struct { func (x *DeleteActorRequest) Reset() { *x = DeleteActorRequest{} - mi := &file_ateapi_proto_msgTypes[70] + mi := &file_ateapi_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5451,7 +5344,7 @@ func (x *DeleteActorRequest) String() string { func (*DeleteActorRequest) ProtoMessage() {} func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[70] + mi := &file_ateapi_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5464,7 +5357,7 @@ func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorRequest.ProtoReflect.Descriptor instead. func (*DeleteActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{70} + return file_ateapi_proto_rawDescGZIP(), []int{69} } func (x *DeleteActorRequest) GetActor() *ObjectRef { @@ -5502,7 +5395,7 @@ type GetActorEgressPolicyRequest struct { func (x *GetActorEgressPolicyRequest) Reset() { *x = GetActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5514,7 +5407,7 @@ func (x *GetActorEgressPolicyRequest) String() string { func (*GetActorEgressPolicyRequest) ProtoMessage() {} func (x *GetActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5527,7 +5420,7 @@ func (x *GetActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*GetActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{71} + return file_ateapi_proto_rawDescGZIP(), []int{70} } func (x *GetActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5558,7 +5451,7 @@ type CreateActorEgressPolicyRequest struct { func (x *CreateActorEgressPolicyRequest) Reset() { *x = CreateActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5570,7 +5463,7 @@ func (x *CreateActorEgressPolicyRequest) String() string { func (*CreateActorEgressPolicyRequest) ProtoMessage() {} func (x *CreateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5583,7 +5476,7 @@ func (x *CreateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{72} + return file_ateapi_proto_rawDescGZIP(), []int{71} } func (x *CreateActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5621,7 +5514,7 @@ type UpdateActorEgressPolicyRequest struct { func (x *UpdateActorEgressPolicyRequest) Reset() { *x = UpdateActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5633,7 +5526,7 @@ func (x *UpdateActorEgressPolicyRequest) String() string { func (*UpdateActorEgressPolicyRequest) ProtoMessage() {} func (x *UpdateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5646,7 +5539,7 @@ func (x *UpdateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{73} + return file_ateapi_proto_rawDescGZIP(), []int{72} } func (x *UpdateActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5681,7 +5574,7 @@ type DeleteActorEgressPolicyRequest struct { func (x *DeleteActorEgressPolicyRequest) Reset() { *x = DeleteActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5693,7 +5586,7 @@ func (x *DeleteActorEgressPolicyRequest) String() string { func (*DeleteActorEgressPolicyRequest) ProtoMessage() {} func (x *DeleteActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5706,7 +5599,7 @@ func (x *DeleteActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{74} + return file_ateapi_proto_rawDescGZIP(), []int{73} } func (x *DeleteActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5734,7 +5627,7 @@ type GetTagRequest struct { func (x *GetTagRequest) Reset() { *x = GetTagRequest{} - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5746,7 +5639,7 @@ func (x *GetTagRequest) String() string { func (*GetTagRequest) ProtoMessage() {} func (x *GetTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5759,7 +5652,7 @@ func (x *GetTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTagRequest.ProtoReflect.Descriptor instead. func (*GetTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{75} + return file_ateapi_proto_rawDescGZIP(), []int{74} } func (x *GetTagRequest) GetTag() *ObjectRef { @@ -5798,7 +5691,7 @@ type MintActorJWTRequest struct { func (x *MintActorJWTRequest) Reset() { *x = MintActorJWTRequest{} - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5810,7 +5703,7 @@ func (x *MintActorJWTRequest) String() string { func (*MintActorJWTRequest) ProtoMessage() {} func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5823,7 +5716,7 @@ func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorJWTRequest.ProtoReflect.Descriptor instead. func (*MintActorJWTRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{76} + return file_ateapi_proto_rawDescGZIP(), []int{75} } func (x *MintActorJWTRequest) GetActor() *ObjectRef { @@ -5873,7 +5766,7 @@ type MintActorJWTResponse struct { func (x *MintActorJWTResponse) Reset() { *x = MintActorJWTResponse{} - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5885,7 +5778,7 @@ func (x *MintActorJWTResponse) String() string { func (*MintActorJWTResponse) ProtoMessage() {} func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5898,7 +5791,7 @@ func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorJWTResponse.ProtoReflect.Descriptor instead. func (*MintActorJWTResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{77} + return file_ateapi_proto_rawDescGZIP(), []int{76} } func (x *MintActorJWTResponse) GetActorJwt() string { @@ -5943,7 +5836,7 @@ type MintActorCertificateRequest struct { func (x *MintActorCertificateRequest) Reset() { *x = MintActorCertificateRequest{} - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5955,7 +5848,7 @@ func (x *MintActorCertificateRequest) String() string { func (*MintActorCertificateRequest) ProtoMessage() {} func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5968,7 +5861,7 @@ func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{78} + return file_ateapi_proto_rawDescGZIP(), []int{77} } func (x *MintActorCertificateRequest) GetActor() *ObjectRef { @@ -6005,7 +5898,7 @@ type MintActorCertificateResponse struct { func (x *MintActorCertificateResponse) Reset() { *x = MintActorCertificateResponse{} - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6017,7 +5910,7 @@ func (x *MintActorCertificateResponse) String() string { func (*MintActorCertificateResponse) ProtoMessage() {} func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6030,7 +5923,7 @@ func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{79} + return file_ateapi_proto_rawDescGZIP(), []int{78} } func (x *MintActorCertificateResponse) GetActorCertificates() [][]byte { @@ -6066,7 +5959,7 @@ type ListTagsRequest struct { func (x *ListTagsRequest) Reset() { *x = ListTagsRequest{} - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6078,7 +5971,7 @@ func (x *ListTagsRequest) String() string { func (*ListTagsRequest) ProtoMessage() {} func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6091,7 +5984,7 @@ func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTagsRequest.ProtoReflect.Descriptor instead. func (*ListTagsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{80} + return file_ateapi_proto_rawDescGZIP(), []int{79} } func (x *ListTagsRequest) GetAtespace() string { @@ -6125,7 +6018,7 @@ type ListTagsResponse struct { func (x *ListTagsResponse) Reset() { *x = ListTagsResponse{} - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6137,7 +6030,7 @@ func (x *ListTagsResponse) String() string { func (*ListTagsResponse) ProtoMessage() {} func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6150,7 +6043,7 @@ func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead. func (*ListTagsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{81} + return file_ateapi_proto_rawDescGZIP(), []int{80} } func (x *ListTagsResponse) GetTags() []*Tag { @@ -6195,7 +6088,7 @@ type CreateTagRequest struct { func (x *CreateTagRequest) Reset() { *x = CreateTagRequest{} - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6207,7 +6100,7 @@ func (x *CreateTagRequest) String() string { func (*CreateTagRequest) ProtoMessage() {} func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6220,7 +6113,7 @@ func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead. func (*CreateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{82} + return file_ateapi_proto_rawDescGZIP(), []int{81} } func (x *CreateTagRequest) GetTag() *Tag { @@ -6250,7 +6143,7 @@ type UpdateTagRequest struct { func (x *UpdateTagRequest) Reset() { *x = UpdateTagRequest{} - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6262,7 +6155,7 @@ func (x *UpdateTagRequest) String() string { func (*UpdateTagRequest) ProtoMessage() {} func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6275,7 +6168,7 @@ func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateTagRequest.ProtoReflect.Descriptor instead. func (*UpdateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{83} + return file_ateapi_proto_rawDescGZIP(), []int{82} } func (x *UpdateTagRequest) GetTag() *Tag { @@ -6300,7 +6193,7 @@ type DeleteTagRequest struct { func (x *DeleteTagRequest) Reset() { *x = DeleteTagRequest{} - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6312,7 +6205,7 @@ func (x *DeleteTagRequest) String() string { func (*DeleteTagRequest) ProtoMessage() {} func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6325,7 +6218,7 @@ func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteTagRequest.ProtoReflect.Descriptor instead. func (*DeleteTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{84} + return file_ateapi_proto_rawDescGZIP(), []int{83} } func (x *DeleteTagRequest) GetTag() *ObjectRef { @@ -6364,7 +6257,7 @@ type DeleteOptions struct { func (x *DeleteOptions) Reset() { *x = DeleteOptions{} - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6376,7 +6269,7 @@ func (x *DeleteOptions) String() string { func (*DeleteOptions) ProtoMessage() {} func (x *DeleteOptions) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6389,7 +6282,7 @@ func (x *DeleteOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteOptions.ProtoReflect.Descriptor instead. func (*DeleteOptions) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{85} + return file_ateapi_proto_rawDescGZIP(), []int{84} } func (x *DeleteOptions) GetVersion() int64 { @@ -6433,7 +6326,7 @@ type ListWorkerActorAssignmentsRequest struct { func (x *ListWorkerActorAssignmentsRequest) Reset() { *x = ListWorkerActorAssignmentsRequest{} - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6445,7 +6338,7 @@ func (x *ListWorkerActorAssignmentsRequest) String() string { func (*ListWorkerActorAssignmentsRequest) ProtoMessage() {} func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6458,7 +6351,7 @@ func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsRequest.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{86} + return file_ateapi_proto_rawDescGZIP(), []int{85} } func (x *ListWorkerActorAssignmentsRequest) GetWorker() *ObjectRef { @@ -6495,7 +6388,7 @@ type ListWorkerActorAssignmentsResponse struct { func (x *ListWorkerActorAssignmentsResponse) Reset() { *x = ListWorkerActorAssignmentsResponse{} - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6507,7 +6400,7 @@ func (x *ListWorkerActorAssignmentsResponse) String() string { func (*ListWorkerActorAssignmentsResponse) ProtoMessage() {} func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6520,7 +6413,7 @@ func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsResponse.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{87} + return file_ateapi_proto_rawDescGZIP(), []int{86} } func (x *ListWorkerActorAssignmentsResponse) GetActorAssignments() []*ActorAssignment { @@ -6558,7 +6451,7 @@ type ListWorkersRequest struct { func (x *ListWorkersRequest) Reset() { *x = ListWorkersRequest{} - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6570,7 +6463,7 @@ func (x *ListWorkersRequest) String() string { func (*ListWorkersRequest) ProtoMessage() {} func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6583,7 +6476,7 @@ func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersRequest.ProtoReflect.Descriptor instead. func (*ListWorkersRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{88} + return file_ateapi_proto_rawDescGZIP(), []int{87} } func (x *ListWorkersRequest) GetPageSize() int32 { @@ -6612,7 +6505,7 @@ type ListWorkersResponse struct { func (x *ListWorkersResponse) Reset() { *x = ListWorkersResponse{} - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6624,7 +6517,7 @@ func (x *ListWorkersResponse) String() string { func (*ListWorkersResponse) ProtoMessage() {} func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6637,7 +6530,7 @@ func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersResponse.ProtoReflect.Descriptor instead. func (*ListWorkersResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{89} + return file_ateapi_proto_rawDescGZIP(), []int{88} } func (x *ListWorkersResponse) GetWorkers() []*Worker { @@ -6667,7 +6560,7 @@ type GetWorkerRequest struct { func (x *GetWorkerRequest) Reset() { *x = GetWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[90] + mi := &file_ateapi_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6679,7 +6572,7 @@ func (x *GetWorkerRequest) String() string { func (*GetWorkerRequest) ProtoMessage() {} func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[90] + mi := &file_ateapi_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6692,7 +6585,7 @@ func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkerRequest.ProtoReflect.Descriptor instead. func (*GetWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{90} + return file_ateapi_proto_rawDescGZIP(), []int{89} } func (x *GetWorkerRequest) GetWorker() *ObjectRef { @@ -6714,7 +6607,7 @@ type CreateWorkerRequest struct { func (x *CreateWorkerRequest) Reset() { *x = CreateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[91] + mi := &file_ateapi_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6726,7 +6619,7 @@ func (x *CreateWorkerRequest) String() string { func (*CreateWorkerRequest) ProtoMessage() {} func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[91] + mi := &file_ateapi_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6739,7 +6632,7 @@ func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkerRequest.ProtoReflect.Descriptor instead. func (*CreateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{91} + return file_ateapi_proto_rawDescGZIP(), []int{90} } func (x *CreateWorkerRequest) GetWorker() *Worker { @@ -6774,7 +6667,7 @@ type UpdateWorkerRequest struct { func (x *UpdateWorkerRequest) Reset() { *x = UpdateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[92] + mi := &file_ateapi_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6786,7 +6679,7 @@ func (x *UpdateWorkerRequest) String() string { func (*UpdateWorkerRequest) ProtoMessage() {} func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[92] + mi := &file_ateapi_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6799,7 +6692,7 @@ func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkerRequest.ProtoReflect.Descriptor instead. func (*UpdateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{92} + return file_ateapi_proto_rawDescGZIP(), []int{91} } func (x *UpdateWorkerRequest) GetWorker() *Worker { @@ -6826,7 +6719,7 @@ type DeleteWorkerRequest struct { func (x *DeleteWorkerRequest) Reset() { *x = DeleteWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[93] + mi := &file_ateapi_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6838,7 +6731,7 @@ func (x *DeleteWorkerRequest) String() string { func (*DeleteWorkerRequest) ProtoMessage() {} func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[93] + mi := &file_ateapi_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6851,7 +6744,7 @@ func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkerRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{93} + return file_ateapi_proto_rawDescGZIP(), []int{92} } func (x *DeleteWorkerRequest) GetWorker() *ObjectRef { @@ -6881,7 +6774,7 @@ type DrainWorkerRequest struct { func (x *DrainWorkerRequest) Reset() { *x = DrainWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[94] + mi := &file_ateapi_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6893,7 +6786,7 @@ func (x *DrainWorkerRequest) String() string { func (*DrainWorkerRequest) ProtoMessage() {} func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[94] + mi := &file_ateapi_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6906,7 +6799,7 @@ func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DrainWorkerRequest.ProtoReflect.Descriptor instead. func (*DrainWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{94} + return file_ateapi_proto_rawDescGZIP(), []int{93} } func (x *DrainWorkerRequest) GetWorker() *ObjectRef { @@ -6944,7 +6837,7 @@ type ListActorsRequest struct { func (x *ListActorsRequest) Reset() { *x = ListActorsRequest{} - mi := &file_ateapi_proto_msgTypes[95] + mi := &file_ateapi_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6956,7 +6849,7 @@ func (x *ListActorsRequest) String() string { func (*ListActorsRequest) ProtoMessage() {} func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[95] + mi := &file_ateapi_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6969,7 +6862,7 @@ func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsRequest.ProtoReflect.Descriptor instead. func (*ListActorsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{95} + return file_ateapi_proto_rawDescGZIP(), []int{94} } func (x *ListActorsRequest) GetAtespace() string { @@ -7005,7 +6898,7 @@ type ListActorsResponse struct { func (x *ListActorsResponse) Reset() { *x = ListActorsResponse{} - mi := &file_ateapi_proto_msgTypes[96] + mi := &file_ateapi_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7017,7 +6910,7 @@ func (x *ListActorsResponse) String() string { func (*ListActorsResponse) ProtoMessage() {} func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[96] + mi := &file_ateapi_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7030,7 +6923,7 @@ func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsResponse.ProtoReflect.Descriptor instead. func (*ListActorsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{96} + return file_ateapi_proto_rawDescGZIP(), []int{95} } func (x *ListActorsResponse) GetActors() []*Actor { @@ -7137,7 +7030,7 @@ type Worker struct { func (x *Worker) Reset() { *x = Worker{} - mi := &file_ateapi_proto_msgTypes[97] + mi := &file_ateapi_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7149,7 +7042,7 @@ func (x *Worker) String() string { func (*Worker) ProtoMessage() {} func (x *Worker) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[97] + mi := &file_ateapi_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7162,7 +7055,7 @@ func (x *Worker) ProtoReflect() protoreflect.Message { // Deprecated: Use Worker.ProtoReflect.Descriptor instead. func (*Worker) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{97} + return file_ateapi_proto_rawDescGZIP(), []int{96} } func (x *Worker) GetMetadata() *ResourceMetadata { @@ -7291,7 +7184,7 @@ type WorkerStatus struct { func (x *WorkerStatus) Reset() { *x = WorkerStatus{} - mi := &file_ateapi_proto_msgTypes[98] + mi := &file_ateapi_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7303,7 +7196,7 @@ func (x *WorkerStatus) String() string { func (*WorkerStatus) ProtoMessage() {} func (x *WorkerStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[98] + mi := &file_ateapi_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7316,7 +7209,7 @@ func (x *WorkerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerStatus.ProtoReflect.Descriptor instead. func (*WorkerStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{98} + return file_ateapi_proto_rawDescGZIP(), []int{97} } func (x *WorkerStatus) GetState() WorkerState { @@ -7383,7 +7276,7 @@ type SandboxRuntime struct { func (x *SandboxRuntime) Reset() { *x = SandboxRuntime{} - mi := &file_ateapi_proto_msgTypes[99] + mi := &file_ateapi_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7395,7 +7288,7 @@ func (x *SandboxRuntime) String() string { func (*SandboxRuntime) ProtoMessage() {} func (x *SandboxRuntime) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[99] + mi := &file_ateapi_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7408,7 +7301,7 @@ func (x *SandboxRuntime) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxRuntime.ProtoReflect.Descriptor instead. func (*SandboxRuntime) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{99} + return file_ateapi_proto_rawDescGZIP(), []int{98} } func (x *SandboxRuntime) GetSandboxClass() string { @@ -7456,7 +7349,7 @@ type VersionedSandboxCompat struct { func (x *VersionedSandboxCompat) Reset() { *x = VersionedSandboxCompat{} - mi := &file_ateapi_proto_msgTypes[100] + mi := &file_ateapi_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7468,7 +7361,7 @@ func (x *VersionedSandboxCompat) String() string { func (*VersionedSandboxCompat) ProtoMessage() {} func (x *VersionedSandboxCompat) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[100] + mi := &file_ateapi_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7481,7 +7374,7 @@ func (x *VersionedSandboxCompat) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionedSandboxCompat.ProtoReflect.Descriptor instead. func (*VersionedSandboxCompat) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{100} + return file_ateapi_proto_rawDescGZIP(), []int{99} } func (x *VersionedSandboxCompat) GetSchemaVersion() string { @@ -7517,7 +7410,7 @@ type AttributeEntry struct { func (x *AttributeEntry) Reset() { *x = AttributeEntry{} - mi := &file_ateapi_proto_msgTypes[101] + mi := &file_ateapi_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7529,7 +7422,7 @@ func (x *AttributeEntry) String() string { func (*AttributeEntry) ProtoMessage() {} func (x *AttributeEntry) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[101] + mi := &file_ateapi_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7542,7 +7435,7 @@ func (x *AttributeEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use AttributeEntry.ProtoReflect.Descriptor instead. func (*AttributeEntry) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{101} + return file_ateapi_proto_rawDescGZIP(), []int{100} } func (x *AttributeEntry) GetKey() string { @@ -7581,7 +7474,7 @@ type WorkerResources struct { func (x *WorkerResources) Reset() { *x = WorkerResources{} - mi := &file_ateapi_proto_msgTypes[102] + mi := &file_ateapi_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7593,7 +7486,7 @@ func (x *WorkerResources) String() string { func (*WorkerResources) ProtoMessage() {} func (x *WorkerResources) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[102] + mi := &file_ateapi_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7606,7 +7499,7 @@ func (x *WorkerResources) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerResources.ProtoReflect.Descriptor instead. func (*WorkerResources) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{102} + return file_ateapi_proto_rawDescGZIP(), []int{101} } func (x *WorkerResources) GetResources() *Resources { @@ -7659,7 +7552,7 @@ type ActorAssignment struct { func (x *ActorAssignment) Reset() { *x = ActorAssignment{} - mi := &file_ateapi_proto_msgTypes[103] + mi := &file_ateapi_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7671,7 +7564,7 @@ func (x *ActorAssignment) String() string { func (*ActorAssignment) ProtoMessage() {} func (x *ActorAssignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[103] + mi := &file_ateapi_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7684,7 +7577,7 @@ func (x *ActorAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorAssignment.ProtoReflect.Descriptor instead. func (*ActorAssignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{103} + return file_ateapi_proto_rawDescGZIP(), []int{102} } func (x *ActorAssignment) GetMetadata() *ResourceMetadata { @@ -7755,7 +7648,7 @@ type RegisterWorkerRequest struct { func (x *RegisterWorkerRequest) Reset() { *x = RegisterWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[104] + mi := &file_ateapi_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7767,7 +7660,7 @@ func (x *RegisterWorkerRequest) String() string { func (*RegisterWorkerRequest) ProtoMessage() {} func (x *RegisterWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[104] + mi := &file_ateapi_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7780,7 +7673,7 @@ func (x *RegisterWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterWorkerRequest.ProtoReflect.Descriptor instead. func (*RegisterWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{104} + return file_ateapi_proto_rawDescGZIP(), []int{103} } func (x *RegisterWorkerRequest) GetWorker() *ObjectRef { @@ -7821,7 +7714,7 @@ type RegisterWorkerResponse struct { func (x *RegisterWorkerResponse) Reset() { *x = RegisterWorkerResponse{} - mi := &file_ateapi_proto_msgTypes[105] + mi := &file_ateapi_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7833,7 +7726,7 @@ func (x *RegisterWorkerResponse) String() string { func (*RegisterWorkerResponse) ProtoMessage() {} func (x *RegisterWorkerResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[105] + mi := &file_ateapi_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7846,7 +7739,7 @@ func (x *RegisterWorkerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterWorkerResponse.ProtoReflect.Descriptor instead. func (*RegisterWorkerResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{105} + return file_ateapi_proto_rawDescGZIP(), []int{104} } func (x *RegisterWorkerResponse) GetWorker() *Worker { @@ -7885,7 +7778,7 @@ type MintAteomActorCertificateRequest struct { func (x *MintAteomActorCertificateRequest) Reset() { *x = MintAteomActorCertificateRequest{} - mi := &file_ateapi_proto_msgTypes[106] + mi := &file_ateapi_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7897,7 +7790,7 @@ func (x *MintAteomActorCertificateRequest) String() string { func (*MintAteomActorCertificateRequest) ProtoMessage() {} func (x *MintAteomActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[106] + mi := &file_ateapi_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7910,7 +7803,7 @@ func (x *MintAteomActorCertificateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintAteomActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintAteomActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{106} + return file_ateapi_proto_rawDescGZIP(), []int{105} } func (x *MintAteomActorCertificateRequest) GetActor() *ObjectRef { @@ -7947,7 +7840,7 @@ type MintAteomActorCertificateResponse struct { func (x *MintAteomActorCertificateResponse) Reset() { *x = MintAteomActorCertificateResponse{} - mi := &file_ateapi_proto_msgTypes[107] + mi := &file_ateapi_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7959,7 +7852,7 @@ func (x *MintAteomActorCertificateResponse) String() string { func (*MintAteomActorCertificateResponse) ProtoMessage() {} func (x *MintAteomActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[107] + mi := &file_ateapi_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7972,7 +7865,7 @@ func (x *MintAteomActorCertificateResponse) ProtoReflect() protoreflect.Message // Deprecated: Use MintAteomActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintAteomActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{107} + return file_ateapi_proto_rawDescGZIP(), []int{106} } func (x *MintAteomActorCertificateResponse) GetActorCertificates() [][]byte { @@ -8010,7 +7903,7 @@ type RequestActorSuspendRequest struct { func (x *RequestActorSuspendRequest) Reset() { *x = RequestActorSuspendRequest{} - mi := &file_ateapi_proto_msgTypes[108] + mi := &file_ateapi_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8022,7 +7915,7 @@ func (x *RequestActorSuspendRequest) String() string { func (*RequestActorSuspendRequest) ProtoMessage() {} func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[108] + mi := &file_ateapi_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8035,7 +7928,7 @@ func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendRequest.ProtoReflect.Descriptor instead. func (*RequestActorSuspendRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{108} + return file_ateapi_proto_rawDescGZIP(), []int{107} } func (x *RequestActorSuspendRequest) GetWorker() *ObjectRef { @@ -8069,7 +7962,7 @@ type RequestActorSuspendResponse struct { func (x *RequestActorSuspendResponse) Reset() { *x = RequestActorSuspendResponse{} - mi := &file_ateapi_proto_msgTypes[109] + mi := &file_ateapi_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8081,7 +7974,7 @@ func (x *RequestActorSuspendResponse) String() string { func (*RequestActorSuspendResponse) ProtoMessage() {} func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[109] + mi := &file_ateapi_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8094,7 +7987,7 @@ func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendResponse.ProtoReflect.Descriptor instead. func (*RequestActorSuspendResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{109} + return file_ateapi_proto_rawDescGZIP(), []int{108} } func (x *RequestActorSuspendResponse) GetActor() *Actor { @@ -8129,7 +8022,7 @@ type AccessPolicy struct { func (x *AccessPolicy) Reset() { *x = AccessPolicy{} - mi := &file_ateapi_proto_msgTypes[110] + mi := &file_ateapi_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8141,7 +8034,7 @@ func (x *AccessPolicy) String() string { func (*AccessPolicy) ProtoMessage() {} func (x *AccessPolicy) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[110] + mi := &file_ateapi_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8154,7 +8047,7 @@ func (x *AccessPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessPolicy.ProtoReflect.Descriptor instead. func (*AccessPolicy) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{110} + return file_ateapi_proto_rawDescGZIP(), []int{109} } func (x *AccessPolicy) GetMetadata() *ResourceMetadata { @@ -8196,7 +8089,7 @@ type Binding struct { func (x *Binding) Reset() { *x = Binding{} - mi := &file_ateapi_proto_msgTypes[111] + mi := &file_ateapi_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8208,7 +8101,7 @@ func (x *Binding) String() string { func (*Binding) ProtoMessage() {} func (x *Binding) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[111] + mi := &file_ateapi_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8221,7 +8114,7 @@ func (x *Binding) ProtoReflect() protoreflect.Message { // Deprecated: Use Binding.ProtoReflect.Descriptor instead. func (*Binding) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{111} + return file_ateapi_proto_rawDescGZIP(), []int{110} } func (x *Binding) GetRole() string { @@ -8247,7 +8140,7 @@ type GetGlobalAccessPolicyRequest struct { func (x *GetGlobalAccessPolicyRequest) Reset() { *x = GetGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[112] + mi := &file_ateapi_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8259,7 +8152,7 @@ func (x *GetGlobalAccessPolicyRequest) String() string { func (*GetGlobalAccessPolicyRequest) ProtoMessage() {} func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[112] + mi := &file_ateapi_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8272,7 +8165,7 @@ func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{112} + return file_ateapi_proto_rawDescGZIP(), []int{111} } // CreateGlobalAccessPolicyRequest creates the deployment-wide global access policy singleton. @@ -8289,7 +8182,7 @@ type CreateGlobalAccessPolicyRequest struct { func (x *CreateGlobalAccessPolicyRequest) Reset() { *x = CreateGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[113] + mi := &file_ateapi_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8301,7 +8194,7 @@ func (x *CreateGlobalAccessPolicyRequest) String() string { func (*CreateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[113] + mi := &file_ateapi_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8314,7 +8207,7 @@ func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{113} + return file_ateapi_proto_rawDescGZIP(), []int{112} } func (x *CreateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -8338,7 +8231,7 @@ type UpdateGlobalAccessPolicyRequest struct { func (x *UpdateGlobalAccessPolicyRequest) Reset() { *x = UpdateGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[114] + mi := &file_ateapi_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8350,7 +8243,7 @@ func (x *UpdateGlobalAccessPolicyRequest) String() string { func (*UpdateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[114] + mi := &file_ateapi_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8363,7 +8256,7 @@ func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{114} + return file_ateapi_proto_rawDescGZIP(), []int{113} } func (x *UpdateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -8387,7 +8280,7 @@ type GetAtespaceAccessPolicyRequest struct { func (x *GetAtespaceAccessPolicyRequest) Reset() { *x = GetAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[115] + mi := &file_ateapi_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8399,7 +8292,7 @@ func (x *GetAtespaceAccessPolicyRequest) String() string { func (*GetAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[115] + mi := &file_ateapi_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8412,7 +8305,7 @@ func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{115} + return file_ateapi_proto_rawDescGZIP(), []int{114} } func (x *GetAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8441,7 +8334,7 @@ type CreateAtespaceAccessPolicyRequest struct { func (x *CreateAtespaceAccessPolicyRequest) Reset() { *x = CreateAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[116] + mi := &file_ateapi_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8453,7 +8346,7 @@ func (x *CreateAtespaceAccessPolicyRequest) String() string { func (*CreateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[116] + mi := &file_ateapi_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8466,7 +8359,7 @@ func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{116} + return file_ateapi_proto_rawDescGZIP(), []int{115} } func (x *CreateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8502,7 +8395,7 @@ type UpdateAtespaceAccessPolicyRequest struct { func (x *UpdateAtespaceAccessPolicyRequest) Reset() { *x = UpdateAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[117] + mi := &file_ateapi_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8514,7 +8407,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) String() string { func (*UpdateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[117] + mi := &file_ateapi_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8527,7 +8420,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{117} + return file_ateapi_proto_rawDescGZIP(), []int{116} } func (x *UpdateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8562,7 +8455,7 @@ type DeleteAtespaceAccessPolicyRequest struct { func (x *DeleteAtespaceAccessPolicyRequest) Reset() { *x = DeleteAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[118] + mi := &file_ateapi_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8574,7 +8467,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) String() string { func (*DeleteAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[118] + mi := &file_ateapi_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8587,7 +8480,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{118} + return file_ateapi_proto_rawDescGZIP(), []int{117} } func (x *DeleteAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8610,24 +8503,20 @@ const file_ateapi_proto_rawDesc = "" + "\n" + "\fateapi.proto\x12\x06ateapi\x1a\x1fgoogle/protobuf/timestamp.proto\"3\n" + "\x0eObjectSnapshot\x12!\n" + - "\fsnapshot_uri\x18\x01 \x01(\tR\vsnapshotUri\"4\n" + - "\rLocalSnapshot\x12#\n" + - "\rsnapshot_name\x18\x01 \x01(\tR\fsnapshotName\"\x97\x02\n" + - "\x0fSnapshotStorage\x12:\n" + - "\n" + - "durability\x18\x01 \x01(\x0e2\x1a.ateapi.SnapshotDurabilityR\n" + - "durability\x125\n" + - "\x06status\x18\x02 \x01(\x0e2\x1d.ateapi.SnapshotStorageStatusR\x06status\x124\n" + - "\bfidelity\x18\x03 \x01(\x0e2\x18.ateapi.SnapshotFidelityR\bfidelity\x12+\n" + - "\x05local\x18\x04 \x01(\v2\x15.ateapi.LocalSnapshotR\x05local\x12.\n" + - "\x06object\x18\x05 \x01(\v2\x16.ateapi.ObjectSnapshotR\x06object\"\xb8\x01\n" + - "\bSnapshot\x12\x1e\n" + + "\fsnapshot_uri\x18\x01 \x01(\tR\vsnapshotUri\"x\n" + + "\x0fSnapshotStorage\x125\n" + + "\x06status\x18\x01 \x01(\x0e2\x1d.ateapi.SnapshotStorageStatusR\x06status\x12.\n" + + "\x06object\x18\x02 \x01(\v2\x16.ateapi.ObjectSnapshotR\x06object\"\xaf\x02\n" + + "\bSnapshot\x12\x12\n" + + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1e\n" + "\n" + - "generation\x18\x01 \x01(\x05R\n" + + "generation\x18\x02 \x01(\x05R\n" + "generation\x12+\n" + - "\x05owner\x18\x02 \x01(\x0e2\x15.ateapi.SnapshotOwnerR\x05owner\x12,\n" + - "\x12actor_template_uid\x18\x03 \x01(\tR\x10actorTemplateUid\x121\n" + - "\astorage\x18\x04 \x03(\v2\x17.ateapi.SnapshotStorageR\astorage\"\x90\x01\n" + + "\x05owner\x18\x03 \x01(\x0e2\x15.ateapi.SnapshotOwnerR\x05owner\x124\n" + + "\bfidelity\x18\x04 \x01(\x0e2\x18.ateapi.SnapshotFidelityR\bfidelity\x12,\n" + + "\x12actor_template_uid\x18\x05 \x01(\tR\x10actorTemplateUid\x12\x1a\n" + + "\blocality\x18\x06 \x01(\tR\blocality\x12B\n" + + "\x10durable_snapshot\x18\a \x01(\v2\x17.ateapi.SnapshotStorageR\x0fdurableSnapshot\"\x90\x01\n" + "\bSelector\x12D\n" + "\fmatch_labels\x18\x01 \x03(\v2!.ateapi.Selector.MatchLabelsEntryR\vmatchLabels\x1a>\n" + "\x10MatchLabelsEntry\x12\x10\n" + @@ -9047,14 +8936,10 @@ const file_ateapi_proto_rawDesc = "" + "\rSnapshotOwner\x12\x1e\n" + "\x1aSNAPSHOT_OWNER_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14SNAPSHOT_OWNER_ACTOR\x10\x01\x12\x16\n" + - "\x12SNAPSHOT_OWNER_TAG\x10\x02*y\n" + - "\x12SnapshotDurability\x12#\n" + - "\x1fSNAPSHOT_DURABILITY_UNSPECIFIED\x10\x00\x12\x1d\n" + - "\x19SNAPSHOT_DURABILITY_LOCAL\x10\x01\x12\x1f\n" + - "\x1bSNAPSHOT_DURABILITY_DURABLE\x10\x02*\x90\x01\n" + + "\x12SNAPSHOT_OWNER_TAG\x10\x02*\x8c\x01\n" + "\x15SnapshotStorageStatus\x12'\n" + - "#SNAPSHOT_STORAGE_STATUS_UNSPECIFIED\x10\x00\x12'\n" + - "#SNAPSHOT_STORAGE_STATUS_IN_PROGRESS\x10\x01\x12%\n" + + "#SNAPSHOT_STORAGE_STATUS_UNSPECIFIED\x10\x00\x12#\n" + + "\x1fSNAPSHOT_STORAGE_STATUS_PENDING\x10\x01\x12%\n" + "!SNAPSHOT_STORAGE_STATUS_COMPLETED\x10\x02*\x90\x01\n" + "\x10SnapshotFidelity\x12!\n" + "\x1dSNAPSHOT_FIDELITY_UNSPECIFIED\x10\x00\x12\x1d\n" + @@ -9156,398 +9041,394 @@ func file_ateapi_proto_rawDescGZIP() []byte { return file_ateapi_proto_rawDescData } -var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 11) -var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 122) +var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 10) +var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 121) var file_ateapi_proto_goTypes = []any{ (SnapshotOwner)(0), // 0: ateapi.SnapshotOwner - (SnapshotDurability)(0), // 1: ateapi.SnapshotDurability - (SnapshotStorageStatus)(0), // 2: ateapi.SnapshotStorageStatus - (SnapshotFidelity)(0), // 3: ateapi.SnapshotFidelity - (TagScope)(0), // 4: ateapi.TagScope - (ActorState)(0), // 5: ateapi.ActorState - (SandboxClass)(0), // 6: ateapi.SandboxClass - (GoldenSnapshotMode)(0), // 7: ateapi.GoldenSnapshotMode - (ActorMetadataField)(0), // 8: ateapi.ActorMetadataField - (WorkerState)(0), // 9: ateapi.WorkerState - (ExternalVolume_Status)(0), // 10: ateapi.ExternalVolume.Status - (*ObjectSnapshot)(nil), // 11: ateapi.ObjectSnapshot - (*LocalSnapshot)(nil), // 12: ateapi.LocalSnapshot - (*SnapshotStorage)(nil), // 13: ateapi.SnapshotStorage - (*Snapshot)(nil), // 14: ateapi.Snapshot - (*Selector)(nil), // 15: ateapi.Selector - (*ResourceMetadata)(nil), // 16: ateapi.ResourceMetadata - (*ExternalVolume)(nil), // 17: ateapi.ExternalVolume - (*Actor)(nil), // 18: ateapi.Actor - (*EgressPolicy)(nil), // 19: ateapi.EgressPolicy - (*EgressRule)(nil), // 20: ateapi.EgressRule - (*HTTPRule)(nil), // 21: ateapi.HTTPRule - (*HTTPSRule)(nil), // 22: ateapi.HTTPSRule - (*TLSPassthroughRule)(nil), // 23: ateapi.TLSPassthroughRule - (*Ports)(nil), // 24: ateapi.Ports - (*AllPorts)(nil), // 25: ateapi.AllPorts - (*HttpRuleEffects)(nil), // 26: ateapi.HttpRuleEffects - (*CredentialHeader)(nil), // 27: ateapi.CredentialHeader - (*ActorJWTSource)(nil), // 28: ateapi.ActorJWTSource - (*ActorStatus)(nil), // 29: ateapi.ActorStatus - (*ActorCrash)(nil), // 30: ateapi.ActorCrash - (*WorkerAssignment)(nil), // 31: ateapi.WorkerAssignment - (*TagStatus)(nil), // 32: ateapi.TagStatus - (*Tag)(nil), // 33: ateapi.Tag - (*Atespace)(nil), // 34: ateapi.Atespace - (*ObjectRef)(nil), // 35: ateapi.ObjectRef - (*ActorTemplate)(nil), // 36: ateapi.ActorTemplate - (*Resources)(nil), // 37: ateapi.Resources - (*Limits)(nil), // 38: ateapi.Limits - (*GoldenSnapshotStatus)(nil), // 39: ateapi.GoldenSnapshotStatus - (*ActorTemplateStatus)(nil), // 40: ateapi.ActorTemplateStatus - (*SandboxConfig)(nil), // 41: ateapi.SandboxConfig - (*SnapshotConfig)(nil), // 42: ateapi.SnapshotConfig - (*GoldenSnapshotConfig)(nil), // 43: ateapi.GoldenSnapshotConfig - (*Container)(nil), // 44: ateapi.Container - (*SecurityContext)(nil), // 45: ateapi.SecurityContext - (*Capabilities)(nil), // 46: ateapi.Capabilities - (*EnvVar)(nil), // 47: ateapi.EnvVar - (*ContainerWakeupProbe)(nil), // 48: ateapi.ContainerWakeupProbe - (*HTTPGetAction)(nil), // 49: ateapi.HTTPGetAction - (*Volume)(nil), // 50: ateapi.Volume - (*ImageVolumeSource)(nil), // 51: ateapi.ImageVolumeSource - (*DurableDirVolumeSource)(nil), // 52: ateapi.DurableDirVolumeSource - (*ExternalVolumeTemplate)(nil), // 53: ateapi.ExternalVolumeTemplate - (*SystemInfoVolumeSource)(nil), // 54: ateapi.SystemInfoVolumeSource - (*SystemInfoDataSource)(nil), // 55: ateapi.SystemInfoDataSource - (*ActorMetadataDataSource)(nil), // 56: ateapi.ActorMetadataDataSource - (*ActorMetadataItem)(nil), // 57: ateapi.ActorMetadataItem - (*TrustBundleDataSource)(nil), // 58: ateapi.TrustBundleDataSource - (*VolumeMount)(nil), // 59: ateapi.VolumeMount - (*CreateAtespaceRequest)(nil), // 60: ateapi.CreateAtespaceRequest - (*GetAtespaceRequest)(nil), // 61: ateapi.GetAtespaceRequest - (*ListAtespacesRequest)(nil), // 62: ateapi.ListAtespacesRequest - (*ListAtespacesResponse)(nil), // 63: ateapi.ListAtespacesResponse - (*DeleteAtespaceRequest)(nil), // 64: ateapi.DeleteAtespaceRequest - (*CreateActorTemplateRequest)(nil), // 65: ateapi.CreateActorTemplateRequest - (*GetActorTemplateRequest)(nil), // 66: ateapi.GetActorTemplateRequest - (*ListActorTemplatesRequest)(nil), // 67: ateapi.ListActorTemplatesRequest - (*ListActorTemplatesResponse)(nil), // 68: ateapi.ListActorTemplatesResponse - (*DeleteActorTemplateRequest)(nil), // 69: ateapi.DeleteActorTemplateRequest - (*GetActorRequest)(nil), // 70: ateapi.GetActorRequest - (*CreateActorRequest)(nil), // 71: ateapi.CreateActorRequest - (*UpdateActorRequest)(nil), // 72: ateapi.UpdateActorRequest - (*SuspendActorRequest)(nil), // 73: ateapi.SuspendActorRequest - (*SuspendActorResponse)(nil), // 74: ateapi.SuspendActorResponse - (*PauseActorRequest)(nil), // 75: ateapi.PauseActorRequest - (*PauseActorResponse)(nil), // 76: ateapi.PauseActorResponse - (*ResumeActorRequest)(nil), // 77: ateapi.ResumeActorRequest - (*ResumeActorResponse)(nil), // 78: ateapi.ResumeActorResponse - (*RevertActorRequest)(nil), // 79: ateapi.RevertActorRequest - (*RevertActorResponse)(nil), // 80: ateapi.RevertActorResponse - (*DeleteActorRequest)(nil), // 81: ateapi.DeleteActorRequest - (*GetActorEgressPolicyRequest)(nil), // 82: ateapi.GetActorEgressPolicyRequest - (*CreateActorEgressPolicyRequest)(nil), // 83: ateapi.CreateActorEgressPolicyRequest - (*UpdateActorEgressPolicyRequest)(nil), // 84: ateapi.UpdateActorEgressPolicyRequest - (*DeleteActorEgressPolicyRequest)(nil), // 85: ateapi.DeleteActorEgressPolicyRequest - (*GetTagRequest)(nil), // 86: ateapi.GetTagRequest - (*MintActorJWTRequest)(nil), // 87: ateapi.MintActorJWTRequest - (*MintActorJWTResponse)(nil), // 88: ateapi.MintActorJWTResponse - (*MintActorCertificateRequest)(nil), // 89: ateapi.MintActorCertificateRequest - (*MintActorCertificateResponse)(nil), // 90: ateapi.MintActorCertificateResponse - (*ListTagsRequest)(nil), // 91: ateapi.ListTagsRequest - (*ListTagsResponse)(nil), // 92: ateapi.ListTagsResponse - (*CreateTagRequest)(nil), // 93: ateapi.CreateTagRequest - (*UpdateTagRequest)(nil), // 94: ateapi.UpdateTagRequest - (*DeleteTagRequest)(nil), // 95: ateapi.DeleteTagRequest - (*DeleteOptions)(nil), // 96: ateapi.DeleteOptions - (*ListWorkerActorAssignmentsRequest)(nil), // 97: ateapi.ListWorkerActorAssignmentsRequest - (*ListWorkerActorAssignmentsResponse)(nil), // 98: ateapi.ListWorkerActorAssignmentsResponse - (*ListWorkersRequest)(nil), // 99: ateapi.ListWorkersRequest - (*ListWorkersResponse)(nil), // 100: ateapi.ListWorkersResponse - (*GetWorkerRequest)(nil), // 101: ateapi.GetWorkerRequest - (*CreateWorkerRequest)(nil), // 102: ateapi.CreateWorkerRequest - (*UpdateWorkerRequest)(nil), // 103: ateapi.UpdateWorkerRequest - (*DeleteWorkerRequest)(nil), // 104: ateapi.DeleteWorkerRequest - (*DrainWorkerRequest)(nil), // 105: ateapi.DrainWorkerRequest - (*ListActorsRequest)(nil), // 106: ateapi.ListActorsRequest - (*ListActorsResponse)(nil), // 107: ateapi.ListActorsResponse - (*Worker)(nil), // 108: ateapi.Worker - (*WorkerStatus)(nil), // 109: ateapi.WorkerStatus - (*SandboxRuntime)(nil), // 110: ateapi.SandboxRuntime - (*VersionedSandboxCompat)(nil), // 111: ateapi.VersionedSandboxCompat - (*AttributeEntry)(nil), // 112: ateapi.AttributeEntry - (*WorkerResources)(nil), // 113: ateapi.WorkerResources - (*ActorAssignment)(nil), // 114: ateapi.ActorAssignment - (*RegisterWorkerRequest)(nil), // 115: ateapi.RegisterWorkerRequest - (*RegisterWorkerResponse)(nil), // 116: ateapi.RegisterWorkerResponse - (*MintAteomActorCertificateRequest)(nil), // 117: ateapi.MintAteomActorCertificateRequest - (*MintAteomActorCertificateResponse)(nil), // 118: ateapi.MintAteomActorCertificateResponse - (*RequestActorSuspendRequest)(nil), // 119: ateapi.RequestActorSuspendRequest - (*RequestActorSuspendResponse)(nil), // 120: ateapi.RequestActorSuspendResponse - (*AccessPolicy)(nil), // 121: ateapi.AccessPolicy - (*Binding)(nil), // 122: ateapi.Binding - (*GetGlobalAccessPolicyRequest)(nil), // 123: ateapi.GetGlobalAccessPolicyRequest - (*CreateGlobalAccessPolicyRequest)(nil), // 124: ateapi.CreateGlobalAccessPolicyRequest - (*UpdateGlobalAccessPolicyRequest)(nil), // 125: ateapi.UpdateGlobalAccessPolicyRequest - (*GetAtespaceAccessPolicyRequest)(nil), // 126: ateapi.GetAtespaceAccessPolicyRequest - (*CreateAtespaceAccessPolicyRequest)(nil), // 127: ateapi.CreateAtespaceAccessPolicyRequest - (*UpdateAtespaceAccessPolicyRequest)(nil), // 128: ateapi.UpdateAtespaceAccessPolicyRequest - (*DeleteAtespaceAccessPolicyRequest)(nil), // 129: ateapi.DeleteAtespaceAccessPolicyRequest - nil, // 130: ateapi.Selector.MatchLabelsEntry - nil, // 131: ateapi.ExternalVolume.VolumeContextEntry - nil, // 132: ateapi.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 133: google.protobuf.Timestamp + (SnapshotStorageStatus)(0), // 1: ateapi.SnapshotStorageStatus + (SnapshotFidelity)(0), // 2: ateapi.SnapshotFidelity + (TagScope)(0), // 3: ateapi.TagScope + (ActorState)(0), // 4: ateapi.ActorState + (SandboxClass)(0), // 5: ateapi.SandboxClass + (GoldenSnapshotMode)(0), // 6: ateapi.GoldenSnapshotMode + (ActorMetadataField)(0), // 7: ateapi.ActorMetadataField + (WorkerState)(0), // 8: ateapi.WorkerState + (ExternalVolume_Status)(0), // 9: ateapi.ExternalVolume.Status + (*ObjectSnapshot)(nil), // 10: ateapi.ObjectSnapshot + (*SnapshotStorage)(nil), // 11: ateapi.SnapshotStorage + (*Snapshot)(nil), // 12: ateapi.Snapshot + (*Selector)(nil), // 13: ateapi.Selector + (*ResourceMetadata)(nil), // 14: ateapi.ResourceMetadata + (*ExternalVolume)(nil), // 15: ateapi.ExternalVolume + (*Actor)(nil), // 16: ateapi.Actor + (*EgressPolicy)(nil), // 17: ateapi.EgressPolicy + (*EgressRule)(nil), // 18: ateapi.EgressRule + (*HTTPRule)(nil), // 19: ateapi.HTTPRule + (*HTTPSRule)(nil), // 20: ateapi.HTTPSRule + (*TLSPassthroughRule)(nil), // 21: ateapi.TLSPassthroughRule + (*Ports)(nil), // 22: ateapi.Ports + (*AllPorts)(nil), // 23: ateapi.AllPorts + (*HttpRuleEffects)(nil), // 24: ateapi.HttpRuleEffects + (*CredentialHeader)(nil), // 25: ateapi.CredentialHeader + (*ActorJWTSource)(nil), // 26: ateapi.ActorJWTSource + (*ActorStatus)(nil), // 27: ateapi.ActorStatus + (*ActorCrash)(nil), // 28: ateapi.ActorCrash + (*WorkerAssignment)(nil), // 29: ateapi.WorkerAssignment + (*TagStatus)(nil), // 30: ateapi.TagStatus + (*Tag)(nil), // 31: ateapi.Tag + (*Atespace)(nil), // 32: ateapi.Atespace + (*ObjectRef)(nil), // 33: ateapi.ObjectRef + (*ActorTemplate)(nil), // 34: ateapi.ActorTemplate + (*Resources)(nil), // 35: ateapi.Resources + (*Limits)(nil), // 36: ateapi.Limits + (*GoldenSnapshotStatus)(nil), // 37: ateapi.GoldenSnapshotStatus + (*ActorTemplateStatus)(nil), // 38: ateapi.ActorTemplateStatus + (*SandboxConfig)(nil), // 39: ateapi.SandboxConfig + (*SnapshotConfig)(nil), // 40: ateapi.SnapshotConfig + (*GoldenSnapshotConfig)(nil), // 41: ateapi.GoldenSnapshotConfig + (*Container)(nil), // 42: ateapi.Container + (*SecurityContext)(nil), // 43: ateapi.SecurityContext + (*Capabilities)(nil), // 44: ateapi.Capabilities + (*EnvVar)(nil), // 45: ateapi.EnvVar + (*ContainerWakeupProbe)(nil), // 46: ateapi.ContainerWakeupProbe + (*HTTPGetAction)(nil), // 47: ateapi.HTTPGetAction + (*Volume)(nil), // 48: ateapi.Volume + (*ImageVolumeSource)(nil), // 49: ateapi.ImageVolumeSource + (*DurableDirVolumeSource)(nil), // 50: ateapi.DurableDirVolumeSource + (*ExternalVolumeTemplate)(nil), // 51: ateapi.ExternalVolumeTemplate + (*SystemInfoVolumeSource)(nil), // 52: ateapi.SystemInfoVolumeSource + (*SystemInfoDataSource)(nil), // 53: ateapi.SystemInfoDataSource + (*ActorMetadataDataSource)(nil), // 54: ateapi.ActorMetadataDataSource + (*ActorMetadataItem)(nil), // 55: ateapi.ActorMetadataItem + (*TrustBundleDataSource)(nil), // 56: ateapi.TrustBundleDataSource + (*VolumeMount)(nil), // 57: ateapi.VolumeMount + (*CreateAtespaceRequest)(nil), // 58: ateapi.CreateAtespaceRequest + (*GetAtespaceRequest)(nil), // 59: ateapi.GetAtespaceRequest + (*ListAtespacesRequest)(nil), // 60: ateapi.ListAtespacesRequest + (*ListAtespacesResponse)(nil), // 61: ateapi.ListAtespacesResponse + (*DeleteAtespaceRequest)(nil), // 62: ateapi.DeleteAtespaceRequest + (*CreateActorTemplateRequest)(nil), // 63: ateapi.CreateActorTemplateRequest + (*GetActorTemplateRequest)(nil), // 64: ateapi.GetActorTemplateRequest + (*ListActorTemplatesRequest)(nil), // 65: ateapi.ListActorTemplatesRequest + (*ListActorTemplatesResponse)(nil), // 66: ateapi.ListActorTemplatesResponse + (*DeleteActorTemplateRequest)(nil), // 67: ateapi.DeleteActorTemplateRequest + (*GetActorRequest)(nil), // 68: ateapi.GetActorRequest + (*CreateActorRequest)(nil), // 69: ateapi.CreateActorRequest + (*UpdateActorRequest)(nil), // 70: ateapi.UpdateActorRequest + (*SuspendActorRequest)(nil), // 71: ateapi.SuspendActorRequest + (*SuspendActorResponse)(nil), // 72: ateapi.SuspendActorResponse + (*PauseActorRequest)(nil), // 73: ateapi.PauseActorRequest + (*PauseActorResponse)(nil), // 74: ateapi.PauseActorResponse + (*ResumeActorRequest)(nil), // 75: ateapi.ResumeActorRequest + (*ResumeActorResponse)(nil), // 76: ateapi.ResumeActorResponse + (*RevertActorRequest)(nil), // 77: ateapi.RevertActorRequest + (*RevertActorResponse)(nil), // 78: ateapi.RevertActorResponse + (*DeleteActorRequest)(nil), // 79: ateapi.DeleteActorRequest + (*GetActorEgressPolicyRequest)(nil), // 80: ateapi.GetActorEgressPolicyRequest + (*CreateActorEgressPolicyRequest)(nil), // 81: ateapi.CreateActorEgressPolicyRequest + (*UpdateActorEgressPolicyRequest)(nil), // 82: ateapi.UpdateActorEgressPolicyRequest + (*DeleteActorEgressPolicyRequest)(nil), // 83: ateapi.DeleteActorEgressPolicyRequest + (*GetTagRequest)(nil), // 84: ateapi.GetTagRequest + (*MintActorJWTRequest)(nil), // 85: ateapi.MintActorJWTRequest + (*MintActorJWTResponse)(nil), // 86: ateapi.MintActorJWTResponse + (*MintActorCertificateRequest)(nil), // 87: ateapi.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 88: ateapi.MintActorCertificateResponse + (*ListTagsRequest)(nil), // 89: ateapi.ListTagsRequest + (*ListTagsResponse)(nil), // 90: ateapi.ListTagsResponse + (*CreateTagRequest)(nil), // 91: ateapi.CreateTagRequest + (*UpdateTagRequest)(nil), // 92: ateapi.UpdateTagRequest + (*DeleteTagRequest)(nil), // 93: ateapi.DeleteTagRequest + (*DeleteOptions)(nil), // 94: ateapi.DeleteOptions + (*ListWorkerActorAssignmentsRequest)(nil), // 95: ateapi.ListWorkerActorAssignmentsRequest + (*ListWorkerActorAssignmentsResponse)(nil), // 96: ateapi.ListWorkerActorAssignmentsResponse + (*ListWorkersRequest)(nil), // 97: ateapi.ListWorkersRequest + (*ListWorkersResponse)(nil), // 98: ateapi.ListWorkersResponse + (*GetWorkerRequest)(nil), // 99: ateapi.GetWorkerRequest + (*CreateWorkerRequest)(nil), // 100: ateapi.CreateWorkerRequest + (*UpdateWorkerRequest)(nil), // 101: ateapi.UpdateWorkerRequest + (*DeleteWorkerRequest)(nil), // 102: ateapi.DeleteWorkerRequest + (*DrainWorkerRequest)(nil), // 103: ateapi.DrainWorkerRequest + (*ListActorsRequest)(nil), // 104: ateapi.ListActorsRequest + (*ListActorsResponse)(nil), // 105: ateapi.ListActorsResponse + (*Worker)(nil), // 106: ateapi.Worker + (*WorkerStatus)(nil), // 107: ateapi.WorkerStatus + (*SandboxRuntime)(nil), // 108: ateapi.SandboxRuntime + (*VersionedSandboxCompat)(nil), // 109: ateapi.VersionedSandboxCompat + (*AttributeEntry)(nil), // 110: ateapi.AttributeEntry + (*WorkerResources)(nil), // 111: ateapi.WorkerResources + (*ActorAssignment)(nil), // 112: ateapi.ActorAssignment + (*RegisterWorkerRequest)(nil), // 113: ateapi.RegisterWorkerRequest + (*RegisterWorkerResponse)(nil), // 114: ateapi.RegisterWorkerResponse + (*MintAteomActorCertificateRequest)(nil), // 115: ateapi.MintAteomActorCertificateRequest + (*MintAteomActorCertificateResponse)(nil), // 116: ateapi.MintAteomActorCertificateResponse + (*RequestActorSuspendRequest)(nil), // 117: ateapi.RequestActorSuspendRequest + (*RequestActorSuspendResponse)(nil), // 118: ateapi.RequestActorSuspendResponse + (*AccessPolicy)(nil), // 119: ateapi.AccessPolicy + (*Binding)(nil), // 120: ateapi.Binding + (*GetGlobalAccessPolicyRequest)(nil), // 121: ateapi.GetGlobalAccessPolicyRequest + (*CreateGlobalAccessPolicyRequest)(nil), // 122: ateapi.CreateGlobalAccessPolicyRequest + (*UpdateGlobalAccessPolicyRequest)(nil), // 123: ateapi.UpdateGlobalAccessPolicyRequest + (*GetAtespaceAccessPolicyRequest)(nil), // 124: ateapi.GetAtespaceAccessPolicyRequest + (*CreateAtespaceAccessPolicyRequest)(nil), // 125: ateapi.CreateAtespaceAccessPolicyRequest + (*UpdateAtespaceAccessPolicyRequest)(nil), // 126: ateapi.UpdateAtespaceAccessPolicyRequest + (*DeleteAtespaceAccessPolicyRequest)(nil), // 127: ateapi.DeleteAtespaceAccessPolicyRequest + nil, // 128: ateapi.Selector.MatchLabelsEntry + nil, // 129: ateapi.ExternalVolume.VolumeContextEntry + nil, // 130: ateapi.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 131: google.protobuf.Timestamp } var file_ateapi_proto_depIdxs = []int32{ - 1, // 0: ateapi.SnapshotStorage.durability:type_name -> ateapi.SnapshotDurability - 2, // 1: ateapi.SnapshotStorage.status:type_name -> ateapi.SnapshotStorageStatus - 3, // 2: ateapi.SnapshotStorage.fidelity:type_name -> ateapi.SnapshotFidelity - 12, // 3: ateapi.SnapshotStorage.local:type_name -> ateapi.LocalSnapshot - 11, // 4: ateapi.SnapshotStorage.object:type_name -> ateapi.ObjectSnapshot - 0, // 5: ateapi.Snapshot.owner:type_name -> ateapi.SnapshotOwner - 13, // 6: ateapi.Snapshot.storage:type_name -> ateapi.SnapshotStorage - 130, // 7: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry - 133, // 8: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 133, // 9: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp - 10, // 10: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status - 131, // 11: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry - 16, // 12: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata - 35, // 13: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef - 15, // 14: ateapi.Actor.worker_selector:type_name -> ateapi.Selector - 35, // 15: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef - 29, // 16: ateapi.Actor.status:type_name -> ateapi.ActorStatus - 16, // 17: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata - 20, // 18: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule - 21, // 19: ateapi.EgressRule.http:type_name -> ateapi.HTTPRule - 22, // 20: ateapi.EgressRule.https:type_name -> ateapi.HTTPSRule - 23, // 21: ateapi.EgressRule.tls_passthrough:type_name -> ateapi.TLSPassthroughRule - 24, // 22: ateapi.HTTPRule.ports:type_name -> ateapi.Ports - 26, // 23: ateapi.HTTPRule.effects:type_name -> ateapi.HttpRuleEffects - 24, // 24: ateapi.HTTPSRule.ports:type_name -> ateapi.Ports - 26, // 25: ateapi.HTTPSRule.effects:type_name -> ateapi.HttpRuleEffects - 24, // 26: ateapi.TLSPassthroughRule.ports:type_name -> ateapi.Ports - 25, // 27: ateapi.Ports.all:type_name -> ateapi.AllPorts - 27, // 28: ateapi.HttpRuleEffects.replace_headers:type_name -> ateapi.CredentialHeader - 28, // 29: ateapi.CredentialHeader.actor_jwt:type_name -> ateapi.ActorJWTSource - 5, // 30: ateapi.ActorStatus.state:type_name -> ateapi.ActorState - 31, // 31: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment - 17, // 32: ateapi.ActorStatus.external_volumes:type_name -> ateapi.ExternalVolume - 30, // 33: ateapi.ActorStatus.crash:type_name -> ateapi.ActorCrash - 14, // 34: ateapi.ActorStatus.snapshots:type_name -> ateapi.Snapshot - 133, // 35: ateapi.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp - 35, // 36: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef - 14, // 37: ateapi.TagStatus.snapshot:type_name -> ateapi.Snapshot - 16, // 38: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata - 32, // 39: ateapi.Tag.status:type_name -> ateapi.TagStatus - 4, // 40: ateapi.Tag.scope:type_name -> ateapi.TagScope - 35, // 41: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef - 16, // 42: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata - 16, // 43: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata - 15, // 44: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector - 44, // 45: ateapi.ActorTemplate.containers:type_name -> ateapi.Container - 50, // 46: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume - 42, // 47: ateapi.ActorTemplate.snapshot_config:type_name -> ateapi.SnapshotConfig - 41, // 48: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig - 37, // 49: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources - 40, // 50: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus - 38, // 51: ateapi.Resources.limits:type_name -> ateapi.Limits - 35, // 52: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef - 133, // 53: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp - 39, // 54: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus - 6, // 55: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass - 3, // 56: ateapi.SnapshotConfig.preferred_fidelity:type_name -> ateapi.SnapshotFidelity - 43, // 57: ateapi.SnapshotConfig.golden_snapshot_config:type_name -> ateapi.GoldenSnapshotConfig - 7, // 58: ateapi.GoldenSnapshotConfig.mode:type_name -> ateapi.GoldenSnapshotMode - 47, // 59: ateapi.Container.env:type_name -> ateapi.EnvVar - 48, // 60: ateapi.Container.wakeup_probe:type_name -> ateapi.ContainerWakeupProbe - 59, // 61: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount - 45, // 62: ateapi.Container.security_context:type_name -> ateapi.SecurityContext - 37, // 63: ateapi.Container.resources:type_name -> ateapi.Resources - 46, // 64: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities - 49, // 65: ateapi.ContainerWakeupProbe.http_get:type_name -> ateapi.HTTPGetAction - 52, // 66: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource - 53, // 67: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate - 54, // 68: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource - 51, // 69: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource - 55, // 70: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource - 56, // 71: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource - 58, // 72: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource - 57, // 73: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem - 8, // 74: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField - 34, // 75: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace - 35, // 76: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 34, // 77: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace - 35, // 78: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 96, // 79: ateapi.DeleteAtespaceRequest.options:type_name -> ateapi.DeleteOptions - 36, // 80: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate - 35, // 81: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 36, // 82: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate - 35, // 83: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 96, // 84: ateapi.DeleteActorTemplateRequest.options:type_name -> ateapi.DeleteOptions - 35, // 85: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef - 18, // 86: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor - 18, // 87: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor - 35, // 88: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef - 18, // 89: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor - 35, // 90: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef - 18, // 91: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor - 35, // 92: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef - 18, // 93: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor - 35, // 94: ateapi.RevertActorRequest.actor:type_name -> ateapi.ObjectRef - 18, // 95: ateapi.RevertActorResponse.actor:type_name -> ateapi.Actor - 35, // 96: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef - 96, // 97: ateapi.DeleteActorRequest.options:type_name -> ateapi.DeleteOptions - 35, // 98: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 35, // 99: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 19, // 100: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 35, // 101: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 19, // 102: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 35, // 103: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 96, // 104: ateapi.DeleteActorEgressPolicyRequest.options:type_name -> ateapi.DeleteOptions - 35, // 105: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef - 35, // 106: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef - 133, // 107: ateapi.MintActorJWTResponse.expires_at:type_name -> google.protobuf.Timestamp - 35, // 108: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 33, // 109: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag - 33, // 110: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag - 33, // 111: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag - 35, // 112: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef - 96, // 113: ateapi.DeleteTagRequest.options:type_name -> ateapi.DeleteOptions - 35, // 114: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef - 114, // 115: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment - 108, // 116: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 35, // 117: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef - 108, // 118: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker - 108, // 119: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker - 35, // 120: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef - 96, // 121: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions - 35, // 122: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef - 18, // 123: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 16, // 124: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata - 132, // 125: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 109, // 126: ateapi.Worker.status:type_name -> ateapi.WorkerStatus - 9, // 127: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState - 113, // 128: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources - 113, // 129: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources - 110, // 130: ateapi.WorkerStatus.default_runtime:type_name -> ateapi.SandboxRuntime - 110, // 131: ateapi.WorkerStatus.restorable_runtimes:type_name -> ateapi.SandboxRuntime - 111, // 132: ateapi.SandboxRuntime.version:type_name -> ateapi.VersionedSandboxCompat - 112, // 133: ateapi.VersionedSandboxCompat.attributes:type_name -> ateapi.AttributeEntry - 37, // 134: ateapi.WorkerResources.resources:type_name -> ateapi.Resources - 16, // 135: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata - 35, // 136: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef - 37, // 137: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources - 35, // 138: ateapi.RegisterWorkerRequest.worker:type_name -> ateapi.ObjectRef - 113, // 139: ateapi.RegisterWorkerRequest.capacity:type_name -> ateapi.WorkerResources - 110, // 140: ateapi.RegisterWorkerRequest.default_runtime:type_name -> ateapi.SandboxRuntime - 110, // 141: ateapi.RegisterWorkerRequest.restorable_runtimes:type_name -> ateapi.SandboxRuntime - 108, // 142: ateapi.RegisterWorkerResponse.worker:type_name -> ateapi.Worker - 35, // 143: ateapi.MintAteomActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 35, // 144: ateapi.RequestActorSuspendRequest.worker:type_name -> ateapi.ObjectRef - 35, // 145: ateapi.RequestActorSuspendRequest.actor:type_name -> ateapi.ObjectRef - 18, // 146: ateapi.RequestActorSuspendResponse.actor:type_name -> ateapi.Actor - 16, // 147: ateapi.AccessPolicy.metadata:type_name -> ateapi.ResourceMetadata - 122, // 148: ateapi.AccessPolicy.bindings:type_name -> ateapi.Binding - 121, // 149: ateapi.CreateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 121, // 150: ateapi.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 35, // 151: ateapi.GetAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 35, // 152: ateapi.CreateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 121, // 153: ateapi.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 35, // 154: ateapi.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 121, // 155: ateapi.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 35, // 156: ateapi.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 96, // 157: ateapi.DeleteAtespaceAccessPolicyRequest.options:type_name -> ateapi.DeleteOptions - 70, // 158: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 71, // 159: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 72, // 160: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 73, // 161: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 75, // 162: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 77, // 163: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 79, // 164: ateapi.Control.RevertActor:input_type -> ateapi.RevertActorRequest - 81, // 165: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 82, // 166: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest - 83, // 167: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest - 84, // 168: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest - 85, // 169: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest - 87, // 170: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest - 89, // 171: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest - 93, // 172: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest - 86, // 173: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest - 91, // 174: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest - 94, // 175: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest - 95, // 176: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest - 99, // 177: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 101, // 178: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest - 102, // 179: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest - 103, // 180: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest - 104, // 181: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest - 105, // 182: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest - 97, // 183: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest - 106, // 184: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 60, // 185: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 61, // 186: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 62, // 187: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 64, // 188: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 65, // 189: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest - 66, // 190: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest - 67, // 191: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest - 69, // 192: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest - 123, // 193: ateapi.Control.GetGlobalAccessPolicy:input_type -> ateapi.GetGlobalAccessPolicyRequest - 124, // 194: ateapi.Control.CreateGlobalAccessPolicy:input_type -> ateapi.CreateGlobalAccessPolicyRequest - 125, // 195: ateapi.Control.UpdateGlobalAccessPolicy:input_type -> ateapi.UpdateGlobalAccessPolicyRequest - 126, // 196: ateapi.Control.GetAtespaceAccessPolicy:input_type -> ateapi.GetAtespaceAccessPolicyRequest - 127, // 197: ateapi.Control.CreateAtespaceAccessPolicy:input_type -> ateapi.CreateAtespaceAccessPolicyRequest - 128, // 198: ateapi.Control.UpdateAtespaceAccessPolicy:input_type -> ateapi.UpdateAtespaceAccessPolicyRequest - 129, // 199: ateapi.Control.DeleteAtespaceAccessPolicy:input_type -> ateapi.DeleteAtespaceAccessPolicyRequest - 115, // 200: ateapi.WorkerService.RegisterWorker:input_type -> ateapi.RegisterWorkerRequest - 117, // 201: ateapi.WorkerService.MintAteomActorCertificate:input_type -> ateapi.MintAteomActorCertificateRequest - 119, // 202: ateapi.WorkerService.RequestActorSuspend:input_type -> ateapi.RequestActorSuspendRequest - 18, // 203: ateapi.Control.GetActor:output_type -> ateapi.Actor - 18, // 204: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 18, // 205: ateapi.Control.UpdateActor:output_type -> ateapi.Actor - 74, // 206: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 76, // 207: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 78, // 208: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 80, // 209: ateapi.Control.RevertActor:output_type -> ateapi.RevertActorResponse - 18, // 210: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 19, // 211: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy - 19, // 212: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 19, // 213: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 19, // 214: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy - 88, // 215: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse - 90, // 216: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse - 33, // 217: ateapi.Control.CreateTag:output_type -> ateapi.Tag - 33, // 218: ateapi.Control.GetTag:output_type -> ateapi.Tag - 92, // 219: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse - 33, // 220: ateapi.Control.UpdateTag:output_type -> ateapi.Tag - 33, // 221: ateapi.Control.DeleteTag:output_type -> ateapi.Tag - 100, // 222: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 108, // 223: ateapi.Control.GetWorker:output_type -> ateapi.Worker - 108, // 224: ateapi.Control.CreateWorker:output_type -> ateapi.Worker - 108, // 225: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker - 108, // 226: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker - 108, // 227: ateapi.Control.DrainWorker:output_type -> ateapi.Worker - 98, // 228: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse - 107, // 229: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 34, // 230: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 34, // 231: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 63, // 232: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 34, // 233: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 36, // 234: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate - 36, // 235: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate - 68, // 236: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse - 36, // 237: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate - 121, // 238: ateapi.Control.GetGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 121, // 239: ateapi.Control.CreateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 121, // 240: ateapi.Control.UpdateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 121, // 241: ateapi.Control.GetAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 121, // 242: ateapi.Control.CreateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 121, // 243: ateapi.Control.UpdateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 121, // 244: ateapi.Control.DeleteAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 116, // 245: ateapi.WorkerService.RegisterWorker:output_type -> ateapi.RegisterWorkerResponse - 118, // 246: ateapi.WorkerService.MintAteomActorCertificate:output_type -> ateapi.MintAteomActorCertificateResponse - 120, // 247: ateapi.WorkerService.RequestActorSuspend:output_type -> ateapi.RequestActorSuspendResponse - 203, // [203:248] is the sub-list for method output_type - 158, // [158:203] is the sub-list for method input_type - 158, // [158:158] is the sub-list for extension type_name - 158, // [158:158] is the sub-list for extension extendee - 0, // [0:158] is the sub-list for field type_name + 1, // 0: ateapi.SnapshotStorage.status:type_name -> ateapi.SnapshotStorageStatus + 10, // 1: ateapi.SnapshotStorage.object:type_name -> ateapi.ObjectSnapshot + 0, // 2: ateapi.Snapshot.owner:type_name -> ateapi.SnapshotOwner + 2, // 3: ateapi.Snapshot.fidelity:type_name -> ateapi.SnapshotFidelity + 11, // 4: ateapi.Snapshot.durable_snapshot:type_name -> ateapi.SnapshotStorage + 128, // 5: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry + 131, // 6: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 131, // 7: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 9, // 8: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status + 129, // 9: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry + 14, // 10: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata + 33, // 11: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef + 13, // 12: ateapi.Actor.worker_selector:type_name -> ateapi.Selector + 33, // 13: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef + 27, // 14: ateapi.Actor.status:type_name -> ateapi.ActorStatus + 14, // 15: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata + 18, // 16: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule + 19, // 17: ateapi.EgressRule.http:type_name -> ateapi.HTTPRule + 20, // 18: ateapi.EgressRule.https:type_name -> ateapi.HTTPSRule + 21, // 19: ateapi.EgressRule.tls_passthrough:type_name -> ateapi.TLSPassthroughRule + 22, // 20: ateapi.HTTPRule.ports:type_name -> ateapi.Ports + 24, // 21: ateapi.HTTPRule.effects:type_name -> ateapi.HttpRuleEffects + 22, // 22: ateapi.HTTPSRule.ports:type_name -> ateapi.Ports + 24, // 23: ateapi.HTTPSRule.effects:type_name -> ateapi.HttpRuleEffects + 22, // 24: ateapi.TLSPassthroughRule.ports:type_name -> ateapi.Ports + 23, // 25: ateapi.Ports.all:type_name -> ateapi.AllPorts + 25, // 26: ateapi.HttpRuleEffects.replace_headers:type_name -> ateapi.CredentialHeader + 26, // 27: ateapi.CredentialHeader.actor_jwt:type_name -> ateapi.ActorJWTSource + 4, // 28: ateapi.ActorStatus.state:type_name -> ateapi.ActorState + 29, // 29: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment + 15, // 30: ateapi.ActorStatus.external_volumes:type_name -> ateapi.ExternalVolume + 28, // 31: ateapi.ActorStatus.crash:type_name -> ateapi.ActorCrash + 12, // 32: ateapi.ActorStatus.snapshots:type_name -> ateapi.Snapshot + 131, // 33: ateapi.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp + 33, // 34: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef + 12, // 35: ateapi.TagStatus.snapshot:type_name -> ateapi.Snapshot + 14, // 36: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata + 30, // 37: ateapi.Tag.status:type_name -> ateapi.TagStatus + 3, // 38: ateapi.Tag.scope:type_name -> ateapi.TagScope + 33, // 39: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef + 14, // 40: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata + 14, // 41: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata + 13, // 42: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector + 42, // 43: ateapi.ActorTemplate.containers:type_name -> ateapi.Container + 48, // 44: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume + 40, // 45: ateapi.ActorTemplate.snapshot_config:type_name -> ateapi.SnapshotConfig + 39, // 46: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig + 35, // 47: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources + 38, // 48: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus + 36, // 49: ateapi.Resources.limits:type_name -> ateapi.Limits + 33, // 50: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef + 131, // 51: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp + 37, // 52: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus + 5, // 53: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass + 2, // 54: ateapi.SnapshotConfig.preferred_fidelity:type_name -> ateapi.SnapshotFidelity + 41, // 55: ateapi.SnapshotConfig.golden_snapshot_config:type_name -> ateapi.GoldenSnapshotConfig + 6, // 56: ateapi.GoldenSnapshotConfig.mode:type_name -> ateapi.GoldenSnapshotMode + 45, // 57: ateapi.Container.env:type_name -> ateapi.EnvVar + 46, // 58: ateapi.Container.wakeup_probe:type_name -> ateapi.ContainerWakeupProbe + 57, // 59: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount + 43, // 60: ateapi.Container.security_context:type_name -> ateapi.SecurityContext + 35, // 61: ateapi.Container.resources:type_name -> ateapi.Resources + 44, // 62: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities + 47, // 63: ateapi.ContainerWakeupProbe.http_get:type_name -> ateapi.HTTPGetAction + 50, // 64: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource + 51, // 65: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate + 52, // 66: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource + 49, // 67: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource + 53, // 68: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource + 54, // 69: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource + 56, // 70: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource + 55, // 71: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem + 7, // 72: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField + 32, // 73: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace + 33, // 74: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 32, // 75: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace + 33, // 76: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 94, // 77: ateapi.DeleteAtespaceRequest.options:type_name -> ateapi.DeleteOptions + 34, // 78: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate + 33, // 79: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 34, // 80: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate + 33, // 81: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 94, // 82: ateapi.DeleteActorTemplateRequest.options:type_name -> ateapi.DeleteOptions + 33, // 83: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef + 16, // 84: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor + 16, // 85: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor + 33, // 86: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef + 16, // 87: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor + 33, // 88: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef + 16, // 89: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor + 33, // 90: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef + 16, // 91: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor + 33, // 92: ateapi.RevertActorRequest.actor:type_name -> ateapi.ObjectRef + 16, // 93: ateapi.RevertActorResponse.actor:type_name -> ateapi.Actor + 33, // 94: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef + 94, // 95: ateapi.DeleteActorRequest.options:type_name -> ateapi.DeleteOptions + 33, // 96: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 33, // 97: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 17, // 98: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 33, // 99: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 17, // 100: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 33, // 101: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 94, // 102: ateapi.DeleteActorEgressPolicyRequest.options:type_name -> ateapi.DeleteOptions + 33, // 103: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef + 33, // 104: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef + 131, // 105: ateapi.MintActorJWTResponse.expires_at:type_name -> google.protobuf.Timestamp + 33, // 106: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 31, // 107: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag + 31, // 108: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag + 31, // 109: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag + 33, // 110: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef + 94, // 111: ateapi.DeleteTagRequest.options:type_name -> ateapi.DeleteOptions + 33, // 112: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef + 112, // 113: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment + 106, // 114: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 33, // 115: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef + 106, // 116: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker + 106, // 117: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker + 33, // 118: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef + 94, // 119: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions + 33, // 120: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef + 16, // 121: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 14, // 122: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata + 130, // 123: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 107, // 124: ateapi.Worker.status:type_name -> ateapi.WorkerStatus + 8, // 125: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState + 111, // 126: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources + 111, // 127: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources + 108, // 128: ateapi.WorkerStatus.default_runtime:type_name -> ateapi.SandboxRuntime + 108, // 129: ateapi.WorkerStatus.restorable_runtimes:type_name -> ateapi.SandboxRuntime + 109, // 130: ateapi.SandboxRuntime.version:type_name -> ateapi.VersionedSandboxCompat + 110, // 131: ateapi.VersionedSandboxCompat.attributes:type_name -> ateapi.AttributeEntry + 35, // 132: ateapi.WorkerResources.resources:type_name -> ateapi.Resources + 14, // 133: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata + 33, // 134: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef + 35, // 135: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources + 33, // 136: ateapi.RegisterWorkerRequest.worker:type_name -> ateapi.ObjectRef + 111, // 137: ateapi.RegisterWorkerRequest.capacity:type_name -> ateapi.WorkerResources + 108, // 138: ateapi.RegisterWorkerRequest.default_runtime:type_name -> ateapi.SandboxRuntime + 108, // 139: ateapi.RegisterWorkerRequest.restorable_runtimes:type_name -> ateapi.SandboxRuntime + 106, // 140: ateapi.RegisterWorkerResponse.worker:type_name -> ateapi.Worker + 33, // 141: ateapi.MintAteomActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 33, // 142: ateapi.RequestActorSuspendRequest.worker:type_name -> ateapi.ObjectRef + 33, // 143: ateapi.RequestActorSuspendRequest.actor:type_name -> ateapi.ObjectRef + 16, // 144: ateapi.RequestActorSuspendResponse.actor:type_name -> ateapi.Actor + 14, // 145: ateapi.AccessPolicy.metadata:type_name -> ateapi.ResourceMetadata + 120, // 146: ateapi.AccessPolicy.bindings:type_name -> ateapi.Binding + 119, // 147: ateapi.CreateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 119, // 148: ateapi.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 33, // 149: ateapi.GetAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 33, // 150: ateapi.CreateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 119, // 151: ateapi.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 33, // 152: ateapi.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 119, // 153: ateapi.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 33, // 154: ateapi.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 94, // 155: ateapi.DeleteAtespaceAccessPolicyRequest.options:type_name -> ateapi.DeleteOptions + 68, // 156: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 69, // 157: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 70, // 158: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 71, // 159: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 73, // 160: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 75, // 161: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 77, // 162: ateapi.Control.RevertActor:input_type -> ateapi.RevertActorRequest + 79, // 163: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 80, // 164: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest + 81, // 165: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest + 82, // 166: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest + 83, // 167: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest + 85, // 168: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest + 87, // 169: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest + 91, // 170: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest + 84, // 171: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest + 89, // 172: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest + 92, // 173: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest + 93, // 174: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest + 97, // 175: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 99, // 176: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest + 100, // 177: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest + 101, // 178: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest + 102, // 179: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest + 103, // 180: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest + 95, // 181: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest + 104, // 182: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 58, // 183: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 59, // 184: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 60, // 185: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 62, // 186: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 63, // 187: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest + 64, // 188: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest + 65, // 189: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest + 67, // 190: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest + 121, // 191: ateapi.Control.GetGlobalAccessPolicy:input_type -> ateapi.GetGlobalAccessPolicyRequest + 122, // 192: ateapi.Control.CreateGlobalAccessPolicy:input_type -> ateapi.CreateGlobalAccessPolicyRequest + 123, // 193: ateapi.Control.UpdateGlobalAccessPolicy:input_type -> ateapi.UpdateGlobalAccessPolicyRequest + 124, // 194: ateapi.Control.GetAtespaceAccessPolicy:input_type -> ateapi.GetAtespaceAccessPolicyRequest + 125, // 195: ateapi.Control.CreateAtespaceAccessPolicy:input_type -> ateapi.CreateAtespaceAccessPolicyRequest + 126, // 196: ateapi.Control.UpdateAtespaceAccessPolicy:input_type -> ateapi.UpdateAtespaceAccessPolicyRequest + 127, // 197: ateapi.Control.DeleteAtespaceAccessPolicy:input_type -> ateapi.DeleteAtespaceAccessPolicyRequest + 113, // 198: ateapi.WorkerService.RegisterWorker:input_type -> ateapi.RegisterWorkerRequest + 115, // 199: ateapi.WorkerService.MintAteomActorCertificate:input_type -> ateapi.MintAteomActorCertificateRequest + 117, // 200: ateapi.WorkerService.RequestActorSuspend:input_type -> ateapi.RequestActorSuspendRequest + 16, // 201: ateapi.Control.GetActor:output_type -> ateapi.Actor + 16, // 202: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 16, // 203: ateapi.Control.UpdateActor:output_type -> ateapi.Actor + 72, // 204: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 74, // 205: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 76, // 206: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 78, // 207: ateapi.Control.RevertActor:output_type -> ateapi.RevertActorResponse + 16, // 208: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 17, // 209: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy + 17, // 210: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 17, // 211: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 17, // 212: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy + 86, // 213: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse + 88, // 214: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse + 31, // 215: ateapi.Control.CreateTag:output_type -> ateapi.Tag + 31, // 216: ateapi.Control.GetTag:output_type -> ateapi.Tag + 90, // 217: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse + 31, // 218: ateapi.Control.UpdateTag:output_type -> ateapi.Tag + 31, // 219: ateapi.Control.DeleteTag:output_type -> ateapi.Tag + 98, // 220: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 106, // 221: ateapi.Control.GetWorker:output_type -> ateapi.Worker + 106, // 222: ateapi.Control.CreateWorker:output_type -> ateapi.Worker + 106, // 223: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker + 106, // 224: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker + 106, // 225: ateapi.Control.DrainWorker:output_type -> ateapi.Worker + 96, // 226: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse + 105, // 227: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 32, // 228: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 32, // 229: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 61, // 230: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 32, // 231: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 34, // 232: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate + 34, // 233: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate + 66, // 234: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse + 34, // 235: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate + 119, // 236: ateapi.Control.GetGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 119, // 237: ateapi.Control.CreateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 119, // 238: ateapi.Control.UpdateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 119, // 239: ateapi.Control.GetAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 119, // 240: ateapi.Control.CreateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 119, // 241: ateapi.Control.UpdateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 119, // 242: ateapi.Control.DeleteAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 114, // 243: ateapi.WorkerService.RegisterWorker:output_type -> ateapi.RegisterWorkerResponse + 116, // 244: ateapi.WorkerService.MintAteomActorCertificate:output_type -> ateapi.MintAteomActorCertificateResponse + 118, // 245: ateapi.WorkerService.RequestActorSuspend:output_type -> ateapi.RequestActorSuspendResponse + 201, // [201:246] is the sub-list for method output_type + 156, // [156:201] is the sub-list for method input_type + 156, // [156:156] is the sub-list for extension type_name + 156, // [156:156] is the sub-list for extension extendee + 0, // [0:156] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } @@ -9560,8 +9441,8 @@ func file_ateapi_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ateapi_proto_rawDesc), len(file_ateapi_proto_rawDesc)), - NumEnums: 11, - NumMessages: 122, + NumEnums: 10, + NumMessages: 121, NumExtensions: 0, NumServices: 2, }, diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index 3bba6da970..232bd44dcd 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -180,18 +180,6 @@ message ObjectSnapshot { string snapshot_uri = 1; } -// LocalSnapshot records information about a node-local snapshot. -message LocalSnapshot { - // The name of the local checkpoint on the node. Checkpoint names are - // server-generated UUIDs, but any resource name is valid here. - // - // TODO: consider removing this since it's the same as the snapshot_id - // - // +k8s:required - // +k8s:format=k8s-short-name - string snapshot_name = 1; -} - // SnapshotOwner indicates the owner of a snapshot. enum SnapshotOwner { SNAPSHOT_OWNER_UNSPECIFIED = 0; @@ -202,99 +190,83 @@ enum SnapshotOwner { // Keep this in sync with Snapshot.owner's maximum. } -// SnapshotDurability indicates the durability level of the snapshot. -// TODO: potentially rename this to survivability level. Is it at the actor or snapshot level? -enum SnapshotDurability { - SNAPSHOT_DURABILITY_UNSPECIFIED = 0; - // Node-local checkpoint on the worker node - // Survives node reboot. - SNAPSHOT_DURABILITY_LOCAL = 1; - // Durable snapshot in external storage. - // Survives node crash and deletion. - SNAPSHOT_DURABILITY_DURABLE = 2; - // Keep this in sync with SnapshotStorage.durability's maximum. -} - -// SnapshotStorageStatus indicates the lifecycle stage of a snapshot storage entry. +// SnapshotStorageStatus indicates the lifecycle stage of a durable snapshot. enum SnapshotStorageStatus { SNAPSHOT_STORAGE_STATUS_UNSPECIFIED = 0; - // Snapshot creation or promotion is in progress. - SNAPSHOT_STORAGE_STATUS_IN_PROGRESS = 1; - // Snapshot is complete and ready for use. + // Snapshot creation in durable storage is in progress. + SNAPSHOT_STORAGE_STATUS_PENDING = 1; + // Snapshot is complete and ready for use in durable storage. SNAPSHOT_STORAGE_STATUS_COMPLETED = 2; // Keep this in sync with SnapshotStorage.status's maximum. } -// SnapshotStorage records the physical storage representation of a Snapshot. +// SnapshotStorage records the durable storage representation of a Snapshot. message SnapshotStorage { - // durability is the durability tier of this storage entry. Only - // SNAPSHOT_DURABILITY_LOCAL and SNAPSHOT_DURABILITY_DURABLE are allowed. - // - // +k8s:required - // +k8s:minimum=1 - // +k8s:maximum=2 # keep this in sync with the SnapshotDurability enum - SnapshotDurability durability = 1; - - // status is the lifecycle status of this storage representation. + // status is the lifecycle status of the durable snapshot. // // +k8s:required // +k8s:minimum=1 // +k8s:maximum=2 # keep this in sync with the SnapshotStorageStatus enum - SnapshotStorageStatus status = 2; - - // fidelity is what the snapshot holds. - // - // +k8s:required - // +k8s:minimum=1 - // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum - SnapshotFidelity fidelity = 3; - - // Exactly one of local or object must be set. - // - // local snapshot is stored on the node where the actor was suspended. - // - // +k8s:optional - // +k8s:unionMember - LocalSnapshot local = 4; + SnapshotStorageStatus status = 1; // object snapshot is stored in object storage. // - // +k8s:optional - // +k8s:unionMember - ObjectSnapshot object = 5; + // +k8s:required + ObjectSnapshot object = 2; } // Snapshot represents one generation of an Actor's or Tag's snapshot state. message Snapshot { + // uuid is the server-generated unique identifier for this snapshot, passed to + // atelet to identify the local checkpoint on disk and used as the snapshot + // name in object storage. + // + // +k8s:required + // +k8s:format=k8s-short-name + string uuid = 1; + // generation is the monotonically increasing generation number of this // snapshot, starting at 1. // // +k8s:required // +k8s:minimum=1 - int32 generation = 1; + int32 generation = 2; // owner identifies the origin and ownership of this snapshot. // // +k8s:required // +k8s:minimum=1 // +k8s:maximum=2 # keep this in sync with the SnapshotOwner enum - SnapshotOwner owner = 2; + SnapshotOwner owner = 3; + + // fidelity is what this snapshot holds. + // + // +k8s:required + // +k8s:minimum=1 + // +k8s:maximum=3 # keep this in sync with the SnapshotFidelity enum + SnapshotFidelity fidelity = 4; // UID of the ActorTemplate whose sandbox this snapshot's guest state was // captured from. // // +k8s:optional // +k8s:format=k8s-uuid - string actor_template_uid = 3; + string actor_template_uid = 5; - // storage lists the storage representations of this snapshot by durability. - // Only SNAPSHOT_DURABILITY_LOCAL and SNAPSHOT_DURABILITY_DURABLE are allowed. + // locality identifies the node holding this snapshot's files (currently the + // Kubernetes node name). Set when a local pause checkpoint completes, and + // cleared once the files are deleted from that node; a resume does not + // delete them. // - // +k8s:required - // +k8s:maxItems=2 - // +k8s:listType=map - // +k8s:listMapKey=durability - repeated SnapshotStorage storage = 4; + // +k8s:optional + // +k8s:format=k8s-long-name + string locality = 6; + + // durable_snapshot holds the durable storage representation of this snapshot. + // Unset when the snapshot only exists locally. + // + // +k8s:optional + SnapshotStorage durable_snapshot = 7; } // SnapshotFidelity orders the state layers a snapshot can hold, lowest to @@ -931,12 +903,15 @@ message WorkerAssignment { // The tag is fixed to one external snapshot once its creation completes, so // the rest is immutable provenance. message TagStatus { - // snapshot is the external snapshot this tag points at. Its storage entry is - // IN_PROGRESS while the tag is being created and transitions to COMPLETED - // once the copy finishes: a tag never moves between snapshots and is usable - // only once COMPLETED. + // snapshot is the external snapshot this tag points at: the tag's own copy, + // stored under a prefix keyed on the tag's UID. It is recorded with its + // durable_snapshot PENDING right after the tag is reserved, before anything + // is copied, and transitions to COMPLETED once the copy finishes: a tag never + // moves between snapshots and is usable only once COMPLETED. Unset until it + // is recorded; a tag whose creation failed first never gets one. // - // +k8s:required + // +k8s:optional + // +k8s:subfield(durable_snapshot)=+k8s:required Snapshot snapshot = 1; // UID of the ActorTemplate the snapshot was taken under. Actors can only be