Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.16-alpine
FROM golang:1.26-alpine

COPY . /go/src/github.com/github/freno
WORKDIR /go/src/github.com/github/freno
Expand Down
7 changes: 1 addition & 6 deletions Dockerfile.test
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
FROM golang:1.16-stretch
FROM golang:1.26-bookworm
LABEL maintainer="github@github.com"

# Update stretch repositories
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
-e 's|security.debian.org|archive.debian.org/|g' \
-e '/stretch-updates/d' /etc/apt/sources.list

RUN useradd -m testuser

RUN apt-get update
Expand Down
1 change: 1 addition & 0 deletions doc/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Noteworthy:
- `prod4` list of servers is dictated by `HAProxy`. `freno` will routinely and dynamically poll given HAProxy server for list of hosts. These will include any hosts not in `NOLB`.
- `local` cluster chooses to override `User`, `Password` and `IgnoreHostsCount`.
- `local` cluster defines a static list of hosts.
- `sharded` list of servers is read from Vitess vtctld's HTTP `API`. To read it with vtctld's gRPC `GetTablets` instead (Vitess v23 removes the HTTP API), set `"GRPCAddress": "vtctld.example.com:15999"` and `"GRPCInsecure": true`. Only plaintext gRPC is supported.


### Non lag metrics
Expand Down
32 changes: 21 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
module github.com/github/freno

go 1.20
go 1.26.0

toolchain go1.27.3

require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878
github.com/armon/go-metrics v0.4.1
github.com/boltdb/bolt v1.3.1
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/go-sql-driver/mysql v1.6.0
github.com/go-sql-driver/mysql v1.7.1
github.com/hashicorp/go-msgpack v1.1.5
github.com/julienschmidt/httprouter v1.3.0
github.com/outbrain/golib v0.0.0-20180830062331-ab954725f502
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563
golang.org/x/sys v0.1.0 // indirect
vitess.io/vitess v2.1.1+incompatible
golang.org/x/sys v0.49.0 // indirect
vitess.io/vitess v0.23.8
)

require github.com/stretchr/testify v1.3.0
require (
github.com/stretchr/testify v1.11.1
google.golang.org/grpc v1.85.0
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
golang.org/x/net v0.62.0 // indirect
golang.org/x/text v0.43.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
131 changes: 111 additions & 20 deletions go.sum

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions pkg/config/vitess_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ package config
//

type VitessConfigurationSettings struct {
API string
Cells []string
Keyspace string
Shard string
TimeoutSecs uint
API string
GRPCAddress string // vtctld gRPC host:port; if set, it is used instead of API
GRPCInsecure bool // must be true with GRPCAddress, as only plaintext gRPC is supported
Cells []string
Keyspace string
Shard string
TimeoutSecs uint
}

func (settings *VitessConfigurationSettings) IsEmpty() bool {
if settings.API == "" {
if settings.API == "" && settings.GRPCAddress == "" {
return true
}
if settings.Keyspace == "" {
Expand Down
10 changes: 7 additions & 3 deletions pkg/throttle/throttler.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,14 +378,18 @@ func (throttler *Throttler) refreshMySQLInventory() error {
}

if !clusterSettings.VitessSettings.IsEmpty() {
log.Debugf("getting vitess data from %s", clusterSettings.VitessSettings.API)
endpoint := clusterSettings.VitessSettings.API
if clusterSettings.VitessSettings.GRPCAddress != "" {
endpoint = clusterSettings.VitessSettings.GRPCAddress
}
log.Debugf("getting vitess data from %s", endpoint)
keyspace := clusterSettings.VitessSettings.Keyspace
shard := clusterSettings.VitessSettings.Shard
tablets, err := vitess.ParseTablets(clusterSettings.VitessSettings)
if err != nil {
return log.Errorf("Unable to get vitess hosts from %s, %s/%s: %+v", clusterSettings.VitessSettings.API, keyspace, shard, err)
return log.Errorf("Unable to get vitess hosts from %s, %s/%s: %+v", endpoint, keyspace, shard, err)
}
log.Debugf("Read %+v hosts from vitess %s, %s/%s, cells=%s", len(tablets), clusterSettings.VitessSettings.API,
log.Debugf("Read %+v hosts from vitess %s, %s/%s, cells=%s", len(tablets), endpoint,
keyspace, shard, strings.Join(vitess.ParseCells(clusterSettings.VitessSettings), ","),
)
clusterProbes := &mysql.ClusterProbes{
Expand Down
7 changes: 5 additions & 2 deletions pkg/vitess/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ func filterReplicaTablets(settings config.VitessConfigurationSettings, tablets [
return replicas
}

// ParseTablets reads from vitess /api/keyspace/<keyspace>/tablets/[shard] and returns a
// listing (mysql_hostname, mysql_port, type) of REPLICA tablets
// ParseTablets reads from vtctld's gRPC GetTablets if GRPCAddress is set, otherwise from vitess
// /api/keyspace/<keyspace>/tablets/[shard], and returns a listing (mysql_hostname, mysql_port, type) of REPLICA tablets
func ParseTablets(settings config.VitessConfigurationSettings) (tablets []Tablet, err error) {
if settings.GRPCAddress != "" {
return parseGRPCTablets(settings)
}
if settings.TimeoutSecs == 0 {
httpClient.Timeout = defaultTimeout
} else {
Expand Down
58 changes: 58 additions & 0 deletions pkg/vitess/grpc_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package vitess

import (
"context"
"fmt"
"time"

"github.com/github/freno/pkg/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"vitess.io/vitess/go/vt/proto/vtctldata"
"vitess.io/vitess/go/vt/proto/vtctlservice"
)

// parseGRPCTablets is the plaintext gRPC equivalent of ParseTablets' HTTP API request, using vtctld's GetTablets
func parseGRPCTablets(settings config.VitessConfigurationSettings) (tablets []Tablet, err error) {
if !settings.GRPCInsecure {
return tablets, fmt.Errorf("VitessSettings.GRPCInsecure must be true: only plaintext gRPC is supported")
}
timeout := defaultTimeout
if settings.TimeoutSecs != 0 {
timeout = time.Duration(settings.TimeoutSecs) * time.Second
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

conn, err := grpc.NewClient(settings.GRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return tablets, err
}
defer conn.Close()

request := &vtctldata.GetTabletsRequest{
Keyspace: settings.Keyspace,
Shard: settings.Shard,
Cells: ParseCells(settings),
}
if len(request.Cells) > 0 {
// GetTablets ignores Cells when Shard is set, so query only the cells and filter the shard below
request.Shard = ""
}
resp, err := vtctlservice.NewVtctldClient(conn).GetTablets(ctx, request)
if err != nil {
return tablets, err
}
for _, tablet := range resp.GetTablets() {
if settings.Shard != "" && tablet.GetShard() != settings.Shard {
continue
}
tablets = append(tablets, Tablet{
Alias: tablet.GetAlias(),
MysqlHostname: tablet.GetMysqlHostname(),
MysqlPort: tablet.GetMysqlPort(),
Type: tablet.GetType(),
})
}
return filterReplicaTablets(settings, tablets), nil
}
102 changes: 102 additions & 0 deletions pkg/vitess/grpc_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package vitess

import (
"context"
"net"
"sync"
"testing"

"github.com/github/freno/pkg/config"
"google.golang.org/grpc"
"vitess.io/vitess/go/vt/proto/topodata"
"vitess.io/vitess/go/vt/proto/vtctldata"
"vitess.io/vitess/go/vt/proto/vtctlservice"
)

type fakeVtctld struct {
vtctlservice.UnimplementedVtctldServer
mu sync.Mutex
request *vtctldata.GetTabletsRequest
}

func (vtctld *fakeVtctld) GetTablets(_ context.Context, request *vtctldata.GetTabletsRequest) (*vtctldata.GetTabletsResponse, error) {
vtctld.mu.Lock()
vtctld.request = request
vtctld.mu.Unlock()
return &vtctldata.GetTabletsResponse{Tablets: []*topodata.Tablet{
{Alias: &topodata.TabletAlias{Cell: "cell1"}, Shard: "-80", MysqlHostname: "replica1", MysqlPort: 3306, Type: topodata.TabletType_REPLICA},
{Alias: &topodata.TabletAlias{Cell: "cell2"}, Shard: "-80", MysqlHostname: "replica2", MysqlPort: 3306, Type: topodata.TabletType_REPLICA},
{Alias: &topodata.TabletAlias{Cell: "cell2"}, Shard: "80-", MysqlHostname: "replica3", MysqlPort: 3306, Type: topodata.TabletType_REPLICA},
{Alias: &topodata.TabletAlias{Cell: "cell2"}, Shard: "-80", MysqlHostname: "primary", MysqlPort: 3306, Type: topodata.TabletType_PRIMARY},
}}, nil
}

func (vtctld *fakeVtctld) lastRequest() *vtctldata.GetTabletsRequest {
vtctld.mu.Lock()
defer vtctld.mu.Unlock()
return vtctld.request
}

func TestParseTabletsGRPC(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
vtctld := &fakeVtctld{}
server := grpc.NewServer()
vtctlservice.RegisterVtctldServer(server, vtctld)
go server.Serve(listener)
defer server.Stop()

t.Run("shard-and-cells", func(t *testing.T) {
tablets, err := ParseTablets(config.VitessConfigurationSettings{
GRPCAddress: listener.Addr().String(),
GRPCInsecure: true,
Cells: []string{"cell2"},
Keyspace: "test",
Shard: "-80",
})
if err != nil {
t.Fatalf("Expected no error, got %q", err)
}
if len(tablets) != 1 || tablets[0].MysqlHostname != "replica2" || tablets[0].MysqlPort != 3306 {
t.Fatalf("Expected only replica2:3306, got %+v", tablets)
}

// vtctld ignores Cells when Shard is set, so the request must be cell-scoped and the shard filtered locally
request := vtctld.lastRequest()
if request.GetKeyspace() != "test" || request.GetShard() != "" || len(request.GetCells()) != 1 || request.GetCells()[0] != "cell2" {
t.Fatalf("Unexpected GetTablets request: %+v", request)
}
})

t.Run("shard", func(t *testing.T) {
tablets, err := ParseTablets(config.VitessConfigurationSettings{
GRPCAddress: listener.Addr().String(),
GRPCInsecure: true,
Keyspace: "test",
Shard: "-80",
})
if err != nil {
t.Fatalf("Expected no error, got %q", err)
}
if len(tablets) != 2 || tablets[0].MysqlHostname != "replica1" || tablets[1].MysqlHostname != "replica2" {
t.Fatalf("Expected replica1 and replica2, got %+v", tablets)
}

request := vtctld.lastRequest()
if request.GetKeyspace() != "test" || request.GetShard() != "-80" || len(request.GetCells()) != 0 {
t.Fatalf("Unexpected GetTablets request: %+v", request)
}
})

t.Run("insecure-required", func(t *testing.T) {
_, err := ParseTablets(config.VitessConfigurationSettings{
GRPCAddress: listener.Addr().String(),
Keyspace: "test",
})
if err == nil {
t.Fatal("Expected error, got nil")
}
})
}
2 changes: 1 addition & 1 deletion script/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -e

readonly supportedGo="go1.2[012345]"
readonly supportedGo="go1\.2[67]"

# Ensure go is installed
if ! command -v go ; then
Expand Down
2 changes: 2 additions & 0 deletions vendor/github.com/armon/go-metrics/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading