Skip to content

Commit 1533e8a

Browse files
committed
Add move-tables environment variables to hooks
1 parent b290e7b commit 1533e8a

5 files changed

Lines changed: 152 additions & 3 deletions

File tree

doc/hooks.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,20 @@ The following variables are available on all hooks:
7373
- `GH_OST_MIGRATED_HOST`
7474
- `GH_OST_INSPECTED_HOST`
7575
- `GH_OST_EXECUTING_HOST`
76+
- `GH_OST_TARGET_HOST` - the target/applier hostname (in `--move-tables`, this is the target cluster host)
7677
- `GH_OST_HOOKS_HINT` - copy of `--hooks-hint` value
7778
- `GH_OST_HOOKS_HINT_OWNER` - copy of `--hooks-hint-owner` value
7879
- `GH_OST_HOOKS_HINT_TOKEN` - copy of `--hooks-hint-token` value
7980
- `GH_OST_DRY_RUN` - whether or not the `gh-ost` run is a dry run
8081
- `GH_OST_REVERT` - whether or not `gh-ost` is running in revert mode
82+
- `GH_OST_MOVE_TABLES` - whether or not `gh-ost` is running in `--move-tables` mode
83+
- `GH_OST_TARGET_DATABASE_NAME` - operation target database name (in `--move-tables`, this is the explicit target database)
84+
- `GH_OST_TARGET_TABLE_NAME` - operation target table name (mode-dependent)
8185

82-
The following variable are available on particular hooks:
86+
The following variables are available on particular hooks:
8387

8488
- `GH_OST_INSTANT_DDL` is only available in `gh-ost-on-success`. The value is `true` if instant DDL was successful, and `false` if it was not.
89+
- `GH_OST_DRAIN_GTID` is only available in `gh-ost-on-success` and only in `--move-tables` mode. It contains the GTID/binlog coordinates captured after draining target relay logs.
8590
- `GH_OST_COMMAND` is only available in `gh-ost-on-interactive-command`
8691
- `GH_OST_STATUS` is only available in `gh-ost-on-status`
8792
- `GH_OST_LAST_BATCH_COPY_ERROR` is only available in `gh-ost-on-batch-copy-retry`

go/base/context.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ type MigrationContext struct {
283283
TargetPass string // Target password for the move. If not specified, it will default to the source password.
284284
TargetDatabase string // Target database name for the move. If not specified, it will default to the source database name.
285285
ConnectionConfig *mysql.ConnectionConfig
286+
287+
DrainGTID mysql.BinlogCoordinates // GTID coordinates up to which we drain the source before cut-over. This is only used in move-tables mode.
286288
}
287289

288290
Log Logger
@@ -491,6 +493,18 @@ func (mctx *MigrationContext) GetInspectorHostname() string {
491493
return mctx.InspectorConnectionConfig.ImpliedKey.Hostname
492494
}
493495

496+
// GetTargetHostname is a safe access method to the target hostname.
497+
// In move-tables mode, this is the hostname of the target database,
498+
// otherwise it's the same as the applier hostname.
499+
func (mctx *MigrationContext) GetTargetHostname() string {
500+
if mctx.IsMoveTablesMode() &&
501+
mctx.MoveTables.ConnectionConfig != nil &&
502+
mctx.MoveTables.ConnectionConfig.ImpliedKey != nil {
503+
return mctx.MoveTables.ConnectionConfig.ImpliedKey.Hostname
504+
}
505+
return mctx.GetApplierHostname()
506+
}
507+
494508
// InspectorIsAlsoApplier is `true` when the both inspector and applier are the
495509
// same database instance. This would be true when running directly on master or when
496510
// testing on replica.

go/logic/hooks.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ func (he *HooksExecutor) applyEnvironmentVariables(extraVariables ...string) []s
233233
env = append(env, fmt.Sprintf("GH_OST_MIGRATED_HOST=%s", he.migrationContext.GetApplierHostname()))
234234
env = append(env, fmt.Sprintf("GH_OST_INSPECTED_HOST=%s", he.migrationContext.GetInspectorHostname()))
235235
env = append(env, fmt.Sprintf("GH_OST_EXECUTING_HOST=%s", he.migrationContext.Hostname))
236+
env = append(env, fmt.Sprintf("GH_OST_TARGET_HOST=%s", he.migrationContext.GetTargetHostname()))
236237
env = append(env, fmt.Sprintf("GH_OST_INSPECTED_LAG=%f", he.migrationContext.GetCurrentLagDuration().Seconds()))
237238
env = append(env, fmt.Sprintf("GH_OST_HEARTBEAT_LAG=%f", he.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds()))
238239
env = append(env, fmt.Sprintf("GH_OST_PROGRESS=%f", he.migrationContext.GetProgressPct()))
@@ -242,6 +243,9 @@ func (he *HooksExecutor) applyEnvironmentVariables(extraVariables ...string) []s
242243
env = append(env, fmt.Sprintf("GH_OST_HOOKS_HINT_TOKEN=%s", he.migrationContext.HooksHintToken))
243244
env = append(env, fmt.Sprintf("GH_OST_DRY_RUN=%t", he.migrationContext.Noop))
244245
env = append(env, fmt.Sprintf("GH_OST_REVERT=%t", he.migrationContext.Revert))
246+
env = append(env, fmt.Sprintf("GH_OST_MOVE_TABLES=%t", he.migrationContext.IsMoveTablesMode()))
247+
env = append(env, fmt.Sprintf("GH_OST_TARGET_DATABASE_NAME=%s", he.migrationContext.GetTargetDatabaseName()))
248+
env = append(env, fmt.Sprintf("GH_OST_TARGET_TABLE_NAME=%s", he.migrationContext.GetTargetTableName()))
245249

246250
env = append(env, extraVariables...)
247251
return env
@@ -320,8 +324,11 @@ func (he *HooksExecutor) OnInteractiveCommand(command string) error {
320324
}
321325

322326
func (he *HooksExecutor) OnSuccess(instantDDL bool) error {
323-
v := fmt.Sprintf("GH_OST_INSTANT_DDL=%t", instantDDL)
324-
return he.executeHooks(onSuccess, v)
327+
v := []string{fmt.Sprintf("GH_OST_INSTANT_DDL=%t", instantDDL)}
328+
if he.migrationContext.IsMoveTablesMode() {
329+
v = append(v, fmt.Sprintf("GH_OST_DRAIN_GTID=%s", he.migrationContext.MoveTables.DrainGTID.String()))
330+
}
331+
return he.executeHooks(onSuccess, v...)
325332
}
326333

327334
func (he *HooksExecutor) OnFailure() error {

go/logic/hooks_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/stretchr/testify/require"
2121

2222
"github.com/github/gh-ost/go/base"
23+
"github.com/github/gh-ost/go/mysql"
2324
)
2425

2526
type recordingHooks struct {
@@ -242,9 +243,130 @@ func TestHooksExecutorExecuteHooks(t *testing.T) {
242243
require.Equal(t, migrationContext.OriginalTableName, split[1])
243244
case "GH_OST_INSTANT_DDL":
244245
require.Equal(t, "false", split[1])
246+
case "GH_OST_MOVE_TABLES":
247+
require.Equal(t, "false", split[1])
248+
case "GH_OST_TARGET_DATABASE_NAME":
249+
require.Equal(t, migrationContext.DatabaseName, split[1])
250+
case "GH_OST_TARGET_TABLE_NAME":
251+
require.Equal(t, fmt.Sprintf("_%s_gho", migrationContext.OriginalTableName), split[1])
252+
case "GH_OST_TARGET_HOST":
253+
require.Equal(t, migrationContext.GetApplierHostname(), split[1])
245254
case "TEST":
246255
require.Equal(t, t.Name(), split[1])
247256
}
248257
}
249258
})
250259
}
260+
261+
func TestHooksExecutorMoveTablesEnvironmentVariables(t *testing.T) {
262+
migrationContext := base.NewMigrationContext()
263+
migrationContext.DatabaseName = "source_db"
264+
migrationContext.OriginalTableName = "tablename"
265+
migrationContext.MoveTables.TableNames = []string{"tablename"}
266+
migrationContext.MoveTables.TargetDatabase = "target_db"
267+
migrationContext.MoveTables.ConnectionConfig = mysql.NewConnectionConfig()
268+
migrationContext.MoveTables.ConnectionConfig.Key.Hostname = "target.example.com"
269+
migrationContext.MoveTables.ConnectionConfig.ImpliedKey = &migrationContext.MoveTables.ConnectionConfig.Key
270+
271+
hooksExecutor := NewHooksExecutor(migrationContext)
272+
273+
hooksPath, err := os.MkdirTemp("", "TestHooksExecutorMoveTablesEnvironmentVariables")
274+
require.NoError(t, err)
275+
defer os.RemoveAll(hooksPath)
276+
migrationContext.HooksPath = hooksPath
277+
278+
require.NoError(t, os.WriteFile(
279+
filepath.Join(hooksPath, "success-hook"),
280+
[]byte("#!/bin/sh\nenv"),
281+
0o777,
282+
))
283+
284+
var buf bytes.Buffer
285+
hooksExecutor.writer = &buf
286+
require.Nil(t, hooksExecutor.executeHooks("success-hook"))
287+
288+
scanner := bufio.NewScanner(&buf)
289+
for scanner.Scan() {
290+
split := strings.SplitN(scanner.Text(), "=", 2)
291+
switch split[0] {
292+
case "GH_OST_MOVE_TABLES":
293+
require.Equal(t, "true", split[1])
294+
case "GH_OST_TARGET_DATABASE_NAME":
295+
require.Equal(t, "target_db", split[1])
296+
case "GH_OST_TARGET_TABLE_NAME":
297+
require.Equal(t, "tablename", split[1])
298+
case "GH_OST_TARGET_HOST":
299+
require.Equal(t, "target.example.com", split[1])
300+
}
301+
}
302+
}
303+
304+
func TestHooksExecutorOnSuccessEnvironmentVariables(t *testing.T) {
305+
writeOnSuccessHook := func(t *testing.T, hooksPath string) {
306+
t.Helper()
307+
require.NoError(t, os.WriteFile(
308+
filepath.Join(hooksPath, onSuccess),
309+
[]byte("#!/bin/sh\nenv"),
310+
0o777,
311+
))
312+
}
313+
314+
envFromBuffer := func(buf *bytes.Buffer) map[string]string {
315+
envMap := map[string]string{}
316+
scanner := bufio.NewScanner(buf)
317+
for scanner.Scan() {
318+
split := strings.SplitN(scanner.Text(), "=", 2)
319+
if len(split) == 2 {
320+
envMap[split[0]] = split[1]
321+
}
322+
}
323+
return envMap
324+
}
325+
326+
t.Run("move-tables-includes-drain-gtid", func(t *testing.T) {
327+
migrationContext := base.NewMigrationContext()
328+
migrationContext.DatabaseName = "source_db"
329+
migrationContext.OriginalTableName = "tablename"
330+
migrationContext.MoveTables.TableNames = []string{"tablename"}
331+
migrationContext.MoveTables.DrainGTID = &mysql.FileBinlogCoordinates{LogFile: "mysql-bin.000001", LogPos: 12345}
332+
333+
hooksExecutor := NewHooksExecutor(migrationContext)
334+
335+
hooksPath, err := os.MkdirTemp("", "TestHooksExecutorOnSuccessEnvironmentVariables-move-tables")
336+
require.NoError(t, err)
337+
defer os.RemoveAll(hooksPath)
338+
migrationContext.HooksPath = hooksPath
339+
writeOnSuccessHook(t, hooksPath)
340+
341+
var buf bytes.Buffer
342+
hooksExecutor.writer = &buf
343+
require.NoError(t, hooksExecutor.OnSuccess(true))
344+
345+
envMap := envFromBuffer(&buf)
346+
require.Equal(t, "true", envMap["GH_OST_INSTANT_DDL"])
347+
require.Equal(t, migrationContext.MoveTables.DrainGTID.String(), envMap["GH_OST_DRAIN_GTID"])
348+
})
349+
350+
t.Run("non-move-tables-omits-drain-gtid", func(t *testing.T) {
351+
migrationContext := base.NewMigrationContext()
352+
migrationContext.DatabaseName = "source_db"
353+
migrationContext.OriginalTableName = "tablename"
354+
355+
hooksExecutor := NewHooksExecutor(migrationContext)
356+
357+
hooksPath, err := os.MkdirTemp("", "TestHooksExecutorOnSuccessEnvironmentVariables-standard")
358+
require.NoError(t, err)
359+
defer os.RemoveAll(hooksPath)
360+
migrationContext.HooksPath = hooksPath
361+
writeOnSuccessHook(t, hooksPath)
362+
363+
var buf bytes.Buffer
364+
hooksExecutor.writer = &buf
365+
require.NoError(t, hooksExecutor.OnSuccess(false))
366+
367+
envMap := envFromBuffer(&buf)
368+
require.Equal(t, "false", envMap["GH_OST_INSTANT_DDL"])
369+
_, exists := envMap["GH_OST_DRAIN_GTID"]
370+
require.False(t, exists)
371+
})
372+
}

go/logic/migrator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,7 @@ func (mgtr *Migrator) moveTablesCutOver() (err error) {
10821082
// OnSuccess call that used to live in MoveTables() (after finalCleanup) has
10831083
// been removed so the hook fires in the order coop_cutover.md §3.2 step 6
10841084
// requires (T5 between T4 and T6, BEFORE finalCleanup).
1085+
mgtr.migrationContext.MoveTables.DrainGTID = drainGTID
10851086
if err := mgtr.hooksExecutor.OnSuccess(false); err != nil {
10861087
return fmt.Errorf("on-success hook failed: %w", err)
10871088
}

0 commit comments

Comments
 (0)