nac-test-pyats-common 0.1.1__py3-none-any.whl → 0.2.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.
@@ -14,14 +14,21 @@ The module implements a two-tier API design:
14
14
 
15
15
  This design ensures efficient session management by reusing valid sessions and only
16
16
  re-authenticating when necessary, reducing unnecessary API calls to the SDWAN Manager.
17
+
18
+ Note on Fork Safety:
19
+ This module uses urllib instead of httpx for synchronous authentication requests.
20
+ httpx is NOT fork-safe on macOS - creating httpx.Client after fork() causes
21
+ silent crashes due to OpenSSL threading issues. urllib uses simpler primitives
22
+ that work correctly after fork().
17
23
  """
18
24
 
19
25
  import os
20
26
  from typing import Any
21
27
 
22
- import httpx
23
- from nac_test.pyats_core.common.auth_cache import (
24
- AuthCache, # type: ignore[import-untyped]
28
+ from nac_test.pyats_core.common.auth_cache import AuthCache
29
+ from nac_test.pyats_core.common.subprocess_auth import (
30
+ SubprocessAuthError, # noqa: F401 - re-exported for callers to catch
31
+ execute_auth_subprocess,
25
32
  )
26
33
 
27
34
  # Default session lifetime for SDWAN Manager authentication in seconds
@@ -63,7 +70,7 @@ class SDWANManagerAuth:
63
70
 
64
71
  @staticmethod
65
72
  def _authenticate(
66
- url: str, username: str, password: str
73
+ url: str, username: str, password: str, verify_ssl: bool = False
67
74
  ) -> tuple[dict[str, Any], int]:
68
75
  """Perform direct SDWAN Manager authentication and obtain session data.
69
76
 
@@ -77,12 +84,19 @@ class SDWANManagerAuth:
77
84
  3. Attempt to fetch XSRF token (for 19.2+ only)
78
85
  4. Return session data with TTL
79
86
 
87
+ Note: On macOS, SSL operations in forked processes crash due to OpenSSL
88
+ threading issues. This method uses subprocess with spawn context to perform
89
+ authentication in a fresh process, avoiding the fork+SSL crash.
90
+
80
91
  Args:
81
92
  url: Base URL of the SDWAN Manager (e.g., "https://sdwan-manager.example.com").
82
93
  Should not include trailing slashes or API paths.
83
94
  username: SDWAN Manager username for authentication. This should be a valid
84
95
  user configured with appropriate permissions.
85
96
  password: Password for the specified user account.
97
+ verify_ssl: Whether to verify SSL certificates. Defaults to False to
98
+ handle self-signed certificates commonly used in lab and development
99
+ deployments.
86
100
 
87
101
  Returns:
88
102
  A tuple containing:
@@ -91,61 +105,99 @@ class SDWANManagerAuth:
91
105
  - expires_in (int): Session lifetime in seconds (typically 1800).
92
106
 
93
107
  Raises:
94
- httpx.HTTPStatusError: If SDWAN Manager returns a non-2xx status code,
95
- typically indicating authentication failure (401) or server error.
96
- httpx.RequestError: If the request fails due to network issues,
97
- connection timeouts, or other transport-level problems.
98
- ValueError: If the JSESSIONID cookie is not received in the response,
99
- indicating a malformed or unexpected response.
100
-
101
- Note:
102
- SSL verification is disabled (verify=False) to handle self-signed
103
- certificates commonly used in lab and development deployments.
104
- In production environments, proper certificate validation should be enabled
105
- by either installing the certificate in the trust store or providing
106
- a custom CA bundle via the verify parameter.
108
+ SubprocessAuthError: If authentication subprocess fails.
109
+ ValueError: If the authentication response is malformed.
107
110
  """
108
- # NOTE: SSL verification is disabled (verify=False) to handle self-signed
109
- # certificates commonly used in lab and development deployments.
110
- with httpx.Client(verify=False, timeout=AUTH_REQUEST_TIMEOUT_SECONDS) as client:
111
- # Step 1: Form-based login to SDWAN Manager
112
- auth_response = client.post(
113
- f"{url}/j_security_check",
114
- data={"j_username": username, "j_password": password},
115
- headers={"Content-Type": "application/x-www-form-urlencoded"},
116
- follow_redirects=False,
117
- )
118
- auth_response.raise_for_status()
119
-
120
- # Validate JSESSIONID cookie was received
121
- if "JSESSIONID" not in auth_response.cookies:
122
- raise ValueError(
123
- "No JSESSIONID cookie received from SDWAN Manager. "
124
- "This may indicate invalid credentials or a server error. "
125
- f"Response status: {auth_response.status_code}"
126
- )
127
-
128
- jsessionid = auth_response.cookies["JSESSIONID"]
129
-
130
- # Step 2: Attempt to get XSRF token (19.2+ only)
131
- # Pre-19.2 versions do not require XSRF token, so failures are expected
132
- xsrf_token: str | None = None
133
- try:
134
- token_response = client.get(
135
- f"{url}/dataservice/client/token",
136
- cookies={"JSESSIONID": jsessionid},
137
- timeout=XSRF_TOKEN_FETCH_TIMEOUT_SECONDS,
138
- )
139
- if token_response.status_code == 200:
140
- xsrf_token = token_response.text.strip()
141
- except (httpx.HTTPError, httpx.TimeoutException):
142
- # Pre-19.2 does not support XSRF tokens, continue without
143
- pass
144
-
145
- return {
146
- "jsessionid": jsessionid,
147
- "xsrf_token": xsrf_token,
148
- }, SDWAN_MANAGER_SESSION_LIFETIME_SECONDS
111
+ # Build auth parameters for subprocess
112
+ auth_params = {
113
+ "url": url,
114
+ "username": username,
115
+ "password": password,
116
+ "timeout": AUTH_REQUEST_TIMEOUT_SECONDS,
117
+ "xsrf_timeout": XSRF_TOKEN_FETCH_TIMEOUT_SECONDS,
118
+ "verify_ssl": verify_ssl,
119
+ }
120
+
121
+ # SDWAN-specific authentication logic
122
+ # This script assumes `params` dict is already loaded by execute_auth_subprocess
123
+ auth_script_body = """
124
+ import http.cookiejar
125
+ import ssl
126
+ import urllib.parse
127
+ import urllib.request
128
+
129
+ url = params["url"]
130
+ username = params["username"]
131
+ password = params["password"]
132
+ timeout = params["timeout"]
133
+ xsrf_timeout = params["xsrf_timeout"]
134
+ verify_ssl = params["verify_ssl"]
135
+
136
+ # Create SSL context
137
+ ssl_context = ssl.create_default_context()
138
+ if not verify_ssl:
139
+ ssl_context.check_hostname = False
140
+ ssl_context.verify_mode = ssl.CERT_NONE
141
+
142
+ # Create cookie jar and opener
143
+ cookie_jar = http.cookiejar.CookieJar()
144
+ https_handler = urllib.request.HTTPSHandler(context=ssl_context)
145
+ cookie_handler = urllib.request.HTTPCookieProcessor(cookie_jar)
146
+ opener = urllib.request.build_opener(https_handler, cookie_handler)
147
+
148
+ # Step 1: Form-based login to /j_security_check
149
+ auth_data = urllib.parse.urlencode({
150
+ "j_username": username,
151
+ "j_password": password
152
+ }).encode("utf-8")
153
+
154
+ auth_request = urllib.request.Request(
155
+ f"{url}/j_security_check",
156
+ data=auth_data,
157
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
158
+ method="POST"
159
+ )
160
+
161
+ try:
162
+ opener.open(auth_request, timeout=timeout)
163
+ except urllib.error.HTTPError as e:
164
+ if e.code != 302: # 302 redirect is expected on successful login
165
+ raise
166
+
167
+ # Extract JSESSIONID from cookies
168
+ jsessionid = None
169
+ for cookie in cookie_jar:
170
+ if cookie.name == "JSESSIONID":
171
+ jsessionid = cookie.value
172
+ break
173
+
174
+ if jsessionid is None:
175
+ result = {"error": "No JSESSIONID cookie received - authentication may have failed"}
176
+ else:
177
+ # Step 2: Fetch XSRF token (required for SDWAN Manager 19.2+)
178
+ xsrf_token = None
179
+ try:
180
+ token_request = urllib.request.Request(
181
+ f"{url}/dataservice/client/token",
182
+ headers={"Cookie": f"JSESSIONID={jsessionid}"},
183
+ method="GET"
184
+ )
185
+ token_response = opener.open(token_request, timeout=xsrf_timeout)
186
+ if token_response.status == 200:
187
+ xsrf_token = token_response.read().decode("utf-8").strip()
188
+ except Exception:
189
+ pass # Pre-19.2 versions do not support XSRF tokens
190
+
191
+ result = {"jsessionid": jsessionid, "xsrf_token": xsrf_token}
192
+ """
193
+
194
+ # Execute authentication in subprocess (fork-safe on macOS)
195
+ auth_result = execute_auth_subprocess(auth_params, auth_script_body)
196
+
197
+ return {
198
+ "jsessionid": auth_result["jsessionid"],
199
+ "xsrf_token": auth_result.get("xsrf_token"),
200
+ }, SDWAN_MANAGER_SESSION_LIFETIME_SECONDS
149
201
 
150
202
  @classmethod
151
203
  def get_auth(cls) -> dict[str, Any]:
@@ -165,6 +217,8 @@ class SDWANManagerAuth:
165
217
  SDWAN_URL: Base URL of the SDWAN Manager
166
218
  SDWAN_USERNAME: SDWAN Manager username for authentication
167
219
  SDWAN_PASSWORD: SDWAN Manager password for authentication
220
+ SDWAN_INSECURE: If "True", "1", or "yes" (default: "True"), SSL certificate
221
+ verification is disabled. Set to "False" to enable SSL verification.
168
222
 
169
223
  Returns:
170
224
  A dictionary containing:
@@ -175,11 +229,10 @@ class SDWANManagerAuth:
175
229
  Raises:
176
230
  ValueError: If any required environment variables (SDWAN_URL,
177
231
  SDWAN_USERNAME, SDWAN_PASSWORD) are not set.
178
- httpx.HTTPStatusError: If SDWAN Manager returns a non-2xx status code during
179
- authentication, typically indicating invalid credentials (401) or
180
- server issues (5xx).
181
- httpx.RequestError: If the request fails due to network issues,
182
- connection timeouts, or other transport-level problems.
232
+ SubprocessAuthError: If authentication fails due to invalid credentials,
233
+ network issues, connection timeouts, or SDWAN Manager server errors.
234
+ The error message will contain details from the authentication
235
+ subprocess.
183
236
 
184
237
  Example:
185
238
  >>> # Set environment variables first
@@ -197,6 +250,11 @@ class SDWANManagerAuth:
197
250
  url = os.environ.get("SDWAN_URL")
198
251
  username = os.environ.get("SDWAN_USERNAME")
199
252
  password = os.environ.get("SDWAN_PASSWORD")
253
+ insecure = os.environ.get("SDWAN_INSECURE", "True").lower() in (
254
+ "true",
255
+ "1",
256
+ "yes",
257
+ )
200
258
 
201
259
  if not all([url, username, password]):
202
260
  missing_vars: list[str] = []
@@ -213,9 +271,12 @@ class SDWANManagerAuth:
213
271
  # Normalize URL by removing trailing slash
214
272
  url = url.rstrip("/") # type: ignore[union-attr]
215
273
 
274
+ # SDWAN_INSECURE=True means verify_ssl=False
275
+ verify_ssl = not insecure
276
+
216
277
  def auth_wrapper() -> tuple[dict[str, Any], int]:
217
278
  """Wrapper for authentication that captures closure variables."""
218
- return cls._authenticate(url, username, password) # type: ignore[arg-type]
279
+ return cls._authenticate(url, username, password, verify_ssl) # type: ignore[arg-type]
219
280
 
220
281
  # AuthCache.get_or_create returns dict[str, Any], but mypy can't verify this
221
282
  # because nac_test lacks py.typed marker.
@@ -5,6 +5,16 @@
5
5
 
6
6
  This module provides the SDWANDeviceResolver class, which extends
7
7
  BaseDeviceResolver to implement SD-WAN schema navigation.
8
+
9
+ Device Fields Returned:
10
+ - hostname: System hostname from device_variables.system_hostname
11
+ - host: Management IP address (CIDR stripped)
12
+ - os: Always 'iosxe' (SD-WAN edges are IOS-XE based)
13
+ - platform: Always 'sdwan' (for PyATS abstraction optimization)
14
+ - device_id: Chassis ID
15
+ - type: Always 'router'
16
+ - username: From IOSXE_USERNAME environment variable
17
+ - password: From IOSXE_PASSWORD environment variable
8
18
  """
9
19
 
10
20
  import logging
@@ -25,13 +35,20 @@ class SDWANDeviceResolver(BaseDeviceResolver):
25
35
 
26
36
  Schema structure:
27
37
  sdwan:
38
+ management_ip_variable: "vpn511_int1_if_ipv4_address" # Global default
28
39
  sites:
29
40
  - name: "site1"
30
41
  routers:
31
42
  - chassis_id: "abc123"
43
+ management_ip_variable: "custom_mgmt_ip" # Router override
32
44
  device_variables:
33
45
  system_hostname: "router1"
34
- vpn10_mgmt_ip: "10.1.1.100/32"
46
+ vpn511_int1_if_ipv4_address: "10.1.1.100/32"
47
+ custom_mgmt_ip: "10.2.2.200/32"
48
+
49
+ Management IP Resolution Priority:
50
+ 1. Router-level management_ip_variable (highest priority)
51
+ 2. Global sdwan-level management_ip_variable (fallback)
35
52
 
36
53
  Credentials:
37
54
  Uses IOSXE_USERNAME and IOSXE_PASSWORD environment variables
@@ -56,7 +73,7 @@ class SDWANDeviceResolver(BaseDeviceResolver):
56
73
  """Return 'sdwan' as the root key in the data model.
57
74
 
58
75
  Returns:
59
- Root key used when loading test inventory and navigating schema.
76
+ Root key used when navigating the schema.
60
77
  """
61
78
  return "sdwan"
62
79
 
@@ -126,6 +143,10 @@ class SDWANDeviceResolver(BaseDeviceResolver):
126
143
  Uses management_ip_variable field to determine which variable
127
144
  contains the management IP.
128
145
 
146
+ Resolution priority:
147
+ 1. Router-level management_ip_variable (highest priority)
148
+ 2. Global sdwan-level management_ip_variable (fallback)
149
+
129
150
  Args:
130
151
  device_data: Router data dictionary from the data model.
131
152
 
@@ -133,27 +154,28 @@ class SDWANDeviceResolver(BaseDeviceResolver):
133
154
  IP address string without CIDR notation (e.g., "10.1.1.100").
134
155
 
135
156
  Raises:
136
- ValueError: If no management IP can be found.
157
+ ValueError: If management_ip_variable is not configured or
158
+ the referenced variable is not found in device_variables.
137
159
  """
138
160
  device_vars = device_data.get("device_variables", {})
139
161
 
140
- # Get the variable name that contains the management IP
162
+ # Cascading lookup: router-level > global sdwan-level
141
163
  ip_var = device_data.get("management_ip_variable")
164
+ if not ip_var:
165
+ ip_var = self.data_model.get("sdwan", {}).get("management_ip_variable")
166
+
167
+ if not ip_var:
168
+ raise ValueError(
169
+ "management_ip_variable not configured. "
170
+ "Set it at router level or sdwan level in sites.nac.yaml."
171
+ )
142
172
 
143
- if ip_var and ip_var in device_vars:
144
- ip_value = str(device_vars[ip_var])
145
- else:
146
- # Fallback: try common variable names
147
- for fallback_var in ["mgmt_ip", "management_ip", "vpn0_ip"]:
148
- if fallback_var in device_vars:
149
- ip_value = str(device_vars[fallback_var])
150
- break
151
- else:
152
- raise ValueError(
153
- "Could not find management IP for device. "
154
- "Set 'management_ip_variable' in test_inventory or use "
155
- "standard variable names (mgmt_ip, management_ip, vpn0_ip)."
156
- )
173
+ if ip_var not in device_vars:
174
+ raise ValueError(
175
+ f"management_ip_variable '{ip_var}' not found in device_variables."
176
+ )
177
+
178
+ ip_value = str(device_vars[ip_var])
157
179
 
158
180
  # Strip CIDR notation if present
159
181
  if "/" in ip_value:
@@ -161,16 +183,41 @@ class SDWANDeviceResolver(BaseDeviceResolver):
161
183
 
162
184
  return ip_value
163
185
 
164
- def extract_os_type(self, device_data: dict[str, Any]) -> str:
165
- """Extract OS type, defaulting to 'iosxe' for SD-WAN edges.
186
+ def extract_os_platform_type(self, device_data: dict[str, Any]) -> dict[str, str]:
187
+ """Return PyATS abstraction info for SD-WAN edge devices.
188
+
189
+ All SD-WAN edge devices are IOS-XE based with 'sdwan' platform.
190
+
191
+ Args:
192
+ device_data: Router data dictionary (unused, values are hardcoded).
193
+
194
+ Returns:
195
+ Dictionary with 'os' and 'platform' keys.
196
+ """
197
+ return {
198
+ "os": "iosxe",
199
+ "platform": "sdwan",
200
+ }
201
+
202
+ def build_device_dict(self, device_data: dict[str, Any]) -> dict[str, Any]:
203
+ """Build device dictionary with SD-WAN specific defaults.
204
+
205
+ Extends the base implementation to add type='router'.
206
+ Platform is set to 'sdwan' via extract_os_platform_type().
166
207
 
167
208
  Args:
168
209
  device_data: Router data dictionary from the data model.
169
210
 
170
211
  Returns:
171
- OS type string (e.g., "iosxe", "nxos", "iosxr").
212
+ Device dictionary with hostname, host, os, platform, device_id, and type.
172
213
  """
173
- return str(device_data.get("os", "iosxe"))
214
+ # Get base device dict from parent
215
+ device_dict = super().build_device_dict(device_data)
216
+
217
+ # Add type - all SD-WAN edges are routers
218
+ device_dict["type"] = "router"
219
+
220
+ return device_dict
174
221
 
175
222
  def get_credential_env_vars(self) -> tuple[str, str]:
176
223
  """Return IOS-XE credential env vars for SD-WAN edge devices.
@@ -48,6 +48,11 @@ class SDWANTestBase(SSHTestBase): # type: ignore[misc]
48
48
  pass
49
49
  """
50
50
 
51
+ # Class-level storage for the last resolver instance
52
+ # This allows nac-test to access skipped_devices after calling
53
+ # get_ssh_device_inventory()
54
+ _last_resolver: "SDWANDeviceResolver | None" = None
55
+
51
56
  @classmethod
52
57
  def get_ssh_device_inventory(
53
58
  cls, data_model: dict[str, Any]
@@ -57,11 +62,13 @@ class SDWANTestBase(SSHTestBase): # type: ignore[misc]
57
62
  This method is the entry point called by nac-test's orchestrator.
58
63
  All device inventory resolution is delegated to SDWANDeviceResolver,
59
64
  which handles:
60
- - Test inventory loading (test_inventory.yaml)
61
65
  - Schema navigation (sites[].routers[])
62
- - Variable resolution (hostnames, IPs)
66
+ - Management IP resolution via management_ip_variable
63
67
  - Credential injection (IOSXE_USERNAME, IOSXE_PASSWORD)
64
68
 
69
+ After calling this method, access cls._last_resolver.skipped_devices
70
+ to get information about devices that failed resolution.
71
+
65
72
  Args:
66
73
  data_model: The merged data model from nac-test containing all
67
74
  sites.nac.yaml data with resolved variables.
@@ -80,13 +87,8 @@ class SDWANTestBase(SSHTestBase): # type: ignore[misc]
80
87
  """
81
88
  logger.info("SDWANTestBase: Resolving device inventory via SDWANDeviceResolver")
82
89
 
83
- # Delegate entirely to the resolver
84
- # SDWANDeviceResolver handles:
85
- # 1. Test inventory loading via BaseDeviceResolver._load_inventory()
86
- # 2. Schema navigation via navigate_to_devices()
87
- # 3. Credential injection via _inject_credentials() using IOSXE_* env vars
88
- resolver = SDWANDeviceResolver(data_model)
89
- return resolver.get_resolved_inventory()
90
+ cls._last_resolver = SDWANDeviceResolver(data_model)
91
+ return cls._last_resolver.get_resolved_inventory()
90
92
 
91
93
  def get_device_credentials(self, device: dict[str, Any]) -> dict[str, str | None]:
92
94
  """Get SD-WAN edge device SSH credentials from environment variables.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nac-test-pyats-common
3
- Version: 0.1.1
3
+ Version: 0.2.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
@@ -23,8 +23,9 @@ Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Topic :: Software Development :: Testing
24
24
  Classifier: Topic :: System :: Networking
25
25
  Requires-Python: >=3.10
26
+ Requires-Dist: filelock>=3.20.1
26
27
  Requires-Dist: httpx>=0.28
27
- Requires-Dist: nac-test==1.1.0b2
28
+ Requires-Dist: nac-test==1.1.0b3
28
29
  Provides-Extra: dev
29
30
  Requires-Dist: bandit[toml]>=1.8.6; extra == 'dev'
30
31
  Requires-Dist: mypy>=1.10; extra == 'dev'
@@ -0,0 +1,25 @@
1
+ nac_test_pyats_common/__init__.py,sha256=4fOaRhzkJJDyW00-wPzEzgWdmLXSLfxkiV-Z39RWmq8,1770
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=pkM7BRWJ1doajqZr73T4Gd3vPWydttk_QtOIEcf8tS4,11558
5
+ nac_test_pyats_common/aci/test_base.py,sha256=Kz_TpY3yeveAfSUMVGhSiQ3s7AHOzIJEz97rpy6pVWE,7575
6
+ nac_test_pyats_common/catc/__init__.py,sha256=ZGOQBvjFvq7h9LtfOyxFEmlir7htpy8sfWoJuDwGjsA,1986
7
+ nac_test_pyats_common/catc/api_test_base.py,sha256=F1zNKaYfHV_vRxJ30TcebDj26itNZ05lLHtUa4wslA0,8905
8
+ nac_test_pyats_common/catc/auth.py,sha256=u-kasgmoZCvZBKwrZUsoo9e1azxJUJg9odZkpXkUq7g,11623
9
+ nac_test_pyats_common/catc/device_resolver.py,sha256=I17QuPQpJj4p7zXzAjHJrHZcaICcHdAX9DV_0qgQni8,5754
10
+ nac_test_pyats_common/catc/ssh_test_base.py,sha256=wUldp2T9cmSEALBtbOgUQvQ2txuid3ErJEO27kM-kiI,4482
11
+ nac_test_pyats_common/common/__init__.py,sha256=Y0qrZEKx2g8Zm1CTHJpeLocwMasspP6NQuhyrRiA1wA,1084
12
+ nac_test_pyats_common/common/base_device_resolver.py,sha256=ul-zao-qcLZc1p_xvDCUAnvR5zqZM8mZda4iBP5S7LU,17625
13
+ nac_test_pyats_common/iosxe/__init__.py,sha256=GSuLGyxcu8dFouspTjXuQ1Mi27vDluVR8ZCcJrPcIiY,2147
14
+ nac_test_pyats_common/iosxe/iosxe_resolver.py,sha256=_2cug-eZfO5iKE3Po2kh9hJB7StRjs-Cm-BdXqKb5pg,2388
15
+ nac_test_pyats_common/iosxe/registry.py,sha256=djRFzxuEu8IdyTmi1puhIHGZGSMjys0l1v4-uzYCwAU,5723
16
+ nac_test_pyats_common/iosxe/test_base.py,sha256=NjXbf0ZY-CBWhd3jpIAhylL5kyRrHM6FlhGlFtjLSjM,4569
17
+ nac_test_pyats_common/sdwan/__init__.py,sha256=mrziVp5kMgykKnFPvWykuVLuky6xwEv6oEcfrbxFXxY,1777
18
+ nac_test_pyats_common/sdwan/api_test_base.py,sha256=dtPKd094gdQTNHaYeoPQxNle6uCULNII2sroAweZ5Jw,8834
19
+ nac_test_pyats_common/sdwan/auth.py,sha256=4qIi2ypMPK2Pkw6rfhSkxpoJGDRRvt4fJ5rO01p-4II,11790
20
+ nac_test_pyats_common/sdwan/device_resolver.py,sha256=5cTfcH6OUH98AiJCDo9uQL3GUcudGkE2V_nlysl6eHk,7811
21
+ nac_test_pyats_common/sdwan/ssh_test_base.py,sha256=rZF7txhu0FGtPtTzGMZFqB9hwClmtggroZwRuX3BGU4,4210
22
+ nac_test_pyats_common-0.2.1.dist-info/METADATA,sha256=cOKNshimaxKY2EHl51EWxhnhsOG8wteZGTRPqf6G6OY,13104
23
+ nac_test_pyats_common-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
24
+ nac_test_pyats_common-0.2.1.dist-info/licenses/LICENSE,sha256=zt2sx-c0iEk6-OO0iqRQ4l6fIGazRKW_qLMqfDpLm6M,16295
25
+ nac_test_pyats_common-0.2.1.dist-info/RECORD,,
@@ -1,67 +0,0 @@
1
- # SPDX-License-Identifier: MPL-2.0
2
- # Copyright (c) 2025 Daniel Schmidt
3
-
4
- """Catalyst Center device resolver placeholder for D2D testing.
5
-
6
- This is a placeholder for the Catalyst Center resolver that will be implemented
7
- when Catalyst Center D2D testing support is added. This resolver will handle
8
- IOS-XE devices managed by Catalyst Center.
9
-
10
- Expected environment variables:
11
- - CC_URL: Catalyst Center controller URL
12
- - CC_USERNAME: Catalyst Center username
13
- - CC_PASSWORD: Catalyst Center password
14
- - IOSXE_USERNAME: SSH username for devices
15
- - IOSXE_PASSWORD: SSH password for devices
16
-
17
- """
18
-
19
- from typing import Any
20
-
21
- from nac_test_pyats_common.common import BaseDeviceResolver
22
-
23
- from .registry import register_iosxe_resolver
24
-
25
-
26
- @register_iosxe_resolver("CC")
27
- class CatalystCenterDeviceResolver(BaseDeviceResolver):
28
- """Placeholder resolver for Catalyst Center D2D testing.
29
-
30
- This resolver will be implemented when Catalyst Center D2D testing
31
- support is added. Currently a placeholder to reserve the CC registry slot.
32
- """
33
-
34
- def get_architecture_name(self) -> str:
35
- """Return architecture name."""
36
- return "catalyst_center"
37
-
38
- def get_schema_root_key(self) -> str:
39
- """Return data model root key."""
40
- return "catalyst_center"
41
-
42
- def navigate_to_devices(self) -> list[dict[str, Any]]:
43
- """Navigate to devices in data model."""
44
- raise NotImplementedError(
45
- "CatalystCenterDeviceResolver is not yet implemented. "
46
- "This placeholder reserves the CC registry slot for future use."
47
- )
48
-
49
- def extract_device_id(self, device_data: dict[str, Any]) -> str:
50
- """Extract device ID."""
51
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
52
-
53
- def extract_hostname(self, device_data: dict[str, Any]) -> str:
54
- """Extract hostname."""
55
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
56
-
57
- def extract_host_ip(self, device_data: dict[str, Any]) -> str:
58
- """Extract management IP."""
59
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
60
-
61
- def extract_os_type(self, device_data: dict[str, Any]) -> str:
62
- """Extract OS type."""
63
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
64
-
65
- def get_credential_env_vars(self) -> tuple[str, str]:
66
- """Return credential environment variable names."""
67
- return ("IOSXE_USERNAME", "IOSXE_PASSWORD")
@@ -1,24 +0,0 @@
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,,