Skip to content

Commit

Permalink
feat: rewrite python calculator service in golang (#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ubisoft-potato authored Jul 11, 2023
1 parent 66b840d commit bc0f652
Show file tree
Hide file tree
Showing 32 changed files with 3,165 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .github/workflows/pr-go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ env:
jobs:
unit-test:
runs-on: ubuntu-latest
services:
# setup httpstan container used to run unit test in experiment service
httpstan:
image: ghcr.io/bucketeer-io/bucketeer-httpstan:0.0.1
ports:
- 8080:8080
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
Expand Down
42 changes: 42 additions & 0 deletions cmd/experimentcalculator/experimentcalculator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2022 The Bucketeer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package main

import (
"log"

"github.com/bucketeer-io/bucketeer/pkg/cli"
"github.com/bucketeer-io/bucketeer/pkg/experimentcalculator/cmd/server"
)

var (
name = "bucketeer-experiment-calculator"
version = ""
build = ""
)

func main() {
app := cli.NewApp(name, "A/B Testing Microservice", version, build)
registerCommands(app)
err := app.Run()
if err != nil {
log.Fatal(err)
}
}

func registerCommands(app *cli.App) {
server.RegisterCommand(app, app)
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ require (
github.com/aws/aws-sdk-go-v2/service/kms v1.21.1
github.com/blang/semver v3.5.1+incompatible
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/go-gota/gota v0.12.0
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-resty/resty/v2 v2.7.0
github.com/go-sql-driver/mysql v1.7.1
github.com/golang-migrate/migrate/v4 v4.11.0
github.com/golang/protobuf v1.5.3
Expand All @@ -36,6 +38,7 @@ require (
golang.org/x/sync v0.2.0
golang.org/x/text v0.9.0
golang.org/x/time v0.3.0
gonum.org/v1/gonum v0.11.0
google.golang.org/api v0.126.0
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc
Expand Down Expand Up @@ -107,6 +110,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
Expand Down
43 changes: 43 additions & 0 deletions go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions manifests/bucketeer/charts/web-gateway/values.yaml

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions pkg/experimentcalculator/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2022 The Bucketeer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package api

import (
"time"

"go.uber.org/zap"
"google.golang.org/grpc"

envclient "github.com/bucketeer-io/bucketeer/pkg/environment/client"
ecclient "github.com/bucketeer-io/bucketeer/pkg/eventcounter/client"
experimentclient "github.com/bucketeer-io/bucketeer/pkg/experiment/client"
"github.com/bucketeer-io/bucketeer/pkg/experimentcalculator/experimentcalc"
"github.com/bucketeer-io/bucketeer/pkg/experimentcalculator/stan"
"github.com/bucketeer-io/bucketeer/pkg/metrics"
"github.com/bucketeer-io/bucketeer/pkg/rpc"
"github.com/bucketeer-io/bucketeer/pkg/storage/v2/mysql"
calculator "github.com/bucketeer-io/bucketeer/proto/experimentcalculator"
)

type calculatorService struct {
calculator *experimentcalc.ExperimentCalculator
logger *zap.Logger
}

func NewCalculatorService(
stan *stan.Stan,
environmentClient envclient.Client,
eventCounterClient ecclient.Client,
experimentClient experimentclient.Client,
mysqlClient mysql.Client,
metrics metrics.Registerer,
loc *time.Location,
logger *zap.Logger,
) rpc.Service {
return &calculatorService{
calculator: experimentcalc.NewExperimentCalculator(
stan,
environmentClient,
eventCounterClient,
experimentClient,
mysqlClient,
metrics,
loc,
logger,
),
logger: logger,
}
}

func (c calculatorService) Register(server *grpc.Server) {
calculator.RegisterExperimentCalculatorServiceServer(server, c)
}
31 changes: 31 additions & 0 deletions pkg/experimentcalculator/api/calculator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2022 The Bucketeer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package api

import (
"context"

calculator "github.com/bucketeer-io/bucketeer/proto/experimentcalculator"
)

func (c calculatorService) CalcExperiment(
ctx context.Context,
request *calculator.BatchCalcRequest,
) (*calculator.BatchCalcResponse, error) {
resp := &calculator.BatchCalcResponse{}
c.calculator.Run(ctx, request)
return resp, nil
}
196 changes: 196 additions & 0 deletions pkg/experimentcalculator/cmd/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// Copyright 2022 The Bucketeer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package server

import (
"context"
"time"

"go.uber.org/zap"
"gopkg.in/alecthomas/kingpin.v2"

"github.com/bucketeer-io/bucketeer/pkg/cli"
envclient "github.com/bucketeer-io/bucketeer/pkg/environment/client"
ecclient "github.com/bucketeer-io/bucketeer/pkg/eventcounter/client"
experimentclient "github.com/bucketeer-io/bucketeer/pkg/experiment/client"
"github.com/bucketeer-io/bucketeer/pkg/experimentcalculator/api"
"github.com/bucketeer-io/bucketeer/pkg/experimentcalculator/stan"
"github.com/bucketeer-io/bucketeer/pkg/health"
"github.com/bucketeer-io/bucketeer/pkg/locale"
"github.com/bucketeer-io/bucketeer/pkg/metrics"
"github.com/bucketeer-io/bucketeer/pkg/rpc"
"github.com/bucketeer-io/bucketeer/pkg/rpc/client"
"github.com/bucketeer-io/bucketeer/pkg/storage/v2/mysql"
)

const command = "server"

type server struct {
*kingpin.CmdClause

port *int
stanHost *string
stanPort *string
timezone *string

mysqlUser *string
mysqlPass *string
mysqlHost *string
mysqlPort *int
mysqlDBName *string

environmentService *string
experimentService *string
eventCounterService *string

certPath *string
keyPath *string
serviceTokenPath *string
}

func RegisterCommand(r cli.CommandRegistry, p cli.ParentCommand) cli.Command {
cmd := p.Command(command, "Start calculator server")
server := &server{
CmdClause: cmd,
port: cmd.Flag("port", "Port to bind to.").Default("9090").Int(),
stanHost: cmd.Flag("stan-host", "httpstan host.").Default("localhost").String(),
stanPort: cmd.Flag("stan-port", "httpstan port.").Default("8080").String(),
timezone: cmd.Flag("timezone", "Time zone").Required().String(),
mysqlUser: cmd.Flag("mysql-user", "MySQL user.").Required().String(),
mysqlPass: cmd.Flag("mysql-pass", "MySQL password.").Required().String(),
mysqlHost: cmd.Flag("mysql-host", "MySQL host.").Required().String(),
mysqlPort: cmd.Flag("mysql-port", "MySQL port.").Required().Int(),
mysqlDBName: cmd.Flag("mysql-db-name", "MySQL database name.").Required().String(),
environmentService: cmd.Flag(
"environment-service",
"bucketeer-environment-service address.",
).Default("environment:9090").String(),
experimentService: cmd.Flag(
"experiment-service",
"bucketeer-experiment-service address.",
).Default("experiment:9090").String(),
eventCounterService: cmd.Flag(
"event-counter-service",
"bucketeer-event-counter-service address.",
).Default("event-counter-server:9090").String(),
certPath: cmd.Flag("cert", "Path to TLS certificate.").Required().String(),
keyPath: cmd.Flag("key", "Path to TLS key.").Required().String(),
serviceTokenPath: cmd.Flag("service-token", "Path to service token.").Required().String(),
}
r.RegisterCommand(server)
return server
}

func (s server) Run(ctx context.Context, metrics metrics.Metrics, logger *zap.Logger) error {
registerer := metrics.DefaultRegisterer()

mysqlClient, err := s.createMySQLClient(ctx, registerer, logger)
if err != nil {
return err
}
defer mysqlClient.Close()

creds, err := client.NewPerRPCCredentials(*s.serviceTokenPath)
if err != nil {
return err
}

experimentClient, err := experimentclient.NewClient(*s.experimentService, *s.certPath,
client.WithPerRPCCredentials(creds),
client.WithDialTimeout(30*time.Second),
client.WithBlock(),
client.WithMetrics(registerer),
client.WithLogger(logger),
)
if err != nil {
return err
}

ecClient, err := ecclient.NewClient(*s.eventCounterService, *s.certPath,
client.WithPerRPCCredentials(creds),
client.WithDialTimeout(30*time.Second),
client.WithBlock(),
client.WithMetrics(registerer),
client.WithLogger(logger),
)
if err != nil {
return err
}

envClient, err := envclient.NewClient(*s.environmentService, *s.certPath,
client.WithPerRPCCredentials(creds),
client.WithDialTimeout(30*time.Second),
client.WithBlock(),
client.WithMetrics(registerer),
client.WithLogger(logger),
)
if err != nil {
return err
}

location, err := locale.GetLocation(*s.timezone)
if err != nil {
return err
}

service := api.NewCalculatorService(
stan.NewStan(*s.stanHost, *s.stanPort),
envClient,
ecClient,
experimentClient,
mysqlClient,
registerer,
location,
logger,
)

healthChecker := health.NewGrpcChecker(
health.WithTimeout(time.Second),
health.WithCheck("metrics", metrics.Check),
)
go healthChecker.Run(ctx)

server := rpc.NewServer(service, *s.certPath, *s.keyPath,
"experiment-calculator-server",
rpc.WithPort(*s.port),
rpc.WithMetrics(registerer),
rpc.WithLogger(logger),
rpc.WithService(healthChecker),
rpc.WithHandler("/health", healthChecker),
)
defer server.Stop(10 * time.Second)
go server.Run()

<-ctx.Done()
return nil
}

func (s server) createMySQLClient(
ctx context.Context,
registerer metrics.Registerer,
logger *zap.Logger,
) (mysql.Client, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
return mysql.NewClient(
ctx,
*s.mysqlUser, *s.mysqlPass, *s.mysqlHost,
*s.mysqlPort,
*s.mysqlDBName,
mysql.WithLogger(logger),
mysql.WithMetrics(registerer),
)
}
Loading

0 comments on commit bc0f652

Please sign in to comment.