A command-line utility to parse Oracle Unified Directory (OUD) proxy configuration files and print a human-readable load-balancing architecture diagram.
This tool targets Oracle Unified Directory Proxy (formerly Oracle Directory Server Enterprise Edition / Directory Proxy Server β "DPS") configurations specifically, identified by com.sun.dps.server.workflowelement.* Java classes (ProxyLdapWorkflowElement, LoadBalancingWorkflowElement, LDAPServerExtension).
It does not apply to a plain OUD Directory Server instance (one that stores data locally via a db-local-backend-workflow-element / JE backend rather than proxying to remote servers). Running it against such a config will produce an empty or near-empty diagram, since no proxy/LB workflow elements will be found.
| OUD Directory Server | OUD Proxy | |
|---|---|---|
| Data backend | Local (JE/DB) | None β routes to remote directories |
| Typical WE | db-local-backend-workflow-element |
proxy-ldap-workflow-element, load-balancing-workflow-element |
| Java namespace | org.opends.server.* only |
org.opends.server.* (shared base) + com.sun.dps.server.workflowelement.* |
| This tool | Not applicable | β Supported |
- Python 3.6+
- No external dependencies (standard library only β
re,base64,collections)
python oud_lb_diagram.py <path-to-config.ldif>
python oud_lb_diagram.py --version # print version and exit
python oud_lb_diagram.py <config> --output <file> # save diagram to file
python oud_lb_diagram.py <config> --no-tree # skip workflow tree(s), print only summary + backend table
python oud_lb_diagram.py <config> --anonymize # mask real backend IPs with RFC 5737 placeholders
python oud_lb_diagram.py <config> --format json # output the parsed model as JSON (pipeable β diagnostics go to stderr)
python oud_lb_diagram.py <config> --format json --anonymize | jq .model.extensions
python oud_lb_diagram.py <config> --output report.txt --no-tree --anonymize # combine flags freely--format json is the only alternative to the default text format. YAML
isn't offered β it isn't in the Python standard library and this project
has zero external dependencies β pipe the JSON through a converter
(yq, python -c "import sys,yaml,json; ...", etc.) if you need YAML.
If no argument is provided, the script looks for config.ldif in the current directory.
The script reads the OUD proxy config.ldif file and:
- Parses all LDIF entries β network groups, workflows, load-balancing workflow elements, proxy workflow elements, and backend extensions
- Builds the routing tree dynamically, at any depth, without hardcoded assumptions on the architecture
- Prints a text diagram showing:
- The full workflow element hierarchy from entry point down to backend IPs
- The load-balancing algorithm at each node (
PROPORTIONAL,FAILOVER,ROUND-ROBIN) - Per-operation weights for proportional nodes (e.g.
add/modify/delete:1 search/bind:0) - Per-operation priorities for failover nodes (e.g.
all:1orsearch/bind:1 add/modify:2) switch-back: ONflag where present- Backend IP, port, SSL port, SSL policy, connection pool size, credential mode
- Prints a backend servers table summarising all proxy WEs and their resolved extensions
- Prints a legend explaining the notation
- Adapts the diagram width automatically to the longest line of content (with safety bounds), so the frame never looks cramped or needlessly wide
- Parses LDIF robustly β handles RFC 4511 line folding (continuation lines), base64-encoded values, and case-insensitive DN references
- Warns on unresolved references β flags any workflow-element or extension DN that cannot be resolved in the parsed config
- Can anonymize backend IPs on demand (
--anonymize) β replaces real IPs with stable RFC 5737 documentation-range placeholders, useful when sharing diagrams outside the team - Highlights disabled components β proxy WEs and their backend extensions each track their own
ds-cfg-enabledstate; either one beingfalseshows a!! DISABLEDmarker in both the workflow tree and the backend servers table - Can emit structured JSON (
--format json) instead of the ASCII diagram, for scripting/automation β diagnostics go to stderr so stdout stays pure, pipeable JSON
[+] Parsed 316 LDIF entries from: config_proxyoud_test.ldif
[+] Found: 2 network group(s) 1 workflow(s) 6 LB WE(s) 6 proxy WE(s) 6 backend extension(s)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OUD PROXY β LOAD BALANCING ARCHITECTURE β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β NETWORK GROUPS β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β cn=network-group priority:1 enabled:true β workflow:workflowLB base-dn:dc=example,dc=com β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WORKFLOW TREE β workflowLB β base-dn: dc=example,dc=com β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ββ LB_MS [PROPORTIONAL] β
β ββ LB_MS_routeR weights: search/bind/compare/extended:1 add/modify/delete/modifydn:0 β
β β ββ LB-Slave [PROPORTIONAL] β
β β ββ LB-Slave_S1 weights: search/bind/add/modify/delete/compare/modifydn/extended:1 β
β β β ββ FO_S1 [FAILOVER switch-back:ON] β
β β β ββ FO_S1_routeS3 prio: all:1 β
β β β β ββ proxy-we5 β 198.51.100.21 port:389 SSL:1636 (always) cred:use-client-identity β
β β β ββ FO_S1_routeS4 prio: all:2 β
β β β β ββ proxy-we6 β 198.51.100.22 port:389 SSL:1636 (always) cred:use-client-identity β
β β β ββ FO_S1_routeS1 prio: all:3 β
β β β β ββ proxy-we3 β 198.51.100.11 port:389 SSL:1636 (always) cred:use-client-identity β
β β β ββ FO_S1_routeS2 prio: all:4 β
β β β ββ proxy-we4 β 198.51.100.12 port:389 SSL:1636 (always) cred:use-client-identity β
β β ββ LB-Slave_S2 weights: search/bind/add/modify/delete/compare/modifydn/extended:1 β
β β ββ FO_S2 [FAILOVER switch-back:ON] β
β β ββ FO_S2_routeS4 prio: all:1 β
β β β ββ proxy-we6 β 198.51.100.22 port:389 SSL:1636 (always) cred:use-client-identity β
β β ββ ... β
β ββ LB_MS_routeW weights: add/modify/delete/modifydn:1 search/bind/compare/extended:0 β
β ββ LB-Master [PROPORTIONAL] β
β ββ LB-Master_M weights: search/bind/add/modify/delete/compare/modifydn/extended:1 β
β ββ FO_M [FAILOVER switch-back:ON] β
β ββ FO_M_routeM2 prio: all:1 β
β β ββ proxy-we2 β 198.51.100.20 port:389 SSL:1636 (always) cred:use-client-identity β
β ββ FO_M_routeM1 prio: all:2 β
β ββ proxy-we1 β 198.51.100.10 port:389 SSL:1636 (always) cred:use-client-identity β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BACKEND SERVERS β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Extension WE IP Address Port SSL Policy Pool Cred-mode β
β -------------------------------------------------------------------------------------------------------- β
β proxy1 proxy-we1 198.51.100.10 389 1636 always 10000 use-client-identity β
β proxy2 proxy-we2 198.51.100.20 389 1636 always 10000 use-client-identity β
β proxy3 proxy-we3 198.51.100.11 389 1636 always 10000 use-client-identity β
β proxy4 proxy-we4 198.51.100.12 389 1636 always 10000 use-client-identity β
β proxy5 proxy-we5 198.51.100.21 389 1636 always 10000 use-client-identity β
β proxy6 proxy-we6 198.51.100.22 389 1636 always 10000 use-client-identity β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LEGEND β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PROPORTIONAL Distributes traffic by weight per operation type. β
β weights shown as ops:value β e.g. add/modify/delete:1 search/bind:0 β
β FAILOVER One active node at a time; lower [prio] = preferred. β
β switch-back:ON = auto-restore to primary when it recovers. β
β ROUND-ROBIN Cycles through available routes in order. β
β ββ <node> Leaf node = backend proxy WE resolved to IP:port. β
β cred-mode How client credentials are forwarded to the backend. β
β !! DISABLED ds-cfg-enabled: false on this WE or its backend extension β inactive. β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Note: the diagram frame width above adapts automatically to the longest line in the loaded config (bounded between
MIN_W=60andMAX_W=200columns) β your output may be narrower or wider depending on attribute lengths, DN depth, and IP formats.
If the config contains a network group without an associated workflow, or a workflow-element / extension reference that cannot be resolved, warnings are printed before the diagram:
[+] Parsed 316 LDIF entries from: config.ldif
[WARN] network-group "Network Group" references unknown workflow:
[WARN] proxy-we "proxy-we7" references unknown extension: cn=proxy7,cn=extensions,cn=config
[+] Found: 2 network group(s) 1 workflow(s) 6 LB WE(s) 7 proxy WE(s) 6 backend extension(s)
| Notation | Meaning |
|---|---|
[PROPORTIONAL] |
Traffic distributed by weight across routes |
[FAILOVER switch-back:ON] |
One active node at a time; lower prio = preferred; auto-restore to primary |
weights: add/modify:1 search:0 |
Operations with weight > 0 are routed here; 0 = blocked |
prio: all:1 |
All operation types have the same priority on this route |
prio: search/bind:1 add/modify:2 |
Per-operation priorities differ (route preferred for reads, backup for writes) |
β x.x.x.151 port:389 SSL:1636 |
Resolved backend IP and ports |
cred:use-client-identity |
Client credentials passed through as-is to the backend |
The script makes no assumptions about the number of levels, branch names, or routing semantics. It explores the tree generically and reports what is in the config. Semantic interpretation (e.g. which branch is WRITE vs READ) is left to the reader and can be inferred from the weights.
Supported load-balancing algorithm types:
PROPORTIONALβ weight-based distribution per operationFAILOVERβ single active node with priority-based fallbackROUND-ROBINβ sequential cycling (if present in config)
The LDIF parser is RFC 4511 compliant:
- Continuation lines (starting with a single space) are folded into the previous logical line
- Base64-encoded values (
attr:: <b64>) are decoded to UTF-8, with hex fallback for binary content - URL references (
attr:< <url>) are recognised and stored as-is - All DNs (entry keys and cross-references) are normalised to lowercase for consistent lookup, regardless of the original casing in the config; display labels still use the original-case
cnattribute value
- Only parses
LDAPServerExtensionbackends (OUD proxy mode) - Local backends (JE/DB), replication domains, virtual ACIs and other config objects are ignored
- Tested on OUD 11g/12c proxy config format
This project follows Semantic Versioning. See CHANGELOG.md for the full version history.
Check the installed version with:
python oud_lb_diagram.py --versionUnit test suites cover LDIF parsing, model extraction, and backend/replication reporting (66 tests total, no external dependencies β standard library unittest only):
python3 -m unittest test_oud_ldif_core.py -v # shared parser (14 tests)
python3 -m unittest test_oud_lb_diagram.py -v # diagram tool (38 tests)
python3 -m unittest test_oud_backend_report.py -v # backend report tool (6 tests)
python3 -m unittest test_oud_config_lint.py -v # config linter (8 tests)
# or run everything at once:
python3 -m unittest discover -p "test_*.py" -voud_lb_diagram.py, oud_config_type.py, oud_backend_report.py, and
oud_config_lint.py all build on a shared parsing layer,
oud_ldif_core.py, which contains the generic LDIF parser
(parse_ldif) and small DN/attribute utilities (first, cn_of) β
nothing specific to any one tool's object model. All files must stay in
the same directory:
oud_ldif_core.py
(shared: parse_ldif, first, cn_of β no dependency on the tools below)
β² β² β² β²
β β β β
oud_lb_diagram.py oud_config_type.py oud_backend_report.py oud_config_lint.py
(proxy load (instance (Directory Server (validator β
balancing classifier) backend/replication imports models from
diagram) report) the two tools above)
oud_lb_diagram.py and oud_backend_report.py each call into
oud_config_type.py at runtime for an early, direction-appropriate scope
warning (B7) β oud_lb_diagram.py warns if the config doesn't look like a
Proxy, oud_backend_report.py warns if it doesn't look like a Directory
Server. oud_config_lint.py goes further: it imports extract_model from
oud_lb_diagram.py and extract_backends/extract_replication_domains
from oud_backend_report.py directly, reusing their object models instead
of re-deriving them, and uses oud_config_type.py to decide which rule
set(s) to run.
All cross-tool links (oud_config_type.py, and the model-reuse imports in
oud_config_lint.py) are soft dependencies: if the imported file isn't
present, that part is silently skipped rather than failing.
oud_ldif_core.py, by contrast, is a hard dependency for all four
tools β parsing is impossible without it, so its absence fails fast with a
clear error.
| File | Description |
|---|---|
oud_lb_diagram.py |
Load balancing diagram for OUD Proxy configs |
oud_backend_report.py |
Backend/index/replication report for OUD Directory Server configs |
oud_config_lint.py |
Validator/linter for both Proxy and Directory Server configs |
oud_config_type.py |
OUD instance classifier (Proxy / Directory Server / Hybrid). Run standalone or used automatically by the tools above for an early scope warning. |
oud_ldif_core.py |
Shared LDIF parser and DN utilities, used by all three tools above |
test_oud_lb_diagram.py |
Unit test suite for the diagram tool (38 tests) |
test_oud_backend_report.py |
Unit test suite for the backend report tool (6 tests) |
test_oud_config_lint.py |
Unit test suite for the config linter (8 tests) |
config_ds_test.ldif |
Safe, synthetic OUD Directory Server config fixture for testing oud_backend_report.py |
test_oud_ldif_core.py |
Unit test suite for the shared parser (14 tests) |
README.md |
This file |
CHANGELOG.md |
Version history |
BACKLOG.md |
Planned work |
Detects what kind of OUD instance a config belongs to, based on ds-cfg-java-class and objectClass evidence β useful to check before running the diagram tool, since oud_lb_diagram.py only supports OUD Proxy configs.
python oud_config_type.py <path-to-config.ldif>Reports one of: OUD Proxy, OUD Directory Server, Hybrid (both proxy-LB and a real local data backend present β unusual), or Unknown / inconclusive, plus the matching DNs as evidence. If placed in the same directory as oud_lb_diagram.py, the diagram tool calls it automatically and prints a [WARN] up front when the config doesn't look like a Proxy instance.
Reads an OUD Directory Server config (not proxy) and reports local user-data backends, their indexes, and replication domains β the counterpart to oud_lb_diagram.py for non-proxy instances.
python oud_backend_report.py <path-to-config.ldif>
python oud_backend_report.py --version
python oud_backend_report.py <config> --output <file>
python oud_backend_report.py <config> --anonymize # mask replication-server IPs with RFC 5737 placeholdersReports:
- Local data backends β base-dn, writability, txn-durability, db-directory, compression, default index-entry-limit, index count, and a
!! DISABLEDmarker if applicable. System/private backends (schema, tasks, admin, trust store, backup) are excluded automatically, same distinctionoud_config_type.pyuses for classification. - Indexes per backend β attribute, index type(s), entry limit.
- Replication domains β base-dn, server-id, group-id, replication servers, isolation policy, window size.
Like oud_lb_diagram.py, it calls into oud_config_type.py automatically (soft dependency) and warns if the loaded config looks like a Proxy rather than a Directory Server.
A safe, synthetic test fixture is included β config_ds_test.ldif (19 entries, RFC 5737 addresses, 1 user-data backend, 9 indexes, 3 replication domains) β so you can try the tool without needing a real config:
python oud_backend_report.py config_ds_test.ldifValidator/linter for both OUD Proxy and Directory Server configs. Uses oud_config_type.py to detect the profile and runs the matching rule set(s); on a Hybrid config, runs both.
python oud_config_lint.py <path-to-config.ldif>
python oud_config_lint.py --version
python oud_config_lint.py <config> --output <file>
python oud_config_lint.py <config> --format jsonFindings are grouped by severity: ERROR, WARNING, INFO. Exit codes are CI-friendly:
| Exit code | Meaning |
|---|---|
0 |
Rules ran, no ERROR-severity findings (WARNING/INFO are fine) |
1 |
At least one ERROR-severity finding |
2 |
No rules were run (profile undetermined or tool modules missing) β distinct from 0 so a pipeline can never pass green on zero executed checks |
Proxy rules:
| Rule | Severity | Checks |
|---|---|---|
P-REF-1 |
ERROR | Broken workflow-element / extension references |
P-ARCH-1 |
WARNING | Workflow element unreachable from any network group |
P-ARCH-2 |
WARNING | Disabled component (proxy WE, its extension, or LB WE) still reachable/referenced |
P-SEC-1 |
WARNING | ssl-trust-all: true (backend certificate not validated) |
P-SEC-2 |
INFO | ssl-policy not always |
P-PERF-1 |
WARNING | Connection pool size missing or zero |
P-PERF-2 |
INFO | Connect/read timeout set to 0 (no timeout) |
P-HYG-1 |
INFO | Duplicate CNs across workflow elements |
P-HYG-2 |
WARNING | Network group with no workflow configured |
Directory Server rules:
| Rule | Severity | Checks |
|---|---|---|
D-ARCH-1 |
INFO | Backend with no replication domain (standalone?) |
D-ARCH-2 |
WARNING | Replication domain with no matching local backend |
D-HYG-1 |
WARNING | Duplicate server-id across replication domains |
D-PERF-1 |
INFO | Commonly-queried attribute (uid/mail) with no index at all β a suggestion, not a confirmed usage pattern |
python oud_config_lint.py config_ds_test.ldif # clean config β 0 findings