From 828d169b82d18647b735e8ac5d2b1e79536ca52a Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 10 Jan 2025 15:27:13 -0500 Subject: [PATCH] refactor: consolidate runtime startup command into an util function (#6199) --- .../runtime/impl/docker/docker_runtime.py | 32 +++------- openhands/runtime/impl/modal/modal_runtime.py | 26 ++------ .../runtime/impl/remote/remote_runtime.py | 22 ++----- .../runtime/impl/runloop/runloop_runtime.py | 28 ++------- openhands/runtime/utils/command.py | 62 +++++++++++++------ 5 files changed, 66 insertions(+), 104 deletions(-) diff --git a/openhands/runtime/impl/docker/docker_runtime.py b/openhands/runtime/impl/docker/docker_runtime.py index 6eb51418b733..5111f0f36831 100644 --- a/openhands/runtime/impl/docker/docker_runtime.py +++ b/openhands/runtime/impl/docker/docker_runtime.py @@ -21,6 +21,7 @@ from openhands.runtime.impl.docker.containers import remove_all_containers from openhands.runtime.plugins import PluginRequirement from openhands.runtime.utils import find_available_tcp_port +from openhands.runtime.utils.command import get_action_execution_server_startup_command from openhands.runtime.utils.log_streamer import LogStreamer from openhands.runtime.utils.runtime_build import build_runtime_image from openhands.utils.async_utils import call_sync_from_async @@ -186,11 +187,7 @@ def _init_docker_client() -> docker.DockerClient: def _init_container(self): self.log('debug', 'Preparing to start container...') self.send_status_message('STATUS$PREPARING_CONTAINER') - plugin_arg = '' - if self.plugins is not None and len(self.plugins) > 0: - plugin_arg = ( - f'--plugins {" ".join([plugin.name for plugin in self.plugins])} ' - ) + self._host_port = self._find_available_port(EXECUTION_SERVER_PORT_RANGE) self._container_port = self._host_port self._vscode_port = self._find_available_port(VSCODE_PORT_RANGE) @@ -203,8 +200,6 @@ def _init_container(self): use_host_network = self.config.sandbox.use_host_network network_mode: str | None = 'host' if use_host_network else None - use_host_network = self.config.sandbox.use_host_network - # Initialize port mappings port_mapping: dict[str, list[dict[str, str]]] | None = None if not use_host_network: @@ -257,26 +252,17 @@ def _init_container(self): f'Sandbox workspace: {self.config.workspace_mount_path_in_sandbox}', ) - if self.config.sandbox.browsergym_eval_env is not None: - browsergym_arg = ( - f'--browsergym-eval-env {self.config.sandbox.browsergym_eval_env}' - ) - else: - browsergym_arg = '' + command = get_action_execution_server_startup_command( + server_port=self._container_port, + plugins=self.plugins, + app_config=self.config, + use_nice_for_root=False, + ) try: self.container = self.docker_client.containers.run( self.runtime_container_image, - command=( - f'/openhands/micromamba/bin/micromamba run -n openhands ' - f'poetry run ' - f'python -u -m openhands.runtime.action_execution_server {self._container_port} ' - f'--working-dir "{self.config.workspace_mount_path_in_sandbox}" ' - f'{plugin_arg}' - f'--username {"openhands" if self.config.run_as_openhands else "root"} ' - f'--user-id {self.config.sandbox.user_id} ' - f'{browsergym_arg}' - ), + command=command, network_mode=network_mode, ports=port_mapping, working_dir='/openhands/code/', # do not change this! diff --git a/openhands/runtime/impl/modal/modal_runtime.py b/openhands/runtime/impl/modal/modal_runtime.py index 473c4ae97b10..6c2be0739615 100644 --- a/openhands/runtime/impl/modal/modal_runtime.py +++ b/openhands/runtime/impl/modal/modal_runtime.py @@ -13,7 +13,7 @@ ActionExecutionClient, ) from openhands.runtime.plugins import PluginRequirement -from openhands.runtime.utils.command import get_remote_startup_command +from openhands.runtime.utils.command import get_action_execution_server_startup_command from openhands.runtime.utils.runtime_build import ( BuildFromImageType, prep_build_folder, @@ -203,11 +203,6 @@ def _init_sandbox( ): try: self.log('debug', 'Preparing to start container...') - plugin_args = [] - if plugins is not None and len(plugins) > 0: - plugin_args.append('--plugins') - plugin_args.extend([plugin.name for plugin in plugins]) - # Combine environment variables environment: dict[str, str | None] = { 'port': str(self.container_port), @@ -216,24 +211,13 @@ def _init_sandbox( if self.config.debug: environment['DEBUG'] = 'true' - browsergym_args = [] - if self.config.sandbox.browsergym_eval_env is not None: - browsergym_args = [ - '-browsergym-eval-env', - self.config.sandbox.browsergym_eval_env, - ] - env_secret = modal.Secret.from_dict(environment) self.log('debug', f'Sandbox workspace: {sandbox_workspace_dir}') - sandbox_start_cmd = get_remote_startup_command( - self.container_port, - sandbox_workspace_dir, - 'openhands' if self.config.run_as_openhands else 'root', - self.config.sandbox.user_id, - plugin_args, - browsergym_args, - is_root=not self.config.run_as_openhands, # is_root=True when running as root + sandbox_start_cmd = get_action_execution_server_startup_command( + server_port=self.container_port, + plugins=self.plugins, + app_config=self.config, ) self.log('debug', f'Starting container with command: {sandbox_start_cmd}') self.sandbox = modal.Sandbox.create( diff --git a/openhands/runtime/impl/remote/remote_runtime.py b/openhands/runtime/impl/remote/remote_runtime.py index 0e0b7adc79e6..ebc1a86b384b 100644 --- a/openhands/runtime/impl/remote/remote_runtime.py +++ b/openhands/runtime/impl/remote/remote_runtime.py @@ -19,7 +19,7 @@ ActionExecutionClient, ) from openhands.runtime.plugins import PluginRequirement -from openhands.runtime.utils.command import get_remote_startup_command +from openhands.runtime.utils.command import get_action_execution_server_startup_command from openhands.runtime.utils.request import send_request from openhands.runtime.utils.runtime_build import build_runtime_image from openhands.utils.async_utils import call_sync_from_async @@ -194,22 +194,10 @@ def _build_runtime(self): def _start_runtime(self): # Prepare the request body for the /start endpoint - plugin_args = [] - if self.plugins is not None and len(self.plugins) > 0: - plugin_args = ['--plugins'] + [plugin.name for plugin in self.plugins] - browsergym_args = [] - if self.config.sandbox.browsergym_eval_env is not None: - browsergym_args = [ - '--browsergym-eval-env' - ] + self.config.sandbox.browsergym_eval_env.split(' ') - command = get_remote_startup_command( - self.port, - self.config.workspace_mount_path_in_sandbox, - 'openhands' if self.config.run_as_openhands else 'root', - self.config.sandbox.user_id, - plugin_args, - browsergym_args, - is_root=not self.config.run_as_openhands, # is_root=True when running as root + command = get_action_execution_server_startup_command( + server_port=self.port, + plugins=self.plugins, + app_config=self.config, ) start_request = { 'image': self.container_image, diff --git a/openhands/runtime/impl/runloop/runloop_runtime.py b/openhands/runtime/impl/runloop/runloop_runtime.py index 93f019561ff0..51628f54056d 100644 --- a/openhands/runtime/impl/runloop/runloop_runtime.py +++ b/openhands/runtime/impl/runloop/runloop_runtime.py @@ -13,7 +13,7 @@ ActionExecutionClient, ) from openhands.runtime.plugins import PluginRequirement -from openhands.runtime.utils.command import get_remote_startup_command +from openhands.runtime.utils.command import get_action_execution_server_startup_command from openhands.utils.tenacity_stop import stop_if_should_exit CONTAINER_NAME_PREFIX = 'openhands-runtime-' @@ -78,28 +78,10 @@ def _wait_for_devbox(self, devbox: DevboxView) -> DevboxView: def _create_new_devbox(self) -> DevboxView: # Note: Runloop connect - sandbox_workspace_dir = self.config.workspace_mount_path_in_sandbox - plugin_args = [] - if self.plugins is not None and len(self.plugins) > 0: - plugin_args.append('--plugins') - plugin_args.extend([plugin.name for plugin in self.plugins]) - - browsergym_args = [] - if self.config.sandbox.browsergym_eval_env is not None: - browsergym_args = [ - '-browsergym-eval-env', - self.config.sandbox.browsergym_eval_env, - ] - - # Copied from EventstreamRuntime - start_command = get_remote_startup_command( - self._sandbox_port, - sandbox_workspace_dir, - 'openhands' if self.config.run_as_openhands else 'root', - self.config.sandbox.user_id, - plugin_args, - browsergym_args, - is_root=not self.config.run_as_openhands, # is_root=True when running as root + start_command = get_action_execution_server_startup_command( + server_port=self._sandbox_port, + plugins=self.plugins, + app_config=self.config, ) # Add some additional commands based on our image diff --git a/openhands/runtime/utils/command.py b/openhands/runtime/utils/command.py index 3a32d45fb7e1..76722daca476 100644 --- a/openhands/runtime/utils/command.py +++ b/openhands/runtime/utils/command.py @@ -1,35 +1,57 @@ -def get_remote_startup_command( - port: int, - sandbox_workspace_dir: str, - username: str, - user_id: int, - plugin_args: list[str], - browsergym_args: list[str], - is_root: bool = False, +from openhands.core.config import AppConfig +from openhands.runtime.plugins import PluginRequirement + +DEFAULT_PYTHON_PREFIX = [ + '/openhands/micromamba/bin/micromamba', + 'run', + '-n', + 'openhands', + 'poetry', + 'run', +] + + +def get_action_execution_server_startup_command( + server_port: int, + plugins: list[PluginRequirement], + app_config: AppConfig, + python_prefix: list[str] = DEFAULT_PYTHON_PREFIX, + use_nice_for_root: bool = True, ): + sandbox_config = app_config.sandbox + + # Plugin args + plugin_args = [] + if plugins is not None and len(plugins) > 0: + plugin_args = ['--plugins'] + [plugin.name for plugin in plugins] + + # Browsergym stuffs + browsergym_args = [] + if sandbox_config.browsergym_eval_env is not None: + browsergym_args = [ + '--browsergym-eval-env' + ] + sandbox_config.browsergym_eval_env.split(' ') + + is_root = not app_config.run_as_openhands + base_cmd = [ - '/openhands/micromamba/bin/micromamba', - 'run', - '-n', - 'openhands', - 'poetry', - 'run', + *python_prefix, 'python', '-u', '-m', 'openhands.runtime.action_execution_server', - str(port), + str(server_port), '--working-dir', - sandbox_workspace_dir, + app_config.workspace_mount_path_in_sandbox, *plugin_args, '--username', - username, + 'openhands' if app_config.run_as_openhands else 'root', '--user-id', - str(user_id), + str(sandbox_config.user_id), *browsergym_args, ] - if is_root: + if is_root and use_nice_for_root: # If running as root, set highest priority and lowest OOM score cmd_str = ' '.join(base_cmd) return [ @@ -41,5 +63,5 @@ def get_remote_startup_command( f'echo -1000 > /proc/self/oom_score_adj && exec {cmd_str}', ] else: - # If not root, run with normal priority + # If not root OR not using nice for root, run with normal priority return base_cmd