diff --git a/cmd/character/app/server.go b/cmd/character/app/server.go index e312f84..5273025 100644 --- a/cmd/character/app/server.go +++ b/cmd/character/app/server.go @@ -38,7 +38,7 @@ func NewServerContext(ctx context.Context, conf *config.GlobalConfig, tracer tra server.KeycloakClient.RegisterMiddlewares(gocloak.OpenTelemetryMiddleware) - postgres, err := repository.ConnectDB(server.GlobalConfig.Character.Postgres) + postgres, err := repository.ConnectDB(conf.Character.Postgres, conf.Redis) if err != nil { return nil, fmt.Errorf("connecting to postgres: %w", err) } diff --git a/cmd/chat/app/server.go b/cmd/chat/app/server.go index 3c559a2..e6073af 100644 --- a/cmd/chat/app/server.go +++ b/cmd/chat/app/server.go @@ -39,7 +39,7 @@ func NewServerContext(ctx context.Context, conf *config.GlobalConfig, tracer tra server.KeycloakClient.RegisterMiddlewares(gocloak.OpenTelemetryMiddleware) - db, err := repository.ConnectDB(conf.Chat.Postgres) + db, err := repository.ConnectDB(conf.Chat.Postgres, conf.Redis) if err != nil { return nil, fmt.Errorf("connecting to postgres database: %w", err) } diff --git a/cmd/gamebackend/app/server.go b/cmd/gamebackend/app/server.go index af3fc8f..997c700 100644 --- a/cmd/gamebackend/app/server.go +++ b/cmd/gamebackend/app/server.go @@ -70,7 +70,7 @@ func NewServerContext(ctx context.Context, conf *config.GlobalConfig, tracer tra } } - db, err := repository.ConnectDB(conf.GameBackend.Postgres) + db, err := repository.ConnectDB(conf.GameBackend.Postgres, conf.Redis) if err != nil { return nil, fmt.Errorf("connecting to postgres database: %w", err) } diff --git a/docker-compose.yaml b/docker-compose.yaml index 2560d04..45a73ca 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -46,6 +46,19 @@ services: networks: - backend + redis-cluster: + image: grokzen/redis-cluster:7.0.10 + environment: + IP: 0.0.0.0 + INITIAL_PORT: 7000 + MASTERS: 3 + SLAVES_PER_MASTER: 1 + restart: always + ports: + - '7000-7050:7000-7050' + - '5000-5010:5000-5010' + networks: + - backend prometheus: image: prom/prometheus:latest diff --git a/go.mod b/go.mod index f6b661c..2af9ebd 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,8 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect github.com/WilSimpson/gocloak/v13 v13.11.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-gorm/caches/v4 v4.0.0 // indirect @@ -56,6 +58,9 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect + github.com/redis/go-redis/v9 v9.5.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect diff --git a/go.sum b/go.sum index 55260fe..e584c20 100644 --- a/go.sum +++ b/go.sum @@ -92,15 +92,20 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bxcodec/faker/v4 v4.0.0-beta.3 h1:gqYNBvN72QtzKkYohNDKQlm+pg+uwBDVMN28nWHS18k= github.com/bxcodec/faker/v4 v4.0.0-beta.3/go.mod h1:m6+Ch1Lj3fqW/unZmvkXIdxWS5+XQWPWxcbbQW2X+Ho= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -130,6 +135,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= @@ -511,6 +518,13 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= diff --git a/pkg/config/db.go b/pkg/config/db.go index 10e8170..a20c734 100644 --- a/pkg/config/db.go +++ b/pkg/config/db.go @@ -4,8 +4,7 @@ import "fmt" // DBConfig Information on how to connect to the database type DBConfig struct { - Host string `yaml:"host"` - Port string `yaml:"port"` + ServerAddress Name string `yaml:"name"` Username string `yaml:"username"` Password string `yaml:"password"` @@ -48,3 +47,12 @@ func (c DBConfig) MongoDSN() string { c.Port, ) } + +func (p DBPoolConfig) Addresses() []string { + addrs := make([]string, len(p.Slaves)+1) + for idx, dbConf := range p.Slaves { + addrs[idx+1] = dbConf.Address() + } + + return addrs +} diff --git a/pkg/config/db_test.go b/pkg/config/db_test.go index f38e50e..d829e07 100644 --- a/pkg/config/db_test.go +++ b/pkg/config/db_test.go @@ -42,7 +42,7 @@ var _ = Describe("Db config", func() { AfterEach(func() { Expect(dsn).To(ContainSubstring(config.Username)) Expect(dsn).To(ContainSubstring(config.Password)) - Expect(dsn).To(ContainSubstring(config.Host)) - Expect(dsn).To(ContainSubstring(config.Port)) + Expect(dsn).To(ContainSubstring(config.ServerAddress.Host)) + Expect(dsn).To(ContainSubstring(config.ServerAddress.Port)) }) }) diff --git a/pkg/config/global.go b/pkg/config/global.go index 11f08da..8e7ed84 100644 --- a/pkg/config/global.go +++ b/pkg/config/global.go @@ -32,6 +32,7 @@ type GlobalConfig struct { Agones AgonesConfig `json:"agones"` Keycloak KeycloakGlobal `yaml:"keycloak"` Version string + Redis DBPoolConfig } type SROServer struct { @@ -80,7 +81,7 @@ type AgonesConfig struct { } type ServerAddress struct { - Port uint `yaml:"port"` + Port string `yaml:"port"` Host string `yaml:"host"` } @@ -89,11 +90,11 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { Character: CharacterServer{ SROServer: SROServer{ Local: ServerAddress{ - Port: 8081, + Port: "8081", Host: "", }, Remote: ServerAddress{ - Port: 8081, + Port: "8081", Host: "", }, Mode: LocalMode, @@ -106,8 +107,10 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { }, Postgres: DBPoolConfig{ Master: DBConfig{ - Host: "localhost", - Port: "5432", + ServerAddress: ServerAddress{ + Host: "localhost", + Port: "5432", + }, Name: "characters", Username: "postgres", Password: "password", @@ -115,8 +118,10 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { }, Mongo: DBPoolConfig{ Master: DBConfig{ - Host: "localhost", - Port: "27017", + ServerAddress: ServerAddress{ + Host: "localhost", + Port: "27017", + }, Username: "mongo", Password: "password", Name: "sro", @@ -126,11 +131,11 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { GameBackend: GamebackendServer{ SROServer: SROServer{ Local: ServerAddress{ - Port: 8082, + Port: "8082", Host: "", }, Remote: ServerAddress{ - Port: 8082, + Port: "8082", Host: "", }, Mode: LocalMode, @@ -143,8 +148,10 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { }, Postgres: DBPoolConfig{ Master: DBConfig{ - Host: "localhost", - Port: "5432", + ServerAddress: ServerAddress{ + Host: "localhost", + Port: "5432", + }, Name: "gamebackend", Username: "postgres", Password: "password", @@ -155,11 +162,11 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { Chat: ChatServer{ SROServer: SROServer{ Local: ServerAddress{ - Port: 8180, + Port: "8180", Host: "", }, Remote: ServerAddress{ - Port: 8180, + Port: "8180", Host: "", }, Mode: LocalMode, @@ -171,13 +178,15 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { }, }, Kafka: ServerAddress{ - Port: 29092, + Port: "29092", Host: "localhost", }, Postgres: DBPoolConfig{ Master: DBConfig{ - Host: "localhost", - Port: "5432", + ServerAddress: ServerAddress{ + Host: "localhost", + Port: "5432", + }, Name: "chat", Username: "postgres", Password: "password", @@ -194,7 +203,7 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { CaCertFile: "/etc/sro/auth/agones/ca/ca", Namespace: "default", Allocator: ServerAddress{ - Port: 443, + Port: "443", Host: "localhost", }, }, @@ -202,6 +211,46 @@ func NewGlobalConfig(ctx context.Context) (*GlobalConfig, error) { BaseURL: "http://localhost:80801/", Realm: "default", }, + Redis: DBPoolConfig{ + Master: DBConfig{ + ServerAddress: ServerAddress{ + Port: "7000", + Host: "localhost", + }, + }, + Slaves: []DBConfig{ + { + ServerAddress: ServerAddress{ + Port: "7001", + Host: "localhost", + }, + }, + { + ServerAddress: ServerAddress{ + Port: "7002", + Host: "localhost", + }, + }, + { + ServerAddress: ServerAddress{ + Port: "7003", + Host: "localhost", + }, + }, + { + ServerAddress: ServerAddress{ + Port: "7004", + Host: "localhost", + }, + }, + { + ServerAddress: ServerAddress{ + Port: "7005", + Host: "localhost", + }, + }, + }, + }, Version: Version, } @@ -274,5 +323,5 @@ func bindRecursive(key string, val reflect.Value) { } func (s *ServerAddress) Address() string { - return fmt.Sprintf("%s:%d", s.Host, s.Port) + return fmt.Sprintf("%s:%s", s.Host, s.Port) } diff --git a/pkg/repository/cacher.go b/pkg/repository/cacher/memory.go similarity index 97% rename from pkg/repository/cacher.go rename to pkg/repository/cacher/memory.go index e7e5a6a..215c273 100644 --- a/pkg/repository/cacher.go +++ b/pkg/repository/cacher/memory.go @@ -1,4 +1,4 @@ -package repository +package cacher import ( "context" diff --git a/pkg/repository/cacher/redis.go b/pkg/repository/cacher/redis.go new file mode 100644 index 0000000..28e52ab --- /dev/null +++ b/pkg/repository/cacher/redis.go @@ -0,0 +1,95 @@ +package cacher + +import ( + "context" + "fmt" + "time" + + "github.com/ShatteredRealms/go-backend/pkg/config" + "github.com/go-gorm/caches/v4" + "github.com/redis/go-redis/extra/redisotel/v9" + "github.com/redis/go-redis/v9" +) + +type redisCacher struct { + rdb *redis.ClusterClient +} + +func (c *redisCacher) Get(ctx context.Context, key string, q *caches.Query[any]) (*caches.Query[any], error) { + res, err := c.rdb.Get(ctx, key).Result() + if err == redis.Nil { + return nil, nil + } + + if err != nil { + return nil, err + } + + if err := q.Unmarshal([]byte(res)); err != nil { + return nil, err + } + + return q, nil +} + +func (c *redisCacher) Store(ctx context.Context, key string, val *caches.Query[any]) error { + res, err := val.Marshal() + if err != nil { + return err + } + + c.rdb.Set(ctx, key, res, 300*time.Second) // Set proper cache time + return nil +} + +func (c *redisCacher) Invalidate(ctx context.Context) error { + var ( + cursor uint64 + keys []string + ) + for { + var ( + k []string + err error + ) + k, cursor, err = c.rdb.Scan(ctx, cursor, fmt.Sprintf("%s*", caches.IdentifierPrefix), 0).Result() + if err != nil { + return err + } + keys = append(keys, k...) + if cursor == 0 { + break + } + } + + if len(keys) > 0 { + if _, err := c.rdb.Del(ctx, keys...).Result(); err != nil { + return err + } + } + return nil +} + +func NewRedisCache(dbPoolConf config.DBPoolConfig) (caches.Cacher, error) { + rdb := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: dbPoolConf.Addresses(), + Username: dbPoolConf.Master.Username, + Password: dbPoolConf.Master.Password, + }) + + if err := redisotel.InstrumentTracing(rdb); err != nil { + return nil, fmt.Errorf("tracing instrumentation: %w", err) + } + + if err := redisotel.InstrumentMetrics(rdb); err != nil { + return nil, fmt.Errorf("metrics instrumentation: %w", err) + } + + if _, err := rdb.Ping(context.Background()).Result(); err != nil { + return nil, fmt.Errorf("ping redis: %w", err) + } + + return &redisCacher{ + rdb: rdb, + }, nil +} diff --git a/pkg/repository/db.go b/pkg/repository/db.go index 80c8200..1699ae1 100644 --- a/pkg/repository/db.go +++ b/pkg/repository/db.go @@ -6,6 +6,7 @@ import ( "github.com/ShatteredRealms/go-backend/pkg/config" "github.com/ShatteredRealms/go-backend/pkg/log" + "github.com/ShatteredRealms/go-backend/pkg/repository/cacher" "github.com/go-gorm/caches/v4" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/stdlib" @@ -18,8 +19,8 @@ import ( ) // ConnectDB Initializes the connection to a Postgres database -func ConnectDB(pool config.DBPoolConfig) (*gorm.DB, error) { - conf, err := pgx.ParseConfig(pool.Master.PostgresDSN()) +func ConnectDB(pgPool config.DBPoolConfig, redisPool config.DBPoolConfig) (*gorm.DB, error) { + conf, err := pgx.ParseConfig(pgPool.Master.PostgresDSN()) if err != nil { return nil, fmt.Errorf("parse config: %w", err) } @@ -46,9 +47,9 @@ func ConnectDB(pool config.DBPoolConfig) (*gorm.DB, error) { return nil, fmt.Errorf("gorm: %w", err) } - if len(pool.Slaves) > 0 { - replicas := make([]gorm.Dialector, len(pool.Slaves)) - for _, slave := range pool.Slaves { + if len(pgPool.Slaves) > 0 { + replicas := make([]gorm.Dialector, len(pgPool.Slaves)) + for _, slave := range pgPool.Slaves { replicas = append(replicas, postgres.Open(slave.PostgresDSN())) } @@ -62,14 +63,18 @@ func ConnectDB(pool config.DBPoolConfig) (*gorm.DB, error) { } } - if err := db.Use(otelgorm.NewPlugin(otelgorm.WithDBName(pool.Master.Name))); err != nil { + if err := db.Use(otelgorm.NewPlugin(otelgorm.WithDBName(pgPool.Master.Name))); err != nil { return nil, fmt.Errorf("opentelemetry: %w", err) } + c, err := cacher.NewRedisCache(redisPool) + if err != nil { + return nil, fmt.Errorf("redis cache: %w", err) + } cachesPlugin := caches.Caches{ Conf: &caches.Config{ Easer: true, - Cacher: NewMemoryCacher(), + Cacher: c, }, } if err = db.Use(&cachesPlugin); err != nil { diff --git a/pkg/repository/db_test.go b/pkg/repository/db_test.go index fbc9cba..40f2e33 100644 --- a/pkg/repository/db_test.go +++ b/pkg/repository/db_test.go @@ -16,26 +16,14 @@ var _ = Describe("Db repository", func() { BeforeEach(func() { log.Logger, _ = test.NewNullLogger() pool = config.DBPoolConfig{ - Master: config.DBConfig{ - Host: gormHost, - Port: gormPort, - Name: "test", - Username: "postgres", - Password: "password", - }, - Slaves: []config.DBConfig{{ - Host: gormHost, - Port: gormPort, - Name: "test", - Username: "postgres", - Password: "password", - }}, + Master: data.gormConfig, + Slaves: []config.DBConfig{data.gormConfig}, } }) Describe("ConnectDb", func() { When("given valid input", func() { It("should work", func() { - out, err := repository.ConnectDB(pool) + out, err := repository.ConnectDB(pool, data.redisConfig) Expect(err).NotTo(HaveOccurred()) Expect(out).NotTo(BeNil()) }) @@ -44,7 +32,7 @@ var _ = Describe("Db repository", func() { When("given invalid input", func() { It("should error", func() { pool.Master.Host = "a" - out, err := repository.ConnectDB(pool) + out, err := repository.ConnectDB(pool, data.redisConfig) Expect(err).To(HaveOccurred()) Expect(out).To(BeNil()) }) diff --git a/pkg/repository/repository_suite_test.go b/pkg/repository/repository_suite_test.go index 62b215e..c1daa8d 100644 --- a/pkg/repository/repository_suite_test.go +++ b/pkg/repository/repository_suite_test.go @@ -1,10 +1,12 @@ package repository_test import ( + "bytes" "context" - "strings" + "encoding/gob" "testing" + "github.com/ShatteredRealms/go-backend/pkg/config" "github.com/ShatteredRealms/go-backend/pkg/log" "github.com/ShatteredRealms/go-backend/pkg/repository" testdb "github.com/ShatteredRealms/go-backend/test/db" @@ -15,17 +17,22 @@ import ( "gorm.io/gorm" ) +type initializeData struct { + gormConfig config.DBConfig + mdbConnStr string + redisConfig config.DBPoolConfig +} + var ( hook *test.Hook gdb *gorm.DB gdbCloseFunc func() - gormHost string - gormPort string - mdb *mongo.Database mdbCloseFunc func() + data initializeData + characterRepo repository.CharacterRepository chatRepo repository.ChatRepository gamebackendRepo repository.GamebackendRepository @@ -33,20 +40,28 @@ var ( ) func TestRepository(t *testing.T) { - splitter := "\n" SynchronizedBeforeSuite(func() []byte { log.Logger, hook = test.NewNullLogger() - var gdbConnStr, mdbConnStr string - gdbCloseFunc, gdbConnStr = testdb.SetupGormWithDocker() + var gormPort string + gdbCloseFunc, gormPort = testdb.SetupGormWithDocker() Expect(gdbCloseFunc).NotTo(BeNil()) - mdbCloseFunc, mdbConnStr = testdb.SetupMongoWithDocker() + mdbCloseFunc, data.mdbConnStr = testdb.SetupMongoWithDocker() Expect(mdbCloseFunc).NotTo(BeNil()) - gdb = testdb.ConnectGormDocker(gdbConnStr) + data.gormConfig = config.DBConfig{ + ServerAddress: config.ServerAddress{ + Port: gormPort, + Host: "localhost", + }, + Name: testdb.DbName, + Username: testdb.Username, + Password: testdb.Password, + } + gdb = testdb.ConnectGormDocker(data.gormConfig.PostgresDSN()) Expect(gdb).NotTo(BeNil()) - mdb = testdb.ConnectMongoDocker(mdbConnStr) + mdb = testdb.ConnectMongoDocker(data.mdbConnStr) Expect(mdb).NotTo(BeNil()) var err error @@ -63,30 +78,21 @@ func TestRepository(t *testing.T) { Expect(gamebackendRepo).NotTo(BeNil()) Expect(gamebackendRepo.Migrate(context.Background())).NotTo(HaveOccurred()) - return []byte(gdbConnStr + splitter + mdbConnStr) - }, func(hostsBytes []byte) { - log.Logger, hook = test.NewNullLogger() + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + Expect(enc.Encode(data)).To(Succeed()) - hosts := strings.Split(string(hostsBytes), splitter) - splitGormConnStr := strings.Split(hosts[0], " ") - Expect(len(splitGormConnStr)).To(BeNumerically(">=", 2)) - for _, attr := range splitGormConnStr { - split := strings.Split(attr, "=") - Expect(split).To(HaveLen(2)) - switch split[0] { - case "host": - gormHost = split[1] - case "port": - gormPort = split[1] - } + return buf.Bytes() + }, func(inBytes []byte) { + log.Logger, hook = test.NewNullLogger() - } - Expect(gormHost).NotTo(BeEmpty()) - Expect(gormPort).NotTo(BeEmpty()) + var buf bytes.Buffer + dec := gob.NewDecoder(&buf) + Expect(dec.Decode(inBytes)).To(Succeed()) - gdb = testdb.ConnectGormDocker(hosts[0]) + gdb = testdb.ConnectGormDocker(data.gormConfig.PostgresDSN()) Expect(gdb).NotTo(BeNil()) - mdb = testdb.ConnectMongoDocker(hosts[1]) + mdb = testdb.ConnectMongoDocker(data.mdbConnStr) Expect(mdb).NotTo(BeNil()) var err error diff --git a/pkg/service/chat_s_test.go b/pkg/service/chat_s_test.go index dcfe239..57d907b 100644 --- a/pkg/service/chat_s_test.go +++ b/pkg/service/chat_s_test.go @@ -31,7 +31,7 @@ var _ = Describe("Chat service", Ordered, func() { chatService service.ChatService - kafkaPort uint + kafkaPort string err error fakeError = fmt.Errorf("error") @@ -81,7 +81,7 @@ var _ = Describe("Chat service", Ordered, func() { It("should fail if kafka connect fails", func() { mockRepository.EXPECT().Migrate(gomock.Any()).Return(nil) chatService, err = service.NewChatService(context.Background(), mockRepository, config.ServerAddress{ - Port: 0, + Port: "0", Host: "nowhere", }) diff --git a/pkg/srv/chat_test.go b/pkg/srv/chat_test.go index e4e4f9a..1a306b4 100644 --- a/pkg/srv/chat_test.go +++ b/pkg/srv/chat_test.go @@ -131,7 +131,7 @@ var _ = Describe("Chat", func() { Eventually(func(g Gomega) error { var err error kafkaConn, err = repository.ConnectKafka(config.ServerAddress{ - Port: uint(kafkaPort), + Port: kafkaPort, Host: "127.0.0.1", }) if err != nil { diff --git a/pkg/srv/srv_suite_test.go b/pkg/srv/srv_suite_test.go index fa57c61..9325f87 100644 --- a/pkg/srv/srv_suite_test.go +++ b/pkg/srv/srv_suite_test.go @@ -3,7 +3,6 @@ package srv_test import ( "context" "fmt" - "strconv" "strings" "testing" @@ -92,7 +91,7 @@ var ( incGuestCtx context.Context // Kafka - kafkaPort uint + kafkaPort string ) func TestSrv(t *testing.T) { @@ -149,7 +148,6 @@ func TestSrv(t *testing.T) { ) Expect(err).NotTo(HaveOccurred()) - var kafkaPort uint kafkaCloseFunc, kafkaPort = testdb.SetupKafkaWithDocker() out := fmt.Sprintf("%s\n%d", host, kafkaPort) @@ -161,9 +159,8 @@ func TestSrv(t *testing.T) { Expect(splitData).To(HaveLen(2)) host := splitData[0] - kafkaPort64, err := strconv.ParseUint(splitData[1], 10, 32) + kafkaPort = splitData[1] Expect(err).NotTo(HaveOccurred()) - kafkaPort = uint(kafkaPort64) keycloak = gocloak.NewClient(string(host)) globalConfig, err = config.NewGlobalConfig(context.Background()) diff --git a/test/db/docker.go b/test/db/docker.go index 67df9d2..3595656 100644 --- a/test/db/docker.go +++ b/test/db/docker.go @@ -9,6 +9,7 @@ import ( "strconv" "time" + "github.com/ShatteredRealms/go-backend/pkg/config" "github.com/ShatteredRealms/go-backend/pkg/log" "github.com/cenkalti/backoff/v4" "github.com/ory/dockertest/v3" @@ -22,9 +23,9 @@ import ( ) const ( - username = "postgres" - password = "password" - dbName = "test" + Username = "postgres" + Password = "password" + DbName = "test" ) var ( @@ -94,7 +95,7 @@ func SetupKeycloakWithDocker() (func(), string) { return closeFunc, host } -func SetupKafkaWithDocker() (func(), uint) { +func SetupKafkaWithDocker() (func(), string) { pool, err := dockertest.NewPool("") chk(err) @@ -165,7 +166,7 @@ func SetupKafkaWithDocker() (func(), uint) { chk(err3) } - return fnCleanup, uint(kafkaPortUint) + return fnCleanup, strconv.Itoa(kafkaPortUint) } func SetupMongoWithDocker() (func(), string) { @@ -221,7 +222,7 @@ func SetupGormWithDocker() (func(), string) { runDockerOpt := &dockertest.RunOptions{ Repository: "postgres", // image Tag: "14", // version - Env: []string{"POSTGRES_PASSWORD=" + password, "POSTGRES_DB=" + dbName}, + Env: []string{"POSTGRES_PASSWORD=" + Password, "POSTGRES_DB=" + DbName}, } fnConfig := func(config *docker.HostConfig) { @@ -237,14 +238,8 @@ func SetupGormWithDocker() (func(), string) { chk(err) } - connStr := fmt.Sprintf("host=localhost port=%s user=postgres dbname=%s password=%s sslmode=disable", - resource.GetPort("5432/tcp"), // get port of localhost - dbName, - password, - ) - // container is ready, return *gorm.Db for testing - return fnCleanup, connStr + return fnCleanup, resource.GetPort("5432/tcp") } func ConnectGormDocker(connStr string) *gorm.DB { @@ -277,6 +272,76 @@ func ConnectGormDocker(connStr string) *gorm.DB { return gdb } +func SetupRedisWithDocker() (fnCleanup func(), redisPoolConfig *config.DBPoolConfig) { + pool, err := dockertest.NewPool("") + chk(err) + + runDockerOpt := &dockertest.RunOptions{ + Repository: "grokzen/redis-cluster", // image + Tag: "7.0.10", // version + Env: []string{ + "INITIAL_PORT=7000", + "MASTERS=3", + "SLAVES_PER_MASTER=1", + }, + } + + fnConfig := func(config *docker.HostConfig) { + config.AutoRemove = true // set AutoRemove to true so that stopped container goes away by itself + config.RestartPolicy = docker.NeverRestart() // don't restart container + } + + resource, err := pool.RunWithOptions(runDockerOpt, fnConfig) + chk(err) + // call clean up function to release resource + fnCleanup = func() { + err := resource.Close() + chk(err) + } + + // container is ready, return *gorm.Db for testing + return fnCleanup, &config.DBPoolConfig{ + Master: config.DBConfig{ + ServerAddress: config.ServerAddress{ + Port: resource.GetPort("7000/tcp"), + Host: "localhost", + }, + }, + Slaves: []config.DBConfig{ + { + ServerAddress: config.ServerAddress{ + Port: resource.GetPort("7001/tcp"), + Host: "localhost", + }, + }, + { + ServerAddress: config.ServerAddress{ + Port: resource.GetPort("7002/tcp"), + Host: "localhost", + }, + }, + { + ServerAddress: config.ServerAddress{ + Port: resource.GetPort("7003/tcp"), + Host: "localhost", + }, + }, + { + ServerAddress: config.ServerAddress{ + Port: resource.GetPort("7004/tcp"), + Host: "localhost", + }, + }, + { + ServerAddress: config.ServerAddress{ + Port: resource.GetPort("7005/tcp"), + Host: "localhost", + }, + }, + }, + } +} + func chk(err error) { if err != nil { panic(err)