github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/store/sqlstore/supplier_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sqlstore_test
     5  
     6  import (
     7  	"regexp"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/mattermost/gorp"
    13  	_ "github.com/mattn/go-sqlite3"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/mattermost/mattermost-server/v5/model"
    18  	"github.com/mattermost/mattermost-server/v5/store/sqlstore"
    19  	"github.com/mattermost/mattermost-server/v5/store/storetest"
    20  )
    21  
    22  func TestGetReplica(t *testing.T) {
    23  	t.Parallel()
    24  	testCases := []struct {
    25  		Description              string
    26  		DataSourceReplicas       []string
    27  		DataSourceSearchReplicas []string
    28  	}{
    29  		{
    30  			"no replicas",
    31  			[]string{},
    32  			[]string{},
    33  		},
    34  		{
    35  			"one source replica",
    36  			[]string{":memory:"},
    37  			[]string{},
    38  		},
    39  		{
    40  			"multiple source replicas",
    41  			[]string{":memory:", ":memory:", ":memory:"},
    42  			[]string{},
    43  		},
    44  		{
    45  			"one source search replica",
    46  			[]string{},
    47  			[]string{":memory:"},
    48  		},
    49  		{
    50  			"multiple source search replicas",
    51  			[]string{},
    52  			[]string{":memory:", ":memory:", ":memory:"},
    53  		},
    54  		{
    55  			"one source replica, one source search replica",
    56  			[]string{":memory:"},
    57  			[]string{":memory:"},
    58  		},
    59  		{
    60  			"one source replica, multiple source search replicas",
    61  			[]string{":memory:"},
    62  			[]string{":memory:", ":memory:", ":memory:"},
    63  		},
    64  		{
    65  			"multiple source replica, one source search replica",
    66  			[]string{":memory:", ":memory:", ":memory:"},
    67  			[]string{":memory:"},
    68  		},
    69  		{
    70  			"multiple source replica, multiple source search replicas",
    71  			[]string{":memory:", ":memory:", ":memory:"},
    72  			[]string{":memory:", ":memory:", ":memory:"},
    73  		},
    74  	}
    75  
    76  	for _, testCase := range testCases {
    77  		testCase := testCase
    78  		t.Run(testCase.Description+" with license", func(t *testing.T) {
    79  			t.Parallel()
    80  
    81  			settings := makeSqlSettings(model.DATABASE_DRIVER_SQLITE)
    82  			settings.DataSourceReplicas = testCase.DataSourceReplicas
    83  			settings.DataSourceSearchReplicas = testCase.DataSourceSearchReplicas
    84  			supplier := sqlstore.NewSqlSupplier(*settings, nil)
    85  			supplier.UpdateLicense(&model.License{})
    86  
    87  			replicas := make(map[*gorp.DbMap]bool)
    88  			for i := 0; i < 5; i++ {
    89  				replicas[supplier.GetReplica()] = true
    90  			}
    91  
    92  			searchReplicas := make(map[*gorp.DbMap]bool)
    93  			for i := 0; i < 5; i++ {
    94  				searchReplicas[supplier.GetSearchReplica()] = true
    95  			}
    96  
    97  			if len(testCase.DataSourceReplicas) > 0 {
    98  				// If replicas were defined, ensure none are the master.
    99  				assert.Len(t, replicas, len(testCase.DataSourceReplicas))
   100  
   101  				for replica := range replicas {
   102  					assert.NotEqual(t, supplier.GetMaster(), replica)
   103  				}
   104  
   105  			} else if assert.Len(t, replicas, 1) {
   106  				// Otherwise ensure the replicas contains only the master.
   107  				for replica := range replicas {
   108  					assert.Equal(t, supplier.GetMaster(), replica)
   109  				}
   110  			}
   111  
   112  			if len(testCase.DataSourceSearchReplicas) > 0 {
   113  				// If search replicas were defined, ensure none are the master nor the replicas.
   114  				assert.Len(t, searchReplicas, len(testCase.DataSourceSearchReplicas))
   115  
   116  				for searchReplica := range searchReplicas {
   117  					assert.NotEqual(t, supplier.GetMaster(), searchReplica)
   118  					for replica := range replicas {
   119  						assert.NotEqual(t, searchReplica, replica)
   120  					}
   121  				}
   122  
   123  			} else if len(testCase.DataSourceReplicas) > 0 {
   124  				// If no search replicas were defined, but replicas were, ensure they are equal.
   125  				assert.Equal(t, replicas, searchReplicas)
   126  
   127  			} else if assert.Len(t, searchReplicas, 1) {
   128  				// Otherwise ensure the search replicas contains the master.
   129  				for searchReplica := range searchReplicas {
   130  					assert.Equal(t, supplier.GetMaster(), searchReplica)
   131  				}
   132  			}
   133  		})
   134  
   135  		t.Run(testCase.Description+" without license", func(t *testing.T) {
   136  			t.Parallel()
   137  
   138  			settings := makeSqlSettings(model.DATABASE_DRIVER_SQLITE)
   139  			settings.DataSourceReplicas = testCase.DataSourceReplicas
   140  			settings.DataSourceSearchReplicas = testCase.DataSourceSearchReplicas
   141  			supplier := sqlstore.NewSqlSupplier(*settings, nil)
   142  
   143  			replicas := make(map[*gorp.DbMap]bool)
   144  			for i := 0; i < 5; i++ {
   145  				replicas[supplier.GetReplica()] = true
   146  			}
   147  
   148  			searchReplicas := make(map[*gorp.DbMap]bool)
   149  			for i := 0; i < 5; i++ {
   150  				searchReplicas[supplier.GetSearchReplica()] = true
   151  			}
   152  
   153  			if len(testCase.DataSourceReplicas) > 0 {
   154  				// If replicas were defined, ensure none are the master.
   155  				assert.Len(t, replicas, 1)
   156  
   157  				for replica := range replicas {
   158  					assert.Same(t, supplier.GetMaster(), replica)
   159  				}
   160  
   161  			} else if assert.Len(t, replicas, 1) {
   162  				// Otherwise ensure the replicas contains only the master.
   163  				for replica := range replicas {
   164  					assert.Equal(t, supplier.GetMaster(), replica)
   165  				}
   166  			}
   167  
   168  			if len(testCase.DataSourceSearchReplicas) > 0 {
   169  				// If search replicas were defined, ensure none are the master nor the replicas.
   170  				assert.Len(t, searchReplicas, 1)
   171  
   172  				for searchReplica := range searchReplicas {
   173  					assert.Same(t, supplier.GetMaster(), searchReplica)
   174  				}
   175  
   176  			} else if len(testCase.DataSourceReplicas) > 0 {
   177  				// If no search replicas were defined, but replicas were, ensure they are equal.
   178  				assert.Equal(t, replicas, searchReplicas)
   179  
   180  			} else if assert.Len(t, searchReplicas, 1) {
   181  				// Otherwise ensure the search replicas contains the master.
   182  				for searchReplica := range searchReplicas {
   183  					assert.Equal(t, supplier.GetMaster(), searchReplica)
   184  				}
   185  			}
   186  		})
   187  	}
   188  }
   189  
   190  func TestGetDbVersion(t *testing.T) {
   191  	testDrivers := []string{
   192  		model.DATABASE_DRIVER_POSTGRES,
   193  		model.DATABASE_DRIVER_MYSQL,
   194  		model.DATABASE_DRIVER_SQLITE,
   195  	}
   196  
   197  	for _, driver := range testDrivers {
   198  		t.Run("Should return db version for "+driver, func(t *testing.T) {
   199  			t.Parallel()
   200  			settings := makeSqlSettings(driver)
   201  			supplier := sqlstore.NewSqlSupplier(*settings, nil)
   202  
   203  			version, err := supplier.GetDbVersion()
   204  			require.Nil(t, err)
   205  			require.Regexp(t, regexp.MustCompile(`\d+\.\d+(\.\d+)?`), version)
   206  		})
   207  	}
   208  }
   209  
   210  func TestRecycleDBConns(t *testing.T) {
   211  	if testing.Short() {
   212  		t.Skip("skipping recycle DBConns test")
   213  	}
   214  	testDrivers := []string{
   215  		model.DATABASE_DRIVER_POSTGRES,
   216  		model.DATABASE_DRIVER_MYSQL,
   217  		model.DATABASE_DRIVER_SQLITE,
   218  	}
   219  
   220  	for _, driver := range testDrivers {
   221  		t.Run(driver, func(t *testing.T) {
   222  			settings := makeSqlSettings(driver)
   223  			supplier := sqlstore.NewSqlSupplier(*settings, nil)
   224  
   225  			var wg sync.WaitGroup
   226  			tables := []string{"Posts", "Channels", "Users"}
   227  			for _, table := range tables {
   228  				wg.Add(1)
   229  				go func(table string) {
   230  					defer wg.Done()
   231  					query := `SELECT count(*) FROM ` + table
   232  					_, err := supplier.GetMaster().SelectInt(query)
   233  					assert.NoError(t, err)
   234  				}(table)
   235  			}
   236  			wg.Wait()
   237  
   238  			stats := supplier.GetMaster().Db.Stats()
   239  			assert.Equal(t, 0, int(stats.MaxLifetimeClosed), "unexpected number of connections closed due to maxlifetime")
   240  
   241  			supplier.RecycleDBConnections(2 * time.Second)
   242  			// We cannot reliably control exactly how many open connections are there. So we
   243  			// just do a basic check and confirm that atleast one has been closed.
   244  			stats = supplier.GetMaster().Db.Stats()
   245  			assert.Greater(t, int(stats.MaxLifetimeClosed), 0, "unexpected number of connections closed due to maxlifetime")
   246  		})
   247  	}
   248  }
   249  
   250  func TestGetAllConns(t *testing.T) {
   251  	t.Parallel()
   252  	testCases := []struct {
   253  		Description              string
   254  		DataSourceReplicas       []string
   255  		DataSourceSearchReplicas []string
   256  		ExpectedNumConnections   int
   257  	}{
   258  		{
   259  			"no replicas",
   260  			[]string{},
   261  			[]string{},
   262  			1,
   263  		},
   264  		{
   265  			"one source replica",
   266  			[]string{":memory:"},
   267  			[]string{},
   268  			2,
   269  		},
   270  		{
   271  			"multiple source replicas",
   272  			[]string{":memory:", ":memory:", ":memory:"},
   273  			[]string{},
   274  			4,
   275  		},
   276  		{
   277  			"one source search replica",
   278  			[]string{},
   279  			[]string{":memory:"},
   280  			1,
   281  		},
   282  		{
   283  			"multiple source search replicas",
   284  			[]string{},
   285  			[]string{":memory:", ":memory:", ":memory:"},
   286  			1,
   287  		},
   288  		{
   289  			"one source replica, one source search replica",
   290  			[]string{":memory:"},
   291  			[]string{":memory:"},
   292  			2,
   293  		},
   294  		{
   295  			"one source replica, multiple source search replicas",
   296  			[]string{":memory:"},
   297  			[]string{":memory:", ":memory:", ":memory:"},
   298  			2,
   299  		},
   300  		{
   301  			"multiple source replica, one source search replica",
   302  			[]string{":memory:", ":memory:", ":memory:"},
   303  			[]string{":memory:"},
   304  			4,
   305  		},
   306  		{
   307  			"multiple source replica, multiple source search replicas",
   308  			[]string{":memory:", ":memory:", ":memory:"},
   309  			[]string{":memory:", ":memory:", ":memory:"},
   310  			4,
   311  		},
   312  	}
   313  
   314  	for _, testCase := range testCases {
   315  		testCase := testCase
   316  		t.Run(testCase.Description, func(t *testing.T) {
   317  			t.Parallel()
   318  			settings := makeSqlSettings(model.DATABASE_DRIVER_SQLITE)
   319  			settings.DataSourceReplicas = testCase.DataSourceReplicas
   320  			settings.DataSourceSearchReplicas = testCase.DataSourceSearchReplicas
   321  			supplier := sqlstore.NewSqlSupplier(*settings, nil)
   322  
   323  			assert.Len(t, supplier.GetAllConns(), testCase.ExpectedNumConnections)
   324  		})
   325  	}
   326  }
   327  
   328  func makeSqlSettings(driver string) *model.SqlSettings {
   329  	switch driver {
   330  	case model.DATABASE_DRIVER_POSTGRES:
   331  		return storetest.MakeSqlSettings(driver)
   332  	case model.DATABASE_DRIVER_MYSQL:
   333  		return storetest.MakeSqlSettings(driver)
   334  	case model.DATABASE_DRIVER_SQLITE:
   335  		return makeSqliteSettings()
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  func makeSqliteSettings() *model.SqlSettings {
   342  	driverName := model.DATABASE_DRIVER_SQLITE
   343  	dataSource := ":memory:"
   344  	maxIdleConns := 1
   345  	connMaxLifetimeMilliseconds := 3600000
   346  	maxOpenConns := 1
   347  	queryTimeout := 5
   348  
   349  	return &model.SqlSettings{
   350  		DriverName:                  &driverName,
   351  		DataSource:                  &dataSource,
   352  		MaxIdleConns:                &maxIdleConns,
   353  		ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds,
   354  		MaxOpenConns:                &maxOpenConns,
   355  		QueryTimeout:                &queryTimeout,
   356  	}
   357  }