diff --git a/.github/workflows/boulder-ci.yml b/.github/workflows/boulder-ci.yml index 5513cd54cd6..17ecdf26fdf 100644 --- a/.github/workflows/boulder-ci.yml +++ b/.github/workflows/boulder-ci.yml @@ -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: diff --git a/.gitignore b/.gitignore index 769b54767cf..f866ccddafd 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 39802cd8b58..f21c9322521 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,8 +50,9 @@ services: - 4001:4001 # ACMEv2 - 4003:4003 # SFE depends_on: - - bmysql + - bmariadb - bproxysql + - bvitess - bredis_1 - bredis_2 - bconsul @@ -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 @@ -101,7 +102,7 @@ services: volumes: - ./test/:/test/:cached depends_on: - - bmysql + - bmariadb networks: bouldernet: aliases: @@ -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 diff --git a/sa/database.go b/sa/database.go index 0d06fac5f90..0f829453a8e 100644 --- a/sa/database.go +++ b/sa/database.go @@ -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 @@ -216,26 +209,6 @@ func adjustMySQLConfig(conf *mysql.Config) error { // . 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) diff --git a/sa/database_test.go b/sa/database_test.go index 1585c6d89cf..1d7147e0273 100644 --- a/sa/database_test.go +++ b/sa/database_test.go @@ -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") } @@ -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") @@ -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 @@ -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. @@ -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'", - }) -} diff --git a/sa/db-next/boulder_sa/20230419000003_OrderToAuthzID.sql b/sa/db-next/boulder_sa/20230419000003_OrderToAuthzID.sql index 2a2ab06cc47..cb393e7b203 100644 --- a/sa/db-next/boulder_sa/20230419000003_OrderToAuthzID.sql +++ b/sa/db-next/boulder_sa/20230419000003_OrderToAuthzID.sql @@ -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 @@ -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; diff --git a/sa/db/boulder_sa/20230419000000_CombinedSchema.sql b/sa/db/boulder_sa/20230419000000_CombinedSchema.sql index 42c489be9a5..c2fa91c9e91 100644 --- a/sa/db/boulder_sa/20230419000000_CombinedSchema.sql +++ b/sa/db/boulder_sa/20230419000000_CombinedSchema.sql @@ -18,9 +18,7 @@ CREATE TABLE `authz2` ( KEY `regID_expires_idx` (`registrationID`,`status`,`expires`), KEY `regID_identifier_status_expires_idx` (`registrationID`,`identifierType`,`identifierValue`,`status`,`expires`), KEY `expires_idx` (`expires`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `blockedKeys` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -53,9 +51,7 @@ CREATE TABLE `certificateStatus` ( KEY `serial` (`serial`), KEY `isExpired_ocspLastUpdated_idx` (`isExpired`,`ocspLastUpdated`), KEY `notAfter_idx` (`notAfter`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `certificates` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, @@ -69,9 +65,7 @@ CREATE TABLE `certificates` ( KEY `serial` (`serial`), KEY `regId_certificates_idx` (`registrationID`) COMMENT 'Common lookup', KEY `issued_idx` (`issued`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `certificatesPerName` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, @@ -93,9 +87,7 @@ CREATE TABLE `fqdnSets` ( PRIMARY KEY (`id`), KEY `serial` (`serial`), KEY `setHash_issued_idx` (`setHash`,`issued`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `incidents` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -104,7 +96,7 @@ CREATE TABLE `incidents` ( `renewBy` datetime NOT NULL, `enabled` boolean DEFAULT false, PRIMARY KEY (`id`) -) CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `issuedNames` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, @@ -114,9 +106,7 @@ CREATE TABLE `issuedNames` ( `renewal` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `reversedName_notBefore_Idx` (`reversedName`,`notBefore`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `keyHashToSerial` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, @@ -147,18 +137,14 @@ CREATE TABLE `orderFqdnSets` ( KEY `setHash_expires_idx` (`setHash`,`expires`), KEY `orderID_idx` (`orderID`), KEY `orderFqdnSets_registrationID_registrations` (`registrationID`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `orderToAuthz2` ( `orderID` bigint(20) NOT NULL, `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; CREATE TABLE `orders` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -171,9 +157,7 @@ CREATE TABLE `orders` ( PRIMARY KEY (`id`), KEY `reg_status_expires` (`registrationID`,`expires`), KEY `regID_created_idx` (`registrationID`,`created`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Note: This table's name is a historical artifact and it is now -- used to store linting certificates, not precertificates. @@ -189,9 +173,7 @@ CREATE TABLE `precertificates` ( KEY `serial` (`serial`), KEY `regId_precertificates_idx` (`registrationID`), KEY `issued_precertificates_idx` (`issued`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `registrations` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, @@ -215,9 +197,7 @@ CREATE TABLE `requestedNames` ( PRIMARY KEY (`id`), KEY `orderID_idx` (`orderID`), KEY `reversedName_idx` (`reversedName`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - PARTITION BY RANGE(id) -(PARTITION p_start VALUES LESS THAN (MAXVALUE)); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Tables below have foreign key constraints, so are created after all other tables. @@ -245,6 +225,7 @@ DROP TABLE `certificateStatus`; DROP TABLE `certificatesPerName`; DROP TABLE `certificates`; DROP TABLE `fqdnSets`; +DROP TABLE `incidents`; DROP TABLE `issuedNames`; DROP TABLE `keyHashToSerial`; DROP TABLE `newOrdersRL`; diff --git a/sa/db/boulder_sa/20230519000000_CrlShards.sql b/sa/db/boulder_sa/20230519000000_CrlShards.sql index 6c0d0f9eb6a..0bec0696098 100644 --- a/sa/db/boulder_sa/20230519000000_CrlShards.sql +++ b/sa/db/boulder_sa/20230519000000_CrlShards.sql @@ -5,8 +5,8 @@ CREATE TABLE `crlShards` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, `issuerID` bigint(20) NOT NULL, `idx` int UNSIGNED NOT NULL, - `thisUpdate` datetime, - `nextUpdate` datetime, + `thisUpdate` datetime DEFAULT NULL, + `nextUpdate` datetime DEFAULT NULL, `leasedUntil` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `shardID` (`issuerID`, `idx`) diff --git a/sa/db/dbconfig.yml b/sa/db/dbconfig.mariadb.yml similarity index 69% rename from sa/db/dbconfig.yml rename to sa/db/dbconfig.mariadb.yml index 747ce0365fb..895c06124f6 100644 --- a/sa/db/dbconfig.yml +++ b/sa/db/dbconfig.mariadb.yml @@ -1,20 +1,20 @@ # https://github.com/rubenv/sql-migrate#readme boulder_sa_test: dialect: mysql - datasource: root@tcp(boulder-proxysql:6033)/boulder_sa_test?parseTime=true + datasource: root@tcp(boulder-proxysql:6033)/boulder_sa_test?parseTime=true&max_statement_time=15&long_query_time=12 dir: boulder_sa boulder_sa_integration: dialect: mysql - datasource: root@tcp(boulder-proxysql:6033)/boulder_sa_integration?parseTime=true + datasource: root@tcp(boulder-proxysql:6033)/boulder_sa_integration?parseTime=true&max_statement_time=15&long_query_time=12 dir: boulder_sa incidents_sa_test: dialect: mysql - datasource: root@tcp(boulder-proxysql:6033)/incidents_sa_test?parseTime=true + datasource: root@tcp(boulder-proxysql:6033)/incidents_sa_test?parseTime=true&max_statement_time=15&long_query_time=12 dir: incidents_sa incidents_sa_integration: dialect: mysql - datasource: root@tcp(boulder-proxysql:6033)/incidents_sa_integration?parseTime=true + datasource: root@tcp(boulder-proxysql:6033)/incidents_sa_integration?parseTime=true&max_statement_time=15&long_query_time=12 dir: incidents_sa diff --git a/sa/db/dbconfig.mysql8.yml b/sa/db/dbconfig.mysql8.yml new file mode 100644 index 00000000000..732ac795e47 --- /dev/null +++ b/sa/db/dbconfig.mysql8.yml @@ -0,0 +1,20 @@ +# https://github.com/rubenv/sql-migrate#readme +boulder_sa_test: + dialect: mysql + datasource: root@tcp(boulder-vitess:33577)/boulder_sa_test?parseTime=true&max_execution_time=13000&long_query_time=11 + dir: boulder_sa + +boulder_sa_integration: + dialect: mysql + datasource: root@tcp(boulder-vitess:33577)/boulder_sa_integration?parseTime=true&max_execution_time=13000&long_query_time=11 + dir: boulder_sa + +incidents_sa_test: + dialect: mysql + datasource: root@tcp(boulder-vitess:33577)/incidents_sa_test?parseTime=true&max_execution_time=13000&long_query_time=11 + dir: incidents_sa + +incidents_sa_integration: + dialect: mysql + datasource: root@tcp(boulder-vitess:33577)/incidents_sa_integration?parseTime=true&max_execution_time=13000&long_query_time=11 + dir: incidents_sa diff --git a/sa/sa.go b/sa/sa.go index a9012d19a40..f925f037a6a 100644 --- a/sa/sa.go +++ b/sa/sa.go @@ -1222,6 +1222,14 @@ func (ssa *SQLStorageAuthority) UpdateCRLShard(ctx context.Context, req *sapb.Up nextUpdate = &nut } + // MariaDB and MySQL 8 handle fractional seconds differently in DATETIME(0) + // columns: MariaDB truncates while MySQL rounds to the nearest second. To + // ensure both databases store identical values, we explicitly truncate to + // whole seconds here. Without this, MySQL could round a timestamp up to the + // next second, causing a subsequent update within the same second to appear + // "older" and be rejected. + thisUpdate := req.ThisUpdate.AsTime().Truncate(time.Second) + _, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { res, err := tx.ExecContext(ctx, `UPDATE crlShards @@ -1230,12 +1238,12 @@ func (ssa *SQLStorageAuthority) UpdateCRLShard(ctx context.Context, req *sapb.Up AND idx = ? AND (thisUpdate is NULL OR thisUpdate <= ?) LIMIT 1`, - req.ThisUpdate.AsTime(), + thisUpdate, nextUpdate, - req.ThisUpdate.AsTime(), + thisUpdate, req.IssuerNameID, req.ShardIdx, - req.ThisUpdate.AsTime(), + thisUpdate, ) if err != nil { return nil, err diff --git a/sa/sysvars.go b/sa/sysvars.go index 6039c82e7f3..d7694e8f8e6 100644 --- a/sa/sysvars.go +++ b/sa/sysvars.go @@ -105,6 +105,7 @@ func checkMariaDBSystemVariables(name string, value string) error { "max_sort_length": {}, "max_sp_recursion_depth": {}, "max_statement_time": {}, + "max_execution_time": {}, "max_user_connections": {}, "min_examined_row_limit": {}, "mrr_buffer_size": {}, diff --git a/sa/sysvars_test.go b/sa/sysvars_test.go index 8c39b62350c..04ba6636198 100644 --- a/sa/sysvars_test.go +++ b/sa/sysvars_test.go @@ -15,7 +15,6 @@ func TestCheckMariaDBSystemVariables(t *testing.T) { for _, tc := range []testCase{ {"sql_select_limit", "'0.1", "requires a numeric value"}, - {"max_statement_time", "0", ""}, {"myBabies", "kids_I_tell_ya", "was unexpected"}, {"sql_mode", "'STRICT_ALL_TABLES", "string is not properly quoted"}, {"sql_mode", "%27STRICT_ALL_TABLES%27", "string is not properly quoted"}, diff --git a/test.sh b/test.sh index 81a9dccfda9..589b7e3c5ab 100755 --- a/test.sh +++ b/test.sh @@ -12,6 +12,7 @@ fi # Defaults # export RACE="false" +export USE_VITESS="false" STAGE="starting" STATUS="FAILURE" RUN=() @@ -21,6 +22,18 @@ INTEGRATION_FLAGS=() FILTER=() COVERAGE="false" COVERAGE_DIR="test/coverage/$(date +%Y-%m-%d_%H-%M-%S)" +DB_URL_FILES=( + backfiller_dburl + badkeyrevoker_dburl + cert_checker_dburl + expiration_mailer_dburl + incidents_dburl + mailer_dburl + ocsp_responder_dburl + revoker_dburl + sa_dburl + sa_ro_dburl +) # # Cleanup Functions @@ -78,6 +91,26 @@ function run_and_expect_silence() { rm "${result_file}" } +configure_database_endpoints() { + target_dir="mariadb" + dbconfig_target="dbconfig.mariadb.yml" + export MYSQL_ADDR="boulder-proxysql:6033" + + if [[ "${USE_VITESS}" == "true" ]] + then + target_dir="mysql8" + dbconfig_target="dbconfig.mysql8.yml" + export MYSQL_ADDR="boulder-vitess:33577" + fi + + rm -f "sa/db/dbconfig.yml" test/secrets/*.dburl || true + ( cd sa/db && ln -sf "${dbconfig_target}" "dbconfig.yml" ) + + for file in ${DB_URL_FILES:+${DB_URL_FILES[@]+"${DB_URL_FILES[@]}"}} + do + ( cd test/secrets && ln -sf "../dburls/${target_dir}/${file}" "${file}" ) + done +} # # Testing Helpers # @@ -121,11 +154,12 @@ With no options passed, runs standard battery of tests (lint, unit, and integrat Example: TestGenerateValidity/TestWFECORS -h, --help Shows this help message + -b --use-vitess Run tests against Vitess + MySQL 8.0 database EOM )" -while getopts luvwecisgnhd:p:f:-: OPT; do +while getopts luvwecisgnhbd:p:f:-: OPT; do if [ "$OPT" = - ]; then # long option: reformulate OPT and OPTARG OPT="${OPTARG%%=*}" # extract long option name OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty) @@ -145,6 +179,7 @@ while getopts luvwecisgnhd:p:f:-: OPT; do n | config-next ) BOULDER_CONFIG_DIR="test/config-next" ;; c | coverage ) COVERAGE="true" ;; d | coverage-dir ) check_arg; COVERAGE_DIR="${OPTARG}" ;; + b | use-vitess ) USE_VITESS="true" ;; h | help ) print_usage_exit ;; ??* ) exit_msg "Illegal option --$OPT" ;; # bad long option ? ) exit 2 ;; # bad short option (error reported via getopts) @@ -152,6 +187,9 @@ while getopts luvwecisgnhd:p:f:-: OPT; do done shift $((OPTIND-1)) # remove parsed options and args from $@ list +# Defaults to MariaDB unless USE_VITESS is true. +configure_database_endpoints + # The list of segments to run. Order doesn't matter. if [ -z "${RUN[@]+x}" ] then @@ -206,6 +244,7 @@ settings="$(cat -- <<-EOM FILTER: ${FILTER[@]} COVERAGE: $COVERAGE COVERAGE_DIR: $COVERAGE_DIR + USE_VITESS: $USE_VITESS EOM )" diff --git a/test/create_db.sh b/test/create_db.sh index d43a9ace116..3492eefde15 100755 --- a/test/create_db.sh +++ b/test/create_db.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -o errexit -cd $(dirname $0)/.. +cd "$(dirname "$0")/.." # If you modify DBS or ENVS, you must also modify the corresponding keys in @@ -40,32 +40,51 @@ function create_empty_db() { mysql ${dbconn} -e "${create_script}" || exit_err "unable to create ${db}" } +backend_label="${BACKEND_LABEL}" +mysql_host="${MYSQL_HOST}" +mysql_port="${MYSQL_PORT}" +skip_create="${SKIP_CREATE}" +skip_users="${SKIP_USERS}" + # set db connection for if running in a separate container or not dbconn="-u root" if [[ $MYSQL_CONTAINER ]] then - dbconn="-u root -h boulder-mysql --port 3306" + dbconn="-u root -h ${mysql_host} --port ${mysql_port}" fi -# MariaDB sets the default binlog_format to STATEMENT, -# which causes warnings that fail tests. Instead set it -# to the format we use in production, MIXED. -mysql ${dbconn} -e "SET GLOBAL binlog_format = 'MIXED';" +if ! mysql ${dbconn} -e "select 1" >/dev/null 2>&1; then + exit_err "unable to connect to ${mysql_host}:${mysql_port}" +fi -# MariaDB sets the default @@max_connections value to 100. The SA alone is -# configured to use up to 100 connections. We increase the max connections here -# to give headroom for other components. -mysql ${dbconn} -e "SET GLOBAL max_connections = 500;" +if [[ ${skip_create} -eq 0 ]] +then + # MariaDB sets the default binlog_format to STATEMENT, + # which causes warnings that fail tests. Instead set it + # to the format we use in production, MIXED. + mysql ${dbconn} -e "SET GLOBAL binlog_format = 'MIXED';" + + # MariaDB sets the default @@max_connections value to 100. The SA alone is + # configured to use up to 100 connections. We increase the max connections here + # to give headroom for other components. + mysql ${dbconn} -e "SET GLOBAL max_connections = 500;" +fi for db in $DBS; do for env in $ENVS; do dbname="${db}_${env}" print_heading "${dbname}" - if mysql ${dbconn} -e 'show databases;' | grep "${dbname}" > /dev/null; then - echo "Already exists - skipping create" + if [[ ${skip_create} -eq 0 ]] + then + if mysql ${dbconn} -e 'show databases;' | grep -q "${dbname}" + then + echo "Already exists - skipping create" + else + echo "Doesn't exist - creating" + create_empty_db "${dbname}" "${dbconn}" + fi else - echo "Doesn't exist - creating" - create_empty_db "${dbname}" "${dbconn}" + echo "Skipping database create for ${dbname}" fi if [[ "${BOULDER_CONFIG_DIR}" == "test/config-next" ]] @@ -78,27 +97,32 @@ for db in $DBS; do # sql-migrate will default to ./dbconfig.yml and treat all configured dirs # as relative. cd "${dbpath}" - r=`sql-migrate up -env="${dbname}" | xargs -0 echo` - if [[ "${r}" == "Migration failed"* ]] + result=$(sql-migrate up -env="${dbname}" | xargs -0 echo) + if [[ "${result}" == "Migration failed"* ]] then echo "Migration failed - dropping and recreating" create_empty_db "${dbname}" "${dbconn}" sql-migrate up -env="${dbname}" || exit_err "Migration failed after dropping and recreating" else - echo "${r}" + echo "${result}" fi USERS_SQL="../db-users/${db}.sql" - if [[ ${MYSQL_CONTAINER} ]] + if [[ ${skip_users} -eq 1 ]] then - sed -e "s/'localhost'/'%'/g" < ${USERS_SQL} | \ - mysql ${dbconn} -D "${dbname}" -f || exit_err "Unable to add users from ${USERS_SQL}" + echo "Skipping user grants for ${dbname}" else - sed -e "s/'localhost'/'127.%'/g" < $USERS_SQL | \ - mysql ${dbconn} -D "${dbname}" -f < $USERS_SQL || exit_err "Unable to add users from ${USERS_SQL}" + if [[ ${MYSQL_CONTAINER:-} ]] + then + sed -e "s/'localhost'/'%'/g" < "${USERS_SQL}" | \ + mysql ${dbconn} -D "${dbname}" -f || exit_err "Unable to add users from ${USERS_SQL}" + else + sed -e "s/'localhost'/'127.%'/g" < "${USERS_SQL}" | \ + mysql ${dbconn} -D "${dbname}" -f || exit_err "Unable to add users from ${USERS_SQL}" + fi + echo "Added users from ${USERS_SQL}" fi - echo "Added users from ${USERS_SQL}" - + # return to the root directory cd "${root_dir}" done diff --git a/test/db.go b/test/db.go index 13a4e5270f0..f42797b02a6 100644 --- a/test/db.go +++ b/test/db.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "io" + "os" "testing" ) @@ -40,7 +41,7 @@ func ResetIncidentsTestDatabase(t testing.TB) func() { } func resetTestDatabase(t testing.TB, ctx context.Context, dbPrefix string) func() { - db, err := sql.Open("mysql", fmt.Sprintf("test_setup@tcp(boulder-proxysql:6033)/%s_sa_test", dbPrefix)) + db, err := sql.Open("mysql", fmt.Sprintf("test_setup@tcp(%s)/%s_sa_test", os.Getenv("MYSQL_ADDR"), dbPrefix)) if err != nil { t.Fatalf("Couldn't create db: %s", err) } diff --git a/test/dburls/mariadb/backfiller_dburl b/test/dburls/mariadb/backfiller_dburl new file mode 100644 index 00000000000..84704344ffb --- /dev/null +++ b/test/dburls/mariadb/backfiller_dburl @@ -0,0 +1 @@ +sa@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/badkeyrevoker_dburl b/test/dburls/mariadb/badkeyrevoker_dburl new file mode 100644 index 00000000000..db3796cccd8 --- /dev/null +++ b/test/dburls/mariadb/badkeyrevoker_dburl @@ -0,0 +1 @@ +badkeyrevoker@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/cert_checker_dburl b/test/dburls/mariadb/cert_checker_dburl new file mode 100644 index 00000000000..7e6aee421b7 --- /dev/null +++ b/test/dburls/mariadb/cert_checker_dburl @@ -0,0 +1 @@ +cert_checker@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/expiration_mailer_dburl b/test/dburls/mariadb/expiration_mailer_dburl new file mode 100644 index 00000000000..c7855832a4b --- /dev/null +++ b/test/dburls/mariadb/expiration_mailer_dburl @@ -0,0 +1 @@ +mailer@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/incidents_dburl b/test/dburls/mariadb/incidents_dburl new file mode 100644 index 00000000000..31dede42932 --- /dev/null +++ b/test/dburls/mariadb/incidents_dburl @@ -0,0 +1 @@ +incidents_sa@tcp(boulder-proxysql:6033)/incidents_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/mailer_dburl b/test/dburls/mariadb/mailer_dburl new file mode 100644 index 00000000000..c7855832a4b --- /dev/null +++ b/test/dburls/mariadb/mailer_dburl @@ -0,0 +1 @@ +mailer@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/ocsp_responder_dburl b/test/dburls/mariadb/ocsp_responder_dburl new file mode 100644 index 00000000000..1976bb097a1 --- /dev/null +++ b/test/dburls/mariadb/ocsp_responder_dburl @@ -0,0 +1 @@ +ocsp_resp@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=800ms&writeTimeout=800ms&timeout=100ms&max_statement_time=0.76&long_query_time=0.64 diff --git a/test/dburls/mariadb/revoker_dburl b/test/dburls/mariadb/revoker_dburl new file mode 100644 index 00000000000..b53be2b35d9 --- /dev/null +++ b/test/dburls/mariadb/revoker_dburl @@ -0,0 +1 @@ +revoker@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/sa_dburl b/test/dburls/mariadb/sa_dburl new file mode 100644 index 00000000000..84704344ffb --- /dev/null +++ b/test/dburls/mariadb/sa_dburl @@ -0,0 +1 @@ +sa@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mariadb/sa_ro_dburl b/test/dburls/mariadb/sa_ro_dburl new file mode 100644 index 00000000000..640ad40b39f --- /dev/null +++ b/test/dburls/mariadb/sa_ro_dburl @@ -0,0 +1 @@ +sa_ro@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s&max_statement_time=13&long_query_time=11 diff --git a/test/dburls/mysql8/backfiller_dburl b/test/dburls/mysql8/backfiller_dburl new file mode 100644 index 00000000000..4d62653172a --- /dev/null +++ b/test/dburls/mysql8/backfiller_dburl @@ -0,0 +1 @@ +sa@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/badkeyrevoker_dburl b/test/dburls/mysql8/badkeyrevoker_dburl new file mode 100644 index 00000000000..6d6c7f88619 --- /dev/null +++ b/test/dburls/mysql8/badkeyrevoker_dburl @@ -0,0 +1 @@ +badkeyrevoker@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/cert_checker_dburl b/test/dburls/mysql8/cert_checker_dburl new file mode 100644 index 00000000000..1127940f6f9 --- /dev/null +++ b/test/dburls/mysql8/cert_checker_dburl @@ -0,0 +1 @@ +cert_checker@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/expiration_mailer_dburl b/test/dburls/mysql8/expiration_mailer_dburl new file mode 100644 index 00000000000..6c45d625b52 --- /dev/null +++ b/test/dburls/mysql8/expiration_mailer_dburl @@ -0,0 +1 @@ +mailer@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/incidents_dburl b/test/dburls/mysql8/incidents_dburl new file mode 100644 index 00000000000..abf47e5d453 --- /dev/null +++ b/test/dburls/mysql8/incidents_dburl @@ -0,0 +1 @@ +incidents_sa@tcp(boulder-vitess:33577)/incidents_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/mailer_dburl b/test/dburls/mysql8/mailer_dburl new file mode 100644 index 00000000000..6c45d625b52 --- /dev/null +++ b/test/dburls/mysql8/mailer_dburl @@ -0,0 +1 @@ +mailer@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/ocsp_responder_dburl b/test/dburls/mysql8/ocsp_responder_dburl new file mode 100644 index 00000000000..a9bc768ab67 --- /dev/null +++ b/test/dburls/mysql8/ocsp_responder_dburl @@ -0,0 +1 @@ +ocsp_resp@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=800ms&writeTimeout=800ms&timeout=100ms&max_execution_time=760&long_query_time=0.64 diff --git a/test/dburls/mysql8/revoker_dburl b/test/dburls/mysql8/revoker_dburl new file mode 100644 index 00000000000..50811d3dd97 --- /dev/null +++ b/test/dburls/mysql8/revoker_dburl @@ -0,0 +1 @@ +revoker@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/sa_dburl b/test/dburls/mysql8/sa_dburl new file mode 100644 index 00000000000..4d62653172a --- /dev/null +++ b/test/dburls/mysql8/sa_dburl @@ -0,0 +1 @@ +sa@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/dburls/mysql8/sa_ro_dburl b/test/dburls/mysql8/sa_ro_dburl new file mode 100644 index 00000000000..1e8bb0f01f7 --- /dev/null +++ b/test/dburls/mysql8/sa_ro_dburl @@ -0,0 +1 @@ +sa_ro@tcp(boulder-vitess:33577)/boulder_sa_integration?readTimeout=60s&writeTimeout=60s&timeout=5s&max_execution_time=13000&long_query_time=11 diff --git a/test/entrypoint.sh b/test/entrypoint.sh index 1d8c363c5d0..b37874bbeeb 100755 --- a/test/entrypoint.sh +++ b/test/entrypoint.sh @@ -10,17 +10,36 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" rm -f /var/run/rsyslogd.pid rsyslogd -# make sure we can reach the mysqldb. -./test/wait-for-it.sh boulder-mysql 3306 +# make sure we can reach mariadb and proxysql +./test/wait-for-it.sh boulder-mariadb 3306 +./test/wait-for-it.sh boulder-proxysql 6033 -# make sure we can reach the proxysql. -./test/wait-for-it.sh bproxysql 6032 +# make sure we can reach vitess +./test/wait-for-it.sh boulder-vitess 33577 # make sure we can reach pkilint ./test/wait-for-it.sh bpkimetal 8080 -# create the database -MYSQL_CONTAINER=1 $DIR/create_db.sh +# create the databases +( cd sa/db && ln -sf dbconfig.mariadb.yml dbconfig.yml ) +MYSQL_CONTAINER=1 \ +BACKEND_LABEL="MariaDB (via ProxySQL)" \ +MYSQL_HOST="boulder-mariadb" \ +MYSQL_PORT=3306 \ +SKIP_CREATE=0 \ +SKIP_USERS=0 \ +"$DIR/create_db.sh" + +( cd sa/db && ln -sf dbconfig.mysql8.yml dbconfig.yml ) +MYSQL_CONTAINER=1 \ +BACKEND_LABEL="Vitess" \ +MYSQL_HOST="boulder-vitess" \ +MYSQL_PORT=33577 \ +SKIP_CREATE=1 \ +SKIP_USERS=1 \ +"$DIR/create_db.sh" + +( cd sa/db && ln -sf dbconfig.mariadb.yml dbconfig.yml ) if [[ $# -eq 0 ]]; then exec python3 ./start.py diff --git a/test/integration/cert_storage_failed_test.go b/test/integration/cert_storage_failed_test.go index 603d3c342b3..db94990af72 100644 --- a/test/integration/cert_storage_failed_test.go +++ b/test/integration/cert_storage_failed_test.go @@ -74,23 +74,27 @@ func getPrecertByName(db *sql.DB, reversedName string) (*x509.Certificate, error func TestIssuanceCertStorageFailed(t *testing.T) { os.Setenv("DIRECTORY", "http://boulder.service.consul:4001/directory") - ctx := context.Background() - db, err := sql.Open("mysql", vars.DBConnSAIntegrationFullPerms) test.AssertNotError(t, err, "failed to open db connection") - _, err = db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`) - test.AssertNotError(t, err, "failed to drop trigger") - - // Make a specific insert into certificates fail, for this test but not others. - // To limit the effect to this one test, we make the trigger aware of a specific - // hostname used in this test. Since the INSERT to the certificates table - // doesn't include the hostname, we look it up in the issuedNames table, keyed - // off of the serial. - // NOTE: CREATE and DROP TRIGGER do not work in prepared statements. Go's - // database/sql will automatically try to use a prepared statement if you pass - // any arguments to Exec besides the query itself, so don't do that. - _, err = db.ExecContext(ctx, ` + if os.Getenv("USE_VITESS") == "false" { + // This block is only necessary for ProxySQL + MariaDB and can be + // deleted once we're fully migrated to Vitess + MySQL 8, where the + // trigger is installed via test/vtcomboserver/install_trigger.sh. + + ctx := context.Background() + _, err = db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`) + test.AssertNotError(t, err, "failed to drop trigger") + + // Make a specific insert into certificates fail, for this test but not others. + // To limit the effect to this one test, we make the trigger aware of a specific + // hostname used in this test. Since the INSERT to the certificates table + // doesn't include the hostname, we look it up in the issuedNames table, keyed + // off of the serial. + // NOTE: CREATE and DROP TRIGGER do not work in prepared statements. Go's + // database/sql will automatically try to use a prepared statement if you pass + // any arguments to Exec besides the query itself, so don't do that. + _, err = db.ExecContext(ctx, ` CREATE TRIGGER fail_ready BEFORE INSERT ON certificates FOR EACH ROW BEGIN @@ -105,9 +109,10 @@ func TestIssuanceCertStorageFailed(t *testing.T) { END IF; END `) - test.AssertNotError(t, err, "failed to create trigger") + test.AssertNotError(t, err, "failed to create trigger") - defer db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`) + defer db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`) + } certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "creating random cert key") diff --git a/test/proxysql/proxysql.cnf b/test/proxysql/proxysql.cnf index f918aa4538d..e8c7a68272a 100644 --- a/test/proxysql/proxysql.cnf +++ b/test/proxysql/proxysql.cnf @@ -56,7 +56,7 @@ mysql_variables = mysql_servers = ( { - address = "boulder-mysql"; + address = "boulder-mariadb"; port = 3306; hostgroup = 0; max_connections = 100; diff --git a/test/secrets/backfiller_dburl b/test/secrets/backfiller_dburl deleted file mode 100644 index b62d870a545..00000000000 --- a/test/secrets/backfiller_dburl +++ /dev/null @@ -1 +0,0 @@ -sa@tcp(boulder-proxysql:6033)/boulder_sa_integration diff --git a/test/secrets/badkeyrevoker_dburl b/test/secrets/badkeyrevoker_dburl deleted file mode 100644 index 51f90c093be..00000000000 --- a/test/secrets/badkeyrevoker_dburl +++ /dev/null @@ -1 +0,0 @@ -badkeyrevoker@tcp(boulder-proxysql:6033)/boulder_sa_integration diff --git a/test/secrets/cert_checker_dburl b/test/secrets/cert_checker_dburl deleted file mode 100644 index 16f6d8a8bf3..00000000000 --- a/test/secrets/cert_checker_dburl +++ /dev/null @@ -1 +0,0 @@ -cert_checker@tcp(boulder-proxysql:6033)/boulder_sa_integration diff --git a/test/secrets/expiration_mailer_dburl b/test/secrets/expiration_mailer_dburl deleted file mode 100644 index 615415cd8bb..00000000000 --- a/test/secrets/expiration_mailer_dburl +++ /dev/null @@ -1 +0,0 @@ -mailer@tcp(boulder-proxysql:6033)/boulder_sa_integration diff --git a/test/secrets/incidents_dburl b/test/secrets/incidents_dburl deleted file mode 100644 index 032afcfce71..00000000000 --- a/test/secrets/incidents_dburl +++ /dev/null @@ -1 +0,0 @@ -incidents_sa@tcp(boulder-proxysql:6033)/incidents_sa_integration?readTimeout=14s&timeout=1s diff --git a/test/secrets/mailer_dburl b/test/secrets/mailer_dburl deleted file mode 100644 index 615415cd8bb..00000000000 --- a/test/secrets/mailer_dburl +++ /dev/null @@ -1 +0,0 @@ -mailer@tcp(boulder-proxysql:6033)/boulder_sa_integration diff --git a/test/secrets/ocsp_responder_dburl b/test/secrets/ocsp_responder_dburl deleted file mode 100644 index 4a789bad0b1..00000000000 --- a/test/secrets/ocsp_responder_dburl +++ /dev/null @@ -1 +0,0 @@ -ocsp_resp@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=800ms&writeTimeout=800ms&timeout=100ms diff --git a/test/secrets/revoker_dburl b/test/secrets/revoker_dburl deleted file mode 100644 index 3e31508e869..00000000000 --- a/test/secrets/revoker_dburl +++ /dev/null @@ -1 +0,0 @@ -revoker@tcp(boulder-proxysql:6033)/boulder_sa_integration diff --git a/test/secrets/sa_dburl b/test/secrets/sa_dburl deleted file mode 100644 index 4da95057bd5..00000000000 --- a/test/secrets/sa_dburl +++ /dev/null @@ -1 +0,0 @@ -sa@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s diff --git a/test/secrets/sa_ro_dburl b/test/secrets/sa_ro_dburl deleted file mode 100644 index 8e6cc85b50e..00000000000 --- a/test/secrets/sa_ro_dburl +++ /dev/null @@ -1 +0,0 @@ -sa_ro@tcp(boulder-proxysql:6033)/boulder_sa_integration?readTimeout=14s&writeTimeout=14s&timeout=1s diff --git a/test/vars/vars.go b/test/vars/vars.go index deb2b56df95..3b4a584208c 100644 --- a/test/vars/vars.go +++ b/test/vars/vars.go @@ -1,25 +1,30 @@ package vars -import "fmt" - -const ( - dbURL = "%s@tcp(boulder-proxysql:6033)/%s" +import ( + "fmt" + "os" ) +var dbHost = os.Getenv("MYSQL_ADDR") + +func formatURL(user, database string) string { + return fmt.Sprintf("%s@tcp(%s)/%s", user, dbHost, database) +} + var ( // DBConnSA is the sa database connection - DBConnSA = fmt.Sprintf(dbURL, "sa", "boulder_sa_test") + DBConnSA = formatURL("sa", "boulder_sa_test") // DBConnSAMailer is the sa mailer database connection - DBConnSAMailer = fmt.Sprintf(dbURL, "mailer", "boulder_sa_test") + DBConnSAMailer = formatURL("mailer", "boulder_sa_test") // DBConnSAFullPerms is the sa database connection with full perms - DBConnSAFullPerms = fmt.Sprintf(dbURL, "test_setup", "boulder_sa_test") + DBConnSAFullPerms = formatURL("test_setup", "boulder_sa_test") // DBConnSAIntegrationFullPerms is the sa database connection for the // integration test DB, with full perms - DBConnSAIntegrationFullPerms = fmt.Sprintf(dbURL, "test_setup", "boulder_sa_integration") + DBConnSAIntegrationFullPerms = formatURL("test_setup", "boulder_sa_integration") // DBInfoSchemaRoot is the root user and the information_schema connection. - DBInfoSchemaRoot = fmt.Sprintf(dbURL, "root", "information_schema") + DBInfoSchemaRoot = formatURL("root", "information_schema") // DBConnIncidents is the incidents database connection. - DBConnIncidents = fmt.Sprintf(dbURL, "incidents_sa", "incidents_sa_test") + DBConnIncidents = formatURL("incidents_sa", "incidents_sa_test") // DBConnIncidentsFullPerms is the incidents database connection with full perms. - DBConnIncidentsFullPerms = fmt.Sprintf(dbURL, "test_setup", "incidents_sa_test") + DBConnIncidentsFullPerms = formatURL("test_setup", "incidents_sa_test") ) diff --git a/test/vtcomboserver/Dockerfile b/test/vtcomboserver/Dockerfile new file mode 100644 index 00000000000..a1e81c54f14 --- /dev/null +++ b/test/vtcomboserver/Dockerfile @@ -0,0 +1,65 @@ +# syntax=docker/dockerfile:1 +ARG VITESS_TAG=v22.0.0 +ARG MYSQL_BASE_IMAGE=mysql/mysql-server:8.0 + +FROM golang:1.25.2-bookworm AS build +ARG VITESS_TAG +ENV CGO_ENABLED=0 + +# Largely cobbled together from Vitess's own Dockerfiles and install scripts: +# - https://github.com/vitessio/vitess/blob/v22.0.1/docker/lite/Dockerfile +# - https://github.com/vitessio/vitess/blob/v22.0.1/docker/lite/Dockerfile.mysql84 +# - https://github.com/vitessio/vitess/blob/v22.0.1/docker/utils/install_dependencies.sh +# - https://github.com/vitessio/vitess/blob/v22.0.1/docker/vttestserver/Dockerfile.mysql80 + +ENV VTROOT=/vt +WORKDIR /src + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone --depth 1 --branch "${VITESS_TAG}" https://github.com/vitessio/vitess.git . + +# Build only the binaries we need +RUN go build -trimpath -o /out/vtcombo ./go/cmd/vtcombo +RUN go build -trimpath -o /out/mysqlctl ./go/cmd/mysqlctl + +FROM ${MYSQL_BASE_IMAGE} AS runtime + +# Vitess expects to run as a non-root user +RUN groupadd -r vitess && useradd -r -g vitess vitess + +ENV VTROOT=/vt \ + VTDATAROOT=/vt/vtdataroot \ + PATH=/vt/bin:$PATH + +WORKDIR /vt + +RUN mkdir -p /vt/vtdataroot /vt/bin /vt/config/mycnf /vt/web \ + && chown -R vitess:vitess /vt + +# Copy only the binaries we need +COPY --from=build /out/vtcombo /vt/bin/vtcombo +COPY --from=build /out/mysqlctl /vt/bin/mysqlctl +RUN chmod +x /vt/bin/* && chown -R vitess:vitess /vt/bin + +# Copy other necessary files +COPY --from=build --chown=vitess:vitess /src/config/init_db.sql /vt/config/ +COPY --from=build --chown=vitess:vitess /src/config/mycnf /vt/config/mycnf +COPY --from=build --chown=vitess:vitess /src/web/vtadmin /vt/web/vtadmin + +# Copy install script we use to trigger failures in our integration tests +COPY --chown=vitess:vitess install_trigger.sh /vt/install_trigger.sh +RUN chmod +x /vt/install_trigger.sh + +VOLUME /vt/vtdataroot + +COPY setup_vschema_folder.sh /vt/setup_vschema_folder.sh +COPY run.sh /vt/run.sh +RUN chmod +x /vt/setup_vschema_folder.sh /vt/run.sh \ + && chown vitess:vitess /vt/setup_vschema_folder.sh /vt/run.sh + +USER vitess + +CMD ["/vt/run.sh", "8.0.40-Vitess"] diff --git a/test/vtcomboserver/install_trigger.sh b/test/vtcomboserver/install_trigger.sh new file mode 100644 index 00000000000..e07a3ddeb48 --- /dev/null +++ b/test/vtcomboserver/install_trigger.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +set -eu + +VT_DB="vt_${MYSQL_DATABASE:-boulder_sa_integration}_0" +SOCK="/vt/vtdataroot/vt_0000000001/mysql.sock" +MYSQL_ARGS=(-uroot -S "$SOCK") +TIMEOUT=120 + +# +# Helpers +# + +exit_msg() { + echo "$*" >&2 + exit 2 +} + +table_exists() { + local result + result="$(mysql "${MYSQL_ARGS[@]}" -Nse \ + "SELECT 1 FROM information_schema.tables WHERE table_schema='${VT_DB}' AND table_name='certificates' LIMIT 1" \ + || true)" + [ "$result" = "1" ] +} + +# Wait for the certificates table to be created +printf '[install_trigger] waiting for %s.certificates to appear...\n' "$VT_DB" + +i=0 +while [ "$i" -lt "$TIMEOUT" ] +do + if table_exists + then + break + fi + sleep 1 + i=$((i+1)) +done + +if ! table_exists +then + exit_msg "[install_trigger] ERROR: ${VT_DB}.certificates not found after ${TIMEOUT}s" +fi + +# Install trigger that simulates an error when inserting into the certificates +# table for TestIssuanceCertStorageFailed in /test/integration/cert_storage_failed_test.go. +printf '[install_trigger] installing trigger on %s.certificates\n' "$VT_DB" +mysql "${MYSQL_ARGS[@]}" "$VT_DB" <<'SQL' +DELIMITER $$ +DROP TRIGGER IF EXISTS fail_ready $$ +CREATE TRIGGER fail_ready +BEFORE INSERT ON certificates +FOR EACH ROW +BEGIN + DECLARE reversedName1 VARCHAR(255); + SELECT reversedName INTO reversedName1 + FROM issuedNames + WHERE serial = NEW.serial + AND reversedName LIKE 'com.wantserror.%'; + IF reversedName1 IS NOT NULL AND reversedName1 != '' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Pretend there was an error inserting into certificates'; + END IF; +END $$ +DELIMITER ; +SQL + +printf '[install_trigger] done\n' diff --git a/test/vtcomboserver/run.sh b/test/vtcomboserver/run.sh new file mode 100755 index 00000000000..4fd7f96ecd5 --- /dev/null +++ b/test/vtcomboserver/run.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Much of the below is adapted from upstream Vitess's vttestserver run.sh +# but instead of using vttestserver, we use vtcombo directly: +# https://github.com/vitessio/vitess/blob/v22.0.1/docker/vttestserver/run.sh + +# Copyright 2021 The Vitess Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Setup the Vschema Folder +/vt/setup_vschema_folder.sh "$KEYSPACES" "$NUM_SHARDS" + +# Set the maximum connections in the cnf file +# use 1000 as the default if it is unspecified +if [[ -z $MYSQL_MAX_CONNECTIONS ]]; then + MYSQL_MAX_CONNECTIONS=1000 +fi +echo "max_connections = $MYSQL_MAX_CONNECTIONS" >> /vt/config/mycnf/test-suite.cnf + +# Delete socket files before running mysqlctld if exists. +# This is the primary reason for unhealthy state on restart. +# https://github.com/vitessio/vitess/pull/5115/files +rm -vf "$VTDATAROOT"/"$tablet_dir"/{mysql.sock,mysql.sock.lock} + +# Kick off script to install trigger we use to simulate +# errors in our integration tests, in the background. +/vt/install_trigger.sh & + +# Create Vitess JSON topo, start vtcombo and mysql. For more details see: +# - https://vitess.io/docs/22.0/reference/programs/vtcombo +# - https://github.com/vitessio/vitess/blob/v22.0.1/go/vt/vttest/vtprocess.go +# - https://github.com/vitessio/vitess/blob/v22.0.1/proto/vttest.proto +/vt/bin/vtcombo \ + --port "${PORT:-33574}" \ + --bind-address "${VTCOMBO_BIND_HOST:-0.0.0.0}" \ + --mysql_server_bind_address "${MYSQL_SERVER_BIND_ADDRESS:-0.0.0.0}" \ + --mysql_server_port "${MYSQL_SERVER_PORT:-33577}" \ + --mysql_auth_server_impl "none" \ + --mysql_server_version "${MYSQL_SERVER_VERSION:-8.0.40-Vitess}" \ + --db_charset "${CHARSET:-utf8mb4}" \ + --foreign_key_mode "${FOREIGN_KEY_MODE:-allow}" \ + --enable_online_ddl \ + --enable_direct_ddl \ + --planner-version "${PLANNER_VERSION:-gen4}" \ + --vschema_ddl_authorized_users "${VSCHEMA_DDL_AUTH_USERS:-%}" \ + --tablet_refresh_interval "${TABLET_REFRESH_INTERVAL:-10s}" \ + --schema_dir "/vt/schema" \ + --queryserver-config-max-result-size "${QUERY_MAX_RESULT_SIZE:-1000000}" \ + --queryserver-config-warn-result-size "${QUERY_WARN_RESULT_SIZE:-1000000}" \ + --normalize_queries \ + --queryserver-config-pool-size 64 \ + --queryserver-config-stream-pool-size 200 \ + --queryserver-config-transaction-cap 80 \ + --queryserver-config-query-timeout 300s \ + --queryserver-config-schema-reload-time 60s \ + --queryserver-config-txpool-timeout 300s \ + --json_topo "$(printf '{"cells":["test"],"keyspaces":[%s]}' \ + "$(IFS=, read -ra ks <<< "${KEYSPACES}"; \ + for i in "${!ks[@]}"; do \ + printf '%s{"name":"%s","shards":[{"name":"0"}]}' \ + "$([ $i -gt 0 ] && echo ,)" "${ks[$i]}"; \ + done)")" \ + --start_mysql diff --git a/test/vtcomboserver/setup_vschema_folder.sh b/test/vtcomboserver/setup_vschema_folder.sh new file mode 100755 index 00000000000..248e292b021 --- /dev/null +++ b/test/vtcomboserver/setup_vschema_folder.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Copied from Vitess's own setup_vschema_folder.sh: +# https://github.com/vitessio/vitess/blob/v22.0.1/docker/vttestserver/setup_vschema_folder.sh + +# Copyright 2021 The Vitess Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# getLength gets the number of elements in a comma separated string +function getLength() { + COUNT=0 + for _ in ${1//,/ } + do + ((COUNT++)) + done + echo "$COUNT" +} + +# The first argument to the file is a comma separated list of keyspaces and the second argument is the comma separated list of number of shards. + +KEYSPACES="$1" +NUM_SHARDS="$2" + +COUNT_KEYSPACES=$(getLength "$KEYSPACES") +COUNT_NUM_SHARDS=$(getLength "$NUM_SHARDS") + +# Incase the number of keyspaces and num_shards do not match, throw an error +if [ "$COUNT_KEYSPACES" != "$COUNT_NUM_SHARDS" ]; then + echo "Incompatible list of keyspaces and number of shards" + exit 1 +fi + +# Convert the strings to lists +read -ra KEYSPACES_LIST <<<"${KEYSPACES//,/ }" +read -ra NUM_SHARDS_LIST <<<"${NUM_SHARDS//,/ }" + +# create the main schema directory +mkdir -p /vt/schema/ + +i=0; +for keyspace in "${KEYSPACES_LIST[@]}"; do + # create a directory for each keyspace + mkdir -p "/vt/schema/$keyspace" + num_shard=${NUM_SHARDS_LIST[$i]} + # Create a vschema.json file only if the number of shards are more than 1 + if [[ $num_shard -gt "1" ]]; then + printf "{\n\t\"sharded\": true\n}\n" > "/vt/schema/$keyspace/vschema.json" + fi + ((i++)) +done diff --git a/test/vtcomboserver/tag_and_upload.sh b/test/vtcomboserver/tag_and_upload.sh new file mode 100755 index 00000000000..56b4ffe1e4f --- /dev/null +++ b/test/vtcomboserver/tag_and_upload.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -feuxo pipefail + +cd $(dirname $0) + +DATESTAMP=$(date +%Y-%m-%d) +DOCKER_REPO="letsencrypt/boulder-vtcomboserver" +VITESS_TAG=v22.0.0 + +echo "Please login to allow push to DockerHub" +docker login + +# Usage: build_and_push_image $VITESS_TAG +build_and_push_image() { + VITESS_TAG="$1" + TAG_NAME="${DOCKER_REPO}:vitess${VITESS_TAG}_${DATESTAMP}" + echo "Building boulder-vtcomboserver image ${TAG_NAME}" + + # build, tag, and push the image. + docker buildx build \ + --build-arg "VITESS_TAG=${VITESS_TAG}" \ + --progress plain \ + --push \ + --tag "${TAG_NAME}" \ + --platform "linux/amd64" \ + . +} + +build_and_push_image $VITESS_TAG