Skip to content

Commit 33d9b13

Browse files
committed
pydantic: migrate NetappIPInterfaceConfig
1 parent 9d9c3e1 commit 33d9b13

File tree

3 files changed

+137
-27
lines changed

3 files changed

+137
-27
lines changed

python/understack-workflows/tests/test_netapp_configure_net.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,10 @@ def test_main_function_initializes_netapp_manager_with_default_path(
18611861
# Mock NetAppManager
18621862
mock_netapp_manager_instance = Mock()
18631863
mock_netapp_manager_instance.create_routes_for_project.return_value = []
1864+
# Properly mock the config property with netapp_nic_slot_prefix
1865+
mock_config = Mock()
1866+
mock_config.netapp_nic_slot_prefix = "e4"
1867+
mock_netapp_manager_instance.config = mock_config
18641868
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
18651869

18661870
# Mock sys.argv with default netapp config path
@@ -1917,6 +1921,10 @@ def test_main_function_initializes_netapp_manager_with_custom_path(
19171921

19181922
# Mock NetAppManager
19191923
mock_netapp_manager_instance = Mock()
1924+
# Mock the config property to return a proper config object
1925+
mock_config = Mock()
1926+
mock_config.netapp_nic_slot_prefix = "e4"
1927+
mock_netapp_manager_instance.config = mock_config
19201928
mock_netapp_manager_instance.create_routes_for_project.return_value = []
19211929
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
19221930

python/understack-workflows/tests/test_netapp_configure_net_integration.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ def test_complete_script_execution_with_mock_nautobot_responses(
5252

5353
# Mock NetAppManager
5454
mock_netapp_manager_instance = Mock()
55+
# Mock the config property to return a proper config object
56+
mock_config = Mock()
57+
mock_config.netapp_nic_slot_prefix = "e4"
58+
mock_netapp_manager_instance.config = mock_config
5559
mock_netapp_manager_instance.create_routes_for_project.return_value = []
5660
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
5761

@@ -132,6 +136,10 @@ def test_output_format_validation_structured_data(
132136

133137
# Mock NetAppManager
134138
mock_netapp_manager_instance = Mock()
139+
# Mock the config property to return a proper config object
140+
mock_config = Mock()
141+
mock_config.netapp_nic_slot_prefix = "e4"
142+
mock_netapp_manager_instance.config = mock_config
135143
mock_netapp_manager_instance.create_routes_for_project.return_value = []
136144
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
137145

@@ -214,6 +222,10 @@ def test_exit_code_scenario_connection_error(
214222

215223
# Mock NetAppManager
216224
mock_netapp_manager_instance = Mock()
225+
# Mock the config property to return a proper config object
226+
mock_config = Mock()
227+
mock_config.netapp_nic_slot_prefix = "e4"
228+
mock_netapp_manager_instance.config = mock_config
217229
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
218230

219231
# Mock Nautobot client to raise connection error
@@ -260,6 +272,10 @@ def test_exit_code_scenario_graphql_error(
260272

261273
# Mock NetAppManager
262274
mock_netapp_manager_instance = Mock()
275+
# Mock the config property to return a proper config object
276+
mock_config = Mock()
277+
mock_config.netapp_nic_slot_prefix = "e4"
278+
mock_netapp_manager_instance.config = mock_config
263279
mock_netapp_manager_instance.create_routes_for_project.return_value = []
264280
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
265281

@@ -311,6 +327,10 @@ def test_exit_code_scenario_data_validation_error(
311327

312328
# Mock NetAppManager
313329
mock_netapp_manager_instance = Mock()
330+
# Mock the config property to return a proper config object
331+
mock_config = Mock()
332+
mock_config.netapp_nic_slot_prefix = "e4"
333+
mock_netapp_manager_instance.config = mock_config
314334
mock_netapp_manager_instance.create_routes_for_project.return_value = []
315335
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
316336

@@ -360,6 +380,10 @@ def test_exit_code_scenario_success_with_empty_results(
360380

361381
# Mock NetAppManager
362382
mock_netapp_manager_instance = Mock()
383+
# Mock the config property to return a proper config object
384+
mock_config = Mock()
385+
mock_config.netapp_nic_slot_prefix = "e4"
386+
mock_netapp_manager_instance.config = mock_config
363387
mock_netapp_manager_instance.create_routes_for_project.return_value = []
364388
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
365389

@@ -478,6 +502,10 @@ def test_end_to_end_workflow_with_various_input_combinations(
478502

479503
# Mock NetAppManager
480504
mock_netapp_manager_instance = Mock()
505+
# Mock the config property to return a proper config object
506+
mock_config = Mock()
507+
mock_config.netapp_nic_slot_prefix = "e4"
508+
mock_netapp_manager_instance.config = mock_config
481509
mock_netapp_manager_instance.create_routes_for_project.return_value = []
482510
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
483511

@@ -562,6 +590,10 @@ def test_complete_script_execution_with_netapp_interface_creation(
562590

563591
# Mock NetAppManager
564592
mock_netapp_manager_instance = Mock()
593+
# Mock the config property to return a proper config object
594+
mock_config = Mock()
595+
mock_config.netapp_nic_slot_prefix = "e4"
596+
mock_netapp_manager_instance.config = mock_config
565597
mock_netapp_manager_instance.create_routes_for_project.return_value = []
566598
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
567599

@@ -641,6 +673,10 @@ def test_script_execution_with_custom_netapp_config_path(
641673

642674
# Mock NetAppManager
643675
mock_netapp_manager_instance = Mock()
676+
# Mock the config property to return a proper config object
677+
mock_config = Mock()
678+
mock_config.netapp_nic_slot_prefix = "e4"
679+
mock_netapp_manager_instance.config = mock_config
644680
mock_netapp_manager_instance.create_routes_for_project.return_value = []
645681
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
646682

@@ -713,6 +749,10 @@ def test_script_handles_netapp_lif_creation_error(
713749

714750
# Mock NetAppManager that raises exception during LIF creation
715751
mock_netapp_manager_instance = Mock()
752+
# Mock the config property to return a proper config object
753+
mock_config = Mock()
754+
mock_config.netapp_nic_slot_prefix = "e4"
755+
mock_netapp_manager_instance.config = mock_config
716756
mock_netapp_manager_instance.create_lif.side_effect = Exception(
717757
"SVM not found for project"
718758
)
@@ -779,6 +819,10 @@ def test_script_execution_with_empty_vm_results_skips_netapp_creation(
779819

780820
# Mock NetAppManager
781821
mock_netapp_manager_instance = Mock()
822+
# Mock the config property to return a proper config object
823+
mock_config = Mock()
824+
mock_config.netapp_nic_slot_prefix = "e4"
825+
mock_netapp_manager_instance.config = mock_config
782826
mock_netapp_manager_instance.create_routes_for_project.return_value = []
783827
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
784828

@@ -852,6 +896,10 @@ def test_end_to_end_netapp_interface_creation_with_realistic_data(
852896

853897
# Mock NetAppManager
854898
mock_netapp_manager_instance = Mock()
899+
# Mock the config property to return a proper config object
900+
mock_config = Mock()
901+
mock_config.netapp_nic_slot_prefix = "e4"
902+
mock_netapp_manager_instance.config = mock_config
855903
mock_netapp_manager_instance.create_routes_for_project.return_value = []
856904
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
857905

@@ -951,6 +999,10 @@ def test_complete_route_creation_workflow_with_complex_sample(
951999

9521000
# Mock NetAppManager with route creation results
9531001
mock_netapp_manager_instance = Mock()
1002+
# Mock the config property to return a proper config object
1003+
mock_config = Mock()
1004+
mock_config.netapp_nic_slot_prefix = "e4"
1005+
mock_netapp_manager_instance.config = mock_config
9541006
expected_route_results = [
9551007
RouteResult(
9561008
uuid="route-uuid-1",
@@ -1051,6 +1103,10 @@ def test_route_creation_with_mocked_netapp_sdk(
10511103

10521104
# Mock NetAppManager with route creation results
10531105
mock_netapp_manager_instance = Mock()
1106+
# Mock the config property to return a proper config object
1107+
mock_config = Mock()
1108+
mock_config.netapp_nic_slot_prefix = "e4"
1109+
mock_netapp_manager_instance.config = mock_config
10541110
expected_route_results = [
10551111
RouteResult(
10561112
uuid="route-uuid-1",
@@ -1128,6 +1184,10 @@ def test_route_creation_error_propagation(
11281184

11291185
# Mock NetAppManager with route creation failure
11301186
mock_netapp_manager_instance = Mock()
1187+
# Mock the config property to return a proper config object
1188+
mock_config = Mock()
1189+
mock_config.netapp_nic_slot_prefix = "e4"
1190+
mock_netapp_manager_instance.config = mock_config
11311191
mock_netapp_manager_instance.create_routes_for_project.side_effect = Exception(
11321192
"Route creation failed: SVM not found"
11331193
)
@@ -1188,6 +1248,10 @@ def test_netapp_rest_error_handling_during_route_creation(
11881248

11891249
# Mock NetAppManager with NetworkOperationError (which wraps NetAppRestError)
11901250
mock_netapp_manager_instance = Mock()
1251+
# Mock the config property to return a proper config object
1252+
mock_config = Mock()
1253+
mock_config.netapp_nic_slot_prefix = "e4"
1254+
mock_netapp_manager_instance.config = mock_config
11911255
mock_netapp_manager_instance.create_routes_for_project.side_effect = (
11921256
NetworkOperationError(
11931257
"Route creation failed: NetApp REST API error",
@@ -1256,6 +1320,10 @@ def test_script_termination_on_route_creation_failure(
12561320

12571321
# Mock NetAppManager with route creation failure that should terminate script
12581322
mock_netapp_manager_instance = Mock()
1323+
# Mock the config property to return a proper config object
1324+
mock_config = Mock()
1325+
mock_config.netapp_nic_slot_prefix = "e4"
1326+
mock_netapp_manager_instance.config = mock_config
12591327
mock_netapp_manager_instance.create_routes_for_project.side_effect = Exception(
12601328
"Critical route creation failure: Unable to connect to NetApp system"
12611329
)
@@ -1331,6 +1399,10 @@ def test_error_logging_and_context_for_route_failures(
13311399
},
13321400
)
13331401
mock_netapp_manager_instance = Mock()
1402+
# Mock the config property to return a proper config object
1403+
mock_config = Mock()
1404+
mock_config.netapp_nic_slot_prefix = "e4"
1405+
mock_netapp_manager_instance.config = mock_config
13341406
mock_netapp_manager_instance.create_routes_for_project.side_effect = (
13351407
detailed_error
13361408
)
@@ -1406,6 +1478,10 @@ def test_invalid_svm_name_handling_during_route_creation(
14061478
},
14071479
)
14081480
mock_netapp_manager_instance = Mock()
1481+
# Mock the config property to return a proper config object
1482+
mock_config = Mock()
1483+
mock_config.netapp_nic_slot_prefix = "e4"
1484+
mock_netapp_manager_instance.config = mock_config
14091485
mock_netapp_manager_instance.create_routes_for_project.side_effect = svm_error
14101486
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
14111487

@@ -1466,6 +1542,10 @@ def test_route_creation_logging_verification(
14661542

14671543
# Mock NetAppManager with successful route creation
14681544
mock_netapp_manager_instance = Mock()
1545+
# Mock the config property to return a proper config object
1546+
mock_config = Mock()
1547+
mock_config.netapp_nic_slot_prefix = "e4"
1548+
mock_netapp_manager_instance.config = mock_config
14691549
expected_route_results = [
14701550
RouteResult(
14711551
uuid="route-uuid-1",
@@ -1545,6 +1625,10 @@ def test_route_creation_with_single_interface_sample(
15451625

15461626
# Mock NetAppManager with route creation results
15471627
mock_netapp_manager_instance = Mock()
1628+
# Mock the config property to return a proper config object
1629+
mock_config = Mock()
1630+
mock_config.netapp_nic_slot_prefix = "e4"
1631+
mock_netapp_manager_instance.config = mock_config
15481632
expected_route_results = [
15491633
RouteResult(
15501634
uuid="route-uuid-1",
@@ -1620,6 +1704,10 @@ def test_route_creation_with_empty_results_skips_route_creation(
16201704

16211705
# Mock NetAppManager
16221706
mock_netapp_manager_instance = Mock()
1707+
# Mock the config property to return a proper config object
1708+
mock_config = Mock()
1709+
mock_config.netapp_nic_slot_prefix = "e4"
1710+
mock_netapp_manager_instance.config = mock_config
16231711
mock_netapp_manager_class.return_value = mock_netapp_manager_instance
16241712

16251713
# Mock sys.argv for argument parsing

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

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"""
77

88
import ipaddress
9-
from dataclasses import dataclass, field
10-
from functools import cached_property
119
from typing import TYPE_CHECKING
1210

1311
from pydantic import BaseModel
@@ -105,27 +103,37 @@ def from_graphql_vm(cls, vm_data: dict) -> "VirtualMachineNetworkInfo":
105103
return cls(interfaces=interfaces)
106104

107105

108-
@dataclass
109-
class NetappIPInterfaceConfig:
106+
class NetappIPInterfaceConfig(BaseModel):
110107
"""Configuration for NetApp IP interface creation."""
111108

112109
name: str
113-
address: ipaddress.IPv4Address
114-
network: ipaddress.IPv4Network
110+
address: IPv4Address
111+
network: IPv4Network
115112
vlan_id: int
116113
nic_slot_prefix: str = "e4"
117114

115+
@field_validator("vlan_id")
116+
@classmethod
117+
def validate_vlan_id(cls, v):
118+
"""Validate VLAN ID is in valid range (1-4094)."""
119+
if not 1 <= v <= 4094:
120+
raise ValueError("VLAN ID must be between 1 and 4094")
121+
return v
122+
118123
def netmask_long(self):
119124
return self.network.netmask
120125

121-
@cached_property
122-
def side(self):
126+
@computed_field
127+
@property
128+
def side(self) -> str:
129+
"""Extract side (A or B) from interface name."""
123130
last_character = self.name[-1].upper()
124131
if last_character in ["A", "B"]:
125132
return last_character
126-
raise ValueError("Cannot determine side from interface %s", self.name)
133+
raise ValueError(f"Cannot determine side from interface {self.name}")
127134

128-
@cached_property
135+
@computed_field
136+
@property
129137
def desired_node_number(self) -> int:
130138
"""Node index in the cluster.
131139
@@ -138,7 +146,25 @@ def desired_node_number(self) -> int:
138146
elif name_part == "N2":
139147
return 2
140148
else:
141-
raise ValueError("Cannot determine node index from name %s", self.name)
149+
raise ValueError(f"Cannot determine node index from name {self.name}")
150+
151+
@computed_field
152+
@property
153+
def base_port_name(self) -> str:
154+
"""Get the base port name using the configured NIC slot prefix."""
155+
return f"{self.nic_slot_prefix}{self.side.lower()}"
156+
157+
@computed_field
158+
@property
159+
def broadcast_domain_name(self) -> str:
160+
"""Get the broadcast domain name based on the side."""
161+
return f"Fabric-{self.side}"
162+
163+
@computed_field
164+
@property
165+
def route_nexthop(self) -> IPv4Address:
166+
"""Calculate next hop for the static route to reach the clients."""
167+
return IPv4Address(str(next(self.network.hosts())))
142168

143169
@classmethod
144170
def from_nautobot_response(
@@ -160,31 +186,19 @@ def from_nautobot_response(
160186
result = []
161187
for interface in response.interfaces:
162188
address, _ = interface.address.split("/")
189+
# Create network with strict=False to handle host addresses
190+
network = IPv4Network(interface.address, strict=False)
163191
result.append(
164192
NetappIPInterfaceConfig(
165193
name=interface.name,
166-
address=ipaddress.IPv4Address(address),
167-
network=ipaddress.IPv4Network(interface.address, strict=False),
194+
address=address, # Pydantic will handle IPv4Address conversion
195+
network=network,
168196
vlan_id=interface.vlan,
169197
nic_slot_prefix=nic_slot_prefix,
170198
)
171199
)
172200
return result
173201

174-
@cached_property
175-
def base_port_name(self):
176-
"""Get the base port name using the configured NIC slot prefix."""
177-
return f"{self.nic_slot_prefix}{self.side.lower()}"
178-
179-
@cached_property
180-
def broadcast_domain_name(self):
181-
return f"Fabric-{self.side}"
182-
183-
@cached_property
184-
def route_nexthop(self):
185-
"""Calculate next hop for the static route to reach the clients."""
186-
return next(self.network.hosts())
187-
188202

189203
# Specification Value Objects
190204

0 commit comments

Comments
 (0)