Skip to content

feat: migrate RPC surface to Connect-RPC#174

Merged
rustatian merged 5 commits into
masterfrom
feature/connect-rpc-migration
May 13, 2026
Merged

feat: migrate RPC surface to Connect-RPC#174
rustatian merged 5 commits into
masterfrom
feature/connect-rpc-migration

Conversation

@rustatian
Copy link
Copy Markdown
Member

@rustatian rustatian commented May 13, 2026

Summary

Migrates the metrics plugin's RPC surface from net/rpc + goridge codec to Connect-RPC over HTTP/2 (h2c), using metricsV1connect.NewMetricsServiceHandler from api-go v6.0.0-beta.9.

RPC HTTP method Notes
Add / Sub / Observe / Set POST each wraps a Metric in its own dedicated request type
Declare POST wraps NamedCollector; idempotent on existing names
Unregister POST takes a name

No GET candidates — every method mutates Prometheus registry state. Scraping continues to happen on the separate Prometheus HTTP endpoint configured under metrics.address.

Typed Connect error codes from the handler:

  • unknown collector → CodeNotFound
  • operation unsupported by collector kind (e.g. Set on a Histogram) → CodeFailedPrecondition
  • missing labels on a *Vec collector → CodeInvalidArgument
  • prometheus registry errors → CodeInternal

All three wire protocols reachable on the same rpc.listen port. New tests/metrics_api_test.go covers Connect / plain HTTP+protojson / plain gRPC, plus an HTTP-GET idempotency table verifying all six methods reject GET.

Breaking changes

Wire protocol change: callers using net/rpc + goridge codec must switch to a Connect / HTTP / gRPC client targeting /metrics.v1.MetricsService/{Add,Sub,Observe,Set,Declare,Unregister}. Note: the wire encoding of Collector.Objectives changes from map<double, double> (Go-side) to repeated Objective { quantile, error } (proto3 forbids float-keyed maps).

Dep bumps:

  • github.com/roadrunner-server/api-go/v6v6.0.0-beta.9
  • github.com/roadrunner-server/goridge/v4v4.0.0-beta.2
  • github.com/roadrunner-server/rpc/v6 (tests) → v6.0.0-beta.4
  • github.com/roadrunner-server/http/v6 (tests, transitive) → v6.0.0-beta.7

Summary by CodeRabbit

  • New Features

    • Metrics API now serves over Connect-RPC, gRPC, and HTTP for broader interoperability.
  • Refactor

    • Modernized the metrics service to a Connect-based RPC surface and centralized collector lifecycle handling.
  • Tests

    • Added end-to-end integration tests covering Connect, gRPC, and HTTP; updated existing tests to exercise the new RPC surface.
  • Chores

    • Updated module dependencies to support the new RPC/transport stack.

Review Change Stack

- plugin.go RPC() now returns (string, http.Handler) via
  metricsV1connect.NewMetricsServiceHandler from api-go v6.0.0-beta.9
- rpc.go reshaped to the Connect handler interface with 6 methods:
  Add / Sub / Observe / Set / Declare / Unregister
- handler-side lookup with typed Connect codes:
  - unknown collector → CodeNotFound
  - operation unsupported by collector kind → CodeFailedPrecondition
  - missing labels on a *Vec collector → CodeInvalidArgument
  - prometheus registry errors → CodeInternal
- vecMetric / vecObserver generic helpers consolidate the GaugeVec /
  CounterVec / HistogramVec / SummaryVec label-validation + lookup path
- tests/metrics_test.go: 15 goridge client.Call sites + 14 client
  constructions collapsed into a single newMetricsClient helper; tests
  now call typed Connect methods with toProtoCollector translating
  metrics.NamedCollector → metricsV1.NamedCollector
- new tests/metrics_api_test.go covers Connect, plain HTTP+protojson,
  plain gRPC against the same handler, plus HTTP-GET idempotency
  (all 6 methods POST-only — no GET candidates)
- new tests/configs/.rr-metrics-api.yaml minimal config for the API tests
- bump api-go to v6.0.0-beta.9, goridge to v4.0.0-beta.2, rpc/v6 to v6.0.0-beta.4,
  http/v6 to v6.0.0-beta.7 (typecheck-fix upstream)
- replace http.NewRequest/http.Get with NewRequestWithContext + Client.Do in
  unrelated test helpers (lint surfaced after http/v6 typecheck started working)
Copilot AI review requested due to automatic review settings May 13, 2026 10:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Warning

Rate limit exceeded

@rustatian has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 49 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3713b1b-593e-4698-8ec5-602b69ea268a

📥 Commits

Reviewing files that changed from the base of the PR and between 947e840 and d8dc27b.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • .github/workflows/linux.yml
  • rpc.go
  • tests/metrics_api_test.go
📝 Walkthrough

Walkthrough

Migrates the metrics RPC from the legacy goridge in-process interface to a ConnectRPC HTTP/HTTP2 service using Protobuf types. Updates module deps (adds Connect, pins gRPC/protobuf), replaces the old Metric API and rpc wrappers with Connect handlers, rewires the plugin to expose an HTTP handler, and converts/extends tests to exercise Connect, HTTP, and gRPC endpoints.

Changes

ConnectRPC metrics API and tests

Layer / File(s) Summary
Dependency and module updates
go.mod, tests/go.mod
Add connectrpc.com/connect, promote/pin google.golang.org/grpc and google.golang.org/protobuf, bump roadrunner-server modules, adjust indirect deps (x/net, x/text, genproto).
Plugin wiring: expose Connect handler
plugin.go
Import generated Connect service (metricsV1connect) and change (*Plugin).RPC() signature to return (string, http.Handler) using metricsV1connect.NewMetricsServiceHandler(&rpc{...}).
ConnectRPC handlers: core implementation
rpc.go
Replace legacy in-process RPC methods with Connect handlers for Add, Sub, Observe, Set, Declare, and Unregister that accept context.Context and *connect.Request[...] and return *connect.Response[...]/error. Add helpers: lookupCollector (central sync-map lookup → Connect error codes), vecMetric (validate labels and fetch vector instances → map errors to CodeInvalidArgument), and buildPromCollector (construct Prometheus collectors and vector variants from metricsV1.NamedCollector). Add file-level sentinel errors and update collector lifecycle (register/unregister) to map Prometheus behavior into Connect responses. Removed the exported legacy Metric type.
Test harness and config
tests/configs/.rr-metrics-api.yaml, tests/metrics_api_test.go
Add YAML test config for RPC/metrics endpoints and implement startMetricsAPIContainer to boot an endure container with logger, RPC, and metrics plugins. Add integration tests exercising Connect, HTTP (protojson), and gRPC clients and HTTP method idempotency checks.
Unit/integration test migration to Connect clients
tests/metrics_test.go
Replace goridge/net RPC usage with an HTTP/2 Connect client factory newMetricsClient, add toProtoCollector helper, and rewrite metric helper tests to call client.Declare, client.Add, client.Set, client.Sub, client.Observe, and client.Unregister. Update HTTP GET utilities to use context-aware requests.
Test deletions / cleanup
rpc_test.go
Removed legacy rpc_test.go including the old Metric JSON/msgpack unmarshal tests (the exported Metric type was removed).
sequenceDiagram
    participant Client as Client (Connect/HTTP/gRPC)
    participant HTTP as Plugin HTTP handler
    participant Plugin as Metrics Plugin (sync.Map)
    participant Prom as Prometheus Registry
    Note over Client,Prom: Declare / Add / Observe / Set / Unregister flows
    Client->>HTTP: POST /metrics (Connect req proto)
    HTTP->>Plugin: Invoke Connect handler (Declare/Add/...)
    Plugin->>Plugin: lookupCollector(name) / buildPromCollector(...)
    alt Declare new collector
        Plugin->>Prom: Register collector
        Prom-->>Plugin: OK / error
        Plugin-->>HTTP: connect.Response{Ok: true} / error (mapped)
    else Mutate metric (Add/Sub/Observe/Set)
        Plugin->>Plugin: resolve collector type
        Plugin->>Plugin: if vector -> vecMetric(labelValues) -> get metric instance
        Plugin->>Prom: update metric or return unsupported
        Prom-->>Plugin: OK / error
        Plugin-->>HTTP: connect.Response{Ok:true} or Connect error code
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped from goridge's old nest,
To ConnectRPC's shiny new trail,
Protos in paw and tests in my vest,
Metrics declare, observe, and set without fail,
Prometheus gardens grow—hop, wag, prevail!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the primary change: migrating the metrics RPC surface from the legacy net/rpc + goridge approach to Connect-RPC, which is the main objective of the changeset.
Description check ✅ Passed The pull request description is comprehensive, covering the migration details, wire protocols, error codes, breaking changes, and dependency bumps. However, the 'Reason for This PR' section lacks explicit reference to an issue number or link.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/connect-rpc-migration

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the metrics plugin’s RPC interface from net/rpc/goridge to a Connect-RPC surface served over HTTP/2 (h2c), aligning the plugin with the metrics.v1.MetricsService API from api-go/v6.

Changes:

  • Replaced the in-process RPC implementation with Connect-RPC handlers (metricsV1connect.NewMetricsServiceHandler) and updated request/response handling + error mapping.
  • Updated existing tests to call the Connect client and added a new API test suite covering Connect, plain HTTP+protojson, and gRPC.
  • Bumped/added Go module dependencies to support Connect-RPC, HTTP/2, and gRPC/protobuf in tests.

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
rpc.go Implements Connect-RPC methods (Add/Sub/Observe/Set/Declare/Unregister) and helper functions for collector lookup/building.
plugin.go Changes RPC() to return a Connect handler mount (string, http.Handler).
tests/metrics_test.go Migrates existing RPC tests from goridge/net-rpc to a Connect client.
tests/metrics_api_test.go Adds end-to-end API coverage for Connect, plain HTTP JSON, and gRPC, plus GET rejection.
tests/configs/.rr-metrics-api.yaml Adds a dedicated config for the new API tests.
go.mod / go.sum Adds Connect-RPC + related networking/proto deps.
tests/go.mod / tests/go.sum Updates test-module deps to include Connect-RPC, gRPC, protojson, and bumps RoadRunner module versions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/metrics_api_test.go
Comment thread rpc.go Outdated
Comment thread rpc.go
- restore exact legacy log-message strings to preserve TestMetricsDifferentRPCCalls
  count assertions ("metric successfully added", "subtracting value from metric",
  "subtracting operation finished successfully", "observe operation finished
  successfully", "set operation finished successfully", "declaring new metric",
  "failed to get metrics with label values", "required labels for collector",
  "collector was successfully unregistered")
- map GetMetricWithLabelValues / missing-labels errors to CodeInvalidArgument
  (caller-input class, not internal)
- bump test config metrics.address 127.0.0.1:2115 → :2118 to avoid port
  collision with .rr-metrics-http.yaml
- add Summary/SummaryVec Observe + unspecified-CollectorType Declare coverage
  in TestMetricsConnectAPI
- drop rpc_test.go: msgpack/json wire-format tests for the deleted Go-side
  Metric struct (relic of the goridge codec)
- end-of-migration go get -u all: server/v6 beta.5→beta.6, quic-go, sysconf
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rpc.go`:
- Around line 45-53: The code currently forwards client-supplied metric value
m.GetValue() directly into Counter.Add (both in the prometheus.Counter case and
after vecMetric returns cv for *prometheus.CounterVec), which will panic if the
value is negative; before calling c.Add(...) or cv.Add(...), check if
m.GetValue() < 0 and return an error (e.g., fmt.Errorf("negative value for
counter: %v", m.GetValue())) to reject bad input; update the branch handling
prometheus.Counter and the branch that calls vecMetric (and then cv.Add) to
perform this validation and return the error rather than calling Add with a
negative value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51fdf622-d328-4015-a820-69fb831fce64

📥 Commits

Reviewing files that changed from the base of the PR and between 97511ff and 9133b35.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • tests/go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • go.mod
  • plugin.go
  • rpc.go
  • tests/configs/.rr-metrics-api.yaml
  • tests/go.mod
  • tests/metrics_api_test.go
  • tests/metrics_test.go

Comment thread rpc.go
rustatian added 2 commits May 13, 2026 16:47
When prometheus.registry.Unregister returns false, the old goridge handler
left the RPC return value at its zero (false) — collector removed from RR
map but not from prometheus, caller could detect the divergence. I had
collapsed both branches to Ok:true. Restoring the legacy contract.
rpc_test.go was removed in efb2ab1 (msgpack/json wire-format tests for
the deleted Go-side Metric struct, goridge codec relic). The workflow's
explicit file list still referenced it, so unit tests failed with
'no Go files' or 'file not found' on the runner.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
rpc.go (1)

45-52: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Reject negative counter deltas before calling Add.

Line 46 and Line 52 pass client input directly into Counter.Add; negative values can panic and crash the handler.

Suggested fix
 	case prometheus.Counter:
+		if m.GetValue() < 0 {
+			return nil, connect.NewError(connect.CodeInvalidArgument,
+				fmt.Errorf("counter %s cannot be incremented by a negative value", m.GetName()))
+		}
 		c.Add(m.GetValue())
 	case *prometheus.CounterVec:
 		cv, err := vecMetric(r, c, m)
 		if err != nil {
 			return nil, err
 		}
+		if m.GetValue() < 0 {
+			return nil, connect.NewError(connect.CodeInvalidArgument,
+				fmt.Errorf("counter %s cannot be incremented by a negative value", m.GetName()))
+		}
 		cv.Add(m.GetValue())
In github.com/prometheus/client_golang v1.23.2, does Counter.Add panic when passed a negative value?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rpc.go` around lines 45 - 52, Reject negative counter deltas before calling
Add: check the incoming metric value (m.GetValue()) for < 0 in both branches
handling prometheus.Counter and *prometheus.CounterVec (the code paths that call
Counter.Add and cv.Add), and return a descriptive error instead of passing it to
Counter.Add; for the CounterVec branch, call vecMetric(r, c, m) first as now but
validate m.GetValue() is non-negative before invoking cv.Add. Ensure the error
message references the metric and the negative value for easier debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rpc.go`:
- Around line 98-113: The Observe switch in function Observe is missing handling
for scalar prometheus.Summary values: add a case for prometheus.Summary (similar
to the existing prometheus.Histogram case) that calls c.Observe(m.GetValue());
keep the existing *prometheus.SummaryVec branch for vector summaries and leave
the default error handling unchanged.

---

Duplicate comments:
In `@rpc.go`:
- Around line 45-52: Reject negative counter deltas before calling Add: check
the incoming metric value (m.GetValue()) for < 0 in both branches handling
prometheus.Counter and *prometheus.CounterVec (the code paths that call
Counter.Add and cv.Add), and return a descriptive error instead of passing it to
Counter.Add; for the CounterVec branch, call vecMetric(r, c, m) first as now but
validate m.GetValue() is non-negative before invoking cv.Add. Ensure the error
message references the metric and the negative value for easier debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: acaabf7a-4f35-4b2b-be3c-1aff7ed49b51

📥 Commits

Reviewing files that changed from the base of the PR and between 9133b35 and 947e840.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • tests/go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • go.mod
  • rpc.go
  • rpc_test.go
  • tests/configs/.rr-metrics-api.yaml
  • tests/go.mod
  • tests/metrics_api_test.go
💤 Files with no reviewable changes (1)
  • rpc_test.go
✅ Files skipped from review due to trivial changes (1)
  • tests/configs/.rr-metrics-api.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/metrics_api_test.go

Comment thread rpc.go
…itch branch

prometheus.Histogram and prometheus.Summary share identical method sets,
so a scalar Summary value matches the existing case prometheus.Histogram
branch (Go type-switch picks the first interface match in source order).
The new test locks this in; the type-switch comment explains why a
dedicated Summary case would be dead code (staticcheck SA4020).
@rustatian rustatian self-assigned this May 13, 2026
@rustatian rustatian added enhancement New feature or request labels May 13, 2026
@rustatian rustatian merged commit 5ffa469 into master May 13, 2026
5 of 6 checks passed
@rustatian rustatian deleted the feature/connect-rpc-migration branch May 13, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants