发布

  • feat(yandex-cloud): add metric, log, instance, balancer tools and a generic API reader (#4990)

    frostbyte_neo 发布于 2026-08-18 22:50:44 +00:00

    • feat(yandex-cloud): add metric, instance and balancer tools plus a generic API reader

    The Yandex Cloud integration could authenticate and verify a folder but could
    not read anything from it. This adds the first tool families and the generic
    reader that covers the rest of the API.

    Curated tools hard-code their own paths:

    • monitoring: query metric series, list metric names and labels
    • compute: list instances, read serial console output for diagnosis
    • network: report unhealthy network and application balancer targets

    Everything else is reached through execute_yc_operation, which resolves a path
    via find_yc_api against an index generated from Yandex's own protobuf
    definitions. Only get: bindings are extracted, so the index doubles as the
    allowlist that keeps the reader GET-only; build_api_index.py regenerates it and
    records the cloudapi commit it came from.

    The client regains post(): Monitoring's metric read takes a request body, which
    is the one read in this set that a GET cannot express.

    Tools are registered for discovery, classified for Sentry coverage, and held to
    a seventeen-schema ceiling so later families stay inside the per-turn budget.

    • feat(yandex-cloud): read Cloud Logging entries and list log groups

    Cloud Logging is the one Yandex Cloud read with no REST endpoint: the entry
    reader is gRPC-only. The stubs therefore ship as an optional extra, following
    the same shape as the kafka and azure_sql extras - imported inside the function
    that needs them, with a failure that explains the install rather than reading
    as a broken integration.

    Listing log groups is plain REST on the management host and works without the
    extra. Only reading entries needs it.

    The reader host is separate from the management one, entry reads are limited to
    five per second, and retention is 31 days, so the client throttles, honours
    Retry-After, caps filter expressions, and flags a window that reaches past
    retention.

    The install hint is checked against the extras this project declares, so a
    message naming something unavailable fails the suite rather than reaching a
    user.

    • fix(yandex-cloud): ship the API index and call the documented target-states path

    Two defects found while auditing the port against the plugin it came from.

    The endpoint index was in no distribution artifact. package-data lists only
    **/SKILL.md under integrations, and the release manifest globs the same, so
    api_index.json was absent from both the wheel and the frozen binary. The
    loader swallowed the resulting OSError and returned an empty index, which the
    tool reports as "no endpoints" - so find_yc_api, the entry point the workflow
    guidance routes to, answered as if Yandex exposed nothing. It now ships, the
    wheel validator covers it, and the loader logs which file it could not read.

    The network balancer tool called :targetStates. The proto-derived index in
    this repo spells the binding :getTargetStates, as does the application
    balancer call ten lines below it, so target health came back empty against a
    real cloud. The test stubbed the misspelling and therefore pinned the bug.

    Also, guidance and tests that were carrying their own small lies: SKILL.md
    pointed the model at tools from families this tree does not ship yet;
    list_yc_metrics was the only tool here with no execution coverage; and the
    schema text that stops the model writing PromQL had nothing holding it in place.

    • fix(yandex-cloud): say when a listing is incomplete instead of implying it is whole

    Two reads answered as if they had seen everything.

    get_yc_lb_health took the first page of each balancer type and dropped the
    rest without a word. "No unhealthy targets" is the one answer that must never be
    a guess, so the tool now reports complete: false and points at the type
    filter and the generic reader for the remainder.

    list_yc_instances matches a name fragment locally, because Yandex's own filter
    compares names for equality and has no substring form. That makes the match
    local to the page just fetched, so an instance on a later page reads as absent.
    It now says so when a filtered read has more pages behind it.

    Also records why Monitoring asks for gapFilling: NULL: PREVIOUS would carry
    the last value forward, and a service that stopped reporting would come back as
    a flat healthy line.

    • fix(yandex-cloud): do not scope a nested collection by folder

    Reading an instance's operation history returned a bare 404, as did every other
    collection nested under a named resource: cluster hosts, node groups, disk and
    balancer operations. A large part of the API answered as if the resource did not
    exist.

    The executor decided to send folderId from "is this a collection", but a folder
    scopes only the collections directly under the version. A nested one is already
    scoped by the resource named in its path, and Yandex rejects the extra parameter
    the same silent way it rejects one on a single-resource read.

    Verified against the live API: /compute/v1/instances/{id}/operations returns the
    history with no folderId and 404s with it, while pageSize is accepted either
    way - so only the folder is withheld and paging still applies.

    • fix(yandex-cloud): clear the three CodeQL findings in the logging client

    CodeQL flagged three new alerts, all in yc_logging.

    The high-severity one is a false positive in a test - a host in url assertion
    reads to CodeQL as incomplete URL sanitization even though it only checks which
    host the tool called. Made it exact: parse the URL and compare the hostname,
    which is both CodeQL-clean and a stronger assertion.

    The client carried a logger that nothing used - removed it and its now-unused
    import. And the throttle's last-read timestamp was a bare module global whose
    reassignment reads as write-but-never-used, because the value is consumed on the
    next call; moved it onto a small dict so the read and write are unambiguous. No
    behaviour change - the rate limiting is identical.

    • fix(yandex-cloud): aggregate application balancer targets and drop a dead tool reference

    Two review findings, both real.

    get_yc_lb_health summarised unhealthy targets from network balancers only. The
    application balancer stored its raw target states under a different key that the
    aggregation never read, so a failing application backend left unhealthy_targets
    empty - the one answer that must not be wrong. Both kinds return the same
    getTargetStates shape, so a shared normaliser now feeds both into the summary;
    the raw application response stays for the detail the flat view drops.

    The empty-Cloud-Logging guidance still told the agent to read managed-database
    logs with read_yc_db_logs, a tool this PR does not ship. Reworded to say those
    logs are not readable yet and to fall back to metrics and cluster state.

    • fix(yandex-cloud): stop calling a target-states path the application balancer does not serve

    The application balancer reused the network balancer's :getTargetStates action,
    but that verb does not exist for it: its target states live under a nested path
    keyed by backend group and target group
    (/apploadbalancer/v1/loadBalancers/{id}/targetStates/{backend_group_id}/{target_group_id}),
    which needs the balancer's backend-group graph walked first. The old call 404s
    against a real cloud, so application target health was never real.

    Rather than ship a request the API rejects or fabricate health from it, the tool
    now lists application balancers with their status and carries a pointer to the
    real nested path, reachable through execute_yc_operation. Network balancers keep
    full per-target health, which does follow the :getTargetStates contract. The
    output docs say which is which so unhealthy_targets is not read as covering both.

    • feat(yandex-cloud): collect application balancer target health through the backend graph

    The application balancer keeps target states behind a nested path that the
    network balancer's :getTargetStates action does not reach:
    /apploadbalancer/v1/loadBalancers/{id}/targetStates/{backend_group}/{target_group}.
    Getting there means walking the balancer's graph - listener to HTTP router to
    route to backend group to target group - and only then reading targetStates,
    where health is reported per zone (a target is unhealthy only when every zone
    fails its active health check).

    get_yc_lb_health now walks that graph and normalises application targets into
    the same shape as network ones, so a failing application backend reaches the
    same unhealthy_targets summary. Single-resource reads on that path pass
    page_size=None, since Yandex answers a target-state read carrying a stray
    pageSize with a bare 404. Shapes captured from a live application balancer.

    • fix(yandex-cloud): follow gRPC routes when collecting application target health

    The backend-group walk read only http routes, so an application balancer that
    routes over gRPC never reached its backend group, and its unhealthy targets
    were left out of unhealthy_targets. A route names its backend group under its
    own protocol key, so both http and grpc are now inspected. The backend group
    itself already handled http, grpc and stream backends.

    • fix(yandex-cloud): keep the one mutating binding out of the read-only index

    A get: binding is not on its own proof that a method only reads.
    OperationService.Cancel is bound to GET /operations/{operation_id}:cancel, and
    cancelling a running operation is a mutation - so the index whose entire purpose
    is to carry no write path carried exactly one, and the generic reader would have
    executed it.

    The read-only test missed it because it checked the verbs Create, Update,
    Delete, Start and Stop, and Cancel is none of those.

    Closed in three layers: the generator now drops mutating RPC names whatever
    their HTTP binding, the shipped index has the entry removed (937 -> 936
    endpoints, all 68 services intact), and the client refuses state-changing action
    suffixes as a backstop. The suffix guard runs in the tool rather than only in
    the client, because the synthetic-backend path never reaches the client and a
    guarantee that depends on which branch a call took is not a guarantee. Read-
    shaped actions the tools rely on - :serialPortOutput, :getTargetStates, :byValue

    • still pass, and the tests now check verbs by prefix so CancelOperation is
      caught as well as Cancel.
    • fix(yandex-cloud): stop naming a discovery tool that does not exist

    The generic reader's description and the client's unknown-service error both
    told the model to call list_yc_services. No such tool ships - the discovery tool
    is find_yc_api - so an agent that followed either was handed a dead next step at
    exactly the moment it was already lost.

    SKILL.md carried the same wrong name and was fixed earlier; these two survived
    because a documentation sweep does not reach string literals in code. A test now
    asserts every tool name mentioned in the reader's description is one the
    registry actually has.

    • fix(yandex-cloud): do not report an unreadable balancer graph as healthy

    The application walk crosses three reads - virtual hosts, backend group, target
    states - and a failure in any of them produced an empty target list that the
    result presented as complete. An unreadable graph is indistinguishable from a
    balancer with no unhealthy targets, and "nothing is wrong" is the one answer
    that must never be a guess.

    A failed virtual-host read is now reported instead of being treated as an empty
    collection, and the per-balancer target_states_error is promoted into the same
    complete: false signal that page truncation already sets, naming the balancers
    whose health could not be read. Tests cover a failure at each of the three
    reads, plus a fully readable graph that must not be flagged.

    • fix(yandex-cloud): page the virtual-host read and name the window arguments correctly

    Two more places where a confident answer was not backed by what was read.

    The application walk read only the first page of a router's virtual hosts, so a
    backend group reachable only from a later page contributed no targets while the
    result still claimed to be complete - the same silent truncation the balancer
    list had, one level deeper in the graph. Every page is followed now, with a
    page cap that is reported rather than passed off as a full read.

    The empty-log advice told the agent to retry a past incident with from_time and
    to_time. read_yc_logs takes since and until, so following that advice would
    have been rejected or silently answered for the default recent window. A test
    now checks the advice only names arguments the schema actually has.

    • fix(yandex-cloud): walk every application listener shape, not just plain HTTP

    A listener is http, tls or stream, and the tls one nests a default handler plus
    any number of SNI handlers, each of which is an httpHandler or a streamHandler.
    The walk only understood http.handler and tls.defaultHandler.httpHandler, so a
    TLS SNI route, a stream listener or a TLS stream handler contributed no targets
    while the result still reported complete health.

    Stream handlers matter twice over: they name their backend group on the listener
    instead of going through an HTTP router, so walking routers alone could never
    reach them however many router shapes were covered.

    Shapes taken from the API reference rather than guessed. Tests cover all five:
    http.handler, tls.defaultHandler.httpHandler, tls.sniHandlers[].handler.
    httpHandler, stream.handler, and the two streamHandler forms.

    • fix(yandex-cloud): key target health by relationship, not by address

    One backend can serve several target groups and be healthy in one while failing
    in another. Collapsing the walk's results by IP alone let the healthy reading
    arrive first and discard the failing one, so unhealthy_targets came back empty
    for a backend that was actively failing a route.

    Records are now keyed by (backend group, target group, address) and carry both
    ids, which is also what tells an operator which route is affected rather than
    just which host.

    • fix(yandex-cloud): gate generic reads on the index and name log windows correctly

    • fixed issues

    • fixed lint issues


    Co-authored-by: Yauhen Bichel yauhen.bichel@gmail.com

    下载附件