Compare commits

..

29 Commits

Author SHA1 Message Date
Mike Hansen
0e0d7771f0 Merge pull request #26 from Telecominfraproject/staging-release-5.0.0-pki-2.0
PKI 2.0 Implementation with Schema v5.0.0 Updates
2026-03-03 10:33:05 -05:00
Mike Hansen
2d0f260f5d Add support for PKI 2.0 with schema v5.0.0 updates
This commit completes the PKI 2.0 implementation by integrating schema
v5.0.0 and fixing runtime configuration handling.

PKI 2.0 Testing:
- Tested with simulated switch (ols-ucentral-client) running in Docker container
- Successfully connected to cloud instance using PKI 2.0 birth certificates
- Verified automatic EST enrollment and operational certificate retrieval
- Confirmed gateway connectivity with operational certificates

Version Update:
- Update client version from 4.1.0 → 5.0.0
- Aligns with PKI 2.0 feature release

Schema Update:
- Update schema reference to release/v5.0.0
- Add version.json and schema.json to /etc/ for runtime capabilities reporting
- Update config-samples/ucentral.schema.pretty.json to v5.0.0
  - New fields: autoneg, qos-priority-mapping
- Regenerate property databases for test suite:
  - property-database-base.c (418 properties)
  - property-database-platform-brcm-sonic.c (418 properties)

Configuration Fixes:
- Fix UC_GATEWAY_ADDRESS parsing to support host:port format
  - Previously required separate -s and -P flags
  - Now supports single environment variable: UC_GATEWAY_ADDRESS=host:port
2026-02-26 13:38:18 -05:00
Mike Hansen
59ef4a8db5 Add EST client stubs to config parser test suite
Add stub implementations for PKI 2.0 EST client functions (est_get_server_url,
est_simple_reenroll, est_get_error) to support testing proto.c with the new
reenroll RPC handler. These stubs allow the config parser test suite to compile
and run without requiring actual EST functionality.

Tests: All 24 config parser tests pass with no regressions
2026-02-25 17:37:56 -05:00
Mike Hansen
d8af348fae Implement PKI 2.0 with EST protocol support
- Add EST (RFC 7030) client implementation for automated certificate lifecycle
  - est-client.c/h: Complete EST protocol implementation using libcurl + OpenSSL
  - Support for simple enrollment, reenrollment, and CA certificate retrieval
  - Auto-detection of EST server based on certificate issuer

- Update ucentral-client for PKI 2.0 certificate flow
  - Remove DigiCert firstcontact flow (marked as legacy, to be removed)
  - Implement automatic EST enrollment on first boot
  - Birth certificates (cert.pem, key.pem, cas.pem) → EST → operational certificates
  - Fallback to birth certificates if enrollment fails

- Add reenroll RPC command handler in proto.c
  - Allows gateway-initiated certificate renewal before expiration
  - Saves renewed certificate and schedules restart after 10 seconds

- Update configuration and documentation
  - Version bump: 4.1.0 → 5.0.0
  - Dockerfile: Reference schema v5.0.0 (tag to be created after PR merge)
  - README.md: Comprehensive PKI 2.0 architecture and workflow documentation
  - partition_script.sh: Add comments clarifying birth certificate provisioning

- Add PKI 2.0 example scripts
  - Test EST enrollment, reenrollment, and CA certificate retrieval
  - Manual testing tools for certificate operations
  - Comprehensive troubleshooting guide

- Update Makefile to compile est-client.o
- Build tested successfully with no regressions (38MB binary)

This implementation follows the proven TIP wlan-ap PKI 2.0 pattern for
consistency across TIP/OpenWifi projects.
2026-02-25 17:25:53 -05:00
Mike Hansen
2c9045c777 Merge pull request #25 from Telecominfraproject/ols-968-documentation-accuracy-and-property-database-regeneration
OLS-968:   Fix documentation accuracy and property database regeneration
2026-01-21 20:32:45 -05:00
Mike Hansen
e36ddea61e OLS-968: Fix documentation accuracy and property database regeneration
- Update line counts across documentation to match actual source files
    (test-config-parser.c: 3304 lines, test-stubs.c: 219 lines)
  - Correct test configuration count to exact "25 configs"
  - Fix schema file references to single canonical file
  - Add property database documentation with accurate statistics
  - Fix broken Makefile regeneration targets (regenerate-property-db,
    regenerate-platform-property-db) to use correct schema-based workflow
  - Replace non-existent rebuild-property-database.py with three-step
    process: extract-schema-properties.py → generate-database-from-schema.py

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-21 16:25:37 -05:00
Mike Hansen
9c91b06be3 Merge pull request #24 from Telecominfraproject/OLS-915-schema-verification-enhancement
[OLS-915] Schema Verification Enhancement

Enhanced schema verification, added 'make clean' before each test run to force correct binary
rebuild when switching between stub/platform modes
2026-01-15 14:05:19 -05:00
Mike Hansen
dc2b9cd8ab [OLS-915] Schema Verification Enhancement
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-15 13:57:47 -05:00
Mike Hansen
00ca1cd1f4 [OLS-915] Schema Verification Enhancement
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-15 13:19:39 -05:00
Mike Hansen
ff4b2095b5 [OLS-915] Schema Verification Enhancement
Fix platform property tracking in test output

  Platform databases now always included for property analysis, showing
  complete base→platform flow with arrow notation in test reports.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-13 11:08:25 -05:00
Mike Hansen
0cb84a23a0 [OLS-915] Schema Verification Enhancement
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-13 10:40:51 -05:00
Mike Hansen
a4a153197b [OLS-915] Schema Verification Enhancement
Enables undefined property detection during make validate-schema, providing warnings about typos and vendor-specific
  properties that aren't in the schema.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-13 10:22:59 -05:00
Mike Hansen
cd135692b3 Merge pull request #23 from Telecominfraproject/ols-925-platform-config-testing-with-stubs
Ols 925 platform config logic testing with hardware stubs
2026-01-07 10:22:48 -05:00
Mike Hansen
cef160a0fa [OLS-915] Schema-based property generation and platform testing
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-05 11:40:10 -05:00
Mike Hansen
04edeb90e4 [OLS-915] Schema-based property generation and platform testing │
Schema-based property database:                                                                                                                                                    │
- Extract 398 properties from schema (vs config files)                                                                                                                             │
- Generate base DB (proto.c: 102 found, 296 unimplemented)                                                                                                                         │
- Generate platform DB (plat-gnma.c: 141 found, 257 unimplemented)                                                                                                                 │
- Support JSON schema (included) and YAML (external repo)                                                                                                                          │

Platform testing framework:                                                                                                                                                        │
- Stub hardware layer (gNMI/gNOI), test real platform logic                                                                                                                        │
- Dual tracking: base parsing + platform application                                                                                                                               │
- Platform mocks for brcm-sonic and example platforms                                                                                                                              │

Changes:                                                                                                                                                                           │
- New: extract-schema-properties.py (JSON/YAML support)                                                                                                                            │
- New: generate-database-from-schema.py, generate-platform-database-from-schema.py                                                                                                 │
- New: Platform mocks and property databases                                                                                                                                       │
- Remove: 4 legacy config-based generation tools                                                                                                                                   │
- Remove: unused ucentral.schema.full.json                                                                                                                                         │
- Update: Simplified fetch-schema.sh, updated MAINTENANCE.md                                                                                                                       │

Tests: 24/24 pass (stub and platform modes)

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2026-01-05 10:00:54 -05:00
Mike Hansen
b00c502016 [OLS-915] Configuration Testing Framework with Property Tracking
- corrected references and paths in markdown files
  hopefully the last

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-12-18 09:44:43 -05:00
Mike Hansen
3963845143 [OLS-915] Configuration Testing Framework with Property Tracking
- corrected references and paths in markdown files

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-12-18 09:40:48 -05:00
Mike Hansen
6c4c918c3b [OLS-915] Configuration Testing Framework with Property Tracking
- corrected paths in markdown files

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-12-18 09:37:14 -05:00
Mike Hansen
f90edc4c41 Merge pull request #22 from Telecominfraproject/OLS-915-configuration-validation-and-parser-tools
[OLS-915] Configuration Testing Framework with Property Tracking
2025-12-18 09:15:47 -05:00
Mike Hansen
9564925df5 [OLS-915] Remove generated build artifacts from repository
- Remove binary executable: tests/config-parser/test-config-parser
- Remove generated test reports: test-report.html, test-report.json
- Update .gitignore to exclude test artifacts and build outputs

These files are generated during build/test and should not be tracked in git.
They will be regenerated when running 'make test-config' or './run-config-tests.sh'.

Related to: OLS-915 Configuration Testing Framework with Property Tracking
2025-12-17 09:20:10 -05:00
Mike Hansen
30b1904d00 [OLS-915] Configuration Testing Framework with Property Tracking - ols-ucentral-client
Add comprehensive configuration testing framework with property tracking

  Implements two-layer validation system (schema + parser) for JSON configurations:
  - Add test-config-parser.c with 628-property database tracking implementation status
  - Add Python schema validator and property database generation tools
  - Add test runner script (run-config-tests.sh) for automated testing
  - Add 25+ test configurations covering core and platform-specific features
  - Modify proto.c with TEST_STATIC macro to expose cfg_parse() for testing
  - Support multiple output formats: human-readable, HTML, JSON, JUnit XML

  Enables automated validation of configuration processing, tracks feature
  implementation coverage, and provides CI/CD integration for continuous testing.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-12-16 17:41:53 -05:00
Mike Hansen
3c9d20d97f Merge pull request #20 from Telecominfraproject/OLS_Update_Version_410
OLS 410
2025-05-29 14:52:06 -04:00
Mike Hansen
84789e07ce OLS 410
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-05-28 10:08:52 -04:00
Mike Hansen
10cc5bec80 Merge pull request #19 from Telecominfraproject/OLS-578-Tag-ols-ucentral-client-and-ols-ucentral-schema-4.0.0-pre-release
[OLS-578] Tag ols-ucentral-client and ols-ucentral-schema 4.0.0 pre-r…
2025-02-07 08:06:04 -05:00
Mike Hansen
ca74a49604 [OLS-578] Tag ols-ucentral-client and ols-ucentral-schema 4.0.0 pre-release
Take the schema version from tag v4.0.0-rc1
Update version.json to 4.0.0

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-02-05 08:36:43 -05:00
Mike Hansen
fb10d141d0 Merge pull request #18 from Telecominfraproject/OLS-563-version-client-tag-and-update-schema-ref
[OLS-563] Add version to ols-ucentral-client
2025-02-03 09:27:55 -05:00
Mike Hansen
176d2b9f36 [OLS-563] Add version to ols-ucentral-client
Dockerfile updated to pull tagged version of schema to get the schema.json file.
This will make subsequent versioning much easier.

Once this is merged we can tag the client as 3.2.7 and then move forward to 4.x as we did with the schema.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-31 16:48:09 -05:00
Mike Hansen
41d50f4650 Merge pull request #17 from Telecominfraproject/OLS-563-Add-version-to-ols-ucentral-client
[OLS-563] Add version to ols-ucentral-client
2025-01-27 09:36:05 -05:00
Mike Hansen
00ae4001e7 [OLS-563] Add version to ols-ucentral-client
Add version to ols-ucentral-client
Augment the build to pull the schema version file from the ols-ucentral-schema repo (if present) based on commit id of
schema used as baseline for this client version.
Use both it and the version to provide the version information in the connect message.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-22 19:49:46 -05:00
105 changed files with 26824 additions and 5469 deletions

27
.gitignore vendored
View File

@@ -5,3 +5,30 @@ src/docker/ucentral-client
*.orig
*.rej
docker/*
CLAUDE.md
# Test artifacts and generated files
tests/config-parser/test-config-parser
tests/config-parser/test-report.*
tests/config-parser/test-results*.txt
tests/config-parser/*.bak
output/
# Platform build artifacts
*.a
*.full.a
*.pb.cc
*.pb.h
*_protoc_stamp
# Generated gRPC/protobuf files
src/ucentral-client/platform/*/gnma/gnmi/*.pb.*
src/ucentral-client/platform/*/gnma/gnmi/*.grpc.pb.*
src/ucentral-client/platform/*/gnma/*.a
src/ucentral-client/platform/*/netlink/*.a
# Schema repository (fetched by tools)
ols-ucentral-schema/
# Backup files
*.backup

View File

@@ -1,9 +1,12 @@
FROM arm64v8/debian:buster
FROM debian:bullseye
LABEL Description="Ucentral client (Build) environment"
ARG HOME /root
ARG EXTERNAL_LIBS ${HOME}/ucentral-external-libs
ARG SCHEMA="release/v5.0.0"
ARG SCHEMA_VERSION="${SCHEMA}"
ARG SCHEMA_ZIP_FILE="${SCHEMA_VERSION}.zip"
ARG SCHEMA_UNZIPPED="ols-ucentral-schema-${SCHEMA}"
ARG OLS_SCHEMA_SRC="https://github.com/Telecominfraproject/ols-ucentral-schema/archive/refs/heads/${SCHEMA_ZIP_FILE}"
SHELL ["/bin/bash", "-c"]
RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
@@ -24,13 +27,17 @@ RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
libtool \
pkg-config \
libjsoncpp-dev \
libhiredis-dev
unzip \
python3 \
python3-jsonschema
RUN git config --global http.sslverify false
RUN git clone https://github.com/DaveGamble/cJSON.git ${HOME}/ucentral-external-libs/cJSON/
RUN git clone https://libwebsockets.org/repo/libwebsockets ${HOME}/ucentral-external-libs/libwebsockets/
RUN git clone --recurse-submodules -b v1.50.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc ${HOME}/ucentral-external-libs/grpc/
RUN git clone --recursive --branch v7.1.4 https://github.com/zhaojh329/rtty.git ${HOME}/ucentral-external-libs/rtty/
ADD ${OLS_SCHEMA_SRC} /tmp/
# The following libs should be prebuilt in docker-build-env img to speed-up
# recompilation of only the ucentral-client itself
@@ -42,6 +49,8 @@ RUN cd ${HOME}/ucentral-external-libs/cJSON/ && \
make install
RUN cd ${HOME}/ucentral-external-libs/libwebsockets/ && \
git branch --all && \
git checkout a9b8fe7ebf61b8c0e7891e06e70d558412933a33 && \
mkdir build && \
cd build && \
cmake .. && \
@@ -63,3 +72,13 @@ RUN cd ${HOME}/ucentral-external-libs/rtty/ && \
cd build && \
cmake .. && \
make -j4
RUN unzip /tmp/${SCHEMA_ZIP_FILE} -d ${HOME}/ucentral-external-libs/
RUN cd ${HOME}/ucentral-external-libs/ && \
mv ${SCHEMA_UNZIPPED} ols-ucentral-schema
# Copy version files to /etc/ for runtime use
COPY version.json /etc/version.json
RUN mkdir -p /etc && \
cp ${HOME}/ucentral-external-libs/ols-ucentral-schema/schema.json /etc/schema.json

View File

@@ -7,9 +7,6 @@ IMG_ID := "ucentral-client-build-env"
IMG_TAG := $(shell cat Dockerfile | sha1sum | awk '{print substr($$1,0,11);}')
CONTAINER_NAME := "ucentral_client_build_env"
DPKG_IMAGE := "dpkg-builder:latest"
DPKG_CONTAINER_NAME := "dpkg_build_env"
.PHONY: all clean build-host-env build-final-deb build-ucentral-docker-img run-host-env run-ucentral-docker-img
all: build-host-env build-ucentral-app build-ucentral-docker-img build-final-deb
@@ -23,9 +20,9 @@ build-host-env:
docker build --file Dockerfile --tag ${IMG_ID}:${IMG_TAG} docker
@echo Docker build done;
@echo Saving docker img to local archive...;
if [ ! -f output/docker-ucentral-client-build-env-${IMG_TAG}.gz ] ; then
if [ ! -f output/docker-ucentral-client-build-env-${IMG_TAG}.gz ] ; then \
docker save ${IMG_ID}:${IMG_TAG} | gzip -c - > \
output/docker-ucentral-client-build-env-${IMG_TAG}.gz;
output/docker-ucentral-client-build-env-${IMG_TAG}.gz; \
fi
@echo Docker save done...;
@@ -53,8 +50,13 @@ build-ucentral-app: run-host-env
@echo Running ucentralclient docker-build-env container to build ucentral-client...;
docker exec -t ${CONTAINER_NAME} /root/ols-nos/docker-build-client.sh
docker cp ${CONTAINER_NAME}:/root/deliverables/ src/docker/
# copy the schema version, if it is there
docker cp ${CONTAINER_NAME}:/root/ucentral-external-libs/ols-ucentral-schema/schema.json src/docker/ || true
docker container stop ${CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${CONTAINER_NAME} > /dev/null 2>&1 || true;
if [ -f version.json ]; then \
cp version.json src/docker/; \
fi
build-ucentral-docker-img: build-ucentral-app
pushd src
@@ -62,10 +64,10 @@ build-ucentral-docker-img: build-ucentral-app
cp docker/deliverables/ucentral-client docker/
cp docker/deliverables/rtty docker/
OLDIMG=$$(docker images --format "{{.ID}}" ucentral-client:latest)
docker build --file docker/Dockerfile --tag ucentral-client:latest docker --label com.azure.sonic.manifest="$$(cat docker/manifest.json)"
docker build --file docker/Dockerfile --tag ucentral-client:latest docker
NEWIMG=$$(docker images --format "{{.ID}}" ucentral-client:latest)
if [ -n "$$OLDIMG" ] && [ ! "$$OLDIMG" = "$$NEWIMG" ]; then
docker image rm $$OLDIMG
if [ -n "$$OLDIMG" ] && [ ! "$$OLDIMG" = "$$NEWIMG" ]; then \
docker image rm $$OLDIMG; \
fi
docker save ucentral-client:latest |gzip -c - > docker-ucentral-client.gz
popd
@@ -84,47 +86,21 @@ build-final-deb: build-ucentral-docker-img
@echo
@echo "ucentral client deb pkg is available under ./output/ dir"
build-arm64-deb: build-ucentral-docker-img
docker inspect --type=image ${DPKG_IMAGE} >/dev/null 2>&1 || \
docker build --file dpkg-builder.Dockerfile --tag ${DPKG_IMAGE} .
docker container stop ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container run -d -t \
--name ${DPKG_CONTAINER_NAME} \
--platform linux/arm64 \
-v $(realpath ./):$(realpath ./) \
-w $(realpath ./src/) \
--user $(shell id -u):$(shell id -g) \
--tmpfs /tmp \
${DPKG_IMAGE} \
bash
docker exec -t ${DPKG_CONTAINER_NAME} dpkg-buildpackage -rfakeroot -b -us -uc -j
mv ucentral-client*deb ./output/
mv src/docker-ucentral-client.gz ./output/
docker container stop ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
clean:
docker container stop ${CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container stop ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker rmi ucentral-client 2>/dev/null || true;
docker rmi ${IMG_ID}:${IMG_TAG} 2>/dev/null || true;
docker rmi ${DPKG_IMAGE} 2>/dev/null || true;
rm -rf output 2>/dev/null || true;
rm -rf docker 2>/dev/null || true;
rm -rf src/docker/deliverables || true;
rm -rf src/docker/lib* || true;
rm -rf src/docker/ucentral-client || true;
rm -rf src/docker/version.json || true;
rm -rf src/docker/schema.json || true;
rm -rf src/debian/ucentral-client.substvars 2>/dev/null || true;
rm -rf src/debian/shasta-ucentral-client.debhelper.log 2>/dev/null || true;
rm -rf src/debian/.debhelper src/debian/ucentral-client 2>/dev/null || true;
rm -rf src/debian/shasta-ucentral-client* 2>/dev/null || true;
rm -rf src/debian/debhelper-build-stamp* 2>/dev/null || true;
rm -rf src/debian/files shasta_1.0_arm64.changes shasta_1.0_arm64.buildinfo 2>/dev/null || true;
rm -rf src/debian/files shasta_1.0_amd64.changes shasta_1.0_amd64.buildinfo 2>/dev/null || true;

544
PREREQUISITES.md Normal file
View File

@@ -0,0 +1,544 @@
# Prerequisites and Dependencies
This document lists all tools, libraries, and dependencies required for building, testing, and developing the OLS uCentral Client.
---
## Table of Contents
1. [Core Build Requirements](#core-build-requirements)
2. [Testing Framework Requirements](#testing-framework-requirements)
3. [Property Database Generation Requirements](#property-database-generation-requirements)
4. [Schema Repository Access](#schema-repository-access)
5. [Optional Tools](#optional-tools)
6. [Quick Setup Guide](#quick-setup-guide)
7. [Verification Commands](#verification-commands)
---
## Core Build Requirements
### Required for Building the Application
| Tool | Version | Purpose | Installation |
|------|---------|---------|-------------|
| **Docker** | 20.10+ | Dockerized build environment | [Install Docker](https://docs.docker.com/get-docker/) |
| **Docker Compose** | 1.29+ (optional) | Multi-container orchestration | Included with Docker Desktop |
| **Make** | 3.81+ | Build system | Usually pre-installed on Linux/macOS |
| **Git** | 2.20+ | Version control and schema fetching | `apt install git` or `brew install git` |
| **Bash** | 4.0+ | Shell scripts | Usually pre-installed |
### Build Process Dependencies (Inside Docker)
These are **automatically installed** inside the Docker build environment (no manual installation required):
- **GCC/G++** - C/C++ compiler
- **CMake** - Build system generator
- **cJSON** - JSON parsing library
- **libwebsockets** - WebSocket client library
- **OpenSSL** - TLS/SSL support
- **gRPC** - RPC framework (platform-specific)
- **Protocol Buffers** - Serialization (platform-specific)
- **jsoncpp** - JSON library (C++)
**Note:** You do NOT need to install these manually - Docker handles everything!
---
## Testing Framework Requirements
### Required for Running Tests
| Tool | Version | Purpose | Required For |
|------|---------|---------|-------------|
| **Python 3** | 3.7+ | Test scripts and schema validation | All testing |
| **PyYAML** | 5.1+ | YAML schema parsing | Schema-based database generation |
| **Docker** | 20.10+ | Consistent test environment | Recommended (but optional) |
### Python Package Dependencies
Install via pip:
```bash
# Install all Python dependencies
pip3 install pyyaml
# Or if you prefer using requirements file (see below)
pip3 install -r tests/requirements.txt
```
**Detailed breakdown:**
1. **PyYAML** (`yaml` module)
- Used by: `tests/tools/extract-schema-properties.py`
- Purpose: Parse YAML schema files from ols-ucentral-schema
- Installation: `pip3 install pyyaml`
- Version: 5.1 or later
2. **Standard Library Only** (no additional packages needed)
- Used by: `tests/schema/validate-schema.py`
- Built-in modules: `json`, `sys`, `argparse`, `os`
- Used by: Most other Python scripts
- Built-in modules: `pathlib`, `typing`, `re`, `subprocess`
### Test Configuration Files
These are **included in the repository** (no installation needed):
- Configuration samples: `config-samples/*.json`
- JSON schema: `config-samples/ucentral.schema.pretty.json`
- Test framework: `tests/config-parser/test-config-parser.c`
---
## Property Database Generation Requirements
### Required for Database Generation/Regeneration
| Tool | Version | Purpose | When Needed |
|------|---------|---------|-------------|
| **Python 3** | 3.7+ | Database generation scripts | Database regeneration |
| **PyYAML** | 5.1+ | Schema parsing | Schema-based generation |
| **Git** | 2.20+ | Fetch ols-ucentral-schema | Schema access |
| **Bash** | 4.0+ | Schema fetch script | Automated schema fetching |
### Scripts Overview
1. **Schema Extraction:**
- `tests/tools/extract-schema-properties.py` - Extract properties from YAML schema
- Dependencies: Python 3.7+, PyYAML
- Input: ols-ucentral-schema YAML files
- Output: Property list (text)
2. **Line Number Finder:**
- `tests/tools/find-property-line-numbers.py` - Find property parsing locations
- Dependencies: Python 3.7+ (standard library only)
- Input: proto.c + property list
- Output: Property database with line numbers
3. **Database Regeneration:**
- `tests/tools/rebuild-property-database.py` - Master regeneration script
- Dependencies: Python 3.7+ (standard library only)
- Input: proto.c + config files
- Output: Complete property database
4. **Database Updater:**
- `tests/tools/update-test-config-parser.py` - Update test file with new database
- Dependencies: Python 3.7+ (standard library only)
- Input: test-config-parser.c + new database
- Output: Updated test file
5. **Schema Fetcher:**
- `tests/tools/fetch-schema.sh` - Fetch/update ols-ucentral-schema
- Dependencies: Bash, Git
- Input: Current branch name
- Output: Downloaded schema repository
---
## Schema Repository Access
### ols-ucentral-schema Repository
The uCentral configuration schema is maintained in a separate GitHub repository:
**Repository:** https://github.com/Telecominfraproject/ols-ucentral-schema
### Access Methods
#### Method 1: Automatic Fetching (Recommended)
Use the provided script with intelligent branch matching:
```bash
cd tests/tools
# Auto-detect branch (matches client branch to schema branch)
./fetch-schema.sh
# Force specific branch
./fetch-schema.sh --branch main
./fetch-schema.sh --branch release-1.0
# Check what branch would be used
./fetch-schema.sh --check-only
# Force re-download
./fetch-schema.sh --force
```
**Branch Matching Logic:**
- Client on `main` → Uses schema `main`
- Client on `release-x` → Tries schema `release-x`, falls back to `main`
- Client on feature branch → Uses schema `main`
#### Method 2: Manual Clone
```bash
# Clone to recommended location (peer to ols-ucentral-client)
cd /path/to/projects
git clone https://github.com/Telecominfraproject/ols-ucentral-schema.git
# Or clone to custom location and set path in tools
git clone https://github.com/Telecominfraproject/ols-ucentral-schema.git /custom/path
```
#### Method 3: Web Access (Read-Only)
View schema files directly on GitHub:
- Browse: https://github.com/Telecominfraproject/ols-ucentral-schema/tree/main/schema
- Raw files: `https://raw.githubusercontent.com/Telecominfraproject/ols-ucentral-schema/main/schema/ucentral.yml`
### Schema Directory Structure
Expected schema layout:
```
ols-ucentral-schema/
├── schema/
│ ├── ucentral.yml # Root schema
│ ├── ethernet.yml
│ ├── interface.ethernet.yml
│ ├── switch.yml
│ ├── unit.yml
│ └── ... (40+ YAML files)
└── README.md
```
### Schema Location Configuration
Default location (peer to client repository):
```
/path/to/projects/
├── ols-ucentral-client/ # This repository
│ └── tests/tools/
└── ols-ucentral-schema/ # Schema repository (default)
└── schema/
```
Custom location (set in scripts):
```bash
# Edit schema path in tools
SCHEMA_DIR="/custom/path/to/ols-ucentral-schema"
```
---
## Optional Tools
### Development and Debugging
| Tool | Purpose | Installation |
|------|---------|-------------|
| **GDB** | C debugger | `apt install gdb` or `brew install gdb` |
| **Valgrind** | Memory leak detection | `apt install valgrind` |
| **clang-format** | Code formatting | `apt install clang-format` |
| **cppcheck** | Static analysis | `apt install cppcheck` |
### Documentation
| Tool | Purpose | Installation |
|------|---------|-------------|
| **Doxygen** | API documentation | `apt install doxygen` |
| **Graphviz** | Diagram generation | `apt install graphviz` |
| **Pandoc** | Markdown conversion | `apt install pandoc` |
### CI/CD Integration
| Tool | Purpose | Notes |
|------|---------|-------|
| **GitHub Actions** | Automated testing | Configuration in `.github/workflows/` |
| **Jenkins** | Build automation | JUnit XML output supported |
| **GitLab CI** | CI/CD pipeline | Docker-based builds supported |
---
## Quick Setup Guide
### For Building Only
```bash
# 1. Install Docker
# Follow: https://docs.docker.com/get-docker/
# 2. Clone repository
git clone https://github.com/Telecominfraproject/ols-ucentral-client.git
cd ols-ucentral-client
# 3. Build everything
make all
# Done! The .deb package is in output/
```
### For Testing
```bash
# 1. Install Python 3 and pip (if not already installed)
# Ubuntu/Debian:
sudo apt update
sudo apt install python3 python3-pip
# macOS:
brew install python3
# 2. Install Python dependencies
pip3 install pyyaml
# 3. Run tests
cd tests/config-parser
make test-config-full
# Or use Docker (recommended)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-full"
```
### For Database Generation
```bash
# 1. Ensure Python 3 and PyYAML are installed (see above)
# 2. Fetch schema repository
cd tests/tools
./fetch-schema.sh
# 3. Extract properties from schema
python3 extract-schema-properties.py \
../../ols-ucentral-schema/schema \
ucentral.yml \
--filter switch --filter ethernet
# 4. Follow property database generation guide
# See: tests/PROPERTY_DATABASE_GENERATION_GUIDE.md
```
---
## Verification Commands
### Verify Docker Installation
```bash
docker --version
# Expected: Docker version 20.10.0 or later
docker ps
# Expected: No errors (should list running containers or empty list)
```
### Verify Python Installation
```bash
python3 --version
# Expected: Python 3.7.0 or later
pip3 --version
# Expected: pip 20.0.0 or later
```
### Verify Python Dependencies
```bash
python3 -c "import yaml; print('PyYAML:', yaml.__version__)"
# Expected: PyYAML: 5.1 or later
python3 -c "import json; print('JSON: built-in')"
# Expected: JSON: built-in (no errors)
```
### Verify Git Installation
```bash
git --version
# Expected: git version 2.20.0 or later
git config --get user.name
# Expected: Your name (if configured)
```
### Verify Build Environment
```bash
# Check if Docker image exists
docker images | grep ucentral-client-build-env
# Expected: ucentral-client-build-env image listed (after first build)
# Check if container is running
docker ps | grep ucentral_client_build_env
# Expected: Container listed (if build-host-env was run)
```
### Verify Schema Access
```bash
# Check schema repository
cd tests/tools
./fetch-schema.sh --check-only
# Expected: Shows which branch would be used
# Verify schema files
ls -la ../../ols-ucentral-schema/schema/ucentral.yml
# Expected: File exists (after fetch-schema.sh)
```
---
## Troubleshooting
### Docker Issues
**Problem:** "Cannot connect to Docker daemon"
```bash
# Solution: Start Docker Desktop or daemon
sudo systemctl start docker # Linux
# Or open Docker Desktop app (macOS/Windows)
```
**Problem:** "Permission denied" when running Docker
```bash
# Solution: Add user to docker group (Linux)
sudo usermod -aG docker $USER
# Log out and back in for changes to take effect
```
### Python Issues
**Problem:** "ModuleNotFoundError: No module named 'yaml'"
```bash
# Solution: Install PyYAML
pip3 install pyyaml
# If pip3 install fails, try:
python3 -m pip install pyyaml
```
**Problem:** "python3: command not found"
```bash
# Solution: Install Python 3
# Ubuntu/Debian:
sudo apt install python3
# macOS:
brew install python3
```
### Schema Access Issues
**Problem:** "Schema repository not found"
```bash
# Solution: Fetch schema manually
cd tests/tools
./fetch-schema.sh --force
# Or clone manually
git clone https://github.com/Telecominfraproject/ols-ucentral-schema.git ../../ols-ucentral-schema
```
**Problem:** "Branch 'release-x' not found in schema repository"
```bash
# Solution: Use main branch
./fetch-schema.sh --branch main
# Or check available branches
git ls-remote --heads https://github.com/Telecominfraproject/ols-ucentral-schema.git
```
---
## Platform-Specific Notes
### Ubuntu/Debian
```bash
# Install all required tools
sudo apt update
sudo apt install -y \
docker.io \
docker-compose \
python3 \
python3-pip \
git \
make \
bash
# Install Python dependencies
pip3 install pyyaml
# Add user to docker group
sudo usermod -aG docker $USER
# Log out and back in
```
### macOS
```bash
# Install Docker Desktop
# Download from: https://docs.docker.com/desktop/mac/install/
# Install Homebrew (if not installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install tools
brew install python3 git
# Install Python dependencies
pip3 install pyyaml
```
### Windows (WSL2)
```bash
# Install Docker Desktop for Windows with WSL2 backend
# Download from: https://docs.docker.com/desktop/windows/install/
# Inside WSL2 Ubuntu:
sudo apt update
sudo apt install -y python3 python3-pip git make
# Install Python dependencies
pip3 install pyyaml
```
---
## Dependencies Summary
### Minimal (Build Only)
- Docker
### Standard (Build + Test)
- Docker
- Python 3.7+
- PyYAML
### Full (Build + Test + Database Generation)
- Docker
- Python 3.7+
- PyYAML
- Git
- Bash
### Everything Included in Repository
- Test framework (C code)
- Test configurations
- JSON schema
- Python scripts
- Shell scripts
- Documentation
---
## See Also
- **[README.md](README.md)** - Main project documentation
- **[TESTING_FRAMEWORK.md](TESTING_FRAMEWORK.md)** - Testing overview
- **[tests/MAINTENANCE.md](tests/MAINTENANCE.md)** - Schema-based database generation workflow
- **[tests/README.md](tests/README.md)** - Complete testing documentation
---
## Questions or Issues?
- **GitHub Issues:** https://github.com/Telecominfraproject/ols-ucentral-client/issues
- **Schema Issues:** https://github.com/Telecominfraproject/ols-ucentral-schema/issues
- **Documentation:** Check the `tests/` directory for detailed guides

218
QUICK_START_TESTING.md Normal file
View File

@@ -0,0 +1,218 @@
# Quick Start: Testing Guide
## TL;DR
```bash
# Test all configs with human-readable output (default)
./run-config-tests.sh
# Generate HTML report
./run-config-tests.sh --format html
# Test single config with HTML output
./run-config-tests.sh --format html cfg0.json
# Results are in: output/
```
## Common Commands
### Test All Configurations
```bash
# Stub mode (default - fast, proto.c parsing only)
./run-config-tests.sh # Console output with colors (default)
./run-config-tests.sh --format html # Interactive HTML report
./run-config-tests.sh --format json # Machine-readable JSON
# Platform mode (integration testing with platform code)
./run-config-tests.sh --mode platform # Console output
./run-config-tests.sh --mode platform --format html # HTML report
```
### Test Single Configuration
```bash
# Stub mode (default)
./run-config-tests.sh cfg0.json # Human output (default)
./run-config-tests.sh --format html cfg0.json # HTML report
./run-config-tests.sh --format json cfg0.json # JSON output
# Platform mode
./run-config-tests.sh --mode platform cfg0.json # Human output
./run-config-tests.sh --mode platform --format html cfg0.json # HTML report
```
### View Results
```bash
# Open HTML report in browser
open output/test-report.html # macOS
xdg-open output/test-report.html # Linux
# View text results
cat output/test-results.txt
# Parse JSON results
cat output/test-report.json | jq '.summary'
```
## What the Script Does
1. ✅ Checks Docker is running
2. ✅ Builds Docker environment (only if needed)
3. ✅ Starts/reuses container
4. ✅ Runs tests inside container
5. ✅ Copies results to `output/` directory
6. ✅ Shows summary
## Output Formats
| Format | Use Case | Output File |
|--------|----------|-------------|
| `human` | Interactive development, debugging | `output/test-results.txt` |
| `html` | Reports, sharing, presentations | `output/test-report.html` |
| `json` | CI/CD, automation, metrics | `output/test-report.json` |
## First Run vs Subsequent Runs
**First Run (cold start):**
- Builds Docker environment: ~10 minutes (one-time)
- Runs tests: ~30 seconds
- **Total: ~10 minutes**
**Subsequent Runs (warm start):**
- Reuses environment: ~2 seconds
- Runs tests: ~30 seconds
- **Total: ~30 seconds**
## Troubleshooting
### Docker not running
```bash
# Start Docker Desktop (macOS/Windows)
# OR
sudo systemctl start docker # Linux
```
### Permission denied
```bash
chmod +x run-config-tests.sh
```
### Config not found
```bash
# List available configs
ls config-samples/*.json
```
## CI/CD Integration
### Exit Codes
- `0` = All tests passed ✅
- `1` = Tests failed ❌
- `2` = System error ⚠️
### Example Pipeline
```yaml
- name: Run tests
run: ./run-config-tests.sh --format json
- name: Check results
run: |
if [ $? -eq 0 ]; then
echo "✅ All tests passed"
else
echo "❌ Tests failed"
exit 1
fi
```
## Available Test Configs
```bash
# List all configs
ls -1 config-samples/*.json | xargs -n1 basename
# Common test configs:
cfg0.json # Basic config
ECS4150-TM.json # Traffic management
ECS4150-ACL.json # Access control lists
ECS4150STP_RSTP.json # Spanning tree
ECS4150_IGMP_Snooping.json # IGMP snooping
ECS4150_POE.json # Power over Ethernet
ECS4150_VLAN.json # VLAN configuration
```
## What Gets Tested
✅ JSON schema validation (structure, types, constraints)
✅ Parser validation (actual C parser implementation)
✅ Property tracking (configured vs unknown properties)
✅ Feature coverage (implemented vs documented features)
✅ Error handling (invalid configs, missing fields)
## Test Modes
### Stub Mode (Default - Fast)
- Tests proto.c parsing only
- Uses simple platform stubs
- Shows base properties only
- Execution time: ~30 seconds
- Use for: Quick validation, CI/CD
### Platform Mode (Integration)
- Tests proto.c + platform code (plat-gnma.c)
- Uses platform implementation with mocks
- Shows base AND platform properties
- Tracks hardware application functions
- Execution time: ~45 seconds
- Use for: Platform-specific validation
## Quick Reference
| Task | Command |
|------|---------|
| Test everything (stub) | `./run-config-tests.sh` |
| Test everything (platform) | `./run-config-tests.sh --mode platform` |
| HTML report | `./run-config-tests.sh --format html` |
| JSON output | `./run-config-tests.sh --format json` |
| Single config | `./run-config-tests.sh cfg0.json` |
| Single config HTML | `./run-config-tests.sh -f html cfg0.json` |
| Platform mode single config | `./run-config-tests.sh -m platform cfg0.json` |
| View HTML | `open output/test-report.html` |
| View results | `cat output/test-results.txt` |
| Parse JSON | `cat output/test-report.json \| jq` |
## Full Documentation
- **TEST_RUNNER_README.md** - Complete script documentation
- **TESTING_FRAMEWORK.md** - Testing framework overview
- **tests/config-parser/TEST_CONFIG_README.md** - Detailed testing guide
- **TEST_CONFIG_PARSER_DESIGN.md** - Test framework architecture
- **tests/MAINTENANCE.md** - Maintenance procedures
- **README.md** - Project overview and build instructions
## Directory Structure
```
ols-ucentral-client/
├── run-config-tests.sh ← Test runner script
├── output/ ← Test results go here
├── config-samples/ ← Test configurations
└── tests/
├── config-parser/
│ ├── test-config-parser.c ← Test implementation
│ ├── test-stubs.c ← Platform stubs
│ ├── config-parser.h ← Test header
│ ├── Makefile ← Test build system
│ └── TEST_CONFIG_README.md ← Detailed guide
├── schema/
│ ├── validate-schema.py ← Schema validator
│ └── SCHEMA_VALIDATOR_README.md
├── tools/ ← Property database tools
└── MAINTENANCE.md ← Maintenance procedures
```
---
**Need help?** Check TEST_RUNNER_README.md for troubleshooting and advanced usage.

208
README.md
View File

@@ -1,9 +1,41 @@
# What is it?
This repo holds the source code for OLS (OpenLAN Switching) uCentral client implementation.
It implements both the ZTP Gateway discovery procedure (calling the ZTP Redirector and locating
the desired Gateway to connect to), as well as handlers to process the Gateway requests.
the desired Gateway to connect to), as well as handlers to process the Gateway requests.
Upon connecting to the Gateway service, the device can be provisioned and controlled from the
cloud in the same manner as uCentral OpenWifi APs do.
cloud in the same manner as uCentral OpenWifi APs do.
# Prerequisites
## Required Tools
- **Docker** (20.10+) - Dockerized build environment
- **Git** (2.20+) - Version control and schema repository access
- **Make** (3.81+) - Build system
## For Testing and Database Generation
- **Python 3** (3.7+) - Test scripts and database generation
- **PyYAML** (5.1+) - YAML schema parsing
Install Python dependencies:
```bash
pip3 install -r tests/requirements.txt
# Or: pip3 install pyyaml
```
## Schema Repository
The uCentral configuration schema is maintained in a separate repository:
- **Repository:** https://github.com/Telecominfraproject/ols-ucentral-schema
Fetch schema automatically (with intelligent branch matching):
```bash
cd tests/tools
./fetch-schema.sh
```
**See [PREREQUISITES.md](PREREQUISITES.md) for complete setup guide and troubleshooting.**
# Build System
The build system implements an automated dockerized-based build enviroment to produce a final
@@ -94,33 +126,155 @@ Technically same as 'all'. Produces final .deb pkg that can be copied to target
and installed as native deb pkg.
# Certificates
TIP Certificates should be preinstalled upon launch of the service. uCentral
client uses certificates to establish a secure connection with both Redirector
(firstcontact), as well as te GW itself.
In order to create a partition, partition_script.sh (part of this repo)
can be used to do so, the steps are as follows (should be executed on device):
Enter superuser mode
## PKI 2.0 Architecture
The uCentral client implements **PKI 2.0** using the EST (Enrollment over Secure Transport, RFC 7030) protocol for automated certificate lifecycle management.
### Certificate Types
**Birth Certificates** (Factory-provisioned):
- `cert.pem` - Birth certificate (factory-issued)
- `key.pem` - Private key
- `cas.pem` - CA certificate bundle
- `dev-id` - Device identifier
**Operational Certificates** (Runtime-generated):
- `operational.pem` - Operational certificate (obtained via EST)
- `operational.ca` - Operational CA certificate
### Certificate Lifecycle
1. **Factory Provisioning**: Birth certificates are provisioned to the device partition during manufacturing or initial setup
2. **First Boot**: On first boot, the uCentral client automatically enrolls with the EST server using birth certificates to obtain operational certificates
3. **Runtime**: The client uses operational certificates for all gateway connections
4. **Renewal**: Operational certificates can be renewed via the `reenroll` RPC command before expiration
### Automatic EST Enrollment
The client automatically:
- Detects the EST server based on certificate issuer (QA vs Production)
- Enrolls with the EST server to obtain operational certificates
- Saves operational certificates to `/etc/ucentral/`
- Falls back to birth certificates if enrollment fails
### Installing Birth Certificates
Birth certificates should be preinstalled before launching the service. Use `partition_script.sh` (included in this repo) to provision certificates to the device partition.
**Steps (execute on device):**
1. Enter superuser mode:
```bash
sudo su
```
$ sudo su
```
Create temp directory to copy certificates + script into, and unpack
Copy both certificates and partition script to the device:
```
$ mkdir /tmp/temp
$ cd /tmp/temp/
$ scp <remote_host>:/certificates/<some_mac>.tar ./
$ scp <remote_host>:/partition_script.sh ./
$ tar -xvf ./<some_mac>.tar
```
After certificate files are being unpacked, launch this script with single argument being
the path to the certificates directory (please note that BASH interpreter should be used explicitly,
it's done to make this script compatible with most ONIE builds as well, as they mostly
pack busybox / sh):
2. Create temp directory and copy certificates + script:
```bash
mkdir /tmp/temp
cd /tmp/temp/
scp <remote_host>:/certificates/<some_mac>.tar ./
scp <remote_host>:/partition_script.sh ./
tar -xvf ./<some_mac>.tar
```
3. Run the partition script to install birth certificates:
```bash
bash ./partition_script.sh ./
```
Once certificates are installed and partition is created, rebooting the device is required.
After reboot and uCentral start, service creates <TCA> volume upon start based on physical partition
(by-label provided by udev - /dev/disk/by-label/ONIE-TIP-CA-CERT) automatically.
4. Reboot the device:
```bash
reboot
```
After reboot, the uCentral service will:
- Mount the certificate partition (ONIE-TIP-CA-CERT)
- Automatically perform EST enrollment to obtain operational certificates
- Connect to the gateway using operational certificates
### Certificate Renewal
To renew operational certificates before expiration, send the `reenroll` RPC command from the gateway:
```json
{
"jsonrpc": "2.0",
"id": 123,
"method": "reenroll",
"params": {
"serial": "device_serial_number"
}
}
```
The client will:
1. Contact the EST server with current operational certificate
2. Obtain a renewed operational certificate
3. Save the new certificate to `/etc/ucentral/operational.pem`
4. Restart after 10 seconds to use the new certificate
### EST Servers
- **QA**: `qaest.certificates.open-lan.org:8001` (for Demo Birth CA)
- **Production**: `est.certificates.open-lan.org` (for Production Birth CA)
The EST server is automatically selected based on the certificate issuer field.
### Certificate Files Location
- Birth certificates: `/etc/ucentral/cert.pem`, `/etc/ucentral/key.pem`, `/etc/ucentral/cas.pem`
- Operational certificates: `/etc/ucentral/operational.pem`, `/etc/ucentral/operational.ca`
# Testing
The repository includes a comprehensive testing framework for configuration validation:
## Running Tests
**Quick Start:**
```bash
# Using the test runner script (recommended)
./run-config-tests.sh
# Generate HTML report
./run-config-tests.sh --format html
# Or run tests directly in the tests directory
cd tests/config-parser
make test-config-full
```
**Docker-based Testing (recommended for consistency):**
```bash
# Run in Docker environment
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-full"
```
## Test Framework
The testing framework validates configurations through two layers:
1. **Schema Validation** - JSON structure validation against uCentral schema
2. **Parser Testing** - Actual C parser implementation testing with property tracking
Tests are organized in the `tests/` directory:
- `tests/config-parser/` - Configuration parser tests
- `tests/schema/` - Schema validation
- `tests/tools/` - Property database generation tools
- `tests/unit/` - Unit tests
## Documentation
- **[TESTING_FRAMEWORK.md](TESTING_FRAMEWORK.md)** - Testing overview and quick reference
- **[tests/README.md](tests/README.md)** - Complete testing documentation
- **[tests/config-parser/TEST_CONFIG_README.md](tests/config-parser/TEST_CONFIG_README.md)** - Detailed testing guide
- **[tests/MAINTENANCE.md](tests/MAINTENANCE.md)** - Schema and property database maintenance
## Test Configuration
Test configurations are located in `config-samples/`:
- 21 positive test configurations covering various features
- 4 negative test configurations for error handling validation
- JSON schema: `config-samples/ucentral.schema.pretty.json`

497
TESTING_FRAMEWORK.md Normal file
View File

@@ -0,0 +1,497 @@
# Configuration Testing Framework
## Overview
The OLS uCentral Client includes a comprehensive configuration testing framework that provides two-layer validation of JSON configurations:
1. **Schema Validation** - Structural validation against the uCentral JSON schema
2. **Parser Testing** - Implementation validation of the C parser with property tracking
This framework enables automated testing, continuous integration, and tracking of configuration feature implementation status.
## Documentation Index
This testing framework includes multiple documentation files, each serving a specific purpose:
### Primary Documentation
1. **[tests/config-parser/TEST_CONFIG_README.md](tests/config-parser/TEST_CONFIG_README.md)** - Complete testing framework guide
- Overview of two-layer validation approach
- Quick start and running tests
- Property tracking system
- Configuration-specific validators
- Test output interpretation
- CI/CD integration
- **Start here** for understanding the testing framework
2. **[tests/schema/SCHEMA_VALIDATOR_README.md](tests/schema/SCHEMA_VALIDATOR_README.md)** - Schema validator detailed documentation
- Standalone validator usage
- Command-line interface
- Programmatic API
- Porting guide for other repositories
- Common validation errors
- **Start here** for schema validation specifics
3. **[tests/MAINTENANCE.md](tests/MAINTENANCE.md)** - Maintenance procedures guide
- Schema update procedures
- Property database update procedures
- Version synchronization
- Testing after updates
- Troubleshooting common issues
- **Start here** when updating schema or property database
4. **[TEST_CONFIG_PARSER_DESIGN.md](TEST_CONFIG_PARSER_DESIGN.md)** - Test framework architecture
- Multi-layer validation design
- Property metadata system (398 schema properties)
- Property inspection engine
- Test execution flow diagrams
- Data structures and algorithms
- Output format implementations
- **Start here** for understanding the test framework internals
### Supporting Documentation
5. **[README.md](README.md)** - Project overview and build instructions
- Build system architecture
- Platform abstraction layer
- Testing framework integration
- Deployment instructions
## Quick Reference
### Running Tests
**RECOMMENDED: Use the test runner script** (handles Docker automatically):
```bash
# Test all configurations in STUB mode (default - fast, proto.c only)
./run-config-tests.sh # Human-readable output
./run-config-tests.sh --format html # HTML report
./run-config-tests.sh --format json # JSON report
# Test all configurations in PLATFORM mode (integration testing)
./run-config-tests.sh --mode platform # Human-readable output
./run-config-tests.sh --mode platform --format html # HTML report
# Test single configuration
./run-config-tests.sh cfg0.json # Stub mode
./run-config-tests.sh --mode platform cfg0.json # Platform mode
```
**Alternative: Run tests directly in Docker** (manual Docker management):
```bash
# Build the Docker environment first (if not already built)
make build-host-env
# Run all tests in STUB mode (default - fast)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-full"
# Run all tests in PLATFORM mode (integration)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-full USE_PLATFORM=brcm-sonic"
# Run individual test suites (stub mode)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make validate-schema"
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config"
# Generate test reports (stub mode)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-html"
# Generate test reports (platform mode)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-html USE_PLATFORM=brcm-sonic"
# Copy report files out of container to view
docker cp ucentral_client_build_env:/root/ols-nos/tests/config-parser/test-report.html output/
```
**Alternative: Run tests locally** (may have OS-specific dependencies):
```bash
# Navigate to test directory
cd tests/config-parser
# Run all tests in STUB mode (default)
make test-config-full
# Run all tests in PLATFORM mode
make test-config-full USE_PLATFORM=brcm-sonic
# Run individual test suites
make validate-schema # Schema validation only
make test-config # Parser tests only
# Generate test reports (stub mode)
make test-config-html # HTML report (browser-viewable)
make test-config-json # JSON report (machine-readable)
make test-config-junit # JUnit XML (CI/CD integration)
# Generate test reports (platform mode)
make test-config-html USE_PLATFORM=brcm-sonic
make test-config-json USE_PLATFORM=brcm-sonic
```
**Note:** Running tests in Docker is the preferred method as it provides a consistent, reproducible environment regardless of your host OS (macOS, Linux, Windows).
### Key Files
**Test Implementation:**
- `tests/config-parser/test-config-parser.c` (3304 lines) - Parser test framework with property tracking
- `tests/config-parser/test-stubs.c` (219 lines) - Platform function stubs for stub mode testing
- `tests/config-parser/platform-mocks/brcm-sonic.c` - gNMI/gNOI mocks for platform mode
- `tests/config-parser/platform-mocks/example-platform.c` - Example platform mocks
- `tests/schema/validate-schema.py` (649 lines) - Standalone schema validator with undefined property detection
- `tests/config-parser/config-parser.h` - Test header exposing cfg_parse()
**Property Database Files:**
- `tests/config-parser/property-database-base.c` (416 lines) - Proto.c parsing (398 properties: 102 implemented, 296 not yet)
- `tests/config-parser/property-database-platform-brcm-sonic.c` (419 lines) - Platform hardware application tracking
- `tests/config-parser/property-database-platform-example.c` - Example platform database
**Configuration Files:**
- `config-samples/ucentral.schema.pretty.json` - uCentral JSON schema (human-readable, single schema file)
- `config-samples/*.json` - Test configuration files (25 configs)
- `config-samples/*invalid*.json` - Negative test cases
**Build System:**
- `tests/config-parser/Makefile` - Test targets and build rules
- `run-config-tests.sh` - Test runner script (recommended)
**Production Code (Minimal Changes):**
- `src/ucentral-client/proto.c` - Added TEST_STATIC macro (2 lines changed)
- `src/ucentral-client/include/router-utils.h` - Added extern declarations (minor change)
## Features
### Schema Validation
- Validates JSON structure against official uCentral schema
- Checks property types, required fields, constraints
- Standalone tool, no dependencies on C code
- Exit codes for CI/CD integration
### Parser Testing
- Tests actual C parser implementation
- Multiple output formats (human-readable, HTML, JSON, JUnit XML)
- Interactive HTML reports with detailed analysis
- Machine-readable JSON for automation
- JUnit XML for CI/CD integration
- Validates configuration processing and struct population
- Configuration-specific validators for business logic
- Memory leak detection
- Hardware constraint validation
### Property Tracking System
- Database of all schema properties and their implementation status (398 canonical properties)
- Tracks which properties are parsed by which functions
- Identifies unimplemented features
- Status classification: CONFIGURED, IGNORED, SYSTEM, INVALID, Unknown
- Property usage reports across all test configurations
- Properties with line numbers are implemented, line_number=0 means not yet implemented
### Two-Layer Validation Strategy
**Why Both Layers?**
Each layer catches different types of errors:
- **Schema catches**: Type mismatches, missing required fields, constraint violations
- **Parser catches**: Implementation bugs, hardware limits, cross-field dependencies
- **Property tracking catches**: Missing implementations, platform-specific features
See TEST_CONFIG_README.md section "Two-Layer Validation Strategy" for detailed explanation.
## Test Coverage
Current test suite includes:
- 25 configuration files covering various features
- Positive tests (configs that should parse successfully)
- Negative tests (configs that should fail)
- Feature-specific validators for critical configurations
- Two testing modes: stub mode (fast, proto.c only) and platform mode (integration, proto.c + platform code)
- Platform stub with 54-port simulation (matches ECS4150 hardware)
### Tested Features
- Port configuration (enable/disable, speed, duplex)
- VLAN configuration and membership
- Spanning Tree Protocol (STP, RSTP, PVST, RPVST)
- IGMP Snooping
- Power over Ethernet (PoE)
- IEEE 802.1X Authentication
- DHCP Relay
- Static routing
- System configuration (timezone, hostname, etc.)
### Platform-Specific Features (Schema-Valid, Platform Implementation Required)
- LLDP (Link Layer Discovery Protocol)
- LACP (Link Aggregation Control Protocol)
- ACLs (Access Control Lists)
- DHCP Snooping
- Loop Detection
- Port Mirroring
- Voice VLAN
These features pass schema validation but show as "Unknown" in property reports, indicating they require platform-specific implementation.
## Changes from Base Repository
The testing framework was added with minimal impact to production code:
### New Files Added
1. `tests/config-parser/test-config-parser.c` (3304 lines) - Complete test framework
2. `tests/config-parser/test-stubs.c` (219 lines) - Platform stubs for stub mode
3. `tests/config-parser/platform-mocks/brcm-sonic.c` - Platform mocks for brcm-sonic
4. `tests/config-parser/platform-mocks/example-platform.c` - Platform mocks for example platform
5. `tests/config-parser/property-database-base.c` (416 lines) - Base property database (398 properties: 102 implemented, 296 not yet)
6. `tests/config-parser/property-database-platform-brcm-sonic.c` (419 lines) - Platform property database
7. `tests/config-parser/property-database-platform-example.c` - Example platform property database
8. `tests/schema/validate-schema.py` (649 lines) - Schema validator
9. `tests/config-parser/config-parser.h` - Test header
10. `tests/config-parser/TEST_CONFIG_README.md` - Framework documentation
11. `tests/config-parser/platform-mocks/README.md` - Platform mocks documentation
12. `tests/schema/SCHEMA_VALIDATOR_README.md` - Validator documentation
13. `tests/MAINTENANCE.md` - Maintenance procedures
14. `tests/ADDING_NEW_PLATFORM.md` - Platform addition guide
15. `tests/config-parser/Makefile` - Test build system with stub and platform modes
16. `tests/tools/extract-schema-properties.py` - Extract properties from schema
17. `tests/tools/generate-database-from-schema.py` - Generate base property database
18. `tests/tools/generate-platform-database-from-schema.py` - Generate platform property database
19. `tests/README.md` - Testing framework overview
20. `TESTING_FRAMEWORK.md` - This file (documentation index, in repository root)
21. `TEST_CONFIG_PARSER_DESIGN.md` - Test framework architecture and design (in repository root)
22. `QUICK_START_TESTING.md` - Quick start guide (in repository root)
23. `TEST_RUNNER_README.md` - Test runner script documentation (in repository root)
24. `run-config-tests.sh` - Test runner script (in repository root)
### Modified Files
1. `src/ucentral-client/proto.c` - Added TEST_STATIC macro pattern (2 lines)
```c
// Changed from:
static struct plat_cfg *cfg_parse(...)
// Changed to:
#ifdef UCENTRAL_TESTING
#define TEST_STATIC
#else
#define TEST_STATIC static
#endif
TEST_STATIC struct plat_cfg *cfg_parse(...)
```
This allows test code to call cfg_parse() while keeping it static in production builds.
2. `src/ucentral-client/include/router-utils.h` - Added extern declarations
- Exposed necessary functions for test stubs
3. `src/ucentral-client/Makefile` - No changes (production build only)
- Test targets are in tests/config-parser/Makefile
- Clean separation between production and test code
### Configuration Files
- Added `config-samples/cfg_invalid_*.json` - Negative test cases
- Added `config-samples/ECS4150_*.json` - Feature-specific test configs
- No changes to existing valid configurations
### Zero Impact on Production
- Production builds: No functional changes, cfg_parse() remains static
- Test builds: cfg_parse() becomes visible with -DUCENTRAL_TESTING flag
- No ABI changes, no performance impact
- No runtime dependencies added
## Integration with Development Workflow
### During Development
```bash
# 1. Make code changes to proto.c
vi src/ucentral-client/proto.c
# 2. Run tests using test runner script
./run-config-tests.sh
# 3. Review property tracking report
# Check for unimplemented features or errors
# 4. If adding new parser function, update property database
vi tests/config-parser/test-config-parser.c
# Add property entries for new function
# 5. Create test configuration
vi config-samples/test-new-feature.json
# 6. Retest
./run-config-tests.sh
```
### Before Committing
```bash
# Ensure all tests pass
./run-config-tests.sh
# Generate full HTML report for review
./run-config-tests.sh --format html
open output/test-report.html
# Check for property database accuracy
# Review "Property Usage Report" section in HTML report
# Look for unexpected "Unknown" properties
```
### In CI/CD Pipeline
```yaml
test-configurations:
stage: test
script:
- ./run-config-tests.sh --format json
artifacts:
paths:
- output/test-report.json
- output/test-report.html
when: always
```
## Property Database Management
The property database is a critical component tracking which JSON properties are parsed by which functions.
### Database Structure
```c
static struct property_metadata properties[] = {
{
.path = "interfaces.ethernet.enabled",
.status = PROP_CONFIGURED,
.source_file = "proto.c",
.source_function = "cfg_ethernet_parse",
.source_line = 1119,
.notes = "Enable/disable ethernet interface"
},
// ... entries for all 398 schema properties (with line numbers for implemented properties) ...
};
```
### Key Rules
1. **Only track properties for functions that exist in this repository's proto.c**
2. **Remove entries when parser functions are removed**
3. **Add entries immediately when adding new parser functions**
4. **Use accurate function names** - different platforms may use different names
5. **Properties not in database show as "Unknown"** - this is correct for platform-specific features
See MAINTENANCE.md for complete property database update procedures.
## Schema Management
The schema file defines what configurations are structurally valid.
### Schema Location
- `config-samples/ucentral.schema.pretty.json` - Human-readable version (single schema file in repository)
### Schema Source
Schema is maintained in the external [ols-ucentral-schema](https://github.com/Telecominfraproject/ols-ucentral-schema) repository.
### Schema Updates
When ols-ucentral-schema releases a new version:
1. Copy new schema to config-samples/
2. Run schema validation on all test configs
3. Fix any configs that fail new requirements
4. Document breaking changes
5. Update property database if new properties are implemented
See MAINTENANCE.md section "Schema Update Procedures" for complete process.
## Platform-Specific Repositories
This is the **base repository** providing the core framework. Platform-specific repositories (like Edgecore EC platform) can:
1. **Fork the test framework** - Copy test files to their repository
2. **Extend property database** - Add entries for platform-specific parser functions
3. **Add platform configs** - Create configs testing platform features
4. **Maintain separate tracking** - Properties "Unknown" in base become "CONFIGURED" in platform
### Example: LLDP Property Status
**In base repository (this repo):**
```
Property: interfaces.ethernet.lldp
Status: Unknown (not in property database)
Note: May require platform-specific implementation
```
**In Edgecore EC platform repository:**
```
Property: interfaces.ethernet.lldp
Parser: cfg_ethernet_lldp_parse()
Status: CONFIGURED
Note: Per-interface LLDP transmit/receive configuration
```
Each platform tracks only the properties it actually implements.
## Troubleshooting
### Common Issues
**Tests fail in Docker but pass locally:**
- Check schema file exists in container
- Verify paths are correct in container environment
- Rebuild container: `make build-host-env`
**Property shows as "Unknown" when it should be CONFIGURED:**
- Verify parser function exists: `grep "function_name" proto.c`
- Check property path matches JSON exactly
- Ensure property entry is in properties[] array
**Schema validation fails for valid config:**
- Schema may be outdated - check version
- Config may use vendor extensions not in base schema
- Validate against specific schema: `./validate-schema.py config.json --schema /path/to/schema.json`
See MAINTENANCE.md "Troubleshooting" section for complete troubleshooting guide.
## Documentation Maintenance
When updating the testing framework:
1. **Update relevant documentation:**
- New features → TEST_CONFIG_README.md
- Schema changes → MAINTENANCE.md + SCHEMA_VALIDATOR_README.md
- Property database changes → MAINTENANCE.md + TEST_CONFIG_README.md
- Build changes → README.md
2. **Keep version information current:**
- Update compatibility matrices
- Document breaking changes
- Maintain changelogs
3. **Update examples:**
- Refresh command output examples
- Update property counts
- Keep test results current
## Contributing
When contributing to the testing framework:
1. **Maintain property database accuracy** - Update when changing parser functions
2. **Add test configurations** - Create configs demonstrating new features
3. **Update documentation** - Keep docs synchronized with code changes
4. **Follow conventions** - Use established patterns for validators and property entries
5. **Test thoroughly** - Run full test suite before committing
## License
BSD-3-Clause (same as parent project)
## See Also
- **[tests/config-parser/TEST_CONFIG_README.md](tests/config-parser/TEST_CONFIG_README.md)** - Complete testing framework guide
- **[TEST_CONFIG_PARSER_DESIGN.md](TEST_CONFIG_PARSER_DESIGN.md)** - Test framework architecture and design
- **[tests/schema/SCHEMA_VALIDATOR_README.md](tests/schema/SCHEMA_VALIDATOR_README.md)** - Schema validator documentation
- **[tests/MAINTENANCE.md](tests/MAINTENANCE.md)** - Update procedures and troubleshooting
- **[TEST_RUNNER_README.md](TEST_RUNNER_README.md)** - Test runner script documentation
- **[QUICK_START_TESTING.md](QUICK_START_TESTING.md)** - Quick start guide
- **[README.md](README.md)** - Project overview and build instructions
- **ols-ucentral-schema repository** - Official schema source

View File

@@ -0,0 +1,308 @@
# Design of test-config-parser.c
The `tests/config-parser/test-config-parser.c` file implements a comprehensive configuration testing framework with a sophisticated multi-layered design. This document describes the architecture and implementation details.
## 1. **Core Architecture: Multi-Layer Validation**
The framework validates configurations through three complementary layers:
### Layer 1: Schema Validation
- Invokes external `tests/schema/validate-schema.py` to verify JSON structure against uCentral schema
- Catches: JSON syntax errors, type mismatches, missing required fields, constraint violations
- If schema validation fails, parsing is skipped to ensure clean error isolation
### Layer 2: Parser Testing
- Calls production `cfg_parse()` function from `src/ucentral-client/proto.c`
- Tests actual C parser implementation with real platform data structures
- Catches: Parser bugs, memory issues, hardware constraints, cross-field dependencies
### Layer 3: Property Tracking
- Deep recursive inspection of JSON tree to classify every property
- Maps properties to property metadata database (398 schema properties)
- Tracks which properties are CONFIGURED, IGNORED, INVALID, UNKNOWN, etc.
- Properties with line numbers are implemented in proto.c; line_number=0 means not yet implemented
## 2. **Property Metadata System**
### Property Database Structure
```c
struct property_metadata {
const char *path; // JSON path: "ethernet[].speed"
enum property_status status; // CONFIGURED, IGNORED, UNKNOWN, etc.
const char *source_file; // Where processed: "proto.c"
const char *source_function; // Function: "cfg_ethernet_parse"
int source_line; // Line number in proto.c (if available)
const char *notes; // Context/rationale
};
```
**Database contains entries for all 398 schema properties** documenting:
- Which properties are actively parsed (PROP_CONFIGURED with line numbers)
- Which are not yet implemented (line_number=0)
- Which are intentionally ignored (PROP_IGNORED)
- Which need platform implementation (PROP_UNKNOWN)
- Which are structural containers (PROP_SYSTEM)
### Property Status Classification
- **PROP_CONFIGURED**: Successfully processed by parser
- **PROP_MISSING**: Required but absent
- **PROP_IGNORED**: Present but intentionally not processed
- **PROP_INVALID**: Invalid value (out of bounds, wrong type)
- **PROP_INCOMPLETE**: Missing required sub-fields
- **PROP_UNKNOWN**: Needs manual classification/testing (may require platform implementation)
- **PROP_SYSTEM**: Structural container (not leaf value)
## 3. **Property Inspection Engine**
### scan_json_tree_recursive() (lines 1399-1459)
Recursive descent through JSON tree:
1. Traverses entire JSON configuration structure
2. For each property, builds full dot-notation path (e.g., `"interfaces[].ipv4.subnet[].prefix"`)
3. Looks up property in metadata database via `lookup_property_metadata()`
4. Records property validation result with status, value, source location
5. Continues recursion into nested objects/arrays
### lookup_property_metadata() (lines 1314-1348)
Smart property matching:
1. Normalizes path by replacing `[N]` with `[]` (e.g., `ethernet[5].speed``ethernet[].speed`)
2. Searches property database for matching canonical path
3. Returns metadata if found, NULL if unknown property
### scan_for_unprocessed_properties() (lines 1666-1765)
Legacy unprocessed property detection:
- Checks properties against known property lists at each config level
- Reports properties that exist in JSON but aren't in "known" lists
- Used alongside property database for comprehensive coverage
## 4. **Test Execution Flow**
### Main Test Function: test_config_file() (lines 1790-1963)
```
┌─────────────────────────────────────────┐
│ 1. Schema Validation │
│ - validate_against_schema() │
│ - If fails: mark test, skip parsing │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 2. JSON Parsing │
│ - read_json_file() │
│ - cJSON_Parse() │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 3. Feature Detection │
│ - detect_json_features() │
│ - Find LLDP, ACL, LACP, etc. │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 4. Property Inspection │
│ - scan_json_tree_recursive() │
│ - Build property validation list │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 5. Parser Invocation │
│ - cfg = cfg_parse(json) │
│ - Invoke production parser │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 6. Feature Statistics │
│ - update_feature_statistics() │
│ - Count ports, VLANs, features │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 7. Validation (Optional) │
│ - run_validator() for specific │
│ configs (cfg0, PoE, DHCP, etc.) │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 8. Result Recording │
│ - finalize_test_result() │
│ - Store in linked list │
└─────────────────────────────────────────┘
```
## 5. **Data Structures**
### test_result (lines 94-128)
Per-test result tracking:
```c
struct test_result {
char filename[256];
int passed;
char error_message[512];
int ports_configured, vlans_configured;
int unprocessed_properties;
// Property counters
int properties_configured;
int properties_missing;
int properties_ignored;
// ... etc
// Feature presence flags
int has_port_config, has_vlan_config;
int has_stp, has_igmp, has_poe;
// ... etc
// Linked list of property validations
struct property_validation *property_validations;
struct test_result *next;
};
```
### property_validation (lines 85-92)
Individual property validation record:
```c
struct property_validation {
char path[128]; // "unit.hostname"
enum property_status status;
char value[512]; // "\"switch01\""
char details[256]; // Additional context
char source_location[128]; // "proto.c:cfg_unit_parse()"
struct property_validation *next;
};
```
## 6. **Feature Statistics Tracking**
### Global Statistics (lines 40-56)
```c
struct feature_stats {
int configs_with_ports;
int configs_with_vlans;
int configs_with_stp;
int configs_with_igmp;
int configs_with_poe;
int configs_with_ieee8021x;
int configs_with_dhcp_relay;
int configs_with_lldp; // JSON-detected
int configs_with_acl; // JSON-detected
int configs_with_lacp; // JSON-detected
// ... etc
};
```
**Two detection methods:**
1. **Parser-based**: Check `plat_cfg` structure for configured values (ports, VLANs, STP mode)
2. **JSON-based**: Detect schema-valid features in JSON that may not be parsed (LLDP, ACL, LACP)
## 7. **Output Formats** (lines 26-31)
### OUTPUT_HUMAN (default)
- Colorful console output with emojis
- Detailed property analysis
- Processing summaries
- Feature statistics
### OUTPUT_JSON (lines 2015-2097)
- Machine-readable JSON report
- Full test results with property details
- CI/CD integration friendly
### OUTPUT_HTML (lines 2099+)
- Interactive web report
- Full test details with styling
- Browser-viewable (982KB typical size)
### OUTPUT_JUNIT (planned)
- JUnit XML format for Jenkins/GitLab CI
## 8. **Validator Registry** (lines 302-343)
Optional per-config validators for deep validation:
```c
static const struct config_validator validators[] = {
{ "cfg0.json", validate_cfg0, "Port disable configuration" },
{ "cfg5_poe.json", validate_cfg_poe, "PoE configuration" },
{ "cfg6_dhcp.json", validate_cfg_dhcp, "DHCP relay" },
// ... etc
};
```
Validators inspect `plat_cfg` structure to verify specific features were correctly parsed.
## 9. **Test Discovery** (lines 1968-2010)
`test_directory()` auto-discovers test configs:
- Scans directory for `.json` files
- Skips `schema.json`, `Readme.json`
- Invokes `test_config_file()` for each config
## 10. **Key Design Patterns**
### Negative Test Support (lines 445-458)
```c
static int is_negative_test(const char *filename) {
if (strstr(filename, "invalid") != NULL) return 1;
if (strstr(filename, "ECS4150_port_isoltaon.json") != NULL) return 1;
return 0;
}
```
Configs expected to fail are marked as "PASS" if parsing fails.
### Schema-First Validation (lines 1818-1836)
Schema validation is a **prerequisite** for parser testing. If schema fails, parser is never invoked, ensuring clean error isolation.
### Linked List Result Storage (lines 221-242)
All test results stored in linked list for:
- Multiple output format generation from same data
- Summary statistics calculation
- Report generation after all tests complete
## 11. **Critical Integration Points**
### With Production Code (minimal impact):
- **proto.c**: Uses `cfg_parse()` exposed via `TEST_STATIC` macro
- **ucentral-log.h**: Registers `test_log_callback()` to capture parser errors (lines 134-160)
- **ucentral-platform.h**: Inspects `struct plat_cfg` to verify parsing results
### With Schema Validator:
- **tests/schema/validate-schema.py**: External Python script invoked via `system()` call
- Schema path: `config-samples/ols.ucentral.schema.pretty.json`
## 12. **Property Database Maintenance Rules**
**Critical Rule**:
> The property database must only contain entries for parser functions that exist in this repository's proto.c. Do not add entries for platform-specific functions that don't exist in the base implementation.
This keeps the base repository clean and allows platform-specific forks to extend the database with their own implementations.
---
## Summary
The design elegantly separates concerns:
1. **Schema layer** validates JSON structure (delegated to Python)
2. **Parser layer** tests C implementation (calls production code)
3. **Property layer** tracks implementation status (metadata database)
4. **Validation layer** verifies specific features (optional validators)
5. **Reporting layer** generates multiple output formats
The property metadata database is the **crown jewel** - it documents the implementation status of all 398 schema properties, enabling automated detection of unimplemented features and validation of parser coverage.
## Related Documentation
For additional information about the testing framework:
- **TESTING_FRAMEWORK.md** - Overview and documentation index
- **tests/config-parser/TEST_CONFIG_README.md** - Complete testing framework guide
- **tests/schema/SCHEMA_VALIDATOR_README.md** - Schema validator documentation
- **tests/MAINTENANCE.md** - Schema and property database update procedures
- **TEST_RUNNER_README.md** - Test runner script documentation
- **QUICK_START_TESTING.md** - Quick start guide
- **README.md** - Project overview and testing framework integration

479
TEST_RUNNER_README.md Normal file
View File

@@ -0,0 +1,479 @@
# Test Runner Script Documentation
## Overview
`run-config-tests.sh` is a comprehensive Docker-based test runner for uCentral configuration validation. It automates the entire testing workflow: building the Docker environment, running tests with various output formats, and copying results to the host.
## Features
- **Automatic Docker Environment Management**
- Builds Docker environment only when needed (checks Dockerfile SHA)
- Starts/reuses existing containers intelligently
- No manual Docker commands required
- **Multiple Output Formats**
- **human**: Human-readable console output with colors and detailed analysis
- **html**: Interactive HTML report with test results and property tracking
- **json**: Machine-readable JSON for automation and metrics
- **Flexible Testing**
- Test all configurations in one run
- Test a single configuration file
- Automatic result file naming and organization
- **Production-Ready**
- Exit codes for CI/CD integration (0 = pass, non-zero = fail/issues)
- Colored output for readability
- Comprehensive error handling
- Results automatically copied to `output/` directory
## Usage
### Basic Syntax
```bash
./run-config-tests.sh [OPTIONS] [config-file]
```
**Options:**
- `-f, --format FORMAT`: Output format - `html`, `json`, or `human` (default: `human`)
- `-m, --mode MODE`: Test mode - `stub` or `platform` (default: `stub`)
- `-p, --platform NAME`: Platform name for platform mode (default: `brcm-sonic`)
- `-h, --help`: Show help message
**Arguments:**
- `config-file` (optional): Specific config file to test (default: test all configs)
### Examples
#### Test All Configurations
```bash
# Human-readable output (default)
./run-config-tests.sh
# HTML report
./run-config-tests.sh --format html
# OR short form:
./run-config-tests.sh -f html
# JSON output
./run-config-tests.sh --format json
# OR short form:
./run-config-tests.sh -f json
```
#### Test Single Configuration
```bash
# Test single config with human output (default)
./run-config-tests.sh cfg0.json
# Test single config with HTML report
./run-config-tests.sh --format html cfg0.json
# OR short form:
./run-config-tests.sh -f html cfg0.json
# Test single config with JSON output
./run-config-tests.sh --format json cfg0.json
# OR short form:
./run-config-tests.sh -f json cfg0.json
```
## Output Files
All output files are saved to the `output/` directory in the repository root.
### Output File Naming
**All Configs:**
- `test-results.txt` - Human-readable output
- `test-report.html` - HTML report
- `test-report.json` - JSON output
**Single Config:**
- `test-results-{config-name}.txt` - Human-readable output
- `test-report-{config-name}.html` - HTML report
- `test-results-{config-name}.json` - JSON output
### Output Directory Structure
```
output/
├── test-results.txt # All configs, human format
├── test-report.html # All configs, HTML format
├── test-report.json # All configs, JSON format
├── test-results-cfg0.txt # Single config results
├── test-report-ECS4150-TM.html # Single config HTML
└── test-results-ECS4150-ACL.json # Single config JSON
```
## How It Works
### Workflow Steps
1. **Docker Check**: Verifies Docker daemon is running
2. **Environment Build**: Builds Docker environment if needed (caches based on Dockerfile SHA)
3. **Container Start**: Starts or reuses existing container
4. **Test Execution**: Runs tests inside container with specified format
5. **Result Copy**: Copies output files from container to host `output/` directory
6. **Summary**: Displays test summary and output file locations
### Docker Environment Management
The script intelligently manages the Docker environment:
```
Dockerfile unchanged → Skip build (use existing image)
Dockerfile modified → Build new image with new SHA tag
Container exists → Reuse existing container
Container missing → Create new container
Container stopped → Start existing container
```
This ensures fast subsequent runs while detecting when rebuilds are necessary.
## Output Format Details
### Human Format (default)
Human-readable console output with:
- Color-coded pass/fail indicators
- Detailed error messages
- Property usage reports
- Feature coverage analysis
- Schema validation results
**Best for:** Interactive development, debugging, manual testing
**Example:**
```
[TEST] config-samples/cfg0.json
✓ PASS - Schema validation
✓ PASS - Parser validation
Properties: 42 configured, 5 unknown
Total tests: 37
Passed: 37
Failed: 0
```
### HTML Format
Interactive web report with:
- Test result summary table
- Pass/fail status with colors
- Expandable test details
- Property tracking information
- Feature coverage matrix
- Timestamp and metadata
**Best for:** Test reports, sharing results, archiving, presentations
**Open with:**
```bash
open output/test-report.html # macOS
xdg-open output/test-report.html # Linux
start output/test-report.html # Windows
```
### JSON Format
Machine-readable structured data with:
- Test results array
- Pass/fail status
- Error details
- Property usage data
- Timestamps
- Exit codes
**Best for:** CI/CD integration, automation, metrics, analysis
**Structure:**
```json
{
"summary": {
"total": 37,
"passed": 37,
"failed": 0,
"timestamp": "2025-12-15T10:30:00Z"
},
"tests": [
{
"config": "cfg0.json",
"passed": true,
"schema_valid": true,
"parser_valid": true,
"properties": { "configured": 42, "unknown": 5 }
}
]
}
```
## Exit Codes
The script uses exit codes for CI/CD integration:
- `0` - All tests passed successfully
- `1` - Some tests failed or had validation errors
- `2` - System errors (Docker not running, file not found, etc.)
**CI/CD Example:**
```bash
./run-config-tests.sh --format json
if [ $? -eq 0 ]; then
echo "All tests passed!"
else
echo "Tests failed, see output/test-report.json"
exit 1
fi
```
## Performance
### First Run (Cold Start)
```
Build Docker environment: 5-10 minutes (one-time)
Run all config tests: 10-30 seconds
Total first run: ~10 minutes
```
### Subsequent Runs (Warm Start)
```
Environment check: 1-2 seconds (skipped if unchanged)
Container startup: 1-2 seconds (or reuse running container)
Run all config tests: 10-30 seconds
Total subsequent run: ~15 seconds
```
### Single Config Test
```
Test single config: 1-3 seconds
Total time: ~5 seconds (with running container)
```
## Troubleshooting
### Docker Not Running
**Error:**
```
✗ Docker is not running. Please start Docker and try again.
```
**Solution:**
- Start Docker Desktop (macOS/Windows)
- Start Docker daemon: `sudo systemctl start docker` (Linux)
### Container Build Failed
**Error:**
```
✗ Failed to build environment
```
**Solution:**
```bash
# Clean Docker and rebuild
docker system prune -a
make clean
./run-config-tests.sh
```
### Config File Not Found
**Error:**
```
✗ Config file not found in container: myconfig.json
```
**Solution:**
- Check available configs: `ls config-samples/*.json`
- Ensure config file is in `config-samples/` directory
- Use correct filename (case-sensitive)
### Test Output Not Copied
**Error:**
```
⚠ Output file not found in container: test-report.html
```
**Solution:**
- Check test execution logs for errors
- Verify test completed successfully inside container
- Try running tests manually: `docker exec ucentral_client_build_env bash -c "cd /root/ols-nos/tests/config-parser && make test-config"`
### Permission Denied
**Error:**
```
Permission denied: ./run-config-tests.sh
```
**Solution:**
```bash
chmod +x run-config-tests.sh
```
## Integration with Existing Workflows
### With Makefile
The script is independent of the Makefile but uses the same Docker infrastructure:
```bash
# Build environment (Makefile or script)
make build-host-env
# OR let script build it automatically
# Run tests (script provides better output management)
./run-config-tests.sh --format html
```
### With CI/CD
#### GitHub Actions
```yaml
name: Configuration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run config tests
run: ./run-config-tests.sh --format json
- name: Upload test results
uses: actions/upload-artifact@v3
with:
name: test-results
path: output/test-report.json
```
#### GitLab CI
```yaml
test-configs:
stage: test
script:
- ./run-config-tests.sh --format json
artifacts:
paths:
- output/test-report.json
when: always
```
#### Jenkins
```groovy
stage('Test Configurations') {
steps {
sh './run-config-tests.sh --format html'
publishHTML([
reportDir: 'output',
reportFiles: 'test-report.html',
reportName: 'Config Test Report'
])
}
}
```
### With Git Hooks
**Pre-commit hook** (test before commit):
```bash
#!/bin/bash
# .git/hooks/pre-commit
echo "Running configuration tests..."
./run-config-tests.sh
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
```
## Advanced Usage
### Custom Output Directory
Modify the `OUTPUT_DIR` variable in the script:
```bash
# Edit run-config-tests.sh
OUTPUT_DIR="$SCRIPT_DIR/my-custom-output"
```
### Test Specific Config Pattern
```bash
# Test all ACL configs
for config in config-samples/*ACL*.json; do
./run-config-tests.sh --format json "$(basename $config)"
done
```
### Parallel Testing (Multiple Containers)
```bash
# Start multiple containers for parallel testing
docker exec ucentral_client_build_env_1 bash -c "cd /root/ols-nos/tests/config-parser && ./test-config-parser config1.json" &
docker exec ucentral_client_build_env_2 bash -c "cd /root/ols-nos/tests/config-parser && ./test-config-parser config2.json" &
wait
```
### Automated Report Generation
```bash
# Generate all format reports
for format in human html json; do
./run-config-tests.sh --format $format
done
# Timestamp reports
mv output/test-report.html output/test-report-$(date +%Y%m%d-%H%M%S).html
```
## Comparison with Direct Make Commands
| Feature | run-config-tests.sh | Direct Make |
|---------|---------------------|-------------|
| Docker management | Automatic | Manual |
| Output to host | Automatic | Manual copy |
| Format selection | Command-line arg | Multiple make targets |
| Single config test | Built-in | Manual setup |
| Result organization | Automatic | Manual |
| Error handling | Comprehensive | Basic |
| CI/CD ready | Yes (exit codes) | Requires scripting |
**Recommendation:** Use `run-config-tests.sh` for all testing workflows. It provides a better user experience and handles Docker complexity automatically.
## Related Documentation
- **TESTING_FRAMEWORK.md** - Overview of testing framework
- **tests/config-parser/TEST_CONFIG_README.md** - Complete testing guide
- **TEST_CONFIG_PARSER_DESIGN.md** - Test framework architecture
- **tests/MAINTENANCE.md** - Schema and property database maintenance
- **QUICK_START_TESTING.md** - Quick start guide
- **README.md** - Project overview and build instructions
## Support
For issues or questions:
1. Check troubleshooting section above
2. Review test output in `output/` directory
3. Check Docker container logs: `docker logs ucentral_client_build_env`
4. File issue in repository issue tracker
## Version
Script version: 1.0.0
Last updated: 2025-12-15
Compatible with: uCentral schema 5.0.0 and later

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,581 @@
{
"strict": false,
"uuid": 1765383961,
"unit":
{
"hostname": "MJH-4150",
"leds-active": true,
"random-password": false,
"usage-threshold": 95
},
"ethernet":
[
{
"select-ports":
[
"Ethernet23"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"lacp-config":
{
"lacp-enable": false,
"lacp-role": "actor",
"lacp-mode": "passive",
"lacp-port-admin-key": 1,
"lacp-port-priority": 32768,
"lacp-system-priority": 32768,
"lacp-timeout": "long"
},
"lldp-interface-config":
{
"lldp-admin-status": "rx-tx",
"lldp-basic-tlv-mgmt-ip-v4": true,
"lldp-basic-tlv-mgmt-ip-v6": true,
"lldp-basic-tlv-port-descr": true,
"lldp-basic-tlv-sys-capab": true,
"lldp-basic-tlv-sys-descr": true,
"lldp-basic-tlv-sys-name": true,
"lldp-dot1-tlv-proto-ident": true,
"lldp-dot1-tlv-proto-vid": true,
"lldp-dot1-tlv-pvid": true,
"lldp-dot1-tlv-vlan-name": true,
"lldp-dot3-tlv-link-agg": true,
"lldp-dot3-tlv-mac-phy": true,
"lldp-dot3-tlv-max-frame": true,
"lldp-dot3-tlv-poe": true,
"lldp-med-location-civic-addr":
{
"lldp-med-location-civic-addr-admin-status": true,
"lldp-med-location-civic-country-code": "CA",
"lldp-med-location-civic-device-type": 1,
"lldp-med-location-civic-ca":
[
{
"lldp-med-location-civic-ca-type": 29,
"lldp-med-location-civic-ca-value": "Mike-WFH"
}
]
},
"lldp-med-notification": true,
"lldp-med-tlv-ext-poe": true,
"lldp-med-tlv-inventory": true,
"lldp-med-tlv-location": true,
"lldp-med-tlv-med-cap": true,
"lldp-med-tlv-network-policy": true,
"lldp-notification": true
},
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true,
"dhcp-snoop-port-client-limit": 16,
"dhcp-snoop-port-circuit-id": "1-5c17834a98a0-24"
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet2"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"lacp-config":
{
"lacp-enable": false,
"lacp-role": "actor",
"lacp-mode": "passive",
"lacp-port-admin-key": 1,
"lacp-port-priority": 32768,
"lacp-system-priority": 32768,
"lacp-timeout": "long"
},
"lldp-interface-config":
{
"lldp-admin-status": "rx-tx",
"lldp-basic-tlv-mgmt-ip-v4": true,
"lldp-basic-tlv-mgmt-ip-v6": true,
"lldp-basic-tlv-port-descr": true,
"lldp-basic-tlv-sys-capab": true,
"lldp-basic-tlv-sys-descr": true,
"lldp-basic-tlv-sys-name": true,
"lldp-dot1-tlv-proto-ident": true,
"lldp-dot1-tlv-proto-vid": true,
"lldp-dot1-tlv-pvid": true,
"lldp-dot1-tlv-vlan-name": true,
"lldp-dot3-tlv-link-agg": true,
"lldp-dot3-tlv-mac-phy": true,
"lldp-dot3-tlv-max-frame": true,
"lldp-dot3-tlv-poe": true,
"lldp-med-location-civic-addr":
{
"lldp-med-location-civic-addr-admin-status": true,
"lldp-med-location-civic-country-code": "CA",
"lldp-med-location-civic-device-type": 1,
"lldp-med-location-civic-ca":
[
{
"lldp-med-location-civic-ca-type": 29,
"lldp-med-location-civic-ca-value": "Mike-WFH"
}
]
},
"lldp-med-notification": true,
"lldp-med-tlv-ext-poe": true,
"lldp-med-tlv-inventory": true,
"lldp-med-tlv-location": true,
"lldp-med-tlv-med-cap": true,
"lldp-med-tlv-network-policy": true,
"lldp-notification": true
},
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true,
"dhcp-snoop-port-client-limit": 16,
"dhcp-snoop-port-circuit-id": "1-5c17834a98a0-3"
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet24",
"Ethernet25",
"Ethernet26",
"Ethernet27"
],
"speed": 10000,
"duplex": "full",
"enabled": true,
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet0"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"lacp-config":
{
"lacp-enable": false,
"lacp-role": "actor",
"lacp-mode": "passive",
"lacp-port-admin-key": 1,
"lacp-port-priority": 32768,
"lacp-system-priority": 32768,
"lacp-timeout": "long"
},
"lldp-interface-config":
{
"lldp-admin-status": "rx-tx",
"lldp-basic-tlv-mgmt-ip-v4": true,
"lldp-basic-tlv-mgmt-ip-v6": true,
"lldp-basic-tlv-port-descr": true,
"lldp-basic-tlv-sys-capab": true,
"lldp-basic-tlv-sys-descr": true,
"lldp-basic-tlv-sys-name": true,
"lldp-dot1-tlv-proto-ident": true,
"lldp-dot1-tlv-proto-vid": true,
"lldp-dot1-tlv-pvid": true,
"lldp-dot1-tlv-vlan-name": true,
"lldp-dot3-tlv-link-agg": true,
"lldp-dot3-tlv-mac-phy": true,
"lldp-dot3-tlv-max-frame": true,
"lldp-dot3-tlv-poe": true,
"lldp-med-location-civic-addr":
{
"lldp-med-location-civic-addr-admin-status": true,
"lldp-med-location-civic-country-code": "CA",
"lldp-med-location-civic-device-type": 1,
"lldp-med-location-civic-ca":
[
{
"lldp-med-location-civic-ca-type": 29,
"lldp-med-location-civic-ca-value": "Mike-WFH"
}
]
},
"lldp-med-notification": true,
"lldp-med-tlv-ext-poe": true,
"lldp-med-tlv-inventory": true,
"lldp-med-tlv-location": true,
"lldp-med-tlv-med-cap": true,
"lldp-med-tlv-network-policy": true,
"lldp-notification": true
},
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true,
"dhcp-snoop-port-client-limit": 16,
"dhcp-snoop-port-circuit-id": "1-5c17834a98a0-1"
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet4"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"lacp-config":
{
"lacp-enable": false,
"lacp-role": "actor",
"lacp-mode": "passive",
"lacp-port-admin-key": 1,
"lacp-port-priority": 32768,
"lacp-system-priority": 32768,
"lacp-timeout": "long"
},
"lldp-interface-config":
{
"lldp-admin-status": "rx-tx",
"lldp-basic-tlv-mgmt-ip-v4": true,
"lldp-basic-tlv-mgmt-ip-v6": true,
"lldp-basic-tlv-port-descr": true,
"lldp-basic-tlv-sys-capab": true,
"lldp-basic-tlv-sys-descr": true,
"lldp-basic-tlv-sys-name": true,
"lldp-dot1-tlv-proto-ident": true,
"lldp-dot1-tlv-proto-vid": true,
"lldp-dot1-tlv-pvid": true,
"lldp-dot1-tlv-vlan-name": true,
"lldp-dot3-tlv-link-agg": true,
"lldp-dot3-tlv-mac-phy": true,
"lldp-dot3-tlv-max-frame": true,
"lldp-dot3-tlv-poe": true,
"lldp-med-location-civic-addr":
{
"lldp-med-location-civic-addr-admin-status": true,
"lldp-med-location-civic-country-code": "CA",
"lldp-med-location-civic-device-type": 1,
"lldp-med-location-civic-ca":
[
{
"lldp-med-location-civic-ca-type": 29,
"lldp-med-location-civic-ca-value": "Mike-WFH"
}
]
},
"lldp-med-notification": true,
"lldp-med-tlv-ext-poe": true,
"lldp-med-tlv-inventory": true,
"lldp-med-tlv-location": true,
"lldp-med-tlv-med-cap": true,
"lldp-med-tlv-network-policy": true,
"lldp-notification": true
},
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true,
"dhcp-snoop-port-client-limit": 16,
"dhcp-snoop-port-circuit-id": "1-5c17834a98a0-5"
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet1",
"Ethernet3"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"lacp-config":
{
"lacp-enable": false,
"lacp-role": "actor",
"lacp-mode": "passive",
"lacp-port-admin-key": 1,
"lacp-port-priority": 32768,
"lacp-system-priority": 32768,
"lacp-timeout": "long"
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet5",
"Ethernet7",
"Ethernet8",
"Ethernet9",
"Ethernet10",
"Ethernet11",
"Ethernet12",
"Ethernet13",
"Ethernet14",
"Ethernet15",
"Ethernet16",
"Ethernet17",
"Ethernet18",
"Ethernet19",
"Ethernet20",
"Ethernet21",
"Ethernet22"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true
},
"edge-port": false
},
{
"select-ports":
[
"Ethernet6"
],
"speed": 1000,
"duplex": "full",
"enabled": true,
"poe":
{
"admin-mode": true
},
"lacp-config":
{
"lacp-enable": false,
"lacp-role": "actor",
"lacp-mode": "passive",
"lacp-port-admin-key": 1,
"lacp-port-priority": 32768,
"lacp-system-priority": 32768,
"lacp-timeout": "long"
},
"lldp-interface-config":
{
"lldp-admin-status": "rx-tx",
"lldp-basic-tlv-mgmt-ip-v4": true,
"lldp-basic-tlv-mgmt-ip-v6": true,
"lldp-basic-tlv-port-descr": true,
"lldp-basic-tlv-sys-capab": true,
"lldp-basic-tlv-sys-descr": true,
"lldp-basic-tlv-sys-name": true,
"lldp-dot1-tlv-proto-ident": true,
"lldp-dot1-tlv-proto-vid": true,
"lldp-dot1-tlv-pvid": true,
"lldp-dot1-tlv-vlan-name": true,
"lldp-dot3-tlv-link-agg": true,
"lldp-dot3-tlv-mac-phy": true,
"lldp-dot3-tlv-max-frame": true,
"lldp-dot3-tlv-poe": true,
"lldp-med-location-civic-addr":
{
"lldp-med-location-civic-addr-admin-status": true,
"lldp-med-location-civic-country-code": "CA",
"lldp-med-location-civic-device-type": 1,
"lldp-med-location-civic-ca":
[
{
"lldp-med-location-civic-ca-type": 29,
"lldp-med-location-civic-ca-value": "Mike-WFH"
}
]
},
"lldp-med-notification": true,
"lldp-med-tlv-ext-poe": true,
"lldp-med-tlv-inventory": true,
"lldp-med-tlv-location": true,
"lldp-med-tlv-med-cap": true,
"lldp-med-tlv-network-policy": true,
"lldp-notification": true
},
"dhcp-snoop-port":
{
"dhcp-snoop-port-trust": true,
"dhcp-snoop-port-client-limit": 16,
"dhcp-snoop-port-circuit-id": "1-5c17834a98a0-7"
},
"edge-port": false
}
],
"switch":
{
"loop-detection":
{
"protocol": "stp",
"instances":
[
{
"enabled": true,
"priority": 32768,
"forward_delay": 15,
"hello_time": 2,
"max_age": 20
}
]
},
"trunk-balance-method": "src-dst-mac",
"jumbo-frames": false,
"dhcp-snooping":
{
"dhcp-snoop-enable": true,
"dhcp-snoop-rate-limit": 1000,
"dhcp-snoop-mac-verify": true,
"dhcp-snoop-inf-opt-82": true,
"dhcp-snoop-inf-opt-encode-subopt": true,
"dhcp-snoop-inf-opt-remoteid": "5c17834a98a0",
"dhcp-snoop-inf-opt-policy": "drop"
},
"lldp-global-config":
{
"lldp-enable": true,
"lldp-holdtime-multiplier": 3,
"lldp-med-fast-start-count": 5,
"lldp-refresh-interval": 60,
"lldp-reinit-delay": 5,
"lldp-tx-delay": 5,
"lldp-notification-interval": 10
},
"mc-lag": false,
"arp-inspect":
{
"ip-arp-inspect": false
}
},
"interfaces":
[
{
"name": "VLAN1",
"role": "upstream",
"services":
[
"lldp",
"ssh"
],
"vlan":
{
"id": 1,
"proto": "802.1q"
},
"ethernet":
[
{
"select-ports":
[
"Ethernet0",
"Ethernet1",
"Ethernet2",
"Ethernet3",
"Ethernet4",
"Ethernet5",
"Ethernet6",
"Ethernet7",
"Ethernet8",
"Ethernet9",
"Ethernet10",
"Ethernet11",
"Ethernet12",
"Ethernet13",
"Ethernet14",
"Ethernet15",
"Ethernet16",
"Ethernet17",
"Ethernet18",
"Ethernet19",
"Ethernet20",
"Ethernet21",
"Ethernet22",
"Ethernet23",
"Ethernet24",
"Ethernet25",
"Ethernet26",
"Ethernet27"
],
"vlan-tag": "un-tagged",
"pvid": true
}
],
"ipv4":
{
"addressing": "dynamic",
"send-hostname": true,
"dhcp-snoop-vlan-enable": true
}
}
],
"services":
{
"lldp":
{
"describe": "MJH-4150",
"location": "Mike-WFH"
},
"ssh":
{
"port": 22,
"password-authentication": true,
"enable": true
},
"log":
{
"host": "192.168.2.38",
"port": 514,
"proto": "udp",
"size": 1000,
"priority": 7
},
"snmp":
{
"enabled": true
}
},
"metrics":
{
"statistics":
{
"interval": 60,
"types":
[
"lldp",
"clients"
]
},
"health":
{
"interval": 60,
"dhcp-local": true,
"dhcp-remote": false,
"dns-local": true,
"dns-remote": true
}
}
}

View File

@@ -5,62 +5,7 @@
"enabled": false,
"speed": 1000,
"select-ports": [
"Ethernet0",
"Ethernet1",
"Ethernet2",
"Ethernet3",
"Ethernet4",
"Ethernet5",
"Ethernet6",
"Ethernet7",
"Ethernet8",
"Ethernet9",
"Ethernet10",
"Ethernet11",
"Ethernet12",
"Ethernet13",
"Ethernet14",
"Ethernet15",
"Ethernet16",
"Ethernet17",
"Ethernet18",
"Ethernet19",
"Ethernet20",
"Ethernet21",
"Ethernet22",
"Ethernet23",
"Ethernet24",
"Ethernet25",
"Ethernet26",
"Ethernet27",
"Ethernet28",
"Ethernet29",
"Ethernet30",
"Ethernet31",
"Ethernet32",
"Ethernet33",
"Ethernet34",
"Ethernet35",
"Ethernet36",
"Ethernet37",
"Ethernet38",
"Ethernet39",
"Ethernet40",
"Ethernet41",
"Ethernet42",
"Ethernet43",
"Ethernet44",
"Ethernet45",
"Ethernet46",
"Ethernet47",
"Ethernet48",
"Ethernet52",
"Ethernet56",
"Ethernet60",
"Ethernet64",
"Ethernet68",
"Ethernet72",
"Ethernet76"
"Ethernet*"
]
}
],

View File

@@ -0,0 +1,530 @@
{
"ethernet": [
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet0"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet1"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet2"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet3"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet4"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet5"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet6"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet7"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet8"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet9"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet10"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet11"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet12"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet13"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet14"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet15"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet16"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet17"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet18"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet19"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet20"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet21"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet22"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet23"
],
"speed": 1000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet24"
],
"speed": 10000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet25"
],
"speed": 10000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet26"
],
"speed": 10000
},
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": true,
"detection": "2pt-dot3af",
"priority": "high"
},
"select-ports": [
"Ethernet27"
],
"speed": 10000
}
],
"interfaces": [
{
"ethernet": [
{
"select-ports": [
"Ethernet0",
"Ethernet5",
"Ethernet6",
"Ethernet7",
"Ethernet8",
"Ethernet9",
"Ethernet10",
"Ethernet11",
"Ethernet12",
"Ethernet13",
"Ethernet14",
"Ethernet15",
"Ethernet16",
"Ethernet17",
"Ethernet18",
"Ethernet19",
"Ethernet20",
"Ethernet21",
"Ethernet22",
"Ethernet23",
"Ethernet24",
"Ethernet25",
"Ethernet26",
"Ethernet27"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "dynamic"
},
"name": "VLAN1",
"vlan": {
"id": 1
}
},
{
"ethernet": [
{ "pvid": true,
"select-ports": [
"Ethernet1",
"Ethernet2",
"Ethernet3",
"Ethernet4"
],
"vlan-tag": "un-tagged"
},
{
"select-ports": [
"Ethernet0"
],
"vlan-tag": "tagged"
}
],
"ipv4": {
"addressing": "static",
"subnet": [
{
"prefix": "10.1.12.157/24"
}
]
},
"name": "VLAN100",
"vlan": {
"id": 100,
"proto": "802.1q"
}
},
{
"ethernet": [
{
"select-ports": [
"Ethernet5",
"Ethernet6",
"Ethernet8"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "static",
"multicast": {
"igmp": {
"fast-leave-enable": true,
"last-member-query-interval": 33,
"max-response-time": 11,
"querier-enable": true,
"query-interval": 14,
"snooping-enable": true,
"static-mcast-groups": [
{
"address": "229.229.229.1",
"egress-ports": [
"Ethernet5",
"Ethernet6",
"Ethernet8"
]
}
],
"version": 3
}
}
},
"role": "upstream",
"services": [
"ssh",
"lldp"
],
"vlan": {
"id": 500,
"proto": "802.1q"
}
}
],
"metrics": {
"dhcp-snooping": {
"filters": [
"ack",
"discover",
"offer",
"request",
"solicit",
"reply",
"renew"
]
},
"health": {
"interval": 60
},
"statistics": {
"interval": 300,
"types": ["lldp",
"clients"
]
}
},
"services": {
"http": {
"enable": true
},
"ssh": {
"enable": true
},
"lldp": {
"describe": "uCentral",
"location": "universe"
}
},
"unit": {
"leds-active": true,
"usage-threshold": 90
},
"uuid": 1719887774
}

View File

@@ -0,0 +1,13 @@
{
"ethernet": [
{
"duplex": "full",
"enabled": true,
"speed": 1000,
"select-ports": ["Ethernet1"]
}
],
"interfaces": "not-an-array",
"services": {},
"uuid": 1
}

View File

@@ -0,0 +1,13 @@
{
"ethernet": [
{
"duplex": "full",
"enabled": true,
"speed": 1000,
"select-ports": [
"Ethernet1"
]
}
],
"uuid": 1
}

View File

@@ -0,0 +1,13 @@
{
"ethernet": [
{
"duplex": "full",
"enabled": true,
"speed": 1000,
"select-ports": ["Ethernet1"]
}
],
"interfaces": [],
"services": ["should-be-object"],
"uuid": 1
}

View File

@@ -0,0 +1,11 @@
{
"ethernet": {
"duplex": "full",
"enabled": true,
"speed": 1000,
"select-ports": ["Ethernet1"]
},
"interfaces": [],
"services": {},
"uuid": 1
}

File diff suppressed because it is too large Load Diff

165
config-samples/cfg_stp_rstp.json Executable file
View File

@@ -0,0 +1,165 @@
{
"ethernet": [
{
"duplex": "full",
"enabled": true,
"poe": {
"admin-mode": false,
"power-limit": 12345
},
"select-ports": [
"Ethernet1",
"Ethernet2"
],
"speed": 1000
}
],
"interfaces": [
{
"ethernet": [
{
"pvid": true,
"select-ports": [
"Ethernet1"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "static",
"dhcp": {
"circuit-id-format": "{VLAN-ID}",
"relay-server": "192.168.5.1"
},
"subnet": [
{
"prefix": "192.168.2.254/24"
}
]
},
"name": "vlan_2",
"vlan": {
"id": 2
}
},
{
"ethernet": [
{
"pvid": true,
"select-ports": [
"Ethernet2"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "static",
"dhcp": {
"circuit-id-format": "{VLAN-ID}",
"relay-server": "192.168.5.1"
},
"subnet": [
{
"prefix": "192.168.3.254/24"
}
]
},
"name": "vlan_3",
"vlan": {
"id": 3
}
},
{
"ethernet": [
{
"pvid": true,
"select-ports": [
"Ethernet4"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "static",
"subnet": [
{
"prefix": "192.168.5.254/24"
}
]
},
"name": "vlan_5",
"vlan": {
"id": 5
}
},
{
"ethernet": [
{
"select-ports": [
"Ethernet8",
"Ethernet9"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "dynamic"
},
"name": "vlan_1234",
"vlan": {
"id": 1234
}
}
],
"metrics": {
"dhcp-snooping": {
"filters": [
"ack",
"discover",
"offer",
"request",
"solicit",
"reply",
"renew"
]
},
"health": {
"interval": 600
},
"statistics": {
"interval": 1200,
"types": []
}
},
"services": {
"http": {
"enable": true
},
"ssh": {
"enable": true
},
"telnet": {
"enable": true
}
},
"switch": {
"loop-detection": {
"instances": [
{
"enabled": true,
"forward_delay": 15,
"hello_time": 3,
"id": 20,
"max_age": 20,
"priority": 32768
}
],
"protocol": "rstp"
}
},
"unit": {
"leds-active": true,
"usage-threshold": 95
},
"uuid": 1713842091
}

View File

@@ -0,0 +1,96 @@
{
"ethernet": [
{
"duplex": "full",
"enabled": true,
"select-ports": [
"Ethernet*"
],
"speed": 1000
}
],
"interfaces": [
{
"ethernet": [
{
"select-ports": [
"Ethernet*"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"addressing": "dynamic"
},
"name": "VLAN1",
"vlan": {
"id": 1
}
},
{
"ethernet": [
{
"select-ports": [
"Ethernet1"
],
"vlan-tag": "un-tagged"
}
],
"ipv4": {
"voice-vlan-intf-config": {
"voice-vlan-intf-detect-voice": "lldp",
"voice-vlan-intf-mode": "auto",
"voice-vlan-intf-priority": 3,
"voice-vlan-intf-security": true
}
}
}
],
"metrics": {
"health": {
"interval": 300
},
"statistics": {
"interval": 300,
"types": []
}
},
"services": {
"http": {
"enable": true
},
"https": {
"enable": true
},
"ssh": {
"enable": false
},
"telnet": {
"enable": false
}
},
"switch": {
"voice-vlan-config": {
"voice-vlan-ageing-time": 1440,
"voice-vlan-enable": true,
"voice-vlan-id": 100,
"voice-vlan-oui-config": [
{
"voice-vlan-oui-description": "Cisco VoIP Phone",
"voice-vlan-oui-mac": "00:1B:44:11:3A:B7",
"voice-vlan-oui-mask": "FF:FF:FF:00:00:00"
},
{
"voice-vlan-oui-description": "Polycom VoIP Phone",
"voice-vlan-oui-mac": "00:0E:8F:12:34:56",
"voice-vlan-oui-mask": "FF:FF:FF:00:00:00"
}
]
}
},
"unit": {
"leds-active": true,
"usage-threshold": 90
},
"uuid": 1730796040
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
FROM arm64v8/debian:buster
RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
build-essential \
fakeroot \
dpkg-dev \
dh-exec \
debhelper

355
examples/pki-2.0/README.md Normal file
View File

@@ -0,0 +1,355 @@
# PKI 2.0 Certificate Examples and Tools
This directory contains examples and tools for working with PKI 2.0 certificates in the uCentral client.
## Overview
PKI 2.0 uses a two-tier certificate system:
- **Birth Certificates**: Factory-provisioned, long-lived certificates used for EST enrollment
- **Operational Certificates**: Runtime-generated, shorter-lived certificates obtained via EST protocol
## Certificate Workflow
```
Factory/Manufacturing
Birth Certificates Generated (openlan-pki-tools)
Certificates Provisioned to Device (partition_script.sh)
Device First Boot
EST Enrollment (automatic via ucentral-client)
Operational Certificates Obtained
Device Runtime (uses operational certs)
Certificate Renewal (via reenroll RPC)
```
## Birth Certificate Generation
Birth certificates are generated using the `openlan-pki-tools` repository.
### Prerequisites
```bash
# Clone openlan-pki-tools
git clone https://github.com/Telecominfraproject/openlan-pki-tools.git
cd openlan-pki-tools
```
### Generating Birth Certificates
The openlan-pki-tools repository provides scripts for generating birth certificates:
**For QA/Demo Environment:**
```bash
cd openlan-pki-tools
./scripts/generate-birth-cert.sh \
--mac "AA:BB:CC:DD:EE:FF" \
--serial "SN123456789" \
--ca demo \
--output /tmp/birth-certs/
```
**For Production Environment:**
```bash
./scripts/generate-birth-cert.sh \
--mac "AA:BB:CC:DD:EE:FF" \
--serial "SN123456789" \
--ca production \
--output /tmp/birth-certs/
```
### Birth Certificate Files
After generation, you'll have:
- `cert.pem` - Birth certificate (contains device identity)
- `key.pem` - Private key (keep secure!)
- `cas.pem` - CA certificate bundle
- `dev-id` - Device identifier file
## Installing Birth Certificates on Device
### Using partition_script.sh
```bash
# On your development machine, package the certificates
cd /tmp/birth-certs
tar -czf device-certs.tar.gz cert.pem key.pem cas.pem dev-id
# Copy to device
scp device-certs.tar.gz admin@<device-ip>:/tmp/
scp ../../partition_script.sh admin@<device-ip>:/tmp/
# On the device
ssh admin@<device-ip>
sudo su
cd /tmp
tar -xzf device-certs.tar.gz
bash ./partition_script.sh ./
# Reboot to mount the partition
reboot
```
## EST Enrollment (Automatic)
After installing birth certificates and rebooting, the uCentral client automatically:
1. Detects birth certificates in `/etc/ucentral/`
2. Determines EST server based on certificate issuer:
- "OpenLAN Demo Birth CA" → `qaest.certificates.open-lan.org:8001`
- "OpenLAN Birth Issuing CA" → `est.certificates.open-lan.org`
3. Performs EST simple enrollment using birth certificate
4. Saves operational certificate to `/etc/ucentral/operational.pem`
5. Saves operational CA to `/etc/ucentral/operational.ca`
6. Uses operational certificates for gateway connection
## Manual EST Enrollment Testing
For testing or manual certificate operations, you can use curl directly:
### Prerequisites
```bash
# Install curl and openssl
apt-get update
apt-get install -y curl openssl
```
### Test EST Enrollment
```bash
#!/bin/bash
# test-est-enrollment.sh
BIRTH_CERT="/etc/ucentral/cert.pem"
BIRTH_KEY="/etc/ucentral/key.pem"
CA_BUNDLE="/etc/ucentral/cas.pem"
EST_SERVER="qaest.certificates.open-lan.org:8001"
# Generate CSR from birth certificate
openssl req -new -key $BIRTH_KEY -out /tmp/device.csr -subj "/CN=$(hostname)"
# Base64 encode CSR (no headers)
CSR_B64=$(openssl req -in /tmp/device.csr -outform DER | base64 | tr -d '\n')
# Perform EST simple enrollment
curl -v --cacert $CA_BUNDLE \
--cert $BIRTH_CERT \
--key $BIRTH_KEY \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data "$CSR_B64" \
"https://${EST_SERVER}/.well-known/est/simpleenroll" \
-o /tmp/operational.p7
# Convert PKCS#7 to PEM
openssl pkcs7 -inform DER -in /tmp/operational.p7 -print_certs -out /tmp/operational.pem
echo "Operational certificate saved to /tmp/operational.pem"
```
### Test EST Re-enrollment
```bash
#!/bin/bash
# test-est-reenrollment.sh
OPERATIONAL_CERT="/etc/ucentral/operational.pem"
KEY="/etc/ucentral/key.pem"
CA_BUNDLE="/etc/ucentral/operational.ca"
EST_SERVER="qaest.certificates.open-lan.org:8001"
# Generate CSR from operational certificate
openssl req -new -key $KEY -out /tmp/device-renew.csr -subj "/CN=$(hostname)"
# Base64 encode CSR
CSR_B64=$(openssl req -in /tmp/device-renew.csr -outform DER | base64 | tr -d '\n')
# Perform EST simple re-enrollment
curl -v --cacert $CA_BUNDLE \
--cert $OPERATIONAL_CERT \
--key $KEY \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data "$CSR_B64" \
"https://${EST_SERVER}/.well-known/est/simplereenroll" \
-o /tmp/operational-renewed.p7
# Convert PKCS#7 to PEM
openssl pkcs7 -inform DER -in /tmp/operational-renewed.p7 -print_certs -out /tmp/operational-renewed.pem
echo "Renewed certificate saved to /tmp/operational-renewed.pem"
```
### Get CA Certificates
```bash
#!/bin/bash
# test-get-cacerts.sh
OPERATIONAL_CERT="/etc/ucentral/operational.pem"
KEY="/etc/ucentral/key.pem"
CA_BUNDLE="/etc/ucentral/operational.ca"
EST_SERVER="qaest.certificates.open-lan.org:8001"
# Fetch CA certificates from EST server
curl -v --cacert $CA_BUNDLE \
--cert $OPERATIONAL_CERT \
--key $KEY \
"https://${EST_SERVER}/.well-known/est/cacerts" \
-o /tmp/cacerts.p7
# Convert PKCS#7 to PEM
openssl pkcs7 -inform DER -in /tmp/cacerts.p7 -print_certs -out /tmp/cacerts.pem
echo "CA certificates saved to /tmp/cacerts.pem"
```
## Certificate Renewal via RPC
To renew operational certificates from the gateway, send a reenroll RPC command:
```json
{
"jsonrpc": "2.0",
"id": 123,
"method": "reenroll",
"params": {
"serial": "device_serial_number"
}
}
```
The device will:
1. Contact EST server with current operational certificate
2. Obtain renewed operational certificate
3. Save to `/etc/ucentral/operational.pem`
4. Restart after 10 seconds to use new certificate
## Certificate Inspection
### View Certificate Details
```bash
# View birth certificate
openssl x509 -in /etc/ucentral/cert.pem -text -noout
# View operational certificate
openssl x509 -in /etc/ucentral/operational.pem -text -noout
# Check certificate expiration
openssl x509 -in /etc/ucentral/operational.pem -noout -enddate
```
### Verify Certificate Chain
```bash
# Verify birth certificate against CA
openssl verify -CAfile /etc/ucentral/cas.pem /etc/ucentral/cert.pem
# Verify operational certificate against CA
openssl verify -CAfile /etc/ucentral/operational.ca /etc/ucentral/operational.pem
```
### Extract Certificate Information
```bash
# Get Common Name (CN)
openssl x509 -in /etc/ucentral/operational.pem -noout -subject | sed 's/.*CN = //'
# Get Issuer
openssl x509 -in /etc/ucentral/cert.pem -noout -issuer
# Get Serial Number
openssl x509 -in /etc/ucentral/operational.pem -noout -serial
```
## Troubleshooting
### EST Enrollment Fails
**Check EST server connectivity:**
```bash
curl -v https://qaest.certificates.open-lan.org:8001/.well-known/est/cacerts
```
**Verify birth certificates are valid:**
```bash
openssl x509 -in /etc/ucentral/cert.pem -text -noout
openssl verify -CAfile /etc/ucentral/cas.pem /etc/ucentral/cert.pem
```
**Check certificate issuer:**
```bash
openssl x509 -in /etc/ucentral/cert.pem -noout -issuer
# Should show: "OpenLAN Demo Birth CA" or "OpenLAN Birth Issuing CA"
```
### Operational Certificate Not Created
**Check uCentral client logs:**
```bash
journalctl -u ucentral-client -f
```
**Look for EST enrollment errors:**
```bash
grep -i "est\|enroll\|pki" /var/log/ucentral-client.log
```
**Manually test EST enrollment:**
```bash
cd /tmp
bash /path/to/examples/pki-2.0/test-est-enrollment.sh
```
### Certificate Expiration
**Check expiration dates:**
```bash
echo "Birth certificate:"
openssl x509 -in /etc/ucentral/cert.pem -noout -enddate
echo "Operational certificate:"
openssl x509 -in /etc/ucentral/operational.pem -noout -enddate
```
**Setup expiration monitoring:**
```bash
# Check if operational cert expires in less than 30 days
CERT_FILE="/etc/ucentral/operational.pem"
EXPIRE_DATE=$(openssl x509 -in $CERT_FILE -noout -enddate | cut -d= -f2)
EXPIRE_EPOCH=$(date -d "$EXPIRE_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRE_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt 30 ]; then
echo "WARNING: Certificate expires in $DAYS_LEFT days"
echo "Consider triggering reenroll RPC command"
fi
```
## Production Checklist
Before deploying to production:
- [ ] Birth certificates generated with production CA
- [ ] Certificates securely stored during manufacturing
- [ ] Device partition properly configured
- [ ] EST server URL matches certificate issuer
- [ ] Operational certificate automatically obtained on first boot
- [ ] Gateway can reach device for reenroll RPC
- [ ] Certificate expiration monitoring in place
- [ ] Backup/recovery procedure documented
## Additional Resources
- **Main README**: `../../README.md` - Certificate architecture overview
- **openlan-pki-tools**: https://github.com/Telecominfraproject/openlan-pki-tools
- **EST RFC 7030**: https://tools.ietf.org/html/rfc7030
- **est-client.c**: `../../src/ucentral-client/est-client.c` - EST client implementation
- **partition_script.sh**: `../../partition_script.sh` - Certificate partition tool

View File

@@ -0,0 +1,171 @@
#!/bin/bash
#
# Test EST Enrollment Script
#
# This script demonstrates manual EST enrollment using birth certificates
# to obtain an operational certificate from the EST server.
#
# Usage: ./test-est-enrollment.sh [est-server]
#
set -e
# Configuration
BIRTH_CERT="${BIRTH_CERT:-/etc/ucentral/cert.pem}"
BIRTH_KEY="${BIRTH_KEY:-/etc/ucentral/key.pem}"
CA_BUNDLE="${CA_BUNDLE:-/etc/ucentral/cas.pem}"
EST_SERVER="${1:-qaest.certificates.open-lan.org:8001}"
OUTPUT_DIR="${OUTPUT_DIR:-/tmp}"
echo "========================================="
echo "EST Enrollment Test"
echo "========================================="
echo "Birth Certificate: $BIRTH_CERT"
echo "Birth Key: $BIRTH_KEY"
echo "CA Bundle: $CA_BUNDLE"
echo "EST Server: $EST_SERVER"
echo "Output Directory: $OUTPUT_DIR"
echo ""
# Verify birth certificates exist
if [ ! -f "$BIRTH_CERT" ]; then
echo "ERROR: Birth certificate not found: $BIRTH_CERT"
exit 1
fi
if [ ! -f "$BIRTH_KEY" ]; then
echo "ERROR: Birth key not found: $BIRTH_KEY"
exit 1
fi
if [ ! -f "$CA_BUNDLE" ]; then
echo "ERROR: CA bundle not found: $CA_BUNDLE"
exit 1
fi
# Verify birth certificate is valid
echo "Verifying birth certificate..."
if ! openssl verify -CAfile "$CA_BUNDLE" "$BIRTH_CERT" > /dev/null 2>&1; then
echo "WARNING: Birth certificate verification failed"
fi
# Extract Common Name from birth certificate
CN=$(openssl x509 -in "$BIRTH_CERT" -noout -subject | sed 's/.*CN = //')
echo "Device Common Name: $CN"
echo ""
# Generate CSR from birth certificate
echo "Generating Certificate Signing Request (CSR)..."
CSR_FILE="$OUTPUT_DIR/device.csr"
openssl req -new -key "$BIRTH_KEY" -out "$CSR_FILE" -subj "/CN=$CN"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to generate CSR"
exit 1
fi
echo "CSR generated: $CSR_FILE"
echo ""
# Base64 encode CSR (no headers, DER format)
echo "Encoding CSR to base64..."
CSR_B64=$(openssl req -in "$CSR_FILE" -outform DER | base64 | tr -d '\n')
if [ -z "$CSR_B64" ]; then
echo "ERROR: Failed to encode CSR"
exit 1
fi
echo "CSR encoded successfully"
echo ""
# Perform EST simple enrollment
echo "Performing EST simple enrollment..."
echo "Contacting EST server: https://$EST_SERVER/.well-known/est/simpleenroll"
echo ""
PKCS7_FILE="$OUTPUT_DIR/operational.p7"
curl -v --cacert "$CA_BUNDLE" \
--cert "$BIRTH_CERT" \
--key "$BIRTH_KEY" \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data "$CSR_B64" \
"https://${EST_SERVER}/.well-known/est/simpleenroll" \
-o "$PKCS7_FILE"
CURL_EXIT=$?
echo ""
if [ $CURL_EXIT -ne 0 ]; then
echo "ERROR: EST enrollment failed with curl exit code: $CURL_EXIT"
echo ""
echo "Troubleshooting:"
echo "1. Verify EST server is reachable:"
echo " curl -I https://$EST_SERVER/.well-known/est/cacerts"
echo "2. Check birth certificate is valid and not expired"
echo "3. Verify CA bundle contains the correct root CA"
exit 1
fi
if [ ! -f "$PKCS7_FILE" ] || [ ! -s "$PKCS7_FILE" ]; then
echo "ERROR: No response received from EST server"
exit 1
fi
echo "EST enrollment response received"
echo ""
# Convert PKCS#7 to PEM
echo "Converting PKCS#7 response to PEM format..."
OPERATIONAL_CERT="$OUTPUT_DIR/operational.pem"
openssl pkcs7 -inform DER -in "$PKCS7_FILE" -print_certs -out "$OPERATIONAL_CERT"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to convert PKCS#7 to PEM"
exit 1
fi
# Verify operational certificate was created
if [ ! -f "$OPERATIONAL_CERT" ] || [ ! -s "$OPERATIONAL_CERT" ]; then
echo "ERROR: Failed to create operational certificate"
exit 1
fi
echo "Operational certificate created: $OPERATIONAL_CERT"
echo ""
# Display certificate information
echo "========================================="
echo "Operational Certificate Information"
echo "========================================="
openssl x509 -in "$OPERATIONAL_CERT" -noout -text | head -30
echo ""
# Display expiration date
echo "Certificate Expiration:"
openssl x509 -in "$OPERATIONAL_CERT" -noout -enddate
echo ""
# Verify operational certificate
echo "Verifying operational certificate..."
if openssl verify -CAfile "$CA_BUNDLE" "$OPERATIONAL_CERT" > /dev/null 2>&1; then
echo "✓ Certificate verification successful"
else
echo "⚠ Certificate verification failed (may need operational CA bundle)"
fi
echo ""
echo "========================================="
echo "EST Enrollment Test Complete"
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Copy operational certificate to /etc/ucentral/operational.pem"
echo "2. Copy operational CA bundle to /etc/ucentral/operational.ca"
echo "3. Restart ucentral-client service"
echo ""
echo "Commands:"
echo " sudo cp $OPERATIONAL_CERT /etc/ucentral/operational.pem"
echo " sudo cp $CA_BUNDLE /etc/ucentral/operational.ca"
echo " sudo systemctl restart ucentral-client"

View File

@@ -0,0 +1,195 @@
#!/bin/bash
#
# Test EST Re-enrollment Script
#
# This script demonstrates manual EST re-enrollment using an existing
# operational certificate to obtain a renewed operational certificate.
#
# Usage: ./test-est-reenrollment.sh [est-server]
#
set -e
# Configuration
OPERATIONAL_CERT="${OPERATIONAL_CERT:-/etc/ucentral/operational.pem}"
KEY="${KEY:-/etc/ucentral/key.pem}"
CA_BUNDLE="${CA_BUNDLE:-/etc/ucentral/operational.ca}"
EST_SERVER="${1:-qaest.certificates.open-lan.org:8001}"
OUTPUT_DIR="${OUTPUT_DIR:-/tmp}"
echo "========================================="
echo "EST Re-enrollment Test"
echo "========================================="
echo "Operational Certificate: $OPERATIONAL_CERT"
echo "Private Key: $KEY"
echo "CA Bundle: $CA_BUNDLE"
echo "EST Server: $EST_SERVER"
echo "Output Directory: $OUTPUT_DIR"
echo ""
# Verify operational certificate exists
if [ ! -f "$OPERATIONAL_CERT" ]; then
echo "ERROR: Operational certificate not found: $OPERATIONAL_CERT"
echo ""
echo "Run test-est-enrollment.sh first to obtain an operational certificate"
exit 1
fi
if [ ! -f "$KEY" ]; then
echo "ERROR: Private key not found: $KEY"
exit 1
fi
if [ ! -f "$CA_BUNDLE" ]; then
echo "ERROR: CA bundle not found: $CA_BUNDLE"
echo "Attempting to use cas.pem as fallback..."
CA_BUNDLE="/etc/ucentral/cas.pem"
if [ ! -f "$CA_BUNDLE" ]; then
echo "ERROR: No CA bundle found"
exit 1
fi
fi
# Display current certificate information
echo "Current Operational Certificate Information:"
echo "Subject: $(openssl x509 -in "$OPERATIONAL_CERT" -noout -subject)"
echo "Issuer: $(openssl x509 -in "$OPERATIONAL_CERT" -noout -issuer)"
echo "Valid until: $(openssl x509 -in "$OPERATIONAL_CERT" -noout -enddate)"
echo ""
# Verify operational certificate is valid
echo "Verifying current operational certificate..."
if ! openssl verify -CAfile "$CA_BUNDLE" "$OPERATIONAL_CERT" > /dev/null 2>&1; then
echo "WARNING: Operational certificate verification failed"
fi
# Extract Common Name from operational certificate
CN=$(openssl x509 -in "$OPERATIONAL_CERT" -noout -subject | sed 's/.*CN = //')
echo "Device Common Name: $CN"
echo ""
# Generate CSR for renewal
echo "Generating Certificate Signing Request (CSR) for renewal..."
CSR_FILE="$OUTPUT_DIR/device-renew.csr"
openssl req -new -key "$KEY" -out "$CSR_FILE" -subj "/CN=$CN"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to generate CSR"
exit 1
fi
echo "CSR generated: $CSR_FILE"
echo ""
# Base64 encode CSR (no headers, DER format)
echo "Encoding CSR to base64..."
CSR_B64=$(openssl req -in "$CSR_FILE" -outform DER | base64 | tr -d '\n')
if [ -z "$CSR_B64" ]; then
echo "ERROR: Failed to encode CSR"
exit 1
fi
echo "CSR encoded successfully"
echo ""
# Perform EST simple re-enrollment
echo "Performing EST simple re-enrollment..."
echo "Contacting EST server: https://$EST_SERVER/.well-known/est/simplereenroll"
echo ""
PKCS7_FILE="$OUTPUT_DIR/operational-renewed.p7"
curl -v --cacert "$CA_BUNDLE" \
--cert "$OPERATIONAL_CERT" \
--key "$KEY" \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data "$CSR_B64" \
"https://${EST_SERVER}/.well-known/est/simplereenroll" \
-o "$PKCS7_FILE"
CURL_EXIT=$?
echo ""
if [ $CURL_EXIT -ne 0 ]; then
echo "ERROR: EST re-enrollment failed with curl exit code: $CURL_EXIT"
echo ""
echo "Troubleshooting:"
echo "1. Verify EST server is reachable:"
echo " curl -I https://$EST_SERVER/.well-known/est/cacerts"
echo "2. Check operational certificate is valid and not expired"
echo "3. Verify CA bundle contains the correct root CA"
echo "4. Check if operational certificate is trusted by EST server"
exit 1
fi
if [ ! -f "$PKCS7_FILE" ] || [ ! -s "$PKCS7_FILE" ]; then
echo "ERROR: No response received from EST server"
exit 1
fi
echo "EST re-enrollment response received"
echo ""
# Convert PKCS#7 to PEM
echo "Converting PKCS#7 response to PEM format..."
RENEWED_CERT="$OUTPUT_DIR/operational-renewed.pem"
openssl pkcs7 -inform DER -in "$PKCS7_FILE" -print_certs -out "$RENEWED_CERT"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to convert PKCS#7 to PEM"
exit 1
fi
# Verify renewed certificate was created
if [ ! -f "$RENEWED_CERT" ] || [ ! -s "$RENEWED_CERT" ]; then
echo "ERROR: Failed to create renewed certificate"
exit 1
fi
echo "Renewed operational certificate created: $RENEWED_CERT"
echo ""
# Display renewed certificate information
echo "========================================="
echo "Renewed Certificate Information"
echo "========================================="
openssl x509 -in "$RENEWED_CERT" -noout -text | head -30
echo ""
# Display expiration date
echo "New Certificate Expiration:"
openssl x509 -in "$RENEWED_CERT" -noout -enddate
echo ""
# Compare old and new expiration dates
OLD_EXPIRE=$(openssl x509 -in "$OPERATIONAL_CERT" -noout -enddate | cut -d= -f2)
NEW_EXPIRE=$(openssl x509 -in "$RENEWED_CERT" -noout -enddate | cut -d= -f2)
echo "Certificate Renewal Comparison:"
echo " Old expiration: $OLD_EXPIRE"
echo " New expiration: $NEW_EXPIRE"
echo ""
# Verify renewed certificate
echo "Verifying renewed certificate..."
if openssl verify -CAfile "$CA_BUNDLE" "$RENEWED_CERT" > /dev/null 2>&1; then
echo "✓ Certificate verification successful"
else
echo "⚠ Certificate verification failed"
fi
echo ""
echo "========================================="
echo "EST Re-enrollment Test Complete"
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Backup current operational certificate"
echo "2. Replace with renewed certificate"
echo "3. Restart ucentral-client service"
echo ""
echo "Commands:"
echo " sudo cp $OPERATIONAL_CERT ${OPERATIONAL_CERT}.backup"
echo " sudo cp $RENEWED_CERT $OPERATIONAL_CERT"
echo " sudo systemctl restart ucentral-client"

View File

@@ -0,0 +1,181 @@
#!/bin/bash
#
# Test EST Get CA Certificates Script
#
# This script demonstrates retrieving CA certificates from an EST server.
# The CA certificates are needed to verify operational certificates.
#
# Usage: ./test-get-cacerts.sh [est-server]
#
set -e
# Configuration
OPERATIONAL_CERT="${OPERATIONAL_CERT:-/etc/ucentral/operational.pem}"
KEY="${KEY:-/etc/ucentral/key.pem}"
CA_BUNDLE="${CA_BUNDLE:-/etc/ucentral/operational.ca}"
EST_SERVER="${1:-qaest.certificates.open-lan.org:8001}"
OUTPUT_DIR="${OUTPUT_DIR:-/tmp}"
echo "========================================="
echo "EST Get CA Certificates Test"
echo "========================================="
echo "Operational Certificate: $OPERATIONAL_CERT"
echo "Private Key: $KEY"
echo "CA Bundle: $CA_BUNDLE"
echo "EST Server: $EST_SERVER"
echo "Output Directory: $OUTPUT_DIR"
echo ""
# Verify operational certificate exists
if [ ! -f "$OPERATIONAL_CERT" ]; then
echo "WARNING: Operational certificate not found: $OPERATIONAL_CERT"
echo "Attempting to use birth certificate..."
OPERATIONAL_CERT="/etc/ucentral/cert.pem"
if [ ! -f "$OPERATIONAL_CERT" ]; then
echo "ERROR: No certificate found for authentication"
exit 1
fi
fi
if [ ! -f "$KEY" ]; then
echo "ERROR: Private key not found: $KEY"
exit 1
fi
# CA bundle may not exist yet, use birth CA as fallback
if [ ! -f "$CA_BUNDLE" ]; then
echo "WARNING: CA bundle not found: $CA_BUNDLE"
echo "Attempting to use birth CA bundle..."
CA_BUNDLE="/etc/ucentral/cas.pem"
if [ ! -f "$CA_BUNDLE" ]; then
echo "ERROR: No CA bundle found"
exit 1
fi
fi
echo "Using certificate: $OPERATIONAL_CERT"
echo "Using CA bundle: $CA_BUNDLE"
echo ""
# Fetch CA certificates from EST server
echo "Fetching CA certificates from EST server..."
echo "Contacting: https://$EST_SERVER/.well-known/est/cacerts"
echo ""
PKCS7_FILE="$OUTPUT_DIR/cacerts.p7"
curl -v --cacert "$CA_BUNDLE" \
--cert "$OPERATIONAL_CERT" \
--key "$KEY" \
"https://${EST_SERVER}/.well-known/est/cacerts" \
-o "$PKCS7_FILE"
CURL_EXIT=$?
echo ""
if [ $CURL_EXIT -ne 0 ]; then
echo "ERROR: Failed to fetch CA certificates, curl exit code: $CURL_EXIT"
echo ""
echo "Troubleshooting:"
echo "1. Verify EST server is reachable:"
echo " curl -I https://$EST_SERVER/.well-known/est/cacerts"
echo "2. Check certificate is valid for authentication"
echo "3. Verify CA bundle contains the correct root CA"
exit 1
fi
if [ ! -f "$PKCS7_FILE" ] || [ ! -s "$PKCS7_FILE" ]; then
echo "ERROR: No response received from EST server"
exit 1
fi
echo "CA certificates response received"
echo ""
# Convert PKCS#7 to PEM
echo "Converting PKCS#7 response to PEM format..."
CACERTS_PEM="$OUTPUT_DIR/cacerts.pem"
openssl pkcs7 -inform DER -in "$PKCS7_FILE" -print_certs -out "$CACERTS_PEM"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to convert PKCS#7 to PEM"
exit 1
fi
# Verify CA certificates were extracted
if [ ! -f "$CACERTS_PEM" ] || [ ! -s "$CACERTS_PEM" ]; then
echo "ERROR: Failed to extract CA certificates"
exit 1
fi
echo "CA certificates extracted: $CACERTS_PEM"
echo ""
# Count number of certificates
CERT_COUNT=$(grep -c "BEGIN CERTIFICATE" "$CACERTS_PEM" || true)
echo "Number of CA certificates: $CERT_COUNT"
echo ""
# Display information about each certificate
echo "========================================="
echo "CA Certificates Information"
echo "========================================="
echo ""
# Split certificates and display info for each
csplit -s -f "$OUTPUT_DIR/ca-" "$CACERTS_PEM" '/-----BEGIN CERTIFICATE-----/' '{*}'
for cert_file in "$OUTPUT_DIR"/ca-*; do
if [ -f "$cert_file" ] && [ -s "$cert_file" ]; then
if grep -q "BEGIN CERTIFICATE" "$cert_file" 2>/dev/null; then
echo "Certificate:"
echo " Subject: $(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null || echo 'N/A')"
echo " Issuer: $(openssl x509 -in "$cert_file" -noout -issuer 2>/dev/null || echo 'N/A')"
echo " Valid until: $(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null || echo 'N/A')"
echo ""
fi
rm -f "$cert_file"
fi
done
# Test verification with the new CA bundle
echo "========================================="
echo "Certificate Verification Test"
echo "========================================="
echo ""
if [ -f "/etc/ucentral/operational.pem" ]; then
echo "Testing operational certificate verification with retrieved CA bundle..."
if openssl verify -CAfile "$CACERTS_PEM" "/etc/ucentral/operational.pem" > /dev/null 2>&1; then
echo "✓ Operational certificate verification successful"
else
echo "⚠ Operational certificate verification failed"
echo " This may be normal if the operational cert was issued by a different CA"
fi
echo ""
fi
if [ -f "/etc/ucentral/cert.pem" ]; then
echo "Testing birth certificate verification with retrieved CA bundle..."
if openssl verify -CAfile "$CACERTS_PEM" "/etc/ucentral/cert.pem" > /dev/null 2>&1; then
echo "✓ Birth certificate verification successful"
else
echo "⚠ Birth certificate verification failed"
echo " This may be normal if the birth cert was issued by a different CA"
fi
echo ""
fi
echo "========================================="
echo "EST Get CA Certificates Test Complete"
echo "========================================="
echo ""
echo "CA certificates saved to: $CACERTS_PEM"
echo ""
echo "Next steps:"
echo "1. Review the CA certificates"
echo "2. Update operational CA bundle if needed"
echo ""
echo "Commands:"
echo " cat $CACERTS_PEM"
echo " sudo cp $CACERTS_PEM /etc/ucentral/operational.ca"

View File

@@ -1,5 +1,9 @@
#!/bin/sh
# PKI 2.0: This script provisions BIRTH certificates to the device partition.
# Birth certificates (cas.pem, cert.pem, key.pem) are used for initial EST enrollment.
# Operational certificates (operational.pem, operational.ca) are generated at runtime
# via EST protocol and stored in /etc/ucentral/ by the ucentral-client daemon.
REQUIRED_CERT_FILES="cas.pem cert.pem key.pem dev-id"
function partition_replace_certs()

384
run-config-tests.sh Executable file
View File

@@ -0,0 +1,384 @@
#!/bin/bash
#
# run-config-tests.sh - Run uCentral configuration tests in Docker
#
# Usage: ./run-config-tests.sh [OPTIONS] [config-file]
#
# Options:
# -m, --mode MODE Test mode: stub or platform (default: stub)
# stub = Fast testing with stubs (proto.c only)
# platform = Full integration testing with platform code
# -p, --platform NAME Platform name for platform mode (default: brcm-sonic)
# Examples: brcm-sonic, ec, example
# -f, --format FORMAT Output format: html, json, human (default: human)
# -h, --help Show this help message
#
# Arguments:
# config-file Optional specific config file to test (default: all configs)
#
# Examples:
# ./run-config-tests.sh # Stub mode, all configs, human output
# ./run-config-tests.sh --mode platform # Platform mode (brcm-sonic), all configs
# ./run-config-tests.sh -m platform -p ec --format html # Platform mode (ec), HTML report
# ./run-config-tests.sh --format json cfg0.json # Stub mode, single config, JSON output
# ./run-config-tests.sh -m platform -f human cfg1.json # Platform mode, single config
#
# Test Modes:
# Stub Mode (default):
# - Fast execution
# - Tests proto.c parsing only
# - Uses simple platform stubs
# - Shows base properties only
# - Use for quick validation and CI/CD
#
# Platform Mode:
# - Integration testing
# - Tests proto.c + platform code (plat-*.c)
# - Uses real platform implementation + mocks
# - Shows base AND platform properties separately
# - Use for platform-specific validation
#
set -e
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONTAINER_NAME="ucentral_client_build_env"
BUILD_DIR="/root/ols-nos/tests/config-parser"
CONFIG_DIR="/root/ols-nos/config-samples"
OUTPUT_DIR="$SCRIPT_DIR/output"
DOCKERFILE_PATH="$SCRIPT_DIR/Dockerfile"
# Default values
TEST_MODE="stub"
PLATFORM_NAME="brcm-sonic"
FORMAT="human"
SINGLE_CONFIG=""
# Function to show help
show_help() {
sed -n '2,39p' "$0" | sed 's/^# \?//'
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
;;
-m|--mode)
TEST_MODE="$2"
shift 2
;;
-p|--platform)
PLATFORM_NAME="$2"
shift 2
;;
-f|--format)
FORMAT="$2"
shift 2
;;
-*)
echo -e "${RED}Error: Unknown option '$1'${NC}"
echo "Use --help to see usage information"
exit 1
;;
*)
# Assume it's the config file
SINGLE_CONFIG="$1"
shift
;;
esac
done
# Validate test mode
case "$TEST_MODE" in
stub|platform)
;;
*)
echo -e "${RED}Error: Invalid mode '$TEST_MODE'. Must be 'stub' or 'platform'${NC}"
echo "Use --help to see usage information"
exit 1
;;
esac
# Validate format
case "$FORMAT" in
html|json|human)
;;
*)
echo -e "${RED}Error: Invalid format '$FORMAT'. Must be 'html', 'json', or 'human'${NC}"
echo "Use --help to see usage information"
exit 1
;;
esac
# Function to print status messages
print_status() {
echo -e "${BLUE}==>${NC} $1"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
# Function to check if Docker is running
check_docker() {
if ! docker info > /dev/null 2>&1; then
print_error "Docker is not running. Please start Docker and try again."
exit 1
fi
print_success "Docker is running"
}
# Function to check if container exists
container_exists() {
docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"
}
# Function to check if container is running
container_running() {
docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"
}
# Function to get Dockerfile SHA
get_dockerfile_sha() {
if [ -f "$DOCKERFILE_PATH" ]; then
shasum -a 1 "$DOCKERFILE_PATH" | awk '{print $1}' | cut -c1-8
else
echo "unknown"
fi
}
# Function to build Docker environment if needed
build_environment() {
local current_sha=$(get_dockerfile_sha)
local image_tag="ucentral-build-env:${current_sha}"
# Check if image exists
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^${image_tag}$"; then
print_success "Build environment image already exists (${image_tag})"
return 0
fi
print_status "Building Docker build environment..."
print_status "This may take several minutes on first run..."
if make build-host-env; then
print_success "Build environment created"
else
print_error "Failed to build environment"
exit 1
fi
}
# Function to start container if not running
start_container() {
if container_running; then
print_success "Container is already running"
return 0
fi
if container_exists; then
print_status "Starting existing container..."
docker start "$CONTAINER_NAME" > /dev/null
print_success "Container started"
else
print_status "Creating and starting new container..."
if make run-host-env; then
print_success "Container created and started"
else
print_error "Failed to start container"
exit 1
fi
fi
# Wait for container to be ready
sleep 2
}
# Function to run tests in Docker
run_tests() {
local test_cmd=""
local build_cmd=""
local output_file=""
local copy_files=()
local use_platform_flag=""
# Set platform flag for build commands
if [ "$TEST_MODE" = "platform" ]; then
use_platform_flag="USE_PLATFORM=$PLATFORM_NAME"
print_status "Test mode: Platform ($PLATFORM_NAME)"
else
print_status "Test mode: Stub (fast)"
fi
if [ -n "$SINGLE_CONFIG" ]; then
print_status "Running test for single config: $SINGLE_CONFIG"
# Verify config exists in container
if ! docker exec "$CONTAINER_NAME" bash -c "test -f $CONFIG_DIR/$SINGLE_CONFIG"; then
print_error "Config file not found in container: $SINGLE_CONFIG"
print_status "Available configs:"
docker exec "$CONTAINER_NAME" bash -c "ls $CONFIG_DIR/*.json 2>/dev/null | xargs -n1 basename" || true
exit 1
fi
# Build test binary with appropriate mode (clean first to ensure correct flags)
build_cmd="cd $BUILD_DIR && make clean && make test-config-parser $use_platform_flag"
case "$FORMAT" in
html)
output_file="test-report-${SINGLE_CONFIG%.json}.html"
test_cmd="$build_cmd && LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --html $CONFIG_DIR/$SINGLE_CONFIG > $BUILD_DIR/$output_file"
copy_files=("$output_file")
;;
json)
output_file="test-results-${SINGLE_CONFIG%.json}.json"
test_cmd="$build_cmd && LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --json $CONFIG_DIR/$SINGLE_CONFIG > $BUILD_DIR/$output_file"
copy_files=("$output_file")
;;
human)
output_file="test-results-${SINGLE_CONFIG%.json}.txt"
test_cmd="$build_cmd && LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser $CONFIG_DIR/$SINGLE_CONFIG 2>&1 | tee $BUILD_DIR/$output_file"
copy_files=("$output_file")
;;
esac
else
print_status "Running tests for all configurations (format: $FORMAT)"
case "$FORMAT" in
html)
output_file="test-report.html"
test_cmd="cd $BUILD_DIR && make clean && make test-config-html $use_platform_flag"
copy_files=("$output_file")
;;
json)
output_file="test-report.json"
test_cmd="cd $BUILD_DIR && make clean && make test-config-json $use_platform_flag"
copy_files=("$output_file")
;;
human)
output_file="test-results.txt"
test_cmd="cd $BUILD_DIR && make clean && make test-config-full $use_platform_flag 2>&1 | tee $BUILD_DIR/$output_file"
copy_files=("$output_file")
;;
esac
fi
print_status "Executing tests in container..."
echo ""
# Run the test command
if docker exec "$CONTAINER_NAME" bash -c "$test_cmd"; then
print_success "Tests completed successfully"
TEST_EXIT_CODE=0
else
TEST_EXIT_CODE=$?
print_warning "Tests completed with issues (exit code: $TEST_EXIT_CODE)"
fi
echo ""
# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"
# Copy output files from container to host
for file in "${copy_files[@]}"; do
if docker exec "$CONTAINER_NAME" bash -c "test -f $BUILD_DIR/$file"; then
print_status "Copying $file from container to host..."
docker cp "$CONTAINER_NAME:$BUILD_DIR/$file" "$OUTPUT_DIR/$file"
print_success "Output saved: $OUTPUT_DIR/$file"
# Show file info
local file_size=$(du -h "$OUTPUT_DIR/$file" | cut -f1)
echo " Size: $file_size"
else
print_warning "Output file not found in container: $file"
fi
done
return $TEST_EXIT_CODE
}
# Function to print summary
print_summary() {
local exit_code=$1
echo ""
echo "========================================"
echo "Test Run Summary"
echo "========================================"
echo "Mode: $TEST_MODE"
if [ "$TEST_MODE" = "platform" ]; then
echo "Platform: $PLATFORM_NAME"
fi
echo "Format: $FORMAT"
if [ -n "$SINGLE_CONFIG" ]; then
echo "Config: $SINGLE_CONFIG"
else
echo "Config: All configurations"
fi
echo "Output Dir: $OUTPUT_DIR"
echo ""
if [ $exit_code -eq 0 ]; then
print_success "All tests passed!"
if [ "$TEST_MODE" = "platform" ]; then
echo ""
echo "Platform properties tracked from: plat-$PLATFORM_NAME.c"
echo "Check output for 'Successfully Configured (Base)' and"
echo "'Successfully Configured (Platform)' sections"
fi
else
print_warning "Some tests failed or had issues"
fi
echo ""
echo "Output files:"
ls -lh "$OUTPUT_DIR" | tail -n +2 | while read -r line; do
echo " $line"
done
}
# Main execution
main() {
print_status "uCentral Configuration Test Runner"
echo ""
# Check prerequisites
check_docker
# Build environment if needed
build_environment
# Start container if needed
start_container
# Run tests
run_tests
TEST_RESULT=$?
# Print summary
print_summary $TEST_RESULT
exit $TEST_RESULT
}
# Run main function
main

View File

@@ -8,7 +8,7 @@ export DH_VERBOSE = 1
export DH_BUILD_DDEBS=1
export DEB_BUILD_OPTIONS=autodbgsym
CONFIGURED_ARCH ?= arm64
CONFIGURED_ARCH ?= amd64
UCENTRAL_CLIENT_VERSION ?= 1.0
DPKG_EXPORT_BUILDFLAGS = 1
INSTALL ?= debian/ucentral-client/

View File

@@ -1,4 +1,4 @@
FROM arm64v8/debian:buster
FROM debian:buster
RUN echo "uCentral client support"
RUN apt-get update && apt-get install --no-install-recommends -y \
@@ -9,7 +9,6 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
curl \
libjsoncpp-dev \
busybox \
libhiredis0.14 \
&& rm -rf /var/lib/apt/lists/*
RUN ln -s /bin/busybox /usr/local/bin/nslookup
@@ -22,11 +21,8 @@ COPY /ucentral-client /usr/local/bin/ucentral-client
COPY /rtty /usr/local/bin/
COPY /lib* /usr/local/lib/
# Install root CA certificate for development purposes
COPY /ca-cert.pem /usr/local/share/ca-certificates/ca-cert.crt
COPY /tip-cert.pem /usr/local/share/ca-certificates/tip-cert.crt
RUN update-ca-certificates
COPY /version.jso[n] /etc/
COPY /schema.jso[n] /etc/
RUN ldconfig
RUN ls -l /usr/local/bin/ucentral-client

View File

@@ -1,35 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIGHzCCBAegAwIBAgIUe4hxamQ8YDS/GuFPIN+uqH4BgMswDQYJKoZIhvcNAQEL
BQAwgZ4xCzAJBgNVBAYTAlVBMRQwEgYDVQQIDAtLeWl2IE9ibGFzdDENMAsGA1UE
BwwES3lpdjEbMBkGA1UECgwSTGFyY2ggTmV0d29ya3MgTHRkMREwDwYDVQQLDAhT
b2Z0d2FyZTERMA8GA1UEAwwIY2EubGFyY2gxJzAlBgkqhkiG9w0BCQEWGGFkbWlu
QGxhcmNoLW5ldHdvcmtzLmNvbTAeFw0yNDA0MDQwNzUxMDlaFw0zNDA0MDIwNzUx
MDlaMIGeMQswCQYDVQQGEwJVQTEUMBIGA1UECAwLS3lpdiBPYmxhc3QxDTALBgNV
BAcMBEt5aXYxGzAZBgNVBAoMEkxhcmNoIE5ldHdvcmtzIEx0ZDERMA8GA1UECwwI
U29mdHdhcmUxETAPBgNVBAMMCGNhLmxhcmNoMScwJQYJKoZIhvcNAQkBFhhhZG1p
bkBsYXJjaC1uZXR3b3Jrcy5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDG5kue5pYz+PC/NBZpBesN2NjWzpcTAunzFnQQ/XAMY9XLcUdyrXLz6r2p
bFbmUiW1am+2TZMg6sQbBPId7vjuHZykXavgUd8ybUoQ2GHnWVsCCy8b/Fn2WF2r
Zc14ffRncZ4BsHCPJsWboCmYVsgNgr1nmDT6Le5VIRJ2MJksuTz+55bK9qCIIWWC
PFBpGjMHpEdybvwa5gzv2Do9vwb0m76LKL5ygyle4hFoI54JwRV8O7g8FishTrmI
oSDmapB7+K9egK2wXhbHQ6pIc4qvs9YdSPaTkLV/tRa3Oyw+vUGDW5Bg5UezPJNf
U9yCZIjKZXYXbqaZRgtcXJfqBGFlmuVX44zfjpEe2ovHXrb7HokZxEcJA/yooxzw
3eZXDxsdVXU02jA9jSQjX1Q44lwxUn1wVZlTdr9AUJM6JiEujITy2fwyfaeTwCyS
Y4xU7qgMr9Wml0kHPPFnlsTWo+dswpztRt5BKCKMcsyLETiLkapsI784ameiEgpA
3klYJsMh/2+Q9ABuK5BQOuHCPvalNoEKn4rUyQgdTL/ay3oqlvNf4r08hgHA72Aj
oM60Ldq2gJeuaEh6l3fV182qd4funDgrMpQpfRcIKDJcDRuN8vKzqmTXLNY8zgNY
xharAsEm3CdBwbpTF4E1yDCH8seuj3TCcMwJAWGq73QRgu0DwQIDAQABo1MwUTAd
BgNVHQ4EFgQUwqiRcQgGkVIdKjDjA3KCVTl0rTwwHwYDVR0jBBgwFoAUwqiRcQgG
kVIdKjDjA3KCVTl0rTwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AgEAwUEtLkinQmWmRqQfKZayUDjKl0B7HZy9en0LI6WzKppfj9OwZcAxWNqavDwp
WRCEmzt27NPF42C6lr/i0ZBOT5V1qDoP8/JR3QW/dsEIS2wW61803pw77U8SyfKP
tEwbQsCuw7jBCSDjDKI5k9aBlltHzTzSKjo1nBTwMYHho1dR5Th6mOXr9soyNopU
l7SlvC5UWuCYxhxjWlZnxaupbKdPNt88SPWDLWa6HLBil2XSu/5wMVdubyCZrlN/
8SajdHtev9lk3pucnLmqxCq6sChim9EzDcVVRPNzR59H6dbyMec16e0XrnNcd9ch
2Zo2HQm3jyIh4YL1iRrHyqJgVfmI1hKlvqUOfAxVCV8pSec40EQD2sd1Ges5QTiH
LCk2osPvDjV1JyFKcr5Pf50+S02MViU8tza+VfUwozRa6A9qR/JlBTBlCrDEYexm
CK7I11Qln26vvtlnJTC7OzXTaOSYQ1NlJCwLwsMOvhYVm0skxL1HX1AYHKZ0t0QW
PAQYij2QP1YK4SU68MlXiE3D5N7vZ+42pfHx0DNZXlsudM7kRmkT3FBIh54mh4xt
0a5nUDjd3IBPcVAeVJZqbxglAwKlue+MhJRKSFxehE3qEKurmH1tGUTb5NnmMllC
6G51ZGoyb64oGejymIc2IB5rXH5+Uu2HUsyoVLt0wihgU7E=
-----END CERTIFICATE-----

View File

@@ -4,8 +4,7 @@
"package": {
"version": "1.0.0",
"depends": [],
"name": "ucentral_client",
"description": "SONiC uCentral client package"
"name": "ucentral_client"
},
"service": {
"name": "ucentral_client",
@@ -26,24 +25,12 @@
},
"container": {
"privileged": false,
"volumes": ["/var/lib/ucentral:/var/lib/ucentral"],
"mounts": [
{
"source": "TCA",
"target": "/etc/ucentral",
"type": "volume",
"volume-opt": {
"device": "/dev/disk/by-label/ONIE-TIP-CA-CERT",
"type": "ext4",
"o": "ro"
}
}
],
"volumes": [],
"tmpfs": []
},
"cli": {
"config": [],
"show": [],
"clear": []
"config": "",
"show": "",
"clear": ""
}
}

View File

@@ -1,49 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEnDCCA4SgAwIBAgIUVpyCUx1MUeUwxg+7I1BvGFTz7HkwDQYJKoZIhvcNAQEL
BQAwaTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSYwJAYDVQQDEx1UZWxlY29tIEluZnJhIFBy
b2plY3QgUm9vdCBDQTAeFw0yMTA0MTMyMjUxMjZaFw0yNjA0MTMyMjM4NDZaMGwx
CzAJBgNVBAYTAlVTMSQwIgYDVQQKExtUZWxlY29tIEluZnJhIFByb2plY3QsIElu
Yy4xDDAKBgNVBAsTA1RJUDEpMCcGA1UEAxMgVGVsZWNvbSBJbmZyYSBQcm9qZWN0
IElzc3VpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtKBrq
qd2aKVSk25KfL5xHu8X7/8rJrz3IvyPuVKWhk/N1zabot3suBcGaYNKjnRHxg78R
yKwKzajKYWtiQFqztu24g16LQeAnoUxZnF6a0z3JkkRPsz14A2y8TUhdEe1tx+UU
4VGsk3n+FMmOQHL+79FO57zQC1LwylgfLSltrI6mF3jowVUQvnwzKhUzT87AJ6EO
ndK/q0T/Bgi+aI39zfVOjJjsTJwghvrmYW3iarP1THSKxeib2s02bZKrvvHa5HL4
UI8+LvREpVZl4mzt1z6Nl344Y6f+UeJlYa/Ci0jJqaXJmyVnUbAz+c0i5JfwAVn3
YQzfC4eLnZCmdF8zAgMBAAGjggE3MIIBMzAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSzG1S44EerPfM4gOQ85f0AYW3R6DAfBgNVHSMEGDAWgBQCRpZgebFT9qny
98WfIUDk6ZEB+jAOBgNVHQ8BAf8EBAMCAYYwgYMGCCsGAQUFBwEBBHcwdTAoBggr
BgEFBQcwAYYcaHR0cDovL29jc3Aub25lLmRpZ2ljZXJ0LmNvbTBJBggrBgEFBQcw
AoY9aHR0cDovL2NhY2VydHMub25lLmRpZ2ljZXJ0LmNvbS9UZWxlY29tSW5mcmFQ
cm9qZWN0Um9vdENBLmNydDBKBgNVHR8EQzBBMD+gPaA7hjlodHRwOi8vY3JsLm9u
ZS5kaWdpY2VydC5jb20vVGVsZWNvbUluZnJhUHJvamVjdFJvb3RDQS5jcmwwDQYJ
KoZIhvcNAQELBQADggEBAFbz+K94bHIkBMJqps0dApniUmOn0pO6Q6cGh47UP/kX
IiPIsnYgG+hqYD/qtsiqJhaWi0hixRWn38UmvZxMRk27aSTGE/TWx0JTC3qDGsSe
XkUagumbSfmS0ZyiTwMPeGAjXwyzGorqZWeA95eKfImntMiOf3E7//GK0K7HpCx8
IPCnLZsZD2q/mLyBsduImFIRQJbLAhwIxpcd1qYJk+BlGFL+HtBpEbq6JxW2Xy+v
DpNWc2WIsUTle0rTc9JNJrLX4ChUJmKqf8obKHap3Xh3//qw/jDB9pOAinA33FLJ
EmCnwBvQr9mfNmPBGMYZVU8cPruDQJ57GjmmvdisbJY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIUPVYBpqNbcLYygF6Mx+qxSWwQyFowDQYJKoZIhvcNAQEL
BQAwaTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSYwJAYDVQQDEx1UZWxlY29tIEluZnJhIFBy
b2plY3QgUm9vdCBDQTAeFw0yMTA0MTMyMjQyNDRaFw0zMTA0MTMyMjM4NDZaMGkx
CzAJBgNVBAYTAlVTMSQwIgYDVQQKExtUZWxlY29tIEluZnJhIFByb2plY3QsIElu
Yy4xDDAKBgNVBAsTA1RJUDEmMCQGA1UEAxMdVGVsZWNvbSBJbmZyYSBQcm9qZWN0
IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIGCibwf5u
AAwZ+1H8U0e3u2V+0d2gSctucoK86XwUmfe1V2a/qlCYZd29r80IuN1IIeB0naIm
KnK/MzXW87clF6tFd1+HzEvmlY/W4KyIXalVCTEzirFSvBEG2oZpM0yC3AefytAO
aOpA00LaM3xTfTqMKIRhJBuLy0I4ANUVG6ixVebbGuc78IodleqiLoWy2Q9QHyEO
t/7hZndJhiVogh0PveRhho45EbsACu7ymDY+JhlIleevqwlE3iQoq0YcmYADHno6
Eq8vcwLpZFxihupUafkd1T3WJYQAJf9coCjBu2qIhNgrcrGD8R9fGswwNRzMRMpX
720+GjcDW3bJAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAJG
lmB5sVP2qfL3xZ8hQOTpkQH6MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEAVjl9dm4epG9NUYnagT9sg7scVQEPfz3Lt6w1NXJXgD8mAUlK0jXmEyvM
dCPD4514n+8+lM7US8fh+nxc7jO//LwK17Wm9FblgjNFR7+anv0Q99T9fP19DLlF
PSNHL2emogy1bl1lLTAoj8nxg2wVKPDSHBGviQ5LR9fsWUIJDv9Bs5k0qWugWYSj
19S6qnHeskRDB8MqRLhKMG82oDVLerSnhD0P6HjySBHgTTU7/tYS/OZr1jI6MPbG
L+/DtiR5fDVMNdBSGU89UNTi0wHY9+RFuNlIuvZC+x/swF0V9R5mN+ywquTPtDLA
5IOM7ItsRmen6u3qu+JXros54e4juQ==
-----END CERTIFICATE-----

View File

@@ -1,3 +1,8 @@
# Production build system for uCentral client
# Configuration parser tests have been moved to tests/config-parser/Makefile
# Unit tests remain here for backward compatibility with original repository structure.
# See TESTING_FRAMEWORK.md for complete test documentation.
.PHONY: test
export CFLAGS+= -Werror -Wall -Wextra
@@ -17,9 +22,9 @@ platform/plat.a:
%.o: %.c
gcc -c -o $@ ${CFLAGS} -I ./ -I ./include $^
ucentral-client: ucentral-client.o proto.o platform/plat.a \
ucentral-client: ucentral-client.o proto.o est-client.o platform/plat.a \
ucentral-json-parser.o ucentral-log.o router-utils.o base64.o
g++ -g -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv -lhiredis
g++ -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv
test:
@echo "========= running unit tests ========="
@@ -31,4 +36,5 @@ test-ucentral-json-parser: test-ucentral-json-parser.o ucentral-json-parser.o
./test-ucentral-json-parser 2>/dev/null
clean:
rm -f ucentral-client 2>/dev/null
rm -f ucentral-client *.o test-ucentral-json-parser 2>/dev/null
$(MAKE) -C platform/${PLATFORM} clean

View File

@@ -0,0 +1,592 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/**
* EST (Enrollment over Secure Transport) Client Implementation
*
* RFC 7030 compliant EST client for PKI 2.0 operational certificate
* enrollment and renewal. Implements:
* - /simpleenroll - Initial certificate enrollment
* - /simplereenroll - Certificate renewal
* - /cacerts - CA certificate retrieval
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pkcs7.h>
#include "est-client.h"
/* EST default servers */
#define EST_SERVER_PROD "est.certificates.open-lan.org"
#define EST_SERVER_QA "qaest.certificates.open-lan.org:8001"
/* EST well-known paths (RFC 7030) */
#define EST_PATH_SIMPLEENROLL "/.well-known/est/simpleenroll"
#define EST_PATH_SIMPLEREENROLL "/.well-known/est/simplereenroll"
#define EST_PATH_CACERTS "/.well-known/est/cacerts"
/* HTTP request timeout */
#define EST_TIMEOUT_SECONDS 30
/* Global error message buffer */
static char est_error_msg[512] = {0};
/* Memory buffer for CURL responses */
struct est_memory {
char *data;
size_t size;
};
/**
* Set error message
*/
static void est_set_error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsnprintf(est_error_msg, sizeof(est_error_msg), fmt, args);
va_end(args);
}
const char* est_get_error(void)
{
return est_error_msg[0] ? est_error_msg : "Unknown error";
}
/**
* CURL write callback - store response data in memory
*/
static size_t est_write_callback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct est_memory *mem = (struct est_memory *)userp;
char *ptr = realloc(mem->data, mem->size + realsize + 1);
if (!ptr) {
est_set_error("Out of memory");
return 0;
}
mem->data = ptr;
memcpy(&(mem->data[mem->size]), contents, realsize);
mem->size += realsize;
mem->data[mem->size] = 0; /* null terminate */
return realsize;
}
/**
* Extract certificate issuer string
*/
static char* est_get_cert_issuer(const char *cert_path)
{
FILE *f = fopen(cert_path, "r");
if (!f) {
est_set_error("Cannot open certificate: %s", cert_path);
return NULL;
}
X509 *cert = PEM_read_X509(f, NULL, NULL, NULL);
fclose(f);
if (!cert) {
est_set_error("Failed to parse certificate");
return NULL;
}
X509_NAME *issuer = X509_get_issuer_name(cert);
if (!issuer) {
X509_free(cert);
est_set_error("Failed to get certificate issuer");
return NULL;
}
char *issuer_str = X509_NAME_oneline(issuer, NULL, 0);
X509_free(cert);
return issuer_str;
}
const char* est_get_server_url(const char *cert_path)
{
/* Check environment variable override */
const char *env_server = getenv("EST_SERVER");
if (env_server && env_server[0])
return env_server;
/* Auto-detect from certificate issuer */
char *issuer = est_get_cert_issuer(cert_path);
if (!issuer)
return EST_SERVER_PROD; /* default fallback */
const char *server = EST_SERVER_PROD;
if (strstr(issuer, "OpenLAN Demo Birth CA")) {
server = EST_SERVER_QA;
} else if (strstr(issuer, "OpenLAN Birth Issuing CA")) {
server = EST_SERVER_PROD;
}
OPENSSL_free(issuer);
return server;
}
/**
* Generate CSR from existing certificate
*/
int est_generate_csr(const char *cert_path, const char *key_path,
char **csr_out, size_t *csr_len)
{
if (!cert_path || !key_path || !csr_out || !csr_len) {
est_set_error("Invalid arguments");
return EST_ERROR_INVALID;
}
*csr_out = NULL;
*csr_len = 0;
/* Read existing certificate */
FILE *cert_file = fopen(cert_path, "r");
if (!cert_file) {
est_set_error("Cannot open certificate: %s", cert_path);
return EST_ERROR_GENERAL;
}
X509 *cert = PEM_read_X509(cert_file, NULL, NULL, NULL);
fclose(cert_file);
if (!cert) {
est_set_error("Failed to parse certificate");
return EST_ERROR_CRYPTO;
}
/* Get subject from existing certificate */
X509_NAME *subject = X509_get_subject_name(cert);
if (!subject) {
X509_free(cert);
est_set_error("Failed to get certificate subject");
return EST_ERROR_CRYPTO;
}
/* Duplicate subject name (we'll free cert but need subject) */
X509_NAME *subject_dup = X509_NAME_dup(subject);
X509_free(cert);
if (!subject_dup) {
est_set_error("Failed to duplicate subject name");
return EST_ERROR_MEMORY;
}
/* Read private key */
FILE *key_file = fopen(key_path, "r");
if (!key_file) {
X509_NAME_free(subject_dup);
est_set_error("Cannot open private key: %s", key_path);
return EST_ERROR_GENERAL;
}
EVP_PKEY *pkey = PEM_read_PrivateKey(key_file, NULL, NULL, NULL);
fclose(key_file);
if (!pkey) {
X509_NAME_free(subject_dup);
est_set_error("Failed to parse private key");
return EST_ERROR_CRYPTO;
}
/* Create new CSR */
X509_REQ *req = X509_REQ_new();
if (!req) {
EVP_PKEY_free(pkey);
X509_NAME_free(subject_dup);
est_set_error("Failed to create CSR");
return EST_ERROR_MEMORY;
}
/* Set version */
X509_REQ_set_version(req, 0L);
/* Set subject */
X509_REQ_set_subject_name(req, subject_dup);
X509_NAME_free(subject_dup);
/* Set public key */
X509_REQ_set_pubkey(req, pkey);
/* Sign CSR */
if (!X509_REQ_sign(req, pkey, EVP_sha256())) {
X509_REQ_free(req);
EVP_PKEY_free(pkey);
est_set_error("Failed to sign CSR");
return EST_ERROR_CRYPTO;
}
EVP_PKEY_free(pkey);
/* Write CSR to memory (DER format) */
BIO *bio = BIO_new(BIO_s_mem());
if (!bio) {
X509_REQ_free(req);
est_set_error("Failed to create BIO");
return EST_ERROR_MEMORY;
}
if (!i2d_X509_REQ_bio(bio, req)) {
BIO_free(bio);
X509_REQ_free(req);
est_set_error("Failed to write CSR");
return EST_ERROR_CRYPTO;
}
X509_REQ_free(req);
/* Get DER data */
BUF_MEM *bio_buf;
BIO_get_mem_ptr(bio, &bio_buf);
/* Base64 encode (no headers) */
BIO *b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO *mem_bio = BIO_new(BIO_s_mem());
BIO_push(b64, mem_bio);
BIO_write(b64, bio_buf->data, bio_buf->length);
BIO_flush(b64);
BUF_MEM *b64_buf;
BIO_get_mem_ptr(mem_bio, &b64_buf);
*csr_out = malloc(b64_buf->length + 1);
if (!*csr_out) {
BIO_free_all(b64);
BIO_free(bio);
est_set_error("Out of memory");
return EST_ERROR_MEMORY;
}
memcpy(*csr_out, b64_buf->data, b64_buf->length);
(*csr_out)[b64_buf->length] = 0;
*csr_len = b64_buf->length;
BIO_free_all(b64);
BIO_free(bio);
return EST_SUCCESS;
}
/**
* Convert PKCS#7 to PEM format
*/
int est_pkcs7_to_pem(const char *pkcs7_data, size_t pkcs7_len,
char **pem_out, size_t *pem_len)
{
if (!pkcs7_data || !pkcs7_len || !pem_out || !pem_len) {
est_set_error("Invalid arguments");
return EST_ERROR_INVALID;
}
*pem_out = NULL;
*pem_len = 0;
/* Decode base64 */
BIO *b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO *bio_mem = BIO_new_mem_buf((void*)pkcs7_data, pkcs7_len);
BIO_push(b64, bio_mem);
/* Read PKCS#7 structure */
PKCS7 *p7 = d2i_PKCS7_bio(b64, NULL);
BIO_free_all(b64);
if (!p7) {
est_set_error("Failed to parse PKCS#7 data");
return EST_ERROR_CRYPTO;
}
/* Extract certificates from PKCS#7 */
STACK_OF(X509) *certs = NULL;
int type = OBJ_obj2nid(p7->type);
if (type == NID_pkcs7_signed) {
certs = p7->d.sign->cert;
} else if (type == NID_pkcs7_signedAndEnveloped) {
certs = p7->d.signed_and_enveloped->cert;
}
if (!certs || sk_X509_num(certs) == 0) {
PKCS7_free(p7);
est_set_error("No certificates in PKCS#7");
return EST_ERROR_CRYPTO;
}
/* Write certificates to PEM */
BIO *out = BIO_new(BIO_s_mem());
if (!out) {
PKCS7_free(p7);
est_set_error("Failed to create output BIO");
return EST_ERROR_MEMORY;
}
for (int i = 0; i < sk_X509_num(certs); i++) {
X509 *cert = sk_X509_value(certs, i);
if (!PEM_write_bio_X509(out, cert)) {
BIO_free(out);
PKCS7_free(p7);
est_set_error("Failed to write certificate");
return EST_ERROR_CRYPTO;
}
}
PKCS7_free(p7);
/* Get PEM data */
BUF_MEM *pem_buf;
BIO_get_mem_ptr(out, &pem_buf);
*pem_out = malloc(pem_buf->length + 1);
if (!*pem_out) {
BIO_free(out);
est_set_error("Out of memory");
return EST_ERROR_MEMORY;
}
memcpy(*pem_out, pem_buf->data, pem_buf->length);
(*pem_out)[pem_buf->length] = 0;
*pem_len = pem_buf->length;
BIO_free(out);
return EST_SUCCESS;
}
/**
* Perform EST HTTP request
*/
static int est_http_request(const char *url, const char *cert, const char *key,
const char *ca_bundle, const char *post_data, size_t post_len,
char **response_out, size_t *response_len)
{
CURL *curl;
CURLcode res;
struct est_memory response = {0};
curl = curl_easy_init();
if (!curl) {
est_set_error("Failed to initialize CURL");
return EST_ERROR_GENERAL;
}
/* Set URL */
curl_easy_setopt(curl, CURLOPT_URL, url);
/* Client certificate authentication */
curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLCERT, cert);
curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLKEY, key);
/* CA bundle for server verification */
if (ca_bundle)
curl_easy_setopt(curl, CURLOPT_CAINFO, ca_bundle);
/* Response handler */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, est_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
/* Timeout */
curl_easy_setopt(curl, CURLOPT_TIMEOUT, EST_TIMEOUT_SECONDS);
/* POST request if data provided */
if (post_data && post_len > 0) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/pkcs10");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_len);
}
/* Perform request */
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
if (response.data)
free(response.data);
est_set_error("CURL error: %s", curl_easy_strerror(res));
return EST_ERROR_NETWORK;
}
*response_out = response.data;
*response_len = response.size;
return EST_SUCCESS;
}
int est_simple_enroll(const char *est_server, const char *birth_cert,
const char *birth_key, const char *ca_bundle,
char **operational_cert_out)
{
if (!est_server || !birth_cert || !birth_key || !operational_cert_out) {
est_set_error("Invalid arguments");
return EST_ERROR_INVALID;
}
*operational_cert_out = NULL;
/* Generate CSR */
char *csr = NULL;
size_t csr_len = 0;
int ret = est_generate_csr(birth_cert, birth_key, &csr, &csr_len);
if (ret != EST_SUCCESS) {
return ret;
}
/* Build EST URL */
char url[512];
snprintf(url, sizeof(url), "https://%s%s", est_server, EST_PATH_SIMPLEENROLL);
/* Perform enrollment */
char *response = NULL;
size_t response_len = 0;
ret = est_http_request(url, birth_cert, birth_key, ca_bundle,
csr, csr_len, &response, &response_len);
free(csr);
if (ret != EST_SUCCESS) {
return ret;
}
/* Convert PKCS#7 response to PEM */
char *pem = NULL;
size_t pem_len = 0;
ret = est_pkcs7_to_pem(response, response_len, &pem, &pem_len);
free(response);
if (ret != EST_SUCCESS) {
return ret;
}
*operational_cert_out = pem;
return EST_SUCCESS;
}
int est_simple_reenroll(const char *est_server, const char *operational_cert,
const char *key, const char *ca_bundle,
char **renewed_cert_out)
{
if (!est_server || !operational_cert || !key || !renewed_cert_out) {
est_set_error("Invalid arguments");
return EST_ERROR_INVALID;
}
*renewed_cert_out = NULL;
/* Generate CSR */
char *csr = NULL;
size_t csr_len = 0;
int ret = est_generate_csr(operational_cert, key, &csr, &csr_len);
if (ret != EST_SUCCESS) {
return ret;
}
/* Build EST URL */
char url[512];
snprintf(url, sizeof(url), "https://%s%s", est_server, EST_PATH_SIMPLEREENROLL);
/* Perform reenrollment */
char *response = NULL;
size_t response_len = 0;
ret = est_http_request(url, operational_cert, key, ca_bundle,
csr, csr_len, &response, &response_len);
free(csr);
if (ret != EST_SUCCESS) {
return ret;
}
/* Convert PKCS#7 response to PEM */
char *pem = NULL;
size_t pem_len = 0;
ret = est_pkcs7_to_pem(response, response_len, &pem, &pem_len);
free(response);
if (ret != EST_SUCCESS) {
return ret;
}
*renewed_cert_out = pem;
return EST_SUCCESS;
}
int est_get_cacerts(const char *est_server, const char *cert, const char *key,
const char *ca_bundle, char **ca_certs_out)
{
if (!est_server || !cert || !key || !ca_certs_out) {
est_set_error("Invalid arguments");
return EST_ERROR_INVALID;
}
*ca_certs_out = NULL;
/* Build EST URL */
char url[512];
snprintf(url, sizeof(url), "https://%s%s", est_server, EST_PATH_CACERTS);
/* Perform GET request */
char *response = NULL;
size_t response_len = 0;
int ret = est_http_request(url, cert, key, ca_bundle,
NULL, 0, &response, &response_len);
if (ret != EST_SUCCESS) {
return ret;
}
/* Convert PKCS#7 response to PEM */
char *pem = NULL;
size_t pem_len = 0;
ret = est_pkcs7_to_pem(response, response_len, &pem, &pem_len);
free(response);
if (ret != EST_SUCCESS) {
return ret;
}
*ca_certs_out = pem;
return EST_SUCCESS;
}
int est_save_cert(const char *cert_data, size_t cert_len, const char *file_path)
{
if (!cert_data || !cert_len || !file_path) {
est_set_error("Invalid arguments");
return EST_ERROR_INVALID;
}
FILE *f = fopen(file_path, "w");
if (!f) {
est_set_error("Cannot create file: %s", file_path);
return EST_ERROR_GENERAL;
}
if (fwrite(cert_data, 1, cert_len, f) != cert_len) {
fclose(f);
est_set_error("Failed to write to file: %s", file_path);
return EST_ERROR_GENERAL;
}
fclose(f);
return EST_SUCCESS;
}

View File

@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef __EST_CLIENT_H
#define __EST_CLIENT_H
/**
* EST (Enrollment over Secure Transport) Client Implementation
*
* Implements RFC 7030 EST protocol for PKI 2.0 operational certificate
* enrollment and renewal. Uses libcurl for HTTPS transport and OpenSSL
* for cryptographic operations.
*/
#include <stddef.h>
/* EST operation return codes */
#define EST_SUCCESS 0
#define EST_ERROR_GENERAL -1
#define EST_ERROR_NETWORK -2
#define EST_ERROR_CRYPTO -3
#define EST_ERROR_MEMORY -4
#define EST_ERROR_INVALID -5
/**
* Generate a Certificate Signing Request (CSR) from an existing certificate
*
* @param cert_path Path to existing certificate (PEM format)
* @param key_path Path to private key (PEM format)
* @param csr_out Output buffer for CSR (base64, no headers) - caller must free()
* @param csr_len Output length of CSR data
* @return EST_SUCCESS on success, error code otherwise
*/
int est_generate_csr(const char *cert_path, const char *key_path,
char **csr_out, size_t *csr_len);
/**
* Perform EST simple enrollment - Get operational certificate from birth certificate
*
* @param est_server EST server URL (e.g., "est.certificates.open-lan.org")
* @param birth_cert Path to birth certificate (PEM format)
* @param birth_key Path to birth certificate private key (PEM format)
* @param ca_bundle Path to CA certificate bundle for server verification
* @param operational_cert_out Output operational certificate (PEM) - caller must free()
* @return EST_SUCCESS on success, error code otherwise
*/
int est_simple_enroll(const char *est_server,
const char *birth_cert, const char *birth_key,
const char *ca_bundle,
char **operational_cert_out);
/**
* Perform EST simple reenrollment - Renew operational certificate
*
* @param est_server EST server URL
* @param operational_cert Path to current operational certificate (PEM format)
* @param key Path to private key (PEM format)
* @param ca_bundle Path to CA certificate bundle
* @param renewed_cert_out Output renewed certificate (PEM) - caller must free()
* @return EST_SUCCESS on success, error code otherwise
*/
int est_simple_reenroll(const char *est_server,
const char *operational_cert, const char *key,
const char *ca_bundle,
char **renewed_cert_out);
/**
* Retrieve operational CA certificates from EST server
*
* @param est_server EST server URL
* @param cert Path to client certificate for authentication
* @param key Path to private key
* @param ca_bundle Path to CA bundle for server verification
* @param ca_certs_out Output CA certificates (PEM) - caller must free()
* @return EST_SUCCESS on success, error code otherwise
*/
int est_get_cacerts(const char *est_server,
const char *cert, const char *key,
const char *ca_bundle,
char **ca_certs_out);
/**
* Convert PKCS#7 format to PEM format using OpenSSL
*
* @param pkcs7_data PKCS#7 data (base64, no headers)
* @param pkcs7_len Length of PKCS#7 data
* @param pem_out Output PEM format certificate(s) - caller must free()
* @param pem_len Output length of PEM data
* @return EST_SUCCESS on success, error code otherwise
*/
int est_pkcs7_to_pem(const char *pkcs7_data, size_t pkcs7_len,
char **pem_out, size_t *pem_len);
/**
* Auto-detect EST server URL based on certificate issuer
*
* Inspects the certificate issuer field to determine appropriate EST server:
* - "OpenLAN Demo Birth CA" -> QA server
* - "OpenLAN Birth Issuing CA" -> Production server
*
* Can be overridden with EST_SERVER environment variable.
*
* @param cert_path Path to certificate to inspect
* @return EST server URL (static string), or NULL on error
*/
const char* est_get_server_url(const char *cert_path);
/**
* Save certificate to file
*
* @param cert_data Certificate data (PEM format)
* @param cert_len Length of certificate data
* @param file_path Destination file path
* @return EST_SUCCESS on success, error code otherwise
*/
int est_save_cert(const char *cert_data, size_t cert_len, const char *file_path);
/**
* Get last error message
*
* @return Human-readable error message (static string)
*/
const char* est_get_error(void);
#endif /* __EST_CLIENT_H */

View File

@@ -1,14 +1,8 @@
#ifndef ROUTER_UTILS_H
#define ROUTER_UTILS_H
/* Defines router types and utils for them (lookup/etc) */
#include <netinet/in.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Fixed: Changed 'key' to proper struct definition with semicolon */
struct ucentral_router_fib_key {
/* TODO vrf */
struct in_addr prefix;
@@ -100,8 +94,3 @@ int ucentral_router_fib_info_cmp(const struct ucentral_router_fib_info *a,
(DIFF) < 0 ? ++(INEW) : 0 \
)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -3,10 +3,6 @@
#include <syslog.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef UC_LOG_COMPONENT
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_UNKNOWN
#endif
@@ -62,8 +58,4 @@ void uc_log(enum uc_log_component c, int sv, const char *fmt, ...);
FMT __VA_OPT__(, ) __VA_ARGS__); \
} while (0)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,6 +1,3 @@
#ifndef UCENTRAL_PLATFORM_H
#define UCENTRAL_PLATFORM_H
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
@@ -28,7 +25,6 @@ extern "C" {
#define RTTY_CFG_FIELD_STR_MAX_LEN (64)
#define PLATFORM_INFO_STR_MAX_LEN (96)
#define SYSLOG_CFG_FIELD_STR_MAX_LEN (64)
#define NTP_CFG_HOSTNAME_STR_MAX_LEN (64)
#define RADIUS_CFG_HOSTNAME_STR_MAX_LEN (64)
#define RADIUS_CFG_PASSKEY_STR_MAX_LEN (64)
#define RADIUS_CFG_DEFAULT_PORT (1812)
@@ -329,15 +325,6 @@ struct plat_enabled_service_cfg {
} http;
};
struct plat_ntp_server {
char hostname[NTP_CFG_HOSTNAME_STR_MAX_LEN];
struct plat_ntp_server *next;
};
struct plat_ntp_cfg {
struct plat_ntp_server *servers;
};
struct plat_rtty_cfg {
char id[RTTY_CFG_FIELD_STR_MAX_LEN];
char passwd[RTTY_CFG_FIELD_STR_MAX_LEN];
@@ -469,7 +456,6 @@ struct plat_cfg {
struct plat_metrics_cfg metrics;
struct plat_syslog_cfg *log_cfg;
struct plat_enabled_service_cfg enabled_services_cfg;
struct plat_ntp_cfg ntp_cfg;
/* Port's interfaces (provide l2 iface w/o bridge caps) */
struct plat_port_l2 portsl2[MAX_NUM_OF_PORTS];
struct ucentral_router router;
@@ -489,7 +475,6 @@ struct plat_cfg {
struct plat_ieee8021x_dac_list *das_dac_list;
} ieee8021x;
struct plat_port_isolation_cfg port_isolation_cfg;
bool jumbo_frames;
};
struct plat_learned_mac_addr {
@@ -794,5 +779,3 @@ plat_reboot_cause_get(struct plat_reboot_cause *cause);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -4177,7 +4177,8 @@ config_port_ieee8021x_apply(uint16_t port_id, struct plat_port *port_cfg)
cached_port->ieee8021x.control_mode = ctrl_mode;
cached_port->ieee8021x.guest_vid = port_cfg->ieee8021x.guest_vid;
cached_port->ieee8021x.auth_fail_vid = port_cfg->ieee8021x.auth_fail_vid;
cached_port->ieee8021x.host_mode = port_cfg->ieee8021x.host_mode;
/* Fixed: Use local variable host_mode instead of incorrect port_cfg->ieee8021x.host_mode */
cached_port->ieee8021x.host_mode = host_mode;
return 0;
}

View File

@@ -2,4 +2,8 @@ plat.a: plat-example.o
ar crs $@ $^
%.o: %.c
ifdef PLATFORM_REVISION
gcc -c -o $@ ${CFLAGS} -I ./ -I ../../include -D PLATFORM_REVISION='"$(PLATFORM_REVISION)"' $^
else
gcc -c -o $@ ${CFLAGS} -I ./ -I ../../include $^
endif

View File

@@ -2,6 +2,7 @@
#include <ucentral-platform.h>
#include <ucentral-log.h>
#include <plat-revision.h>
#define UNUSED_PARAM(param) (void)((param))
@@ -12,7 +13,11 @@ int plat_init(void)
int plat_info_get(struct plat_platform_info *info)
{
UNUSED_PARAM(info);
*info = (struct plat_platform_info){0};
snprintf(info->platform, sizeof info->platform, "%s", "Example Platform" );
snprintf(info->hwsku, sizeof info->hwsku, "%s", "example-platform-sku");
snprintf(info->mac, sizeof info->mac, "%s", "24:fe:9a:0f:48:f0");
return 0;
}
@@ -156,10 +161,45 @@ int plat_port_num_get(uint16_t *num_of_active_ports)
UNUSED_PARAM(num_of_active_ports);
return 0;
}
int plat_revision_get(char *str, size_t str_max_len)
{
snprintf(str, str_max_len, PLATFORM_REVISION);
return 0;
}
int plat_reboot_cause_get(struct plat_reboot_cause *cause)
{
UNUSED_PARAM(cause);
return 0;
}
int plat_event_subscribe(const struct plat_event_callbacks *cbs)
{
UNUSED_PARAM(cbs);
return 0;
}
void plat_event_unsubscribe(void)
{
return;
}
int plat_running_img_name_get(char *str, size_t str_max_len)
{
UNUSED_PARAM(str_max_len);
UNUSED_PARAM(str);
return 0;
}
int plat_metrics_save(const struct plat_metrics_cfg *cfg)
{
UNUSED_PARAM(cfg);
return 0;
}
int plat_metrics_restore(struct plat_metrics_cfg *cfg)
{
UNUSED_PARAM(cfg);
return 0;
}
int plat_run_script(struct plat_run_script *p)
{
UNUSED_PARAM(p);
return 0;
}

View File

@@ -0,0 +1,14 @@
#ifndef _PLAT_REVISION
#define _PLAT_REVISION
#define XSTR(x) STR(x)
#define STR(x) #x
#define PLATFORM_REL_NUM 3.2.0
#define PLATFORM_BUILD_NUM 5
#ifndef PLATFORM_REVISION
#define PLATFORM_REVISION "Rel " XSTR(PLATFORM_REL_NUM) " build " XSTR(PLATFORM_BUILD_NUM)
#endif
#endif

View File

@@ -1,52 +0,0 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: false
AfterControlStatement: Always
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
ColumnLimit: 120
DerivePointerAlignment: false
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
NamespaceIndentation: Inner
PackConstructorInitializers: CurrentLine
PointerAlignment: Right
RequiresExpressionIndentation: OuterScope
UseTab: Never
...

View File

@@ -1,118 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(larch-sonic VERSION 0.1.0 LANGUAGES C CXX)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(grpc)
include(FetchContent)
FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
FetchContent_MakeAvailable(json)
set(REDIS_PLUS_PLUS_BUILD_TEST OFF)
set(REDIS_PLUS_PLUS_BUILD_SHARED OFF)
FetchContent_Declare(redis-plus-plus URL https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.12.zip)
FetchContent_MakeAvailable(redis-plus-plus)
add_subdirectory(protos)
add_library(larch-sonic
STATIC
"fdb.cpp"
"igmp.cpp"
"metrics.cpp"
"plat-larch.cpp"
"port.cpp"
"route.cpp"
"sai_redis.cpp"
"services.cpp"
"state.cpp"
"stp.cpp"
"syslog.cpp"
"utils.cpp"
"vlan.cpp"
)
target_compile_features(larch-sonic PRIVATE cxx_std_17)
target_link_libraries(larch-sonic
PRIVATE
proto-objects
nlohmann_json::nlohmann_json
redis++::redis++_static
)
target_include_directories(larch-sonic
PRIVATE
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/../../include
)
set_target_properties(larch-sonic PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
add_custom_target(plat.a
DEPENDS larch-sonic
COMMAND ${CMAKE_AR} rcsT ${PROJECT_SOURCE_DIR}/plat.a
$<TARGET_FILE:larch-sonic>
$<TARGET_FILE:redis++::redis++_static>
/usr/local/lib/libabsl_bad_optional_access.a
/usr/local/lib/libabsl_bad_variant_access.a
/usr/local/lib/libabsl_base.a
/usr/local/lib/libabsl_city.a
/usr/local/lib/libabsl_civil_time.a
/usr/local/lib/libabsl_cord.a
/usr/local/lib/libabsl_cord_internal.a
/usr/local/lib/libabsl_cordz_functions.a
/usr/local/lib/libabsl_cordz_handle.a
/usr/local/lib/libabsl_cordz_info.a
/usr/local/lib/libabsl_debugging_internal.a
/usr/local/lib/libabsl_demangle_internal.a
/usr/local/lib/libabsl_exponential_biased.a
/usr/local/lib/libabsl_graphcycles_internal.a
/usr/local/lib/libabsl_hash.a
/usr/local/lib/libabsl_hashtablez_sampler.a
/usr/local/lib/libabsl_int128.a
/usr/local/lib/libabsl_log_severity.a
/usr/local/lib/libabsl_low_level_hash.a
/usr/local/lib/libabsl_malloc_internal.a
/usr/local/lib/libabsl_random_distributions.a
/usr/local/lib/libabsl_random_internal_platform.a
/usr/local/lib/libabsl_random_internal_pool_urbg.a
/usr/local/lib/libabsl_random_internal_randen.a
/usr/local/lib/libabsl_random_internal_randen_hwaes.a
/usr/local/lib/libabsl_random_internal_randen_hwaes_impl.a
/usr/local/lib/libabsl_random_internal_randen_slow.a
/usr/local/lib/libabsl_random_internal_seed_material.a
/usr/local/lib/libabsl_random_seed_gen_exception.a
/usr/local/lib/libabsl_random_seed_sequences.a
/usr/local/lib/libabsl_raw_hash_set.a
/usr/local/lib/libabsl_raw_logging_internal.a
/usr/local/lib/libabsl_spinlock_wait.a
/usr/local/lib/libabsl_stacktrace.a
/usr/local/lib/libabsl_status.a
/usr/local/lib/libabsl_statusor.a
/usr/local/lib/libabsl_str_format_internal.a
/usr/local/lib/libabsl_strerror.a
/usr/local/lib/libabsl_strings.a
/usr/local/lib/libabsl_strings_internal.a
/usr/local/lib/libabsl_symbolize.a
/usr/local/lib/libabsl_synchronization.a
/usr/local/lib/libabsl_throw_delegate.a
/usr/local/lib/libabsl_time.a
/usr/local/lib/libabsl_time_zone.a
/usr/local/lib/libaddress_sorting.a
/usr/local/lib/libcares.a
/usr/local/lib/libcrypto.a
/usr/local/lib/libgpr.a
/usr/local/lib/libgrpc++.a
/usr/local/lib/libgrpc++_reflection.a
/usr/local/lib/libgrpc.a
/usr/local/lib/libprotobuf.a
/usr/local/lib/libre2.a
/usr/local/lib/libssl.a
/usr/local/lib/libupb.a
/usr/local/lib/libz.a
VERBATIM
)

View File

@@ -1,22 +0,0 @@
# This is a connector between ucentral-client Makefile and larch-sonic platform CMake.
# When `plat.a` target is requested, CMake generates the underlying build system files
# (i.e. Makefile) and make is run there.
CMAKE_FILE := CMakeLists.txt
CMAKE_BUILD_DIR := build
MACHINE_ARCH := $(shell arch)
CMAKE_VERSION := 3.29.3
CMAKE_DIR := cmake-$(CMAKE_VERSION)-linux-$(MACHINE_ARCH)
CMAKE_BINARY := $(CMAKE_DIR)/bin/cmake
plat.a: $(CMAKE_BUILD_DIR)/Makefile
$(MAKE) -C $(<D) $@
# <D - directory part (i.e. path without file) of the first prerequisite
# @D - directory part of the target
$(CMAKE_BUILD_DIR)/Makefile: $(CMAKE_BINARY) $(CMAKE_FILE)
$(CMAKE_BINARY) -S $(dir $(CMAKE_FILE)) -B $(@D) -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug
$(CMAKE_BINARY):
wget -qO- --no-check-certificate "https://github.com/Kitware/CMake/releases/download/v$(CMAKE_VERSION)/cmake-$(CMAKE_VERSION)-linux-$(MACHINE_ARCH).tar.gz" | tar -xz

View File

@@ -1,14 +0,0 @@
# Find Protobuf
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using Protobuf ${Protobuf_VERSION}")
set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)
set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
# Find gRPC
find_package(gRPC CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")
set(_GRPC_GRPCPP gRPC::grpc++)
set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)

View File

@@ -1,143 +0,0 @@
#include <fdb.hpp>
#include <sai_redis.hpp>
#include <state.hpp>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis++.h>
#include <ucentral-platform.h>
#include <cstdint>
#include <cstdio> // std::snprintf
#include <iterator> // std:inserter
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using nlohmann::json;
namespace larch {
std::vector<plat_learned_mac_addr> get_learned_mac_addrs()
{
const auto bridge_mapping = sai::get_bridge_port_mapping();
const auto port_name_mapping = sai::get_port_name_mapping();
std::unordered_map<sai::object_id, std::uint16_t> vlan_cache;
std::vector<plat_learned_mac_addr> learned_mac_addrs;
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
do
{
constexpr std::string_view pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*";
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
for (const auto &key : keys)
{
plat_learned_mac_addr learned_entry{};
std::unordered_map<std::string, std::string> entry;
// Get port name
state->redis_asic->hgetall(
key,
std::inserter(entry, entry.begin()));
if (entry.at("SAI_FDB_ENTRY_ATTR_TYPE")
== "SAI_FDB_ENTRY_TYPE_STATIC")
continue;
const auto port_it = bridge_mapping.find(
entry.at("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID")
.substr(sai::oid_prefix.size()));
if (port_it == bridge_mapping.cend())
continue;
const auto interface_it =
port_name_mapping.find(port_it->second);
const std::string interface_name =
(interface_it != port_name_mapping.cend())
? interface_it->second
: port_it->second;
// Get VLAN ID
std::uint16_t vlan_id{};
const json fdb_json =
json::parse(key.substr(pattern.size() - 1));
if (fdb_json.contains("vlan"))
{
vlan_id = static_cast<std::uint16_t>(std::stoul(
fdb_json.at("vlan")
.template get<std::string>()));
}
else
{
if (!fdb_json.contains("bvid"))
continue;
std::string vlan_oid =
fdb_json.at("bvid")
.template get<std::string>()
.substr(sai::oid_prefix.size());
const auto vlan_it = vlan_cache.find(vlan_oid);
if (vlan_it != vlan_cache.cend())
{
// VLAN is found in cache, using it
vlan_id = vlan_it->second;
}
else
{
auto vlan_id_opt =
sai::get_vlan_by_oid(vlan_oid);
if (!vlan_id_opt)
continue;
vlan_id = *vlan_id_opt;
vlan_cache.try_emplace(
std::move(vlan_oid),
std::move(*vlan_id_opt));
}
}
std::snprintf(
learned_entry.port,
PORT_MAX_NAME_LEN,
"%s",
interface_name.c_str());
learned_entry.vid = vlan_id;
std::snprintf(
learned_entry.mac,
PLATFORM_MAC_STR_SIZE,
"%s",
fdb_json.at("mac")
.template get<std::string>()
.c_str());
learned_mac_addrs.push_back(learned_entry);
}
} while (cursor != 0);
return learned_mac_addrs;
}
} // namespace larch

View File

@@ -1,14 +0,0 @@
#ifndef LARCH_PLATFORM_FDB_HPP_
#define LARCH_PLATFORM_FDB_HPP_
#include <ucentral-platform.h>
#include <vector>
namespace larch {
std::vector<plat_learned_mac_addr> get_learned_mac_addrs();
} // namespace larch
#endif // !LARCH_PLATFORM_FDB_HPP_

View File

@@ -1,136 +0,0 @@
#include <igmp.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <string>
using nlohmann::json;
using namespace std;
namespace larch {
typedef enum {
GNMI_IGMP_VERSION_NA = 0,
GNMI_IGMP_VERSION_1 = 1,
GNMI_IGMP_VERSION_2 = 2,
GNMI_IGMP_VERSION_3 = 3
} gnmi_igmp_version_t;
static void disable_igmp_snooping(uint16_t vid)
{
gnmi_operation op;
const auto igmp_snooping_list
= gnmi_get ("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_TABLE/"
"CFG_L2MC_TABLE_LIST");
const json igmp_snooping_list_json = json::parse(igmp_snooping_list);
/* There are no IGMP-snoooping */
if (!igmp_snooping_list_json.contains("sonic-igmp-snooping:CFG_L2MC_TABLE_LIST"))
return;
/* Delete igmp-snooping config for specific VLAN. */
op.add_delete("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_TABLE/CFG_L2MC_TABLE_LIST[vlan-name=Vlan" + to_string(vid) + "]");
op.execute();
}
static void set_igmp_snooping(uint16_t vid, struct plat_igmp *igmp)
{
bool enabled = igmp->snooping_enabled || igmp->querier_enabled;
gnmi_igmp_version_t gnmi_igmp_version = GNMI_IGMP_VERSION_NA;
gnmi_operation op;
if (!enabled)
{
disable_igmp_snooping(vid);
return;
}
if (igmp->version == PLAT_IGMP_VERSION_1)
gnmi_igmp_version = GNMI_IGMP_VERSION_1;
else if (igmp->version == PLAT_IGMP_VERSION_2)
gnmi_igmp_version = GNMI_IGMP_VERSION_2;
else if (igmp->version == PLAT_IGMP_VERSION_3)
gnmi_igmp_version = GNMI_IGMP_VERSION_3;
else
{
UC_LOG_ERR("Failed IGMP version");
throw std::runtime_error{"Failed IGMP version"};
}
/* Config IGMP-snooping */
json igmp_snooping_list_json;
igmp_snooping_list_json["vlan-name"] = "Vlan" + to_string(vid);
igmp_snooping_list_json["enabled"] = igmp->snooping_enabled;
igmp_snooping_list_json["querier"] = igmp->querier_enabled;
if (igmp->querier_enabled)
{
igmp_snooping_list_json["query-interval"] = igmp->query_interval;
igmp_snooping_list_json["query-max-response-time"] = igmp->max_response_time;
igmp_snooping_list_json["last-member-query-interval"] = igmp->last_member_query_interval;
if (gnmi_igmp_version != GNMI_IGMP_VERSION_NA)
{
igmp_snooping_list_json["version"] = igmp->version;
}
}
if (igmp->snooping_enabled) {
igmp_snooping_list_json["fast-leave"] = igmp->fast_leave_enabled;
}
json add_igmp_snooping_list_json;
add_igmp_snooping_list_json["sonic-igmp-snooping:CFG_L2MC_TABLE_LIST"] = {igmp_snooping_list_json};
op.add_update("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_TABLE/CFG_L2MC_TABLE_LIST", add_igmp_snooping_list_json.dump());
op.execute();
}
static void set_igmp_static_groups(uint16_t vid, struct plat_igmp *igmp)
{
struct plat_ports_list *port_node = NULL;
size_t group_idx;
gnmi_operation op;
for (group_idx = 0; group_idx < igmp->num_groups; group_idx++)
{
const std::string ip_addr = addr_to_str(igmp->groups[group_idx].addr);
json igmp_static_group_json;
igmp_static_group_json["vlan-name"] = "Vlan" + to_string(vid);
igmp_static_group_json["group-addr"] = ip_addr;
json out_intf_list_json = json::array();
UCENTRAL_LIST_FOR_EACH_MEMBER(port_node, &igmp->groups[group_idx].egress_ports_list)
{
out_intf_list_json.push_back(port_node->name);
}
igmp_static_group_json["out-intf"] = out_intf_list_json;
json add_igmp_static_group_json;
add_igmp_static_group_json["sonic-igmp-snooping:CFG_L2MC_STATIC_GROUP_TABLE_LIST"] = {igmp_static_group_json};
op.add_update("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_STATIC_GROUP_TABLE/CFG_L2MC_STATIC_GROUP_TABLE_LIST", add_igmp_static_group_json.dump());
}
op.execute();
}
void apply_igmp_config(uint16_t vid, struct plat_igmp *igmp)
{
set_igmp_snooping(vid, igmp);
if (igmp->num_groups > 0)
{
set_igmp_static_groups(vid, igmp);
}
}
}

View File

@@ -1,12 +0,0 @@
#ifndef LARCH_PLATFORM_IGMP_HPP_
#define LARCH_PLATFORM_IGMP_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_igmp_config(uint16_t vid, struct plat_igmp *igmp);
}
#endif // !LARCH_PLATFORM_IGMP_HPP_

View File

@@ -1,307 +0,0 @@
#include <fdb.hpp>
#include <metrics.hpp>
#include <port.hpp>
#include <route.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <sys/sysinfo.h>
#include <array>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <iomanip> // std::setw
#include <iterator> // std::begin, std::size
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility> // std::move
using nlohmann::json;
namespace larch {
namespace {
const std::string metrics_config_path =
"/var/lib/ucentral/metrics_cfg.json";
}
static plat_system_info get_system_info()
{
plat_system_info system_info{};
// Get load average
std::array<double, std::size(system_info.load_average)> load_average{};
if (getloadavg(load_average.data(), load_average.size()) < 0)
{
UC_LOG_ERR("Failed to get load average");
}
else
{
for (double &elem : load_average)
elem /= 100.0;
std::copy(
load_average.cbegin(),
load_average.cend(),
std::begin(system_info.load_average));
}
// Get RAM cached
std::ifstream is{"/proc/meminfo"};
if (!is)
{
UC_LOG_ERR("Failed to get memory info");
}
else
{
std::string line;
std::uint64_t cached = 0;
bool found = false;
while (std::getline(is, line))
{
if (std::sscanf(line.c_str(), "Cached:%lu", &cached)
== 1)
{
system_info.ram_cached = cached * 1024;
found = true;
}
}
if (!found)
{
UC_LOG_ERR("Can't find Cached entry in /proc/meminfo");
}
}
// Get other system information
struct sysinfo sys_info = {};
if (sysinfo(&sys_info) < 0)
{
UC_LOG_ERR(
"Failed to get system info: %s",
std::strerror(errno));
}
else
{
system_info.localtime =
static_cast<std::uint64_t>(std::time(nullptr));
system_info.uptime = sys_info.uptime;
system_info.ram_buffered =
sys_info.bufferram * sys_info.mem_unit;
system_info.ram_free =
(sys_info.freeram + sys_info.freeswap) * sys_info.mem_unit;
system_info.ram_total = sys_info.totalram * sys_info.mem_unit;
}
return system_info;
}
std::pair<plat_state_info, state_data> get_state_info()
{
plat_state_info state_info{};
state_data data{};
state_info.system_info = get_system_info();
// Get port info
try
{
data.port_info = get_port_info();
state_info.port_info = data.port_info.data();
state_info.port_info_count = data.port_info.size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get port info: %s", ex.what());
}
// Get learned MAC addresses
try
{
data.learned_mac_addrs = get_learned_mac_addrs();
state_info.learned_mac_list = data.learned_mac_addrs.data();
state_info.learned_mac_list_size =
data.learned_mac_addrs.size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR(
"Failed to get learned MAC addresses: %s",
ex.what());
}
// Get GW addresses
try
{
data.gw_addresses = get_gw_addresses();
state_info.gw_addr_list = data.gw_addresses.data();
state_info.gw_addr_list_size = data.gw_addresses.size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get GW addresses: %s", ex.what());
}
return {std::move(state_info), std::move(data)};
}
void save_metrics_config(const plat_metrics_cfg *cfg)
{
json metrics_cfg;
json &telemetry_cfg = metrics_cfg["telemetry"];
telemetry_cfg["enabled"] = static_cast<bool>(cfg->telemetry.enabled);
telemetry_cfg["interval"] = cfg->telemetry.interval;
json &healthcheck_cfg = metrics_cfg["healthcheck"];
healthcheck_cfg["enabled"] =
static_cast<bool>(cfg->healthcheck.enabled);
healthcheck_cfg["interval"] = cfg->healthcheck.interval;
json &state_cfg = metrics_cfg["state"];
state_cfg["enabled"] = static_cast<bool>(cfg->state.enabled);
state_cfg["lldp_enabled"] = static_cast<bool>(cfg->state.lldp_enabled);
state_cfg["clients_enabled"] =
static_cast<bool>(cfg->state.clients_enabled);
state_cfg["interval"] = cfg->state.interval;
state_cfg["max_mac_count"] = cfg->state.max_mac_count;
state_cfg["public_ip_lookup"] = cfg->state.public_ip_lookup;
std::ofstream os{metrics_config_path};
os << std::setw(4) << metrics_cfg << std::endl;
if (!os)
{
throw std::runtime_error{
"Failed to write metrics config to the file"};
}
}
void load_metrics_config(plat_metrics_cfg *cfg)
{
std::ifstream is{metrics_config_path};
// Metrics configuration doesn't exist yet, return silently without any
// error
if (!is.is_open())
return;
json metrics_cfg = json::parse(is);
if (metrics_cfg.contains("telemetry"))
{
const json &telemetry_cfg = metrics_cfg.at("telemetry");
cfg->telemetry.enabled =
telemetry_cfg.at("enabled").template get<bool>();
cfg->telemetry.interval =
telemetry_cfg.at("interval").template get<std::size_t>();
}
if (metrics_cfg.contains("healthcheck"))
{
const json &healthcheck_cfg = metrics_cfg.at("healthcheck");
cfg->healthcheck.enabled =
healthcheck_cfg.at("enabled").template get<bool>();
cfg->healthcheck.interval =
healthcheck_cfg.at("interval").template get<std::size_t>();
}
if (metrics_cfg.contains("state"))
{
const json &state_cfg = metrics_cfg.at("state");
cfg->state.enabled =
state_cfg.at("enabled").template get<bool>();
cfg->state.lldp_enabled =
state_cfg.at("lldp_enabled").template get<bool>();
cfg->state.clients_enabled =
state_cfg.at("clients_enabled").template get<bool>();
cfg->state.interval =
state_cfg.at("interval").template get<std::size_t>();
cfg->state.max_mac_count =
state_cfg.at("max_mac_count").template get<std::size_t>();
std::strncpy(
cfg->state.public_ip_lookup,
state_cfg.at("public_ip_lookup")
.template get<std::string>()
.c_str(),
std::size(cfg->state.public_ip_lookup) - 1);
}
}
periodic::~periodic()
{
if (thread_ && thread_->joinable())
thread_->join();
}
void periodic::start(
std::function<void()> callback,
std::chrono::seconds period)
{
if (thread_)
stop();
callback_ = std::move(callback);
period_ = std::move(period);
thread_ =
std::make_unique<std::thread>(std::bind(&periodic::worker, this));
}
void periodic::stop()
{
if (!thread_)
return;
{
std::scoped_lock lk{mut_};
stop_signal_ = true;
}
cv_.notify_one();
if (thread_->joinable())
thread_->join();
thread_.reset();
stop_signal_ = false;
}
void periodic::worker()
{
std::unique_lock lk{mut_};
while (!stop_signal_)
{
try
{
callback_();
}
catch (const std::exception &ex)
{
UC_LOG_ERR(
"Error occurred during periodic task execution: %s",
ex.what());
}
cv_.wait_for(lk, period_, [this] { return stop_signal_; });
}
}
} // namespace larch

View File

@@ -1,62 +0,0 @@
#ifndef LARCH_PLATFORM_METRICS_HPP_
#define LARCH_PLATFORM_METRICS_HPP_
#include <state.hpp>
#include <ucentral-platform.h>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <utility> // std::pair
#include <vector>
namespace larch {
struct state_data {
std::vector<plat_port_info> port_info;
std::vector<plat_learned_mac_addr> learned_mac_addrs;
std::vector<plat_gw_address> gw_addresses;
};
/**
* @brief Get state information.
*
* @return A pair of @c plat_state_info and @c state_data. The latter is used as
* an actual storage for all the gathered information, while the former
* references the data in it. Pay attention to the fact that @c state_data must
* outlive @c plat_state_info.
*/
std::pair<plat_state_info, state_data> get_state_info();
void save_metrics_config(const plat_metrics_cfg *cfg);
void load_metrics_config(plat_metrics_cfg *cfg);
class periodic {
public:
periodic() = default;
~periodic();
void start(std::function<void()> callback, std::chrono::seconds period);
void stop();
protected:
void worker();
std::unique_ptr<std::thread> thread_;
std::condition_variable cv_;
std::mutex mut_;
bool stop_signal_ = false;
std::chrono::seconds period_{};
std::function<void()> callback_;
};
} // namespace larch
#endif // !LARCH_PLATFORM_METRICS_HPP_

View File

@@ -1,631 +0,0 @@
#include <metrics.hpp>
#include <port.hpp>
#include <route.hpp>
#include <services.hpp>
#include <state.hpp>
#include <stp.hpp>
#include <syslog.hpp>
#include <utils.hpp>
#include <vlan.hpp>
#include <gnmi.grpc.pb.h>
#include <gnmi.pb.h>
#include <grpc++/create_channel.h>
#include <grpc++/security/credentials.h>
#include <grpc/grpc.h>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis++.h>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <router-utils.h>
#include <cerrno>
#include <chrono>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility> // std::move
#include <sys/types.h>
#include <sys/wait.h>
#define UNUSED_PARAM(param) (void)((param))
#define RTTY_SESS_MAX (10)
using nlohmann::json;
namespace {
const std::string config_id_path = "/var/lib/ucentral/saved_config_id";
}
int plat_init(void)
{
using namespace larch;
state = std::make_unique<platform_state>();
auto verifier = grpc::experimental::ExternalCertificateVerifier::Create<certificate_verifier>();
grpc::experimental::TlsChannelCredentialsOptions options;
options.set_verify_server_certs(false);
options.set_certificate_verifier(verifier);
options.set_check_call_host(false);
auto credentials = grpc::experimental::TlsCredentials(options);
state->channel = grpc::CreateChannel("127.0.0.1:8080", credentials);
state->gnmi_stub = gnmi::gNMI::NewStub(state->channel);
// created new channel for gnoi
state->system_gnoi_stub = gnoi::system::System::NewStub(grpc::CreateChannel("127.0.0.1:8080", credentials));
state->stub_gnoi_sonic = gnoi::sonic::SonicService::NewStub(grpc::CreateChannel("127.0.0.1:8080", credentials));
state->telemetry_periodic = std::make_unique<periodic>();
state->state_periodic = std::make_unique<periodic>();
state->health_periodic = std::make_unique<periodic>();
state->redis_asic = std::make_unique<sw::redis::Redis>("tcp://127.0.0.1:6379/1");
state->redis_counters = std::make_unique<sw::redis::Redis>("tcp://127.0.0.1:6379/2");
/*
* Workaround to fix the issue when ucentral-client starts too early
* (before gNMI container or before ports are up)
*/
UC_LOG_INFO("Trying to get initial port list...");
std::vector<port> ports;
while (true)
{
try
{
ports = get_port_list();
if (ports.empty())
{
UC_LOG_DBG("Port list is empty");
}
else
{
break;
}
}
catch (const std::exception &ex)
{
UC_LOG_DBG(
"Failed to get initial port list: %s",
ex.what());
}
UC_LOG_DBG("Retrying in 10 seconds...");
std::this_thread::sleep_for(std::chrono::seconds{10});
}
UC_LOG_INFO("Successfully got the port list, continuing platform "
"initialization");
try
{
/*
* Get the state of interfaces addresses
*/
const plat_ipv4 no_address{false};
for (port &p : get_port_list())
{
const auto addresses = get_port_addresses(p);
state->interfaces_addrs.push_back(
addresses.empty() ? no_address : addresses[0]);
}
/*
* Initialize the router
*/
ucentral_router_fib_db_free(&state->router);
auto routes = get_routes(0);
if (ucentral_router_fib_db_alloc(&state->router, routes.size())
!= 0)
{
UC_LOG_CRIT("Failed to allocate FIB DB");
return -1;
}
for (auto &node : routes)
{
if (ucentral_router_fib_db_append(&state->router, &node)
!= 0)
{
UC_LOG_CRIT("Failed to append entry to FIB DB");
return -1;
}
}
}
catch (const std::exception &ex)
{
UC_LOG_CRIT("Platform initialization failed: %s", ex.what());
return -1;
}
return 0;
}
int plat_info_get(struct plat_platform_info *info)
{
using namespace larch;
try
{
const json metadata_json =
json::parse(
gnmi_get("/sonic-device_metadata:sonic-device_metadata/"
"DEVICE_METADATA/localhost"))
.at("sonic-device_metadata:localhost");
auto copy_from_json =
[](const json &obj, char *dest, std::size_t dest_size) {
std::strncpy(
dest,
obj.template get<std::string>().c_str(),
dest_size > 0 ? dest_size - 1 : 0);
};
copy_from_json(
metadata_json.at("platform"),
info->platform,
std::size(info->platform));
copy_from_json(
metadata_json.at("hwsku"),
info->hwsku,
std::size(info->hwsku));
copy_from_json(
metadata_json.at("mac"),
info->mac,
std::size(info->mac));
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get device metadata: %s", ex.what());
return -1;
}
return 0;
}
int plat_reboot(void)
{
grpc::Status status;
gnoi::system::RebootResponse gres;
gnoi::system::RebootRequest greq;
grpc::ClientContext context;
status = (larch::state->system_gnoi_stub)->Reboot(&context, greq, &gres);
if (!status.ok()) {
UC_LOG_ERR("Request failed");
UC_LOG_ERR("Code: %d", status.error_code());
return 1;
}
return 0;
}
int plat_config_apply(struct plat_cfg *cfg, uint32_t id)
{
using namespace larch;
UNUSED_PARAM(id);
try
{
apply_vlan_config(cfg);
apply_port_config(cfg);
apply_route_config(cfg);
apply_stp_config(cfg);
apply_services_config(cfg);
apply_vlan_ipv4_config(cfg);
/* apply_syslog_config() call must always be the last one */
apply_syslog_config(cfg->log_cfg, cfg->log_cfg_cnt);
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to apply config: %s", ex.what());
return -1;
}
return 0;
}
int plat_config_save(uint64_t id)
{
// TO-DO: actually save the config, not only config id
std::ofstream os{config_id_path};
if (!os)
{
UC_LOG_ERR(
"Failed to save config id - can't open the file: %s",
std::strerror(errno));
return -1;
}
os << id << std::endl;
return 0;
}
int plat_config_restore(void)
{
return 0;
}
int plat_metrics_save(const struct plat_metrics_cfg *cfg)
{
try
{
larch::save_metrics_config(cfg);
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to save metrics config: %s", ex.what());
return -1;
}
return 0;
}
int plat_metrics_restore(struct plat_metrics_cfg *cfg)
{
try
{
larch::load_metrics_config(cfg);
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to load metrics config: %s", ex.what());
return -1;
}
return 0;
}
int plat_saved_config_id_get(uint64_t *id)
{
std::ifstream is{config_id_path};
if (!is)
{
UC_LOG_ERR(
"Failed to get saved config id - can't open the file: %s",
std::strerror(errno));
return -1;
}
is >> *id;
if (!is.good())
{
UC_LOG_ERR("Failed to get saved config id - read failed");
return -1;
}
return 0;
}
void plat_config_destroy(struct plat_cfg *cfg)
{
UNUSED_PARAM(cfg);
}
int plat_factory_default(void)
{
return 0;
}
int plat_rtty(struct plat_rtty_cfg *rtty_cfg)
{
static pid_t child[RTTY_SESS_MAX];
int n, i, e;
/* wait the dead children */
for (i = 0; i < RTTY_SESS_MAX;) {
n = 0;
if (child[i] > 0) {
while ((n = waitpid(child[i], 0, WNOHANG)) < 0 && errno == EINTR);
}
if (n <= 0) {
++i;
} else {
if (RTTY_SESS_MAX > 1)
memmove(&child[i], &child[i+1], (RTTY_SESS_MAX-i-1)*sizeof(pid_t));
child[RTTY_SESS_MAX - 1] = -1;
}
}
/* find a place for a new session */
for (i = 0; i < RTTY_SESS_MAX && child[i] > 0; ++i);
/* if there are RTTY_SESS_MAX sessions, kill the oldest */
if (i == RTTY_SESS_MAX) {
if (child[0] <= 0) {
UC_LOG_CRIT("child[0]==%jd", (intmax_t)child[0]);
} else {
if (kill(child[0], SIGKILL)) {
UC_LOG_CRIT("kill failed: %s", strerror(errno));
} else {
while ((n = waitpid(child[0], 0, 0)) < 0 && errno == EINTR);
if (n < 0)
UC_LOG_CRIT("waitpid failed: %s", strerror(errno));
}
if (RTTY_SESS_MAX > 1)
memmove(&child[0], &child[1], (RTTY_SESS_MAX - 1) * sizeof(pid_t));
}
i = RTTY_SESS_MAX - 1;
}
child[i] = fork();
if (!child[i]) {
char argv[][128] = {
"--id=",
"--host=",
"--port=",
"--token="
};
setsid();
strcat(argv[0], rtty_cfg->id);
strcat(argv[1], rtty_cfg->server);
sprintf(argv[2], "--port=%u", rtty_cfg->port);
strcat(argv[3], rtty_cfg->token);
execl("/usr/local/bin/rtty", "rtty", argv[0], argv[1], argv[2], argv[3], "-d Larch Switch device", "-v", "-s", NULL);
e = errno;
UC_LOG_DBG("execv failed %d\n", e);
/* If we got to this line, that means execl failed, and
* currently, due to simple design (fork/exec), it's impossible
* to notify <main> process, that forked child failed to execl.
* TBD: notify about execl fail.
*/
_exit(e);
}
if (child[i] < (pid_t)0) {
return -1;
}
return 0;
}
int plat_upgrade(char *uri, char *signature)
{
UNUSED_PARAM(signature);
UNUSED_PARAM(uri);
return 0;
}
char *plat_log_pop(void)
{
return NULL;
}
void plat_log_flush(void)
{
}
char *plat_log_pop_concatenate(void)
{
return NULL;
}
int plat_event_subscribe(const struct plat_event_callbacks *cbs)
{
UNUSED_PARAM(cbs);
return 0;
}
void plat_event_unsubscribe(void)
{
}
void plat_health_poll(void (*cb)(struct plat_health_info *), int period_sec)
{
using namespace larch;
try
{
state->health_periodic->stop();
state->health_periodic->start(
[cb] {
plat_health_info health_info{100};
cb(&health_info);
},
std::chrono::seconds{period_sec});
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to start health poll: %s", ex.what());
}
}
void plat_health_poll_stop(void)
{
using namespace larch;
try
{
state->health_periodic->stop();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to stop health poll: %s", ex.what());
}
}
void plat_telemetry_poll(void (*cb)(struct plat_state_info *), int period_sec)
{
using namespace larch;
try
{
state->telemetry_periodic->stop();
state->telemetry_periodic->start(
[cb] {
auto [state_info, data] = get_state_info();
cb(&state_info);
},
std::chrono::seconds{period_sec});
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to start telemetry poll: %s", ex.what());
}
}
void plat_telemetry_poll_stop(void)
{
using namespace larch;
try
{
state->telemetry_periodic->stop();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to stop telemetry poll: %s", ex.what());
}
}
void plat_state_poll(void (*cb)(struct plat_state_info *), int period_sec)
{
using namespace larch;
try
{
state->state_periodic->stop();
state->state_periodic->start(
[cb] {
auto [state_info, data] = get_state_info();
cb(&state_info);
},
std::chrono::seconds{period_sec});
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to start state poll: %s", ex.what());
}
}
void plat_state_poll_stop(void)
{
using namespace larch;
try
{
state->state_periodic->stop();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to stop state poll: %s", ex.what());
}
}
void plat_upgrade_poll(int (*cb)(struct plat_upgrade_info *), int period_sec)
{
UNUSED_PARAM(period_sec);
UNUSED_PARAM(cb);
}
void plat_upgrade_poll_stop(void)
{
}
int plat_run_script(struct plat_run_script *)
{
return 0;
}
int plat_port_list_get(uint16_t list_size, struct plat_ports_list *ports)
{
try
{
const auto port_list = larch::get_port_list();
if (port_list.size() < list_size)
{
UC_LOG_ERR(
"Too much ports requested (requested %hu, while "
"only %zu available)",
list_size,
port_list.size());
return -1;
}
auto it = port_list.cbegin();
for (plat_ports_list *node = ports; node; node = node->next)
{
std::strncpy(
node->name,
it++->name.c_str(),
sizeof(node->name) - 1);
}
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get list of ports: %s", ex.what());
return -1;
}
return 0;
}
int plat_port_num_get(uint16_t *num_of_active_ports)
{
try
{
*num_of_active_ports = larch::get_port_list().size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get count of ports: %s", ex.what());
return -1;
}
return 0;
}
int plat_running_img_name_get(char *str, size_t str_max_len)
{
UNUSED_PARAM(str_max_len);
UNUSED_PARAM(str);
return 0;
}
int plat_revision_get(char *str, size_t str_max_len)
{
UNUSED_PARAM(str_max_len);
UNUSED_PARAM(str);
return 0;
}
int plat_reboot_cause_get(struct plat_reboot_cause *cause)
{
UNUSED_PARAM(cause);
return 0;
}
int plat_diagnostic(char *res_path)
{
UNUSED_PARAM(res_path);
return 0;
}

View File

@@ -1,528 +0,0 @@
#include <port.hpp>
#include <state.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#include <bitmap.h>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <arpa/inet.h>
#include <algorithm> // std::find_if
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio> // std::snprintf, std::sscanf
#include <cstring>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility> // std::move
#include <vector>
using nlohmann::json;
namespace larch {
std::vector<port> get_port_list()
{
const json port_list_json =
json::parse(gnmi_get("/sonic-port:sonic-port/PORT/PORT_LIST"));
std::vector<port> port_list;
for (const auto port_json :
port_list_json.value("sonic-port:PORT_LIST", json::array()))
{
port &p = port_list.emplace_back();
p.name = port_json.at("name").template get<std::string>();
}
return port_list;
}
static bool get_port_oper_status(const std::string &port_name)
{
std::string port_status_data;
try
{
port_status_data = gnmi_get(
"/openconfig-interfaces:interfaces/interface[name="
+ port_name + "]/state/oper-status");
}
catch (const gnmi_exception &ex)
{
// For some reason there's no oper-status field in the gNMI
// response when carrier is down
return false;
}
const json port_status_json = json::parse(port_status_data);
const std::string port_status_str =
port_status_json.at("openconfig-interfaces:oper-status")
.template get<std::string>();
if (port_status_str == "UP")
return true;
else if (port_status_str == "DOWN")
return false;
else
{
UC_LOG_ERR(
"Unknown port oper status: %s",
port_status_str.c_str());
throw std::runtime_error{
"Unknown oper status: " + port_status_str};
}
}
static void set_port_admin_state(const std::string &port_name, bool state)
{
json port_state_json;
port_state_json["openconfig-interfaces:config"]["enabled"] = state;
gnmi_set(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/config",
port_state_json.dump());
}
static std::uint32_t get_port_speed(const std::string &port_name)
{
const json port_speed_json = json::parse(gnmi_get(
"/sonic-port:sonic-port/PORT/PORT_LIST[name=" + port_name
+ "]/speed"));
return port_speed_json["sonic-port:speed"]
.template get<std::uint32_t>();
}
static void set_port_speed(const std::string &port_name, std::uint32_t speed)
{
auto speed_to_num = [](std::uint32_t speed) -> std::uint32_t {
switch (speed)
{
case UCENTRAL_PORT_SPEED_10_E:
return 10;
case UCENTRAL_PORT_SPEED_100_E:
return 100;
case UCENTRAL_PORT_SPEED_1000_E:
return 1000;
case UCENTRAL_PORT_SPEED_2500_E:
return 2500;
case UCENTRAL_PORT_SPEED_5000_E:
return 5000;
case UCENTRAL_PORT_SPEED_10000_E:
return 10000;
case UCENTRAL_PORT_SPEED_25000_E:
return 25000;
case UCENTRAL_PORT_SPEED_40000_E:
return 40000;
case UCENTRAL_PORT_SPEED_100000_E:
return 100000;
default:
{
UC_LOG_ERR("Unknown port speed");
throw std::runtime_error{"Unknown port speed"};
}
}
};
json port_speed_json;
port_speed_json["name"] = port_name;
port_speed_json["speed"] = speed_to_num(speed);
json set_port_speed_json;
set_port_speed_json["sonic-port:PORT_LIST"] = {port_speed_json};
gnmi_set(
"/sonic-port:sonic-port/PORT/PORT_LIST[name=" + port_name + "]",
set_port_speed_json.dump());
}
static void set_port_mtu(const std::string &port_name, std::uint16_t mtu)
{
json port_mtu_json;
port_mtu_json["name"] = port_name;
port_mtu_json["mtu"] = mtu;
json set_port_mtu_json;
set_port_mtu_json["sonic-port:PORT_LIST"] = {port_mtu_json};
gnmi_set(
"/sonic-port:sonic-port/PORT/PORT_LIST[name=" + port_name + "]",
set_port_mtu_json.dump());
}
static std::unordered_map<std::string, std::uint64_t>
get_port_counters(const std::string &port_name)
{
const json port_counters_json = json::parse(gnmi_get(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/state/counters"));
std::unordered_map<std::string, std::uint64_t> counters;
if (!port_counters_json.contains("openconfig-interfaces:counters"))
return counters;
for (const auto &item :
port_counters_json["openconfig-interfaces:counters"].items())
{
counters[item.key()] =
std::stoull(item.value().template get<std::string>());
}
return counters;
}
static std::optional<plat_port_lldp_peer_info>
get_lldp_peer_info(const std::string &port_name)
{
/*
* Actually, more specific YANG path should be used here
* (/openconfig-lldp:lldp/interfaces/interface[name=<interface>]/neighbors/neighbor[id=<interface>])
* but for some reason gNMI response for this path is empty, so the
* workaround is to make more generic request and filter the response
* and find necessary data.
*/
const json lldp_json = json::parse(gnmi_get(
"/openconfig-lldp:lldp/interfaces/interface[name=" + port_name
+ "]"));
const auto &neighbors = lldp_json.at("openconfig-lldp:interface")
.at(0)
.at("neighbors")
.at("neighbor");
const auto neighbor_it = std::find_if(
neighbors.cbegin(),
neighbors.cend(),
[&port_name](const auto &neighbor) {
return neighbor.at("id").template get<std::string>()
== port_name;
});
if (neighbor_it == neighbors.cend())
{
throw std::runtime_error{"Failed to find LLDP neighbor"};
}
if (!neighbor_it->contains("capabilities"))
{
return std::nullopt;
}
plat_port_lldp_peer_info peer_info{};
for (const auto &cap : neighbor_it->at("capabilities").at("capability"))
{
const std::string name =
cap.at("name").template get<std::string>();
const bool enabled =
cap.at("state").at("enabled").template get<bool>();
if (name == "openconfig-lldp-types:MAC_BRIDGE")
peer_info.capabilities.is_bridge = enabled;
else if (name == "openconfig-lldp-types:ROUTER")
peer_info.capabilities.is_router = enabled;
else if (name == "openconfig-lldp-types:WLAN_ACCESS_POINT")
peer_info.capabilities.is_wlan_ap = enabled;
else if (name == "openconfig-lldp-types:STATION_ONLY")
peer_info.capabilities.is_station = enabled;
}
const json &state = neighbor_it->at("state");
std::strncpy(
peer_info.name,
state.at("system-name").template get<std::string>().c_str(),
std::size(peer_info.name) - 1);
std::strncpy(
peer_info.description,
state.at("system-description").template get<std::string>().c_str(),
std::size(peer_info.description) - 1);
std::strncpy(
peer_info.mac,
state.at("chassis-id").template get<std::string>().c_str(),
std::size(peer_info.mac) - 1);
std::strncpy(
peer_info.port,
neighbor_it->at("id").template get<std::string>().c_str(),
std::size(peer_info.port) - 1);
// Parse management addresses
const auto addresses = split_string(
state.at("management-address").template get<std::string>(),
",");
for (std::size_t i = 0; i < UCENTRAL_PORT_LLDP_PEER_INFO_MAX_MGMT_IPS;
++i)
{
if (i >= addresses.size())
break;
const char *address = addresses[i].c_str();
// Verify that retrieved address is either valid IPv4 or IPv6
// address. If so - copy it to peer_info.
bool success = false;
std::array<unsigned char, sizeof(in6_addr)> addr_buf{};
if (inet_pton(AF_INET, address, addr_buf.data()) == 1)
success = true;
else if (inet_pton(AF_INET6, address, addr_buf.data()) == 1)
success = true;
if (success)
std::strncpy(
peer_info.mgmt_ips[i],
address,
INET6_ADDRSTRLEN);
}
return peer_info;
}
std::vector<plat_ipv4> get_port_addresses(const port &p)
{
// TO-DO: should gnmi_exception be caught (this would mean that there're
// no addresses assigned to interface)?
const json addresses_json = json::parse(gnmi_get(
"/openconfig-interfaces:interfaces/interface[name=" + p.name
+ "]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/"
"addresses"));
if (!addresses_json.contains("openconfig-if-ip:addresses"))
return {};
std::vector<plat_ipv4> addresses;
for (const auto &address_json :
addresses_json.at("openconfig-if-ip:addresses")
.value("address", json::array()))
{
const json &config_json = address_json.at("config");
const std::string address_str =
config_json.at("ip").template get<std::string>();
plat_ipv4 address{};
if (inet_pton(AF_INET, address_str.c_str(), &address.subnet)
!= 1)
{
UC_LOG_ERR(
"Failed to parse interface IP address: %s",
address_str.c_str());
continue;
}
address.subnet_len = config_json.at("prefix-length")
.template get<std::int32_t>();
if (address.subnet_len < 0 || address.subnet_len > 32)
{
UC_LOG_ERR(
"Incorrect subnet length: %d (address %s)",
address.subnet_len,
address_str.c_str());
continue;
}
address.exist = true;
addresses.push_back(std::move(address));
}
return addresses;
}
static void
add_port_address(const std::string &port_name, const plat_ipv4 &address)
{
const std::string addr_str = addr_to_str(address.subnet);
json address_json;
address_json["ip"] = addr_str;
address_json["config"]["ip"] = addr_str;
address_json["config"]["prefix-length"] = address.subnet_len;
json port_json;
port_json["index"] = 0;
port_json["openconfig-if-ip:ipv4"]["addresses"]["address"] = {
address_json};
json add_port_json;
add_port_json["openconfig-interfaces:subinterface"] = {port_json};
gnmi_set(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/subinterfaces/subinterface",
add_port_json.dump());
}
static void
delete_port_address(const std::string &port_name, const plat_ipv4 &address)
{
const std::string addr_str = addr_to_str(address.subnet);
gnmi_operation op;
op.add_delete(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/"
"addresses/address[ip="
+ addr_str + "]");
op.execute();
}
void apply_port_config(plat_cfg *cfg)
{
std::size_t i = 0;
BITMAP_FOR_EACH_BIT_SET(i, cfg->ports_to_cfg, MAX_NUM_OF_PORTS)
{
const plat_port &port = cfg->ports[i];
const std::string port_name = "Ethernet" + std::to_string(i);
const bool admin_state = port.state == UCENTRAL_PORT_ENABLED_E;
set_port_admin_state(port_name, admin_state);
if (admin_state)
{
set_port_speed(port_name, port.speed);
set_port_mtu(
port_name,
cfg->jumbo_frames ? 9216 : 1500);
}
/*
* Configure the interface address
*/
const plat_ipv4 &address = cfg->portsl2[i].ipv4;
plat_ipv4 &port_addr = state->interfaces_addrs.at(i);
if (address.exist)
{
if (!port_addr.exist
|| port_addr.subnet.s_addr != address.subnet.s_addr
|| port_addr.subnet_len != address.subnet_len)
{
if (port_addr.exist)
{
delete_port_address(
port_name,
port_addr);
}
add_port_address(port_name, address);
port_addr = address;
}
}
else if (port_addr.exist)
{
delete_port_address(port_name, port_addr);
port_addr = plat_ipv4{false};
}
}
}
std::vector<plat_port_info> get_port_info()
{
std::vector<port> ports = get_port_list();
std::vector<plat_port_info> ports_info(ports.size());
std::size_t i = 0;
for (auto &port_info : ports_info)
{
const std::string &port_name = ports[i++].name;
std::snprintf(
port_info.name,
PORT_MAX_NAME_LEN,
"%s",
port_name.c_str());
port_info.speed = get_port_speed(port_name);
port_info.duplex = true;
port_info.carrier_up = get_port_oper_status(port_name);
// Get port counters
std::unordered_map<std::string, std::uint64_t> counters;
try
{
counters = get_port_counters(port_name);
}
catch (const gnmi_exception &ex)
{
UC_LOG_ERR(
"Couldn't get counters for port %s: %s",
port_name.c_str(),
ex.what());
}
auto get_counter =
[&counters](const std::string &counter) -> std::uint64_t {
const auto it = counters.find(counter);
return it != counters.cend() ? it->second : 0;
};
auto &stats = port_info.stats;
stats.collisions = 0;
stats.multicast = 0;
stats.rx_bytes = get_counter("in-octets");
stats.rx_dropped = get_counter("in-discards");
stats.rx_error = get_counter("in-errors");
stats.rx_packets = get_counter("in-unicast-pkts")
+ get_counter("in-multicast-pkts")
+ get_counter("in-broadcast-pkts");
stats.tx_bytes = get_counter("out-octets");
stats.tx_dropped = get_counter("out-discards");
stats.tx_error = get_counter("out-errors");
stats.tx_packets = get_counter("out-unicast-pkts")
+ get_counter("out-multicast-pkts")
+ get_counter("out-broadcast-pkts");
try
{
auto peer_info_opt = get_lldp_peer_info(port_name);
if (peer_info_opt)
{
port_info.lldp_peer_info =
std::move(*peer_info_opt);
port_info.has_lldp_peer_info = 1;
}
}
catch (const std::exception &ex)
{
UC_LOG_DBG(
"Couldn't get LLDP peer info: %s",
ex.what());
}
}
return ports_info;
}
} // namespace larch

View File

@@ -1,26 +0,0 @@
#ifndef LARCH_PLATFORM_PORT_HPP_
#define LARCH_PLATFORM_PORT_HPP_
#include <ucentral-platform.h>
#include <cstddef>
#include <string>
#include <vector>
namespace larch {
struct port {
std::string name;
};
std::vector<port> get_port_list();
std::vector<plat_ipv4> get_port_addresses(const port &p);
void apply_port_config(plat_cfg *cfg);
std::vector<plat_port_info> get_port_info();
} // namespace larch
#endif // !LARCH_PLATFORM_PORT_HPP_

View File

@@ -1,27 +0,0 @@
file(GLOB proto_files CONFIGURE_DEPENDS "*.proto")
add_library(proto-objects OBJECT ${proto_files})
target_link_libraries(proto-objects PUBLIC protobuf::libprotobuf gRPC::grpc++)
set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(PROTO_IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}")
target_include_directories(proto-objects PUBLIC "$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>")
file(MAKE_DIRECTORY ${PROTO_BINARY_DIR})
protobuf_generate(
TARGET proto-objects
IMPORT_DIRS ${PROTO_IMPORT_DIRS}
PROTOC_OUT_DIR "${PROTO_BINARY_DIR}"
)
protobuf_generate(
TARGET proto-objects
LANGUAGE grpc
GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc
PLUGIN "protoc-gen-grpc=\$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
IMPORT_DIRS ${PROTO_IMPORT_DIRS}
PROTOC_OUT_DIR "${PROTO_BINARY_DIR}"
)

View File

@@ -1,49 +0,0 @@
//
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
package gnoi.common;
import "types.proto";
option go_package = "github.com/openconfig/gnoi/common";
// RemoteDownload defines the details for a device to initiate a file transfer
// from or to a remote location.
message RemoteDownload {
// The path information containing where to download the data from or to.
// For HTTP(S), this will be the URL (i.e. foo.com/file.tbz2).
// For SFTP and SCP, this will be the address:/path/to/file
// (i.e. host.foo.com:/bar/baz).
string path = 1;
enum Protocol {
UNKNOWN = 0;
SFTP = 1;
HTTP = 2;
HTTPS = 3;
SCP = 4;
}
Protocol protocol = 2;
types.Credentials credentials = 3;
// Optional source address used to initiate connections from the device.
// It can be either an IPv4 address or an IPv6 address, depending on the
// connection's destination address.
string source_address = 4;
}

View File

@@ -1,457 +0,0 @@
//
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
import "google/protobuf/any.proto";
import "google/protobuf/descriptor.proto";
import "gnmi_ext.proto";
// Package gNMI defines a service specification for the gRPC Network Management
// Interface. This interface is defined to be a standard interface via which
// a network management system ("client") can subscribe to state values,
// retrieve snapshots of state information, and manipulate the state of a data
// tree supported by a device ("target").
//
// This document references the gNMI Specification which can be found at
// http://github.com/openconfig/reference/blob/master/rpc/gnmi
package gnmi;
// Define a protobuf FileOption that defines the gNMI service version.
extend google.protobuf.FileOptions {
// The gNMI service semantic version.
string gnmi_service = 1001;
}
// gNMI_service is the current version of the gNMI service, returned through
// the Capabilities RPC.
option (gnmi_service) = "0.7.0";
service gNMI {
// Capabilities allows the client to retrieve the set of capabilities that
// is supported by the target. This allows the target to validate the
// service version that is implemented and retrieve the set of models that
// the target supports. The models can then be specified in subsequent RPCs
// to restrict the set of data that is utilized.
// Reference: gNMI Specification Section 3.2
rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);
// Retrieve a snapshot of data from the target. A Get RPC requests that the
// target snapshots a subset of the data tree as specified by the paths
// included in the message and serializes this to be returned to the
// client using the specified encoding.
// Reference: gNMI Specification Section 3.3
rpc Get(GetRequest) returns (GetResponse);
// Set allows the client to modify the state of data on the target. The
// paths to modified along with the new values that the client wishes
// to set the value to.
// Reference: gNMI Specification Section 3.4
rpc Set(SetRequest) returns (SetResponse);
// Subscribe allows a client to request the target to send it values
// of particular paths within the data tree. These values may be streamed
// at a particular cadence (STREAM), sent one off on a long-lived channel
// (POLL), or sent as a one-off retrieval (ONCE).
// Reference: gNMI Specification Section 3.5
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);
}
// Notification is a re-usable message that is used to encode data from the
// target to the client. A Notification carries two types of changes to the data
// tree:
// - Deleted values (delete) - a set of paths that have been removed from the
// data tree.
// - Updated values (update) - a set of path-value pairs indicating the path
// whose value has changed in the data tree.
// Reference: gNMI Specification Section 2.1
message Notification {
int64 timestamp = 1; // Timestamp in nanoseconds since Epoch.
Path prefix = 2; // Prefix used for paths in the message.
// An alias for the path specified in the prefix field.
// Reference: gNMI Specification Section 2.4.2
string alias = 3;
repeated Update update = 4; // Data elements that have changed values.
repeated Path delete = 5; // Data elements that have been deleted.
// This notification contains a set of paths that are always updated together
// referenced by a globally unique prefix.
bool atomic = 6;
}
// Update is a re-usable message that is used to store a particular Path,
// Value pair.
// Reference: gNMI Specification Section 2.1
message Update {
Path path = 1; // The path (key) for the update.
Value value = 2 [deprecated=true]; // The value (value) for the update.
TypedValue val = 3; // The explicitly typed update value.
uint32 duplicates = 4; // Number of coalesced duplicates.
}
// TypedValue is used to encode a value being sent between the client and
// target (originated by either entity).
message TypedValue {
// One of the fields within the val oneof is populated with the value
// of the update. The type of the value being included in the Update
// determines which field should be populated. In the case that the
// encoding is a particular form of the base protobuf type, a specific
// field is used to store the value (e.g., json_val).
oneof value {
string string_val = 1; // String value.
int64 int_val = 2; // Integer value.
uint64 uint_val = 3; // Unsigned integer value.
bool bool_val = 4; // Bool value.
bytes bytes_val = 5; // Arbitrary byte sequence value.
float float_val = 6; // Floating point value.
Decimal64 decimal_val = 7; // Decimal64 encoded value.
ScalarArray leaflist_val = 8; // Mixed type scalar array value.
google.protobuf.Any any_val = 9; // protobuf.Any encoded bytes.
bytes json_val = 10; // JSON-encoded text.
bytes json_ietf_val = 11; // JSON-encoded text per RFC7951.
string ascii_val = 12; // Arbitrary ASCII text.
// Protobuf binary encoded bytes. The message type is not included.
// See the specification at
// github.com/openconfig/reference/blob/master/rpc/gnmi/protobuf-vals.md
// for a complete specification.
bytes proto_bytes = 13;
}
}
// Path encodes a data tree path as a series of repeated strings, with
// each element of the path representing a data tree node name and the
// associated attributes.
// Reference: gNMI Specification Section 2.2.2.
message Path {
// Elements of the path are no longer encoded as a string, but rather within
// the elem field as a PathElem message.
repeated string element = 1 [deprecated=true];
string origin = 2; // Label to disambiguate path.
repeated PathElem elem = 3; // Elements of the path.
string target = 4; // The name of the target
// (Sec. 2.2.2.1)
}
// PathElem encodes an element of a gNMI path, along with any attributes (keys)
// that may be associated with it.
// Reference: gNMI Specification Section 2.2.2.
message PathElem {
string name = 1; // The name of the element in the path.
map<string, string> key = 2; // Map of key (attribute) name to value.
}
// Value encodes a data tree node's value - along with the way in which
// the value is encoded. This message is deprecated by gNMI 0.3.0.
// Reference: gNMI Specification Section 2.2.3.
message Value {
option deprecated = true;
bytes value = 1; // Value of the variable being transmitted.
Encoding type = 2; // Encoding used for the value field.
}
// Encoding defines the value encoding formats that are supported by the gNMI
// protocol. These encodings are used by both the client (when sending Set
// messages to modify the state of the target) and the target when serializing
// data to be returned to the client (in both Subscribe and Get RPCs).
// Reference: gNMI Specification Section 2.3
enum Encoding {
JSON = 0; // JSON encoded text.
BYTES = 1; // Arbitrarily encoded bytes.
PROTO = 2; // Encoded according to out-of-band agreed Protobuf.
ASCII = 3; // ASCII text of an out-of-band agreed format.
JSON_IETF = 4; // JSON encoded text as per RFC7951.
}
// Error message previously utilised to return errors to the client. Deprecated
// in favour of using the google.golang.org/genproto/googleapis/rpc/status
// message in the RPC response.
// Reference: gNMI Specification Section 2.5
message Error {
option deprecated = true;
uint32 code = 1; // Canonical gRPC error code.
string message = 2; // Human readable error.
google.protobuf.Any data = 3; // Optional additional information.
}
// Decimal64 is used to encode a fixed precision decimal number. The value
// is expressed as a set of digits with the precision specifying the
// number of digits following the decimal point in the digit set.
message Decimal64 {
int64 digits = 1; // Set of digits.
uint32 precision = 2; // Number of digits following the decimal point.
}
// ScalarArray is used to encode a mixed-type array of values.
message ScalarArray {
// The set of elements within the array. Each TypedValue message should
// specify only elements that have a field identifier of 1-7 (i.e., the
// values are scalar values).
repeated TypedValue element = 1;
}
// SubscribeRequest is the message sent by the client to the target when
// initiating a subscription to a set of paths within the data tree. The
// request field must be populated and the initial message must specify a
// SubscriptionList to initiate a subscription. The message is subsequently
// used to define aliases or trigger polled data to be sent by the target.
// Reference: gNMI Specification Section 3.5.1.1
message SubscribeRequest {
oneof request {
SubscriptionList subscribe = 1; // Specify the paths within a subscription.
Poll poll = 3; // Trigger a polled update.
AliasList aliases = 4; // Aliases to be created.
}
// Extension messages associated with the SubscribeRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// Poll is sent within a SubscribeRequest to trigger the device to
// send telemetry updates for the paths that are associated with the
// subscription.
// Reference: gNMI Specification Section Section 3.5.1.4
message Poll {
}
// SubscribeResponse is the message used by the target within a Subscribe RPC.
// The target includes a Notification message which is used to transmit values
// of the path(s) that are associated with the subscription. The same message
// is to indicate that the target has sent all data values once (is
// synchronized).
// Reference: gNMI Specification Section 3.5.1.4
message SubscribeResponse {
oneof response {
Notification update = 1; // Changed or sampled value for a path.
// Indicate target has sent all values associated with the subscription
// at least once.
bool sync_response = 3;
// Deprecated in favour of google.golang.org/genproto/googleapis/rpc/status
Error error = 4 [deprecated=true];
}
// Extension messages associated with the SubscribeResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// SubscriptionList is used within a Subscribe message to specify the list of
// paths that the client wishes to subscribe to. The message consists of a
// list of (possibly prefixed) paths, and options that relate to the
// subscription.
// Reference: gNMI Specification Section 3.5.1.2
message SubscriptionList {
Path prefix = 1; // Prefix used for paths.
repeated Subscription subscription = 2; // Set of subscriptions to create.
// Whether target defined aliases are allowed within the subscription.
bool use_aliases = 3;
QOSMarking qos = 4; // DSCP marking to be used.
// Mode of the subscription.
enum Mode {
STREAM = 0; // Values streamed by the target (Sec. 3.5.1.5.2).
ONCE = 1; // Values sent once-off by the target (Sec. 3.5.1.5.1).
POLL = 2; // Values sent in response to a poll request (Sec. 3.5.1.5.3).
}
Mode mode = 5;
// Whether elements of the schema that are marked as eligible for aggregation
// should be aggregated or not.
bool allow_aggregation = 6;
// The set of schemas that define the elements of the data tree that should
// be sent by the target.
repeated ModelData use_models = 7;
// The encoding that the target should use within the Notifications generated
// corresponding to the SubscriptionList.
Encoding encoding = 8;
// An optional field to specify that only updates to current state should be
// sent to a client. If set, the initial state is not sent to the client but
// rather only the sync message followed by any subsequent updates to the
// current state. For ONCE and POLL modes, this causes the server to send only
// the sync message (Sec. 3.5.2.3).
bool updates_only = 9;
}
// Subscription is a single request within a SubscriptionList. The path
// specified is interpreted (along with the prefix) as the elements of the data
// tree that the client is subscribing to. The mode determines how the target
// should trigger updates to be sent.
// Reference: gNMI Specification Section 3.5.1.3
message Subscription {
Path path = 1; // The data tree path.
SubscriptionMode mode = 2; // Subscription mode to be used.
uint64 sample_interval = 3; // ns between samples in SAMPLE mode.
// Indicates whether values that have not changed should be sent in a SAMPLE
// subscription.
bool suppress_redundant = 4;
// Specifies the maximum allowable silent period in nanoseconds when
// suppress_redundant is in use. The target should send a value at least once
// in the period specified.
uint64 heartbeat_interval = 5;
}
// SubscriptionMode is the mode of the subscription, specifying how the
// target must return values in a subscription.
// Reference: gNMI Specification Section 3.5.1.3
enum SubscriptionMode {
TARGET_DEFINED = 0; // The target selects the relevant mode for each element.
ON_CHANGE = 1; // The target sends an update on element value change.
SAMPLE = 2; // The target samples values according to the interval.
}
// QOSMarking specifies the DSCP value to be set on transmitted telemetry
// updates from the target.
// Reference: gNMI Specification Section 3.5.1.2
message QOSMarking {
uint32 marking = 1;
}
// Alias specifies a data tree path, and an associated string which defines an
// alias which is to be used for this path in the context of the RPC. The alias
// is specified as a string which is prefixed with "#" to disambiguate it from
// data tree element paths.
// Reference: gNMI Specification Section 2.4.2
message Alias {
Path path = 1; // The path to be aliased.
string alias = 2; // The alias value, a string prefixed by "#".
}
// AliasList specifies a list of aliases. It is used in a SubscribeRequest for
// a client to create a set of aliases that the target is to utilize.
// Reference: gNMI Specification Section 3.5.1.6
message AliasList {
repeated Alias alias = 1; // The set of aliases to be created.
}
// SetRequest is sent from a client to the target to update values in the data
// tree. Paths are either deleted by the client, or modified by means of being
// updated, or replaced. Where a replace is used, unspecified values are
// considered to be replaced, whereas when update is used the changes are
// considered to be incremental. The set of changes that are specified within
// a single SetRequest are considered to be a transaction.
// Reference: gNMI Specification Section 3.4.1
message SetRequest {
Path prefix = 1; // Prefix used for paths in the message.
repeated Path delete = 2; // Paths to be deleted from the data tree.
repeated Update replace = 3; // Updates specifying elements to be replaced.
repeated Update update = 4; // Updates specifying elements to updated.
// Extension messages associated with the SetRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// SetResponse is the response to a SetRequest, sent from the target to the
// client. It reports the result of the modifications to the data tree that were
// specified by the client. Errors for this RPC should be reported using the
// https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
// message in the RPC return. The gnmi.Error message can be used to add additional
// details where required.
// Reference: gNMI Specification Section 3.4.2
message SetResponse {
Path prefix = 1; // Prefix used for paths.
// A set of responses specifying the result of the operations specified in
// the SetRequest.
repeated UpdateResult response = 2;
Error message = 3 [deprecated=true]; // The overall status of the transaction.
int64 timestamp = 4; // Timestamp of transaction (ns since epoch).
// Extension messages associated with the SetResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// UpdateResult is used within the SetResponse message to communicate the
// result of an operation specified within a SetRequest message.
// Reference: gNMI Specification Section 3.4.2
message UpdateResult {
// The operation that was associated with the Path specified.
enum Operation {
INVALID = 0;
DELETE = 1; // The result relates to a delete of Path.
REPLACE = 2; // The result relates to a replace of Path.
UPDATE = 3; // The result relates to an update of Path.
}
// Deprecated timestamp for the UpdateResult, this field has been
// replaced by the timestamp within the SetResponse message, since
// all mutations effected by a set should be applied as a single
// transaction.
int64 timestamp = 1 [deprecated=true];
Path path = 2; // Path associated with the update.
Error message = 3 [deprecated=true]; // Status of the update operation.
Operation op = 4; // Update operation type.
}
// GetRequest is sent when a client initiates a Get RPC. It is used to specify
// the set of data elements for which the target should return a snapshot of
// data. The use_models field specifies the set of schema modules that are to
// be used by the target - where use_models is not specified then the target
// must use all schema models that it has.
// Reference: gNMI Specification Section 3.3.1
message GetRequest {
Path prefix = 1; // Prefix used for paths.
repeated Path path = 2; // Paths requested by the client.
// Type of elements within the data tree.
enum DataType {
ALL = 0; // All data elements.
CONFIG = 1; // Config (rw) only elements.
STATE = 2; // State (ro) only elements.
// Data elements marked in the schema as operational. This refers to data
// elements whose value relates to the state of processes or interactions
// running on the device.
OPERATIONAL = 3;
}
DataType type = 3; // The type of data being requested.
Encoding encoding = 5; // Encoding to be used.
repeated ModelData use_models = 6; // The schema models to be used.
// Extension messages associated with the GetRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 7;
}
// GetResponse is used by the target to respond to a GetRequest from a client.
// The set of Notifications corresponds to the data values that are requested
// by the client in the GetRequest.
// Reference: gNMI Specification Section 3.3.2
message GetResponse {
repeated Notification notification = 1; // Data values.
Error error = 2 [deprecated=true]; // Errors that occurred in the Get.
// Extension messages associated with the GetResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 3;
}
// CapabilityRequest is sent by the client in the Capabilities RPC to request
// that the target reports its capabilities.
// Reference: gNMI Specification Section 3.2.1
message CapabilityRequest {
// Extension messages associated with the CapabilityRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 1;
}
// CapabilityResponse is used by the target to report its capabilities to the
// client within the Capabilities RPC.
// Reference: gNMI Specification Section 3.2.2
message CapabilityResponse {
repeated ModelData supported_models = 1; // Supported schema models.
repeated Encoding supported_encodings = 2; // Supported encodings.
string gNMI_version = 3; // Supported gNMI version.
// Extension messages associated with the CapabilityResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 4;
}
// ModelData is used to describe a set of schema modules. It can be used in a
// CapabilityResponse where a target reports the set of modules that it
// supports, and within the SubscribeRequest and GetRequest messages to specify
// the set of models from which data tree elements should be reported.
// Reference: gNMI Specification Section 3.2.3
message ModelData {
string name = 1; // Name of the model.
string organization = 2; // Organization publishing the model.
string version = 3; // Semantic version of the model.
}

View File

@@ -1,89 +0,0 @@
//
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
// Package gnmi_ext defines a set of extensions messages which can be optionally
// included with the request and response messages of gNMI RPCs. A set of
// well-known extensions are defined within this file, along with a registry for
// extensions defined outside of this package.
package gnmi_ext;
// The Extension message contains a single gNMI extension.
message Extension {
oneof ext {
RegisteredExtension registered_ext = 1; // A registered extension.
// Well known extensions.
MasterArbitration master_arbitration = 2; // Master arbitration extension.
History history = 3; // History extension.
}
}
// The RegisteredExtension message defines an extension which is defined outside
// of this file.
message RegisteredExtension {
ExtensionID id = 1; // The unique ID assigned to this extension.
bytes msg = 2; // The binary-marshalled protobuf extension payload.
}
// RegisteredExtension is an enumeration acting as a registry for extensions
// defined by external sources.
enum ExtensionID {
EID_UNSET = 0;
// New extensions are to be defined within this enumeration - their definition
// MUST link to a reference describing their implementation.
// An experimental extension that may be used during prototyping of a new
// extension.
EID_EXPERIMENTAL = 999;
}
// MasterArbitration is used to select the master among multiple gNMI clients
// with the same Roles. The client with the largest election_id is honored as
// the master.
// The document about gNMI master arbitration can be found at
// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-master-arbitration.md
message MasterArbitration {
Role role = 1;
Uint128 election_id = 2;
}
// Representation of unsigned 128-bit integer.
message Uint128 {
uint64 high = 1;
uint64 low = 2;
}
// There can be one master for each role. The role is identified by its id.
message Role {
string id = 1;
// More fields can be added if needed, for example, to specify what paths the
// role can read/write.
}
// The History extension allows clients to request historical data. Its
// spec can be found at
// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md
message History {
oneof request {
int64 snapshot_time = 1; // Nanoseconds since the epoch
TimeRange range = 2;
}
}
message TimeRange {
int64 start = 1; // Nanoseconds since the epoch
int64 end = 2; // Nanoseconds since the epoch
}

View File

@@ -1,315 +0,0 @@
syntax = "proto3";
package gnoi.sonic;
//option (types.gnoi_version) = "0.1.0";
service SonicService {
rpc ShowTechsupport (TechsupportRequest) returns (TechsupportResponse) {}
rpc ShowTechsupportCancel (TechsupportCancelRequest) returns (TechsupportCancelResponse) {}
rpc Sum (SumRequest) returns (SumResponse) {}
rpc ImageInstall(ImageInstallRequest) returns (ImageInstallResponse) {}
rpc ImageRemove(ImageRemoveRequest) returns (ImageRemoveResponse) {}
rpc ImageDefault(ImageDefaultRequest) returns (ImageDefaultResponse) {}
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {}
rpc Refresh(RefreshRequest) returns (RefreshResponse) {}
rpc ClearNeighbors(ClearNeighborsRequest) returns (ClearNeighborsResponse) {}
rpc VlanReplace(VlanReplaceRequest) returns (VlanReplaceResponse) {}
rpc GetAuditLog (GetAuditLogRequest) returns (GetAuditLogResponse) {}
rpc ClearAuditLog (ClearAuditLogRequest) returns (ClearAuditLogResponse) {}
rpc ShowSysLog(ShowSysLogRequest) returns (GetShowSysLogResponse) {}
rpc GetEvents (GetEventsRequest) returns (GetEventsResponse) {}
rpc GetAlarms (GetAlarmsRequest) returns (GetAlarmsResponse) {}
rpc AckAlarms (AckAlarmsRequest) returns (AckAlarmsResponse) {}
rpc UnackAlarms (UnackAlarmsRequest) returns (UnackAlarmsResponse) {}
rpc GetEventProfile (GetEventProfileRequest) returns (GetEventProfileResponse) {}
rpc SetEventProfile (SetEventProfileRequest) returns (SetEventProfileResponse) {}
}
message SonicOutput {
int32 status = 1;
string status_detail = 2;
}
message GetEventProfileRequest {
}
message GetEventProfileResponse {
message Output {
string file_name = 1;
repeated string file_list = 2;
}
Output output = 1;
}
message SetEventProfileRequest {
message Input {
string filename =1;
}
Input input =1;
}
message SetEventProfileResponse {
SonicOutput output = 1;
}
message AckAlarmsRequest {
message Input {
repeated string id = 1;
}
Input input = 1;
}
message AckAlarmsResponse {
SonicOutput output = 1;
}
message UnackAlarmsRequest {
message Input {
repeated string id = 1;
}
Input input = 1;
}
message UnackAlarmsResponse {
SonicOutput output = 1;
}
message EventTimeFilter {
string begin = 1;
string end = 2;
}
message EventId {
string begin = 1;
string end = 2;
}
message EventsFilter {
EventTimeFilter time = 1;
string interval = 2;
string severity = 3;
EventId id = 4;
}
message GetEventsRequest {
EventsFilter input = 1;
}
message Event {
string id = 1;
string resource = 2;
string text = 3;
string time_created = 4;
string type_id = 5;
string severity = 6;
string action = 7;
}
message Events {
repeated Event EVENT_LIST = 1;
}
message EventsResponse {
int32 status = 1;
string status_detail = 2;
Events EVENT =3;
}
message GetEventsResponse {
EventsResponse output = 1;
}
message Alarm {
string id = 1;
string resource = 2;
string text = 3;
string time_created = 4;
string type_id = 5;
string severity = 6;
bool acknowledged = 7;
string acknowledge_time = 8;
}
message GetAlarmsRequest {
EventsFilter input = 1;
}
message Alarms {
repeated Alarm ALARM_LIST = 1;
}
message AlarmsResponse {
int32 status = 1;
string status_detail = 2;
Alarms ALARM =3;
}
message GetAlarmsResponse {
AlarmsResponse output = 1;
}
message TechsupportRequest {
message Input {
string date = 1;
}
Input input = 1;
}
message TechsupportResponse {
message Output {
uint32 status = 1;
string status_detail = 2;
string output_filename = 3;
}
Output output = 1;
}
message TechsupportCancelRequest {
}
message TechsupportCancelResponse {
message Output {
uint32 status = 1;
string status_detail = 2;
}
Output output = 1;
}
message ClearNeighborsRequest {
message Input {
bool force = 1;
string family = 2;
string ip = 3;
string ifname = 4;
}
Input input = 1;
}
message ClearNeighborsResponse {
message Output {
string response = 1;
}
Output output = 1;
}
message VlanReplaceRequest {
message Input {
string ifname = 1;
string vlanlist = 2;
}
Input input = 1;
}
message VlanReplaceResponse {
message Output {
string response = 1;
}
Output output = 1;
}
message SumRequest {
message Input {
int32 left = 1;
int32 right = 2;
}
Input input = 1;
}
message SumResponse {
message Output {
int32 result = 1;
}
Output output = 1;
}
message ImageInstallRequest {
message Input {
string imagename = 1;
}
Input input = 1;
}
message ImageInstallResponse {
SonicOutput output = 1;
}
message ImageRemoveRequest {
message Input {
string imagename = 1;
}
Input input = 1;
}
message ImageRemoveResponse {
SonicOutput output = 1;
}
message ImageDefaultRequest {
message Input {
string imagename = 1;
}
Input input = 1;
}
message ImageDefaultResponse {
SonicOutput output = 1;
}
message GetAuditLogRequest {
message Input {
string content_type = 1;
}
Input input = 1;
}
message GetAuditLogResponse {
message AuditOutput {
repeated string audit_content = 1;
}
AuditOutput output = 1;
}
message ClearAuditLogRequest {
}
message ClearAuditLogResponse {
SonicOutput output = 1;
}
message ShowSysLogRequest {
message Input {
int32 num_lines = 1 ;
}
Input input = 1;
}
message ShowSysLogResponse {
repeated string status_detail = 1;
}
message GetShowSysLogResponse {
ShowSysLogResponse output = 1;
}
message JwtToken {
string access_token = 1;
string type = 2;
int64 expires_in = 3;
}
message AuthenticateRequest {
string username = 1;
string password = 2;
}
message AuthenticateResponse {
JwtToken Token = 1;
}
message RefreshRequest {
}
message RefreshResponse {
JwtToken Token = 1;
}

View File

@@ -1,339 +0,0 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Generic Network Operation Interface, GNOI, defines a set of RPC's used for
// the operational aspects of network targets. These services are meant to be
// used in conjunction with GNMI for all target state and operational aspects
// of a network target. The gnoi.system.Service is the only mandatory vendor
// implementation.
syntax = "proto3";
package gnoi.system;
import "common.proto";
import "types.proto";
option (types.gnoi_version) = "1.0.0";
// The gNOI service is a collection of operational RPC's that allow for the
// management of a target outside of the configuration and telemetry pipeline.
service System {
// Ping executes the ping command on the target and streams back
// the results. Some targets may not stream any results until all
// results are in. The stream should provide single ping packet responses
// and must provide summary statistics.
rpc Ping(PingRequest) returns (stream PingResponse) {}
// Traceroute executes the traceroute command on the target and streams back
// the results. Some targets may not stream any results until all
// results are in. If a hop count is not explicitly provided,
// 30 is used.
rpc Traceroute(TracerouteRequest) returns (stream TracerouteResponse) {}
// Time returns the current time on the target. Time is typically used to
// test if a target is actually responding.
rpc Time(TimeRequest) returns (TimeResponse) {}
// SetPackage places a software package (possibly including bootable images)
// on the target. The file is sent in sequential messages, each message
// up to 64KB of data. A final message must be sent that includes the hash
// of the data sent. An error is returned if the location does not exist or
// there is an error writing the data. If no checksum is received, the target
// must assume the operation is incomplete and remove the partially
// transmitted file. The target should initially write the file to a temporary
// location so a failure does not destroy the original file.
rpc SetPackage(stream SetPackageRequest) returns (SetPackageResponse) {}
// SwitchControlProcessor will switch from the current route processor to the
// provided route processor. If the current route processor is the same as the
// one provided it is a NOOP. If the target does not exist an error is
// returned.
rpc SwitchControlProcessor(SwitchControlProcessorRequest)
returns (SwitchControlProcessorResponse) {}
// Reboot causes the target to reboot, possibly at some point in the future.
// If the method of reboot is not supported then the Reboot RPC will fail.
// If the reboot is immediate the command will block until the subcomponents
// have restarted.
// If a reboot on the active control processor is pending the service must
// reject all other reboot requests.
// If a reboot request for active control processor is initiated with other
// pending reboot requests it must be rejected.
rpc Reboot(RebootRequest) returns (RebootResponse) {}
// RebootStatus returns the status of reboot for the target.
rpc RebootStatus(RebootStatusRequest) returns (RebootStatusResponse) {}
// CancelReboot cancels any pending reboot request.
rpc CancelReboot(CancelRebootRequest) returns (CancelRebootResponse) {}
// KillProcess kills an OS process and optionally restarts it.
rpc KillProcess(KillProcessRequest) returns (KillProcessResponse) {}
// TODO(hines): Add RotateCertificate workflow
// TODO(hines): Add SetSSHPrivateKey
}
message SwitchControlProcessorRequest {
types.Path control_processor = 1;
}
message SwitchControlProcessorResponse {
types.Path control_processor = 1;
string version = 2; // Current software version.
int64 uptime = 3; // Uptime in nanoseconds since epoch.
}
// A RebootRequest requests the specified target be rebooted using the specified
// method after the specified delay. Only the DEFAULT method with a delay of 0
// is guaranteed to be accepted for all target types.
message RebootRequest {
RebootMethod method = 1;
// Delay in nanoseconds before issuing reboot.
uint64 delay = 2;
// Informational reason for the reboot.
string message = 3;
// Optional sub-components to reboot.
repeated types.Path subcomponents = 4;
// Force reboot if sanity checks fail. (ex. uncommited configuration)
bool force = 5;
}
message RebootResponse {
}
// A RebootMethod determines what should be done with a target when a Reboot is
// requested. Only the COLD method is required to be supported by all
// targets. Methods the target does not support should result in failure.
//
// It is vendor defined if a WARM reboot is the same as an NSF reboot.
enum RebootMethod {
UNKNOWN = 0; // Invalid default method.
COLD = 1; // Shutdown and restart OS and all hardware.
POWERDOWN = 2; // Halt and power down, if possible.
HALT = 3; // Halt, if possible.
WARM = 4; // Reload configuration but not underlying hardware.
NSF = 5; // Non-stop-forwarding reboot, if possible.
// RESET method is deprecated in favor of the gNOI FactoryReset.Start().
reserved 6;
POWERUP = 7; // Apply power, no-op if power is already on.
}
// A CancelRebootRequest requests the cancelation of any outstanding reboot
// request.
message CancelRebootRequest {
string message = 1; // informational reason for the cancel
repeated types.Path subcomponents = 2; // optional sub-components.
}
message CancelRebootResponse {
}
message RebootStatusRequest {
repeated types.Path subcomponents = 1; // optional sub-component.
}
message RebootStatusResponse {
bool active = 1; // If reboot is active.
uint64 wait = 2; // Time left until reboot.
uint64 when = 3; // Time to reboot in nanoseconds since the epoch.
string reason = 4; // Reason for reboot.
uint32 count = 5; // Number of reboots since active.
}
// A TimeRequest requests the current time accodring to the target.
message TimeRequest {
}
message TimeResponse {
uint64 time = 1; // Current time in nanoseconds since epoch.
}
// A PingRequest describes the ping operation to perform. Only the destination
// fields is required. Any field not specified is set to a reasonable server
// specified value. Not all fields are supported by all vendors.
//
// A count of 0 defaults to a vendor specified value, typically 5. A count of
// -1 means continue until the RPC times out or is canceled.
//
// If the interval is -1 then a flood ping is issued.
//
// If the size is 0, the vendor default size will be used (typically 56 bytes).
message PingRequest {
string destination = 1; // Destination address to ping. required.
string source = 2; // Source address to ping from.
int32 count = 3; // Number of packets.
int64 interval = 4; // Nanoseconds between requests.
int64 wait = 5; // Nanoseconds to wait for a response.
int32 size = 6; // Size of request packet. (excluding ICMP header)
bool do_not_fragment = 7; // Set the do not fragment bit. (IPv4 destinations)
bool do_not_resolve = 8; // Do not try resolve the address returned.
types.L3Protocol l3protocol = 9; // Layer3 protocol requested for the ping.
}
// A PingResponse represents either the reponse to a single ping packet
// (the bytes field is non-zero) or the summary statistics (sent is non-zero).
//
// For a single ping packet, time is the round trip time, in nanoseconds. For
// summary statistics, it is the time spent by the ping operation. The time is
// not always present in summary statistics. The std_dev is not always present
// in summary statistics.
message PingResponse {
string source = 1; // Source of received bytes.
int64 time = 2;
int32 sent = 3; // Total packets sent.
int32 received = 4; // Total packets received.
int64 min_time = 5; // Minimum round trip time in nanoseconds.
int64 avg_time = 6; // Average round trip time in nanoseconds.
int64 max_time = 7; // Maximum round trip time in nanoseconds.
int64 std_dev = 8; // Standard deviation in round trip time.
int32 bytes = 11; // Bytes received.
int32 sequence = 12; // Sequence number of received packet.
int32 ttl = 13; // Remaining time to live value.
}
// A TracerouteRequest describes the traceroute operation to perform. Only the
// destination field is required. Any field not specified is set to a
// reasonable server specified value. Not all fields are supported by all
// vendors.
//
// If the hop_count is -1 the traceroute will continue forever.
//
message TracerouteRequest {
string source = 1; // Source address to ping from.
string destination = 2; // Destination address to ping.
uint32 initial_ttl = 3; // Initial TTL. (default=1)
int32 max_ttl = 4; // Maximum number of hops. (default=30)
int64 wait = 5; // Nanoseconds to wait for a response.
bool do_not_fragment = 6; // Set the do not fragment bit. (IPv4 destinations)
bool do_not_resolve = 7; // Do not try resolve the address returned.
types.L3Protocol l3protocol = 8; // Layer-3 protocol requested for the ping.
enum L4Protocol {
ICMP = 0; // Use ICMP ECHO for probes.
TCP = 1; // Use TCP SYN for probes.
UDP = 2; // Use UDP for probes.
}
L4Protocol l4protocol = 9;
bool do_not_lookup_asn = 10; // Do not try to lookup ASN
}
// A TraceRouteResponse contains the result of a single traceoute packet.
//
// There may be an optional initial response that provides information about the
// traceroute request itself and contains at least one of the fields in the the
// initial block of fields and none of the fields following that block. All
// subsequent responses should not contain any of these fields.
//
// Typically multiple responses are received for each hop, as the packets are
// received.
//
// The mpls field maps names to values. Example names include "Label", "CoS",
// "TTL", "S", and "MRU".
// [Perhaps we should list the canonical names that must be used when
// applicable].
message TracerouteResponse {
// The following fields are only filled in for the first message.
// If any of these fields are specified, all fields following this
// block are left unspecified.
string destination_name = 1;
string destination_address = 2;
int32 hops = 3;
int32 packet_size = 4;
// State is the resulting state of a single traceoroute packet.
enum State {
DEFAULT = 0; // Normal hop response.
NONE = 1; // No response.
UNKNOWN = 2; // Unknown response state.
ICMP = 3; // See icmp_code field.
HOST_UNREACHABLE = 4; // Host unreachable.
NETWORK_UNREACHABLE = 5; // Network unreachable.
PROTOCOL_UNREACHABLE = 6; // Protocol unreachable.
SOURCE_ROUTE_FAILED = 7; // Source route failed.
FRAGMENTATION_NEEDED = 8; // Fragmentation needed.
PROHIBITED = 9; // Communication administratively prohibited.
PRECEDENCE_VIOLATION = 10; // Host precedence violation.
PRECEDENCE_CUTOFF = 11; // Precedence cutoff in effect.
}
// The following fields provide the disposition of a single traceroute
// packet.
int32 hop = 5; // Hop number. required.
string address = 6; // Address of responding hop. required.
string name = 7; // Name of responding hop.
int64 rtt = 8; // Round trip time in nanoseconds.
State state = 9; // State of this hop.
int32 icmp_code = 10; // Code terminating hop.
map<string, string> mpls = 11; // MPLS key/value pairs.
repeated int32 as_path = 12; // AS path.
}
// Package defines a single package file to be placed on the target.
message Package {
// Destination path and filename of the package.
string filename = 1;
// Version of the package. (vendor internal name)
string version = 4;
// Indicates that the package should be made active after receipt on
// the device. For system image packages, the new image is expected to
// be active after a reboot.
bool activate = 5;
// Details for the device to download the package from a remote location.
common.RemoteDownload remote_download = 6;
}
// SetPackageRequest will place the package onto the target and optionally mark
// it as the next bootable image. The initial message must be a package
// message containing the filename and information about the file. Following the
// initial message the contents are then streamed in maximum 64k chunks. The
// final message must be a hash message contains the hash of the file contents.
message SetPackageRequest {
oneof request {
Package package = 1;
bytes contents = 2;
types.HashType hash = 3; // Verification hash of data.
}
}
message SetPackageResponse {
}
// KillProcessRequest describes the process kill operation. Either a pid or
// process name must be specified, and a termination signal must be specified.
message KillProcessRequest {
// Process ID of the process to be killed.
uint32 pid = 1;
// Name of the process to be killed.
string name = 2;
// Termination signal sent to the process.
enum Signal {
SIGNAL_UNSPECIFIED = 0; // Invalid default.
SIGNAL_TERM = 1; // Terminate the process gracefully.
SIGNAL_KILL = 2; // Terminate the process immediately.
SIGNAL_HUP = 3; // Reload the process configuration.
}
Signal signal = 3;
// Whether the process should be restarted after termination.
// This value is ignored when the termination signal is SIGHUP.
bool restart = 4;
}
// KillProcessResponse describes the result of the process kill operation.
message KillProcessResponse {
}

View File

@@ -1,75 +0,0 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
package gnoi.types;
import "google/protobuf/descriptor.proto";
option go_package = "github.com/openconfig/gnoi/types";
// Define a protobuf FileOption that defines the gNOI service version.
extend google.protobuf.FileOptions {
// The gNOI service semantic version.
string gnoi_version = 1002;
}
// Generic Layer 3 Protocol enumeration.
enum L3Protocol {
UNSPECIFIED = 0;
IPV4 = 1;
IPV6 = 2;
}
// HashType defines the valid hash methods for data verification. UNSPECIFIED
// should be treated an error.
message HashType {
enum HashMethod {
UNSPECIFIED = 0;
SHA256 = 1;
SHA512 = 2;
MD5 = 3;
}
HashMethod method = 1;
bytes hash = 2;
}
// Path encodes a data tree path as a series of repeated strings, with
// each element of the path representing a data tree node name and the
// associated attributes.
// Reference: gNMI Specification Section 2.2.2.
message Path {
string origin = 2; // Label to disambiguate path.
repeated PathElem elem = 3; // Elements of the path.
}
// PathElem encodes an element of a gNMI path, along with any attributes (keys)
// that may be associated with it.
// Reference: gNMI Specification Section 2.2.2.
message PathElem {
string name = 1; // The name of the element in the path.
map<string, string> key = 2; // Map of key (attribute) name to value.
}
// Credentials defines credentials needed to perform authentication on a device.
message Credentials {
string username = 1;
oneof password {
string cleartext = 2;
HashType hashed = 3;
}
}

View File

@@ -1,408 +0,0 @@
#include <route.hpp>
#include <sai_redis.hpp>
#include <state.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis++.h>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <router-utils.h>
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <arpa/inet.h>
#include <algorithm> // std::find_if
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iterator> // std::inserter
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using nlohmann::json;
namespace larch {
static std::string fib_key_to_str(const ucentral_router_fib_key &fib_key)
{
std::array<char, INET_ADDRSTRLEN + 1> ip_buf{};
if (!inet_ntop(AF_INET, &fib_key.prefix, ip_buf.data(), ip_buf.size()))
throw std::runtime_error{
"Failed to convert FIB prefix from binary to text form"};
std::string ip{ip_buf.data()};
ip += "/" + std::to_string(fib_key.prefix_len);
return ip;
}
void create_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key,
const ucentral_router_fib_info &fib_info)
{
// VRF is not supported
if (router_id != 0)
return;
json route_json;
route_json["prefix"] = fib_key_to_str(fib_key);
route_json["vrf-name"] = "default";
switch (fib_info.type)
{
case ucentral_router_fib_info::UCENTRAL_ROUTE_BLACKHOLE:
{
route_json["blackhole"] = "true,false";
break;
}
case ucentral_router_fib_info::UCENTRAL_ROUTE_CONNECTED:
{
route_json["ifname"] =
"Vlan" + std::to_string(fib_info.connected.vid);
break;
}
case ucentral_router_fib_info::UCENTRAL_ROUTE_NH:
{
route_json["ifname"] =
"Vlan" + std::to_string(fib_info.nh.vid);
std::array<char, INET_ADDRSTRLEN + 1> ip_buf{};
if (!inet_ntop(
AF_INET,
&fib_info.nh.gw,
ip_buf.data(),
ip_buf.size()))
{
throw std::runtime_error{
"Failed to convert gateway address from "
"binary to text form"};
}
break;
}
default:
{
return;
}
}
json add_route_json;
add_route_json["sonic-static-route:sonic-static-route"]["STATIC_ROUTE"]
["STATIC_ROUTE_LIST"] = {route_json};
gnmi_set(
"/sonic-static-route:sonic-static-route/",
add_route_json.dump());
}
void remove_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key)
{
// VRF is not supported
if (router_id != 0)
return;
gnmi_operation op;
op.add_delete(
"/sonic-static-route:sonic-static-route/STATIC_ROUTE/"
"STATIC_ROUTE_LIST[prefix="
+ fib_key_to_str(fib_key) + "][vrf-name=default]");
op.execute();
}
std::vector<ucentral_router_fib_node> get_routes(std::uint16_t router_id)
{
// VRF is not supported
if (router_id != 0)
return {};
const json static_routes_json =
json::parse(gnmi_get("/sonic-static-route:sonic-static-route/"
"STATIC_ROUTE/STATIC_ROUTE_LIST"));
std::vector<ucentral_router_fib_node> routes;
for (const auto &route_json : static_routes_json.value(
"sonic-static-route:STATIC_ROUTE_LIST",
json::array()))
{
if (route_json.contains("vrf-name")
&& route_json.at("vrf-name").template get<std::string>()
!= "default")
{
continue;
}
if (!route_json.contains("prefix"))
continue;
ucentral_router_fib_info fib_info{};
// For now only blackhole is supported
if (route_json.contains("blackhole"))
fib_info.type =
ucentral_router_fib_info::UCENTRAL_ROUTE_BLACKHOLE;
else
continue;
ucentral_router_fib_key fib_key{};
const int ret = inet_net_pton(
AF_INET,
route_json.at("prefix").template get<std::string>().c_str(),
&fib_key.prefix,
sizeof(fib_key.prefix));
if (ret == -1)
continue;
fib_key.prefix_len = ret;
routes.push_back({fib_key, fib_info});
}
return routes;
}
struct router_interface {
std::string mac;
sai::object_id port_oid;
};
static std::optional<router_interface>
parse_router_interface(const sai::object_id &oid)
{
router_interface router_if{};
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
"ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE:" + oid,
std::inserter(entry, entry.begin()));
try
{
if (entry.at("SAI_ROUTER_INTERFACE_ATTR_TYPE")
!= "SAI_ROUTER_INTERFACE_TYPE_PORT")
{
// Other types are not supported
return std::nullopt;
}
router_if.mac =
entry.at("SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS");
router_if.port_oid =
entry.at("SAI_ROUTER_INTERFACE_ATTR_PORT_ID");
}
catch (const std::out_of_range &ex)
{
return std::nullopt;
}
return router_if;
}
std::vector<plat_gw_address> get_gw_addresses()
{
// TO-DO: remove this and use state->interfaces_addrs after mergin
// interface-addresses PR
std::vector<plat_ipv4> interfaces_addrs;
const auto port_name_mapping = sai::get_port_name_mapping();
std::vector<plat_gw_address> gw_addresses;
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
do
{
constexpr std::string_view pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:*";
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
for (const auto &key : keys)
{
const json route_json =
json::parse(key.substr(pattern.size() - 1));
plat_gw_address gw_addr{};
// Get IP
cidr gw_ip_range = parse_cidr(
route_json.at("dest").template get<std::string>());
if (inet_pton(
AF_INET,
gw_ip_range.ip_address.c_str(),
&gw_addr.ip)
!= 1)
{
UC_LOG_ERR(
"Failed to parse GW IP address %s",
gw_ip_range.ip_address.c_str());
continue;
}
const auto *static_routes_begin = state->router.arr;
const auto *static_routes_end =
static_routes_begin + state->router.len;
if (std::find_if(
static_routes_begin,
static_routes_end,
[&gw_addr](const auto &fib_node) {
// TO-DO: uncomment after merging
// interface-addresses PR return
// addr_to_str(fib_node.key.prefix)
// == addr_to_str(gw_addr.ip);
return false;
})
!= static_routes_end)
{
continue;
}
if (std::find_if(
interfaces_addrs.cbegin(),
interfaces_addrs.cend(),
[&gw_addr](const auto &interface_addr) {
// TO-DO: uncomment after merging
// interface-addresses PR return
// addr_to_str(interface_addr.subnet)
// == addr_to_str(gw_addr.ip);
return false;
})
!= interfaces_addrs.cend())
{
continue;
}
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
key,
std::inserter(entry, entry.begin()));
const auto router_it =
entry.find("SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID");
if (router_it == entry.cend())
continue;
auto router_if_opt =
parse_router_interface(router_it->second);
if (!router_if_opt)
continue;
// Get MAC
std::strncpy(
gw_addr.mac,
router_if_opt->mac.c_str(),
std::size(gw_addr.mac) - 1);
// Get port name
const auto port_name_it =
port_name_mapping.find(router_if_opt->port_oid);
if (port_name_it == port_name_mapping.cend())
continue;
std::strncpy(
gw_addr.port,
port_name_it->second.c_str(),
std::size(gw_addr.port) - 1);
gw_addresses.push_back(gw_addr);
}
} while (cursor != 0);
return gw_addresses;
}
void apply_route_config(plat_cfg *cfg)
{
ucentral_router old_router{}, new_router{};
// Save the old router
old_router = state->router;
// Load new router, this also does the necessary allocations
if (ucentral_router_fib_db_copy(&cfg->router, &new_router) != 0)
throw std::runtime_error{"Failed to copy FIB DB"};
if (!old_router.sorted)
ucentral_router_fib_db_sort(&old_router);
if (!new_router.sorted)
ucentral_router_fib_db_sort(&new_router);
std::size_t old_idx = 0, new_idx = 0;
int diff = 0;
for_router_db_diff(&new_router, &old_router, new_idx, old_idx, diff)
{
diff = router_db_diff_get(
&new_router,
&old_router,
new_idx,
old_idx);
if (diff_case_upd(diff))
{
if (!ucentral_router_fib_info_cmp(
&router_db_get(&old_router, old_idx)->info,
&router_db_get(&new_router, new_idx)->info))
continue;
const auto &node = *router_db_get(&new_router, new_idx);
remove_route(0, node.key);
create_route(0, node.key, node.info);
}
if (diff_case_del(diff))
{
remove_route(
0,
router_db_get(&old_router, old_idx)->key);
}
if (diff_case_add(diff))
{
const auto &node = *router_db_get(&new_router, new_idx);
create_route(0, node.key, node.info);
}
}
ucentral_router_fib_db_free(&old_router);
state->router = new_router;
}
} // namespace larch

View File

@@ -1,29 +0,0 @@
#ifndef LARCH_PLATFORM_ROUTE_HPP_
#define LARCH_PLATFORM_ROUTE_HPP_
#include <router-utils.h>
#include <ucentral-platform.h>
#include <cstdint>
#include <vector>
namespace larch {
void create_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key,
const ucentral_router_fib_info &fib_info);
void remove_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key);
std::vector<ucentral_router_fib_node> get_routes(std::uint16_t router_id);
std::vector<plat_gw_address> get_gw_addresses();
void apply_route_config(plat_cfg *cfg);
} // namespace larch
#endif // !LARCH_PLATFORM_ROUTE_HPP_

View File

@@ -1,139 +0,0 @@
#include <sai_redis.hpp>
#include <state.hpp>
#include <sw/redis++/redis++.h>
#include <cstddef>
#include <cstdint>
#include <iterator> // std::inserter
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace larch::sai {
std::unordered_map<object_id, object_id> get_bridge_port_mapping()
{
std::unordered_map<object_id, object_id> mapping;
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
do
{
constexpr std::string_view pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT:*";
// Example key is
// ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT:oid:0x3a000000000616
// and we need to get only the 3a00... part. -1 here is for the
// trailing '*' at the end of the pattern.
constexpr std::size_t offset =
pattern.size() - 1 + oid_prefix.size();
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
for (const auto &key : keys)
{
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
key,
std::inserter(entry, entry.begin()));
const auto it =
entry.find("SAI_BRIDGE_PORT_ATTR_PORT_ID");
if (it != entry.cend())
{
mapping[key.substr(offset)] =
it->second.substr(oid_prefix.size());
}
}
} while (cursor != 0);
return mapping;
}
std::unordered_map<object_id, std::string> get_port_name_mapping()
{
std::unordered_map<std::string, std::string> entry;
state->redis_counters->hgetall(
"COUNTERS_PORT_NAME_MAP",
std::inserter(entry, entry.begin()));
state->redis_counters->hgetall(
"COUNTERS_LAG_NAME_MAP",
std::inserter(entry, entry.begin()));
std::unordered_map<object_id, std::string> mapping;
for (auto it = entry.cbegin(); it != entry.cend();)
{
// TO-DO: validate interface name?
auto node = entry.extract(it++);
mapping.try_emplace(
node.mapped().substr(oid_prefix.size()),
std::move(node.key()));
}
return mapping;
}
std::optional<std::uint16_t> get_vlan_by_oid(const object_id &oid)
{
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
const std::string pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_VLAN:" + std::string{oid_prefix} + oid;
// There is no guarantee that the necessary key will be found during the
// first scan, so we need to scan until we find it
do
{
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
if (keys.empty())
continue;
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
*keys.begin(),
std::inserter(entry, entry.begin()));
const auto it = entry.find("SAI_VLAN_ATTR_VLAN_ID");
if (it == entry.cend())
return std::nullopt;
try
{
return static_cast<std::uint16_t>(
std::stoul(it->second));
}
catch (const std::logic_error &)
{
throw std::runtime_error{"Failed to parse VLAN ID"};
}
{}
} while (cursor != 0);
throw std::runtime_error{"Failed to get VLAN by object ID"};
}
} // namespace larch::sai

View File

@@ -1,46 +0,0 @@
/**
* @file sai_redis.hpp
* @brief Commonly used functions to interact with SAI via Redis DB.
*
* Note, that object IDs (OIDs) are used without "oid:0x" prefix.
*/
#ifndef LARCH_PLATFORM_SAI_REDIS_HPP_
#define LARCH_PLATFORM_SAI_REDIS_HPP_
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
namespace larch::sai {
using object_id = std::string;
inline constexpr std::string_view oid_prefix = "oid:0x";
/**
* @brief Get mapping of bridge port OIDs to port OIDs.
*
* @return Map with bridge port OID as a key and port OID as a value.
*/
std::unordered_map<object_id, object_id> get_bridge_port_mapping();
/**
* @brief Get mapping of port OIDs to port names.
*
* @return Map with port OID and port name as a value.
*/
std::unordered_map<object_id, std::string> get_port_name_mapping();
/**
* @brief Get VLAN ID by its OID.
*
* @throw std::runtime_error If VLAN can't be found
*/
std::optional<std::uint16_t> get_vlan_by_oid(const object_id &oid);
} // namespace larch::sai
#endif // !LARCH_PLATFORM_SAI_REDIS_HPP_

View File

@@ -1,114 +0,0 @@
#include <services.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <arpa/inet.h>
#include <array>
#include <string>
#include <unordered_set>
using nlohmann::json;
namespace larch {
static std::unordered_set<std::string> get_existing_ntp_servers()
{
const json existing_servers_json = json::parse(
gnmi_get("/sonic-ntp:sonic-ntp/NTP_SERVER/NTP_SERVER_LIST"));
std::unordered_set<std::string> existing_servers;
if (existing_servers_json.contains("sonic-ntp:NTP_SERVER_LIST"))
{
for (const auto &server_json :
existing_servers_json.at("sonic-ntp:NTP_SERVER_LIST"))
{
if (!server_json.contains("server_address"))
continue;
existing_servers.insert(
server_json.at("server_address")
.template get<std::string>());
}
}
return existing_servers;
}
static void apply_ntp_config(const plat_ntp_cfg *ntp_cfg)
{
if (ntp_cfg->servers)
{
gnmi_operation op;
std::unordered_set<std::string> existing_servers =
get_existing_ntp_servers();
json ntp_json;
json &server_list_json = ntp_json["sonic-ntp:NTP_SERVER_LIST"];
server_list_json = json::array();
const plat_ntp_server *it = nullptr;
UCENTRAL_LIST_FOR_EACH_MEMBER(it, &ntp_cfg->servers)
{
const auto existing_it =
existing_servers.find(it->hostname);
if (existing_it != existing_servers.cend())
{
existing_servers.erase(existing_it);
continue;
}
std::array<unsigned char, sizeof(in6_addr)> addr_buf{};
if (inet_pton(AF_INET, it->hostname, addr_buf.data())
!= 1
&& inet_pton(
AF_INET6,
it->hostname,
addr_buf.data())
!= 1)
{
UC_LOG_ERR(
"Domains are not supported in NTP server "
"list, use IP addresses");
continue;
}
json server_json;
server_json["association_type"] = "server";
server_json["server_address"] = it->hostname;
server_json["resolve_as"] = it->hostname;
server_list_json.push_back(server_json);
}
op.add_update(
"/sonic-ntp:sonic-ntp/NTP_SERVER/NTP_SERVER_LIST",
ntp_json.dump());
for (const auto &server : existing_servers)
{
op.add_delete(
"/sonic-ntp:sonic-ntp/NTP_SERVER/"
"NTP_SERVER_LIST[server_address="
+ server + "]");
}
op.execute();
}
}
void apply_services_config(plat_cfg *cfg)
{
apply_ntp_config(&cfg->ntp_cfg);
}
} // namespace larch

View File

@@ -1,12 +0,0 @@
#ifndef LARCH_PLATFORM_SERVICES_HPP_
#define LARCH_PLATFORM_SERVICES_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_services_config(plat_cfg *cfg);
}
#endif // !LARCH_PLATFORM_SERVICES_HPP_

View File

@@ -1,10 +0,0 @@
#include <metrics.hpp>
#include <state.hpp>
#include <sw/redis++/redis++.h>
namespace larch {
platform_state::~platform_state() = default;
} // namespace larch

View File

@@ -1,53 +0,0 @@
#ifndef LARCH_PLATFORM_STATE_HPP_
#define LARCH_PLATFORM_STATE_HPP_
#include <gnmi.grpc.pb.h>
#include <gnmi.pb.h>
#include <sonic_gnoi.grpc.pb.h>
#include <sonic_gnoi.pb.h>
#include <system.grpc.pb.h>
#include <system.pb.h>
#include <grpc++/grpc++.h>
#include <router-utils.h>
#include <ucentral-platform.h>
#include <memory>
#include <vector>
// Forward declarations
namespace sw::redis {
class Redis;
}
namespace larch {
class periodic;
struct platform_state {
~platform_state();
std::shared_ptr<grpc::ChannelInterface> channel;
std::unique_ptr<gnmi::gNMI::Stub> gnmi_stub;
std::unique_ptr<gnoi::system::System::Stub> system_gnoi_stub;
std::unique_ptr<gnoi::sonic::SonicService::Stub> stub_gnoi_sonic;
std::unique_ptr<periodic> telemetry_periodic;
std::unique_ptr<periodic> state_periodic;
std::unique_ptr<periodic> health_periodic;
std::unique_ptr<sw::redis::Redis> redis_asic;
std::unique_ptr<sw::redis::Redis> redis_counters;
std::vector<plat_ipv4> interfaces_addrs;
ucentral_router router{};
};
inline std::unique_ptr<platform_state> state;
} // namespace larch
#endif // !LARCH_PLATFORM_STATE_HPP_

View File

@@ -1,107 +0,0 @@
#include <stp.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <string>
using nlohmann::json;
namespace larch {
void apply_stp_config(struct plat_cfg *cfg)
{
std::size_t i = 0;
gnmi_operation op;
switch (cfg->stp_mode) {
case PLAT_STP_MODE_NONE:
{
const auto stp_list = gnmi_get("/sonic-spanning-tree:sonic-spanning-tree/STP/STP_LIST");
const json stp_list_json = json::parse(stp_list);
/* There are no STPs */
if (!stp_list_json.contains("sonic-spanning-tree:STP_LIST"))
return;
/* This will clear all per port/vlan stp entries */
/* Delete global config since you cannot change the stp mode otherwise */
op.add_delete("/sonic-spanning-tree:sonic-spanning-tree/STP/STP_LIST[keyleaf=GLOBAL]");
break;
}
case PLAT_STP_MODE_PVST:
{
/* Config mode */
json stp_cfg_mode_json;
stp_cfg_mode_json["priority"] = cfg->stp_instances[0].priority;
stp_cfg_mode_json["keyleaf"] = "GLOBAL";
stp_cfg_mode_json["bpdu_filter"] = false;
stp_cfg_mode_json["mode"] = "pvst";
stp_cfg_mode_json["rootguard_timeout"] = 30;
json add_stp_cfg_mode_json;
add_stp_cfg_mode_json["sonic-spanning-tree:STP_LIST"] = {stp_cfg_mode_json};
op.add_update("/sonic-spanning-tree:sonic-spanning-tree/STP/STP_LIST", add_stp_cfg_mode_json.dump());
/* Once mode enabled - create entries for all ports */
BITMAP_FOR_EACH_BIT_SET(i, cfg->ports_to_cfg, MAX_NUM_OF_PORTS)
{
json stp_port_json;
stp_port_json["ifname"] = "Ethernet" + std::to_string(i);
stp_port_json["enabled"] = true;
json add_stp_port_json;
add_stp_port_json["sonic-spanning-tree:STP_PORT_LIST"] = {stp_port_json};
op.add_update("/sonic-spanning-tree:sonic-spanning-tree/STP_PORT/STP_PORT_LIST", add_stp_port_json.dump());
}
/* Config vlans */
for (i = FIRST_VLAN; i < MAX_VLANS; i++) {
if (!cfg->stp_instances[i].enabled) {
continue;
}
json stp_vlan_json;
stp_vlan_json["vlanid"] = i;
stp_vlan_json["name"] = "Vlan" + std::to_string(i);
stp_vlan_json["enabled"] = cfg->stp_instances[i].enabled;
stp_vlan_json["priority"] = cfg->stp_instances[i].priority;
stp_vlan_json["forward_delay"] = cfg->stp_instances[i].forward_delay;
stp_vlan_json["hello_time"] = cfg->stp_instances[i].hello_time;
stp_vlan_json["max_age"] = cfg->stp_instances[i].max_age;
json add_stp_vlan_json;
add_stp_vlan_json["sonic-spanning-tree:STP_VLAN_LIST"] = {stp_vlan_json};
UC_LOG_DBG(
"set vlan=%d state.enabled=%d state.priority=%d "
"state.forward_delay=%d state.hello_time=%d "
"state.max_age=%d ",
i,
cfg->stp_instances[i].enabled,
cfg->stp_instances[i].priority,
cfg->stp_instances[i].forward_delay,
cfg->stp_instances[i].hello_time,
cfg->stp_instances[i].max_age);
op.add_update("/sonic-spanning-tree:sonic-spanning-tree/STP_VLAN/STP_VLAN_LIST", add_stp_vlan_json.dump());
}
break;
}
default:
throw std::runtime_error{"Failed apply stp config"};
}
op.execute();
}
}

View File

@@ -1,12 +0,0 @@
#ifndef LARCH_PLATFORM_STP_HPP_
#define LARCH_PLATFORM_STP_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_stp_config(plat_cfg *cfg);
}
#endif // !LARCH_PLATFORM_STP_HPP_

View File

@@ -1,61 +0,0 @@
#include <syslog.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <string>
using nlohmann::json;
namespace larch {
void gnma_syslog_cfg_clear(void)
{
std::string path = "/sonic-syslog:sonic-syslog/SYSLOG_SERVER/SYSLOG_SERVER_LIST";
gnmi_operation op;
op.add_delete(path);
op.execute();
}
void apply_syslog_config(struct plat_syslog_cfg *cfg, int count)
{
std::string path = "/sonic-syslog:sonic-syslog/SYSLOG_SERVER/SYSLOG_SERVER_LIST";
const std::array<std::string, 8> priority2str = {
"crit",
"crit",
"crit",
"error",
"warn",
"notice",
"info",
"debug"
};
int i;
gnmi_operation op;
for (i = 0; i < count; ++i)
{
json syslog_cfg_member_json;
syslog_cfg_member_json["server_address"] = cfg[i].host;
syslog_cfg_member_json["port"] = cfg[i].port;
syslog_cfg_member_json["protocol"] = cfg[i].is_tcp ? "tcp" : "udp";
syslog_cfg_member_json["severity"] = priority2str.at(cfg[i].priority);
syslog_cfg_member_json["vrf"] = "default";
json add_syslog_cfg_member_json;
add_syslog_cfg_member_json["sonic-syslog:SYSLOG_SERVER_LIST"] = {syslog_cfg_member_json};
op.add_update(path, add_syslog_cfg_member_json.dump());
}
gnma_syslog_cfg_clear();
op.execute();
}
}

View File

@@ -1,14 +0,0 @@
#ifndef LARCH_PLATFORM_SYSLOG_HPP_
#define LARCH_PLATFORM_SYSLOG_HPP_
#include <ucentral-platform.h>
namespace larch {
void gnma_syslog_cfg_clear(void);
void apply_syslog_config(struct plat_syslog_cfg *cfg, int count);
}
#endif // !LARCH_PLATFORM_SYSLOG_HPP_

View File

@@ -1,308 +0,0 @@
#include <state.hpp>
#include <utils.hpp>
#include <gnmi.grpc.pb.h>
#include <gnmi.pb.h>
#include <grpcpp/grpcpp.h>
#include <arpa/inet.h>
#include <array>
#include <cstring>
#include <map>
#include <stdexcept>
#include <string>
#include <utility> // std::move
#include <vector>
namespace larch {
std::vector<std::string>
split_string(std::string str, const std::string &delimiter)
{
std::vector<std::string> results;
std::string::size_type pos{};
while ((pos = str.find(delimiter)) != std::string::npos)
{
results.push_back(str.substr(0, pos));
str.erase(0, pos + 1);
}
// Process the last part
results.push_back(std::move(str));
return results;
}
std::map<std::string, std::string> parse_kv(const std::string &kv_str)
{
enum class parse_state { open_bracket, close_bracket, key, value };
parse_state state = parse_state::close_bracket;
std::map<std::string, std::string> kv;
std::string key_buf, value_buf;
for (const char c : kv_str)
{
switch (c)
{
case '[':
if (state != parse_state::close_bracket)
throw std::runtime_error{
"Unexpected opening bracket"};
state = parse_state::open_bracket;
break;
case ']':
if (state != parse_state::value)
throw std::runtime_error{
"Unexpected closing bracket"};
state = parse_state::close_bracket;
if (key_buf.empty())
throw std::runtime_error{"Empty key"};
if (value_buf.empty())
throw std::runtime_error{"Empty value"};
kv.emplace(
std::move(key_buf),
std::move(value_buf));
key_buf.clear();
value_buf.clear();
break;
case '=':
if (state != parse_state::key)
throw std::runtime_error{
"Unexpected equals sign"};
state = parse_state::value;
break;
default:
{
if (state == parse_state::open_bracket)
state = parse_state::key;
if (state == parse_state::key)
key_buf.push_back(c);
else if (state == parse_state::value)
value_buf.push_back(c);
else
throw std::runtime_error{
"Unexpected character '"
+ std::string{c} + "'"};
break;
}
}
}
if (state != parse_state::close_bracket)
throw std::runtime_error{"Couldn't find closing bracket"};
return kv;
}
void convert_yang_path_to_proto(std::string yang_path, gnmi::Path *proto_path)
{
struct path_element {
std::string name;
std::map<std::string, std::string> kv;
};
std::vector<path_element> elements;
auto process_elem_str = [&elements](std::string elem_str) {
if (!elem_str.empty())
{
const auto open_bracket_pos = elem_str.find('[');
if (open_bracket_pos == std::string::npos)
{
elements.push_back({std::move(elem_str), {}});
}
else
{
// Parse the key-value part of YANG path
// (e.g. [Vlan=100][SomeKey=SomeValue]...)
try
{
elements.push_back(
{elem_str.substr(
0,
open_bracket_pos),
parse_kv(elem_str.substr(
open_bracket_pos))});
}
catch (const std::runtime_error &ex)
{
using namespace std::string_literals;
throw std::runtime_error{
"Failed to parse key-value part of YANG path: "s
+ ex.what()};
}
}
}
};
std::string::size_type pos{};
while ((pos = yang_path.find('/')) != std::string::npos)
{
process_elem_str(yang_path.substr(0, pos));
yang_path.erase(0, pos + 1);
}
// Process the last part of split string
process_elem_str(std::move(yang_path));
std::string &first_element = elements[0].name;
const auto colon_pos = first_element.find(':');
if (colon_pos != std::string::npos)
{
proto_path->set_origin(first_element.substr(0, colon_pos));
first_element.erase(0, colon_pos + 1);
}
for (const auto &elem : elements)
{
gnmi::PathElem *path_elem = proto_path->add_elem();
path_elem->set_name(elem.name);
if (!elem.kv.empty())
{
auto path_kv = path_elem->mutable_key();
for (const auto &[key, value] : elem.kv)
(*path_kv)[key] = value;
}
}
}
std::string gnmi_get(const std::string &yang_path)
{
gnmi::GetRequest greq;
greq.set_encoding(gnmi::JSON_IETF);
convert_yang_path_to_proto(yang_path, greq.add_path());
grpc::ClientContext context;
gnmi::GetResponse gres;
const grpc::Status status = state->gnmi_stub->Get(&context, greq, &gres);
if (!status.ok())
{
throw gnmi_exception{
"gNMI get operation wasn't successful: "
+ status.error_message() + "; error code "
+ std::to_string(status.error_code())};
}
if (gres.notification_size() != 1)
{
throw gnmi_exception{"Unsupported notification size"};
}
gnmi::Notification notification = gres.notification(0);
if (notification.update_size() != 1)
{
throw gnmi_exception{"Unsupported update size"};
}
gnmi::Update update = notification.update(0);
if (!update.has_val())
{
throw gnmi_exception{"Empty value"};
}
gnmi::TypedValue value = update.val();
if (!value.has_json_ietf_val())
{
throw gnmi_exception{"Empty JSON value"};
}
return value.json_ietf_val();
}
void gnmi_set(const std::string &yang_path, const std::string &json_data)
{
gnmi_operation op;
op.add_update(yang_path, json_data);
op.execute();
}
void gnmi_operation::add_update(const std::string &yang_path, const std::string &json_data)
{
gnmi::Update *update = set_request_.add_update();
convert_yang_path_to_proto(yang_path, update->mutable_path());
update->mutable_val()->set_json_ietf_val(json_data);
}
void gnmi_operation::add_delete(const std::string &yang_path)
{
convert_yang_path_to_proto(yang_path, set_request_.add_delete_());
}
void gnmi_operation::execute()
{
grpc::ClientContext context;
gnmi::SetResponse response;
const grpc::Status status = state->gnmi_stub->Set(&context, set_request_, &response);
set_request_.Clear();
if (!status.ok())
{
throw gnmi_exception{
"gNMI set operation wasn't successful: "
+ status.error_message() + "; error code "
+ std::to_string(status.error_code())};
}
}
std::string addr_to_str(const in_addr &address)
{
std::array<char, INET_ADDRSTRLEN> addr_str_buf{};
if (!inet_ntop(
AF_INET,
&address,
addr_str_buf.data(),
addr_str_buf.size()))
{
throw std::runtime_error{
"Failed to convert binary IP address to string"};
}
return addr_str_buf.data();
}
cidr parse_cidr(const std::string &str)
{
cidr result{};
std::array<char, 16> addr_buf{};
if (std::sscanf(
str.c_str(),
"%15[^/]/%hhu",
addr_buf.data(),
&result.mask)
!= 2)
throw std::runtime_error{"Failed to parse CIDR notation"};
result.ip_address = addr_buf.data();
return result;
}
}

View File

@@ -1,93 +0,0 @@
#ifndef LARCH_PLATFORM_UTILS_HPP_
#define LARCH_PLATFORM_UTILS_HPP_
#include <grpcpp/grpcpp.h>
#include <gnmi.pb.h>
#include <arpa/inet.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>
namespace larch {
std::vector<std::string>
split_string(std::string str, const std::string &delimiter);
void convert_yang_path_to_proto(std::string yang_path, gnmi::Path *proto_path);
class gnmi_exception : public std::runtime_error {
using std::runtime_error::runtime_error;
};
/**
* @brief Get value by specified path.
*
* @throw std::runtime_error If get request wasn't successful
*/
std::string gnmi_get(const std::string &yang_path);
/**
* @brief Convenience wrapper to set only one entry.
*
* @throw std::runtime_error If set request wasn't successful
*/
void gnmi_set(const std::string &yang_path, const std::string &json_data);
class gnmi_operation {
public:
gnmi_operation() = default;
void add_update(const std::string &yang_path, const std::string &json_data);
void add_delete(const std::string &yang_path);
/**
* @brief Execute the previously added modifications.
*
* @throw std::runtime_error If set request wasn't successful
*/
void execute();
protected:
gnmi::SetRequest set_request_;
};
/**
* @brief Convert address from binary form to text form.
*
* @throw std::runtime_error If conversion failed
*/
std::string addr_to_str(const in_addr &address);
/**
* @brief Verifier that marks all the certificates as valid.
*/
class certificate_verifier : public grpc::experimental::ExternalCertificateVerifier {
public:
bool Verify(
grpc::experimental::TlsCustomVerificationCheckRequest *request,
std::function<void(grpc::Status)> callback,
grpc::Status *sync_status) override
{
(void)request;
(void)callback;
*sync_status = grpc::Status(grpc::StatusCode::OK, "");
return true;
}
void Cancel(grpc::experimental::TlsCustomVerificationCheckRequest *) override {}
};
struct cidr {
std::string ip_address;
std::uint16_t mask;
};
cidr parse_cidr(const std::string &str);
}
#endif // !LARCH_PLATFORM_UTILS_HPP_

View File

@@ -1,229 +0,0 @@
#include <igmp.hpp>
#include <vlan.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>
#include <tuple>
#include <utility> // std::move
using nlohmann::json;
using std::bitset;
using std::vector;
namespace larch {
void del_nonconfig_vlans(BITMAP_DECLARE(vlans_to_cfg, MAX_VLANS))
{
const auto vlan_list = gnmi_get("/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST");
gnmi_operation op;
const json vlan_list_json = json::parse(vlan_list);
// There are no VLANs
if (!vlan_list_json.contains("sonic-vlan:VLAN_LIST"))
return;
for (const auto vlan : vlan_list_json.at("sonic-vlan:VLAN_LIST"))
{
const int vlan_id = vlan.at("vlanid").template get<int>();
if (vlan_id < MAX_VLANS)
{
if (BITMAP_TEST_BIT(vlans_to_cfg, vlan_id))
continue;
if (vlan_id == FIRST_VLAN)
continue;
op.add_delete("/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[name=Vlan" + std::to_string(vlan_id) + "]");
}
}
op.execute();
}
std::tuple<vector<bitset<MAX_NUM_OF_PORTS>>, vector<bitset<MAX_NUM_OF_PORTS>>>
get_vlan_membership()
{
vector<bitset<MAX_NUM_OF_PORTS>> vlan_membership(MAX_VLANS);
vector<bitset<MAX_NUM_OF_PORTS>> vlan_tagged(MAX_VLANS);
const auto vlan_membership_data = gnmi_get("/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST");
const json vlan_membership_json = json::parse(vlan_membership_data);
for (const auto entry : vlan_membership_json.value(
"sonic-vlan:VLAN_MEMBER_LIST",
json::array()))
{
std::uint16_t vlan_id = 0;
std::uint16_t port_id = 0;
if (NAME_TO_VLAN(&vlan_id, entry.at("name").template get<std::string>().c_str()) < 1)
{
UC_LOG_ERR("Failed to parse VLAN ID");
throw std::runtime_error{"Failed to parse VLAN ID"};
}
if (NAME_TO_PID(&port_id, entry.at("port").template get<std::string>().c_str()) < 1)
{
UC_LOG_ERR("Failed to parse port ID");
throw std::runtime_error{"Failed to parse port ID"};
}
vlan_membership[vlan_id].set(port_id);
if (entry.at("tagging_mode").template get<std::string>() == "tagged")
vlan_tagged[vlan_id].set(port_id);
}
return {std::move(vlan_membership), std::move(vlan_tagged)};
}
void apply_vlan_config(plat_cfg *cfg)
{
if (BITMAP_TEST_BIT(cfg->vlans_to_cfg, FIRST_VLAN))
{
UC_LOG_ERR("VLAN 1 is reserved and cannot be configured");
throw std::runtime_error{"VLAN 1 is reserved and cannot be configured"};
}
// Step 1: delete VLANs that currently exist, but are not present in the supplied config
del_nonconfig_vlans(cfg->vlans_to_cfg);
const auto [vlan_membership, vlan_tagged] = get_vlan_membership();
gnmi_operation op;
std::size_t i = 0;
BITMAP_FOR_EACH_BIT_SET(i, cfg->vlans_to_cfg, MAX_VLANS)
{
const plat_port_vlan *vlan = &cfg->vlans[i];
// Step 2: create the VLAN
json vlan_json;
vlan_json["vlanid"] = vlan->id;
vlan_json["name"] = "Vlan" + std::to_string(vlan->id);
// add DHCP Relay server
if (vlan->dhcp.relay.enabled)
{
const std::string addr_str = addr_to_str(vlan->dhcp.relay.server_address);
std::array<unsigned char, sizeof(in6_addr)> addr_buf{};
if (inet_pton(AF_INET, addr_str.c_str(), addr_buf.data()) == 1)
{
vlan_json["dhcp_servers"] = json::array();
vlan_json["dhcp_servers"].push_back(addr_str);
}
else if (inet_pton(AF_INET6, addr_str.c_str(), addr_buf.data()) == 1)
{
vlan_json["dhcpv6_servers"] = json::array();
vlan_json["dhcpv6_servers"].push_back(addr_str);
}
else
{
UC_LOG_ERR("Failed to parse DHCP Relay server address");
throw std::runtime_error{"Failed to parse DHCP Relay server address"};
}
}
json add_vlan_json;
add_vlan_json["sonic-vlan:VLAN_LIST"] = {vlan_json};
op.add_update("/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST", add_vlan_json.dump());
// Step 3: delete VLAN members that are not in the config
bitset<MAX_NUM_OF_PORTS> vlan_members_config;
bitset<MAX_NUM_OF_PORTS> vlan_tagged_config;
for (plat_vlan_memberlist *pv = vlan->members_list_head; pv; pv = pv->next)
{
std::uint16_t port_id = 0;
if (NAME_TO_PID(&port_id, pv->port.name) < 1)
{
UC_LOG_ERR("Failed to parse port ID");
throw std::runtime_error{"Failed to parse port ID"};
}
vlan_members_config.set(port_id);
if (pv->tagged)
vlan_tagged_config.set(port_id);
}
// Get bits that are set in the first bitset, but not set in the second one
const auto vlan_members_to_delete = vlan_membership[vlan->id] & ~vlan_members_config;
for (std::size_t port_id = 0; port_id < vlan_members_to_delete.size(); ++port_id)
{
if (vlan_members_to_delete[port_id])
{
op.add_delete(
"/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[name=Vlan"
+ std::to_string(i) + "][port=Ethernet" + std::to_string(port_id) + "]");
}
}
// Step 4: add VLAN members
for (std::size_t port_id = 0; port_id < vlan_members_config.size(); ++port_id)
{
if (!vlan_members_config[port_id])
continue;
const bool tagged = vlan_tagged_config.test(port_id);
// VLAN member is already configured in the same way as in the config, skipping
if (vlan_membership[vlan->id].test(port_id) && tagged == vlan_tagged[vlan->id].test(port_id))
continue;
json vlan_member_json;
vlan_member_json["name"] = "Vlan" + std::to_string(vlan->id);
vlan_member_json["port"] = "Ethernet" + std::to_string(port_id);
vlan_member_json["tagging_mode"] = tagged ? "tagged" : "untagged";
json add_vlan_member_json;
add_vlan_member_json["sonic-vlan:VLAN_MEMBER_LIST"] = {vlan_member_json};
op.add_update("/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST", add_vlan_member_json.dump());
}
}
op.execute();
}
void apply_vlan_ipv4_config(struct plat_cfg *cfg)
{
size_t i;
BITMAP_FOR_EACH_BIT_SET(i, cfg->vlans_to_cfg, MAX_VLANS)
{
if (i == FIRST_VLAN)
{
UC_LOG_DBG("Skipping L3 configuration for default vlan\n");
continue;
}
UC_LOG_DBG("Configuring vlan ip <%u>\n", (uint16_t)i);
// TODO: configure vlan IP
if (cfg->vlans[i].igmp.exist)
{
apply_igmp_config(cfg->vlans[i].id, &cfg->vlans[i].igmp);
}
}
}
}

View File

@@ -1,14 +0,0 @@
#ifndef LARCH_PLATFORM_VLAN_HPP_
#define LARCH_PLATFORM_VLAN_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_vlan_config(plat_cfg *cfg);
void apply_vlan_ipv4_config(plat_cfg *cfg);
}
#endif // !LARCH_PLATFORM_VLAN_HPP_

View File

@@ -10,11 +10,33 @@
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PROTO
#include "ucentral.h"
#include "est-client.h"
#define CONFIGURE_STATUS_REJECTED 2
#define CONFIGURE_STATUS_PARTIALLY_APPLIED 1
#define CONFIGURE_STATUS_APPLIED 0
/*
* Test Framework Support - Conditional Function Visibility
*
* RATIONALE: Allows cfg_parse() to be tested in isolation by the test framework
* in tests/config-parser/. When UCENTRAL_TESTING is defined during test builds,
* TEST_STATIC expands to empty (making functions visible). In production builds,
* TEST_STATIC expands to 'static' (keeping functions private).
*
* PRODUCTION IMPACT: Zero impact. Production builds never define UCENTRAL_TESTING,
* so TEST_STATIC always expands to 'static', maintaining identical behavior to
* original code. No changes to ABI, performance, or runtime behavior.
*
* TEST USAGE: Test builds compile with -DUCENTRAL_TESTING flag, making cfg_parse()
* callable from test code in tests/config-parser/test-config-parser.c
*/
#ifdef UCENTRAL_TESTING
#define TEST_STATIC /* empty - makes static functions visible to tests */
#else
#define TEST_STATIC static
#endif
struct blob {
cJSON *obj;
char *rendered_string;
@@ -406,17 +428,46 @@ err:
proto_destroy_blob(&blob);
}
void
connect_send(void)
{
static cJSON *readJsonFile(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
fprintf(stderr, "Error opening file: %s\n", filename);
return NULL;
}
cJSON *ret;
// Get the file size
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
// Read the entire file into a buffer
char *buffer = (char *)malloc(fileSize + 1);
fread(buffer, 1, fileSize, file);
buffer[fileSize] = '\0'; // Null-terminate the string
// Close the file
fclose(file);
ret = cJSON_Parse(buffer);
return ret;
}
void connect_send(void) {
/* WIP: TMP hardcode; to be removed*/
unsigned mac[6];
struct plat_platform_info pinfo = {0};
struct plat_metrics_cfg restore_metrics = { 0 };
struct plat_metrics_cfg restore_metrics = {0};
struct blob blob = {0};
uint64_t uuid_buf; /* fixed storage size */
cJSON *params;
cJSON *cap;
cJSON *ver;
int ret;
blob.obj = proto_new_blob("connect");
@@ -446,7 +497,6 @@ connect_send(void)
if (password) {
if (!cJSON_AddStringToObject(params, "password", password))
goto err;
memset(password, 0, strlen(password));
free(password);
password = NULL;
@@ -456,30 +506,60 @@ connect_send(void)
if (!cap)
goto err;
ver = cJSON_AddObjectToObject(cap, "version");
if (!ver)
goto err;
if (plat_info_get(&pinfo)) {
UC_LOG_CRIT("failed to get platform info");
} else {
if (!cJSON_AddStringToObject(cap, "compatible", pinfo.hwsku))
goto err;
if (!cJSON_AddStringToObject(cap, "model", pinfo.platform))
goto err;
}
if (!cJSON_AddStringToObject(cap, "serial", client.serial))
goto err;
if (!cJSON_AddStringToObject(cap, "firmware", client.firmware))
goto err;
cJSON *client_version_json = readJsonFile(client.ols_client_version_file);
if (!client_version_json)
goto err;
if (!cJSON_AddItemToObject(ver, "switch", client_version_json))
goto err;
else
UC_LOG_DBG("client version added to connect.capabilities.version");
cJSON *schema_version_json = readJsonFile(client.ols_schema_version_file);
if (!schema_version_json) {
UC_LOG_DBG("No schema version present.");
}
else {
if (!cJSON_AddItemToObject(ver, "schema", schema_version_json))
goto err;
else
UC_LOG_DBG("schema version added to connect.capabilities.version");
}
if (!cJSON_AddStringToObject(cap, "compatible", pinfo.hwsku))
goto err;
if (!cJSON_AddStringToObject(cap, "model", pinfo.platform))
goto err;
if (!cJSON_AddStringToObject(cap, "platform", "switch"))
goto err;
if (client.serial &&
sscanf(client.serial, "%2x%2x%2x%2x%2x%2x",
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
sscanf(client.serial, "%2x%2x%2x%2x%2x%2x",
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
char label_mac[32];
snprintf(label_mac, sizeof label_mac,
"%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
"%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
if (!cJSON_AddStringToObject(cap, "label_macaddr", label_mac)) {
goto err;
}
} else {
}
else {
UC_LOG_DBG("failed to parse serial as label_macaddr");
}
@@ -490,11 +570,11 @@ connect_send(void)
if (ucentral_metrics.state.enabled)
plat_state_poll(state_send,
ucentral_metrics.state.interval);
ucentral_metrics.state.interval);
if (ucentral_metrics.healthcheck.enabled)
plat_health_poll(health_send,
ucentral_metrics.healthcheck.interval);
ucentral_metrics.healthcheck.interval);
}
UC_LOG_DBG("xmit connect\n");
@@ -1522,49 +1602,6 @@ err:
return ret;
}
static int cfg_service_ntp_parse(const cJSON *s, struct plat_ntp_cfg *ntp_cfg)
{
int ret = -1;
cJSON *servers;
const cJSON *server_json;
const char *hostname;
struct plat_ntp_server *server;
servers = cJSON_GetObjectItemCaseSensitive(s, "servers");
if (!cJSON_IsArray(servers)) {
UC_LOG_ERR("Unexpected type of services:ntp:servers: Array expected");
goto err;
}
cJSON_ArrayForEach(server_json, servers) {
if (!cJSON_IsString(server_json)) {
UC_LOG_ERR("Unexpected type of services:ntp:servers:<element>: String expected");
continue;
}
hostname = cJSON_GetStringValue(server_json);
if (!hostname) {
UC_LOG_ERR("Cannot read services:ntp:servers:<element (string)>");
continue;
}
server = calloc(1, sizeof(struct plat_ntp_server));
if (!server) {
UC_LOG_ERR("calloc failed");
continue;
}
strncpy(server->hostname, hostname, sizeof(server->hostname) - 1);
UCENTRAL_LIST_PUSH_MEMBER(&ntp_cfg->servers, server);
}
ret = 0;
err:
return ret;
}
static int cfg_services_parse(cJSON *services, struct plat_cfg *cfg)
{
cJSON *s;
@@ -1641,17 +1678,6 @@ static int cfg_services_parse(cJSON *services, struct plat_cfg *cfg)
cfg->enabled_services_cfg.http.enabled = cJSON_IsTrue(enable);
}
s = cJSON_GetObjectItemCaseSensitive(services, "ntp");
if (s) {
if (!cJSON_IsObject(s)) {
UC_LOG_ERR("Unexpected type of services:ntp: Object expected");
return -1;
}
if (cfg_service_ntp_parse(s, &cfg->ntp_cfg))
return -1;
}
return 0;
}
@@ -1743,7 +1769,7 @@ static int cfg_switch_ieee8021x_parse(cJSON *sw, struct plat_cfg *cfg)
static int cfg_switch_parse(cJSON *root, struct plat_cfg *cfg)
{
cJSON *sw, *obj, *iter, *arr, *port_isolation, *jumbo_frames;
cJSON *sw, *obj, *iter, *arr, *port_isolation;
BITMAP_DECLARE(instances_parsed, MAX_VLANS);
int id, prio, fwd, hello, age;
bool enabled;
@@ -1817,14 +1843,6 @@ static int cfg_switch_parse(cJSON *root, struct plat_cfg *cfg)
return -1;
}
jumbo_frames = cJSON_GetObjectItemCaseSensitive(sw, "jumbo-frames");
if (jumbo_frames && !cJSON_IsBool(jumbo_frames)) {
UC_LOG_ERR("Unexpected type of switch:jumbo-frames: Boolean expected");
return -1;
}
cfg->jumbo_frames = cJSON_IsTrue(jumbo_frames);
return 0;
}
@@ -2059,7 +2077,7 @@ static int cfg_unit_parse(cJSON *unit, struct plat_cfg *cfg)
return 0;
}
static struct plat_cfg * cfg_parse(cJSON *config)
TEST_STATIC struct plat_cfg * cfg_parse(cJSON *config)
{
struct plat_ports_list *port_node = NULL;
struct plat_ports_list *ports = NULL;
@@ -2214,14 +2232,6 @@ static struct plat_cfg * cfg_parse(cJSON *config)
err_parse:
/* TODO: free all ports->vlans as well */
/* Empty statement is needed because labels can only be followed by
* statements (and declaration is not a statement)
*/
;
struct plat_ntp_server *ntp_node = NULL;
UCENTRAL_LIST_DESTROY_SAFE(&cfg->ntp_cfg.servers, ntp_node);
free(cfg->log_cfg);
free(cfg);
return NULL;
@@ -2325,9 +2335,6 @@ configure_handle(cJSON **rpc)
err_apply:
plat_config_destroy(plat_cfg);
struct plat_ntp_server *ntp_node = NULL;
UCENTRAL_LIST_DESTROY_SAFE(&plat_cfg->ntp_cfg.servers, ntp_node);
free(plat_cfg->log_cfg);
free(plat_cfg);
@@ -2368,6 +2375,79 @@ reboot_handle(cJSON **rpc)
UC_LOG_DBG("Reboot OK\n");
}
static void
reenroll_handle(cJSON **rpc)
{
cJSON *tb[__PARAMS_MAX] = {0};
double id = 0;
char *renewed_cert = NULL;
int ret;
const char *operational_cert = UCENTRAL_CONFIG "operational.pem";
const char *key_path = UCENTRAL_CONFIG "key.pem";
const char *ca_bundle = UCENTRAL_CONFIG "cas.pem";
const char *est_server;
FILE *fp;
tb[PARAMS_SERIAL] =
cJSON_GetObjectItemCaseSensitive(rpc[JSONRPC_PARAMS], "serial");
if (rpc[JSONRPC_ID])
id = cJSON_GetNumberValue(rpc[JSONRPC_ID]);
if (!tb[PARAMS_SERIAL]) {
UC_LOG_ERR("reenroll message is missing parameters\n");
action_reply(1, "invalid parameters", 1, id);
return;
}
/* Auto-detect EST server from certificate issuer */
est_server = est_get_server_url(operational_cert);
if (!est_server) {
UC_LOG_ERR("reenroll: Failed to detect EST server URL\n");
action_reply(1, "Failed to detect EST server", 1, id);
return;
}
UC_LOG_INFO("PKI 2.0: Re-enrolling certificate with EST server: %s\n", est_server);
/* Perform EST reenrollment */
ret = est_simple_reenroll(est_server, operational_cert, key_path,
ca_bundle, &renewed_cert);
if (ret != EST_SUCCESS) {
UC_LOG_ERR("reenroll: EST reenrollment failed: %s\n", est_get_error());
action_reply(1, "Certificate reenrollment failed", 1, id);
return;
}
/* Save renewed operational certificate */
fp = fopen(operational_cert, "w");
if (!fp) {
UC_LOG_ERR("reenroll: Failed to open %s for writing\n", operational_cert);
free(renewed_cert);
action_reply(1, "Failed to save renewed certificate", 1, id);
return;
}
if (fwrite(renewed_cert, 1, strlen(renewed_cert), fp) != strlen(renewed_cert)) {
UC_LOG_ERR("reenroll: Failed to write renewed certificate\n");
fclose(fp);
free(renewed_cert);
action_reply(1, "Failed to save renewed certificate", 1, id);
return;
}
fclose(fp);
free(renewed_cert);
UC_LOG_INFO("PKI 2.0: Certificate renewed successfully, saved to %s\n", operational_cert);
/* Schedule client restart after 10 seconds to use new certificate */
alarm(10);
action_reply(0, "Certificate reenrollment successful, restarting in 10 seconds", 0, id);
UC_LOG_DBG("Reenroll OK\n");
}
static void
factory_handle(cJSON **rpc)
{
@@ -4187,6 +4267,8 @@ proto_handle_blob(struct blob *blob)
ping_handle(rpc);
else if (!strcmp(method, "reboot"))
reboot_handle(rpc);
else if (!strcmp(method, "reenroll"))
reenroll_handle(rpc);
else if (!strcmp(method, "factory"))
factory_handle(rpc);
else if (!strcmp(method, "rtty"))

View File

@@ -30,6 +30,8 @@
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include "est-client.h"
struct per_vhost_data__minimal {
struct lws_context *context;
struct lws_vhost *vhost;
@@ -67,6 +69,8 @@ lws_protocols protocols[] = {
struct client_config client = {
.redirector_file = "/tmp/ucentral-redirector.json",
.redirector_file_dbg = "/tmp/firstcontact.hdr",
.ols_schema_version_file = "/etc/schema.json",
.ols_client_version_file = "/etc/version.json",
.server = NULL,
.port = 15002,
.path = "/",
@@ -74,6 +78,10 @@ struct client_config client = {
.CN = {0},
.firmware = {0},
.devid = {0},
/* PKI 2.0 defaults */
.ca = NULL, /* Will be set to operational.ca or cas.pem */
.cert = NULL, /* Will be set to operational.pem or cert.pem */
.hostname_validate = 0,
};
static const char file_cert[] = UCENTRAL_CONFIG "cert.pem";
@@ -195,7 +203,11 @@ int ssl_cert_get_common_name(char *cn, size_t size, const char *cert_path)
return 0;
}
static int
/*
* LEGACY: DigiCert redirector parsing - deprecated with PKI 2.0
* Kept for reference during transition period. To be removed in future release.
*/
static int __attribute__((unused))
ucentral_redirector_parse(char **gw_host)
{
size_t json_data_size = 0;
@@ -506,8 +518,10 @@ static int client_config_read(void)
const char *file_devid = UCENTRAL_CONFIG "dev-id";
/* UGLY W/A for now: get MAC from cert's CN */
if (ssl_cert_get_common_name(client.CN, 63, file_cert)) {
UC_LOG_ERR("CN read from cert failed");
/* PKI 2.0: Extract CN from operational or birth certificate */
const char *cert_for_cn = client.cert ? client.cert : file_cert;
if (ssl_cert_get_common_name(client.CN, 63, cert_for_cn)) {
UC_LOG_ERR("CN read from cert failed (%s)\n", cert_for_cn);
return -1;
}
client.serial = &client.CN[10];
@@ -546,7 +560,12 @@ static int client_config_read(void)
return 0;
}
static int firstcontact(void)
/*
* LEGACY: DigiCert first contact flow - deprecated with PKI 2.0
* Kept for reference during transition period. To be removed in future release.
*/
static int __attribute__((unused))
firstcontact(void)
{
const char *redirector_host = redirector_host_get();
FILE *fp_json;
@@ -740,6 +759,92 @@ out:
return -1;
}
/**
* PKI 2.0: Check for operational certificate and enroll if needed
* Follows TIP/OpenWifi proven pattern from wlan-ap
*
* Returns: 0 on success (operational cert available), -1 on failure
*/
static int pki2_check_and_enroll(void)
{
const char *operational_cert = UCENTRAL_CONFIG "operational.pem";
const char *operational_ca = UCENTRAL_CONFIG "operational.ca";
const char *birth_cert = UCENTRAL_CONFIG "cert.pem";
const char *birth_key = UCENTRAL_CONFIG "key.pem";
const char *birth_ca = UCENTRAL_CONFIG "cas.pem"; /* or insta.pem */
struct stat st;
int ret;
/* Check if operational certificate already exists */
if (stat(operational_cert, &st) == 0) {
UC_LOG_INFO("PKI 2.0: Operational certificate found, using it\n");
client.cert = operational_cert;
client.ca = operational_ca;
return 0;
}
/* No operational cert - check if we have birth certificate */
if (stat(birth_cert, &st) != 0) {
UC_LOG_ERR("PKI 2.0: No birth certificate found at %s\n", birth_cert);
return -1;
}
UC_LOG_INFO("PKI 2.0: No operational certificate, attempting EST enrollment\n");
/* Get EST server URL (auto-detects from certificate issuer) */
const char *est_server = est_get_server_url(birth_cert);
if (!est_server) {
UC_LOG_ERR("PKI 2.0: Failed to determine EST server\n");
/* Fall back to using birth certificate */
client.cert = birth_cert;
client.ca = birth_ca;
return -1;
}
UC_LOG_INFO("PKI 2.0: EST server: %s\n", est_server);
/* Perform EST simple enrollment */
char *enrolled_cert = NULL;
ret = est_simple_enroll(est_server, birth_cert, birth_key, birth_ca, &enrolled_cert);
if (ret != EST_SUCCESS) {
UC_LOG_ERR("PKI 2.0: EST enrollment failed: %s\n", est_get_error());
/* Fall back to using birth certificate */
client.cert = birth_cert;
client.ca = birth_ca;
return -1;
}
UC_LOG_INFO("PKI 2.0: EST enrollment successful\n");
/* Save operational certificate */
ret = est_save_cert(enrolled_cert, strlen(enrolled_cert), operational_cert);
free(enrolled_cert);
if (ret != EST_SUCCESS) {
UC_LOG_ERR("PKI 2.0: Failed to save operational certificate: %s\n", est_get_error());
client.cert = birth_cert;
client.ca = birth_ca;
return -1;
}
/* Get operational CA certificates */
char *ca_certs = NULL;
ret = est_get_cacerts(est_server, operational_cert, birth_key, birth_ca, &ca_certs);
if (ret == EST_SUCCESS) {
est_save_cert(ca_certs, strlen(ca_certs), operational_ca);
free(ca_certs);
UC_LOG_INFO("PKI 2.0: Operational CA certificates saved\n");
} else {
UC_LOG_INFO("PKI 2.0: Failed to get CA certs, using birth CA\n");
}
/* Use newly enrolled operational certificate */
client.cert = operational_cert;
client.ca = operational_ca;
UC_LOG_INFO("PKI 2.0: Successfully enrolled and saved operational certificate\n");
return 0;
}
int main(void)
{
int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_CLIENT;
@@ -753,7 +858,6 @@ int main(void)
size_t password_len;
char password[64];
struct stat st;
int ret;
sigthread_create(); /* move signal handling to a dedicated thread */
@@ -783,40 +887,58 @@ int main(void)
plat_revision_get(client.firmware, sizeof(client.firmware));
/* PKI 2.0: Check for operational certificate and enroll if needed */
if (pki2_check_and_enroll() != 0) {
UC_LOG_INFO("PKI 2.0: Enrollment failed, using birth certificate as fallback\n");
}
/* Get gateway address from environment or use default */
if ((gw_host = getenv("UC_GATEWAY_ADDRESS"))) {
client.server = strdup(gw_host);
} else {
while (1) {
if (uc_loop_interrupted_get())
char *colon_pos;
/* Parse host:port format */
colon_pos = strrchr(gw_host, ':');
if (colon_pos && colon_pos != gw_host) {
/* Found colon - split into host and port */
size_t host_len = colon_pos - gw_host;
int env_port;
client.server = strndup(gw_host, host_len);
env_port = atoi(colon_pos + 1);
if (env_port == 0) {
UC_LOG_ERR("Invalid port in UC_GATEWAY_ADDRESS: %s\n", gw_host);
goto exit;
if (firstcontact()) {
UC_LOG_INFO(
"Firstcontact failed; trying again in 1 second...\n");
sleep(1);
continue;
}
break;
}
/* Workaround for now: if parse failed, use default one */
ret = ucentral_redirector_parse(&gw_host);
if (ret) {
UC_LOG_ERR("Firstcontact json data parse failed: %d\n",
ret);
/* Only use port from environment if not already set via command line */
if (client.port == 0 || client.port == 15002) { /* 15002 is the default */
client.port = env_port;
}
UC_LOG_INFO("Using gateway from environment: %s:%u\n", client.server, client.port);
} else {
client.server = gw_host;
/* No colon found - assume just hostname */
client.server = strdup(gw_host);
UC_LOG_INFO("Using gateway from environment: %s (using port %u)\n",
client.server, client.port);
}
} else {
UC_LOG_ERR("No gateway address configured. Set UC_GATEWAY_ADDRESS environment variable.\n");
/* TODO: Could add discovery service support here if needed */
goto exit;
}
memset(&info, 0, sizeof info);
info.port = CONTEXT_PORT_NO_LISTEN;
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.client_ssl_cert_filepath = UCENTRAL_CONFIG"cert.pem";
/* Use PKI 2.0 certificates (operational or birth fallback) */
info.client_ssl_cert_filepath = client.cert ? client.cert : UCENTRAL_CONFIG"cert.pem";
if (!stat(UCENTRAL_CONFIG"key.pem", &st))
info.client_ssl_private_key_filepath = UCENTRAL_CONFIG"key.pem";
info.ssl_ca_filepath = UCENTRAL_CONFIG"cas.pem";
info.ssl_ca_filepath = client.ca ? client.ca : UCENTRAL_CONFIG"cas.pem";
UC_LOG_INFO("PKI 2.0: Using certificate: %s\n", info.client_ssl_cert_filepath);
UC_LOG_INFO("PKI 2.0: Using CA: %s\n", info.ssl_ca_filepath);
info.protocols = protocols;
info.fd_limit_per_thread = 1 + 1 + 1;
info.connect_timeout_secs = 30;

View File

@@ -41,6 +41,8 @@ extern "C" {
struct client_config {
const char *redirector_file;
const char *redirector_file_dbg;
const char *ols_client_version_file;
const char *ols_schema_version_file;
const char *server;
int16_t port;
const char *path;
@@ -50,6 +52,10 @@ struct client_config {
char devid[UCENTRAL_DEVID_F_MAX_LEN + 1];
int selfsigned;
int debug;
/* PKI 2.0 certificate configuration */
const char *ca; /* CA certificate path (default: operational.ca) */
const char *cert; /* Client certificate path (default: operational.pem) */
int hostname_validate; /* Enable hostname validation */
};
typedef void (*uc_send_msg_cb)(const char *msg, size_t len);

View File

@@ -0,0 +1,580 @@
# Adding New Platform to Test Framework
This document explains how to add support for a new platform to the configuration testing framework.
## Overview
The test framework supports two modes:
- **Stub mode**: Fast testing with simple stubs (no platform code)
- **Platform mode**: Integration testing with real platform implementation
To add a new platform, you need to:
1. Implement the platform in `src/ucentral-client/platform/your-platform/`
2. Create platform mocks in `tests/config-parser/platform-mocks/your-platform.c`
3. Test the integration
## What Gets Tested vs Mocked
### ✅ Your Platform Code is TESTED
When you add platform support, these parts of your code are **fully tested**:
- `plat_config_apply()` - Main configuration application function
- All `config_*_apply()` functions - VLAN, port, STP, etc.
- Configuration parsing and validation logic
- Business rules and error handling
- All the code in `platform/your-platform/*.c`
### ❌ Only Hardware Interface is MOCKED
Only the lowest-level hardware abstraction layer is mocked:
- gNMI/gNOI calls (for SONiC-based platforms)
- REST API calls (if your platform uses REST)
- System calls (ioctl, sysfs, etc.)
- Hardware driver calls
**Important**: You're testing real platform code, just not sending commands to actual hardware.
## Prerequisites
Your platform must:
- Implement `plat_config_apply()` in `platform/your-platform/`
- Build a `plat.a` static library
- Follow the platform interface defined in `include/ucentral-platform.h`
## Step-by-Step Guide
### Step 1: Verify Platform Implementation
Ensure your platform builds successfully:
```bash
cd src/ucentral-client/platform/your-platform
make clean
make
ls -la plat.a # Should exist
```
### Step 2: Create Platform Mock File
Create a mock file for your platform's hardware abstraction layer:
```bash
cd tests/config-parser/platform-mocks
cp example-platform.c your-platform.c
```
Edit `your-platform.c` and add mock implementations for your platform's HAL functions.
**Strategy:**
1. Start with empty file (just includes)
2. Try to build: `make clean && make test-config-parser USE_PLATFORM=your-platform`
3. For each "undefined reference" error, add a mock function
4. Repeat until it links successfully
**Example mock function:**
```c
/* Mock your platform's port configuration function */
int your_platform_port_set(int port_id, int speed, int duplex)
{
fprintf(stderr, "[MOCK:your-platform] port_set(port=%d, speed=%d, duplex=%d)\n",
port_id, speed, duplex);
return 0; /* Success */
}
```
### Step 3: Test Your Platform Integration
```bash
cd tests/config-parser
# Try to build
make clean
make test-config-parser USE_PLATFORM=your-platform
# If build fails, check for:
# - Missing mock functions (add to your-platform.c)
# - Missing includes (add to Makefile PLAT_INCLUDES)
# - Missing libraries (add to Makefile PLAT_LDFLAGS)
# When build succeeds, run tests
make test-config-full USE_PLATFORM=your-platform
```
### Step 4: Verify Test Results
Your tests should:
- ✓ Parse configurations successfully
- ✓ Call `plat_config_apply()` from your platform
- ✓ Exercise your platform's configuration logic
- ✓ Report apply success/failure correctly
Check test output for:
```
[TEST] cfg0.json
✓ SCHEMA: Valid
✓ PARSER: Success
✓ APPLY: Success
```
### Step 5: Add Platform-Specific Test Configurations
Create test configurations that exercise your platform's specific features:
```bash
cd config-samples
vim cfg_your_platform_feature.json
```
Run tests to verify:
```bash
cd tests/config-parser
make test-config-full USE_PLATFORM=your-platform
```
## Platform Mock Guidelines
### What to Mock
Mock your platform's **hardware abstraction layer** (HAL) functions - the lowest-level functions that would normally talk to hardware or external services.
**Examples:**
- gNMI/gNOI calls (brcm-sonic platform)
- REST API calls (if your platform uses REST)
- System calls (ioctl, sysfs access, etc.)
- Hardware driver calls
### What NOT to Mock
Don't mock your platform's **configuration logic** - that's what we're testing!
**Don't mock:**
- `plat_config_apply()` - This is the function under test
- `config_vlan_apply()`, `config_port_apply()`, etc. - Platform logic
- Validation functions - These should run normally
### Mock Strategies
#### Strategy 1: Success Stubs (Simple)
Return success for all operations, no state tracking.
```c
int platform_hal_function(...)
{
fprintf(stderr, "[MOCK] platform_hal_function(...)\n");
return 0; /* Success */
}
```
**Pros:** Simple, fast to implement
**Cons:** Doesn't catch validation errors in platform code
**Use when:** Getting started, CI/CD
#### Strategy 2: Validation Mocks (Moderate)
Check parameters, return errors for invalid inputs.
```c
int platform_hal_port_set(int port_id, int speed)
{
fprintf(stderr, "[MOCK] platform_hal_port_set(port=%d, speed=%d)\n",
port_id, speed);
/* Validate parameters */
if (port_id < 0 || port_id >= MAX_PORTS) {
fprintf(stderr, "[MOCK] ERROR: Invalid port ID\n");
return -1; /* Error */
}
if (speed != 100 && speed != 1000 && speed != 10000) {
fprintf(stderr, "[MOCK] ERROR: Invalid speed\n");
return -1; /* Error */
}
return 0; /* Success */
}
```
**Pros:** Catches some validation bugs
**Cons:** More code to maintain
**Use when:** Pre-release testing, debugging
#### Strategy 3: Stateful Mocks (Complex)
Track configuration state, simulate hardware behavior.
```c
/* Mock hardware state */
static struct {
int port_speed[MAX_PORTS];
bool port_enabled[MAX_PORTS];
} mock_hw_state = {0};
int platform_hal_port_set(int port_id, int speed)
{
fprintf(stderr, "[MOCK] platform_hal_port_set(port=%d, speed=%d)\n",
port_id, speed);
/* Validate */
if (port_id < 0 || port_id >= MAX_PORTS)
return -1;
/* Update mock state */
mock_hw_state.port_speed[port_id] = speed;
return 0;
}
int platform_hal_port_get(int port_id, int *speed)
{
if (port_id < 0 || port_id >= MAX_PORTS)
return -1;
/* Return mock state */
*speed = mock_hw_state.port_speed[port_id];
return 0;
}
```
**Pros:** Full platform behavior simulation
**Cons:** Significant effort, complex maintenance
**Use when:** Comprehensive integration testing
## Troubleshooting
### Build Errors
**Problem:** `undefined reference to 'some_function'`
**Solution:** Add mock implementation to `platform-mocks/your-platform.c`
**Problem:** `fatal error: your_platform.h: No such file or directory`
**Solution:** Add include path to Makefile `PLAT_INCLUDES`
If your platform has additional include directories, update the Makefile:
```makefile
# In tests/config-parser/Makefile, update the platform mode section:
PLAT_INCLUDES = -I $(PLAT_DIR) -I $(PLAT_DIR)/your_extra_dir
```
**Problem:** `undefined reference to 'pthread_create'` (or other library)
**Solution:** Add library to Makefile `PLAT_LDFLAGS`
If your platform needs additional libraries, update the Makefile:
```makefile
# In tests/config-parser/Makefile, update the platform mode section:
PLAT_LDFLAGS = -lgrpc++ -lprotobuf -lyour_library
```
### Runtime Errors
**Problem:** Segmentation fault in platform code
**Solution:** Check mock functions return valid pointers, not NULL
**Problem:** Tests always pass even with bad configurations
**Solution:** Your mocks might be too simple - add validation logic
**Problem:** Tests fail but should pass
**Solution:** Check if mock functions are returning correct values
## Example: Adding Your Platform
Let's walk through adding support for your platform (we'll use "myvendor" as an example):
### 1. Check platform exists
```bash
$ ls src/ucentral-client/platform/
brcm-sonic/ example-platform/ myvendor/
```
Platform exists. Good!
### 2. Create mock file
```bash
$ cd tests/config-parser/platform-mocks
$ touch myvendor.c
```
### 3. Try building (will fail with undefined references)
```bash
$ cd ..
$ make clean
$ make test-config-parser USE_PLATFORM=myvendor
...
undefined reference to `myvendor_port_config_set'
undefined reference to `myvendor_vlan_create'
...
```
### 4. Add mocks iteratively
Edit `platform-mocks/myvendor.c`:
```c
/* platform-mocks/myvendor.c */
#include <stdio.h>
int myvendor_port_config_set(int port, int speed, int duplex)
{
fprintf(stderr, "[MOCK:myvendor] port_config_set(%d, %d, %d)\n",
port, speed, duplex);
return 0;
}
int myvendor_vlan_create(int vlan_id)
{
fprintf(stderr, "[MOCK:myvendor] vlan_create(%d)\n", vlan_id);
return 0;
}
/* Add more as linker reports undefined references */
```
### 5. Build until successful
```bash
$ make clean
$ make test-config-parser USE_PLATFORM=myvendor
# Add more mocks for any remaining undefined references
# Repeat until build succeeds
```
### 6. Run tests
```bash
$ make test-config-full USE_PLATFORM=myvendor
========= running schema validation =========
[SCHEMA] cfg0.json: VALID
...
========= running config parser tests =========
Mode: platform
Platform: myvendor
================================================
[TEST] cfg0.json
[MOCK:myvendor] port_config_set(1, 1000, 1)
[MOCK:myvendor] vlan_create(100)
✓ SCHEMA: Valid
✓ PARSER: Success
✓ APPLY: Success
...
Total tests: 25
Passed: 25
Failed: 0
```
Success! Your platform is now integrated into the test framework.
## Property Database
### Overview
The testing framework uses property databases to track where configuration properties are parsed in the codebase. This enables detailed test reports showing the exact source location (file, function, line number) for each property.
Property tracking uses **separate databases**:
- **Base database** (`property-database-base.c`): Tracks properties parsed in proto.c
- **Platform database** (`property-database-platform-PLATFORM.c`): Tracks platform-specific properties
### Database Architecture
**Stub mode (no platform):**
- Only uses `base_property_database` from property-database-base.c
- Shows proto.c source locations in test reports
**Platform mode (with USE_PLATFORM):**
- Uses both `base_property_database` AND `platform_property_database_PLATFORM`
- Shows both proto.c and platform source locations in test reports
### When to Regenerate Property Databases
**Regenerate base database if you:**
- Modified proto.c parsing code
- Added vendor-specific #ifdef code to proto.c
- Line numbers changed significantly
- Want updated line numbers in test reports
**Regenerate platform database when:**
- Modified platform parsing code (plat-*.c)
- Added new configuration features to platform
- Want updated line numbers in test reports
### How to Regenerate Databases
```bash
# Regenerate base database (if you modified proto.c)
cd tests/config-parser
make regenerate-property-db
# Regenerate platform database (requires USE_PLATFORM)
make regenerate-platform-property-db USE_PLATFORM=myvendor
```
**What happens:**
- Base: Extracts properties from proto.c and finds line numbers
- Platform: Extracts properties from platform/myvendor/plat-*.c and finds line numbers
- Generated database files are C arrays included by test-config-parser.c
### Test Reports with Property Tracking
**Stub mode shows only proto.c properties:**
```
✓ Successfully Configured: 3 properties
- ethernet[0].speed = 1000 [proto.c:cfg_ethernet_parse():line 1119]
- ethernet[0].duplex = "full" [proto.c:cfg_ethernet_parse():line 1120]
- ethernet[0].enabled = false [proto.c:cfg_ethernet_parse():line 1119]
```
**Platform mode shows both proto.c AND platform properties:**
```
✓ Successfully Configured: 3 properties (proto.c)
- ethernet[0].speed = 1000 [proto.c:cfg_ethernet_parse():line 1119]
- ethernet[0].duplex = "full" [proto.c:cfg_ethernet_parse():line 1120]
✓ Platform Applied: 2 properties (platform/myvendor)
- ethernet[0].lldp.enabled [platform/myvendor/plat-config.c:config_lldp_apply():line 1234]
- ethernet[0].lacp.mode [platform/myvendor/plat-config.c:config_lacp_apply():line 567]
```
### Property Database Template Structure
Property databases are C arrays with this structure:
```c
static const struct property_metadata base_property_database[] = {
{"ethernet[].speed", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1119, ""},
{"ethernet[].duplex", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1120, ""},
// ... more entries ...
{NULL, 0, NULL, NULL, 0, NULL} /* Terminator */
};
```
Each entry maps:
- JSON property path → Source file → Parser function → Line number
### Vendor Modifications to proto.c
**Important for vendors:** If your platform adds #ifdef code to proto.c:
```c
/* In proto.c - vendor-specific code */
#ifdef VENDOR_MYVENDOR
/* Parse vendor-specific property */
if (cJSON_HasObjectItem(obj, "my-vendor-feature")) {
cfg->vendor_feature = cJSON_GetNumber...
}
#endif
```
You should regenerate the base property database in your repository:
```bash
cd tests/config-parser
make regenerate-property-db
```
This captures your proto.c modifications in the base database, ensuring accurate test reports showing your vendor-specific parsing code.
## Testing Workflow
### Development Workflow (Fast Iteration)
```bash
# Use stub mode for quick testing during development
make test-config-full
# Fast, no platform dependencies
# Edit code → test → repeat
```
### Pre-Release Workflow (Comprehensive Testing)
```bash
# Use platform mode for integration testing before release
make test-config-full USE_PLATFORM=your-platform
# Slower, but exercises real platform code
# Catches platform-specific bugs
```
### CI/CD Workflow (Automated)
```yaml
# .gitlab-ci.yml or similar
test-parser:
script:
- cd tests/config-parser
- make test-config-full # Stub mode for speed
test-platform:
script:
- cd tests/config-parser
- make test-config-full USE_PLATFORM=brcm-sonic
allow_failure: true # Platform mode may need special environment
```
## Advanced: Platform-Specific Makefile Configuration
If your platform needs special build configuration, you can add conditional logic to the Makefile:
```makefile
# In tests/config-parser/Makefile, in the platform mode section:
ifeq ($(PLATFORM_NAME),your-platform)
# Add your-platform specific includes
PLAT_INCLUDES += -I $(PLAT_DIR)/special_dir
# Add your-platform specific libraries
PLAT_LDFLAGS += -lyour_special_lib
endif
```
## Summary Checklist
Before considering your platform integration complete:
- [ ] Platform implementation exists in `platform/your-platform/`
- [ ] Platform builds successfully and produces `plat.a`
- [ ] Created `platform-mocks/your-platform.c` with mock HAL functions
- [ ] Build succeeds: `make test-config-parser USE_PLATFORM=your-platform`
- [ ] Tests run: `make test-config-full USE_PLATFORM=your-platform`
- [ ] All existing test configs pass (or expected failures documented)
- [ ] Generated platform property database: `make regenerate-platform-property-db USE_PLATFORM=your-platform`
- [ ] Test reports show platform source locations (file, function, line number)
- [ ] If modified proto.c with #ifdef, regenerated base database: `make regenerate-property-db`
- [ ] Added platform-specific test configurations (optional)
- [ ] Documented platform-specific requirements (optional)
- [ ] Tested in Docker environment (if applicable)
## Getting Help
- **Build issues**: Check Makefile configuration and include paths
- **Link issues**: Add missing mock functions to platform mock file
- **Runtime issues**: Check mock function return values and pointers
- **Test failures**: Verify platform code validation logic
For more help, see:
- `tests/config-parser/TEST_CONFIG_README.md` - Test framework documentation
- `TESTING_FRAMEWORK.md` - Overall testing architecture
- `platform/example-platform/` - Platform implementation template
- `tests/config-parser/platform-mocks/README.md` - Mock implementation guide
## FAQ
**Q: Why do I need to create mocks? Can't the test just call the platform code directly?**
A: The platform code calls hardware abstraction functions (like gNMI APIs). Those functions need hardware or external services. Mocks let us test the platform logic without requiring actual hardware.
**Q: How do I know which functions to mock?**
A: Try building - the linker will tell you with "undefined reference" errors. Mock those functions.
**Q: Can I reuse mocks from another platform?**
A: Not usually - each platform has its own HAL. But you can use another platform's mock file as a reference for structure.
**Q: Do I need to mock every function perfectly?**
A: No! Start with simple success stubs (return 0). Add validation later if needed.
**Q: My platform doesn't use gNMI. Can I still add it?**
A: Yes! Mock whatever your platform uses (REST API, system calls, etc.). The same principles apply.
**Q: The tests pass but I want more validation. What should I do?**
A: Add validation logic to your mock functions (Strategy 2 or 3 above). Check parameters and return errors for invalid inputs.
**Q: Can I test multiple platforms in the same test run?**
A: Not currently. Run separate test commands for each platform: `make test-config-full USE_PLATFORM=platform1` then `make test-config-full USE_PLATFORM=platform2`

1188
tests/MAINTENANCE.md Normal file

File diff suppressed because it is too large Load Diff

485
tests/README.md Normal file
View File

@@ -0,0 +1,485 @@
# Configuration Testing Framework
## Overview
The OLS uCentral Client includes a comprehensive configuration testing framework that provides two-layer validation of JSON configurations:
1. **Schema Validation** - Structural validation against the uCentral JSON schema
2. **Parser Testing** - Implementation validation of the C parser with property tracking
This framework enables automated testing, continuous integration, and tracking of configuration feature implementation status.
## Documentation Index
This testing framework includes multiple documentation files, each serving a specific purpose:
### Primary Documentation
1. **[TEST_CONFIG_README.md](config-parser/TEST_CONFIG_README.md)** - Complete testing framework guide
- Overview of two-layer validation approach
- Quick start and running tests
- Property tracking system
- Configuration-specific validators
- Test output interpretation
- CI/CD integration
- **Start here** for understanding the testing framework
2. **[SCHEMA_VALIDATOR_README.md](schema/SCHEMA_VALIDATOR_README.md)** - Schema validator detailed documentation
- Standalone validator usage
- Command-line interface
- Programmatic API
- Porting guide for other repositories
- Common validation errors
- **Start here** for schema validation specifics
3. **[MAINTENANCE.md](MAINTENANCE.md)** - Maintenance procedures guide
- Schema update procedures
- Property database update procedures
- Version synchronization
- Testing after updates
- Troubleshooting common issues
- **Start here** when updating schema or property database
4. **[TEST_CONFIG_PARSER_DESIGN.md](../TEST_CONFIG_PARSER_DESIGN.md)** - Test framework architecture
- Multi-layer validation design
- Property metadata system (560+ entries)
- Property inspection engine
- Test execution flow diagrams
- Data structures and algorithms
- Output format implementations
- **Start here** for understanding the test framework internals
### Supporting Documentation
## Quick Reference
### Test Modes
The testing framework supports two modes:
**Stub Mode (Default - Fast)**
- Tests proto.c parsing only
- Uses simple platform stubs (test-stubs.c)
- Shows base properties only (proto.c)
- Fast execution (~30 seconds)
- Use for: Quick validation, CI/CD pipelines
**Platform Mode (Integration)**
- Tests proto.c + platform implementation (plat-gnma.c)
- Uses platform code with hardware mocks
- Shows base AND platform properties (proto.c → plat-gnma.c)
- Tracks hardware application functions called
- Slower execution (~45 seconds)
- Use for: Platform-specific validation, integration testing
### Running Tests
**RECOMMENDED: Run tests inside Docker build environment** to eliminate OS-specific issues (works on macOS, Linux, Windows):
```bash
# Build the Docker environment first (if not already built)
make build-host-env
# Run all tests in STUB mode (default - fast)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-full"
# Run all tests in PLATFORM mode (integration)
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-full USE_PLATFORM=brcm-sonic"
# Run individual test suites
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make validate-schema"
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config"
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test"
# Generate test reports
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-html"
docker exec ucentral_client_build_env bash -c \
"cd /root/ols-nos/tests/config-parser && make test-config-json"
# Copy report files out of container to view
docker cp ucentral_client_build_env:/root/ols-nos/tests/config-parser/test-report.html ./
docker cp ucentral_client_build_env:/root/ols-nos/tests/config-parser/test-results.json ./
```
**Alternative: Run tests locally** (may have OS-specific dependencies):
```bash
# Navigate to test directory
cd tests/config-parser
# Run all tests in STUB mode (default)
make test-config-full
# Run all tests in PLATFORM mode
make test-config-full USE_PLATFORM=brcm-sonic
# Run individual test suites
make validate-schema # Schema validation only
make test-config # Parser tests only
make test # Unit tests
# Generate test reports (stub mode)
make test-config-html # HTML report (browser-viewable)
make test-config-json # JSON report (machine-readable)
make test-config-junit # JUnit XML (CI/CD integration)
# Generate test reports (platform mode)
make test-config-html USE_PLATFORM=brcm-sonic
make test-config-json USE_PLATFORM=brcm-sonic
```
**Note:** Running tests in Docker is the preferred method as it provides a consistent, reproducible environment regardless of your host OS (macOS, Linux, Windows).
### Key Files
**Test Implementation:**
- `tests/config-parser/test-config-parser.c` (3304 lines) - Parser test framework with property tracking
- `tests/config-parser/test-stubs.c` (219 lines) - Platform function stubs for stub mode testing
- `tests/schema/validate-schema.py` (649 lines) - Standalone schema validator with undefined property detection
- `tests/config-parser/config-parser.h` - Test header exposing cfg_parse()
**Configuration Files:**
- `config-samples/ucentral.schema.pretty.json` - uCentral JSON schema (human-readable, single schema file)
- `config-samples/*.json` - Test configuration files (25 configs total)
- `config-samples/*invalid*.json` - Negative test cases
**Build System:**
- `tests/config-parser/Makefile` - Test targets and build rules
**Production Code (Minimal Changes):**
- `src/ucentral-client/proto.c` - Added TEST_STATIC macro (2 lines changed)
- `src/ucentral-client/include/router-utils.h` - Added extern declarations (minor change)
## Features
### Schema Validation
- Validates JSON structure against official uCentral schema
- Checks property types, required fields, constraints
- Standalone tool, no dependencies on C code
- Exit codes for CI/CD integration
### Parser Testing
- Tests actual C parser implementation
- Multiple output formats (human-readable, HTML, JSON, JUnit XML)
- Interactive HTML reports with detailed analysis
- Machine-readable JSON for automation
- JUnit XML for CI/CD integration
- Validates configuration processing and struct population
- Configuration-specific validators for business logic
- Memory leak detection
- Hardware constraint validation
### Property Tracking System
- Database of all schema properties and their implementation status (398 canonical properties)
- Tracks which properties are parsed by which functions
- Identifies unimplemented features
- Status classification: CONFIGURED, IGNORED, SYSTEM, INVALID, Unknown
- Property usage reports across all test configurations
### Two-Layer Validation Strategy
**Why Both Layers?**
Each layer catches different types of errors:
- **Schema catches**: Type mismatches, missing required fields, constraint violations
- **Parser catches**: Implementation bugs, hardware limits, cross-field dependencies
- **Property tracking catches**: Missing implementations, platform-specific features
See TEST_CONFIG_README.md section "Two-Layer Validation Strategy" for detailed explanation.
## Test Coverage
Current test suite includes:
- 25 configuration files covering various features
- Positive tests (configs that should parse successfully)
- Negative tests (configs that should fail)
- Feature-specific validators for critical configurations
- Two testing modes: stub mode (fast, proto.c only) and platform mode (integration, proto.c + platform code)
- Platform stub with 54-port simulation (matches ECS4150 hardware)
### Tested Features
- Port configuration (enable/disable, speed, duplex)
- VLAN configuration and membership
- Spanning Tree Protocol (STP, RSTP, PVST, RPVST)
- IGMP Snooping
- Power over Ethernet (PoE)
- IEEE 802.1X Authentication
- DHCP Relay
- Static routing
- System configuration (timezone, hostname, etc.)
### Platform-Specific Features (Schema-Valid, Platform Implementation Required)
- LLDP (Link Layer Discovery Protocol)
- LACP (Link Aggregation Control Protocol)
- ACLs (Access Control Lists)
- DHCP Snooping
- Loop Detection
- Port Mirroring
- Voice VLAN
These features pass schema validation but show as "Unknown" in property reports, indicating they require platform-specific implementation.
## Changes from Base Repository
The testing framework was added with minimal impact to production code:
### New Files Added
1. `tests/config-parser/test-config-parser.c` (3304 lines) - Complete test framework
2. `tests/config-parser/test-stubs.c` (219 lines) - Platform stubs for stub mode
3. `tests/config-parser/platform-mocks/brcm-sonic.c` - Platform mocks for brcm-sonic
4. `tests/config-parser/platform-mocks/example-platform.c` - Platform mocks for example platform
5. `tests/config-parser/property-database-base.c` (416 lines) - Base property database (398 properties: 102 implemented, 296 not yet)
6. `tests/config-parser/property-database-platform-brcm-sonic.c` (419 lines) - Platform property database
7. `tests/config-parser/property-database-platform-example.c` - Example platform property database
8. `tests/schema/validate-schema.py` (649 lines) - Schema validator
9. `tests/config-parser/config-parser.h` - Test header
10. `tests/config-parser/TEST_CONFIG_README.md` - Framework documentation
11. `tests/config-parser/platform-mocks/README.md` - Platform mocks documentation
12. `tests/schema/SCHEMA_VALIDATOR_README.md` - Validator documentation
13. `tests/MAINTENANCE.md` - Maintenance procedures
14. `tests/ADDING_NEW_PLATFORM.md` - Platform addition guide
15. `tests/config-parser/Makefile` - Test build system with stub and platform modes
16. `tests/tools/extract-schema-properties.py` - Extract properties from schema
17. `tests/tools/generate-database-from-schema.py` - Generate base property database
18. `tests/tools/generate-platform-database-from-schema.py` - Generate platform property database
19. `tests/README.md` - This file (testing framework overview)
20. `TESTING_FRAMEWORK.md` - Documentation index (in repository root)
21. `TEST_CONFIG_PARSER_DESIGN.md` - Test framework architecture and design (in repository root)
22. `QUICK_START_TESTING.md` - Quick start guide (in repository root)
23. `TEST_RUNNER_README.md` - Test runner script documentation (in repository root)
24. `run-config-tests.sh` - Test runner script (in repository root)
### Modified Files
1. `src/ucentral-client/proto.c` - Added TEST_STATIC macro pattern (2 lines)
```c
// Changed from:
static struct plat_cfg *cfg_parse(...)
// Changed to:
#ifdef UCENTRAL_TESTING
#define TEST_STATIC
#else
#define TEST_STATIC static
#endif
TEST_STATIC struct plat_cfg *cfg_parse(...)
```
This allows test code to call cfg_parse() while keeping it static in production builds.
2. `src/ucentral-client/include/router-utils.h` - Added extern declarations
- Exposed necessary functions for test stubs
3. `tests/config-parser/Makefile` - Test build system
```makefile
test-config-parser: # Build parser test tool
test-config: # Run parser tests
validate-schema: # Run schema validation
test-config-full: # Run both schema + parser tests
```
### Configuration Files
- Added `config-samples/cfg_invalid_*.json` - Negative test cases
- Added `config-samples/ECS4150_*.json` - Feature-specific test configs
- No changes to existing valid configurations
### Zero Impact on Production
- Production builds: No functional changes, cfg_parse() remains static
- Test builds: cfg_parse() becomes visible with -DUCENTRAL_TESTING flag
- No ABI changes, no performance impact
- No runtime dependencies added
## Integration with Development Workflow
### During Development
```bash
# 1. Make code changes to proto.c
vi src/ucentral-client/proto.c
# 2. Run tests
cd tests/config-parser
make test-config-full
# 3. Review property tracking report
# Check for unimplemented features or errors
# 4. If adding new parser function, update property database
vi test-config-parser.c
# Add property entries for new function
# 5. Create test configuration
vi ../../config-samples/test-new-feature.json
# 6. Retest
make test-config-full
```
### Before Committing
```bash
# Ensure all tests pass
cd tests/config-parser
make clean
make test-config-full
# Check for property database accuracy
make test-config | grep -A 50 "PROPERTY USAGE REPORT"
# Look for unexpected "Unknown" properties
```
### In CI/CD Pipeline
```yaml
test-configurations:
stage: test
script:
- make build-host-env
- docker exec ucentral_client_build_env bash -c
"cd /root/ols-nos/tests/config-parser && make test-config-full"
artifacts:
paths:
- tests/config-parser/test-results.txt
```
## Property Database Management
The property database is a critical component tracking which JSON properties are parsed by which functions.
### Database Structure
```c
static struct property_info properties[] = {
{
.path = "interfaces.ethernet.enabled",
.parser_function = "cfg_ethernet_parse()",
.status = PROP_CONFIGURED,
.notes = "Enable/disable ethernet interface"
},
// ... entries for all 398 schema properties ...
};
```
### Key Rules
1. **Only track properties for functions that exist in this repository's proto.c**
2. **Remove entries when parser functions are removed**
3. **Add entries immediately when adding new parser functions**
4. **Use accurate function names** - different platforms may use different names
5. **Properties not in database show as "Unknown"** - this is correct for platform-specific features
See MAINTENANCE.md for complete property database update procedures.
## Schema Management
The schema file defines what configurations are structurally valid.
### Schema Location
- `config-samples/ucentral.schema.pretty.json` - Human-readable version (single schema file in repository)
### Schema Source
Schema is maintained in the external [ols-ucentral-schema](https://github.com/Telecominfraproject/ols-ucentral-schema) repository.
### Schema Updates
When ols-ucentral-schema releases a new version:
1. Copy new schema to config-samples/
2. Run schema validation on all test configs
3. Fix any configs that fail new requirements
4. Document breaking changes
5. Update property database if new properties are implemented
See MAINTENANCE.md section "Schema Update Procedures" for complete process.
## Platform-Specific Repositories
This is the **base repository** providing the core framework. Platform-specific repositories (like Edgecore EC platform) can:
1. **Fork the test framework** - Copy test files to their repository
2. **Extend property database** - Add entries for platform-specific parser functions
3. **Add platform configs** - Create configs testing platform features
4. **Maintain separate tracking** - Properties "Unknown" in base become "CONFIGURED" in platform
### Example: LLDP Property Status
**In base repository (this repo):**
```
Property: interfaces.ethernet.lldp
Status: Unknown (not in property database)
Note: May require platform-specific implementation
```
**In Edgecore EC platform repository:**
```
Property: interfaces.ethernet.lldp
Parser: cfg_ethernet_lldp_parse()
Status: CONFIGURED
Note: Per-interface LLDP transmit/receive configuration
```
Each platform tracks only the properties it actually implements.
## Troubleshooting
### Common Issues
**Tests fail in Docker but pass locally:**
- Check schema file exists in container
- Verify paths are correct in container environment
- Rebuild container: `make build-host-env`
**Property shows as "Unknown" when it should be CONFIGURED:**
- Verify parser function exists: `grep "function_name" proto.c`
- Check property path matches JSON exactly
- Ensure property entry is in properties[] array
**Schema validation fails for valid config:**
- Schema may be outdated - check version
- Config may use vendor extensions not in base schema
- Validate against specific schema: `./validate-schema.py config.json --schema /path/to/schema.json`
See MAINTENANCE.md "Troubleshooting" section for complete troubleshooting guide.
## Documentation Maintenance
When updating the testing framework:
1. **Update relevant documentation:**
- New features → TEST_CONFIG_README.md
- Schema changes → MAINTENANCE.md + SCHEMA_VALIDATOR_README.md
- Property database changes → MAINTENANCE.md + TEST_CONFIG_README.md
2. **Keep version information current:**
- Update compatibility matrices
- Document breaking changes
- Maintain changelogs
3. **Update examples:**
- Refresh command output examples
- Update property counts
- Keep test results current
## Contributing
When contributing to the testing framework:
1. **Maintain property database accuracy** - Update when changing parser functions
2. **Add test configurations** - Create configs demonstrating new features
3. **Update documentation** - Keep docs synchronized with code changes
4. **Follow conventions** - Use established patterns for validators and property entries
5. **Test thoroughly** - Run full test suite before committing
## License
BSD-3-Clause (same as parent project)
## See Also
- **TEST_CONFIG_README.md** - Complete testing framework guide
- **TEST_CONFIG_PARSER_DESIGN.md** - Test framework architecture and design
- **SCHEMA_VALIDATOR_README.md** - Schema validator documentation
- **MAINTENANCE.md** - Update procedures and troubleshooting
- **ols-ucentral-schema repository** - Official schema source

View File

@@ -0,0 +1,279 @@
# Configuration Parser Test Suite Makefile
# Supports two testing modes:
# 1. Stub mode (default) - Fast testing with simple stubs
# 2. Platform mode - Integration testing with real platform code
.PHONY: test test-config validate-schema test-config-full test-config-html test-config-json test-config-junit clean config help
# Compiler flags
export CFLAGS+= -Werror -Wall -Wextra
# ============================================================================
# PLATFORM SELECTION
# ============================================================================
# USE_PLATFORM can be:
# - Not set (default): Use stub mode
# - "brcm-sonic": Use Broadcom SONiC platform
# - "example-platform": Use example platform
# - Custom platform name from platform/ directory
#
# Example usage:
# make test-config-full # Stub mode
# make test-config-full USE_PLATFORM=brcm-sonic # Platform mode
# ============================================================================
ifdef USE_PLATFORM
TEST_MODE = platform
PLATFORM_NAME = $(USE_PLATFORM)
else
TEST_MODE = stub
endif
# Paths relative to tests/config-parser/
SRC_DIR = ../../src/ucentral-client
CONFIG_SAMPLES = ../../config-samples
SCHEMA_VALIDATOR = ../schema/validate-schema.py
# Source files from production code (common to both modes)
SRC_OBJS = $(SRC_DIR)/ucentral-json-parser.o \
$(SRC_DIR)/ucentral-log.o \
$(SRC_DIR)/router-utils.o \
$(SRC_DIR)/base64.o
# ============================================================================
# MODE-SPECIFIC CONFIGURATION
# ============================================================================
ifeq ($(TEST_MODE),stub)
# Stub mode: Use simple test stubs
PLAT_OBJS = test-stubs.o
PLAT_INCLUDES =
PLAT_CFLAGS =
PLAT_LDFLAGS =
MODE_INFO = "STUB MODE - Fast testing with simple stubs"
else
# Platform mode: Use real platform implementation
PLAT_DIR = $(SRC_DIR)/platform/$(PLATFORM_NAME)
# Link only platform logic (plat-gnma.o), not full plat.a with hardware libs
PLAT_LOGIC = $(PLAT_DIR)/plat-gnma.o
PLAT_MOCK = platform-mock-$(PLATFORM_NAME).o
PLAT_OBJS = $(PLAT_MOCK) $(PLAT_LOGIC)
PLAT_INCLUDES = -I $(PLAT_DIR) -I $(PLAT_DIR)/gnma -I $(PLAT_DIR)/netlink
# Define platform macro to enable platform property database lookup
# Convert platform name to uppercase and replace hyphens with underscores
# e.g., brcm-sonic -> USE_PLATFORM_BRCM_SONIC
PLAT_MACRO = $(shell echo "$(PLATFORM_NAME)" | tr '[:lower:]-' '[:upper:]_')
PLAT_CFLAGS = -DUSE_PLATFORM_$(PLAT_MACRO)
PLAT_LDFLAGS =
MODE_INFO = "PLATFORM MODE - Integration testing with $(PLATFORM_NAME)"
endif
# ============================================================================
# BUILD TARGETS
# ============================================================================
# Default target
all: test-config-parser
# Display current configuration
config:
@echo "============================================"
@echo "Test Configuration"
@echo "============================================"
@echo "Test mode: $(TEST_MODE)"
ifdef USE_PLATFORM
@echo "Platform: $(PLATFORM_NAME)"
endif
@echo "Platform objects: $(PLAT_OBJS)"
@echo "Info: $(MODE_INFO)"
@echo "============================================"
# Build test files with TEST_STATIC to expose cfg_parse() for testing
test-config-parser.o: test-config-parser.c
gcc -c -o $@ ${CFLAGS} -DUCENTRAL_TESTING $(PLAT_CFLAGS) $(PLAT_INCLUDES) \
-I $(SRC_DIR) -I $(SRC_DIR)/include -I ./ $<
proto-test.o: $(SRC_DIR)/proto.c
gcc -c -o $@ ${CFLAGS} -DUCENTRAL_TESTING \
-I $(SRC_DIR) -I $(SRC_DIR)/include $<
# Stub mode: build test-stubs.o
test-stubs.o: test-stubs.c
gcc -c -o $@ ${CFLAGS} -DUCENTRAL_TESTING \
-I $(SRC_DIR) -I $(SRC_DIR)/include $<
# Platform mode: build platform-specific mock
platform-mock-%.o: platform-mocks/%.c
@if [ ! -f platform-mocks/$*.c ]; then \
echo "ERROR: Platform mock file platform-mocks/$*.c not found"; \
echo "Please create platform-mocks/$*.c with required mock functions"; \
echo "See tests/ADDING_NEW_PLATFORM.md for guidance"; \
exit 1; \
fi
gcc -c -o $@ ${CFLAGS} $(PLAT_INCLUDES) \
-I $(SRC_DIR) -I $(SRC_DIR)/include $<
# Platform mode: build platform logic object if needed
$(PLAT_LOGIC):
@echo "Building platform logic: $(PLATFORM_NAME)"
@if [ ! -d $(PLAT_DIR) ]; then \
echo "ERROR: Platform directory $(PLAT_DIR) not found"; \
echo "Available platforms:"; \
ls -1 $(SRC_DIR)/platform/; \
exit 1; \
fi
$(MAKE) -C $(PLAT_DIR) plat-gnma.o
# Build production source files (if not already built)
$(SRC_DIR)/%.o: $(SRC_DIR)/%.c
$(MAKE) -C $(SRC_DIR) $*.o
# Link test binary (mode-dependent)
test-config-parser: test-config-parser.o proto-test.o $(SRC_OBJS) $(PLAT_OBJS)
@echo "Linking test binary: $(TEST_MODE) mode"
g++ -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv $(PLAT_LDFLAGS)
# ============================================================================
# TEST TARGETS
# ============================================================================
# Run configuration parser tests
test-config:
@echo "=========================================="
@echo "Configuration Parser Tests"
@echo "=========================================="
@echo "Mode: $(TEST_MODE)"
ifdef USE_PLATFORM
@echo "Platform: $(PLATFORM_NAME)"
endif
@echo "=========================================="
$(MAKE) test-config-parser
LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser $(CONFIG_SAMPLES)
@echo "=========================================="
@echo "Tests completed"
@echo "=========================================="
# Run schema validation
validate-schema:
@echo "========= Schema Validation ========="
@python3 $(SCHEMA_VALIDATOR) $(CONFIG_SAMPLES) --check-undefined || true
@echo "========= Schema validation completed ========"
# Combined validation: schema + parser
test-config-full: validate-schema test-config
@echo "========= All validation completed ========"
# Generate HTML test report
test-config-html:
@echo "========= Generating HTML test report ========="
$(MAKE) test-config-parser
LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --html $(CONFIG_SAMPLES) > test-report.html
@echo "========= HTML report: test-report.html ========"
# Generate JSON test report
test-config-json:
@echo "========= Generating JSON test report ========="
$(MAKE) test-config-parser
LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --json $(CONFIG_SAMPLES) > test-report.json
@echo "========= JSON report: test-report.json ========"
# Generate JUnit XML test report
test-config-junit:
@echo "========= Generating JUnit XML test report ========="
$(MAKE) test-config-parser
LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --junit $(CONFIG_SAMPLES) > test-report.xml
@echo "========= JUnit XML report: test-report.xml ========"
# ============================================================================
# PROPERTY DATABASE REGENERATION
# ============================================================================
# Regenerate base property database from proto.c
.PHONY: regenerate-property-db
regenerate-property-db:
@echo "==========================================="
@echo "Regenerating base property database"
@echo "==========================================="
@echo "Source: ../../src/ucentral-client/proto.c"
@echo "Schema: ../../config-samples/ucentral.schema.pretty.json"
@echo "==========================================="
@cd ../tools && \
python3 extract-schema-properties.py ../../config-samples/ucentral.schema.pretty.json 2>/dev/null > /tmp/schema-props.txt && \
python3 generate-database-from-schema.py \
../../src/ucentral-client/proto.c \
/tmp/schema-props.txt \
/tmp/base-db-new.c && \
cp /tmp/base-db-new.c ../config-parser/property-database-base.c
@echo "Base property database regenerated"
@echo "==========================================="
# Regenerate platform-specific property database
.PHONY: regenerate-platform-property-db
ifdef USE_PLATFORM
regenerate-platform-property-db:
@echo "==========================================="
@echo "Regenerating platform property database"
@echo "==========================================="
@echo "Platform: $(PLATFORM_NAME)"
@echo "Source: ../../src/ucentral-client/platform/$(PLATFORM_NAME)/plat-gnma.c"
@echo "Schema: ../../config-samples/ucentral.schema.pretty.json"
@echo "==========================================="
@cd ../tools && \
python3 extract-schema-properties.py ../../config-samples/ucentral.schema.pretty.json 2>/dev/null > /tmp/schema-props.txt && \
python3 generate-platform-database-from-schema.py \
../../src/ucentral-client/platform/$(PLATFORM_NAME)/plat-gnma.c \
/tmp/schema-props.txt \
/tmp/platform-db-new.c && \
cp /tmp/platform-db-new.c ../config-parser/property-database-platform-$(PLATFORM_NAME).c
@echo "Platform property database regenerated"
@echo "==========================================="
else
regenerate-platform-property-db:
@echo "ERROR: USE_PLATFORM not set"
@echo "Usage: make regenerate-platform-property-db USE_PLATFORM=brcm-sonic"
@exit 1
endif
# ============================================================================
# CLEANUP
# ============================================================================
clean:
rm -f test-config-parser proto-test.o test-config-parser.o test-stubs.o
rm -f platform-mock-*.o
rm -f test-report.html test-report.json test-report.xml test-results.txt
@echo "Test artifacts cleaned"
# ============================================================================
# HELP
# ============================================================================
help:
@echo "Configuration Parser Test Suite"
@echo ""
@echo "Testing Modes:"
@echo " Stub mode (default) - Fast testing with simple stubs"
@echo " Platform mode - Integration testing with real platform"
@echo ""
@echo "Usage:"
@echo " make test-config-full # Stub mode"
@echo " make test-config-full USE_PLATFORM=brcm-sonic # Platform mode"
@echo ""
@echo "Available targets:"
@echo " config - Show current build configuration"
@echo " test-config - Run configuration parser tests"
@echo " validate-schema - Run schema validation only"
@echo " test-config-full - Run both schema validation and parser tests"
@echo " test-config-html - Generate HTML test report"
@echo " test-config-json - Generate JSON test report"
@echo " test-config-junit - Generate JUnit XML test report"
@echo " regenerate-property-db - Regenerate base property database from proto.c"
@echo " regenerate-platform-property-db - Regenerate platform property database (requires USE_PLATFORM)"
@echo " clean - Remove test artifacts"
@echo " help - Show this help message"
@echo ""
@echo "Supported platforms:"
@echo " brcm-sonic - Broadcom SONiC platform (gNMI-based)"
@echo " example-platform - Template for new platforms"
@echo ""
@echo "For more information, see tests/ADDING_NEW_PLATFORM.md"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef CONFIG_PARSER_H
#define CONFIG_PARSER_H
#include <cjson/cJSON.h>
#include <ucentral-platform.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* cfg_parse - Parse a JSON configuration object into a platform configuration
* @config: cJSON object containing the configuration
*
* Parses the uCentral configuration JSON and populates a struct plat_cfg
* with the parsed values. The caller is responsible for freeing the returned
* structure using plat_config_destroy() and free().
*
* Returns: Pointer to allocated plat_cfg on success, NULL on failure
*/
struct plat_cfg *cfg_parse(cJSON *config);
#ifdef __cplusplus
}
#endif
#endif /* CONFIG_PARSER_H */

View File

@@ -0,0 +1,147 @@
# Platform Mocks Directory
This directory contains mock implementations of platform-specific hardware abstraction layers (HAL) for integration testing.
## Purpose
Platform mocks allow the test framework to link against real platform implementation code without requiring actual hardware or external services (like gNMI servers). This enables:
- **Integration testing** - Test real platform code paths
- **Development** - Test platform changes without hardware
- **CI/CD** - Run comprehensive tests in automated pipelines
## What Gets Tested vs Mocked
### ✅ TESTED (Real Production Code)
- All platform implementation code in `src/ucentral-client/platform/`
- `plat_config_apply()` and all `config_*_apply()` functions
- Configuration parsing and validation logic
- Business rules and error handling
- Everything except the hardware interface calls
### ❌ MOCKED (Hardware Interface)
- Lowest-level HAL functions (gNMI, REST, system calls)
- Functions that would talk to actual hardware
- External service calls (gNMI server, etc.)
**Example:** When testing brcm-sonic platform, `config_vlan_apply()` runs real code, but `gnma_vlan_create()` is mocked.
## Available Platform Mocks
| File | Platform | Description |
|------|----------|-------------|
| `brcm-sonic.c` | Broadcom SONiC | Mocks for gNMI/gNOI APIs |
| `example-platform.c` | Template | Template for creating new platform mocks |
## How to Use
### Using Existing Platform Mocks
```bash
cd tests/config-parser
# Run tests with brcm-sonic platform
make test-config-full USE_PLATFORM=brcm-sonic
```
### Creating Platform Mocks for a New Platform
See `../ADDING_NEW_PLATFORM.md` for complete documentation.
**Quick steps:**
1. Copy the template:
```bash
cp example-platform.c your-platform.c
```
2. Try building (will fail):
```bash
make clean
make test-config-parser USE_PLATFORM=your-platform
```
3. Add mock functions for each "undefined reference" error
4. Repeat until it builds successfully
## Mock Strategy
### What to Mock
Mock the **lowest-level HAL functions** that would normally interact with hardware or external services:
- gNMI/gNOI calls (brcm-sonic)
- REST API calls
- System calls (ioctl, sysfs, etc.)
- Hardware driver calls
### What NOT to Mock
Don't mock the platform's **configuration logic** - that's what we're testing!
- ❌ Don't mock: `plat_config_apply()`
- ❌ Don't mock: `config_vlan_apply()`, `config_port_apply()`, etc.
- ✅ Do mock: `gnma_vlan_create()`, `gnma_port_speed_set()`, etc.
### Mock Patterns
**Pattern 1: Simple Success Stub**
```c
int hal_function(...)
{
fprintf(stderr, "[MOCK:platform] hal_function(...)\n");
return 0; /* Success */
}
```
**Pattern 2: With Parameter Logging**
```c
int hal_port_set(int port, int speed)
{
fprintf(stderr, "[MOCK:platform] hal_port_set(port=%d, speed=%d)\n",
port, speed);
return 0;
}
```
**Pattern 3: With Validation**
```c
int hal_port_set(int port, int speed)
{
fprintf(stderr, "[MOCK:platform] hal_port_set(port=%d, speed=%d)\n",
port, speed);
if (port < 0 || port >= MAX_PORTS) {
fprintf(stderr, "[MOCK:platform] ERROR: Invalid port\n");
return -1; /* Error */
}
return 0; /* Success */
}
```
## Tips
- **Start simple**: Return success for everything
- **Add logging**: Use `fprintf(stderr, ...)` to see what's called
- **Iterate**: Build, check for undefined references, add mocks, repeat
- **Test frequently**: Run tests after adding each mock function
- **Reference existing mocks**: Look at `brcm-sonic.c` for examples
## Troubleshooting
**Problem**: Undefined reference errors
**Solution**: Add mock functions for the missing symbols
**Problem**: Tests crash with segfault
**Solution**: Check that mock functions return valid values/pointers
**Problem**: Tests always pass
**Solution**: Your mocks might be too simple - consider adding validation logic
## See Also
- `../ADDING_NEW_PLATFORM.md` - Complete guide for adding platform support
- `../TEST_CONFIG_README.md` - Testing framework documentation
- `../../TESTING_FRAMEWORK.md` - Overall testing architecture

View File

@@ -0,0 +1,982 @@
/*
* Platform Mock: Broadcom SONiC (brcm-sonic)
*
* This file provides mock implementations of the gNMI/gNOI APIs
* used by the brcm-sonic platform. These mocks allow integration
* testing of the platform code without requiring actual hardware
* or gNMI server connection.
*
* Mock Strategy:
* - All functions return success (0) by default
* - Logs function calls for debugging
* - No actual hardware interaction
*
* ITERATIVE DEVELOPMENT:
* Start with minimal mocks and add more as linker reports undefined references.
*/
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
/* Platform includes */
#include <time.h>
#include "gnma/gnma_common.h"
#include "ucentral.h"
/* ============================================================================
* MOCK CONFIGURATION
* ============================================================================
* These constants control the mock behavior to simulate realistic hardware
*/
/* Number of ports to simulate (matches ECS4150-54P/54T hardware) */
#define MOCK_NUM_PORTS 54
/* ============================================================================
* GLOBAL VARIABLES (from ucentral-client.c)
* ============================================================================
* These are needed by proto.c but not provided by platform library
*/
struct client_config client = {
.redirector_file = "/tmp/test",
.redirector_file_dbg = "/tmp/test",
.ols_client_version_file = "/tmp/test",
.ols_schema_version_file = "/tmp/test",
.server = "test.example.com",
.port = 443,
.path = "/",
.serial = "TEST123456",
.CN = "test",
.firmware = "1.0.0",
.devid = "00000000-0000-0000-0000-000000000000",
.selfsigned = 0,
.debug = 0
};
time_t conn_time = 0;
struct plat_metrics_cfg ucentral_metrics = {0};
/* ============================================================================
* FEATURE STATUS MOCKS
* ============================================================================
* The platform checks feature initialization status. Mock all features as OK.
*/
#define FEAT_CORE 0
#define FEAT_AAA 1
#define FEATSTS_OK 0
int featsts[10] = {FEATSTS_OK, FEATSTS_OK, FEATSTS_OK}; /* All features OK */
/* ============================================================================
* FUNCTION MOCKS
* ============================================================================
* Add mock functions here as the linker reports undefined references.
* See tests/ADDING_NEW_PLATFORM.md for guidance.
*/
/*
* When you get linker errors like:
* undefined reference to `gnma_some_function'
*
* Add a mock implementation like this:
*
* int gnma_some_function(parameters...)
* {
* fprintf(stderr, "[MOCK:brcm-sonic] gnma_some_function(...)\n");
* return GNMA_OK; // or 0 for success
* }
*
* Then rebuild and check for more undefined references.
* Iterate until the build succeeds.
*/
/* VLAN Management Mocks */
int gnma_vlan_list_get(BITMAP_DECLARE(vlans, GNMA_MAX_VLANS))
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_list_get()\n");
/* Return empty VLAN list (all zeros) */
memset(vlans, 0, BITS_TO_UINT32(GNMA_MAX_VLANS) * sizeof(uint32_t));
return 0; /* Success */
}
int gnma_vlan_remove(uint16_t vid)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_remove(vid=%u)\n", vid);
return 0; /* Success */
}
int gnma_vlan_member_bmap_get(struct gnma_vlan_member_bmap *vlan_mbr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_member_bmap_get()\n");
/* Return empty member bitmap */
memset(vlan_mbr, 0, sizeof(*vlan_mbr));
return 0; /* Success */
}
struct gnma_change *gnma_change_create(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_change_create()\n");
/* Return a dummy non-NULL pointer */
return (struct gnma_change *)0x1; /* Mock handle */
}
int gnma_vlan_create(struct gnma_change *c, uint16_t vid)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_create(vid=%u)\n", vid);
(void)c;
return 0; /* Success */
}
int gnma_change_exec(struct gnma_change *c)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_change_exec()\n");
(void)c;
return 0; /* Success */
}
/* STP Mocks */
int gnma_stp_mode_set(gnma_stp_mode_t mode, struct gnma_stp_attr *attr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_stp_mode_set(mode=%d)\n", mode);
(void)attr;
return 0;
}
int gnma_stp_vid_set(uint16_t vid, struct gnma_stp_attr *attr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_stp_vid_set(vid=%u)\n", vid);
(void)attr;
return 0;
}
/* 802.1X Mocks */
int gnma_port_ieee8021x_pae_mode_set(struct gnma_port_key *port_key, bool is_authenticator)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_pae_mode_set(is_authenticator=%d)\n", is_authenticator);
(void)port_key;
return 0;
}
int gnma_port_ieee8021x_port_ctrl_set(struct gnma_port_key *port_key, gnma_8021x_port_ctrl_mode_t mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_port_ctrl_set(mode=%d)\n", mode);
(void)port_key;
return 0;
}
int gnma_port_ieee8021x_port_host_mode_set(struct gnma_port_key *port_key, gnma_8021x_port_host_mode_t mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_port_host_mode_set(mode=%d)\n", mode);
(void)port_key;
return 0;
}
int gnma_port_ieee8021x_guest_vlan_set(struct gnma_port_key *port_key, uint16_t vid)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_guest_vlan_set(vlan=%u)\n", vid);
(void)port_key;
return 0;
}
int gnma_port_ieee8021x_unauthorized_vlan_set(struct gnma_port_key *port_key, uint16_t vid)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_unauthorized_vlan_set(vlan=%u)\n", vid);
(void)port_key;
return 0;
}
int gnma_ieee8021x_system_auth_control_set(bool is_enabled)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_system_auth_control_set(enabled=%d)\n", is_enabled);
return 0;
}
int gnma_ieee8021x_das_dac_host_remove(struct gnma_das_dac_host_key *key)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_dac_host_remove()\n");
(void)key;
return 0;
}
int gnma_ieee8021x_das_dac_host_add(struct gnma_das_dac_host_key *key, const char *passkey)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_dac_host_add()\n");
(void)key;
(void)passkey;
return 0;
}
/* PoE Mocks */
int gnma_poe_port_detection_mode_set(struct gnma_port_key *port_key, gnma_poe_port_detection_mode_t mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_detection_mode_set(mode=%d)\n", mode);
(void)port_key;
return 0;
}
int gnma_poe_port_priority_set(struct gnma_port_key *port_key, gnma_poe_port_priority_t priority)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_priority_set(priority=%d)\n", priority);
(void)port_key;
return 0;
}
int gnma_poe_port_power_limit_set(struct gnma_port_key *port_key, bool user_defined, uint32_t power_limit)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_power_limit_set(user_defined=%d, limit=%u)\n",
user_defined, power_limit);
(void)port_key;
return 0;
}
int gnma_poe_port_admin_mode_set(struct gnma_port_key *port_key, bool enabled)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_admin_mode_set(enabled=%d)\n", enabled);
(void)port_key;
return 0;
}
int gnma_poe_port_reset(struct gnma_port_key *port_key)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_reset()\n");
(void)port_key;
return 0;
}
int gnma_poe_power_mgmt_set(gnma_poe_power_mgmt_mode_t mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_power_mgmt_set(mode=%d)\n", mode);
return 0;
}
int gnma_poe_usage_threshold_set(uint8_t threshold)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_usage_threshold_set(threshold=%u)\n", threshold);
return 0;
}
/* Routing Mocks */
int gnma_route_remove(uint16_t vr_id, struct gnma_ip_prefix *prefix)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_route_remove(vr_id=%u)\n", vr_id);
(void)prefix;
return 0;
}
int gnma_route_create(uint16_t vr_id, struct gnma_ip_prefix *prefix, struct gnma_route_attrs *attr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_route_create(vr_id=%u)\n", vr_id);
(void)prefix;
(void)attr;
return 0;
}
/* RADIUS Mocks */
int gnma_radius_host_remove(struct gnma_radius_host_key *key)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_radius_host_remove()\n");
(void)key;
return 0;
}
int gnma_radius_host_add(struct gnma_radius_host_key *key, const char *passkey,
uint16_t auth_port, uint8_t prio)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_radius_host_add(port=%u, prio=%u)\n",
auth_port, prio);
(void)key;
(void)passkey;
return 0;
}
/* System Mocks */
int gnma_system_password_set(char *password)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_system_password_set()\n");
(void)password; /* Don't log passwords */
return 0;
}
int gnma_techsupport_start(char *res_path)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_techsupport_start()\n");
(void)res_path;
return 0;
}
/* Configuration Management Mocks */
int gnma_config_restore(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_config_restore()\n");
return 0;
}
int gnma_factory_default(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_factory_default()\n");
return 0;
}
int gnma_metadata_get(struct gnma_metadata *md)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_metadata_get()\n");
(void)md;
return 0;
}
/* Firmware Upgrade Mocks */
int gnma_image_install(char *uri)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_image_install(uri=%s)\n", uri);
return 0;
}
int gnma_image_install_status(uint16_t *buf_size, char *buf)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_image_install_status()\n");
(void)buf_size;
(void)buf;
return 0;
}
int gnma_image_running_name_get(char *str, size_t str_max_len)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_image_running_name_get()\n");
(void)str;
(void)str_max_len;
return 0;
}
int gnma_rebootcause_get(char *buf, size_t buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_rebootcause_get()\n");
(void)buf;
(void)buf_size;
return 0;
}
/* Subscription/Telemetry Mocks */
int gnma_subscribe(void **handle, const struct gnma_subscribe_callbacks *callbacks)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_subscribe()\n");
(void)handle;
(void)callbacks;
return 0;
}
void gnma_unsubscribe(void **handle)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_unsubscribe()\n");
(void)handle;
}
/* Syslog Mocks */
int gnma_syslog_cfg_clear(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_syslog_cfg_clear()\n");
return 0;
}
int gnma_syslog_cfg_set(struct gnma_syslog_cfg *cfg, int count)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_syslog_cfg_set(count=%d)\n", count);
(void)cfg;
return 0;
}
/* VLAN/RIF Mocks */
int gnma_vlan_erif_attr_pref_list_get(uint16_t vid, uint16_t *list_size, struct gnma_ip_prefix *prefix_list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_erif_attr_pref_list_get(vid=%u)\n", vid);
(void)list_size;
(void)prefix_list;
return 0;
}
int gnma_vlan_erif_attr_pref_delete(uint16_t vid, struct gnma_ip_prefix *pref)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_erif_attr_pref_delete(vid=%u)\n", vid);
(void)pref;
return 0;
}
int gnma_vlan_erif_attr_pref_update(uint16_t vid, uint16_t list_size, struct gnma_ip_prefix *pref)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_erif_attr_pref_update(vid=%u, list_size=%u)\n",
vid, list_size);
(void)pref;
return 0;
}
/* DHCP Relay Mocks */
int gnma_vlan_dhcp_relay_server_remove(uint16_t vid, struct gnma_ip *ip)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_server_remove(vid=%u)\n", vid);
(void)ip;
return 0;
}
int gnma_vlan_dhcp_relay_server_add(uint16_t vid, struct gnma_ip *ip)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_server_add(vid=%u)\n", vid);
(void)ip;
return 0;
}
int gnma_vlan_dhcp_relay_max_hop_cnt_set(uint16_t vid, uint8_t max_hop)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_max_hop_cnt_set(vid=%u, max_hop=%u)\n",
vid, max_hop);
return 0;
}
int gnma_vlan_dhcp_relay_policy_action_set(uint16_t vid, gnma_dhcp_relay_policy_action_type_t act)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_policy_action_set(vid=%u, action=%d)\n",
vid, act);
return 0;
}
int gnma_vlan_dhcp_relay_ciruit_id_set(uint16_t vid, gnma_dhcp_relay_circuit_id_t id)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_ciruit_id_set(vid=%u, id=%d)\n",
vid, id);
return 0;
}
/* IGMP Snooping Mocks */
int gnma_igmp_snooping_set(uint16_t vid, struct gnma_igmp_snoop_attr *attr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_igmp_snooping_set(vid=%u)\n", vid);
(void)attr;
return 0;
}
int gnma_igmp_static_groups_set(uint16_t vid, size_t num_groups, struct gnma_igmp_static_group_attr *groups)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_igmp_static_groups_set(vid=%u, num_groups=%zu)\n",
vid, num_groups);
(void)groups;
return 0;
}
/* Port L2 RIF Mocks */
int gnma_portl2_erif_attr_pref_delete(struct gnma_port_key *port_key, struct gnma_ip_prefix *pref)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_portl2_erif_attr_pref_delete()\n");
(void)port_key;
(void)pref;
return 0;
}
int gnma_portl2_erif_attr_pref_update(struct gnma_port_key *port_key, uint16_t list_size, struct gnma_ip_prefix *pref)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_portl2_erif_attr_pref_update(list_size=%u)\n",
list_size);
(void)port_key;
(void)pref;
return 0;
}
/* Port List Mocks */
/**
* gnma_port_list_get - Get list of available ports (dynamic mock)
* @list_size: [in/out] Buffer size on input, actual port count on output
* @port_key_list: [out] Array to fill with port information
*
* This mock simulates the real gNMI API behavior:
* 1. If buffer is too small: Returns GNMA_ERR_OVERFLOW, sets *list_size to required size
* 2. If buffer is sufficient: Fills port_key_list with port names, returns 0
*
* This allows the platform code to query the number of ports dynamically:
* uint16_t size = 1;
* gnma_port_list_get(&size, &dummy); // Returns GNMA_ERR_OVERFLOW, size=54
* array = malloc(size * sizeof(*array));
* gnma_port_list_get(&size, array); // Returns 0, fills array
*/
int gnma_port_list_get(uint16_t *list_size, struct gnma_port_key *port_key_list)
{
uint16_t requested_size = list_size ? *list_size : 0;
uint16_t actual_ports = MOCK_NUM_PORTS;
uint16_t i;
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_list_get(requested=%u, actual=%u)\n",
requested_size, actual_ports);
/* Always return the actual number of ports available */
if (list_size) {
*list_size = actual_ports;
}
/* If buffer is too small, return overflow error */
if (requested_size < actual_ports) {
fprintf(stderr, "[MOCK:brcm-sonic] -> GNMA_ERR_OVERFLOW (need %u slots)\n", actual_ports);
return GNMA_ERR_OVERFLOW;
}
/* Fill in port names like Ethernet0, Ethernet1, ..., Ethernet53 */
if (port_key_list) {
for (i = 0; i < actual_ports; i++) {
snprintf(port_key_list[i].name, sizeof(port_key_list[i].name), "Ethernet%u", i);
}
fprintf(stderr, "[MOCK:brcm-sonic] -> SUCCESS (filled %u ports)\n", actual_ports);
}
return 0;
}
/* IP Interface Mocks */
int gnma_ip_iface_addr_get(struct gnma_vlan_ip_t *address_list, size_t *list_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ip_iface_addr_get()\n");
(void)address_list;
(void)list_size;
return 0;
}
/* MAC Address Table Mocks */
int gnma_mac_address_list_get(size_t *list_size, struct gnma_fdb_entry *list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_mac_address_list_get()\n");
(void)list_size;
(void)list;
return 0;
}
/* 802.1X Stats Mocks */
int gnma_iee8021x_das_dac_global_stats_get(uint32_t num_of_counters,
gnma_ieee8021x_das_dac_stat_type_t *counter_ids,
uint64_t *counters)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_iee8021x_das_dac_global_stats_get(num_counters=%u)\n",
num_of_counters);
(void)counter_ids;
(void)counters;
return 0;
}
/* Dynamic Route Mocks */
int gnma_dyn_route_list_get(size_t *list_size, struct gnma_ip_prefix *prefix_list, struct gnma_route_attrs *attr_list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_dyn_route_list_get()\n");
(void)list_size;
(void)prefix_list;
(void)attr_list;
return 0;
}
/* Neighbor Mocks */
int gnma_nei_addr_get(struct gnma_port_key *iface, struct in_addr *ip, char *mac, size_t buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_nei_addr_get()\n");
(void)iface;
(void)ip;
(void)mac;
(void)buf_size;
return 0;
}
/* Change Management Mocks */
void gnma_change_destory(struct gnma_change *c)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_change_destory()\n");
(void)c;
}
/* STP Port Mocks */
int gnma_stp_ports_enable(uint32_t list_size, struct gnma_port_key *ports_list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_stp_ports_enable(list_size=%u)\n", list_size);
(void)ports_list;
return 0;
}
/* GET Functions for Telemetry/State */
int gnma_poe_port_list_get(uint16_t *list_size, struct gnma_port_key *port_key_arr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_list_get()\n");
if (list_size) *list_size = 0;
(void)port_key_arr;
return 0;
}
int gnma_poe_port_admin_mode_get(struct gnma_port_key *port_key, bool *enabled)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_admin_mode_get()\n");
(void)port_key;
if (enabled) *enabled = false;
return 0;
}
int gnma_poe_port_detection_mode_get(struct gnma_port_key *port_key, gnma_poe_port_detection_mode_t *mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_detection_mode_get()\n");
(void)port_key;
(void)mode;
return 0;
}
int gnma_poe_port_power_limit_get(struct gnma_port_key *port_key, bool *user_defined, uint32_t *power_limit)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_power_limit_get()\n");
(void)port_key;
(void)user_defined;
(void)power_limit;
return 0;
}
int gnma_poe_port_priority_get(struct gnma_port_key *port_key, gnma_poe_port_priority_t *priority)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_priority_get()\n");
(void)port_key;
(void)priority;
return 0;
}
int gnma_poe_power_mgmt_get(gnma_poe_power_mgmt_mode_t *mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_power_mgmt_get()\n");
(void)mode;
return 0;
}
int gnma_poe_usage_threshold_get(uint8_t *power_threshold)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_usage_threshold_get()\n");
(void)power_threshold;
return 0;
}
int gnma_radius_hosts_list_get(size_t *list_size, struct gnma_radius_host_key *key_arr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_radius_hosts_list_get()\n");
if (list_size) *list_size = 0;
(void)key_arr;
return 0;
}
int gnma_portl2_erif_attr_pref_list_get(struct gnma_port_key *port_key, uint16_t *list_size, struct gnma_ip_prefix *prefix_list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_portl2_erif_attr_pref_list_get()\n");
(void)port_key;
if (list_size) *list_size = 0;
(void)prefix_list;
return 0;
}
int gnma_port_ieee8021x_pae_mode_get(struct gnma_port_key *port_key, bool *is_authenticator)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_pae_mode_get()\n");
(void)port_key;
(void)is_authenticator;
return 0;
}
int gnma_port_ieee8021x_port_ctrl_get(struct gnma_port_key *port_key, gnma_8021x_port_ctrl_mode_t *mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_port_ctrl_get()\n");
(void)port_key;
(void)mode;
return 0;
}
int gnma_port_ieee8021x_port_host_mode_get(struct gnma_port_key *port_key, gnma_8021x_port_host_mode_t *mode)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_port_host_mode_get()\n");
(void)port_key;
(void)mode;
return 0;
}
int gnma_port_ieee8021x_guest_vlan_get(struct gnma_port_key *port_key, uint16_t *vid)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_guest_vlan_get()\n");
(void)port_key;
(void)vid;
return 0;
}
int gnma_port_ieee8021x_unauthorized_vlan_get(struct gnma_port_key *port_key, uint16_t *vid)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_ieee8021x_unauthorized_vlan_get()\n");
(void)port_key;
(void)vid;
return 0;
}
int gnma_ieee8021x_das_dac_hosts_list_get(size_t *list_size, struct gnma_das_dac_host_key *das_dac_keys_arr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_dac_hosts_list_get()\n");
if (list_size) *list_size = 0;
(void)das_dac_keys_arr;
return 0;
}
int gnma_ieee8021x_system_auth_control_get(bool *is_enabled)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_system_auth_control_get()\n");
if (is_enabled) *is_enabled = false;
return 0;
}
int gnma_ieee8021x_das_bounce_port_ignore_get(bool *enabled)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_bounce_port_ignore_get()\n");
if (enabled) *enabled = false;
return 0;
}
int gnma_ieee8021x_system_auth_clients_get(char *buf, size_t buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_system_auth_clients_get()\n");
(void)buf;
(void)buf_size;
return 0;
}
int gnma_port_lldp_peer_info_get(struct gnma_port_key *port_key, char *buf, size_t buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_lldp_peer_info_get()\n");
(void)port_key;
(void)buf;
(void)buf_size;
return 0;
}
int gnma_igmp_iface_groups_get(struct gnma_port_key *iface, char *buf, size_t *buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_igmp_iface_groups_get()\n");
(void)iface;
(void)buf;
(void)buf_size;
return 0;
}
int gnma_poe_port_state_get(struct gnma_port_key *port_key, char *buf, size_t buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_port_state_get()\n");
(void)port_key;
(void)buf;
(void)buf_size;
return 0;
}
int gnma_poe_state_get(char *buf, size_t buf_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_poe_state_get()\n");
(void)buf;
(void)buf_size;
return 0;
}
int gnma_vlan_member_remove(struct gnma_change *c, uint16_t vid, struct gnma_port_key *port_key)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_member_remove(vid=%u)\n", vid);
(void)c;
(void)port_key;
return 0;
}
int gnma_vlan_member_create(struct gnma_change *c, uint16_t vid, struct gnma_port_key *port_key, bool tagged)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_member_create(vid=%u, tagged=%d)\n", vid, tagged);
(void)c;
(void)port_key;
return 0;
}
int gnma_reboot(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_reboot()\n");
return 0;
}
int gnma_config_save(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_config_save()\n");
return 0;
}
/* ============================================================================
* ADDITIONAL MOCK FUNCTIONS (Added for complete platform testing)
* ============================================================================ */
int gnma_switch_create(void)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_switch_create()\n");
return 0;
}
int gnma_port_admin_state_set(struct gnma_port_key *port_key, bool up)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_admin_state_set(up=%d)\n", up);
(void)port_key;
return 0;
}
int gnma_port_speed_set(struct gnma_port_key *port_key, const char *speed)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_speed_set(speed=%s)\n", speed ? speed : "NULL");
(void)port_key;
return 0;
}
int gnma_port_duplex_set(struct gnma_port_key *port_key, bool full_duplex)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_duplex_set(full_duplex=%d)\n", full_duplex);
(void)port_key;
return 0;
}
int gnma_port_speed_get(struct gnma_port_key *port_key, char *speed, size_t speed_size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_speed_get()\n");
(void)port_key;
if (speed && speed_size > 0) {
snprintf(speed, speed_size, "1000");
}
return 0;
}
int gnma_port_duplex_get(struct gnma_port_key *port_key, bool *full_duplex)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_duplex_get()\n");
(void)port_key;
if (full_duplex) {
*full_duplex = true;
}
return 0;
}
int gnma_port_oper_status_get(struct gnma_port_key *port_key, bool *is_up)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_oper_status_get()\n");
(void)port_key;
if (is_up) {
*is_up = true;
}
return 0;
}
int gnma_port_stats_get(struct gnma_port_key *port_key,
uint32_t num_of_counters,
gnma_port_stat_type_t *counter_ids,
uint64_t *counters)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_port_stats_get(num_of_counters=%u)\n", num_of_counters);
(void)port_key;
(void)counter_ids;
if (counters) {
for (uint32_t i = 0; i < num_of_counters; i++) {
counters[i] = 0;
}
}
return 0;
}
int gnma_route_list_get(uint16_t vr_id, uint32_t *list_size,
struct gnma_ip_prefix *prefix_list,
struct gnma_route_attrs *attr_list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_route_list_get(vr_id=%u)\n", vr_id);
(void)prefix_list;
(void)attr_list;
if (list_size) {
*list_size = 0;
}
return 0;
}
int gnma_stp_mode_get(gnma_stp_mode_t *mode, struct gnma_stp_attr *attr)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_stp_mode_get()\n");
if (mode) {
*mode = GNMA_STP_MODE_RPVST;
}
(void)attr;
return 0;
}
int gnma_stp_vid_bulk_get(struct gnma_stp_attr *list, ssize_t size)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_stp_vid_bulk_get(size=%ld)\n", (long)size);
(void)list;
return 0;
}
int gnma_vlan_dhcp_relay_server_list_get(uint16_t vid, size_t *list_size,
struct gnma_ip *ip_list)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_server_list_get(vid=%u)\n", vid);
(void)ip_list;
if (list_size) {
*list_size = 0;
}
return 0;
}
int gnma_vlan_dhcp_relay_ciruit_id_get(uint16_t vid, gnma_dhcp_relay_circuit_id_t *id)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_ciruit_id_get(vid=%u)\n", vid);
if (id) {
memset(id, 0, sizeof(*id));
}
return 0;
}
int gnma_vlan_dhcp_relay_policy_action_get(uint16_t vid, gnma_dhcp_relay_policy_action_type_t *action)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_policy_action_get(vid=%u)\n", vid);
if (action) {
*action = 0;
}
return 0;
}
int gnma_vlan_dhcp_relay_max_hop_cnt_get(uint16_t vid, uint8_t *max_hop_cnt)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_vlan_dhcp_relay_max_hop_cnt_get(vid=%u)\n", vid);
if (max_hop_cnt) {
*max_hop_cnt = 10;
}
return 0;
}
int gnma_ieee8021x_das_disable_port_ignore_get(bool *disable_port_ignore)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_disable_port_ignore_get()\n");
if (disable_port_ignore) {
*disable_port_ignore = false;
}
return 0;
}
int gnma_ieee8021x_das_ignore_server_key_get(bool *ignore_server_key)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_ignore_server_key_get()\n");
if (ignore_server_key) {
*ignore_server_key = false;
}
return 0;
}
int gnma_ieee8021x_das_ignore_session_key_get(bool *ignore_session_key)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_ignore_session_key_get()\n");
if (ignore_session_key) {
*ignore_session_key = false;
}
return 0;
}
int gnma_ieee8021x_das_auth_type_key_get(gnma_das_auth_type_t *auth_type)
{
fprintf(stderr, "[MOCK:brcm-sonic] gnma_ieee8021x_das_auth_type_key_get()\n");
if (auth_type) {
*auth_type = 0;
}
return 0;
}

View File

@@ -0,0 +1,67 @@
/*
* Platform Mock Template
*
* This is a template for creating platform mocks for new platforms.
* Copy this file and replace with your platform-specific mock implementations.
*
* See tests/ADDING_NEW_PLATFORM.md for complete documentation.
*
* USAGE:
* 1. Copy this file: cp example-platform.c your-platform.c
* 2. Try building: make clean && make test-config-parser USE_PLATFORM=your-platform
* 3. For each "undefined reference" error, add a mock function here
* 4. Repeat until it builds successfully
*/
#include <stdio.h>
#include <ucentral-platform.h>
/* ============================================================================
* EXAMPLE MOCK FUNCTIONS
* ============================================================================
* Replace these with your platform's hardware abstraction layer functions
*/
/* Example initialization mock */
int example_platform_init(void)
{
fprintf(stderr, "[MOCK:example-platform] Initialized\n");
return 0;
}
/* Example port configuration mock */
int example_platform_port_set(int port_id, int speed, int duplex)
{
fprintf(stderr, "[MOCK:example-platform] port_set(port=%d, speed=%d, duplex=%d)\n",
port_id, speed, duplex);
return 0; /* Success */
}
/* Example VLAN creation mock */
int example_platform_vlan_create(int vlan_id)
{
fprintf(stderr, "[MOCK:example-platform] vlan_create(vlan=%d)\n", vlan_id);
return 0; /* Success */
}
/* ============================================================================
* ADD YOUR PLATFORM'S MOCK FUNCTIONS BELOW
* ============================================================================
*
* General pattern for mock functions:
*
* int your_platform_function_name(parameters...)
* {
* fprintf(stderr, "[MOCK:your-platform] function_name(...)\n");
* // Log parameters for debugging
* // Optionally validate parameters
* return 0; // Return success
* }
*
* Tips:
* - Use fprintf(stderr, ...) to log function calls
* - Return 0 for success, -1 for error
* - Start simple (always return success)
* - Add validation logic later if needed
* - See brcm-sonic.c for more complex examples
*/

View File

@@ -0,0 +1,436 @@
/*
* Property Database Generated from Schema
*
* Source: ../../src/ucentral-client/proto.c
* Properties: 418 from schema
* Found: 102 implemented
* Not found: 316 not yet implemented
*
* This database tracks ALL properties in the uCentral schema,
* whether implemented or not. Properties with line_number=0
* are in the schema but not yet implemented in the code.
*/
static const struct property_metadata base_property_database[] = {
{"config-raw[][]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].acl[].acl-inf-counters-egress", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].acl[].acl-inf-counters-ingress", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].acl[].acl-inf-policy-egress", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].acl[].acl-inf-policy-ingress", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].acl[].acl-inf-policy-preference", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].autoneg", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].bpdu-guard.auto-recovery-secs", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].bpdu-guard.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"ethernet[].dhcp-snoop-port.dhcp-snoop-port-circuit-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].dhcp-snoop-port.dhcp-snoop-port-client-limit", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].dhcp-snoop-port.dhcp-snoop-port-trust", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].duplex", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1135, "Parsed in cfg_ethernet_parse()"},
{"ethernet[].edge-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"ethernet[].ieee8021x.authentication-mode", PROP_CONFIGURED, "proto.c", "cfg_ethernet_ieee8021x_parse", 919, "Parsed in cfg_ethernet_ieee8021x_parse()"},
{"ethernet[].ieee8021x.guest-vlan", PROP_CONFIGURED, "proto.c", "cfg_ethernet_ieee8021x_parse", 921, "Parsed in cfg_ethernet_ieee8021x_parse()"},
{"ethernet[].ieee8021x.host-mode", PROP_CONFIGURED, "proto.c", "cfg_ethernet_ieee8021x_parse", 920, "Parsed in cfg_ethernet_ieee8021x_parse()"},
{"ethernet[].ieee8021x.is-authenticator", PROP_CONFIGURED, "proto.c", "cfg_ethernet_ieee8021x_parse", 918, "Parsed in cfg_ethernet_ieee8021x_parse()"},
{"ethernet[].ieee8021x.mac-address-bypass", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].ieee8021x.mac-address-bypass-timeout-minutes", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].ieee8021x.unauthenticated-vlan", PROP_CONFIGURED, "proto.c", "cfg_ethernet_ieee8021x_parse", 922, "Parsed in cfg_ethernet_ieee8021x_parse()"},
{"ethernet[].ip-arp-inspect-port.rate-limit-pps", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].ip-arp-inspect-port.trusted", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].ip-source-guard-port.max-binding", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].ip-source-guard-port.mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].ip-source-guard-port.rule", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-pchan-admin-key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-port-admin-key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-port-priority", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-role", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-system-priority", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lacp-config.lacp-timeout", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-admin-status", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-mgmt-ip-v4", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-mgmt-ip-v6", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-port-descr", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-sys-capab", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-sys-descr", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-sys-name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-proto-ident", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-proto-vid", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-pvid", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-vlan-name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-link-agg", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-mac-phy", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-max-frame", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-poe", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-addr-admin-status", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-ca[].lldp-med-location-civic-ca-type", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-ca[].lldp-med-location-civic-ca-value", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-country-code", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-device-type", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-notification", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-ext-poe", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-inventory", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-location", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-med-cap", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-network-policy", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].lldp-interface-config.lldp-notification", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].poe.admin-mode", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 864, "Parsed in cfg_ethernet_poe_parse()"},
{"ethernet[].poe.detection", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 866, "Parsed in cfg_ethernet_poe_parse()"},
{"ethernet[].poe.do-reset", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 865, "Parsed in cfg_ethernet_poe_parse()"},
{"ethernet[].poe.power-limit", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 867, "Parsed in cfg_ethernet_poe_parse()"},
{"ethernet[].poe.priority", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 868, "Parsed in cfg_ethernet_poe_parse()"},
{"ethernet[].qos-priority-mapping.priority-untagged", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].cfi", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].cos", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].drop-preced", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].phb", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-dscpmutate[].drop-preced", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-dscpmutate[].dscp", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-dscpmutate[].phb", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-ipprec2dscp[].drop-preced", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-ipprec2dscp[].phb", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-ipprec2dscp[].preced", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-phb2queue[].phb", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-phb2queue[].queue-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].qos-priority-mapping.qos-map-trust-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].rate-limit-port.egress-kbps", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].rate-limit-port.ingress-kbps", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].select-ports[]", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1133, "Parsed in cfg_ethernet_parse()"},
{"ethernet[].services[]", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2107, "Parsed in cfg_unit_parse()"},
{"ethernet[].speed", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1136, "Parsed in cfg_ethernet_parse()"},
{"ethernet[].storm-control.broadcast-pps", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].storm-control.multicast-pps", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].storm-control.unknown-unicast-pps", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].trunk-group", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-detect-voice", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-priority", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-security", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"globals.ipv4-blackhole[].prefix", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1223, "Parsed in cfg_ethernet_parse()"},
{"globals.ipv4-blackhole[].vrf", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"globals.ipv4-network", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"globals.ipv4-unreachable[].prefix", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1223, "Parsed in cfg_ethernet_parse()"},
{"globals.ipv4-unreachable[].vrf", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"globals.ipv6-network", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].bridge.isolate-ports", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].bridge.mtu", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].bridge.tx-queue-len", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ethernet[].isolate", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ethernet[].learning", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ethernet[].macaddr", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ethernet[].multicast", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1487, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ethernet[].reverse-path-filter", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ethernet[].select-ports[]", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1133, "Parsed in cfg_ethernet_parse()"},
{"interfaces[].ethernet[].vlan-tag", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1443, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.addressing", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.broadcast[].prefix", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1223, "Parsed in cfg_ethernet_parse()"},
{"interfaces[].ipv4.broadcast[].vrf", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp-leases[].lease-time", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp-leases[].macaddr", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp-leases[].publish-hostname", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp-leases[].static-lease-offset", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp-snoop-vlan-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp.circuit-id-format", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1501, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.dhcp.lease-count", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp.lease-first", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp.lease-time", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.dhcp.relay-server", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1500, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.gateway[].metric", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.gateway[].nexthop", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1928, "Parsed in route_prefix_obj2node_key()"},
{"interfaces[].ipv4.gateway[].prefix", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1223, "Parsed in cfg_ethernet_parse()"},
{"interfaces[].ipv4.gateway[].vrf", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.ip-arp-inspect-vlan.vlan-acl-nodhcp-bindings", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.ip-arp-inspect-vlan.vlan-acl-rule", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.ip-arp-inspect-vlan.vlan-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.multicast.igmp.fast-leave-enable", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1331, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.last-member-query-interval", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1335, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.max-response-time", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1337, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.querier-enable", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1329, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.query-interval", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1333, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.snooping-enable", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1327, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.static-mcast-groups[].address", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1352, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.static-mcast-groups[].egress-ports[]", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1353, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.igmp.version", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1311, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"interfaces[].ipv4.multicast.mvr.mvr-intf-assoc-domain", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.multicast.mvr.mvr-intf-immed-leave", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.multicast.mvr.mvr-intf-mvr-role", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.multicast.unknown-multicast-flood-control", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2071, "Parsed in cfg_unit_parse()"},
{"interfaces[].ipv4.port-forward[].external-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.port-forward[].internal-address", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.port-forward[].internal-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.port-forward[].protocol", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1788, "Parsed in cfg_services_parse()"},
{"interfaces[].ipv4.send-hostname", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.subnet[].prefix", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1223, "Parsed in cfg_ethernet_parse()"},
{"interfaces[].ipv4.subnet[].vrf", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv4.use-dns[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.addressing", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.dhcpv6.announce-dns[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.dhcpv6.filter-prefix", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.dhcpv6.mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.gateway", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1916, "Parsed in route_prefix_obj2node_key()"},
{"interfaces[].ipv6.port-forward[].external-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.port-forward[].internal-address", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.port-forward[].internal-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.port-forward[].protocol", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1788, "Parsed in cfg_services_parse()"},
{"interfaces[].ipv6.prefix-size", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.subnet", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1276, "Parsed in cfg_ethernet_parse()"},
{"interfaces[].ipv6.traffic-allow[].destination-address", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.traffic-allow[].destination-ports[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.traffic-allow[].protocol", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1788, "Parsed in cfg_services_parse()"},
{"interfaces[].ipv6.traffic-allow[].source-address", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].ipv6.traffic-allow[].source-ports[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].isolate-hosts", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].metric", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].mtu", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].role", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].services[]", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2107, "Parsed in cfg_unit_parse()"},
{"interfaces[].vlan-awareness.first", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].vlan-awareness.last", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].vlan.id", PROP_CONFIGURED, "proto.c", "cfg_ethernet_port_isolation_interface_parse", 1057, "Parsed in cfg_ethernet_port_isolation_interface_parse()"},
{"interfaces[].vlan.proto", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1547, "Parsed in cfg_interfaces_parse()"},
{"interfaces[].vlan.range-end", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].vlan.range-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"interfaces[].vlan.stp-instance", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"metrics.dhcp-snooping.filters[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"metrics.health.dhcp-local", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"metrics.health.dhcp-remote", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"metrics.health.dns-local", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"metrics.health.dns-remote", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"metrics.health.interval", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1990, "Parsed in route_prefix_obj2node_key()"},
{"metrics.realtime.types[]", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1991, "Parsed in route_prefix_obj2node_key()"},
{"metrics.statistics.interval", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1990, "Parsed in route_prefix_obj2node_key()"},
{"metrics.statistics.types[]", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1991, "Parsed in route_prefix_obj2node_key()"},
{"metrics.statistics.wired-clients-max-num", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1992, "Parsed in route_prefix_obj2node_key()"},
{"metrics.telemetry.interval", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1990, "Parsed in route_prefix_obj2node_key()"},
{"metrics.telemetry.types[]", PROP_CONFIGURED, "proto.c", "route_prefix_obj2node_key", 1991, "Parsed in route_prefix_obj2node_key()"},
{"public_ip_lookup", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2227, "Parsed in cfg_unit_parse()"},
{"services.data-plane.ingress-filters[].name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.data-plane.ingress-filters[].program", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.gps.adjust-time", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.gps.baud-rate", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.http.enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1640, "Parsed in cfg_services_parse()"},
{"services.http.http-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.https.enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1640, "Parsed in cfg_services_parse()"},
{"services.https.https-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.igmp.enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1640, "Parsed in cfg_services_parse()"},
{"services.lldp.describe", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.lldp.location", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.log.host", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1546, "Parsed in cfg_interfaces_parse()"},
{"services.log.port", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1543, "Parsed in cfg_interfaces_parse()"},
{"services.log.priority", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 868, "Parsed in cfg_ethernet_poe_parse()"},
{"services.log.proto", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1547, "Parsed in cfg_interfaces_parse()"},
{"services.log.size", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1545, "Parsed in cfg_interfaces_parse()"},
{"services.mdns.enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1640, "Parsed in cfg_services_parse()"},
{"services.ntp.local-server", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.ntp.servers[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.online-check.action[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.online-check.check-interval", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.online-check.check-threshold", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.online-check.download-hosts[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.online-check.ping-hosts[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.bandwidth-down", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.bandwidth-up", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.bulk-detection.dscp", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.bulk-detection.packets-per-second", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.classifier[].dns[].fqdn", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.classifier[].dns[].reclassify", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.classifier[].dns[].suffix-matching", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.classifier[].dscp", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.classifier[].ports[].port", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1543, "Parsed in cfg_interfaces_parse()"},
{"services.quality-of-service.classifier[].ports[].protocol", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1788, "Parsed in cfg_services_parse()"},
{"services.quality-of-service.classifier[].ports[].range-end", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.classifier[].ports[].reclassify", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.quality-of-service.select-ports[]", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1133, "Parsed in cfg_ethernet_parse()"},
{"services.quality-of-service.services[]", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2107, "Parsed in cfg_unit_parse()"},
{"services.radius-proxy.proxy-secret", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.rtty.host", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1546, "Parsed in cfg_interfaces_parse()"},
{"services.rtty.mutual-tls", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.rtty.port", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1543, "Parsed in cfg_interfaces_parse()"},
{"services.rtty.token", PROP_CONFIGURED, "proto.c", "ping_handle", 2534, "Parsed in ping_handle()"},
{"services.ssh.authorized-keys[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.ssh.enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1640, "Parsed in cfg_services_parse()"},
{"services.ssh.password-authentication", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.ssh.port", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1543, "Parsed in cfg_interfaces_parse()"},
{"services.telnet.enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1640, "Parsed in cfg_services_parse()"},
{"services.wireguard-overlay.hosts[].endpoint", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.hosts[].ipaddr[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.hosts[].key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.hosts[].name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.hosts[].subnet[]", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1276, "Parsed in cfg_ethernet_parse()"},
{"services.wireguard-overlay.peer-exchange-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.peer-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.private-key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.proto", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1547, "Parsed in cfg_interfaces_parse()"},
{"services.wireguard-overlay.root-node.endpoint", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.root-node.ipaddr[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.root-node.key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.vxlan.isolate", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.vxlan.mtu", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"services.wireguard-overlay.vxlan.port", PROP_CONFIGURED, "proto.c", "cfg_interfaces_parse", 1543, "Parsed in cfg_interfaces_parse()"},
{"strict", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.acl[].acl-name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.acl[].acl-rules", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.acl[].acl-type", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.arp-inspect.ip-arp-inspect", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.arp-inspect.validate-allow-zeros", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.arp-inspect.validate-dst-mac", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.arp-inspect.validate-ip", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.arp-inspect.validate-src-mac", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-82", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-encode-subopt", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-policy", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-remoteid", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-mac-verify", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dhcp-snooping.dhcp-snoop-rate-limit", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.dns[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ieee8021x.auth-control-enable", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1697, "Parsed in cfg_services_parse()"},
{"switch.ieee8021x.dynamic-authorization.auth-type", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ieee8021x.dynamic-authorization.bounce-port-ignore", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ieee8021x.dynamic-authorization.client[].address", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1352, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"switch.ieee8021x.dynamic-authorization.client[].server-key", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1721, "Parsed in cfg_services_parse()"},
{"switch.ieee8021x.dynamic-authorization.disable-port-ignore", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ieee8021x.dynamic-authorization.ignore-server-key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ieee8021x.dynamic-authorization.ignore-session-key", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ieee8021x.dynamic-authorization.server-key", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1721, "Parsed in cfg_services_parse()"},
{"switch.ieee8021x.radius[].server-authentication-port", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1720, "Parsed in cfg_services_parse()"},
{"switch.ieee8021x.radius[].server-host", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1719, "Parsed in cfg_services_parse()"},
{"switch.ieee8021x.radius[].server-key", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1721, "Parsed in cfg_services_parse()"},
{"switch.ieee8021x.radius[].server-priority", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1722, "Parsed in cfg_services_parse()"},
{"switch.intrusion-detection-access-lockout.lockout-attempt-count", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.intrusion-detection-access-lockout.lockout-period-seconds", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ip-source-guard.bindings[].binding-ip", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ip-source-guard.bindings[].binding-mac", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ip-source-guard.bindings[].binding-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ip-source-guard.bindings[].binding-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.ip-source-guard.bindings[].binding-vlans", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.jumbo-frames", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-holdtime-multiplier", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-med-fast-start-count", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-notification-interval", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-refresh-interval", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-reinit-delay", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.lldp-global-config.lldp-tx-delay", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.bpdu-flooding", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.bpdu-tx-limit", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.bridge-prio", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.forward-delay-secs", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.hello-time-secs", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.instances[].enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.loop-detection.instances[].forward-delay", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.instances[].hello-time", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.instances[].id", PROP_CONFIGURED, "proto.c", "cfg_ethernet_port_isolation_interface_parse", 1057, "Parsed in cfg_ethernet_port_isolation_interface_parse()"},
{"switch.loop-detection.instances[].max-age", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.instances[].path-cost", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.instances[].priority", PROP_CONFIGURED, "proto.c", "cfg_ethernet_poe_parse", 868, "Parsed in cfg_ethernet_poe_parse()"},
{"switch.loop-detection.instances[].vlan-end", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.instances[].vlan-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.max-age-secs", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.mst-region.name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.mst-region.revision", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.pathcost-method", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.protocol", PROP_CONFIGURED, "proto.c", "cfg_services_parse", 1788, "Parsed in cfg_services_parse()"},
{"switch.loop-detection.roles[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.loop-detection.root-guard", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mc-lag", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.global-gateway-mac", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].dual-active-detection", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].gateway-mac", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].keepalive-interval", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-domain", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-group[].group-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-group[].lacp-config.lacp-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-group[].lacp-config.lacp-role", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-group[].lacp-config.lacp-timeout", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-group[].members[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].mclag-group[].trunk-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].peer-ip", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].peer-link.link-type", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].peer-link.port-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].peer-link.trunk-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].session-timeout", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].source-ip", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].system-mac-address", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mclag-config.mclag-domains[].system-priority", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-config.mvr-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-config.mvr-proxy-query-intvl", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-config.mvr-proxy-switching", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-config.mvr-robustness-val", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-config.mvr-source-port-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-domain-config[].mvr-domain-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-domain-config[].mvr-domain-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-domain-config[].mvr-domain-upstream-sip", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-domain-config[].mvr-domain-vlan-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-group-config[].mvr-group-assoc-domain[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-group-config[].mvr-group-name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-group-config[].mvr-group-range-end", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.mvr-group-config[].mvr-group-range-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.port-isolation.sessions[].downlink.interface-list[]", PROP_CONFIGURED, "proto.c", "cfg_ethernet_port_isolation_interface_parse", 996, "Parsed in cfg_ethernet_port_isolation_interface_parse()"},
{"switch.port-isolation.sessions[].id", PROP_CONFIGURED, "proto.c", "cfg_ethernet_port_isolation_interface_parse", 1057, "Parsed in cfg_ethernet_port_isolation_interface_parse()"},
{"switch.port-isolation.sessions[].uplink.interface-list[]", PROP_CONFIGURED, "proto.c", "cfg_ethernet_port_isolation_interface_parse", 996, "Parsed in cfg_ethernet_port_isolation_interface_parse()"},
{"switch.port-mirror[].analysis-port", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.port-mirror[].monitor-ports[]", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.qos-queue-config.queue-config[].queue-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.qos-queue-config.queue-config[].queue-strict-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.qos-queue-config.queue-config[].queue-weight", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.qos-queue-config.queue-scheduler-mode", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.dhcp-snooping.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.rt-events.dhcp-snooping.sub-events.dhcp-snooping.violation-cleared", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.dhcp-snooping.sub-events.dhcp-snooping.violation-detected", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.rt-events.fw-upgrade.sub-events.upg.backup-current-firmware", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.download-failed", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.download-in-progress", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.download-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.install-failed", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.install-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.reboot-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.success", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.validation-failed", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.validation-start", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.fw-upgrade.sub-events.upg.validation-success", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.module.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.rt-events.module.sub-events.module.plugin", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.module.sub-events.module.plugout", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.port-status.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.rt-events.port-status.sub-events.wired.carrier-down", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.port-status.sub-events.wired.carrier-up", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.rstp.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.rt-events.rstp.sub-events.rstp.loop-cleared", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.rstp.sub-events.rstp.loop-detected", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.rstp.sub-events.rstp.state-change", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.stp.enabled", PROP_CONFIGURED, "proto.c", "cfg_ethernet_parse", 1134, "Parsed in cfg_ethernet_parse()"},
{"switch.rt-events.stp.sub-events.stp.loop-cleared", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.stp.sub-events.stp.loop-detected", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.rt-events.stp.sub-events.stp.state-change", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.trunk-balance-method", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.voice-vlan-config.voice-vlan-ageing-time", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.voice-vlan-config.voice-vlan-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.voice-vlan-config.voice-vlan-oui-config[].voice-vlan-oui-description", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.voice-vlan-config.voice-vlan-oui-config[].voice-vlan-oui-mac", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"switch.voice-vlan-config.voice-vlan-oui-config[].voice-vlan-oui-mask", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"third-party", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.beacon-advertisement.device-name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.beacon-advertisement.device-serial", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.beacon-advertisement.network-id", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.hostname", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.leds-active", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.location", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.multicast.igmp-snooping-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.multicast.mld-snooping-enable", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.multicast.querier-enable", PROP_CONFIGURED, "proto.c", "__cfg_vlan_interface_parse_multicast", 1329, "Parsed in __cfg_vlan_interface_parse_multicast()"},
{"unit.multicast.unknown-multicast-flood-control", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2071, "Parsed in cfg_unit_parse()"},
{"unit.name", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.poe.power-management", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2050, "Parsed in cfg_unit_parse()"},
{"unit.poe.usage-threshold", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2051, "Parsed in cfg_unit_parse()"},
{"unit.random-password", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"unit.system-password", PROP_CONFIGURED, "proto.c", "cfg_unit_parse", 2065, "Parsed in cfg_unit_parse()"},
{"unit.timezone", PROP_CONFIGURED, "proto.c", "NULL", 0, "Not yet implemented"},
{"uuid", PROP_CONFIGURED, "proto.c", "configure_handle", 2256, "Parsed in configure_handle()"},
/* Sentinel */
{NULL, PROP_CONFIGURED, NULL, NULL, 0, NULL}
};

View File

@@ -0,0 +1,439 @@
/*
* Platform Property Database Generated from Schema
*
* Platform: brcm-sonic
* Source: ../../src/ucentral-client/platform/brcm-sonic/plat-gnma.c
* Properties: 418 from schema
* Found: 141 potentially implemented
* Not found: 277 not yet implemented
*
* This database tracks ALL properties in the uCentral schema.
* Platform code doesn't parse JSON - it applies structured config
* from proto.c to hardware. Functions are matched by feature area.
*
* Properties with line_number=0 are not yet implemented in platform.
*/
static const struct property_metadata platform_property_database_brcm_sonic[] = {
{"config-raw[][]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].acl[].acl-inf-counters-egress", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].acl[].acl-inf-counters-ingress", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].acl[].acl-inf-policy-egress", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].acl[].acl-inf-policy-ingress", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].acl[].acl-inf-policy-preference", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].autoneg", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].bpdu-guard.auto-recovery-secs", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].bpdu-guard.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].dhcp-snoop-port.dhcp-snoop-port-circuit-id", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].dhcp-snoop-port.dhcp-snoop-port-client-limit", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].dhcp-snoop-port.dhcp-snoop-port-trust", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].duplex", PROP_CONFIGURED, "plat-gnma.c", "plat_port_duplex_set", 74, "Applied in plat_port_duplex_set()"},
{"ethernet[].edge-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].ieee8021x.authentication-mode", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"ethernet[].ieee8021x.guest-vlan", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"ethernet[].ieee8021x.host-mode", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"ethernet[].ieee8021x.is-authenticator", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"ethernet[].ieee8021x.mac-address-bypass", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"ethernet[].ieee8021x.mac-address-bypass-timeout-minutes", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"ethernet[].ieee8021x.unauthenticated-vlan", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"ethernet[].ip-arp-inspect-port.rate-limit-pps", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].ip-arp-inspect-port.trusted", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].ip-source-guard-port.max-binding", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].ip-source-guard-port.mode", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].ip-source-guard-port.rule", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].lacp-config.lacp-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lacp-config.lacp-mode", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lacp-config.lacp-pchan-admin-key", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lacp-config.lacp-port-admin-key", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].lacp-config.lacp-port-priority", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].lacp-config.lacp-role", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lacp-config.lacp-system-priority", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lacp-config.lacp-timeout", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-admin-status", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-mgmt-ip-v4", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-mgmt-ip-v6", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-port-descr", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-sys-capab", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-sys-descr", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-basic-tlv-sys-name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-proto-ident", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-proto-vid", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-pvid", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot1-tlv-vlan-name", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-link-agg", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-mac-phy", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-max-frame", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-dot3-tlv-poe", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-addr-admin-status", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-ca[].lldp-med-location-civic-ca-type", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-ca[].lldp-med-location-civic-ca-value", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-country-code", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-location-civic-addr.lldp-med-location-civic-device-type", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-notification", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-ext-poe", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-inventory", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-location", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-med-cap", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-med-tlv-network-policy", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].lldp-interface-config.lldp-notification", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].poe.admin-mode", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].poe.detection", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].poe.do-reset", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].poe.power-limit", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].poe.priority", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"ethernet[].qos-priority-mapping.priority-untagged", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].cfi", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].cos", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].drop-preced", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-cos2dscp[].phb", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-dscpmutate[].drop-preced", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-dscpmutate[].dscp", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-dscpmutate[].phb", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-ipprec2dscp[].drop-preced", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-ipprec2dscp[].phb", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-ipprec2dscp[].preced", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-phb2queue[].phb", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-phb2queue[].queue-id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].qos-priority-mapping.qos-map-trust-mode", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].rate-limit-port.egress-kbps", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].rate-limit-port.ingress-kbps", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].select-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].services[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].speed", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"ethernet[].storm-control.broadcast-pps", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].storm-control.multicast-pps", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].storm-control.unknown-unicast-pps", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].trunk-group", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-detect-voice", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-mode", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-priority", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"ethernet[].voice-vlan-intf-config.voice-vlan-intf-security", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"globals.ipv4-blackhole[].prefix", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"globals.ipv4-blackhole[].vrf", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"globals.ipv4-network", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"globals.ipv4-unreachable[].prefix", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"globals.ipv4-unreachable[].vrf", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"globals.ipv6-network", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].bridge.isolate-ports", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].bridge.mtu", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].bridge.tx-queue-len", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ethernet[].isolate", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ethernet[].learning", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ethernet[].macaddr", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ethernet[].multicast", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ethernet[].reverse-path-filter", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ethernet[].select-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ethernet[].vlan-tag", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].ipv4.addressing", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.broadcast[].prefix", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.broadcast[].vrf", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp-leases[].lease-time", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp-leases[].macaddr", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp-leases[].publish-hostname", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp-leases[].static-lease-offset", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp-snoop-vlan-enable", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].ipv4.dhcp.circuit-id-format", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp.lease-count", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp.lease-first", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp.lease-time", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.dhcp.relay-server", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"interfaces[].ipv4.gateway[].metric", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"interfaces[].ipv4.gateway[].nexthop", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.gateway[].prefix", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.gateway[].vrf", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.ip-arp-inspect-vlan.vlan-acl-nodhcp-bindings", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].ipv4.ip-arp-inspect-vlan.vlan-acl-rule", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].ipv4.ip-arp-inspect-vlan.vlan-enable", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].ipv4.multicast.igmp.fast-leave-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.last-member-query-interval", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.max-response-time", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.querier-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.query-interval", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.snooping-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.static-mcast-groups[].address", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.igmp.static-mcast-groups[].egress-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ipv4.multicast.igmp.version", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.mvr.mvr-intf-assoc-domain", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.mvr.mvr-intf-immed-leave", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.mvr.mvr-intf-mvr-role", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.multicast.unknown-multicast-flood-control", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.port-forward[].external-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ipv4.port-forward[].internal-address", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.port-forward[].internal-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ipv4.port-forward[].protocol", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.send-hostname", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.subnet[].prefix", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.subnet[].vrf", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv4.use-dns[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.addressing", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.dhcpv6.announce-dns[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.dhcpv6.filter-prefix", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.dhcpv6.mode", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.gateway", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.port-forward[].external-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ipv6.port-forward[].internal-address", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.port-forward[].internal-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ipv6.port-forward[].protocol", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.prefix-size", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.subnet", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.traffic-allow[].destination-address", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.traffic-allow[].destination-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].ipv6.traffic-allow[].protocol", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.traffic-allow[].source-address", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].ipv6.traffic-allow[].source-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"interfaces[].isolate-hosts", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].metric", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"interfaces[].mtu", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].role", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].services[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"interfaces[].vlan-awareness.first", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].vlan-awareness.last", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].vlan.id", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].vlan.proto", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].vlan.range-end", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].vlan.range-start", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"interfaces[].vlan.stp-instance", PROP_CONFIGURED, "plat-gnma.c", "config_stp_apply", 3929, "Applied in config_stp_apply()"},
{"metrics.dhcp-snooping.filters[]", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"metrics.health.dhcp-local", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"metrics.health.dhcp-remote", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"metrics.health.dns-local", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.health.dns-remote", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.health.interval", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.realtime.types[]", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.statistics.interval", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.statistics.types[]", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.statistics.wired-clients-max-num", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.telemetry.interval", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"metrics.telemetry.types[]", PROP_CONFIGURED, "plat-gnma.c", "config_metrics_apply", 4019, "Applied in config_metrics_apply()"},
{"public_ip_lookup", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.data-plane.ingress-filters[].name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.data-plane.ingress-filters[].program", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.gps.adjust-time", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.gps.baud-rate", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.http.enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.http.http-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.https.enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.https.https-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.igmp.enable", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_igmp_set", 3096, "Applied in plat_vlan_igmp_set()"},
{"services.lldp.describe", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.lldp.location", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.log.host", PROP_CONFIGURED, "plat-gnma.c", "plat_syslog_set", 81, "Applied in plat_syslog_set()"},
{"services.log.port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.log.priority", PROP_CONFIGURED, "plat-gnma.c", "plat_syslog_set", 81, "Applied in plat_syslog_set()"},
{"services.log.proto", PROP_CONFIGURED, "plat-gnma.c", "plat_syslog_set", 81, "Applied in plat_syslog_set()"},
{"services.log.size", PROP_CONFIGURED, "plat-gnma.c", "plat_syslog_set", 81, "Applied in plat_syslog_set()"},
{"services.mdns.enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.ntp.local-server", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.ntp.servers[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.online-check.action[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.online-check.check-interval", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.online-check.check-threshold", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.online-check.download-hosts[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.online-check.ping-hosts[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.bandwidth-down", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.bandwidth-up", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.bulk-detection.dscp", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.bulk-detection.packets-per-second", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].dns[].fqdn", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].dns[].reclassify", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].dns[].suffix-matching", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].dscp", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].ports[].port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.quality-of-service.classifier[].ports[].protocol", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].ports[].range-end", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.classifier[].ports[].reclassify", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.quality-of-service.select-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.quality-of-service.services[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.radius-proxy.proxy-secret", PROP_CONFIGURED, "plat-gnma.c", "plat_radius_hosts_list_set", 4349, "Applied in plat_radius_hosts_list_set()"},
{"services.rtty.host", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.rtty.mutual-tls", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.rtty.port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.rtty.token", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.ssh.authorized-keys[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.ssh.enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.ssh.password-authentication", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.ssh.port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.telnet.enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.hosts[].endpoint", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.hosts[].ipaddr[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.hosts[].key", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.hosts[].name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.hosts[].subnet[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.peer-exchange-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.wireguard-overlay.peer-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"services.wireguard-overlay.private-key", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.proto", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.root-node.endpoint", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.root-node.ipaddr[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.root-node.key", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.vxlan.isolate", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.vxlan.mtu", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"services.wireguard-overlay.vxlan.port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"strict", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.acl[].acl-name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.acl[].acl-rules", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.acl[].acl-type", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.arp-inspect.ip-arp-inspect", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.arp-inspect.validate-allow-zeros", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.arp-inspect.validate-dst-mac", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.arp-inspect.validate-ip", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.arp-inspect.validate-src-mac", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.dhcp-snooping.dhcp-snoop-enable", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-82", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-encode-subopt", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-policy", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dhcp-snooping.dhcp-snoop-inf-opt-remoteid", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dhcp-snooping.dhcp-snoop-mac-verify", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dhcp-snooping.dhcp-snoop-rate-limit", PROP_CONFIGURED, "plat-gnma.c", "config_vlan_dhcp_relay_apply", 3717, "Applied in config_vlan_dhcp_relay_apply()"},
{"switch.dns[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.ieee8021x.auth-control-enable", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.dynamic-authorization.auth-type", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.dynamic-authorization.bounce-port-ignore", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.ieee8021x.dynamic-authorization.client[].address", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.dynamic-authorization.client[].server-key", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.dynamic-authorization.disable-port-ignore", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.ieee8021x.dynamic-authorization.ignore-server-key", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.dynamic-authorization.ignore-session-key", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.dynamic-authorization.server-key", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.radius[].server-authentication-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.ieee8021x.radius[].server-host", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.radius[].server-key", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.ieee8021x.radius[].server-priority", PROP_CONFIGURED, "plat-gnma.c", "config_ieee8021x_apply", 4557, "Applied in config_ieee8021x_apply()"},
{"switch.intrusion-detection-access-lockout.lockout-attempt-count", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.intrusion-detection-access-lockout.lockout-period-seconds", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.ip-source-guard.bindings[].binding-ip", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.ip-source-guard.bindings[].binding-mac", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.ip-source-guard.bindings[].binding-mode", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.ip-source-guard.bindings[].binding-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.ip-source-guard.bindings[].binding-vlans", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.jumbo-frames", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-holdtime-multiplier", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-med-fast-start-count", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-notification-interval", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-refresh-interval", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-reinit-delay", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.lldp-global-config.lldp-tx-delay", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.bpdu-flooding", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.bpdu-tx-limit", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.bridge-prio", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.forward-delay-secs", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.hello-time-secs", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].forward-delay", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].hello-time", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].max-age", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].path-cost", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].priority", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.instances[].vlan-end", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.loop-detection.instances[].vlan-start", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.loop-detection.max-age-secs", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.mst-region.name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.mst-region.revision", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.pathcost-method", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.protocol", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.roles[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.loop-detection.root-guard", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mc-lag", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.global-gateway-mac", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].dual-active-detection", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].gateway-mac", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].keepalive-interval", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-domain", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-group[].group-id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-group[].lacp-config.lacp-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-group[].lacp-config.lacp-role", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-group[].lacp-config.lacp-timeout", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-group[].members[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].mclag-group[].trunk-id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].peer-ip", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].peer-link.link-type", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].peer-link.port-id", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.mclag-config.mclag-domains[].peer-link.trunk-id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].session-timeout", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].source-ip", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].system-mac-address", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mclag-config.mclag-domains[].system-priority", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-config.mvr-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-config.mvr-proxy-query-intvl", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-config.mvr-proxy-switching", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-config.mvr-robustness-val", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-config.mvr-source-port-mode", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.mvr-domain-config[].mvr-domain-enable", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-domain-config[].mvr-domain-id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-domain-config[].mvr-domain-upstream-sip", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-domain-config[].mvr-domain-vlan-id", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.mvr-group-config[].mvr-group-assoc-domain[]", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-group-config[].mvr-group-name", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-group-config[].mvr-group-range-end", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.mvr-group-config[].mvr-group-range-start", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.port-isolation.sessions[].downlink.interface-list[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.port-isolation.sessions[].id", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.port-isolation.sessions[].uplink.interface-list[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.port-mirror[].analysis-port", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.port-mirror[].monitor-ports[]", PROP_CONFIGURED, "plat-gnma.c", "plat_port_speed_set", 73, "Applied in plat_port_speed_set()"},
{"switch.qos-queue-config.queue-config[].queue-id", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.qos-queue-config.queue-config[].queue-strict-mode", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.qos-queue-config.queue-config[].queue-weight", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.qos-queue-config.queue-scheduler-mode", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.dhcp-snooping.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.dhcp-snooping.sub-events.dhcp-snooping.violation-cleared", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.dhcp-snooping.sub-events.dhcp-snooping.violation-detected", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.backup-current-firmware", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.download-failed", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.download-in-progress", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.download-start", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.install-failed", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.install-start", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.reboot-start", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.success", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.validation-failed", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.validation-start", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.fw-upgrade.sub-events.upg.validation-success", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.module.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.module.sub-events.module.plugin", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.module.sub-events.module.plugout", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.port-status.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.port-status.sub-events.wired.carrier-down", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.port-status.sub-events.wired.carrier-up", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.rstp.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.rstp.sub-events.rstp.loop-cleared", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.rstp.sub-events.rstp.loop-detected", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.rstp.sub-events.rstp.state-change", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.stp.enabled", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.stp.sub-events.stp.loop-cleared", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.stp.sub-events.stp.loop-detected", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.rt-events.stp.sub-events.stp.state-change", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.trunk-balance-method", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"switch.voice-vlan-config.voice-vlan-ageing-time", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.voice-vlan-config.voice-vlan-id", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.voice-vlan-config.voice-vlan-oui-config[].voice-vlan-oui-description", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.voice-vlan-config.voice-vlan-oui-config[].voice-vlan-oui-mac", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"switch.voice-vlan-config.voice-vlan-oui-config[].voice-vlan-oui-mask", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_rif_set", 76, "Applied in plat_vlan_rif_set()"},
{"third-party", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
{"unit.beacon-advertisement.device-name", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.beacon-advertisement.device-serial", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.beacon-advertisement.network-id", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.hostname", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.leds-active", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.location", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.multicast.igmp-snooping-enable", PROP_CONFIGURED, "plat-gnma.c", "plat_vlan_igmp_set", 3096, "Applied in plat_vlan_igmp_set()"},
{"unit.multicast.mld-snooping-enable", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.multicast.querier-enable", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.multicast.unknown-multicast-flood-control", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.name", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.poe.power-management", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"unit.poe.usage-threshold", PROP_CONFIGURED, "plat-gnma.c", "config_poe_port_apply", 4186, "Applied in config_poe_port_apply()"},
{"unit.random-password", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.system-password", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"unit.timezone", PROP_CONFIGURED, "plat-gnma.c", "config_unit_apply", 4414, "Applied in config_unit_apply()"},
{"uuid", PROP_CONFIGURED, "plat-gnma.c", "NULL", 0, "Not yet implemented in platform"},
/* Sentinel */
{NULL, PROP_CONFIGURED, NULL, NULL, 0, NULL}
};

View File

@@ -0,0 +1,25 @@
/* Platform-specific property database template
*
* Copy this file to property-database-platform-YOUR_PLATFORM.c
* and regenerate with your platform code.
*
* This database tracks properties parsed by platform-specific code in:
* - platform/your-platform/plat-*.c
*
* To regenerate this database:
* cd tests/tools
* python3 rebuild-property-database.py \
* ../../src/ucentral-client/platform/your-platform/plat-*.c \
* ../../config-samples/cfg*.json \
* > ../config-parser/property-database-platform-your-platform.c
*/
static const struct property_metadata platform_property_database_example[] = {
/* Platform-specific properties go here */
/* Example entries:
* {"services.custom-feature.enabled", PROP_CONFIGURED, "platform/example/plat-example.c", "config_custom_apply", 100, ""},
* {"ethernet[].vendor-specific", PROP_CONFIGURED, "platform/example/plat-example.c", "config_port_vendor_apply", 200, ""},
*/
{NULL, 0, NULL, NULL, 0, NULL} /* Terminator */
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,245 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Test Stubs for Configuration Parser Tests
*
* Provides stub/mock implementations of global variables and functions
* that proto.c references but are not needed for testing cfg_parse()
*/
#include <time.h>
#include <string.h>
#include "ucentral.h"
/*
* Minimal stub definition for struct blob
* proto.c uses this type in many functions, but these functions are not
* actually called during cfg_parse() testing. We just need the type to exist
* so proto.c can compile.
*/
struct blob {
char *data;
size_t len;
};
/* Stub global variables needed by proto.c */
struct client_config client = {
.redirector_file = "/tmp/test",
.redirector_file_dbg = "/tmp/test",
.ols_client_version_file = "/tmp/test",
.ols_schema_version_file = "/tmp/test",
.server = "test.example.com",
.port = 443,
.path = "/",
.serial = "TEST123456",
.CN = "test",
.firmware = "1.0.0",
.devid = "00000000-0000-0000-0000-000000000000",
.selfsigned = 0,
.debug = 0
};
time_t conn_time = 0;
struct plat_metrics_cfg ucentral_metrics = {0};
/* Stub platform functions that cfg_parse() needs */
/*
* Return a dummy port count for testing
*
* RATIONALE: Configuration parser tests need to simulate a real hardware platform
* to properly validate port selection and configuration. Originally this returned
* 100 ports, but that didn't match actual ECS hardware models.
*
* FIX: Changed to return 54 ports (Ethernet0-Ethernet53) to accurately simulate
* the ECS4150-54P (54-port PoE) and ECS4150-54T (54-port non-PoE) switch models.
* This ensures test configurations using port ranges or wildcards are validated
* against realistic hardware constraints.
*
* NOTE: This affects all test configs that use "Ethernet*" wildcard or specify
* individual ports - they will now be validated against 0-53 port range.
*/
int plat_port_num_get(uint16_t *num_of_active_ports)
{
/* Return 54 ports (Ethernet0-Ethernet53) to match ECS4150-54P/54T hardware */
*num_of_active_ports = 54;
return 0; /* Success */
}
/* Fill in dummy port list for testing */
int plat_port_list_get(uint16_t list_size, struct plat_ports_list *ports)
{
struct plat_ports_list *port = ports;
uint16_t i;
/* Fill in port names like Ethernet0, Ethernet1, etc. */
for (i = 0; i < list_size && port; i++) {
snprintf(port->name, PORT_MAX_NAME_LEN, "Ethernet%u", i);
port = port->next;
}
return 0; /* Success */
}
/* Stub function for destroying platform config - no-op in tests */
void plat_config_destroy(struct plat_cfg *cfg)
{
(void)cfg; /* Unused in test - just a no-op */
}
/* Additional platform function stubs needed by proto.c */
int plat_saved_config_id_get(uint64_t *id)
{
*id = 0;
return 0;
}
int plat_info_get(struct plat_platform_info *info)
{
strncpy(info->platform, "test", sizeof(info->platform) - 1);
strncpy(info->hwsku, "test", sizeof(info->hwsku) - 1);
strncpy(info->mac, "00:00:00:00:00:00", sizeof(info->mac) - 1);
return 0;
}
int plat_metrics_restore(struct plat_metrics_cfg *cfg)
{
(void)cfg;
return 0;
}
void plat_state_poll_stop(void) {}
void plat_health_poll_stop(void) {}
void plat_telemetry_poll_stop(void) {}
void plat_upgrade_poll_stop(void) {}
void plat_state_poll(void (*cb)(struct plat_state_info *), int period_sec)
{
(void)cb;
(void)period_sec;
}
void plat_health_poll(void (*cb)(struct plat_health_info *), int period_sec)
{
(void)cb;
(void)period_sec;
}
void plat_telemetry_poll(void (*cb)(struct plat_state_info *), int period_sec)
{
(void)cb;
(void)period_sec;
}
void plat_log_flush(void) {}
int plat_config_apply(struct plat_cfg *cfg, uint32_t id)
{
(void)cfg;
(void)id;
return 0;
}
int plat_config_restore(void)
{
return 0;
}
int plat_config_save(uint64_t id)
{
(void)id;
return 0;
}
int plat_metrics_save(const struct plat_metrics_cfg *cfg)
{
(void)cfg;
return 0;
}
char *plat_log_pop_concatenate(void)
{
return NULL;
}
int plat_reboot(void)
{
return 0;
}
int plat_factory_default(void)
{
return 0;
}
int plat_rtty(struct plat_rtty_cfg *rtty_cfg)
{
(void)rtty_cfg;
return 0;
}
int plat_upgrade(char *uri, char *signature)
{
(void)uri;
(void)signature;
return 0;
}
void plat_upgrade_poll(int (*cb)(struct plat_upgrade_info *), int period_sec)
{
(void)cb;
(void)period_sec;
}
int plat_run_script(struct plat_run_script *script)
{
(void)script;
return 0;
}
int plat_reboot_cause_get(struct plat_reboot_cause *cause)
{
cause->cause = PLAT_REBOOT_CAUSE_UNAVAILABLE;
cause->ts = 0;
strncpy(cause->desc, "test", sizeof(cause->desc) - 1);
return 0;
}
int plat_event_subscribe(const struct plat_event_callbacks *cbs)
{
(void)cbs;
return 0;
}
void plat_event_unsubscribe(void) {}
int plat_init(void)
{
fprintf(stderr, "[STUB] plat_init() - platform initialization (stub mode)\n");
return 0;
}
/* EST client function stubs for PKI 2.0 testing */
const char* est_get_server_url(const char *cert_path)
{
(void)cert_path;
return "est.test.example.com"; /* Dummy EST server URL */
}
int est_simple_reenroll(const char *est_server,
const char *operational_cert, const char *key,
const char *ca_bundle,
char **renewed_cert_out)
{
(void)est_server;
(void)operational_cert;
(void)key;
(void)ca_bundle;
(void)renewed_cert_out;
return 0; /* EST_SUCCESS - stub always succeeds */
}
const char* est_get_error(void)
{
return "EST stub mode - no real error";
}

22
tests/requirements.txt Normal file
View File

@@ -0,0 +1,22 @@
# Python Dependencies for OLS uCentral Client Testing Framework
#
# Install all dependencies:
# pip3 install -r tests/requirements.txt
#
# Or install individually:
# pip3 install pyyaml
# YAML parsing for schema extraction
# Used by: tests/tools/extract-schema-properties.py
# Purpose: Parse YAML schema files from ols-ucentral-schema repository
pyyaml>=5.1
# Note: All other Python scripts use standard library only:
# - json (built-in)
# - sys (built-in)
# - argparse (built-in)
# - os (built-in)
# - pathlib (built-in, Python 3.4+)
# - typing (built-in, Python 3.5+)
# - re (built-in)
# - subprocess (built-in)

View File

@@ -0,0 +1,356 @@
# uCentral Schema Validator
A modular, portable tool for validating JSON configuration files against the uCentral schema with advanced undefined property detection and typo suggestions.
## Features
- **Schema Validation**: Full JSON Schema Draft-7 validation (types, enums, constraints, etc.)
- **Undefined Property Detection**: Identifies properties in config not defined in schema
- **Smart Typo Detection**: Suggests corrections for likely misspellings
- Separator mismatches: `lldp_admin_status``lldp-admin-status` (underscore vs dash)
- Case mismatches: `lacpEnable``lacp-enable` (camelCase vs dash-case)
- Similar spelling: Edit distance analysis with confidence scoring
- **Multiple Output Formats**: Human-readable and machine-readable JSON
- **Directory Validation**: Validate entire directories of configs at once
- **CI/CD Ready**: Exit codes and strict mode for pipeline integration
- **Schema Auto-Detection**: Automatically finds schema in common locations
- **Modular Design**: Easy to port to other repositories (platform-specific implementations, etc.)
- **Standalone Operation**: Works independently without external dependencies beyond Python 3 + jsonschema
## Installation
The validator requires Python 3 and the `jsonschema` module:
```bash
# In Docker build environment (already installed)
pip3 install jsonschema
# On host system
pip3 install jsonschema
```
## Usage
### Basic Usage
```bash
# Schema validation only (default behavior)
python3 validate-schema.py config.json
# Check for undefined properties (informational warnings)
python3 validate-schema.py config.json --check-undefined
# Strict mode: treat undefined properties as errors (for CI/CD)
python3 validate-schema.py config.json --strict-schema
# Validate all configs in a directory
python3 validate-schema.py ../../config-samples/ --check-undefined
# Specify custom schema
python3 validate-schema.py config.json --schema path/to/schema.json
```
### Understanding Undefined Properties
**Undefined properties are NOT validation errors** - they are informational warnings that help identify:
1. **Typos/Misspellings**: Properties that won't be applied even though config is valid
- Example: `lldp_admin_status` instead of `lldp-admin-status`
- Example: `lacpEnable` instead of `lacp-enable`
2. **Vendor-Specific Extensions**: ODM/vendor proprietary properties not in schema
- Risk: May change without notice, not portable across platforms
- Recommendation: Document and coordinate with schema maintainers
3. **Deprecated Properties**: Properties removed from newer schema versions
- Check schema version compatibility
**When to use each mode:**
- **Default mode** (no flags): Standard validation, undefined properties ignored
- **`--check-undefined`**: Development mode, see warnings but don't fail builds
- **`--strict-schema`**: CI/CD enforcement mode, fail on any undefined properties
### Output Formats
```bash
# Human-readable output (default)
python3 validate-schema.py config.json --check-undefined
# Machine-readable JSON output
python3 validate-schema.py config.json --check-undefined --format json > report.json
```
### Via Makefile
```bash
# Schema validation only
make validate-schema
# Configuration parser tests only
make test-config
# Both schema validation + parser tests
make test-config-full
```
## Exit Codes
- `0`: All configurations are valid
- Undefined properties don't affect exit code unless `--strict-schema` is used
- `1`: Validation errors OR (strict mode AND undefined properties found)
- `2`: File/schema errors (file not found, invalid schema, etc.)
## Output Examples
### Valid Configuration (Schema Only)
```
✓ Schema Valid: cfg0.json
```
### Valid Configuration with Undefined Properties Check
```
✓ Schema Valid: cfg0.json
✓ All properties defined in schema
```
### Configuration with Undefined Properties
```
✓ Schema Valid: test_config.json
Undefined Properties (informational):
Found 3 property/properties not in schema
These may be:
• Typos/misspellings (won't be applied even though config is valid)
• Vendor-specific extensions (not portable, may change)
• Deprecated properties (check schema version)
1. ethernet[].lldp_admin_status
→ Not defined in schema
→ Possible matches:
✓ ethernet[].lldp-interface-config.lldp-admin-status (use '-' not '_')
2. ethernet[].lacpEnable
→ Not defined in schema
→ Possible matches:
✓ ethernet[].lacp-config.lacp-enable (use dash-case not camelCase)
? interfaces[].ipv4.ip-arp-inspect-vlan.vlan-enable (similar spelling)
3. ethernet[].custom-property
→ Not defined in schema
```
**Note**: This config is schema-valid (exit code 0), but has informational warnings about undefined properties.
### Invalid Configuration (Schema Errors)
```
✗ Schema Invalid: bad-config.json
Found 2 validation error(s):
Error 1:
Path: $.ethernet
Message: {'speed': 1000} is not of type 'array'
Validator: type
Error 2:
Path: $.interfaces[0].vlan.id
Message: 5000 is greater than the maximum of 4094
Validator: maximum
```
**Note**: This config fails schema validation (exit code 1).
### Directory Summary
```
Summary: 37 file(s) checked, 34 valid, 3 invalid
5 file(s) with undefined properties
```
## Integration with test-config-parser.c
The test-config-parser.c tool automatically calls the schema validator before running parser tests. This provides two-layer validation:
1. **Layer 1 (Schema)**: Structural validation - is the JSON valid per schema?
2. **Layer 2 (Parser)**: Implementation validation - can proto.c process it?
## Porting to Other Repositories
The validator is designed to be repository-agnostic. To port to another repository:
1. Copy `validate-schema.py` to the target repository
2. Ensure the schema file is in one of the search paths, or specify with `--schema`
3. Update Makefile targets if desired
### Default Schema Search Paths
Relative to the validator script location:
- `../../config-samples/ucentral.schema.pretty.json`
- `../../config-samples/ols.ucentral.schema.json`
- `../../../config-samples/ucentral.schema.pretty.json`
- `./ols.ucentral.schema.json`
## Python API
The `SchemaValidator` class can be imported and used programmatically:
```python
from validate_schema import SchemaValidator
# Initialize validator
validator = SchemaValidator(schema_path="/path/to/schema.json")
# Validate a file
is_valid, errors = validator.validate_file("config.json")
# Validate a config dict
config = {"uuid": 123, "ethernet": []}
is_valid, errors = validator.validate_config(config)
# Validate directory
results = validator.validate_directory("/path/to/configs")
```
## Typo Detection Features
The validator includes intelligent typo detection that identifies naming mistakes and suggests corrections:
### Separator Mismatches
**Underscore vs Dash:**
```
ethernet[].lldp_admin_status ❌ Underscore
ethernet[].lldp-admin-status ✓ Correct (dash-case)
```
**camelCase vs dash-case:**
```
ethernet[].lacpEnable ❌ camelCase
ethernet[].lacp-enable ✓ Correct (dash-case)
```
### Similar Spelling
Uses Levenshtein distance algorithm to find properties with similar spelling:
```
services.logSettings.enabled ❌ Not in schema
services.log.enabled ✓ Possible match (edit distance: 1)
```
### Confidence Levels
- **✓ High confidence**: Exact match after normalization (separator/case fix)
- **? Medium confidence**: Similar spelling (edit distance 2-3)
- **No suggestion**: No similar properties found (likely vendor-specific)
## Common Validation Errors
### Schema Validation Errors (Exit Code 1)
These are actual schema violations that must be fixed:
**Type Errors:**
```
$.ethernet is not of type 'array'
```
**Fix**: Ensure `ethernet` is an array: `"ethernet": [...]`
**Out of Range:**
```
$.interfaces[0].vlan.id is greater than the maximum of 4094
```
**Fix**: VLAN IDs must be between 1-4094
**Required Property Missing:**
```
'uuid' is a required property
```
**Fix**: Add the required field: `"uuid": 1234567890`
**Additional Properties Not Allowed:**
```
Additional properties are not allowed ('unknown_field' was unexpected)
```
**Fix**: Remove the field or check spelling (only if schema has `additionalProperties: false`)
### Undefined Properties (Exit Code 0 by default)
These are informational warnings that don't cause validation failure:
**Typo/Misspelling:**
```
ethernet[].lldp_admin_status not in schema
Suggestion: ethernet[].lldp-interface-config.lldp-admin-status
```
**Impact**: Property won't be applied even though config is valid
**Vendor Extension:**
```
ethernet[].edgecore-specific-property not in schema
No suggestions found
```
**Impact**: Not portable, may change without notice
## CI/CD Integration
### Basic Pipeline
```yaml
validate-configs:
stage: test
script:
# Standard validation (undefined properties are warnings)
- python3 validate-schema.py config-samples/
artifacts:
when: on_failure
paths:
- validation-report.json
```
### Strict Enforcement Pipeline
```yaml
validate-configs-strict:
stage: test
script:
# Strict mode: fail on undefined properties
- python3 validate-schema.py config-samples/ --strict-schema
# Generate JSON report for analysis
- python3 validate-schema.py config-samples/ --check-undefined --format json > report.json
artifacts:
always:
paths:
- report.json
```
### Development Workflow
```bash
# Before committing: check for typos
python3 validate-schema.py my-config.json --check-undefined
# Review suggestions and fix obvious typos
# Document any intentional vendor-specific properties
# CI/CD will enforce with --strict-schema
```
## Files
- **validate-schema.py**: Standalone schema validator script (649 lines)
- **Makefile**: Build targets for schema validation
- **test-config-parser.c**: Enhanced with schema validation integration
- **SCHEMA_VALIDATOR_README.md**: This documentation
## See Also
- [TEST_CONFIG_README.md](../config-parser/TEST_CONFIG_README.md) - Configuration parser testing guide
- [ucentral.schema.pretty.json](../../config-samples/ucentral.schema.pretty.json) - Official uCentral schema
## License
BSD-3-Clause

649
tests/schema/validate-schema.py Executable file
View File

@@ -0,0 +1,649 @@
#!/usr/bin/env python3
"""
uCentral Configuration Schema Validator
A modular, standalone tool for validating JSON configuration files against
the uCentral schema. Can be used independently or integrated into test suites.
Usage:
# Validate a single file (schema validation only)
./validate-schema.py config.json
# Check for undefined properties (informational, doesn't affect exit code)
./validate-schema.py config.json --check-undefined
# Strict mode: treat undefined properties as errors (for CI/CD)
./validate-schema.py config.json --strict-schema
# Validate with specific schema
./validate-schema.py config.json --schema path/to/schema.json
# Validate directory of configs
./validate-schema.py config-dir/
# Machine-readable JSON output
./validate-schema.py config.json --format json
Exit codes:
0 = all valid (undefined properties don't affect this unless --strict-schema)
1 = validation errors OR (strict mode AND undefined properties found)
2 = file/schema errors (file not found, invalid schema, etc.)
Undefined Properties:
Properties in config but not defined in schema are INFORMATIONAL warnings,
not validation errors. They may indicate:
• Typos/misspellings (property won't be applied even though config is valid)
• Vendor-specific extensions (not portable across platforms)
• Deprecated properties (check schema version)
Use --strict-schema in CI/CD pipelines to enforce schema compliance.
Author: Generated for OLS uCentral Client
License: BSD-3-Clause
"""
import sys
import json
import argparse
import os
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Set
from collections import defaultdict
try:
import jsonschema
from jsonschema import Draft7Validator, validators
except ImportError:
print("ERROR: jsonschema module not found. Install with: pip3 install jsonschema", file=sys.stderr)
sys.exit(2)
def levenshtein_distance(s1: str, s2: str) -> int:
"""
Calculate Levenshtein distance between two strings.
Returns minimum number of single-character edits needed to change s1 into s2.
"""
if len(s1) < len(s2):
return levenshtein_distance(s2, s1)
if len(s2) == 0:
return len(s1)
previous_row = range(len(s2) + 1)
for i, c1 in enumerate(s1):
current_row = [i + 1]
for j, c2 in enumerate(s2):
# Cost of insertions, deletions, or substitutions
insertions = previous_row[j + 1] + 1
deletions = current_row[j] + 1
substitutions = previous_row[j] + (c1 != c2)
current_row.append(min(insertions, deletions, substitutions))
previous_row = current_row
return previous_row[-1]
def normalize_separator(s: str, target: str = '-') -> str:
"""Convert string separators. Default converts underscores and camelCase to dashes."""
# Handle camelCase: insert dash before uppercase letters
result = []
for i, char in enumerate(s):
if i > 0 and char.isupper() and s[i-1].islower():
result.append('-')
result.append(char.lower())
normalized = ''.join(result)
# Replace underscores with target separator
normalized = normalized.replace('_', target)
return normalized
def detect_naming_issue(config_prop: str, schema_prop: str) -> Optional[Dict[str, str]]:
"""
Detect if config_prop is a likely typo/variation of schema_prop.
Returns dict with issue type and confidence, or None if no clear match.
"""
# Extract just the property name (last component after last dot or bracket)
def get_prop_name(path: str) -> str:
# Handle array notation: ethernet[].lldp-config -> lldp-config
if '.' in path:
return path.split('.')[-1]
return path
config_name = get_prop_name(config_prop)
schema_name = get_prop_name(schema_prop)
# Check for exact match after normalization
config_normalized = normalize_separator(config_name, '-')
schema_normalized = normalize_separator(schema_name, '-')
if config_normalized == schema_normalized and config_name != schema_name:
# Separator mismatch (dash vs underscore vs camelCase)
if '_' in config_name and '-' in schema_name:
return {'type': 'separator_mismatch', 'detail': 'underscore_vs_dash', 'confidence': 'high'}
elif any(c.isupper() for c in config_name) and '-' in schema_name:
return {'type': 'separator_mismatch', 'detail': 'camelCase_vs_dash', 'confidence': 'high'}
else:
return {'type': 'separator_mismatch', 'detail': 'format_difference', 'confidence': 'high'}
# Check Levenshtein distance
distance = levenshtein_distance(config_name.lower(), schema_name.lower())
if distance <= 2:
return {'type': 'similar_spelling', 'detail': f'edit_distance_{distance}', 'confidence': 'high' if distance == 1 else 'medium'}
elif distance <= 3:
return {'type': 'similar_spelling', 'detail': f'edit_distance_{distance}', 'confidence': 'medium'}
return None
class SchemaValidator:
"""
Modular schema validator for uCentral configurations.
This class is designed to be easily portable across repositories.
It has no dependencies on specific file paths or repository structure.
"""
def __init__(self, schema_path: Optional[str] = None, check_undefined: bool = False,
similarity_threshold: int = 3):
"""
Initialize validator with schema.
Args:
schema_path: Path to JSON schema file. If None, attempts to find
schema in common locations relative to this script.
check_undefined: If True, check for properties in config not defined in schema
similarity_threshold: Maximum Levenshtein distance for suggesting similar properties
"""
self.schema_path = schema_path
self.schema = None
self.validator = None
self.check_undefined = check_undefined
self.similarity_threshold = similarity_threshold
self._schema_properties = None # Cache of all valid schema property paths
self._load_schema()
def _find_default_schema(self) -> Optional[str]:
"""Find schema in common locations relative to script."""
script_dir = Path(__file__).parent
# Search paths (relative to script location)
search_paths = [
script_dir / "../../config-samples/ucentral.schema.pretty.json",
script_dir / "../../config-samples/ucentral.schema.json",
script_dir / "../../../config-samples/ucentral.schema.pretty.json",
script_dir / "ucentral.schema.json",
]
for path in search_paths:
if path.exists():
return str(path.resolve())
return None
def _load_schema(self):
"""Load and parse the JSON schema."""
if self.schema_path is None:
self.schema_path = self._find_default_schema()
if self.schema_path is None:
raise FileNotFoundError(
"Could not find schema file. Please specify --schema path"
)
try:
with open(self.schema_path, 'r') as f:
self.schema = json.load(f)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in schema file {self.schema_path}: {e}")
except FileNotFoundError:
raise FileNotFoundError(f"Schema file not found: {self.schema_path}")
# Create validator
self.validator = Draft7Validator(self.schema)
def _extract_schema_properties(self, schema: Dict = None, path: str = "", visited: Set[str] = None) -> Set[str]:
"""
Recursively extract all valid property paths from the schema.
Args:
schema: Schema object (or sub-schema) to process
path: Current path prefix
visited: Set of visited $ref paths to prevent infinite recursion
Returns:
Set of all valid property paths in the schema
"""
if schema is None:
schema = self.schema
if visited is None:
visited = set()
properties = set()
# Handle $ref references
if '$ref' in schema:
ref_path = schema['$ref']
# Prevent infinite recursion
if ref_path in visited:
return properties
visited.add(ref_path)
# Resolve $ref (handle #/$defs/name references)
if ref_path.startswith('#/'):
ref_parts = ref_path[2:].split('/')
ref_schema = self.schema
for part in ref_parts:
ref_schema = ref_schema.get(part, {})
# Recursively extract from referenced schema
return self._extract_schema_properties(ref_schema, path, visited)
# Handle object properties
if 'properties' in schema:
for prop_name, prop_schema in schema['properties'].items():
prop_path = f"{path}.{prop_name}" if path else prop_name
properties.add(prop_path)
# Recursively process nested properties
nested = self._extract_schema_properties(prop_schema, prop_path, visited.copy())
properties.update(nested)
# Handle arrays with items schema
if 'items' in schema:
items_schema = schema['items']
# Use [] notation for arrays
array_path = f"{path}[]" if path else "[]"
# Extract properties from array items
nested = self._extract_schema_properties(items_schema, array_path, visited.copy())
properties.update(nested)
# Handle additional properties (if true, allows any property)
# We don't add these to valid properties as they're wildcards
return properties
def _extract_config_properties(self, config: Dict, path: str = "") -> Set[str]:
"""
Recursively extract all property paths from a configuration object.
Args:
config: Configuration object to analyze
path: Current path prefix
Returns:
Set of all property paths in the configuration
"""
properties = set()
if isinstance(config, dict):
for key, value in config.items():
prop_path = f"{path}.{key}" if path else key
properties.add(prop_path)
# Recursively process nested values
nested = self._extract_config_properties(value, prop_path)
properties.update(nested)
elif isinstance(config, list):
# For arrays, use [] notation and process all items
array_path = f"{path}[]" if path else "[]"
for item in config:
nested = self._extract_config_properties(item, array_path)
properties.update(nested)
return properties
def _find_similar_properties(self, config_prop: str, schema_props: Set[str]) -> List[Dict]:
"""
Find schema properties similar to a config property.
Args:
config_prop: Property path from configuration
schema_props: Set of all valid schema property paths
Returns:
List of suggestions with similarity information
"""
suggestions = []
for schema_prop in schema_props:
issue = detect_naming_issue(config_prop, schema_prop)
if issue:
suggestions.append({
'schema_property': schema_prop,
'issue_type': issue['type'],
'detail': issue['detail'],
'confidence': issue['confidence']
})
# Sort by confidence (high first) and then alphabetically
confidence_order = {'high': 0, 'medium': 1, 'low': 2}
suggestions.sort(key=lambda x: (confidence_order.get(x['confidence'], 3), x['schema_property']))
return suggestions
def _check_undefined_properties(self, config: Dict) -> Dict[str, any]:
"""
Check for properties in config that are not defined in schema.
Args:
config: Configuration object to check
Returns:
Dict with undefined property analysis results
"""
# Extract all valid schema properties (with caching)
if self._schema_properties is None:
self._schema_properties = self._extract_schema_properties()
# Extract all config properties
config_props = self._extract_config_properties(config)
# Find undefined properties
undefined = []
for config_prop in sorted(config_props):
# Check if property or its array form exists in schema
is_defined = False
# Direct match
if config_prop in self._schema_properties:
is_defined = True
else:
# Check with array index normalization: ethernet[0].speed -> ethernet[].speed
normalized_prop = config_prop
import re
# Replace array indices with []
normalized_prop = re.sub(r'\[\d+\]', '[]', normalized_prop)
if normalized_prop in self._schema_properties:
is_defined = True
if not is_defined:
# Find similar properties
suggestions = self._find_similar_properties(config_prop, self._schema_properties)
undefined.append({
'path': config_prop,
'suggestions': suggestions[:3] # Top 3 suggestions
})
return {
'total_config_properties': len(config_props),
'total_schema_properties': len(self._schema_properties),
'undefined_count': len(undefined),
'undefined_properties': undefined
}
def validate_file(self, config_path: str) -> Tuple[bool, List[Dict], Optional[Dict]]:
"""Validate a single configuration file against the schema."""
try:
with open(config_path, 'r') as f:
config = json.load(f)
except json.JSONDecodeError as e:
return False, [{
'path': '$',
'message': f'Invalid JSON: {e}',
'validator': 'json_parse'
}], None
except FileNotFoundError:
return False, [{
'path': '$',
'message': f'File not found: {config_path}',
'validator': 'file_access'
}], None
return self.validate_config(config)
def validate_config(self, config: Dict) -> Tuple[bool, List[Dict], Optional[Dict]]:
"""
Validate a configuration object against the schema.
Returns:
Tuple of (is_valid, errors, undefined_analysis)
- is_valid: True if no schema validation errors
- errors: List of schema validation errors
- undefined_analysis: Dict with undefined property analysis (if check_undefined=True)
"""
errors = []
for error in sorted(self.validator.iter_errors(config), key=str):
# Build JSON path
path = '$.' + '.'.join(str(p) for p in error.absolute_path) if error.absolute_path else '$'
errors.append({
'path': path,
'message': error.message,
'validator': error.validator,
'schema_path': '.'.join(str(p) for p in error.absolute_schema_path) if error.absolute_schema_path else '$'
})
# Check for undefined properties if enabled
undefined_analysis = None
if self.check_undefined:
undefined_analysis = self._check_undefined_properties(config)
return len(errors) == 0, errors, undefined_analysis
def validate_directory(self, dir_path: str, pattern: str = "*.json") -> Dict[str, Tuple[bool, List[Dict], Optional[Dict]]]:
"""Validate all JSON files in a directory."""
results = {}
dir_path_obj = Path(dir_path)
if not dir_path_obj.is_dir():
raise NotADirectoryError(f"Not a directory: {dir_path}")
# Find all matching files
for file_path in sorted(dir_path_obj.glob(pattern)):
# Skip schema files
if 'schema' in file_path.name.lower():
continue
results[file_path.name] = self.validate_file(str(file_path))
return results
def format_human_output(filename: str, is_valid: bool, errors: List[Dict],
undefined_analysis: Optional[Dict] = None) -> str:
"""Format validation results in human-readable format."""
output = []
# Schema validation results
if is_valid:
output.append(f"✓ Schema Valid: {filename}")
else:
output.append(f"✗ Schema Invalid: {filename}")
output.append(f" Found {len(errors)} validation error(s):")
for i, error in enumerate(errors, 1):
output.append(f"\n Error {i}:")
output.append(f" Path: {error['path']}")
output.append(f" Message: {error['message']}")
if error.get('validator'):
output.append(f" Validator: {error['validator']}")
# Undefined properties analysis (informational warnings, not errors)
if undefined_analysis and undefined_analysis['undefined_count'] > 0:
output.append(f"\n Undefined Properties (informational):")
output.append(f" Found {undefined_analysis['undefined_count']} property/properties not in schema")
output.append(f" These may be:")
output.append(f" • Typos/misspellings (won't be applied even though config is valid)")
output.append(f" • Vendor-specific extensions (not portable, may change)")
output.append(f" • Deprecated properties (check schema version)\n")
for i, item in enumerate(undefined_analysis['undefined_properties'], 1):
output.append(f" {i}. {item['path']}")
output.append(f" → Not defined in schema")
if item['suggestions']:
output.append(f" → Possible matches:")
for suggestion in item['suggestions']:
confidence_icon = "" if suggestion['confidence'] == 'high' else "?"
detail_msg = ""
if suggestion['issue_type'] == 'separator_mismatch':
if 'underscore_vs_dash' in suggestion['detail']:
detail_msg = " (use '-' not '_')"
elif 'camelCase_vs_dash' in suggestion['detail']:
detail_msg = " (use dash-case not camelCase)"
elif suggestion['issue_type'] == 'similar_spelling':
detail_msg = f" (similar spelling)"
output.append(f" {confidence_icon} {suggestion['schema_property']}{detail_msg}")
output.append("") # Blank line between items
elif undefined_analysis and undefined_analysis['undefined_count'] == 0:
output.append(f"✓ All properties defined in schema")
return '\n'.join(output)
def format_json_output(results: Dict[str, Tuple[bool, List[Dict], Optional[Dict]]]) -> str:
"""Format validation results as JSON."""
output = {
'summary': {
'total': len(results),
'valid': sum(1 for is_valid, _, _ in results.values() if is_valid),
'invalid': sum(1 for is_valid, _, _ in results.values() if not is_valid),
'with_undefined_properties': sum(1 for _, _, undefined in results.values()
if undefined and undefined['undefined_count'] > 0)
},
'results': {}
}
for filename, (is_valid, errors, undefined_analysis) in results.items():
result_data = {
'schema_valid': is_valid,
'errors': errors
}
if undefined_analysis:
result_data['schema_compliance'] = {
'total_config_properties': undefined_analysis['total_config_properties'],
'undefined_count': undefined_analysis['undefined_count'],
'undefined_properties': undefined_analysis['undefined_properties']
}
output['results'][filename] = result_data
return json.dumps(output, indent=2)
def main():
"""Main entry point for standalone usage."""
parser = argparse.ArgumentParser(
description='Validate uCentral JSON configurations against schema',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s config.json
%(prog)s config.json --schema my-schema.json
%(prog)s config-samples/
%(prog)s config-samples/ --format json > report.json
"""
)
parser.add_argument('path',
help='Configuration file or directory to validate')
parser.add_argument('--schema', '-s',
help='Path to JSON schema file (auto-detected if not specified)')
parser.add_argument('--format', '-f',
choices=['human', 'json'],
default='human',
help='Output format (default: human)')
parser.add_argument('--pattern', '-p',
default='*.json',
help='File pattern for directory validation (default: *.json)')
parser.add_argument('--check-undefined', '-u',
action='store_true',
help='Check for properties not defined in schema (informational, does not affect exit code)')
parser.add_argument('--strict-schema',
action='store_true',
help='Treat undefined properties as errors (exit code 1). Use for CI/CD enforcement. (implies --check-undefined)')
parser.add_argument('--similarity-threshold', '-t',
type=int,
default=3,
help='Maximum edit distance for suggesting similar properties (default: 3)')
args = parser.parse_args()
# --strict-schema implies --check-undefined
check_undefined = args.check_undefined or args.strict_schema
# Initialize validator
try:
validator = SchemaValidator(args.schema, check_undefined=check_undefined,
similarity_threshold=args.similarity_threshold)
except (FileNotFoundError, ValueError) as e:
print(f"ERROR: {e}", file=sys.stderr)
return 2
# Determine if path is file or directory
path_obj = Path(args.path)
if not path_obj.exists():
print(f"ERROR: Path not found: {args.path}", file=sys.stderr)
return 2
# Validate
results = {}
if path_obj.is_file():
is_valid, errors, undefined_analysis = validator.validate_file(args.path)
results[path_obj.name] = (is_valid, errors, undefined_analysis)
elif path_obj.is_dir():
try:
results = validator.validate_directory(args.path, args.pattern)
except NotADirectoryError as e:
print(f"ERROR: {e}", file=sys.stderr)
return 2
else:
print(f"ERROR: Path is neither file nor directory: {args.path}", file=sys.stderr)
return 2
# Format and output results
if args.format == 'json':
print(format_json_output(results))
else:
for filename, (is_valid, errors, undefined_analysis) in results.items():
print(format_human_output(filename, is_valid, errors, undefined_analysis))
print() # Blank line between files
# Summary for multiple files
if len(results) > 1:
valid_count = sum(1 for is_valid, _, _ in results.values() if is_valid)
invalid_count = len(results) - valid_count
undefined_count = sum(1 for _, _, undefined in results.values()
if undefined and undefined['undefined_count'] > 0)
print(f"Summary: {len(results)} file(s) checked, {valid_count} valid, {invalid_count} invalid")
if check_undefined:
print(f" {undefined_count} file(s) with undefined properties")
# Exit code logic
# Only schema validation errors cause failure by default
all_valid = all(is_valid for is_valid, _, _ in results.values())
# In strict mode, undefined properties also cause failure
has_undefined = False
if args.strict_schema:
has_undefined = any(undefined and undefined['undefined_count'] > 0
for _, _, undefined in results.values())
# Exit codes:
# 0 = all valid (undefined properties don't affect this unless --strict-schema)
# 1 = validation errors OR (strict mode AND undefined properties found)
if not all_valid:
return 1
elif args.strict_schema and has_undefined:
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

Some files were not shown because too many files have changed in this diff Show More