mirror of
https://github.com/Telecominfraproject/ols-ucentral-client.git
synced 2026-03-20 03:39:28 +00:00
Compare commits
19 Commits
OLS-915-co
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e0d7771f0 | ||
|
|
2d0f260f5d | ||
|
|
59ef4a8db5 | ||
|
|
d8af348fae | ||
|
|
2c9045c777 | ||
|
|
e36ddea61e | ||
|
|
9c91b06be3 | ||
|
|
dc2b9cd8ab | ||
|
|
00ca1cd1f4 | ||
|
|
ff4b2095b5 | ||
|
|
0cb84a23a0 | ||
|
|
a4a153197b | ||
|
|
cd135692b3 | ||
|
|
cef160a0fa | ||
|
|
04edeb90e4 | ||
|
|
b00c502016 | ||
|
|
3963845143 | ||
|
|
6c4c918c3b | ||
|
|
f90edc4c41 |
23
.gitignore
vendored
23
.gitignore
vendored
@@ -5,9 +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/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
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -2,11 +2,11 @@ FROM debian:bullseye
|
||||
LABEL Description="Ucentral client (Build) environment"
|
||||
|
||||
ARG HOME /root
|
||||
ARG SCHEMA="4.1.0-rc1"
|
||||
ARG SCHEMA_VERSION="v${SCHEMA}"
|
||||
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/tags/${SCHEMA_ZIP_FILE}"
|
||||
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 \
|
||||
@@ -77,3 +77,8 @@ 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
|
||||
|
||||
544
PREREQUISITES.md
Normal file
544
PREREQUISITES.md
Normal 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
|
||||
@@ -3,14 +3,14 @@
|
||||
## TL;DR
|
||||
|
||||
```bash
|
||||
# Test all configs with human-readable output
|
||||
# Test all configs with human-readable output (default)
|
||||
./run-config-tests.sh
|
||||
|
||||
# Generate HTML report
|
||||
./run-config-tests.sh html
|
||||
./run-config-tests.sh --format html
|
||||
|
||||
# Test single config
|
||||
./run-config-tests.sh human ECS4150-TM.json
|
||||
# Test single config with HTML output
|
||||
./run-config-tests.sh --format html cfg0.json
|
||||
|
||||
# Results are in: output/
|
||||
```
|
||||
@@ -20,17 +20,27 @@
|
||||
### Test All Configurations
|
||||
|
||||
```bash
|
||||
./run-config-tests.sh human # Console output with colors
|
||||
./run-config-tests.sh html # Interactive HTML report
|
||||
./run-config-tests.sh json # Machine-readable JSON
|
||||
# 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
|
||||
./run-config-tests.sh human cfg0.json
|
||||
./run-config-tests.sh html ECS4150-ACL.json
|
||||
./run-config-tests.sh json ECS4150-TM.json
|
||||
# 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
|
||||
@@ -106,7 +116,7 @@ ls config-samples/*.json
|
||||
### Example Pipeline
|
||||
```yaml
|
||||
- name: Run tests
|
||||
run: ./run-config-tests.sh json
|
||||
run: ./run-config-tests.sh --format json
|
||||
- name: Check results
|
||||
run: |
|
||||
if [ $? -eq 0 ]; then
|
||||
@@ -141,14 +151,34 @@ ECS4150_VLAN.json # VLAN configuration
|
||||
✅ 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 | `./run-config-tests.sh` |
|
||||
| HTML report | `./run-config-tests.sh html` |
|
||||
| JSON output | `./run-config-tests.sh json` |
|
||||
| Single config | `./run-config-tests.sh human cfg0.json` |
|
||||
| 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` |
|
||||
|
||||
157
README.md
157
README.md
@@ -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,36 +126,105 @@ 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
|
||||
|
||||
@@ -137,7 +238,7 @@ The repository includes a comprehensive testing framework for configuration vali
|
||||
./run-config-tests.sh
|
||||
|
||||
# Generate HTML report
|
||||
./run-config-tests.sh html
|
||||
./run-config-tests.sh --format html
|
||||
|
||||
# Or run tests directly in the tests directory
|
||||
cd tests/config-parser
|
||||
|
||||
@@ -42,7 +42,7 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
|
||||
4. **[TEST_CONFIG_PARSER_DESIGN.md](TEST_CONFIG_PARSER_DESIGN.md)** - Test framework architecture
|
||||
- Multi-layer validation design
|
||||
- Property metadata system (628 properties)
|
||||
- Property metadata system (398 schema properties)
|
||||
- Property inspection engine
|
||||
- Test execution flow diagrams
|
||||
- Data structures and algorithms
|
||||
@@ -64,17 +64,18 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
**RECOMMENDED: Use the test runner script** (handles Docker automatically):
|
||||
|
||||
```bash
|
||||
# Test all configurations (human-readable output)
|
||||
./run-config-tests.sh
|
||||
# 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
|
||||
|
||||
# Generate HTML report
|
||||
./run-config-tests.sh html
|
||||
|
||||
# Generate JSON report
|
||||
./run-config-tests.sh json
|
||||
# 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 human cfg0.json
|
||||
./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):
|
||||
@@ -83,21 +84,29 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
# Build the Docker environment first (if not already built)
|
||||
make build-host-env
|
||||
|
||||
# Run all tests (schema + parser) - RECOMMENDED
|
||||
# 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 individual test suites
|
||||
# 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
|
||||
# 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/
|
||||
```
|
||||
@@ -108,17 +117,24 @@ docker cp ucentral_client_build_env:/root/ols-nos/tests/config-parser/test-repor
|
||||
# Navigate to test directory
|
||||
cd tests/config-parser
|
||||
|
||||
# Run all tests (schema + 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
|
||||
# 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).
|
||||
@@ -126,14 +142,21 @@ make test-config-junit # JUnit XML (CI/CD integration)
|
||||
### Key Files
|
||||
|
||||
**Test Implementation:**
|
||||
- `tests/config-parser/test-config-parser.c` - Parser test framework with property tracking (628 properties)
|
||||
- `tests/config-parser/test-stubs.c` - Platform function stubs for testing
|
||||
- `tests/schema/validate-schema.py` - Standalone schema validator
|
||||
- `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/ols.ucentral.schema.pretty.json` - uCentral JSON schema (human-readable)
|
||||
- `config-samples/*.json` - Test configuration files (25+ configs)
|
||||
- `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:**
|
||||
@@ -164,12 +187,12 @@ make test-config-junit # JUnit XML (CI/CD integration)
|
||||
- Hardware constraint validation
|
||||
|
||||
### Property Tracking System
|
||||
- Database of 628 properties and their processing status
|
||||
- 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
|
||||
- 199 properties (32%) with line number references
|
||||
- Properties with line numbers are implemented, line_number=0 means not yet implemented
|
||||
|
||||
### Two-Layer Validation Strategy
|
||||
|
||||
@@ -186,12 +209,12 @@ See TEST_CONFIG_README.md section "Two-Layer Validation Strategy" for detailed e
|
||||
## Test Coverage
|
||||
|
||||
Current test suite includes:
|
||||
- 25+ configuration files covering various features
|
||||
- 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)
|
||||
- All tests currently passing (25/25)
|
||||
|
||||
### Tested Features
|
||||
- Port configuration (enable/disable, speed, duplex)
|
||||
@@ -220,17 +243,30 @@ These features pass schema validation but show as "Unknown" in property reports,
|
||||
The testing framework was added with minimal impact to production code:
|
||||
|
||||
### New Files Added
|
||||
1. `tests/config-parser/test-config-parser.c` - Complete test framework with 628-property database
|
||||
2. `tests/config-parser/test-stubs.c` - Platform stubs
|
||||
3. `tests/schema/validate-schema.py` - Schema validator
|
||||
4. `tests/config-parser/config-parser.h` - Test header
|
||||
5. `tests/config-parser/TEST_CONFIG_README.md` - Framework documentation
|
||||
6. `tests/schema/SCHEMA_VALIDATOR_README.md` - Validator documentation
|
||||
7. `tests/MAINTENANCE.md` - Maintenance procedures
|
||||
8. `tests/config-parser/Makefile` - Test build system
|
||||
9. `TESTING_FRAMEWORK.md` - This file (documentation index)
|
||||
10. `TEST_CONFIG_PARSER_DESIGN.md` - Test framework architecture and design
|
||||
11. `run-config-tests.sh` - Test runner script
|
||||
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)
|
||||
@@ -252,8 +288,9 @@ The testing framework was added with minimal impact to production code:
|
||||
2. `src/ucentral-client/include/router-utils.h` - Added extern declarations
|
||||
- Exposed necessary functions for test stubs
|
||||
|
||||
3. `src/ucentral-client/Makefile` - Removed test targets (moved to tests/config-parser/Makefile)
|
||||
- Production Makefile now focuses only on deliverable code
|
||||
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
|
||||
@@ -296,7 +333,7 @@ vi config-samples/test-new-feature.json
|
||||
./run-config-tests.sh
|
||||
|
||||
# Generate full HTML report for review
|
||||
./run-config-tests.sh html
|
||||
./run-config-tests.sh --format html
|
||||
open output/test-report.html
|
||||
|
||||
# Check for property database accuracy
|
||||
@@ -309,7 +346,7 @@ open output/test-report.html
|
||||
test-configurations:
|
||||
stage: test
|
||||
script:
|
||||
- ./run-config-tests.sh json
|
||||
- ./run-config-tests.sh --format json
|
||||
artifacts:
|
||||
paths:
|
||||
- output/test-report.json
|
||||
@@ -332,7 +369,7 @@ static struct property_metadata properties[] = {
|
||||
.source_line = 1119,
|
||||
.notes = "Enable/disable ethernet interface"
|
||||
},
|
||||
// ... 628 total entries (199 with line numbers) ...
|
||||
// ... entries for all 398 schema properties (with line numbers for implemented properties) ...
|
||||
};
|
||||
```
|
||||
|
||||
@@ -350,8 +387,7 @@ See MAINTENANCE.md for complete property database update procedures.
|
||||
The schema file defines what configurations are structurally valid.
|
||||
|
||||
### Schema Location
|
||||
- `config-samples/ucentral.schema.pretty.json` - Human-readable version (recommended)
|
||||
- `config-samples/ols.ucentral.schema.json` - Compact version
|
||||
- `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.
|
||||
|
||||
@@ -18,9 +18,9 @@ The framework validates configurations through three complementary layers:
|
||||
|
||||
### Layer 3: Property Tracking
|
||||
- Deep recursive inspection of JSON tree to classify every property
|
||||
- Maps properties to property metadata database (628 properties total)
|
||||
- Maps properties to property metadata database (398 schema properties)
|
||||
- Tracks which properties are CONFIGURED, IGNORED, INVALID, UNKNOWN, etc.
|
||||
- 199 properties (32%) include line number references to proto.c
|
||||
- Properties with line numbers are implemented in proto.c; line_number=0 means not yet implemented
|
||||
|
||||
## 2. **Property Metadata System**
|
||||
|
||||
@@ -36,12 +36,12 @@ struct property_metadata {
|
||||
};
|
||||
```
|
||||
|
||||
**Database contains 628 entries** documenting:
|
||||
- Which properties are actively parsed (PROP_CONFIGURED)
|
||||
**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)
|
||||
- Line numbers for 199 properties (32%) showing exact parsing locations
|
||||
|
||||
### Property Status Classification
|
||||
- **PROP_CONFIGURED**: Successfully processed by parser
|
||||
@@ -293,7 +293,7 @@ The design elegantly separates concerns:
|
||||
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 560+ configuration properties, enabling automated detection of unimplemented features and validation of parser coverage.
|
||||
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
|
||||
|
||||
|
||||
@@ -32,11 +32,16 @@
|
||||
### Basic Syntax
|
||||
|
||||
```bash
|
||||
./run-config-tests.sh [format] [config-file]
|
||||
./run-config-tests.sh [OPTIONS] [config-file]
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `format` (optional): Output format - `html`, `json`, or `human` (default: `human`)
|
||||
**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
|
||||
@@ -47,27 +52,32 @@
|
||||
# Human-readable output (default)
|
||||
./run-config-tests.sh
|
||||
|
||||
# Human-readable output (explicit)
|
||||
./run-config-tests.sh human
|
||||
|
||||
# HTML report
|
||||
./run-config-tests.sh html
|
||||
./run-config-tests.sh --format html
|
||||
# OR short form:
|
||||
./run-config-tests.sh -f html
|
||||
|
||||
# JSON output
|
||||
./run-config-tests.sh json
|
||||
./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
|
||||
./run-config-tests.sh human cfg0.json
|
||||
# Test single config with human output (default)
|
||||
./run-config-tests.sh cfg0.json
|
||||
|
||||
# Test single config with HTML report
|
||||
./run-config-tests.sh html ECS4150-TM.json
|
||||
./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 json ECS4150-ACL.json
|
||||
./run-config-tests.sh --format json cfg0.json
|
||||
# OR short form:
|
||||
./run-config-tests.sh -f json cfg0.json
|
||||
```
|
||||
|
||||
## Output Files
|
||||
@@ -210,7 +220,7 @@ The script uses exit codes for CI/CD integration:
|
||||
|
||||
**CI/CD Example:**
|
||||
```bash
|
||||
./run-config-tests.sh json
|
||||
./run-config-tests.sh --format json
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "All tests passed!"
|
||||
else
|
||||
@@ -321,7 +331,7 @@ make build-host-env
|
||||
# OR let script build it automatically
|
||||
|
||||
# Run tests (script provides better output management)
|
||||
./run-config-tests.sh html
|
||||
./run-config-tests.sh --format html
|
||||
```
|
||||
|
||||
### With CI/CD
|
||||
@@ -338,7 +348,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run config tests
|
||||
run: ./run-config-tests.sh json
|
||||
run: ./run-config-tests.sh --format json
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -352,7 +362,7 @@ jobs:
|
||||
test-configs:
|
||||
stage: test
|
||||
script:
|
||||
- ./run-config-tests.sh json
|
||||
- ./run-config-tests.sh --format json
|
||||
artifacts:
|
||||
paths:
|
||||
- output/test-report.json
|
||||
@@ -364,7 +374,7 @@ test-configs:
|
||||
```groovy
|
||||
stage('Test Configurations') {
|
||||
steps {
|
||||
sh './run-config-tests.sh html'
|
||||
sh './run-config-tests.sh --format html'
|
||||
publishHTML([
|
||||
reportDir: 'output',
|
||||
reportFiles: 'test-report.html',
|
||||
@@ -382,7 +392,7 @@ stage('Test Configurations') {
|
||||
# .git/hooks/pre-commit
|
||||
|
||||
echo "Running configuration tests..."
|
||||
./run-config-tests.sh human
|
||||
./run-config-tests.sh
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Tests failed. Commit aborted."
|
||||
@@ -406,7 +416,7 @@ OUTPUT_DIR="$SCRIPT_DIR/my-custom-output"
|
||||
```bash
|
||||
# Test all ACL configs
|
||||
for config in config-samples/*ACL*.json; do
|
||||
./run-config-tests.sh json "$(basename $config)"
|
||||
./run-config-tests.sh --format json "$(basename $config)"
|
||||
done
|
||||
```
|
||||
|
||||
@@ -414,8 +424,8 @@ done
|
||||
|
||||
```bash
|
||||
# Start multiple containers for parallel testing
|
||||
docker exec ucentral_client_build_env_1 bash -c "cd /root/ols-nos/src/ucentral-client && ./test-config-parser config1.json" &
|
||||
docker exec ucentral_client_build_env_2 bash -c "cd /root/ols-nos/src/ucentral-client && ./test-config-parser config2.json" &
|
||||
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
|
||||
```
|
||||
|
||||
@@ -424,7 +434,7 @@ wait
|
||||
```bash
|
||||
# Generate all format reports
|
||||
for format in human html json; do
|
||||
./run-config-tests.sh $format
|
||||
./run-config-tests.sh --format $format
|
||||
done
|
||||
|
||||
# Timestamp reports
|
||||
@@ -466,4 +476,4 @@ For issues or questions:
|
||||
|
||||
Script version: 1.0.0
|
||||
Last updated: 2025-12-15
|
||||
Compatible with: uCentral schema 4.1.0-rc1 and later
|
||||
Compatible with: uCentral schema 5.0.0 and later
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -253,6 +253,11 @@
|
||||
"cloud_uplink_port"
|
||||
]
|
||||
},
|
||||
"autoneg": {
|
||||
"description": "Controls whether link autonegotiation is enabled. When set to true, the switch negotiates speed and duplex with the link partner. When set to false, the configured speed and duplex values are forced.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"speed": {
|
||||
"description": "The link speed that shall be forced.",
|
||||
"type": "integer",
|
||||
@@ -831,6 +836,143 @@
|
||||
"description": "Maximum allowed unknown unicast packets per second. 0 disables unknown unicast storm control."
|
||||
}
|
||||
}
|
||||
},
|
||||
"qos-priority-mapping": {
|
||||
"type": "object",
|
||||
"description": "Interface-level QoS priority mapping configuration. Defines how ingress packet markings (IP Precedence, CoS, DSCP) are interpreted and mapped to internal per-hop behavior (PHB), drop precedence, and egress queue selection.",
|
||||
"properties": {
|
||||
"priority-untagged": {
|
||||
"type": "integer",
|
||||
"description": "Sets the default priority for incoming untagged Ethernet frames.",
|
||||
"minimum": 0,
|
||||
"maximum": 7
|
||||
},
|
||||
"qos-map-trust-mode": {
|
||||
"type": "string",
|
||||
"description": "Determines which packet header field is trusted for ingress classification.",
|
||||
"enum": [
|
||||
"cos",
|
||||
"dscp",
|
||||
"ip-precedence"
|
||||
]
|
||||
},
|
||||
"qos-map-ipprec2dscp": {
|
||||
"type": "array",
|
||||
"description": "Maps IP Precedence values (0\u20137) in ingress packets to per-hop behavior (PHB) and drop precedence. Used when trust mode is set to ip-precedence.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"preced": {
|
||||
"type": "integer",
|
||||
"description": "IP Precedence value in the incoming packet.",
|
||||
"minimum": 0,
|
||||
"maximum": 7
|
||||
},
|
||||
"phb": {
|
||||
"type": "integer",
|
||||
"description": "Per-hop behavior (PHB) assigned to this IP Precedence.",
|
||||
"minimum": 0,
|
||||
"maximum": 63
|
||||
},
|
||||
"drop-preced": {
|
||||
"type": "string",
|
||||
"description": "Drop precedence assigned for congestion control.",
|
||||
"enum": [
|
||||
"green",
|
||||
"yellow",
|
||||
"red"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"qos-map-cos2dscp": {
|
||||
"type": "array",
|
||||
"description": "Maps CoS/CFI values in ingress packets to PHB and drop precedence. Applied when trust mode is set to cos.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cos": {
|
||||
"type": "integer",
|
||||
"description": "CoS value extracted from ingress frames.",
|
||||
"minimum": 0,
|
||||
"maximum": 7
|
||||
},
|
||||
"cfi": {
|
||||
"type": "integer",
|
||||
"description": "Canonical Format Indicator (0 or 1).",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"phb": {
|
||||
"type": "integer",
|
||||
"description": "PHB assigned to this CoS/CFI mapping.",
|
||||
"minimum": 0,
|
||||
"maximum": 63
|
||||
},
|
||||
"drop-preced": {
|
||||
"type": "string",
|
||||
"description": "Drop precedence for congestion handling.",
|
||||
"enum": [
|
||||
"green",
|
||||
"yellow",
|
||||
"red"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"qos-map-dscpmutate": {
|
||||
"type": "array",
|
||||
"description": "Maps DSCP values (0\u201363) in ingress packets to custom PHB and drop precedence. Used when trust mode is set to dscp.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dscp": {
|
||||
"type": "integer",
|
||||
"description": "DSCP value from the IP header.",
|
||||
"minimum": 0,
|
||||
"maximum": 63
|
||||
},
|
||||
"phb": {
|
||||
"type": "integer",
|
||||
"description": "PHB derived from DSCP-to-internal-priority mapping.",
|
||||
"minimum": 0,
|
||||
"maximum": 63
|
||||
},
|
||||
"drop-preced": {
|
||||
"type": "string",
|
||||
"description": "Drop precedence classification for congestion.",
|
||||
"enum": [
|
||||
"green",
|
||||
"yellow",
|
||||
"red"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"qos-map-phb2queue": {
|
||||
"type": "array",
|
||||
"description": "Maps PHB to output hardware queues for traffic scheduling.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"phb": {
|
||||
"type": "integer",
|
||||
"description": "Per-hop behavior value (internal priority).",
|
||||
"minimum": 0,
|
||||
"maximum": 63
|
||||
},
|
||||
"queue-id": {
|
||||
"type": "integer",
|
||||
"description": "Hardware queue ID chosen for this PHB.",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1940,6 +2082,12 @@
|
||||
"default": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"trunk-id": {
|
||||
"description": "Specifies the trunk group ID used as the mclag-group.",
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 64
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2023,6 +2171,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"qos-queue-config": {
|
||||
"type": "object",
|
||||
"description": "Global configuration for QoS priority queue scheduling and processing on L2 switches.",
|
||||
"properties": {
|
||||
"queue-scheduler-mode": {
|
||||
"type": "string",
|
||||
"description": "Sets the scheduling mode used for processing each of the Class of Service (CoS) priority queues.",
|
||||
"enum": [
|
||||
"strict",
|
||||
"wrr",
|
||||
"strict-wrr",
|
||||
"wfq",
|
||||
"dwrr"
|
||||
]
|
||||
},
|
||||
"queue-config": {
|
||||
"type": "array",
|
||||
"description": "List of priority queue configurations applied at the global QoS layer.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queue-id": {
|
||||
"type": "integer",
|
||||
"description": "Identifier of the priority queue under configuration.",
|
||||
"minimum": 0
|
||||
},
|
||||
"queue-weight": {
|
||||
"type": "integer",
|
||||
"description": "Assigns weights to the CoS priority queues when using WRR or hybrid scheduling modes.",
|
||||
"minimum": 1
|
||||
},
|
||||
"queue-strict-mode": {
|
||||
"type": "boolean",
|
||||
"description": "Ensures that the highest priority packets are always serviced first, ahead of all other traffic.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"arp-inspect": {
|
||||
"$ref": "#/$defs/switch.arp-inspect"
|
||||
},
|
||||
|
||||
355
examples/pki-2.0/README.md
Normal file
355
examples/pki-2.0/README.md
Normal 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
|
||||
171
examples/pki-2.0/test-est-enrollment.sh
Executable file
171
examples/pki-2.0/test-est-enrollment.sh
Executable 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"
|
||||
195
examples/pki-2.0/test-est-reenrollment.sh
Executable file
195
examples/pki-2.0/test-est-reenrollment.sh
Executable 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"
|
||||
181
examples/pki-2.0/test-get-cacerts.sh
Executable file
181
examples/pki-2.0/test-get-cacerts.sh
Executable 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"
|
||||
@@ -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()
|
||||
|
||||
@@ -2,16 +2,41 @@
|
||||
#
|
||||
# run-config-tests.sh - Run uCentral configuration tests in Docker
|
||||
#
|
||||
# Usage: ./run-config-tests.sh [format] [config-file]
|
||||
# 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:
|
||||
# format - Output format: html, json, human (default: human)
|
||||
# config-file - Optional specific config file to test (default: all configs)
|
||||
# config-file Optional specific config file to test (default: all configs)
|
||||
#
|
||||
# Examples:
|
||||
# ./run-config-tests.sh human # Test all configs, human output
|
||||
# ./run-config-tests.sh html # Test all configs, HTML report
|
||||
# ./run-config-tests.sh json cfg0.json # Test single config, JSON output
|
||||
# ./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
|
||||
@@ -31,9 +56,59 @@ 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
|
||||
FORMAT="${1:-human}"
|
||||
SINGLE_CONFIG="${2:-}"
|
||||
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
|
||||
@@ -41,7 +116,7 @@ case "$FORMAT" in
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Error: Invalid format '$FORMAT'. Must be 'html', 'json', or 'human'${NC}"
|
||||
echo "Usage: $0 [html|json|human] [config-file]"
|
||||
echo "Use --help to see usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -141,8 +216,18 @@ start_container() {
|
||||
# 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"
|
||||
@@ -155,20 +240,23 @@ run_tests() {
|
||||
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="cd $BUILD_DIR && make test-config-parser && LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --html $CONFIG_DIR/$SINGLE_CONFIG > $BUILD_DIR/$output_file"
|
||||
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="cd $BUILD_DIR && make test-config-parser && LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser --json $CONFIG_DIR/$SINGLE_CONFIG > $BUILD_DIR/$output_file"
|
||||
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="cd $BUILD_DIR && make test-config-parser && LD_LIBRARY_PATH=/usr/local/lib ./test-config-parser $CONFIG_DIR/$SINGLE_CONFIG 2>&1 | tee $BUILD_DIR/$output_file"
|
||||
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
|
||||
@@ -178,17 +266,17 @@ run_tests() {
|
||||
case "$FORMAT" in
|
||||
html)
|
||||
output_file="test-report.html"
|
||||
test_cmd="cd $BUILD_DIR && make test-config-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 test-config-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 test-config-full 2>&1 | tee $BUILD_DIR/$output_file"
|
||||
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
|
||||
@@ -236,6 +324,10 @@ print_summary() {
|
||||
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"
|
||||
@@ -247,6 +339,12 @@ print_summary() {
|
||||
|
||||
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
|
||||
|
||||
@@ -22,7 +22,7 @@ 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++ -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv
|
||||
|
||||
|
||||
592
src/ucentral-client/est-client.c
Normal file
592
src/ucentral-client/est-client.c
Normal 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;
|
||||
}
|
||||
124
src/ucentral-client/est-client.h
Normal file
124
src/ucentral-client/est-client.h
Normal 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 */
|
||||
@@ -10,6 +10,7 @@
|
||||
#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
|
||||
@@ -2374,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)
|
||||
{
|
||||
@@ -4193,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"))
|
||||
|
||||
@@ -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;
|
||||
@@ -76,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";
|
||||
@@ -197,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;
|
||||
@@ -508,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];
|
||||
@@ -548,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;
|
||||
@@ -742,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;
|
||||
@@ -755,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 */
|
||||
|
||||
@@ -785,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;
|
||||
|
||||
@@ -52,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);
|
||||
|
||||
580
tests/ADDING_NEW_PLATFORM.md
Normal file
580
tests/ADDING_NEW_PLATFORM.md
Normal 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`
|
||||
@@ -1,22 +1,234 @@
|
||||
# Configuration Testing Framework Maintenance Guide
|
||||
|
||||
This document provides procedures for maintaining the configuration testing framework as the software evolves. The framework consists of two main components that require periodic updates:
|
||||
This document provides procedures for maintaining the configuration testing framework as the software evolves.
|
||||
|
||||
1. **Schema Files** - JSON schema definitions from ols-ucentral-schema repository
|
||||
2. **Property Database** - Tracking of parsed properties in test-config-parser.c
|
||||
## Overview
|
||||
|
||||
**Current Approach (December 2024): Schema-Based Property Database Generation**
|
||||
|
||||
The framework uses the **uCentral schema as the single source of truth** for property databases. This ensures complete coverage of all 398 schema properties, whether implemented or not.
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **Schema Files** - JSON/YAML schema definitions from ols-ucentral-schema repository
|
||||
2. **Property Databases** - Generated from schema, tracking implementation status:
|
||||
- `property-database-base.c` - Proto.c parsing (102 found, 296 not implemented)
|
||||
- `property-database-platform-brcm-sonic.c` - Platform application (141 found, 257 not implemented)
|
||||
|
||||
### Schema-Based Workflow
|
||||
|
||||
```
|
||||
ols-ucentral-schema (YAML)
|
||||
↓
|
||||
fetch-schema.sh → ols-ucentral-schema/
|
||||
↓
|
||||
extract-schema-properties.py → 398 properties
|
||||
↓
|
||||
generate-database-from-schema.py → base database
|
||||
generate-platform-database-from-schema.py → platform database
|
||||
↓
|
||||
Property databases with line numbers
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Start: Schema-Based Regeneration](#quick-start-schema-based-regeneration)
|
||||
- [Schema Update Procedures](#schema-update-procedures)
|
||||
- [Property Database Update Procedures](#property-database-update-procedures)
|
||||
- [Property Database Regeneration](#property-database-regeneration)
|
||||
- [Adding New Parser Functions](#adding-new-parser-functions)
|
||||
- [Platform-Specific Updates](#platform-specific-updates)
|
||||
- [Version Synchronization](#version-synchronization)
|
||||
- [Testing After Updates](#testing-after-updates)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Legacy Approach](#legacy-approach)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start: Schema-Based Regeneration
|
||||
|
||||
**This repository includes default schema files in `config-samples/`**, so you can regenerate property databases immediately without fetching external repositories.
|
||||
|
||||
**Complete regeneration of both property databases from scratch:**
|
||||
|
||||
```bash
|
||||
cd tests/tools
|
||||
|
||||
# 1. Obtain schema (use included version OR fetch newer version)
|
||||
# Option A: Use included schema (recommended for most cases)
|
||||
SCHEMA_SOURCE="../../config-samples/ucentral.schema.pretty.json"
|
||||
|
||||
# Option B: Fetch newer schema if needed (optional)
|
||||
# ./fetch-schema.sh main
|
||||
# SCHEMA_SOURCE="../../ols-ucentral-schema/schema"
|
||||
|
||||
# 2. Extract all properties from schema
|
||||
# For JSON schema file (recommended):
|
||||
python3 extract-schema-properties.py ../../config-samples/ucentral.schema.pretty.json \
|
||||
2>/dev/null > /tmp/all-schema-properties.txt
|
||||
|
||||
# For YAML schema directory (if using ols-ucentral-schema repo):
|
||||
# python3 extract-schema-properties.py ../../ols-ucentral-schema/schema ucentral.yml \
|
||||
# 2>/dev/null > /tmp/all-schema-properties.txt
|
||||
|
||||
# 3. Generate base database (proto.c)
|
||||
python3 generate-database-from-schema.py \
|
||||
../../src/ucentral-client/proto.c \
|
||||
/tmp/all-schema-properties.txt \
|
||||
/tmp/base-database-new.c
|
||||
|
||||
# 4. Generate platform database (plat-gnma.c)
|
||||
python3 generate-platform-database-from-schema.py \
|
||||
../../src/ucentral-client/platform/brcm-sonic/plat-gnma.c \
|
||||
/tmp/all-schema-properties.txt \
|
||||
/tmp/platform-database-new.c
|
||||
|
||||
# 5. Install new databases
|
||||
cp /tmp/base-database-new.c ../config-parser/property-database-base.c
|
||||
cp /tmp/platform-database-new.c ../config-parser/property-database-platform-brcm-sonic.c
|
||||
|
||||
# 6. Test in Docker
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/tests/config-parser && make clean && make test-config-full"
|
||||
```
|
||||
|
||||
**Result:** Both databases regenerated with all schema properties, showing which are implemented (with line numbers) and which are not (line_number=0).
|
||||
|
||||
---
|
||||
|
||||
## Property Database Regeneration
|
||||
|
||||
### When to Regenerate
|
||||
|
||||
Regenerate property databases when:
|
||||
- **New parser functions added** to proto.c or platform code
|
||||
- **Schema updated** with new properties
|
||||
- **Parser functions renamed or refactored**
|
||||
- **Starting fresh** after major refactoring
|
||||
- **Periodic audit** (quarterly recommended)
|
||||
|
||||
### Why Schema-Based?
|
||||
|
||||
1. **Complete Coverage** - Tracks ALL 398 schema properties
|
||||
2. **Single Source of Truth** - Schema defines what's possible
|
||||
3. **Shows Gaps** - Properties with line_number=0 are not yet implemented
|
||||
4. **Consistent** - Same properties across all platforms
|
||||
5. **Maintainable** - Automatic updates when schema changes
|
||||
|
||||
### Base Database Generation
|
||||
|
||||
Generates `property-database-base.c` from proto.c:
|
||||
|
||||
```bash
|
||||
cd tests/tools
|
||||
|
||||
# Extract schema properties from included JSON file
|
||||
python3 extract-schema-properties.py ../../config-samples/ucentral.schema.pretty.json \
|
||||
2>/dev/null > /tmp/schema-props.txt
|
||||
|
||||
# OR if using YAML from ols-ucentral-schema repository:
|
||||
# python3 extract-schema-properties.py ../../ols-ucentral-schema/schema ucentral.yml \
|
||||
# 2>/dev/null > /tmp/schema-props.txt
|
||||
|
||||
# Generate database
|
||||
python3 generate-database-from-schema.py \
|
||||
../../src/ucentral-client/proto.c \
|
||||
/tmp/schema-props.txt \
|
||||
/tmp/base-db.c
|
||||
|
||||
# Install database
|
||||
cp /tmp/base-db.c ../config-parser/property-database-base.c
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Searches proto.c for cJSON property access patterns
|
||||
- Finds line numbers where each property is parsed
|
||||
- Marks unimplemented properties with line_number=0
|
||||
- Generates complete C array with all schema properties
|
||||
|
||||
### Platform Database Generation
|
||||
|
||||
Generates `property-database-platform-*.c` from platform code:
|
||||
|
||||
```bash
|
||||
cd tests/tools
|
||||
|
||||
# Generate platform database (brcm-sonic example)
|
||||
python3 generate-platform-database-from-schema.py \
|
||||
../../src/ucentral-client/platform/brcm-sonic/plat-gnma.c \
|
||||
/tmp/schema-props.txt \
|
||||
/tmp/platform-db.c
|
||||
|
||||
cp /tmp/platform-db.c ../config-parser/property-database-platform-brcm-sonic.c
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Analyzes platform code for config_*_apply() functions
|
||||
- Maps properties to platform functions by feature area
|
||||
- Platform code doesn't parse JSON directly
|
||||
- Uses feature-based matching (poe → config_poe_port_apply)
|
||||
|
||||
---
|
||||
|
||||
## Adding New Parser Functions
|
||||
|
||||
When you add a new parser function to proto.c:
|
||||
|
||||
```c
|
||||
// Example: New parser function
|
||||
static int cfg_new_feature_parse(cJSON *obj, struct plat_cfg *cfg) {
|
||||
cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, "new-property");
|
||||
// ...parse new-property...
|
||||
}
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Implement the parser** in proto.c
|
||||
2. **Regenerate database** using schema-based approach (see Quick Start)
|
||||
3. **Verify** the new property appears with correct line number:
|
||||
```bash
|
||||
grep "new-property" tests/config-parser/property-database-base.c
|
||||
# Should show: {"path.to.new-property", PROP_CONFIGURED, "proto.c", "cfg_new_feature_parse", LINE, "..."}
|
||||
```
|
||||
4. **Test** with a config containing the new property
|
||||
5. **Commit** proto.c changes and regenerated database together
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Updates
|
||||
|
||||
Platform vendors (Edgecore, Dell, etc.) maintain their own platform databases.
|
||||
|
||||
### For Platform Developers
|
||||
|
||||
When adding platform-specific features:
|
||||
|
||||
```bash
|
||||
cd tests/tools
|
||||
|
||||
# Regenerate YOUR platform database
|
||||
python3 generate-platform-database-from-schema.py \
|
||||
../../src/ucentral-client/platform/YOUR-PLATFORM/plat-YOUR.c \
|
||||
/tmp/schema-props.txt \
|
||||
/tmp/platform-db-YOUR.c
|
||||
|
||||
# Install it
|
||||
cp /tmp/platform-db-YOUR.c ../config-parser/property-database-platform-YOUR.c
|
||||
```
|
||||
|
||||
**Note:** Each platform tracks only its own implementation. Properties showing as "Unknown" in base repo may be "CONFIGURED" in your platform.
|
||||
|
||||
---
|
||||
|
||||
## Schema Update Procedures
|
||||
|
||||
### Included Schema File
|
||||
|
||||
This repository includes a default schema file in `config-samples/`:
|
||||
- `ucentral.schema.pretty.json` - uCentral JSON schema (human-readable format)
|
||||
|
||||
This file allows immediate use of the testing framework without fetching external repositories.
|
||||
|
||||
### When to Update Schema
|
||||
|
||||
Update the schema when:
|
||||
@@ -41,40 +253,59 @@ head -20 config-samples/ucentral.schema.pretty.json | grep -i version
|
||||
|
||||
#### Step 2: Obtain New Schema
|
||||
|
||||
**Option A: From ols-ucentral-schema Repository**
|
||||
**Repository Location:**
|
||||
- GitHub: https://github.com/Telecominfraproject/ols-ucentral-schema
|
||||
- Schema files are in the `schema/` directory (YAML format)
|
||||
- Built/converted JSON schemas may be in releases or build artifacts
|
||||
|
||||
**Option A: Using fetch-schema.sh Helper (Recommended)**
|
||||
|
||||
```bash
|
||||
# Clone or update the schema repository
|
||||
cd /tmp
|
||||
git clone https://github.com/Telecominfraproject/ols-ucentral-schema.git
|
||||
# OR
|
||||
cd /path/to/existing/ols-ucentral-schema
|
||||
git pull origin main
|
||||
cd tests/tools
|
||||
|
||||
# Check available versions/tags
|
||||
git tag -l
|
||||
# Fetch main branch
|
||||
./fetch-schema.sh
|
||||
|
||||
# Checkout specific version (recommended for stability)
|
||||
git checkout v4.2.0
|
||||
# OR fetch specific branch
|
||||
./fetch-schema.sh release-1.0
|
||||
|
||||
# Copy schema to your repository
|
||||
cp ols.ucentral.schema.json /path/to/ols-ucentral-client/config-samples/
|
||||
cp ucentral.schema.pretty.json /path/to/ols-ucentral-client/config-samples/
|
||||
# View help
|
||||
./fetch-schema.sh --help
|
||||
```
|
||||
|
||||
**Option B: From Build Artifacts**
|
||||
The script clones the schema repository to `tests/tools/ols-ucentral-schema/`.
|
||||
|
||||
**Option B: Manual Git Clone**
|
||||
|
||||
If schema is embedded in builds:
|
||||
```bash
|
||||
# Extract from build artifacts
|
||||
cd /path/to/ols-ucentral-client
|
||||
# Schema may be copied during build process - check Makefile
|
||||
cd tests/tools
|
||||
|
||||
# Clone the schema repository
|
||||
git clone https://github.com/Telecominfraproject/ols-ucentral-schema.git
|
||||
|
||||
# Checkout specific version (recommended for stability)
|
||||
cd ols-ucentral-schema
|
||||
git tag -l # List available versions
|
||||
git checkout v4.2.0 # Checkout specific version
|
||||
|
||||
# OR use specific branch
|
||||
git checkout release-1.0
|
||||
```
|
||||
|
||||
**Option C: Download Specific File**
|
||||
|
||||
If you only need the JSON schema file:
|
||||
```bash
|
||||
# Download directly from GitHub
|
||||
cd config-samples
|
||||
wget https://raw.githubusercontent.com/Telecominfraproject/ols-ucentral-schema/main/schema/ucentral.schema.json \
|
||||
-O ucentral.schema.pretty.json
|
||||
```
|
||||
|
||||
#### Step 3: Validate Schema File
|
||||
|
||||
```bash
|
||||
cd /path/to/ols-ucentral-client/src/ucentral-client
|
||||
cd /path/to/ols-ucentral-client/tests/config-parser
|
||||
|
||||
# Verify schema is valid JSON
|
||||
python3 -c "import json; json.load(open('../../config-samples/ucentral.schema.pretty.json'))"
|
||||
@@ -233,7 +464,7 @@ grep -n "cfg_port_mirroring_parse" src/ucentral-client/proto.c
|
||||
|
||||
If updating from upstream or merging branches:
|
||||
```bash
|
||||
cd /path/to/ols-ucentral-client/src/ucentral-client
|
||||
cd /path/to/ols-ucentral-client/tests/config-parser
|
||||
|
||||
# Compare parser functions between versions
|
||||
git diff HEAD~1 proto.c | grep "^+.*cfg_.*_parse"
|
||||
@@ -332,7 +563,7 @@ print(f"Removed entries for {function_name}")
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
cd /path/to/ols-ucentral-client/src/ucentral-client
|
||||
cd /path/to/ols-ucentral-client/tests/config-parser
|
||||
|
||||
# Remove entries for obsolete function
|
||||
python3 remove-properties.py cfg_old_feature_parse
|
||||
@@ -472,7 +703,7 @@ static struct property_info properties[] = {
|
||||
#### Step 4: Verify Property Database Accuracy
|
||||
|
||||
```bash
|
||||
cd /path/to/ols-ucentral-client/src/ucentral-client
|
||||
cd /path/to/ols-ucentral-client/tests/config-parser
|
||||
|
||||
# Rebuild test suite
|
||||
make clean
|
||||
@@ -528,7 +759,7 @@ EOF
|
||||
**B. Validate Configuration**
|
||||
|
||||
```bash
|
||||
cd /path/to/ols-ucentral-client/src/ucentral-client
|
||||
cd /path/to/ols-ucentral-client/tests/config-parser
|
||||
|
||||
# Schema validation
|
||||
./validate-schema.py ../../config-samples/test-new-feature.json
|
||||
@@ -660,7 +891,7 @@ After updating schema or property database:
|
||||
cd /path/to/ols-ucentral-client
|
||||
|
||||
# 1. Clean build
|
||||
cd src/ucentral-client
|
||||
cd tests/config-parser
|
||||
make clean
|
||||
|
||||
# 2. Rebuild test tools
|
||||
@@ -856,7 +1087,7 @@ docker exec ucentral_client_build_env bash -c \
|
||||
|
||||
# Verify paths in container
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && ls -la ../../config-samples/*.json"
|
||||
"cd /root/ols-nos/tests/config-parser && ls -la ../../config-samples/*.json"
|
||||
|
||||
# Rebuild container if needed
|
||||
make clean
|
||||
@@ -924,3 +1155,34 @@ make test-config
|
||||
- **proto.c** - Parser implementation
|
||||
- **test-config-parser.c** - Property database location
|
||||
- **ols-ucentral-schema repository** - Official schema source
|
||||
|
||||
## Legacy Approach
|
||||
|
||||
**Note:** The config-based approach described below has been superseded by the schema-based approach (see above). Legacy tools are archived in `tests/tools/legacy/`.
|
||||
|
||||
### Old Config-Based Method (Pre-December 2024)
|
||||
|
||||
The original approach extracted properties FROM configuration files:
|
||||
|
||||
```bash
|
||||
# OLD METHOD - No longer recommended
|
||||
cd tests/tools/legacy
|
||||
python3 generate-property-database.py ../../config-samples/*.json > /tmp/props.txt
|
||||
python3 find-property-line-numbers.py ../../src/ucentral-client/proto.c /tmp/props.txt
|
||||
```
|
||||
|
||||
**Limitations of config-based approach:**
|
||||
- Only tracked properties that appeared in test configs (628 properties)
|
||||
- Missed properties defined in schema but not in configs
|
||||
- No way to identify unimplemented schema properties
|
||||
- Inconsistent property paths (array indices varied)
|
||||
- Required manual curation
|
||||
|
||||
**Migration to schema-based:**
|
||||
- December 2024: Switched to schema as source of truth
|
||||
- Now tracks all 398 schema properties consistently
|
||||
- Shows implementation status for each property
|
||||
- Fully automated regeneration
|
||||
|
||||
**For historical reference**, see `tests/tools/legacy/README.md`.
|
||||
|
||||
|
||||
140
tests/README.md
140
tests/README.md
@@ -15,7 +15,7 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
|
||||
### Primary Documentation
|
||||
|
||||
1. **[TEST_CONFIG_README.md](src/ucentral-client/TEST_CONFIG_README.md)** - Complete testing framework guide
|
||||
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
|
||||
@@ -24,7 +24,7 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
- CI/CD integration
|
||||
- **Start here** for understanding the testing framework
|
||||
|
||||
2. **[SCHEMA_VALIDATOR_README.md](src/ucentral-client/SCHEMA_VALIDATOR_README.md)** - Schema validator detailed documentation
|
||||
2. **[SCHEMA_VALIDATOR_README.md](schema/SCHEMA_VALIDATOR_README.md)** - Schema validator detailed documentation
|
||||
- Standalone validator usage
|
||||
- Command-line interface
|
||||
- Programmatic API
|
||||
@@ -32,7 +32,7 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
- Common validation errors
|
||||
- **Start here** for schema validation specifics
|
||||
|
||||
3. **[MAINTENANCE.md](src/ucentral-client/MAINTENANCE.md)** - Maintenance procedures guide
|
||||
3. **[MAINTENANCE.md](MAINTENANCE.md)** - Maintenance procedures guide
|
||||
- Schema update procedures
|
||||
- Property database update procedures
|
||||
- Version synchronization
|
||||
@@ -40,7 +40,7 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
- 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
|
||||
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
|
||||
@@ -51,14 +51,27 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
|
||||
### Supporting Documentation
|
||||
|
||||
5. **[CLAUDE.md](CLAUDE.md)** - Project overview and AI assistant guidance
|
||||
- Build system architecture
|
||||
- Platform abstraction layer
|
||||
- Testing framework integration
|
||||
- References to all test-related files
|
||||
|
||||
## 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):
|
||||
@@ -67,50 +80,61 @@ This testing framework includes multiple documentation files, each serving a spe
|
||||
# Build the Docker environment first (if not already built)
|
||||
make build-host-env
|
||||
|
||||
# Run all tests (schema + parser) - RECOMMENDED
|
||||
# Run all tests in STUB mode (default - fast)
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-full"
|
||||
"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/src/ucentral-client && make validate-schema"
|
||||
"cd /root/ols-nos/tests/config-parser && make validate-schema"
|
||||
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config"
|
||||
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test"
|
||||
"cd /root/ols-nos/tests/config-parser && make test"
|
||||
|
||||
# Generate test reports
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-html"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config-html"
|
||||
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-json"
|
||||
"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/src/ucentral-client/test-report.html ./
|
||||
docker cp ucentral_client_build_env:/root/ols-nos/src/ucentral-client/test-results.json ./
|
||||
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 src/ucentral-client
|
||||
cd tests/config-parser
|
||||
|
||||
# Run all tests (schema + 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
|
||||
# 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).
|
||||
@@ -118,19 +142,18 @@ make test-config-junit # JUnit XML (CI/CD integration)
|
||||
### Key Files
|
||||
|
||||
**Test Implementation:**
|
||||
- `tests/config-parser/test-config-parser.c` (3445 lines) - Parser test framework with property tracking
|
||||
- `tests/config-parser/test-stubs.c` (214 lines) - Platform function stubs for testing
|
||||
- `tests/schema/validate-schema.py` (305 lines) - Standalone schema validator
|
||||
- `src/ucentral-client/include/config-parser.h` - Test header exposing cfg_parse()
|
||||
- `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)
|
||||
- `config-samples/ols.ucentral.schema.json` - uCentral JSON schema (compact)
|
||||
- `config-samples/*.json` - Test configuration files (37+ configs)
|
||||
- `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:**
|
||||
- `src/ucentral-client/Makefile` - Test targets and build rules
|
||||
- `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)
|
||||
@@ -156,7 +179,7 @@ make test-config-junit # JUnit XML (CI/CD integration)
|
||||
- Hardware constraint validation
|
||||
|
||||
### Property Tracking System
|
||||
- Database of 450+ properties and their processing status
|
||||
- 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
|
||||
@@ -177,10 +200,11 @@ See TEST_CONFIG_README.md section "Two-Layer Validation Strategy" for detailed e
|
||||
## Test Coverage
|
||||
|
||||
Current test suite includes:
|
||||
- 37+ configuration files covering various features
|
||||
- 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
|
||||
@@ -210,17 +234,30 @@ These features pass schema validation but show as "Unknown" in property reports,
|
||||
The testing framework was added with minimal impact to production code:
|
||||
|
||||
### New Files Added
|
||||
1. `tests/config-parser/test-config-parser.c` - Complete test framework (3445 lines)
|
||||
2. `tests/config-parser/test-stubs.c` - Platform stubs (214 lines)
|
||||
3. `tests/schema/validate-schema.py` - Schema validator (305 lines)
|
||||
4. `src/ucentral-client/include/config-parser.h` - Test header
|
||||
5. `tests/config-parser/TEST_CONFIG_README.md` - Framework documentation
|
||||
6. `tests/schema/SCHEMA_VALIDATOR_README.md` - Validator documentation
|
||||
7. `tests/MAINTENANCE.md` - Maintenance procedures
|
||||
8. `tests/config-parser/Makefile` - Test build system
|
||||
9. `tests/tools/` - Property database generation tools
|
||||
8. `TESTING_FRAMEWORK.md` - This file (documentation index)
|
||||
9. `TEST_CONFIG_PARSER_DESIGN.md` - Test framework architecture and design
|
||||
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)
|
||||
@@ -242,7 +279,7 @@ The testing framework was added with minimal impact to production code:
|
||||
2. `src/ucentral-client/include/router-utils.h` - Added extern declarations
|
||||
- Exposed necessary functions for test stubs
|
||||
|
||||
3. `src/ucentral-client/Makefile` - Added test targets
|
||||
3. `tests/config-parser/Makefile` - Test build system
|
||||
```makefile
|
||||
test-config-parser: # Build parser test tool
|
||||
test-config: # Run parser tests
|
||||
@@ -269,7 +306,7 @@ The testing framework was added with minimal impact to production code:
|
||||
vi src/ucentral-client/proto.c
|
||||
|
||||
# 2. Run tests
|
||||
cd src/ucentral-client
|
||||
cd tests/config-parser
|
||||
make test-config-full
|
||||
|
||||
# 3. Review property tracking report
|
||||
@@ -289,7 +326,7 @@ make test-config-full
|
||||
### Before Committing
|
||||
```bash
|
||||
# Ensure all tests pass
|
||||
cd src/ucentral-client
|
||||
cd tests/config-parser
|
||||
make clean
|
||||
make test-config-full
|
||||
|
||||
@@ -305,10 +342,10 @@ test-configurations:
|
||||
script:
|
||||
- make build-host-env
|
||||
- docker exec ucentral_client_build_env bash -c
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-full"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config-full"
|
||||
artifacts:
|
||||
paths:
|
||||
- src/ucentral-client/test-results.txt
|
||||
- tests/config-parser/test-results.txt
|
||||
```
|
||||
|
||||
## Property Database Management
|
||||
@@ -324,7 +361,7 @@ static struct property_info properties[] = {
|
||||
.status = PROP_CONFIGURED,
|
||||
.notes = "Enable/disable ethernet interface"
|
||||
},
|
||||
// ... 450+ more entries ...
|
||||
// ... entries for all 398 schema properties ...
|
||||
};
|
||||
```
|
||||
|
||||
@@ -342,8 +379,7 @@ See MAINTENANCE.md for complete property database update procedures.
|
||||
The schema file defines what configurations are structurally valid.
|
||||
|
||||
### Schema Location
|
||||
- `config-samples/ucentral.schema.pretty.json` - Human-readable version (recommended)
|
||||
- `config-samples/ols.ucentral.schema.json` - Compact version
|
||||
- `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.
|
||||
@@ -415,7 +451,6 @@ When updating the testing framework:
|
||||
- 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 → CLAUDE.md
|
||||
|
||||
2. **Keep version information current:**
|
||||
- Update compatibility matrices
|
||||
@@ -447,5 +482,4 @@ BSD-3-Clause (same as parent project)
|
||||
- **TEST_CONFIG_PARSER_DESIGN.md** - Test framework architecture and design
|
||||
- **SCHEMA_VALIDATOR_README.md** - Schema validator documentation
|
||||
- **MAINTENANCE.md** - Update procedures and troubleshooting
|
||||
- **CLAUDE.md** - Project overview and build system
|
||||
- **ols-ucentral-schema repository** - Official schema source
|
||||
|
||||
@@ -1,97 +1,279 @@
|
||||
# Configuration Parser Test Suite Makefile
|
||||
# This Makefile builds and runs tests for the uCentral configuration parser
|
||||
# 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
|
||||
.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
|
||||
# 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 -I $(SRC_DIR) -I $(SRC_DIR)/include -I ./ $<
|
||||
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 $<
|
||||
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 $<
|
||||
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
|
||||
test-config-parser: test-config-parser.o proto-test.o test-stubs.o $(SRC_OBJS)
|
||||
g++ -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv
|
||||
# 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 "========= running config parser tests ========="
|
||||
@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 "========= config parser tests completed ========"
|
||||
@echo "=========================================="
|
||||
@echo "Tests completed"
|
||||
@echo "=========================================="
|
||||
|
||||
# Run schema validation
|
||||
validate-schema:
|
||||
@echo "========= running schema validation ========="
|
||||
@python3 $(SCHEMA_VALIDATOR) $(CONFIG_SAMPLES) || true
|
||||
@echo "========= schema validation completed ========"
|
||||
@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 ========"
|
||||
@echo "========= All validation completed ========"
|
||||
|
||||
# Generate HTML test report
|
||||
test-config-html:
|
||||
@echo "========= generating HTML test report ========="
|
||||
@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 generated: test-report.html ========"
|
||||
@echo "========= HTML report: test-report.html ========"
|
||||
|
||||
# Generate JSON test report
|
||||
test-config-json:
|
||||
@echo "========= generating JSON test report ========="
|
||||
@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 generated: test-report.json ========"
|
||||
@echo "========= JSON report: test-report.json ========"
|
||||
|
||||
# Generate JUnit XML test report
|
||||
test-config-junit:
|
||||
@echo "========= generating JUnit XML test report ========="
|
||||
@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 generated: 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 test artifacts
|
||||
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 target
|
||||
# ============================================================================
|
||||
# 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 " 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 " clean - Remove test artifacts"
|
||||
@echo " help - Show this help message"
|
||||
@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"
|
||||
|
||||
@@ -34,37 +34,37 @@ make build-host-env
|
||||
|
||||
# Run all tests - RECOMMENDED
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-full"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config-full"
|
||||
```
|
||||
|
||||
#### Run Individual Test Suites
|
||||
```bash
|
||||
# Schema validation only
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make validate-schema"
|
||||
"cd /root/ols-nos/tests/config-parser && make validate-schema"
|
||||
|
||||
# Parser tests only
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config"
|
||||
|
||||
# Unit tests
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test"
|
||||
"cd /root/ols-nos/tests/config-parser && make test"
|
||||
```
|
||||
|
||||
#### Generate Test Reports
|
||||
```bash
|
||||
# Generate HTML report (viewable in browser)
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-html"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config-html"
|
||||
|
||||
# Generate JSON report (machine-readable)
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-json"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config-json"
|
||||
|
||||
# Copy reports out of container to view
|
||||
docker cp ucentral_client_build_env:/root/ols-nos/src/ucentral-client/test-report.html ./
|
||||
docker cp ucentral_client_build_env:/root/ols-nos/src/ucentral-client/test-results.json ./
|
||||
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
|
||||
@@ -72,7 +72,7 @@ docker cp ucentral_client_build_env:/root/ols-nos/src/ucentral-client/test-resul
|
||||
**Note:** Running tests locally may encounter OS-specific dependency issues. Docker is the recommended approach.
|
||||
|
||||
```bash
|
||||
cd src/ucentral-client
|
||||
cd tests/config-parser
|
||||
|
||||
# Run all tests (schema + parser)
|
||||
make test-config-full
|
||||
@@ -114,8 +114,8 @@ make run-host-env
|
||||
# In another terminal, exec into the container
|
||||
docker exec -it ucentral_client_build_env bash
|
||||
|
||||
# Navigate to source directory and run tests
|
||||
cd /root/ols-nos/src/ucentral-client
|
||||
# Navigate to test directory and run tests
|
||||
cd /root/ols-nos/tests/config-parser
|
||||
make test-config-full
|
||||
```
|
||||
|
||||
@@ -127,7 +127,7 @@ make build-host-env
|
||||
|
||||
# Run tests in container
|
||||
docker exec ucentral_client_build_env bash -c \
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config-full"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config-full"
|
||||
```
|
||||
|
||||
## Test Features
|
||||
@@ -389,7 +389,7 @@ To check if the database is accurate:
|
||||
```bash
|
||||
cd src/ucentral-client
|
||||
grep "cfg_ethernet_lldp_parse" proto.c
|
||||
# If this returns nothing, the function doesn't exist
|
||||
# If this returns nothing, the function doesn't exist in the base repository
|
||||
```
|
||||
|
||||
2. **Run property usage report:**
|
||||
@@ -679,9 +679,13 @@ The test suite uses a minimal-impact approach to expose `cfg_parse()` for testin
|
||||
|
||||
### Files
|
||||
|
||||
- `test-config-parser.c` - Test framework and validators (3445 lines)
|
||||
- `test-stubs.c` - Platform function stubs for testing (214 lines)
|
||||
- `validate-schema.py` - Modular schema validation tool (305 lines)
|
||||
- `test-config-parser.c` - Test framework and validators (3304 lines)
|
||||
- `test-stubs.c` - Platform function stubs for stub mode testing (219 lines)
|
||||
- `platform-mocks/brcm-sonic.c` - Platform mocks for brcm-sonic integration testing
|
||||
- `platform-mocks/example-platform.c` - Platform mocks for example platform
|
||||
- `property-database-base.c` - Base property database (416 lines, 398 properties: 102 implemented, 296 not yet)
|
||||
- `property-database-platform-brcm-sonic.c` - Platform property database (419 lines)
|
||||
- `validate-schema.py` - Modular schema validation tool (649 lines)
|
||||
- `include/config-parser.h` - Header declaring cfg_parse
|
||||
- `Makefile` - Test targets: test-config, validate-schema, test-config-full
|
||||
- `proto.c` - Added TEST_STATIC macro pattern (2 lines modified)
|
||||
@@ -914,21 +918,21 @@ test-configs:
|
||||
|
||||
# Run schema validation
|
||||
- docker exec ucentral_client_build_env bash -c
|
||||
"cd /root/ols-nos/src/ucentral-client && make validate-schema"
|
||||
"cd /root/ols-nos/tests/config-parser && make validate-schema"
|
||||
|
||||
# Run parser tests
|
||||
- docker exec ucentral_client_build_env bash -c
|
||||
"cd /root/ols-nos/src/ucentral-client && make test-config"
|
||||
"cd /root/ols-nos/tests/config-parser && make test-config"
|
||||
|
||||
# Generate JSON reports
|
||||
- docker exec ucentral_client_build_env bash -c
|
||||
"cd /root/ols-nos/src/ucentral-client &&
|
||||
"cd /root/ols-nos/tests/schema &&
|
||||
python3 validate-schema.py ../../config-samples/ --format json > schema-report.json"
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- src/ucentral-client/schema-report.json
|
||||
- src/ucentral-client/test-results.txt
|
||||
- tests/schema/schema-report.json
|
||||
- tests/config-parser/test-results.txt
|
||||
when: always
|
||||
|
||||
coverage: '/Property coverage: (\d+\.\d+)%/'
|
||||
@@ -1015,7 +1019,6 @@ grep "parser_function" test-config-parser.c | sort | uniq
|
||||
- **../../config-samples/ucentral.schema.pretty.json** - Official uCentral JSON schema
|
||||
- **include/ucentral-platform.h** - Platform API and plat_cfg structure definitions
|
||||
- **proto.c** - Configuration parser implementation
|
||||
- **CLAUDE.md** - Project overview and build instructions
|
||||
|
||||
## License
|
||||
|
||||
|
||||
147
tests/config-parser/platform-mocks/README.md
Normal file
147
tests/config-parser/platform-mocks/README.md
Normal 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
|
||||
982
tests/config-parser/platform-mocks/brcm-sonic.c
Normal file
982
tests/config-parser/platform-mocks/brcm-sonic.c
Normal 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;
|
||||
}
|
||||
67
tests/config-parser/platform-mocks/example-platform.c
Normal file
67
tests/config-parser/platform-mocks/example-platform.c
Normal 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
|
||||
*/
|
||||
436
tests/config-parser/property-database-base.c
Normal file
436
tests/config-parser/property-database-base.c
Normal 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}
|
||||
};
|
||||
439
tests/config-parser/property-database-platform-brcm-sonic.c
Normal file
439
tests/config-parser/property-database-platform-brcm-sonic.c
Normal 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}
|
||||
};
|
||||
25
tests/config-parser/property-database-platform-example.c
Normal file
25
tests/config-parser/property-database-platform-example.c
Normal 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
@@ -211,3 +211,35 @@ int plat_event_subscribe(const struct plat_event_callbacks *cbs)
|
||||
}
|
||||
|
||||
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
22
tests/requirements.txt
Normal 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)
|
||||
@@ -1,15 +1,21 @@
|
||||
# uCentral Schema Validator
|
||||
|
||||
A modular, portable tool for validating JSON configuration files against the uCentral schema.
|
||||
A modular, portable tool for validating JSON configuration files against the uCentral schema with advanced undefined property detection and typo suggestions.
|
||||
|
||||
## Features
|
||||
|
||||
- **Standalone Operation**: Works independently without external dependencies beyond Python 3 + jsonschema
|
||||
- **Modular Design**: Easy to port to other repositories (EC, etc.)
|
||||
- **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 suitable for automated testing
|
||||
- **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
|
||||
|
||||
@@ -28,24 +34,50 @@ pip3 install jsonschema
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Validate a single file
|
||||
# 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/
|
||||
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
|
||||
python3 validate-schema.py config.json --check-undefined
|
||||
|
||||
# Machine-readable JSON output
|
||||
python3 validate-schema.py config.json --format json > report.json
|
||||
python3 validate-schema.py config.json --check-undefined --format json > report.json
|
||||
```
|
||||
|
||||
### Via Makefile
|
||||
@@ -64,21 +96,58 @@ make test-config-full
|
||||
## Exit Codes
|
||||
|
||||
- `0`: All configurations are valid
|
||||
- `1`: One or more configurations failed validation
|
||||
- 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
|
||||
### Valid Configuration (Schema Only)
|
||||
|
||||
```
|
||||
✓ Valid: cfg0.json
|
||||
✓ Schema Valid: cfg0.json
|
||||
```
|
||||
|
||||
### Invalid Configuration
|
||||
### Valid Configuration with Undefined Properties Check
|
||||
|
||||
```
|
||||
✗ Invalid: bad-config.json
|
||||
✓ 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:
|
||||
@@ -92,10 +161,13 @@ make test-config-full
|
||||
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
|
||||
@@ -122,17 +194,6 @@ Relative to the validator script location:
|
||||
- `../../../config-samples/ucentral.schema.pretty.json`
|
||||
- `./ols.ucentral.schema.json`
|
||||
|
||||
### Example: Porting to EC Repository
|
||||
|
||||
```bash
|
||||
# Copy validator
|
||||
cp validate-schema.py /path/to/ec-repo/src/ucentral-client/
|
||||
|
||||
# Use with EC's schema location
|
||||
cd /path/to/ec-repo/src/ucentral-client
|
||||
python3 validate-schema.py --schema ../../config-tests/schema.json ../../config-tests/
|
||||
```
|
||||
|
||||
## Python API
|
||||
|
||||
The `SchemaValidator` class can be imported and used programmatically:
|
||||
@@ -154,50 +215,140 @@ is_valid, errors = validator.validate_config(config)
|
||||
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
|
||||
|
||||
### Type 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
|
||||
|
||||
**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
|
||||
|
||||
**Required Property Missing:**
|
||||
```
|
||||
'uuid' is a required property
|
||||
```
|
||||
|
||||
**Fix**: Add the required field: `"uuid": 1234567890`
|
||||
|
||||
### Additional Properties Not Allowed
|
||||
|
||||
**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`)
|
||||
|
||||
**Fix**: Remove the field or check spelling
|
||||
### 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 (305 lines)
|
||||
- **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](TEST_CONFIG_README.md) - Configuration parser testing guide
|
||||
- [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
|
||||
|
||||
@@ -6,9 +6,15 @@ 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
|
||||
# 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
|
||||
|
||||
@@ -18,7 +24,19 @@ Usage:
|
||||
# Machine-readable JSON output
|
||||
./validate-schema.py config.json --format json
|
||||
|
||||
# Exit code: 0 = all valid, 1 = validation errors, 2 = file/schema errors
|
||||
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
|
||||
@@ -29,7 +47,8 @@ import json
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
from typing import Dict, List, Tuple, Optional, Set
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import jsonschema
|
||||
@@ -39,6 +58,84 @@ except ImportError:
|
||||
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.
|
||||
@@ -47,17 +144,23 @@ class SchemaValidator:
|
||||
It has no dependencies on specific file paths or repository structure.
|
||||
"""
|
||||
|
||||
def __init__(self, schema_path: Optional[str] = None):
|
||||
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]:
|
||||
@@ -98,7 +201,185 @@ class SchemaValidator:
|
||||
# Create validator
|
||||
self.validator = Draft7Validator(self.schema)
|
||||
|
||||
def validate_file(self, config_path: str) -> Tuple[bool, List[Dict]]:
|
||||
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:
|
||||
@@ -108,18 +389,26 @@ class SchemaValidator:
|
||||
'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]]:
|
||||
"""Validate a configuration object against the schema."""
|
||||
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):
|
||||
@@ -133,9 +422,14 @@ class SchemaValidator:
|
||||
'schema_path': '.'.join(str(p) for p in error.absolute_schema_path) if error.absolute_schema_path else '$'
|
||||
})
|
||||
|
||||
return len(errors) == 0, errors
|
||||
# Check for undefined properties if enabled
|
||||
undefined_analysis = None
|
||||
if self.check_undefined:
|
||||
undefined_analysis = self._check_undefined_properties(config)
|
||||
|
||||
def validate_directory(self, dir_path: str, pattern: str = "*.json") -> Dict[str, Tuple[bool, List[Dict]]]:
|
||||
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)
|
||||
@@ -154,14 +448,16 @@ class SchemaValidator:
|
||||
return results
|
||||
|
||||
|
||||
def format_human_output(filename: str, is_valid: bool, errors: List[Dict]) -> str:
|
||||
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"✓ Valid: {filename}")
|
||||
output.append(f"✓ Schema Valid: {filename}")
|
||||
else:
|
||||
output.append(f"✗ Invalid: {filename}")
|
||||
output.append(f"✗ Schema Invalid: {filename}")
|
||||
output.append(f" Found {len(errors)} validation error(s):")
|
||||
|
||||
for i, error in enumerate(errors, 1):
|
||||
@@ -171,26 +467,69 @@ def format_human_output(filename: str, is_valid: bool, errors: List[Dict]) -> st
|
||||
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]]]) -> str:
|
||||
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)
|
||||
'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) in results.items():
|
||||
output['results'][filename] = {
|
||||
'valid': is_valid,
|
||||
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)
|
||||
|
||||
|
||||
@@ -219,12 +558,26 @@ Examples:
|
||||
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)
|
||||
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
|
||||
@@ -240,8 +593,8 @@ Examples:
|
||||
results = {}
|
||||
|
||||
if path_obj.is_file():
|
||||
is_valid, errors = validator.validate_file(args.path)
|
||||
results[path_obj.name] = (is_valid, errors)
|
||||
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)
|
||||
@@ -256,19 +609,40 @@ Examples:
|
||||
if args.format == 'json':
|
||||
print(format_json_output(results))
|
||||
else:
|
||||
for filename, (is_valid, errors) in results.items():
|
||||
print(format_human_output(filename, is_valid, errors))
|
||||
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)
|
||||
valid_count = sum(1 for is_valid, _, _ in results.values() if is_valid)
|
||||
invalid_count = len(results) - valid_count
|
||||
print(f"Summary: {len(results)} file(s) checked, {valid_count} valid, {invalid_count} invalid")
|
||||
undefined_count = sum(1 for _, _, undefined in results.values()
|
||||
if undefined and undefined['undefined_count'] > 0)
|
||||
|
||||
# Exit code: 0 if all valid, 1 if any invalid
|
||||
all_valid = all(is_valid for is_valid, _ in results.values())
|
||||
return 0 if all_valid else 1
|
||||
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__':
|
||||
|
||||
329
tests/tools/extract-schema-properties.py
Normal file
329
tests/tools/extract-schema-properties.py
Normal file
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Extract property paths from uCentral schema (JSON or YAML).
|
||||
|
||||
This script extracts all leaf property paths from the schema to use as a basis
|
||||
for property database generation. Unlike config-file extraction (which only gets
|
||||
properties that exist in test configs), this gets ALL schema-defined properties.
|
||||
|
||||
Supports both JSON and YAML schema formats:
|
||||
- JSON: Single file with all definitions (e.g., ucentral.schema.pretty.json)
|
||||
- YAML: Multi-file schema with $ref resolution (e.g., ols-ucentral-schema/schema/)
|
||||
|
||||
Usage:
|
||||
# From JSON schema file (included in repository)
|
||||
python3 extract-schema-properties.py ../../config-samples/ucentral.schema.pretty.json
|
||||
|
||||
# From YAML schema directory (ols-ucentral-schema repo)
|
||||
python3 extract-schema-properties.py /path/to/ols-ucentral-schema/schema ucentral.yml
|
||||
|
||||
# Filter by prefix
|
||||
python3 extract-schema-properties.py schema.json --filter switch --filter ethernet
|
||||
|
||||
Output:
|
||||
One property path per line, suitable for piping to other tools
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Set, Dict, Any
|
||||
|
||||
|
||||
def schema_filename(ref_uri: str) -> str:
|
||||
"""
|
||||
Convert schema $ref URI to filename.
|
||||
|
||||
Example: "https://ucentral.io/schema/v1/ethernet/" -> "ethernet.yml"
|
||||
"https://ucentral.io/schema/v1/interface/ethernet/" -> "interface.ethernet.yml"
|
||||
"""
|
||||
file_parts = ref_uri.split("v1/")
|
||||
if len(file_parts) < 2:
|
||||
return None
|
||||
filename = file_parts[1].rstrip("/").replace("/", ".") + ".yml"
|
||||
return filename
|
||||
|
||||
|
||||
def schema_load(schema_dir: Path, filename: str, loaded_cache: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Load a schema YAML file with caching."""
|
||||
cache_key = str(schema_dir / filename)
|
||||
|
||||
if cache_key in loaded_cache:
|
||||
return loaded_cache[cache_key]
|
||||
|
||||
schema_path = schema_dir / filename
|
||||
if not schema_path.exists():
|
||||
print(f"WARNING: Schema file not found: {schema_path}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(schema_path) as f:
|
||||
schema = yaml.safe_load(f)
|
||||
loaded_cache[cache_key] = schema
|
||||
return schema
|
||||
except yaml.YAMLError as exc:
|
||||
print(f"ERROR loading {schema_path}: {exc}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
|
||||
def resolve_schema(schema: Any, schema_dir: Path, loaded_cache: Dict[str, Any], depth: int = 0, root_schema: Any = None) -> Any:
|
||||
"""
|
||||
Recursively resolve $ref references in schema.
|
||||
|
||||
Supports both:
|
||||
- External refs (YAML): https://ucentral.io/schema/v1/ethernet/
|
||||
- Internal refs (JSON): #/$defs/ethernet
|
||||
|
||||
Based on merge-schema.py from ols-ucentral-schema repo.
|
||||
"""
|
||||
if depth > 20: # Prevent infinite recursion
|
||||
return schema
|
||||
|
||||
# Keep root schema for resolving internal $defs references
|
||||
if root_schema is None:
|
||||
root_schema = schema
|
||||
|
||||
if isinstance(schema, dict):
|
||||
resolved = {}
|
||||
|
||||
for key, value in schema.items():
|
||||
if key == "$ref" and isinstance(value, str):
|
||||
if value.startswith("https://"):
|
||||
# External reference (YAML multi-file schema)
|
||||
filename = schema_filename(value)
|
||||
if filename:
|
||||
ref_schema = schema_load(schema_dir, filename, loaded_cache)
|
||||
# Recursively resolve the referenced schema
|
||||
resolved_ref = resolve_schema(ref_schema, schema_dir, loaded_cache, depth + 1, root_schema)
|
||||
# Merge resolved reference into current dict
|
||||
for ref_key, ref_value in resolved_ref.items():
|
||||
resolved[ref_key] = ref_value
|
||||
else:
|
||||
resolved[key] = value
|
||||
elif value.startswith("#/$defs/"):
|
||||
# Internal reference (JSON schema $defs)
|
||||
def_name = value.replace("#/$defs/", "")
|
||||
if '$defs' in root_schema and def_name in root_schema['$defs']:
|
||||
ref_schema = root_schema['$defs'][def_name]
|
||||
# Recursively resolve the referenced schema
|
||||
resolved_ref = resolve_schema(ref_schema, schema_dir, loaded_cache, depth + 1, root_schema)
|
||||
# Merge resolved reference into current dict
|
||||
for ref_key, ref_value in resolved_ref.items():
|
||||
resolved[ref_key] = ref_value
|
||||
else:
|
||||
print(f"WARNING: $defs reference not found: {def_name}", file=sys.stderr)
|
||||
resolved[key] = value
|
||||
else:
|
||||
# Unknown reference format
|
||||
resolved[key] = value
|
||||
elif isinstance(value, (dict, list)):
|
||||
resolved[key] = resolve_schema(value, schema_dir, loaded_cache, depth + 1, root_schema)
|
||||
else:
|
||||
resolved[key] = value
|
||||
|
||||
return resolved
|
||||
|
||||
elif isinstance(schema, list):
|
||||
return [resolve_schema(item, schema_dir, loaded_cache, depth + 1, root_schema) for item in schema]
|
||||
|
||||
else:
|
||||
return schema
|
||||
|
||||
|
||||
def extract_properties(schema: Any, base_path: str = "", properties: Set[str] = None) -> Set[str]:
|
||||
"""
|
||||
Recursively extract all property paths from resolved schema.
|
||||
"""
|
||||
if properties is None:
|
||||
properties = set()
|
||||
|
||||
if not isinstance(schema, dict):
|
||||
return properties
|
||||
|
||||
schema_type = schema.get("type")
|
||||
|
||||
if schema_type == "object":
|
||||
# Object with properties
|
||||
obj_properties = schema.get("properties", {})
|
||||
|
||||
if not obj_properties:
|
||||
# Leaf object with no sub-properties
|
||||
if base_path:
|
||||
properties.add(base_path)
|
||||
else:
|
||||
for prop_name, prop_schema in obj_properties.items():
|
||||
new_path = f"{base_path}.{prop_name}" if base_path else prop_name
|
||||
extract_properties(prop_schema, new_path, properties)
|
||||
|
||||
elif schema_type == "array":
|
||||
# Array - add [] and recurse into items
|
||||
items_schema = schema.get("items", {})
|
||||
array_path = f"{base_path}[]" if base_path else "[]"
|
||||
extract_properties(items_schema, array_path, properties)
|
||||
|
||||
else:
|
||||
# Leaf property (string, number, boolean, etc.)
|
||||
if base_path and schema_type:
|
||||
properties.add(base_path)
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
def filter_properties(properties: Set[str], filters: list) -> Set[str]:
|
||||
"""Filter properties by prefix."""
|
||||
if not filters:
|
||||
return properties
|
||||
|
||||
filtered = set()
|
||||
for prop in properties:
|
||||
for f in filters:
|
||||
if prop.startswith(f):
|
||||
filtered.add(prop)
|
||||
break
|
||||
return filtered
|
||||
|
||||
|
||||
def exclude_containers(properties: Set[str]) -> Set[str]:
|
||||
"""
|
||||
Remove container properties (keep only leaves).
|
||||
|
||||
For example, if we have both:
|
||||
- interfaces[].ipv4
|
||||
- interfaces[].ipv4.subnet
|
||||
|
||||
We only keep interfaces[].ipv4.subnet (the leaf).
|
||||
"""
|
||||
leaf_properties = set()
|
||||
|
||||
for prop in properties:
|
||||
# Check if this property is a prefix of any other property
|
||||
is_container = any(
|
||||
other != prop and other.startswith(prop + ".")
|
||||
for other in properties
|
||||
)
|
||||
|
||||
if not is_container:
|
||||
leaf_properties.add(prop)
|
||||
|
||||
return leaf_properties
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract property paths from uCentral schema (JSON or YAML)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Extract from JSON schema file (included in repo)
|
||||
python3 extract-schema-properties.py ../../config-samples/ucentral.schema.pretty.json
|
||||
|
||||
# Extract from YAML schema directory (ols-ucentral-schema repo)
|
||||
python3 extract-schema-properties.py /path/to/ols-ucentral-schema/schema ucentral.yml
|
||||
|
||||
# Filter by prefix (works with both JSON and YAML)
|
||||
python3 extract-schema-properties.py schema.json --filter switch --filter ethernet
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"schema_path",
|
||||
help="Path to JSON schema file OR directory containing YAML files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"root_schema",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Root schema filename (required for YAML, e.g., ucentral.yml)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filter",
|
||||
action="append",
|
||||
help="Filter properties by prefix (can specify multiple times)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-containers",
|
||||
action="store_true",
|
||||
help="Include container properties (not just leaves)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-sort",
|
||||
action="store_true",
|
||||
help="Don't sort output"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
schema_path = Path(args.schema_path)
|
||||
if not schema_path.exists():
|
||||
print(f"ERROR: Schema path not found: {schema_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Determine if JSON file or YAML directory
|
||||
if schema_path.is_file() and schema_path.suffix in ['.json', '.JSON']:
|
||||
# JSON schema file
|
||||
print(f"Loading JSON schema from {schema_path}...", file=sys.stderr)
|
||||
try:
|
||||
with open(schema_path) as f:
|
||||
root_schema = json.load(f)
|
||||
except json.JSONDecodeError as exc:
|
||||
print(f"ERROR loading JSON: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Resolve all $ref references (including internal #/$defs/)
|
||||
print("Resolving schema references...", file=sys.stderr)
|
||||
resolved_schema = resolve_schema(root_schema, schema_path.parent, {}, 0, root_schema)
|
||||
|
||||
elif schema_path.is_dir():
|
||||
# YAML schema directory
|
||||
if not args.root_schema:
|
||||
print("ERROR: For YAML schema directory, you must specify root schema filename", file=sys.stderr)
|
||||
print("Example: python3 extract-schema-properties.py /path/to/schema ucentral.yml", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
root_schema_path = schema_path / args.root_schema
|
||||
if not root_schema_path.exists():
|
||||
print(f"ERROR: Root schema not found: {root_schema_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Loading YAML schema from {root_schema_path}...", file=sys.stderr)
|
||||
|
||||
# Load root schema
|
||||
loaded_cache = {}
|
||||
root_schema = schema_load(schema_path, args.root_schema, loaded_cache)
|
||||
|
||||
# Resolve all $ref references
|
||||
print("Resolving schema references...", file=sys.stderr)
|
||||
resolved_schema = resolve_schema(root_schema, schema_path, loaded_cache)
|
||||
|
||||
else:
|
||||
print(f"ERROR: Schema path must be either JSON file or YAML directory: {schema_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Extract properties
|
||||
print("Extracting properties...", file=sys.stderr)
|
||||
properties = extract_properties(resolved_schema)
|
||||
|
||||
# Filter if requested
|
||||
if args.filter:
|
||||
properties = filter_properties(properties, args.filter)
|
||||
|
||||
# Remove containers unless explicitly requested
|
||||
if not args.include_containers:
|
||||
properties = exclude_containers(properties)
|
||||
|
||||
# Output
|
||||
if not args.no_sort:
|
||||
properties = sorted(properties)
|
||||
|
||||
for prop in properties:
|
||||
print(prop)
|
||||
|
||||
# Print summary to stderr
|
||||
print(f"\nExtracted {len(properties)} leaf properties from schema", file=sys.stderr)
|
||||
if args.filter:
|
||||
print(f"Filtered by: {', '.join(args.filter)}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
88
tests/tools/fetch-schema.sh
Executable file
88
tests/tools/fetch-schema.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Schema Repository Helper
|
||||
#
|
||||
# Simple helper script to fetch the uCentral schema from GitHub.
|
||||
# This repository includes default schema files in config-samples/, so fetching
|
||||
# is optional and only needed when updating to a newer schema version.
|
||||
#
|
||||
# Usage:
|
||||
# ./fetch-schema.sh [BRANCH]
|
||||
#
|
||||
# Examples:
|
||||
# ./fetch-schema.sh # Clone using 'main' branch
|
||||
# ./fetch-schema.sh release-1.0 # Clone specific branch
|
||||
# ./fetch-schema.sh --help # Show this help
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
SCHEMA_REPO="https://github.com/Telecominfraproject/ols-ucentral-schema.git"
|
||||
SCHEMA_DIR="../../ols-ucentral-schema"
|
||||
DEFAULT_BRANCH="main"
|
||||
|
||||
# Show help
|
||||
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
||||
grep '^#' "$0" | sed 's/^# \?//'
|
||||
echo ""
|
||||
echo -e "${BLUE}Repository Information:${NC}"
|
||||
echo " Location: $SCHEMA_REPO"
|
||||
echo " Clone to: $SCHEMA_DIR"
|
||||
echo ""
|
||||
echo -e "${BLUE}Manual Alternative:${NC}"
|
||||
echo " cd tests/tools"
|
||||
echo " git clone $SCHEMA_REPO"
|
||||
echo " cd ols-ucentral-schema && git checkout <branch>"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Determine branch
|
||||
BRANCH="${1:-$DEFAULT_BRANCH}"
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}uCentral Schema Fetcher${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Repository:${NC} $SCHEMA_REPO"
|
||||
echo -e "${BLUE}Branch:${NC} $BRANCH"
|
||||
echo -e "${BLUE}Target:${NC} $SCHEMA_DIR"
|
||||
echo ""
|
||||
|
||||
# Check if directory exists
|
||||
if [[ -d "$SCHEMA_DIR" ]]; then
|
||||
echo -e "${YELLOW}⚠️ Schema directory already exists: $SCHEMA_DIR${NC}"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " 1. Update existing clone: cd $SCHEMA_DIR && git pull"
|
||||
echo " 2. Switch branch: cd $SCHEMA_DIR && git checkout <branch>"
|
||||
echo " 3. Remove and re-clone: rm -rf $SCHEMA_DIR && ./fetch-schema.sh $BRANCH"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clone the repository
|
||||
echo -e "${BLUE}ℹ️ Cloning schema repository...${NC}"
|
||||
git clone --branch "$BRANCH" --depth 1 "$SCHEMA_REPO" "$SCHEMA_DIR"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Schema cloned successfully${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Schema location:${NC} $SCHEMA_DIR"
|
||||
echo -e "${BLUE}Branch:${NC} $(cd "$SCHEMA_DIR" && git rev-parse --abbrev-ref HEAD)"
|
||||
echo -e "${BLUE}Commit:${NC} $(cd "$SCHEMA_DIR" && git rev-parse --short HEAD)"
|
||||
echo ""
|
||||
echo -e "${BLUE}Next steps:${NC}"
|
||||
echo " # Extract properties from schema"
|
||||
echo " python3 extract-schema-properties.py $SCHEMA_DIR/schema ucentral.yml"
|
||||
echo ""
|
||||
echo " # See full workflow in documentation"
|
||||
echo " cat ../MAINTENANCE.md"
|
||||
echo ""
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Find line numbers where properties are accessed in proto.c.
|
||||
|
||||
This script searches proto.c for cJSON property access patterns and returns
|
||||
the line number where each property is first accessed.
|
||||
|
||||
Usage:
|
||||
python3 find-property-line-numbers.py <proto.c> <property-path>
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
def extract_json_key(property_path: str) -> str:
|
||||
"""
|
||||
Extract the JSON key from a property path.
|
||||
|
||||
Args:
|
||||
property_path: Full path like "ethernet[].enabled" or "unit.hostname"
|
||||
|
||||
Returns:
|
||||
JSON key like "enabled" or "hostname"
|
||||
"""
|
||||
# Remove array brackets
|
||||
path = property_path.replace('[]', '')
|
||||
|
||||
# Get last component
|
||||
if '.' in path:
|
||||
return path.split('.')[-1]
|
||||
return path
|
||||
|
||||
|
||||
def find_function_containing_line(proto_lines: list, target_line: int) -> Optional[str]:
|
||||
"""
|
||||
Find which function contains a given line number.
|
||||
|
||||
Args:
|
||||
proto_lines: List of lines from proto.c
|
||||
target_line: Line number to find (1-indexed)
|
||||
|
||||
Returns:
|
||||
Function name or None if not found
|
||||
"""
|
||||
# Search backwards from target line to find function definition
|
||||
for i in range(target_line - 1, -1, -1):
|
||||
line = proto_lines[i]
|
||||
|
||||
# Look for function definition patterns
|
||||
# static int cfg_*_parse(...)
|
||||
# static void cfg_*_parse(...)
|
||||
# TEST_STATIC struct plat_cfg *cfg_parse(...)
|
||||
match = re.search(r'(static|TEST_STATIC)\s+\w+\s+\*?(\w+)\s*\(', line)
|
||||
if match:
|
||||
return match.group(2)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def find_property_line(proto_file: str, property_path: str) -> Tuple[Optional[int], Optional[str]]:
|
||||
"""
|
||||
Find the line number and function where a property is accessed in proto.c.
|
||||
|
||||
Args:
|
||||
proto_file: Path to proto.c
|
||||
property_path: Property path like "ethernet[].enabled"
|
||||
|
||||
Returns:
|
||||
Tuple of (line_number, function_name) or (None, None) if not found
|
||||
"""
|
||||
json_key = extract_json_key(property_path)
|
||||
|
||||
with open(proto_file, 'r') as f:
|
||||
proto_lines = f.readlines()
|
||||
|
||||
# Search patterns for cJSON property access
|
||||
patterns = [
|
||||
# Most common: cJSON_GetObjectItemCaseSensitive(obj, "key")
|
||||
rf'cJSON_GetObjectItemCaseSensitive\([^,]+,\s*"{re.escape(json_key)}"\)',
|
||||
|
||||
# Also check for cJSON_GetObjectItem (without CaseSensitive)
|
||||
rf'cJSON_GetObjectItem\([^,]+,\s*"{re.escape(json_key)}"\)',
|
||||
|
||||
# Check for string literal in assignments
|
||||
rf'["\']' + re.escape(json_key) + rf'["\']',
|
||||
]
|
||||
|
||||
# Search for first occurrence
|
||||
for i, line in enumerate(proto_lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
# Found it! Get the function name
|
||||
func_name = find_function_containing_line(proto_lines, i)
|
||||
return (i, func_name)
|
||||
|
||||
return (None, None)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: find-property-line-numbers.py <proto.c> <property-path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
proto_file = sys.argv[1]
|
||||
property_path = sys.argv[2]
|
||||
|
||||
line_num, func_name = find_property_line(proto_file, property_path)
|
||||
|
||||
if line_num:
|
||||
if func_name:
|
||||
print(f"{property_path}: line {line_num} in {func_name}()")
|
||||
else:
|
||||
print(f"{property_path}: line {line_num} (function unknown)")
|
||||
else:
|
||||
print(f"{property_path}: NOT FOUND")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
233
tests/tools/generate-database-from-schema.py
Executable file
233
tests/tools/generate-database-from-schema.py
Executable file
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate complete property database from schema.
|
||||
|
||||
This script takes ALL properties from the schema and generates a complete
|
||||
property database with line numbers from the source code.
|
||||
|
||||
Usage:
|
||||
python3 generate-database-from-schema.py <source-file> <schema-properties-file> <output-file>
|
||||
|
||||
Example:
|
||||
# For base database (proto.c)
|
||||
python3 generate-database-from-schema.py \
|
||||
../../src/ucentral-client/proto.c \
|
||||
/tmp/all-schema-properties.txt \
|
||||
/tmp/base-database-new.c
|
||||
|
||||
# For platform database (plat-gnma.c)
|
||||
python3 generate-database-from-schema.py \
|
||||
../../src/ucentral-client/platform/brcm-sonic/plat-gnma.c \
|
||||
/tmp/all-schema-properties.txt \
|
||||
/tmp/platform-database-new.c
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Dict
|
||||
|
||||
def extract_json_key(property_path: str) -> str:
|
||||
"""Extract the JSON key from a property path."""
|
||||
# Remove array brackets first
|
||||
path = property_path.replace('[]', '')
|
||||
|
||||
# Get last component
|
||||
if '.' in path:
|
||||
key = path.split('.')[-1]
|
||||
else:
|
||||
key = path
|
||||
|
||||
# Return the key - it may contain hyphens which are valid in JSON keys
|
||||
return key
|
||||
|
||||
def find_function_containing_line(source_lines: list, target_line: int) -> Optional[str]:
|
||||
"""
|
||||
Find which function contains a given line number.
|
||||
|
||||
Searches backward from target line to find the nearest function definition.
|
||||
Handles multiple function definition styles:
|
||||
static int function_name(params) # Single line
|
||||
static int # Return type on one line
|
||||
function_name(params) # Function name on next line
|
||||
"""
|
||||
for i in range(target_line - 1, -1, -1):
|
||||
line_raw = source_lines[i]
|
||||
line = line_raw.strip()
|
||||
|
||||
# Skip empty lines
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# IMPORTANT: Function definitions must start at column 0 (or only whitespace before)
|
||||
# This excludes indented function calls inside function bodies
|
||||
|
||||
# Pattern 1: Function name with opening paren on its own line (line 2 of split definition)
|
||||
# Example: "cfg_ethernet_ieee8021x_parse(cJSON *ieee8021x, struct plat_port *port)"
|
||||
# Must be at column 0 (no leading whitespace in original line)
|
||||
if line_raw[0] not in (' ', '\t'):
|
||||
match = re.match(r'^([a-z_][a-z0-9_]*)\s*\(', line, re.IGNORECASE)
|
||||
if match:
|
||||
func_name = match.group(1)
|
||||
# Check if previous line has return type (static int, void, etc.)
|
||||
if i > 0:
|
||||
prev_line = source_lines[i - 1].strip()
|
||||
# If previous line looks like a return type, this is a function definition
|
||||
# Examples: "static int", "void", "static char *", "struct foo"
|
||||
if re.match(r'^(static\s+)?(\w+\s*\**\s*)+$', prev_line):
|
||||
# Make sure it's not followed by semicolon (declaration)
|
||||
# Check next few lines for opening brace or statements
|
||||
for j in range(i + 1, min(i + 5, len(source_lines))):
|
||||
next_line = source_lines[j].strip()
|
||||
if next_line == '{' or (next_line and not next_line.endswith(';')):
|
||||
return func_name
|
||||
if next_line.endswith(';'):
|
||||
break # It's a declaration
|
||||
|
||||
# Pattern 2: Complete function definition on one line
|
||||
# Example: "static int cfg_ethernet_poe_parse(cJSON *poe,"
|
||||
match = re.match(r'^(static\s+)?(\w+\s+\**)+([a-z_][a-z0-9_]*)\s*\(', line, re.IGNORECASE)
|
||||
if match:
|
||||
func_name = match.group(3)
|
||||
# Check it's not followed by a semicolon (declaration)
|
||||
for j in range(i + 1, min(i + 10, len(source_lines))):
|
||||
next_line = source_lines[j].strip()
|
||||
if ')' in next_line:
|
||||
if next_line.endswith(';') or (j + 1 < len(source_lines) and source_lines[j + 1].strip() == ';'):
|
||||
break # It's a declaration
|
||||
return func_name
|
||||
|
||||
return None
|
||||
|
||||
def find_property_in_source(source_file: Path, property_path: str) -> Tuple[Optional[int], Optional[str]]:
|
||||
"""
|
||||
Find where a property is accessed in source file.
|
||||
|
||||
Returns:
|
||||
(line_number, function_name) or (None, None) if not found
|
||||
"""
|
||||
json_key = extract_json_key(property_path)
|
||||
|
||||
try:
|
||||
with open(source_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
except Exception as e:
|
||||
print(f"Error reading {source_file}: {e}", file=sys.stderr)
|
||||
return None, None
|
||||
|
||||
# Search patterns for cJSON property access
|
||||
patterns = [
|
||||
rf'cJSON_GetObjectItem\s*\([^,]+,\s*"{json_key}"\)',
|
||||
rf'cJSON_GetObjectItemCaseSensitive\s*\([^,]+,\s*"{json_key}"\)',
|
||||
rf'cJSON_GetStringValue\s*\(.*"{json_key}".*\)',
|
||||
rf'cJSON_GetNumberValue\s*\(.*"{json_key}".*\)',
|
||||
rf'cJSON_IsTrue\s*\(.*"{json_key}".*\)',
|
||||
rf'cJSON_IsFalse\s*\(.*"{json_key}".*\)',
|
||||
]
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
# Found it!
|
||||
function = find_function_containing_line(lines, line_num)
|
||||
return line_num, function
|
||||
|
||||
return None, None
|
||||
|
||||
def generate_database_entry(property_path: str, line_num: Optional[int],
|
||||
function: Optional[str], source_file: str) -> str:
|
||||
"""Generate a C database entry for a property."""
|
||||
if line_num and function:
|
||||
status = "PROP_CONFIGURED"
|
||||
description = f"Parsed in {function}()"
|
||||
else:
|
||||
status = "PROP_CONFIGURED"
|
||||
line_num = 0
|
||||
function = "NULL"
|
||||
description = "Not yet implemented"
|
||||
|
||||
return f' {{"{property_path}", {status}, "{source_file}", "{function}", {line_num}, "{description}"}},'
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
source_file = Path(sys.argv[1])
|
||||
properties_file = Path(sys.argv[2])
|
||||
output_file = Path(sys.argv[3])
|
||||
|
||||
if not source_file.exists():
|
||||
print(f"Error: Source file not found: {source_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not properties_file.exists():
|
||||
print(f"Error: Properties file not found: {properties_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Read all properties
|
||||
with open(properties_file, 'r') as f:
|
||||
properties = [line.strip() for line in f if line.strip()]
|
||||
|
||||
print(f"Processing {len(properties)} properties from schema...", file=sys.stderr)
|
||||
print(f"Searching in: {source_file}", file=sys.stderr)
|
||||
|
||||
# Find line numbers for all properties
|
||||
results = {}
|
||||
found_count = 0
|
||||
not_found_count = 0
|
||||
|
||||
for i, prop in enumerate(properties, 1):
|
||||
if i % 50 == 0:
|
||||
print(f" Processed {i}/{len(properties)} properties...", file=sys.stderr)
|
||||
|
||||
line_num, function = find_property_in_source(source_file, prop)
|
||||
results[prop] = (line_num, function)
|
||||
|
||||
if line_num:
|
||||
found_count += 1
|
||||
else:
|
||||
not_found_count += 1
|
||||
|
||||
print(f"\nResults:", file=sys.stderr)
|
||||
print(f" Found: {found_count} properties", file=sys.stderr)
|
||||
print(f" Not found: {not_found_count} properties", file=sys.stderr)
|
||||
print(f" Total: {len(properties)} properties", file=sys.stderr)
|
||||
|
||||
# Generate database
|
||||
source_filename = source_file.name
|
||||
database_entries = []
|
||||
|
||||
for prop in sorted(properties):
|
||||
line_num, function = results[prop]
|
||||
entry = generate_database_entry(prop, line_num, function, source_filename)
|
||||
database_entries.append(entry)
|
||||
|
||||
# Write output
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(f"/*\n")
|
||||
f.write(f" * Property Database Generated from Schema\n")
|
||||
f.write(f" *\n")
|
||||
f.write(f" * Source: {source_file}\n")
|
||||
f.write(f" * Properties: {len(properties)} from schema\n")
|
||||
f.write(f" * Found: {found_count} implemented\n")
|
||||
f.write(f" * Not found: {not_found_count} not yet implemented\n")
|
||||
f.write(f" *\n")
|
||||
f.write(f" * This database tracks ALL properties in the uCentral schema,\n")
|
||||
f.write(f" * whether implemented or not. Properties with line_number=0\n")
|
||||
f.write(f" * are in the schema but not yet implemented in the code.\n")
|
||||
f.write(f" */\n\n")
|
||||
f.write(f"static const struct property_metadata base_property_database[] = {{\n")
|
||||
|
||||
for entry in database_entries:
|
||||
f.write(entry + "\n")
|
||||
|
||||
f.write(f"\n /* Sentinel */\n")
|
||||
f.write(f' {{NULL, PROP_CONFIGURED, NULL, NULL, 0, NULL}}\n')
|
||||
f.write(f"}};\n")
|
||||
|
||||
print(f"\nDatabase written to: {output_file}", file=sys.stderr)
|
||||
print(f" Total entries: {len(database_entries)}", file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
275
tests/tools/generate-platform-database-from-schema.py
Executable file
275
tests/tools/generate-platform-database-from-schema.py
Executable file
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate platform property database from schema.
|
||||
|
||||
Platform code doesn't parse JSON directly - it receives structured data from proto.c
|
||||
and applies it to hardware. This script searches for platform application patterns:
|
||||
- config_*_apply() functions
|
||||
- plat_*_set() functions
|
||||
- gnma_*() hardware API calls
|
||||
|
||||
Usage:
|
||||
python3 generate-platform-database-from-schema.py <platform-file> <schema-properties> <output-file>
|
||||
|
||||
Example:
|
||||
python3 generate-platform-database-from-schema.py \
|
||||
../../src/ucentral-client/platform/brcm-sonic/plat-gnma.c \
|
||||
/tmp/all-schema-properties.txt \
|
||||
/tmp/platform-database-new.c
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Dict, List
|
||||
|
||||
def extract_property_components(property_path: str) -> Tuple[str, str, str]:
|
||||
"""
|
||||
Extract components from property path.
|
||||
|
||||
Returns:
|
||||
(top_level, mid_level, leaf) - e.g., ("ethernet", "poe", "admin-mode")
|
||||
"""
|
||||
path = property_path.replace('[]', '')
|
||||
parts = path.split('.')
|
||||
|
||||
top_level = parts[0] if len(parts) > 0 else ""
|
||||
mid_level = parts[1] if len(parts) > 1 else ""
|
||||
leaf = parts[-1] if len(parts) > 0 else ""
|
||||
|
||||
return top_level, mid_level, leaf
|
||||
|
||||
def find_function_containing_line(source_lines: list, target_line: int) -> Optional[str]:
|
||||
"""Find which function contains a given line number."""
|
||||
for i in range(target_line - 1, -1, -1):
|
||||
line = source_lines[i]
|
||||
# Look for function definition patterns
|
||||
match = re.search(r'^\s*(static\s+)?(int|void|struct\s+\w+\s+\*?)\s+(\w+)\s*\([^)]*\)\s*{?\s*$', line)
|
||||
if match:
|
||||
func_name = match.group(3)
|
||||
return func_name
|
||||
return None
|
||||
|
||||
def analyze_platform_code(source_file: Path) -> Dict[str, List[Tuple[int, str]]]:
|
||||
"""
|
||||
Analyze platform code to find configuration application patterns.
|
||||
|
||||
Returns:
|
||||
Dict mapping feature areas to list of (line_number, function_name) tuples
|
||||
"""
|
||||
try:
|
||||
with open(source_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
except Exception as e:
|
||||
print(f"Error reading {source_file}: {e}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
feature_map = {
|
||||
# STP/Loop Detection
|
||||
'loop-detection': [],
|
||||
'stp': [],
|
||||
'rstp': [],
|
||||
'mstp': [],
|
||||
|
||||
# VLAN
|
||||
'vlan': [],
|
||||
|
||||
# Port configuration
|
||||
'port': [],
|
||||
'ethernet': [],
|
||||
'speed': [],
|
||||
'duplex': [],
|
||||
'enabled': [],
|
||||
|
||||
# PoE
|
||||
'poe': [],
|
||||
'power': [],
|
||||
|
||||
# 802.1X
|
||||
'ieee8021x': [],
|
||||
'authenticator': [],
|
||||
'radius': [],
|
||||
|
||||
# DHCP
|
||||
'dhcp': [],
|
||||
'relay': [],
|
||||
|
||||
# Routing
|
||||
'routing': [],
|
||||
'route': [],
|
||||
'static': [],
|
||||
|
||||
# Multicast/IGMP
|
||||
'igmp': [],
|
||||
'multicast': [],
|
||||
'querier': [],
|
||||
|
||||
# Metrics
|
||||
'metrics': [],
|
||||
'health': [],
|
||||
'statistics': [],
|
||||
|
||||
# Services
|
||||
'syslog': [],
|
||||
'log': [],
|
||||
|
||||
# Port isolation
|
||||
'isolation': [],
|
||||
|
||||
# Unit/System
|
||||
'unit': [],
|
||||
'threshold': [],
|
||||
}
|
||||
|
||||
# Search for config_*_apply and plat_*_set functions
|
||||
function_patterns = [
|
||||
r'(config_\w+_apply|plat_\w+_set|plat_\w+_config)',
|
||||
]
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
# Find function definitions
|
||||
for pattern in function_patterns:
|
||||
match = re.search(r'^\s*(static\s+)?(int|void)\s+(' + pattern + r')\s*\(', line)
|
||||
if match:
|
||||
func_name = match.group(3)
|
||||
|
||||
# Map function to feature areas based on name
|
||||
func_lower = func_name.lower()
|
||||
|
||||
for feature, locations in feature_map.items():
|
||||
if feature in func_lower:
|
||||
locations.append((line_num, func_name))
|
||||
|
||||
return feature_map
|
||||
|
||||
def find_property_in_platform(property_path: str, feature_map: Dict[str, List[Tuple[int, str]]]) -> Tuple[Optional[int], Optional[str]]:
|
||||
"""
|
||||
Find which platform function likely handles this property.
|
||||
|
||||
Returns:
|
||||
(line_number, function_name) or (None, None) if not found
|
||||
"""
|
||||
top_level, mid_level, leaf = extract_property_components(property_path)
|
||||
|
||||
# Search feature map for matching functions
|
||||
search_terms = [leaf, mid_level, top_level]
|
||||
search_terms = [term for term in search_terms if term] # Remove empty
|
||||
|
||||
for term in search_terms:
|
||||
term_lower = term.replace('-', '_').replace('-', '').lower()
|
||||
|
||||
# Check if any feature matches
|
||||
for feature, locations in feature_map.items():
|
||||
if term_lower in feature or feature in term_lower:
|
||||
if locations:
|
||||
# Return first matching function
|
||||
return locations[0]
|
||||
|
||||
return None, None
|
||||
|
||||
def generate_database_entry(property_path: str, line_num: Optional[int],
|
||||
function: Optional[str], source_file: str) -> str:
|
||||
"""Generate a C database entry for a property."""
|
||||
if line_num and function:
|
||||
status = "PROP_CONFIGURED"
|
||||
description = f"Applied in {function}()"
|
||||
else:
|
||||
status = "PROP_CONFIGURED"
|
||||
line_num = 0
|
||||
function = "NULL"
|
||||
description = "Not yet implemented in platform"
|
||||
|
||||
return f' {{"{property_path}", {status}, "{source_file}", "{function}", {line_num}, "{description}"}},'
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
source_file = Path(sys.argv[1])
|
||||
properties_file = Path(sys.argv[2])
|
||||
output_file = Path(sys.argv[3])
|
||||
|
||||
if not source_file.exists():
|
||||
print(f"Error: Source file not found: {source_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not properties_file.exists():
|
||||
print(f"Error: Properties file not found: {properties_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Read all properties
|
||||
with open(properties_file, 'r') as f:
|
||||
properties = [line.strip() for line in f if line.strip()]
|
||||
|
||||
print(f"Processing {len(properties)} properties from schema...", file=sys.stderr)
|
||||
print(f"Analyzing platform code: {source_file}", file=sys.stderr)
|
||||
|
||||
# Analyze platform code to find configuration functions
|
||||
feature_map = analyze_platform_code(source_file)
|
||||
|
||||
total_functions = sum(len(locations) for locations in feature_map.values())
|
||||
print(f"Found {total_functions} configuration functions in platform code", file=sys.stderr)
|
||||
|
||||
# Find which function handles each property
|
||||
results = {}
|
||||
found_count = 0
|
||||
not_found_count = 0
|
||||
|
||||
for i, prop in enumerate(properties, 1):
|
||||
if i % 50 == 0:
|
||||
print(f" Processed {i}/{len(properties)} properties...", file=sys.stderr)
|
||||
|
||||
line_num, function = find_property_in_platform(prop, feature_map)
|
||||
results[prop] = (line_num, function)
|
||||
|
||||
if line_num:
|
||||
found_count += 1
|
||||
else:
|
||||
not_found_count += 1
|
||||
|
||||
print(f"\nResults:", file=sys.stderr)
|
||||
print(f" Found: {found_count} properties", file=sys.stderr)
|
||||
print(f" Not found: {not_found_count} properties", file=sys.stderr)
|
||||
print(f" Total: {len(properties)} properties", file=sys.stderr)
|
||||
|
||||
# Generate database
|
||||
source_filename = source_file.name
|
||||
database_entries = []
|
||||
|
||||
for prop in sorted(properties):
|
||||
line_num, function = results[prop]
|
||||
entry = generate_database_entry(prop, line_num, function, source_filename)
|
||||
database_entries.append(entry)
|
||||
|
||||
# Write output
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(f"/*\n")
|
||||
f.write(f" * Platform Property Database Generated from Schema\n")
|
||||
f.write(f" *\n")
|
||||
f.write(f" * Platform: brcm-sonic\n")
|
||||
f.write(f" * Source: {source_file}\n")
|
||||
f.write(f" * Properties: {len(properties)} from schema\n")
|
||||
f.write(f" * Found: {found_count} potentially implemented\n")
|
||||
f.write(f" * Not found: {not_found_count} not yet implemented\n")
|
||||
f.write(f" *\n")
|
||||
f.write(f" * This database tracks ALL properties in the uCentral schema.\n")
|
||||
f.write(f" * Platform code doesn't parse JSON - it applies structured config\n")
|
||||
f.write(f" * from proto.c to hardware. Functions are matched by feature area.\n")
|
||||
f.write(f" *\n")
|
||||
f.write(f" * Properties with line_number=0 are not yet implemented in platform.\n")
|
||||
f.write(f" */\n\n")
|
||||
f.write(f"static const struct property_metadata platform_property_database_brcm_sonic[] = {{\n")
|
||||
|
||||
for entry in database_entries:
|
||||
f.write(entry + "\n")
|
||||
|
||||
f.write(f"\n /* Sentinel */\n")
|
||||
f.write(f' {{NULL, PROP_CONFIGURED, NULL, NULL, 0, NULL}}\n')
|
||||
f.write(f"}};\n")
|
||||
|
||||
print(f"\nDatabase written to: {output_file}", file=sys.stderr)
|
||||
print(f" Total entries: {len(database_entries)}", file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Extract all property paths from JSON configuration files.
|
||||
|
||||
This script recursively parses JSON config files and extracts all property paths,
|
||||
converting array indices to [] notation for use in the property database.
|
||||
|
||||
Usage:
|
||||
python3 generate-property-database.py <config-file1> [config-file2 ...]
|
||||
python3 generate-property-database.py ../../config-samples/*.json
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Set, Any, List
|
||||
|
||||
|
||||
def extract_properties(obj: Any, prefix: str = "", properties: Set[str] = None) -> Set[str]:
|
||||
"""
|
||||
Recursively extract all property paths from a JSON object.
|
||||
|
||||
Args:
|
||||
obj: JSON object (dict, list, or primitive)
|
||||
prefix: Current path prefix
|
||||
properties: Set to accumulate property paths
|
||||
|
||||
Returns:
|
||||
Set of property paths in format like "unit.hostname", "ethernet[].enabled"
|
||||
"""
|
||||
if properties is None:
|
||||
properties = set()
|
||||
|
||||
if isinstance(obj, dict):
|
||||
for key, value in obj.items():
|
||||
# Build the path
|
||||
if prefix:
|
||||
path = f"{prefix}.{key}"
|
||||
else:
|
||||
path = key
|
||||
|
||||
# Add this property
|
||||
properties.add(path)
|
||||
|
||||
# Recurse into the value
|
||||
extract_properties(value, path, properties)
|
||||
|
||||
elif isinstance(obj, list):
|
||||
# For arrays, use [] notation and process first element as template
|
||||
# e.g., ethernet[0] becomes ethernet[]
|
||||
if obj: # If array is not empty
|
||||
# Add array itself
|
||||
array_path = prefix + "[]"
|
||||
properties.add(array_path)
|
||||
|
||||
# Process first element to get array item properties
|
||||
extract_properties(obj[0], array_path, properties)
|
||||
|
||||
# For primitives (str, int, bool, null), just return - already added
|
||||
return properties
|
||||
|
||||
|
||||
def normalize_property_path(path: str) -> str:
|
||||
"""
|
||||
Normalize property path by converting [N] to [].
|
||||
|
||||
Args:
|
||||
path: Property path like "ethernet[0].enabled"
|
||||
|
||||
Returns:
|
||||
Normalized path like "ethernet[].enabled"
|
||||
"""
|
||||
import re
|
||||
# Replace [N] with []
|
||||
return re.sub(r'\[\d+\]', '[]', path)
|
||||
|
||||
|
||||
def extract_from_file(filepath: str) -> Set[str]:
|
||||
"""
|
||||
Extract all property paths from a single JSON file.
|
||||
|
||||
Args:
|
||||
filepath: Path to JSON config file
|
||||
|
||||
Returns:
|
||||
Set of property paths found in the file
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
properties = extract_properties(config)
|
||||
|
||||
# Normalize all paths (convert [N] to [])
|
||||
normalized = {normalize_property_path(p) for p in properties}
|
||||
|
||||
return normalized
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"ERROR: Failed to parse {filepath}: {e}", file=sys.stderr)
|
||||
return set()
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to read {filepath}: {e}", file=sys.stderr)
|
||||
return set()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: generate-property-database.py <config-file1> [config-file2 ...]", file=sys.stderr)
|
||||
print(" generate-property-database.py ../../config-samples/*.json", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Collect properties from all config files
|
||||
all_properties: Set[str] = set()
|
||||
files_processed = 0
|
||||
|
||||
for filepath in sys.argv[1:]:
|
||||
# Skip invalid/negative test configs
|
||||
if 'invalid' in filepath.lower():
|
||||
continue
|
||||
|
||||
path = Path(filepath)
|
||||
if not path.exists():
|
||||
print(f"WARNING: File not found: {filepath}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
if not path.suffix == '.json':
|
||||
continue
|
||||
|
||||
properties = extract_from_file(filepath)
|
||||
if properties:
|
||||
all_properties.update(properties)
|
||||
files_processed += 1
|
||||
print(f"Processed {path.name}: {len(properties)} properties", file=sys.stderr)
|
||||
|
||||
print(f"\nTotal: {len(all_properties)} unique properties from {files_processed} files\n", file=sys.stderr)
|
||||
|
||||
# Output sorted list of properties
|
||||
for prop in sorted(all_properties):
|
||||
print(prop)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,200 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rebuild the property database with line numbers from config files.
|
||||
|
||||
This script:
|
||||
1. Extracts all properties from config files
|
||||
2. Finds line numbers in proto.c for each property
|
||||
3. Generates the property_database[] array in C format
|
||||
|
||||
Usage:
|
||||
python3 rebuild-property-database.py <proto.c> <config-files...>
|
||||
python3 rebuild-property-database.py proto.c ../../config-samples/*.json
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Set, Dict, Tuple, Optional
|
||||
import subprocess
|
||||
|
||||
|
||||
def run_property_extractor(config_files: list) -> Set[str]:
|
||||
"""
|
||||
Run generate-property-database.py to extract properties from config files.
|
||||
|
||||
Args:
|
||||
config_files: List of config file paths
|
||||
|
||||
Returns:
|
||||
Set of property paths
|
||||
"""
|
||||
try:
|
||||
# Run the property extractor
|
||||
result = subprocess.run(
|
||||
['python3', 'generate-property-database.py'] + config_files,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
# Parse output (one property per line)
|
||||
properties = set()
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
properties.add(line)
|
||||
|
||||
return properties
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"ERROR: Failed to extract properties: {e}", file=sys.stderr)
|
||||
return set()
|
||||
|
||||
|
||||
def find_property_line(proto_file: str, property_path: str) -> Tuple[Optional[int], Optional[str]]:
|
||||
"""
|
||||
Find line number and function for a property.
|
||||
|
||||
Args:
|
||||
proto_file: Path to proto.c
|
||||
property_path: Property path like "ethernet[].enabled"
|
||||
|
||||
Returns:
|
||||
Tuple of (line_number, function_name) or (None, None)
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['python3', 'find-property-line-numbers.py', proto_file, property_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
output = result.stdout.strip()
|
||||
|
||||
# Parse output: "property: line N in function()"
|
||||
match = re.match(r'.+: line (\d+) in (\w+)\(\)', output)
|
||||
if match:
|
||||
return (int(match.group(1)), match.group(2))
|
||||
|
||||
return (None, None)
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
return (None, None)
|
||||
|
||||
|
||||
def classify_property(property_path: str) -> str:
|
||||
"""
|
||||
Determine the status of a property.
|
||||
|
||||
Args:
|
||||
property_path: Property path
|
||||
|
||||
Returns:
|
||||
Property status: PROP_CONFIGURED, PROP_SYSTEM, PROP_UNKNOWN, etc.
|
||||
"""
|
||||
# System properties (exact matches only)
|
||||
if property_path in ['uuid', 'strict', 'public_ip_lookup', 'third-party']:
|
||||
return 'PROP_SYSTEM'
|
||||
|
||||
# Container objects (top-level only, no nested properties)
|
||||
top_level_containers = [
|
||||
'ethernet', 'ethernet[]',
|
||||
'unit', 'globals', 'interfaces', 'interfaces[]', 'services', 'metrics', 'switch',
|
||||
'ssids', 'ssids[]',
|
||||
'config-raw', 'config-raw[]'
|
||||
]
|
||||
if property_path in top_level_containers:
|
||||
return 'PROP_SYSTEM'
|
||||
|
||||
# All other properties are configured
|
||||
return 'PROP_CONFIGURED'
|
||||
|
||||
|
||||
def get_property_notes(property_path: str, line_num: Optional[int]) -> str:
|
||||
"""
|
||||
Generate notes for a property.
|
||||
|
||||
Args:
|
||||
property_path: Property path
|
||||
line_num: Line number where property is accessed (or None)
|
||||
|
||||
Returns:
|
||||
Notes string
|
||||
"""
|
||||
if line_num is None:
|
||||
return "Property not found in proto.c (may be platform-specific or unimplemented)"
|
||||
|
||||
# Container objects
|
||||
if property_path in ['ethernet', 'unit', 'interfaces', 'services', 'globals', 'switch', 'metrics']:
|
||||
return "Container object (not a leaf value)"
|
||||
|
||||
if property_path.endswith('[]') and not '.' in property_path.replace('[]', ''):
|
||||
return "Array container"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: rebuild-property-database.py <proto.c> <config-files...>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
proto_file = sys.argv[1]
|
||||
config_files = [f for f in sys.argv[2:] if 'invalid' not in f.lower()]
|
||||
|
||||
print(f"# Extracting properties from {len(config_files)} config files...", file=sys.stderr)
|
||||
properties = run_property_extractor(config_files)
|
||||
|
||||
if not properties:
|
||||
print("ERROR: No properties extracted", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"# Found {len(properties)} unique properties", file=sys.stderr)
|
||||
print(f"# Finding line numbers in {proto_file}...", file=sys.stderr)
|
||||
|
||||
# Build property database entries
|
||||
entries = []
|
||||
found_count = 0
|
||||
|
||||
for prop in sorted(properties):
|
||||
line_num, func_name = find_property_line(proto_file, prop)
|
||||
|
||||
if line_num:
|
||||
found_count += 1
|
||||
|
||||
status = classify_property(prop)
|
||||
notes = get_property_notes(prop, line_num)
|
||||
|
||||
entries.append({
|
||||
'path': prop,
|
||||
'status': status,
|
||||
'file': 'proto.c',
|
||||
'function': func_name or 'cfg_parse',
|
||||
'line': line_num or 0,
|
||||
'notes': notes
|
||||
})
|
||||
|
||||
print(f"# Found line numbers for {found_count}/{len(properties)} properties", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
|
||||
# Generate C code
|
||||
print("/* Property database: " + str(len(entries)) + " entries mapping JSON paths to parsing status and source location */")
|
||||
print("static const struct property_metadata property_database[] = {")
|
||||
|
||||
for entry in entries:
|
||||
# Format: {"path", STATUS, "file", "function", line_number, "notes"},
|
||||
path = entry['path'].replace('"', '\\"')
|
||||
notes = entry['notes'].replace('"', '\\"')
|
||||
|
||||
print(f' {{"{path}", {entry["status"]}, "{entry["file"]}", "{entry["function"]}", {entry["line"]}, "{notes}"}},')
|
||||
|
||||
# Sentinel
|
||||
print(" /* Sentinel */")
|
||||
print(" {NULL, PROP_CONFIGURED, NULL, NULL, 0, NULL}")
|
||||
print("};")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Update test-config-parser.c with new property database.
|
||||
|
||||
This script replaces the property_database[] array in test-config-parser.c
|
||||
with the newly generated one from rebuild-property-database.py.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: update-test-config-parser.py <test-config-parser.c> <new-property-database.c>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
test_parser_file = sys.argv[1]
|
||||
new_database_file = sys.argv[2]
|
||||
|
||||
# Read the test-config-parser.c file
|
||||
with open(test_parser_file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Read the new property database
|
||||
with open(new_database_file, 'r') as f:
|
||||
new_database = f.read()
|
||||
|
||||
# Remove comment lines starting with #
|
||||
new_database_lines = [line for line in new_database.split('\n') if not line.startswith('#')]
|
||||
new_database_clean = '\n'.join(new_database_lines)
|
||||
|
||||
# Find and replace the property_database array
|
||||
# Pattern: from "/* Property database:" to "};" after the sentinel
|
||||
pattern = r'(/\* Property database:.*?\*/\s*static const struct property_metadata property_database\[\] = \{.*?/\* Sentinel \*/\s*\{NULL.*?\}\s*\};)'
|
||||
|
||||
match = re.search(pattern, content, re.DOTALL)
|
||||
|
||||
if not match:
|
||||
print("ERROR: Could not find property_database array in test-config-parser.c", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Replace with new database
|
||||
new_content = content[:match.start()] + new_database_clean + content[match.end():]
|
||||
|
||||
# Write back
|
||||
with open(test_parser_file, 'w') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print(f"SUCCESS: Updated {test_parser_file} with new property database", file=sys.stderr)
|
||||
print(f" Old database: {len(match.group(1).split(chr(10)))} lines", file=sys.stderr)
|
||||
print(f" New database: {len(new_database_clean.split(chr(10)))} lines", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 1,
|
||||
"patch": 0
|
||||
"major": 5,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user