diff --git a/.github/workflows/upgrade_downgrade_test_backups_e2e.yml b/.github/workflows/upgrade_downgrade_test_backups_e2e.yml index b7f6ddc092a..ee94ead08cd 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 29add46604e..54ee5457f40 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 2e2490b53b8..4aafdc7d7ad 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 f58dff29f84..50f493afe2a 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 c0ca258c1db..82071eda28d 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 ca75c2fbf6d..0c6bf84f896 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 116a211cfe1..a309d1fc99a 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 a1bfe3fb8e9..4add0acab41 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 e288c5b9c26..12fde50dc98 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 ad1febeab85..de97a18a095 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..031b889c89b 100644 --- a/config/init_db.sql +++ b/config/init_db.sql @@ -11,6 +11,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 +83,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 +# add custom sql here + +# We need to set super_read_only back to what it was before +SET GLOBAL super_read_only=IFNULL(@original_super_read_only, 'OFF'); diff --git a/config/init_testserver_db.sql b/config/init_testserver_db.sql new file mode 100644 index 00000000000..e79083e400e --- /dev/null +++ b/config/init_testserver_db.sql @@ -0,0 +1,91 @@ +# This file is for testing purpose only. +# This file is executed immediately after mysql_install_db, to initialize 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 +# add custom sql here 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..d23efa54a30 100644 --- a/config/mycnf/test-suite.cnf +++ b/config/mycnf/test-suite.cnf @@ -23,3 +23,4 @@ sql_mode = STRICT_TRANS_TABLES # set a short heartbeat interval in order to detect failures quickly slave_net_timeout = 4 +super-read-only = false diff --git a/go/cmd/vtbackup/vtbackup.go b/go/cmd/vtbackup/vtbackup.go index eba97493170..de67f24925f 100644 --- a/go/cmd/vtbackup/vtbackup.go +++ b/go/cmd/vtbackup/vtbackup.go @@ -294,6 +294,7 @@ func takeBackup(ctx context.Context, topoServer *topo.Server, backupStorage back } // In initial_backup mode, just take a backup of this empty database. if initialBackup { + log.Infof("Inside initialBackup creating initial binlog entry.") // Take a backup of this empty DB without restoring anything. // First, initialize it the way InitShardPrimary would, so this backup // produces a result that can be used to skip InitShardPrimary entirely. @@ -302,6 +303,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 database. + resetFunc, err := mysqld.SetSuperReadOnly(false) + if err != nil { + return fmt.Errorf("can't turn-off super-read-only during backup: %v", err) + } + if resetFunc != nil { + defer func() { + err := resetFunc() + if err != nil { + log.Info("not able to set super_read_only to its original value during backup") + } + }() + } cmd := mysqlctl.GenerateInitialBinlogEntry() if err := mysqld.ExecuteSuperQueryList(ctx, []string{cmd}); err != nil { return err @@ -390,6 +404,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 ed8c0f434a6..2e7676a0f07 100644 --- a/go/flags/endtoend/vttablet.txt +++ b/go/flags/endtoend/vttablet.txt @@ -347,7 +347,7 @@ 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. + --use_super_read_only Set super_read_only flag when performing planned failover. (default true) --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/collations/integration/helpers_test.go b/go/mysql/collations/integration/helpers_test.go index d185168d9d1..c2601421bc2 100644 --- a/go/mysql/collations/integration/helpers_test.go +++ b/go/mysql/collations/integration/helpers_test.go @@ -137,7 +137,7 @@ func verifyWeightString(t *testing.T, local collations.Collation, remote *remote } func exec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result { - res, err := conn.ExecuteFetch(query, -1, true) + res, err := conn.ExecuteFetchWithSuperReadOnlyHandling(query, -1, true) require.NoError(t, err, "failed to execute %q: %v", query, err) return res diff --git a/go/mysql/fakesqldb/server.go b/go/mysql/fakesqldb/server.go index 5b00b2c2e01..935797378c2 100644 --- a/go/mysql/fakesqldb/server.go +++ b/go/mysql/fakesqldb/server.go @@ -783,3 +783,39 @@ func (db *DB) MockQueriesForTable(table string, result *sqltypes.Result) { cols..., )) } + +func (db *DB) GetRejectedQueryResult(key string) error { + // check if we should reject it. + if err, ok := db.rejectedData[key]; ok { + return err + } + + return nil +} + +func (db *DB) GetQueryResult(key string) *ExpectedResult { + // Check explicit queries from AddQuery(). + result, ok := db.data[key] + if ok { + return result + } + return nil +} + +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/mysql/query.go b/go/mysql/query.go index 0107e7606bc..f969fd0d4eb 100644 --- a/go/mysql/query.go +++ b/go/mysql/query.go @@ -328,6 +328,74 @@ func (c *Conn) ExecuteFetchMulti(query string, maxrows int, wantfields bool) (re return res, more, err } +// ExecuteFetchWithSuperReadOnlyHandling should be used if you are executing a query +// on any tablet regardless of tablet type. +// This function will temporarily make the mysql instance read-write and +// re-enable read-only mode after the query is executed if needed. +func (c *Conn) ExecuteFetchWithSuperReadOnlyHandling(query string, maxrows int, wantfields bool) (result *sqltypes.Result, err error) { + // Note: MariaDB does not have super_read_only but support for it is EOL in v14.0+ + superReadOnlyEnabled := false + if !c.IsMariaDB() { + if err := c.WriteComQuery("SELECT @@global.super_read_only"); err != nil { + return nil, err + } + res, _, _, err := c.ReadQueryResult(1, false) + if err == nil && len(res.Rows) == 1 { + sro := res.Rows[0][0].ToString() + if sro == "1" || sro == "ON" { + superReadOnlyEnabled = true + if _, err = c.ExecuteFetch("SET GLOBAL super_read_only='OFF'", 1, false); err != nil { + return nil, err + } + } + } + } + + result, _, err = c.ExecuteFetchMulti(query, maxrows, wantfields) + // TODO: may be use it in defer() + if superReadOnlyEnabled { + if _, err := c.ExecuteFetch("SET GLOBAL super_read_only='ON'", 1, false); err != nil { + return nil, err + } + } + return result, err +} + +// SetSuperReadOnly tries to set super-read-only either `true` or `false` +// Since setting global super-read-only is idempotent we set them with finding current status +func (c *Conn) SetSuperReadOnly(enableSuperReadOnly bool) (result *sqltypes.Result, err error) { + var val = "OFF" + if enableSuperReadOnly { + val = "ON" + } + // Note: MariaDB does not have super_read_only but support for it is EOL in v14.0+ + if !c.IsMariaDB() { + query := fmt.Sprintf("SET GLOBAL super_read_only='%s'", val) + if err = c.WriteComQuery(query); err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("MariaDB not supported for super-read-only") + } + + return result, err +} + +// SetReadOnly tries to set read-only to false only if it is currently enable +func (c *Conn) SetReadOnly(enableReadOnly bool) (result *sqltypes.Result, err error) { + var val = "OFF" + if enableReadOnly { + val = "ON" + } + + query := fmt.Sprintf("SET GLOBAL read_only='%s'", val) + if err = c.WriteComQuery(query); err != nil { + return nil, err + } + + return result, err +} + // ExecuteFetchWithWarningCount is for fetching results and a warning count // Note: In a future iteration this should be abolished and merged into the // ExecuteFetch API. 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..aca83fe77fe 100644 --- a/go/test/endtoend/backup/vtbackup/backup_only_test.go +++ b/go/test/endtoend/backup/vtbackup/backup_only_test.go @@ -62,12 +62,21 @@ func TestTabletInitialBackup(t *testing.T) { // Initialize the tablets initTablets(t, false, false) - // Restore the Tablets + vtTabletVersion, err := cluster.GetMajorVersion("vttablet") + require.NoError(t, err) + // For all version above v15, each replica will start in super-read-only mode. + if vtTabletVersion > 15 { + 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 Tablets 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 +263,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) } @@ -302,7 +307,7 @@ func tearDown(t *testing.T, initMysql bool) { _, 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) + _, err = tablet.VttabletProcess.QueryTabletWithSuperReadOnlyHandling(fmt.Sprintf("drop database if exists %s", db), keyspaceName, true) require.Nil(t, err) } } diff --git a/go/test/endtoend/backup/vtbackup/main_test.go b/go/test/endtoend/backup/vtbackup/main_test.go index a5fc9d23c8b..54240296fa9 100644 --- a/go/test/endtoend/backup/vtbackup/main_test.go +++ b/go/test/endtoend/backup/vtbackup/main_test.go @@ -22,6 +22,7 @@ import ( "os" "os/exec" "path" + "strings" "testing" "vitess.io/vitess/go/test/endtoend/cluster" @@ -89,8 +90,15 @@ func TestMain(m *testing.M) { dbCredentialFile = cluster.WriteDbCredentialToTmp(localCluster.TmpDirectory) initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) sql := string(initDb) + // Since password update is DML we need to insert it before we disable + // super-read-only therefore doing the split below. + splitString := strings.Split(sql, "# add custom sql here") + if len(splitString) < 2 { + return 1, fmt.Errorf("missing `# add custom sql here` in init_db.sql file") + } + firstPart := splitString[0] + cluster.GetPasswordUpdateSQL(localCluster) + sql = firstPart + splitString[1] 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 @@ -126,11 +134,18 @@ func TestMain(m *testing.M) { return 1, err } } - - // Create database - for _, tablet := range []cluster.Vttablet{*primary, *replica1} { - if err := tablet.VttabletProcess.CreateDB(keyspaceName); err != nil { - return 1, err + vtTabletVersion, err := cluster.GetMajorVersion("vttablet") + if err != nil { + return 1, err + } + log.Infof("cluster.VtTabletMajorVersion: %d", vtTabletVersion) + // For downgrade / upgrade test, tablet version < 16 will not have super read only code handling + // Therefore we are explicitly setting super-read-only to `false` here. + if vtTabletVersion <= 15 { + for _, tablet := range []cluster.Vttablet{*primary, *replica1, *replica2} { + if err := tablet.VttabletProcess.SetSuperReadOnly("", false); err != nil { + return 1, err + } } } diff --git a/go/test/endtoend/backup/vtctlbackup/backup_utils.go b/go/test/endtoend/backup/vtctlbackup/backup_utils.go index 69334b51637..c4a42a0274f 100644 --- a/go/test/endtoend/backup/vtctlbackup/backup_utils.go +++ b/go/test/endtoend/backup/vtctlbackup/backup_utils.go @@ -118,8 +118,15 @@ func LaunchCluster(setupType int, streamMode string, stripes int, cDetails *Comp dbCredentialFile = cluster.WriteDbCredentialToTmp(localCluster.TmpDirectory) initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) sql := string(initDb) + // Since password update is DML we need to insert it before we disable + // super-read-only therefore doing the split below. + splitString := strings.Split(sql, "# add custom sql here") + if len(splitString) < 2 { + return 1, fmt.Errorf("missing `# add custom sql here` in init_db.sql file") + } + firstPart := splitString[0] + cluster.GetPasswordUpdateSQL(localCluster) + sql = firstPart + splitString[1] 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 @@ -207,9 +214,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 } @@ -674,8 +678,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/cluster/cluster_process.go b/go/test/endtoend/cluster/cluster_process.go index 712824b84dd..cbc4140b904 100644 --- a/go/test/endtoend/cluster/cluster_process.go +++ b/go/test/endtoend/cluster/cluster_process.go @@ -679,7 +679,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/mysqlctl_process.go b/go/test/endtoend/cluster/mysqlctl_process.go index eafc8f6b98f..cd857d13e6a 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" ) @@ -236,11 +237,18 @@ 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 { + var initFile = path.Join(os.Getenv("VTROOT"), "/config/init_db.sql") //default value + if isSQL, err := isSQLFlavor(); err == nil { + if !isSQL { + // execute init_db without `super_read_only` + initFile = path.Join(os.Getenv("VTROOT"), "config/init_testserver_db.sql") + } + } 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 @@ -249,6 +257,22 @@ func MysqlCtlProcessInstanceOptionalInit(tabletUID int, mySQLPort int, tmpDirect return mysqlctl } +func isSQLFlavor() (bool, error) { + versionStr, err := mysqlctl.GetVersionString() + if err != nil { + return false, err + } + flavor, _, err := mysqlctl.ParseVersionString(versionStr) + if err != nil { + return false, err + } + if flavor == mysqlctl.FlavorMySQL || flavor == mysqlctl.FlavorPercona { + return true, nil + } + + return false, nil +} + // MysqlCtlProcessInstance returns a Mysqlctl handle for mysqlctl process // configured with the given Config. func MysqlCtlProcessInstance(tabletUID int, mySQLPort int, tmpDirectory string) *MysqlctlProcess { diff --git a/go/test/endtoend/cluster/mysqlctld_process.go b/go/test/endtoend/cluster/mysqlctld_process.go index d71f2e3b1c8..e111cc9f7f0 100644 --- a/go/test/endtoend/cluster/mysqlctld_process.go +++ b/go/test/endtoend/cluster/mysqlctld_process.go @@ -145,11 +145,18 @@ 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 { + var initFile = path.Join(os.Getenv("VTROOT"), "/config/init_db.sql") //default value + if isSQL, err := isSQLFlavor(); err == nil { + if !isSQL { + // execute init_db without `super_read_only` + initFile = path.Join(os.Getenv("VTROOT"), "config/init_testserver_db.sql") + } + } 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 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/cluster/vttablet_process.go b/go/test/endtoend/cluster/vttablet_process.go index 68dfddb831e..9285a541e64 100644 --- a/go/test/endtoend/cluster/vttablet_process.go +++ b/go/test/endtoend/cluster/vttablet_process.go @@ -628,3 +628,69 @@ func VttabletProcessInstance(port, grpcPort, tabletUID int, cell, shard, keyspac return vttablet } + +// SetSuperReadOnly switch-off super-read-only flag in db +func (vttablet *VttabletProcess) SetSuperReadOnly(dbname string, enableSuperReadOnly bool) error { + conn, err := vttablet.defaultConn(dbname) + if err != nil { + log.Infof("error in getting connection object %s", err) + return err + } + defer conn.Close() + + _, err = conn.SetSuperReadOnly(enableSuperReadOnly) + return err +} + +// SetReadOnly switch-off read-only flag in db +func (vttablet *VttabletProcess) SetReadOnly(dbname string, enableReadOnly bool) error { + conn, err := vttablet.defaultConn(dbname) + if err != nil { + log.Infof("error in getting connection object %s", err) + return err + } + defer conn.Close() + + _, err = conn.SetReadOnly(enableReadOnly) + return err +} + +// QueryTabletWithSuperReadOnlyHandling lets you execute a query in this tablet while disabling super-read-only and get the result +// It will enable super-read-only once its done executing the query. +func (vttablet *VttabletProcess) QueryTabletWithSuperReadOnlyHandling(query string, keyspace string, useDb bool) (*sqltypes.Result, error) { + if !useDb { + keyspace = "" + } + dbParams := NewConnParams(vttablet.DbPort, vttablet.DbPassword, path.Join(vttablet.Directory, "mysql.sock"), keyspace) + conn, err := vttablet.conn(&dbParams) + if err != nil { + return nil, err + } + defer conn.Close() + return executeQueryWithSuperReadOnlyHandling(conn, query) +} + +// executeQuery will retry the query up to 10 times with a small sleep in between each try. +// This allows the tests to be more robust in the face of transient failures. It disables +// super-read-only during query execution. +func executeQueryWithSuperReadOnlyHandling(dbConn *mysql.Conn, query string) (*sqltypes.Result, error) { + var ( + err error + result *sqltypes.Result + ) + retries := 10 + retryDelay := 1 * time.Second + for i := 0; i < retries; i++ { + if i > 0 { + // We only audit from 2nd attempt and onwards, otherwise this is just too verbose. + log.Infof("Executing query %s (attempt %d of %d)", query, (i + 1), retries) + } + result, err = dbConn.ExecuteFetchWithSuperReadOnlyHandling(query, 10000, true) + if err == nil { + break + } + time.Sleep(retryDelay) + } + + return result, err +} diff --git a/go/test/endtoend/recovery/pitr/shardedpitr_test.go b/go/test/endtoend/recovery/pitr/shardedpitr_test.go index c7013557549..295c5afbc4f 100644 --- a/go/test/endtoend/recovery/pitr/shardedpitr_test.go +++ b/go/test/endtoend/recovery/pitr/shardedpitr_test.go @@ -448,8 +448,9 @@ func initializeCluster(t *testing.T) { for _, shard := range clusterInstance.Keyspaces[0].Shards { for _, tablet := range shard.Vttablets { + for _, query := range queryCmds { - _, err = tablet.VttabletProcess.QueryTablet(query, keyspace.Name, false) + _, err = tablet.VttabletProcess.QueryTabletWithSuperReadOnlyHandling(query, keyspace.Name, false) require.NoError(t, err) } diff --git a/go/test/endtoend/recovery/unshardedrecovery/recovery.go b/go/test/endtoend/recovery/unshardedrecovery/recovery.go index f9824c23b2f..b215f5df558 100644 --- a/go/test/endtoend/recovery/unshardedrecovery/recovery.go +++ b/go/test/endtoend/recovery/unshardedrecovery/recovery.go @@ -23,6 +23,7 @@ import ( "os" "os/exec" "path" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -94,13 +95,19 @@ func TestMainImpl(m *testing.M) { dbCredentialFile = cluster.WriteDbCredentialToTmp(localCluster.TmpDirectory) initDb, _ := os.ReadFile(path.Join(os.Getenv("VTROOT"), "/config/init_db.sql")) sql := string(initDb) - newInitDBFile = path.Join(localCluster.TmpDirectory, "init_db_with_passwords.sql") - sql = sql + cluster.GetPasswordUpdateSQL(localCluster) + // Since password update is DML we need to insert it before we disable + // super-read-only therefore doing the split below. + splitString := strings.Split(sql, "# add custom sql here") + if len(splitString) < 2 { + return 1, fmt.Errorf("missing `# add custom sql here` in init_db.sql file") + } + firstPart := splitString[0] + cluster.GetPasswordUpdateSQL(localCluster) + // https://github.com/vitessio/vitess/issues/8315 - oldAlterTableMode := ` -SET GLOBAL old_alter_table = ON; -` - sql = sql + oldAlterTableMode + oldAlterTableMode := `SET GLOBAL old_alter_table = ON;` + sql = firstPart + oldAlterTableMode + sql = sql + splitString[1] + newInitDBFile = path.Join(localCluster.TmpDirectory, "init_db_with_passwords.sql") os.WriteFile(newInitDBFile, []byte(sql), 0666) extraArgs := []string{"--db-credentials-file", dbCredentialFile} 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..1ac69b1726c 100644 --- a/go/test/endtoend/tabletmanager/tablet_test.go +++ b/go/test/endtoend/tabletmanager/tablet_test.go @@ -44,6 +44,7 @@ func TestEnsureDB(t *testing.T) { require.NoError(t, err) // Make it the primary. + // primary will fail with `--super_read-only` since in external re-parenting we expect caller to set DB to read-write mode. err = clusterInstance.VtctlclientProcess.ExecuteCommand("TabletExternallyReparented", tablet.Alias) require.EqualError(t, err, "exit status 1") @@ -53,7 +54,6 @@ func TestEnsureDB(t *testing.T) { assert.Contains(t, status, "read-only") // Switch to read-write and verify that we go serving. - // Note: for TabletExternallyReparented, we expect SetReadWrite to be called by the user err = clusterInstance.VtctlclientProcess.ExecuteCommand("SetReadWrite", tablet.Alias) require.NoError(t, err) err = tablet.VttabletProcess.WaitForTabletStatus("SERVING") diff --git a/go/test/endtoend/utils/mysql.go b/go/test/endtoend/utils/mysql.go index 5bbf75ffb71..89a7856ead6 100644 --- a/go/test/endtoend/utils/mysql.go +++ b/go/test/endtoend/utils/mysql.go @@ -114,7 +114,7 @@ func createInitSQLFile(mysqlDir, ksName string) (string, error) { return "", err } defer f.Close() - + _, _ = f.WriteString("SET GLOBAL super_read_only='OFF';") _, err = f.WriteString(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", ksName)) if err != nil { return "", err diff --git a/go/test/endtoend/vault/vault_test.go b/go/test/endtoend/vault/vault_test.go index da66f3a52e3..f2c053cd7de 100644 --- a/go/test/endtoend/vault/vault_test.go +++ b/go/test/endtoend/vault/vault_test.go @@ -267,6 +267,12 @@ func initializeClusterLate(t *testing.T) { require.NoError(t, err) } + for _, tablet := range shard.Vttablets { + // remove super read-only from vttablet + tablet.VttabletProcess.SetReadOnly("", false) + } + + // TODO: Try moving this after InitPrimary. May be thats a better place. for _, tablet := range []*cluster.Vttablet{primary, replica} { for _, user := range mysqlUsers { query := fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s';", user, hostname, mysqlPassword) @@ -279,9 +285,6 @@ func initializeClusterLate(t *testing.T) { 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/vtorc/readtopologyinstance/main_test.go b/go/test/endtoend/vtorc/readtopologyinstance/main_test.go index 5d8c129dbbd..e7ae4385aab 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..ebc04fd8fa6 100644 --- a/go/test/endtoend/vtorc/utils/utils.go +++ b/go/test/endtoend/vtorc/utils/utils.go @@ -585,6 +585,25 @@ 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) + ctx, cancel := context.WithTimeout(context.Background(), 5*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..cfcbf19ad9e 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 all privileges 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 all privileges 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 4a7781c1be9..88ccbddddbf 100644 --- a/go/vt/mysqlctl/backup.go +++ b/go/vt/mysqlctl/backup.go @@ -350,14 +350,23 @@ func Restore(ctx context.Context, params RestoreParams) (*BackupManifest, error) // 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 { + resetFunc, err := params.Mysqld.SetSuperReadOnly(false) + if err != nil { if strings.Contains(err.Error(), strconv.Itoa(mysql.ERUnknownSystemVariable)) { - params.Logger.Warningf("Restore: server does not know about super_read_only, continuing anyway...") + 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 } } + if resetFunc != nil { + defer func() { + err := resetFunc() + if err != nil { + params.Logger.Errorf("Not able to set super_read_only to its original value during restore.") + } + }() + } params.Logger.Infof("Restore: running mysql_upgrade") if err := params.Mysqld.RunMysqlUpgrade(); err != nil { diff --git a/go/vt/mysqlctl/builtinbackupengine.go b/go/vt/mysqlctl/builtinbackupengine.go index 56fabc1dd99..34d9fe8316d 100644 --- a/go/vt/mysqlctl/builtinbackupengine.go +++ b/go/vt/mysqlctl/builtinbackupengine.go @@ -324,15 +324,20 @@ 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, "can't get read_only status") } + sReadOnly, 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:%v", readOnly, sReadOnly) // 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") + params.Logger.Infof("turning primary super-read-only before backup") + if _, err = params.Mysqld.SetSuperReadOnly(true); err != nil { + return false, vterrors.Wrap(err, "can't set super-read-only status") } } replicationPosition, err = params.Mysqld.PrimaryPosition() @@ -386,8 +391,14 @@ func (be *BuiltinBackupEngine) executeFullBackup(ctx context.Context, params Bac // And set read-only mode params.Logger.Infof("resetting mysqld read-only to %v", readOnly) - if err := params.Mysqld.SetReadOnly(readOnly); err != nil { - return usable, err + if !readOnly { + if err := params.Mysqld.SetReadOnly(readOnly); err != nil { + return usable, err + } + } else { + if _, err := params.Mysqld.SetSuperReadOnly(readOnly); err != nil { + return usable, err + } } // Restore original mysqld state that we saved above. @@ -739,7 +750,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) @@ -773,7 +783,7 @@ func (be *BuiltinBackupEngine) executeRestoreIncrementalBackup(ctx context.Conte func (be *BuiltinBackupEngine) ExecuteRestore(ctx context.Context, params RestoreParams, bh backupstorage.BackupHandle) (*BackupManifest, error) { var bm builtinBackupManifest - + //time.Sleep(10 * time.Second) if err := getBackupManifestInto(ctx, bh, &bm); err != nil { return nil, err } @@ -785,8 +795,10 @@ func (be *BuiltinBackupEngine) ExecuteRestore(ctx context.Context, params Restor var err error if bm.Incremental { + log.Infof("inside executeRestoreIncrementalBackup...") err = be.executeRestoreIncrementalBackup(ctx, params, bh, bm) } else { + log.Infof("inside executeRestoreFullBackup...") err = be.executeRestoreFullBackup(ctx, params, bh, bm) } if err != nil { diff --git a/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go b/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go index 3bc1c984a15..0fb2a96a59f 100644 --- a/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go +++ b/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go @@ -366,6 +366,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, nil +} + // SetReadOnly is part of the MysqlDaemon interface func (fmd *FakeMysqlDaemon) SetReadOnly(on bool) error { fmd.ReadOnly = on @@ -373,10 +378,10 @@ func (fmd *FakeMysqlDaemon) SetReadOnly(on bool) error { } // SetSuperReadOnly is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) SetSuperReadOnly(on bool) error { +func (fmd *FakeMysqlDaemon) SetSuperReadOnly(on bool) (mysqlctl.ResetSuperReadOnlyFunc, error) { fmd.SuperReadOnly = 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 ac0aede5614..18929eb86c0 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 int, 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..3938dafd3ab 100644 --- a/go/vt/mysqlctl/mysqld.go +++ b/go/vt/mysqlctl/mysqld.go @@ -696,6 +696,7 @@ func (mysqld *Mysqld) Init(ctx context.Context, cnf *Mycnf, initDBSQLFile string return err } + log.Infof("InitDBSQLFile: %s", initDBSQLFile) 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) @@ -1217,12 +1218,16 @@ func (mysqld *Mysqld) applyBinlogFile(binlogFile string, includeGTIDs mysql.GTID return err } args := []string{} - if gtids := includeGTIDs.String(); gtids != "" { - args = append(args, - "--include-gtids", - gtids, - ) + if includeGTIDs != nil { + log.Infof("includeGTIDs.String() are %s", includeGTIDs.String()) + if gtids := includeGTIDs.String(); gtids != "" { + args = append(args, + "--include-gtids", + gtids, + ) + } } + args = append(args, binlogFile) mysqlbinlogCmd = exec.Command(name, args...) @@ -1251,6 +1256,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 strings.Contains(err.Error(), strconv.Itoa(mysql.ERUnknownSystemVariable)) { + log.Warningf("Restore: server does not know about super_read_only, continuing anyway...") + } else { + log.Errorf("Restore: 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 1f9ca28af7c..02ad605fd80 100644 --- a/go/vt/mysqlctl/replication.go +++ b/go/vt/mysqlctl/replication.go @@ -38,6 +38,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 { @@ -231,6 +233,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 @@ -254,14 +272,50 @@ func (mysqld *Mysqld) SetReadOnly(on bool) error { } // SetSuperReadOnly set/unset the super_read_only flag -func (mysqld *Mysqld) SetSuperReadOnly(on bool) error { +func (mysqld *Mysqld) SetSuperReadOnly(on bool) (ResetSuperReadOnlyFunc, error) { + // return function for switching `OFF` super_read_only + var returnFunc ResetSuperReadOnlyFunc + var resetFunc = func() error { + query := "SET GLOBAL super_read_only = 'OFF'" + err := mysqld.ExecuteSuperQuery(context.TODO(), query) + return err + } + + // return function for switching `ON` super_read_only + var setFunc = func() error { + query := "SET GLOBAL super_read_only = 'ON'" + err := mysqld.ExecuteSuperQuery(context.TODO(), query) + return err + } + + //var err error = nil + superReadOnlyEnabled, err := mysqld.IsSuperReadOnly() + if err != nil { + return nil, err + } + + // if non-idempotent then set the right call-back + if on && !superReadOnlyEnabled { + returnFunc = resetFunc + } + if !on && superReadOnlyEnabled { + returnFunc = setFunc + } + 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.TODO(), query); err != nil { + return nil, err + } + if superReadOnlyEnabled { + return returnFunc, nil + } + + return nil, 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 9a7b297a4fa..e136ff70c2d 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-- @@ -740,6 +752,9 @@ func (m *FullStatus) SizeVT() (n int) { if m.SemiSyncWaitForReplicaCount != 0 { n += 2 + sov(uint64(m.SemiSyncWaitForReplicaCount)) } + if m.SuperReadOnly { + n += 3 + } if m.unknownFields != nil { n += len(m.unknownFields) } @@ -2135,6 +2150,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..256f9be048d 100644 --- a/go/vt/vtexplain/vtexplain_test.go +++ b/go/vt/vtexplain/vtexplain_test.go @@ -24,13 +24,14 @@ import ( "strings" "testing" + querypb "vitess.io/vitess/go/vt/proto/query" + "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" ) diff --git a/go/vt/vtexplain/vtexplain_vttablet.go b/go/vt/vtexplain/vtexplain_vttablet.go index 6784efe4cbd..0cd1838b809 100644 --- a/go/vt/vtexplain/vtexplain_vttablet.go +++ b/go/vt/vtexplain/vtexplain_vttablet.go @@ -501,6 +501,31 @@ func (t *explainTablet) HandleQuery(c *mysql.Conn, query string, callback func(* if result != nil { return callback(result) } + + // 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) + } + switch sqlparser.Preview(query) { case sqlparser.StmtSelect: // Parse the select statement to figure out the table and columns diff --git a/go/vt/vttablet/endtoend/main_test.go b/go/vt/vttablet/endtoend/main_test.go index a809e7e42ae..193e0db1b21 100644 --- a/go/vt/vttablet/endtoend/main_test.go +++ b/go/vt/vttablet/endtoend/main_test.go @@ -75,7 +75,7 @@ func TestMain(m *testing.M) { fmt.Fprintf(os.Stderr, "could not launch mysql: %v\n", err) return 1 } - err := cluster.Execute(procSQL, "vttest") + err := cluster.ExecuteWithSuperReadOnly(procSQL, "vttest") if err != nil { fmt.Fprintf(os.Stderr, "%v", err) return 1 @@ -96,6 +96,7 @@ func TestMain(m *testing.M) { fmt.Fprintf(os.Stderr, "%v", err) return 1 } + //cluster.SetReadOnly("") return m.Run() }() os.Exit(exitCode) diff --git a/go/vt/vttablet/tabletmanager/rpc_replication.go b/go/vt/vttablet/tabletmanager/rpc_replication.go index f952a2f5cb8..a6d8d408ae2 100644 --- a/go/vt/vttablet/tabletmanager/rpc_replication.go +++ b/go/vt/vttablet/tabletmanager/rpc_replication.go @@ -38,7 +38,7 @@ import ( topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) -var setSuperReadOnly bool +var setSuperReadOnly = true var disableReplicationManager bool func registerReplicationFlags(fs *pflag.FlagSet) { @@ -112,6 +112,12 @@ func (tm *TabletManager) FullStatus(ctx context.Context) (*replicationdatapb.Ful return nil, err } + // Super read only - "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 } @@ -310,14 +317,12 @@ func (tm *TabletManager) InitPrimary(ctx context.Context, semiSync bool) (string // Initializing as primary implies undoing any previous "do not replicate". tm.replManager.reset() - 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(), strconv.Itoa(mysql.ERUnknownSystemVariable)) { - 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 strings.Contains(err.Error(), strconv.Itoa(mysql.ERUnknownSystemVariable)) { + log.Warningf("server does not know about super_read_only, continuing anyway...") + } else { + return "", err } } @@ -477,23 +482,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(), strconv.Itoa(mysql.ERUnknownSystemVariable)) { - 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 strings.Contains(err.Error(), strconv.Itoa(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 297b1c5687a..c79f357a668 100644 --- a/go/vt/vttablet/tabletmanager/tm_init.go +++ b/go/vt/vttablet/tabletmanager/tm_init.go @@ -76,14 +76,13 @@ const denyListQueryList string = "DenyListQueryRules" var ( // The following flags initialize the tablet record. - tabletHostname string - initKeyspace string - initShard string - initTabletType string - initDbNameOverride string - skipBuildInfoTags = "/.*/" - initTags flagutil.StringMapValue - + tabletHostname string + initKeyspace string + initShard string + initTabletType string + initDbNameOverride string + skipBuildInfoTags = "/.*/" + initTags flagutil.StringMapValue initPopulateMetadata bool initTimeout = 1 * time.Minute ) @@ -96,7 +95,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/go/vt/vttest/environment.go b/go/vt/vttest/environment.go index f58d9d48944..34ae84389ca 100644 --- a/go/vt/vttest/environment.go +++ b/go/vt/vttest/environment.go @@ -24,6 +24,8 @@ import ( "strings" "time" + "vitess.io/vitess/go/vt/mysqlctl" + "vitess.io/vitess/go/vt/proto/vttest" // we use gRPC everywhere, so import the vtgate client. @@ -144,9 +146,15 @@ func (env *LocalTestEnv) BinaryPath(binary string) string { // MySQLManager implements MySQLManager for LocalTestEnv func (env *LocalTestEnv) MySQLManager(mycnf []string, snapshot string) (MySQLManager, error) { + // maria db doesn't recognize super-read-only, therefore we have separate sql for that. + var initFile = path.Join(os.Getenv("VTROOT"), "config/init_db.sql") + if isRunningMariaDB() { + // execute init_db without `super_read_only` + initFile = path.Join(os.Getenv("VTROOT"), "config/init_testserver_db.sql") + } return &Mysqlctl{ Binary: env.BinaryPath("mysqlctl"), - InitFile: path.Join(os.Getenv("VTROOT"), "config/init_db.sql"), + InitFile: initFile, Directory: env.TmpPath, Port: env.PortForProtocol("mysql", ""), MyCnf: append(env.DefaultMyCnf, mycnf...), @@ -155,6 +163,22 @@ func (env *LocalTestEnv) MySQLManager(mycnf []string, snapshot string) (MySQLMan }, nil } +func isRunningMariaDB() bool { + mysqldVersionStr, err := mysqlctl.GetVersionString() + if err != nil { + return false + } + flavor, _, err := mysqlctl.ParseVersionString(mysqldVersionStr) + if err != nil { + return false + } + if flavor == mysqlctl.FlavorMariaDB { + return true + } + + return false +} + // TopoManager implements TopoManager for LocalTestEnv func (env *LocalTestEnv) TopoManager(topoImplementation, topoServerAddress, topoRoot string, topology *vttest.VTTestTopology) TopoManager { return &Topoctl{ diff --git a/go/vt/vttest/local_cluster.go b/go/vt/vttest/local_cluster.go index 3d954de860d..88ea58187ad 100644 --- a/go/vt/vttest/local_cluster.go +++ b/go/vt/vttest/local_cluster.go @@ -483,7 +483,7 @@ func (db *LocalCluster) loadSchema(shouldRunDatabaseMigrations bool) error { } for _, dbname := range db.shardNames(kpb) { - if err := db.Execute(cmds, dbname); err != nil { + if err := db.ExecuteWithSuperReadOnly(cmds, dbname); err != nil { return err } } @@ -533,7 +533,7 @@ func (db *LocalCluster) createDatabases() error { sql = append(sql, fmt.Sprintf("create database `%s`", dbname)) } } - return db.Execute(sql, "") + return db.ExecuteWithSuperReadOnly(sql, "") } // Execute runs a series of SQL statements on the MySQL instance backing @@ -564,6 +564,34 @@ func (db *LocalCluster) Execute(sql []string, dbname string) error { return err } +// ExecuteWithSuperReadOnly runs a series of SQL statements with super-read-only permission +// on the MySQL instance backing this local cluster. This is provided for debug/introspection purposes; +// normal cluster access should be performed through the Vitess GRPC interface. +func (db *LocalCluster) ExecuteWithSuperReadOnly(sql []string, dbname string) error { + params := db.mysql.Params(dbname) + conn, err := mysql.Connect(context.Background(), ¶ms) + if err != nil { + return err + } + defer conn.Close() + + _, err = conn.ExecuteFetch("START TRANSACTION", 0, false) + if err != nil { + return err + } + + for _, cmd := range sql { + log.Infof("Execute(%s): \"%s\"", dbname, cmd) + _, err := conn.ExecuteFetchWithSuperReadOnlyHandling(cmd, 0, false) + if err != nil { + return err + } + } + + _, err = conn.ExecuteFetch("COMMIT", 0, false) + return err +} + // Query runs a SQL query on the MySQL instance backing this local cluster and returns // its result. This is provided for debug/introspection purposes; // normal cluster access should be performed through the Vitess GRPC interface. 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 25b0c92bfa1..1a1e2b45142 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -31992,6 +31992,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. */ @@ -32063,6 +32066,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 cd7a5fe2936..7862951a54a 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -75678,6 +75678,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 */ /** @@ -75855,6 +75856,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 @@ -75919,6 +75928,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; }; @@ -76013,6 +76024,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; @@ -76112,6 +76126,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; }; @@ -76180,6 +76197,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; }; @@ -76221,6 +76240,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; @@ -76265,6 +76285,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; };