Skip to content

Instantly share code, notes, and snippets.

@a-hilaly
Created October 15, 2019 09:08
Show Gist options
  • Save a-hilaly/834e71146144a2f8b580e84dcf6e82b5 to your computer and use it in GitHub Desktop.
Save a-hilaly/834e71146144a2f8b580e84dcf6e82b5 to your computer and use it in GitHub Desktop.
grpc + json proxy
package start
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"time"
"github.com/go-xorm/core"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gitlab.com/supbank/api/pkg/auth"
"gitlab.com/supbank/api/pkg/bc"
"gitlab.com/supbank/api/pkg/cmd"
configpb "gitlab.com/supbank/api/pkg/config"
"gitlab.com/supbank/api/pkg/drivers/postgres"
"gitlab.com/supbank/api/pkg/drivers/redis"
"gitlab.com/supbank/api/pkg/grpc"
grpcproxy "gitlab.com/supbank/api/pkg/grpc-proxy"
"gitlab.com/supbank/api/pkg/hc"
"gitlab.com/supbank/api/pkg/log"
"gitlab.com/supbank/api/pkg/store"
authv1 "gitlab.com/supbank/api/pkg/svc/v1/auth"
bcv1 "gitlab.com/supbank/api/pkg/svc/v1/bc"
hcv1 "gitlab.com/supbank/api/pkg/svc/v1/hc"
metav1 "gitlab.com/supbank/api/pkg/svc/v1/meta"
userv1 "gitlab.com/supbank/api/pkg/svc/v1/user"
walletv1 "gitlab.com/supbank/api/pkg/svc/v1/wallet"
"gitlab.com/supbank/api/pkg/valid"
)
var (
config = configpb.NewEmpty()
)
func init() {
// startup options
Cmd.Flags().Int64VarP(&config.Options.GrpcPort, "grpc-port", "g", 11251, "grpc server port")
Cmd.Flags().Int64Var(&config.Options.GrpcProxyPort, "grpc-proxy-port", 11253, "grpx proxy server port")
Cmd.Flags().BoolVar(&config.Options.GrpcProxyOn, "grpc-proxy", true, "run grpc json proxy")
Cmd.Flags().BoolVar(&config.Options.SwaggerUI, "swagger-ui", true, "run swagger ui with grpc proxy")
// wait postgres
Cmd.Flags().BoolVar(&config.Options.WaitPostgres, "wait-postgres", true, "wait for postgres database before startup")
Cmd.Flags().Int64Var(&config.Options.WaitPostgresDelay, "wait-postgres-delay", 1000, "wait database delay (ms)")
Cmd.Flags().Int64Var(&config.Options.WaitPostgresMaxAttempts, "wait-postgres-max-attempts", 60, "wait database max attempt")
// wait redis
Cmd.Flags().BoolVar(&config.Options.WaitRedis, "wait-redis", true, "wait for redis before startup")
Cmd.Flags().Int64Var(&config.Options.WaitRedisDelay, "wait-redis-delay", 1000, "wait redis delay (ms)")
Cmd.Flags().Int64Var(&config.Options.WaitRedisMaxAttempts, "wait-redis-max-attempts", 60, "wait redis max attempt")
// database utility
Cmd.Flags().BoolVarP(&config.Options.CreateTables, "database-create-tables", "d", true, "create database tables")
Cmd.Flags().BoolVar(&config.Options.CreateTokens, "auth-create-tokens", true, "create database tables")
Cmd.Flags().BoolVar(&config.Options.PopulateDb, "database-prepend", true, "populate database with data")
Cmd.Flags().BoolVar(&config.Options.Production, "production", false, "production logging")
Cmd.Flags().StringVar(&config.Options.PrependDataPath, "data-prepend-file", "/etc/api/data.yaml", "prepend data from file")
// logging and runtime options
Cmd.Flags().Int64VarP(&config.Options.LoggingLevel, "logging", "v", 6, "logging level")
Cmd.Flags().Int64Var(&config.Options.CpuCount, "cpu-count", 0, "cpu usage count")
Cmd.Flags().Int64Var(&config.Options.ShutdownDelay, "shutdown-delay", 10000, "shutdown delay (ms)")
// postgres config
Cmd.Flags().StringVarP(&config.Postgres.Name, "postgres-database", "p", "supbank", "database name")
Cmd.Flags().StringVar(&config.Postgres.Hostname, "postgres-host", "postgres", "postgres hostname")
Cmd.Flags().StringVar(&config.Postgres.Protocol, "postgres-protocol", "tcp", "postgres protocol")
Cmd.Flags().StringVar(&config.Postgres.User, "postgres-user", "supbank", "postgres user")
Cmd.Flags().StringVar(&config.Postgres.Password, "postgres-password", "supbank", "postgres password")
Cmd.Flags().Int64Var(&config.Postgres.Port, "postgres-port", 5432, "postgres port")
Cmd.Flags().Int64Var(&config.Postgres.MaxOpenConnections, "postgres-max-open-connections", 10, "max open connections")
Cmd.Flags().Int64Var(&config.Postgres.MaxIdleConnections, "postgres-max-idle-connections", 10, "max idle connections")
Cmd.Flags().Int64Var(&config.Postgres.MaxConnectionLifetime, "postgres-max-connection-liftime", 2500, "max connection lifetime")
// redis config
Cmd.Flags().Uint32VarP(&config.Redis.Database, "redis-database", "r", 0, "database name")
Cmd.Flags().StringVar(&config.Redis.Hostname, "redis-host", "redis", "hostname")
Cmd.Flags().StringVar(&config.Redis.Protocol, "redis-protocol", "tcp", "redis protocol")
Cmd.Flags().StringVar(&config.Redis.Password, "redis-password", "", "redis password")
Cmd.Flags().Int64Var(&config.Redis.Port, "redis-port", 6379, "redis port")
Cmd.Flags().Int64Var(&config.Redis.MaxRetries, "redis-max-retries", 0, "redis default max retries")
Cmd.Flags().Int64Var(&config.Redis.DialTimeout, "redis-dial-timeout", 5000, "redis dial max time out (minutes)")
// auth service
Cmd.Flags().BoolVarP(&config.Options.AuthServiceOn, "auth", "A", true, "activate auth service")
Cmd.Flags().StringVar(&config.AuthService.GenAlgo, "auth-gen-algo", "sha256", "token gen algo")
Cmd.Flags().StringVar(&config.AuthService.Secret, "auth-secret", "****", "token key prefix in redis")
Cmd.Flags().StringVar(&config.AuthService.HashAlgo, "auth-hash-salt", "****", "token key prefix in redis")
Cmd.Flags().StringVar(&config.AuthService.HashAlgo, "auth-hash-algo", "sha256", "token key prefix in redis")
Cmd.Flags().Int64Var(&config.AuthService.TokenLifetime, "auth-token-lifetime", 172800, "session lifetime (minutes)")
Cmd.Flags().StringVar(&config.AuthService.TokenKeyPrefix, "auth-token-key-prefix", "/auth/token/", "token key prefix in redis")
Cmd.Flags().StringArrayVar(&config.AuthService.DebugTokens, "auth-debug", []string{"DEBUG", "SUPBANK"}, "token key prefix in redis")
// meta service
Cmd.Flags().BoolVarP(&config.Options.MetaServiceOn, "meta", "M", true, "activate meta service")
Cmd.Flags().StringVar(&config.MetaService.Hostname, "meta-hostname", os.Getenv("HOSTNAME"), "hostname information")
Cmd.Flags().StringVar(&config.MetaService.Namespace, "meta-namespace", os.Getenv("NAMESPACE"), "redis namespace inforamtion")
Cmd.Flags().StringVar(&config.MetaService.Kind, "meta-kind", os.Getenv("KIND"), "kind information")
Cmd.Flags().StringVar(&config.MetaService.Role, "meta-role", os.Getenv("ROLE"), "role information")
// user service
Cmd.Flags().BoolVarP(&config.Options.UserServiceOn, "user", "U", true, "activate user service")
Cmd.Flags().StringVar(&config.UserService.HashAlgo, "user-hash-algo", "sha256", "hash to use for passwords at signup")
// health check service
Cmd.Flags().BoolVarP(&config.Options.UserServiceOn, "hc", "H", true, "activate health check service")
// blockchain service
Cmd.Flags().StringVar(&config.BlockchainService.Node, "blockchain-node", "node-0", "node name")
Cmd.Flags().StringVar(&config.BlockchainService.Addr, "blockchain-addr", "127.0.0.1:19000", "node address")
}
var Cmd = &cobra.Command{
Use: "start",
Short: "start grpc & proxy server and control startup strategy",
Run: startServer,
}
func startServer(*cobra.Command, []string) {
// init logging entry
logger := logrus.New()
logger.SetLevel(logrus.Level(config.Options.LoggingLevel))
if config.Options.Production {
logger.SetFormatter(&logrus.JSONFormatter{})
}
logrusEntry := logrus.NewEntry(logger)
// set package met logger
log.Set(logrusEntry)
// postgres client engine
postgresEngine, err := postgres.New(config.Postgres)
if err != nil {
log.Fatal("init database engine: %v", err)
}
// new postgres store
dbStore, _ := store.New(postgresEngine)
dbStore.GetEngine().SetLogLevel(core.LOG_OFF)
// wait for database ping response
if config.Options.WaitPostgres {
err := cmd.WaitPostgres(dbStore.GetEngine(), config)
if err != nil {
log.Fatal("postgres host isn't up: %v", err)
}
}
// initialize database
// create tables and populate with fake data
if config.Options.CreateTables {
// create tables
err := dbStore.Sync()
if err != nil {
log.Fatal("create tables: %v", err)
}
}
if config.Options.PopulateDb {
err := cmd.PopulateDatabase(dbStore, config)
if err != nil {
log.Fatal("populate db: %v", err)
}
}
// Initiliazing cache
redisClient, err := redis.New(config.Redis)
if err != nil {
log.Info("init cache client error: %v", err)
}
// wait for database ping response
if config.Options.WaitRedis {
err := cmd.WaitRedis(redisClient, config)
if err != nil {
log.Fatal("redis host isn't up: %v", err)
}
}
// Auth manager
authManager, _ := auth.NewManager(
config.AuthService,
dbStore,
redisClient,
)
if config.Options.CreateTokens {
err := cmd.CreateTokens(authManager, config)
if err != nil {
log.Fatal("create tokens: %v", err)
}
}
validator := valid.New()
// services
healthChecker := hc.New(redisClient, postgresEngine)
hcService := hcv1.NewService(authManager, healthChecker)
// metadata service
metaService := metav1.NewService(authManager, config)
// user service
userService := userv1.NewService(dbStore, authManager, validator)
// auth service
authService := authv1.NewService(dbStore, authManager, validator)
// wallet service
walletService := walletv1.NewService(dbStore, authManager)
bridge, err := bc.NewBridge(config.BlockchainService.Node, config.BlockchainService.Addr)
if err != nil {
log.Fatal("bridge connection failed: %v", err)
}
// bc service
bcService := bcv1.NewService(authManager, dbStore, bridge)
// grpc server
grpcServer, _ := grpc.New(
logrusEntry,
authManager,
hcService,
metaService,
authService,
userService,
bcService,
walletService,
)
// grpc listener
grpcListener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", config.Options.GrpcPort))
if err != nil {
log.Fatal("listener: %v", err)
}
defer grpcListener.Close()
// grace shutdown settings
done := make(chan struct{}, 2)
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
// spawn grpc server thread
go func() {
log.Info("starting grpc server at port %d", config.Options.GrpcPort)
if err := grpcServer.Serve(grpcListener); err != nil {
log.Info("error shutdown grpc server: %v", err)
}
log.Info("grpc server: stopped.")
// send signal when server shutdown
done <- struct{}{}
}()
ctx := context.Background()
// grpc proxy server
var grpcProxyServer *http.Server
if config.Options.GrpcProxyOn {
var err error
// spawn grpc proxy server thread
grpcProxyServer, err = grpcproxy.NewHttpServer(
ctx,
fmt.Sprintf("0.0.0.0:%d", config.Options.GrpcPort),
int(config.Options.GrpcProxyPort),
config.Options.SwaggerUI,
)
if err != nil {
log.Fatal("can't create grpc proxy server: %v", err)
}
go func() {
log.Info("starting grpc proxy server at port %d", config.Options.GrpcProxyPort)
err := grpcProxyServer.ListenAndServe()
if err != nil && err.Error() != "http: Server closed" {
log.Info("error shutdown grpc proxy server: %v", err)
}
log.Info("grpc proxy server: stopped.")
// send signal when server shutdown
done <- struct{}{}
}()
}
// block until sig kill send signal
_ = <-stop
if config.Options.GrpcProxyOn {
// time to wait before forcing shutdown
sleepSecond := time.Duration(config.Options.ShutdownDelay) * time.Millisecond
// timeout context
ctx, cancel := context.WithTimeout(ctx, sleepSecond)
log.Info("shutting down grpc proxy server timeout: %v seconds", sleepSecond)
// shutdown service
if err := grpcProxyServer.Shutdown(ctx); err != nil {
log.Fatal("error shutting down: ", err)
}
// cancel context
cancel()
// block until grpc proxy server send done signal
_ = <-done
}
log.Info("shutting down grpc server")
grpcServer.GracefulStop()
// block until grpc server send done signal
_ = <-done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment