mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-05 08:36:37 +00:00
MySQL Support (#2837)
* Update store.go * Update sql_store.go * Update store.go * Update golang-test-linux.yml * Update store.go * Update go.mod * Update go.mod * Update go.sum * Update store.go * Update sql_store.go * TestContainer * Update go.sum * Update store.go * TestUtil Duplicate * dsn fix * go mod tidy * NETBIRD_STORE_ENGINE_MYSQL_DSN * Skip Test * Update test-infrastructure-files.yml * Update test-infrastructure-files.yml * MYSQL_ROOT_PASSWORD added * Update test-infrastructure-files.yml * Update store.go * Debug + Mysql JSON Query * swicth/case convert * Update store.go * Update store.go * Debug * MySQL Test Version Change * Root Test * Ignore other sql tests. * MySQL Connection Fix * enable other tests * The word "key" is a reserved word in MySQL. * Remove Debugs * Update sql_store.go * Added default null value for datetime. * Added default null value for datetime. * MySQL Hooks * MySQL Config File * remove default values * test timeout change * MySQL max lifetime change * WithConfigFile * disable other tests * Update mysql.cnf * Update golang-test-linux.yml * Delete sql_hooks.go * enable other tests * test timeout change * update packets * Fix the Inactivity Expiration problem * Update sql_store.go * Update mysql.cnf * Update sql_store.go * Update sql_store.go * timeout change * MySQL Connection LifeTime Change * TestContainers have been optimized. * Update store_ios.go * Update sql_store.go * timeout fix * fix migration (setup keys) * Update event.go * Add disable option for event activities. * Revert "Update event.go" * Update event.go * Fix Gorm Mysql Bug * update go-jose module * containerd module update * containerd downgrade * Revert commits * Revert "Revert commits" This reverts commit62b3eac799. * Revert "containerd downgrade" This reverts commit4e46108915. * Revert "containerd module update" This reverts commite8cfa87d16. * Revert "update go-jose module" This reverts commit1fabdc7606.
This commit is contained in:
@@ -1220,7 +1220,6 @@ func (am *DefaultAccountManager) handleGroupsPropagationSettings(ctx context.Con
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, account *Account, oldSettings, newSettings *Settings, userID, accountID string) error {
|
||||
|
||||
if newSettings.PeerInactivityExpirationEnabled {
|
||||
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
|
||||
oldSettings.PeerInactivityExpiration = newSettings.PeerInactivityExpiration
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -11,6 +12,11 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
)
|
||||
|
||||
func isEnabled() bool {
|
||||
response := os.Getenv("NB_EVENT_ACTIVITY_LOG_ENABLED")
|
||||
return response == "" || response == "true"
|
||||
}
|
||||
|
||||
// GetEvents returns a list of activity events of an account
|
||||
func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userID string) ([]*activity.Event, error) {
|
||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||
@@ -56,20 +62,20 @@ func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userI
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) StoreEvent(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||
|
||||
go func() {
|
||||
_, err := am.eventStore.Save(ctx, &activity.Event{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Activity: activityID,
|
||||
InitiatorID: initiatorID,
|
||||
TargetID: targetID,
|
||||
AccountID: accountID,
|
||||
Meta: meta,
|
||||
})
|
||||
if err != nil {
|
||||
// todo add metric
|
||||
log.WithContext(ctx).Errorf("received an error while storing an activity event, error: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if isEnabled() {
|
||||
go func() {
|
||||
_, err := am.eventStore.Save(ctx, &activity.Event{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Activity: activityID,
|
||||
InitiatorID: initiatorID,
|
||||
TargetID: targetID,
|
||||
AccountID: accountID,
|
||||
Meta: meta,
|
||||
})
|
||||
if err != nil {
|
||||
// todo add metric
|
||||
log.WithContext(ctx).Errorf("received an error while storing an activity event, error: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,8 +472,14 @@ func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.Clie
|
||||
|
||||
func Test_SyncStatusRace(t *testing.T) {
|
||||
t.Skip()
|
||||
if os.Getenv("CI") == "true" && os.Getenv("NETBIRD_STORE_ENGINE") == "postgres" {
|
||||
t.Skip("Skipping on CI and Postgres store")
|
||||
if os.Getenv("CI") == "true" {
|
||||
if os.Getenv("NETBIRD_STORE_ENGINE") == "postgres" {
|
||||
t.Skip("Skipping on CI and Postgres store")
|
||||
}
|
||||
|
||||
if os.Getenv("NETBIRD_STORE_ENGINE") == "mysql" {
|
||||
t.Skip("Skipping on CI and MySQL store")
|
||||
}
|
||||
}
|
||||
for i := 0; i < 500; i++ {
|
||||
t.Run(fmt.Sprintf("TestRun-%d", i), func(t *testing.T) {
|
||||
|
||||
@@ -17,12 +17,21 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func GetColumnName(db *gorm.DB, column string) string {
|
||||
|
||||
if db.Name() == "mysql" {
|
||||
return fmt.Sprintf("`%s`", column)
|
||||
}
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
// MigrateFieldFromGobToJSON migrates a column from Gob encoding to JSON encoding.
|
||||
// T is the type of the model that contains the field to be migrated.
|
||||
// S is the type of the field to be migrated.
|
||||
func MigrateFieldFromGobToJSON[T any, S any](ctx context.Context, db *gorm.DB, fieldName string) error {
|
||||
|
||||
oldColumnName := fieldName
|
||||
orgColumnName := fieldName
|
||||
oldColumnName := GetColumnName(db, orgColumnName)
|
||||
newColumnName := fieldName + "_tmp"
|
||||
|
||||
var model T
|
||||
@@ -72,7 +81,7 @@ func MigrateFieldFromGobToJSON[T any, S any](ctx context.Context, db *gorm.DB, f
|
||||
for _, row := range rows {
|
||||
var field S
|
||||
|
||||
str, ok := row[oldColumnName].(string)
|
||||
str, ok := row[orgColumnName].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("type assertion failed")
|
||||
}
|
||||
@@ -111,7 +120,8 @@ func MigrateFieldFromGobToJSON[T any, S any](ctx context.Context, db *gorm.DB, f
|
||||
// MigrateNetIPFieldFromBlobToJSON migrates a Net IP column from Blob encoding to JSON encoding.
|
||||
// T is the type of the model that contains the field to be migrated.
|
||||
func MigrateNetIPFieldFromBlobToJSON[T any](ctx context.Context, db *gorm.DB, fieldName string, indexName string) error {
|
||||
oldColumnName := fieldName
|
||||
orgColumnName := fieldName
|
||||
oldColumnName := GetColumnName(db, orgColumnName)
|
||||
newColumnName := fieldName + "_tmp"
|
||||
|
||||
var model T
|
||||
@@ -163,7 +173,7 @@ func MigrateNetIPFieldFromBlobToJSON[T any](ctx context.Context, db *gorm.DB, fi
|
||||
|
||||
for _, row := range rows {
|
||||
var blobValue string
|
||||
if columnValue := row[oldColumnName]; columnValue != nil {
|
||||
if columnValue := row[orgColumnName]; columnValue != nil {
|
||||
value, ok := columnValue.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("type assertion failed")
|
||||
@@ -210,7 +220,9 @@ func MigrateNetIPFieldFromBlobToJSON[T any](ctx context.Context, db *gorm.DB, fi
|
||||
}
|
||||
|
||||
func MigrateSetupKeyToHashedSetupKey[T any](ctx context.Context, db *gorm.DB) error {
|
||||
oldColumnName := "key"
|
||||
|
||||
orgColumnName := "key"
|
||||
oldColumnName := GetColumnName(db, orgColumnName)
|
||||
newColumnName := "key_secret"
|
||||
|
||||
var model T
|
||||
@@ -250,8 +262,9 @@ func MigrateSetupKeyToHashedSetupKey[T any](ctx context.Context, db *gorm.DB) er
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
|
||||
var plainKey string
|
||||
if columnValue := row[oldColumnName]; columnValue != nil {
|
||||
if columnValue := row[orgColumnName]; columnValue != nil {
|
||||
value, ok := columnValue.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("type assertion failed")
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@@ -36,6 +37,7 @@ const (
|
||||
storeSqliteFileName = "store.db"
|
||||
idQueryCondition = "id = ?"
|
||||
keyQueryCondition = "key = ?"
|
||||
mysqlKeyQueryCondition = "`key` = ?"
|
||||
accountAndIDQueryCondition = "account_id = ? and id = ?"
|
||||
accountAndIDsQueryCondition = "account_id = ? AND id IN ?"
|
||||
accountIDCondition = "account_id = ?"
|
||||
@@ -80,6 +82,12 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine StoreEngine, metr
|
||||
|
||||
sql.SetMaxOpenConns(conns)
|
||||
|
||||
if storeEngine == MysqlStoreEngine {
|
||||
sql.SetConnMaxLifetime(time.Minute * 2)
|
||||
sql.SetConnMaxIdleTime(time.Minute * 2)
|
||||
sql.SetMaxIdleConns(conns)
|
||||
}
|
||||
|
||||
log.WithContext(ctx).Infof("Set max open db connections to %d", conns)
|
||||
|
||||
if err := migrate(ctx, db); err != nil {
|
||||
@@ -97,6 +105,15 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine StoreEngine, metr
|
||||
return &SqlStore{db: db, storeEngine: storeEngine, metrics: metrics, installationPK: 1}, nil
|
||||
}
|
||||
|
||||
func GetKeyQueryCondition(s *SqlStore) string {
|
||||
|
||||
if s.storeEngine == MysqlStoreEngine {
|
||||
return mysqlKeyQueryCondition
|
||||
}
|
||||
|
||||
return keyQueryCondition
|
||||
}
|
||||
|
||||
// AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock
|
||||
func (s *SqlStore) AcquireGlobalLock(ctx context.Context) (unlock func()) {
|
||||
log.WithContext(ctx).Tracef("acquiring global lock")
|
||||
@@ -393,7 +410,7 @@ func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.P
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
if result.RowsAffected == 0 && s.storeEngine != MysqlStoreEngine {
|
||||
return status.Errorf(status.NotFound, peerNotFoundFMT, peerWithLocation.ID)
|
||||
}
|
||||
|
||||
@@ -483,7 +500,8 @@ func (s *SqlStore) GetAccountIDByPrivateDomain(ctx context.Context, lockStrength
|
||||
|
||||
func (s *SqlStore) GetAccountBySetupKey(ctx context.Context, setupKey string) (*Account, error) {
|
||||
var key SetupKey
|
||||
result := s.db.Select("account_id").First(&key, keyQueryCondition, setupKey)
|
||||
result := s.db.Select("account_id").First(&key, GetKeyQueryCondition(s), setupKey)
|
||||
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, status.NewSetupKeyNotFoundError(setupKey)
|
||||
@@ -711,7 +729,8 @@ func (s *SqlStore) GetAccountByPeerID(ctx context.Context, peerID string) (*Acco
|
||||
|
||||
func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (*Account, error) {
|
||||
var peer nbpeer.Peer
|
||||
result := s.db.Select("account_id").First(&peer, keyQueryCondition, peerKey)
|
||||
result := s.db.Select("account_id").First(&peer, GetKeyQueryCondition(s), peerKey)
|
||||
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||
@@ -729,7 +748,7 @@ func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (
|
||||
func (s *SqlStore) GetAccountIDByPeerPubKey(ctx context.Context, peerKey string) (string, error) {
|
||||
var peer nbpeer.Peer
|
||||
var accountID string
|
||||
result := s.db.Model(&peer).Select("account_id").Where(keyQueryCondition, peerKey).First(&accountID)
|
||||
result := s.db.Model(&peer).Select("account_id").Where(GetKeyQueryCondition(s), peerKey).First(&accountID)
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return "", status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||
@@ -755,7 +774,7 @@ func (s *SqlStore) GetAccountIDByUserID(userID string) (string, error) {
|
||||
|
||||
func (s *SqlStore) GetAccountIDBySetupKey(ctx context.Context, setupKey string) (string, error) {
|
||||
var accountID string
|
||||
result := s.db.Model(&SetupKey{}).Select("account_id").Where(keyQueryCondition, setupKey).First(&accountID)
|
||||
result := s.db.Model(&SetupKey{}).Select("account_id").Where(GetKeyQueryCondition(s), setupKey).First(&accountID)
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return "", status.NewSetupKeyNotFoundError(setupKey)
|
||||
@@ -828,7 +847,8 @@ func (s *SqlStore) GetAccountNetwork(ctx context.Context, lockStrength LockingSt
|
||||
|
||||
func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) {
|
||||
var peer nbpeer.Peer
|
||||
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).First(&peer, keyQueryCondition, peerKey)
|
||||
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).First(&peer, GetKeyQueryCondition(s), peerKey)
|
||||
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Errorf(status.NotFound, "peer not found")
|
||||
@@ -921,11 +941,22 @@ func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMe
|
||||
return NewSqlStore(ctx, db, PostgresStoreEngine, metrics)
|
||||
}
|
||||
|
||||
// NewMysqlStore creates a new MySQL store.
|
||||
func NewMysqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) {
|
||||
db, err := gorm.Open(mysql.Open(dsn+"?charset=utf8&parseTime=True&loc=Local"), getGormConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSqlStore(ctx, db, MysqlStoreEngine, metrics)
|
||||
}
|
||||
|
||||
func getGormConfig() *gorm.Config {
|
||||
return &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
CreateBatchSize: 400,
|
||||
PrepareStmt: true,
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
CreateBatchSize: 400,
|
||||
PrepareStmt: true,
|
||||
SkipDefaultTransaction: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,6 +969,15 @@ func newPostgresStore(ctx context.Context, metrics telemetry.AppMetrics) (Store,
|
||||
return NewPostgresqlStore(ctx, dsn, metrics)
|
||||
}
|
||||
|
||||
// newMysqlStore initializes a new MySQL store.
|
||||
func newMysqlStore(ctx context.Context, metrics telemetry.AppMetrics) (Store, error) {
|
||||
dsn, ok := os.LookupEnv(mysqlDsnEnv)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not set", mysqlDsnEnv)
|
||||
}
|
||||
return NewMysqlStore(ctx, dsn, metrics)
|
||||
}
|
||||
|
||||
// NewSqliteStoreFromFileStore restores a store from FileStore and stores SQLite DB in the file located in datadir.
|
||||
func NewSqliteStoreFromFileStore(ctx context.Context, fileStore *FileStore, dataDir string, metrics telemetry.AppMetrics) (*SqlStore, error) {
|
||||
store, err := NewSqliteStore(ctx, dataDir, metrics)
|
||||
@@ -982,10 +1022,33 @@ func NewPostgresqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore,
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// NewMysqlStoreFromSqlStore restores a store from SqlStore and stores MySQL DB.
|
||||
func NewMysqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) {
|
||||
store, err := NewMysqlStore(ctx, dsn, metrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = store.SaveInstallationID(ctx, sqliteStore.GetInstallationID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, account := range sqliteStore.GetAllAccounts(ctx) {
|
||||
err := store.SaveAccount(ctx, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength LockingStrength, key string) (*SetupKey, error) {
|
||||
var setupKey SetupKey
|
||||
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).
|
||||
First(&setupKey, keyQueryCondition, key)
|
||||
First(&setupKey, GetKeyQueryCondition(s), key)
|
||||
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, status.NewSetupKeyNotFoundError(key)
|
||||
@@ -1222,9 +1285,13 @@ func (s *SqlStore) GetGroupByName(ctx context.Context, lockStrength LockingStren
|
||||
// TODO: This fix is accepted for now, but if we need to handle this more frequently
|
||||
// we may need to reconsider changing the types.
|
||||
query := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Preload(clause.Associations)
|
||||
if s.storeEngine == PostgresStoreEngine {
|
||||
|
||||
switch s.storeEngine {
|
||||
case PostgresStoreEngine:
|
||||
query = query.Order("json_array_length(peers::json) DESC")
|
||||
} else {
|
||||
case MysqlStoreEngine:
|
||||
query = query.Order("JSON_LENGTH(JSON_EXTRACT(peers, \"$\")) DESC")
|
||||
default:
|
||||
query = query.Order("json_array_length(peers) DESC")
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/netbirdio/netbird/dns"
|
||||
|
||||
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||
"github.com/netbirdio/netbird/management/server/testutil"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/migration"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
"github.com/netbirdio/netbird/management/server/testutil"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
@@ -148,8 +148,10 @@ const (
|
||||
FileStoreEngine StoreEngine = "jsonfile"
|
||||
SqliteStoreEngine StoreEngine = "sqlite"
|
||||
PostgresStoreEngine StoreEngine = "postgres"
|
||||
MysqlStoreEngine StoreEngine = "mysql"
|
||||
|
||||
postgresDsnEnv = "NETBIRD_STORE_ENGINE_POSTGRES_DSN"
|
||||
mysqlDsnEnv = "NETBIRD_STORE_ENGINE_MYSQL_DSN"
|
||||
)
|
||||
|
||||
func getStoreEngineFromEnv() StoreEngine {
|
||||
@@ -160,7 +162,7 @@ func getStoreEngineFromEnv() StoreEngine {
|
||||
}
|
||||
|
||||
value := StoreEngine(strings.ToLower(kind))
|
||||
if value == SqliteStoreEngine || value == PostgresStoreEngine {
|
||||
if value == SqliteStoreEngine || value == MysqlStoreEngine || value == PostgresStoreEngine {
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -211,6 +213,9 @@ func NewStore(ctx context.Context, kind StoreEngine, dataDir string, metrics tel
|
||||
case PostgresStoreEngine:
|
||||
log.WithContext(ctx).Info("using Postgres store engine")
|
||||
return newPostgresStore(ctx, metrics)
|
||||
case MysqlStoreEngine:
|
||||
log.WithContext(ctx).Info("using MySQL store engine")
|
||||
return newMysqlStore(ctx, metrics)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported kind of store: %s", kind)
|
||||
}
|
||||
@@ -294,12 +299,14 @@ func NewTestStoreFromSQL(ctx context.Context, filename string, dataDir string) (
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create test store: %v", err)
|
||||
}
|
||||
cleanUp := func() {
|
||||
store.Close(ctx)
|
||||
}
|
||||
|
||||
return getSqlStoreEngine(ctx, store, kind)
|
||||
}
|
||||
|
||||
func getSqlStoreEngine(ctx context.Context, store *SqlStore, kind StoreEngine) (Store, func(), error) {
|
||||
|
||||
if kind == PostgresStoreEngine {
|
||||
cleanUp, err = testutil.CreatePGDB()
|
||||
cleanUp, err := testutil.CreatePostgresTestContainer()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -313,9 +320,34 @@ func NewTestStoreFromSQL(ctx context.Context, filename string, dataDir string) (
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return store, cleanUp, nil
|
||||
}
|
||||
|
||||
return store, cleanUp, nil
|
||||
if kind == MysqlStoreEngine {
|
||||
cleanUp, err := testutil.CreateMysqlTestContainer()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dsn, ok := os.LookupEnv(mysqlDsnEnv)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("%s is not set", mysqlDsnEnv)
|
||||
}
|
||||
|
||||
store, err = NewMysqlStoreFromSqlStore(ctx, store, dsn, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return store, cleanUp, nil
|
||||
}
|
||||
|
||||
closeConnection := func() {
|
||||
store.Close(ctx)
|
||||
}
|
||||
|
||||
return store, closeConnection, nil
|
||||
}
|
||||
|
||||
func loadSQL(db *gorm.DB, filepath string) error {
|
||||
|
||||
41
management/server/testdata/mysql.cnf
vendored
Normal file
41
management/server/testdata/mysql.cnf
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# For advice on how to change settings please see
|
||||
# http://dev.mysql.com/doc/refman/8.1/en/server-configuration-defaults.html
|
||||
|
||||
[mysqld]
|
||||
#
|
||||
# Remove leading # and set to the amount of RAM for the most important data
|
||||
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
|
||||
# innodb_buffer_pool_size = 128M
|
||||
#
|
||||
# Remove leading # to turn on a very important data integrity option: logging
|
||||
# changes to the binary log between backups.
|
||||
# log_bin
|
||||
#
|
||||
# Remove leading # to set options mainly useful for reporting servers.
|
||||
# The server defaults are faster for transactions and fast SELECTs.
|
||||
# Adjust sizes as needed, experiment to find the optimal values.
|
||||
# join_buffer_size = 128M
|
||||
# sort_buffer_size = 2M
|
||||
# read_rnd_buffer_size = 2M
|
||||
|
||||
# Remove leading # to revert to previous value for default_authentication_plugin,
|
||||
# this will increase compatibility with older clients. For background, see:
|
||||
# https://dev.mysql.com/doc/refman/8.1/en/server-system-variables.html#sysvar_default_authentication_plugin
|
||||
# default-authentication-plugin=mysql_native_password
|
||||
host_cache_size=0
|
||||
skip-name-resolve
|
||||
datadir=/var/lib/mysql
|
||||
socket=/var/run/mysqld/mysqld.sock
|
||||
secure-file-priv=/var/lib/mysql-files
|
||||
user=mysql
|
||||
sql_mode=""
|
||||
wait_timeout=300
|
||||
interactive_timeout=300
|
||||
innodb_flush_log_at_trx_commit=2
|
||||
default_storage_engine = InnoDB
|
||||
|
||||
pid-file=/var/run/mysqld/mysqld.pid
|
||||
[client]
|
||||
socket=/var/run/mysqld/mysqld.sock
|
||||
|
||||
!includedir /etc/mysql/conf.d/
|
||||
@@ -8,19 +8,68 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/mysql"
|
||||
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
func CreatePGDB() (func(), error) {
|
||||
var (
|
||||
mysqlContainer = (*mysql.MySQLContainer)(nil)
|
||||
mysqlContainerString = ""
|
||||
mysqlContainerConfigPath = "../../management/server/testdata/mysql.cnf"
|
||||
postgresContainer = (*postgres.PostgresContainer)(nil)
|
||||
postgresContainerString = ""
|
||||
)
|
||||
|
||||
func emptyCleanup() {
|
||||
// Empty function, don't do anything.
|
||||
}
|
||||
|
||||
func CreateMysqlTestContainer() (func(), error) {
|
||||
|
||||
ctx := context.Background()
|
||||
c, err := postgres.RunContainer(ctx,
|
||||
testcontainers.WithImage("postgres:alpine"),
|
||||
postgres.WithDatabase("test"),
|
||||
postgres.WithUsername("postgres"),
|
||||
postgres.WithPassword("postgres"),
|
||||
|
||||
if mysqlContainerString != "" && mysqlContainer != nil && mysqlContainer.IsRunning() {
|
||||
RefreshMysqlDatabase(ctx)
|
||||
return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_MYSQL_DSN", mysqlContainerString)
|
||||
}
|
||||
|
||||
container, err := mysql.Run(ctx,
|
||||
"mysql:8.0.40",
|
||||
mysql.WithConfigFile(mysqlContainerConfigPath),
|
||||
mysql.WithDatabase("netbird"),
|
||||
mysql.WithUsername("root"),
|
||||
mysql.WithPassword(""),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
talksConn, _ := container.ConnectionString(ctx)
|
||||
|
||||
mysqlContainer = container
|
||||
mysqlContainerString = talksConn
|
||||
|
||||
RefreshMysqlDatabase(ctx)
|
||||
return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_MYSQL_DSN", talksConn)
|
||||
}
|
||||
|
||||
func CreatePostgresTestContainer() (func(), error) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if postgresContainerString != "" && postgresContainer != nil && postgresContainer.IsRunning() {
|
||||
RefreshPostgresDatabase(ctx)
|
||||
return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", postgresContainerString)
|
||||
}
|
||||
|
||||
container, err := postgres.Run(ctx,
|
||||
"postgres:16-alpine",
|
||||
postgres.WithDatabase("netbird"),
|
||||
postgres.WithUsername("root"),
|
||||
postgres.WithPassword("netbird"),
|
||||
testcontainers.WithWaitStrategy(
|
||||
wait.ForLog("database system is ready to accept connections").
|
||||
WithOccurrence(2).WithStartupTimeout(15*time.Second)),
|
||||
@@ -29,17 +78,21 @@ func CreatePGDB() (func(), error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
timeout := 10 * time.Second
|
||||
err = c.Stop(ctx, &timeout)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to stop container: %s", err)
|
||||
}
|
||||
}
|
||||
talksConn, _ := container.ConnectionString(ctx)
|
||||
|
||||
talksConn, err := c.ConnectionString(ctx)
|
||||
if err != nil {
|
||||
return cleanup, err
|
||||
}
|
||||
return cleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", talksConn)
|
||||
postgresContainerString = talksConn
|
||||
postgresContainer = container
|
||||
|
||||
RefreshPostgresDatabase(ctx)
|
||||
return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", postgresContainerString)
|
||||
}
|
||||
|
||||
func RefreshMysqlDatabase(ctx context.Context) {
|
||||
_, _, _ = mysqlContainer.Exec(ctx, []string{"mysqladmin", "--user=root", "drop", "netbird", "-f"})
|
||||
_, _, _ = mysqlContainer.Exec(ctx, []string{"mysqladmin", "--user=root", "create", "netbird"})
|
||||
}
|
||||
|
||||
func RefreshPostgresDatabase(ctx context.Context) {
|
||||
_, _, _ = postgresContainer.Exec(ctx, []string{"dropdb", "-f", "netbird"})
|
||||
_, _, _ = postgresContainer.Exec(ctx, []string{"createdb", "netbird"})
|
||||
}
|
||||
|
||||
@@ -3,4 +3,14 @@
|
||||
|
||||
package testutil
|
||||
|
||||
func CreatePGDB() (func(), error) { return func() {}, nil }
|
||||
func CreatePostgresTestContainer() (func(), error) {
|
||||
return func() {
|
||||
// Empty function for Postgres
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CreateMysqlTestContainer() (func(), error) {
|
||||
return func() {
|
||||
// Empty function for MySQL
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user