diff --git a/MODULE.bazel b/MODULE.bazel index a02ef9c6..32c7cb19 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -33,6 +33,8 @@ use_repo( "com_github_go_sql_driver_mysql", "com_github_gogo_protobuf", "com_github_stretchr_testify", + "com_github_testcontainers_testcontainers_go", + "com_github_testcontainers_testcontainers_go_modules_mysql", "com_github_uber_go_tally_v4", "org_golang_google_grpc", "org_golang_google_protobuf", diff --git a/Makefile b/Makefile index e625d90b..3239e6bb 100644 --- a/Makefile +++ b/Makefile @@ -68,10 +68,10 @@ integration-test: @echo "Running all service integration tests..." @$(BAZEL) test //gateway/integration_tests:integration_tests_test //orchestrator/integration_tests:integration_tests_test //speculator/integration_tests:integration_tests_test --test_output=all -# Run end-to-end tests (requires all services to be running) +# Run end-to-end integration tests (hermetic, no manual server setup needed) e2e-test: - @echo "Running end-to-end tests..." - @$(BAZEL) test //integration_tests:e2e_test --test_output=all + @echo "Running integration tests..." + @$(BAZEL) test //integration_tests:integration_test --test_output=all # Clean generated files and binaries clean: @@ -186,7 +186,9 @@ help: @echo " make integration-test-orchestrator - Test Orchestrator service" @echo " make integration-test-speculator - Test Speculator service" @echo " make integration-test - Test all services" - @echo " make e2e-test - Run end-to-end tests" + @echo "" + @echo "End-to-End Tests (hermetic, no setup needed):" + @echo " make e2e-test - Run integration tests with Testcontainers" @echo "" @echo "Run Clients:" @echo " make run-client-gateway - Run gateway client" diff --git a/examples/server/gateway/BUILD.bazel b/examples/server/gateway/BUILD.bazel index c483e448..46c4bf02 100644 --- a/examples/server/gateway/BUILD.bazel +++ b/examples/server/gateway/BUILD.bazel @@ -6,6 +6,7 @@ go_library( importpath = "github.com/uber/submitqueue/examples/server/gateway", visibility = ["//visibility:private"], deps = [ + "//extensions/storage/mysql", "//gateway/controller", "//gateway/protopb", "@com_github_uber_go_tally_v4//:tally", diff --git a/examples/server/gateway/main.go b/examples/server/gateway/main.go index 2f20e4d4..a27a0030 100644 --- a/examples/server/gateway/main.go +++ b/examples/server/gateway/main.go @@ -11,6 +11,7 @@ import ( "time" "github.com/uber-go/tally/v4" + "github.com/uber/submitqueue/extensions/storage/mysql" "github.com/uber/submitqueue/gateway/controller" pb "github.com/uber/submitqueue/gateway/protopb" "go.uber.org/zap" @@ -21,12 +22,18 @@ import ( // GatewayServer wraps the controller and implements the gRPC service interface type GatewayServer struct { pb.UnimplementedSubmitQueueGatewayServer - controller *controller.PingController + pingController *controller.PingController + landController *controller.LandController } // Ping delegates to the controller func (s *GatewayServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PingResponse, error) { - return s.controller.Ping(ctx, req) + return s.pingController.Ping(ctx, req) +} + +// Land delegates to the controller +func (s *GatewayServer) Land(ctx context.Context, req *pb.LandRequest) (*pb.LandResponse, error) { + return s.landController.Land(ctx, req) } func main() { @@ -75,21 +82,42 @@ func run() error { metricsWgDone.Wait() }() + // Initialize MySQL storage factory + mysqlDSN := os.Getenv("MYSQL_DSN") + if mysqlDSN == "" { + mysqlDSN = "root:root@tcp(localhost:3306)/submitqueue?parseTime=true" + } + storeFactory, err := mysql.NewFactory(mysql.MySQLParameters{ + DSN: mysqlDSN, + MaxOpenConns: 10, + MaxIdleConns: 5, + ConnMaxLifetime: 5 * time.Minute, + }) + if err != nil { + return fmt.Errorf("failed to create MySQL storage factory: %w", err) + } + defer storeFactory.Close() + // Create gRPC server grpcServer := grpc.NewServer() - // Create ping controller and wrap it for gRPC + // Create controllers and wrap them for gRPC pingController := controller.NewPingController(logger, scope) + landController := controller.NewLandController(logger, scope, storeFactory) gatewayServer := &GatewayServer{ - controller: pingController, + pingController: pingController, + landController: landController, } pb.RegisterSubmitQueueGatewayServer(grpcServer, gatewayServer) // Register reflection service for debugging with grpcurl reflection.Register(grpcServer) - // Listen on port 8081 - port := ":8081" + // Listen on configurable port + port := os.Getenv("PORT") + if port == "" { + port = ":8081" + } listener, err := net.Listen("tcp", port) if err != nil { return fmt.Errorf("failed to listen on port %s: %w", port, err) diff --git a/examples/server/orchestrator/main.go b/examples/server/orchestrator/main.go index 9dd82dc3..588b9d60 100644 --- a/examples/server/orchestrator/main.go +++ b/examples/server/orchestrator/main.go @@ -88,8 +88,11 @@ func run() error { // Register reflection service for debugging with grpcurl reflection.Register(grpcServer) - // Listen on port 8082 - port := ":8082" + // Listen on configurable port + port := os.Getenv("PORT") + if port == "" { + port = ":8082" + } listener, err := net.Listen("tcp", port) if err != nil { return fmt.Errorf("failed to listen on port %s: %w", port, err) diff --git a/examples/server/speculator/main.go b/examples/server/speculator/main.go index 3abb36b7..71bf416d 100644 --- a/examples/server/speculator/main.go +++ b/examples/server/speculator/main.go @@ -88,8 +88,11 @@ func run() error { // Register reflection service for debugging with grpcurl reflection.Register(grpcServer) - // Listen on port 8083 - port := ":8083" + // Listen on configurable port + port := os.Getenv("PORT") + if port == "" { + port = ":8083" + } listener, err := net.Listen("tcp", port) if err != nil { return fmt.Errorf("failed to listen on port %s: %w", port, err) diff --git a/extensions/storage/mysql/factory.go b/extensions/storage/mysql/factory.go index 5c50b3a9..cf8d586c 100644 --- a/extensions/storage/mysql/factory.go +++ b/extensions/storage/mysql/factory.go @@ -49,11 +49,6 @@ func NewFactory(p MySQLParameters) (storage.StoreFactory, error) { db.SetConnMaxLifetime(p.ConnMaxLifetime) } - if err := db.Ping(); err != nil { - db.Close() - return nil, fmt.Errorf("failed to ping MySQL: %w", err) - } - return &factory{ db: db, requestStore: NewRequestStore(db), diff --git a/extensions/storage/mysql/schema/BUILD.bazel b/extensions/storage/mysql/schema/BUILD.bazel new file mode 100644 index 00000000..3412d773 --- /dev/null +++ b/extensions/storage/mysql/schema/BUILD.bazel @@ -0,0 +1,5 @@ +filegroup( + name = "schema", + srcs = glob(["*.sql"]), + visibility = ["//visibility:public"], +) diff --git a/extensions/storage/mysql/schema/request.sql b/extensions/storage/mysql/schema/request.sql index a27fa155..0c596053 100644 --- a/extensions/storage/mysql/schema/request.sql +++ b/extensions/storage/mysql/schema/request.sql @@ -3,8 +3,8 @@ CREATE TABLE IF NOT EXISTS request ( seq BIGINT NOT NULL, change_source VARCHAR(255) NOT NULL, change_ids JSON NOT NULL, - land_strategy INT NOT NULL DEFAULT 0, + land_strategy INT NOT NULL, state INT NOT NULL, - version INT NOT NULL DEFAULT 1, + version INT NOT NULL, PRIMARY KEY (queue, seq) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/gateway/controller/BUILD.bazel b/gateway/controller/BUILD.bazel index c8f94c86..3b6f682e 100644 --- a/gateway/controller/BUILD.bazel +++ b/gateway/controller/BUILD.bazel @@ -9,6 +9,8 @@ go_library( importpath = "github.com/uber/submitqueue/gateway/controller", visibility = ["//visibility:public"], deps = [ + "//entities", + "//extensions/storage", "//gateway/protopb", "@com_github_uber_go_tally_v4//:tally", "@org_uber_go_zap//:zap", @@ -23,6 +25,8 @@ go_test( ], embed = [":controller"], deps = [ + "//entities", + "//extensions/storage", "//gateway/protopb", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", diff --git a/gateway/controller/land.go b/gateway/controller/land.go index 4d6f3a45..ee77d26b 100644 --- a/gateway/controller/land.go +++ b/gateway/controller/land.go @@ -6,6 +6,8 @@ import ( "time" "github.com/uber-go/tally/v4" + "github.com/uber/submitqueue/entities" + "github.com/uber/submitqueue/extensions/storage" pb "github.com/uber/submitqueue/gateway/protopb" "go.uber.org/zap" ) @@ -14,13 +16,15 @@ import ( type LandController struct { logger *zap.Logger metricsScope tally.Scope + storeFactory storage.StoreFactory } // NewLandController creates a new instance of the gateway land controller -func NewLandController(logger *zap.Logger, scope tally.Scope) *LandController { +func NewLandController(logger *zap.Logger, scope tally.Scope, storeFactory storage.StoreFactory) *LandController { return &LandController{ logger: logger, metricsScope: scope, + storeFactory: storeFactory, } } @@ -33,8 +37,18 @@ func (c *LandController) Land(ctx context.Context, req *pb.LandRequest) (*pb.Lan c.metricsScope.Counter("land_request_count").Inc(1) - // TODO: Implement proper SQID generation and send the request to the appropriate queue. So far unix time to make it sequential. - sqid := fmt.Sprintf("%d", time.Now().Unix()) + change := entities.Change{ + Source: req.Change.GetSource(), + IDs: req.Change.GetIds(), + } + strategy := entities.RequestLandStrategy(int(req.Strategy)) + + request, err := c.storeFactory.GetRequestStore().Create(ctx, req.Queue, change, strategy, entities.RequestStateNew) + if err != nil { + return nil, fmt.Errorf("LandController failed to create request for queue=%s: %w", req.Queue, err) + } + + sqid := request.GetID() c.logger.Debug("land request received", zap.String("queue", req.Queue), diff --git a/gateway/controller/land_test.go b/gateway/controller/land_test.go index 539fe138..f3b693ac 100644 --- a/gateway/controller/land_test.go +++ b/gateway/controller/land_test.go @@ -2,21 +2,70 @@ package controller import ( "context" + "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/uber-go/tally/v4" + "github.com/uber/submitqueue/entities" + "github.com/uber/submitqueue/extensions/storage" pb "github.com/uber/submitqueue/gateway/protopb" "go.uber.org/zap" ) +type mockRequestStore struct { + createFunc func(ctx context.Context, queue string, change entities.Change, strategy entities.RequestLandStrategy, state entities.RequestState) (entities.Request, error) +} + +func (m *mockRequestStore) Get(ctx context.Context, id string) (entities.Request, error) { + return entities.Request{}, nil +} + +func (m *mockRequestStore) Create(ctx context.Context, queue string, change entities.Change, strategy entities.RequestLandStrategy, state entities.RequestState) (entities.Request, error) { + return m.createFunc(ctx, queue, change, strategy, state) +} + +func (m *mockRequestStore) UpdateState(ctx context.Context, id string, version int32, newState entities.RequestState) error { + return nil +} + +type mockStoreFactory struct { + requestStore storage.RequestStore +} + +func (m *mockStoreFactory) GetRequestStore() storage.RequestStore { + return m.requestStore +} + +func (m *mockStoreFactory) Close() error { + return nil +} + func TestNewLandController(t *testing.T) { - controller := NewLandController(zap.NewNop(), tally.NoopScope) + factory := &mockStoreFactory{requestStore: &mockRequestStore{ + createFunc: func(ctx context.Context, queue string, change entities.Change, strategy entities.RequestLandStrategy, state entities.RequestState) (entities.Request, error) { + return entities.Request{}, nil + }, + }} + controller := NewLandController(zap.NewNop(), tally.NoopScope, factory) require.NotNil(t, controller) } func TestLand_ReturnsSqid(t *testing.T) { - controller := NewLandController(zap.NewNop(), tally.NoopScope) + factory := &mockStoreFactory{requestStore: &mockRequestStore{ + createFunc: func(ctx context.Context, queue string, change entities.Change, strategy entities.RequestLandStrategy, state entities.RequestState) (entities.Request, error) { + return entities.Request{ + Queue: queue, + Seq: 1, + Change: change, + LandStrategy: strategy, + State: state, + Version: 1, + }, nil + }, + }} + controller := NewLandController(zap.NewNop(), tally.NoopScope, factory) ctx := context.Background() req := &pb.LandRequest{ @@ -26,5 +75,64 @@ func TestLand_ReturnsSqid(t *testing.T) { resp, err := controller.Land(ctx, req) require.NoError(t, err) - require.NotEmpty(t, resp.Sqid) + assert.Equal(t, "test-queue/1", resp.Sqid) +} + +func TestLand_PassesCorrectParametersToStore(t *testing.T) { + var capturedQueue string + var capturedChange entities.Change + var capturedStrategy entities.RequestLandStrategy + var capturedState entities.RequestState + + factory := &mockStoreFactory{requestStore: &mockRequestStore{ + createFunc: func(ctx context.Context, queue string, change entities.Change, strategy entities.RequestLandStrategy, state entities.RequestState) (entities.Request, error) { + capturedQueue = queue + capturedChange = change + capturedStrategy = strategy + capturedState = state + return entities.Request{ + Queue: queue, + Seq: 42, + Change: change, + LandStrategy: strategy, + State: state, + Version: 1, + }, nil + }, + }} + controller := NewLandController(zap.NewNop(), tally.NoopScope, factory) + ctx := context.Background() + + req := &pb.LandRequest{ + Queue: "my-queue", + Change: &pb.Change{Source: "github", Ids: []string{"pr-1", "pr-2"}}, + Strategy: pb.Strategy_STRATEGY_REBASE, + } + resp, err := controller.Land(ctx, req) + + require.NoError(t, err) + assert.Equal(t, "my-queue", capturedQueue) + assert.Equal(t, "github", capturedChange.Source) + assert.Equal(t, []string{"pr-1", "pr-2"}, capturedChange.IDs) + assert.Equal(t, entities.RequestLandStrategyRebase, capturedStrategy) + assert.Equal(t, entities.RequestStateNew, capturedState) + assert.Equal(t, "my-queue/42", resp.Sqid) +} + +func TestLand_ReturnsErrorOnStorageFailure(t *testing.T) { + factory := &mockStoreFactory{requestStore: &mockRequestStore{ + createFunc: func(ctx context.Context, queue string, change entities.Change, strategy entities.RequestLandStrategy, state entities.RequestState) (entities.Request, error) { + return entities.Request{}, fmt.Errorf("database connection failed") + }, + }} + controller := NewLandController(zap.NewNop(), tally.NoopScope, factory) + ctx := context.Background() + + req := &pb.LandRequest{ + Queue: "test-queue", + Change: &pb.Change{Source: "github", Ids: []string{"123"}}, + } + _, err := controller.Land(ctx, req) + + require.Error(t, err) } diff --git a/go.mod b/go.mod index 53fa26c9..be9c74ac 100644 --- a/go.mod +++ b/go.mod @@ -5,48 +5,98 @@ go 1.24.5 require ( github.com/go-sql-driver/mysql v1.9.3 github.com/gogo/protobuf v1.3.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.11.1 + github.com/testcontainers/testcontainers-go v0.40.0 + github.com/testcontainers/testcontainers-go/modules/mysql v0.40.0 github.com/uber-go/tally/v4 v4.1.17 go.uber.org/fx v1.22.0 go.uber.org/yarpc v1.81.0 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.68.1 - google.golang.org/protobuf v1.36.1 + google.golang.org/protobuf v1.36.3 ) require ( + dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.5.1+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/status v1.1.0 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.11.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect + github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/twmb/murmur3 v1.1.8 // indirect github.com/uber-go/tally v3.5.8+incompatible // indirect github.com/uber/tchannel-go v1.34.4 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/net/metrics v1.4.0 // indirect go.uber.org/thriftrw v1.32.0 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools/go/expect v0.1.1-deprecated // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.4.3 // indirect diff --git a/go.sum b/go.sum index 5c70c1d7..6ad3254d 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,17 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -17,20 +25,53 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDfD5VcY4CIfh1hRXBUavxrvELjTiOE= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -63,10 +104,15 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -80,32 +126,63 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a h1:AA9vgIBDjMHPC2McaGPojgV2dcI78ZC0TLNhYCXEKH8= github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a/go.mod h1:lzZQ3Noex5pfAy7mkAeCjcBDteYU85uWWnJ/y6gKU8k= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -132,20 +209,37 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/samuel/go-thrift v0.0.0-20191111193933-5165175b40af h1:EiWVfh8mr40yFZEui2oF0d45KgH48PkB2H0Z0GANvSI= github.com/samuel/go-thrift v0.0.0-20191111193933-5165175b40af/go.mod h1:Vrkh1pnjV9Bl8c3P9zH0/D4NlOHWP5d4/hF4YTULaec= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d h1:X4+kt6zM/OVO6gbJdAfJR60MGPsqCzbtXNnjoGqdfAs= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= +github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= +github.com/testcontainers/testcontainers-go/modules/mysql v0.40.0 h1:P9Txfy5Jothx2wFdcus0QoSmX/PKSIXZxrTbZPVJswA= +github.com/testcontainers/testcontainers-go/modules/mysql v0.40.0/go.mod h1:oZPHHqJqXG7FD8OB/yWH7gLnDvZUlFHAVJNrGftL+eg= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/uber-go/mapdecode v1.0.0 h1:euUEFM9KnuCa1OBixz1xM+FIXmpixyay5DLymceOVrU= @@ -164,6 +258,26 @@ github.com/uber/tchannel-go v1.34.4/go.mod h1:ERHDsQa50nNJxV8Mm6V4nxPWyGvGxiV+T/ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -190,6 +304,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -201,8 +317,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -214,8 +330,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -225,13 +341,14 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -240,20 +357,29 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -265,14 +391,18 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def h1:0Km0hi+g2KXbXL0+riZzSCKz23f4MmwicuEb00JeonI= +google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def/go.mod h1:u2DoMSpCXjrzqLdobRccQMc9wrnMAJ1DLng0a2yqM2Q= google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def h1:4P81qv5JXI/sDNae2ClVx88cgDDA6DPilADkG9tYKz8= google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def/go.mod h1:bdAgzvd4kFrpykc5/AC2eLUiegK9T/qxZHD4hXYf/ho= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -285,14 +415,14 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -301,8 +431,11 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= diff --git a/integration_tests/BUILD.bazel b/integration_tests/BUILD.bazel index b9ddda0b..046a4b7b 100644 --- a/integration_tests/BUILD.bazel +++ b/integration_tests/BUILD.bazel @@ -3,21 +3,31 @@ load("@rules_go//go:def.bzl", "go_test") go_test( - name = "e2e_test", - srcs = ["ping_test.go"], - # End-to-end tests require all servers to be running - # They validate the entire system working together - tags = [ - "e2e", - "integration", - "manual", + name = "integration_test", + srcs = [ + "mysql.go", + "servers.go", + "suite_test.go", ], + data = [ + "//extensions/storage/mysql/schema", + "//examples/server/gateway", + "//examples/server/orchestrator", + "//examples/server/speculator", + ], + tags = ["integration"], deps = [ "//gateway/protopb", "//orchestrator/protopb", "//speculator/protopb", + "@com_github_go_sql_driver_mysql//:mysql", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", + "@com_github_stretchr_testify//suite", + "@com_github_testcontainers_testcontainers_go//:testcontainers-go", + "@com_github_testcontainers_testcontainers_go//network", + "@com_github_testcontainers_testcontainers_go//wait", + "@com_github_testcontainers_testcontainers_go_modules_mysql//:mysql", "@org_golang_google_grpc//:grpc", "@org_golang_google_grpc//credentials/insecure", ], diff --git a/integration_tests/README.md b/integration_tests/README.md index 1a35077f..d2fe8a8e 100644 --- a/integration_tests/README.md +++ b/integration_tests/README.md @@ -1,169 +1,83 @@ -# End-to-End Tests +# Integration Tests -This directory contains end-to-end (e2e) tests for the SubmitQueue system. These tests validate that all services work together correctly as a complete system. +This directory contains hermetic integration tests for the SubmitQueue system. All infrastructure (MySQL, gRPC servers) is managed automatically via [Testcontainers-Go](https://golang.testcontainers.org/) — no manual setup required. -**Note:** For testing individual services in isolation, see the integration tests in each service's directory: -- `gateway/integration_tests/` - Gateway service tests -- `orchestrator/integration_tests/` - Orchestrator service tests -- `speculator/integration_tests/` - Speculator service tests +## Architecture -## Running Tests - -### Prerequisites +Tests run as a `testify/suite` that manages the full lifecycle: -All servers must be running before executing the tests. Start them in separate terminals or in the background: +1. **Docker network** is created for inter-container communication +2. **MySQL container** starts on the network (alias `mysql`), schema is applied +3. **Server containers** (gateway, orchestrator, speculator) are built from the actual `go_binary` targets in `examples/server/` and started on the network +4. **gRPC clients** connect to the mapped host ports +5. **Tests execute** against the real server binaries +6. **Cleanup** tears down all containers and the network -```bash -# Build everything first -make build - -# Start all servers -./bin/gateway_server & -./bin/orchestrator_server & -./bin/speculator_server & -``` +All servers listen on port `8080` inside their containers. Docker maps each to a random host port, so there are no port conflicts even when tests run in parallel. The fixed internal port also simplifies inter-service communication on the Docker network — services reach each other at `:8080` (e.g., `orchestrator:8080`). -### Using Make (Recommended) +## Files -```bash -# Run end-to-end tests (all servers must be running) -make e2e-test - -# Run integration tests for individual services -make integration-test-gateway # Just Gateway -make integration-test-orchestrator # Just Orchestrator -make integration-test-speculator # Just Speculator -make integration-test # All services - -``` +| File | Purpose | +|------|---------| +| `suite_test.go` | Test suite with `SetupSuite`/`TearDownSuite` and all test methods | +| `servers.go` | Helpers to build Docker images from server binaries and start containers | +| `mysql.go` | MySQL container setup, schema application, and test logger | +| `BUILD.bazel` | Bazel test target with binary and schema data dependencies | -### Using Bazel directly +## Running Tests ```bash -# Run end-to-end tests -./tools/bazel test //integration_tests:e2e_test --test_output=all - -# Run individual service integration tests -./tools/bazel test //gateway/integration_tests:integration_tests_test --test_output=all -./tools/bazel test //orchestrator/integration_tests:integration_tests_test --test_output=all -./tools/bazel test //speculator/integration_tests:integration_tests_test --test_output=all +# Run with Bazel +bazel test //integration_tests:integration_test --test_output=all -# Run all service integration tests -./tools/bazel test //gateway/integration_tests:integration_tests_test //orchestrator/integration_tests:integration_tests_test //speculator/integration_tests:integration_tests_test --test_output=all +# Run with verbose output +bazel test //integration_tests:integration_test --test_output=all --test_arg=-test.v -# The tests are tagged as 'manual' so they won't run with 'bazel test //...' -# This is intentional since they require servers to be running +# Run with Go (from repo root) +go test ./integration_tests -v ``` -## Test Structure - -### End-to-End Tests (this directory) - -- `TestEndToEndAllServices` - Validates all services are running and responding correctly +The test target is tagged `integration` (not `manual`), so it is discovered by `bazel test //integration_tests/...`. -### Service Integration Tests (in each service directory) +## Test Cases -- `gateway/integration_tests/` - Tests Gateway service in isolation -- `orchestrator/integration_tests/` - Tests Orchestrator service in isolation -- `speculator/integration_tests/` - Tests Speculator service in isolation +- `TestPingGateway` — Ping gateway, assert `service_name="gateway"` +- `TestPingOrchestrator` — Ping orchestrator, assert `service_name="orchestrator"` +- `TestPingSpeculator` — Ping speculator, assert `service_name="speculator"` +- `TestLandRequest` — Send `LandRequest` through gateway gRPC, assert `sqid` is returned ## Adding New Tests -### 1. Add a test for a new API endpoint in an existing service - -Add the test to the service's `integration_tests/` folder (e.g., create `gateway/integration_tests/submit_test.go`): +Add a method to `IntegrationSuite` in `suite_test.go`: ```go -func TestNewMethod(t *testing.T) { - addr := getEnvOrDefault("GATEWAY_ADDR", "localhost:8081") - - conn, err := waitForServer(t, addr, serverReadyTimeout) - if err != nil { - t.Fatalf("Gateway server not ready: %v", err) - } - defer conn.Close() - - client := pb.NewSubmitQueueGatewayClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - - req := &pb.NewMethodRequest{ - Field: "value", - } - - resp, err := client.NewMethod(ctx, req) - if err != nil { - t.Fatalf("NewMethod failed: %v", err) - } - - // Add your assertions here +func (s *IntegrationSuite) TestNewEndpoint() { + ctx := context.Background() + resp, err := s.gatewayClient.NewMethod(ctx, &gatewaypb.NewRequest{...}) + require.NoError(s.T(), err) + assert.Equal(s.T(), "expected", resp.Field) } ``` -### 2. Add integration tests for a new service - -1. Create `newservice/integration_tests/` folder -2. Add test files like `newservice/integration_tests/ping_test.go` -3. Add `go_test` rule to `newservice/integration_tests/BUILD.bazel`: -```python -go_test( - name = "integration_tests_test", - srcs = ["ping_test.go"], - deps = [ - "//newservice/protopb", - "@org_golang_google_grpc//:grpc", - "@org_golang_google_grpc//credentials/insecure", - ], - tags = ["manual", "integration"], -) -``` -4. Update Makefile to add `integration-test-newservice` target -5. Update CI workflow to start the new service and run its tests - -### 3. Add end-to-end tests - -For system-wide tests that involve multiple services, add them to `integration_tests/api_test.go`. - -## Environment Variables - -Tests support the following environment variables to customize server addresses: - -- `GATEWAY_ADDR` - Gateway server address (default: `localhost:8081`) -- `ORCHESTRATOR_ADDR` - Orchestrator server address (default: `localhost:8082`) -- `SPECULATOR_ADDR` - Speculator server address (default: `localhost:8083`) +If the servers need to communicate with each other, pass addresses via environment variables in `servers.go`: -Example with Bazel: -```bash -GATEWAY_ADDR=10.0.0.1:8081 ./tools/bazel test //integration_tests:integration_tests --test_output=all -``` - -Example with Go: -```bash -GATEWAY_ADDR=10.0.0.1:8081 go test ./integration_tests -v +```go +_, addr := startServerContainer(ctx, t, log, "gateway", map[string]string{ + "MYSQL_DSN": "root:root@tcp(mysql:3306)/submitqueue?parseTime=true", + "ORCHESTRATOR_ADDR": "orchestrator:8080", + "SPECULATOR_ADDR": "speculator:8080", +}, nw) ``` -## CI Usage - -In GitHub Actions CI, the workflow: -1. Builds all services -2. Starts all servers in the background -3. Runs integration tests -4. Stops servers +## Troubleshooting -See `.github/workflows/build_and_test.yml` for the full workflow. +**`$HOME is not defined`** — The Bazel sandbox doesn't set `HOME`. This is handled in `SetupSuite` by setting it to a temp directory. -## Troubleshooting +**Ryuk reaper failure** — The Testcontainers reaper container may fail in Docker-in-Docker environments. This is handled by setting `TESTCONTAINERS_RYUK_DISABLED=true` in `SetupSuite`. -**Tests fail with "connection refused":** -- Ensure all servers are running -- Check that servers are listening on the expected ports: `lsof -i :8081` -- Verify servers are healthy using the clients: `./bin/gateway_client -message test` +**Binary not found** — Ensure the `data` attribute in `BUILD.bazel` includes the server binary targets. Bazel places them in runfiles at `examples/server//_/`. -**Tests timeout:** -- Servers may be slow to start -- Increase `serverReadyTimeout` in the test code -- Check server logs for startup errors +## TODO -**Import errors:** -- Run `go mod tidy` to ensure all dependencies are downloaded -- Regenerate proto files if needed: `make proto` +- [ ] Speed up container setup (pre-built images, parallel container starts, image caching) +- [ ] Support Tracetest/Jaeger for trace-based assertions diff --git a/integration_tests/mysql.go b/integration_tests/mysql.go new file mode 100644 index 00000000..f64677f0 --- /dev/null +++ b/integration_tests/mysql.go @@ -0,0 +1,113 @@ +package integration_tests + +import ( + "context" + "database/sql" + "os" + "path/filepath" + "sort" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/mysql" + "github.com/testcontainers/testcontainers-go/network" +) + +// testLogger is a simple test-aware logger that records elapsed time between logs. +type testLogger struct { + t *testing.T // The testing object to report logs to. + last time.Time // Timestamp of the last log, for elapsed calculation. +} + +// newTestLogger creates a testLogger for the current test. +func newTestLogger(t *testing.T) *testLogger { + t.Helper() + return &testLogger{t: t} +} + +// logf prints a formatted log message with timestamp and elapsed time since last log. +func (l *testLogger) logf(format string, args ...any) { + l.t.Helper() + now := time.Now() + delta := "" + if !l.last.IsZero() { + delta = " +" + now.Sub(l.last).Truncate(time.Millisecond).String() + } + l.last = now + l.t.Logf("[%s%s] "+format, append([]any{now.Format(time.RFC3339Nano), delta}, args...)...) +} + +// schemaDir returns the path to the MySQL schema directory. +// It checks for both Bazel runfiles and direct go test paths. +func schemaDir() string { + // Bazel runfiles path + if dir := os.Getenv("TEST_SRCDIR"); dir != "" { + return filepath.Join(dir, os.Getenv("TEST_WORKSPACE"), "extensions/storage/mysql/schema") + } + // Direct go test path (run from repo root) + return "extensions/storage/mysql/schema" +} + +// applySchema reads all .sql files from the schema directory and executes them on the database. +func applySchema(t *testing.T, log *testLogger, db *sql.DB) { + t.Helper() + + dir := schemaDir() + files, err := filepath.Glob(filepath.Join(dir, "*.sql")) + require.NoError(t, err, "failed to glob schema files") + require.NotEmpty(t, files, "no .sql schema files found in %s", dir) + + // Sort files to ensure deterministic schema application order. + sort.Strings(files) + + for _, f := range files { + name := filepath.Base(f) + log.logf("Applying schema: %s", name) + + content, err := os.ReadFile(f) + require.NoError(t, err, "failed to read schema file %s", name) + + _, err = db.ExecContext(context.Background(), string(content)) + require.NoError(t, err, "failed to execute schema file %s", name) + + log.logf("Schema applied: %s", name) + } +} + +// setupMySQL starts a MySQL container on the given Docker network, applies the schema, +// and registers cleanup. The container is reachable by other containers on the network at "mysql:3306". +func setupMySQL(t *testing.T, log *testLogger, nw *testcontainers.DockerNetwork) { + t.Helper() + + ctx := context.Background() + + log.logf("Starting MySQL container") + mysqlContainer, err := mysql.Run(ctx, "mysql:8.0", + mysql.WithDatabase("submitqueue"), + mysql.WithUsername("root"), + mysql.WithPassword("root"), + network.WithNetwork([]string{"mysql"}, nw), + ) + require.NoError(t, err, "failed to start MySQL container") + log.logf("MySQL container started") + t.Cleanup(func() { + log.logf("Terminating MySQL container") + require.NoError(t, mysqlContainer.Terminate(ctx), "failed to terminate MySQL container") + log.logf("MySQL container terminated") + }) + + dsn, err := mysqlContainer.ConnectionString(ctx, "parseTime=true") + require.NoError(t, err, "failed to get MySQL connection string") + log.logf("MySQL DSN obtained: %s", dsn) + + log.logf("Opening MySQL connection") + db, err := sql.Open("mysql", dsn) + require.NoError(t, err, "failed to open MySQL connection") + log.logf("MySQL connection opened") + defer db.Close() + + applySchema(t, log, db) +} diff --git a/integration_tests/ping_test.go b/integration_tests/ping_test.go deleted file mode 100644 index c346dfc0..00000000 --- a/integration_tests/ping_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package integration_tests - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - gatewaypb "github.com/uber/submitqueue/gateway/protopb" - orchestratorpb "github.com/uber/submitqueue/orchestrator/protopb" - speculatorpb "github.com/uber/submitqueue/speculator/protopb" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -const ( - defaultTimeout = 5 * time.Second - serverReadyTimeout = 30 * time.Second - retryInterval = 500 * time.Millisecond -) - -// TestPingForAllServices tests that all services are running and responding -// This is an end-to-end test that validates the entire system is running -func TestPingForAllServices(t *testing.T) { - // Test Gateway - t.Run("Gateway", func(t *testing.T) { - addr := getEnvOrDefault("GATEWAY_ADDR", "localhost:8081") - conn, err := waitForServer(t, addr, serverReadyTimeout) - require.NoError(t, err, "Gateway server not ready") - defer conn.Close() - - client := gatewaypb.NewSubmitQueueGatewayClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - - resp, err := client.Ping(ctx, &gatewaypb.PingRequest{Message: "e2e test"}) - require.NoError(t, err, "Gateway Ping failed") - assert.Equal(t, "gateway", resp.ServiceName) - t.Logf("Gateway is healthy: %s", resp.Message) - }) - - // Test Orchestrator - t.Run("Orchestrator", func(t *testing.T) { - addr := getEnvOrDefault("ORCHESTRATOR_ADDR", "localhost:8082") - conn, err := waitForServer(t, addr, serverReadyTimeout) - require.NoError(t, err, "Orchestrator server not ready") - defer conn.Close() - - client := orchestratorpb.NewSubmitQueueOrchestratorClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - - resp, err := client.Ping(ctx, &orchestratorpb.PingRequest{Message: "e2e test"}) - require.NoError(t, err, "Orchestrator Ping failed") - assert.Equal(t, "orchestrator", resp.ServiceName) - t.Logf("Orchestrator is healthy: %s", resp.Message) - }) - - // Test Speculator - t.Run("Speculator", func(t *testing.T) { - addr := getEnvOrDefault("SPECULATOR_ADDR", "localhost:8083") - conn, err := waitForServer(t, addr, serverReadyTimeout) - require.NoError(t, err, "Speculator server not ready") - defer conn.Close() - - client := speculatorpb.NewSubmitQueueSpeculatorClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - - resp, err := client.Ping(ctx, &speculatorpb.PingRequest{Message: "e2e test"}) - require.NoError(t, err, "Speculator Ping failed") - assert.Equal(t, "speculator", resp.ServiceName) - t.Logf("Speculator is healthy: %s", resp.Message) - }) - - t.Log("All services are healthy and responding correctly") -} - -// waitForServer waits for a gRPC server to become ready -func waitForServer(t *testing.T, addr string, timeout time.Duration) (*grpc.ClientConn, error) { - t.Helper() - - deadline := time.Now().Add(timeout) - var lastErr error - - for time.Now().Before(deadline) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - conn, err := grpc.DialContext( - ctx, - addr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock(), - ) - cancel() - - if err == nil { - t.Logf("Server at %s is ready", addr) - return conn, nil - } - - lastErr = err - time.Sleep(retryInterval) - } - - return nil, fmt.Errorf("server at %s not ready after %v: %w", addr, timeout, lastErr) -} - -// getEnvOrDefault returns the value of an environment variable or a default value -func getEnvOrDefault(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value - } - return defaultValue -} diff --git a/integration_tests/servers.go b/integration_tests/servers.go new file mode 100644 index 00000000..b10a5be1 --- /dev/null +++ b/integration_tests/servers.go @@ -0,0 +1,115 @@ +package integration_tests + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" +) + +const serverPort = "8080" + +// serverBinaryPath returns the path to a Bazel-built server binary. +func serverBinaryPath(name string) string { + if dir := os.Getenv("TEST_SRCDIR"); dir != "" { + workspace := os.Getenv("TEST_WORKSPACE") + return filepath.Join(dir, workspace, "examples/server", name, name+"_", name) + } + return filepath.Join("examples/server", name, name) +} + +// startServerContainer builds a Docker image from the server binary and starts it. +func startServerContainer( + ctx context.Context, + t *testing.T, + log *testLogger, + name string, + env map[string]string, + nw *testcontainers.DockerNetwork, +) (testcontainers.Container, string) { + t.Helper() + + binaryPath := serverBinaryPath(name) + log.logf("Resolved %s binary: %s", name, binaryPath) + + // Create temp build context with binary and Dockerfile. + tmpDir := t.TempDir() + copyBinary(t, binaryPath, filepath.Join(tmpDir, "server")) + + dockerfile := "FROM debian:bookworm-slim\nCOPY server /usr/local/bin/server\nCMD [\"/usr/local/bin/server\"]\n" + os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o644) + + env["PORT"] = ":" + serverPort + + log.logf("Starting %s container", name) + ctr, err := testcontainers.Run(ctx, "", + testcontainers.WithDockerfile(testcontainers.FromDockerfile{ + Context: tmpDir, + Dockerfile: "Dockerfile", + }), + testcontainers.WithExposedPorts(serverPort+"/tcp"), + testcontainers.WithEnv(env), + testcontainers.WithWaitStrategy(wait.ForLog("server is running")), + network.WithNetwork([]string{name}, nw), + ) + require.NoError(t, err, "failed to start %s container", name) + t.Cleanup(func() { + log.logf("Terminating %s container", name) + if err := ctr.Terminate(ctx); err != nil { + t.Logf("failed to terminate %s container: %v", name, err) + } + log.logf("%s container terminated", name) + }) + + mappedPort, err := ctr.MappedPort(ctx, serverPort+"/tcp") + require.NoError(t, err, "failed to get mapped port for %s", name) + host, err := ctr.Host(ctx) + require.NoError(t, err, "failed to get host for %s", name) + addr := fmt.Sprintf("%s:%s", host, mappedPort.Port()) + log.logf("%s container started on %s", name, addr) + return ctr, addr +} + + +func startGatewayContainer(ctx context.Context, t *testing.T, log *testLogger, nw *testcontainers.DockerNetwork) string { + t.Helper() + _, addr := startServerContainer(ctx, t, log, "gateway", map[string]string{ + "MYSQL_DSN": "root:root@tcp(mysql:3306)/submitqueue?parseTime=true", + }, nw) + return addr +} + +func startOrchestratorContainer(ctx context.Context, t *testing.T, log *testLogger, nw *testcontainers.DockerNetwork) string { + t.Helper() + _, addr := startServerContainer(ctx, t, log, "orchestrator", map[string]string{}, nw) + return addr +} + +func startSpeculatorContainer(ctx context.Context, t *testing.T, log *testLogger, nw *testcontainers.DockerNetwork) string { + t.Helper() + _, addr := startServerContainer(ctx, t, log, "speculator", map[string]string{}, nw) + return addr +} + +// copyBinary copies a file from src to dst preserving executable permissions. +func copyBinary(t *testing.T, src, dst string) { + t.Helper() + + in, err := os.Open(src) + require.NoError(t, err, "failed to open binary %s", src) + defer in.Close() + + out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o755) + require.NoError(t, err, "failed to create binary copy %s", dst) + defer out.Close() + + _, err = io.Copy(out, in) + require.NoError(t, err, "failed to copy binary from %s to %s", src, dst) +} diff --git a/integration_tests/suite_test.go b/integration_tests/suite_test.go new file mode 100644 index 00000000..6dc45a92 --- /dev/null +++ b/integration_tests/suite_test.go @@ -0,0 +1,131 @@ +package integration_tests + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" + gatewaypb "github.com/uber/submitqueue/gateway/protopb" + orchestratorpb "github.com/uber/submitqueue/orchestrator/protopb" + speculatorpb "github.com/uber/submitqueue/speculator/protopb" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type IntegrationSuite struct { + suite.Suite + log *testLogger + + nw *testcontainers.DockerNetwork + + gatewayClient gatewaypb.SubmitQueueGatewayClient + orchestratorClient orchestratorpb.SubmitQueueOrchestratorClient + speculatorClient speculatorpb.SubmitQueueSpeculatorClient + + cleanups []func() +} + +func TestIntegration(t *testing.T) { + suite.Run(t, new(IntegrationSuite)) +} + +func (s *IntegrationSuite) SetupSuite() { + t := s.T() + ctx := context.Background() + s.log = newTestLogger(t) + + // Disable Ryuk reaper container which may not work in Docker-in-Docker environments. + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + + // Ensure HOME is set for Docker config resolution in Bazel sandbox. + if os.Getenv("HOME") == "" { + t.Setenv("HOME", t.TempDir()) + } + + // Create Docker network for inter-container communication. + nw, err := network.New(ctx) + require.NoError(t, err, "failed to create Docker network") + s.nw = nw + t.Cleanup(func() { + s.log.logf("Removing Docker network") + require.NoError(t, nw.Remove(ctx), "failed to remove Docker network") + }) + s.log.logf("Docker network created: %s", nw.Name) + + // Start MySQL container on the network and apply schema. + setupMySQL(t, s.log, s.nw) + + // Start all server containers. + gatewayAddr := startGatewayContainer(ctx, t, s.log, s.nw) + orchestratorAddr := startOrchestratorContainer(ctx, t, s.log, s.nw) + speculatorAddr := startSpeculatorContainer(ctx, t, s.log, s.nw) + + // Create gRPC client connections. + opts := grpc.WithTransportCredentials(insecure.NewCredentials()) + s.gatewayClient = gatewaypb.NewSubmitQueueGatewayClient(s.dial(gatewayAddr, opts)) + s.orchestratorClient = orchestratorpb.NewSubmitQueueOrchestratorClient(s.dial(orchestratorAddr, opts)) + s.speculatorClient = speculatorpb.NewSubmitQueueSpeculatorClient(s.dial(speculatorAddr, opts)) + + s.log.logf("All containers started and clients connected") +} + +func (s *IntegrationSuite) TearDownSuite() { + for i := len(s.cleanups) - 1; i >= 0; i-- { + s.cleanups[i]() + } +} + +func (s *IntegrationSuite) addCleanup(fn func()) { + s.cleanups = append(s.cleanups, fn) +} + +func (s *IntegrationSuite) dial(addr string, opts ...grpc.DialOption) *grpc.ClientConn { + conn, err := grpc.NewClient(addr, opts...) + require.NoError(s.T(), err, "failed to connect to %s", addr) + s.addCleanup(func() { conn.Close() }) + return conn +} + +func (s *IntegrationSuite) TestPingGateway() { + ctx := context.Background() + resp, err := s.gatewayClient.Ping(ctx, &gatewaypb.PingRequest{Message: "integration test"}) + require.NoError(s.T(), err, "Gateway Ping failed") + assert.Equal(s.T(), "gateway", resp.ServiceName) + s.log.logf("Gateway ping: %s", resp.Message) +} + +func (s *IntegrationSuite) TestPingOrchestrator() { + ctx := context.Background() + resp, err := s.orchestratorClient.Ping(ctx, &orchestratorpb.PingRequest{Message: "integration test"}) + require.NoError(s.T(), err, "Orchestrator Ping failed") + assert.Equal(s.T(), "orchestrator", resp.ServiceName) + s.log.logf("Orchestrator ping: %s", resp.Message) +} + +func (s *IntegrationSuite) TestPingSpeculator() { + ctx := context.Background() + resp, err := s.speculatorClient.Ping(ctx, &speculatorpb.PingRequest{Message: "integration test"}) + require.NoError(s.T(), err, "Speculator Ping failed") + assert.Equal(s.T(), "speculator", resp.ServiceName) + s.log.logf("Speculator ping: %s", resp.Message) +} + +func (s *IntegrationSuite) TestLandRequest() { + ctx := context.Background() + req := &gatewaypb.LandRequest{ + Queue: "integration-test-queue", + Change: &gatewaypb.Change{Source: "github", Ids: []string{"pr-100", "pr-101"}}, + Strategy: gatewaypb.Strategy_STRATEGY_REBASE, + } + + s.log.logf("Sending Land request for queue=%s", req.Queue) + resp, err := s.gatewayClient.Land(ctx, req) + require.NoError(s.T(), err, "Land request failed") + require.NotEmpty(s.T(), resp.Sqid, "SQID should not be empty") + s.log.logf("Land request succeeded: sqid=%s", resp.Sqid) +}