chore(openstack): support multi-region in the same provider (#10135)

This commit is contained in:
Daniel Barranquero
2026-02-24 12:50:52 +01:00
committed by GitHub
parent 61076c755f
commit 030d053c84
12 changed files with 1192 additions and 130 deletions

View File

@@ -62,6 +62,9 @@ def set_mocked_openstack_provider(
# Mock connection
provider.connection = MagicMock()
# Mock regional connections (single-region default)
provider.regional_connections = {region_name: provider.connection}
# Mock audit config
provider.audit_config = audit_config or {}
provider.fixer_config = {}

View File

@@ -14,11 +14,14 @@ from prowler.config.config import (
)
from prowler.providers.common.models import Connection
from prowler.providers.openstack.exceptions.exceptions import (
OpenStackAmbiguousRegionError,
OpenStackAuthenticationError,
OpenStackCloudNotFoundError,
OpenStackConfigFileNotFoundError,
OpenStackCredentialsError,
OpenStackInvalidConfigError,
OpenStackInvalidProviderIdError,
OpenStackNoRegionError,
)
from prowler.providers.openstack.models import OpenStackIdentityInfo, OpenStackSession
from prowler.providers.openstack.openstack_provider import OpenstackProvider
@@ -1023,3 +1026,623 @@ clouds:
assert provider.session.auth_url == "https://openstack.example.com:5000/v3"
assert provider.session.username == "test-user"
assert provider.session.project_id == "test-project"
class TestOpenstackProviderRegionValidation:
"""Test suite for OpenStack Provider region validation (region_name XOR regions)."""
@pytest.fixture(autouse=True)
def clean_openstack_env(self, monkeypatch):
"""Ensure clean OpenStack environment for all tests."""
openstack_env_vars = [
"OS_AUTH_URL",
"OS_USERNAME",
"OS_PASSWORD",
"OS_PROJECT_ID",
"OS_REGION_NAME",
"OS_CLOUD",
"OS_IDENTITY_API_VERSION",
"OS_USER_DOMAIN_NAME",
"OS_PROJECT_DOMAIN_NAME",
]
for env_var in openstack_env_vars:
monkeypatch.delenv(env_var, raising=False)
def test_clouds_yaml_content_with_region_name_only(self):
"""Test that clouds.yaml content with only region_name produces a valid session."""
clouds_yaml_content = """
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
region_name: RegionOne
identity_api_version: 3
"""
session = OpenstackProvider._setup_session_from_clouds_yaml_content(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="test-cloud",
)
assert session.region_name == "RegionOne"
assert session.regions is None
def test_clouds_yaml_content_with_regions_list_only(self):
"""Test that clouds.yaml content with only regions list produces a valid session."""
clouds_yaml_content = """
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
regions:
- RegionOne
- RegionTwo
identity_api_version: 3
"""
session = OpenstackProvider._setup_session_from_clouds_yaml_content(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="test-cloud",
)
assert session.region_name is None
assert session.regions == ["RegionOne", "RegionTwo"]
def test_clouds_yaml_content_with_both_region_name_and_regions(self):
"""Test that clouds.yaml content with both region_name and regions raises error."""
clouds_yaml_content = """
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
region_name: RegionOne
regions:
- RegionOne
- RegionTwo
identity_api_version: 3
"""
with pytest.raises(OpenStackAmbiguousRegionError) as excinfo:
OpenstackProvider._setup_session_from_clouds_yaml_content(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="test-cloud",
)
assert "both 'region_name' and 'regions'" in str(excinfo.value)
def test_clouds_yaml_content_with_neither_region_name_nor_regions(self):
"""Test that clouds.yaml content with neither region_name nor regions raises error."""
clouds_yaml_content = """
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
identity_api_version: 3
"""
with pytest.raises(OpenStackNoRegionError) as excinfo:
OpenstackProvider._setup_session_from_clouds_yaml_content(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="test-cloud",
)
assert "neither 'region_name' nor 'regions'" in str(excinfo.value)
def test_clouds_yaml_file_with_regions_list(self, tmp_path):
"""Test loading clouds.yaml file with regions list."""
clouds_yaml = tmp_path / "clouds.yaml"
clouds_yaml.write_text(
"""
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
regions:
- RegionOne
- RegionTwo
identity_api_version: 3
"""
)
mock_connection = MagicMock()
mock_connection.authorize.return_value = None
mock_connection.current_user_id = None
mock_connection.current_project_id = "test-project-id"
mock_connection.identity.get_project.return_value = None
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.return_value = mock_connection
provider = OpenstackProvider(
clouds_yaml_file=str(clouds_yaml),
clouds_yaml_cloud="test-cloud",
)
assert provider.session.region_name is None
assert provider.session.regions == ["RegionOne", "RegionTwo"]
def test_clouds_yaml_file_with_both_regions_raises_error(self, tmp_path):
"""Test that clouds.yaml file with both region_name and regions raises error."""
clouds_yaml = tmp_path / "clouds.yaml"
clouds_yaml.write_text(
"""
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
region_name: RegionOne
regions:
- RegionOne
- RegionTwo
identity_api_version: 3
"""
)
with pytest.raises(OpenStackAmbiguousRegionError):
OpenstackProvider(
clouds_yaml_file=str(clouds_yaml),
clouds_yaml_cloud="test-cloud",
)
def test_clouds_yaml_file_with_no_region_raises_error(self, tmp_path):
"""Test that clouds.yaml file with neither region_name nor regions raises error."""
clouds_yaml = tmp_path / "clouds.yaml"
clouds_yaml.write_text(
"""
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
identity_api_version: 3
"""
)
with pytest.raises(OpenStackNoRegionError):
OpenstackProvider(
clouds_yaml_file=str(clouds_yaml),
clouds_yaml_cloud="test-cloud",
)
def test_session_as_sdk_config_with_regions_list(self):
"""Test OpenStackSession.as_sdk_config() with regions list uses first region."""
session = OpenStackSession(
auth_url="https://openstack.example.com:5000/v3",
identity_api_version="3",
username="test-user",
password="test-password",
project_id="test-project",
regions=["RegionOne", "RegionTwo"],
user_domain_name="Default",
project_domain_name="Default",
)
sdk_config = session.as_sdk_config()
# SDK does not iterate over regions automatically, so we pass the
# first region as region_name for the default connection
assert sdk_config["region_name"] == "RegionOne"
assert "regions" not in sdk_config
def test_session_as_sdk_config_with_region_name(self):
"""Test OpenStackSession.as_sdk_config() with single region_name."""
session = OpenStackSession(
auth_url="https://openstack.example.com:5000/v3",
identity_api_version="3",
username="test-user",
password="test-password",
project_id="test-project",
region_name="RegionOne",
user_domain_name="Default",
project_domain_name="Default",
)
sdk_config = session.as_sdk_config()
assert sdk_config["region_name"] == "RegionOne"
assert "regions" not in sdk_config
class TestOpenstackProviderIdValidation:
"""Test suite for OpenStack Provider ID validation."""
@pytest.fixture(autouse=True)
def clean_openstack_env(self, monkeypatch):
"""Ensure clean OpenStack environment for all tests."""
openstack_env_vars = [
"OS_AUTH_URL",
"OS_USERNAME",
"OS_PASSWORD",
"OS_PROJECT_ID",
"OS_REGION_NAME",
"OS_CLOUD",
"OS_IDENTITY_API_VERSION",
"OS_USER_DOMAIN_NAME",
"OS_PROJECT_DOMAIN_NAME",
]
for env_var in openstack_env_vars:
monkeypatch.delenv(env_var, raising=False)
def test_test_connection_provider_id_matches(self):
"""Test test_connection succeeds when provider_id matches project_id."""
mock_connection = MagicMock()
mock_connection.authorize.return_value = None
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.return_value = mock_connection
connection_result = OpenstackProvider.test_connection(
auth_url="https://openstack.example.com:5000/v3",
username="test-user",
password="test-password",
project_id="test-project-id",
region_name="RegionOne",
provider_id="test-project-id",
raise_on_exception=False,
)
assert connection_result.is_connected is True
assert connection_result.error is None
def test_test_connection_provider_id_does_not_match(self):
"""Test test_connection fails when provider_id doesn't match project_id."""
connection_result = OpenstackProvider.test_connection(
auth_url="https://openstack.example.com:5000/v3",
username="test-user",
password="test-password",
project_id="actual-project-id",
region_name="RegionOne",
provider_id="different-project-id",
raise_on_exception=False,
)
assert connection_result.is_connected is False
assert isinstance(connection_result.error, OpenStackInvalidProviderIdError)
def test_test_connection_provider_id_mismatch_raises(self):
"""Test test_connection raises when provider_id doesn't match and raise_on_exception=True."""
with pytest.raises(OpenStackInvalidProviderIdError) as excinfo:
OpenstackProvider.test_connection(
auth_url="https://openstack.example.com:5000/v3",
username="test-user",
password="test-password",
project_id="actual-project-id",
region_name="RegionOne",
provider_id="different-project-id",
raise_on_exception=True,
)
assert "different-project-id" in str(excinfo.value)
assert "actual-project-id" in str(excinfo.value)
def test_test_connection_no_provider_id_skips_validation(self):
"""Test test_connection skips provider_id validation when not provided."""
mock_connection = MagicMock()
mock_connection.authorize.return_value = None
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.return_value = mock_connection
connection_result = OpenstackProvider.test_connection(
auth_url="https://openstack.example.com:5000/v3",
username="test-user",
password="test-password",
project_id="test-project-id",
region_name="RegionOne",
raise_on_exception=False,
)
assert connection_result.is_connected is True
def test_test_connection_provider_id_with_clouds_yaml_content(self):
"""Test test_connection validates provider_id against clouds.yaml content project_id."""
clouds_yaml_content = """
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: yaml-project-id
region_name: RegionOne
"""
connection_result = OpenstackProvider.test_connection(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="test-cloud",
provider_id="wrong-project-id",
raise_on_exception=False,
)
assert connection_result.is_connected is False
assert isinstance(connection_result.error, OpenStackInvalidProviderIdError)
def test_test_connection_region_error_surfaced(self):
"""Test test_connection surfaces region validation errors."""
clouds_yaml_content = """
clouds:
test-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
identity_api_version: 3
"""
connection_result = OpenstackProvider.test_connection(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="test-cloud",
raise_on_exception=False,
)
assert connection_result.is_connected is False
assert isinstance(connection_result.error, OpenStackNoRegionError)
class TestOpenstackProviderRegionalConnections:
"""Test suite for OpenStack Provider regional_connections."""
@pytest.fixture(autouse=True)
def clean_openstack_env(self, monkeypatch):
"""Ensure clean OpenStack environment for all tests."""
openstack_env_vars = [
"OS_AUTH_URL",
"OS_USERNAME",
"OS_PASSWORD",
"OS_PROJECT_ID",
"OS_REGION_NAME",
"OS_CLOUD",
"OS_IDENTITY_API_VERSION",
"OS_USER_DOMAIN_NAME",
"OS_PROJECT_DOMAIN_NAME",
]
for env_var in openstack_env_vars:
monkeypatch.delenv(env_var, raising=False)
def test_single_region_regional_connections(self):
"""Test regional_connections has one entry for single-region provider."""
mock_connection = MagicMock()
mock_connection.authorize.return_value = None
mock_connection.current_user_id = None
mock_connection.current_project_id = "test-project"
mock_connection.identity.get_project.return_value = None
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.return_value = mock_connection
provider = OpenstackProvider(
auth_url="https://openstack.example.com:5000/v3",
username="test-user",
password="test-password",
project_id="test-project",
region_name="RegionOne",
)
assert len(provider.regional_connections) == 1
assert "RegionOne" in provider.regional_connections
assert provider.regional_connections["RegionOne"] is provider.connection
mock_connect.assert_called_once()
def test_multi_region_regional_connections(self):
"""Test regional_connections has entries for each region in multi-region setup."""
mock_conn_region1 = MagicMock()
mock_conn_region1.authorize.return_value = None
mock_conn_region1.current_user_id = None
mock_conn_region1.current_project_id = "test-project-id"
mock_conn_region1.identity.get_project.return_value = None
mock_conn_region2 = MagicMock()
mock_conn_region2.authorize.return_value = None
clouds_yaml_content = """
clouds:
multi-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
regions:
- UK1
- DE1
identity_api_version: 3
"""
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.side_effect = [mock_conn_region1, mock_conn_region2]
provider = OpenstackProvider(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="multi-cloud",
)
assert len(provider.regional_connections) == 2
assert "UK1" in provider.regional_connections
assert "DE1" in provider.regional_connections
assert provider.regional_connections["UK1"] is mock_conn_region1
assert provider.regional_connections["DE1"] is mock_conn_region2
# Default connection should be the first region
assert provider.connection is mock_conn_region1
assert mock_connect.call_count == 2
def test_multi_region_test_connection_tests_all_regions(self):
"""Test test_connection tests connectivity to every region."""
mock_connection = MagicMock()
mock_connection.authorize.return_value = None
clouds_yaml_content = """
clouds:
multi-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
regions:
- UK1
- DE1
identity_api_version: 3
"""
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.return_value = mock_connection
result = OpenstackProvider.test_connection(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="multi-cloud",
raise_on_exception=False,
)
assert result.is_connected is True
# Should have called connect once per region
assert mock_connect.call_count == 2
def test_multi_region_test_connection_fails_if_one_region_fails(self):
"""Test test_connection fails if any region fails."""
mock_conn_ok = MagicMock()
mock_conn_ok.authorize.return_value = None
mock_conn_fail = MagicMock()
mock_conn_fail.authorize.side_effect = openstack_exceptions.SDKException(
"Connection failed in DE1"
)
clouds_yaml_content = """
clouds:
multi-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
regions:
- UK1
- DE1
identity_api_version: 3
"""
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.side_effect = [mock_conn_ok, mock_conn_fail]
result = OpenstackProvider.test_connection(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="multi-cloud",
raise_on_exception=False,
)
assert result.is_connected is False
def test_session_as_sdk_config_region_override(self):
"""Test as_sdk_config with region_override overrides region_name."""
session = OpenStackSession(
auth_url="https://openstack.example.com:5000/v3",
identity_api_version="3",
username="test-user",
password="test-password",
project_id="test-project",
region_name="RegionOne",
user_domain_name="Default",
project_domain_name="Default",
)
sdk_config = session.as_sdk_config(region_override="RegionTwo")
assert sdk_config["region_name"] == "RegionTwo"
def test_session_as_sdk_config_region_override_with_regions_list(self):
"""Test as_sdk_config with region_override overrides regions list."""
session = OpenStackSession(
auth_url="https://openstack.example.com:5000/v3",
identity_api_version="3",
username="test-user",
password="test-password",
project_id="test-project",
regions=["UK1", "DE1"],
user_domain_name="Default",
project_domain_name="Default",
)
sdk_config = session.as_sdk_config(region_override="DE1")
assert sdk_config["region_name"] == "DE1"
def test_multi_region_test_connection_provider_id_matches(self):
"""Test test_connection validates provider_id in multi-region setup."""
mock_connection = MagicMock()
mock_connection.authorize.return_value = None
clouds_yaml_content = """
clouds:
multi-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: test-project-id
regions:
- UK1
- DE1
identity_api_version: 3
"""
with patch(
"prowler.providers.openstack.openstack_provider.connect"
) as mock_connect:
mock_connect.return_value = mock_connection
result = OpenstackProvider.test_connection(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="multi-cloud",
provider_id="test-project-id",
raise_on_exception=False,
)
assert result.is_connected is True
def test_multi_region_test_connection_provider_id_mismatch(self):
"""Test test_connection fails when provider_id doesn't match in multi-region."""
clouds_yaml_content = """
clouds:
multi-cloud:
auth:
auth_url: https://openstack.example.com:5000/v3
username: test-user
password: test-password
project_id: actual-project-id
regions:
- UK1
- DE1
identity_api_version: 3
"""
result = OpenstackProvider.test_connection(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud="multi-cloud",
provider_id="wrong-project-id",
raise_on_exception=False,
)
assert result.is_connected is False
assert isinstance(result.error, OpenStackInvalidProviderIdError)

View File

@@ -28,9 +28,10 @@ class TestComputeService:
assert compute.service_name == "Compute"
assert compute.provider == provider
assert compute.connection == provider.connection
assert compute.regional_connections == provider.regional_connections
assert compute.audited_regions == [OPENSTACK_REGION]
assert compute.region == OPENSTACK_REGION
assert compute.project_id == OPENSTACK_PROJECT_ID
assert compute.client == provider.connection.compute
assert compute.instances == []
mock_list.assert_called_once()
@@ -303,6 +304,8 @@ class TestComputeService:
assert hasattr(compute, "service_name")
assert hasattr(compute, "provider")
assert hasattr(compute, "connection")
assert hasattr(compute, "regional_connections")
assert hasattr(compute, "audited_regions")
assert hasattr(compute, "session")
assert hasattr(compute, "region")
assert hasattr(compute, "project_id")
@@ -343,3 +346,153 @@ class TestComputeService:
assert len(compute.instances) == 1
assert compute.instances[0].id == "instance-1"
assert compute.instances[0].networks == {} # Should default to empty dict
def test_compute_list_instances_multi_region(self):
"""Test listing instances across multiple regions."""
provider = set_mocked_openstack_provider()
# Create two mock connections for two regions
mock_conn_uk1 = MagicMock()
mock_conn_de1 = MagicMock()
# Set up regional connections
provider.regional_connections = {"UK1": mock_conn_uk1, "DE1": mock_conn_de1}
mock_server_uk = MagicMock()
mock_server_uk.id = "instance-uk"
mock_server_uk.name = "Instance UK"
mock_server_uk.status = "ACTIVE"
mock_server_uk.flavor = {"id": "flavor-1"}
mock_server_uk.security_groups = [{"name": "default"}]
mock_server_uk.is_locked = False
mock_server_uk.locked_reason = ""
mock_server_uk.key_name = ""
mock_server_uk.user_id = ""
mock_server_uk.access_ipv4 = ""
mock_server_uk.access_ipv6 = ""
mock_server_uk.public_v4 = ""
mock_server_uk.public_v6 = ""
mock_server_uk.private_v4 = "10.0.0.1"
mock_server_uk.private_v6 = ""
mock_server_uk.addresses = {"private": [{"version": 4, "addr": "10.0.0.1"}]}
mock_server_uk.has_config_drive = False
mock_server_uk.metadata = {}
mock_server_uk.user_data = ""
mock_server_uk.trusted_image_certificates = []
mock_server_de = MagicMock()
mock_server_de.id = "instance-de"
mock_server_de.name = "Instance DE"
mock_server_de.status = "ACTIVE"
mock_server_de.flavor = {"id": "flavor-2"}
mock_server_de.security_groups = [{"name": "default"}]
mock_server_de.is_locked = False
mock_server_de.locked_reason = ""
mock_server_de.key_name = ""
mock_server_de.user_id = ""
mock_server_de.access_ipv4 = ""
mock_server_de.access_ipv6 = ""
mock_server_de.public_v4 = ""
mock_server_de.public_v6 = ""
mock_server_de.private_v4 = "10.0.0.2"
mock_server_de.private_v6 = ""
mock_server_de.addresses = {"private": [{"version": 4, "addr": "10.0.0.2"}]}
mock_server_de.has_config_drive = False
mock_server_de.metadata = {}
mock_server_de.user_data = ""
mock_server_de.trusted_image_certificates = []
mock_conn_uk1.compute.servers.return_value = [mock_server_uk]
mock_conn_de1.compute.servers.return_value = [mock_server_de]
compute = Compute(provider)
assert len(compute.instances) == 2
# Verify instances have correct region tags
uk_instance = next(i for i in compute.instances if i.id == "instance-uk")
de_instance = next(i for i in compute.instances if i.id == "instance-de")
assert uk_instance.region == "UK1"
assert de_instance.region == "DE1"
def test_compute_list_instances_multi_region_partial_failure(self):
"""Test that a failing region doesn't prevent other regions from being listed."""
provider = set_mocked_openstack_provider()
mock_conn_ok = MagicMock()
mock_conn_fail = MagicMock()
provider.regional_connections = {"UK1": mock_conn_ok, "DE1": mock_conn_fail}
mock_server = MagicMock()
mock_server.id = "instance-uk"
mock_server.name = "Instance UK"
mock_server.status = "ACTIVE"
mock_server.flavor = {"id": "flavor-1"}
mock_server.security_groups = [{"name": "default"}]
mock_server.is_locked = False
mock_server.locked_reason = ""
mock_server.key_name = ""
mock_server.user_id = ""
mock_server.access_ipv4 = ""
mock_server.access_ipv6 = ""
mock_server.public_v4 = ""
mock_server.public_v6 = ""
mock_server.private_v4 = "10.0.0.1"
mock_server.private_v6 = ""
mock_server.addresses = {}
mock_server.has_config_drive = False
mock_server.metadata = {}
mock_server.user_data = ""
mock_server.trusted_image_certificates = []
mock_conn_ok.compute.servers.return_value = [mock_server]
mock_conn_fail.compute.servers.side_effect = openstack_exceptions.SDKException(
"API error in DE1"
)
compute = Compute(provider)
# Should have the instance from UK1, DE1 failure is logged but doesn't crash
assert len(compute.instances) == 1
assert compute.instances[0].id == "instance-uk"
assert compute.instances[0].region == "UK1"
def test_compute_list_instances_multi_region_one_empty(self):
"""Test multi-region where one region has instances and the other is empty."""
provider = set_mocked_openstack_provider()
mock_conn_uk1 = MagicMock()
mock_conn_de1 = MagicMock()
provider.regional_connections = {"UK1": mock_conn_uk1, "DE1": mock_conn_de1}
mock_server = MagicMock()
mock_server.id = "instance-uk"
mock_server.name = "Instance UK"
mock_server.status = "ACTIVE"
mock_server.flavor = {"id": "flavor-1"}
mock_server.security_groups = [{"name": "default"}]
mock_server.is_locked = False
mock_server.locked_reason = ""
mock_server.key_name = ""
mock_server.user_id = ""
mock_server.access_ipv4 = ""
mock_server.access_ipv6 = ""
mock_server.public_v4 = ""
mock_server.public_v6 = ""
mock_server.private_v4 = "10.0.0.1"
mock_server.private_v6 = ""
mock_server.addresses = {}
mock_server.has_config_drive = False
mock_server.metadata = {}
mock_server.user_data = ""
mock_server.trusted_image_certificates = []
mock_conn_uk1.compute.servers.return_value = [mock_server]
mock_conn_de1.compute.servers.return_value = [] # Empty region
compute = Compute(provider)
assert len(compute.instances) == 1
assert compute.instances[0].id == "instance-uk"
assert compute.instances[0].region == "UK1"