mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-02 07:34:45 +08:00
* fix(ci): repair post-merge release canary Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(packaging): bootstrap canary runtime prerequisites Signed-off-by: Drew Newberry <anewberry@nvidia.com> * ci(canary): collect macOS VM diagnostics Signed-off-by: Drew Newberry <anewberry@nvidia.com> * ci(canary): pin libkrun-compatible macOS runner Signed-off-by: Drew Newberry <anewberry@nvidia.com> * ci(canary): limit macOS smoke test to package startup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(compute)!: remove gateway callback listeners Run Docker supervisors on host networking so they use the operator-configured primary gateway endpoint. Remove the unused compute-driver callback listener negotiation and listener-scoped routing machinery. BREAKING CHANGE: The ComputeDriver API no longer exposes GetGatewayListenerRequirements or GatewayListenerRequirement. External drivers must regenerate bindings and connect supervisors to the configured primary gateway endpoint. Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): use sandbox runtime image in launcher Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(podman): exercise production endpoint selection Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(e2e): route supervisors to reachable gateways Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): align Podman endpoint fixtures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): preserve host aliases for supervisors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): align sandbox host gateway pin Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(e2e): address Docker fixtures by bridge IP Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): serialize sandbox lifecycle cases Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(e2e): host Docker TCP fixture with gateway Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(e2e): use loopback for host-network supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com>
223 lines
7.7 KiB
Python
223 lines
7.7 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
MIGRATOR = REPO_ROOT / "deploy/rpm/migrate-gateway-config.sh"
|
|
CURRENT = REPO_ROOT / "deploy/rpm/gateway.toml.default"
|
|
LEGACY = REPO_ROOT / "deploy/rpm/gateway.toml.default.v1"
|
|
SHIPPED_V1_FIXTURE = b"""# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Default gateway configuration for RPM installs.
|
|
#
|
|
# This file is seeded to ~/.config/openshell/gateway.toml on first start
|
|
# of the openshell-gateway.service systemd user unit. Edit that copy to
|
|
# customize. This file is not read directly at runtime.
|
|
#
|
|
# Configuration precedence (highest to lowest):
|
|
# CLI flag > OPENSHELL_* env var > TOML file > built-in default
|
|
#
|
|
# To override settings without editing this file, set OPENSHELL_* variables
|
|
# in ~/.config/openshell/gateway.env or run:
|
|
# systemctl --user edit openshell-gateway
|
|
|
|
[openshell]
|
|
version = 1
|
|
|
|
[openshell.gateway]
|
|
# Keep the primary listener on the built-in 127.0.0.1:17670 default. The
|
|
# host-networked Podman supervisor uses this same loopback endpoint.
|
|
|
|
# Pin to the Podman compute driver. Without this, the gateway auto-detects
|
|
# in order: Kubernetes, Podman, Docker. Pinning prevents unexpected driver
|
|
# selection if Docker is also installed on the host.
|
|
compute_drivers = ["podman"]
|
|
"""
|
|
|
|
|
|
def run_migrator(
|
|
destination: Path, *, check: bool = True
|
|
) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["sh", str(MIGRATOR), str(destination), str(CURRENT), str(LEGACY)],
|
|
check=check,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
|
|
def test_migrator_seeds_missing_config_and_is_idempotent(tmp_path: Path) -> None:
|
|
destination = tmp_path / "config/openshell/gateway.toml"
|
|
|
|
run_migrator(destination)
|
|
assert destination.read_bytes() == CURRENT.read_bytes()
|
|
|
|
run_migrator(destination)
|
|
assert destination.read_bytes() == CURRENT.read_bytes()
|
|
|
|
|
|
def test_migrator_replaces_only_exact_legacy_default(tmp_path: Path) -> None:
|
|
destination = tmp_path / "gateway.toml"
|
|
shipped_v1 = SHIPPED_V1_FIXTURE
|
|
assert b'compute_drivers = ["podman"]' in shipped_v1
|
|
assert LEGACY.read_bytes() == shipped_v1
|
|
destination.write_bytes(shipped_v1)
|
|
|
|
run_migrator(destination)
|
|
|
|
assert destination.read_bytes() == CURRENT.read_bytes()
|
|
assert destination.stat().st_mode & 0o777 == 0o644
|
|
|
|
|
|
def test_migrator_preserves_edited_legacy_and_current_configs(tmp_path: Path) -> None:
|
|
destination = tmp_path / "gateway.toml"
|
|
edited = LEGACY.read_text(encoding="utf-8") + "# operator edit\n"
|
|
destination.write_text(edited, encoding="utf-8")
|
|
|
|
run_migrator(destination)
|
|
assert destination.read_text(encoding="utf-8") == edited
|
|
|
|
destination.write_bytes(CURRENT.read_bytes())
|
|
run_migrator(destination)
|
|
assert destination.read_bytes() == CURRENT.read_bytes()
|
|
|
|
|
|
def test_migrator_refuses_symlink_destination(tmp_path: Path) -> None:
|
|
target = tmp_path / "target.toml"
|
|
target.write_text("operator-owned\n", encoding="utf-8")
|
|
destination = tmp_path / "gateway.toml"
|
|
destination.symlink_to(target)
|
|
|
|
result = run_migrator(destination, check=False)
|
|
|
|
assert result.returncode != 0
|
|
assert "non-regular gateway config" in result.stderr
|
|
assert target.read_text(encoding="utf-8") == "operator-owned\n"
|
|
|
|
|
|
def test_shipped_rpm_defaults_have_expected_schema_shapes() -> None:
|
|
import tomllib
|
|
|
|
current = tomllib.loads(CURRENT.read_text(encoding="utf-8"))
|
|
legacy = tomllib.loads(LEGACY.read_text(encoding="utf-8"))
|
|
|
|
assert current["openshell"]["version"] == 2
|
|
assert current["openshell"]["gateway"]["compute_driver"] == "podman"
|
|
assert "compute_drivers" not in current["openshell"]["gateway"]
|
|
assert current["openshell"]["drivers"]["podman"]["health_check_interval_secs"] == 10
|
|
assert LEGACY.read_bytes() == SHIPPED_V1_FIXTURE
|
|
assert legacy["openshell"]["version"] == 1
|
|
assert legacy["openshell"]["gateway"]["compute_drivers"] == ["podman"]
|
|
assert "compute_driver" not in legacy["openshell"]["gateway"]
|
|
|
|
|
|
def test_migrator_rejects_wrong_argument_count() -> None:
|
|
result = subprocess.run(
|
|
["sh", str(MIGRATOR)], text=True, capture_output=True, check=False
|
|
)
|
|
|
|
assert result.returncode == 2
|
|
assert result.stderr == (
|
|
f"usage: {MIGRATOR} DESTINATION CURRENT_DEFAULT LEGACY_DEFAULT\n"
|
|
)
|
|
|
|
|
|
def test_migrator_seeds_with_mode_0644(tmp_path: Path) -> None:
|
|
destination = tmp_path / "config/openshell/gateway.toml"
|
|
|
|
run_migrator(destination)
|
|
|
|
assert destination.read_bytes() == CURRENT.read_bytes()
|
|
assert destination.stat().st_mode & 0o777 == 0o644
|
|
|
|
|
|
def test_migrator_preserves_operator_content_and_mode(tmp_path: Path) -> None:
|
|
destination = tmp_path / "gateway.toml"
|
|
edited = LEGACY.read_text(encoding="utf-8") + "# operator edit\\n"
|
|
destination.write_text(edited, encoding="utf-8")
|
|
destination.chmod(0o600)
|
|
|
|
run_migrator(destination)
|
|
|
|
assert destination.read_text(encoding="utf-8") == edited
|
|
assert destination.stat().st_mode & 0o777 == 0o600
|
|
|
|
destination.write_bytes(CURRENT.read_bytes())
|
|
destination.chmod(0o640)
|
|
run_migrator(destination)
|
|
assert destination.read_bytes() == CURRENT.read_bytes()
|
|
assert destination.stat().st_mode & 0o777 == 0o640
|
|
|
|
|
|
def test_migrator_handles_paths_containing_spaces(tmp_path: Path) -> None:
|
|
package = tmp_path / "package defaults"
|
|
package.mkdir()
|
|
current = package / "current default.toml"
|
|
legacy = package / "legacy default.toml"
|
|
current.write_bytes(CURRENT.read_bytes())
|
|
legacy.write_bytes(LEGACY.read_bytes())
|
|
destination = tmp_path / "operator config/gateway config.toml"
|
|
|
|
subprocess.run(
|
|
["sh", str(MIGRATOR), str(destination), str(current), str(legacy)],
|
|
check=True,
|
|
)
|
|
|
|
assert destination.read_bytes() == current.read_bytes()
|
|
assert destination.stat().st_mode & 0o777 == 0o644
|
|
|
|
|
|
def test_migrator_rejects_missing_and_nonregular_sources(tmp_path: Path) -> None:
|
|
for source_name in ("current", "legacy"):
|
|
for source_kind in ("missing", "directory", "symlink"):
|
|
current = tmp_path / f"{source_name}-{source_kind}-current.toml"
|
|
legacy = tmp_path / f"{source_name}-{source_kind}-legacy.toml"
|
|
current.write_bytes(CURRENT.read_bytes())
|
|
legacy.write_bytes(LEGACY.read_bytes())
|
|
source = current if source_name == "current" else legacy
|
|
if source_kind == "missing":
|
|
source.unlink()
|
|
elif source_kind == "directory":
|
|
source.unlink()
|
|
source.mkdir()
|
|
else:
|
|
source.unlink()
|
|
source.symlink_to(CURRENT)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
"sh",
|
|
str(MIGRATOR),
|
|
str(tmp_path / f"{source_name}-{source_kind}-destination.toml"),
|
|
str(current),
|
|
str(legacy),
|
|
],
|
|
check=False,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
assert result.returncode == 1
|
|
assert (
|
|
f"gateway config migration source is not a regular file: {source}"
|
|
in result.stderr
|
|
)
|
|
|
|
|
|
def test_migrator_refuses_directory_destination(tmp_path: Path) -> None:
|
|
destination = tmp_path / "gateway.toml"
|
|
destination.mkdir()
|
|
|
|
result = run_migrator(destination, check=False)
|
|
|
|
assert result.returncode == 1
|
|
assert (
|
|
f"refusing to replace non-regular gateway config: {destination}"
|
|
in result.stderr
|
|
)
|