...

Source file src/github.com/letsencrypt/boulder/sa/database_test.go

Documentation: github.com/letsencrypt/boulder/sa

     1  package sa
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"os"
     8  	"path"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/go-sql-driver/mysql"
    14  	"github.com/letsencrypt/boulder/cmd"
    15  	"github.com/letsencrypt/boulder/config"
    16  	"github.com/letsencrypt/boulder/test"
    17  	"github.com/letsencrypt/boulder/test/vars"
    18  )
    19  
    20  func TestInvalidDSN(t *testing.T) {
    21  	_, err := DBMapForTest("invalid")
    22  	test.AssertError(t, err, "DB connect string missing the slash separating the database name")
    23  
    24  	DSN := "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&stringVarThatDoesntExist=%27whoopsidaisies"
    25  	_, err = DBMapForTest(DSN)
    26  	test.AssertError(t, err, "Variable does not exist in curated system var list, but didn't return an error and should have")
    27  
    28  	DSN = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&concurrent_insert=2"
    29  	_, err = DBMapForTest(DSN)
    30  	test.AssertError(t, err, "Variable is unable to be set in the SESSION scope, but was declared")
    31  
    32  	DSN = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&optimizer_switch=incorrect-quoted-string"
    33  	_, err = DBMapForTest(DSN)
    34  	test.AssertError(t, err, "Variable declared with incorrect quoting")
    35  
    36  	DSN = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&concurrent_insert=%272%27"
    37  	_, err = DBMapForTest(DSN)
    38  	test.AssertError(t, err, "Integer enum declared, but should not have been quoted")
    39  }
    40  
    41  var errExpected = errors.New("expected")
    42  
    43  func TestDbSettings(t *testing.T) {
    44  	// TODO(#5248): Add a full db.mockWrappedMap to sa/database tests
    45  	oldSetMaxOpenConns := setMaxOpenConns
    46  	oldSetMaxIdleConns := setMaxIdleConns
    47  	oldSetConnMaxLifetime := setConnMaxLifetime
    48  	oldSetConnMaxIdleTime := setConnMaxIdleTime
    49  	defer func() {
    50  		setMaxOpenConns = oldSetMaxOpenConns
    51  		setMaxIdleConns = oldSetMaxIdleConns
    52  		setConnMaxLifetime = oldSetConnMaxLifetime
    53  		setConnMaxIdleTime = oldSetConnMaxIdleTime
    54  	}()
    55  
    56  	maxOpenConns := -1
    57  	maxIdleConns := -1
    58  	connMaxLifetime := time.Second * 1
    59  	connMaxIdleTime := time.Second * 1
    60  
    61  	setMaxOpenConns = func(db *sql.DB, m int) {
    62  		maxOpenConns = m
    63  		oldSetMaxOpenConns(db, maxOpenConns)
    64  	}
    65  	setMaxIdleConns = func(db *sql.DB, m int) {
    66  		maxIdleConns = m
    67  		oldSetMaxIdleConns(db, maxIdleConns)
    68  	}
    69  	setConnMaxLifetime = func(db *sql.DB, c time.Duration) {
    70  		connMaxLifetime = c
    71  		oldSetConnMaxLifetime(db, connMaxLifetime)
    72  	}
    73  	setConnMaxIdleTime = func(db *sql.DB, c time.Duration) {
    74  		connMaxIdleTime = c
    75  		oldSetConnMaxIdleTime(db, connMaxIdleTime)
    76  	}
    77  	dsnFile := path.Join(t.TempDir(), "dbconnect")
    78  	err := os.WriteFile(dsnFile,
    79  		[]byte("sa@tcp(boulder-proxysql:6033)/boulder_sa_integration"),
    80  		os.ModeAppend)
    81  	test.AssertNotError(t, err, "writing dbconnect file")
    82  
    83  	config := cmd.DBConfig{
    84  		DBConnectFile:   dsnFile,
    85  		MaxOpenConns:    100,
    86  		MaxIdleConns:    100,
    87  		ConnMaxLifetime: config.Duration{Duration: 100 * time.Second},
    88  		ConnMaxIdleTime: config.Duration{Duration: 100 * time.Second},
    89  	}
    90  	_, err = InitWrappedDb(config, nil, nil)
    91  	if err != nil {
    92  		t.Errorf("connecting to DB: %s", err)
    93  	}
    94  	if maxOpenConns != 100 {
    95  		t.Errorf("maxOpenConns was not set: expected 100, got %d", maxOpenConns)
    96  	}
    97  	if maxIdleConns != 100 {
    98  		t.Errorf("maxIdleConns was not set: expected 100, got %d", maxIdleConns)
    99  	}
   100  	if connMaxLifetime != 100*time.Second {
   101  		t.Errorf("connMaxLifetime was not set: expected 100s, got %s", connMaxLifetime)
   102  	}
   103  	if connMaxIdleTime != 100*time.Second {
   104  		t.Errorf("connMaxIdleTime was not set: expected 100s, got %s", connMaxIdleTime)
   105  	}
   106  }
   107  
   108  // TODO: Change this to test `newDbMapFromMySQLConfig` instead?
   109  func TestNewDbMap(t *testing.T) {
   110  	const mysqlConnectURL = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms"
   111  	const expected = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?clientFoundRows=true&parseTime=true&readTimeout=800ms&writeTimeout=800ms&long_query_time=0.6400000000000001&max_statement_time=0.76&sql_mode=%27STRICT_ALL_TABLES%27"
   112  	oldSQLOpen := sqlOpen
   113  	defer func() {
   114  		sqlOpen = oldSQLOpen
   115  	}()
   116  	sqlOpen = func(dbType, connectString string) (*sql.DB, error) {
   117  		if connectString != expected {
   118  			t.Errorf("incorrect connection string mangling, want %#v, got %#v", expected, connectString)
   119  		}
   120  		return nil, errExpected
   121  	}
   122  
   123  	dbMap, err := DBMapForTest(mysqlConnectURL)
   124  	if err != errExpected {
   125  		t.Errorf("got incorrect error. Got %v, expected %v", err, errExpected)
   126  	}
   127  	if dbMap != nil {
   128  		t.Errorf("expected nil, got %v", dbMap)
   129  	}
   130  
   131  }
   132  
   133  func TestStrictness(t *testing.T) {
   134  	dbMap, err := DBMapForTest(vars.DBConnSA)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	_, err = dbMap.ExecContext(ctx, `insert into orderToAuthz2 set
   139  		orderID=999999999999999999999999999,
   140  		authzID=999999999999999999999999999;`)
   141  	if err == nil {
   142  		t.Fatal("Expected error when providing out of range value, got none.")
   143  	}
   144  	if !strings.Contains(err.Error(), "Out of range value for column") {
   145  		t.Fatalf("Got wrong type of error: %s", err)
   146  	}
   147  }
   148  
   149  func TestTimeouts(t *testing.T) {
   150  	dbMap, err := DBMapForTest(vars.DBConnSA + "?max_statement_time=1")
   151  	if err != nil {
   152  		t.Fatal("Error setting up DB:", err)
   153  	}
   154  	// SLEEP is defined to return 1 if it was interrupted, but we want to actually
   155  	// get an error to simulate what would happen with a slow query. So we wrap
   156  	// the SLEEP in a subselect.
   157  	_, err = dbMap.ExecContext(ctx, `SELECT 1 FROM (SELECT SLEEP(5)) as subselect;`)
   158  	if err == nil {
   159  		t.Fatal("Expected error when running slow query, got none.")
   160  	}
   161  
   162  	// We expect to get:
   163  	// Error 1969: Query execution was interrupted (max_statement_time exceeded)
   164  	// https://mariadb.com/kb/en/mariadb/mariadb-error-codes/
   165  	if !strings.Contains(err.Error(), "Error 1969") {
   166  		t.Fatalf("Got wrong type of error: %s", err)
   167  	}
   168  }
   169  
   170  // TestAutoIncrementSchema tests that all of the tables in the boulder_*
   171  // databases that have auto_increment columns use BIGINT for the data type. Our
   172  // data is too big for INT.
   173  func TestAutoIncrementSchema(t *testing.T) {
   174  	dbMap, err := DBMapForTest(vars.DBInfoSchemaRoot)
   175  	test.AssertNotError(t, err, "unexpected err making NewDbMap")
   176  
   177  	var count int64
   178  	err = dbMap.SelectOne(
   179  		context.Background(),
   180  		&count,
   181  		`SELECT COUNT(*) FROM columns WHERE
   182  			table_schema LIKE 'boulder%' AND
   183  			extra LIKE '%auto_increment%' AND
   184  			data_type != "bigint"`)
   185  	test.AssertNotError(t, err, "unexpected err querying columns")
   186  	test.AssertEquals(t, count, int64(0))
   187  }
   188  
   189  func TestAdjustMySQLConfig(t *testing.T) {
   190  	conf := &mysql.Config{}
   191  	err := adjustMySQLConfig(conf)
   192  	test.AssertNotError(t, err, "unexpected err setting server variables")
   193  	test.AssertDeepEquals(t, conf.Params, map[string]string{
   194  		"sql_mode": "'STRICT_ALL_TABLES'",
   195  	})
   196  
   197  	conf = &mysql.Config{ReadTimeout: 100 * time.Second}
   198  	err = adjustMySQLConfig(conf)
   199  	test.AssertNotError(t, err, "unexpected err setting server variables")
   200  	test.AssertDeepEquals(t, conf.Params, map[string]string{
   201  		"sql_mode":           "'STRICT_ALL_TABLES'",
   202  		"max_statement_time": "95",
   203  		"long_query_time":    "80",
   204  	})
   205  
   206  	conf = &mysql.Config{
   207  		ReadTimeout: 100 * time.Second,
   208  		Params: map[string]string{
   209  			"max_statement_time": "0",
   210  		},
   211  	}
   212  	err = adjustMySQLConfig(conf)
   213  	test.AssertNotError(t, err, "unexpected err setting server variables")
   214  	test.AssertDeepEquals(t, conf.Params, map[string]string{
   215  		"sql_mode":        "'STRICT_ALL_TABLES'",
   216  		"long_query_time": "80",
   217  	})
   218  
   219  	conf = &mysql.Config{
   220  		Params: map[string]string{
   221  			"max_statement_time": "0",
   222  		},
   223  	}
   224  	err = adjustMySQLConfig(conf)
   225  	test.AssertNotError(t, err, "unexpected err setting server variables")
   226  	test.AssertDeepEquals(t, conf.Params, map[string]string{
   227  		"sql_mode": "'STRICT_ALL_TABLES'",
   228  	})
   229  }
   230  

View as plain text