AlinsRan opened a new pull request, #13899:
URL: https://github.com/apache/apisix/pull/13899

   ### Description
   
   `/v1/healthcheck` only walks `checks` on upstreams (`value.checks` / 
`value.upstream.checks`), so a route whose real upstreams are LLM instances 
configured on `ai-proxy-multi` reports no health state at all — the very state 
the plugin consults when it skips an unhealthy instance. A monitoring system 
polling the control API sees those instances as unchecked.
   
   `ai-proxy-multi` keys each instance checker by `<resource 
key>#plugins['ai-proxy-multi'].instances[<i>]`, a layout nothing outside the 
plugin can guess. Rather than hard-coding it in the control API, a plugin now 
declares its own checkers:
   
   - `apisix/plugins/ai-proxy-multi.lua` — new `list_healthcheck_targets(conf, 
resource_key)`, returning each checker's resource path, its `checks` and an 
opaque `meta`. The path built in `pick_target()` is extracted into 
`instance_resource_path()` and shared, so the checker identity has one 
definition.
   - `apisix/control/v1.lua` — `iter_and_add_healthcheck_info()` additionally 
asks each configured plugin for its checkers; node lookup is factored into 
`get_checker_nodes()` so both entry kinds read the same shm.
   
   A plugin entry keeps the shape of an upstream entry and adds two fields:
   
   ```json
   {
     "name": "/apisix/routes/1#plugins['ai-proxy-multi'].instances[0]",
     "plugin": "ai-proxy-multi",
     "meta": {"instance": "openai"},
     "type": "http",
     "nodes": [
       {"ip": "52.86.68.46", "port": 443, "status": "healthy",
        "counter": {"success": 2, "http_failure": 0, "tcp_failure": 0, 
"timeout_failure": 0}}
     ]
   }
   ```
   
   `meta` is filled by the plugin and reported verbatim — what a checker stands 
for is the plugin's business, so the control API does not know what an instance 
is.
   
   ### New sub-resource: `GET /v1/healthcheck/{src_type}/{src_id}/checkers`
   
   One resource can now own several checkers — its upstream plus one per plugin 
instance — which `/v1/healthcheck/{src_type}/{src_id}` cannot express: it 
returns a single object, and a route whose upstreams live on `ai-proxy-multi` 
answers `404 {"error_msg":"no checker for routes[1]"}` there, because its 
upstream declares no `checks`.
   
   The sub-resource returns all of them, as an array of the same entries the 
listing uses, so a client parses one entry shape for both:
   
   ```json
   GET /v1/healthcheck/routes/1/checkers
   [
     {"name": "/apisix/routes/1", "type": "http", "nodes": [...]},
     {"name": "/apisix/routes/1#plugins['ai-proxy-multi'].instances[0]",
      "plugin": "ai-proxy-multi", "meta": {"instance": "openai"},
      "type": "http", "nodes": [...]}
   ]
   ```
   
   - The existing single-object endpoint is untouched.
   - A resource with no health check at all is not an error here — it owns an 
empty set, serialized as `[]` (`array_mt`, so never `{}`).
   - `404` only when the resource itself is missing, `400` for an unknown 
sub-resource.
   - HTML rendering (`Accept: text/html`) works the same way as on the other 
two endpoints.
   
   Docs updated in `docs/en/latest/control-api.md`.
   
   ### Which issue(s) this PR fixes
   
   None filed; reported by a user whose monitoring platform polls 
`/v1/healthcheck` and cannot see the health of LLM upstreams behind 
`ai-proxy-multi`.
   
   ### Checklist
   
   - [x] I have explained the need for this PR and the problem it solves
   - [x] I have explained the changes or the new features added to this PR
   - [x] I have added tests corresponding to this change
   - [x] I have updated the documentation to reflect this change
   - [x] I have verified that this change is backward compatible
   
   ### Notes
   
   - `upstreams` cannot carry plugins (`schema_def.upstream` has no `plugins` 
field), so their entries never gain a `plugin` field and their `checkers` set 
holds at most one element.
   - A single-instance `ai-proxy-multi` config never creates a checker, because 
`pick_ai_instance()` short-circuits `pick_target()`; its entry is therefore 
listed with an empty node list. Pre-existing plugin behaviour, untouched here.
   - A plugin reaching a route through a `plugin_config` is not covered, since 
that merge happens per request; the endpoint does not walk plugin configs today 
either.
   
   ### Testing
   
   New `t/control/healthcheck-ai-proxy-multi.t`, 8 cases: instance health 
reported as `healthy`/`unhealthy`; plugin and upstream checkers coexisting in 
one listing; `disable_upstream_healthcheck` leaving every node list unprobed; 
instances listed before any probe; the `checkers` sub-resource on an AI route 
and on a plain upstream route; and its empty-set / `404` / `400` paths.
   
   All 24 assertions pass locally, as does `t/control/healthcheck.t`. Locally 
the AI cases need `plugin_attr.ai-proxy.http_client: lua-resty-http`, because 
this box has no `ngx_http_ffi_client` C module — the committed file uses the 
default client, and the existing `t/plugin/ai-proxy-multi-*.t` files fail here 
for the same reason, on master as well as on this branch.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to