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
2 changes: 2 additions & 0 deletions .github/workflows/benchmarks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ jobs:
mkdir -p ~/.ssh
echo "${{ secrets.HETZNER_SSH_PRIVATE_KEY }}" > ~/.ssh/hetzner_key
chmod 600 ~/.ssh/hetzner_key
# Generate public key from private key for Hetzner registration
ssh-keygen -y -f ~/.ssh/hetzner_key > ~/.ssh/hetzner_key.pub

- name: Run benchmarks on Hetzner
env:
Expand Down
25 changes: 24 additions & 1 deletion csp_benchmarks/hetzner/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ def run_benchmarks(args: argparse.Namespace) -> int:
logger.error("Hetzner Cloud token required. Set HCLOUD_TOKEN or use --token")
return 1

# Determine SSH key name - either explicit or derive from key file
ssh_key_name = args.ssh_key_name
if not ssh_key_name and args.ssh_key:
ssh_key_name = "csp-benchmarks"

# Configure server
server_config = ServerConfig(
name=args.server_name,
server_type=args.server_type,
ssh_key_name=args.ssh_key_name,
ssh_key_name=ssh_key_name,
)

# Configure benchmarks
Expand All @@ -48,6 +53,24 @@ def run_benchmarks(args: argparse.Namespace) -> int:
server = None

try:
# Upload SSH public key to Hetzner if we have a private key
if args.ssh_key and ssh_key_name:
pub_key_path = args.ssh_key + ".pub"
if os.path.exists(pub_key_path):
with open(pub_key_path) as f:
public_key = f.read().strip()
manager.ensure_ssh_key(public_key, ssh_key_name)
else:
# Try to generate public key from private key
import subprocess

result = subprocess.run(["ssh-keygen", "-y", "-f", args.ssh_key], capture_output=True, text=True)
if result.returncode == 0:
public_key = result.stdout.strip()
manager.ensure_ssh_key(public_key, ssh_key_name)
else:
logger.warning(f"Could not extract public key from {args.ssh_key}")

# Check if server already exists
server = manager.get_server()
if server and not args.reuse:
Expand Down
13 changes: 8 additions & 5 deletions csp_benchmarks/hetzner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,29 @@ def _scp_from_server(self, remote_path: str, local_path: str) -> None:

subprocess.run(scp_args, check=True)

def _wait_for_ssh(self, timeout: int = 120, interval: int = 10) -> None:
def _wait_for_ssh(self, timeout: int = 300, interval: int = 15) -> None:
"""Wait for SSH to become available on the server."""
import time

logger.info("Waiting for SSH to become available...")
start_time = time.time()
last_error = None

while time.time() - start_time < timeout:
try:
result = self._run_ssh_command("echo 'SSH ready'", check=False)
if result.returncode == 0:
logger.info("SSH connection established")
return
except Exception:
pass
last_error = f"exit code {result.returncode}: {result.stderr}"
except Exception as e:
last_error = str(e)

logger.debug(f"SSH not ready, retrying in {interval}s...")
elapsed = int(time.time() - start_time)
logger.info(f"SSH not ready after {elapsed}s, retrying... (last error: {last_error})")
time.sleep(interval)

raise TimeoutError(f"SSH not available after {timeout} seconds")
raise TimeoutError(f"SSH not available after {timeout} seconds. Last error: {last_error}")

def _setup_environment(self) -> None:
"""Set up the benchmark environment on the remote server."""
Expand Down
18 changes: 18 additions & 0 deletions csp_benchmarks/hetzner/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ def _wait_for_server_ready(self, server: BoundServer, timeout: int = 300) -> Non

raise TimeoutError(f"Server did not become ready within {timeout} seconds")

def ensure_ssh_key(self, public_key: str, key_name: str = "csp-benchmarks") -> None:
"""
Ensure an SSH public key exists in Hetzner Cloud.

Args:
public_key: The SSH public key content
key_name: Name for the key in Hetzner Cloud
"""
# Check if key already exists
existing = self.client.ssh_keys.get_by_name(key_name)
if existing:
logger.info(f"SSH key '{key_name}' already exists in Hetzner Cloud")
return

# Create the key
logger.info(f"Creating SSH key '{key_name}' in Hetzner Cloud")
self.client.ssh_keys.create(name=key_name, public_key=public_key)

def _get_cloud_init_script(self) -> str:
"""Get the cloud-init script for server setup."""
return """#cloud-config
Expand Down