Skip to content

FabricSwitchInventory Utillity and related dependencies#271

Open
AKDRG wants to merge 11 commits into
CiscoDevNet:developfrom
AKDRG:fabric_inventory
Open

FabricSwitchInventory Utillity and related dependencies#271
AKDRG wants to merge 11 commits into
CiscoDevNet:developfrom
AKDRG:fabric_inventory

Conversation

@AKDRG
Copy link
Copy Markdown
Collaborator

@AKDRG AKDRG commented May 6, 2026

  1. plugins/module_utils/utils.py

    ADDED:

    • Import statements:

      • logging
      • EpManageFabricsSwitchesGet from endpoints
      • NDConfigCollection
    • SwitchOperationError exception class

      • Custom exception for switch operation failures
    • ApiDataChecker class

      • Validates API responses for embedded error codes
      • Detects controller errors in DATA payloads
      • Method: check(data, context, log, fail_callback)
    • FabricSwitchInventory class

      • Indexes switch model instances for fast lookup
      • Methods:
        • init(switches)
        • from_fabric(nd, fabric, log, model_class) [classmethod]
        • by_ip() -> Dict[str, Any]
        • by_id() -> Dict[str, Any]
        • query_fabric_switches(nd, fabric, log) [staticmethod]
      • Properties:
        • switches: List of switch models
        • collection: NDConfigCollection instance
  2. plugins/module_utils/endpoints/v1/manage/manage_fabrics_switches.py

    Switch inventory endpoint definitions

    CLASSES:

    • FabricSwitchesGetEndpointParams

      • Query parameters for list switches endpoint
      • Fields: hostname, max, offset, filter
    • FabricSwitchesAddEndpointParams

      • Query parameters for add switches endpoint
      • Fields: cluster_name, ticket_id
    • _EpManageFabricsSwitchesBase

      • Base class for fabric switches endpoints
      • Provides common functionality
    • EpManageFabricsSwitchesGet

      • GET endpoint: /api/v1/manage/fabrics/{fabricName}/switches
      • Lists all switches in a fabric with optional filtering
  3. plugins/module_utils/models/manage_switches/init.py

    Package initialization

    EXPORTS:

    • SwitchDataModel
    • Enumerations: SwitchRole, SystemMode, PlatformType, SnmpV3AuthProtocol,
      DiscoveryStatus, ConfigSyncStatus, VpcRole, RemoteCredentialStore,
      AnomalyLevel, AdvisoryLevel
  4. plugins/module_utils/models/manage_switches/switch_data_models.py

    Switch inventory data models

    CLASSES:

    • TelemetryIpCollection (NDNestedModel)

      • Inband and out-of-band telemetry IP addresses
      • Fields: inband_ipv4_address, inband_ipv6_address,
        out_of_band_ipv4_address, out_of_band_ipv6_address
    • VpcData (NDNestedModel)

      • vPC pair configuration and operational status
      • Fields: vpc_domain, peer_switch_id, consistent_status,
        intended_peer_name, keep_alive_status, peer_link_status,
        peer_name, vpc_role
    • SwitchMetadata (NDNestedModel)

      • Internal database identifiers
      • Fields: switch_db_id, switch_uuid
    • AdditionalSwitchData (NDNestedModel)

      • Platform-specific data for NX-OS switches
      • Fields: usage, config_sync_status, discovery_status, domain_name,
        smart_switch, platform_type, system_mode, vendor, username, etc.
    • AdditionalAciSwitchData (NDNestedModel)

      • Platform-specific data for ACI switches
      • Fields: usage, admin_status, health_score, node_id, pod_id, etc.
    • SwitchDataModel (NDBaseModel) *** MAIN MODEL ***

      • Complete inventory record for a switch
      • Identifier: switch_id (serial number)
      • Key Fields:
        • switch_id (required)
        • serial_number
        • fabric_management_ip
        • hostname
        • model
        • software_version
        • switch_role
        • system_up_time
        • additional_data
        • vpc_configured, vpc_data
        • telemetry_ip_collection
      • Methods:
        • to_payload() -> Dict
        • from_response(response) -> SwitchDataModel [classmethod]
        • to_config_dict() -> Dict
      • Validators:
        • validate_switch_id (requires non-empty serial number)
        • validate_mgmt_ip (validates IP address format)
        • parse_additional_data (routes to correct nested model)
  5. plugins/module_utils/models/manage_switches/enums.py

    Enumerations for switch operations

    ENUMERATIONS:

    • SwitchRole

      • Values: border, borderGateway, leaf, spine, superSpine, tor,
        access, aggregation, coreRouter, edgeRouter, meta, neighbor, etc.
      • Methods: choices(), from_user_input(), normalize()
    • SystemMode

      • Values: normal, maintenance, migration, inconsistent, waiting,
        notApplicable
    • PlatformType

      • Values: nx-os, other, ios-xe, ios-xr, sonic, apic
    • SnmpV3AuthProtocol

      • Values: md5, sha, md5-des, md5-aes, sha-aes, sha-des,
        sha-aes-256, sha-224, sha-256, sha-384, sha-512, etc.
    • DiscoveryStatus

      • Values: ok, discovering, rediscovering, unreachable,
        discoveryTimeout, retrying, sshSessionError, etc.
    • ConfigSyncStatus

      • Values: deployed, deploymentInProgress, failed, inProgress,
        inSync, outOfSync, pending, success, etc.
    • VpcRole

      • Values: primary, secondary, operationalPrimary,
        operationalSecondary, noneEstablished
    • RemoteCredentialStore

      • Values: local, cyberark
    • AnomalyLevel

      • Values: critical, major, minor, warning, healthy,
        notApplicable, unknown
    • AdvisoryLevel

      • Values: critical, major, minor, warning, healthy,
        none, notApplicable
  6. plugins/module_utils/models/manage_switches/validators.py

    NEW FILE - Field validation utilities

    CLASS: SwitchValidators

    VALIDATION METHODS (nullable):

    • validate_ip_address(v) -> Optional[str]

      • Validates IPv4 or IPv6 address format
    • validate_cidr(v) -> Optional[str]

      • Validates CIDR notation (IP/mask)
    • validate_serial_number(v) -> Optional[str]

      • Validates alphanumeric serial number with optional hyphens
    • validate_hostname(v) -> Optional[str]

      • Validates RFC 1123 hostname format
    • validate_mac_address(v) -> Optional[str]

      • Validates MAC address format (colon or hyphen separated)
    • validate_vpc_domain(v) -> Optional[int]

      • Validates VPC domain ID (1-1000)

    REQUIRED FIELD HELPERS:

    • require_serial_number(v, field_name) -> str

      • Ensures non-empty serial number
    • require_hostname(v) -> str

      • Ensures non-empty hostname
    • require_ip_address(v) -> str

      • Ensures non-empty IP address
    • validate_cidr_optional(v) -> Optional[str]

      • Validates optional CIDR string
    • check_discovery_credentials_pair(username, password) -> None

      • Enforces mutual presence of username and password

================================================================================
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

    • NDConfigCollection class with from_api_response() method
  • plugins/module_utils/models/base.py

    • NDBaseModel class
  • plugins/module_utils/models/nested.py

    • NDNestedModel class
  • plugins/module_utils/common/pydantic_compat.py

    • Field, field_validator from Pydantic
  • plugins/module_utils/enums.py

    • HttpVerbEnum
  • plugins/module_utils/endpoints/base.py

    • NDEndpointBaseModel
  • plugins/module_utils/endpoints/mixins.py

    • FabricNameMixin, FilterMixin, MaxMixin, OffsetMixin, etc.
  • plugins/module_utils/endpoints/query_params.py

    • EndpointQueryParams
  • plugins/module_utils/endpoints/v1/manage/base_path.py

    • BasePath class

================================================================================
KEY FEATURES

  1. Fast Lookup

    • O(1) lookup by IP address using by_ip() dictionary
    • O(1) lookup by serial number using by_id() dictionary
  2. API Response Validation

    • ApiDataChecker detects embedded error codes in responses
    • Prevents silent failures from controller errors
  3. Flexible Model Support

    • Works with any Pydantic model class
    • SwitchDataModel provided for standard switch inventory
  4. Comprehensive Switch Data

    • Complete switch inventory information
    • vPC configuration and status
    • Telemetry IP addresses
    • Platform-specific additional data
    • ACI and NX-OS switch support
  5. Field Validation

    • IP address validation (IPv4/IPv6)
    • Serial number format validation
    • Hostname RFC 1123 compliance
    • Enumeration normalization
  6. Integration Ready

    • Compatible with NDConfigCollection
    • Works with existing ND module infrastructure
    • No breaking changes to existing code

Co-authored-by: Copilot <copilot@github.com>
@AKDRG AKDRG force-pushed the fabric_inventory branch from e08073e to e861771 Compare May 6, 2026 10:36
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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still refer to NDFC?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, good catch we need to make sure we only use ND

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines +42 to +46
if v is None:
return None
v = str(v).strip()
if not v:
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this block is repeated over multiple validation functions? should we create a helper function for this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

from typing import Optional


class SwitchValidators:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do we test the behaviour of these validators?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Unit Tests for validators.

Comment thread plugins/module_utils/enums.py Outdated
v = str(v).strip()
if not v:
return None
if "/" not in v:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider normalising a single IP to a /32?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

if not v:
return None
# Serial numbers are typically alphanumeric with optional hyphens
if not re.match(r"^[A-Za-z0-9_-]+$", v):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +94 to +97
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}")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

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})$"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants