From dcce6d9dd7982e2f00bed164d151e1d9c6f7b6fa Mon Sep 17 00:00:00 2001 From: cweld510 Date: Tue, 17 Dec 2024 18:12:00 +0000 Subject: [PATCH 01/12] wip --- modal/sandbox.py | 12 ++++++++++++ modal_proto/api.proto | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/modal/sandbox.py b/modal/sandbox.py index 3e34352b8..b67c7b0f3 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -530,6 +530,18 @@ async def exec( resp = await retry_transient_errors(self._client.stub.ContainerExec, req) by_line = bufsize == 1 return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line) + + async def snapshot(self) -> str: + task_id = await self._get_task_id() + req = api_pb2.SandboxSnapshotRequest(task_id=task_id) + resp = await retry_transient_errors(self._client.stub.SandboxSnapshot, req) + return resp.snapshot_id + + @staticmethod + async def from_snapshot(snapshot_id: str): + req = api_pb2.SandboxRestoreRequest(snapshot_id=snapshot_id) + # TODO (colin) how tf do loaders work? + return @overload async def open( diff --git a/modal_proto/api.proto b/modal_proto/api.proto index b15768803..e53a6e43e 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -863,6 +863,24 @@ message ContainerFilesystemExecResponse { optional string file_descriptor = 2; } +message SandboxSnapshotRequest { + string sandbox_id = 1; +} + +message SandboxSnapshotResponse { + string snapshot_id = 1; + GenericResult result = 2; +} + +message SandboxRestoreRequest { + string snapshot_id = 1; +} + +// TODO (colin) this should return a sandbox +message SandboxRestoreResponse { + +} + message ContainerHeartbeatRequest { bool canceled_inputs_return_outputs = 4; // Bad client version. bool canceled_inputs_return_outputs_v2 = 5; @@ -2821,6 +2839,8 @@ service ModalClient { rpc SandboxTagsSet(SandboxTagsSetRequest) returns (google.protobuf.Empty); rpc SandboxTerminate(SandboxTerminateRequest) returns (SandboxTerminateResponse); rpc SandboxWait(SandboxWaitRequest) returns (SandboxWaitResponse); + rpc SandboxSnapshot(SandboxSnapshotRequest) returns (SandboxSnapshotResponse); + rpc SandboxRestore(SandboxRestoreRequest) returns (SandboxRestoreResponse); // TODO (colin) what is the overlap between this and SandboxCreate? // Secrets rpc SecretDelete(SecretDeleteRequest) returns (google.protobuf.Empty); From 340215c4abc95d46b323e540c4b971e31d750f11 Mon Sep 17 00:00:00 2001 From: cweld510 Date: Wed, 18 Dec 2024 00:23:06 +0000 Subject: [PATCH 02/12] wip --- modal/sandbox.py | 36 +++++++++++++++++++++++++++++++++--- modal_proto/api.proto | 16 +++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/modal/sandbox.py b/modal/sandbox.py index b67c7b0f3..5fd37b658 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -4,6 +4,8 @@ from collections.abc import AsyncGenerator, Sequence from typing import TYPE_CHECKING, Literal, Optional, Union, overload +from modal.app import _App + if TYPE_CHECKING: import _typeshed @@ -538,10 +540,38 @@ async def snapshot(self) -> str: return resp.snapshot_id @staticmethod - async def from_snapshot(snapshot_id: str): + async def from_snapshot(snapshot_id: str, app: Optional["modal.app._App"] = None): + app_client: Optional[_Client] = None + + if app is not None: + if app.app_id is None: + raise ValueError( + "App has not been initialized yet. To create an App lazily, use `App.lookup`: \n" + "app = modal.App.lookup('my-app', create_if_missing=True)\n" + "modal.Sandbox.create('echo', 'hi', app=app)\n" + "In order to initialize an existing `App` object, refer to our docs: https://modal.com/docs/guide/apps" + ) + + app_client = app._client + elif _App._container_app is not None: + app_client = _App._container_app.client + else: + deprecation_error( + (2024, 9, 14), + "Creating a `Sandbox` without an `App` is deprecated.\n\n" + "You may pass in an `App` object, or reference one by name with `App.lookup`:\n\n" + "```\n" + "app = modal.App.lookup('sandbox-app', create_if_missing=True)\n" + f"sb = modal.Sandbox.from_snapshot({snapshot_id}, app=app)\n" + "```", + ) + + client = client or app_client or await _Client.from_env() + req = api_pb2.SandboxRestoreRequest(snapshot_id=snapshot_id) - # TODO (colin) how tf do loaders work? - return + resp: api_pb2.SandboxRestoreResponse = retry_transient_errors(client.stub.SandboxWait, req) + sandbox = await _Sandbox.from_id(resp.sandbox_id, client) + return sandbox @overload async def open( diff --git a/modal_proto/api.proto b/modal_proto/api.proto index e53a6e43e..7ca5c6e23 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -873,12 +873,13 @@ message SandboxSnapshotResponse { } message SandboxRestoreRequest { - string snapshot_id = 1; + string app_id = 1 [ (modal.options.audit_target_attr) = true ]; + string snapshot_id = 2; + string environment_name = 3; } -// TODO (colin) this should return a sandbox message SandboxRestoreResponse { - + string sandbox_id = 1; } message ContainerHeartbeatRequest { @@ -2106,6 +2107,13 @@ message S3Mount { bool read_only = 4; } +message SandboxSnapshotInfo { + string checksum = 1; + string snapshot_id = 2; // TODO (colin) i don't think this field needs to exist + string runtime_version = 3; + string original_task_id = 4; +} + message Sandbox { repeated string entrypoint_args = 1; repeated string mount_ids = 2; @@ -2149,6 +2157,8 @@ message Sandbox { NetworkAccess network_access = 22; optional string proxy_id = 23; + + optional SandboxSnapshotInfo snapshot = 24; } message SandboxCreateRequest { From 0c259cb249b7a3a71fcedbd418bdc5363c27ce46 Mon Sep 17 00:00:00 2001 From: cweld510 Date: Wed, 18 Dec 2024 00:40:42 +0000 Subject: [PATCH 03/12] wip --- modal/sandbox.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modal/sandbox.py b/modal/sandbox.py index 5fd37b658..6c1175494 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -534,8 +534,7 @@ async def exec( return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line) async def snapshot(self) -> str: - task_id = await self._get_task_id() - req = api_pb2.SandboxSnapshotRequest(task_id=task_id) + req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id) resp = await retry_transient_errors(self._client.stub.SandboxSnapshot, req) return resp.snapshot_id From 7e2f58c6908b556a00078287944e0f15c07b9201 Mon Sep 17 00:00:00 2001 From: cweld510 Date: Thu, 19 Dec 2024 01:43:50 +0000 Subject: [PATCH 04/12] wip --- modal/sandbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modal/sandbox.py b/modal/sandbox.py index 6c1175494..1db09b3ed 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -565,10 +565,10 @@ async def from_snapshot(snapshot_id: str, app: Optional["modal.app._App"] = None "```", ) - client = client or app_client or await _Client.from_env() + client = app_client or await _Client.from_env() req = api_pb2.SandboxRestoreRequest(snapshot_id=snapshot_id) - resp: api_pb2.SandboxRestoreResponse = retry_transient_errors(client.stub.SandboxWait, req) + resp: api_pb2.SandboxRestoreResponse = await retry_transient_errors(client.stub.SandboxRestore, req) sandbox = await _Sandbox.from_id(resp.sandbox_id, client) return sandbox From 8f8f71b64bb7f4d462be3c95f1e54ed64fc1c92d Mon Sep 17 00:00:00 2001 From: cweld510 Date: Sat, 21 Dec 2024 01:04:50 +0000 Subject: [PATCH 05/12] wip --- modal_proto/api.proto | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 7ca5c6e23..af23102fa 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -2107,6 +2107,7 @@ message S3Mount { bool read_only = 4; } +// TODO (colin) this can move to the backend message SandboxSnapshotInfo { string checksum = 1; string snapshot_id = 2; // TODO (colin) i don't think this field needs to exist @@ -2157,8 +2158,6 @@ message Sandbox { NetworkAccess network_access = 22; optional string proxy_id = 23; - - optional SandboxSnapshotInfo snapshot = 24; } message SandboxCreateRequest { From 2ee3c7cece73c3911a60a4bfecfcf405744226b4 Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Tue, 14 Jan 2025 18:28:32 +0000 Subject: [PATCH 06/12] wip --- modal/sandbox.py | 15 ++++++++++++++- modal_proto/api.proto | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/modal/sandbox.py b/modal/sandbox.py index 819483c6a..fa901f423 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -60,6 +60,7 @@ class _Sandbox(_Object, type_prefix="sb"): _stdin: _StreamWriter _task_id: Optional[str] = None _tunnels: Optional[dict[int, Tunnel]] = None + _enable_memory_snapshot: bool = False @staticmethod def _new( @@ -83,6 +84,7 @@ def _new( unencrypted_ports: Sequence[int] = [], proxy: Optional[_Proxy] = None, _experimental_scheduler_placement: Optional[SchedulerPlacement] = None, + enable_memory_snapshot: bool = False, ) -> "_Sandbox": """mdmd:hidden""" @@ -178,6 +180,7 @@ async def _load(self: _Sandbox, resolver: Resolver, _existing_object_id: Optiona open_ports=api_pb2.PortSpecs(ports=open_ports), network_access=network_access, proxy_id=(proxy.object_id if proxy else None), + enable_memory_snapshot=enable_memory_snapshot, ) # Note - `resolver.app_id` will be `None` for app-less sandboxes @@ -225,6 +228,8 @@ async def create( unencrypted_ports: Sequence[int] = [], # Reference to a Modal Proxy to use in front of this Sandbox. proxy: Optional[_Proxy] = None, + # Enable memory snapshots. + enable_memory_snapshot: bool = False, _experimental_scheduler_placement: Optional[ SchedulerPlacement ] = None, # Experimental controls over fine-grained scheduling (alpha). @@ -262,7 +267,9 @@ async def create( unencrypted_ports=unencrypted_ports, proxy=proxy, _experimental_scheduler_placement=_experimental_scheduler_placement, + enable_memory_snapshot=enable_memory_snapshot, ) + obj._enable_memory_snapshot = enable_memory_snapshot app_id: Optional[str] = None app_client: Optional[_Client] = None @@ -531,8 +538,13 @@ async def exec( resp = await retry_transient_errors(self._client.stub.ContainerExec, req) by_line = bufsize == 1 return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line) - + async def snapshot(self) -> str: + if not self._enable_memory_snapshot: + raise ValueError( + "Memory snapshots are not supported for this sandbox. To enable memory snapshots, " + "set `enable_memory_snapshot=True` when creating the sandbox." + ) req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id) resp = await retry_transient_errors(self._client.stub.SandboxSnapshot, req) return resp.snapshot_id @@ -541,6 +553,7 @@ async def snapshot(self) -> str: async def from_snapshot(snapshot_id: str, app: Optional["modal.app._App"] = None): app_client: Optional[_Client] = None + # TODO: I think we should do this without passing in an app. It should be more similar to `Sandbox.from_id`. if app is not None: if app.app_id is None: raise ValueError( diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 5b4c3b276..9b319a66a 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -2180,6 +2180,9 @@ message Sandbox { NetworkAccess network_access = 22; optional string proxy_id = 23; + + // Enable memory snapshots. + bool enable_memory_snapshot = 24; } message SandboxCreateRequest { From 81a082616494a3d01c824174beb53b6d205d81df Mon Sep 17 00:00:00 2001 From: Nathan Wang Date: Tue, 14 Jan 2025 18:43:24 -0500 Subject: [PATCH 07/12] throw error on snapshot failure; move sandboxsnapshotinfo to public_api.proto --- modal/sandbox.py | 2 ++ modal_proto/api.proto | 8 -------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/modal/sandbox.py b/modal/sandbox.py index fa901f423..a70a34141 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -547,6 +547,8 @@ async def snapshot(self) -> str: ) req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id) resp = await retry_transient_errors(self._client.stub.SandboxSnapshot, req) + if resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS: + raise ExecutionError(resp.result.exception) return resp.snapshot_id @staticmethod diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 9b319a66a..7d14e88b3 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -2129,14 +2129,6 @@ message S3Mount { bool read_only = 4; } -// TODO (colin) this can move to the backend -message SandboxSnapshotInfo { - string checksum = 1; - string snapshot_id = 2; // TODO (colin) i don't think this field needs to exist - string runtime_version = 3; - string original_task_id = 4; -} - message Sandbox { repeated string entrypoint_args = 1; repeated string mount_ids = 2; From 5c88bef48fecf56f10a9a180eef58ce2c04bcef2 Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Wed, 15 Jan 2025 03:36:41 +0000 Subject: [PATCH 08/12] add worker_fingerprint_version to sandbox definition --- modal_proto/api.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 7d14e88b3..04bd408ad 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -2175,6 +2175,9 @@ message Sandbox { // Enable memory snapshots. bool enable_memory_snapshot = 24; + + // Used to pin gVisor version for memory-snapshottable sandboxes. + optional uint32 worker_fingerprint_version = 25; } message SandboxCreateRequest { From 4b41d1e86c64c7ba77d22297c2a73e4f94676485 Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Wed, 15 Jan 2025 16:49:24 +0000 Subject: [PATCH 09/12] have snapshot_restore return result (may revert later) --- modal/sandbox.py | 2 ++ modal_proto/api.proto | 1 + 2 files changed, 3 insertions(+) diff --git a/modal/sandbox.py b/modal/sandbox.py index a70a34141..f3702861f 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -583,6 +583,8 @@ async def from_snapshot(snapshot_id: str, app: Optional["modal.app._App"] = None req = api_pb2.SandboxRestoreRequest(snapshot_id=snapshot_id) resp: api_pb2.SandboxRestoreResponse = await retry_transient_errors(client.stub.SandboxRestore, req) + if resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS: + raise ExecutionError(resp.result.exception) sandbox = await _Sandbox.from_id(resp.sandbox_id, client) return sandbox diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 04bd408ad..6bb56ccb6 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -890,6 +890,7 @@ message SandboxRestoreRequest { message SandboxRestoreResponse { string sandbox_id = 1; + GenericResult result = 2; } message ContainerHeartbeatRequest { From 6295add16950397eeb622db22612eb06e2f3a0f0 Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Wed, 15 Jan 2025 19:56:57 +0000 Subject: [PATCH 10/12] rename worker_fingerprint_version -> snapshot_version --- modal_proto/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 6bb56ccb6..ef1c866d9 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -2178,7 +2178,7 @@ message Sandbox { bool enable_memory_snapshot = 24; // Used to pin gVisor version for memory-snapshottable sandboxes. - optional uint32 worker_fingerprint_version = 25; + optional uint32 snapshot_version = 25; } message SandboxCreateRequest { From 7b68aa802497510b4aff7304e57b2a8e928b9d34 Mon Sep 17 00:00:00 2001 From: Nathan Wang Date: Thu, 16 Jan 2025 12:23:27 -0500 Subject: [PATCH 11/12] (wip) api changes --- modal_proto/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modal_proto/api.proto b/modal_proto/api.proto index ef1c866d9..3dfd67303 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -875,6 +875,7 @@ message ContainerFilesystemExecResponse { message SandboxSnapshotRequest { string sandbox_id = 1; + float timeout = 2; } message SandboxSnapshotResponse { @@ -890,7 +891,6 @@ message SandboxRestoreRequest { message SandboxRestoreResponse { string sandbox_id = 1; - GenericResult result = 2; } message ContainerHeartbeatRequest { From 29b00eb6fe34c5c08c0af61d81a0cd64f3ce3c0b Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Tue, 21 Jan 2025 16:11:56 +0000 Subject: [PATCH 12/12] (wip) api changes --- modal/sandbox.py | 61 +++++++++++++------------------------------ modal_proto/api.proto | 26 +----------------- 2 files changed, 19 insertions(+), 68 deletions(-) diff --git a/modal/sandbox.py b/modal/sandbox.py index 0229b8fda..e5f575673 100644 --- a/modal/sandbox.py +++ b/modal/sandbox.py @@ -4,8 +4,6 @@ from collections.abc import AsyncGenerator, Sequence from typing import TYPE_CHECKING, AsyncIterator, Literal, Optional, Union, overload -from modal.app import _App - if TYPE_CHECKING: import _typeshed @@ -25,6 +23,7 @@ from ._utils.deprecation import deprecation_error from ._utils.grpc_utils import retry_transient_errors from ._utils.mount_utils import validate_network_file_systems, validate_volumes +from .app import _App from .client import _Client from .config import config from .container_process import _ContainerProcess @@ -60,7 +59,7 @@ class _Sandbox(_Object, type_prefix="sb"): _stdin: _StreamWriter _task_id: Optional[str] = None _tunnels: Optional[dict[int, Tunnel]] = None - _enable_memory_snapshot: bool = False + _enable_snapshot: bool = False @staticmethod def _new( @@ -84,7 +83,7 @@ def _new( unencrypted_ports: Sequence[int] = [], proxy: Optional[_Proxy] = None, _experimental_scheduler_placement: Optional[SchedulerPlacement] = None, - enable_memory_snapshot: bool = False, + enable_snapshot: bool = False, ) -> "_Sandbox": """mdmd:hidden""" @@ -181,7 +180,7 @@ async def _load(self: _Sandbox, resolver: Resolver, _existing_object_id: Optiona open_ports=api_pb2.PortSpecs(ports=open_ports), network_access=network_access, proxy_id=(proxy.object_id if proxy else None), - enable_memory_snapshot=enable_memory_snapshot, + enable_snapshot=enable_snapshot, ) # Note - `resolver.app_id` will be `None` for app-less sandboxes @@ -230,14 +229,12 @@ async def create( # Reference to a Modal Proxy to use in front of this Sandbox. proxy: Optional[_Proxy] = None, # Enable memory snapshots. - enable_memory_snapshot: bool = False, + enable_snapshot: bool = False, _experimental_scheduler_placement: Optional[ SchedulerPlacement ] = None, # Experimental controls over fine-grained scheduling (alpha). client: Optional[_Client] = None, ) -> "_Sandbox": - from .app import _App - environment_name = _get_environment_name(environment_name) # If there are no entrypoint args, we'll sleep forever so that the sandbox will stay @@ -268,9 +265,9 @@ async def create( unencrypted_ports=unencrypted_ports, proxy=proxy, _experimental_scheduler_placement=_experimental_scheduler_placement, - enable_memory_snapshot=enable_memory_snapshot, + enable_snapshot=enable_snapshot, ) - obj._enable_memory_snapshot = enable_memory_snapshot + obj._enable_snapshot = enable_snapshot app_id: Optional[str] = None app_client: Optional[_Client] = None @@ -544,52 +541,30 @@ async def exec( return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line) async def snapshot(self) -> str: - if not self._enable_memory_snapshot: + if not self._enable_snapshot: raise ValueError( "Memory snapshots are not supported for this sandbox. To enable memory snapshots, " - "set `enable_memory_snapshot=True` when creating the sandbox." + "set `enable_snapshot=True` when creating the sandbox." ) req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id) resp = await retry_transient_errors(self._client.stub.SandboxSnapshot, req) + snapshot_id = resp.snapshot_id + wait_req = api_pb2.SandboxSnapshotWaitRequest(snapshot_id=resp.snapshot_id, timeout=55.0) + resp = await retry_transient_errors(self._client.stub.SandboxSnapshotWait, wait_req) if resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS: raise ExecutionError(resp.result.exception) - return resp.snapshot_id + return snapshot_id @staticmethod - async def from_snapshot(snapshot_id: str, app: Optional["modal.app._App"] = None): - app_client: Optional[_Client] = None - - # TODO: I think we should do this without passing in an app. It should be more similar to `Sandbox.from_id`. - if app is not None: - if app.app_id is None: - raise ValueError( - "App has not been initialized yet. To create an App lazily, use `App.lookup`: \n" - "app = modal.App.lookup('my-app', create_if_missing=True)\n" - "modal.Sandbox.create('echo', 'hi', app=app)\n" - "In order to initialize an existing `App` object, refer to our docs: https://modal.com/docs/guide/apps" - ) - - app_client = app._client - elif _App._container_app is not None: - app_client = _App._container_app.client - else: - deprecation_error( - (2024, 9, 14), - "Creating a `Sandbox` without an `App` is deprecated.\n\n" - "You may pass in an `App` object, or reference one by name with `App.lookup`:\n\n" - "```\n" - "app = modal.App.lookup('sandbox-app', create_if_missing=True)\n" - f"sb = modal.Sandbox.from_snapshot({snapshot_id}, app=app)\n" - "```", - ) - - client = app_client or await _Client.from_env() + async def from_snapshot(snapshot_id: str, client: Optional[_Client] = None): + client = client or await _Client.from_env() req = api_pb2.SandboxRestoreRequest(snapshot_id=snapshot_id) resp: api_pb2.SandboxRestoreResponse = await retry_transient_errors(client.stub.SandboxRestore, req) - if resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS: - raise ExecutionError(resp.result.exception) sandbox = await _Sandbox.from_id(resp.sandbox_id, client) + wait_req = api_pb2.SandboxWaitRequest(sandbox_id=resp.sandbox_id, timeout=0) + resp = await retry_transient_errors(client.stub.SandboxWait, wait_req) + print("from_snapshot resp", resp) return sandbox @overload diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 2c30fbc9e..38686a281 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -878,26 +878,6 @@ message ContainerFilesystemExecResponse { optional string file_descriptor = 2; } -message SandboxSnapshotRequest { - string sandbox_id = 1; - float timeout = 2; -} - -message SandboxSnapshotResponse { - string snapshot_id = 1; - GenericResult result = 2; -} - -message SandboxRestoreRequest { - string app_id = 1 [ (modal.options.audit_target_attr) = true ]; - string snapshot_id = 2; - string environment_name = 3; -} - -message SandboxRestoreResponse { - string sandbox_id = 1; -} - message ContainerHeartbeatRequest { bool canceled_inputs_return_outputs = 4; // Bad client version. bool canceled_inputs_return_outputs_v2 = 5; @@ -2338,9 +2318,7 @@ message SandboxListResponse { } message SandboxRestoreRequest { - string app_id = 1 [ (modal.options.audit_target_attr) = true ]; - string snapshot_id = 2; - string environment_name = 3; + string snapshot_id = 1; } message SandboxRestoreResponse { @@ -3008,8 +2986,6 @@ service ModalClient { rpc SandboxTagsSet(SandboxTagsSetRequest) returns (google.protobuf.Empty); rpc SandboxTerminate(SandboxTerminateRequest) returns (SandboxTerminateResponse); rpc SandboxWait(SandboxWaitRequest) returns (SandboxWaitResponse); - rpc SandboxSnapshot(SandboxSnapshotRequest) returns (SandboxSnapshotResponse); - rpc SandboxRestore(SandboxRestoreRequest) returns (SandboxRestoreResponse); // TODO (colin) what is the overlap between this and SandboxCreate? // Secrets rpc SecretDelete(SecretDeleteRequest) returns (google.protobuf.Empty);