116 lines
4.4 KiB
Python
116 lines
4.4 KiB
Python
"""Tests for Azure autoscaler Path object serialization and SSH key handling.
|
|
|
|
This test verifies that the Azure autoscaler properly:
|
|
1. Converts Path objects to strings before storing them in configuration
|
|
2. Always removes ssh_public_key from auth config (both user-specified and auto-generated)
|
|
to prevent bootstrap config from containing paths that don't exist on head node
|
|
3. Always injects public key content into ARM template parameters for VM creation
|
|
4. Ensures configuration can be properly serialized to JSON
|
|
|
|
The ssh_public_key path is removed because bootstrap config gets copied to worker nodes
|
|
and must only contain paths that exist on the head node. The public key content is
|
|
still used via ARM template parameter injection during VM creation.
|
|
|
|
The original issue was introduced in PR #54596 which added automatic SSH key
|
|
generation but stored Path objects directly in the configuration, causing
|
|
serialization errors.
|
|
"""
|
|
import json
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from ray.autoscaler._private._azure.config import _configure_key_pair
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"test_case,auth_config,expected_public_key_content",
|
|
[
|
|
(
|
|
"user_specified_keys",
|
|
{
|
|
"ssh_user": "ubuntu",
|
|
"ssh_private_key": "private_key_path", # Will be replaced with actual path
|
|
"ssh_public_key": "public_key_path", # Will be replaced with actual path
|
|
},
|
|
"ssh-rsa TEST_KEY user@example.com",
|
|
),
|
|
(
|
|
"auto_generated_keys",
|
|
{"ssh_user": "ubuntu"},
|
|
None, # Will be auto-generated, so we just check it exists
|
|
),
|
|
],
|
|
)
|
|
def test_azure_key_pair_string_conversion(
|
|
tmp_path, test_case, auth_config, expected_public_key_content
|
|
):
|
|
"""Test that Azure key pair configuration converts Path objects to strings.
|
|
|
|
Tests both user-specified and auto-generated SSH key scenarios.
|
|
"""
|
|
|
|
# Create the key files under pytest's temporary path (needed for user-specified case)
|
|
private_key_path = tmp_path / "id_rsa"
|
|
public_key_path = tmp_path / "id_rsa.pub"
|
|
|
|
private_key_path.write_text("")
|
|
public_key_path.write_text("ssh-rsa TEST_KEY user@example.com")
|
|
|
|
# Replace placeholder paths with actual paths for user-specified keys
|
|
if (
|
|
"ssh_private_key" in auth_config
|
|
and auth_config["ssh_private_key"] == "private_key_path"
|
|
):
|
|
auth_config["ssh_private_key"] = private_key_path
|
|
if (
|
|
"ssh_public_key" in auth_config
|
|
and auth_config["ssh_public_key"] == "public_key_path"
|
|
):
|
|
auth_config["ssh_public_key"] = public_key_path
|
|
|
|
# Create test configuration
|
|
config = {
|
|
"auth": auth_config,
|
|
"provider": {"location": "westus2", "resource_group": "test-group"},
|
|
"available_node_types": {"ray.head.default": {"node_config": {}}},
|
|
}
|
|
|
|
# Process the config
|
|
result_config = _configure_key_pair(config)
|
|
|
|
# Verify private key path exists and was converted to string
|
|
assert "ssh_private_key" in result_config["auth"]
|
|
assert isinstance(result_config["auth"]["ssh_private_key"], str)
|
|
|
|
# Verify ssh_public_key is always removed (both user-specified and auto-generated)
|
|
# because bootstrap config must only contain paths that exist on head node
|
|
assert "ssh_public_key" not in result_config["auth"]
|
|
|
|
# Verify public key content was injected into ARM parameters
|
|
head_node_config = result_config["available_node_types"]["ray.head.default"][
|
|
"node_config"
|
|
]
|
|
assert "azure_arm_parameters" in head_node_config
|
|
assert "publicKey" in head_node_config["azure_arm_parameters"]
|
|
|
|
actual_public_key = head_node_config["azure_arm_parameters"]["publicKey"]
|
|
if expected_public_key_content is not None:
|
|
# User-specified case: verify exact content
|
|
assert actual_public_key == expected_public_key_content
|
|
else:
|
|
# Auto-generated case: just verify it exists and looks like an SSH key
|
|
assert actual_public_key.strip()
|
|
assert actual_public_key.startswith("ssh-rsa")
|
|
|
|
# Verify config can be serialized to JSON without errors
|
|
json_str = json.dumps(result_config)
|
|
# If we get here, serialization succeeded
|
|
# Now try to deserialize to make sure it's valid JSON
|
|
deserialized = json.loads(json_str)
|
|
assert isinstance(deserialized, dict)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-sv", __file__]))
|