diff --git a/go/base/context.go b/go/base/context.go index 78a7a41..438490b 100644 --- a/go/base/context.go +++ b/go/base/context.go @@ -92,6 +92,7 @@ type MigrationContext struct { InitiallyDropGhostTable bool CutOverType CutOver + Hostname string TableEngine string RowsEstimate int64 UsedRowsEstimateMethod RowsEstimateMethod diff --git a/go/cmd/gh-ost/main.go b/go/cmd/gh-ost/main.go index b614831..501d04b 100644 --- a/go/cmd/gh-ost/main.go +++ b/go/cmd/gh-ost/main.go @@ -189,6 +189,7 @@ func main() { migrator := logic.NewMigrator() err := migrator.Migrate() if err != nil { + migrator.ExecOnFailureHook() log.Fatale(err) } log.Info("Done") diff --git a/go/logic/hooks.go b/go/logic/hooks.go index 58c1403..1c562ef 100644 --- a/go/logic/hooks.go +++ b/go/logic/hooks.go @@ -15,6 +15,20 @@ import ( "github.com/openark/golib/log" ) +const ( + onStartup = "gh-ost-on-startup" + onValidated = "gh-ost-on-validated" + onAboutToRowCopy = "gh-ost-on-about-row-copy" + onRowCopyComplete = "gh-ost-on-row-copy-complete" + onBeginPostponed = "gh-ost-on-begin-postponed" + onAboutToCutOver = "gh-ost-on-about-cut-over" + onInteractiveCommand = "gh-ost-on-interactive-command" + onSuccess = "gh-ost-on-success" + onFailure = "gh-ost-on-failure" + onStatus = "gh-ost-on-status" + onStopReplication = "gh-ost-on-stop-replication" +) + type HooksExecutor struct { migrationContext *base.MigrationContext } @@ -29,7 +43,7 @@ func (this *HooksExecutor) initHooks() error { return nil } -func (this *HooksExecutor) applyEnvironmentVairables() []string { +func (this *HooksExecutor) applyEnvironmentVairables(extraVariables ...string) []string { env := os.Environ() env = append(env, fmt.Sprintf("GH_OST_DATABASE_NAME=%s", this.migrationContext.DatabaseName)) env = append(env, fmt.Sprintf("GH_OST_TABLE_NAME=%s", this.migrationContext.OriginalTableName)) @@ -37,13 +51,20 @@ func (this *HooksExecutor) applyEnvironmentVairables() []string { env = append(env, fmt.Sprintf("GH_OST_OLD_TABLE_NAME=%s", this.migrationContext.GetOldTableName())) env = append(env, fmt.Sprintf("GH_OST_DDL=%s", this.migrationContext.AlterStatement)) env = append(env, fmt.Sprintf("GH_OST_ELAPSED_SECONDS=%f", this.migrationContext.ElapsedTime().Seconds())) + env = append(env, fmt.Sprintf("GH_OST_MIGRATED_HOST=%s", this.migrationContext.ApplierConnectionConfig.ImpliedKey.Hostname)) + env = append(env, fmt.Sprintf("GH_OST_INSPECTED_HOST=%s", this.migrationContext.InspectorConnectionConfig.ImpliedKey.Hostname)) + env = append(env, fmt.Sprintf("GH_OST_EXECUTING_HOST=%s", this.migrationContext.Hostname)) + + for _, variable := range extraVariables { + env = append(env, variable) + } return env } -// executeHook executes a command with arguments, and set relevant environment variables -func (this *HooksExecutor) executeHook(hook string, arguments ...string) error { - cmd := exec.Command(hook, arguments...) - cmd.Env = this.applyEnvironmentVairables() +// executeHook executes a command, and sets relevant environment variables +func (this *HooksExecutor) executeHook(hook string, extraVariables ...string) error { + cmd := exec.Command(hook) + cmd.Env = this.applyEnvironmentVairables(extraVariables...) if err := cmd.Run(); err != nil { return log.Errore(err) @@ -60,13 +81,13 @@ func (this *HooksExecutor) detectHooks(baseName string) (hooks []string, err err return hooks, err } -func (this *HooksExecutor) executeHooks(baseName string) error { +func (this *HooksExecutor) executeHooks(baseName string, extraVariables ...string) error { hooks, err := this.detectHooks(baseName) if err != nil { return err } for _, hook := range hooks { - if err := this.executeHook(hook); err != nil { + if err := this.executeHook(hook, extraVariables...); err != nil { return err } } @@ -74,41 +95,47 @@ func (this *HooksExecutor) executeHooks(baseName string) error { } func (this *HooksExecutor) onStartup() error { - return nil + return this.executeHooks(onStartup) } func (this *HooksExecutor) onValidated() error { - return nil + return this.executeHooks(onValidated) } func (this *HooksExecutor) onAboutToRowCopy() error { - return nil + return this.executeHooks(onAboutToRowCopy) } func (this *HooksExecutor) onRowCopyComplete() error { - return nil + return this.executeHooks(onRowCopyComplete) } func (this *HooksExecutor) onBeginPostponed() error { - return nil + return this.executeHooks(onBeginPostponed) } func (this *HooksExecutor) onAboutToCutOver() error { - return nil + return this.executeHooks(onAboutToCutOver) } func (this *HooksExecutor) onInteractiveCommand(command string) error { - return nil + v := fmt.Sprintf("GH_OST_COMMAND='%s'", command) + return this.executeHooks(onInteractiveCommand, v) } func (this *HooksExecutor) onSuccess() error { - return nil + return this.executeHooks(onSuccess) } func (this *HooksExecutor) onFailure() error { - return nil + return this.executeHooks(onFailure) } func (this *HooksExecutor) onStatus(statusMessage string) error { - return nil + v := fmt.Sprintf("GH_OST_STATUS='%s'", statusMessage) + return this.executeHooks(onStatus, v) +} + +func (this *HooksExecutor) onStopReplication() error { + return this.executeHooks(onStopReplication) } diff --git a/go/logic/migrator.go b/go/logic/migrator.go index 9d1ba6a..17c37c7 100644 --- a/go/logic/migrator.go +++ b/go/logic/migrator.go @@ -58,7 +58,6 @@ type Migrator struct { server *Server hooksExecutor *HooksExecutor migrationContext *base.MigrationContext - hostname string tablesInPlace chan bool rowCopyComplete chan bool @@ -375,7 +374,7 @@ func (this *Migrator) validateStatement() (err error) { func (this *Migrator) Migrate() (err error) { log.Infof("Migrating %s.%s", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName)) this.migrationContext.StartTime = time.Now() - if this.hostname, err = os.Hostname(); err != nil { + if this.migrationContext.Hostname, err = os.Hostname(); err != nil { return err } @@ -473,6 +472,12 @@ func (this *Migrator) Migrate() (err error) { return nil } +// ExecOnFailureHook executes the onFailure hook, and this method is provided as the only external +// hook access point +func (this *Migrator) ExecOnFailureHook() (err error) { + return this.hooksExecutor.onFailure() +} + // cutOver performs the final step of migration, based on migration // type (on replica? bumpy? safe?) func (this *Migrator) cutOver() (err error) { @@ -516,6 +521,7 @@ func (this *Migrator) cutOver() (err error) { // and swap the tables. // The difference is that we will later swap the tables back. log.Debugf("testing on replica. Stopping replication IO thread") + this.hooksExecutor.onStopReplication() if err := this.retryOperation(this.applier.StopReplication); err != nil { return err } @@ -909,7 +915,7 @@ func (this *Migrator) printMigrationStatusHint(writers ...io.Writer) { fmt.Fprintln(w, fmt.Sprintf("# Migrating %+v; inspecting %+v; executing on %+v", *this.applier.connectionConfig.ImpliedKey, *this.inspector.connectionConfig.ImpliedKey, - this.hostname, + this.migrationContext.Hostname, )) fmt.Fprintln(w, fmt.Sprintf("# Migration started at %+v", this.migrationContext.StartTime.Format(time.RubyDate),