diff --git a/.github/workflows/upgrade_downgrade_test_backups_e2e.yml b/.github/workflows/upgrade_downgrade_test_backups_e2e.yml index bde1a3283f7..e7e815050e5 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_e2e.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_e2e.yml @@ -161,8 +161,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vttablet --version # Run test with VTTablet at version N-1 and VTBackup at version N @@ -181,9 +183,11 @@ jobs: run: | source build.env - rm -f $PWD/bin/vtbackup $PWD/bin/vttablet + rm -f $PWD/bin/vtbackup $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vtbackup $PWD/bin/vtbackup cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtbackup --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml b/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml index d92da4088e0..a6cf20d0ed7 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml @@ -164,8 +164,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vttablet --version # Run test with VTTablet at version N+1 and VTBackup at version N @@ -184,9 +186,11 @@ jobs: run: | source build.env - rm -f $PWD/bin/vtbackup $PWD/bin/vttablet - cp /tmp/vitess-build-current/bin/vtbackup $PWD/bin/vtbackup - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + rm -f $PWD/bin/vtbackup $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld + cp /tmp/vitess-build-other/bin/vtbackup $PWD/bin/vtbackup + cp /tmp/vitess-build-current/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-current/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-current/bin/mysqlctld $PWD/bin/mysqlctld vtbackup --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_backups_manual.yml b/.github/workflows/upgrade_downgrade_test_backups_manual.yml index 6201d9b49ba..1cacf55a22e 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_manual.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_manual.yml @@ -234,8 +234,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vttablet --version # Starting the tablets again, they will automatically start restoring the last backup. @@ -290,8 +292,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-current/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-current/bin/mysqlctld $PWD/bin/mysqlctld vttablet --version # Starting the tablets again and restoring the previous backup. diff --git a/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml b/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml index dc0ad902478..87eda3d9648 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml @@ -237,8 +237,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vttablet --version # Starting the tablets again, they will automatically start restoring the last backup. @@ -293,8 +295,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-current/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-current/bin/mysqlctld $PWD/bin/mysqlctld vttablet --version # Starting the tablets again and restoring the next backup. diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml index 2d8aceb6ceb..c0c240b0944 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml @@ -209,9 +209,11 @@ jobs: run: | source build.env - rm -f $PWD/bin/vtgate $PWD/bin/vttablet + rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtgate --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml index 7de07a3892d..c2c06e274b8 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml @@ -212,9 +212,11 @@ jobs: run: | source build.env - rm -f $PWD/bin/vtgate $PWD/bin/vttablet + rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtgate --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml b/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml index 5b8e3107757..f7820c59739 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml @@ -209,9 +209,11 @@ jobs: run: | source build.env - rm -f $PWD/bin/vtgate $PWD/bin/vttablet + rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtgate --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml b/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml index 18075b62e0f..7e44b3e0e54 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml @@ -212,9 +212,11 @@ jobs: run: | source build.env - rm -f $PWD/bin/vtgate $PWD/bin/vttablet + rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtgate --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml b/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml index 6cc0f1d9882..b5e5d67b5e8 100644 --- a/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml +++ b/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml @@ -182,8 +182,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtctl --version vttablet --version diff --git a/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml b/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml index bbb17c43e99..4029a082069 100644 --- a/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml +++ b/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml @@ -179,8 +179,10 @@ jobs: run: | source build.env - rm -f $PWD/bin/vttablet + rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet + cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl + cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld vtctl --version vttablet --version diff --git a/config/init_db.sql b/config/init_db.sql index 7be4de6f7ea..d04960633de 100644 --- a/config/init_db.sql +++ b/config/init_db.sql @@ -1,5 +1,4 @@ -# This file is executed immediately after mysql_install_db, -# to initialize a fresh data directory. +# This file is executed immediately after initializing a fresh data directory. ############################################################################### # WARNING: This sql is *NOT* safe for production use, @@ -11,6 +10,12 @@ ############################################################################### # Equivalent of mysql_secure_installation ############################################################################### +# We need to ensure that super_read_only is disabled so that we can execute +# these commands. Note that disabling it does NOT disable read_only. +# We save the current value so that we only re-enable it at the end if it was +# enabled before. +SET @original_super_read_only=IF(@@global.super_read_only=1, 'ON', 'OFF'); +SET GLOBAL super_read_only='OFF'; # Changes during the init db should not make it to the binlog. # They could potentially create errant transactions on replicas. @@ -77,3 +82,9 @@ FLUSH PRIVILEGES; RESET SLAVE ALL; RESET MASTER; + +# custom sql is used to add custom scripts like creating users/passwords. We use it in our tests +# {{custom_sql}} + +# We need to set super_read_only back to what it was before +SET GLOBAL super_read_only=IFNULL(@original_super_read_only, 'ON'); diff --git a/config/mycnf/default.cnf b/config/mycnf/default.cnf index 0a375cb69c7..c17165f9959 100644 --- a/config/mycnf/default.cnf +++ b/config/mycnf/default.cnf @@ -14,9 +14,6 @@ port = {{.MysqlPort}} secure-file-priv = {{.SecureFilePriv}} {{end}} -# all db instances should start in read-only mode - once the db is started and -# fully functional, we'll push it into read-write mode -read-only server-id = {{.ServerID}} # all db instances should skip starting replication threads - that way we can do any diff --git a/config/mycnf/mariadb10.cnf b/config/mycnf/mariadb10.cnf index 1912cd8e154..120bd7b7d00 100644 --- a/config/mycnf/mariadb10.cnf +++ b/config/mycnf/mariadb10.cnf @@ -39,3 +39,5 @@ slave_net_timeout = 60 character_set_server = utf8 collation_server = utf8_general_ci +# All MariaDB instances should start in read-only mode +read-only diff --git a/config/mycnf/mysql57.cnf b/config/mycnf/mysql57.cnf index 7a8c45a187c..44c462749a7 100644 --- a/config/mycnf/mysql57.cnf +++ b/config/mycnf/mysql57.cnf @@ -32,3 +32,7 @@ plugin-load = rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisy rpl_semi_sync_master_timeout = 1000000000000000000 rpl_semi_sync_master_wait_no_slave = 1 +# In order to protect against any errand GTIDs we will start the mysql instance +# in super-read-only mode. +super-read-only + diff --git a/config/mycnf/mysql80.cnf b/config/mycnf/mysql80.cnf index 39fab576533..13447a7de0a 100644 --- a/config/mycnf/mysql80.cnf +++ b/config/mycnf/mysql80.cnf @@ -28,3 +28,7 @@ plugin-load = rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisy loose_rpl_semi_sync_master_timeout = 1000000000000000000 loose_rpl_semi_sync_master_wait_no_slave = 1 +# In order to protect against any errand GTIDs we will start the mysql instance +# in super-read-only mode. +super-read-only + diff --git a/config/mycnf/test-suite.cnf b/config/mycnf/test-suite.cnf index e57368a41db..e6d0992f6e6 100644 --- a/config/mycnf/test-suite.cnf +++ b/config/mycnf/test-suite.cnf @@ -23,3 +23,6 @@ sql_mode = STRICT_TRANS_TABLES # set a short heartbeat interval in order to detect failures quickly slave_net_timeout = 4 +# Disabling `super-read-only`. `test-suite` is mainly used for `vttestserver`. Since `vttestserver` uses a single MySQL for primary and replicas, +# so it is not possible to run it with `super-read-only`. Therefore, we are disabling it. +super-read-only = false diff --git a/doc/releasenotes/17_0_0_summary.md b/doc/releasenotes/17_0_0_summary.md index bacc18506e6..7b84459a42d 100644 --- a/doc/releasenotes/17_0_0_summary.md +++ b/doc/releasenotes/17_0_0_summary.md @@ -11,6 +11,9 @@ - [Detailed backup and restore stats](#detailed-backup-and-restore-stats) - **[Deprecations and Deletions](#deprecations-and-deletions)** - [Deprecated Stats](#deprecated-stats) + - **[VTTablet](#vttablet)** + - [VTTablet: Initializing all replicas with super_read_only](#vttablet-initialization) + - [Deprecated Flags](#deprecated-flags) ## Major Changes @@ -197,4 +200,15 @@ These stats are deprecated in v17. | Deprecated stat | Supported alternatives | |-|-| | `backup_duration_seconds` | `BackupDurationNanoseconds` | -| `restore_duration_seconds` | `RestoreDurationNanoseconds` | \ No newline at end of file +| `restore_duration_seconds` | `RestoreDurationNanoseconds` | +### VTTablet +#### Initializing all replicas with super_read_only +In order to prevent SUPER privileged users like `root` or `vt_dba` from producing errant GTIDs on replicas, all the replica MySQL servers are initialized with the MySQL +global variable `super_read_only` value set to `ON`. During failovers, we set `super_read_only` to `OFF` for the promoted primary tablet. This will allow the +primary to accept writes. All of the shard's tablets, except the current primary, will still have their global variable `super_read_only` set to `ON`. This will make sure that apart from +MySQL replication no other component, offline system or operator can write directly to a replica. + +Reference PR for this change is [PR #12206](https://github.com/vitessio/vitess/pull/12206) + +#### Deprecated Flags +The flag `use_super_read_only` is deprecated and will be removed in a later release. diff --git a/go/cmd/vtbackup/vtbackup.go b/go/cmd/vtbackup/vtbackup.go index 5ea15f728fd..8f5a39e8d0c 100644 --- a/go/cmd/vtbackup/vtbackup.go +++ b/go/cmd/vtbackup/vtbackup.go @@ -312,6 +312,19 @@ func takeBackup(ctx context.Context, topoServer *topo.Server, backupStorage back if err := mysqld.ResetReplication(ctx); err != nil { return fmt.Errorf("can't reset replication: %v", err) } + // We need to switch off super_read_only before we create the database. + resetFunc, err := mysqld.SetSuperReadOnly(false) + if err != nil { + return fmt.Errorf("failed to disable super_read_only during backup: %v", err) + } + if resetFunc != nil { + defer func() { + err := resetFunc() + if err != nil { + log.Error("Failed to set super_read_only back to its original value during backup") + } + }() + } cmd := mysqlctl.GenerateInitialBinlogEntry() if err := mysqld.ExecuteSuperQueryList(ctx, []string{cmd}); err != nil { return err @@ -404,6 +417,8 @@ func takeBackup(ctx context.Context, topoServer *topo.Server, backupStorage back return err } + log.Infof("takeBackup: primary position is: %s", primaryPos.String()) + // Remember the time when we fetched the primary position, not when we caught // up to it, so the timestamp on our backup is honest (assuming we make it // to the goal position). diff --git a/go/flags/endtoend/vttablet.txt b/go/flags/endtoend/vttablet.txt index ec9cdb1a277..0b0ab67a7aa 100644 --- a/go/flags/endtoend/vttablet.txt +++ b/go/flags/endtoend/vttablet.txt @@ -348,7 +348,6 @@ Usage of vttablet: --tx_throttler_config string The configuration of the transaction throttler as a text formatted throttlerdata.Configuration protocol buffer message (default "target_replication_lag_sec: 2\nmax_replication_lag_sec: 10\ninitial_rate: 100\nmax_increase: 1\nemergency_decrease: 0.5\nmin_duration_between_increases_sec: 40\nmax_duration_between_increases_sec: 62\nmin_duration_between_decreases_sec: 20\nspread_backlog_across_sec: 20\nage_bad_rate_after_sec: 180\nbad_rate_increase: 0.1\nmax_rate_approach_threshold: 0.9\n") --tx_throttler_healthcheck_cells strings A comma-separated list of cells. Only tabletservers running in these cells will be monitored for replication lag by the transaction throttler. --unhealthy_threshold duration replication lag after which a replica is considered unhealthy (default 2h0m0s) - --use_super_read_only Set super_read_only flag when performing planned failover. --v Level log level for V logs -v, --version print binary version --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging diff --git a/go/mysql/fakesqldb/server.go b/go/mysql/fakesqldb/server.go index 5b00b2c2e01..e40d092e244 100644 --- a/go/mysql/fakesqldb/server.go +++ b/go/mysql/fakesqldb/server.go @@ -783,3 +783,40 @@ func (db *DB) MockQueriesForTable(table string, result *sqltypes.Result) { cols..., )) } + +// GetRejectedQueryResult checks if we should reject the query. +func (db *DB) GetRejectedQueryResult(key string) error { + if err, ok := db.rejectedData[key]; ok { + return err + } + + return nil +} + +// GetQueryResult checks for explicit queries add through AddQuery(). +func (db *DB) GetQueryResult(key string) *ExpectedResult { + result, ok := db.data[key] + if ok { + return result + } + return nil +} + +// GetQueryPatternResult checks if a query matches any pattern previously added using AddQueryPattern(). +func (db *DB) GetQueryPatternResult(key string) (func(string), ExpectedResult, bool, error) { + for _, pat := range db.patternData { + if pat.expr.MatchString(key) { + userCallback, ok := db.queryPatternUserCallback[pat.expr] + if ok { + if pat.err != "" { + return userCallback, ExpectedResult{pat.result, nil}, true, fmt.Errorf(pat.err) + } + return userCallback, ExpectedResult{pat.result, nil}, true, nil + } + + return nil, ExpectedResult{nil, nil}, false, nil + } + } + + return nil, ExpectedResult{nil, nil}, false, nil +} diff --git a/go/test/endtoend/backup/pitr/backup_mysqlctld_pitr_test.go b/go/test/endtoend/backup/pitr/backup_mysqlctld_pitr_test.go index f93dfa475b6..dfb869b4de2 100644 --- a/go/test/endtoend/backup/pitr/backup_mysqlctld_pitr_test.go +++ b/go/test/endtoend/backup/pitr/backup_mysqlctld_pitr_test.go @@ -138,9 +138,9 @@ func TestIncrementalBackupMysqlctld(t *testing.T) { if tc.writeBeforeBackup { backup.InsertRowOnPrimary(t, "") } - // we wait for 1 second because backups ar ewritten to a directory named after the current timestamp, - // in 1 second resolution. We want to aoid two backups that have the same pathname. Realistically this - // is only ever a problem in this endtoend test, not in production. + // we wait for 1 second because backups are written to a directory named after the current timestamp, + // in 1 second resolution. We want to avoid two backups that have the same pathname. Realistically this + // is only ever a problem in this end-to-end test, not in production. // Also, we gie the replica a chance to catch up. time.Sleep(1100 * time.Millisecond) waitForReplica(t) diff --git a/go/test/endtoend/backup/vtbackup/backup_only_test.go b/go/test/endtoend/backup/vtbackup/backup_only_test.go index 3730a1fa586..408cc64a21b 100644 --- a/go/test/endtoend/backup/vtbackup/backup_only_test.go +++ b/go/test/endtoend/backup/vtbackup/backup_only_test.go @@ -18,6 +18,7 @@ package vtbackup import ( "context" + "encoding/json" "fmt" "os" "path" @@ -56,18 +57,29 @@ func TestTabletInitialBackup(t *testing.T) { // - list the backups, remove them defer cluster.PanicHandler(t) + waitForReplicationToCatchup([]cluster.Vttablet{*replica1, *replica2}) + vtBackup(t, true, false, false) verifyBackupCount(t, shardKsName, 1) // Initialize the tablets initTablets(t, false, false) - // Restore the Tablets + vtTabletVersion, err := cluster.GetMajorVersion("vttablet") + require.NoError(t, err) + // For all version at or above v17.0.0, each replica will start in super_read_only mode. Let's verify that is working correctly. + if vtTabletVersion >= 17 { + err := primary.VttabletProcess.CreateDB("testDB") + require.ErrorContains(t, err, "The MySQL server is running with the --super-read-only option so it cannot execute this statement") + err = replica1.VttabletProcess.CreateDB("testDB") + require.ErrorContains(t, err, "The MySQL server is running with the --super-read-only option so it cannot execute this statement") + } + // Restore the Tablet restore(t, primary, "replica", "NOT_SERVING") // Vitess expects that the user has set the database into ReadWrite mode before calling // TabletExternallyReparented - err := localCluster.VtctlclientProcess.ExecuteCommand( + err = localCluster.VtctlclientProcess.ExecuteCommand( "SetReadWrite", primary.Alias) require.Nil(t, err) err = localCluster.VtctlclientProcess.ExecuteCommand( @@ -254,19 +266,15 @@ func initTablets(t *testing.T, startTablet bool, initShardPrimary bool) { func restore(t *testing.T, tablet *cluster.Vttablet, tabletType string, waitForState string) { // Erase mysql/tablet dir, then start tablet with restore enabled. - log.Infof("restoring tablet %s", time.Now()) resetTabletDirectory(t, *tablet, true) - err := tablet.VttabletProcess.CreateDB(keyspaceName) - require.Nil(t, err) - // Start tablets tablet.VttabletProcess.ExtraArgs = []string{"--db-credentials-file", dbCredentialFile} tablet.VttabletProcess.TabletType = tabletType tablet.VttabletProcess.ServingStatus = waitForState tablet.VttabletProcess.SupportsBackup = true - err = tablet.VttabletProcess.Setup() + err := tablet.VttabletProcess.Setup() require.Nil(t, err) } @@ -294,6 +302,12 @@ func resetTabletDirectory(t *testing.T, tablet cluster.Vttablet, initMysql bool) func tearDown(t *testing.T, initMysql bool) { // reset replication + for _, db := range []string{"_vt", "vt_insert_test"} { + _, err := primary.VttabletProcess.QueryTablet(fmt.Sprintf("drop database if exists %s", db), keyspaceName, true) + require.Nil(t, err) + } + caughtUp := waitForReplicationToCatchup([]cluster.Vttablet{*replica1, *replica2}) + require.True(t, caughtUp, "Timed out waiting for all replicas to catch up") promoteCommands := "STOP SLAVE; RESET SLAVE ALL; RESET MASTER;" disableSemiSyncCommands := "SET GLOBAL rpl_semi_sync_master_enabled = false; SET GLOBAL rpl_semi_sync_slave_enabled = false" for _, tablet := range []cluster.Vttablet{*primary, *replica1, *replica2} { @@ -301,10 +315,6 @@ func tearDown(t *testing.T, initMysql bool) { require.Nil(t, err) _, err = tablet.VttabletProcess.QueryTablet(disableSemiSyncCommands, keyspaceName, true) require.Nil(t, err) - for _, db := range []string{"_vt", "vt_insert_test"} { - _, err = tablet.VttabletProcess.QueryTablet(fmt.Sprintf("drop database if exists %s", db), keyspaceName, true) - require.Nil(t, err) - } } // TODO: Ideally we should not be resetting the mysql. @@ -367,3 +377,39 @@ func verifyDisableEnableRedoLogs(ctx context.Context, t *testing.T, mysqlSocket } } } + +// This helper function wait for all replicas to catch-up the replication. +// It does this by querying the status detail url of each replica and find the lag. +func waitForReplicationToCatchup(tablets []cluster.Vttablet) bool { + endTime := time.Now().Add(time.Second * 30) + timeout := time.After(time.Until(endTime)) + // key-value structure returned by status url. + type kv struct { + Key string + Class string + Value string + } + // defining a struct instance + var statuslst []kv + for { + select { + case <-timeout: + return false + default: + var replicaCount = 0 + for _, tablet := range tablets { + status := tablet.VttabletProcess.GetStatusDetails() + json.Unmarshal([]byte(status), &statuslst) + for _, obj := range statuslst { + if obj.Key == "Replication Lag" && obj.Value == "0s" { + replicaCount++ + } + } + if replicaCount == len(tablets) { + return true + } + } + time.Sleep(time.Second * 1) + } + } +} diff --git a/go/test/endtoend/backup/vtbackup/main_test.go b/go/test/endtoend/backup/vtbackup/main_test.go index 069f83fbba5..36bfae123d8 100644 --- a/go/test/endtoend/backup/vtbackup/main_test.go +++ b/go/test/endtoend/backup/vtbackup/main_test.go @@ -25,6 +25,7 @@ import ( "testing" "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/log" ) @@ -86,11 +87,16 @@ func TestMain(m *testing.M) { // Create a new init_db.sql file that sets up passwords for all users. // Then we use a db-credentials-file with the passwords. + // TODO: We could have operated with empty password here. Create a separate test for --db-credentials-file functionality (@rsajwani) dbCredentialFile = cluster.WriteDbCredentialToTmp(localCluster.TmpDirectory) initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) sql := string(initDb) + // The original init_db.sql does not have any passwords. Here we update the init file with passwords + sql, err = utils.GetInitDBSQL(sql, cluster.GetPasswordUpdateSQL(localCluster), "") + if err != nil { + return 1, err + } newInitDBFile = path.Join(localCluster.TmpDirectory, "init_db_with_passwords.sql") - sql = sql + cluster.GetPasswordUpdateSQL(localCluster) err = os.WriteFile(newInitDBFile, []byte(sql), 0666) if err != nil { return 1, err @@ -112,7 +118,11 @@ func TestMain(m *testing.M) { tablet.VttabletProcess.ExtraArgs = commonTabletArg tablet.VttabletProcess.SupportsBackup = true - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess tablet.MysqlctlProcess.InitDBFile = newInitDBFile tablet.MysqlctlProcess.ExtraArgs = extraArgs proc, err := tablet.MysqlctlProcess.StartProcess() @@ -127,13 +137,6 @@ func TestMain(m *testing.M) { } } - // Create database - for _, tablet := range []cluster.Vttablet{*primary, *replica1} { - if err := tablet.VttabletProcess.CreateDB(keyspaceName); err != nil { - return 1, err - } - } - if localCluster.VtTabletMajorVersion >= 16 { // If vttablets are any lower than version 16, then they are running the replication manager. // Running VTOrc and replication manager sometimes creates the situation where VTOrc has set up semi-sync on the primary, diff --git a/go/test/endtoend/backup/vtctlbackup/backup_utils.go b/go/test/endtoend/backup/vtctlbackup/backup_utils.go index 87e854b7d64..f6afcc131ac 100644 --- a/go/test/endtoend/backup/vtctlbackup/backup_utils.go +++ b/go/test/endtoend/backup/vtctlbackup/backup_utils.go @@ -28,19 +28,19 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/textutil" "vitess.io/vitess/go/vt/mysqlctl" "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "vitess.io/vitess/go/test/endtoend/cluster" ) // constants for test variants @@ -115,11 +115,18 @@ func LaunchCluster(setupType int, streamMode string, stripes int, cDetails *Comp } shard := &localCluster.Keyspaces[0].Shards[0] + // Create a new init_db.sql file that sets up passwords for all users. + // Then we use a db-credentials-file with the passwords. + // TODO: We could have operated with empty password here. Create a separate test for --db-credentials-file functionality (@rsajwani) dbCredentialFile = cluster.WriteDbCredentialToTmp(localCluster.TmpDirectory) initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) sql := string(initDb) + // The original init_db.sql does not have any passwords. Here we update the init file with passwords + sql, err = utils.GetInitDBSQL(sql, cluster.GetPasswordUpdateSQL(localCluster), "") + if err != nil { + return 1, err + } newInitDBFile = path.Join(localCluster.TmpDirectory, "init_db_with_passwords.sql") - sql = sql + cluster.GetPasswordUpdateSQL(localCluster) err = os.WriteFile(newInitDBFile, []byte(sql), 0666) if err != nil { return 1, err @@ -163,7 +170,11 @@ func LaunchCluster(setupType int, streamMode string, stripes int, cDetails *Comp tablet.VttabletProcess.SupportsBackup = true if setupType == Mysqlctld { - tablet.MysqlctldProcess = *cluster.MysqlCtldProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + mysqlctldProcess, err := cluster.MysqlCtldProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctldProcess = *mysqlctldProcess tablet.MysqlctldProcess.InitDBFile = newInitDBFile tablet.MysqlctldProcess.ExtraArgs = extraArgs tablet.MysqlctldProcess.Password = tablet.VttabletProcess.DbPassword @@ -174,7 +185,11 @@ func LaunchCluster(setupType int, streamMode string, stripes int, cDetails *Comp continue } - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess tablet.MysqlctlProcess.InitDBFile = newInitDBFile tablet.MysqlctlProcess.ExtraArgs = extraArgs proc, err := tablet.MysqlctlProcess.StartProcess() @@ -207,9 +222,6 @@ func LaunchCluster(setupType int, streamMode string, stripes int, cDetails *Comp } for _, tablet := range []cluster.Vttablet{*primary, *replica1} { - if err := tablet.VttabletProcess.CreateDB(keyspaceName); err != nil { - return 1, err - } if err := tablet.VttabletProcess.Setup(); err != nil { return 1, err } @@ -679,8 +691,6 @@ func restartPrimaryAndReplica(t *testing.T) { for _, tablet := range []*cluster.Vttablet{primary, replica1} { err := localCluster.VtctlclientProcess.InitTablet(tablet, cell, keyspaceName, hostname, shardName) require.Nil(t, err) - err = tablet.VttabletProcess.CreateDB(keyspaceName) - require.Nil(t, err) err = tablet.VttabletProcess.Setup() require.Nil(t, err) } diff --git a/go/test/endtoend/cellalias/cell_alias_test.go b/go/test/endtoend/cellalias/cell_alias_test.go index 47f3108e6c7..3b6aa8c6bda 100644 --- a/go/test/endtoend/cellalias/cell_alias_test.go +++ b/go/test/endtoend/cellalias/cell_alias_test.go @@ -132,7 +132,11 @@ func TestMain(m *testing.M) { var mysqlProcs []*exec.Cmd for _, tablet := range []*cluster.Vttablet{shard1Primary, shard1Replica, shard1Rdonly, shard2Primary, shard2Replica, shard2Rdonly} { - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess tablet.VttabletProcess = cluster.VttabletProcessInstance(tablet.HTTPPort, tablet.GrpcPort, tablet.TabletUID, diff --git a/go/test/endtoend/cluster/cluster_process.go b/go/test/endtoend/cluster/cluster_process.go index 82c667f95ae..e7df056fa56 100644 --- a/go/test/endtoend/cluster/cluster_process.go +++ b/go/test/endtoend/cluster/cluster_process.go @@ -352,7 +352,11 @@ func (cluster *LocalProcessCluster) startKeyspace(keyspace Keyspace, shardNames } // Start Mysqlctl process log.Infof("Starting mysqlctl for table uid %d, mysql port %d", tablet.TabletUID, tablet.MySQLPort) - tablet.MysqlctlProcess = *MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory, !cluster.ReusingVTDATAROOT) + mysqlctlProcess, err := MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory, !cluster.ReusingVTDATAROOT) + if err != nil { + return err + } + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() if err != nil { log.Errorf("error starting mysqlctl process: %v, %v", tablet.MysqlctldProcess, err) @@ -499,7 +503,11 @@ func (cluster *LocalProcessCluster) StartKeyspaceLegacy(keyspace Keyspace, shard } // Start Mysqlctl process log.Infof("Starting mysqlctl for table uid %d, mysql port %d", tablet.TabletUID, tablet.MySQLPort) - tablet.MysqlctlProcess = *MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory, !cluster.ReusingVTDATAROOT) + mysqlctlProcess, err := MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory, !cluster.ReusingVTDATAROOT) + if err != nil { + return err + } + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() if err != nil { log.Errorf("error starting mysqlctl process: %v, %v", tablet.MysqlctldProcess, err) @@ -619,7 +627,11 @@ func (cluster *LocalProcessCluster) SetupCluster(keyspace *Keyspace, shards []Sh for _, shard := range shards { for _, tablet := range shard.Vttablets { // Setup MysqlctlProcess - tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory) + mysqlctlProcess, err := MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory) + if err != nil { + return err + } + tablet.MysqlctlProcess = *mysqlctlProcess // Setup VttabletProcess tablet.VttabletProcess = VttabletProcessInstance( tablet.HTTPPort, @@ -703,7 +715,10 @@ func NewBareCluster(cell string, hostname string) *LocalProcessCluster { // path/to/whatever exists cluster.ReusingVTDATAROOT = true } else { - _ = createDirectory(cluster.CurrentVTDATAROOT, 0700) + err = createDirectory(cluster.CurrentVTDATAROOT, 0700) + if err != nil { + log.Fatal(err) + } } _ = os.Setenv("VTDATAROOT", cluster.CurrentVTDATAROOT) log.Infof("Created cluster on %s. ReusingVTDATAROOT=%v", cluster.CurrentVTDATAROOT, cluster.ReusingVTDATAROOT) diff --git a/go/test/endtoend/cluster/cluster_util.go b/go/test/endtoend/cluster/cluster_util.go index ea2dd0d7e20..6ebd8806928 100644 --- a/go/test/endtoend/cluster/cluster_util.go +++ b/go/test/endtoend/cluster/cluster_util.go @@ -306,6 +306,7 @@ func GetPasswordUpdateSQL(localCluster *LocalProcessCluster) string { SET PASSWORD FOR 'vt_allprivs'@'localhost' = 'VtAllprivsPass'; SET PASSWORD FOR 'vt_repl'@'%' = 'VtReplPass'; SET PASSWORD FOR 'vt_filtered'@'localhost' = 'VtFilteredPass'; + SET PASSWORD FOR 'vt_appdebug'@'localhost' = 'VtDebugPass'; FLUSH PRIVILEGES; ` return pwdChangeCmd diff --git a/go/test/endtoend/cluster/mysqlctl_process.go b/go/test/endtoend/cluster/mysqlctl_process.go index eafc8f6b98f..7c8f2607841 100644 --- a/go/test/endtoend/cluster/mysqlctl_process.go +++ b/go/test/endtoend/cluster/mysqlctl_process.go @@ -30,6 +30,7 @@ import ( "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/mysqlctl" "vitess.io/vitess/go/vt/tlstest" ) @@ -235,36 +236,65 @@ func (mysqlctl *MysqlctlProcess) Connect(ctx context.Context, username string) ( // MysqlCtlProcessInstanceOptionalInit returns a Mysqlctl handle for mysqlctl process // configured with the given Config. -func MysqlCtlProcessInstanceOptionalInit(tabletUID int, mySQLPort int, tmpDirectory string, initMySQL bool) *MysqlctlProcess { +func MysqlCtlProcessInstanceOptionalInit(tabletUID int, mySQLPort int, tmpDirectory string, initMySQL bool) (*MysqlctlProcess, error) { + initFile, err := getInitDBFileUsed() + if err != nil { + return nil, err + } mysqlctl := &MysqlctlProcess{ Name: "mysqlctl", Binary: "mysqlctl", LogDirectory: tmpDirectory, - InitDBFile: path.Join(os.Getenv("VTROOT"), "/config/init_db.sql"), + InitDBFile: initFile, } mysqlctl.MySQLPort = mySQLPort mysqlctl.TabletUID = tabletUID mysqlctl.InitMysql = initMySQL mysqlctl.SecureTransport = false - return mysqlctl + return mysqlctl, nil +} + +func getInitDBFileUsed() (string, error) { + versionStr, err := mysqlctl.GetVersionString() + if err != nil { + return "", err + } + flavor, _, err := mysqlctl.ParseVersionString(versionStr) + if err != nil { + return "", err + } + if flavor == mysqlctl.FlavorMySQL || flavor == mysqlctl.FlavorPercona { + return path.Join(os.Getenv("VTROOT"), "/config/init_db.sql"), nil + } + // Non-MySQL instances for example MariaDB, will use init_testserver_db.sql which does not contain super_read_only global variable. + // Even though MariaDB support is deprecated (https://github.com/vitessio/vitess/issues/9518) but we still support migration scenario. + return path.Join(os.Getenv("VTROOT"), "go/test/endtoend/vreplication/testdata/config/init_testserver_db.sql"), nil } // MysqlCtlProcessInstance returns a Mysqlctl handle for mysqlctl process // configured with the given Config. -func MysqlCtlProcessInstance(tabletUID int, mySQLPort int, tmpDirectory string) *MysqlctlProcess { +func MysqlCtlProcessInstance(tabletUID int, mySQLPort int, tmpDirectory string) (*MysqlctlProcess, error) { return MysqlCtlProcessInstanceOptionalInit(tabletUID, mySQLPort, tmpDirectory, true) } // StartMySQL starts mysqlctl process func StartMySQL(ctx context.Context, tablet *Vttablet, username string, tmpDirectory string) error { - tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, tmpDirectory) + mysqlctlProcess, err := MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, tmpDirectory) + if err != nil { + return err + } + tablet.MysqlctlProcess = *mysqlctlProcess return tablet.MysqlctlProcess.Start() } // StartMySQLAndGetConnection create a connection to tablet mysql func StartMySQLAndGetConnection(ctx context.Context, tablet *Vttablet, username string, tmpDirectory string) (*mysql.Conn, error) { - tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, tmpDirectory) - err := tablet.MysqlctlProcess.Start() + mysqlctlProcess, err := MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, tmpDirectory) + if err != nil { + return nil, err + } + tablet.MysqlctlProcess = *mysqlctlProcess + err = tablet.MysqlctlProcess.Start() if err != nil { return nil, err } diff --git a/go/test/endtoend/cluster/mysqlctld_process.go b/go/test/endtoend/cluster/mysqlctld_process.go index d71f2e3b1c8..673870e62bb 100644 --- a/go/test/endtoend/cluster/mysqlctld_process.go +++ b/go/test/endtoend/cluster/mysqlctld_process.go @@ -144,17 +144,21 @@ func (mysqlctld *MysqlctldProcess) CleanupFiles(tabletUID int) { // MysqlCtldProcessInstance returns a Mysqlctld handle for mysqlctld process // configured with the given Config. -func MysqlCtldProcessInstance(tabletUID int, mySQLPort int, tmpDirectory string) *MysqlctldProcess { +func MysqlCtldProcessInstance(tabletUID int, mySQLPort int, tmpDirectory string) (*MysqlctldProcess, error) { + initFile, err := getInitDBFileUsed() + if err != nil { + return nil, err + } mysqlctld := &MysqlctldProcess{ Name: "mysqlctld", Binary: "mysqlctld", LogDirectory: tmpDirectory, - InitDBFile: path.Join(os.Getenv("VTROOT"), "/config/init_db.sql"), + InitDBFile: initFile, } mysqlctld.MySQLPort = mySQLPort mysqlctld.TabletUID = tabletUID mysqlctld.InitMysql = true - return mysqlctld + return mysqlctld, nil } // IsHealthy gives the health status of mysql. diff --git a/go/test/endtoend/cluster/vtbackup_process.go b/go/test/endtoend/cluster/vtbackup_process.go index b7beed67936..be75026bf0d 100644 --- a/go/test/endtoend/cluster/vtbackup_process.go +++ b/go/test/endtoend/cluster/vtbackup_process.go @@ -54,7 +54,6 @@ type VtbackupProcess struct { // Setup starts vtbackup process with required arguements func (vtbackup *VtbackupProcess) Setup() (err error) { - vtbackup.proc = exec.Command( vtbackup.Binary, "--topo_implementation", vtbackup.CommonArg.TopoImplementation, diff --git a/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go b/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go index 8900db58ce1..d7de8052599 100644 --- a/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go +++ b/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go @@ -142,7 +142,11 @@ func initializeCluster(t *testing.T) (int, error) { tablet := clusterInstance.NewVttabletInstance("replica", tabletUID, cell) // Start Mysqlctl process - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() if err != nil { return 1, err diff --git a/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go b/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go index b0d0a507a43..e81b019d273 100644 --- a/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go +++ b/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go @@ -360,7 +360,11 @@ func clusterSetUp(t *testing.T) (int, error) { tablet := clusterInstance.NewVttabletInstance("replica", 0, cell) // Start Mysqlctl process - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() if err != nil { return 1, err diff --git a/go/test/endtoend/mysqlctl/mysqlctl_test.go b/go/test/endtoend/mysqlctl/mysqlctl_test.go index ec54ffb91d5..5b348cd4eb5 100644 --- a/go/test/endtoend/mysqlctl/mysqlctl_test.go +++ b/go/test/endtoend/mysqlctl/mysqlctl_test.go @@ -96,7 +96,11 @@ func initCluster(shardNames []string, totalTabletsRequired int) { tablet.Type = "primary" } // Start Mysqlctl process - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + if err != nil { + return + } + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() if err != nil { return diff --git a/go/test/endtoend/mysqlctld/mysqlctld_test.go b/go/test/endtoend/mysqlctld/mysqlctld_test.go index 28d2bb71ced..89e35aea31d 100644 --- a/go/test/endtoend/mysqlctld/mysqlctld_test.go +++ b/go/test/endtoend/mysqlctld/mysqlctld_test.go @@ -96,8 +96,12 @@ func initCluster(shardNames []string, totalTabletsRequired int) error { tablet.Type = "primary" } // Start Mysqlctld process - tablet.MysqlctldProcess = *cluster.MysqlCtldProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) - err := tablet.MysqlctldProcess.Start() + mysqlctldProcess, err := cluster.MysqlCtldProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + if err != nil { + return err + } + tablet.MysqlctldProcess = *mysqlctldProcess + err = tablet.MysqlctldProcess.Start() if err != nil { return err } diff --git a/go/test/endtoend/recovery/pitr/shardedpitr_test.go b/go/test/endtoend/recovery/pitr/shardedpitr_test.go index 5a7ae3e1399..ab54693993a 100644 --- a/go/test/endtoend/recovery/pitr/shardedpitr_test.go +++ b/go/test/endtoend/recovery/pitr/shardedpitr_test.go @@ -19,7 +19,9 @@ package pitr import ( "context" "fmt" + "os" "os/exec" + "path" "testing" "time" @@ -29,6 +31,7 @@ import ( "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/log" ) @@ -51,20 +54,22 @@ var ( shard1Replica1 *cluster.Vttablet shard1Replica2 *cluster.Vttablet - cell = "zone1" - hostname = "localhost" - binlogHost = "127.0.0.1" - keyspaceName = "ks" - restoreKS1Name = "restoreks1" - restoreKS2Name = "restoreks2" - restoreKS3Name = "restoreks3" - shardName = "0" - shard0Name = "-80" - shard1Name = "80-" - dbName = "vt_ks" - mysqlUserName = "vt_dba" - mysqlPassword = "password" - vSchema = `{ + cell = "zone1" + hostname = "localhost" + binlogHost = "127.0.0.1" + keyspaceName = "ks" + restoreKS1Name = "restoreks1" + restoreKS2Name = "restoreks2" + restoreKS3Name = "restoreks3" + shardName = "0" + shard0Name = "-80" + shard1Name = "80-" + dbName = "vt_ks" + mysqlUserName = "vt_dba" + mysqlPassword = "VtDbaPass" + dbCredentialFile = "" + initDBFileWithPassword = "" + vSchema = `{ "sharded": true, "vindexes": { "hash_index": { @@ -408,6 +413,10 @@ func initializeCluster(t *testing.T) { shard0.Vttablets = []*cluster.Vttablet{shard0Primary, shard0Replica1, shard0Replica2} shard1.Vttablets = []*cluster.Vttablet{shard1Primary, shard1Replica1, shard1Replica2} + dbCredentialFile = cluster.WriteDbCredentialToTmp(clusterInstance.TmpDirectory) + extraArgs := []string{"--db-credentials-file", dbCredentialFile} + commonTabletArg = append(commonTabletArg, "--db-credentials-file", dbCredentialFile) + clusterInstance.VtTabletExtraArgs = append(clusterInstance.VtTabletExtraArgs, commonTabletArg...) clusterInstance.VtTabletExtraArgs = append(clusterInstance.VtTabletExtraArgs, "--restore_from_backup") @@ -416,10 +425,23 @@ func initializeCluster(t *testing.T) { vtctldClientProcess := cluster.VtctldClientProcessInstance("localhost", clusterInstance.VtctldProcess.GrpcPort, clusterInstance.TmpDirectory) out, err := vtctldClientProcess.ExecuteCommandWithOutput("SetKeyspaceDurabilityPolicy", keyspaceName, "--durability-policy=semi_sync") require.NoError(t, err, out) + + initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) + sql := string(initDb) + // The original init_db.sql does not have any passwords. Here we update the init file with passwords + sql, err = utils.GetInitDBSQL(sql, cluster.GetPasswordUpdateSQL(clusterInstance), "") + require.NoError(t, err, "expected to load init_db file") + initDBFileWithPassword = path.Join(clusterInstance.TmpDirectory, "init_db_with_passwords.sql") + err = os.WriteFile(initDBFileWithPassword, []byte(sql), 0660) + require.NoError(t, err, "expected to load init_db file") + // Start MySql var mysqlCtlProcessList []*exec.Cmd for _, shard := range clusterInstance.Keyspaces[0].Shards { for _, tablet := range shard.Vttablets { + tablet.MysqlctlProcess.InitDBFile = initDBFileWithPassword + tablet.VttabletProcess.DbPassword = mysqlPassword + tablet.MysqlctlProcess.ExtraArgs = extraArgs proc, err := tablet.MysqlctlProcess.StartProcess() require.NoError(t, err) mysqlCtlProcessList = append(mysqlCtlProcessList, proc) @@ -432,21 +454,8 @@ func initializeCluster(t *testing.T) { require.NoError(t, err) } - queryCmds := []string{ - fmt.Sprintf("CREATE USER '%s'@'%%' IDENTIFIED BY '%s';", mysqlUserName, mysqlPassword), - fmt.Sprintf("GRANT ALL ON *.* TO '%s'@'%%';", mysqlUserName), - fmt.Sprintf("GRANT GRANT OPTION ON *.* TO '%s'@'%%';", mysqlUserName), - fmt.Sprintf("create database %s;", "vt_ks"), - "FLUSH PRIVILEGES;", - } - for _, shard := range clusterInstance.Keyspaces[0].Shards { for _, tablet := range shard.Vttablets { - for _, query := range queryCmds { - _, err = tablet.VttabletProcess.QueryTablet(query, keyspace.Name, false) - require.NoError(t, err) - } - err = tablet.VttabletProcess.Setup() require.NoError(t, err) } @@ -509,8 +518,14 @@ func testTabletRecovery(t *testing.T, binlogServer *binLogServer, lookupTimeout, } func launchRecoveryTablet(t *testing.T, tablet *cluster.Vttablet, binlogServer *binLogServer, lookupTimeout, restoreKeyspaceName, shardName string) { - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) - err := tablet.MysqlctlProcess.Start() + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + require.NoError(t, err) + tablet.MysqlctlProcess = *mysqlctlProcess + extraArgs := []string{"--db-credentials-file", dbCredentialFile} + tablet.MysqlctlProcess.InitDBFile = initDBFileWithPassword + tablet.VttabletProcess.DbPassword = mysqlPassword + tablet.MysqlctlProcess.ExtraArgs = extraArgs + err = tablet.MysqlctlProcess.Start() require.NoError(t, err) tablet.VttabletProcess = cluster.VttabletProcessInstance( @@ -550,6 +565,7 @@ func launchRecoveryTablet(t *testing.T, tablet *cluster.Vttablet, binlogServer * "--lock_tables_timeout", "5s", "--watch_replication_stream", "--serving_state_grace_period", "1s", + "--db-credentials-file", dbCredentialFile, } tablet.VttabletProcess.ServingStatus = "" diff --git a/go/test/endtoend/recovery/unshardedrecovery/recovery.go b/go/test/endtoend/recovery/unshardedrecovery/recovery.go index 1ab9f1647ca..eff072f651d 100644 --- a/go/test/endtoend/recovery/unshardedrecovery/recovery.go +++ b/go/test/endtoend/recovery/unshardedrecovery/recovery.go @@ -30,6 +30,7 @@ import ( "vitess.io/vitess/go/test/endtoend/cluster" "vitess.io/vitess/go/test/endtoend/recovery" + "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/vtgate/vtgateconn" ) @@ -91,16 +92,19 @@ func TestMainImpl(m *testing.M) { } localCluster.Keyspaces = append(localCluster.Keyspaces, *keyspace) + // Create a new init_db.sql file that sets up passwords for all users. + // Then we use a db-credentials-file with the passwords. + // TODO: We could have operated with empty password here. Create a separate test for --db-credentials-file functionality (@rsajwani) dbCredentialFile = cluster.WriteDbCredentialToTmp(localCluster.TmpDirectory) initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) sql := string(initDb) + // The original init_db.sql does not have any passwords. Here we update the init file with passwords + oldAlterTableMode := `SET GLOBAL old_alter_table = ON;` + sql, err = utils.GetInitDBSQL(sql, cluster.GetPasswordUpdateSQL(localCluster), oldAlterTableMode) + if err != nil { + return 1, err + } newInitDBFile = path.Join(localCluster.TmpDirectory, "init_db_with_passwords.sql") - sql = sql + cluster.GetPasswordUpdateSQL(localCluster) - // https://github.com/vitessio/vitess/issues/8315 - oldAlterTableMode := ` -SET GLOBAL old_alter_table = ON; -` - sql = sql + oldAlterTableMode os.WriteFile(newInitDBFile, []byte(sql), 0666) extraArgs := []string{"--db-credentials-file", dbCredentialFile} @@ -125,7 +129,11 @@ SET GLOBAL old_alter_table = ON; } tablet.VttabletProcess.SupportsBackup = true - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, localCluster.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess tablet.MysqlctlProcess.InitDBFile = newInitDBFile tablet.MysqlctlProcess.ExtraArgs = extraArgs proc, err := tablet.MysqlctlProcess.StartProcess() diff --git a/go/test/endtoend/reparent/plannedreparent/reparent_test.go b/go/test/endtoend/reparent/plannedreparent/reparent_test.go index de7e6a0368b..a46f3990cdf 100644 --- a/go/test/endtoend/reparent/plannedreparent/reparent_test.go +++ b/go/test/endtoend/reparent/plannedreparent/reparent_test.go @@ -427,6 +427,14 @@ func TestFullStatus(t *testing.T) { assert.Contains(t, primaryStatus.PrimaryStatus.String(), "vt-0000000101-bin") assert.Equal(t, primaryStatus.GtidPurged, "MySQL56/") assert.False(t, primaryStatus.ReadOnly) + vtTabletVersion, err := cluster.GetMajorVersion("vttablet") + require.NoError(t, err) + vtcltlVersion, err := cluster.GetMajorVersion("vtctl") + require.NoError(t, err) + // For all version at or above v17.0.0, each replica will start in super_read_only mode. + if vtTabletVersion >= 17 && vtcltlVersion >= 17 { + assert.False(t, primaryStatus.SuperReadOnly) + } assert.True(t, primaryStatus.SemiSyncPrimaryEnabled) assert.True(t, primaryStatus.SemiSyncReplicaEnabled) assert.True(t, primaryStatus.SemiSyncPrimaryStatus) @@ -479,6 +487,10 @@ func TestFullStatus(t *testing.T) { assert.Contains(t, replicaStatus.PrimaryStatus.String(), "vt-0000000102-bin") assert.Equal(t, replicaStatus.GtidPurged, "MySQL56/") assert.True(t, replicaStatus.ReadOnly) + // For all version at or above v17.0.0, each replica will start in super_read_only mode. + if vtTabletVersion >= 17 && vtcltlVersion >= 17 { + assert.True(t, replicaStatus.SuperReadOnly) + } assert.False(t, replicaStatus.SemiSyncPrimaryEnabled) assert.True(t, replicaStatus.SemiSyncReplicaEnabled) assert.False(t, replicaStatus.SemiSyncPrimaryStatus) diff --git a/go/test/endtoend/reparent/utils/utils.go b/go/test/endtoend/reparent/utils/utils.go index c2ab9d48306..1ec834f1cc7 100644 --- a/go/test/endtoend/reparent/utils/utils.go +++ b/go/test/endtoend/reparent/utils/utils.go @@ -193,7 +193,9 @@ func StartNewVTTablet(t *testing.T, clusterInstance *cluster.LocalProcessCluster shard := keyspace.Shards[0] // Setup MysqlctlProcess - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + require.NoError(t, err) + tablet.MysqlctlProcess = *mysqlctlProcess // Setup VttabletProcess tablet.VttabletProcess = cluster.VttabletProcessInstance( tablet.HTTPPort, diff --git a/go/test/endtoend/sharded/sharded_keyspace_test.go b/go/test/endtoend/sharded/sharded_keyspace_test.go index d5f5e5b2255..0298da0bdb2 100644 --- a/go/test/endtoend/sharded/sharded_keyspace_test.go +++ b/go/test/endtoend/sharded/sharded_keyspace_test.go @@ -215,7 +215,11 @@ func initCluster(shardNames []string, totalTabletsRequired int) { tablet.Type = "primary" } // Start Mysqlctl process - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + if err != nil { + return + } + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() if err != nil { return diff --git a/go/test/endtoend/tabletmanager/tablet_health_test.go b/go/test/endtoend/tabletmanager/tablet_health_test.go index 19359406607..aaa5719cdcd 100644 --- a/go/test/endtoend/tabletmanager/tablet_health_test.go +++ b/go/test/endtoend/tabletmanager/tablet_health_test.go @@ -103,9 +103,6 @@ func TestHealthCheck(t *testing.T) { defer replicaConn.Close() - // Create database in mysql - utils.Exec(t, replicaConn, fmt.Sprintf("create database vt_%s", keyspaceName)) - // start vttablet process, should be in SERVING state as we already have a primary err = clusterInstance.StartVttablet(rTablet, "SERVING", false, cell, keyspaceName, hostname, shardName) require.NoError(t, err) diff --git a/go/test/endtoend/tabletmanager/tablet_test.go b/go/test/endtoend/tabletmanager/tablet_test.go index 3c597e97981..643785dcd89 100644 --- a/go/test/endtoend/tabletmanager/tablet_test.go +++ b/go/test/endtoend/tabletmanager/tablet_test.go @@ -34,8 +34,11 @@ func TestEnsureDB(t *testing.T) { // Create new tablet tablet := clusterInstance.NewVttabletInstance("replica", 0, "") - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) - err := tablet.MysqlctlProcess.Start() + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + require.NoError(t, err) + + tablet.MysqlctlProcess = *mysqlctlProcess + err = tablet.MysqlctlProcess.Start() require.NoError(t, err) log.Info(fmt.Sprintf("Started vttablet %v", tablet)) @@ -67,8 +70,10 @@ func TestResetReplicationParameters(t *testing.T) { // Create new tablet tablet := clusterInstance.NewVttabletInstance("replica", 0, "") - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) - err := tablet.MysqlctlProcess.Start() + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + require.NoError(t, err) + tablet.MysqlctlProcess = *mysqlctlProcess + err = tablet.MysqlctlProcess.Start() require.NoError(t, err) log.Info(fmt.Sprintf("Started vttablet %v", tablet)) diff --git a/go/test/endtoend/topoconncache/main_test.go b/go/test/endtoend/topoconncache/main_test.go index 2a074e8428a..7cfea8839b0 100644 --- a/go/test/endtoend/topoconncache/main_test.go +++ b/go/test/endtoend/topoconncache/main_test.go @@ -140,7 +140,11 @@ func TestMain(m *testing.M) { var mysqlProcs []*exec.Cmd for _, tablet := range []*cluster.Vttablet{shard1Primary, shard1Replica, shard1Rdonly, shard2Primary, shard2Replica, shard2Rdonly} { - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + if err != nil { + return 1, err + } + tablet.MysqlctlProcess = *mysqlctlProcess tablet.VttabletProcess = cluster.VttabletProcessInstance(tablet.HTTPPort, tablet.GrpcPort, tablet.TabletUID, diff --git a/go/test/endtoend/topoconncache/topo_conn_cache_test.go b/go/test/endtoend/topoconncache/topo_conn_cache_test.go index 02f14a7304d..504ca218047 100644 --- a/go/test/endtoend/topoconncache/topo_conn_cache_test.go +++ b/go/test/endtoend/topoconncache/topo_conn_cache_test.go @@ -136,7 +136,9 @@ func addCellback(t *testing.T) { // create sql process for vttablets var mysqlProcs []*exec.Cmd for _, tablet := range []*cluster.Vttablet{shard1Replica, shard1Rdonly, shard2Replica, shard2Rdonly} { - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) + require.NoError(t, err) + tablet.MysqlctlProcess = *mysqlctlProcess tablet.VttabletProcess = cluster.VttabletProcessInstance(tablet.HTTPPort, tablet.GrpcPort, tablet.TabletUID, diff --git a/go/test/endtoend/utils/mysql.go b/go/test/endtoend/utils/mysql.go index a73160503cd..fc1a62d6736 100644 --- a/go/test/endtoend/utils/mysql.go +++ b/go/test/endtoend/utils/mysql.go @@ -40,7 +40,8 @@ import ( // The mysql.ConnParams to connect to the new database is returned, along with a function to // teardown the database. func NewMySQL(cluster *cluster.LocalProcessCluster, dbName string, schemaSQL ...string) (mysql.ConnParams, func(), error) { - return NewMySQLWithDetails(cluster.GetAndReservePort(), cluster.Hostname, dbName, schemaSQL...) + mysqlParam, _, closer, error := NewMySQLWithMysqld(cluster.GetAndReservePort(), cluster.Hostname, dbName, schemaSQL...) + return mysqlParam, closer, error } // CreateMysqldAndMycnf returns a Mysqld and a Mycnf object to use for working with a MySQL @@ -60,24 +61,24 @@ func CreateMysqldAndMycnf(tabletUID uint32, mysqlSocket string, mysqlPort int) ( return mysqlctl.NewMysqld(&cfg), mycnf, nil } -func NewMySQLWithDetails(port int, hostname, dbName string, schemaSQL ...string) (mysql.ConnParams, func(), error) { +func NewMySQLWithMysqld(port int, hostname, dbName string, schemaSQL ...string) (mysql.ConnParams, *mysqlctl.Mysqld, func(), error) { mysqlDir, err := createMySQLDir() if err != nil { - return mysql.ConnParams{}, nil, err + return mysql.ConnParams{}, nil, nil, err } initMySQLFile, err := createInitSQLFile(mysqlDir, dbName) if err != nil { - return mysql.ConnParams{}, nil, err + return mysql.ConnParams{}, nil, nil, err } mysqlPort := port mysqld, mycnf, err := CreateMysqldAndMycnf(0, "", mysqlPort) if err != nil { - return mysql.ConnParams{}, nil, err + return mysql.ConnParams{}, nil, nil, err } err = initMysqld(mysqld, mycnf, initMySQLFile) if err != nil { - return mysql.ConnParams{}, nil, err + return mysql.ConnParams{}, nil, nil, err } params := mysql.ConnParams{ @@ -89,10 +90,10 @@ func NewMySQLWithDetails(port int, hostname, dbName string, schemaSQL ...string) for _, sql := range schemaSQL { err = prepareMySQLWithSchema(params, sql) if err != nil { - return mysql.ConnParams{}, nil, err + return mysql.ConnParams{}, nil, nil, err } } - return params, func() { + return params, mysqld, func() { ctx := context.Background() _ = mysqld.Teardown(ctx, mycnf, true) }, nil @@ -114,7 +115,10 @@ func createInitSQLFile(mysqlDir, ksName string) (string, error) { return "", err } defer f.Close() - + _, err = f.WriteString("SET GLOBAL super_read_only='OFF';") + if err != nil { + return "", err + } _, err = f.WriteString(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", ksName)) if err != nil { return "", err diff --git a/go/test/endtoend/utils/mysql_test.go b/go/test/endtoend/utils/mysql_test.go index d2816cb1227..de9db23dab1 100644 --- a/go/test/endtoend/utils/mysql_test.go +++ b/go/test/endtoend/utils/mysql_test.go @@ -22,15 +22,18 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/vt/mysqlctl" ) var ( clusterInstance *cluster.LocalProcessCluster mysqlParams mysql.ConnParams + mysqld *mysqlctl.Mysqld keyspaceName = "ks" cell = "test" schemaSQL = `create table t1( @@ -48,13 +51,14 @@ func TestMain(m *testing.M) { clusterInstance = cluster.NewCluster(cell, "localhost") defer clusterInstance.Teardown() - conn, closer, err := NewMySQL(clusterInstance, keyspaceName, schemaSQL) + var closer func() + var err error + mysqlParams, mysqld, closer, err = NewMySQLWithMysqld(clusterInstance.GetAndReservePort(), clusterInstance.Hostname, keyspaceName, schemaSQL) if err != nil { fmt.Println(err) return 1 } defer closer() - mysqlParams = conn return m.Run() }() os.Exit(exitCode) @@ -64,9 +68,58 @@ func TestCreateMySQL(t *testing.T) { ctx := context.Background() conn, err := mysql.Connect(ctx, &mysqlParams) require.NoError(t, err) - AssertMatches(t, conn, "show databases;", `[[VARCHAR("information_schema")] [VARCHAR("ks")] [VARCHAR("mysql")] [VARCHAR("performance_schema")] [VARCHAR("sys")]]`) AssertMatches(t, conn, "show tables;", `[[VARCHAR("t1")]]`) Exec(t, conn, "insert into t1(id1, id2, id3) values (1, 1, 1), (2, 2, 2), (3, 3, 3)") AssertMatches(t, conn, "select * from t1;", `[[INT64(1) INT64(1) INT64(1)] [INT64(2) INT64(2) INT64(2)] [INT64(3) INT64(3) INT64(3)]]`) } + +func TestSetSuperReadOnlyMySQL(t *testing.T) { + require.NotNil(t, mysqld) + isSuperReadOnly, _ := mysqld.IsSuperReadOnly() + assert.False(t, isSuperReadOnly, "super_read_only should be set to False") + retFunc1, err := mysqld.SetSuperReadOnly(true) + assert.NotNil(t, retFunc1, "SetSuperReadOnly is supposed to return a defer function") + assert.NoError(t, err, "SetSuperReadOnly should not have failed") + + isSuperReadOnly, _ = mysqld.IsSuperReadOnly() + assert.True(t, isSuperReadOnly, "super_read_only should be set to True") + // if value is already true then retFunc2 will be nil + retFunc2, err := mysqld.SetSuperReadOnly(true) + assert.Nil(t, retFunc2, "SetSuperReadOnly is supposed to return a nil function") + assert.NoError(t, err, "SetSuperReadOnly should not have failed") + + retFunc1() + isSuperReadOnly, _ = mysqld.IsSuperReadOnly() + assert.False(t, isSuperReadOnly, "super_read_only should be set to False") + isReadOnly, _ := mysqld.IsReadOnly() + assert.True(t, isReadOnly, "read_only should be set to True") + + isSuperReadOnly, _ = mysqld.IsSuperReadOnly() + assert.False(t, isSuperReadOnly, "super_read_only should be set to False") + retFunc1, err = mysqld.SetSuperReadOnly(false) + assert.Nil(t, retFunc1, "SetSuperReadOnly is supposed to return a nil function") + assert.NoError(t, err, "SetSuperReadOnly should not have failed") + + _, err = mysqld.SetSuperReadOnly(true) + assert.NoError(t, err) + + isSuperReadOnly, _ = mysqld.IsSuperReadOnly() + assert.True(t, isSuperReadOnly, "super_read_only should be set to True") + retFunc1, err = mysqld.SetSuperReadOnly(false) + assert.NotNil(t, retFunc1, "SetSuperReadOnly is supposed to return a defer function") + assert.NoError(t, err, "SetSuperReadOnly should not have failed") + + isSuperReadOnly, _ = mysqld.IsSuperReadOnly() + assert.False(t, isSuperReadOnly, "super_read_only should be set to False") + // if value is already false then retFunc2 will be nil + retFunc2, err = mysqld.SetSuperReadOnly(false) + assert.Nil(t, retFunc2, "SetSuperReadOnly is supposed to return a nil function") + assert.NoError(t, err, "SetSuperReadOnly should not have failed") + + retFunc1() + isSuperReadOnly, _ = mysqld.IsSuperReadOnly() + assert.True(t, isSuperReadOnly, "super_read_only should be set to True") + isReadOnly, _ = mysqld.IsReadOnly() + assert.True(t, isReadOnly, "read_only should be set to True") +} diff --git a/go/test/endtoend/utils/utils.go b/go/test/endtoend/utils/utils.go index 1aca889025b..82d696fa856 100644 --- a/go/test/endtoend/utils/utils.go +++ b/go/test/endtoend/utils/utils.go @@ -22,14 +22,13 @@ import ( "testing" "time" - "vitess.io/vitess/go/test/endtoend/cluster" - "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/test/endtoend/cluster" ) // AssertContains ensures the given query result contains the expected results. @@ -288,3 +287,23 @@ func convertToMap(input interface{}) map[string]interface{} { output := input.(map[string]interface{}) return output } + +func GetInitDBSQL(initDBSQL string, updatedPasswords string, oldAlterTableMode string) (string, error) { + // Since password update is DML we need to insert it before we disable + // super_read_only therefore doing the split below. + splitString := strings.Split(initDBSQL, "# {{custom_sql}}") + if len(splitString) != 2 { + return "", fmt.Errorf("missing `# {{custom_sql}}` in init_db.sql file") + } + var builder strings.Builder + builder.WriteString(splitString[0]) + builder.WriteString(updatedPasswords) + + // https://github.com/vitessio/vitess/issues/8315 + if oldAlterTableMode != "" { + builder.WriteString(oldAlterTableMode) + } + builder.WriteString(splitString[1]) + + return builder.String(), nil +} diff --git a/go/test/endtoend/vault/dbcreds_secret.json b/go/test/endtoend/vault/dbcreds_secret.json index 96fff38bdcd..ee0a4af534b 100644 --- a/go/test/endtoend/vault/dbcreds_secret.json +++ b/go/test/endtoend/vault/dbcreds_secret.json @@ -1,17 +1,23 @@ { + "root": [ + "RootPass" + ], "vt_app": [ - "password" + "VtAppPass" ], "vt_dba": [ - "password" + "VtDbaPass" ], "vt_repl": [ - "password" + "VtReplPass" ], "vt_appdebug": [ - "password" + "VtDebugPass" ], "vt_filtered": [ - "password" + "VtFilteredPass" + ], + "vt_allprivs": [ + "VtAllprivsPass" ] } diff --git a/go/test/endtoend/vault/vault_test.go b/go/test/endtoend/vault/vault_test.go index 25ed88f4335..2c4bf3059d6 100644 --- a/go/test/endtoend/vault/vault_test.go +++ b/go/test/endtoend/vault/vault_test.go @@ -33,6 +33,7 @@ import ( "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/log" ) @@ -53,7 +54,7 @@ var ( shardName = "0" dbName = "vt_ks" mysqlUsers = []string{"vt_dba", "vt_app", "vt_appdebug", "vt_repl", "vt_filtered"} - mysqlPassword = "password" + mysqlPassword = "VtDbaPass" vtgateUser = "vtgate_user" vtgatePassword = "password123" commonTabletArg = []string{ @@ -251,10 +252,21 @@ func initializeClusterLate(t *testing.T) { out, err := vtctldClientProcess.ExecuteCommandWithOutput("SetKeyspaceDurabilityPolicy", keyspaceName, "--durability-policy=semi_sync") require.NoError(t, err, out) + initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) + sql := string(initDb) + // The original init_db.sql does not have any passwords. Here we update the init file with passwords + sql, err = utils.GetInitDBSQL(sql, cluster.GetPasswordUpdateSQL(clusterInstance), "") + require.NoError(t, err, "expected to load init_db file") + newInitDBFile := path.Join(clusterInstance.TmpDirectory, "init_db_with_passwords.sql") + err = os.WriteFile(newInitDBFile, []byte(sql), 0660) + require.NoError(t, err, "expected to load init_db file") + // Start MySQL var mysqlCtlProcessList []*exec.Cmd for _, shard := range clusterInstance.Keyspaces[0].Shards { for _, tablet := range shard.Vttablets { + tablet.MysqlctlProcess.InitDBFile = newInitDBFile + tablet.VttabletProcess.DbPassword = mysqlPassword proc, err := tablet.MysqlctlProcess.StartProcess() require.NoError(t, err) mysqlCtlProcessList = append(mysqlCtlProcessList, proc) @@ -268,21 +280,6 @@ func initializeClusterLate(t *testing.T) { } for _, tablet := range []*cluster.Vttablet{primary, replica} { - for _, user := range mysqlUsers { - query := fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s';", user, hostname, mysqlPassword) - _, err = tablet.VttabletProcess.QueryTablet(query, keyspace.Name, false) - // Reset after the first ALTER, or we lock ourselves out. - tablet.VttabletProcess.DbPassword = mysqlPassword - if err != nil { - query = fmt.Sprintf("ALTER USER '%s'@'%%' IDENTIFIED BY '%s';", user, mysqlPassword) - _, err = tablet.VttabletProcess.QueryTablet(query, keyspace.Name, false) - require.NoError(t, err) - } - } - query := fmt.Sprintf("create database %s;", dbName) - _, err = tablet.VttabletProcess.QueryTablet(query, keyspace.Name, false) - require.NoError(t, err) - err = tablet.VttabletProcess.Setup() require.NoError(t, err) diff --git a/go/test/endtoend/vreplication/cluster_test.go b/go/test/endtoend/vreplication/cluster_test.go index 43991454b6e..cd2c60a6336 100644 --- a/go/test/endtoend/vreplication/cluster_test.go +++ b/go/test/endtoend/vreplication/cluster_test.go @@ -450,7 +450,9 @@ func (vc *VitessCluster) AddTablet(t testing.TB, cell *Cell, keyspace *Keyspace, require.NotNil(t, vttablet) vttablet.SupportsBackup = false - tablet.DbServer = cluster.MysqlCtlProcessInstance(tabletID, vc.ClusterConfig.tabletMysqlPortBase+tabletID, vc.ClusterConfig.tmpDir) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstance(tabletID, vc.ClusterConfig.tabletMysqlPortBase+tabletID, vc.ClusterConfig.tmpDir) + require.NoError(t, err) + tablet.DbServer = mysqlctlProcess require.NotNil(t, tablet.DbServer) tablet.DbServer.InitMysql = true proc, err := tablet.DbServer.StartProcess() diff --git a/go/test/endtoend/vreplication/testdata/config/init_testserver_db.sql b/go/test/endtoend/vreplication/testdata/config/init_testserver_db.sql new file mode 100644 index 00000000000..03df754ea21 --- /dev/null +++ b/go/test/endtoend/vreplication/testdata/config/init_testserver_db.sql @@ -0,0 +1,91 @@ +# This file is for testing purpose only. +# This file is executed immediately after initializing a fresh data directory. +# It is equivalent of init_db.sql. Given init_db.sql is for mysql which has super_read_only +# related stuff therefore for testing purpose we avoid setting `super_read_only` during initialization. + +############################################################################### +# WARNING: Any change to init_db.sql should gets reflected in this file as well. +############################################################################### + +############################################################################### +# WARNING: This sql is *NOT* safe for production use, +# as it contains default well-known users and passwords. +# Care should be taken to change these users and passwords +# for production. +############################################################################### + +############################################################################### +# Equivalent of mysql_secure_installation +############################################################################### +# We need to ensure that read_only is disabled so that we can execute +# these commands. +SET GLOBAL read_only='OFF'; + +# Changes during the init db should not make it to the binlog. +# They could potentially create errant transactions on replicas. +SET sql_log_bin = 0; +# Remove anonymous users. +DELETE FROM mysql.user WHERE User = ''; + +# Disable remote root access (only allow UNIX socket). +DELETE FROM mysql.user WHERE User = 'root' AND Host != 'localhost'; + +# Remove test database. +DROP DATABASE IF EXISTS test; + +############################################################################### +# Vitess defaults +############################################################################### + +# Admin user with all privileges. +CREATE USER 'vt_dba'@'localhost'; +GRANT ALL ON *.* TO 'vt_dba'@'localhost'; +GRANT GRANT OPTION ON *.* TO 'vt_dba'@'localhost'; + +# User for app traffic, with global read-write access. +CREATE USER 'vt_app'@'localhost'; +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, FILE, + REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, + LOCK TABLES, EXECUTE, REPLICATION CLIENT, CREATE VIEW, + SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER + ON *.* TO 'vt_app'@'localhost'; + +# User for app debug traffic, with global read access. +CREATE USER 'vt_appdebug'@'localhost'; +GRANT SELECT, SHOW DATABASES, PROCESS ON *.* TO 'vt_appdebug'@'localhost'; + +# User for administrative operations that need to be executed as non-SUPER. +# Same permissions as vt_app here. +CREATE USER 'vt_allprivs'@'localhost'; +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, FILE, + REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, + LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, + SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER + ON *.* TO 'vt_allprivs'@'localhost'; + +# User for slave replication connections. +CREATE USER 'vt_repl'@'%'; +GRANT REPLICATION SLAVE ON *.* TO 'vt_repl'@'%'; + +# User for Vitess VReplication (base vstreamers and vplayer). +CREATE USER 'vt_filtered'@'localhost'; +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, FILE, + REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, + LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, + SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER + ON *.* TO 'vt_filtered'@'localhost'; + +# User for general MySQL monitoring. +CREATE USER 'vt_monitoring'@'localhost'; +GRANT SELECT, PROCESS, SUPER, REPLICATION CLIENT, RELOAD + ON *.* TO 'vt_monitoring'@'localhost'; +GRANT SELECT, UPDATE, DELETE, DROP + ON performance_schema.* TO 'vt_monitoring'@'localhost'; + +FLUSH PRIVILEGES; + +RESET SLAVE ALL; +RESET MASTER; + +# custom sql is used to add custom scripts like creating users/passwords. We use it in our tests +# {{custom_sql}} diff --git a/go/test/endtoend/vreplication/vreplication_test.go b/go/test/endtoend/vreplication/vreplication_test.go index 3f8f7f91997..6e65a72544e 100644 --- a/go/test/endtoend/vreplication/vreplication_test.go +++ b/go/test/endtoend/vreplication/vreplication_test.go @@ -386,6 +386,7 @@ func TestMultiCellVreplicationWorkflow(t *testing.T) { insertInitialData(t) shardCustomer(t, true, []*Cell{cell1, cell2}, cell2.Name, true) + checkIfDenyListExists(t, vc, "product:0", "customer") // we tag along this test so as not to create the overhead of creating another cluster testVStreamCellFlag(t) } diff --git a/go/test/endtoend/vtgate/tablet_healthcheck_cache/correctness_test.go b/go/test/endtoend/vtgate/tablet_healthcheck_cache/correctness_test.go index 386ef325996..928cb2d1c6a 100644 --- a/go/test/endtoend/vtgate/tablet_healthcheck_cache/correctness_test.go +++ b/go/test/endtoend/vtgate/tablet_healthcheck_cache/correctness_test.go @@ -183,7 +183,9 @@ func addTablet(t *testing.T, tabletUID int, tabletType string) *cluster.Vttablet Alias: fmt.Sprintf("%s-%010d", cell, tabletUID), } // Start Mysqlctl process - tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory, !clusterInstance.ReusingVTDATAROOT) + mysqlctlProcess, err := cluster.MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory, !clusterInstance.ReusingVTDATAROOT) + require.Nil(t, err) + tablet.MysqlctlProcess = *mysqlctlProcess proc, err := tablet.MysqlctlProcess.StartProcess() require.Nil(t, err) diff --git a/go/test/endtoend/vtorc/readtopologyinstance/main_test.go b/go/test/endtoend/vtorc/readtopologyinstance/main_test.go index fbc29600e98..fe8b53f5103 100644 --- a/go/test/endtoend/vtorc/readtopologyinstance/main_test.go +++ b/go/test/endtoend/vtorc/readtopologyinstance/main_test.go @@ -27,6 +27,7 @@ import ( "vitess.io/vitess/go/vt/servenv" "vitess.io/vitess/go/vt/vtorc/config" "vitess.io/vitess/go/vt/vtorc/inst" + "vitess.io/vitess/go/vt/vtorc/logic" "vitess.io/vitess/go/vt/vtorc/server" _ "github.com/go-sql-driver/mysql" @@ -104,8 +105,19 @@ func TestReadTopologyInstanceBufferable(t *testing.T) { assert.Equal(t, primaryInstance.ReplicationIOThreadState, inst.ReplicationThreadStateNoThread) assert.Equal(t, primaryInstance.ReplicationSQLThreadState, inst.ReplicationThreadStateNoThread) - // insert an errant GTID in the replica - _, err = utils.RunSQL(t, "insert into vt_insert_test(id, msg) values (10173, 'test 178342')", replica, "vt_ks") + // Insert an errant GTID in the replica. + // The way to do this is to disable global recoveries, stop replication and inject an errant GTID. + // After this we restart the replication and enable the recoveries again. + err = logic.DisableRecovery() + require.NoError(t, err) + err = utils.RunSQLs(t, []string{`STOP SLAVE;`, + `SET GTID_NEXT="12345678-1234-1234-1234-123456789012:1";`, + `BEGIN;`, `COMMIT;`, + `SET GTID_NEXT="AUTOMATIC";`, + `START SLAVE;`, + }, replica, "") + require.NoError(t, err) + err = logic.EnableRecovery() require.NoError(t, err) replicaInstance, err := inst.ReadTopologyInstanceBufferable(&inst.InstanceKey{ diff --git a/go/test/endtoend/vtorc/utils/utils.go b/go/test/endtoend/vtorc/utils/utils.go index d4f23c0de70..fc118ef3ac5 100644 --- a/go/test/endtoend/vtorc/utils/utils.go +++ b/go/test/endtoend/vtorc/utils/utils.go @@ -316,7 +316,7 @@ func SetupVttabletsAndVTOrcs(t *testing.T, clusterInfo *VTOrcClusterInfo, numRep // cleanAndStartVttablet cleans the MySQL instance underneath for running a new test. It also starts the vttablet. func cleanAndStartVttablet(t *testing.T, clusterInfo *VTOrcClusterInfo, vttablet *cluster.Vttablet) { t.Helper() - // set super-read-only to false + // set super_read_only to false _, err := RunSQL(t, "SET GLOBAL super_read_only = OFF", vttablet, "") require.NoError(t, err) // remove the databases if they exist @@ -585,6 +585,26 @@ func RunSQL(t *testing.T, sql string, tablet *cluster.Vttablet, db string) (*sql return execute(t, conn, sql) } +// RunSQLs is used to run a list of SQL statements on the given tablet +func RunSQLs(t *testing.T, sqls []string, tablet *cluster.Vttablet, db string) error { + // Get Connection + tabletParams := getMysqlConnParam(tablet, db) + var timeoutDuration = time.Duration(5 * len(sqls)) + ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration*time.Second) + defer cancel() + conn, err := mysql.Connect(ctx, &tabletParams) + require.Nil(t, err) + defer conn.Close() + + // Run SQLs + for _, sql := range sqls { + if _, err := execute(t, conn, sql); err != nil { + return err + } + } + return nil +} + func execute(t *testing.T, conn *mysql.Conn, query string) (*sqltypes.Result, error) { t.Helper() return conn.ExecuteFetch(query, 1000, true) diff --git a/go/vt/dbconfigs/dbconfigs.go b/go/vt/dbconfigs/dbconfigs.go index 371892144d3..940652094c9 100644 --- a/go/vt/dbconfigs/dbconfigs.go +++ b/go/vt/dbconfigs/dbconfigs.go @@ -216,12 +216,12 @@ func (dbcfgs *DBConfigs) AppDebugWithDB() Connector { return dbcfgs.makeParams(&dbcfgs.appdebugParams, true) } -// AllPrivsConnector returns connection parameters for appdebug with no dbname set. +// AllPrivsConnector returns connection parameters for allprivs with no dbname set. func (dbcfgs *DBConfigs) AllPrivsConnector() Connector { return dbcfgs.makeParams(&dbcfgs.allprivsParams, false) } -// AllPrivsWithDB returns connection parameters for appdebug with dbname set. +// AllPrivsWithDB returns connection parameters for allprivs with dbname set. func (dbcfgs *DBConfigs) AllPrivsWithDB() Connector { return dbcfgs.makeParams(&dbcfgs.allprivsParams, true) } diff --git a/go/vt/mysqlctl/backup.go b/go/vt/mysqlctl/backup.go index 0865759f2a3..b1751ba3bd9 100644 --- a/go/vt/mysqlctl/backup.go +++ b/go/vt/mysqlctl/backup.go @@ -25,22 +25,19 @@ import ( "strings" "time" + "github.com/spf13/pflag" "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/spf13/pflag" - - "vitess.io/vitess/go/vt/servenv" - - "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/mysqlctl/backupstats" - stats "vitess.io/vitess/go/vt/mysqlctl/backupstats" "vitess.io/vitess/go/vt/mysqlctl/backupstorage" "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/servenv" "vitess.io/vitess/go/vt/topo/topoproto" "vitess.io/vitess/go/vt/vterrors" + stats "vitess.io/vitess/go/vt/mysqlctl/backupstats" topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) @@ -390,21 +387,6 @@ func Restore(ctx context.Context, params RestoreParams) (*BackupManifest, error) return nil, err } - // We disable super_read_only, in case it is in the default MySQL startup - // parameters and will be blocking the writes we need to do in - // PopulateMetadataTables(). We do it blindly, since - // this will fail on MariaDB, which doesn't have super_read_only - // This is safe, since we're restarting MySQL after the restore anyway - params.Logger.Infof("Restore: disabling super_read_only") - if err := params.Mysqld.SetSuperReadOnly(false); err != nil { - if strings.Contains(err.Error(), mysql.ERUnknownSystemVariable.ToString()) { - params.Logger.Warningf("Restore: server does not know about super_read_only, continuing anyway...") - } else { - params.Logger.Errorf("Restore: unexpected error while trying to set super_read_only: %v", err) - return nil, err - } - } - params.Logger.Infof("Restore: running mysql_upgrade") if err := params.Mysqld.RunMysqlUpgrade(); err != nil { return nil, vterrors.Wrap(err, "mysql_upgrade failed") diff --git a/go/vt/mysqlctl/builtinbackupengine.go b/go/vt/mysqlctl/builtinbackupengine.go index 68625ede3ef..7c16aec37b7 100644 --- a/go/vt/mysqlctl/builtinbackupengine.go +++ b/go/vt/mysqlctl/builtinbackupengine.go @@ -318,7 +318,8 @@ func (be *BuiltinBackupEngine) executeFullBackup(ctx context.Context, params Bac // Save initial state so we can restore. replicaStartRequired := false sourceIsPrimary := false - readOnly := true //nolint + superReadOnly := true //nolint + readOnly := true //nolint var replicationPosition mysql.Position semiSyncSource, semiSyncReplica := params.Mysqld.SemiSyncEnabled() @@ -338,16 +339,30 @@ func (be *BuiltinBackupEngine) executeFullBackup(ctx context.Context, params Bac // get the read-only flag readOnly, err = params.Mysqld.IsReadOnly() if err != nil { - return false, vterrors.Wrap(err, "can't get read-only status") + return false, vterrors.Wrap(err, "failed to get read_only status") } + superReadOnly, err = params.Mysqld.IsSuperReadOnly() + if err != nil { + return false, vterrors.Wrap(err, "can't get super_read_only status") + } + log.Infof("Flag values during full backup, read_only: %v, super_read_only:%t", readOnly, superReadOnly) // get the replication position if sourceIsPrimary { - if !readOnly { - params.Logger.Infof("turning primary read-only before backup") - if err = params.Mysqld.SetReadOnly(true); err != nil { - return false, vterrors.Wrap(err, "can't set read-only status") + // No need to set read_only because super_read_only will implicitly set read_only to true as well. + if !superReadOnly { + params.Logger.Infof("Enabling super_read_only on primary prior to backup") + if _, err = params.Mysqld.SetSuperReadOnly(true); err != nil { + return false, vterrors.Wrap(err, "failed to enable super_read_only") } + defer func() { + // Resetting super_read_only back to its original value + params.Logger.Infof("resetting mysqld super_read_only to %v", superReadOnly) + if _, err := params.Mysqld.SetSuperReadOnly(false); err != nil { + log.Error("Failed to set super_read_only back to its original value") + } + }() + } replicationPosition, err = params.Mysqld.PrimaryPosition() if err != nil { @@ -398,9 +413,9 @@ func (be *BuiltinBackupEngine) executeFullBackup(ctx context.Context, params Bac return usable, vterrors.Wrap(err, "can't restart mysqld") } - // And set read-only mode - params.Logger.Infof("resetting mysqld read-only to %v", readOnly) - if err := params.Mysqld.SetReadOnly(readOnly); err != nil { + // Resetting super_read_only back to its original value + params.Logger.Infof("resetting mysqld super_read_only to %v", superReadOnly) + if _, err := params.Mysqld.SetSuperReadOnly(superReadOnly); err != nil { return usable, err } @@ -785,7 +800,6 @@ func (be *BuiltinBackupEngine) executeRestoreFullBackup(ctx context.Context, par // The underlying mysql database is expected to be up and running. func (be *BuiltinBackupEngine) executeRestoreIncrementalBackup(ctx context.Context, params RestoreParams, bh backupstorage.BackupHandle, bm builtinBackupManifest) error { params.Logger.Infof("Restoring incremental backup to position: %v", bm.Position) - createdDir, err := be.restoreFiles(context.Background(), params, bh, bm) defer os.RemoveAll(createdDir) mysqld, ok := params.Mysqld.(*Mysqld) @@ -819,7 +833,6 @@ func (be *BuiltinBackupEngine) executeRestoreIncrementalBackup(ctx context.Conte func (be *BuiltinBackupEngine) ExecuteRestore(ctx context.Context, params RestoreParams, bh backupstorage.BackupHandle) (*BackupManifest, error) { var bm builtinBackupManifest - if err := getBackupManifestInto(ctx, bh, &bm); err != nil { return nil, err } diff --git a/go/vt/mysqlctl/fakemysqldaemon.go b/go/vt/mysqlctl/fakemysqldaemon.go index 25b4f328de4..475d4251fbc 100644 --- a/go/vt/mysqlctl/fakemysqldaemon.go +++ b/go/vt/mysqlctl/fakemysqldaemon.go @@ -101,7 +101,7 @@ type FakeMysqlDaemon struct { ReadOnly bool // SuperReadOnly is the current value of the flag - SuperReadOnly bool + SuperReadOnly atomic.Bool // SetReplicationPositionPos is matched against the input of SetReplicationPosition. // If it doesn't match, SetReplicationPosition will return an error. @@ -364,6 +364,11 @@ func (fmd *FakeMysqlDaemon) IsReadOnly() (bool, error) { return fmd.ReadOnly, nil } +// IsSuperReadOnly is part of the MysqlDaemon interface +func (fmd *FakeMysqlDaemon) IsSuperReadOnly() (bool, error) { + return fmd.SuperReadOnly.Load(), nil +} + // SetReadOnly is part of the MysqlDaemon interface func (fmd *FakeMysqlDaemon) SetReadOnly(on bool) error { fmd.ReadOnly = on @@ -371,10 +376,10 @@ func (fmd *FakeMysqlDaemon) SetReadOnly(on bool) error { } // SetSuperReadOnly is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) SetSuperReadOnly(on bool) error { - fmd.SuperReadOnly = on +func (fmd *FakeMysqlDaemon) SetSuperReadOnly(on bool) (ResetSuperReadOnlyFunc, error) { + fmd.SuperReadOnly.Store(on) fmd.ReadOnly = on - return nil + return nil, nil } // StartReplication is part of the MysqlDaemon interface. diff --git a/go/vt/mysqlctl/mysql_daemon.go b/go/vt/mysqlctl/mysql_daemon.go index 020595b0277..190b81fb001 100644 --- a/go/vt/mysqlctl/mysql_daemon.go +++ b/go/vt/mysqlctl/mysql_daemon.go @@ -72,8 +72,9 @@ type MysqlDaemon interface { ResetReplication(ctx context.Context) error PrimaryPosition() (mysql.Position, error) IsReadOnly() (bool, error) + IsSuperReadOnly() (bool, error) SetReadOnly(on bool) error - SetSuperReadOnly(on bool) error + SetSuperReadOnly(on bool) (ResetSuperReadOnlyFunc, error) SetReplicationPosition(ctx context.Context, pos mysql.Position) error SetReplicationSource(ctx context.Context, host string, port int32, stopReplicationBefore bool, startReplicationAfter bool) error WaitForReparentJournal(ctx context.Context, timeCreatedNS int64) error diff --git a/go/vt/mysqlctl/mysqld.go b/go/vt/mysqlctl/mysqld.go index f91d880bc5f..19ab541a322 100644 --- a/go/vt/mysqlctl/mysqld.go +++ b/go/vt/mysqlctl/mysqld.go @@ -667,7 +667,7 @@ func (mysqld *Mysqld) InitConfig(cnf *Mycnf) error { // generate / configure a my.cnf file install a skeleton database, // and apply the provided initial SQL file. func (mysqld *Mysqld) Init(ctx context.Context, cnf *Mycnf, initDBSQLFile string) error { - log.Infof("mysqlctl.Init") + log.Infof("mysqlctl.Init running with contents previously embedded from %s", initDBSQLFile) err := mysqld.InitConfig(cnf) if err != nil { log.Errorf("%s", err.Error()) @@ -695,7 +695,6 @@ func (mysqld *Mysqld) Init(ctx context.Context, cnf *Mycnf, initDBSQLFile string log.Errorf("failed starting mysqld in time: %v\n%v", err, readTailOfMysqldErrorLog(cnf.ErrorLogPath)) return err } - if initDBSQLFile == "" { // default to built-in if err := mysqld.executeMysqlScript(params, strings.NewReader(config.DefaultInitDB)); err != nil { return fmt.Errorf("failed to initialize mysqld: %v", err) @@ -1223,6 +1222,7 @@ func (mysqld *Mysqld) applyBinlogFile(binlogFile string, includeGTIDs mysql.GTID gtids, ) } + args = append(args, binlogFile) mysqlbinlogCmd = exec.Command(name, args...) @@ -1251,6 +1251,29 @@ func (mysqld *Mysqld) applyBinlogFile(binlogFile string, includeGTIDs mysql.GTID args := []string{ "--defaults-extra-file=" + cnf, } + + // We disable super_read_only, in case it is in the default MySQL startup + // parameters. We do it blindly, since this will fail on MariaDB, which doesn't + // have super_read_only This is safe, since we're restarting MySQL after the restore anyway + log.Infof("applyBinlogFile: disabling super_read_only") + resetFunc, err := mysqld.SetSuperReadOnly(false) + if err != nil { + if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERUnknownSystemVariable { + log.Warningf("applyBinlogFile: server does not know about super_read_only, continuing anyway...") + } else { + log.Errorf("applyBinlogFile: unexpected error while trying to set super_read_only: %v", err) + return err + } + } + if resetFunc != nil { + defer func() { + err := resetFunc() + if err != nil { + log.Error("Not able to set super_read_only to its original value during applyBinlogFile.") + } + }() + } + mysqlCmd = exec.Command(name, args...) mysqlCmd.Dir = dir mysqlCmd.Env = env diff --git a/go/vt/mysqlctl/replication.go b/go/vt/mysqlctl/replication.go index 3a4aee6e063..10258b16d2c 100644 --- a/go/vt/mysqlctl/replication.go +++ b/go/vt/mysqlctl/replication.go @@ -37,6 +37,8 @@ import ( "vitess.io/vitess/go/vt/log" ) +type ResetSuperReadOnlyFunc func() error + // WaitForReplicationStart waits until the deadline for replication to start. // This validates the current primary is correct and can be connected to. func WaitForReplicationStart(mysqld MysqlDaemon, replicaStartDeadline int) error { @@ -230,6 +232,22 @@ func (mysqld *Mysqld) IsReadOnly() (bool, error) { return false, nil } +// IsSuperReadOnly return true if the instance is super read only +func (mysqld *Mysqld) IsSuperReadOnly() (bool, error) { + qr, err := mysqld.FetchSuperQuery(context.TODO(), "SELECT @@global.super_read_only") + if err != nil { + return false, err + } + if err == nil && len(qr.Rows) == 1 { + sro := qr.Rows[0][0].ToString() + if sro == "1" || sro == "ON" { + return true, nil + } + } + + return false, nil +} + // SetReadOnly set/unset the read_only flag func (mysqld *Mysqld) SetReadOnly(on bool) error { // temp logging, to be removed in v17 @@ -252,15 +270,52 @@ func (mysqld *Mysqld) SetReadOnly(on bool) error { return mysqld.ExecuteSuperQuery(context.TODO(), query) } -// SetSuperReadOnly set/unset the super_read_only flag -func (mysqld *Mysqld) SetSuperReadOnly(on bool) error { +// SetSuperReadOnly set/unset the super_read_only flag. +// Returns a function which is called to set super_read_only back to its original value. +func (mysqld *Mysqld) SetSuperReadOnly(on bool) (ResetSuperReadOnlyFunc, error) { + // return function for switching `OFF` super_read_only + var resetFunc ResetSuperReadOnlyFunc + var disableFunc = func() error { + query := "SET GLOBAL super_read_only = 'OFF'" + err := mysqld.ExecuteSuperQuery(context.Background(), query) + return err + } + + // return function for switching `ON` super_read_only. + var enableFunc = func() error { + query := "SET GLOBAL super_read_only = 'ON'" + err := mysqld.ExecuteSuperQuery(context.Background(), query) + return err + } + + superReadOnlyEnabled, err := mysqld.IsSuperReadOnly() + if err != nil { + return nil, err + } + + // If non-idempotent then set the right call-back. + // We are asked to turn on super_read_only but original value is false, + // therefore return disableFunc, that can be used as defer by caller. + if on && !superReadOnlyEnabled { + resetFunc = disableFunc + } + // We are asked to turn off super_read_only but original value is true, + // therefore return enableFunc, that can be used as defer by caller. + if !on && superReadOnlyEnabled { + resetFunc = enableFunc + } + query := "SET GLOBAL super_read_only = " if on { - query += "ON" + query += "'ON'" } else { - query += "OFF" + query += "'OFF'" } - return mysqld.ExecuteSuperQuery(context.TODO(), query) + if err := mysqld.ExecuteSuperQuery(context.Background(), query); err != nil { + return nil, err + } + + return resetFunc, nil } // WaitSourcePos lets replicas wait to given replication position diff --git a/go/vt/proto/replicationdata/replicationdata.pb.go b/go/vt/proto/replicationdata/replicationdata.pb.go index 55bcdf99b55..58aedab8bbc 100644 --- a/go/vt/proto/replicationdata/replicationdata.pb.go +++ b/go/vt/proto/replicationdata/replicationdata.pb.go @@ -441,6 +441,7 @@ type FullStatus struct { SemiSyncPrimaryClients uint32 `protobuf:"varint,18,opt,name=semi_sync_primary_clients,json=semiSyncPrimaryClients,proto3" json:"semi_sync_primary_clients,omitempty"` SemiSyncPrimaryTimeout uint64 `protobuf:"varint,19,opt,name=semi_sync_primary_timeout,json=semiSyncPrimaryTimeout,proto3" json:"semi_sync_primary_timeout,omitempty"` SemiSyncWaitForReplicaCount uint32 `protobuf:"varint,20,opt,name=semi_sync_wait_for_replica_count,json=semiSyncWaitForReplicaCount,proto3" json:"semi_sync_wait_for_replica_count,omitempty"` + SuperReadOnly bool `protobuf:"varint,21,opt,name=super_read_only,json=superReadOnly,proto3" json:"super_read_only,omitempty"` } func (x *FullStatus) Reset() { @@ -615,6 +616,13 @@ func (x *FullStatus) GetSemiSyncWaitForReplicaCount() uint32 { return 0 } +func (x *FullStatus) GetSuperReadOnly() bool { + if x != nil { + return x.SuperReadOnly + } + return false +} + var File_replicationdata_proto protoreflect.FileDescriptor var file_replicationdata_proto_rawDesc = []byte{ @@ -690,7 +698,7 @@ var file_replicationdata_proto_rawDesc = []byte{ 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x66, 0x69, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc3, 0x07, 0x0a, + 0x66, 0x69, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xeb, 0x07, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, @@ -751,14 +759,16 @@ var file_replicationdata_proto_rawDesc = []byte{ 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1b, 0x73, 0x65, 0x6d, 0x69, 0x53, 0x79, 0x6e, 0x63, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x2a, 0x3b, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4f, 0x41, - 0x4e, 0x44, 0x53, 0x51, 0x4c, 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, - 0x0c, 0x49, 0x4f, 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x42, - 0x2e, 0x5a, 0x2c, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, - 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x61, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x64, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x2a, 0x3b, 0x0a, 0x13, 0x53, 0x74, + 0x6f, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, + 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4f, 0x41, 0x4e, 0x44, 0x53, 0x51, 0x4c, 0x54, 0x48, 0x52, + 0x45, 0x41, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4f, 0x54, 0x48, 0x52, 0x45, 0x41, + 0x44, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x76, 0x69, 0x74, 0x65, 0x73, + 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, + 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/vt/proto/replicationdata/replicationdata_vtproto.pb.go b/go/vt/proto/replicationdata/replicationdata_vtproto.pb.go index 350a733e865..64e5a8d8b94 100644 --- a/go/vt/proto/replicationdata/replicationdata_vtproto.pb.go +++ b/go/vt/proto/replicationdata/replicationdata_vtproto.pb.go @@ -354,6 +354,18 @@ func (m *FullStatus) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.SuperReadOnly { + i-- + if m.SuperReadOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa8 + } if m.SemiSyncWaitForReplicaCount != 0 { i = encodeVarint(dAtA, i, uint64(m.SemiSyncWaitForReplicaCount)) i-- @@ -734,6 +746,9 @@ func (m *FullStatus) SizeVT() (n int) { if m.SemiSyncWaitForReplicaCount != 0 { n += 2 + sov(uint64(m.SemiSyncWaitForReplicaCount)) } + if m.SuperReadOnly { + n += 3 + } n += len(m.unknownFields) return n } @@ -2127,6 +2142,26 @@ func (m *FullStatus) UnmarshalVT(dAtA []byte) error { break } } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SuperReadOnly", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.SuperReadOnly = bool(v != 0) default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/go/vt/vtexplain/vtexplain_test.go b/go/vt/vtexplain/vtexplain_test.go index 8145c59b44d..3ba417778da 100644 --- a/go/vt/vtexplain/vtexplain_test.go +++ b/go/vt/vtexplain/vtexplain_test.go @@ -24,15 +24,15 @@ import ( "strings" "testing" - "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv/tabletenvtest" - "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "vitess.io/vitess/go/vt/key" - querypb "vitess.io/vitess/go/vt/proto/query" "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv/tabletenvtest" + + querypb "vitess.io/vitess/go/vt/proto/query" ) func defaultTestOpts() *Options { diff --git a/go/vt/vtexplain/vtexplain_vttablet.go b/go/vt/vtexplain/vtexplain_vttablet.go index a1bef6547fc..94c03671659 100644 --- a/go/vt/vtexplain/vtexplain_vttablet.go +++ b/go/vt/vtexplain/vtexplain_vttablet.go @@ -496,6 +496,30 @@ func (t *explainTablet) HandleQuery(c *mysql.Conn, query string, callback func(* t.mu.Lock() defer t.mu.Unlock() + // If query is part of rejected list then return error right away. + if err := t.db.GetRejectedQueryResult(query); err != nil { + return err + } + + // If query is expected to have a specific result then return the result. + if result := t.db.GetQueryResult(query); result != nil { + if f := result.BeforeFunc; f != nil { + f() + } + return callback(result.Result) + } + + // return result if query is part of defined pattern. + if userCallback, expResult, ok, err := t.db.GetQueryPatternResult(query); ok { + if userCallback != nil { + userCallback(query) + } + if err != nil { + return err + } + return callback(expResult.Result) + } + if !strings.Contains(query, "1 != 1") { t.mysqlQueries = append(t.mysqlQueries, &MysqlQuery{ Time: t.currentTime, @@ -506,13 +530,11 @@ func (t *explainTablet) HandleQuery(c *mysql.Conn, query string, callback func(* // return the pre-computed results for any schema introspection queries tEnv := t.vte.getGlobalTabletEnv() result := tEnv.getResult(query) - emptyResult := &sqltypes.Result{} - if sidecardb.MatchesInitQuery(query) { - return callback(emptyResult) - } + if result != nil { return callback(result) } + switch sqlparser.Preview(query) { case sqlparser.StmtSelect: var err error diff --git a/go/vt/vttablet/tabletmanager/rpc_replication.go b/go/vt/vttablet/tabletmanager/rpc_replication.go index 196014b8271..445e8d94c8b 100644 --- a/go/vt/vttablet/tabletmanager/rpc_replication.go +++ b/go/vt/vttablet/tabletmanager/rpc_replication.go @@ -37,11 +37,11 @@ import ( topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) -var setSuperReadOnly bool var disableReplicationManager bool func registerReplicationFlags(fs *pflag.FlagSet) { - fs.BoolVar(&setSuperReadOnly, "use_super_read_only", setSuperReadOnly, "Set super_read_only flag when performing planned failover.") + fs.Bool("use_super_read_only", true, "Set super_read_only flag when performing planned failover.") + fs.MarkDeprecated("use_super_read_only", "From v17 onwards MySQL server will always try to start with super_read_only=ON") fs.BoolVar(&disableReplicationManager, "disable-replication-manager", disableReplicationManager, "Disable replication manager to prevent replication repairs.") fs.MarkDeprecated("disable-replication-manager", "Replication manager is deleted") } @@ -112,6 +112,12 @@ func (tm *TabletManager) FullStatus(ctx context.Context) (*replicationdatapb.Ful return nil, err } + // superReadOnly - "SELECT @@global.super_read_only" + superReadOnly, err := tm.MysqlDaemon.IsSuperReadOnly() + if err != nil { + return nil, err + } + // Binlog Information - "select @@global.binlog_format, @@global.log_bin, @@global.log_slave_updates, @@global.binlog_row_image" binlogFormat, logBin, logReplicaUpdates, binlogRowImage, err := tm.MysqlDaemon.GetBinlogInformation(ctx) if err != nil { @@ -157,6 +163,7 @@ func (tm *TabletManager) FullStatus(ctx context.Context) (*replicationdatapb.Ful SemiSyncPrimaryClients: semiSyncClients, SemiSyncPrimaryTimeout: semiSyncTimeout, SemiSyncWaitForReplicaCount: semiSyncNumReplicas, + SuperReadOnly: superReadOnly, }, nil } @@ -297,14 +304,12 @@ func (tm *TabletManager) InitPrimary(ctx context.Context, semiSync bool) (string } defer tm.unlock() - if setSuperReadOnly { - // Setting super_read_only off so that we can run the DDL commands - if err := tm.MysqlDaemon.SetSuperReadOnly(false); err != nil { - if strings.Contains(err.Error(), mysql.ERUnknownSystemVariable.ToString()) { - log.Warningf("server does not know about super_read_only, continuing anyway...") - } else { - return "", err - } + // Setting super_read_only `OFF` so that we can run the DDL commands + if _, err := tm.MysqlDaemon.SetSuperReadOnly(false); err != nil { + if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERUnknownSystemVariable { + log.Warningf("server does not know about super_read_only, continuing anyway...") + } else { + return "", err } } @@ -463,23 +468,17 @@ func (tm *TabletManager) demotePrimary(ctx context.Context, revertPartialFailure } // Now that we know no writes are in-flight and no new writes can occur, - // set MySQL to read-only mode. If we are already read-only because of a + // set MySQL to super_read_only mode. If we are already super_read_only because of a // previous demotion, or because we are not primary anyway, this should be // idempotent. - if setSuperReadOnly { - // Setting super_read_only also sets read_only - if err := tm.MysqlDaemon.SetSuperReadOnly(true); err != nil { - if strings.Contains(err.Error(), mysql.ERUnknownSystemVariable.ToString()) { - log.Warningf("server does not know about super_read_only, continuing anyway...") - } else { - return nil, err - } - } - } else { - if err := tm.MysqlDaemon.SetReadOnly(true); err != nil { + if _, err := tm.MysqlDaemon.SetSuperReadOnly(true); err != nil { + if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERUnknownSystemVariable { + log.Warningf("server does not know about super_read_only, continuing anyway...") + } else { return nil, err } } + defer func() { if finalErr != nil && revertPartialFailure && !wasReadOnly { // setting read_only OFF will also set super_read_only OFF if it was set diff --git a/go/vt/vttablet/tabletmanager/tm_init.go b/go/vt/vttablet/tabletmanager/tm_init.go index c4e1c425667..06f42e8bee6 100644 --- a/go/vt/vttablet/tabletmanager/tm_init.go +++ b/go/vt/vttablet/tabletmanager/tm_init.go @@ -96,7 +96,6 @@ func registerInitFlags(fs *pflag.FlagSet) { fs.StringVar(&initDbNameOverride, "init_db_name_override", initDbNameOverride, "(init parameter) override the name of the db used by vttablet. Without this flag, the db name defaults to vt_") fs.StringVar(&skipBuildInfoTags, "vttablet_skip_buildinfo_tags", skipBuildInfoTags, "comma-separated list of buildinfo tags to skip from merging with --init_tags. each tag is either an exact match or a regular expression of the form '/regexp/'.") fs.Var(&initTags, "init_tags", "(init parameter) comma separated list of key:value pairs used to tag the tablet") - fs.BoolVar(&initPopulateMetadata, "init_populate_metadata", initPopulateMetadata, "(init parameter) populate metadata tables even if restore_from_backup is disabled. If restore_from_backup is enabled, metadata tables are always populated regardless of this flag.") fs.MarkDeprecated("init_populate_metadata", "this flag is no longer being used and will be removed in future versions") fs.DurationVar(&initTimeout, "init_timeout", initTimeout, "(init parameter) timeout to use for the init phase.") diff --git a/proto/replicationdata.proto b/proto/replicationdata.proto index 536ea2c4d13..2f98e30576f 100644 --- a/proto/replicationdata.proto +++ b/proto/replicationdata.proto @@ -93,4 +93,5 @@ message FullStatus { uint32 semi_sync_primary_clients = 18; uint64 semi_sync_primary_timeout = 19; uint32 semi_sync_wait_for_replica_count = 20; + bool super_read_only = 21; } diff --git a/web/vtadmin/src/proto/vtadmin.d.ts b/web/vtadmin/src/proto/vtadmin.d.ts index b05b8420064..79f8c2ead63 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -32005,6 +32005,9 @@ export namespace replicationdata { /** FullStatus semi_sync_wait_for_replica_count */ semi_sync_wait_for_replica_count?: (number|null); + + /** FullStatus super_read_only */ + super_read_only?: (boolean|null); } /** Represents a FullStatus. */ @@ -32076,6 +32079,9 @@ export namespace replicationdata { /** FullStatus semi_sync_wait_for_replica_count. */ public semi_sync_wait_for_replica_count: number; + /** FullStatus super_read_only. */ + public super_read_only: boolean; + /** * Creates a new FullStatus instance using the specified properties. * @param [properties] Properties to set diff --git a/web/vtadmin/src/proto/vtadmin.js b/web/vtadmin/src/proto/vtadmin.js index 9f05ca795a5..0e32a463451 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -75777,6 +75777,7 @@ $root.replicationdata = (function() { * @property {number|null} [semi_sync_primary_clients] FullStatus semi_sync_primary_clients * @property {number|Long|null} [semi_sync_primary_timeout] FullStatus semi_sync_primary_timeout * @property {number|null} [semi_sync_wait_for_replica_count] FullStatus semi_sync_wait_for_replica_count + * @property {boolean|null} [super_read_only] FullStatus super_read_only */ /** @@ -75954,6 +75955,14 @@ $root.replicationdata = (function() { */ FullStatus.prototype.semi_sync_wait_for_replica_count = 0; + /** + * FullStatus super_read_only. + * @member {boolean} super_read_only + * @memberof replicationdata.FullStatus + * @instance + */ + FullStatus.prototype.super_read_only = false; + /** * Creates a new FullStatus instance using the specified properties. * @function create @@ -76018,6 +76027,8 @@ $root.replicationdata = (function() { writer.uint32(/* id 19, wireType 0 =*/152).uint64(message.semi_sync_primary_timeout); if (message.semi_sync_wait_for_replica_count != null && Object.hasOwnProperty.call(message, "semi_sync_wait_for_replica_count")) writer.uint32(/* id 20, wireType 0 =*/160).uint32(message.semi_sync_wait_for_replica_count); + if (message.super_read_only != null && Object.hasOwnProperty.call(message, "super_read_only")) + writer.uint32(/* id 21, wireType 0 =*/168).bool(message.super_read_only); return writer; }; @@ -76112,6 +76123,9 @@ $root.replicationdata = (function() { case 20: message.semi_sync_wait_for_replica_count = reader.uint32(); break; + case 21: + message.super_read_only = reader.bool(); + break; default: reader.skipType(tag & 7); break; @@ -76211,6 +76225,9 @@ $root.replicationdata = (function() { if (message.semi_sync_wait_for_replica_count != null && message.hasOwnProperty("semi_sync_wait_for_replica_count")) if (!$util.isInteger(message.semi_sync_wait_for_replica_count)) return "semi_sync_wait_for_replica_count: integer expected"; + if (message.super_read_only != null && message.hasOwnProperty("super_read_only")) + if (typeof message.super_read_only !== "boolean") + return "super_read_only: boolean expected"; return null; }; @@ -76279,6 +76296,8 @@ $root.replicationdata = (function() { message.semi_sync_primary_timeout = new $util.LongBits(object.semi_sync_primary_timeout.low >>> 0, object.semi_sync_primary_timeout.high >>> 0).toNumber(true); if (object.semi_sync_wait_for_replica_count != null) message.semi_sync_wait_for_replica_count = object.semi_sync_wait_for_replica_count >>> 0; + if (object.super_read_only != null) + message.super_read_only = Boolean(object.super_read_only); return message; }; @@ -76320,6 +76339,7 @@ $root.replicationdata = (function() { } else object.semi_sync_primary_timeout = options.longs === String ? "0" : 0; object.semi_sync_wait_for_replica_count = 0; + object.super_read_only = false; } if (message.server_id != null && message.hasOwnProperty("server_id")) object.server_id = message.server_id; @@ -76364,6 +76384,8 @@ $root.replicationdata = (function() { object.semi_sync_primary_timeout = options.longs === String ? $util.Long.prototype.toString.call(message.semi_sync_primary_timeout) : options.longs === Number ? new $util.LongBits(message.semi_sync_primary_timeout.low >>> 0, message.semi_sync_primary_timeout.high >>> 0).toNumber(true) : message.semi_sync_primary_timeout; if (message.semi_sync_wait_for_replica_count != null && message.hasOwnProperty("semi_sync_wait_for_replica_count")) object.semi_sync_wait_for_replica_count = message.semi_sync_wait_for_replica_count; + if (message.super_read_only != null && message.hasOwnProperty("super_read_only")) + object.super_read_only = message.super_read_only; return object; };