FabricSwitchInventory Utillity and related dependencies#271
Conversation
Co-authored-by: Copilot <copilot@github.com>
| def parse_additional_data(cls, v: Any) -> Any: | ||
| """Route additionalData to the correct nested model. | ||
|
|
||
| The NDFC API may omit the ``usage`` field for non-ACI switches. |
There was a problem hiding this comment.
do we still refer to NDFC?
There was a problem hiding this comment.
No, good catch we need to make sure we only use ND
| if v is None: | ||
| return None | ||
| v = str(v).strip() | ||
| if not v: | ||
| return None |
There was a problem hiding this comment.
this block is repeated over multiple validation functions? should we create a helper function for this?
| from typing import Optional | ||
|
|
||
|
|
||
| class SwitchValidators: |
There was a problem hiding this comment.
how do we test the behaviour of these validators?
There was a problem hiding this comment.
Added Unit Tests for validators.
| v = str(v).strip() | ||
| if not v: | ||
| return None | ||
| if "/" not in v: |
There was a problem hiding this comment.
Did you consider normalising a single IP to a /32?
| if not v: | ||
| return None | ||
| # Serial numbers are typically alphanumeric with optional hyphens | ||
| if not re.match(r"^[A-Za-z0-9_-]+$", v): |
There was a problem hiding this comment.
Is there a place in the repository/collection to define re-usable regex statements? I see regex statements defined throughout various PRs, where re-use might be possible for thing like serial numbers, hostnames.
| if not re.match(r"^[a-zA-Z0-9][a-zA-Z0-9._-]*$", v): | ||
| raise ValueError(f"Invalid hostname format. Must start with alphanumeric and " f"contain only alphanumeric, dots, hyphens, underscores: {v}") | ||
| if v.startswith(".") or v.endswith(".") or ".." in v: | ||
| raise ValueError(f"Invalid hostname format (dots): {v}") |
There was a problem hiding this comment.
these could be combined, is there a reason to separate? also the v.startswith(".") check is redundant because the first regex (^[a-zA-Z0-9]) makes a leading . impossible.
is there a reason for " f" in the first ValueError?
| if not v: | ||
| return None | ||
| # Accept colon or hyphen separated MAC addresses | ||
| mac_pattern = r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" |
There was a problem hiding this comment.
is this the only mac address pattern we accept? aabb.ccdd.eeff or bare-hex should also classify as valid mac addresses in my opinion.
we could leverage something like netaddr dependency or extend the regex
There was a problem hiding this comment.
The validators was originally designed to pick in the data, and hence the MAC only from the ND. It has now been extended to include the suggested patterns as well. Thanks!
| from typing import Optional | ||
|
|
||
|
|
||
| class SwitchValidators: |
There was a problem hiding this comment.
if only static methods why define the class? can't you simply import the function that you require?
if we want to have re-usable validators (ip, mac, hostname, serial, etc ) should we consider these to be designed with re-use throughout modules?
There was a problem hiding this comment.
You're right. Fixed.
…fabric_inventory.py
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
plugins/module_utils/utils.py
ADDED:
Import statements:
SwitchOperationError exception class
ApiDataChecker class
FabricSwitchInventory class
plugins/module_utils/endpoints/v1/manage/manage_fabrics_switches.py
Switch inventory endpoint definitions
CLASSES:
FabricSwitchesGetEndpointParams
FabricSwitchesAddEndpointParams
_EpManageFabricsSwitchesBase
EpManageFabricsSwitchesGet
plugins/module_utils/models/manage_switches/init.py
Package initialization
EXPORTS:
DiscoveryStatus, ConfigSyncStatus, VpcRole, RemoteCredentialStore,
AnomalyLevel, AdvisoryLevel
plugins/module_utils/models/manage_switches/switch_data_models.py
Switch inventory data models
CLASSES:
TelemetryIpCollection (NDNestedModel)
out_of_band_ipv4_address, out_of_band_ipv6_address
VpcData (NDNestedModel)
intended_peer_name, keep_alive_status, peer_link_status,
peer_name, vpc_role
SwitchMetadata (NDNestedModel)
AdditionalSwitchData (NDNestedModel)
smart_switch, platform_type, system_mode, vendor, username, etc.
AdditionalAciSwitchData (NDNestedModel)
SwitchDataModel (NDBaseModel) *** MAIN MODEL ***
plugins/module_utils/models/manage_switches/enums.py
Enumerations for switch operations
ENUMERATIONS:
SwitchRole
access, aggregation, coreRouter, edgeRouter, meta, neighbor, etc.
SystemMode
notApplicable
PlatformType
SnmpV3AuthProtocol
sha-aes-256, sha-224, sha-256, sha-384, sha-512, etc.
DiscoveryStatus
discoveryTimeout, retrying, sshSessionError, etc.
ConfigSyncStatus
inSync, outOfSync, pending, success, etc.
VpcRole
operationalSecondary, noneEstablished
RemoteCredentialStore
AnomalyLevel
notApplicable, unknown
AdvisoryLevel
none, notApplicable
plugins/module_utils/models/manage_switches/validators.py
NEW FILE - Field validation utilities
CLASS: SwitchValidators
VALIDATION METHODS (nullable):
validate_ip_address(v) -> Optional[str]
validate_cidr(v) -> Optional[str]
validate_serial_number(v) -> Optional[str]
validate_hostname(v) -> Optional[str]
validate_mac_address(v) -> Optional[str]
validate_vpc_domain(v) -> Optional[int]
REQUIRED FIELD HELPERS:
require_serial_number(v, field_name) -> str
require_hostname(v) -> str
require_ip_address(v) -> str
validate_cidr_optional(v) -> Optional[str]
check_discovery_credentials_pair(username, password) -> None
================================================================================
USAGE EXAMPLE
from ansible_collections.cisco.nd.plugins.module_utils.utils import (
FabricSwitchInventory,
ApiDataChecker,
SwitchOperationError,
)
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_switches import (
SwitchDataModel,
)
Fetch and index all switches in a fabric
inventory = FabricSwitchInventory.from_fabric(
nd=nd_module,
fabric="MyFabric",
log=logger,
model_class=SwitchDataModel
)
Lookup by IP address
switch = inventory.by_ip().get("192.168.1.1")
if switch:
print(f"Found switch: {switch.hostname}")
Lookup by serial number (switch ID)
switch = inventory.by_id().get("FDO12345ABC")
if switch:
print(f"Switch IP: {switch.fabric_management_ip}")
Access all switches
all_switches = list(inventory.collection)
for switch in all_switches:
print(f"{switch.hostname} - {switch.fabric_management_ip}")
Access the NDConfigCollection directly
collection = inventory.collection
existing_switch = collection.get("FDO12345ABC")
================================================================================
DEPENDENCIES
REQUIRED (already exist in ND_Changes):
plugins/module_utils/nd_config_collection.py
plugins/module_utils/models/base.py
plugins/module_utils/models/nested.py
plugins/module_utils/common/pydantic_compat.py
plugins/module_utils/enums.py
plugins/module_utils/endpoints/base.py
plugins/module_utils/endpoints/mixins.py
plugins/module_utils/endpoints/query_params.py
plugins/module_utils/endpoints/v1/manage/base_path.py
================================================================================
KEY FEATURES
Fast Lookup
API Response Validation
Flexible Model Support
Comprehensive Switch Data
Field Validation
Integration Ready