Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/boulder-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,19 @@ jobs:
- "./t.sh --unit --enable-race-detection"
- "./tn.sh --unit --enable-race-detection"
- "./t.sh --start-py"
# Same cases but backed by Vitess + MySQL 8 instead of ProxySQL + MariaDB
- "./t.sh --use-vitess --integration"
- "./tn.sh --use-vitess --integration"
- "./t.sh --use-vitess --unit --enable-race-detection"
- "./tn.sh --use-vitess --unit --enable-race-detection"
- "./t.sh --use-vitess --start-py"

env:
# This sets the docker image tag for the boulder-tools repository to
# use in tests. It will be set appropriately for each tag in the list
# defined in the matrix.
BOULDER_TOOLS_TAG: ${{ matrix.BOULDER_TOOLS_TAG }}
BOULDER_VTCOMBOSERVER_TAG: vitessv22.0.0_2025-11-03

# Sequence of tasks that will be executed as part of the job.
steps:
Expand Down
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,16 @@ test/proxysql/*.log*

# Coverage files
test/coverage

# Database config symlinks
sa/db/dbconfig.yml
test/secrets/backfiller_dburl
test/secrets/badkeyrevoker_dburl
test/secrets/cert_checker_dburl
test/secrets/expiration_mailer_dburl
test/secrets/incidents_dburl
test/secrets/mailer_dburl
test/secrets/ocsp_responder_dburl
test/secrets/revoker_dburl
test/secrets/sa_dburl
test/secrets/sa_ro_dburl
24 changes: 20 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ services:
- 4001:4001 # ACMEv2
- 4003:4003 # SFE
depends_on:
- bmysql
- bmariadb
- bproxysql
- bvitess
- bredis_1
- bredis_2
- bconsul
Expand All @@ -74,12 +75,12 @@ services:
# with a "docker compose up bsetup".
- setup

bmysql:
bmariadb:
image: mariadb:10.11.13
networks:
bouldernet:
aliases:
- boulder-mysql
- boulder-mariadb
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
# Send slow queries to a table so we can check for them in the
Expand All @@ -101,7 +102,7 @@ services:
volumes:
- ./test/:/test/:cached
depends_on:
- bmysql
- bmariadb
networks:
bouldernet:
aliases:
Expand Down Expand Up @@ -144,6 +145,21 @@ services:
networks:
- bouldernet

bvitess:
# The `letsencrypt/boulder-vtcomboserver:latest` tag is automatically built
# in local dev environments. In CI a specific BOULDER_VTCOMBOSERVER_TAG is
# passed, and it is pulled with `docker compose pull`.
image: letsencrypt/boulder-vtcomboserver:${BOULDER_VTCOMBOSERVER_TAG:-latest}
environment:
# By specifying KEYSPACES vttestserver will create the corresponding
# databases on startup.
KEYSPACES: boulder_sa_test,boulder_sa_integration,incidents_sa_test,incidents_sa_integration
NUM_SHARDS: 1,1,1,1
networks:
bouldernet:
aliases:
- boulder-vitess

networks:
# This network represents the data-center internal network. It is used for
# boulder services and their infrastructure, such as consul, mariadb, and
Expand Down
27 changes: 0 additions & 27 deletions sa/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,6 @@ func adjustMySQLConfig(conf *mysql.Config) error {
}
}

// If a given parameter has the value "0", delete it from conf.Params.
omitZero := func(name string) {
if conf.Params[name] == "0" {
delete(conf.Params, name)
}
}

// Ensures that MySQL/MariaDB warnings are treated as errors. This
// avoids a number of nasty edge conditions we could wander into.
// Common things this discovers includes places where data being sent
Expand All @@ -216,26 +209,6 @@ func adjustMySQLConfig(conf *mysql.Config) error {
// <https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-strict>.
setDefault("sql_mode", "'STRICT_ALL_TABLES'")

// If a read timeout is set, we set max_statement_time to 95% of that, and
// long_query_time to 80% of that. That way we get logs of queries that are
// close to timing out but not yet doing so, and our queries get stopped by
// max_statement_time before timing out the read. This generates clearer
// errors, and avoids unnecessary reconnects.
// To override these values, set them in the DSN, e.g.
// `?max_statement_time=2`. A zero value in the DSN means these won't be
// sent on new connections.
if conf.ReadTimeout != 0 {
// In MariaDB, max_statement_time and long_query_time are both seconds,
// but can have up to microsecond granularity.
// Note: in MySQL (which we don't use), max_statement_time is millis.
readTimeout := conf.ReadTimeout.Seconds()
setDefault("max_statement_time", fmt.Sprintf("%.6f", readTimeout*0.95))
setDefault("long_query_time", fmt.Sprintf("%.6f", readTimeout*0.80))
}

omitZero("max_statement_time")
omitZero("long_query_time")

// Finally, perform validation over all variables set by the DSN and via Boulder.
for k, v := range conf.Params {
err := checkMariaDBSystemVariables(k, v)
Expand Down
81 changes: 10 additions & 71 deletions sa/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,38 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"os"
"path"
"strings"
"testing"
"time"

"github.com/go-sql-driver/mysql"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/config"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/test/vars"
)

var dbHost = os.Getenv("MYSQL_ADDR")

func TestInvalidDSN(t *testing.T) {
_, err := DBMapForTest("invalid")
test.AssertError(t, err, "DB connect string missing the slash separating the database name")

DSN := "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&stringVarThatDoesntExist=%27whoopsidaisies"
DSN := fmt.Sprintf("policy:password@tcp(%s)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&stringVarThatDoesntExist=%%27whoopsidaisies", dbHost)
_, err = DBMapForTest(DSN)
test.AssertError(t, err, "Variable does not exist in curated system var list, but didn't return an error and should have")

DSN = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&concurrent_insert=2"
DSN = fmt.Sprintf("policy:password@tcp(%s)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&concurrent_insert=2", dbHost)
_, err = DBMapForTest(DSN)
test.AssertError(t, err, "Variable is unable to be set in the SESSION scope, but was declared")

DSN = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&optimizer_switch=incorrect-quoted-string"
DSN = fmt.Sprintf("policy:password@tcp(%s)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&optimizer_switch=incorrect-quoted-string", dbHost)
_, err = DBMapForTest(DSN)
test.AssertError(t, err, "Variable declared with incorrect quoting")

DSN = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&concurrent_insert=%272%27"
DSN = fmt.Sprintf("policy:password@tcp(%s)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms&concurrent_insert=%%272%%27", dbHost)
_, err = DBMapForTest(DSN)
test.AssertError(t, err, "Integer enum declared, but should not have been quoted")
}
Expand Down Expand Up @@ -76,7 +78,7 @@ func TestDbSettings(t *testing.T) {
}
dsnFile := path.Join(t.TempDir(), "dbconnect")
err := os.WriteFile(dsnFile,
[]byte("sa@tcp(boulder-proxysql:6033)/boulder_sa_integration"),
[]byte(fmt.Sprintf("sa@tcp(%s)/boulder_sa_integration", dbHost)),
os.ModeAppend)
test.AssertNotError(t, err, "writing dbconnect file")

Expand Down Expand Up @@ -107,8 +109,8 @@ func TestDbSettings(t *testing.T) {

// TODO: Change this to test `newDbMapFromMySQLConfig` instead?
func TestNewDbMap(t *testing.T) {
const mysqlConnectURL = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms"
const expected = "policy:password@tcp(boulder-proxysql:6033)/boulder_policy_integration?clientFoundRows=true&parseTime=true&readTimeout=800ms&writeTimeout=800ms&long_query_time=0.640000&max_statement_time=0.760000&sql_mode=%27STRICT_ALL_TABLES%27"
mysqlConnectURL := fmt.Sprintf("policy:password@tcp(%s)/boulder_policy_integration?readTimeout=800ms&writeTimeout=800ms", dbHost)
expected := fmt.Sprintf("policy:password@tcp(%s)/boulder_policy_integration?clientFoundRows=true&parseTime=true&readTimeout=800ms&writeTimeout=800ms&sql_mode=%%27STRICT_ALL_TABLES%%27", dbHost)
oldSQLOpen := sqlOpen
defer func() {
sqlOpen = oldSQLOpen
Expand Down Expand Up @@ -146,27 +148,6 @@ func TestStrictness(t *testing.T) {
}
}

func TestTimeouts(t *testing.T) {
dbMap, err := DBMapForTest(vars.DBConnSA + "?max_statement_time=1")
if err != nil {
t.Fatal("Error setting up DB:", err)
}
// SLEEP is defined to return 1 if it was interrupted, but we want to actually
// get an error to simulate what would happen with a slow query. So we wrap
// the SLEEP in a subselect.
_, err = dbMap.ExecContext(ctx, `SELECT 1 FROM (SELECT SLEEP(5)) as subselect;`)
if err == nil {
t.Fatal("Expected error when running slow query, got none.")
}

// We expect to get:
// Error 1969: Query execution was interrupted (max_statement_time exceeded)
// https://mariadb.com/kb/en/mariadb/mariadb-error-codes/
if !strings.Contains(err.Error(), "Error 1969") {
t.Fatalf("Got wrong type of error: %s", err)
}
}

// TestAutoIncrementSchema tests that all of the tables in the boulder_*
// databases that have auto_increment columns use BIGINT for the data type. Our
// data is too big for INT.
Expand All @@ -185,45 +166,3 @@ func TestAutoIncrementSchema(t *testing.T) {
test.AssertNotError(t, err, "unexpected err querying columns")
test.AssertEquals(t, count, int64(0))
}

func TestAdjustMySQLConfig(t *testing.T) {
conf := &mysql.Config{}
err := adjustMySQLConfig(conf)
test.AssertNotError(t, err, "unexpected err setting server variables")
test.AssertDeepEquals(t, conf.Params, map[string]string{
"sql_mode": "'STRICT_ALL_TABLES'",
})

conf = &mysql.Config{ReadTimeout: 100 * time.Second}
err = adjustMySQLConfig(conf)
test.AssertNotError(t, err, "unexpected err setting server variables")
test.AssertDeepEquals(t, conf.Params, map[string]string{
"sql_mode": "'STRICT_ALL_TABLES'",
"max_statement_time": "95.000000",
"long_query_time": "80.000000",
})

conf = &mysql.Config{
ReadTimeout: 100 * time.Second,
Params: map[string]string{
"max_statement_time": "0",
},
}
err = adjustMySQLConfig(conf)
test.AssertNotError(t, err, "unexpected err setting server variables")
test.AssertDeepEquals(t, conf.Params, map[string]string{
"sql_mode": "'STRICT_ALL_TABLES'",
"long_query_time": "80.000000",
})

conf = &mysql.Config{
Params: map[string]string{
"max_statement_time": "0",
},
}
err = adjustMySQLConfig(conf)
test.AssertNotError(t, err, "unexpected err setting server variables")
test.AssertDeepEquals(t, conf.Params, map[string]string{
"sql_mode": "'STRICT_ALL_TABLES'",
})
}
8 changes: 2 additions & 6 deletions sa/db-next/boulder_sa/20230419000003_OrderToAuthzID.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ CREATE TABLE `orderToAuthz2` (
PRIMARY KEY (`id`),
KEY `orderID_idx` (`orderID`),
KEY `authzID_idx` (`authzID`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (`id`)
(PARTITION p_start VALUES LESS THAN (MAXVALUE));
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;

-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
Expand All @@ -22,6 +20,4 @@ CREATE TABLE `orderToAuthz2` (
`authzID` bigint(20) NOT NULL,
PRIMARY KEY (`orderID`,`authzID`),
KEY `authzID` (`authzID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE COLUMNS(orderID, authzID)
(PARTITION p_start VALUES LESS THAN (MAXVALUE, MAXVALUE));
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Loading