Skip to content

Commit 05abb6d

Browse files
committed
wire-in new service to the Manager
1 parent b1286cd commit 05abb6d

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

python/understack-workflows/tests/test_netapp_manager.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,3 +374,225 @@ def test_desired_node_number_extraction(self):
374374
vlan_id=100,
375375
)
376376
assert config_n2.desired_node_number == 2
377+
378+
379+
class TestNetAppManagerRouteIntegration:
380+
"""Test NetAppManager route integration functionality."""
381+
382+
@pytest.fixture
383+
def mock_config_file(self):
384+
"""Create a temporary config file for testing."""
385+
config_content = """[netapp_nvme]
386+
netapp_server_hostname = test-hostname
387+
netapp_login = test-user
388+
netapp_password = test-password
389+
"""
390+
with tempfile.NamedTemporaryFile(mode="w", suffix=".conf", delete=False) as f:
391+
f.write(config_content)
392+
f.flush()
393+
yield f.name
394+
os.unlink(f.name)
395+
396+
@pytest.fixture
397+
def sample_interface_configs(self):
398+
"""Create sample interface configurations for testing."""
399+
return [
400+
NetappIPInterfaceConfig(
401+
name="N1-lif-A",
402+
address=ipaddress.IPv4Address("100.127.0.21"),
403+
network=ipaddress.IPv4Network("100.127.0.16/29"),
404+
vlan_id=100,
405+
),
406+
NetappIPInterfaceConfig(
407+
name="N1-lif-B",
408+
address=ipaddress.IPv4Address("100.127.128.21"),
409+
network=ipaddress.IPv4Network("100.127.128.16/29"),
410+
vlan_id=200,
411+
),
412+
NetappIPInterfaceConfig(
413+
name="N2-lif-A",
414+
address=ipaddress.IPv4Address("100.127.0.22"),
415+
network=ipaddress.IPv4Network("100.127.0.16/29"),
416+
vlan_id=100,
417+
),
418+
]
419+
420+
@patch("understack_workflows.netapp.manager.config")
421+
@patch("understack_workflows.netapp.manager.HostConnection")
422+
def test_route_service_initialization(
423+
self, mock_host_connection, mock_config, mock_config_file
424+
):
425+
"""Test that RouteService is properly initialized in NetAppManager."""
426+
manager = NetAppManager(mock_config_file)
427+
428+
# Verify route service is initialized
429+
assert hasattr(manager, "_route_service")
430+
assert manager._route_service is not None
431+
432+
@patch("understack_workflows.netapp.manager.config")
433+
@patch("understack_workflows.netapp.manager.HostConnection")
434+
def test_create_routes_for_project_delegates_to_service(
435+
self,
436+
mock_host_connection,
437+
mock_config,
438+
mock_config_file,
439+
sample_interface_configs,
440+
):
441+
"""Test create_routes_for_project delegates to RouteService."""
442+
from understack_workflows.netapp.value_objects import RouteResult
443+
444+
manager = NetAppManager(mock_config_file)
445+
446+
# Mock route service
447+
expected_results = [
448+
RouteResult(
449+
uuid="route-uuid-1",
450+
gateway="100.127.0.17",
451+
destination="100.126.0.0/17",
452+
svm_name="os-test-project",
453+
),
454+
RouteResult(
455+
uuid="route-uuid-2",
456+
gateway="100.127.128.17",
457+
destination="100.126.128.0/17",
458+
svm_name="os-test-project",
459+
),
460+
]
461+
manager._route_service.create_routes_from_interfaces = MagicMock(
462+
return_value=expected_results
463+
)
464+
465+
result = manager.create_routes_for_project(
466+
"test-project", sample_interface_configs
467+
)
468+
469+
# Verify delegation with correct parameters
470+
manager._route_service.create_routes_from_interfaces.assert_called_once_with(
471+
"test-project", sample_interface_configs
472+
)
473+
assert result == expected_results
474+
475+
@patch("understack_workflows.netapp.manager.config")
476+
@patch("understack_workflows.netapp.manager.HostConnection")
477+
def test_create_routes_for_project_error_handling(
478+
self,
479+
mock_host_connection,
480+
mock_config,
481+
mock_config_file,
482+
sample_interface_configs,
483+
):
484+
"""Test create_routes_for_project error handling and propagation."""
485+
from understack_workflows.netapp.exceptions import NetworkOperationError
486+
487+
manager = NetAppManager(mock_config_file)
488+
489+
# Mock route service to raise an error
490+
manager._route_service.create_routes_from_interfaces = MagicMock(
491+
side_effect=NetworkOperationError("Route creation failed")
492+
)
493+
494+
# Verify error is propagated
495+
with pytest.raises(NetworkOperationError, match="Route creation failed"):
496+
manager.create_routes_for_project("test-project", sample_interface_configs)
497+
498+
# Verify service was called
499+
manager._route_service.create_routes_from_interfaces.assert_called_once_with(
500+
"test-project", sample_interface_configs
501+
)
502+
503+
@patch("understack_workflows.netapp.manager.config")
504+
@patch("understack_workflows.netapp.manager.HostConnection")
505+
def test_create_routes_for_project_logging(
506+
self,
507+
mock_host_connection,
508+
mock_config,
509+
mock_config_file,
510+
sample_interface_configs,
511+
):
512+
"""Test create_routes_for_project logging behavior."""
513+
from understack_workflows.netapp.value_objects import RouteResult
514+
515+
manager = NetAppManager(mock_config_file)
516+
517+
# Mock route service
518+
expected_results = [
519+
RouteResult(
520+
uuid="route-uuid-1",
521+
gateway="100.127.0.17",
522+
destination="100.126.0.0/17",
523+
svm_name="os-test-project",
524+
),
525+
]
526+
manager._route_service.create_routes_from_interfaces = MagicMock(
527+
return_value=expected_results
528+
)
529+
530+
with patch("understack_workflows.netapp.manager.logger") as mock_logger:
531+
result = manager.create_routes_for_project(
532+
"test-project", sample_interface_configs
533+
)
534+
535+
# Verify logging calls
536+
mock_logger.info.assert_any_call(
537+
"Creating routes for project %(project_id)s with %(count)d interfaces",
538+
{"project_id": "test-project", "count": 3},
539+
)
540+
mock_logger.info.assert_any_call(
541+
"Successfully created %(count)d routes for project %(project_id)s",
542+
{"count": 1, "project_id": "test-project"},
543+
)
544+
545+
assert result == expected_results
546+
547+
@patch("understack_workflows.netapp.manager.config")
548+
@patch("understack_workflows.netapp.manager.HostConnection")
549+
def test_create_routes_for_project_empty_interfaces(
550+
self, mock_host_connection, mock_config, mock_config_file
551+
):
552+
"""Test create_routes_for_project with empty interface list."""
553+
manager = NetAppManager(mock_config_file)
554+
555+
# Mock route service
556+
manager._route_service.create_routes_from_interfaces = MagicMock(
557+
return_value=[]
558+
)
559+
560+
result = manager.create_routes_for_project("test-project", [])
561+
562+
# Verify delegation with empty list
563+
manager._route_service.create_routes_from_interfaces.assert_called_once_with(
564+
"test-project", []
565+
)
566+
assert result == []
567+
568+
@patch("understack_workflows.netapp.manager.config")
569+
@patch("understack_workflows.netapp.manager.HostConnection")
570+
def test_route_service_dependency_injection(
571+
self, mock_host_connection, mock_config, mock_config_file
572+
):
573+
"""Test NetAppManager with injected RouteService dependency."""
574+
from understack_workflows.netapp.route_service import RouteService
575+
576+
# Create mock dependencies
577+
mock_client = MagicMock()
578+
mock_error_handler = MagicMock()
579+
mock_route_service = MagicMock(spec=RouteService)
580+
581+
manager = NetAppManager(
582+
config_path=mock_config_file,
583+
netapp_client=mock_client,
584+
route_service=mock_route_service,
585+
error_handler=mock_error_handler,
586+
)
587+
588+
# Verify injected route service is used
589+
assert manager._route_service is mock_route_service
590+
591+
# Test delegation works with injected service
592+
mock_route_service.create_routes_from_interfaces.return_value = []
593+
result = manager.create_routes_for_project("test-project", [])
594+
595+
mock_route_service.create_routes_from_interfaces.assert_called_once_with(
596+
"test-project", []
597+
)
598+
assert result == []

python/understack-workflows/understack_workflows/netapp/manager.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
from understack_workflows.netapp.config import NetAppConfig
1111
from understack_workflows.netapp.error_handler import ErrorHandler
1212
from understack_workflows.netapp.lif_service import LifService
13+
from understack_workflows.netapp.route_service import RouteService
1314
from understack_workflows.netapp.svm_service import SvmService
1415
from understack_workflows.netapp.value_objects import NetappIPInterfaceConfig
1516
from understack_workflows.netapp.value_objects import NodeResult
17+
from understack_workflows.netapp.value_objects import RouteResult
1618
from understack_workflows.netapp.volume_service import VolumeService
1719

1820
logger = setup_logger(__name__)
@@ -35,6 +37,7 @@ def __init__(
3537
svm_service=None,
3638
volume_service=None,
3739
lif_service=None,
40+
route_service=None,
3841
error_handler=None,
3942
):
4043
"""Initialize NetAppManager with dependency injection support.
@@ -46,6 +49,7 @@ def __init__(
4649
svm_service: SvmService instance (optional, for dependency injection)
4750
volume_service: VolumeService instance (optional, for dependency injection)
4851
lif_service: LifService instance (optional, for dependency injection)
52+
route_service: RouteService instance (optional, for dependency injection)
4953
error_handler: ErrorHandler instance (optional, for dependency injection)
5054
"""
5155
# Set up dependencies with dependency injection or create defaults
@@ -56,6 +60,7 @@ def __init__(
5660
svm_service,
5761
volume_service,
5862
lif_service,
63+
route_service,
5964
error_handler,
6065
)
6166

@@ -67,6 +72,7 @@ def _setup_dependencies(
6772
svm_service,
6873
volume_service,
6974
lif_service,
75+
route_service,
7076
error_handler,
7177
):
7278
"""Set up all service dependencies with dependency injection."""
@@ -96,6 +102,7 @@ def _setup_dependencies(
96102
and svm_service is None
97103
and volume_service is None
98104
and lif_service is None
105+
and route_service is None
99106
):
100107
# Traditional constructor usage - set up connection directly
101108
# Check if connection needs to be established (handle both real and
@@ -145,6 +152,11 @@ def _setup_dependencies(
145152
else:
146153
self._lif_service = LifService(self._client, self._error_handler)
147154

155+
if route_service is not None:
156+
self._route_service = route_service
157+
else:
158+
self._route_service = RouteService(self._client, self._error_handler)
159+
148160
def create_svm(self, project_id: str, aggregate_name: str):
149161
"""Creates a new Storage Virtual Machine (SVM)."""
150162
return self._svm_service.create_svm(project_id, aggregate_name)
@@ -447,6 +459,49 @@ def identify_home_node(self, config: NetappIPInterfaceConfig) -> NodeResult | No
447459
"""
448460
return self._lif_service.identify_home_node(config)
449461

462+
def create_routes_for_project(
463+
self,
464+
project_id: str,
465+
interface_configs: list[NetappIPInterfaceConfig],
466+
) -> list[RouteResult]:
467+
"""Create routes for a project based on interface configurations.
468+
469+
Args:
470+
project_id: The project identifier
471+
interface_configs: List of network interface configurations
472+
473+
Returns:
474+
list[RouteResult]: List of created route results
475+
476+
Raises:
477+
NetworkOperationError: If route creation fails
478+
"""
479+
logger.info(
480+
"Creating routes for project %(project_id)s with %(count)d interfaces",
481+
{"project_id": project_id, "count": len(interface_configs)},
482+
)
483+
484+
try:
485+
results = self._route_service.create_routes_from_interfaces(
486+
project_id, interface_configs
487+
)
488+
489+
logger.info(
490+
"Successfully created %(count)d routes for project %(project_id)s",
491+
{"count": len(results), "project_id": project_id},
492+
)
493+
494+
return results
495+
496+
except Exception as e:
497+
logger.error(
498+
"Failed to create routes for project %(project_id)s: %(error)s",
499+
{"project_id": project_id, "error": str(e)},
500+
)
501+
# Re-raise the exception to ensure it propagates up the call stack
502+
# This ensures route creation failures terminate script execution
503+
raise
504+
450505
def _svm_by_project(self, project_id):
451506
try:
452507
svm_name = self._svm_name(project_id)

0 commit comments

Comments
 (0)