Skip to content

Commit 1b5ec8a

Browse files
authored
Merge pull request #1194 from rackerlabs/svm-configure-routes
feat: NetApp Route Management Support
2 parents 2ddf789 + cd9363c commit 1b5ec8a

11 files changed

+2521
-25
lines changed

python/understack-workflows/tests/test_netapp_client.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for NetAppClient abstraction layer."""
22

3+
import ipaddress
34
from unittest.mock import MagicMock
45
from unittest.mock import Mock
56
from unittest.mock import patch
@@ -21,6 +22,7 @@
2122
from understack_workflows.netapp.value_objects import NodeResult
2223
from understack_workflows.netapp.value_objects import PortResult
2324
from understack_workflows.netapp.value_objects import PortSpec
25+
from understack_workflows.netapp.value_objects import RouteSpec
2426
from understack_workflows.netapp.value_objects import SvmResult
2527
from understack_workflows.netapp.value_objects import SvmSpec
2628
from understack_workflows.netapp.value_objects import VolumeResult
@@ -521,6 +523,185 @@ def test_get_namespaces_failure(
521523
with pytest.raises(NetAppManagerError):
522524
netapp_client.get_namespaces(namespace_spec)
523525

526+
@patch("understack_workflows.netapp.client.NetworkRoute")
527+
def test_create_route_success(self, mock_route_class, netapp_client):
528+
"""Test successful route creation."""
529+
mock_route_instance = MagicMock()
530+
mock_route_instance.uuid = "route-uuid-123"
531+
mock_route_class.return_value = mock_route_instance
532+
533+
route_spec = RouteSpec(
534+
svm_name="os-test-project",
535+
gateway="100.127.0.17",
536+
destination=ipaddress.IPv4Network("100.126.0.0/17"),
537+
)
538+
539+
result = netapp_client.create_route(route_spec)
540+
541+
# Verify route configuration
542+
assert mock_route_instance.svm == {"name": "os-test-project"}
543+
assert mock_route_instance.gateway == "100.127.0.17"
544+
assert mock_route_instance.destination == {
545+
"address": "100.126.0.0",
546+
"netmask": "255.255.128.0",
547+
}
548+
mock_route_instance.post.assert_called_once_with(hydrate=True)
549+
550+
# Verify result
551+
assert result.uuid == "route-uuid-123"
552+
assert result.gateway == "100.127.0.17"
553+
assert result.destination == ipaddress.IPv4Network("100.126.0.0/17")
554+
assert result.svm_name == "os-test-project"
555+
556+
@patch("understack_workflows.netapp.client.NetworkRoute")
557+
def test_create_route_netapp_rest_error(self, mock_route_class, netapp_client):
558+
"""Test route creation with NetAppRestError."""
559+
mock_route_instance = MagicMock()
560+
mock_route_instance.post.side_effect = NetAppRestError("SVM not found")
561+
mock_route_class.return_value = mock_route_instance
562+
563+
# Configure error handler to raise NetworkOperationError
564+
netapp_client._error_handler.handle_netapp_error.side_effect = (
565+
NetworkOperationError(
566+
"Route creation failed: SVM not found",
567+
interface_name="test-route",
568+
context={"svm_name": "os-nonexistent-project"},
569+
)
570+
)
571+
572+
route_spec = RouteSpec(
573+
svm_name="os-nonexistent-project",
574+
gateway="100.127.0.17",
575+
destination=ipaddress.IPv4Network("100.126.0.0/17"),
576+
)
577+
578+
with pytest.raises(NetworkOperationError):
579+
netapp_client.create_route(route_spec)
580+
581+
# Verify error handler was called with correct context
582+
netapp_client._error_handler.handle_netapp_error.assert_called_once()
583+
call_args = netapp_client._error_handler.handle_netapp_error.call_args
584+
assert isinstance(call_args[0][0], NetAppRestError)
585+
assert call_args[0][1] == "Route creation"
586+
assert call_args[0][2]["svm_name"] == "os-nonexistent-project"
587+
assert call_args[0][2]["gateway"] == "100.127.0.17"
588+
assert call_args[0][2]["destination"] == ipaddress.IPv4Network("100.126.0.0/17")
589+
590+
@patch("understack_workflows.netapp.client.NetworkRoute")
591+
def test_create_route_invalid_svm_error(self, mock_route_class, netapp_client):
592+
"""Test route creation with invalid SVM name."""
593+
mock_route_instance = MagicMock()
594+
mock_route_instance.post.side_effect = NetAppRestError(
595+
"SVM 'os-invalid-svm' does not exist"
596+
)
597+
mock_route_class.return_value = mock_route_instance
598+
599+
# Configure error handler to raise NetworkOperationError
600+
netapp_client._error_handler.handle_netapp_error.side_effect = (
601+
NetworkOperationError(
602+
"Route creation failed: SVM does not exist",
603+
interface_name="test-route",
604+
context={"svm_name": "os-invalid-svm"},
605+
)
606+
)
607+
608+
route_spec = RouteSpec(
609+
svm_name="os-invalid-svm",
610+
gateway="100.127.0.17",
611+
destination=ipaddress.IPv4Network("100.126.0.0/17"),
612+
)
613+
614+
with pytest.raises(NetworkOperationError):
615+
netapp_client.create_route(route_spec)
616+
617+
# Verify error context includes SVM information
618+
call_args = netapp_client._error_handler.handle_netapp_error.call_args
619+
assert call_args[0][2]["svm_name"] == "os-invalid-svm"
620+
621+
@patch("understack_workflows.netapp.client.NetworkRoute")
622+
def test_create_route_gateway_unreachable_error(
623+
self, mock_route_class, netapp_client
624+
):
625+
"""Test route creation with unreachable gateway."""
626+
mock_route_instance = MagicMock()
627+
mock_route_instance.post.side_effect = NetAppRestError(
628+
"Gateway 192.168.1.1 is not reachable from SVM network"
629+
)
630+
mock_route_class.return_value = mock_route_instance
631+
632+
# Configure error handler to raise NetworkOperationError
633+
netapp_client._error_handler.handle_netapp_error.side_effect = (
634+
NetworkOperationError(
635+
"Route creation failed: Gateway unreachable",
636+
interface_name="test-route",
637+
context={"gateway": "192.168.1.1"},
638+
)
639+
)
640+
641+
route_spec = RouteSpec(
642+
svm_name="os-test-project",
643+
gateway="192.168.1.1", # Invalid gateway for this network
644+
destination=ipaddress.IPv4Network("100.126.0.0/17"),
645+
)
646+
647+
with pytest.raises(NetworkOperationError):
648+
netapp_client.create_route(route_spec)
649+
650+
# Verify error context includes gateway information
651+
call_args = netapp_client._error_handler.handle_netapp_error.call_args
652+
assert call_args[0][2]["gateway"] == "192.168.1.1"
653+
assert call_args[0][2]["destination"] == ipaddress.IPv4Network("100.126.0.0/17")
654+
655+
@patch("understack_workflows.netapp.client.NetworkRoute")
656+
def test_create_route_logging_behavior(self, mock_route_class, netapp_client):
657+
"""Test route creation logging behavior."""
658+
mock_route_instance = MagicMock()
659+
mock_route_instance.uuid = "route-uuid-456"
660+
mock_route_class.return_value = mock_route_instance
661+
662+
route_spec = RouteSpec(
663+
svm_name="os-logging-test",
664+
gateway="100.127.128.17",
665+
destination=ipaddress.IPv4Network("100.126.128.0/17"),
666+
)
667+
668+
netapp_client.create_route(route_spec)
669+
670+
# Verify logging calls
671+
error_handler = netapp_client._error_handler
672+
log_info_calls = error_handler.log_info.call_args_list
673+
log_debug_calls = error_handler.log_debug.call_args_list
674+
675+
# Should have info logs for start and completion
676+
assert len(log_info_calls) >= 2
677+
678+
# Should have debug log for route creation
679+
assert len(log_debug_calls) >= 1
680+
681+
# Find the route-specific log messages
682+
route_start_logs = [
683+
call for call in log_info_calls if "Creating route:" in call[0][0]
684+
]
685+
route_completion_logs = [
686+
call
687+
for call in log_info_calls
688+
if "Route created successfully:" in call[0][0]
689+
]
690+
691+
# Verify route start log
692+
assert len(route_start_logs) == 1
693+
start_log = route_start_logs[0]
694+
assert start_log[0][1]["destination"] == ipaddress.IPv4Network(
695+
"100.126.128.0/17"
696+
)
697+
assert start_log[0][1]["gateway"] == "100.127.128.17"
698+
assert start_log[0][1]["svm_name"] == "os-logging-test"
699+
700+
# Verify route completion log
701+
assert len(route_completion_logs) == 1
702+
completion_log = route_completion_logs[0]
703+
assert completion_log[0][1]["uuid"] == "route-uuid-456"
704+
524705

525706
class TestNetAppClientInterface:
526707
"""Test cases for NetAppClientInterface abstract class."""
@@ -544,5 +725,6 @@ def test_interface_methods_are_abstract(self):
544725
"create_port",
545726
"get_nodes",
546727
"get_namespaces",
728+
"create_route",
547729
}
548730
assert abstract_methods == expected_methods

0 commit comments

Comments
 (0)