diff --git a/README.md b/README.md index 10119ce..690040c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ pip install netboxlabs-diode-sdk * `DIODE_SENTRY_DSN` - Optional Sentry DSN for error reporting * `DIODE_CLIENT_ID` - Client ID for OAuth2 authentication * `DIODE_CLIENT_SECRET` - Client Secret for OAuth2 authentication +* `DIODE_DRY_RUN_OUTPUT_DIR` - Directory where `DiodeDryRunClient` will write JSON files ### Example @@ -75,6 +76,34 @@ if __name__ == "__main__": ``` +### Dry run mode + +`DiodeDryRunClient` generates ingestion requests without contacting a Diode server. Requests are printed to stdout by default, or written to JSON files when `output_dir` (or the `DIODE_DRY_RUN_OUTPUT_DIR` environment variable) is specified. The `app_name` parameter serves as the filename prefix; if not provided, `dryrun` is used as the default prefix. The file name is suffixed with a nanosecond-precision timestamp, resulting in the format `_.json`. + +```python +from netboxlabs.diode.sdk import DiodeDryRunClient + +with DiodeDryRunClient(app_name="my_app", output_dir="/tmp") as client: + client.ingest([ + Entity(device="Device A"), + ]) +``` + +The produced file can later be ingested by a real Diode instance using +`load_dryrun_entities` with a standard `DiodeClient`: + +```python +from netboxlabs.diode.sdk import DiodeClient, load_dryrun_entities + +with DiodeClient( + target="grpc://localhost:8080/diode", + app_name="my-test-app", + app_version="0.0.1", +) as client: + entities = list(load_dryrun_entities("my_app_92722156890707.json")) + client.ingest(entities=entities) +``` + ## Supported entities (object types) * ASN diff --git a/netboxlabs/diode/sdk/__init__.py b/netboxlabs/diode/sdk/__init__.py index c499df8..89a22e9 100644 --- a/netboxlabs/diode/sdk/__init__.py +++ b/netboxlabs/diode/sdk/__init__.py @@ -2,6 +2,12 @@ # Copyright 2024 NetBox Labs Inc """NetBox Labs, Diode - SDK.""" -from netboxlabs.diode.sdk.client import DiodeClient +from netboxlabs.diode.sdk.client import ( + DiodeClient, + DiodeDryRunClient, + load_dryrun_entities, +) assert DiodeClient +assert DiodeDryRunClient +assert load_dryrun_entities diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 3c650f1..85f45b8 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -9,13 +9,17 @@ import os import platform import ssl +import sys +import time import uuid from collections.abc import Iterable +from pathlib import Path from urllib.parse import urlencode, urlparse import certifi import grpc import sentry_sdk +from google.protobuf.json_format import MessageToJson, ParseDict from netboxlabs.diode.sdk.diode.v1 import ingester_pb2, ingester_pb2_grpc from netboxlabs.diode.sdk.exceptions import DiodeClientError, DiodeConfigError @@ -27,11 +31,28 @@ _DIODE_SENTRY_DSN_ENVVAR_NAME = "DIODE_SENTRY_DSN" _CLIENT_ID_ENVVAR_NAME = "DIODE_CLIENT_ID" _CLIENT_SECRET_ENVVAR_NAME = "DIODE_CLIENT_SECRET" +_DRY_RUN_OUTPUT_DIR_ENVVAR_NAME = "DIODE_DRY_RUN_OUTPUT_DIR" _INGEST_SCOPE = "diode:ingest" _DEFAULT_STREAM = "latest" _LOGGER = logging.getLogger(__name__) +def load_dryrun_entities(file_path: str | Path) -> Iterable[Entity]: + """Yield entities from a file with concatenated JSON messages.""" + path = Path(file_path) + with path.open("r") as fh: + request = json.load(fh) + req_pb = ingester_pb2.IngestRequest() + ParseDict(request, req_pb) + yield from req_pb.entities + + +class DiodeClientInterface: + """Runtime placeholder for the Diode client interface.""" + + pass + + def _load_certs() -> bytes: """Loads cacert.pem.""" with open(certifi.where(), "rb") as f: @@ -82,7 +103,7 @@ def _get_optional_config_value( return value -class DiodeClient: +class DiodeClient(DiodeClientInterface): """Diode Client.""" _name = "diode-sdk-python" @@ -287,6 +308,77 @@ def _authenticate(self, scope: str): ) + [("authorization", f"Bearer {access_token}")] +class DiodeDryRunClient(DiodeClientInterface): + """Client that outputs ingestion requests instead of sending them.""" + + _name = "diode-sdk-python-dry-run" + _version = version_semver() + _app_name = None + _app_version = None + + def __init__(self, app_name: str = "dryrun", output_dir: str | None = None): + """Initiate a new dry run client.""" + self._output_dir = os.getenv(_DRY_RUN_OUTPUT_DIR_ENVVAR_NAME, output_dir) + self._app_name = app_name + + @property + def name(self) -> str: + """Retrieve the name.""" + return self._name + + @property + def version(self) -> str: + """Retrieve the version.""" + return self._version + + @property + def app_name(self) -> str: + """Retrieve the app name.""" + return self._app_name + + @property + def output_dir(self) -> str | None: + """Retrieve the dry run output dir.""" + return self._output_dir + + def __enter__(self): + """Enters the runtime context related to the channel object.""" + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + """Exits the runtime context related to the channel object.""" + + def ingest( + self, + entities: Iterable[Entity | ingester_pb2.Entity | None], + stream: str | None = _DEFAULT_STREAM, + ) -> ingester_pb2.IngestResponse: + """Ingest entities in dry run mode.""" + request = ingester_pb2.IngestRequest( + stream=stream, + id=str(uuid.uuid4()), + producer_app_name=self._app_name, + entities=entities, + sdk_name=self.name, + sdk_version=self.version, + ) + + output = MessageToJson(request, preserving_proto_field_name=True) + if self._output_dir: + timestamp = time.perf_counter_ns() + path = Path(self._output_dir) + path.mkdir(parents=True, exist_ok=True) + filename = "".join( + c if c.isalnum() or c in ("_", "-") else "_" for c in self._app_name + ) + file_path = path / f"{filename}_{timestamp}.json" + with file_path.open("w") as fh: + fh.write(output) + else: + print(output, file=sys.stdout) + return ingester_pb2.IngestResponse() + + class _DiodeAuthentication: def __init__( self, diff --git a/netboxlabs/diode/sdk/client.pyi b/netboxlabs/diode/sdk/client.pyi new file mode 100644 index 0000000..16deeaf --- /dev/null +++ b/netboxlabs/diode/sdk/client.pyi @@ -0,0 +1,25 @@ +from __future__ import annotations + +from collections.abc import Iterable +from typing import Protocol, runtime_checkable + +from netboxlabs.diode.sdk.diode.v1 import ingester_pb2 +from netboxlabs.diode.sdk.ingester import Entity + +_DEFAULT_STREAM: str + +@runtime_checkable +class DiodeClientInterface(Protocol): + """Interface implemented by diode clients.""" + + @property + def name(self) -> str: ... + @property + def version(self) -> str: ... + def ingest( + self, + entities: Iterable[Entity | ingester_pb2.Entity | None], + stream: str | None = _DEFAULT_STREAM, + ) -> ingester_pb2.IngestResponse: ... + def __enter__(self) -> DiodeClientInterface: ... + def __exit__(self, exc_type, exc_value, exc_traceback) -> None: ... diff --git a/tests/fixtures/message.json b/tests/fixtures/message.json new file mode 100644 index 0000000..8235e8a --- /dev/null +++ b/tests/fixtures/message.json @@ -0,0 +1,3643 @@ +{ + "stream": "latest", + "entities": [ + { + "asn": { + "asn": "555", + "rir": { + "name": "RIR 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "description": "ASN 555 Description", + "comments": "ASN 555 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "asn_range": { + "name": "ASN Range 1", + "slug": "asn-range-1", + "rir": { + "name": "RIR 1" + }, + "start": "1", + "end": "2", + "tenant": { + "name": "Tenant 1" + }, + "description": "ASN Range 1 Description", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "aggregate": { + "prefix": "182.82.82.0/24", + "rir": { + "name": "RIR 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "date_added": "2025-04-14T08:08:55Z", + "description": "Aggregate Description", + "comments": "Aggregate Comments", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "circuit": { + "cid": "Circuit 1", + "provider": { + "name": "Provider 1" + }, + "provider_account": { + "provider": { + "name": "Provider 1" + }, + "account": "account1" + }, + "type": { + "name": "Circuit Type 1" + }, + "status": "offline", + "tenant": { + "name": "Tenant 1" + }, + "install_date": "2025-04-14T00:00:00Z", + "termination_date": "2025-04-14T00:00:00Z", + "commit_rate": "10", + "description": "Circuit 1 Description", + "distance": 12.4, + "distance_unit": "ft", + "comments": "Circuit 1 Comments", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "circuit_group": { + "name": "Circuit Group 1", + "description": "Circuit Group 1 Description", + "tenant": { + "name": "Tenant 1" + }, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "circuit_group_assignment": { + "group": { + "name": "Circuit Group 1" + }, + "member_circuit": { + "cid": "Circuit 1", + "type": { + "name": "Circuit Type 1" + }, + "provider": { + "name": "Provider 1" + } + }, + "priority": "tertiary" + } + }, + { + "circuit_group_assignment": { + "group": { + "name": "Circuit Group 1" + }, + "member_virtual_circuit": { + "cid": "Virtual Circuit 1", + "type": { + "name": "Virtual Circuit Type 1" + }, + "provider_network": { + "name": "Provider Network 1", + "provider": { + "name": "Provider 1" + } + } + }, + "priority": "tertiary" + } + }, + { + "circuit_termination": { + "circuit": { + "cid": "Circuit 1", + "type": { + "name": "Circuit Type 1" + }, + "provider": { + "name": "Provider 1" + } + }, + "term_side": "A", + "termination_location": { + "name": "attic", + "site": { + "name": "Site 1" + } + }, + "port_speed": "9600", + "upstream_speed": "14400", + "xconnect_id": "xconnect.1", + "pp_info": "pp info", + "description": "description", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "circuit_termination": { + "circuit": { + "cid": "Circuit 1", + "type": { + "name": "Circuit Type 1" + }, + "provider": { + "name": "Provider 1" + } + }, + "term_side": "A", + "termination_provider_network": { + "provider": { + "name": "Provider 1" + }, + "name": "Provider Network 1", + "service_id": "service.1" + }, + "port_speed": "9600", + "upstream_speed": "14400", + "xconnect_id": "xconnect.1", + "pp_info": "pp info", + "description": "description", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "circuit_termination": { + "circuit": { + "cid": "Circuit 1", + "type": { + "name": "Circuit Type 1" + }, + "provider": { + "name": "Provider 1" + } + }, + "term_side": "A", + "termination_site": { + "name": "Site 1" + }, + "port_speed": "9600", + "upstream_speed": "14400", + "xconnect_id": "xconnect.1", + "pp_info": "pp info", + "description": "description", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "circuit_termination": { + "circuit": { + "cid": "Circuit 1", + "type": { + "name": "Circuit Type 1" + }, + "provider": { + "name": "Provider 1" + } + }, + "term_side": "A", + "termination_site_group": { + "name": "Site Group 1" + }, + "port_speed": "9600", + "upstream_speed": "14400", + "xconnect_id": "xconnect.1", + "pp_info": "pp info", + "description": "description", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "circuit_type": { + "name": "Circuit Type 1", + "slug": "circuit-type-1", + "color": "0000ff", + "description": "Circuit Type 1 Description", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "cluster": { + "name": "Cluster A", + "type": { + "name": "Cluster Type 1" + }, + "group": { + "name": "Cluster Group 1" + }, + "status": "active", + "tenant": { + "name": "Tenant 1" + }, + "scope_location": { + "name": "Location 1", + "site": { + "name": "Site 1" + } + }, + "description": "Cluster 1 Description", + "comments": "Cluster 1 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "cluster": { + "name": "Cluster 2", + "type": { + "name": "Cluster Type 1" + }, + "group": { + "name": "Cluster Group 1" + }, + "status": "active", + "tenant": { + "name": "Tenant 1" + }, + "scope_region": { + "name": "Region 1" + }, + "description": "Cluster 1 Description", + "comments": "Cluster 1 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "cluster": { + "name": "Cluster 3", + "type": { + "name": "Cluster Type 1" + }, + "group": { + "name": "Cluster Group 1" + }, + "status": "active", + "tenant": { + "name": "Tenant 1" + }, + "scope_site": { + "name": "Site 1" + }, + "description": "Cluster 1 Description", + "comments": "Cluster 1 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "cluster": { + "name": "Cluster 4", + "type": { + "name": "Cluster Type 1" + }, + "group": { + "name": "Cluster Group 1" + }, + "status": "active", + "tenant": { + "name": "Tenant 1" + }, + "scope_site_group": { + "name": "Site Group 1" + }, + "description": "Cluster 1 Description", + "comments": "Cluster 1 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "cluster_group": { + "name": "Cluster Group 1", + "description": "Cluster Group 1 Description", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "cluster_type": { + "name": "Cluster Type 1", + "description": "Cluster Type 1 Description", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "console_port": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Manufacturer 1" + }, + "model": "Module Type 1" + } + }, + "name": "Console Port 1", + "label": "Console Port 1 Label", + "type": "db-25", + "speed": "1200", + "description": "Console Port 1 Description", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "console_server_port": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Manufacturer 1" + }, + "model": "Module Type 1" + } + }, + "name": "Console Server Port 1", + "label": "Console Server Port 1 Label", + "type": "db-25", + "speed": "1200", + "description": "Console Server Port 1 Description", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "contact": { + "group": { + "name": "Contact Group 1" + }, + "name": "Contact 1", + "title": "Contact 1 Title", + "phone": "1234567890", + "email": "contact1@example.com", + "address": "1234 Main St, Anytown, USA", + "link": "https://example.com", + "description": "Contact 1 Description", + "comments": "Contact 1 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "contact_assignment": { + "contact": { + "name": "Contact 1" + }, + "role": { + "name": "Contact Role 1" + }, + "priority": "primary", + "tags": [ + { + "name": "Tag 1" + } + ], + "object_site": { + "name": "Site 1" + } + } + }, + { + "contact_group": { + "name": "Contact Group 1", + "parent": { + "name": "Contact Group 2" + }, + "description": "Contact Group 1 Description", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "contact_role": { + "name": "Contact Role 1", + "description": "Contact Role 1 Description", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "device": { + "name": "Device ABC", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "platform": { + "name": "Platform 1" + }, + "serial": "1234567890", + "asset_tag": "asset.1", + "site": { + "name": "Site 1" + }, + "location": { + "name": "Location 1", + "site": { + "name": "Site 1" + } + }, + "rack": { + "name": "Rack 1", + "site": { + "name": "Site 1" + }, + "location": { + "name": "Location 1", + "site": { + "name": "Site 1" + } + } + }, + "position": 1.0, + "face": "front", + "latitude": 34.0, + "longitude": 75.0, + "status": "active", + "airflow": "bottom-to-top", + "primary_ip4": { + "address": "192.168.99.1", + "assigned_object_interface": { + "name": "GigabitEthernet1/0/1", + "type": "1000base-t", + "device": { + "name": "Device ABC", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "site": { + "name": "Site 1" + } + } + } + }, + "primary_ip6": { + "address": "2001:ddd::1", + "assigned_object_interface": { + "name": "GigabitEthernet1/0/1", + "type": "1000base-t", + "device": { + "name": "Device ABC", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "site": { + "name": "Site 1" + } + } + } + }, + "cluster": { + "name": "Cluster 1", + "type": { + "name": "Cluster Type 1" + } + }, + "virtual_chassis": { + "name": "Virtual Chassis 1" + }, + "vc_position": "1", + "vc_priority": "1", + "description": "Device 1 Description", + "comments": "Device 1 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "device_bay": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C3P0", + "subdevice_role": "parent" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Device Bay 1", + "label": "Device Bay 1 Label", + "description": "Device Bay 1 Description", + "installed_device": { + "name": "Device 2", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "tags": [ + { + "name": "Tag 1" + } + ] + } + }, + { + "device_role": { + "name": "Core Router", + "slug": "core-router", + "color": "ff0000", + "vm_role": true, + "description": "Primary network routing device", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "default_platform": { + "name": "IOS-XE" + }, + "model": "Catalyst 9300", + "slug": "catalyst-9300", + "part_number": "C9300-48P-E", + "u_height": 1.0, + "exclude_from_utilization": false, + "is_full_depth": true, + "subdevice_role": "parent", + "airflow": "front-to-rear", + "weight": 14.5, + "weight_unit": "lb", + "description": "Enterprise Series Switch", + "comments": "High-performance access switch", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "fhrp_group": { + "name": "HSRP Group 10", + "protocol": "hsrp", + "group_id": "10", + "auth_type": "md5", + "auth_key": "secretkey123", + "description": "Core Router HSRP Group", + "comments": "Primary gateway redundancy group", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "fhrp_group_assignment": { + "group": { + "name": "HSRP Group 10", + "protocol": "hsrp", + "group_id": "10" + }, + "interface_interface": { + "device": { + "name": "Device 1", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "site": { + "name": "Site 1" + } + }, + "name": "GigabitEthernet1/0/1", + "type": "1000base-t", + "enabled": true + }, + "priority": "100" + } + }, + { + "front_port": { + "device": { + "name": "Device 1", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-MODULE" + } + }, + "name": "Front Port 1", + "label": "FP1", + "type": "lc-apc", + "color": "0000ff", + "rear_port": { + "device": { + "name": "Device 1", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Rear Port 1", + "type": "lc-apc" + }, + "rear_port_position": "1", + "description": "Front fiber port", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "ike_policy": { + "name": "IKE-POLICY-1", + "description": "Main IPSec IKE Policy", + "version": "2", + "preshared_key": "secretPSK123!", + "comments": "Primary IKE policy for VPN tunnels", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "proposals": [ + { + "name": "IKE-PROPOSAL-1", + "description": "AES-256 with SHA-256", + "authentication_method": "preshared-keys", + "encryption_algorithm": "aes-256-cbc", + "authentication_algorithm": "hmac-sha256", + "group": "14", + "sa_lifetime": "28800" + } + ] + } + }, + { + "ike_proposal": { + "name": "IKE-PROPOSAL-2", + "description": "High Security IKE Proposal", + "authentication_method": "certificates", + "encryption_algorithm": "aes-256-gcm", + "authentication_algorithm": "hmac-sha512", + "group": "21", + "sa_lifetime": "86400", + "comments": "Enhanced security proposal for critical VPNs", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "ip_address": { + "address": "192.168.100.1/24", + "vrf": { + "name": "PROD-VRF", + "rd": "65000:1" + }, + "tenant": { + "name": "Tenant 1" + }, + "status": "active", + "role": "vip", + "assigned_object_interface": { + "device": { + "name": "Device 1", + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "role": { + "name": "Device Role 1" + }, + "site": { + "name": "Site 1" + } + }, + "name": "GigabitEthernet1/0/1", + "type": "1000base-t" + }, + "nat_inside": { + "address": "10.0.0.1/24" + }, + "dns_name": "prod-vip.example.com", + "description": "Production VIP Address", + "comments": "Primary virtual IP for load balancing", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "ip_range": { + "start_address": "10.100.0.1", + "end_address": "10.100.0.254", + "vrf": { + "name": "PROD-VRF", + "rd": "65000:1" + }, + "tenant": { + "name": "Tenant 1" + }, + "status": "active", + "role": { + "name": "Server Pool", + "slug": "server-pool" + }, + "description": "Production Server IP Range", + "comments": "Allocated for production server deployments", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "mark_utilized": true + } + }, + { + "ip_sec_policy": { + "name": "IPSEC-POLICY-1", + "description": "Site-to-Site VPN Policy", + "pfs_group": "14", + "comments": "High-security IPSec policy for site-to-site VPN", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "proposals": [ + { + "name": "IPSEC-PROPOSAL-1", + "description": "AES-256-GCM with ESP", + "encryption_algorithm": "aes-256-gcm", + "sa_lifetime_seconds": "28800", + "sa_lifetime_data": "28800", + "comments": "Strong encryption proposal for VPN tunnels" + } + ] + } + }, + { + "ip_sec_profile": { + "name": "IPSEC-PROFILE-1", + "description": "Remote Access VPN Profile", + "mode": "esp", + "ike_policy": { + "name": "IKE-POLICY-1", + "version": "2", + "preshared_key": "secretkey123" + }, + "ipsec_policy": { + "name": "IPSEC-POLICY-1", + "description": "Strong encryption policy", + "pfs_group": "14" + }, + "comments": "Standard IPSec profile for remote access VPN tunnels", + "tags": [ + { + "name": "VPN" + }, + { + "name": "Remote-Access" + } + ] + } + }, + { + "ip_sec_proposal": { + "name": "IPSec-Proposal-AES256", + "description": "High security IPSec proposal using AES-256-GCM", + "encryption_algorithm": "aes-256-gcm", + "authentication_algorithm": "hmac-sha512", + "sa_lifetime_seconds": "28800", + "sa_lifetime_data": "42949", + "comments": "Used for critical infrastructure VPNs", + "tags": [ + { + "name": "high-security", + "slug": "high-security", + "color": "0000ff" + }, + { + "name": "production", + "slug": "production", + "color": "0000ff" + } + ] + } + }, + { + "interface": { + "name": "GigabitEthernet1/0/1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-MODULE" + } + }, + "label": "Core Link 1", + "type": "1000base-t", + "enabled": true, + "bridge": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Bridge1", + "type": "bridge" + }, + "lag": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Port-Channel2", + "type": "lag" + }, + "mtu": "9000", + "primary_mac_address": { + "mac_address": "00:11:22:33:44:55" + }, + "speed": "1000000000", + "duplex": "full", + "wwn": "50:01:43:80:00:00:00:00", + "vrf": { + "name": "PROD-VRF", + "rd": "65000:1" + }, + "description": "Core network interface", + "mode": "tagged", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "mgmt_only": false, + "poe_mode": "pse", + "poe_type": "type3-ieee802.3bt", + "untagged_vlan": { + "vid": 100, + "name": "Data VLAN", + "status": "active" + }, + "vlan_translation_policy": { + "name": "Customer Translation Policy", + "description": "VLAN translation for customer traffic" + }, + "vdcs": [ + { + "name": "VDC1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "identifier": 1, + "status": "active", + "description": "Primary VDC" + } + ], + "tagged_vlans": [ + { + "vid": 101, + "name": "Voice VLAN", + "status": "active" + }, + { + "vid": 102, + "name": "Data VLAN", + "status": "active" + } + ] + } + }, + { + "interface": { + "name": "WirelessGigabitEthernet1/0/1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-MODULE" + } + }, + "label": "Core Link 1", + "type": "other-wireless", + "enabled": true, + "bridge": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Bridge1", + "type": "bridge" + }, + "lag": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Port-Channel2", + "type": "lag" + }, + "mtu": "9000", + "primary_mac_address": { + "mac_address": "00:11:22:33:44:55" + }, + "speed": "1000000000", + "duplex": "full", + "wwn": "50:01:43:80:00:00:00:00", + "rf_role": "ap", + "rf_channel": "2.4g-1-2412-22", + "tx_power": "20", + "wireless_lans": [ + { + "ssid": "Corp-Secure", + "description": "Corporate secure wireless network", + "group": { + "name": "Corporate Networks", + "slug": "corporate-networks" + }, + "status": "active", + "vlan": { + "vid": 800, + "name": "Production Servers" + }, + "tenant": { + "name": "Tenant 1" + } + } + ], + "vrf": { + "name": "PROD-VRF" + }, + "description": "Core network interface", + "mode": "access", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "mgmt_only": false, + "untagged_vlan": { + "vid": 900, + "name": "Data VLAN", + "status": "active" + }, + "vlan_translation_policy": { + "name": "Customer Translation Policy", + "description": "VLAN translation for customer traffic" + }, + "vdcs": [ + { + "name": "VDC1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "identifier": 1, + "status": "active", + "description": "Primary VDC" + } + ], + "tagged_vlans": [ + { + "vid": 101, + "name": "Voice VLAN", + "status": "active" + }, + { + "vid": 102, + "name": "Data VLAN", + "status": "active" + } + ] + } + }, + { + "interface": { + "name": "VirtualGigabitEthernet1/0/1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "parent": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Port-Channel1", + "type": "1000base-t" + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-MODULE" + } + }, + "label": "Core Link 1", + "type": "virtual", + "enabled": true, + "bridge": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Bridge1", + "type": "bridge" + }, + "mtu": "9000", + "primary_mac_address": { + "mac_address": "00:11:22:33:44:55" + }, + "speed": "1000000000", + "duplex": "full", + "wwn": "50:01:43:80:00:00:00:00", + "vrf": { + "name": "PROD-VRF" + }, + "description": "Core network interface", + "mode": "q-in-q", + "mark_connected": false, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "mgmt_only": false, + "untagged_vlan": { + "vid": 444, + "name": "Data VLAN", + "status": "active" + }, + "qinq_svlan": { + "vid": 2000, + "name": "Service VLAN", + "status": "active" + }, + "vlan_translation_policy": { + "name": "Customer Translation Policy", + "description": "VLAN translation for customer traffic" + }, + "vdcs": [ + { + "name": "VDC1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "identifier": 1, + "status": "active", + "description": "Primary VDC" + } + ], + "tagged_vlans": [ + { + "vid": 101, + "name": "Voice VLAN", + "status": "active" + }, + { + "vid": 102, + "name": "Data VLAN", + "status": "active" + } + ] + } + }, + { + "inventory_item": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "parent": { + "name": "Chassis 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "name": "Power Supply 1", + "label": "PSU1", + "role": { + "name": "Power Supply", + "color": "00ff00" + }, + "manufacturer": { + "name": "Cisco" + }, + "part_id": "PWR-C1-715WAC", + "serial": "ABC123XYZ", + "asset_tag": "ASSET-001", + "discovered": true, + "description": "715W AC Power Supply", + "status": "active", + "component_power_port": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "PSU1 Power Port", + "type": "iec-60320-c14", + "maximum_draw": 715, + "allocated_draw": 500, + "description": "Power input port for PSU1" + }, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "inventory_item_role": { + "name": "Line Card", + "slug": "line-card", + "color": "0000ff", + "description": "Network switch line card module", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "l2vpn": { + "name": "Customer-VPLS-1", + "slug": "customer-vpls-1", + "type": "vpls", + "identifier": "65000", + "import_targets": [ + { + "name": "65000:1001", + "description": "Primary import target" + }, + { + "name": "65000:1002", + "description": "Secondary import target" + } + ], + "export_targets": [ + { + "name": "65000:1003", + "description": "Primary export target" + } + ], + "description": "Customer VPLS service for multi-site connectivity", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "l2vpn_termination": { + "l2vpn": { + "name": "Customer-VPLS-1", + "type": "vpls", + "identifier": "65000" + }, + "assigned_object_interface": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "GigabitEthernet1/0/1", + "type": "1000base-t", + "enabled": true + }, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "location": { + "name": "Data Center East Wing", + "slug": "dc-east-wing", + "site": { + "name": "Site 1" + }, + "parent": { + "name": "Main Data Center", + "slug": "main-dc", + "site": { + "name": "Site 1" + } + }, + "status": "active", + "tenant": { + "name": "Tenant 1" + }, + "facility": "Building A, Floor 3", + "description": "East wing of the main data center facility", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "mac_address": { + "mac_address": "00:1A:2B:3C:4D:5E", + "assigned_object_interface": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "GigabitEthernet1/0/1", + "type": "1000base-t", + "enabled": true + }, + "description": "Primary management interface MAC", + "comments": "Reserved for network management access", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "manufacturer": { + "name": "Arista Networks", + "slug": "arista-networks", + "description": "Leading provider of cloud networking solutions", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-STACK" + }, + "status": "active", + "serial": "MOD123XYZ", + "asset_tag": "MOD-001", + "description": "Stacking module for switch interconnect", + "comments": "Primary stack member module", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "module_bay": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Stack Module Bay 2", + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-STACK" + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + } + }, + "label": "STACK-2", + "position": "Rear", + "description": "Secondary stacking module bay", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C9300-NM-8X", + "part_number": "C9300-NM-8X=", + "airflow": "front-to-rear", + "weight": 0.7, + "weight_unit": "kg", + "description": "Catalyst 9300 8 x 10GE Network Module", + "comments": "Hot-swappable uplink module for C9300 series switches", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "platform": { + "name": "Cisco IOS-XE", + "slug": "cisco-ios-xe", + "manufacturer": { + "name": "Cisco" + }, + "description": "Enterprise-class IOS operating system for Catalyst switches and ISR routers", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "power_feed": { + "power_panel": { + "site": { + "name": "Site 1" + }, + "name": "Panel A" + }, + "rack": { + "name": "Rack 1", + "site": { + "name": "Site 1" + } + }, + "name": "Power Feed A1", + "status": "active", + "type": "primary", + "supply": "ac", + "phase": "three-phase", + "voltage": "208", + "amperage": "30", + "max_utilization": "80", + "mark_connected": true, + "description": "Primary power feed for network equipment rack", + "tenant": { + "name": "Tenant 1" + }, + "comments": "Connected to UPS system A with redundant backup", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "power_outlet": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "PWR-C1-715WAC" + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + } + }, + "name": "PSU1-Outlet1", + "label": "OUT-1", + "type": "iec-60320-c13", + "color": "0000ff", + "power_port": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "PSU1" + }, + "feed_leg": "A", + "description": "Power outlet for network switch PSU", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "power_panel": { + "site": { + "name": "Site 1" + }, + "location": { + "name": "Data Center East Wing", + "slug": "dc-east-wing", + "site": { + "name": "Site 1" + } + }, + "name": "Panel B", + "description": "Secondary power distribution panel for network equipment", + "comments": "Connected to redundant UPS system with automatic transfer switch", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "power_port": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "PWR-C1-715WAC" + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + } + }, + "name": "PSU2", + "label": "PWR-2", + "type": "iec-60320-c14", + "maximum_draw": "715", + "allocated_draw": "650", + "description": "Secondary power supply unit input", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "prefix": { + "prefix": "10.100.0.0/16", + "vrf": { + "name": "PROD-VRF", + "rd": "65000:1" + }, + "scope_site": { + "name": "Site 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "vlan": { + "name": "Production VLAN", + "vid": "112" + }, + "status": "active", + "role": { + "name": "Production", + "slug": "production" + }, + "is_pool": true, + "mark_utilized": true, + "description": "Production network address space", + "comments": "Primary address allocation for production services", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "provider": { + "name": "Level 3 Communications", + "slug": "level3", + "description": "Global Tier 1 Internet Service Provider", + "comments": "Primary transit provider for data center connectivity", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "accounts": [ + { + "provider": { + "name": "Level 3 Communications" + }, + "name": "East Coast Account", + "account": "L3-12345", + "description": "East Coast regional services account", + "comments": "Managed through regional NOC" + }, + { + "provider": { + "name": "Level 3 Communications" + }, + "name": "West Coast Account", + "account": "L3-67890", + "description": "West Coast regional services account", + "comments": "Managed through regional NOC" + } + ], + "asns": [ + { + "asn": "3356", + "rir": { + "name": "ARIN" + }, + "tenant": { + "name": "Tenant 1" + }, + "description": "Level 3 Global ASN", + "comments": "Primary transit ASN" + } + ] + } + }, + { + "provider_account": { + "provider": { + "name": "AT&T" + }, + "name": "Global Enterprise Services", + "account": "ATT-GES-123456", + "description": "Global enterprise services and solutions account", + "comments": "Primary account for worldwide MPLS and managed services", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "provider_network": { + "provider": { + "name": "Level 3 Communications" + }, + "name": "Global MPLS Network", + "service_id": "L3-MPLS-001", + "description": "Global MPLS backbone network", + "comments": "Primary enterprise MPLS network infrastructure", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "rir": { + "name": "ARIN", + "slug": "arin", + "is_private": false, + "description": "American Registry for Internet Numbers", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "rack": { + "name": "Rack 1", + "facility_id": "FAC-001", + "site": { + "name": "Site 1" + }, + "location": { + "name": "Data Center East Wing", + "site": { + "name": "Site 1" + } + }, + "tenant": { + "name": "Tenant 1" + }, + "status": "active", + "role": { + "name": "Server Rack", + "slug": "server-rack", + "color": "0000ff", + "description": "Primary server rack role" + }, + "serial": "RACK123XYZ", + "asset_tag": "RACK-001", + "rack_type": { + "manufacturer": { + "name": "Manufacturer 1" + }, + "model": "R2000", + "slug": "r2000", + "form_factor": "4-post-cabinet" + }, + "form_factor": "4-post-cabinet", + "width": "19", + "u_height": "42", + "starting_unit": "1", + "weight": "350.5", + "max_weight": "1000", + "weight_unit": "lb", + "desc_units": false, + "outer_width": "24", + "outer_depth": "36", + "outer_unit": "in", + "mounting_depth": "30", + "airflow": "front-to-rear", + "description": "Standard 42U server rack", + "comments": "Located in primary data center", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "rack_reservation": { + "rack": { + "name": "Rack 1", + "site": { + "name": "Site 1" + } + }, + "units": [ + "1", + "2", + "3", + "4" + ], + "tenant": { + "name": "Tenant 1" + }, + "description": "Reserved for new server deployment", + "comments": "Project XYZ equipment installation", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "rack_role": { + "name": "Network Equipment", + "slug": "network-equipment", + "color": "0000ff", + "description": "Dedicated racks for network infrastructure", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "rack_type": { + "manufacturer": { + "name": "Manufacturer 1" + }, + "model": "R2000", + "slug": "r2000", + "description": "Standard 42U server rack", + "form_factor": "4-post-cabinet", + "width": "19", + "u_height": "42", + "starting_unit": "1", + "desc_units": false, + "outer_width": "24", + "outer_depth": "36", + "outer_unit": "in", + "weight": 350.5, + "max_weight": "1000", + "weight_unit": "lb", + "mounting_depth": "30", + "comments": "Standard enterprise rack configuration", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "rear_port": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module": { + "asset_tag": "module-1234567890", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "module_bay": { + "name": "Module Bay 1", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + } + }, + "module_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S-MODULE" + } + }, + "name": "Rear Port 1", + "label": "RP1", + "type": "lc-apc", + "color": "ff00ff", + "positions": "1", + "description": "Rear fiber port", + "mark_connected": true, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "region": { + "name": "North America", + "slug": "north-america", + "parent": { + "name": "Global", + "slug": "global", + "description": "Global Region", + "tags": [ + { + "name": "Tag 1" + } + ] + }, + "description": "North American Region", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "role": { + "name": "Network Administrator", + "slug": "network-admin", + "weight": "1000", + "description": "Primary network administration role", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "route_target": { + "name": "65000:3001", + "tenant": { + "name": "Tenant 1" + }, + "description": "Primary route target for MPLS VPN", + "comments": "Used for customer VPN service", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "service": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Web Server", + "protocol": "tcp", + "ports": [ + "80", + "443" + ], + "description": "Primary web server service", + "comments": "Handles HTTPS traffic for main website", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "ipaddresses": [ + { + "address": "192.168.1.100/24", + "status": "active", + "dns_name": "web.example.com" + } + ] + } + }, + { + "site": { + "name": "Data Center West", + "slug": "dc-west", + "status": "active", + "region": { + "name": "North America", + "slug": "north-america" + }, + "group": { + "name": "Primary Data Centers", + "slug": "primary-dcs" + }, + "tenant": { + "name": "Tenant 1" + }, + "facility": "Building 7", + "time_zone": "America/Los_Angeles", + "description": "Primary West Coast Data Center", + "physical_address": "123 Tech Drive, San Jose, CA 95134", + "shipping_address": "Receiving Dock 3, 123 Tech Drive, San Jose, CA 95134", + "latitude": 37.3382, + "longitude": -121.8863, + "comments": "24x7 access requires security clearance", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "asns": [ + { + "asn": "555", + "rir": { + "name": "RIR 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "description": "ASN 555 Description", + "comments": "ASN 555 Comments", + "tags": [ + { + "name": "Tag 1" + } + ] + } + ] + } + }, + { + "site_group": { + "name": "Global Data Centers", + "slug": "global-dcs", + "parent": { + "name": "Infrastructure", + "slug": "infrastructure" + }, + "description": "Worldwide data center facilities", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "tag": { + "name": "Production", + "slug": "production", + "color": "ff0000" + } + }, + { + "tenant": { + "name": "Acme Corporation", + "slug": "acme-corp", + "group": { + "name": "Enterprise Customers", + "slug": "enterprise-customers" + }, + "description": "Global technology solutions provider", + "comments": "Fortune 500 company with worldwide operations", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "tenant_group": { + "name": "Financial Services", + "slug": "financial-services", + "parent": { + "name": "Enterprise Sectors", + "slug": "enterprise-sectors" + }, + "description": "Banking and financial industry customers", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "tunnel": { + "name": "DC-West-to-East-Primary", + "status": "active", + "group": { + "name": "Inter-DC Tunnels", + "slug": "inter-dc-tunnels" + }, + "encapsulation": "ipsec-tunnel", + "ipsec_profile": { + "name": "IPSEC-PROFILE-1", + "mode": "esp", + "ike_policy": { + "name": "IKE-POLICY-TUN-1", + "version": "2", + "preshared_key": "1234567890", + "comments": "Using AES-256-GCM encryption with PFS" + }, + "ipsec_policy": { + "name": "IPSEC-POLICY-1", + "pfs_group": "2" + } + }, + "tenant": { + "name": "Tenant 1" + }, + "tunnel_id": "1001", + "description": "Primary IPSec tunnel between West and East data centers", + "comments": "Using AES-256-GCM encryption with PFS", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "tunnel_group": { + "name": "Regional Backbones", + "slug": "regional-backbones", + "description": "High-capacity encrypted tunnels between regional data centers", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "tunnel_termination": { + "tunnel": { + "name": "DC-West-to-East-Primary", + "status": "active", + "encapsulation": "ipsec-tunnel" + }, + "role": "hub", + "termination_device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "outside_ip": { + "address": "203.0.113.1/24", + "status": "active", + "dns_name": "vpn1.example.com" + }, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "vlan": { + "group": { + "name": "Production VLANs", + "slug": "production-vlans" + }, + "vid": "807", + "name": "Production Servers", + "tenant": { + "name": "Tenant 1" + }, + "status": "active", + "role": { + "name": "Production", + "slug": "production" + }, + "description": "Primary production server network", + "qinq_role": "cvlan", + "qinq_svlan": { + "vid": "1909", + "name": "Service Provider VLAN" + }, + "comments": "Used for customer-facing production workloads", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "vlan_group": { + "name": "Data Center Core", + "slug": "dc-core", + "scope_site": { + "name": "Data Center West", + "slug": "dc-west", + "status": "active" + }, + "description": "Core network VLANs for data center infrastructure", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "vlan_translation_policy": { + "name": "Customer Edge Translation", + "description": "VLAN translation policy for customer edge interfaces" + } + }, + { + "vlan_translation_rule": { + "policy": { + "name": "Customer Edge Translation", + "description": "VLAN translation policy for customer edge interfaces" + }, + "local_vid": "100", + "remote_vid": "1100", + "description": "Map customer VLAN 100 to provider VLAN 1100" + } + }, + { + "vm_interface": { + "virtual_machine": { + "name": "web-server-01", + "status": "active", + "role": { + "name": "Web Server" + }, + "site": { + "name": "Site 1" + } + }, + "name": "eth0", + "enabled": true, + "parent": { + "virtual_machine": { + "name": "web-server-01", + "status": "active", + "role": { + "name": "Web Server" + }, + "site": { + "name": "Site 1" + } + }, + "name": "bond0" + }, + "bridge": { + "virtual_machine": { + "name": "web-server-01", + "status": "active", + "role": { + "name": "Web Server" + }, + "site": { + "name": "Site 1" + } + }, + "name": "br0" + }, + "mtu": "9000", + "primary_mac_address": { + "mac_address": "00:1A:2B:3C:4D:5E" + }, + "description": "Primary network interface", + "mode": "q-in-q", + "untagged_vlan": { + "vid": "1101", + "name": "Production Servers" + }, + "qinq_svlan": { + "vid": "1000", + "name": "Service Provider VLAN" + }, + "vlan_translation_policy": { + "name": "Customer Edge Translation" + }, + "vrf": { + "name": "PROD-VRF", + "rd": "65000:1" + }, + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "vrf": { + "name": "Customer-A-VRF", + "rd": "65000:100", + "tenant": { + "name": "Tenant 1" + }, + "enforce_unique": true, + "description": "Isolated routing domain for Customer A", + "comments": "Used for customer's private network services", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ], + "import_targets": [ + { + "name": "65000:100" + }, + { + "name": "65000:101" + } + ], + "export_targets": [ + { + "name": "65000:103" + } + ] + } + }, + { + "virtual_chassis": { + "name": "Stack-DC1-Core", + "domain": "dc1-core.example.com", + "master": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "description": "Core switch stack in DC1", + "comments": "Primary switching infrastructure for data center 1", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "virtual_circuit": { + "cid": "VC-001-LAX-NYC", + "provider_network": { + "provider": { + "name": "Level 3 Communications" + }, + "name": "Global MPLS Network", + "service_id": "L3-MPLS-001" + }, + "provider_account": { + "provider": { + "name": "Level 3 Communications" + }, + "name": "East Coast Account", + "account": "L3-12345" + }, + "type": { + "name": "MPLS L3VPN", + "slug": "mpls-l3vpn" + }, + "status": "active", + "tenant": { + "name": "Tenant 1" + }, + "description": "LAX to NYC MPLS circuit", + "comments": "Primary east-west connectivity", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "virtual_circuit_termination": { + "virtual_circuit": { + "cid": "VC-001-LAX-NYC", + "provider_network": { + "provider": { + "name": "Level 3 Communications" + }, + "name": "Global MPLS Network" + }, + "type": { + "name": "MPLS L3VPN", + "slug": "mpls-l3vpn" + } + }, + "role": "hub", + "interface": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "MegabitEthernet1/0/1", + "type": "virtual", + "enabled": true + }, + "description": "LAX hub termination for east-west MPLS circuit", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "virtual_circuit_type": { + "name": "EVPN-VXLAN", + "slug": "evpn-vxlan", + "color": "0000ff", + "description": "Data center interconnect using EVPN-VXLAN overlay", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "virtual_device_context": { + "name": "VDC-Production", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "identifier": "1", + "tenant": { + "name": "Tenant 1" + }, + "primary_ip4": { + "address": "192.168.1.1", + "assigned_object_interface": { + "type": "1000base-t", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "eth0" + } + }, + "primary_ip6": { + "address": "2001:db8::1", + "assigned_object_interface": { + "type": "1000base-t", + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "eth0" + } + }, + "status": "active", + "description": "Production virtual device context", + "comments": "Isolated network context for production services", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "virtual_disk": { + "virtual_machine": { + "name": "web-server-01", + "status": "active", + "role": { + "name": "Web Server" + }, + "site": { + "name": "Site 1" + } + }, + "name": "root-volume", + "description": "Primary system disk", + "size": "182400", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "virtual_machine": { + "name": "app-server-01", + "status": "active", + "site": { + "name": "Site 1" + }, + "cluster": { + "name": "Cluster 1", + "type": { + "name": "Cluster Type 1" + }, + "scope_site": { + "name": "Site 1" + } + }, + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + }, + "cluster": { + "name": "Cluster 1", + "type": { + "name": "Cluster Type 1" + }, + "scope_site": { + "name": "Site 1" + } + } + }, + "serial": "VM-2023-001", + "role": { + "name": "Application Server" + }, + "tenant": { + "name": "Tenant 1" + }, + "platform": { + "name": "Ubuntu 22.04" + }, + "primary_ip4": { + "address": "192.168.2.99", + "assigned_object_vm_interface": { + "virtual_machine": { + "name": "app-server-01", + "cluster": { + "name": "Cluster 1", + "type": { + "name": "Cluster Type 1" + }, + "scope_site": { + "name": "Site 1" + } + }, + "tenant": { + "name": "Tenant 1" + } + }, + "name": "eth0", + "enabled": true, + "mtu": "1500" + } + }, + "primary_ip6": { + "address": "2001:db8::99", + "assigned_object_vm_interface": { + "virtual_machine": { + "name": "app-server-01", + "cluster": { + "name": "Cluster 1", + "type": { + "name": "Cluster Type 1" + }, + "scope_site": { + "name": "Site 1" + } + }, + "tenant": { + "name": "Tenant 1" + } + }, + "name": "eth0", + "enabled": true, + "mtu": "1500" + } + }, + "vcpus": 4.0, + "memory": "214748364", + "disk": "147483647", + "description": "Primary application server instance", + "comments": "Hosts critical business applications", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "wireless_lan": { + "ssid": "Corp-Secure", + "description": "Corporate secure wireless network", + "group": { + "name": "Corporate Networks", + "slug": "corporate-networks" + }, + "status": "active", + "vlan": { + "vid": 100, + "name": "Production Servers" + }, + "scope_site": { + "name": "Site 1" + }, + "tenant": { + "name": "Tenant 1" + }, + "auth_type": "wpa-enterprise", + "auth_cipher": "aes", + "auth_psk": "SecureWiFiKey123!", + "comments": "Primary corporate wireless network with 802.1X authentication", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "wireless_lan_group": { + "name": "Corporate Networks", + "slug": "corporate-networks", + "parent": { + "name": "All Networks", + "slug": "all-networks" + }, + "description": "Enterprise corporate wireless networks", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + }, + { + "wireless_link": { + "interface_a": { + "device": { + "name": "Device 1", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Radio0/1", + "type": "ieee802.11ac", + "enabled": true + }, + "interface_b": { + "device": { + "name": "Device 2", + "role": { + "name": "Device Role 1" + }, + "device_type": { + "manufacturer": { + "name": "Cisco" + }, + "model": "C2960S" + }, + "site": { + "name": "Site 1" + } + }, + "name": "Radio0/1", + "type": "ieee802.11ac", + "enabled": true + }, + "ssid": "P2P-Link-1", + "status": "connected", + "tenant": { + "name": "Tenant 1" + }, + "auth_type": "wpa-personal", + "auth_cipher": "aes", + "auth_psk": "P2PLinkKey123!", + "distance": 1.5, + "distance_unit": "km", + "description": "Point-to-point wireless backhaul link", + "comments": "Building A to Building B wireless bridge", + "tags": [ + { + "name": "Tag 1" + }, + { + "name": "Tag 2" + } + ] + } + } + ], + "id": "f87ea004-45e3-4c61-b75b-02116ae9c3b2", + "sdk_name": "diode-sdk-python-dry-run", + "producer_app_name": "dryrun", + "sdk_version": "1.2.0" +} \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index 7af797d..d2178d4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,6 +4,7 @@ import json import os +from pathlib import Path from unittest import mock from unittest.mock import MagicMock, patch @@ -13,14 +14,18 @@ from netboxlabs.diode.sdk.client import ( _DIODE_SENTRY_DSN_ENVVAR_NAME, DiodeClient, + DiodeDryRunClient, DiodeMethodClientInterceptor, _ClientCallDetails, _DiodeAuthentication, _get_sentry_dsn, _load_certs, + load_dryrun_entities, parse_target, ) +from netboxlabs.diode.sdk.diode.v1 import ingester_pb2 from netboxlabs.diode.sdk.exceptions import DiodeClientError, DiodeConfigError +from netboxlabs.diode.sdk.ingester import Entity from netboxlabs.diode.sdk.version import version_semver @@ -60,7 +65,9 @@ def test_config_errors(client_id, client_secret, env_var_name): client_id=client_id, client_secret=client_secret, ) - assert str(err.value) == f"parameter or {env_var_name} environment variable required" + assert ( + str(err.value) == f"parameter or {env_var_name} environment variable required" + ) def test_client_error(mock_diode_authentication): @@ -86,7 +93,10 @@ def test_diode_client_error_repr_returns_correct_string(): error = DiodeClientError(grpc_error) error._status_code = grpc.StatusCode.UNAVAILABLE error._details = "Some details about the error" - assert repr(error) == "" + assert ( + repr(error) + == "" + ) def test_load_certs_returns_bytes(): @@ -176,7 +186,9 @@ def test_setup_sentry_initializes_with_correct_parameters(mock_diode_authenticat ) -def test_client_sets_up_secure_channel_when_grpcs_scheme_is_found_in_target(mock_diode_authentication): +def test_client_sets_up_secure_channel_when_grpcs_scheme_is_found_in_target( + mock_diode_authentication, +): """Check that DiodeClient.__init__() sets up the gRPC secure channel when grpcs:// scheme is found in the target.""" client = DiodeClient( target="grpcs://localhost:8081", @@ -201,7 +213,9 @@ def test_client_sets_up_secure_channel_when_grpcs_scheme_is_found_in_target(mock mock_secure_channel.assert_called_once() -def test_client_sets_up_insecure_channel_when_grpc_scheme_is_found_in_target(mock_diode_authentication): +def test_client_sets_up_insecure_channel_when_grpc_scheme_is_found_in_target( + mock_diode_authentication, +): """Check that DiodeClient.__init__() sets up the gRPC insecure channel when grpc:// scheme is found in the target.""" client = DiodeClient( target="grpc://localhost:8081", @@ -343,10 +357,14 @@ def test_client_setup_sentry_called_when_sentry_dsn_exists(mock_diode_authentica client_secret="123456", sentry_dsn="https://user@password.mock.dsn/123456", ) - mock_setup_sentry.assert_called_once_with("https://user@password.mock.dsn/123456", 1.0, 1.0) + mock_setup_sentry.assert_called_once_with( + "https://user@password.mock.dsn/123456", 1.0, 1.0 + ) -def test_client_setup_sentry_not_called_when_sentry_dsn_not_exists(mock_diode_authentication): +def test_client_setup_sentry_not_called_when_sentry_dsn_not_exists( + mock_diode_authentication, +): """Check that DiodeClient._setup_sentry() is not called when sentry_dsn does not exist.""" client = DiodeClient( target="grpc://localhost:8081", @@ -467,7 +485,10 @@ def continuation(x, _): None, ) request = None - assert interceptor.intercept_unary_unary(continuation, client_call_details, request) == "/my/path/diode.v1.IngesterService/Ingest" + assert ( + interceptor.intercept_unary_unary(continuation, client_call_details, request) + == "/my/path/diode.v1.IngesterService/Ingest" + ) def test_interceptor_intercepts_stream_unary_calls(): @@ -487,11 +508,19 @@ def continuation(x, _): ) request_iterator = None assert ( - interceptor.intercept_stream_unary(continuation, client_call_details, request_iterator) + interceptor.intercept_stream_unary( + continuation, client_call_details, request_iterator + ) == "/my/path/diode.v1.IngesterService/Ingest" ) +@pytest.fixture +def message_path() -> Path: + """Path to the bundled dry-run message.""" + return Path(__file__).resolve().parent / "fixtures" / "message.json" + + @pytest.fixture def mock_diode_authentication(): """ @@ -565,7 +594,9 @@ def test_diode_authentication_success(mock_diode_authentication): with mock.patch("http.client.HTTPConnection") as mock_http_conn: mock_conn_instance = mock_http_conn.return_value mock_conn_instance.getresponse.return_value.status = 200 - mock_conn_instance.getresponse.return_value.read.return_value = json.dumps({"access_token": "mocked_token"}).encode() + mock_conn_instance.getresponse.return_value.read.return_value = json.dumps( + {"access_token": "mocked_token"} + ).encode() token = auth.authenticate() assert token == "mocked_token" @@ -591,14 +622,17 @@ def test_diode_authentication_failure(mock_diode_authentication): assert "Failed to obtain access token" in str(excinfo.value) -@pytest.mark.parametrize("path", [ - "/diode", - "", - None, - "/diode/", - "diode", - "diode/", - ]) +@pytest.mark.parametrize( + "path", + [ + "/diode", + "", + None, + "/diode/", + "diode", + "diode/", + ], +) def test_diode_authentication_url_with_path(mock_diode_authentication, path): """Test that the authentication URL is correctly formatted with a path.""" auth = _DiodeAuthentication( @@ -612,9 +646,13 @@ def test_diode_authentication_url_with_path(mock_diode_authentication, path): with mock.patch("http.client.HTTPConnection") as mock_http_conn: mock_conn_instance = mock_http_conn.return_value mock_conn_instance.getresponse.return_value.status = 200 - mock_conn_instance.getresponse.return_value.read.return_value = json.dumps({"access_token": "mocked_token"}).encode() + mock_conn_instance.getresponse.return_value.read.return_value = json.dumps( + {"access_token": "mocked_token"} + ).encode() auth.authenticate() - mock_conn_instance.request.assert_called_once_with("POST", f"{(path or '').rstrip('/')}/auth/token", mock.ANY, mock.ANY) + mock_conn_instance.request.assert_called_once_with( + "POST", f"{(path or '').rstrip('/')}/auth/token", mock.ANY, mock.ANY + ) def test_diode_authentication_request_exception(mock_diode_authentication): @@ -635,3 +673,85 @@ def test_diode_authentication_request_exception(mock_diode_authentication): auth.authenticate() assert "Failed to obtain access token: Connection error" in str(excinfo.value) + +def test_ingest_dry_run_stdout(capsys): + """Verify ingest prints JSON when dry run is enabled.""" + client = DiodeDryRunClient() + + client._stub = MagicMock() + client.ingest(entities=[]) + + captured = capsys.readouterr() + assert client._stub.Ingest.call_count == 0 + assert captured.out.startswith("{") + + +def test_ingest_dry_run_file(tmp_path): + """Verify ingest writes JSON to file when dry run output file is set.""" + client = DiodeDryRunClient( + app_name="agent/my-producer", + output_dir=str(tmp_path), + ) + + client._stub = MagicMock() + client.ingest(entities=[Entity(site="Site1"), Entity(device="Device1")]) + client.ingest(entities=[Entity(site="Site2"), Entity(device="Device2")]) + + files = list(tmp_path.glob("agent_my-producer*.json")) + assert len(files) == 2 + assert client._stub.Ingest.call_count == 0 + for f in files: + assert f.read_text().startswith("{") + + +def test_load_dryrun_entities(tmp_path): + """Verify ``load_dryrun_entities`` yields protobuf entities.""" + client = DiodeDryRunClient(output_dir=str(tmp_path)) + + client.ingest(entities=[Entity(site="Site1"), Entity(device="Device1")]) + + files = list(tmp_path.glob("dryrun*.json")) + assert len(files) == 1 + entities = list(load_dryrun_entities(files[0])) + + assert len(entities) == 2 + assert isinstance(entities[0], ingester_pb2.Entity) + assert entities[0].site.name == "Site1" + assert isinstance(entities[1], ingester_pb2.Entity) + assert entities[1].device.name == "Device1" + + +def test_load_dryrun_entities_from_fixture(message_path, tmp_path): + """Ensure entities load correctly from the bundled fixture.""" + entities = list(load_dryrun_entities(message_path)) + + assert len(entities) == 94 + assert isinstance(entities[0], ingester_pb2.Entity) + assert entities[0].asn.asn == 555 + assert entities[33].ip_address.address == "192.168.100.1/24" + assert ( + entities[33].ip_address.assigned_object_interface.name == "GigabitEthernet1/0/1" + ) + assert entities[-1].wireless_link.ssid == "P2P-Link-1" + + client = DiodeDryRunClient(output_dir=str(tmp_path)) + + client._stub = MagicMock() + client.ingest(entities=entities) + + assert client._stub.Ingest.call_count == 0 + files = list(tmp_path.glob("dryrun*.json")) + assert len(files) == 1 + entities = list(load_dryrun_entities(files[0])) + assert files[0].read_text().startswith("{") + + entities = list(load_dryrun_entities(files[0])) + + assert len(entities) == 94 + assert isinstance(entities[0], ingester_pb2.Entity) + assert entities[0].asn.asn == 555 + assert entities[33].ip_address.address == "192.168.100.1/24" + assert ( + entities[33].ip_address.assigned_object_interface.name == "GigabitEthernet1/0/1" + ) + assert entities[-1].wireless_link.ssid == "P2P-Link-1"