Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
from nemo_deployments_plugin.entities import Container, DeploymentConfig
from nemo_deployments_plugin.secrets import SecretResolutionError, resolve_deployment_config_secrets
from nemo_deployments_plugin.types import DeploymentStatus, Endpoint
from nemo_platform.resources.entities import AsyncEntitiesResource
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.entities.client import AsyncEntitiesClient
from nemo_platform_plugin.entity_client import NemoEntitiesClient, NemoEntityNotFoundError

if TYPE_CHECKING:
Expand All @@ -70,7 +71,7 @@

_OPENSHELL_INSTALL_HINT = (
"The 'openshell' package is required for OpenShellDeploymentBackend. "
"Install it with: uv sync --package nemo-deployments-plugin --extra openshell"
'Install it with: uv pip install "openshell>=0.0.92" "grpcio>=1.78.0" "protobuf>=6.31.1"'
)

_SERVE_LOG = "/tmp/nemo-serve.log"
Expand Down Expand Up @@ -154,7 +155,7 @@ class OpenShellDeploymentBackend(DeploymentBackend):
def init(self) -> None:
_ensure_openshell()
self._executor_config = OpenShellExecutorConfig.model_validate(self._config)
self._entities = NemoEntitiesClient(AsyncEntitiesResource(self._sdk))
self._entities = NemoEntitiesClient(client_from_platform(self._sdk, AsyncEntitiesClient))
# Build the policy once (fail fast on a bad path/shape). The gateway default
# policy would not permit the agent's own exec paths, so we always apply one.
self._policy = self._build_executor_policy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def generate_sandbox_policy(*, filesystem: SandboxFilesystem, egress: PlatformEg

_OPENSHELL_INSTALL_HINT = (
"The 'openshell' package is required to build OpenShell sandbox policies. "
"Install it with: uv sync --package nemo-deployments-plugin --extra openshell"
'Install it with: uv pip install "openshell>=0.0.92" "grpcio>=1.78.0" "protobuf>=6.31.1"'
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def openshell_backend(
mock_sdk: MagicMock, mock_entities: AsyncMock, mock_stub: MagicMock
) -> Iterator[OpenShellDeploymentBackend]:
with (
patch("nemo_deployments_plugin.backends.openshell.backend.AsyncEntitiesResource"),
patch("nemo_deployments_plugin.backends.openshell.backend.client_from_platform"),
patch("nemo_deployments_plugin.backends.openshell.backend.NemoEntitiesClient", return_value=mock_entities),
patch("grpc.insecure_channel", return_value=MagicMock()),
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from unittest.mock import AsyncMock, MagicMock, patch

import grpc
import httpx
import pytest
from nemo_deployments_plugin.backends.labels import (
CONFIG_NAME_LABEL,
Expand All @@ -35,6 +36,8 @@
from nemo_deployments_plugin.constants import MANAGED_BY_LABEL
from nemo_deployments_plugin.entities import Container, ContainerPort, DeploymentConfig, EnvVar
from nemo_deployments_plugin.secrets import SecretResolutionError
from nemo_platform import AsyncNeMoPlatform
from nemo_platform_plugin.entity_client import NemoEntityNotFoundError

pytest.importorskip("openshell") # platform-restricted extra; skip where not installed (e.g. CI)

Expand Down Expand Up @@ -126,6 +129,34 @@ def test_registry_contains_openshell() -> None:
assert BACKEND_CLASSES["openshell"] is OpenShellDeploymentBackend


async def test_load_deployment_config_wraps_an_entities_client_that_accepts_query_params() -> None:
"""init() must adapt the SDK with client_from_platform(AsyncEntitiesClient), not wrap the
raw generated AsyncEntitiesResource (AIRCORE-977).

NemoEntitiesClient.get() forwards a ``query_params`` kwarg. The generated resource does not
accept it, so wrapping the resource made every first reconcile die with
``TypeError: ... unexpected keyword argument 'query_params'`` before any request left the box.
Drive the real contract with a live entities client over a mock transport: a 404 must surface
as NemoEntityNotFoundError, which is only reachable once ``get_entity_by_name(query_params=...)``
is accepted and the request actually goes out.
"""

def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(404, json={"detail": "not found"}, request=request)

sdk = AsyncNeMoPlatform(
base_url="http://entities.test",
workspace="default",
http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
)
with patch("grpc.insecure_channel", return_value=MagicMock()):
backend = OpenShellDeploymentBackend(sdk, {"gateway_endpoint": "http://127.0.0.1:17670"})

# Old (buggy) wrapping raised TypeError about query_params here; the fix reaches the 404.
with pytest.raises(NemoEntityNotFoundError):
await backend._load_deployment_config("default", "missing-config")


def test_sandbox_name_within_limit_and_deterministic() -> None:
name = _sandbox_name("a-long-workspace-name", "a-long-deployment-name")
assert name.startswith("nmp-")
Expand Down
Loading