Ansible ND 4.X | Resource Manager Module with Pydantic Models + Smart Endpoints#274
Ansible ND 4.X | Resource Manager Module with Pydantic Models + Smart Endpoints#274jeetugangwar11 wants to merge 46 commits into
Conversation
Co-authored-by: Copilot <copilot@github.com>
…fabric_inventory.py
…esource_manager_resources.py also removed Dict to dict and List to list
…l development time logging
There was a problem hiding this comment.
Can you check if this file is used? switch_data_models.py functions are being called instead of functions from this file
There was a problem hiding this comment.
Yes @sivakasi-cisco, You are right. I added this file initially while I was having implementation to make inventory call. As this impl has been moved to utils, I removed deleted it now. Thanks for highlighting it.
| - name: ASSERT - Check condition | ||
| ansible.builtin.assert: | ||
| that: | ||
| # TODO(Jeet): result.changed should be false and merged should be 0 but due to controller api is not creating a resources, hence accepting 1 for now. This needs to be changed in future. |
There was a problem hiding this comment.
Can you explain what the idempotence issue here?
There was a problem hiding this comment.
Due to the ND create resource API issue, we are receiving a 200 OK response for VPC pair resource creation, but the resource is not actually being created. When the test cases attempt to create the same resource to check if the module is idempotent, the module makes a call to ND, retrieves resources from there, and finds that one resource is missing in ND. It incorrectly tries to set change=true, which is wrong; it should be false since we are trying to create the same resource. This has been highlighted, and Mike has discussed it with the API owners. They will fix it in the coming days, so I have added a TODO to address it later once we have the changes from ND in place.
Description:
This PR adds the nd_manage_resource_manager module for resource allocation management in the cisco.nd collection, including the core resource handler, endpoint wrappers, Pydantic validation/response models, and shared constants.
What's Included:
Resource handler — [nd_manage_resource_manager_resources.py] supports gathered, merged and deleted states.
The module pre-fetches all existing resources and switches at init time, translates playbook switch management IPs to switchId serial numbers automatically, and uses a single-batch POST for creates and a POST to actions/remove for deletes.
A ResourceManagerDiffEngine stateless helper class handles entity-name normalisation (tilde-order-insensitive), IPv4/IPv6-aware value comparison, to_add/to_update/to_delete/idempotent diff classification, and partial-match mismatch diagnostics (same entity_name, differing pool_name/scope_type/switch) logged to the debugs output bucket.
Endpoint Definitions:
Three endpoint classes in manage_fabrics_resources.py:
EpManageFabricResourcesGet — GET /api/v1/manage/fabrics/{fabricName}/resources — supports switchId, poolName query params and Lucene-style filter/max/offset/sort.
EpManageFabricResourcesPost — POST /api/v1/manage/fabrics/{fabricName}/resources — batch resource allocation; optional tenantName query param.
EpManageFabricResourcesActionsRemovePost — POST /api/v1/manage/fabrics/{fabricName}/resources/actions/remove — batch release of allocated resource IDs.
One additional endpoint in manage_fabrics_switches.py:
EpManageFabricSwitchesGet — GET /api/v1/manage/fabrics/{fabricName}/switches — used at init to build the fabricManagementIp to switchId translation map.
Pydantic Model Layer:
Model files in plugins/module_utils/models/manage_resource_manager/:
resource_manager_config_model.py — ResourceManagerConfigModel: validates a single playbook config entry including per-field normalisation (pool_type uppercase, scope_type underscore form, IPv4/v6/CIDR resource value), cross-field rules (pre-allocated requires resource, switch required for non-fabric scopes, pool_name/scope_type compatibility via POOL_SCOPE_MAP), and state-aware context validation.
resource_manager_request_model.py — ResourceManagerRequest / ResourceManagerBatchRequest: POST body models; includes five discriminated scope models (FabricScope, DeviceScope, DeviceInterfaceScope, DevicePairScope, LinkScope).
resource_manager_response_model.py — ResourceManagerResponse / ResourcesManagerBatchResponse: parse GET and batch POST responses; reuse the same scope union models.
remove_resource_by_id_request_model.py — RemoveResourcesByIdsRequest: {"resourceIds": [...]} body for the actions/remove endpoint.
remove_resource_by_id_response_model.py — RemoveResourcesByIdsResponse / RemoveResourcesByIdResponse: parse the per-item removal result list.
switchs_response_model.py — GetAllSwitchesResponse / SwitchRecord: parse the switches GET response used for IP-to-serial translation.
resource_validators.py — ResourceValidators: shared static helpers for IP address, CIDR, and range validation reused across models.
constants.py — POOL_SCOPE_MAP, SCOPE_TYPE_TO_API, API_SCOPE_TYPE_TO_PLAYBOOK, PoolType, ScopeType, VlanType enums.
Input aliasing:
switch_id is aliased as switch_ip in the argument spec, allowing users to specify either name interchangeably. Pydantic validators normalize both to the field expected by the API.
Merged State:
Merged state — manage_merged uses ResourceManagerDiffEngine.compute_changes to classify each proposed resource as to_add (not in fabric) or to_update (value differs).
Idempotent resources (already matching) are skipped. All pending creates — both new and value-changed — are sent in a single batch POST to resources.
Each item in the batch response is then validated against the corresponding playbook config via validate_resource_api_fields, which performs tilde-order-insensitive entity name comparison and IPv4/v6-aware resource value comparison.
Resources not listed in config are left untouched. Check mode records what would be created without issuing any API calls.
Phase 1 — Delete: orphan resources (present in fabric but absent from config, to_delete bucket) and old values of changed resources (to_update bucket) are collected by integer resourceId and removed in a single batch POST to resources/actions/remove.
Phase 2 — Create: new resources (to_add) and reissued resources with new values (to_update) are created in a single batch POST to resources. The API response is validated per-item via validate_resource_api_fields. Resources whose value already matches the desired config (idempotent) are left untouched. Check mode logs what would be deleted and created without issuing any API calls.
Delete State:
Delete state — manage_deleted uses ResourceManagerDiffEngine.compute_changes to identify which playbook-listed resources are present in the fabric (idempotent or to_update buckets).
Only resources explicitly listed in config are removed; unrelated existing resources are left untouched, matching the ND nd_rm_get_diff_deleted() behaviour.
Deletion is a single batch POST to resources/actions/remove with the collected integer resourceId list. Resources not found in the fabric are silently skipped — the operation is idempotent.
Gathered State:
Gathered state — manage_gathered returns resources in the playbook config format (entity_name, pool_type, pool_name, scope_type, resource, and optionally switch).
When config is omitted, all fabric resources are returned. When config is provided, each entry acts as a filter on entity_name, pool_name, and switch (matched by switchId); a resource must satisfy every non-None criterion to be included, and deduplication by resourceId prevents a resource from appearing twice when matched by multiple filter entries.
The pool_type field (ID/IP/SUBNET) is inferred from the raw resource value by attempting ip_network then ip_address parsing. Multi-switch resources sharing the same (entity_name, pool_name, pool_type, scope_type, resource) key are merged into a single entry with a consolidated switch list.
The gathered output can be fed back directly into state: merged without modification.