-
Notifications
You must be signed in to change notification settings - Fork 16
Basic Sandboxed Coder #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
6ed2394
hello coder
allenwang28 20847b9
docstrings
allenwang28 3677580
Merge remote-tracking branch 'upstream/main' into coder
allenwang28 239ce7d
undo vllm example
allenwang28 932908f
add coder test
allenwang28 b08da7f
Merge branch 'main' into coder
allenwang28 6188066
double stop doesn't work
allenwang28 03c97f7
address comments
allenwang28 3ccf3a1
reset -> recreate
allenwang28 2a3f817
remove line
allenwang28 132b233
final cleanups
allenwang28 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -199,3 +199,7 @@ assets/wheels/vllm*.whl | |
# DCP artifacts | ||
forge_dcp_tmp/ | ||
demo_top_down.md | ||
|
||
|
||
# enroot / sqsh | ||
*.sqsh |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# Copyright (c) Meta Platforms, Inc. and affiliates. | ||
# All rights reserved. | ||
# | ||
# This source code is licensed under the BSD-style license found in the | ||
# LICENSE file in the root directory of this source tree. | ||
|
||
import logging | ||
import os | ||
import subprocess | ||
import tempfile | ||
from pathlib import Path | ||
|
||
from monarch.actor import endpoint | ||
|
||
from forge.controller import ForgeActor | ||
|
||
logger = logging.getLogger(__name__) | ||
logger.setLevel(logging.DEBUG) | ||
|
||
|
||
class SandboxedCoder(ForgeActor): | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
"""A sandboxed code execution environment using enroot containers. | ||
SandboxedCoder provides a secure, isolated environment for executing Python code | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
using NVIDIA's enroot containerization technology. It automatically manages the | ||
entire container lifecycle including image import, container creation, and cleanup. | ||
The actor follows a three-stage workflow: | ||
1. Image Management: Automatically imports Docker images to enroot .sqsh format | ||
2. Container Lifecycle: Creates fresh container instances for isolated execution | ||
3. Code Execution: Safely runs Python code with proper error handling and output capture | ||
Dependencies: | ||
- enroot: NVIDIA's container runtime (must be installed on host) | ||
- Docker images: Accessible via docker:// URLs or local paths | ||
- Python 3.x: For the container environment | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
Args: | ||
docker_image: Docker image URL to import (e.g., "docker://python:3.10"). | ||
Can be any Docker Hub image or custom registry URL. | ||
sqsh_image_path: Local filesystem path where the enroot .sqsh image will be stored. | ||
If the file doesn't exist, it will be created via enroot import. | ||
container_name: Unique name for the enroot container instance. Used for | ||
container lifecycle management (create/remove operations). | ||
""" | ||
|
||
def __init__( | ||
self, | ||
docker_image: str = "docker://python:3.10", | ||
sqsh_image_path: str = "python-image.sqsh", | ||
container_name: str = "sandbox", | ||
): | ||
self.docker_image = docker_image | ||
self.sqsh_image_path = sqsh_image_path | ||
self.container_name = container_name | ||
self._initialized = False | ||
|
||
@endpoint | ||
async def setup(self): | ||
logging.debug("Setting up sandboxed actor") | ||
await self._ensure_image() | ||
self._reset() | ||
|
||
@endpoint | ||
async def reset(self): | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
self._reset() | ||
|
||
async def _ensure_image(self): | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
"""Ensure the enroot image exists, import it if necessary.""" | ||
if not os.path.exists(self.sqsh_image_path): | ||
logging.debug( | ||
f"Image {self.sqsh_image_path} not found, importing from {self.docker_image}" | ||
) | ||
result = subprocess.run( | ||
["enroot", "import", "-o", self.sqsh_image_path, self.docker_image], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
text=True, | ||
) | ||
if result.returncode != 0: | ||
raise RuntimeError(f"Failed to import image: {result.stderr}") | ||
logging.debug( | ||
f"Successfully imported {self.docker_image} to {self.sqsh_image_path}" | ||
) | ||
else: | ||
logging.info(f"Using existing image: {self.sqsh_image_path}") | ||
|
||
def _reset(self): | ||
"""(Re)create a clean container instance from the base image.""" | ||
allenwang28 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Remove any old container | ||
logging.debug(f"Removing container {self.container_name}") | ||
subprocess.run( | ||
["enroot", "remove", "-f", self.container_name], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
) | ||
# Create new container from image | ||
result = subprocess.run( | ||
["enroot", "create", "--name", self.container_name, self.sqsh_image_path], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
text=True, | ||
) | ||
logging.debug(f"Container creation result: {result}") | ||
if result.returncode != 0: | ||
raise RuntimeError(f"Failed to reset container: {result.stderr}") | ||
self._initialized = True | ||
logging.debug("Successfully initialized container") | ||
|
||
@endpoint | ||
async def execute(self, code: str) -> str: | ||
""" | ||
Execute Python code inside the container. | ||
:param code: Python source code string to execute. | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
:return: Captured stdout. | ||
""" | ||
logging.debug(f"Executing {code}") | ||
if not self._initialized: | ||
raise RuntimeError("Container not initialized. Call reset() first.") | ||
|
||
# Write code to a temporary file that we can mount | ||
with tempfile.TemporaryDirectory() as tmpdir: | ||
code_path = Path(tmpdir) / "script.py" | ||
code_path.write_text(code) | ||
|
||
# Run the code inside the container, mounting tmpdir | ||
allenwang28 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
cmd = [ | ||
"enroot", | ||
"start", | ||
"--mount", | ||
f"{tmpdir}:/work", | ||
self.container_name, | ||
"python3", | ||
"/work/script.py", | ||
] | ||
result = subprocess.run( | ||
cmd, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
text=True, | ||
) | ||
if result.returncode != 0: | ||
raise RuntimeError(f"Execution failed:\n{result.stderr}") | ||
allenwang28 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
return result.stdout |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.