PONGo_android/docs/MCMS_API.md
2026-05-31 14:09:00 +02:00

24 KiB
Raw Blame History

Ciena MCMS 6.2 REST API — Implementer's Field Guide

Reference for building an HTTP client against a Ciena MicroClimate Management System (MCMS) 6.2 PON Manager. Distilled from the official dev guide (323-1961-306) plus empirical findings against a working deployment with GNXS / Tibit MicroPlug ONUs on Interos Everest 5.x firmware.

Language-agnostic — every quirk listed here was hit in production. If you skip the gotchas in §3, you will lose an afternoon to one of them.


1. Connection basics

  • Base URL. The root of the PON Manager web UI (e.g. https://mcms.example.net). All REST paths are prefixed with /api.
  • Transport. HTTPS. Internal deployments commonly use self-signed certificates on private IPs — your client needs an opt-in to skip TLS verification for that case (do not disable globally).
  • HTTP version. HTTP/1.1 works. The server also negotiates HTTP/2 but the dev-guide examples are 1.1 and the only behavioural difference observed is irrelevant to clients.
  • Path normalisation. The dev guide writes paths as /v1/users/authenticate/ — the real wire path is /api/v1/users/authenticate/. Normalise once in your client.
  • API versions. Both /api/v1/... and /api/v3/... exist and serve different endpoints. Use whichever the dev guide lists for that resource — they are not interchangeable. See §4 endpoint table.
  • Trailing slashes. Required. /api/v1/onus/configs/<id>/ works; /api/v1/onus/configs/<id> does not (Django's APPEND_SLASH is on, but a few endpoints 500 instead of redirecting).

2. Login & session

MCMS uses Django session cookies + a CSRF token. Sessions do not auto-expire on idle — log out explicitly when done.

2.1 Authenticate

POST /api/v1/users/authenticate/
Content-Type: application/json

{ "data": { "email": "user@example.net", "password": "..." } }

Response on success (HTTP 200):

{ "status": "success" }

Set-Cookie headers carry the session. On observed MCMS 6.2 builds:

Set-Cookie: __Host-csrftoken=<token>; Path=/; Secure; SameSite=Lax
Set-Cookie: __Host-sessionid=<id>; Path=/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: __Host-sessionexpire=<epoch>; Path=/; Secure; SameSite=Lax

The __Host- prefix is significant. Some MCMS builds (older ones, mostly) use bare csrftoken / sessionid. Your cookie reader should accept either prefix.

→ Some build variants accept an unwrapped body too: {"email": ..., "password": ...} with no data envelope. If wrapped returns HTTP 400, retry unwrapped.

2.2 Subsequent requests

Every authenticated request must send:

Cookie: __Host-csrftoken=<token>; __Host-sessionid=<id>; ...
X-CSRFToken: <token>     # the value from __Host-csrftoken
Referer: <baseURL>/      # MCMS rejects POST/PUT/DELETE without this
User-Agent: <anything>   # see §3.1 — must not be empty
Accept: application/json

For PUT/POST/PATCH also:

Content-Type: application/json
Content-Length: <bytes>

2.3 (Optional) Database selection

If the deployment manages multiple databases:

PUT /api/v1/databases/selection/
{ "data": "<database_id>" }

Most deployments only have one and skip this step.

2.4 Logout

GET /api/v1/users/logout/

Best-effort — don't fail your client if this errors.


3. The gotchas (read this section twice)

3.1 User-Agent header is REQUIRED

If you don't send a User-Agent, some MCMS middleware dereferences HTTP_USER_AGENT without a null check and returns HTTP 500 with body:

{"status": "fail", "details": {"message": "Internal server error. See server logs for details."}}

Reproduce: curl -A '' https://mcms/api/v3/onus/configs/. Any non-empty string works (PonFleetClient/1.0, etc.).

For iOS/Swift: URLSession sets a default User-Agent automatically (e.g. <bundle>/1 CFNetwork/... Darwin/...), so this is unlikely to bite you there — but if you ever set a custom URLRequest with setValue(nil, forHTTPHeaderField: "User-Agent"), the 500s will return.

3.2 CSRF token field name

The token cookie is __Host-csrftoken on new builds, bare csrftoken on older. The header you send back is always X-CSRFToken (no prefix, capital T in Token). Both Django case-fold the header name, so casing is fine — but the body name is exact.

3.3 Pagination: use next, NOT skip

/api/v3/onus/configs/?skip=N has been observed returning HTTP 500. The working approach uses the last document's _id as a cursor:

GET /api/v3/onus/configs/?limit=100
   → returns 100 docs, the last with _id = "X"
GET /api/v3/onus/configs/?limit=100&next=X
   → returns the next 100, EXCLUDING all _ids <= X

Loop until you get a short page (< limit) or an empty page. Most list endpoints accept this pattern.

3.4 Page size 100 is the safe default

?limit=1000 against /api/v3/onus/configs/ on a ~1500-ONU fleet times out at the Apache front-end before Django logs the request. Full ONU-CFG documents are ~30 KB each, so 1000 × 30 KB = 30 MB, which exceeds the proxy timeout. 100 per page keeps each response ~3 MB and well under any default proxy budget.

This applies to all bulk list endpoints (configs, states, OLT states).

3.5 Request body envelope: {"data": payload}

PUT/POST/PATCH bodies are wrapped:

{ "data": { "_id": "...", "ONU": { ... } } }

Response envelope is similar:

{ "status": "success", "data": { ... } }
{ "status": "fail",    "details": { "message": "..." } }
{ "status": "warning", "data": { ... }, "details": { ... } }

"warning" is treated as success by MCMS (the write happened) but indicates a schema mismatch you should log.

3.6 PUT is full-document replace, NOT patch

PUT /api/v1/onus/configs/<id>/ replaces the entire ONU-CFG document. You must GET the current doc, mutate fields, and PUT the entire result. There is no PATCH equivalent that works reliably on MCMS 6.2.

Forgetting this turns into "I PUT'd just the FW Bank fields and now the ONU has no service config" — the dev guide is explicit but easy to skim past.

3.7 Timestamp format

MCMS emits and accepts timestamps as:

"YYYY-MM-DD HH:MM:SS"           # second precision
"YYYY-MM-DD HH:MM:SS.ffffff"    # microsecond precision (for STATE.Time)

Always UTC, always with a literal space between date and time (not ISO-8601 T). Used on:

  • ONU-STATE.Time
  • OLT-STATE.Time
  • Alarm timestamps
  • AUTO-TASK-CFG.Task["Scheduled Start Time"]
  • start-time / end-time URL params on stats endpoints

For Swift: ISO8601DateFormatter will NOT parse this. Use a plain DateFormatter with:

let fmt = DateFormatter()
fmt.timeZone = TimeZone(identifier: "UTC")
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS"     // or "yyyy-MM-dd HH:mm:ss"
fmt.locale = Locale(identifier: "en_US_POSIX")

The en_US_POSIX locale matters — without it, devices set to a 24-hour-format locale will still parse fine but devices set to 12-hour-format locales will fail. POSIX locale forces parse rules.

3.8 Server-side query/projection filters are brittle

The dev guide documents query, projection, sort, limit, skip, next, distinct URL parameters. In practice:

  • query (Mongo-style filter) works for simple equality on bare field names. Anything with spaces in field names, quoted values with non-ASCII characters, or projection paths that don't exist on every document version is unreliable — you'll get either empty results or 400s.
  • projection is very picky about paths. Better to fetch full docs and project client-side.

Recommended approach: load the full fleet (paginated by next) and filter client-side. The data volume is fine for any device with enough RAM to hold a few thousand 30 KB documents.

3.9 URL encoding

Use percent-encoding (encodeURIComponent equivalent). MCMS does NOT accept + for spaces in URL parameters — use %20.

This matters for query params like start-time which carry literal spaces in the timestamp.

3.10 ONU IDs

The _id field of an ONU document is:

  • The serial number for XGS-PON / GPON ONUs (e.g. GNXS05057fee)
  • The MAC address for 10G-EPON ONUs

Treat as opaque strings. They're URL-safe in observed deployments but always encodeURIComponent to be defensive.

3.11 Registration status lives on OLT-STATE, not ONU-STATE

This is non-obvious. The 9 registration buckets live as arrays on the OLT's state document:

{
  "_id": "<OLT MAC>",
  "ONU States": {
    "Registered":          ["GNXS01", "GNXS02", ...],
    "Deregistered":        ["GNXS09", ...],
    "Dying Gasp":          [...],
    "Disabled":            [...],
    "Disallowed Admin":    [...],
    "Disallowed Error":    [...],
    "Disallowed Reg ID":   [...],
    "Unspecified":         [...],
    "Unprovisioned":       [...]
  }
}

To resolve "what's the status of ONU X?", you walk every OLT-STATE doc, find which bucket contains X, and that's your answer. See Procedure 2 in the dev guide.

An ONU should appear in exactly one bucket on exactly one OLT. In the rare duplicate case, prefer the non-Registered bucket — that's the interesting signal.

3.12 FEC counters live on ONU-STATE.STATS, not on /onus/stats/

GET /api/v1/onus/stats/<id>/?start-time=... returns historical PM samples (time-series). For the current FEC counters and optical levels, read the live ONU-STATE document:

GET /api/v3/onus/states/<id>/
→ data.STATS["ONU-PON"]["RX Pre-FEC BER"]
→ data.STATS["ONU-PON"]["RX Post-FEC BER"]
→ data.STATS["ONU-PON"]["RX Optical Level"]      (dBm)
→ data.STATS["ONU-PON"]["TX Optical Level"]      (dBm)
→ data.STATS["OLT-PON"]["RX Pre-FEC BER"]
→ data.STATS["OLT-PON"]["RX Post-FEC BER"]

The MCMS Web UI's /api/onu/summary helper returns the same data under data.state_collection.STATS.* (wrapper shape).

3.13 Bulk endpoints don't return everything in one shot

Even on a small fleet, MCMS may silently truncate large list responses. Always paginate — don't trust ?limit=large_number to be authoritative.

3.14 Verbose error logging matters

When MCMS 500s, the response body sometimes contains useful detail (missing required param, schema validation failure, etc.). Always log both the response headers and the FULL body on non-2xx during development — most "mysterious 500" cases resolve to a one-line hint in the body.


4. Endpoints used by the fleet-upgrade app

Method Path Purpose
POST /api/v1/users/authenticate/ Login
GET /api/v1/users/logout/ Logout
PUT /api/v1/databases/selection/ (Optional) DB selection
GET /api/v3/onus/configs/ List ONU configs (paginated)
GET /api/v1/onus/configs/<id>/ Get single ONU config
PUT /api/v1/onus/configs/<id>/ Replace ONU config (full doc)
DELETE /api/v1/onus/configs/<id>/ Delete ONU (Procedure 43)
GET /api/v3/onus/states/ List ONU states (paginated)
GET /api/v3/onus/states/<id>/ Get single ONU state
DELETE /api/v1/onus/states/<id>/ Delete ONU state (best-effort)
GET /api/v3/olts/states/ List OLT states (paginated; registration)
GET /api/v1/files/onu-firmware/ List uploaded firmware files
POST /api/v1/files/onu-firmware/<name>/ Upload firmware
PUT /api/v3/tasks/configs/<id>/ Create bulk upgrade task (Procedure 8)
GET /api/v3/tasks/configs/<id>/ Poll bulk task config
GET /api/v1/onus/<id>/upgrade/status/ (Build-dependent) per-ONU status

Other endpoints documented but not (yet) used

Method Path Notes
GET /api/v1/onus/stats/<id>/?start-time=... Time-series ONU PM data
GET /api/v1/olts/stats/<id>/?start-time=... OLT PM data
GET /api/v1/onus/logs/<id>/?start-time=... Per-ONU syslog
GET /api/v1/olts/logs/<id>/?start-time=... Per-OLT syslog
GET /api/v3/tasks/states/<id>/ Bulk task progress (good follow-up)
GET /api/v1/onus/automation/states/ PON automation state
GET /api/v1/onus/cpe-states/ ONU CPE states (downstream devices)

5. Data shapes (observed)

These are not formally specified in the dev guide for MCMS 6.2 — they were extracted from live traffic.

5.1 ONU-CFG

{
  "_id": "GNXS05057fee",
  "ONU": {
    "Name": "Operator-set label",
    "Address": "Street, city",
    "PON Mode": "GPON",                // GPON | XGS-PON | 10G-EPON
    "Equipment ID": "FT-XGS2110",

    "FW Bank Files":    ["...bin", "...bin"],   // [slot 0, slot 1]
    "FW Bank Versions": ["EV05110R", "EV05100R"],
    "FW Bank Ptr": 0,                  // 0 | 1 | 65535 (unset)

    "ONU-ALARM-CFG": "Default",
    "Vendor": "GNXS",
    "Serial Number": "GNXS05057fee",
    "Host MAC Address": "58:00:32:05:7f:ee",
    // ...many more fields - preserve all of them when PUTing
  },
  "OLT-Service 0": { ... },            // service config slots
  "OLT-Service 1": { ... },
  // ...
}

Critical: when mutating for an upgrade, only change ONU["FW Bank Files"], ONU["FW Bank Versions"], and ONU["FW Bank Ptr"]. Pass through everything else unchanged.

5.2 ONU-STATE

{
  "_id": "GNXS05057fee",
  "Time": "2026-05-04 21:51:57.394127",     // UTC, last update
  "ONU": {
    "Equipment ID": "FT-XGS2110",
    "FW Bank Files":    [...],
    "FW Bank Versions": [...],
    "FW Bank Ptr": 0,
    "FW Version": "EV05110R",                // resolved active version
    "FW Upgrade Status": {
      "Bank": 0,
      "Status": "Success",                   // status string
      "Progress": 100,                       // percent
      "Upgrade Time": "2026-03-18 17:12:43.093893",
      "Total Blocks": 482915,
      "Sent Blocks": 482915,
      "Failures": 0,
      // ...
    },
    "PON Mode": "GPON",
    "Online Time": "2026-03-18 17:13:42.285965",
    // ...
  },
  "STATS": {
    "ONU-PON": {
      "RX Pre-FEC BER":  22388041291,        // cumulative since reset
      "RX Post-FEC BER": 22379111198,
      "RX Optical Level": -17.544,            // dBm
      "TX Optical Level": 5.532               // dBm
    },
    "OLT-PON": {
      "RX Pre-FEC BER":  0,                  // upstream view
      "RX Post-FEC BER": 0,
      "TX Optical Level": 5.6,
      "RX Optical Level": -18.3
    },
    "ONU-EnhancedFecPmHistData-32769": {     // NOT the same as the BER fields above
      "uncorrectable_code_words": 1829,      // (these are codeword counts,
      "corrected_bytes": 181,                //  not bit-error-rate ratios)
      "corrected_code_words": 181
    },
    // ...many more
  },
  "Alarm": {
    "0-EMERG": [], "1-ALERT": [], "2-CRIT": [],
    "3-ERROR": [], "4-WARNING": [], "5-NOTICE": [],
    "6-INFO": [], "7-DEBUG": []
  }
}

5.3 OLT-STATE (relevant subset)

{
  "_id": "<OLT MAC>",
  "Time": "2026-05-04 21:51:57.394127",
  "ONU States": {
    "Registered":          ["onu1", "onu2", ...],
    "Deregistered":        [...],
    "Dying Gasp":          [...],
    "Disabled":            [...],
    "Disallowed Admin":    [...],
    "Disallowed Error":    [...],
    "Disallowed Reg ID":   [...],
    "Unspecified":         [...],
    "Unprovisioned":       [...]
  }
  // ...
}

5.4 AUTO-TASK-CFG (bulk firmware task, Procedure 8)

{
  "_id": "<your-task-id>",                  // unique, caller-generated
  "Task": {
    "Device Type": "ONU",
    "Operation": "Firmware Upgrade",
    "Scheduled Start Time": "2026-05-12 03:00:00"  // UTC space format
  },
  "Task Details": {
    "Devices": ["GNXS01", "GNXS02", ...],   // ONU IDs (serial / MAC)
    "ONU": {
      "FW Bank Ptr": 1,                     // target slot (single value!)
      "FW Bank Files":    { "0": "old.bin", "1": "new.bin" },
      "FW Bank Versions": { "0": "EV05110R", "1": "EV05120R" }
    }
  }
}

PUT to /api/v3/tasks/configs/<task-id>/. Note: FW Bank Files and FW Bank Versions here are objects keyed by slot index as strings ("0", "1"), not arrays. This is different from the ONU-CFG shape where the same fields are arrays — almost certainly a Mongo serialisation quirk. Match it as-is.

Single bank per task. Because FW Bank Ptr accepts one slot, if your selection spans ONUs with different active banks, bucket them by target slot and create one task per bucket.

5.5 Firmware file entry (from /api/v1/files/onu-firmware/)

{
  "_id": "...",
  "filename": "Interos-Everest-5.12.0-R-EV05120R.bin",
  "metadata": {
    "Compatible Manufacturer": "TIBITCOM",
    "Compatible Model": ["MicroPlug ONU"],
    "Version": "EV05120R"
  },
  // GridFS metadata...
}

To upload:

POST /api/v1/files/onu-firmware/<filename>/
{
  "data": {
    "file": "<base64 binary contents>",
    "metadata": {
      "Compatible Manufacturer": "TIBITCOM",
      "Compatible Model": ["MicroPlug ONU"],
      "Version": "EV05120R"
    }
  }
}

6. Firmware upgrade flow

Two methods documented:

6.1 Procedure 7 — per-ONU PUT

For each ONU:

  1. GET /api/v1/onus/configs/<id>/ → full doc.
  2. Compute the inactive bank (see §6.3).
  3. Set:
    • ONU["FW Bank Files"][writeSlot] = "<filename>"
    • ONU["FW Bank Versions"][writeSlot] = "<version>"
    • ONU["FW Bank Ptr"] = writeSlot (this initiates the download)
  4. PUT /api/v1/onus/configs/<id>/ with the entire mutated document.

Pros: see errors per ONU live. Cons: slow for large fleets, you own retries.

Build one AUTO-TASK-CFG per (target slot) bucket:

PUT /api/v3/tasks/configs/<task-id>/
   (body shape in §5.4)

MCMS owns the rollout: scheduled start, retries, pacing. Monitor progress (build permitting):

GET /api/v3/tasks/states/<task-id>/

Or just poll the ONU configs and watch FW Bank Versions / FW Bank Ptr flip.

6.3 Inactive-bank selection

FW Bank Ptr identifies the active bank (0, 1, or 65535 = unset). Always write to the inactive bank so you don't blow away the running image:

Current FW Bank Ptr Write slot Why
0 1 preserve active
1 0 preserve active
65535 (unset) 1 matches Procedure 7 example; slot 0 stays as factory fallback

MCMS flips FW Bank Ptr to the new slot when the download completes. The ONU reboots into the new image on its next reboot.


7. FEC + optical interpretation

7.1 Four FEC counters

Per the user guide green-LED criteria (323-1961-302 p149):

Field Path Green Red
ONU Pre-FEC BER STATS["ONU-PON"]["RX Pre-FEC BER"] 0 (informational)
ONU Post-FEC BER STATS["ONU-PON"]["RX Post-FEC BER"] 0 > 0
OLT Pre-FEC BER STATS["OLT-PON"]["RX Pre-FEC BER"] 0 (informational)
OLT Post-FEC BER STATS["OLT-PON"]["RX Post-FEC BER"] 0 > 0

Note: the field name is "BER" but the magnitudes are big integers (cumulative error byte counts since reset), not ratios in [0,1]. Treat them as counts.

7.2 Health bucket rules

Suggested 4-state reduction:

  • OK: all four counters present and == 0.
  • Pre-FEC errors: any pre > 0, all post == 0. (Marginal link; FEC is correcting.)
  • Post-FEC errors: any post > 0. (Uncorrected bits reaching the user.)
  • Buggy (pre ≈ post): ONU pre > 0 AND post > 0 AND post / pre >= 0.95. Real FEC reduces errors; if post is within 5% of pre, the firmware is mirroring the counter. Observed on Interos Everest ONUs.

7.3 Optical levels

STATS["ONU-PON"]["RX Optical Level"]   // ONU receive power, dBm
STATS["ONU-PON"]["TX Optical Level"]   // ONU transmit power, dBm

User guide thresholds:

Green Red
RX 30 dBm < 30 dBm
TX ≥ 3 dBm < 3 dBm

Operators usually want a margin warning band — e.g. yellow when RX is between 28 and 30 dBm.


8. iOS-specific notes

8.1 Library stack suggestions

  • HTTP: URLSession is fine. The features you need are standard cookie storage, custom headers, and the option to bypass cert validation. Don't reach for Alamofire just for this.
  • Cookies: HTTPCookieStorage handles __Host- prefixes correctly. Get the CSRF value via:
    HTTPCookieStorage.shared.cookies(for: baseURL)?
      .first { $0.name == "__Host-csrftoken" || $0.name == "csrftoken" }?.value
    
  • Self-signed certs: Implement URLSessionDelegate.urlSession(_:didReceive:completionHandler:) and accept serverTrust only when the user has explicitly toggled "accept self-signed" for the configured host. Never default-on.
  • JSON: JSONSerialization is simpler than Codable for the envelope pattern ({status, data, details}) because the data payload is heterogeneous. Codable works if you model each endpoint individually.

8.2 Concurrency

The fleet load is N HTTPS round-trips for per-ONU state (the bulk endpoint is the optimisation but the per-ID fallback may also be needed). Use Swift Concurrency (TaskGroup) with a concurrency cap of 810 to avoid stampeding the server. The desktop client uses 5 for deletes and 8 for state fetches.

8.3 Background uploads

Firmware .bin files are 1050 MB base64-encoded. If you implement uploads:

  • Don't block the main thread.
  • Consider URLSession background configuration so an upload can survive an app suspend.
  • The upload is a single POST; no chunking required.

8.4 Storing the session

For UX: persist the base URL and username in UserDefaults, never the password. Re-prompt for password on launch.

The session cookie itself goes in the per-session cookie storage and is automatically discarded — don't try to persist it.

8.5 Date handling

See §3.7. Default DateFormatter with en_US_POSIX locale + UTC timezone, format "yyyy-MM-dd HH:mm:ss.SSSSSS" (microseconds) or "yyyy-MM-dd HH:mm:ss" (seconds). Try the microsecond format first, fall back to second if it fails.

8.6 Cell-vs-Wi-Fi

MCMS APIs are typically reachable only from the operator's internal network. Test on cellular: many internal hosts won't be reachable, and your error UI needs to communicate that clearly.

8.7 Self-signed cert prompt

Make this a first-class onboarding step. The lab MCMS at https://mcms.lab.svorka.net/ uses a self-signed cert, and any auto-rejection will silently break login.


9. Procedures referenced in the dev guide

# Title Notes
2 Determining ONU registration status OLT-STATE["ONU States"] buckets
7 Per-ONU firmware upgrade Full-doc PUT to ONU-CFG
8 Bulk firmware upgrade AUTO-TASK-CFG via PUT
38 Resetting bit error rate values Names the four FEC fields
43 Deleting an ONU DELETE on ONU-CFG

(Page numbers vary by document revision; cite by procedure number.)


10. Quick implementation checklist for a new client

A correct first POST-login GET against /api/v3/onus/configs/?limit=1 returning HTTP 200 means you've got the eight critical pieces right:

  • HTTPS reachable (with cert exception if needed)
  • Path prefix /api added
  • User-Agent set to non-empty value
  • Cookie jar storing __Host-sessionid + __Host-csrftoken
  • X-CSRFToken header pulled from __Host-csrftoken cookie value
  • Referer header set to base URL
  • Accept: application/json
  • Trailing slash on the path

Add for write operations:

  • Content-Type: application/json
  • Body wrapped: {"data": ...}
  • Full-document PUT (never partial)

Add for list endpoints:

  • Page size ≤ 100
  • next cursor (not skip)
  • Loop until short page

Once those land, the API is comfortable to work with — the hard parts are upfront.


Compiled from 323-1961-306 (REST API dev guide), 323-1961-302 (PON Manager User Guide), and empirical testing against MCMS 6.2 with Interos Everest 5.11/5.12 firmware on GNXS / Tibit MicroPlug ONUs.