nac-test-pyats-common 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nac_test_pyats_common/__init__.py +3 -0
- nac_test_pyats_common/aci/__init__.py +3 -0
- nac_test_pyats_common/aci/auth.py +8 -2
- nac_test_pyats_common/aci/test_base.py +12 -3
- nac_test_pyats_common/catc/__init__.py +3 -0
- nac_test_pyats_common/catc/auth.py +18 -6
- nac_test_pyats_common/catc/test_base.py +12 -3
- nac_test_pyats_common/common/__init__.py +3 -0
- nac_test_pyats_common/common/base_device_resolver.py +13 -4
- nac_test_pyats_common/iosxe/__init__.py +3 -0
- nac_test_pyats_common/iosxe/catc_resolver.py +3 -0
- nac_test_pyats_common/iosxe/iosxe_resolver.py +3 -0
- nac_test_pyats_common/iosxe/registry.py +15 -5
- nac_test_pyats_common/iosxe/test_base.py +6 -1
- nac_test_pyats_common/sdwan/__init__.py +5 -1
- nac_test_pyats_common/sdwan/api_test_base.py +25 -15
- nac_test_pyats_common/sdwan/auth.py +17 -6
- nac_test_pyats_common/sdwan/device_resolver.py +6 -1
- nac_test_pyats_common/sdwan/ssh_test_base.py +9 -2
- {nac_test_pyats_common-0.1.0.dist-info → nac_test_pyats_common-0.1.1.dist-info}/METADATA +2 -1
- nac_test_pyats_common-0.1.1.dist-info/RECORD +24 -0
- nac_test_pyats_common-0.1.0.dist-info/RECORD +0 -24
- {nac_test_pyats_common-0.1.0.dist-info → nac_test_pyats_common-0.1.1.dist-info}/WHEEL +0 -0
- {nac_test_pyats_common-0.1.0.dist-info → nac_test_pyats_common-0.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""APIC authentication module for Cisco ACI (Application Centric Infrastructure).
|
|
2
5
|
|
|
3
6
|
This module provides authentication functionality for Cisco APIC (Application Policy
|
|
@@ -14,7 +17,9 @@ re-authenticating when necessary, reducing unnecessary API calls to the APIC con
|
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
19
|
import httpx
|
|
17
|
-
from nac_test.pyats_core.common.auth_cache import
|
|
20
|
+
from nac_test.pyats_core.common.auth_cache import (
|
|
21
|
+
AuthCache, # type: ignore[import-untyped]
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
# Default token lifetime for APIC authentication tokens in seconds
|
|
20
25
|
# APIC tokens are typically valid for 10 minutes (600 seconds) by default
|
|
@@ -82,7 +87,8 @@ class APICAuth:
|
|
|
82
87
|
response.raise_for_status()
|
|
83
88
|
|
|
84
89
|
# Parse the APIC response and extract the token
|
|
85
|
-
# Response structure:
|
|
90
|
+
# Response structure:
|
|
91
|
+
# {"imdata": [{"aaaLogin": {"attributes": {"token": "..."}}}]}
|
|
86
92
|
try:
|
|
87
93
|
response_data = response.json()
|
|
88
94
|
token = response_data["imdata"][0]["aaaLogin"]["attributes"]["token"]
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""APIC-specific base test class for ACI API testing.
|
|
2
5
|
|
|
3
6
|
This module provides the APICTestBase class, which extends the generic NACTestBase
|
|
@@ -13,7 +16,9 @@ import asyncio
|
|
|
13
16
|
from typing import Any
|
|
14
17
|
|
|
15
18
|
import httpx
|
|
16
|
-
from nac_test.pyats_core.common.base_test import
|
|
19
|
+
from nac_test.pyats_core.common.base_test import (
|
|
20
|
+
NACTestBase, # type: ignore[import-untyped]
|
|
21
|
+
)
|
|
17
22
|
from pyats import aetest # type: ignore[import-untyped]
|
|
18
23
|
|
|
19
24
|
from .auth import APICAuth
|
|
@@ -69,7 +74,9 @@ class APICTestBase(NACTestBase): # type: ignore[misc]
|
|
|
69
74
|
super().setup()
|
|
70
75
|
|
|
71
76
|
# Get shared APIC token using file-based locking
|
|
72
|
-
self.token = APICAuth.get_token(
|
|
77
|
+
self.token = APICAuth.get_token(
|
|
78
|
+
self.controller_url, self.username, self.password
|
|
79
|
+
)
|
|
73
80
|
|
|
74
81
|
# Store the APIC client for use in verification methods
|
|
75
82
|
self.client = self.get_apic_client()
|
|
@@ -94,7 +101,9 @@ class APICTestBase(NACTestBase): # type: ignore[misc]
|
|
|
94
101
|
"""
|
|
95
102
|
headers = {"Cookie": f"APIC-cookie={self.token}"}
|
|
96
103
|
# SSL verification disabled for lab environment compatibility
|
|
97
|
-
client = self.pool.get_client(
|
|
104
|
+
client = self.pool.get_client(
|
|
105
|
+
base_url=self.controller_url, headers=headers, verify=False
|
|
106
|
+
)
|
|
98
107
|
|
|
99
108
|
# Use the generic tracking wrapper from base class
|
|
100
109
|
return self.wrap_client_for_tracking(client, device_name="APIC") # type: ignore[no-any-return]
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""Catalyst Center-specific authentication implementation.
|
|
2
5
|
|
|
3
6
|
This module provides authentication functionality for Cisco Catalyst Center
|
|
@@ -5,7 +8,8 @@ This module provides authentication functionality for Cisco Catalyst Center
|
|
|
5
8
|
networks. The authentication mechanism uses token-based login with Basic Auth.
|
|
6
9
|
|
|
7
10
|
The module implements a two-tier API design:
|
|
8
|
-
1. _authenticate() - Low-level method that performs direct Catalyst Center
|
|
11
|
+
1. _authenticate() - Low-level method that performs direct Catalyst Center
|
|
12
|
+
authentication
|
|
9
13
|
2. get_auth() - High-level method that leverages caching for efficient token reuse
|
|
10
14
|
|
|
11
15
|
This design ensures efficient token management by reusing valid tokens and only
|
|
@@ -16,7 +20,9 @@ import os
|
|
|
16
20
|
from typing import Any
|
|
17
21
|
|
|
18
22
|
import httpx
|
|
19
|
-
from nac_test.pyats_core.common.auth_cache import
|
|
23
|
+
from nac_test.pyats_core.common.auth_cache import (
|
|
24
|
+
AuthCache, # type: ignore[import-untyped]
|
|
25
|
+
)
|
|
20
26
|
|
|
21
27
|
# Default token lifetime for Catalyst Center authentication in seconds
|
|
22
28
|
# Catalyst Center tokens are typically valid for 1 hour (3600 seconds) by default
|
|
@@ -96,7 +102,9 @@ class CatalystCenterAuth:
|
|
|
96
102
|
"""
|
|
97
103
|
last_error: Exception | None = None
|
|
98
104
|
|
|
99
|
-
with httpx.Client(
|
|
105
|
+
with httpx.Client(
|
|
106
|
+
verify=verify_ssl, timeout=AUTH_REQUEST_TIMEOUT_SECONDS
|
|
107
|
+
) as client:
|
|
100
108
|
for endpoint in AUTH_ENDPOINTS:
|
|
101
109
|
try:
|
|
102
110
|
auth_response = client.post(
|
|
@@ -129,7 +137,8 @@ class CatalystCenterAuth:
|
|
|
129
137
|
|
|
130
138
|
# All endpoints failed
|
|
131
139
|
raise RuntimeError(
|
|
132
|
-
f"Catalyst Center authentication failed on all endpoints.
|
|
140
|
+
f"Catalyst Center authentication failed on all endpoints. "
|
|
141
|
+
f"Last error: {last_error}"
|
|
133
142
|
) from last_error
|
|
134
143
|
|
|
135
144
|
@classmethod
|
|
@@ -148,7 +157,8 @@ class CatalystCenterAuth:
|
|
|
148
157
|
CC_URL: Base URL of the Catalyst Center
|
|
149
158
|
CC_USERNAME: Catalyst Center username for authentication
|
|
150
159
|
CC_PASSWORD: Catalyst Center password for authentication
|
|
151
|
-
CC_INSECURE: Optional. Set to "True" to disable SSL verification
|
|
160
|
+
CC_INSECURE: Optional. Set to "True" to disable SSL verification
|
|
161
|
+
(default: True)
|
|
152
162
|
|
|
153
163
|
Returns:
|
|
154
164
|
A dictionary containing:
|
|
@@ -186,7 +196,9 @@ class CatalystCenterAuth:
|
|
|
186
196
|
missing_vars.append("CC_USERNAME")
|
|
187
197
|
if not password:
|
|
188
198
|
missing_vars.append("CC_PASSWORD")
|
|
189
|
-
raise ValueError(
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"Missing required environment variables: {', '.join(missing_vars)}"
|
|
201
|
+
)
|
|
190
202
|
|
|
191
203
|
# Normalize URL by removing trailing slash
|
|
192
204
|
url = url.rstrip("/") # type: ignore[union-attr]
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""Catalyst Center-specific base test class for API testing.
|
|
2
5
|
|
|
3
6
|
This module provides the CatalystCenterTestBase class, which extends the generic
|
|
@@ -14,7 +17,9 @@ import os
|
|
|
14
17
|
from typing import Any
|
|
15
18
|
|
|
16
19
|
import httpx
|
|
17
|
-
from nac_test.pyats_core.common.base_test import
|
|
20
|
+
from nac_test.pyats_core.common.base_test import (
|
|
21
|
+
NACTestBase, # type: ignore[import-untyped]
|
|
22
|
+
)
|
|
18
23
|
from pyats import aetest # type: ignore[import-untyped]
|
|
19
24
|
|
|
20
25
|
from .auth import CatalystCenterAuth
|
|
@@ -52,7 +57,9 @@ class CatalystCenterTestBase(NACTestBase): # type: ignore[misc]
|
|
|
52
57
|
return ['device1', 'device2']
|
|
53
58
|
|
|
54
59
|
async def verify_item(self, item):
|
|
55
|
-
response = await self.client.get(
|
|
60
|
+
response = await self.client.get(
|
|
61
|
+
f"/dna/intent/api/v1/network-device/{item}"
|
|
62
|
+
)
|
|
56
63
|
return response.status_code == 200
|
|
57
64
|
|
|
58
65
|
@aetest.test
|
|
@@ -90,7 +97,9 @@ class CatalystCenterTestBase(NACTestBase): # type: ignore[misc]
|
|
|
90
97
|
self.client = self.get_catc_client()
|
|
91
98
|
|
|
92
99
|
def get_catc_client(self) -> httpx.AsyncClient:
|
|
93
|
-
"""Get an httpx async client configured for Catalyst Center
|
|
100
|
+
"""Get an httpx async client configured for Catalyst Center.
|
|
101
|
+
|
|
102
|
+
Configured with response tracking.
|
|
94
103
|
|
|
95
104
|
Creates an HTTP client specifically configured for Catalyst Center API
|
|
96
105
|
communication with authentication headers, base URL, and automatic response
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""Base device resolver for SSH/D2D testing.
|
|
2
5
|
|
|
3
6
|
Provides the Template Method pattern for device inventory resolution.
|
|
@@ -35,7 +38,8 @@ class BaseDeviceResolver(ABC):
|
|
|
35
38
|
- get_credential_env_vars(): Return (username_env_var, password_env_var)
|
|
36
39
|
|
|
37
40
|
Subclasses MAY override:
|
|
38
|
-
- get_inventory_filename(): Return inventory filename
|
|
41
|
+
- get_inventory_filename(): Return inventory filename
|
|
42
|
+
(default: "test_inventory.yaml")
|
|
39
43
|
- build_device_dict(): Customize device dict construction
|
|
40
44
|
- _load_inventory(): Customize inventory loading
|
|
41
45
|
|
|
@@ -269,7 +273,9 @@ class BaseDeviceResolver(ABC):
|
|
|
269
273
|
logger.debug(f"Filtered to {len(devices_to_test)} devices from test inventory")
|
|
270
274
|
return devices_to_test
|
|
271
275
|
|
|
272
|
-
def _build_device_index(
|
|
276
|
+
def _build_device_index(
|
|
277
|
+
self, devices: list[dict[str, Any]]
|
|
278
|
+
) -> dict[str, dict[str, Any]]:
|
|
273
279
|
"""Build a lookup index of devices by their ID.
|
|
274
280
|
|
|
275
281
|
Args:
|
|
@@ -301,7 +307,9 @@ class BaseDeviceResolver(ABC):
|
|
|
301
307
|
if key in inventory_entry:
|
|
302
308
|
return str(inventory_entry[key])
|
|
303
309
|
|
|
304
|
-
logger.warning(
|
|
310
|
+
logger.warning(
|
|
311
|
+
f"Could not extract device ID from inventory entry: {inventory_entry}"
|
|
312
|
+
)
|
|
305
313
|
return ""
|
|
306
314
|
|
|
307
315
|
def _safe_extract_device_id(self, device_data: dict[str, Any]) -> str:
|
|
@@ -333,7 +341,8 @@ class BaseDeviceResolver(ABC):
|
|
|
333
341
|
|
|
334
342
|
if missing_vars:
|
|
335
343
|
raise ValueError(
|
|
336
|
-
f"Missing required credential environment variables:
|
|
344
|
+
f"Missing required credential environment variables: "
|
|
345
|
+
f"{', '.join(missing_vars)}. "
|
|
337
346
|
f"These are required for {self.get_architecture_name()} D2D testing."
|
|
338
347
|
)
|
|
339
348
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""Registry for IOS-XE device resolvers.
|
|
2
5
|
|
|
3
6
|
This module implements a plugin architecture for registering and retrieving
|
|
@@ -79,20 +82,24 @@ def register_iosxe_resolver(controller_type: str) -> Callable[[type[T]], type[T]
|
|
|
79
82
|
"""
|
|
80
83
|
# Validate the class extends BaseDeviceResolver
|
|
81
84
|
if not issubclass(cls, BaseDeviceResolver):
|
|
82
|
-
raise TypeError(
|
|
85
|
+
raise TypeError(
|
|
86
|
+
f"Resolver class {cls.__name__} must extend BaseDeviceResolver"
|
|
87
|
+
)
|
|
83
88
|
|
|
84
89
|
# Check for duplicate registration
|
|
85
90
|
if controller_type in _IOSXE_RESOLVER_REGISTRY:
|
|
86
91
|
existing_class = _IOSXE_RESOLVER_REGISTRY[controller_type]
|
|
87
92
|
raise ValueError(
|
|
88
|
-
f"A resolver is already registered for controller type
|
|
93
|
+
f"A resolver is already registered for controller type "
|
|
94
|
+
f"'{controller_type}': "
|
|
89
95
|
f"{existing_class.__module__}.{existing_class.__name__}"
|
|
90
96
|
)
|
|
91
97
|
|
|
92
98
|
# Register the resolver
|
|
93
99
|
_IOSXE_RESOLVER_REGISTRY[controller_type] = cls
|
|
94
100
|
logger.debug(
|
|
95
|
-
f"Registered IOS-XE resolver {cls.__name__} for controller type
|
|
101
|
+
f"Registered IOS-XE resolver {cls.__name__} for controller type "
|
|
102
|
+
f"'{controller_type}'"
|
|
96
103
|
)
|
|
97
104
|
|
|
98
105
|
return cls
|
|
@@ -100,7 +107,9 @@ def register_iosxe_resolver(controller_type: str) -> Callable[[type[T]], type[T]
|
|
|
100
107
|
return decorator
|
|
101
108
|
|
|
102
109
|
|
|
103
|
-
def get_resolver_for_controller(
|
|
110
|
+
def get_resolver_for_controller(
|
|
111
|
+
controller_type: str,
|
|
112
|
+
) -> type[BaseDeviceResolver] | None:
|
|
104
113
|
"""Get the device resolver class for a specific controller type.
|
|
105
114
|
|
|
106
115
|
Retrieves the registered resolver class for the specified controller type.
|
|
@@ -124,7 +133,8 @@ def get_resolver_for_controller(controller_type: str) -> type[BaseDeviceResolver
|
|
|
124
133
|
|
|
125
134
|
if resolver_class:
|
|
126
135
|
logger.debug(
|
|
127
|
-
f"Found resolver {resolver_class.__name__} for controller type
|
|
136
|
+
f"Found resolver {resolver_class.__name__} for controller type "
|
|
137
|
+
f"'{controller_type}'"
|
|
128
138
|
)
|
|
129
139
|
else:
|
|
130
140
|
logger.debug(
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""IOS-XE test base class for SSH/D2D testing."""
|
|
2
5
|
|
|
3
6
|
import os
|
|
@@ -19,7 +22,9 @@ class IOSXETestBase(SSHTestBase): # type: ignore[misc]
|
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
@classmethod
|
|
22
|
-
def get_ssh_device_inventory(
|
|
25
|
+
def get_ssh_device_inventory(
|
|
26
|
+
cls, data_model: dict[str, Any]
|
|
27
|
+
) -> list[dict[str, Any]]:
|
|
23
28
|
"""Get the SSH device inventory for IOS-XE devices.
|
|
24
29
|
|
|
25
30
|
Main entry point that detects the architecture and returns
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""SD-WAN (SDWAN Manager) adapter module for NAC PyATS testing.
|
|
2
5
|
|
|
3
6
|
This module provides SD-WAN-specific authentication, test base classes, and device
|
|
@@ -5,7 +8,8 @@ resolver implementations for use with the nac-test framework. It includes suppor
|
|
|
5
8
|
for both SDWAN Manager API testing and SSH-based device-to-device (D2D) testing.
|
|
6
9
|
|
|
7
10
|
Classes:
|
|
8
|
-
SDWANManagerAuth: SDWAN Manager authentication with JSESSIONID and XSRF token
|
|
11
|
+
SDWANManagerAuth: SDWAN Manager authentication with JSESSIONID and XSRF token
|
|
12
|
+
management.
|
|
9
13
|
SDWANManagerTestBase: Base class for SDWAN Manager API tests with tracking.
|
|
10
14
|
SDWANTestBase: Base class for SD-WAN SSH/D2D tests with device inventory.
|
|
11
15
|
SDWANDeviceResolver: Resolves device information from the SD-WAN data model.
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""SDWAN Manager-specific base test class for SD-WAN API testing.
|
|
2
5
|
|
|
3
|
-
This module provides the SDWANManagerTestBase class, which extends the generic
|
|
4
|
-
to add SDWAN Manager-specific functionality for testing SD-WAN
|
|
5
|
-
session management (JSESSIONID and XSRF token), client
|
|
6
|
-
a standardized interface for running asynchronous
|
|
6
|
+
This module provides the SDWANManagerTestBase class, which extends the generic
|
|
7
|
+
NACTestBase to add SDWAN Manager-specific functionality for testing SD-WAN
|
|
8
|
+
controllers. It handles session management (JSESSIONID and XSRF token), client
|
|
9
|
+
configuration, and provides a standardized interface for running asynchronous
|
|
10
|
+
verification tests against SDWAN Manager.
|
|
7
11
|
|
|
8
12
|
The class integrates with PyATS/Genie test frameworks and provides automatic
|
|
9
13
|
API call tracking for enhanced HTML reporting.
|
|
@@ -13,7 +17,9 @@ import asyncio
|
|
|
13
17
|
from typing import Any
|
|
14
18
|
|
|
15
19
|
import httpx
|
|
16
|
-
from nac_test.pyats_core.common.base_test import
|
|
20
|
+
from nac_test.pyats_core.common.base_test import (
|
|
21
|
+
NACTestBase, # type: ignore[import-untyped]
|
|
22
|
+
)
|
|
17
23
|
from pyats import aetest # type: ignore[import-untyped]
|
|
18
24
|
|
|
19
25
|
from .auth import SDWANManagerAuth
|
|
@@ -43,7 +49,8 @@ class SDWANManagerTestBase(NACTestBase): # type: ignore[misc]
|
|
|
43
49
|
|
|
44
50
|
Methods:
|
|
45
51
|
setup(): Initialize SDWAN Manager authentication and client.
|
|
46
|
-
get_sdwan_manager_client(): Create and configure an SDWAN Manager-specific
|
|
52
|
+
get_sdwan_manager_client(): Create and configure an SDWAN Manager-specific
|
|
53
|
+
HTTP client.
|
|
47
54
|
run_async_verification_test(): Execute async verification tests with PyATS.
|
|
48
55
|
|
|
49
56
|
Example:
|
|
@@ -68,7 +75,8 @@ class SDWANManagerTestBase(NACTestBase): # type: ignore[misc]
|
|
|
68
75
|
|
|
69
76
|
Initializes the SDWAN Manager test environment by:
|
|
70
77
|
1. Calling the parent class setup method
|
|
71
|
-
2. Obtaining SDWAN Manager session data (jsessionid, xsrf_token) using
|
|
78
|
+
2. Obtaining SDWAN Manager session data (jsessionid, xsrf_token) using
|
|
79
|
+
cached auth
|
|
72
80
|
3. Creating and storing an SDWAN Manager client for use in verification methods
|
|
73
81
|
|
|
74
82
|
The session data is obtained through the SDWANManagerAuth utility which
|
|
@@ -84,12 +92,14 @@ class SDWANManagerTestBase(NACTestBase): # type: ignore[misc]
|
|
|
84
92
|
self.client = self.get_sdwan_manager_client()
|
|
85
93
|
|
|
86
94
|
def get_sdwan_manager_client(self) -> httpx.AsyncClient:
|
|
87
|
-
"""Get an httpx async client configured for SDWAN Manager
|
|
95
|
+
"""Get an httpx async client configured for SDWAN Manager.
|
|
96
|
+
|
|
97
|
+
Configured with response tracking.
|
|
88
98
|
|
|
89
|
-
Creates an HTTP client specifically configured for SDWAN Manager API
|
|
90
|
-
with session headers, base URL, and automatic response
|
|
91
|
-
report generation. The client is wrapped to capture all
|
|
92
|
-
for detailed test reporting.
|
|
99
|
+
Creates an HTTP client specifically configured for SDWAN Manager API
|
|
100
|
+
communication with session headers, base URL, and automatic response
|
|
101
|
+
tracking for HTML report generation. The client is wrapped to capture all
|
|
102
|
+
API interactions for detailed test reporting.
|
|
93
103
|
|
|
94
104
|
The client includes:
|
|
95
105
|
- JSESSIONID cookie in all requests (via Cookie header)
|
|
@@ -98,9 +108,9 @@ class SDWANManagerTestBase(NACTestBase): # type: ignore[misc]
|
|
|
98
108
|
- Automatic API call tracking for reporting
|
|
99
109
|
|
|
100
110
|
Returns:
|
|
101
|
-
httpx.AsyncClient: Configured client with SDWAN Manager session data,
|
|
102
|
-
and wrapped for automatic API call tracking. The client
|
|
103
|
-
verification disabled for lab environment compatibility.
|
|
111
|
+
httpx.AsyncClient: Configured client with SDWAN Manager session data,
|
|
112
|
+
base URL, and wrapped for automatic API call tracking. The client
|
|
113
|
+
has SSL verification disabled for lab environment compatibility.
|
|
104
114
|
|
|
105
115
|
Note:
|
|
106
116
|
SSL verification is disabled (verify=False) to support lab environments
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""SDWAN Manager authentication implementation for Cisco SD-WAN.
|
|
2
5
|
|
|
3
6
|
This module provides authentication functionality for Cisco SDWAN Manager (formerly
|
|
4
7
|
vManage), which manages the software-defined WAN fabric. The authentication mechanism
|
|
5
|
-
uses form-based login with JSESSIONID cookie and optional XSRF token for CSRF
|
|
8
|
+
uses form-based login with JSESSIONID cookie and optional XSRF token for CSRF
|
|
9
|
+
protection.
|
|
6
10
|
|
|
7
11
|
The module implements a two-tier API design:
|
|
8
12
|
1. _authenticate() - Low-level method that performs direct SDWAN Manager authentication
|
|
@@ -16,7 +20,9 @@ import os
|
|
|
16
20
|
from typing import Any
|
|
17
21
|
|
|
18
22
|
import httpx
|
|
19
|
-
from nac_test.pyats_core.common.auth_cache import
|
|
23
|
+
from nac_test.pyats_core.common.auth_cache import (
|
|
24
|
+
AuthCache, # type: ignore[import-untyped]
|
|
25
|
+
)
|
|
20
26
|
|
|
21
27
|
# Default session lifetime for SDWAN Manager authentication in seconds
|
|
22
28
|
# SDWAN Manager sessions are typically valid for 30 minutes (1800 seconds) by default
|
|
@@ -56,7 +62,9 @@ class SDWANManagerAuth:
|
|
|
56
62
|
"""
|
|
57
63
|
|
|
58
64
|
@staticmethod
|
|
59
|
-
def _authenticate(
|
|
65
|
+
def _authenticate(
|
|
66
|
+
url: str, username: str, password: str
|
|
67
|
+
) -> tuple[dict[str, Any], int]:
|
|
60
68
|
"""Perform direct SDWAN Manager authentication and obtain session data.
|
|
61
69
|
|
|
62
70
|
This method performs a direct authentication request to the SDWAN Manager
|
|
@@ -149,8 +157,9 @@ class SDWANManagerAuth:
|
|
|
149
157
|
expired ones. This significantly reduces the number of authentication
|
|
150
158
|
requests to the SDWAN Manager.
|
|
151
159
|
|
|
152
|
-
The method uses a cache key based on the controller type ("SDWAN_MANAGER")
|
|
153
|
-
URL to ensure proper session isolation between different SDWAN Manager
|
|
160
|
+
The method uses a cache key based on the controller type ("SDWAN_MANAGER")
|
|
161
|
+
and URL to ensure proper session isolation between different SDWAN Manager
|
|
162
|
+
instances.
|
|
154
163
|
|
|
155
164
|
Environment Variables Required:
|
|
156
165
|
SDWAN_URL: Base URL of the SDWAN Manager
|
|
@@ -197,7 +206,9 @@ class SDWANManagerAuth:
|
|
|
197
206
|
missing_vars.append("SDWAN_USERNAME")
|
|
198
207
|
if not password:
|
|
199
208
|
missing_vars.append("SDWAN_PASSWORD")
|
|
200
|
-
raise ValueError(
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"Missing required environment variables: {', '.join(missing_vars)}"
|
|
211
|
+
)
|
|
201
212
|
|
|
202
213
|
# Normalize URL by removing trailing slash
|
|
203
214
|
url = url.rstrip("/") # type: ignore[union-attr]
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""SD-WAN-specific device resolver for parsing the NAC data model.
|
|
2
5
|
|
|
3
6
|
This module provides the SDWANDeviceResolver class, which extends
|
|
@@ -111,7 +114,9 @@ class SDWANDeviceResolver(BaseDeviceResolver):
|
|
|
111
114
|
|
|
112
115
|
# Fallback to chassis_id
|
|
113
116
|
chassis_id = device_data.get("chassis_id", "unknown")
|
|
114
|
-
logger.warning(
|
|
117
|
+
logger.warning(
|
|
118
|
+
f"No system_hostname found for {chassis_id}, using chassis_id as hostname"
|
|
119
|
+
)
|
|
115
120
|
return str(chassis_id)
|
|
116
121
|
|
|
117
122
|
def extract_host_ip(self, device_data: dict[str, Any]) -> str:
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright (c) 2025 Daniel Schmidt
|
|
3
|
+
|
|
1
4
|
"""SD-WAN specific base test class for SSH/Direct-to-Device testing.
|
|
2
5
|
|
|
3
6
|
This module provides the SDWANTestBase class, which extends the generic SSHTestBase
|
|
@@ -17,7 +20,9 @@ import logging
|
|
|
17
20
|
import os
|
|
18
21
|
from typing import Any
|
|
19
22
|
|
|
20
|
-
from nac_test.pyats_core.common.ssh_base_test import
|
|
23
|
+
from nac_test.pyats_core.common.ssh_base_test import (
|
|
24
|
+
SSHTestBase, # type: ignore[import-untyped]
|
|
25
|
+
)
|
|
21
26
|
|
|
22
27
|
from .device_resolver import SDWANDeviceResolver
|
|
23
28
|
|
|
@@ -44,7 +49,9 @@ class SDWANTestBase(SSHTestBase): # type: ignore[misc]
|
|
|
44
49
|
"""
|
|
45
50
|
|
|
46
51
|
@classmethod
|
|
47
|
-
def get_ssh_device_inventory(
|
|
52
|
+
def get_ssh_device_inventory(
|
|
53
|
+
cls, data_model: dict[str, Any]
|
|
54
|
+
) -> list[dict[str, Any]]:
|
|
48
55
|
"""Parse the SD-WAN data model to retrieve the device inventory.
|
|
49
56
|
|
|
50
57
|
This method is the entry point called by nac-test's orchestrator.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nac-test-pyats-common
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Architecture adapters for Network as Code (NaC) PyATS testing - auth classes, test base classes, and device resolvers
|
|
5
5
|
Project-URL: Homepage, https://github.com/netascode/nac-test-pyats-common
|
|
6
6
|
Project-URL: Documentation, https://github.com/netascode/nac-test-pyats-common
|
|
@@ -24,6 +24,7 @@ Classifier: Topic :: Software Development :: Testing
|
|
|
24
24
|
Classifier: Topic :: System :: Networking
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Requires-Dist: httpx>=0.28
|
|
27
|
+
Requires-Dist: nac-test==1.1.0b2
|
|
27
28
|
Provides-Extra: dev
|
|
28
29
|
Requires-Dist: bandit[toml]>=1.8.6; extra == 'dev'
|
|
29
30
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
nac_test_pyats_common/__init__.py,sha256=bQ3oB1eajVj8pmMnWCxxQEHUW-FkQX94j-6ocBq9OxA,1623
|
|
2
|
+
nac_test_pyats_common/py.typed,sha256=BrP39il8_cNN1K0KDeyBUtySS9oI2_SK5xKx1SxdSoY,59
|
|
3
|
+
nac_test_pyats_common/aci/__init__.py,sha256=Y9VhBZ3_GXLBk3_Wtg1SRG_Dxx134Etf9k-0wvrOMwQ,1335
|
|
4
|
+
nac_test_pyats_common/aci/auth.py,sha256=1Rk2uBGb_CGgkBh9dD3LagIsbJPKRgsSFDYEeyiC50U,7867
|
|
5
|
+
nac_test_pyats_common/aci/test_base.py,sha256=SoPh91AXPvMshJXIyjiZumSgxOAN4xPkpSb3t0B9kdE,6256
|
|
6
|
+
nac_test_pyats_common/catc/__init__.py,sha256=NKIHm6zOiiG8T_wcanPTh8621_RGcVJCMOeQLygTsy8,1319
|
|
7
|
+
nac_test_pyats_common/catc/auth.py,sha256=1oDZr_PEWOz1IZOUm_kyKzIFPQubMo_wemj-Q3D7ALU,9522
|
|
8
|
+
nac_test_pyats_common/catc/test_base.py,sha256=kGp6rZ4S3zP3MW5BD02K5uVxmtE-vRlG2A99kaLgyDo,7507
|
|
9
|
+
nac_test_pyats_common/common/__init__.py,sha256=Y0qrZEKx2g8Zm1CTHJpeLocwMasspP6NQuhyrRiA1wA,1084
|
|
10
|
+
nac_test_pyats_common/common/base_device_resolver.py,sha256=bPZP_dJjBfvaUGg_8F1JhfGAnWzAqI8oIC_JEejDJ4E,18558
|
|
11
|
+
nac_test_pyats_common/iosxe/__init__.py,sha256=GSuLGyxcu8dFouspTjXuQ1Mi27vDluVR8ZCcJrPcIiY,2147
|
|
12
|
+
nac_test_pyats_common/iosxe/catc_resolver.py,sha256=t14Pa6WjrjrbinXTtwc0Wa14Cctcae2yLLgdIpByzoU,2513
|
|
13
|
+
nac_test_pyats_common/iosxe/iosxe_resolver.py,sha256=XzzwRCa1cuQ6HmeUB1Z2vITOwYdhuDlugDD1eVXAoIE,2355
|
|
14
|
+
nac_test_pyats_common/iosxe/registry.py,sha256=djRFzxuEu8IdyTmi1puhIHGZGSMjys0l1v4-uzYCwAU,5723
|
|
15
|
+
nac_test_pyats_common/iosxe/test_base.py,sha256=s-x9hGJ4Yi6313gVamXnHWO7Jzr8tTKXzSOVJ6HcTxU,4306
|
|
16
|
+
nac_test_pyats_common/sdwan/__init__.py,sha256=mrziVp5kMgykKnFPvWykuVLuky6xwEv6oEcfrbxFXxY,1777
|
|
17
|
+
nac_test_pyats_common/sdwan/api_test_base.py,sha256=NuqrxODrRw7ZvEIuoc1Bup60thKqJOfiGz96T4QJ8Ng,7632
|
|
18
|
+
nac_test_pyats_common/sdwan/auth.py,sha256=CMgf_AYCalVQNjdUfDlE-0-z5tnhU_Wnscgt5ZW61RY,10322
|
|
19
|
+
nac_test_pyats_common/sdwan/device_resolver.py,sha256=3eKdIpoZsWHL8EzoKma1ZL6cFiMAQRZiMLp_xf9ZcEw,6062
|
|
20
|
+
nac_test_pyats_common/sdwan/ssh_test_base.py,sha256=8ddzOOyrxVF1uYiOOg2v8kXBI628c6hg-P-hiOC-v3Q,4173
|
|
21
|
+
nac_test_pyats_common-0.1.1.dist-info/METADATA,sha256=1vxKpKhdqZbR0N7wFJH_VYcpGB9MiJiumhUvzaP_4Hg,13072
|
|
22
|
+
nac_test_pyats_common-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
23
|
+
nac_test_pyats_common-0.1.1.dist-info/licenses/LICENSE,sha256=zt2sx-c0iEk6-OO0iqRQ4l6fIGazRKW_qLMqfDpLm6M,16295
|
|
24
|
+
nac_test_pyats_common-0.1.1.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
nac_test_pyats_common/__init__.py,sha256=yhvIGEkgMoZhwZKWxXUTzkzQT6-6YtpdOOa8mAVgNaY,1551
|
|
2
|
-
nac_test_pyats_common/py.typed,sha256=BrP39il8_cNN1K0KDeyBUtySS9oI2_SK5xKx1SxdSoY,59
|
|
3
|
-
nac_test_pyats_common/aci/__init__.py,sha256=hEqGdhP_DtCqmLnUncYdmmH5d5WFRsdIkoCzH3DbV1k,1263
|
|
4
|
-
nac_test_pyats_common/aci/auth.py,sha256=CSBHmmN8GYNXP-uCC6u31S-AOWcUDhIeLwuolN4OEjU,7772
|
|
5
|
-
nac_test_pyats_common/aci/test_base.py,sha256=Em0GTrU0lJLWhwoZOptwnEEDnY3L8YkB-2Sre4endzI,6131
|
|
6
|
-
nac_test_pyats_common/catc/__init__.py,sha256=1e5RjeEKApalS9CqSRi_nvER1MRtaCfQJ-kVOq8zp0w,1247
|
|
7
|
-
nac_test_pyats_common/catc/auth.py,sha256=yCp9NGe48CngxGN0QVLS17ukfJge7rdBcV-L2_lez7U,9350
|
|
8
|
-
nac_test_pyats_common/catc/test_base.py,sha256=myhdMFN310z6D3IC_BcSvtNQ4iyGbZZtiROxoNAHs0I,7367
|
|
9
|
-
nac_test_pyats_common/common/__init__.py,sha256=tMBObh27Bl3AHe4UVu3EoI6rbhxWV901WKw4SEMvnwk,1012
|
|
10
|
-
nac_test_pyats_common/common/base_device_resolver.py,sha256=VQDkV6EzZCJL9TrJcB2i34BFz9Y8cK7g2Y_8NP7AVrQ,18420
|
|
11
|
-
nac_test_pyats_common/iosxe/__init__.py,sha256=dIXPUdOJAy8P7KM4_scfkFSB4g1c-b7AdsEC3Ok8gEE,2075
|
|
12
|
-
nac_test_pyats_common/iosxe/catc_resolver.py,sha256=YK8-1Y0EIkX5CBawAqAxYk2kMjYwoBWXLtM3Yz_7kfQ,2441
|
|
13
|
-
nac_test_pyats_common/iosxe/iosxe_resolver.py,sha256=CmvbF8eFo5I_Uy_JDx1lO-2YaB_fiDxK-I7WBvu7X5o,2283
|
|
14
|
-
nac_test_pyats_common/iosxe/registry.py,sha256=toRNcIcXkPPrm-kC4tpxOf6kTXgvC2Dsh8Vm5EXEwRY,5562
|
|
15
|
-
nac_test_pyats_common/iosxe/test_base.py,sha256=dJrTpRy3V8Q0vK5FODHzDh85EZ7eM8dS9PnBvRF-i_0,4220
|
|
16
|
-
nac_test_pyats_common/sdwan/__init__.py,sha256=QUnYDV25cwubonnpXkkhGrDcSOcDtmabAAx8fPnM4zQ,1697
|
|
17
|
-
nac_test_pyats_common/sdwan/api_test_base.py,sha256=gTy57JSa6pjuCayfZMzS1lUPZHYSTqZjr8LeDNoLouc,7507
|
|
18
|
-
nac_test_pyats_common/sdwan/auth.py,sha256=V_OVAZzCSOvfTYrPGyv-xXZEDDHIpTl2BBxDoB5Mwro,10189
|
|
19
|
-
nac_test_pyats_common/sdwan/device_resolver.py,sha256=huAfUG6TADF3lWeOUyr244jRogC5SqWhz4k-1191ZnY,5968
|
|
20
|
-
nac_test_pyats_common/sdwan/ssh_test_base.py,sha256=KvV_Hw56EiX5HcVRNG2A2ryLH0v6RbmLlUzyJMQ-424,4078
|
|
21
|
-
nac_test_pyats_common-0.1.0.dist-info/METADATA,sha256=hZNaqHtElHIADhf2CumhWpg8qf9aEWEE_eM2tIr6IQw,13039
|
|
22
|
-
nac_test_pyats_common-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
23
|
-
nac_test_pyats_common-0.1.0.dist-info/licenses/LICENSE,sha256=zt2sx-c0iEk6-OO0iqRQ4l6fIGazRKW_qLMqfDpLm6M,16295
|
|
24
|
-
nac_test_pyats_common-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
{nac_test_pyats_common-0.1.0.dist-info → nac_test_pyats_common-0.1.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|