Skip to content

Commit a6ff5c7

Browse files
author
Karthik Satheesh Kumar
committed
Enhance device metrics export workflow to support JSON output and include optional location data
- Updated README to reflect new CSV and JSON output formats and added optional AP floorplan location data. - Refactored account_credentials.yaml to unify credential structure. - Modified device_metrics_export.py to fetch and process device location data, including new command line options for output formats. - Updated requirements.txt to use pycentral version 2.0a22. - Enhanced utils.py with functions to derive site IDs and fetch device locations.
1 parent 45cead0 commit a6ff5c7

5 files changed

Lines changed: 376 additions & 91 deletions

File tree

‎device-metrics-export/README.md‎

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Device Metrics Export
22

3-
This workflow retrieves comprehensive device information from both HPE Aruba Networking Central and GreenLake Platform (GLP) APIs. It consolidates device attributes, monitoring data, connectivity status, and subscription details into a single CSV file for easy analysis and reporting.
3+
This workflow retrieves comprehensive device information from both HPE Aruba Networking Central and GreenLake Platform (GLP) APIs. It consolidates device attributes, monitoring data, connectivity status, subscription details, and optional AP floorplan location data into CSV and JSON files for analysis, automation, and reporting.
44

55
## Prerequisites
66

77
- Python 3.8 or higher
8-
- API credentials for HPE Aruba Networking Central & GLP (JSON or YAML format)
8+
- API credentials for HPE Aruba Networking Central and GLP (JSON or YAML format)
99

1010
## Installation
1111

@@ -25,93 +25,115 @@ source venv/bin/activate # On Windows use: venv\Scripts\activate
2525
pip install -r requirements.txt
2626
```
2727

28-
This workflow is tested on the `pycentral` SDK (version: `v2.0a10`). Please check compatibility before executing on older/newer versions as there may be changes.
28+
This workflow is tested on the `pycentral` SDK (version: `v2.0a22`). Please check compatibility before executing on older/newer versions as there may be changes.
2929

3030
## Configuration
3131

32-
#### New HPE Aruba Networking Central & GLP Credentials (account_credentials.yaml)
32+
#### Credentials (account_credentials.yaml)
3333

34-
For API operations in new HPE Aruba Networking Central & GLP:
34+
This workflow requires API credentials generated for GLP platform as the script makes API calls to GLP & Central.
3535

3636
```yaml
37-
new_central:
38-
cluster_name: <cluster-name> # or base_url: <central-api-base-url>
39-
client_id: <new-central-client-id>
40-
client_secret: <new-central-client-secret>
41-
glp:
42-
client_id: <glp-client-id>
43-
client_secret: <glp-client-secret>
37+
unified:
38+
cluster_name: <cluster-name> # e.g. EU-1, or use base_url: <central-api-base-url>
39+
workspace_id: <glp-workspace-id>
40+
client_id: <glp-client-id>
41+
client_secret: <glp-client-secret>
4442
```
43+
4544
**Sample Input:** See [`account_credentials.yaml`](./account_credentials.yaml) in this repository for an example credential file.
4645

4746
> [!TIP]
4847
> **Where to find these:**
49-
> - [New Central API Gateway Base URLs](https://developer.arubanetworks.com/new-hpe-anw-central/docs/getting-started-with-rest-apis#api-gateway-base-urls)
50-
> - [How to get API Credentials for new Central](https://developer.arubanetworks.com/new-hpe-anw-central/docs/generating-and-managing-access-tokens)
51-
> - [How to get API Credentials for GLP](https://developer.greenlake.hpe.com/docs/greenlake/guides/public/authentication/authentication#creating-a-personal-api-client)
48+
> - [Finding your Central API Gateway Base URL](https://developer.arubanetworks.com/new-central/docs/getting-started-with-rest-apis#finding-your-base-url)
49+
> - [Generating & Managing Access Token](https://developer.arubanetworks.com/new-central/docs/generating-and-managing-access-tokens)
5250

5351
## Execution
5452

5553
This workflow is executed by the script device_metrics_export.py and covers the following:
5654
- Loads and validates the credentials file (JSON/YAML) & establishes a connection to Central & GLP
5755
- Runs API calls -
58-
- New Central - [Get a list of devices](https://developer.arubanetworks.com/new-central/reference/getdevices)
59-
- New Central - [Get a device inventory list](https://developer.arubanetworks.com/new-central/reference/getdeviceinventory)
60-
- GLP - [Get Devices managed in a workspace](https://developer.greenlake.hpe.com/docs/greenlake/services/device-management/public/openapi/nbapi-inventory-latest/openapi/devices-v1/getdevicesv1)
61-
- GLP - [Get subscriptions of a workspace](https://developer.greenlake.hpe.com/docs/greenlake/services/subscription-management/public/openapi/nbapi-subscription/subscriptions-v1/getsubscriptionsv1)
62-
- Processes results from the above mentioned APIs and outputs the result in a CSV format for usage.
56+
- GLP
57+
- [Get Devices managed in a workspace](https://developer.greenlake.hpe.com/docs/greenlake/services/device-management/public/openapi/nbapi-inventory-latest/openapi/devices-v1/getdevicesv1)
58+
- [Get subscriptions of a workspace](https://developer.greenlake.hpe.com/docs/greenlake/services/subscription-management/public/openapi/nbapi-subscription/subscriptions-v1/getsubscriptionsv1)
59+
- New Central
60+
- [Get a list of devices](https://developer.arubanetworks.com/new-central/reference/getdevices)
61+
- [Get a device inventory list](https://developer.arubanetworks.com/new-central/reference/getdeviceinventory)
62+
- Optionally, [List devices with location information](https://developer.arubanetworks.com/new-central/reference/getdevicelocationsv1)
63+
Processes results from the above mentioned APIs and outputs the result in CSV and structured JSON formats.
6364

6465
Example usage:
6566

6667
```bash
67-
python device_metrics_export.py -c <credentials_file> --output <output_file>
68+
python device_metrics_export.py -c <credentials_file> --output <output_file.csv> [--output-json <output_file.json>] [--include-floorplan] [--include-raw-location]
6869
```
6970

7071

7172
### Command Line Options
7273

73-
The script accepts the following command line argument
74+
The script accepts the following command line arguments
7475

75-
| Name | Type | Description | Required |
76-
|-------------|--------|----------------------|----------|
77-
| credentials_file | string | Path to file with New Central & GLP credentials (JSON or YAML). Must end in .json, .yaml, or .yml. | Yes |
78-
| output | string | Path to output CSV file. Defaults to device_data.csv. | No |
76+
| Name | Type | Description | Required |
77+
|------------------|--------|-----------------------------------------------------------------------------|----------|
78+
| credentials_file | string | Path to credentials (JSON or YAML). Must end in .json, .yaml, or .yml. | Yes |
79+
| output | string | Path to output CSV file. Defaults to device_data.csv. | No |
80+
| --output-json | string | Optional path to write structured JSON output (single array). Defaults to same name as CSV with .json extension. | No |
81+
| --include-floorplan | flag | If set, fetch per-site device locations from the Central and include location fields in output. | No |
82+
| --include-raw-location | flag | If set, include the Raw Location column in the CSV and JSON output, and also fetch per-site device locations (implies `--include-floorplan`). | No |
7983

8084
## Output
8185

82-
A CSV file (default: device_data.csv) containing merged device details from Central and GLP. You can find a sample output file [here](device_data.csv)
83-
84-
CSV column titles (ordered):
85-
- Serial Number
86-
- Mac Address
87-
- Device Name
88-
- Device Type
89-
- Device Model
90-
- Deployment
91-
- IPv4
92-
- Firmware Version
93-
- Site
94-
- Device Group
95-
- Status
96-
- Uptime
97-
- Last Seen At
98-
- Config Status
99-
- Config Last Modified At
100-
- Subscription Key
101-
- Subscription Tier
102-
- Subscription Type
103-
- Subscription End Time
86+
This workflow emits both a CSV (traditional tabular output) and a structured JSON file (single JSON array of device objects).
87+
88+
- CSV (default: device_data.csv): tabular rows where missing values are empty strings. The CSV includes a Raw Location column which contains a JSON string (serialized) when location details are present.
89+
- JSON (default: device_data.json alongside CSV unless overridden): a single JSON array of device objects. Fields with no data are serialized as null in JSON. Location data (Raw Location) is kept as a nested object when available.
90+
91+
CSV / JSON fields (ordered for CSV; JSON objects contain the same keys):
92+
93+
| Field | Description |
94+
|-------|-------------|
95+
| Serial Number | Device serial number from Central inventory. |
96+
| Mac Address | Device MAC address. |
97+
| Device Name | Device name configured in Central. |
98+
| Device Type | Device category, such as access point, switch, or gateway. |
99+
| Device Model | Device hardware model. |
100+
| Deployment | Deployment mode reported by Central inventory. |
101+
| IPv4 | Device IPv4 address. |
102+
| Firmware Version | Software or firmware version reported for the device. |
103+
| Site | Central site name associated with the device. |
104+
| Device Group | Central compatible device group name. |
105+
| Status | Device status from Central inventory. |
106+
| Uptime | Device uptime converted to a human-readable duration. |
107+
| Last Seen At | Last time the device was seen, converted to UTC. |
108+
| Config Status | Device configuration status from monitoring data. |
109+
| Config Last Modified At | Last configuration update time, converted to UTC. |
110+
| Subscription Key | GLP subscription key assigned to the device. |
111+
| Subscription Tier | GLP subscription tier. |
112+
| Subscription Type | Derived subscription type, such as Foundation or Advanced. |
113+
| Subscription End Time | GLP subscription end time, converted to UTC. |
114+
| Floorplan ID | Floorplan identifier from Central device-location data. |
115+
| Building ID | Building identifier from Central device-location data. |
116+
| X Coordinate | Device's X coordinate on the floorplan. |
117+
| Y Coordinate | Device's Y coordinate on the floorplan. |
118+
| Floor Coordinate Unit | Unit used for floorplan's X/Y coordinates. |
119+
| Latitude | Device's latitude from location data. |
120+
| Longitude | Device's longitude from location data. |
121+
| Raw Location | Full normalized location payload. In CSV this is serialized as a JSON string; in JSON this remains a nested object. |
104122

105123
> [!TIP]
106-
> **Device Group**: The Device Group field will only be populated for groups that are New Central compatible device groups. Classic Central groups will not appear in this export.
124+
> The Device Group field will only be populated for groups that are Central compatible device groups. Classic Central groups will not appear in this export.
125+
126+
> Location fields from Floorplan ID through Longitude are populated only when `--include-floorplan` (or `--include-raw-location`) is provided. The Raw Location column is additionally gated behind `--include-raw-location`; passing this flag also implies `--include-floorplan`, so no separate flag is needed. All other location columns remain present with blank/null values when location data is unavailable.
127+
107128

108129
If no data is returned from the APIs or post-processing yields an empty list, the script prints "No data to save."
109130

110131
## Troubleshooting
111132

112-
- Authentication / tokens: Ensure your credentials file is complete and has valid credentials for GLP & New Central.
133+
- Authentication / tokens: Ensure your credentials file contains a top-level `unified` section with `workspace_id`, `client_id`, `client_secret`, and `cluster_name` or `base_url`.
113134
- Empty output: Confirm that devices exist in Central inventory and that GLP subscriptions/devices exist for the account.
114-
- SDK compatibility: If API calls fail unexpectedly, confirm the installed pycentral version matches tested versions (v2.0a10) or update helpers accordingly.
135+
- Missing location fields: Confirm APs have `siteId` values and floorplan/device-location data exists for the site. Devices remain in the output with blank/null location fields when location data is unavailable.
136+
- SDK compatibility: If API calls fail unexpectedly, confirm the installed pycentral version matches tested versions (v2.0a22) or update helpers accordingly.
115137

116138
## Support
117139

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
new_central:
2-
cluster_name: <cluster-name> #e.g. EU-1
3-
client_id: <new-central-client-id>
4-
client_secret: <new-central-client-secret>
5-
glp:
1+
unified:
2+
cluster_name: <central-cluster-name> #e.g. EU-1
63
client_id: <glp-client-id>
74
client_secret: <glp-client-secret>
5+
workspace_id: <glp-workspace-id>

‎device-metrics-export/device_metrics_export.py‎

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
import os
13
from pycentral.new_monitoring import MonitoringDevices
24
from pycentral import NewCentralBase
35
from pycentral.glp import Devices, Subscriptions
@@ -11,6 +13,8 @@
1113
processed_data,
1214
ensure_tokens_available,
1315
get_all_device_inventory,
16+
derive_site_ids_with_aps,
17+
fetch_device_locations,
1418
)
1519

1620
CSV_COLUMNS = [
@@ -36,8 +40,19 @@
3640
"Subscription Tier",
3741
"Subscription Type",
3842
"Subscription End Time",
43+
# Location fields (may be empty)
44+
"Floorplan ID",
45+
"Building ID",
46+
"X Coordinate",
47+
"Y Coordinate",
48+
"Floor Coordinate Unit",
49+
"Latitude",
50+
"Longitude",
51+
"Raw Location",
3952
]
4053

54+
LOCATION_FETCH_WORKERS = 3
55+
4156

4257
def main():
4358
args = parse_args()
@@ -66,35 +81,82 @@ def main():
6681
conn=new_central_conn, select="id,key,endTime,tier"
6782
),
6883
}
69-
# Fetch and process data
7084
raw_data = run_concurrent_tasks(tasks)
71-
processed = process_all_data(raw_data)
7285

73-
# Save results to CSV
86+
processed_inventory = process_list(_safe("device_inventory", raw_data), "serialNumber")
87+
processed_devices = process_monitoring_data(_safe("monitoring_devices", raw_data))
88+
processed_locations = {}
89+
if args.include_floorplan or args.include_raw_location:
90+
site_ids = derive_site_ids_with_aps(processed_inventory, processed_devices)
91+
if site_ids:
92+
processed_locations = fetch_device_locations(
93+
new_central_conn, site_ids, max_workers=LOCATION_FETCH_WORKERS
94+
)
95+
else:
96+
print("No site IDs with APs found; skipping floorplan/device-location fetch.")
97+
98+
processed = process_all_data(raw_data, processed_locations)
99+
100+
# Save results to CSV and structured JSON
74101
output = processed_data(**processed)
102+
effective_columns = (
103+
CSV_COLUMNS
104+
if args.include_raw_location
105+
else [col for col in CSV_COLUMNS if col != "Raw Location"]
106+
)
75107
if output:
76-
df = pd.DataFrame(output)
77-
df = df.reindex(columns=CSV_COLUMNS, fill_value="")
108+
# Prepare CSV rows: ensure Raw Location is serialized to a JSON string
109+
csv_rows = []
110+
for row in output:
111+
r = row.copy()
112+
raw = r.get("Raw Location", "")
113+
if isinstance(raw, (dict, list)):
114+
r["Raw Location"] = json.dumps(raw, ensure_ascii=False)
115+
elif raw is None:
116+
r["Raw Location"] = ""
117+
csv_rows.append(r)
118+
119+
df = pd.DataFrame(csv_rows)
120+
df = df.reindex(columns=effective_columns, fill_value="")
78121
df = df.sort_values(
79122
by="Device Type",
80123
ascending=True,
81124
na_position="last",
82125
)
83126
df.to_csv(args.output, index=False)
127+
128+
# Determine JSON output path
129+
json_path = getattr(args, "output_json", None)
130+
if not json_path:
131+
base, ext = os.path.splitext(args.output)
132+
json_path = f"{base}.json" if ext else f"{args.output}.json"
133+
134+
# Prepare JSON rows: convert empty-string placeholders to null (None) in JSON
135+
json_rows = []
136+
for row in output:
137+
jr = {}
138+
for column in effective_columns:
139+
value = row.get(column, "")
140+
jr[column] = None if value == "" else value
141+
json_rows.append(jr)
142+
143+
with open(json_path, "w", encoding="utf-8") as jf:
144+
json.dump(json_rows, jf, indent=2, ensure_ascii=False)
145+
84146
print(
85-
f"{len(output)} Central devices processed. Device data is saved to {args.output}"
147+
f"{len(output)} Central devices processed. Device data is saved to {args.output} and {json_path}"
86148
)
87149
else:
88150
print("No data to save.")
89151

90152

91153
def parse_args():
92154
"""Parse command line arguments"""
93-
parser = argparse.ArgumentParser(description="Device Onboarding")
155+
parser = argparse.ArgumentParser(description="Device Metrics Export")
94156
parser.add_argument(
95157
"-c",
96158
"--credentials",
97-
help="Credentials file for New Central API (JSON or YAML)",
159+
help="Credentials file for New Central API (JSON or YAML). Unified PyCentral credential files are supported.",
98160
required=True,
99161
type=validate_file_format,
100162
)
@@ -105,6 +167,24 @@ def parse_args():
105167
required=False,
106168
default="device_data.csv",
107169
)
170+
parser.add_argument(
171+
"--include-floorplan",
172+
help="Include per-site AP floorplan/location data (optional)",
173+
action="store_true",
174+
default=False,
175+
)
176+
parser.add_argument(
177+
"--include-raw-location",
178+
help="Include the Raw Location column in the output. Also implies --include-floorplan (fetches per-site device locations).",
179+
action="store_true",
180+
default=False,
181+
)
182+
parser.add_argument(
183+
"--output-json",
184+
help="Optional path to write structured JSON output (single array). Defaults to replacing CSV extension with .json",
185+
required=False,
186+
default=None,
187+
)
108188
return parser.parse_args()
109189

110190

@@ -119,19 +199,22 @@ def validate_file_format(file_path):
119199
return file_path
120200

121201

122-
def process_all_data(raw_data):
123-
"""Process raw API data into structured format"""
202+
def process_all_data(raw_data, processed_locations=None):
203+
"""Process raw API data into structured format. Accept optional processed_locations map."""
204+
processed_inventory = process_list(
205+
_safe("device_inventory", raw_data), "serialNumber"
206+
)
124207
return {
125208
"processed_devices": process_monitoring_data(
126209
_safe("monitoring_devices", raw_data)
127210
),
128-
"processed_inventory": process_list(
129-
_safe("device_inventory", raw_data), "serialNumber"
130-
),
211+
"processed_inventory": processed_inventory,
131212
"processed_glp_devices": process_glp_device(
132-
process_list(_safe("glp_devices", raw_data), "serialNumber")
213+
process_list(_safe("glp_devices", raw_data), "serialNumber"),
214+
known_serials=processed_inventory,
133215
),
134216
"processed_subs": process_list(_safe("glp_subs", raw_data), "id"),
217+
"processed_locations": processed_locations or {},
135218
}
136219

137220

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
pandas==2.3.3
2-
pycentral==2.0a10
2+
pycentral==2.0a22

0 commit comments

Comments
 (0)