diff --git a/go/authorization-service/go.mod b/go/authorization-service/go.mod index 1f5a4c64..8ea2aad0 100644 --- a/go/authorization-service/go.mod +++ b/go/authorization-service/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/envoyproxy/go-control-plane v0.9.5 - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/gogo/googleapis v1.4.0 github.com/golang/protobuf v1.4.2 github.com/google/uuid v1.1.1 diff --git a/go/authorization-service/go.sum b/go/authorization-service/go.sum index 129d4e33..e921457f 100644 --- a/go/authorization-service/go.sum +++ b/go/authorization-service/go.sum @@ -13,6 +13,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= diff --git a/go/authorization-service/service.go b/go/authorization-service/service.go index 6f8df440..9beee2b1 100644 --- a/go/authorization-service/service.go +++ b/go/authorization-service/service.go @@ -15,14 +15,14 @@ import ( "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" "log" + "log/slog" "net" "os" + "os/signal" "strings" + "syscall" ) -var logFlags = log.Ltime | log.Lshortfile -var errLog = log.New(os.Stderr, "", logFlags) - type user struct { id string desk string @@ -34,7 +34,7 @@ type authService struct { users map[string]user } -func (a authService) Login(_ context.Context, params *loginservice.LoginParams) (*loginservice.Token, error) { +func (a *authService) Login(_ context.Context, params *loginservice.LoginParams) (*loginservice.Token, error) { log.Printf("logging in") @@ -53,7 +53,7 @@ func (a *authService) Check(_ context.Context, req *auth.CheckRequest) (*auth.Ch path, ok := req.Attributes.Request.Http.Headers[":path"] if ok && strings.HasPrefix(path, "/loginservice.LoginService") { - log.Printf("permitted login for path:%v", path) + slog.Info("permitted login for path", "path", path) return newOkResponse(), nil } @@ -147,8 +147,7 @@ const ( func main() { - log.SetOutput(os.Stdout) - log.SetFlags(logFlags) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) dbString := bootstrap.GetEnvVar(DatabaseConnectionString) dbDriverName := bootstrap.GetEnvVar(DatabaseDriverName) @@ -159,13 +158,13 @@ func main() { } defer func() { if err := db.Close(); err != nil { - errLog.Printf("error when closing database connection: %v", err) + slog.Error("error when closing database connection", "error", err) } }() err = db.Ping() if err != nil { - log.Panic("could not establish a connection with the database: ", err) + log.Panicf("could not establish a connection with the database: %v", err) } r, err := db.Query("SELECT id, desk, permissionflags FROM users.users") @@ -184,24 +183,35 @@ func main() { users[u.id] = u } - log.Printf("loaded %v users", len(users)) + slog.Info("loaded users", "userCount", len(users)) authServer := &authService{users: users} go func() { + loginPort := "50551" lis, err := net.Listen("tcp", ":"+loginPort) if err != nil { log.Panicf("failed to listen: %v", err) } - log.Printf("listening on %s", lis.Addr()) - - grpcServer := grpc.NewServer() - loginservice.RegisterLoginServiceServer(grpcServer, authServer) - - log.Print("starting login server of port:", loginPort) - if err := grpcServer.Serve(lis); err != nil { - log.Fatalf("Failed to start server: %v", err) + slog.Info("authentication server listening", "listenAddress", lis.Addr()) + + authenticationGrpcServer := grpc.NewServer() + loginservice.RegisterLoginServiceServer(authenticationGrpcServer, authServer) + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + authenticationGrpcServer.GracefulStop() + }() + + slog.Info("starting authentication server", "port", loginPort) + if err := authenticationGrpcServer.Serve(lis); err != nil { + log.Panicf("Failed to start authentication server: %v", err) } }() @@ -210,15 +220,25 @@ func main() { if err != nil { log.Panicf("failed to listen: %v", err) } - log.Printf("listening on %s", lis.Addr()) + slog.Info("authorisation server listening", "listenAddress", lis.Addr()) grpcServer := grpc.NewServer() auth.RegisterAuthorizationServer(grpcServer, authServer) - log.Print("starting authorization server of port:", authPort) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + grpcServer.GracefulStop() + }() + + slog.Info("starting authorization server", "port", authPort) if err := grpcServer.Serve(lis); err != nil { - log.Panicf("Failed to start server: %v", err) + log.Panicf("Failed to start authorization server: %v", err) } } diff --git a/go/client-config-service/go.mod b/go/client-config-service/go.mod index b3e3376a..551a8425 100644 --- a/go/client-config-service/go.mod +++ b/go/client-config-service/go.mod @@ -3,7 +3,7 @@ module github.com/ettech/open-trading-platform/go/client-config-service go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/protobuf v1.4.2 github.com/lib/pq v1.2.0 google.golang.org/grpc v1.25.1 diff --git a/go/client-config-service/go.sum b/go/client-config-service/go.sum index 9479ab18..a1c2fd06 100644 --- a/go/client-config-service/go.sum +++ b/go/client-config-service/go.sum @@ -8,6 +8,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/go/client-config-service/service.go b/go/client-config-service/service.go index a18cbf4d..ef638549 100644 --- a/go/client-config-service/service.go +++ b/go/client-config-service/service.go @@ -11,13 +11,13 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/reflection" "log" + "log/slog" "net" "os" + "os/signal" + "syscall" ) -var logFlags = log.Ltime|log.Lshortfile -var errLog = log.New(os.Stderr, "", logFlags) - type service struct { db *sql.DB } @@ -56,11 +56,9 @@ func (s *service) StoreClientConfig(_ context.Context, params *api.StoreConfigPa } - - func (s *service) GetClientConfig(_ context.Context, parameters *api.GetConfigParameters) (*api.Config, error) { - lq := fmt.Sprintf( "select config from clientconfig.reactclientconfig where userid = '%v'", parameters.UserId) + lq := fmt.Sprintf("select config from clientconfig.reactclientconfig where userid = '%v'", parameters.UserId) r, err := s.db.Query(lq) @@ -75,7 +73,6 @@ func (s *service) GetClientConfig(_ context.Context, parameters *api.GetConfigPa return nil, fmt.Errorf("failed to fetch config from database:%w", err) } - return &api.Config{ Config: config, }, nil @@ -97,7 +94,7 @@ func newService(driverName, dbConnString string) (*service, error) { err = db.Ping() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to ping database: %w", err) } return s, nil @@ -107,16 +104,14 @@ func (s *service) Close() { if s.db != nil { err := s.db.Close() if err != nil { - errLog.Printf("erron closing database connection %v", err) + slog.Error("error closing database connection", "error", err) } } } - func main() { - log.SetOutput(os.Stdout) - log.SetFlags(logFlags) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) dbString := bootstrap.GetEnvVar("DB_CONN_STRING") dbDriverName := bootstrap.GetEnvVar("DB_DRIVER_NAME") @@ -137,8 +132,18 @@ func main() { s := grpc.NewServer() api.RegisterClientConfigServiceServer(s, service) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + reflection.Register(s) - fmt.Println("Started client config service on port:" + port) + slog.Info("Started client config service", "port", port) if err := s.Serve(lis); err != nil { log.Panicf("Error while serving : %v", err) } diff --git a/go/execution-venues/fix-sim-execution-venue/go.mod b/go/execution-venues/fix-sim-execution-venue/go.mod index 937ce2fd..4d2375c9 100644 --- a/go/execution-venues/fix-sim-execution-venue/go.mod +++ b/go/execution-venues/fix-sim-execution-venue/go.mod @@ -3,11 +3,12 @@ module github.com/ettec/open-trading-platform/go/execution-venues/fix-sim-execut go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/protobuf v1.4.2 github.com/google/uuid v1.1.1 github.com/quickfixgo/quickfix v0.6.0 github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 + github.com/stretchr/testify v1.4.0 google.golang.org/grpc v1.25.1 ) @@ -21,6 +22,7 @@ require ( github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/kafka-go v0.3.4 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 // indirect diff --git a/go/execution-venues/fix-sim-execution-venue/go.sum b/go/execution-venues/fix-sim-execution-venue/go.sum index a3b7a879..1079db58 100644 --- a/go/execution-venues/fix-sim-execution-venue/go.sum +++ b/go/execution-venues/fix-sim-execution-venue/go.sum @@ -27,10 +27,14 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXeraZhKn5pdLH7JnawKFQjib+ejRqoxOY= -github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/executionvenueservice.go b/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/executionvenueservice.go index a487d7e9..c793ac6b 100644 --- a/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/executionvenueservice.go +++ b/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/executionvenueservice.go @@ -5,7 +5,7 @@ import ( "fmt" api "github.com/ettec/otp-common/api/executionvenue" "github.com/ettec/otp-common/model" - "log" + "log/slog" ) type orderManager interface { @@ -15,7 +15,6 @@ type orderManager interface { SetOrderStatus(orderId string, status model.OrderStatus) error SetErrorMsg(orderId string, msg string) error AddExecution(orderId string, lastPrice model.Decimal64, lastQty model.Decimal64, execId string) error - Close() } type ExecVenueService struct { @@ -29,7 +28,7 @@ func New(om orderManager) *ExecVenueService { func (s *ExecVenueService) CreateAndRouteOrder(_ context.Context, params *api.CreateAndRouteOrderParams) (*api.OrderId, error) { - log.Printf("Received order parameters-> %v", params) + slog.Info("Received order parameters", "params", params) if params.GetQuantity() == nil { return nil, fmt.Errorf("quantity required on params:%v", params) @@ -53,11 +52,10 @@ func (s *ExecVenueService) CreateAndRouteOrder(_ context.Context, params *api.Cr result, err := s.orderManager.CreateAndRouteOrder(params) if err != nil { - log.Printf("error when creating and routing order:%v", err) - return nil, err + return nil, fmt.Errorf("error when creating and routing order:%w", err) } - log.Printf("created order id:%v", result.OrderId) + slog.Info("created order", "orderId", result.OrderId) return &api.OrderId{ OrderId: result.OrderId, @@ -65,9 +63,8 @@ func (s *ExecVenueService) CreateAndRouteOrder(_ context.Context, params *api.Cr } func (s *ExecVenueService) CancelOrder(_ context.Context, p *api.CancelOrderParams) (*model.Empty, error) { - err := s.orderManager.CancelOrder(p) - if err != nil { - return nil, err + if err := s.orderManager.CancelOrder(p); err != nil { + return nil, fmt.Errorf("error when cancelling order:%w", err) } return &model.Empty{}, nil @@ -75,9 +72,8 @@ func (s *ExecVenueService) CancelOrder(_ context.Context, p *api.CancelOrderPara func (s *ExecVenueService) ModifyOrder(_ context.Context, params *api.ModifyOrderParams) (*model.Empty, error) { - err := s.orderManager.ModifyOrder(params) - if err != nil { - return nil, err + if err := s.orderManager.ModifyOrder(params); err != nil { + return nil, fmt.Errorf("error when modifying order:%w", err) } return &model.Empty{}, nil @@ -86,9 +82,3 @@ func (s *ExecVenueService) ModifyOrder(_ context.Context, params *api.ModifyOrde func (s *ExecVenueService) GetExecutionParametersMetaData(context.Context, *model.Empty) (*api.ExecParamsMetaDataJson, error) { return &api.ExecParamsMetaDataJson{}, nil } - -func (s *ExecVenueService) Close() { - if s.orderManager != nil { - s.orderManager.Close() - } -} diff --git a/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager.go b/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager.go index 42d62871..6a0a77c3 100644 --- a/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager.go +++ b/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager.go @@ -8,8 +8,7 @@ import ( "github.com/ettec/otp-common/ordermanagement" "github.com/ettec/otp-common/staticdata" "github.com/google/uuid" - "log" - "os" + "log/slog" ) type orderGateway interface { @@ -18,6 +17,11 @@ type orderGateway interface { Modify(order *model.Order, listing *model.Listing, Quantity *model.Decimal64, Price *model.Decimal64) error } +// orderManager is responsible for the creation, modification and cancellation of orders. It depends on two resources +// that are single threaded and IO bound, the order cache and the order gateway. Therefore it is effectively single +// threaded. To increase throughput additional instances of the execution venue should be deployed. The order manager +// uses channels to queue commands, primarily to ensure that cancel commands are prioritised above all others and +// additionally to ensure that commands are executed fairly, i.e. in the order they are received, across clients type orderManagerImpl struct { createOrderChan chan createAndRouteOrderCmd cancelOrderChan chan cancelOrderCmd @@ -26,31 +30,25 @@ type orderManagerImpl struct { setOrderErrMsgChan chan setOrderErrorMsgCmd addExecChan chan addExecutionCmd - closeChan chan struct{} - orderStore *ordermanagement.OrderCache gateway orderGateway getListing func(ctx context.Context, listingId int32, result chan<- staticdata.ListingResult) - - errLog *log.Logger } func NewOrderManager(ctx context.Context, cache *ordermanagement.OrderCache, gateway orderGateway, - getListing func(ctx context.Context, listingId int32, result chan<- staticdata.ListingResult)) *orderManagerImpl { + getListing func(ctx context.Context, listingId int32, result chan<- staticdata.ListingResult), + cmdBufferSize int) *orderManagerImpl { om := &orderManagerImpl{ - errLog: log.New(os.Stderr, "", log.Lshortfile|log.Ltime), getListing: getListing, } - om.createOrderChan = make(chan createAndRouteOrderCmd, 100) - om.cancelOrderChan = make(chan cancelOrderCmd, 100) - om.modifyOrderChan = make(chan modifyOrderCmd, 100) - om.setOrderStatusChan = make(chan setOrderStatusCmd, 100) - om.setOrderErrMsgChan = make(chan setOrderErrorMsgCmd, 100) - om.addExecChan = make(chan addExecutionCmd, 100) - - om.closeChan = make(chan struct{}, 1) + om.createOrderChan = make(chan createAndRouteOrderCmd, cmdBufferSize) + om.cancelOrderChan = make(chan cancelOrderCmd, cmdBufferSize) + om.modifyOrderChan = make(chan modifyOrderCmd, cmdBufferSize) + om.setOrderStatusChan = make(chan setOrderStatusCmd, cmdBufferSize) + om.setOrderErrMsgChan = make(chan setOrderErrorMsgCmd, cmdBufferSize) + om.addExecChan = make(chan addExecutionCmd, cmdBufferSize) om.orderStore = cache om.gateway = gateway @@ -66,8 +64,6 @@ func (om *orderManagerImpl) executeOrderCommands(ctx context.Context) { select { case <-ctx.Done(): return - case <-om.closeChan: - return // Cancel Requests take priority over all other message types case oc := <-om.cancelOrderChan: om.executeCancelOrderCmd(ctx, oc.Params, oc.ResultChan) @@ -93,12 +89,8 @@ func (om *orderManagerImpl) executeOrderCommands(ctx context.Context) { } -func (om *orderManagerImpl) Close() { - om.closeChan <- struct{}{} -} - func (om *orderManagerImpl) SetErrorMsg(orderId string, msg string) error { - log.Printf("updating order %v error message to %v", orderId, msg) + slog.Info("updating order error message", "orderId", orderId, "newMsg", msg) resultChan := make(chan errorCmdResult) @@ -114,7 +106,7 @@ func (om *orderManagerImpl) SetErrorMsg(orderId string, msg string) error { } func (om *orderManagerImpl) SetOrderStatus(orderId string, status model.OrderStatus) error { - log.Printf("updating order %v status to %v", orderId, status) + slog.Info("updating order status", "orderId", orderId, "newStatus", status) resultChan := make(chan errorCmdResult) @@ -131,7 +123,7 @@ func (om *orderManagerImpl) SetOrderStatus(orderId string, status model.OrderSta func (om *orderManagerImpl) AddExecution(orderId string, lastPrice model.Decimal64, lastQty model.Decimal64, execId string) error { - log.Printf(orderId+":adding execution for price %v and quantity %v", lastPrice, lastQty) + slog.Info("adding execution to order", "orderId", orderId, "price", lastPrice, "quantity", lastQty) resultChan := make(chan errorCmdResult) @@ -145,8 +137,6 @@ func (om *orderManagerImpl) AddExecution(orderId string, lastPrice model.Decimal result := <-resultChan - log.Printf(orderId+":update traded quantity result:%v", result) - return result.Error } @@ -169,7 +159,7 @@ func (om *orderManagerImpl) CreateAndRouteOrder(params *api.CreateAndRouteOrderP } func (om *orderManagerImpl) ModifyOrder(params *api.ModifyOrderParams) error { - log.Printf("modifying order %v, price %v, quantity %v", params.OrderId, params.Price, params.Quantity) + slog.Info("modifying order", "params", params) resultChan := make(chan errorCmdResult) om.modifyOrderChan <- modifyOrderCmd{ @@ -179,14 +169,12 @@ func (om *orderManagerImpl) ModifyOrder(params *api.ModifyOrderParams) error { result := <-resultChan - log.Printf(params.OrderId+":modify order result: %v", result) - return result.Error } func (om *orderManagerImpl) CancelOrder(params *api.CancelOrderParams) error { - log.Print(params.OrderId + ":cancelling order") + slog.Info("cancelling order", "params", params) resultChan := make(chan errorCmdResult) @@ -197,8 +185,6 @@ func (om *orderManagerImpl) CancelOrder(params *api.CancelOrderParams) error { result := <-resultChan - log.Printf(params.OrderId+":cancel order result: %v", result) - return result.Error } @@ -237,18 +223,17 @@ func (om *orderManagerImpl) executeSetErrorMsg(ctx context.Context, id string, m order, exists, err := om.orderStore.GetOrder(id) if err != nil { - resultChan <- errorCmdResult{Error: fmt.Errorf("failed to get order from cache %v", id)} + resultChan <- errorCmdResult{Error: fmt.Errorf("failed to get order for id %s from cache: %w", id, err)} } if !exists { - resultChan <- errorCmdResult{Error: fmt.Errorf("set order error message failed, no order found for id %v", id)} + resultChan <- errorCmdResult{Error: fmt.Errorf("set order error message failed, no order found for id %s", id)} return } order.ErrorMessage = msg err = om.orderStore.Store(ctx, order) - } func (om *orderManagerImpl) executeSetOrderStatusCmd(ctx context.Context, id string, status model.OrderStatus, @@ -328,7 +313,7 @@ func (om *orderManagerImpl) executeCancelOrderCmd(ctx context.Context, } if !exists { - resultChan <- errorCmdResult{Error: fmt.Errorf("cancel order failed, no order found for id %v", params.OrderId)} + resultChan <- errorCmdResult{Error: fmt.Errorf("cancel order failed, no order found for id %s", params.OrderId)} return } @@ -366,9 +351,8 @@ func (om *orderManagerImpl) executeCreateAndRouteOrderCmd(ctx context.Context, p params.Price, params.ListingId, params.OriginatorId, params.OriginatorRef, params.RootOriginatorId, params.RootOriginatorRef, params.Destination) - err = order.SetTargetStatus(model.OrderStatus_LIVE) - if err != nil { - om.errLog.Printf("failed to set target status;%v", err) + if err = order.SetTargetStatus(model.OrderStatus_LIVE); err != nil { + slog.Error("failed to set target status to live", "error", err) } err = om.orderStore.Store(ctx, order) diff --git a/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager_test.go b/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager_test.go index b9645c34..4ee2ca6e 100644 --- a/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager_test.go +++ b/go/execution-venues/fix-sim-execution-venue/internal/executionvenue/ordermanager_test.go @@ -7,6 +7,7 @@ import ( "github.com/ettec/otp-common/ordermanagement" "github.com/ettec/otp-common/staticdata" "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" "os" "testing" ) @@ -16,7 +17,6 @@ func TestMain(m *testing.M) { defer cancel() setup(ctx) code := m.Run() - teardown() os.Exit(code) } @@ -32,11 +32,7 @@ func setup(ctx context.Context) { om = NewOrderManager(ctx, orderCache, &TestOrderManager{}, func(ctx context.Context, listingId int32, result chan<- staticdata.ListingResult) { result <- staticdata.ListingResult{Listing: &model.Listing{Id: 1}} - }) -} - -func teardown() { - defer om.Close() + }, 100) } func IntToDecimal64(i int) *model.Decimal64 { @@ -46,7 +42,7 @@ func IntToDecimal64(i int) *model.Decimal64 { } } -func TestOrderManagerImpl_CreateAndRouteOrderPartialFillImmediate(t *testing.T) { +func TestCreateAndRouteOrderPartialFillImmediate(t *testing.T) { params := &api.CreateAndRouteOrderParams{ OrderSide: model.Side_BUY, @@ -66,7 +62,8 @@ func TestOrderManagerImpl_CreateAndRouteOrderPartialFillImmediate(t *testing.T) t.Fatalf("created order not found in store") } - om.AddExecution(id.OrderId, *IntToDecimal64(20), *IntToDecimal64(5), "testexecid") + err = om.AddExecution(id.OrderId, *IntToDecimal64(20), *IntToDecimal64(5), "testexecid") + assert.NoError(t, err) testOrder := &model.Order{ Version: 1, @@ -91,7 +88,7 @@ func TestOrderManagerImpl_CreateAndRouteOrderPartialFillImmediate(t *testing.T) } -func TestOrderManagerImpl_CreateAndRouteOrderFullyFilledImmediate(t *testing.T) { +func TestCreateAndRouteOrderFullyFilledImmediate(t *testing.T) { params := &api.CreateAndRouteOrderParams{ OrderSide: model.Side_BUY, @@ -111,7 +108,8 @@ func TestOrderManagerImpl_CreateAndRouteOrderFullyFilledImmediate(t *testing.T) t.Fatalf("created order not found in store") } - om.AddExecution(id.OrderId, *IntToDecimal64(20), *IntToDecimal64(10), "testexecid") + err = om.AddExecution(id.OrderId, *IntToDecimal64(20), *IntToDecimal64(10), "testexecid") + assert.NoError(t, err) testOrder := &model.Order{ Id: id.OrderId, @@ -135,7 +133,7 @@ func TestOrderManagerImpl_CreateAndRouteOrderFullyFilledImmediate(t *testing.T) } -func TestOrderManagerImpl_CreateAndRouteOrder(t *testing.T) { +func TestCreateAndRouteOrder(t *testing.T) { params := &api.CreateAndRouteOrderParams{ OrderSide: model.Side_BUY, @@ -155,7 +153,8 @@ func TestOrderManagerImpl_CreateAndRouteOrder(t *testing.T) { t.Fatalf("created order not found in store") } - om.SetOrderStatus(id.OrderId, model.OrderStatus_LIVE) + err = om.SetOrderStatus(id.OrderId, model.OrderStatus_LIVE) + assert.NoError(t, err) testOrder := &model.Order{ Version: 1, @@ -180,7 +179,7 @@ func TestOrderManagerImpl_CreateAndRouteOrder(t *testing.T) { } -func TestOrderManagerImpl_CancelOrder(t *testing.T) { +func TestCancelOrder(t *testing.T) { listing := &model.Listing{Id: 1} @@ -194,18 +193,18 @@ func TestOrderManagerImpl_CancelOrder(t *testing.T) { id, _ := om.CreateAndRouteOrder(params) - om.SetOrderStatus(id.OrderId, model.OrderStatus_LIVE) + err := om.SetOrderStatus(id.OrderId, model.OrderStatus_LIVE) + assert.NoError(t, err) - err := om.CancelOrder(&api.CancelOrderParams{ + err = om.CancelOrder(&api.CancelOrderParams{ OrderId: id.OrderId, ListingId: listing.Id, OwnerId: "XNAS", }) - if err != nil { - t.Fatalf("cancel order call failed: %v", err) - } + assert.NoError(t, err) - om.SetOrderStatus(id.OrderId, model.OrderStatus_CANCELLED) + err = om.SetOrderStatus(id.OrderId, model.OrderStatus_CANCELLED) + assert.NoError(t, err) testOrder := &model.Order{ Version: 3, @@ -234,15 +233,15 @@ func TestOrderManagerImpl_CancelOrder(t *testing.T) { type TestOrderManager struct { } -func (f *TestOrderManager) Send(order *model.Order, listing *model.Listing) error { +func (f *TestOrderManager) Send(_ *model.Order, _ *model.Listing) error { return nil } -func (f *TestOrderManager) Cancel(order *model.Order) error { +func (f *TestOrderManager) Cancel(_ *model.Order) error { return nil } -func (f *TestOrderManager) Modify(order *model.Order, listing *model.Listing, Quantity *model.Decimal64, Price *model.Decimal64) error { +func (f *TestOrderManager) Modify(_ *model.Order, _ *model.Listing, _ *model.Decimal64, _ *model.Decimal64) error { return nil } @@ -260,7 +259,7 @@ type testOrderStore struct { ordersMap map[string]*model.Order } -func (t *testOrderStore) Write(ctx context.Context, order *model.Order) error { +func (t *testOrderStore) Write(_ context.Context, order *model.Order) error { t.orders = append(t.orders, order) @@ -269,7 +268,7 @@ func (t *testOrderStore) Write(ctx context.Context, order *model.Order) error { return nil } -func (t *testOrderStore) LoadOrders(ctx context.Context, loadOrder func(order *model.Order) bool) (map[string]*model.Order, error) { +func (t *testOrderStore) LoadOrders(_ context.Context, _ func(order *model.Order) bool) (map[string]*model.Order, error) { return map[string]*model.Order{}, nil } diff --git a/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway.go b/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway.go index cd6413f5..643de0ea 100644 --- a/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway.go +++ b/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway.go @@ -13,8 +13,7 @@ import ( "github.com/quickfixgo/quickfix/fix50sp2/executionreport" "github.com/quickfixgo/quickfix/fix50sp2/newordersingle" "github.com/shopspring/decimal" - "log" - "os" + "log/slog" "strings" "time" ) @@ -24,7 +23,7 @@ type fixOrderGateway struct { orderHandler OrderHandler } -func NewFixOrderGateway(sessionID quickfix.SessionID) *fixOrderGateway{ +func NewFixOrderGateway(sessionID quickfix.SessionID) *fixOrderGateway { return &fixOrderGateway{ sessionID: sessionID, } @@ -34,7 +33,7 @@ func (f *fixOrderGateway) Send(order *model.Order, listing *model.Listing) error side, err := getFixSide(order.Side) if err != nil { - return err + return fmt.Errorf("failed to get fix side: %w", err) } msg := newordersingle.New(field.NewClOrdID(order.Id), field.NewSide(side), @@ -116,13 +115,10 @@ type fixHandler struct { sessionToHandler map[quickfix.SessionID]OrderHandler inboundRouter *quickfix.MessageRouter outboundRouter *quickfix.MessageRouter - errLog *log.Logger } func NewFixHandler(sessionID quickfix.SessionID, handler OrderHandler) quickfix.Application { - f := fixHandler{sessionToHandler: make(map[quickfix.SessionID]OrderHandler), - errLog: log.New(os.Stderr, "error", log.Ltime | log.Lshortfile), - } + f := fixHandler{sessionToHandler: make(map[quickfix.SessionID]OrderHandler)} f.sessionToHandler[sessionID] = handler f.inboundRouter = quickfix.NewMessageRouter() @@ -142,46 +138,44 @@ func (f *fixHandler) onOrderCancelReject(msg ordercancelreject.OrderCancelReject return nil } - orderId, err := msg.GetClOrdID() - if err != nil { - return err + orderId, msgRejectErr := msg.GetClOrdID() + if msgRejectErr != nil { + return msgRejectErr } - errMsg, err := msg.GetText() - if err != nil { - return err + errMsg, msgRejectErr := msg.GetText() + if msgRejectErr != nil { + return msgRejectErr } er := handler.SetErrorMsg(orderId, errMsg) if er != nil { - f.errLog.Printf("Failed to set error msg on order %v, message: %v, error:%v", orderId, errMsg, er) + slog.Info("Failed to set error msg on order", "orderId", orderId, "errorMessage", errMsg, "error", er) } return nil } func logSessionMsg(sessionID quickfix.SessionID, msg string) { - log.Print(sessionID.String() + ":" + msg) + slog.Info(msg, "sessionID", sessionID.String()) } -func logSessionMsgf(sessionID quickfix.SessionID, format string, v ...interface{}) { - log.Printf(sessionID.String()+":"+format, v) +func logSessionMsgf(sessionID quickfix.SessionID, format string, v ...any) { + slog.Info(fmt.Sprintf(format, v...), "sessionID", sessionID.String()) } func (f *fixHandler) onOutboundBusinessMessageReject(msg businessmessagereject.BusinessMessageReject, sessionID quickfix.SessionID) (err quickfix.MessageRejectError) { - logSessionMsgf(sessionID, "Sending reject message to target: %v", toReadableString(msg.Message)) - return nil } -func (f *fixHandler) onExecutionReport(msg executionreport.ExecutionReport, sessionID quickfix.SessionID) (err quickfix.MessageRejectError) { +func (f *fixHandler) onExecutionReport(msg executionreport.ExecutionReport, sessionID quickfix.SessionID) quickfix.MessageRejectError { logSessionMsg(sessionID, "received execution report:"+toReadableString(msg.Message)) - execType, err := msg.GetExecType() - if err != nil { - return err + execType, msgRejectErr := msg.GetExecType() + if msgRejectErr != nil { + return msgRejectErr } handler, exists := f.sessionToHandler[sessionID] @@ -190,9 +184,9 @@ func (f *fixHandler) onExecutionReport(msg executionreport.ExecutionReport, sess return nil } - orderId, err := msg.GetClOrdID() - if err != nil { - return err + orderId, msgRejectErr := msg.GetClOrdID() + if msgRejectErr != nil { + return msgRejectErr } switch execType { @@ -215,64 +209,60 @@ func (f *fixHandler) onExecutionReport(msg executionreport.ExecutionReport, sess return nil } case enum.ExecType_TRADE: - lastQty, err := msg.GetLastQty() + lastQty, msgRejectErr := msg.GetLastQty() - if err != nil { - return err + if msgRejectErr != nil { + return msgRejectErr } - lastPrice, err := msg.GetLastPx() - if err != nil { - return err + lastPrice, msgRejectErr := msg.GetLastPx() + if msgRejectErr != nil { + return msgRejectErr } - execId, err := msg.GetExecID() - if err != nil { - return err + execId, msgRejectErr := msg.GetExecID() + if msgRejectErr != nil { + return msgRejectErr } - er := handler.AddExecution(orderId, *model.ToDecimal64(lastPrice), *model.ToDecimal64(lastQty), execId) - if er != nil { - f.errLog.Printf("failed to add execution to order %v, error: %v", orderId, er) + if err := handler.AddExecution(orderId, *model.ToDecimal64(lastPrice), *model.ToDecimal64(lastQty), execId); err != nil { + slog.Error("failed to add execution to order", "orderId", orderId, "error", err) } } return nil } -//Notification of a session begin created. +// Notification of a session begin created. func (f *fixHandler) OnCreate(sessionID quickfix.SessionID) { logSessionMsg(sessionID, "created") } -//Notification of a session successfully logging on. +// Notification of a session successfully logging on. func (f *fixHandler) OnLogon(sessionID quickfix.SessionID) { logSessionMsg(sessionID, "logon received") } -//Notification of a session logging off or disconnecting. +// Notification of a session logging off or disconnecting. func (f *fixHandler) OnLogout(sessionID quickfix.SessionID) { logSessionMsg(sessionID, "logout received") } -//Notification of admin message being sent to target. +// Notification of admin message being sent to target. func (f *fixHandler) ToAdmin(message *quickfix.Message, sessionID quickfix.SessionID) { } -//Notification of app message being sent to target. +// Notification of app message being sent to target. func (f *fixHandler) ToApp(message *quickfix.Message, sessionID quickfix.SessionID) error { - return nil } -//Notification of admin message being received from target. +// Notification of admin message being received from target. func (f *fixHandler) FromAdmin(message *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError { - return nil } -//Notification of app message being received from target. +// Notification of app message being received from target. func (f *fixHandler) FromApp(message *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError { - return f.inboundRouter.Route(message, sessionID) } diff --git a/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway_test.go b/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway_test.go index 0b758439..0da4c380 100644 --- a/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway_test.go +++ b/go/execution-venues/fix-sim-execution-venue/internal/fixgateway/fixgateway_test.go @@ -12,7 +12,7 @@ func toFixString(decimal64 model.Decimal64) string { return str } -func Test_toFixDecimal(t *testing.T) { +func TestToFixDecimal(t *testing.T) { type args struct { d model.Decimal64 diff --git a/go/execution-venues/fix-sim-execution-venue/service.go b/go/execution-venues/fix-sim-execution-venue/service.go index e026cd33..8b27d34e 100644 --- a/go/execution-venues/fix-sim-execution-venue/service.go +++ b/go/execution-venues/fix-sim-execution-venue/service.go @@ -9,6 +9,9 @@ import ( "github.com/ettec/otp-common/ordermanagement" "github.com/ettec/otp-common/orderstore" "github.com/ettec/otp-common/staticdata" + "log/slog" + "os/signal" + "syscall" "github.com/ettec/otp-common/bootstrap" @@ -25,14 +28,11 @@ import ( func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime | log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) kafkaBrokers := bootstrap.GetEnvVar("KAFKA_BROKERS") id := bootstrap.GetEnvVar("ID") - s := grpc.NewServer() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -62,37 +62,44 @@ func main() { gateway := fixgateway.NewFixOrderGateway(sessionID) - om := executionvenue.NewOrderManager(ctx, orderCache, gateway, sds.GetListing) + om := executionvenue.NewOrderManager(ctx, orderCache, gateway, sds.GetListing, + bootstrap.GetOptionalIntEnvVar("ORDER_MANAGER_CMD_BUFFER_SIZE", 100)) - fixServerCloseChan := make(chan struct{}) - err = createFixGateway(fixServerCloseChan, sessionID, om) + closeFixGatewayFn, err := createFixGateway(sessionID, om) if err != nil { - panic(fmt.Errorf("failed to create fix gateway: %v", err)) + log.Panicf("failed to create fix gateway: %v", err) } - - defer func() { fixServerCloseChan <- struct{}{} }() + defer closeFixGatewayFn() service := executionvenue.New(om) - defer service.Close() + s := grpc.NewServer() api.RegisterExecutionVenueServer(s, service) reflection.Register(s) port := "50551" - fmt.Println("Starting Execution Venue Service on port:" + port) + slog.Info("Starting Execution Venue Service", "port", port) lis, err := net.Listen("tcp", "0.0.0.0:"+port) if err != nil { - log.Fatalf("Error while listening : %v", err) + log.Panicf("Error while listening : %v", err) } + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { - log.Fatalf("error while serving : %v", err) + log.Panicf("error while serving : %v", err) } - - fmt.Println("Exiting execution venue service") } func getFixConfig(sessionId quickfix.SessionID) string { @@ -135,38 +142,31 @@ func getFixConfig(sessionId quickfix.SessionID) string { return template } -func createFixGateway(done chan struct{}, id quickfix.SessionID, handler fixgateway.OrderHandler) error { +func createFixGateway(id quickfix.SessionID, handler fixgateway.OrderHandler) (close func(), err error) { fixConfig := getFixConfig(id) app := fixgateway.NewFixHandler(id, handler) - log.Printf("Creating fix engine with config: %v", fixConfig) + slog.Info("Creating fix engine", "config", fixConfig) appSettings, err := quickfix.ParseSettings(strings.NewReader(fixConfig)) if err != nil { - return fmt.Errorf("failed parse config: %v", err) + return nil, fmt.Errorf("failed parse config: %w", err) } storeFactory := quickfix.NewFileStoreFactory(appSettings) logFactory, err := quickfix.NewFileLogFactory(appSettings) if err != nil { - return fmt.Errorf("failed to create logFactory: %v", err) + return nil, fmt.Errorf("failed to create logFactory: %w", err) } initiator, err := quickfix.NewInitiator(app, storeFactory, appSettings, logFactory) if err != nil { - return fmt.Errorf("failed to create initiator: %v", err) + return nil, fmt.Errorf("failed to create initiator: %w", err) } - go func() { - err = initiator.Start() - if err != nil { - panic(fmt.Errorf("failed to start the fix engine: %v", err)) - } - - <-done - - defer initiator.Stop() - }() + if err = initiator.Start(); err != nil { + return nil, fmt.Errorf("failed to start the fix engine: %w", err) + } - return nil + return initiator.Stop, nil } diff --git a/go/execution-venues/order-router/go.mod b/go/execution-venues/order-router/go.mod index bd6d16f6..2d8e0697 100644 --- a/go/execution-venues/order-router/go.mod +++ b/go/execution-venues/order-router/go.mod @@ -3,7 +3,7 @@ module github.com/ettec/open-trading-platform/go/order-router go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 google.golang.org/grpc v1.25.1 k8s.io/api v0.17.4 k8s.io/apimachinery v0.17.4 diff --git a/go/execution-venues/order-router/go.sum b/go/execution-venues/order-router/go.sum index 694a382f..dbd3cbe2 100644 --- a/go/execution-venues/order-router/go.sum +++ b/go/execution-venues/order-router/go.sum @@ -28,6 +28,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/execution-venues/order-router/orderrouter.go b/go/execution-venues/order-router/orderrouter.go index cfeeae97..94021d22 100644 --- a/go/execution-venues/order-router/orderrouter.go +++ b/go/execution-venues/order-router/orderrouter.go @@ -20,42 +20,41 @@ import ( type orderRouter struct { micToExecVenue map[string]map[int]*execVenue ownerIdToExecVenue map[string]*execVenue - venueMux sync.Mutex + mux sync.Mutex } -func New(connectRetrySecs int) *orderRouter { +func NewOrderRouter(connectRetrySecs int) (*orderRouter, error) { router := &orderRouter{ micToExecVenue: map[string]map[int]*execVenue{}, ownerIdToExecVenue: map[string]*execVenue{}, - venueMux: sync.Mutex{}, + mux: sync.Mutex{}, } - go func() { + namespace := "default" + clientSet := k8s.GetK8sClientSet(false) + serviceType := "execution-venue" + pods, err := clientSet.CoreV1().Pods(namespace).Watch(v1.ListOptions{ + LabelSelector: "servicetype=" + serviceType, + }) - namespace := "default" - clientSet := k8s.GetK8sClientSet(false) - serviceType := "execution-venue" - pods, err := clientSet.CoreV1().Pods(namespace).Watch(v1.ListOptions{ - LabelSelector: "servicetype=" + serviceType, - }) + if err != nil { + return nil, fmt.Errorf("failed to watch pods for service type %v, error: %v", serviceType, err) + } - if err != nil { - panic(err) - } + go func() { for e := range pods.ResultChan() { pod := e.Object.(*v12.Pod) bsp, err := loadbalancing.GetBalancingStatefulPod(*pod) if err != nil { - panic(err) + log.Panicf("failed to get balancing stateful pod: %v", err) } if e.Type == watch.Added { - client, err := createExecVenueConnection(time.Duration(connectRetrySecs)*time.Second, bsp.TargetAddress) if err != nil { - errLog.Printf("failed to create connection to execution venue service at %v, error: %v", bsp.TargetAddress, err) + slog.Error("failed to create connection to execution venue service", "targetAddress", bsp.TargetAddress, "error", err) continue } @@ -66,25 +65,26 @@ func New(connectRetrySecs int) *orderRouter { } }() - return router + return router, nil } func (o *orderRouter) removeExecVenue(bsp *loadbalancing.BalancingStatefulPod) { - o.venueMux.Lock() - defer o.venueMux.Unlock() + o.mux.Lock() + defer o.mux.Unlock() - if ordToEv, ok := o.micToExecVenue[bsp.Mic]; ok { - delete(ordToEv, bsp.Ordinal) + if ordinalToExecutionVenue, ok := o.micToExecVenue[bsp.Mic]; ok { + delete(ordinalToExecutionVenue, bsp.Ordinal) } delete(o.ownerIdToExecVenue, bsp.Name) - log.Printf("removed execution venue for mic: %v, target address: %v, stateful set ordinal %v", bsp.Mic, bsp.TargetAddress, bsp.Ordinal) + slog.Info("removed execution venue", "mic", bsp.Mic, + "targetAddress", bsp.TargetAddress, "ordinal", bsp.Ordinal) } func (o *orderRouter) addExecVenue(bsp *loadbalancing.BalancingStatefulPod, ev *execVenue) { - o.venueMux.Lock() - defer o.venueMux.Unlock() + o.mux.Lock() + defer o.mux.Unlock() if _, ok := o.micToExecVenue[bsp.Mic]; !ok { o.micToExecVenue[bsp.Mic] = map[int]*execVenue{} @@ -94,8 +94,8 @@ func (o *orderRouter) addExecVenue(bsp *loadbalancing.BalancingStatefulPod, ev * o.ownerIdToExecVenue[bsp.Name] = ev - log.Printf("added execution venue for mic: %v, target address: %v, stateful set ordinal %v", bsp.Mic, bsp.TargetAddress, bsp.Ordinal) - + slog.Info("added execution venue", "mic", bsp.Mic, + "targetAddress", bsp.TargetAddress, "ordinal", bsp.Ordinal) } func (o *orderRouter) GetExecutionParametersMetaData(context.Context, *model.Empty) (*executionvenue.ExecParamsMetaDataJson, error) { @@ -107,7 +107,8 @@ func (o *orderRouter) CreateAndRouteOrder(c context.Context, p *executionvenue.C ev, err := o.getExecutionVenueForListing(p.ListingId, p.Destination) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get execution venue for listing ID %d and destination %s, error: %w", + p.ListingId, p.Destination, err) } id, err := ev.client.CreateAndRouteOrder(c, p) @@ -117,14 +118,14 @@ func (o *orderRouter) CreateAndRouteOrder(c context.Context, p *executionvenue.C } slog.Info("routed create order request", "request", p, "executionVenue", ev.podId, "orderId", id) - + return id, nil } func (o *orderRouter) getExecutionVenueForOwnerId(ownerId string) (*execVenue, error) { - o.venueMux.Lock() - defer o.venueMux.Unlock() + o.mux.Lock() + defer o.mux.Unlock() if ev, ok := o.ownerIdToExecVenue[ownerId]; ok { return ev, nil @@ -135,8 +136,8 @@ func (o *orderRouter) getExecutionVenueForOwnerId(ownerId string) (*execVenue, e } func (o *orderRouter) getExecutionVenueForListing(listingId int32, destination string) (*execVenue, error) { - o.venueMux.Lock() - defer o.venueMux.Unlock() + o.mux.Lock() + defer o.mux.Unlock() if evs, ok := o.micToExecVenue[destination]; ok { numVenues := int32(len(evs)) ordinal := loadbalancing.GetBalancingOrdinal(listingId, numVenues) @@ -183,12 +184,12 @@ func (o *orderRouter) CancelOrder(c context.Context, p *executionvenue.CancelOrd func createExecVenueConnection(maxReconnectInterval time.Duration, targetAddress string) (cac *execVenue, err error) { - log.Printf("connecting to execution venue service at: %v", targetAddress) + slog.Info("connecting to execution venue service", "targetAddress", targetAddress) conn, err := grpc.Dial(targetAddress, grpc.WithInsecure(), grpc.WithBackoffMaxDelay(maxReconnectInterval)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to dial execution venue service: %w", err) } client := executionvenue.NewExecutionVenueClient(conn) diff --git a/go/execution-venues/order-router/service.go b/go/execution-venues/order-router/service.go index b3357709..e43cb0c8 100644 --- a/go/execution-venues/order-router/service.go +++ b/go/execution-venues/order-router/service.go @@ -1,18 +1,19 @@ package main import ( - "fmt" "github.com/ettec/otp-common/api/executionvenue" "github.com/ettec/otp-common/bootstrap" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "k8s.io/apimachinery/pkg/types" "log" + "log/slog" "net" "os" + "os/signal" + "syscall" ) - var errLog = log.New(os.Stderr, "", log.Ltime|log.Lshortfile) type execVenue struct { @@ -21,22 +22,23 @@ type execVenue struct { conn *grpc.ClientConn } - func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime|log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) maxConnectRetrySecs := bootstrap.GetOptionalIntEnvVar("MAX_CONNECT_RETRY_SECONDS", 60) - orderRouter := New(maxConnectRetrySecs) + orderRouter, err := NewOrderRouter(maxConnectRetrySecs) + if err != nil { + log.Panicf("failed to create order router: %v", err) + } port := "50581" - fmt.Println("Starting Order Router on port:" + port) + slog.Info("Starting Order Router", "port", port) lis, err := net.Listen("tcp", "0.0.0.0:"+port) if err != nil { - log.Fatalf("Error while listening : %v", err) + log.Panicf("Error while listening : %v", err) } s := grpc.NewServer() @@ -44,9 +46,18 @@ func main() { executionvenue.RegisterExecutionVenueServer(s, orderRouter) reflection.Register(s) - if err := s.Serve(lis); err != nil { - log.Fatalf("Error while serving : %v", err) - } + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { + log.Panicf("Error while serving : %v", err) + } } diff --git a/go/execution-venues/smart-router/go.mod b/go/execution-venues/smart-router/go.mod index ce54d477..6c46bd9e 100644 --- a/go/execution-venues/smart-router/go.mod +++ b/go/execution-venues/smart-router/go.mod @@ -3,7 +3,7 @@ module github.com/ettec/open-trading-platform/go/smart-router go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/google/uuid v1.1.1 google.golang.org/grpc v1.25.1 ) diff --git a/go/execution-venues/smart-router/go.sum b/go/execution-venues/smart-router/go.sum index ce6d651b..9f0c270c 100644 --- a/go/execution-venues/smart-router/go.sum +++ b/go/execution-venues/smart-router/go.sum @@ -41,6 +41,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/execution-venues/smart-router/service.go b/go/execution-venues/smart-router/service.go index 2ba001ce..60307bf5 100644 --- a/go/execution-venues/smart-router/service.go +++ b/go/execution-venues/smart-router/service.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" common "github.com/ettec/otp-common" "github.com/ettec/otp-common/api" "github.com/ettec/otp-common/api/executionvenue" @@ -11,7 +10,10 @@ import ( "github.com/ettec/otp-common/ordermanagement" "github.com/ettec/otp-common/staticdata" "github.com/ettec/otp-common/strategy" + "log/slog" "os" + "os/signal" + "syscall" "time" "github.com/ettec/otp-common/bootstrap" @@ -27,16 +29,13 @@ import ( func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime | log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) id := bootstrap.GetEnvVar("ID") maxConnectRetry := time.Duration(bootstrap.GetOptionalIntEnvVar("MAX_CONNECT_RETRY_SECONDS", 60)) * time.Second kafkaBrokersString := bootstrap.GetEnvVar("KAFKA_BROKERS") - s := grpc.NewServer() - kafkaBrokers := strings.Split(kafkaBrokersString, ",") ctx, cancel := context.WithCancel(context.Background()) @@ -89,18 +88,28 @@ func main() { log.Panicf("failed to create strategy manager: %v", err) } + s := grpc.NewServer() executionvenue.RegisterExecutionVenueServer(s, sm) - reflection.Register(s) port := "50551" - fmt.Println("Starting Smart Router on port:" + port) + slog.Info("Starting Smart Router", "port", port) lis, err := net.Listen("tcp", "0.0.0.0:"+port) if err != nil { log.Panicf("failed to listen on port %s : %v", port, err) } + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { log.Panicf("error while serving: %v", err) } diff --git a/go/execution-venues/vwap-strategy/go.mod b/go/execution-venues/vwap-strategy/go.mod index 4a71b182..a79c0fb7 100644 --- a/go/execution-venues/vwap-strategy/go.mod +++ b/go/execution-venues/vwap-strategy/go.mod @@ -3,7 +3,7 @@ module github.com/ettec/open-trading-platform/go/execution-venues/vwap-strategy go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/stretchr/testify v1.4.0 google.golang.org/grpc v1.25.1 ) diff --git a/go/execution-venues/vwap-strategy/go.sum b/go/execution-venues/vwap-strategy/go.sum index 04c2c029..e76dc4ad 100644 --- a/go/execution-venues/vwap-strategy/go.sum +++ b/go/execution-venues/vwap-strategy/go.sum @@ -31,6 +31,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/execution-venues/vwap-strategy/service.go b/go/execution-venues/vwap-strategy/service.go index d3479377..8ca6a8e8 100644 --- a/go/execution-venues/vwap-strategy/service.go +++ b/go/execution-venues/vwap-strategy/service.go @@ -17,24 +17,24 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/reflection" "log" + "log/slog" "net" "os" + "os/signal" "strings" + "syscall" "time" ) func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime | log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) id := bootstrap.GetEnvVar("ID") maxConnectRetry := time.Duration(bootstrap.GetOptionalIntEnvVar("MAX_CONNECT_RETRY_SECONDS", 60)) * time.Second kafkaBrokersString := bootstrap.GetEnvVar("KAFKA_BROKERS") - log.Print("Starting vwap strategy") - - s := grpc.NewServer() + slog.Info("Starting vwap strategy") kafkaBrokers := strings.Split(kafkaBrokersString, ",") @@ -43,7 +43,7 @@ func main() { sds, err := staticdata.NewStaticDataSource(ctx) if err != nil { - log.Fatalf("failed to create static data source:%v", err) + log.Panicf("failed to create static data source:%v", err) } clientSet := k8s.GetK8sClientSet(false) @@ -107,14 +107,14 @@ func main() { orderstore.DefaultWriterConfig(common.ORDERS_TOPIC, kafkaBrokers), id) if err != nil { - panic(fmt.Errorf("failed to create order store: %v", err)) + log.Panicf("failed to create order store: %v", err) } childOrderUpdates, err := ordermanagement.GetChildOrders(ctx, id, orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, kafkaBrokers), bootstrap.GetOptionalIntEnvVar("VWAPSTRATEGY_CHILD_ORDER_UPDATES_BUFFER_SIZE", 1000)) if err != nil { - panic(err) + log.Panicf("failed to create child order updates channel:%v", err) } distributor := ordermanagement.NewChildOrderUpdatesDistributor(childOrderUpdates, 10000) @@ -124,20 +124,31 @@ func main() { log.Panicf("failed to create strategy manager:%v", err) } + s := grpc.NewServer() executionvenue.RegisterExecutionVenueServer(s, sm) reflection.Register(s) port := "50551" - fmt.Println("Starting Execution Venue Service on port:" + port) + slog.Info("Starting Execution Venue Service", "port", port) lis, err := net.Listen("tcp", "0.0.0.0:"+port) if err != nil { - log.Fatalf("Error while listening : %v", err) + log.Panicf("Error while listening : %v", err) } + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { - log.Fatalf("error while serving : %v", err) + log.Panicf("error while serving : %v", err) } } diff --git a/go/market-data/market-data-gateway-fixsim/go.mod b/go/market-data/market-data-gateway-fixsim/go.mod index 1ba99c38..ca0a9702 100644 --- a/go/market-data/market-data-gateway-fixsim/go.mod +++ b/go/market-data/market-data-gateway-fixsim/go.mod @@ -3,7 +3,7 @@ module github.com/ettec/open-trading-platform/go/market-data/market-data-gateway go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/protobuf v1.4.2 github.com/prometheus/client_golang v1.7.1 github.com/stretchr/testify v1.4.0 diff --git a/go/market-data/market-data-gateway-fixsim/go.sum b/go/market-data/market-data-gateway-fixsim/go.sum index 226102ab..1a73195b 100644 --- a/go/market-data/market-data-gateway-fixsim/go.sum +++ b/go/market-data/market-data-gateway-fixsim/go.sum @@ -38,6 +38,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixquotestream.go b/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixquotestream.go index faa0d742..48044a0a 100644 --- a/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixquotestream.go +++ b/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixquotestream.go @@ -47,7 +47,7 @@ func NewQuoteStreamFromFixClient(parentCtx context.Context, ctx, cancel := context.WithCancel(parentCtx) out := make(chan *model.ClobQuote, sendBufferSize) - n := &FixQuoteStream{ + quoteStream := &FixQuoteStream{ ctx: ctx, connectionName: connectionName, out: out, @@ -63,12 +63,13 @@ func NewQuoteStreamFromFixClient(parentCtx context.Context, go func() { defer close(out) + defer quoteStream.Close() for { select { case <-ctx.Done(): break - case lr := <-n.getListingResultChan: + case lr := <-quoteStream.getListingResultChan: if lr.Err != nil { log.Error("failed to get listing", "error", lr.Err) @@ -76,11 +77,11 @@ func NewQuoteStreamFromFixClient(parentCtx context.Context, } symbolToListingId[lr.Listing.MarketSymbol] = lr.Listing.Id go func() { - if err := n.fixMarketDataClient.Subscribe(lr.Listing.MarketSymbol); err != nil { + if err := quoteStream.fixMarketDataClient.Subscribe(lr.Listing.MarketSymbol); err != nil { log.Error("failed to subscribe", "error", err) } }() - case r, ok := <-n.fixMarketDataClient.Chan(): + case r, ok := <-quoteStream.fixMarketDataClient.Chan(): if !ok { log.Warn("fix sim client closed") return @@ -121,7 +122,7 @@ func NewQuoteStreamFromFixClient(parentCtx context.Context, } idToQuote[listingId] = newQuote - n.out <- newQuote + quoteStream.out <- newQuote } else { log.Warn("received refresh for unknown symbol", "symbol", symbol) @@ -134,14 +135,14 @@ func NewQuoteStreamFromFixClient(parentCtx context.Context, emptyQuote.StreamInterrupted = true emptyQuote.StreamStatusMsg = "fix sim adapter stream interrupted" idToQuote[id] = emptyQuote - n.out <- emptyQuote + quoteStream.out <- emptyQuote } } } } }() - return n, nil + return quoteStream, nil } func copyQuote(quote *model.ClobQuote) (*model.ClobQuote, error) { diff --git a/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixsimmarketdataclient.go b/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixsimmarketdataclient.go index bf021966..a58bf743 100644 --- a/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixsimmarketdataclient.go +++ b/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim/fixsimmarketdataclient.go @@ -31,8 +31,6 @@ type GrpcConnection interface { func NewFixSimMarketDataClient(ctx context.Context, id string, client FixSimMarketDataServiceClient, conn GrpcConnection, outBufferSize int) (*fixSimMarketDataClient, error) { - log := slog.Default() - mdClient := &fixSimMarketDataClient{ subscriptionsChan: make(chan string, 100), out: make(chan *marketdata.MarketDataIncrementalRefresh, outBufferSize), @@ -50,18 +48,18 @@ func NewFixSimMarketDataClient(ctx context.Context, id string, client FixSimMark case newStream := <-streamChan: stream = newStream if stream != nil { - log.Info("new stream connected, resubscribing to all listings") + slog.Info("new stream connected, resubscribing to all listings") for symbol := range subscriptions { err := stream.Send(&marketdata.MarketDataRequest{Parties: []*common.Parties{{PartyId: id}}, InstrmtMdReqGrp: []*common.InstrmtMDReqGrp{{Instrument: &common.Instrument{Symbol: symbol}}}}) if err != nil { - log.Error("failed to resubscribe to quote", "symbol", symbol, "error", err) + slog.Error("failed to resubscribe to quote", "symbol", symbol, "error", err) break } } - log.Info("resubscribed to all quotes", "numSubscriptions", len(subscriptions)) + slog.Info("resubscribed to all quotes", "numSubscriptions", len(subscriptions)) } case symbol := <-mdClient.subscriptionsChan: if !subscriptions[symbol] { @@ -71,7 +69,7 @@ func NewFixSimMarketDataClient(ctx context.Context, id string, client FixSimMark InstrmtMdReqGrp: []*common.InstrmtMDReqGrp{{Instrument: &common.Instrument{Symbol: symbol}}}}) if err != nil { - log.Error("failed so subscribe to quote", "symbol", symbol, "error", err) + slog.Error("failed so subscribe to quote", "symbol", symbol, "error", err) } } } @@ -86,27 +84,27 @@ func NewFixSimMarketDataClient(ctx context.Context, id string, client FixSimMark for { state := conn.GetState() for state != connectivity.Ready { - log.Info("waiting for fix sim market data connection to be ready....") + slog.Info("waiting for fix sim market data connection to be ready....") conn.WaitForStateChange(ctx, state) state = conn.GetState() - log.Info("market gateway connection state updated", "newState", state) + slog.Info("market gateway connection state updated", "newState", state) } stream, err := client.Connect(metadata.AppendToOutgoingContext(ctx, "subscriber_id", id)) if err != nil { - log.Error("failed to connect to fix market simulator", "error", err) + slog.Error("failed to connect to fix market simulator", "error", err) continue } - log.Info("connected to fix market simulator") + slog.Info("connected to fix market simulator") streamChan <- stream for { incRefresh, err := stream.Recv() if err != nil { - log.Error("error receiving from inbound stream", "error", err) + slog.Error("error receiving from inbound stream", "error", err) mdClient.out <- nil break } else { diff --git a/go/market-data/market-data-gateway-fixsim/service.go b/go/market-data/market-data-gateway-fixsim/service.go index b5a08e77..0301950f 100644 --- a/go/market-data/market-data-gateway-fixsim/service.go +++ b/go/market-data/market-data-gateway-fixsim/service.go @@ -5,7 +5,10 @@ import ( "fmt" "github.com/ettec/otp-common/api/marketdatasource" "github.com/ettec/otp-common/bootstrap" + "log/slog" "os" + "os/signal" + "syscall" "github.com/ettec/open-trading-platform/go/market-data/market-data-gateway-fixsim/internal/connections/fixsim" md "github.com/ettec/otp-common/marketdata" @@ -55,8 +58,7 @@ func newService(ctx context.Context, id string, fixSimAddress string, maxReconne func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime | log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) id := bootstrap.GetEnvVar("GATEWAY_ID") fixSimAddress := bootstrap.GetEnvVar("FIX_SIM_ADDRESS") @@ -65,11 +67,16 @@ func main() { clientQuoteBufferSize := bootstrap.GetOptionalIntEnvVar("CLIENT_QUOTE_BUFFER_SIZE", 1000) port := "50551" - fmt.Println("Starting Market Data Gateway on port:" + port) + slog.Info("Starting Market Data Gateway", "port", port) lis, err := net.Listen("tcp", "0.0.0.0:"+port) http.Handle("/metrics", promhttp.Handler()) - go http.ListenAndServe(":8080", nil) + go func() { + err := http.ListenAndServe(":8080", nil) + if err != nil { + log.Panicf("Error while serving metrics: %v", err) + } + }() if err != nil { log.Panicf("Error while listening : %v", err) @@ -89,6 +96,17 @@ func main() { marketdatasource.RegisterMarketDataSourceServer(s, service) reflection.Register(s) + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { log.Panicf("Error while serving : %v", err) } diff --git a/go/market-data/market-data-service/go.mod b/go/market-data/market-data-service/go.mod index ac858fe9..2b910504 100644 --- a/go/market-data/market-data-service/go.mod +++ b/go/market-data/market-data-service/go.mod @@ -3,7 +3,7 @@ module github.com/ettech/open-trading-platform/go/market-data/market-data-servic go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/mock v1.6.0 github.com/prometheus/client_golang v1.7.1 github.com/stretchr/testify v1.4.0 diff --git a/go/market-data/market-data-service/go.sum b/go/market-data/market-data-service/go.sum index d7fa893f..4d5766b6 100644 --- a/go/market-data/market-data-service/go.sum +++ b/go/market-data/market-data-service/go.sum @@ -38,6 +38,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/market-data/market-data-service/marketdatasource/connection.go b/go/market-data/market-data-service/marketdatasource/connection.go deleted file mode 100644 index 8ada4aa5..00000000 --- a/go/market-data/market-data-service/marketdatasource/connection.go +++ /dev/null @@ -1,59 +0,0 @@ -package marketdatasource - -import ( - "context" - "fmt" - "github.com/ettec/otp-common/marketdata" - "log" - "sync" - "time" -) - -type MdsConnection struct { - partyIdToConnection map[string]marketdata.QuoteStream - quoteDistributor *marketdata.QuoteDistributor - - connMux sync.Mutex - maxSubscriptions int -} - -func NewMdsConnection(ctx context.Context, id string, marketGatewayAddress string, maxReconnectInterval time.Duration, - maxSubscriptions int) (*MdsConnection, error) { - - stream, err := marketdata.NewQuoteStreamFromMdSource(ctx, id, marketGatewayAddress, maxReconnectInterval, 1000) - if err != nil { - return nil, fmt.Errorf("failed to create quote stream: %w", err) - } - - qd := marketdata.NewQuoteDistributor(ctx, stream, 1000) - gateway := &MdsConnection{partyIdToConnection: make(map[string]marketdata.QuoteStream), quoteDistributor: qd, - maxSubscriptions: maxSubscriptions} - - return gateway, nil -} - -func (s *MdsConnection) GetConnection(partyId string) (marketdata.QuoteStream, bool) { - s.connMux.Lock() - defer s.connMux.Unlock() - - con, ok := s.partyIdToConnection[partyId] - return con, ok -} - -func (s *MdsConnection) AddConnection(subscriberId string) marketdata.QuoteStream { - s.connMux.Lock() - defer s.connMux.Unlock() - - if conn, ok := s.partyIdToConnection[subscriberId]; ok { - log.Printf("connection for client %v already exists, closing existing connection.", subscriberId) - conn.Close() - log.Print("connection closed:", subscriberId) - } - - quoteStream := marketdata.NewConflatedQuoteStream(subscriberId, s.quoteDistributor.NewQuoteStream(), - s.maxSubscriptions) - - s.partyIdToConnection[subscriberId] = quoteStream - - return quoteStream -} diff --git a/go/market-data/market-data-service/marketdatasource/marketdataservice.go b/go/market-data/market-data-service/marketdatasource/marketdataservice.go index a3a1bb41..2df05367 100644 --- a/go/market-data/market-data-service/marketdatasource/marketdataservice.go +++ b/go/market-data/market-data-service/marketdatasource/marketdataservice.go @@ -31,7 +31,6 @@ type MarketDataService struct { ctx context.Context id string gatewayStreamSource GatewayStreamSource - micToSources map[string]map[int]*MdsConnection getListing getListingFn subscribers map[string]chan *model.ClobQuote bufferSize int @@ -52,7 +51,6 @@ func NewMarketDataService(ctx context.Context, id string, ctx: ctx, id: id, gatewayStreamSource: gatewayStreamSource, - micToSources: map[string]map[int]*MdsConnection{}, getListing: getListing, subscribers: map[string]chan *model.ClobQuote{}, bufferSize: toClientBufferSize, @@ -164,7 +162,7 @@ func (c *connection) Subscribe(listingId int32) error { mic := listingResult.Listing.Market.Mic var gatewaysForMic []MarketDataGateway - for gateway, _ := range c.gatewayToQuoteStream { + for gateway := range c.gatewayToQuoteStream { if gateway.GetMarketMic() == mic { gatewaysForMic = append(gatewaysForMic, gateway) } diff --git a/go/market-data/market-data-service/service.go b/go/market-data/market-data-service/service.go index 0bd7278d..25ed2b1c 100644 --- a/go/market-data/market-data-service/service.go +++ b/go/market-data/market-data-service/service.go @@ -25,7 +25,9 @@ import ( "net/http" _ "net/http/pprof" "os" + "os/signal" "sync" + "syscall" "time" ) @@ -183,6 +185,16 @@ func main() { reflection.Register(s) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { log.Panicf("Error while serving : %v", err) } diff --git a/go/market-data/quote-aggregator/go.mod b/go/market-data/quote-aggregator/go.mod index 97e44d28..ba3d2b15 100644 --- a/go/market-data/quote-aggregator/go.mod +++ b/go/market-data/quote-aggregator/go.mod @@ -3,7 +3,7 @@ module github.com/ettec/open-trading-platform/go/market-data/quote-aggregator go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/prometheus/client_golang v1.7.1 github.com/stretchr/testify v1.4.0 google.golang.org/grpc v1.25.1 diff --git a/go/market-data/quote-aggregator/go.sum b/go/market-data/quote-aggregator/go.sum index 226102ab..1a73195b 100644 --- a/go/market-data/quote-aggregator/go.sum +++ b/go/market-data/quote-aggregator/go.sum @@ -38,6 +38,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator.go b/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator.go index 7321140c..8f2ad3ef 100644 --- a/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator.go +++ b/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator.go @@ -12,19 +12,19 @@ import ( type getListingsWithSameInstrument = func(ctx context.Context, listingId int32, resultChan chan<- staticdata.ListingsResult) type quoteAggregator struct { - ctx context.Context - getListings getListingsWithSameInstrument - listingGroupsIn chan staticdata.ListingsResult - stream chan *model.ClobQuote - cancel context.CancelFunc + ctx context.Context + cancel context.CancelFunc + getListingsWithSameInstrument getListingsWithSameInstrument + listingGroupsIn chan staticdata.ListingsResult + outChan chan *model.ClobQuote } func (q *quoteAggregator) Chan() <-chan *model.ClobQuote { - return q.stream + return q.outChan } func (q *quoteAggregator) Subscribe(listingId int32) error { - q.getListingsWithSameInstrument(q.ctx, listingId) + q.getListingsWithSameInstrument(q.ctx, listingId, q.listingGroupsIn) return nil } @@ -32,22 +32,23 @@ func (q *quoteAggregator) Close() { q.cancel() } -func New(parentCtx context.Context, getListingsWithSameInstrument getListingsWithSameInstrument, stream marketdata.QuoteStream, +func New(ctx context.Context, getListingsWithSameInstrument getListingsWithSameInstrument, stream marketdata.QuoteStream, inboundListingsBufferSize int) *quoteAggregator { - ctx, cancel := context.WithCancel(parentCtx) + ctx, cancel := context.WithCancel(ctx) qa := "eAggregator{ - ctx: ctx, - getListings: getListingsWithSameInstrument, - listingGroupsIn: make(chan staticdata.ListingsResult, inboundListingsBufferSize), - stream: make(chan *model.ClobQuote), - cancel: cancel, + ctx: ctx, + cancel: cancel, + getListingsWithSameInstrument: getListingsWithSameInstrument, + listingGroupsIn: make(chan staticdata.ListingsResult, inboundListingsBufferSize), + outChan: make(chan *model.ClobQuote), } - listingIdToQuoteChan := map[int32]chan<- *model.ClobQuote{} - go func() { + listingIdToQuoteChan := map[int32]chan<- *model.ClobQuote{} + subscribedListings := map[int32]bool{} + for { select { case <-ctx.Done(): @@ -60,9 +61,21 @@ func New(parentCtx context.Context, getListingsWithSameInstrument getListingsWit continue } + var quoteAggListingId int32 = -1 + for _, listing := range listingsResult.Listings { + if listing.Market.Mic == common.SR_MIC { + quoteAggListingId = listing.Id + if _, ok := subscribedListings[listing.Id]; ok { + slog.Warn("already subscribed to quote stream", "listingId", listing.Id) + continue + } else { + subscribedListings[listing.Id] = true + } + } + } + quoteChan := make(chan *model.ClobQuote) numStreams := 0 - var quoteAggListingId int32 = -1 for _, listing := range listingsResult.Listings { if listing.Market.Mic != common.SR_MIC { listingIdToQuoteChan[listing.Id] = quoteChan @@ -70,8 +83,6 @@ func New(parentCtx context.Context, getListingsWithSameInstrument getListingsWit slog.Error("failed to subscribe to quote stream", "listingId", listing.Id, "error", err) } numStreams++ - } else { - quoteAggListingId = listing.Id } } @@ -88,7 +99,7 @@ func New(parentCtx context.Context, getListingsWithSameInstrument getListingsWit for _, q := range listingIdToLastQuote { quotes = append(quotes, q) } - qa.stream <- combineQuotes(quoteAggListingId, quotes, q) + qa.outChan <- combineQuotes(quoteAggListingId, quotes, q) } } }() @@ -180,7 +191,3 @@ func getCombinedLines(quotes []*model.ClobQuote, getQuoteLines func(quote *model } return result } - -func (q *quoteAggregator) getListingsWithSameInstrument(ctx context.Context, listingId int32) { - q.getListings(ctx, listingId, q.listingGroupsIn) -} diff --git a/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator_test.go b/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator_test.go index 08efa0b9..7ed81191 100644 --- a/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator_test.go +++ b/go/market-data/quote-aggregator/quoteaggregator/quoteaggregator_test.go @@ -340,7 +340,7 @@ func TestCombineQuotes(t *testing.T) { } } -func Test_combineQuoteStatus(t *testing.T) { +func TestCombinedQuoteStatus(t *testing.T) { type args struct { combinedListingId int32 quotes []*model.ClobQuote @@ -456,7 +456,7 @@ func Test_combineQuoteStatus(t *testing.T) { } -func Test_combineTradeData(t *testing.T) { +func TestCombinedTradeData(t *testing.T) { type args struct { combinedListingId int32 quotes []*model.ClobQuote diff --git a/go/market-data/quote-aggregator/service.go b/go/market-data/quote-aggregator/service.go index 5101b5a9..62011b00 100644 --- a/go/market-data/quote-aggregator/service.go +++ b/go/market-data/quote-aggregator/service.go @@ -17,12 +17,14 @@ import ( "net/http" _ "net/http/pprof" "os" + "os/signal" + "syscall" "time" ) func main() { - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) id := bootstrap.GetEnvVar("GATEWAY_ID") toClientBufferSize := bootstrap.GetOptionalIntEnvVar("TO_CLIENT_BUFFER_SIZE", 1000) @@ -31,7 +33,12 @@ func main() { inboundListingsBufferSize := bootstrap.GetOptionalIntEnvVar("INBOUND_LISTINGS_BUFFER_SIZE", 1000) http.Handle("/metrics", promhttp.Handler()) - go http.ListenAndServe(":8080", nil) + go func() { + err := http.ListenAndServe(":8080", nil) + if err != nil { + log.Panicf("failed to start metrics server:%v", err) + } + }() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -67,6 +74,17 @@ func main() { marketdatasource.RegisterMarketDataSourceServer(s, mdSource) reflection.Register(s) + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(lis); err != nil { log.Panicf("Error while serving : %v", err) } diff --git a/go/order-data-service/go.mod b/go/order-data-service/go.mod index fc646517..c004c8c3 100644 --- a/go/order-data-service/go.mod +++ b/go/order-data-service/go.mod @@ -3,17 +3,21 @@ module github.com/ettec/open-trading-platform/go/order-data-service go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/protobuf v1.4.2 github.com/segmentio/kafka-go v0.3.4 + github.com/stretchr/testify v1.4.0 google.golang.org/grpc v1.25.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect google.golang.org/protobuf v1.23.0 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go/order-data-service/go.sum b/go/order-data-service/go.sum index 1d2c4c80..60acf4c8 100644 --- a/go/order-data-service/go.sum +++ b/go/order-data-service/go.sum @@ -4,13 +4,20 @@ github.com/DataDog/zstd v1.4.0 h1:vhoV+DUHnRZdKW1i5UMjAk2G4JY8wN4ayRfYDNdEhwo= github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXeraZhKn5pdLH7JnawKFQjib+ejRqoxOY= -github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -32,11 +39,16 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/segmentio/kafka-go v0.3.4 h1:Mv9AcnCgU14/cU6Vd0wuRdG1FBO0HzXQLnjBduDLy70= github.com/segmentio/kafka-go v0.3.4/go.mod h1:OT5KXBPbaJJTcvokhWR2KFmm0niEx3mnccTwjmLvSi4= github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 h1:Gojs/hac/DoYEM7WEICT45+hNWczIeuL5D21e5/HPAw= github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= @@ -95,5 +107,10 @@ 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 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go/order-data-service/kafkamessagesource.go b/go/order-data-service/kafkamessagesource.go index 88b300d2..a0f566b3 100644 --- a/go/order-data-service/kafkamessagesource.go +++ b/go/order-data-service/kafkamessagesource.go @@ -6,7 +6,6 @@ import ( "time" ) - type KafkaMessageSource struct { reader *kafka.Reader } diff --git a/go/order-data-service/service.go b/go/order-data-service/service.go index d901cba5..f2ace057 100644 --- a/go/order-data-service/service.go +++ b/go/order-data-service/service.go @@ -14,78 +14,98 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/reflection" "log" + "log/slog" "net" "os" + "os/signal" "strings" "sync" + "syscall" "time" ) -var logFlags = log.Ltime|log.Lshortfile -var errLog = log.New(os.Stderr,"", logFlags) - -type service struct { - orderSubscriptions sync.Map - kafkaBrokers []string - ordersAfter time.Time - toClientBufferSize int -} - type orderAndWriteTime struct { order *model.Order writeTime time.Time } +const maxInitialOrderConflationInterval = 500 * time.Millisecond -type Source interface { - ReadMessage(ctx context.Context) (key []byte, value []byte, writeTime time.Time, err error) - Close() error +type service struct { + orderSubscriptions sync.Map + kafkaBrokers []string + ordersAfter time.Time + toClientBufferSize int } -const maxInitialOrderConflationInterval = 500 * time.Millisecond - func (s *service) SubscribeToOrdersWithRootOriginatorId(request *api.SubscribeToOrdersWithRootOriginatorIdArgs, stream api.OrderDataService_SubscribeToOrdersWithRootOriginatorIdServer) error { username, appInstanceId, err := getMetaData(stream.Context()) if err != nil { - return err + return fmt.Errorf("failed to get metadata, error:%w", err) } - after := &model.Timestamp{Seconds: s.ordersAfter.Unix()} + streamLog := slog.With("appInstanceId", appInstanceId, "topic", common.ORDERS_TOPIC) - log.Printf("subscribe to orders from app instance id:%v, user:%v, args:%v", appInstanceId, username, request) + ordersAfter := &model.Timestamp{Seconds: s.ordersAfter.Unix()} _, exists := s.orderSubscriptions.LoadOrStore(appInstanceId, appInstanceId) if !exists { - - defer s.orderSubscriptions.Delete(appInstanceId) - - out := make(chan orderAndWriteTime, 1000) - - - go func() { - source := NewKafkaMessageSource(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, s.kafkaBrokers)) - defer func() { - if err := source.Close(); err != nil { - errLog.Printf("error when closing kafka message source:%v", err) - } - }() - streamOrderTopic(common.ORDERS_TOPIC, source, appInstanceId, out, after, request.RootOriginatorId) - + defer func() { + s.orderSubscriptions.Delete(appInstanceId) + slog.Info("unsubscribed from order updates") }() - err := sendUpdates(out, stream.Send) - if err != nil { - return err + slog.Info("subscribing to order updates", "username", username, "request", request) + orderUpdatesChan := s.getOrderUpdatesChan(stream.Context(), streamLog, request.RootOriginatorId, ordersAfter) + if err = sendOrderUpdates(stream.Context(), orderUpdatesChan, stream.Send); err != nil { + return fmt.Errorf("failed to send order updates:%w", err) } - } else { - return fmt.Errorf("subscription to orders already exists for application instance id %v", appInstanceId) + slog.Warn("subscription already exists, ignoring subscription request") + return fmt.Errorf("subscription to orders already exists for application instance id %s", appInstanceId) } return nil +} +func (s *service) GetOrderHistory(ctx context.Context, args *api.GetOrderHistoryArgs) (*api.OrderHistory, error) { + _, _, err := getMetaData(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get metadata, error:%w", err) + } + + reader := NewKafkaMessageSource(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, s.kafkaBrokers)) + defer func() { + if err := reader.Close(); err != nil { + slog.Error("error when closing kafka message source", "error", err) + } + }() + + var updates []*api.OrderUpdate + for { + key, value, writeTime, err := reader.ReadMessage(ctx) + if err != nil { + return nil, fmt.Errorf("failed to read message:%w", err) + } + + orderId := string(key) + if orderId == args.OrderId { + order := &model.Order{} + err = proto.Unmarshal(value, order) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal order:%w", err) + } + updates = append(updates, &api.OrderUpdate{ + Order: order, + Time: model.NewTimeStamp(writeTime), + }) + if order.Version >= args.ToVersion { + return &api.OrderHistory{Updates: updates}, nil + } + } + } } -func sendUpdates(out chan orderAndWriteTime, send func(*model.Order) error) error { +func sendOrderUpdates(ctx context.Context, in <-chan orderAndWriteTime, send func(*model.Order) error) error { firstOrder := true startTime := time.Time{} @@ -93,6 +113,7 @@ func sendUpdates(out chan orderAndWriteTime, send func(*model.Order) error) erro conflatingInitialOrderState := true ticker := time.NewTicker(maxInitialOrderConflationInterval) + defer ticker.Stop() tickerChan := ticker.C var lastReceivedTime time.Time var lastReceivedOrder *orderAndWriteTime @@ -100,13 +121,14 @@ func sendUpdates(out chan orderAndWriteTime, send func(*model.Order) error) erro for { select { - case oat, ok := <-out: + case <-ctx.Done(): + return nil + case oat, ok := <-in: if !ok { return errors.New("inbound channel closed") } if conflatingInitialOrderState { - if firstOrder { firstOrder = false startTime = time.Now() @@ -129,17 +151,15 @@ func sendUpdates(out chan orderAndWriteTime, send func(*model.Order) error) erro continue } - err := send(oat.order) - if err != nil { - return fmt.Errorf("failed to send order, closing connection, error:%v", err) + if err := send(oat.order); err != nil { + return fmt.Errorf("failed to send order:%w", err) } case <-tickerChan: conflatingInitialOrderState = conflatingOrders(startTime, lastReceivedOrder, lastReceivedTime) if !conflatingInitialOrderState { ticker.Stop() - err := sendConflatedOrders(send, conflatedOrders) - if err != nil { - return err + if err := sendConflatedOrders(send, conflatedOrders); err != nil { + return fmt.Errorf("failed to send conflated orders: %w", err) } } } @@ -168,44 +188,6 @@ func conflatingOrders(startTime time.Time, lastReceivedOrder *orderAndWriteTime, return lastReceivedOrder.writeTime.Before(startTime) && now.Sub(lastReceivedTime) < maxInitialOrderConflationInterval } -func (s *service) GetOrderHistory(ctx context.Context, args *api.GetOrderHistoryArgs) (*api.OrderHistory, error) { - _, _, err := getMetaData(ctx) - if err != nil { - return nil, err - } - - reader := NewKafkaMessageSource(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, s.kafkaBrokers)) - defer func() { - if err := reader.Close(); err != nil { - errLog.Printf("error when closing kafka message source:%v", err) - } - }() - - var updates []*api.OrderUpdate - for { - key, value, writeTime, err := reader.ReadMessage(ctx) - if err != nil { - return nil, err - } - - orderId := string(key) - if orderId == args.OrderId { - order := &model.Order{} - err = proto.Unmarshal(value, order) - if err != nil { - return nil, err - } - updates = append(updates, &api.OrderUpdate{ - Order: order, - Time: model.NewTimeStamp(writeTime), - }) - if order.Version >= args.ToVersion { - return &api.OrderHistory{Updates: updates}, nil - } - } - } -} - func newService(toClientBufferSize int) *service { now := time.Now() @@ -242,66 +224,78 @@ func getMetaData(ctx context.Context) (username string, appInstanceId string, er return username, appInstanceId, nil } -func streamOrderTopic(topic string, reader Source, appInstanceId string, - out chan<- orderAndWriteTime, after *model.Timestamp, rootOriginatorId string) { +func (s *service) getOrderUpdatesChan(ctx context.Context, streamLog *slog.Logger, forRootOriginatorId string, after *model.Timestamp) <-chan orderAndWriteTime { - for { - _, value, writeTime, err := reader.ReadMessage(context.Background()) + out := make(chan orderAndWriteTime, s.toClientBufferSize) - if err != nil { - logTopicReadError(appInstanceId, topic, err) - return - } + go func() { + defer close(out) + reader := NewKafkaMessageSource(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, s.kafkaBrokers)) + defer func() { + if err := reader.Close(); err != nil { + slog.Error("error when closing kafka message source", "error", err) + } + }() - order := &model.Order{} - err = proto.Unmarshal(value, order) - if err != nil { - logTopicReadError(appInstanceId, topic, err) - close(out) - return - } + for { + _, value, writeTime, err := reader.ReadMessage(ctx) + if err != nil { + streamLog.Error("failed to read message", "error", err) + return + } - if order.Created != nil && order.Created.After(after) && order.RootOriginatorId == rootOriginatorId { - out <- orderAndWriteTime{ - order: order, - writeTime: writeTime, + order := &model.Order{} + err = proto.Unmarshal(value, order) + if err != nil { + streamLog.Error("failed to unmarshal order", "error", err) + return + } + + if order.Created != nil && order.Created.After(after) && order.RootOriginatorId == forRootOriginatorId { + out <- orderAndWriteTime{ + order: order, + writeTime: writeTime, + } } } - } -} + }() -func logTopicReadError(appInstanceId string, topic string, err error) { - errLog.Printf("AppInstanceId: %v, Topic: %v, error occurred whilt attempting to stream message: %v", appInstanceId, - topic, err) + return out } func main() { - log.SetOutput(os.Stdout) - log.SetFlags(logFlags) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) toClientBufferSize := bootstrap.GetOptionalIntEnvVar("TO_CLIENT_BUFFER_SIZE", 1000) port := "50551" - fmt.Println("Starting view service on port:" + port) + slog.Info("Starting order data service", "port", port) listener, err := net.Listen("tcp", "0.0.0.0:"+port) + if err != nil { + log.Panicf("Error while listening : %v", err) + } defer func() { if err := listener.Close(); err != nil { - errLog.Printf("error when closing listener:%v", err) + slog.Error("error when closing listener", "error", err) } }() - if err != nil { - log.Panicf("Error while listening : %v", err) - } - s := grpc.NewServer() - defer s.Stop() - api.RegisterOrderDataServiceServer(s, newService(toClientBufferSize)) - reflection.Register(s) + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + if err := s.Serve(listener); err != nil { log.Panicf("Error while serving : %v", err) } diff --git a/go/order-data-service/service_test.go b/go/order-data-service/service_test.go index b19324f8..39caae89 100644 --- a/go/order-data-service/service_test.go +++ b/go/order-data-service/service_test.go @@ -1,12 +1,16 @@ package main import ( + "context" "github.com/ettec/otp-common/model" + "github.com/stretchr/testify/assert" "testing" "time" ) -func Test_sendUpdatesConflationConflatesWhenPauseBeforeReceivingInitialOrder(t *testing.T) { +func TestConflatesWhenPauseBeforeReceivingInitialOrder(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() before := time.Now() @@ -17,7 +21,10 @@ func Test_sendUpdatesConflationConflatesWhenPauseBeforeReceivingInitialOrder(t * return nil } - go sendUpdates(out, send) + go func() { + err := sendOrderUpdates(ctx, out, send) + assert.NoError(t, err) + }() time.Sleep(maxInitialOrderConflationInterval) @@ -40,7 +47,9 @@ func Test_sendUpdatesConflationConflatesWhenPauseBeforeReceivingInitialOrder(t * } -func Test_sendUpdatesConflationWhenNoOrderIsSendThenOneOrderIsSend(t *testing.T) { +func TestConflationWhenNoOrderIsSentThenOneOrderIsSent(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() before := time.Now() @@ -51,7 +60,10 @@ func Test_sendUpdatesConflationWhenNoOrderIsSendThenOneOrderIsSend(t *testing.T) return nil } - go sendUpdates(out, send) + go func() { + err := sendOrderUpdates(ctx, out, send) + assert.NoError(t, err) + }() time.Sleep(maxInitialOrderConflationInterval) @@ -73,7 +85,9 @@ func Test_sendUpdatesConflationWhenNoOrderIsSendThenOneOrderIsSend(t *testing.T) } -func Test_sendUpdatesConflationWhenNoNewOrderIsSent(t *testing.T) { +func TestConflationWhenNoNewOrderIsSent(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() before := time.Now() @@ -84,7 +98,10 @@ func Test_sendUpdatesConflationWhenNoNewOrderIsSent(t *testing.T) { return nil } - go sendUpdates(out, send) + go func() { + err := sendOrderUpdates(ctx, out, send) + assert.NoError(t, err) + }() out <- orderAndWriteTime{order: &model.Order{Id: "1", Version: 0}, writeTime: before} out <- orderAndWriteTime{order: &model.Order{Id: "1", Version: 1}, writeTime: before} @@ -130,7 +147,9 @@ func Test_sendUpdatesConflationWhenNoNewOrderIsSent(t *testing.T) { } -func Test_sendUpdatesConflationWhenNewOrderIsSent(t *testing.T) { +func TestConflationWhenNewOrderIsSent(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() before := time.Now() @@ -141,7 +160,10 @@ func Test_sendUpdatesConflationWhenNewOrderIsSent(t *testing.T) { return nil } - go sendUpdates(out, send) + go func() { + err := sendOrderUpdates(ctx, out, send) + assert.NoError(t, err) + }() out <- orderAndWriteTime{order: &model.Order{Id: "1", Version: 0}, writeTime: before} out <- orderAndWriteTime{order: &model.Order{Id: "1", Version: 1}, writeTime: before} diff --git a/go/order-monitor/go.mod b/go/order-monitor/go.mod index 29f0adf6..68492ca3 100644 --- a/go/order-monitor/go.mod +++ b/go/order-monitor/go.mod @@ -3,7 +3,7 @@ module github.com/ettech/open-trading-platform/go/order-monitor go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/protobuf v1.4.2 github.com/prometheus/client_golang v1.7.1 google.golang.org/grpc v1.25.1 diff --git a/go/order-monitor/go.sum b/go/order-monitor/go.sum index 1fa59131..d6003ee7 100644 --- a/go/order-monitor/go.sum +++ b/go/order-monitor/go.sum @@ -41,6 +41,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/order-monitor/resources/README.md b/go/order-monitor/resources/README.md deleted file mode 100644 index 51158a44..00000000 --- a/go/order-monitor/resources/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# order-monitor - -This service implements the [order monitor api](https://github.com/ettec/open-trading-platform/blob/master/protobuf/services/ordermonitor.proto). The order monitor tracks all platform order updates and publishes summary statistics to prometheus. In addition it provides an api to cancel all orders for a given originator such as a trading desk or trading strategy. - diff --git a/go/order-monitor/service.go b/go/order-monitor/service.go index 87877fa9..ea938637 100644 --- a/go/order-monitor/service.go +++ b/go/order-monitor/service.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" common "github.com/ettec/otp-common" "github.com/ettec/otp-common/api" @@ -13,17 +14,20 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + "log" + "log/slog" + "net" "net/http" + "os/signal" + "syscall" "os" "time" "github.com/ettec/otp-common/bootstrap" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" - "log" - "net" "strings" ) @@ -64,173 +68,230 @@ var pendingCancelOrders = promauto.NewGauge(prometheus.GaugeOpts{ type orderMonitor struct { cancelOrdersForOriginatorChan chan *ordermonitor.CancelAllOrdersForOriginatorIdParams + kafkaBrokers []string + orderUpdatesBufSize int + ordersAfter time.Time } -func (s *orderMonitor) CancelAllOrdersForOriginatorId(_ context.Context, params *ordermonitor.CancelAllOrdersForOriginatorIdParams) (*model.Empty, error) { - s.cancelOrdersForOriginatorChan <- params +func (m *orderMonitor) CancelAllOrdersForOriginatorId(_ context.Context, params *ordermonitor.CancelAllOrdersForOriginatorIdParams) (*model.Empty, error) { + m.cancelOrdersForOriginatorChan <- params return &model.Empty{}, nil } func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime | log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) - logAllOrderUpdates := bootstrap.GetOptionalBoolEnvVar("LOG_ORDER_UPDATES", false) + logAllOrderUpdates := bootstrap.GetOptionalBoolEnvVar("LOG_ORDER_UPDATES", true) maxConnectRetry := time.Duration(bootstrap.GetOptionalIntEnvVar("MAX_CONNECT_RETRY_SECONDS", 60)) * time.Second kafkaBrokersString := bootstrap.GetEnvVar("KAFKA_BROKERS") cancelTimeoutDuration := time.Duration(bootstrap.GetOptionalIntEnvVar("CANCEL_TIMEOUT_SECS", 5)) * time.Second orderUpdatesBufSize := bootstrap.GetOptionalIntEnvVar("INBOUND_ORDER_UPDATES_BUFFER_SIZE", 1000) - http.Handle("/metrics", promhttp.Handler()) - go http.ListenAndServe(":8080", nil) - - kafkaBrokers := strings.Split(kafkaBrokersString, ",") - + cancelAllCmdChan := make(chan *ordermonitor.CancelAllOrdersForOriginatorIdParams) now := time.Now() ordersAfter := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) - store, err := orderstore.NewKafkaStore(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, kafkaBrokers), - orderstore.DefaultWriterConfig(common.ORDERS_TOPIC, kafkaBrokers), "") + om := &orderMonitor{ + cancelOrdersForOriginatorChan: cancelAllCmdChan, + kafkaBrokers: strings.Split(kafkaBrokersString, ","), + orderUpdatesBufSize: orderUpdatesBufSize, + ordersAfter: ordersAfter, + } - if err != nil { - panic(fmt.Errorf("failed to create order store: %v", err)) + http.Handle("/metrics", promhttp.Handler()) + go func() { + err := http.ListenAndServe(":8080", nil) + if err != nil { + slog.Error("failed to start metrics server", "error", err) + } + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + if err := om.startOrderStatusMonitoring(ctx, logAllOrderUpdates); err != nil { + log.Panicf("failed to start order status monitoring: %v", err) } - clientSet := k8s.GetK8sClientSet(false) + if err := om.startCancelAllHandler(ctx, maxConnectRetry, cancelTimeoutDuration); err != nil { + log.Panicf("failed to start cancel all handler: %v", err) + } + + s := grpc.NewServer() + ordermonitor.RegisterOrderMonitorServer(s, om) + reflection.Register(s) + + port := "50551" + lis, err := net.Listen("tcp", "0.0.0.0:"+port) - orderRouter, err := api.GetOrderRouter(clientSet, maxConnectRetry) if err != nil { - panic(err) + log.Panicf("Error while listening : %v", err) } - cancelChan := make(chan *ordermonitor.CancelAllOrdersForOriginatorIdParams) - om := &orderMonitor{ - cancelOrdersForOriginatorChan: cancelChan, + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + + slog.Info("Starting Order Monitor Service", "port", port) + + if err := s.Serve(lis); err != nil { + log.Panicf("error while serving : %v", err) } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - orders, updates, err := store.SubscribeToAllOrders(ctx, ordersAfter, orderUpdatesBufSize) +} + +func (m *orderMonitor) startCancelAllHandler(ctx context.Context, maxConnectRetry time.Duration, cancelTimeoutDuration time.Duration) error { + clientSet := k8s.GetK8sClientSet(false) + + orderRouter, err := api.GetOrderRouter(clientSet, maxConnectRetry) if err != nil { - log.Panic("failed to subscribe to orders: ", err) + return fmt.Errorf("failed to get order router: %w", err) } + go func() { - listingsToFetch := map[int32]bool{} - for _, order := range orders { - listingsToFetch[order.ListingId] = true - - totalOrders.Inc() - gauge, err := getStatusGauge(order) + store, err := orderstore.NewKafkaStore(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, m.kafkaBrokers), + orderstore.DefaultWriterConfig(common.ORDERS_TOPIC, m.kafkaBrokers), "") if err != nil { - log.Print("failed to update status gauge:", err) + slog.Error("failed to create order store", "error", err) + return } - gauge.Inc() - if logAllOrderUpdates { - log.Printf("Order Update:%v\n\n", order) + orders, updates, err := store.SubscribeToAllOrders(ctx, m.ordersAfter, m.orderUpdatesBufSize) + if err != nil { + slog.Error("failed to create order store", "error", err) + return } - } - go func() { + nonTerminatedOrders := make(map[string]*model.Order) + for _, order := range orders { + if !order.IsTerminalState() { + nonTerminatedOrders[order.GetId()] = order + } + } for { select { case <-ctx.Done(): return - case cr := <-cancelChan: - log.Print("cancelling all orders for root originator id:", cr.OriginatorId) + case update := <-updates: + slog.Info("got order update", "order", update) + if update.IsTerminalState() { + delete(nonTerminatedOrders, update.GetId()) + } else { + nonTerminatedOrders[update.GetId()] = update + } + case cr := <-m.cancelOrdersForOriginatorChan: + slog.Info("cancelling all orders for root originator", "originatorId", cr.OriginatorId) var cancelParams []*executionvenue.CancelOrderParams - for _, order := range orders { - if order.GetOriginatorId() == cr.OriginatorId && - order.Status == model.OrderStatus_LIVE { - + for _, order := range nonTerminatedOrders { + if order.GetOriginatorId() == cr.OriginatorId { cancelParams = append(cancelParams, &executionvenue.CancelOrderParams{ OrderId: order.GetId(), ListingId: order.GetListingId(), OwnerId: order.GetOwnerId(), }) - } } numOrdersToCancel := len(cancelParams) - log.Printf("%v cancellable orders found for originator id:%v", numOrdersToCancel, cr.OriginatorId) + slog.Info("cancellable orders found for originator id", "numCancellableOrders", numOrdersToCancel, + "originatorId", cr.OriginatorId) go func() { - for idx, params := range cancelParams { - deadline, cancel := context.WithDeadline(context.Background(), time.Now().Add(cancelTimeoutDuration)) - _, err := orderRouter.CancelOrder(deadline, params) + for _, params := range cancelParams { + deadline, cancel := context.WithDeadline(ctx, time.Now().Add(cancelTimeoutDuration)) + _, err = orderRouter.CancelOrder(deadline, params) cancel() if err != nil { - if err != context.DeadlineExceeded { - log.Printf("Failed to cancel order %v of %v, id: %v, error:%v", idx+1, numOrdersToCancel, params.GetOrderId(), err) + if !errors.Is(err, context.DeadlineExceeded) { + slog.Error("Failed to cancel order", "orderId", params.GetOrderId(), "error", err) } else { - log.Printf("Deadline exceed, failed to cancel order %v of %v, id: %v", idx+1, numOrdersToCancel, params.GetOrderId()) + slog.Error("Deadline exceed, failed to cancel order", "orderId", params.GetOrderId()) } } else { - log.Printf("Cancelled order %v of %v, id: %v", idx+1, numOrdersToCancel, params.GetOrderId()) + slog.Info("Cancelled order", "orderId", params.GetOrderId()) } } }() + } + } + }() + return err +} - case u := <-updates: +func (m *orderMonitor) startOrderStatusMonitoring(ctx context.Context, logAllOrderUpdates bool) error { + + store, err := orderstore.NewKafkaStore(orderstore.DefaultReaderConfig(common.ORDERS_TOPIC, m.kafkaBrokers), + orderstore.DefaultWriterConfig(common.ORDERS_TOPIC, m.kafkaBrokers), "") + if err != nil { + log.Panicf("failed to create order store: %v", err) + } + + orders, updates, err := store.SubscribeToAllOrders(ctx, m.ordersAfter, m.orderUpdatesBufSize) + if err != nil { + return fmt.Errorf("failed to subscribe to all orders:%w", err) + } + + // Setup initial order stats + for _, order := range orders { + totalOrders.Inc() + gauge, err := getOrderStatusGauge(order) + if err != nil { + slog.Error("failed to get status gauge for order", "order", order, "error", err) + } + gauge.Inc() + + if logAllOrderUpdates { + slog.Info("Initial state", "order", order) + } + } + // Listening for order status counts + go func() { + for { + select { + case <-ctx.Done(): + return + case update := <-updates: if logAllOrderUpdates { - log.Printf("Order Update:%v\n\n", u) + slog.Info("Updated state", "order", update) } - if order, exists := orders[u.Id]; exists { - orders[u.Id] = u - g, err := getStatusGauge(order) + if order, exists := orders[update.Id]; exists { + orders[update.Id] = update + g, err := getOrderStatusGauge(order) if err != nil { - log.Print("failed to update status gauge:", err) + slog.Error("failed to update status gauge", "error", err) continue } g.Dec() - - g, err = getStatusGauge(u) - if err != nil { - log.Print("failed to update status gauge:", err) - continue - } - g.Inc() - } else { totalOrders.Inc() - orders[u.Id] = u - g, err := getStatusGauge(u) - if err != nil { - log.Print("failed to update status gauge:", err) - continue - } - g.Inc() + orders[update.Id] = update } + g, err := getOrderStatusGauge(update) + if err != nil { + slog.Error("failed to update status gauge", "error", err) + continue + } + g.Inc() } } }() - s := grpc.NewServer() - ordermonitor.RegisterOrderMonitorServer(s, om) - reflection.Register(s) - - port := "50551" - lis, err := net.Listen("tcp", "0.0.0.0:"+port) - - if err != nil { - log.Fatalf("Error while listening : %v", err) - } - - if err := s.Serve(lis); err != nil { - log.Fatalf("error while serving : %v", err) - } - - fmt.Println("Started Order Monitor Service on port:" + port) + return nil } -func getStatusGauge(order *model.Order) (prometheus.Gauge, error) { +func getOrderStatusGauge(order *model.Order) (prometheus.Gauge, error) { if order.TargetStatus == model.OrderStatus_CANCELLED { return pendingCancelOrders, nil diff --git a/go/scripts/all.sh b/go/scripts/all.sh new file mode 100755 index 00000000..485811df --- /dev/null +++ b/go/scripts/all.sh @@ -0,0 +1,27 @@ +# Script to make it easy to run go commands across all go projects. E.g. "./all.sh go test ./..." will run the tests of all go projects. +# The alternative approach here would be to combine all services into one go project, however on balance it was considered that having each +# distinct service in its own project aids comprehension. + +DIRECTORY=$(cd `dirname $0` && pwd)/.. +cd $DIRECTORY +echo working dir `pwd` + +cmd="$*" + +find . -type f -name '*go.mod*' | sed -r 's|/[^/]+$||' |sort |uniq | while read line; do +cd $line + +if ls *.go 1> /dev/null 2>&1; +then + + if eval "$cmd"; then + echo executed "$cmd" in "$line" + else + echo failed to execute "$cmd" + exit 1 + fi + +fi +cd $DIRECTORY + +done diff --git a/go/scripts/buildAll.sh b/go/scripts/buildAll.sh deleted file mode 100755 index 587643fe..00000000 --- a/go/scripts/buildAll.sh +++ /dev/null @@ -1,21 +0,0 @@ -DIRECTORY=$(cd `dirname $0` && pwd)/.. -cd $DIRECTORY -echo working dir `pwd` - -find . -type f -name '*go.mod*' | sed -r 's|/[^/]+$||' |sort |uniq | while read line; do -cd $line - -if ls *.go 1> /dev/null 2>&1; -then - - if go build -o /tmp/guff; then - echo built $line - else - echo failed to build $line - exit 1 - fi - -fi -cd $DIRECTORY - -done diff --git a/go/scripts/modTidyAll.sh b/go/scripts/modTidyAll.sh deleted file mode 100755 index ed0ea1e2..00000000 --- a/go/scripts/modTidyAll.sh +++ /dev/null @@ -1,21 +0,0 @@ -DIRECTORY=$(cd `dirname $0` && pwd)/.. -cd $DIRECTORY -echo working dir `pwd` - -find . -type f -name '*go.mod*' | sed -r 's|/[^/]+$||' |sort |uniq | while read line; do -cd $line - -if ls *.go 1> /dev/null 2>&1; -then - - if go mod tidy; then - echo mod tidied $line - else - echo failed to mod tidy $line - exit 1 - fi - -fi -cd $DIRECTORY - -done diff --git a/go/scripts/testAll.sh b/go/scripts/testAll.sh deleted file mode 100755 index 82f80b64..00000000 --- a/go/scripts/testAll.sh +++ /dev/null @@ -1,18 +0,0 @@ -DIRECTORY=$(cd `dirname $0` && pwd)/.. -cd $DIRECTORY -echo working dir `pwd` - -find . -type f -name '*go.mod*' | sed -r 's|/[^/]+$||' |sort |uniq | while read line; do -cd $line - - - if go test ./...; then - echo tested $line - else - echo $line tests failed - exit 1 - fi - -cd $DIRECTORY - -done diff --git a/go/scripts/updateAllOtpCommon.sh b/go/scripts/updateAllOtpCommon.sh deleted file mode 100755 index 3b2bbefe..00000000 --- a/go/scripts/updateAllOtpCommon.sh +++ /dev/null @@ -1,18 +0,0 @@ -#Specify the tag as the first argument e.g. @v1.0.1 or leave it blank to update to head of the master branch - -DIRECTORY=$(cd `dirname $0` && pwd)/.. -cd $DIRECTORY -echo working dir `pwd` - -VERSION=$1 - -find . -type f -name '*go.mod*' | sed -r 's|/[^/]+$||' |sort |uniq | while read line; do -cd $line - echo updating otp common in $line - go get github.com/ettec/otp-common$VERSION - cd $DIRECTORY -done - -echo All updated - - diff --git a/go/scripts/vetAll.sh b/go/scripts/vetAll.sh deleted file mode 100755 index b0c4b3c5..00000000 --- a/go/scripts/vetAll.sh +++ /dev/null @@ -1,22 +0,0 @@ -DIRECTORY=$(cd `dirname $0` && pwd)/.. -cd $DIRECTORY -echo working dir `pwd` - -find . -type f -name '*go.mod*' | sed -r 's|/[^/]+$||' |sort |uniq | while read line; do -cd $line - -if ls *.go 1> /dev/null 2>&1; -then - - if go vet; then - echo vetted $line - else - echo failed to vet $line - exit 1 - fi - -fi -cd $DIRECTORY - -done - diff --git a/go/static-data-loaders/go.mod b/go/static-data-loaders/go.mod index bb00cbc7..e59ae685 100644 --- a/go/static-data-loaders/go.mod +++ b/go/static-data-loaders/go.mod @@ -4,4 +4,4 @@ go 1.21 require github.com/lib/pq v1.2.0 -require github.com/ettec/otp-common v1.4.0 // indirect +require github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 // indirect diff --git a/go/static-data-loaders/go.sum b/go/static-data-loaders/go.sum index 35d0472a..cc3bd09b 100644 --- a/go/static-data-loaders/go.sum +++ b/go/static-data-loaders/go.sum @@ -125,6 +125,8 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/go/static-data-loaders/iex-instrument-loader/cmd.go b/go/static-data-loaders/iex-instrument-loader/cmd.go index c3e0a65b..949f2958 100644 --- a/go/static-data-loaders/iex-instrument-loader/cmd.go +++ b/go/static-data-loaders/iex-instrument-loader/cmd.go @@ -61,7 +61,10 @@ func main() { log.Panic("Error: Could not establish a connection with the database") } - db.Exec(`set search_path="referencedata"`) + _, err = db.Exec(`set search_path="referencedata"`) + if err != nil { + log.Panicf("Failed to set search path:%v", err) + } for _, iexInst := range iexInstruments { @@ -79,7 +82,9 @@ func main() { sql := "INSERT INTO instruments (name, display_symbol, enabled, raw_sources) VALUES ($1, $2, $3, $4) RETURNING id" lastInsertId := 0 - err = db.QueryRow(sql, iexInst.Name, iexInst.Symbol, iexInst.IsEnabled, jsonStr).Scan(&lastInsertId) + if err = db.QueryRow(sql, iexInst.Name, iexInst.Symbol, iexInst.IsEnabled, jsonStr).Scan(&lastInsertId); err != nil { + log.Printf("Failed to insert instrument:%v", err) + } } diff --git a/go/static-data-loaders/iso-market-loader/cmd.go b/go/static-data-loaders/iso-market-loader/cmd.go index 61535a4d..03917297 100644 --- a/go/static-data-loaders/iso-market-loader/cmd.go +++ b/go/static-data-loaders/iso-market-loader/cmd.go @@ -22,7 +22,7 @@ func main() { data, err := ioutil.ReadFile("./resources/IISO10383_MIC.csv") if err != nil { - log.Fatalf("failed to read markets file:%v", err) + log.Panicf("failed to read markets file:%v", err) } csvString := string(data) @@ -62,7 +62,10 @@ func main() { log.Panic("Error: Could not establish a connection with the database") } - db.Exec(`set search_path="referencedata"`) + _, err = db.Exec(`set search_path="referencedata"`) + if err != nil { + log.Panicf("Error: Failed to set search path:%v", err) + } for _, market := range markets { @@ -70,7 +73,7 @@ func main() { _, err := db.Exec(sqlstmt) if err != nil { - log.Printf("Error: Failed to insert row error:%v row sql:%v", err, sqlstmt) + log.Printf("Error: Failed to insert market error:%v row sql:%v", err, sqlstmt) } } diff --git a/go/static-data-loaders/listings-loader/cmd.go b/go/static-data-loaders/listings-loader/cmd.go index 5deb425b..8d4d29cd 100644 --- a/go/static-data-loaders/listings-loader/cmd.go +++ b/go/static-data-loaders/listings-loader/cmd.go @@ -25,7 +25,10 @@ func main() { log.Panic("Error: Could not establish a connection with the database") } - db.Exec(`set search_path="referencedata"`) + _, err = db.Exec(`set search_path="referencedata"`) + if err != nil { + log.Panicf("failed to set search path:%v", err) + } marketIds := []int32{} diff --git a/go/static-data-loaders/resources/.~lock.IISO10383_MIC.csv# b/go/static-data-loaders/resources/.~lock.IISO10383_MIC.csv# deleted file mode 100644 index fe6cfa0f..00000000 --- a/go/static-data-loaders/resources/.~lock.IISO10383_MIC.csv# +++ /dev/null @@ -1 +0,0 @@ -,mpendrey,mpendrey-Veriton-X4620G,20.03.2020 14:30,file:///home/mpendrey/snap/libreoffice/175/.config/libreoffice/4; \ No newline at end of file diff --git a/go/static-data-service/go.mod b/go/static-data-service/go.mod index e2b01182..4a2ee3f9 100644 --- a/go/static-data-service/go.mod +++ b/go/static-data-service/go.mod @@ -3,7 +3,7 @@ module github.com/ettec/open-trading-platform/go/static-data-service go 1.21 require ( - github.com/ettec/otp-common v1.4.0 + github.com/ettec/otp-common v1.4.2 github.com/golang/protobuf v1.4.2 github.com/lib/pq v1.2.0 google.golang.org/grpc v1.25.1 diff --git a/go/static-data-service/go.sum b/go/static-data-service/go.sum index 9479ab18..a1c2fd06 100644 --- a/go/static-data-service/go.sum +++ b/go/static-data-service/go.sum @@ -8,6 +8,12 @@ github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e h1:oSJ6u7EUItXe github.com/ettec/otp-common v1.3.1-0.20231121170433-6f1dd05c001e/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/ettec/otp-common v1.4.0 h1:cjwwHtqsP9Kwr08DSN6yqhcR3HnEqhtCy795tkSRRZQ= github.com/ettec/otp-common v1.4.0/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74 h1:R9dxpl1H76ImUpTiUPAqEzS59zug785h9zw+fqgNgmk= +github.com/ettec/otp-common v1.4.1-0.20231128151006-7b41d465cd74/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.1 h1:Im44hSM83Jy56BnurMw/ZJwtwTlnzUNlCdTFzFTn65o= +github.com/ettec/otp-common v1.4.1/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= +github.com/ettec/otp-common v1.4.2 h1:qmgPXctGWyHAwsyz0WnSgRFvhll8OGF4sfZkSZi+1tA= +github.com/ettec/otp-common v1.4.2/go.mod h1:68PDrJp1ccRawgvPg0vYmSDPUi03ZCtBp4YJOJzeUjc= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/go/static-data-service/service.go b/go/static-data-service/service.go index 70f9a29d..2c22d3d6 100644 --- a/go/static-data-service/service.go +++ b/go/static-data-service/service.go @@ -13,10 +13,13 @@ import ( "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "log" + "log/slog" "net" "os" + "os/signal" "strconv" "strings" + "syscall" ) type service struct { @@ -33,9 +36,8 @@ func NewService(driverName, dbConnString string) (*service, error) { return nil, fmt.Errorf("failed to open database connection: %w", err) } - err = db.Ping() - if err != nil { - log.Fatal("could not establish a connection with the database: ", err) + if err = db.Ping(); err != nil { + return nil, fmt.Errorf("could not establish a connection with the database: %w", err) } return s, nil @@ -50,19 +52,19 @@ func (s *service) GetListingsWithSameInstrument(c context.Context, id *api.Listi listing, err := s.GetListing(c, id) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to fetch listing:%w", err) } lq := listingsSelect + " where instruments.id = " + strconv.Itoa(int(listing.Instrument.Id)) r, err := s.db.Query(lq) if err != nil { - return nil, fmt.Errorf("failed to fetch listings from database:%w", err) + return nil, fmt.Errorf("failed to fetch listings:%w", err) } result, err := hydrateListings(r) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate listings:%w", err) } return result, nil @@ -75,12 +77,12 @@ func (s *service) GetListingMatching(_ context.Context, m *api.ExactMatchParamet r, err := s.db.Query(lq) if err != nil { - return nil, fmt.Errorf("failed to fetch listings from database:%w", err) + return nil, fmt.Errorf("failed to fetch listings:%w", err) } result, err := hydrateListings(r) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate listings:%w", err) } if len(result.Listings) == 0 { @@ -105,7 +107,7 @@ func (s *service) GetListingsMatching(_ context.Context, m *api.MatchParameters) result, err := hydrateListings(r) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate listings:%w", err) } return result, nil @@ -121,7 +123,7 @@ func (s *service) GetListing(_ context.Context, id *api.ListingId) (*model.Listi result, err := hydrateListings(r) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate listings:%w", err) } if len(result.Listings) != 1 { @@ -143,7 +145,7 @@ func (s *service) GetListings(_ context.Context, ids *api.ListingIds) (*api.List result, err := hydrateListings(r) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate listings:%w", err) } return result, nil @@ -151,9 +153,8 @@ func (s *service) GetListings(_ context.Context, ids *api.ListingIds) (*api.List func (s *service) Close() { if s.db != nil { - err := s.db.Close() - if err != nil { - log.Printf("erron closing database connection %v", err) + if err := s.db.Close(); err != nil { + slog.Error("error when closing database connection", "error", err) } } } @@ -172,7 +173,7 @@ func hydrateListings(r *sql.Rows) (*api.Listings, error) { err := r.Scan(&l.Id, &l.MarketSymbol, &l.Market.Id, &l.Market.Name, &l.Market.Mic, &l.Market.CountryCode, &l.Instrument.Id, &l.Instrument.Name, &l.Instrument.DisplaySymbol, &l.Instrument.Enabled) if err != nil { - return nil, fmt.Errorf("failed to marshal database row into listing %w", err) + return nil, fmt.Errorf("failed to scan database row into listing %w", err) } l.SizeIncrement = &model.Decimal64{ @@ -212,8 +213,7 @@ func hydrateListings(r *sql.Rows) (*api.Listings, error) { func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ltime|log.Lshortfile) + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) dbString := bootstrap.GetEnvVar("DB_CONN_STRING") dbDriverName := bootstrap.GetEnvVar("DB_DRIVER_NAME") @@ -225,22 +225,31 @@ func main() { log.Panicf("Error while listening : %v", err) } - service, err := NewService(dbDriverName, dbString) + staticDataService, err := NewService(dbDriverName, dbString) if err != nil { log.Panicf("failed to create service: %v", err) } - defer service.Close() + defer staticDataService.Close() s := grpc.NewServer() - api.RegisterStaticDataServiceServer(s, service) + api.RegisterStaticDataServiceServer(s, staticDataService) reflection.Register(s) - fmt.Println("Starting static data service on port:" + port) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + <-sigCh + s.GracefulStop() + }() + + slog.Info("Starting static data service", "port", port) if err := s.Serve(lis); err != nil { log.Panicf("Error while serving : %v", err) } } -