Skip to content

Commit 33e7750

Browse files
committed
PayloadVrfsAttachments: validate extensionValues
1. plugins/module_utils/vrf/model_payload_vrfs_attachments.py Previously, this model accepted any string for extensionValues. This update provides validation and serialization for most fields within extensionValues. Some tweaking is still pending (e.g. validating DOT1Q_ID for which any string is currently accepted). Also, add copyright and module docstring 2. plugins/module_utils/vrf/transmute_diff_attach_to_payload.py - Leverage the change in 1 above. 3. tests/unit/modules/dcnm/fixtures/dcnm_vrf_12.json Update fixture to align with changes in 1 above (extensionValues should now be a dict).
1 parent 8cc7336 commit 33e7750

File tree

3 files changed

+294
-42
lines changed

3 files changed

+294
-42
lines changed

plugins/module_utils/vrf/model_payload_vrfs_attachments.py

Lines changed: 251 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,253 @@
11
# -*- coding: utf-8 -*-
2+
# @author: Allen Robel
3+
# @file: plugins/module_utils/vrf/vrf_playbook_model.py
4+
# Copyright (c) 2020-2023 Cisco and/or its affiliates.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
# pylint: disable=wrong-import-position
18+
"""
19+
Validation model for VRF attachment payload.
20+
"""
221
import json
3-
from typing import Optional
22+
from typing import Optional, Union
423

524
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
625

726
from ..common.models.ipv4_cidr_host import IPv4CidrHostModel
27+
from ..common.models.ipv4_host import IPv4HostModel
828
from ..common.models.ipv6_cidr_host import IPv6CidrHostModel
929

1030

31+
class PayloadVrfsAttachmentsLanAttachListExtensionValuesMultisiteConn(BaseModel):
32+
"""
33+
# Summary
34+
35+
Represents the multisite connection values for a single lan attach item within VrfAttachPayload.lan_attach_list.
36+
37+
# Structure
38+
39+
- MULTISITE_CONN: list, alias: MULTISITE_CONN
40+
41+
## Example
42+
43+
```json
44+
{
45+
"MULTISITE_CONN": []
46+
}
47+
}
48+
```
49+
"""
50+
51+
MULTISITE_CONN: list = Field(alias="MULTISITE_CONN", default_factory=list)
52+
53+
54+
class PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConnItem(BaseModel):
55+
"""
56+
# Summary
57+
58+
Represents a single VRF Lite connection item within VrfAttachPayload.lan_attach_list.
59+
60+
# Structure
61+
62+
- AUTO_VRF_LITE_FLAG: bool, alias: AUTO_VRF_LITE_FLAG
63+
- DOT1Q_ID: str, alias: DOT1Q_ID
64+
- IF_NAME: str, alias: IF_NAME
65+
- IP_MASK: str, alias: IP_MASK
66+
- IPV6_MASK: str, alias: IPV6_MASK
67+
- IPV6_NEIGHBOR: str, alias: IPV6_NEIGHBOR
68+
- NEIGHBOR_ASN: str, alias: NEIGHBOR_ASN
69+
- NEIGHBOR_IP: str, alias: NEIGHBOR_IP
70+
- PEER_VRF_NAME: str, alias: PEER_VRF_NAME
71+
- VRF_LITE_JYTHON_TEMPLATE: str, alias: VRF_LITE_JYTHON_TEMPLATE
72+
73+
## Example
74+
75+
```json
76+
{
77+
"AUTO_VRF_LITE_FLAG": "true",
78+
"DOT1Q_ID": "2",
79+
"IF_NAME": "Ethernet2/10",
80+
"IP_MASK": "10.33.0.2/30",
81+
"IPV6_MASK": "2010::10:34:0:7/64",
82+
"IPV6_NEIGHBOR": "2010::10:34:0:3",
83+
"NEIGHBOR_ASN": "65001",
84+
"NEIGHBOR_IP": "10.33.0.1",
85+
"PEER_VRF_NAME": "ansible-vrf-int1",
86+
"VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython"
87+
}
88+
```
89+
"""
90+
91+
AUTO_VRF_LITE_FLAG: bool = Field(alias="AUTO_VRF_LITE_FLAG", default=True)
92+
DOT1Q_ID: str = Field(alias="DOT1Q_ID")
93+
IF_NAME: str = Field(alias="IF_NAME")
94+
IP_MASK: str = Field(alias="IP_MASK", default="")
95+
IPV6_MASK: str = Field(alias="IPV6_MASK", default="")
96+
IPV6_NEIGHBOR: str = Field(alias="IPV6_NEIGHBOR", default="")
97+
NEIGHBOR_ASN: str = Field(alias="NEIGHBOR_ASN", default="")
98+
NEIGHBOR_IP: str = Field(alias="NEIGHBOR_IP", default="")
99+
PEER_VRF_NAME: str = Field(alias="PEER_VRF_NAME", default="")
100+
VRF_LITE_JYTHON_TEMPLATE: str = Field(alias="VRF_LITE_JYTHON_TEMPLATE")
101+
102+
@field_validator("IP_MASK", mode="before")
103+
@classmethod
104+
def validate_ip_mask(cls, value: str) -> str:
105+
"""
106+
Validate IP_MASK to ensure it is a valid IPv4 CIDR host address.
107+
"""
108+
if value == "":
109+
return value
110+
try:
111+
return IPv4CidrHostModel(ipv4_cidr_host=value).ipv4_cidr_host
112+
except ValueError as error:
113+
msg = f"Invalid IP_MASK: {value}. detail: {error}"
114+
raise ValueError(msg) from error
115+
116+
@field_validator("IPV6_MASK", mode="before")
117+
@classmethod
118+
def validate_ipv6_mask(cls, value: str) -> str:
119+
"""
120+
Validate IPV6_MASK to ensure it is a valid IPv6 CIDR host address.
121+
"""
122+
if value == "":
123+
return value
124+
try:
125+
return IPv6CidrHostModel(ipv6_cidr_host=value).ipv6_cidr_host
126+
except ValueError as error:
127+
msg = f"Invalid IPV6_MASK: {value}. detail: {error}"
128+
raise ValueError(msg) from error
129+
130+
@field_validator("NEIGHBOR_IP", mode="before")
131+
@classmethod
132+
def validate_neighbor_ip(cls, value: str) -> str:
133+
"""
134+
Validate NEIGHBOR_IP to ensure it is a valid IPv4 host address without prefix length.
135+
"""
136+
if value == "":
137+
return value
138+
try:
139+
return IPv4HostModel(ipv4_host=value).ipv4_host
140+
except ValueError as error:
141+
msg = f"Invalid neighbor IP address (NEIGHBOR_IP): {value}. detail: {error}"
142+
raise ValueError(msg) from error
143+
144+
145+
class PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConn(BaseModel):
146+
"""
147+
# Summary
148+
149+
Represents a list of PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConnItem.
150+
151+
# Structure
152+
153+
- VRF_LITE_CONN: list[PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConnItem], alias: VRF_LITE_CONN
154+
155+
## Example
156+
157+
```json
158+
{
159+
"VRF_LITE_CONN": [
160+
{
161+
"AUTO_VRF_LITE_FLAG": "true",
162+
"DOT1Q_ID": "2",
163+
"IF_NAME": "Ethernet2/10",
164+
"IP_MASK": "10.33.0.2/30",
165+
"IPV6_MASK": "2010::10:34:0:7/64",
166+
"IPV6_NEIGHBOR": "2010::10:34:0:3",
167+
"NEIGHBOR_ASN": "65001",
168+
"NEIGHBOR_IP": "10.33.0.1",
169+
"PEER_VRF_NAME": "ansible-vrf-int1",
170+
"VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython"
171+
}
172+
]
173+
}
174+
```
175+
"""
176+
177+
VRF_LITE_CONN: list[PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConnItem] = Field(alias="VRF_LITE_CONN", default_factory=list)
178+
179+
180+
class PayloadVrfsAttachmentsLanAttachListExtensionValues(BaseModel):
181+
"""
182+
# Summary
183+
184+
Represents the extension values for a single lan attach item within VrfAttachPayload.lan_attach_list.
185+
186+
# Structure
187+
188+
# Example
189+
190+
```json
191+
{
192+
'MULTISITE_CONN': {'MULTISITE_CONN': []},
193+
'VRF_LITE_CONN': {
194+
'VRF_LITE_CONN': [
195+
{
196+
'AUTO_VRF_LITE_FLAG': 'true',
197+
'DOT1Q_ID': '2',
198+
'IF_NAME': 'Ethernet2/10',
199+
'IP_MASK': '10.33.0.2/30',
200+
'IPV6_MASK': '2010::10:34:0:7/64',
201+
'IPV6_NEIGHBOR': '2010::10:34:0:3',
202+
'NEIGHBOR_ASN': '65001',
203+
'NEIGHBOR_IP': '10.33.0.1',
204+
'PEER_VRF_NAME': 'ansible-vrf-int1',
205+
'VRF_LITE_JYTHON_TEMPLATE': 'Ext_VRF_Lite_Jython'
206+
}
207+
]
208+
}
209+
}
210+
```
211+
"""
212+
213+
MULTISITE_CONN: PayloadVrfsAttachmentsLanAttachListExtensionValuesMultisiteConn = Field(
214+
alias="MULTISITE_CONN", default_factory=PayloadVrfsAttachmentsLanAttachListExtensionValuesMultisiteConn
215+
)
216+
VRF_LITE_CONN: PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConn = Field(
217+
alias="VRF_LITE_CONN", default_factory=PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConn
218+
)
219+
220+
@field_validator("MULTISITE_CONN", mode="before")
221+
@classmethod
222+
def preprocess_multisite_conn(cls, value: Union[str, dict]) -> Optional[PayloadVrfsAttachmentsLanAttachListExtensionValuesMultisiteConn]:
223+
"""
224+
Convert incoming data
225+
226+
- If data is a JSON string, use json.loads() to convert to a dict.
227+
- If data is a dict, return it as is.
228+
"""
229+
if isinstance(value, str):
230+
if value == "":
231+
return ""
232+
return json.loads(value)
233+
return value
234+
235+
@field_validator("VRF_LITE_CONN", mode="before")
236+
@classmethod
237+
def preprocess_vrf_lite_conn(cls, value: dict) -> Optional[PayloadVrfsAttachmentsLanAttachListExtensionValuesVrfLiteConn]:
238+
"""
239+
Convert incoming data
240+
241+
- If data is a JSON string, use json.loads() to convert to a dict.
242+
- If data is a dict, return it as is.
243+
"""
244+
if isinstance(value, str):
245+
if value == "":
246+
return ""
247+
return json.loads(value)
248+
return value
249+
250+
11251
class PayloadVrfsAttachmentsLanAttachListInstanceValues(BaseModel):
12252
"""
13253
# Summary
@@ -141,14 +381,23 @@ class PayloadVrfsAttachmentsLanAttachListItem(BaseModel):
141381
"""
142382

143383
deployment: bool = Field(alias="deployment")
144-
extension_values: Optional[str] = Field(alias="extensionValues", default="")
384+
extension_values: Optional[PayloadVrfsAttachmentsLanAttachListExtensionValues] = Field(alias="extensionValues", default="")
145385
fabric: str = Field(alias="fabric", min_length=1, max_length=64)
146386
freeform_config: Optional[str] = Field(alias="freeformConfig", default="")
147387
instance_values: Optional[PayloadVrfsAttachmentsLanAttachListInstanceValues] = Field(alias="instanceValues", default="")
148388
serial_number: str = Field(alias="serialNumber")
149389
vlan: int = Field(alias="vlan")
150390
vrf_name: str = Field(alias="vrfName", min_length=1, max_length=32)
151391

392+
@field_serializer("extension_values")
393+
def serialize_extension_values(self, value: PayloadVrfsAttachmentsLanAttachListExtensionValues) -> str:
394+
"""
395+
Serialize extension_values to a JSON string.
396+
"""
397+
if value == "":
398+
return json.dumps({}) # return empty JSON value
399+
return value.model_dump_json(by_alias=True)
400+
152401
@field_serializer("instance_values")
153402
def serialize_instance_values(self, value: PayloadVrfsAttachmentsLanAttachListInstanceValues) -> str:
154403
"""

0 commit comments

Comments
 (0)