MAV Service API Migration
Background ¶
MAV (Monitoring Agent Version) service is being retired. Its APIs are consumed by monitoring agents (telegraf / tel-conf) to obtain Prometheus remote-write endpoints, credentials, and metric filter rules. These APIs need to be migrated to MCT central-api so that clients can switch to the MCT endpoint with minimal disruption.
Migration Principles ¶
The migration follows several key principles to ensure a seamless, user-transparent transition:
- Behavioral parity: MCT API responses must be byte-for-byte identical to the original MAV API responses. Every field — URL, credentials, namepass list, fieldpass list, error messages — must match exactly.
- URL compatibility: The original MAV API path
/mavs/pop/...must continue to work on MCT. A Tomcat Valve-based URL rewrite (MavUrlRewriteConfiguration.java) transparently rewrites/mavs/*to/mpi/mavs/*at the engine level, so clients do not need to know about MCT's/mpicontext path. - No authentication change: MAV APIs are unauthenticated. MCT's CI OAuth is disabled at the controller level (
@CIOauth(enable = false)) so the migrated APIs remain open, matching the original behavior. - Data integrity: All MAV MySQL data is migrated to MCT PostgreSQL with
INSERT ... ON CONFLICT DO NOTHINGto handle MySQL's case-insensitive collation duplicates safely. - Case-insensitive query behavior: MySQL uses
utf8_general_ci(case-insensitive). PostgreSQL is case-sensitive by default. The MCT MyBatis queries useLOWER()on both sides of DC name comparisons to preserve the original case-insensitive lookup behavior.
MAV API Overview ¶
MAV exposes 5 APIs under /mavs/pop.
| # | Endpoint | Method | Purpose | Client Usage |
|---|---|---|---|---|
| 1 | /pop/getlmaprometheus | GET | Return Prometheus remote-write URL, credentials, orgId, namepass and fieldpass filter lists for a given host/DC/svrtype | ✅ tel-conf calls this |
| 2 | /pop/getlmaprometheus | POST | Same logic as GET (dual entry point) | ✅ tel-conf calls this |
| 3 | /pop/getlmaprometheusurl | GET | Return only the Prometheus remote-write URL (lightweight version) | Not found in client code |
| 4 | /pop/query-metrics-filter | GET | Query namepass + fieldpass filter rules by svrtype | Not found in client code |
| 5 | /pop/add-metrics-filter | POST | Add new namepass/fieldpass filter entries for a svrtype | Not found in client code |
API 1 & 2 — Core Prometheus Config Lookup ¶
This is the most critical API. It implements a 4-level cascade lookup from the lma_prometheus_detail table:
- Exact match:
env + svrtype + dc + environment - Fallback:
env + dc + environment(svrtype = 'all') - Fallback:
svrtype + dc + environment(env = 'all') - Fallback:
dc + environment(env = 'all', svrtype = 'all')
Meaning:
- Exact match means the query first looks for the most specific row where all four dimensions match the request exactly.
- Fallback means if that exact row does not exist, the query relaxes one condition at a time by using the shared default value
all, so the API can still return a valid broker config for the same DC and environment.
Example compare case:
# MAV
curl -sk -X GET \
'https://mavservice.webex.com/mavs/pop/getlmaprometheus' \
-H 'Content-Type: application/json' \
-d '{"host":"test-host","type":"install","dc":"SJC02","svrtype":"linus","env":"PROD","environment":"COMMERCIAL","env_type":"PROD"}' \
| python3 -m json.tool
# MCT
curl -sk -X GET \
'https://mctapi.webex.com/mavs/pop/getlmaprometheus' \
-H 'Content-Type: application/json' \
-d '{"host":"test-host","type":"install","dc":"SJC02","svrtype":"linus","env":"PROD","environment":"COMMERCIAL","env_type":"PROD"}' \
| python3 -m json.tool
Note: for MAV/MCT comparison, the tested GET form sends a JSON request body. Calling this endpoint with only query parameters returns a bad request.
POST compare case for the same lookup:
# MAV
curl -sk -X POST \
'https://mavservice.webex.com/mavs/pop/getlmaprometheus' \
-H 'Content-Type: application/json' \
-d '{"host":"test-host","type":"install","dc":"SJC02","svrtype":"linus","env":"PROD","environment":"COMMERCIAL","env_type":"PROD"}' \
| python3 -m json.tool
# MCT
curl -sk -X POST \
'https://mctapi.webex.com/mavs/pop/getlmaprometheus' \
-H 'Content-Type: application/json' \
-d '{"host":"test-host","type":"install","dc":"SJC02","svrtype":"linus","env":"PROD","environment":"COMMERCIAL","env_type":"PROD"}' \
| python3 -m json.tool
It also queries lma_namepass and lma_fieldpass for metric filter rules (including common svrtype rules), and asynchronously logs agent version information to agent_status and agent_version_history.
API 3 — Prometheus URL Only ¶
A simplified variant that returns only the url field using the same cascade lookup logic.
Example compare case:
# MAV
curl -sk -X GET \
'https://mavservice.webex.com/mavs/pop/getlmaprometheusurl' \
-H 'Content-Type: application/json' \
-d '{"host":"test-host","type":"install","dc":"SJC02","svrtype":"linus","env":"PROD","environment":"COMMERCIAL","env_type":"PROD"}' \
| python3 -m json.tool
# MCT
curl -sk -X GET \
'https://mctapi.webex.com/mavs/pop/getlmaprometheusurl' \
-H 'Content-Type: application/json' \
-d '{"host":"test-host","type":"install","dc":"SJC02","svrtype":"linus","env":"PROD","environment":"COMMERCIAL","env_type":"PROD"}' \
| python3 -m json.tool
API 4 — Query Metrics Filter ¶
Returns the namepass and fieldpass lists for a given svrtype, merged with the common svrtype rules.
Example compare case:
# MAV
curl -sk -X GET \
'https://mavservice.webex.com/mavs/pop/query-metrics-filter?svrtype=linus' \
| python3 -m json.tool
# MCT
curl -sk -X GET \
'https://mctapi.webex.com/mavs/pop/query-metrics-filter?svrtype=linus' \
| python3 -m json.tool
API 5 — Add Metrics Filter ¶
Inserts new namepass/fieldpass entries for a svrtype. Validates that the svrtype is not null/empty.
requester is also required for successful write requests.
Warning: this API is a write operation. Running the curl examples below will modify MAV or MCT database data by inserting new namepass or fieldpass rows for the specified svrtype.
Use only a disposable test svrtype such as _test_migr_tmp, and clean up the inserted rows after verification.
Example compare case:
# MAV
curl -sk -X POST \
'https://mavservice.webex.com/mavs/pop/add-metrics-filter' \
-H 'Content-Type: application/json' \
-d '{"svrtype":"_test_migr_tmp","requester":"migration-test","namepass":["test_cpu","test_mem"]}' \
| python3 -m json.tool
# MCT
curl -sk -X POST \
'https://mctapi.webex.com/mavs/pop/add-metrics-filter' \
-H 'Content-Type: application/json' \
-d '{"svrtype":"_test_migr_tmp","requester":"migration-test","namepass":["test_cpu","test_mem"]}' \
| python3 -m json.tool
Fieldpass compare case:
# MAV
curl -sk -X POST \
'https://mavservice.webex.com/mavs/pop/add-metrics-filter' \
-H 'Content-Type: application/json' \
-d '{"svrtype":"_test_migr_tmp","requester":"migration-test","fieldpass":["test_usage_idle","test_used_percent"]}' \
| python3 -m json.tool
# MCT
curl -sk -X POST \
'https://mctapi.webex.com/mavs/pop/add-metrics-filter' \
-H 'Content-Type: application/json' \
-d '{"svrtype":"_test_migr_tmp","requester":"migration-test","fieldpass":["test_usage_idle","test_used_percent"]}' \
| python3 -m json.tool
For API 5, use a disposable svrtype like _test_migr_tmp and clean it up after verification. Do not run these examples directly against prod unless you intend to write and then remove test data.
MCT Migration Implementation ¶
Code Changes ¶
All migration code lives in mct-central-api:
| File | Role |
|---|---|
MavWebService.java | Controller — @RequestMapping("/mavs/pop"), @CIOauth(enable = false) |
MavService.java / MavServiceImpl.java | Service layer — business logic for all 5 APIs |
MavDao.java / MavDao.xml | MyBatis DAO — PostgreSQL queries with LOWER() for case-insensitive DC matching |
MavUrlRewriteConfiguration.java | Tomcat Valve — rewrites /mavs/* → /mpi/mavs/* so clients can call without the /mpi prefix |
Database Schema ¶
5 MySQL tables are mapped to 5 PostgreSQL tables:
| MAV MySQL Table | MCT PostgreSQL Table |
|---|---|
lma_prometheus_detail | mct_mav_prometheus |
lma_namepass | mct_mav_namepass |
lma_fieldpass | mct_mav_fieldpass |
agent_status | mct_mav_client_status |
agent_version_history | mct_mav_client_version_history |
Key Bug Fix — Case Sensitivity ¶
MySQL's utf8_general_ci collation treats DFW02 and dfw02 as the same value. PostgreSQL does not. This caused 4 test cases to fail in early test rounds.
Fix: Added LOWER() to all DC name comparisons in MavDao.xml:
LOWER(dc_name) = LOWER(#{dcName})
This fix is applied to all 4 cascade-level queries, ensuring the same case-insensitive behavior as the original MySQL queries.
URL Rewrite — /mavs/* Without /mpi Prefix ¶
MCT uses server.servlet.context-path=/mpi, but MAV clients call /mavs/pop/... directly. Removing the context path would affect all other MCT APIs.
Solution: MavUrlRewriteConfiguration.java registers a Tomcat ValveBase at the engine level. It intercepts requests with URI starting with /mavs/ and remaps them to /mpi/mavs/... internally, before the servlet container routes the request.
Both paths work after this change:
https://<mct-host>/mavs/pop/getlmaprometheus→ 200https://<mct-host>/mpi/mavs/pop/getlmaprometheus→ 200
Test Results ¶
Test Methodology ¶
An automated test script calls both MCT and MAV endpoints simultaneously with identical parameters, then compares responses field-by-field:
- JSON fields compared individually:
url,usr,pss,orgId - List fields (
namepass,fieldpass) compared as sets (order-insensitive) - Error messages compared as exact strings
- Write tests (API 5) use a disposable svrtype
_test_migr_tmpwith automatic cleanup
31 Test Cases ¶
| API | Endpoint | Method | Cases | What's Tested |
|---|---|---|---|---|
| 1 | /getlmaprometheus | GET | 10 | 4-level cascade, case insensitivity (DFW02/dfw02/SJC02/sjc02), FedRAMP/ATS/APAC environments, error path |
| 2 | /getlmaprometheus | POST | 4 | Same logic via POST body |
| 3 | /getlmaprometheusurl | GET | 7 | URL-only variant, same DC/env combinations plus error path |
| 4 | /query-metrics-filter | GET | 7 | Multiple svrtypes (linus, mulsvr, common, bchsrv, rmcsvr, dbbasvr), nonexistent svrtype fallback |
| 5 | /add-metrics-filter | POST | 3 | Insert namepass, insert fieldpass, missing svrtype error |
Environment Test Summary ¶
| Environment | MCT API DNS | Data Source | Test Result |
|---|---|---|---|
| WFRA | mctapi.int.wfraint-gen-a.int.infra.webex.com | Manual data copy (MySQL → PG) | ✅ 31/31 passed |
| BTS JFK | mctapi.stage.webex.com | Manual data copy (MySQL → PG) | ✅ 28/28 passed (3 write tests skipped) |
| BTS DFW | mctapi.int.wdfwgen-b-2.prod.infra.webex.com | SharePlex sync from JFK | ✅ 28/28 passed (3 write tests skipped) |
| Prod JFK | mctapi.webex.com | Manual data copy (MySQL → PG) | ✅ 31/31 passed |
| Prod DFW | mctapi.int.wdfwgen-p-4.prod.infra.webex.com | SharePlex sync from Prod JFK | ✅ 28/28 passed (3 write tests skipped) |
All read-only API comparisons produce identical responses between MCT and MAV across all five environments. Full write-test validation was completed in WFRA and Prod JFK.
WFRA — Full 31/31 ¶
WFRA was the first environment tested. All 31 test cases passed, including 3 write tests (API 5). The write tests insert test data, verify the response matches MAV, then clean up.
BTS JFK — 28/28 ¶
BTS JFK data was manually copied from MAV MySQL using bts-jfk/01_copy_data.py. 28 read-only tests passed. Write tests were skipped because they require a local MySQL connection for cleanup verification.
BTS DFW — 28/28 ¶
BTS DFW receives its data from BTS JFK via SharePlex replication (managed by DBA). No manual data copy was needed. 28 read-only tests passed, confirming the SharePlex-sync'd data is fully consistent.
Prod JFK — 31/31 ¶
Prod JFK data was manually copied from MAV MySQL to PostgreSQL. All 31 test cases passed, including 3 write tests for add-metrics-filter. The write tests used disposable svrtype _test_migr_tmp and cleaned up after each case.
Prod DFW — 28/28 ¶
Prod DFW receives its data from Prod JFK via SharePlex replication. No separate manual data copy was needed. 28 read-only tests passed, confirming the replicated data is consistent with MAV and with Prod JFK.