From 54512b413aa396951dab7219271f550277db99f8 Mon Sep 17 00:00:00 2001 From: Yuuki Takahashi <20282867+yktakaha4@users.noreply.github.com> Date: Sun, 25 May 2025 15:46:11 +0900 Subject: [PATCH 1/3] Add postpone-cut-over-flag-file interactive command --- doc/interactive-commands.md | 1 + go/logic/server.go | 24 ++++++++++++++++++++++++ go/logic/server_test.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/doc/interactive-commands.md b/doc/interactive-commands.md index 44413d580..af37935d1 100644 --- a/doc/interactive-commands.md +++ b/doc/interactive-commands.md @@ -41,6 +41,7 @@ Both interfaces may serve at the same time. Both respond to simple text command, - `throttle-control-replicas='replica1,replica2'`: change list of throttle-control replicas, these are replicas `gh-ost` will check. This takes a comma separated list of replica's to check and replaces the previous list. - `throttle`: force migration suspend - `no-throttle`: cancel forced suspension (though other throttling reasons may still apply) +- `postpone-cut-over-flag-file=`: Postpone the [cut-over](cut-over.md) phase, writing a cut over flag file to the given path - `unpostpone`: at a time where `gh-ost` is postponing the [cut-over](cut-over.md) phase, instruct `gh-ost` to stop postponing and proceed immediately to cut-over. - `panic`: immediately panic and abort operation diff --git a/go/logic/server.go b/go/logic/server.go index 4e41fd26b..b5d05b758 100644 --- a/go/logic/server.go +++ b/go/logic/server.go @@ -99,6 +99,16 @@ func (this *Server) runCPUProfile(args string) (io.Reader, error) { return &buf, nil } +func (this *Server) createPostponeCutOverFlagFile(filePath string) (err error) { + if !base.FileExists(filePath) { + if err := base.TouchFile(filePath); err != nil { + return fmt.Errorf("Failed to create postpone cut-over flag file %s: %w", filePath, err) + } + this.migrationContext.Log.Infof("Created postpone-cut-over-flag-file: %s", filePath) + } + return nil +} + func (this *Server) BindSocketFile() (err error) { if this.migrationContext.ServeSocketFile == "" { return nil @@ -222,6 +232,7 @@ throttle-http= # Set a new throttle URL throttle-control-replicas= # Set a new comma delimited list of throttle control replicas throttle # Force throttling no-throttle # End forced throttling (other throttling may still apply) +postpone-cut-over-flag-file= # Postpone the cut-over phase, writing a cut over flag file to the given path unpostpone # Bail out a cut-over postpone; proceed to cut-over panic # panic and quit without cleanup help # This message @@ -395,6 +406,19 @@ help # This message atomic.StoreInt64(&this.migrationContext.ThrottleCommandedByUser, 0) return ForcePrintStatusAndHintRule, nil } + case "postpone-cut-over-flag-file": + { + if arg == "" { + err := fmt.Errorf("User commanded 'postpone-cut-over-flag-file' without specifying file path") + return NoPrintStatusRule, err + } + if err := this.createPostponeCutOverFlagFile(arg); err != nil { + return NoPrintStatusRule, err + } + this.migrationContext.PostponeCutOverFlagFile = arg + fmt.Fprintf(writer, "Postponed\n") + return ForcePrintStatusAndHintRule, nil + } case "unpostpone", "no-postpone", "cut-over": { if arg == "" && this.migrationContext.ForceNamedCutOverCommand { diff --git a/go/logic/server_test.go b/go/logic/server_test.go index 78f0fd6e6..55ce608d0 100644 --- a/go/logic/server_test.go +++ b/go/logic/server_test.go @@ -1,6 +1,8 @@ package logic import ( + "os" + "path" "testing" "time" @@ -66,3 +68,37 @@ func TestServerRunCPUProfile(t *testing.T) { require.Equal(t, int64(0), s.isCPUProfiling) }) } + +func TestServerCreatePostponeCutOverFlagFile(t *testing.T) { + t.Parallel() + + t.Run("success", func(t *testing.T) { + s := &Server{ + migrationContext: base.NewMigrationContext(), + } + dir, err := os.MkdirTemp("", "gh-ost-test-") + require.NoError(t, err) + + filePath := path.Join(dir, "postpone-cut-over.flag") + + err = s.createPostponeCutOverFlagFile(filePath) + require.NoError(t, err) + require.FileExists(t, filePath) + }) + + t.Run("file already exists", func(t *testing.T) { + s := &Server{ + migrationContext: base.NewMigrationContext(), + } + dir, err := os.MkdirTemp("", "gh-ost-test-") + require.NoError(t, err) + + filePath := path.Join(dir, "postpone-cut-over.flag") + err = base.TouchFile(filePath) + require.NoError(t, err) + + err = s.createPostponeCutOverFlagFile(filePath) + require.NoError(t, err) + require.FileExists(t, filePath) + }) +} From a13ea7bb05e3aec81c1cf21cb146a3788e424208 Mon Sep 17 00:00:00 2001 From: Yuuki Takahashi <20282867+yktakaha4@users.noreply.github.com> Date: Sun, 25 May 2025 17:00:50 +0900 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- go/logic/server_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/logic/server_test.go b/go/logic/server_test.go index 55ce608d0..d28774d8d 100644 --- a/go/logic/server_test.go +++ b/go/logic/server_test.go @@ -78,8 +78,9 @@ func TestServerCreatePostponeCutOverFlagFile(t *testing.T) { } dir, err := os.MkdirTemp("", "gh-ost-test-") require.NoError(t, err) + defer os.RemoveAll(dir) - filePath := path.Join(dir, "postpone-cut-over.flag") + filePath := filepath.Join(dir, "postpone-cut-over.flag") err = s.createPostponeCutOverFlagFile(filePath) require.NoError(t, err) From 9702e898533b419689779df2748eab03dfa64a78 Mon Sep 17 00:00:00 2001 From: Yuuki Takahashi <20282867+yktakaha4@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:01:44 +0900 Subject: [PATCH 3/3] fix import error --- go/logic/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/logic/server_test.go b/go/logic/server_test.go index d28774d8d..52624a27d 100644 --- a/go/logic/server_test.go +++ b/go/logic/server_test.go @@ -80,7 +80,7 @@ func TestServerCreatePostponeCutOverFlagFile(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dir) - filePath := filepath.Join(dir, "postpone-cut-over.flag") + filePath := path.Join(dir, "postpone-cut-over.flag") err = s.createPostponeCutOverFlagFile(filePath) require.NoError(t, err)