vitess.io/vitess@v0.16.2/go/test/endtoend/vreplication/time_zone_test.go (about)

     1  /*
     2  Copyright 2022 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vreplication
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"testing"
    23  	"time"
    24  
    25  	"vitess.io/vitess/go/test/endtoend/cluster"
    26  
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"vitess.io/vitess/go/vt/log"
    30  )
    31  
    32  // TestMoveTablesTZ tests the conversion of datetime based on the source timezone passed to the MoveTables workflow
    33  func TestMoveTablesTZ(t *testing.T) {
    34  	allCellNames = "zone1"
    35  	defaultCellName := "zone1"
    36  	workflow := "tz"
    37  	sourceKs := "product"
    38  	targetKs := "customer"
    39  	shard := "0"
    40  	ksWorkflow := fmt.Sprintf("%s.%s", targetKs, workflow)
    41  	ksReverseWorkflow := fmt.Sprintf("%s.%s_reverse", sourceKs, workflow)
    42  
    43  	vc = NewVitessCluster(t, "TestCellAliasVreplicationWorkflow", []string{"zone1"}, mainClusterConfig)
    44  	require.NotNil(t, vc)
    45  	defaultCell = vc.Cells[defaultCellName]
    46  	cells := []*Cell{defaultCell}
    47  
    48  	defer vc.TearDown(t)
    49  
    50  	cell1 := vc.Cells["zone1"]
    51  	vc.AddKeyspace(t, []*Cell{cell1}, sourceKs, "0", initialProductVSchema, initialProductSchema, 0, 0, 100, sourceKsOpts)
    52  
    53  	vtgate = cell1.Vtgates[0]
    54  	require.NotNil(t, vtgate)
    55  	err := cluster.WaitForHealthyShard(vc.VtctldClient, sourceKs, shard)
    56  	require.NoError(t, err)
    57  
    58  	vtgateConn = getConnection(t, vc.ClusterConfig.hostname, vc.ClusterConfig.vtgateMySQLPort)
    59  	defer vtgateConn.Close()
    60  	verifyClusterHealth(t, vc)
    61  
    62  	productTab := vc.Cells[defaultCell.Name].Keyspaces[sourceKs].Shards["0"].Tablets["zone1-100"].Vttablet
    63  	timeZoneSQLBytes, _ := os.ReadFile("tz.sql")
    64  	timeZoneSQL := string(timeZoneSQLBytes)
    65  
    66  	// it seems to take some time for the mysql server to load time zone info after the tables in mysql db have been populated
    67  	loadTimeZoneInfo := func(tab *cluster.VttabletProcess, sql, timezone string) {
    68  		_, err := tab.QueryTabletWithDB(timeZoneSQL, "mysql")
    69  		require.NoError(t, err)
    70  		timer := time.NewTimer(1 * time.Minute)
    71  		for {
    72  			select {
    73  			case <-timer.C:
    74  				require.Fail(t, "could not load time zone info")
    75  			default:
    76  			}
    77  			_, err = tab.QueryTablet(fmt.Sprintf("SET GLOBAL time_zone = '%s';", timezone), "", false)
    78  			if err == nil {
    79  				timer.Stop()
    80  				return
    81  			}
    82  			time.Sleep(100 * time.Millisecond)
    83  		}
    84  	}
    85  	loadTimeZoneInfo(productTab, timeZoneSQL, "US/Pacific")
    86  
    87  	insertInitialData(t)
    88  
    89  	if _, err := vc.AddKeyspace(t, cells, targetKs, "0", customerVSchema, customerSchema, defaultReplicas, defaultRdonly, 200, targetKsOpts); err != nil {
    90  		t.Fatal(err)
    91  	}
    92  	err = cluster.WaitForHealthyShard(vc.VtctldClient, targetKs, shard)
    93  	require.NoError(t, err)
    94  
    95  	defaultCell := vc.Cells["zone1"]
    96  	custKs := vc.Cells[defaultCell.Name].Keyspaces[targetKs]
    97  	customerTab := custKs.Shards["0"].Tablets["zone1-200"].Vttablet
    98  
    99  	loadTimeZoneInfo(customerTab, timeZoneSQL, "UTC")
   100  
   101  	tables := "datze"
   102  
   103  	ksErrorWorkflow := fmt.Sprintf("%s.%s", targetKs, "tzerr")
   104  	output, err := vc.VtctlClient.ExecuteCommandWithOutput("MoveTables", "--", "--source", sourceKs, "--tables",
   105  		tables, "--source_time_zone", "US/Pacifik", "Create", ksErrorWorkflow)
   106  	require.Error(t, err, output)
   107  	require.Contains(t, output, "time zone is invalid")
   108  
   109  	output, err = vc.VtctlClient.ExecuteCommandWithOutput("MoveTables", "--", "--source", sourceKs, "--tables",
   110  		tables, "--source_time_zone", "US/Pacific", "Create", ksWorkflow)
   111  	require.NoError(t, err, output)
   112  
   113  	catchup(t, customerTab, workflow, "MoveTables")
   114  
   115  	// inserts to test date conversions in replication (vplayer) mode (insert statements)
   116  	_, err = vtgateConn.ExecuteFetch("insert into datze(id, dt2) values (11, '2022-01-01 10:20:30')", 1, false) // standard time
   117  	require.NoError(t, err)
   118  	_, err = vtgateConn.ExecuteFetch("insert into datze(id, dt2) values (12, '2022-04-01 5:06:07')", 1, false) // dst
   119  	require.NoError(t, err)
   120  
   121  	vdiff1(t, ksWorkflow, "")
   122  
   123  	// update to test date conversions in replication (vplayer) mode (update statements)
   124  	_, err = vtgateConn.ExecuteFetch("update datze set dt2 = '2022-04-01 5:06:07' where id = 11", 1, false) // dst
   125  	require.NoError(t, err)
   126  	_, err = vtgateConn.ExecuteFetch("update datze set dt2 = '2022-01-01 10:20:30' where id = 12", 1, false) // standard time
   127  	require.NoError(t, err)
   128  
   129  	vdiff1(t, ksWorkflow, "")
   130  
   131  	query := "select * from datze"
   132  	qrSourceUSPacific, err := productTab.QueryTablet(query, sourceKs, true)
   133  	require.NoError(t, err)
   134  	require.NotNil(t, qrSourceUSPacific)
   135  
   136  	qrTargetUTC, err := customerTab.QueryTablet(query, targetKs, true)
   137  	require.NoError(t, err)
   138  	require.NotNil(t, qrTargetUTC)
   139  
   140  	require.Equal(t, len(qrSourceUSPacific.Rows), len(qrTargetUTC.Rows))
   141  
   142  	pacificLocation, err := time.LoadLocation("US/Pacific")
   143  	require.NoError(t, err)
   144  
   145  	// for reference the columns in the test are as follows:
   146  	//  * dt1 datetime default current_timestamp, constant for all rows
   147  	//  * dt2 datetime, different values. First row is in standard time, rest with daylight savings including times around the time zone switch
   148  	//  * ts1 timestamp default current_timestamp, constant for all rows
   149  	for i, row := range qrSourceUSPacific.Named().Rows {
   150  		// source and UTC results must differ since source is in US/Pacific
   151  		require.NotEqual(t, row.AsString("dt1", ""), qrTargetUTC.Named().Rows[i].AsString("dt1", ""))
   152  		require.NotEqual(t, row.AsString("dt2", ""), qrTargetUTC.Named().Rows[i].AsString("dt2", ""))
   153  		require.NotEqual(t, row.AsString("ts1", ""), qrTargetUTC.Named().Rows[i].AsString("ts1", ""))
   154  
   155  		dtLayout := "2006-01-02 15:04:05"
   156  		// now compare times b/w source and target (actual). VDiff has already compared, but we want to validate that vdiff1 is right too!
   157  		dt2a, err := time.Parse(dtLayout, qrTargetUTC.Named().Rows[i].AsString("dt2", ""))
   158  		require.NoError(t, err)
   159  		targetUTCTUnix := dt2a.Unix()
   160  
   161  		dt2b, err := time.Parse(dtLayout, qrSourceUSPacific.Named().Rows[i].AsString("dt2", ""))
   162  		require.NoError(t, err)
   163  		sourceUSPacific := dt2b.Unix()
   164  
   165  		dtt := dt2b.In(pacificLocation)
   166  		zone, _ := dtt.Zone()
   167  		var hoursBehind int64
   168  		if zone == "PDT" { // daylight savings is on
   169  			hoursBehind = 7
   170  		} else {
   171  			hoursBehind = 8
   172  		}
   173  		// extra logging, so that we can spot any issues in CI test runs
   174  		log.Infof("times are %s, %s, hours behind %d", dt2a, dt2b, hoursBehind)
   175  		require.Equal(t, hoursBehind*3600, targetUTCTUnix-sourceUSPacific)
   176  	}
   177  
   178  	// user should be either running this query or have set their location in their driver to map from the time in Vitess/UTC to local
   179  	query = "select id, convert_tz(dt1, 'UTC', 'US/Pacific') dt1, convert_tz(dt2, 'UTC', 'US/Pacific') dt2, convert_tz(ts1, 'UTC', 'US/Pacific') ts1 from datze"
   180  	qrTargetUSPacific, err := customerTab.QueryTablet(query, "customer", true)
   181  	require.NoError(t, err)
   182  	require.NotNil(t, qrTargetUSPacific)
   183  	require.Equal(t, len(qrSourceUSPacific.Rows), len(qrTargetUSPacific.Rows))
   184  
   185  	for i, row := range qrSourceUSPacific.Named().Rows {
   186  		// source and target results must match since source is in US/Pacific and we are converting target columns explicitly to US/Pacific
   187  		require.Equal(t, row.AsString("dt1", ""), qrTargetUSPacific.Named().Rows[i].AsString("dt1", ""))
   188  		require.Equal(t, row.AsString("dt2", ""), qrTargetUSPacific.Named().Rows[i].AsString("dt2", ""))
   189  		require.Equal(t, row.AsString("ts1", ""), qrTargetUSPacific.Named().Rows[i].AsString("ts1", ""))
   190  	}
   191  	output, err = vc.VtctlClient.ExecuteCommandWithOutput("MoveTables", "--", "SwitchTraffic", ksWorkflow)
   192  	require.NoError(t, err, output)
   193  
   194  	qr, err := productTab.QueryTablet(fmt.Sprintf("select * from _vt.vreplication where workflow='%s_reverse'", workflow), "", false)
   195  	if err != nil {
   196  		return
   197  	}
   198  	for _, row := range qr.Named().Rows {
   199  		bls := row["source"].ToString()
   200  		require.Contains(t, bls, "source_time_zone:\"UTC\"")
   201  		require.Contains(t, bls, "target_time_zone:\"US/Pacific\"")
   202  	}
   203  
   204  	// inserts to test date conversions in reverse replication
   205  	execVtgateQuery(t, vtgateConn, "customer", "insert into datze(id, dt2) values (13, '2022-01-01 18:20:30')")
   206  	execVtgateQuery(t, vtgateConn, "customer", "insert into datze(id, dt2) values (14, '2022-04-01 12:06:07')")
   207  	vdiff1(t, ksReverseWorkflow, "")
   208  }