Skip to content

Add ProxySQL topology query API#37

Merged
renecannao merged 1 commit intomasterfrom
issue31-proxysql-topology
Mar 24, 2026
Merged

Add ProxySQL topology query API#37
renecannao merged 1 commit intomasterfrom
issue31-proxysql-topology

Conversation

@renecannao
Copy link
Copy Markdown

Summary

  • Add ServerEntry struct and GetServers()/GetServersByHostgroup() query methods to the ProxySQL client package
  • Expose two new HTTP API endpoints: GET /api/proxysql/servers and GET /api/proxysql/servers/:hostgroup
  • Add GetClient() accessor to Hook for use by the HTTP layer
  • Document the new topology API endpoints in docs/proxysql-hooks.md

Closes #31

Test plan

  • go build succeeds
  • go test ./go/proxysql/... -v passes (10 tests including new topology tests)
  • Manual test with a running ProxySQL instance to verify server list queries
  • Verify API returns proper error when ProxySQL is not configured

Add server query functions to the ProxySQL client (GetServers,
GetServersByHostgroup) and expose them as HTTP API endpoints at
/api/proxysql/servers and /api/proxysql/servers/:hostgroup.

- New go/proxysql/topology.go with ServerEntry struct and query methods
- New go/proxysql/topology_test.go with struct and nil-client tests
- GetClient() accessor on Hook for use by HTTP handlers
- API endpoints registered in go/http/api.go
- Documentation updated in docs/proxysql-hooks.md

Closes #31
Copilot AI review requested due to automatic review settings March 24, 2026 00:13
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Warning

Rate limit exceeded

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

⌛ 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c42927e5-24f9-4251-9af2-57329b6f4126

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee3f12 and ed8054b.

📒 Files selected for processing (5)
  • docs/proxysql-hooks.md
  • go/http/api.go
  • go/proxysql/hook.go
  • go/proxysql/topology.go
  • go/proxysql/topology_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue31-proxysql-topology

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.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new ProxySQL topology query API, providing HTTP endpoints to retrieve detailed information about ProxySQL's runtime_mysql_servers. This enhancement allows for better monitoring, debugging, and integration with other tools by exposing the current state of backend MySQL servers managed by ProxySQL.

Highlights

  • ProxySQL Client Enhancements: Introduced ServerEntry struct and GetServers(), GetServersByHostgroup() methods to the ProxySQL client package for querying server topology.
  • New HTTP API Endpoints: Exposed GET /api/proxysql/servers and GET /api/proxysql/servers/:hostgroup to allow external systems to retrieve ProxySQL server lists.
  • Hook Client Accessor: Added a GetClient() accessor to the Hook struct, enabling the HTTP layer to interact with the ProxySQL client.
  • API Documentation: Documented the newly added topology API endpoints in docs/proxysql-hooks.md.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new API for querying ProxySQL server topology, which is a valuable addition for monitoring and debugging. The changes include new API endpoints, corresponding data access logic in the proxysql package, and documentation. The implementation is solid. I've provided a couple of suggestions to reduce code duplication and improve maintainability in the new API handlers and data access functions. Overall, this is a great enhancement.

Comment thread go/http/api.go
Comment on lines +3768 to +3771
hook := proxysql.GetHook()
if hook == nil || !hook.IsConfigured() {
Respond(r, &APIResponse{Code: ERROR, Message: "ProxySQL is not configured"})
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic to get the ProxySQL hook and check if it's configured is duplicated here and in the ProxySQLServersByHostgroup function. To improve maintainability and reduce code duplication, consider extracting this logic into a private helper method on HttpAPI.

For example, you could create a helper like this:

func (this *HttpAPI) getProxySQLClient(r render.Render) *proxysql.Client {
	hook := proxysql.GetHook()
	if hook == nil || !hook.IsConfigured() {
		Respond(r, &APIResponse{Code: ERROR, Message: "ProxySQL is not configured"})
		return nil
	}
	return hook.GetClient()
}

Then, both ProxySQLServers and ProxySQLServersByHostgroup can call this helper at the beginning:

func (this *HttpAPI) ProxySQLServers(params martini.Params, r render.Render, req *http.Request) {
	client := this.getProxySQLClient(r)
	if client == nil {
		return
	}
    // ... rest of the function
}

Comment thread go/proxysql/topology.go
Comment on lines +22 to +80
func (c *Client) GetServers() ([]ServerEntry, error) {
if c == nil {
return []ServerEntry{}, nil
}

query := "SELECT hostgroup_id, hostname, port, status, weight, max_connections, comment FROM runtime_mysql_servers ORDER BY hostgroup_id, hostname, port"
rows, db, err := c.Query(query)
if err != nil {
return nil, fmt.Errorf("proxysql: GetServers: %v", err)
}
defer db.Close()
defer rows.Close()

var servers []ServerEntry
for rows.Next() {
var s ServerEntry
if err := rows.Scan(&s.HostgroupID, &s.Hostname, &s.Port, &s.Status, &s.Weight, &s.MaxConns, &s.Comment); err != nil {
return nil, fmt.Errorf("proxysql: GetServers scan: %v", err)
}
servers = append(servers, s)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("proxysql: GetServers rows: %v", err)
}

log.Infof("proxysql: GetServers returned %d entries", len(servers))
return servers, nil
}

// GetServersByHostgroup returns servers filtered by hostgroup ID.
// Returns an empty slice (no error) if the client is nil.
func (c *Client) GetServersByHostgroup(hostgroupID int) ([]ServerEntry, error) {
if c == nil {
return []ServerEntry{}, nil
}

query := "SELECT hostgroup_id, hostname, port, status, weight, max_connections, comment FROM runtime_mysql_servers WHERE hostgroup_id = ? ORDER BY hostname, port"
rows, db, err := c.Query(query, hostgroupID)
if err != nil {
return nil, fmt.Errorf("proxysql: GetServersByHostgroup(%d): %v", hostgroupID, err)
}
defer db.Close()
defer rows.Close()

var servers []ServerEntry
for rows.Next() {
var s ServerEntry
if err := rows.Scan(&s.HostgroupID, &s.Hostname, &s.Port, &s.Status, &s.Weight, &s.MaxConns, &s.Comment); err != nil {
return nil, fmt.Errorf("proxysql: GetServersByHostgroup scan: %v", err)
}
servers = append(servers, s)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("proxysql: GetServersByHostgroup rows: %v", err)
}

log.Infof("proxysql: GetServersByHostgroup(%d) returned %d entries", hostgroupID, len(servers))
return servers, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic for iterating through *sql.Rows and scanning them into ServerEntry structs is duplicated in GetServers and GetServersByHostgroup. You can extract this into a helper function to avoid repetition and make the code more modular. This will improve the maintainability of this new file.

func (c *Client) GetServers() ([]ServerEntry, error) {
	if c == nil {
		return []ServerEntry{}, nil
	}

	query := "SELECT hostgroup_id, hostname, port, status, weight, max_connections, comment FROM runtime_mysql_servers ORDER BY hostgroup_id, hostname, port"
	rows, db, err := c.Query(query)
	if err != nil {
		return nil, fmt.Errorf("proxysql: GetServers: %v", err)
	}
	defer db.Close()
	defer rows.Close()

	servers, err := scanServerEntries(rows)
	if err != nil {
		return nil, fmt.Errorf("proxysql: GetServers: failed to process rows: %v", err)
	}

	log.Infof("proxysql: GetServers returned %d entries", len(servers))
	return servers, nil
}

// GetServersByHostgroup returns servers filtered by hostgroup ID.
// Returns an empty slice (no error) if the client is nil.
func (c *Client) GetServersByHostgroup(hostgroupID int) ([]ServerEntry, error) {
	if c == nil {
		return []ServerEntry{}, nil
	}

	query := "SELECT hostgroup_id, hostname, port, status, weight, max_connections, comment FROM runtime_mysql_servers WHERE hostgroup_id = ? ORDER BY hostname, port"
	rows, db, err := c.Query(query, hostgroupID)
	if err != nil {
		return nil, fmt.Errorf("proxysql: GetServersByHostgroup(%d): %v", hostgroupID, err)
	}
	defer db.Close()
	defer rows.Close()

	servers, err := scanServerEntries(rows)
	if err != nil {
		return nil, fmt.Errorf("proxysql: GetServersByHostgroup: failed to process rows: %v", err)
	}

	log.Infof("proxysql: GetServersByHostgroup(%d) returned %d entries", hostgroupID, len(servers))
	return servers, nil
}

// scanServerEntries is a helper to scan rows into a slice of ServerEntry.
func scanServerEntries(rows *sql.Rows) ([]ServerEntry, error) {
	var servers []ServerEntry
	for rows.Next() {
		var s ServerEntry
		if err := rows.Scan(&s.HostgroupID, &s.Hostname, &s.Port, &s.Status, &s.Weight, &s.MaxConns, &s.Comment); err != nil {
			return nil, err
		}
		servers = append(servers, s)
	}
	return servers, rows.Err()
}

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

Adds a ProxySQL topology query surface to orchestrator by extending the ProxySQL client with runtime_mysql_servers queries and exposing those results via new HTTP API endpoints.

Changes:

  • Add ServerEntry plus GetServers() / GetServersByHostgroup() to the ProxySQL client package.
  • Add HTTP endpoints GET /api/proxysql/servers and GET /api/proxysql/servers/:hostgroup to return ProxySQL server entries.
  • Document the new topology endpoints in docs/proxysql-hooks.md and add a Hook.GetClient() accessor for the HTTP layer.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
go/proxysql/topology.go Introduces a server row model and query helpers against runtime_mysql_servers.
go/proxysql/topology_test.go Adds basic unit tests for the new struct and nil-client behavior.
go/proxysql/hook.go Exposes the underlying ProxySQL client via a new accessor.
go/http/api.go Registers and implements new REST endpoints to fetch ProxySQL server topology.
docs/proxysql-hooks.md Documents the new topology query endpoints and sample responses.

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

Comment thread go/proxysql/hook.go
Comment on lines +32 to +38
// GetClient returns the underlying ProxySQL client, or nil if the hook is not configured.
func (h *Hook) GetClient() *Client {
if h == nil {
return nil
}
return h.client
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The GetClient() docstring says it returns nil when the hook is not configured, but the implementation only checks h == nil and otherwise returns h.client even when writerHostgroup is 0 (i.e., IsConfigured() is false). Either update the comment to reflect the actual behavior, or change GetClient() to return nil when !h.IsConfigured() to avoid misleading callers.

Copilot uses AI. Check for mistakes.
Comment thread go/http/api.go
Comment on lines +3768 to +3772
hook := proxysql.GetHook()
if hook == nil || !hook.IsConfigured() {
Respond(r, &APIResponse{Code: ERROR, Message: "ProxySQL is not configured"})
return
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

These endpoints treat ProxySQL as “not configured” unless hook.IsConfigured() is true, but IsConfigured() currently requires ProxySQLWriterHostgroup > 0. That prevents using the topology query API when only the ProxySQL Admin connection is configured (address/user/pass) but failover hooks are intentionally disabled. Consider gating on hook.GetClient() != nil (or adding a dedicated “admin client configured” check) so read-only topology queries can work without writer hostgroup configuration.

Copilot uses AI. Check for mistakes.
Comment thread go/proxysql/topology.go
"github.com/proxysql/golib/log"
)

// ServerEntry represents a row from ProxySQL's mysql_servers table.
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The struct comment says ServerEntry represents a row from mysql_servers, but the new query methods read from runtime_mysql_servers. Please update the comment to match the actual source table (or mention both if intended).

Suggested change
// ServerEntry represents a row from ProxySQL's mysql_servers table.
// ServerEntry represents a row from ProxySQL's runtime_mysql_servers table
// (the runtime view of the mysql_servers configuration).

Copilot uses AI. Check for mistakes.
Comment thread go/proxysql/topology.go
Comment on lines +43 to +48
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("proxysql: GetServers rows: %v", err)
}

log.Infof("proxysql: GetServers returned %d entries", len(servers))
return servers, nil
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

GetServers/GetServersByHostgroup log at Info level on every call. Since these methods may be invoked frequently via the new HTTP endpoints (polling/monitoring), this can create noisy logs in production. Consider lowering this to a debug-level log or removing it (or rate-limiting) to avoid log volume issues.

Copilot uses AI. Check for mistakes.
@renecannao renecannao merged commit 727434b into master Mar 24, 2026
6 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3.2: ProxySQL-aware topology management

2 participants