Skip to content

TCM: Add tt tcm status/stop #1152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- `tt tcm status`: added command to check TCM runtime status (modes: `watchdog` or `interactive`).
- `tt tcm stop`: add command for graceful termination of TCM processes (modes: `watchdog` or `interactive`).

### Changed

### Fixed
Expand Down Expand Up @@ -755,4 +758,4 @@ Additionally, several fixes were implemented to improve stability.
- Module ``tt create``, to create an application from a template.
- Module ``tt build``, to build an application.
- Module ``tt install``, to install tarantool/tt.
- Module ``tt remove``, to remove tarantool/tt.
- Module ``tt remove``, to remove tarantool/tt.
116 changes: 98 additions & 18 deletions cli/cmd/tcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,103 @@ package cmd

import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"time"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
"github.com/tarantool/tt/cli/cmdcontext"
"github.com/tarantool/tt/cli/modules"
"github.com/tarantool/tt/cli/process_utils"
tcmCmd "github.com/tarantool/tt/cli/tcm"
"github.com/tarantool/tt/cli/util"
libwatchdog "github.com/tarantool/tt/lib/watchdog"
)

var tcmCtx = tcmCmd.TcmCtx{}

const (
tcmPidFile = "tcm.pid"
watchdogPidFile = "watchdog.pid"
)

func newTcmStartCmd() *cobra.Command {
var tcmCmd = &cobra.Command{
Use: "start",
Short: "Start tcm application",
Long: `Start to the tcm.
tt tcm start --watchdog
tt tcm start --path`,
Run: func(cmd *cobra.Command, args []string) {
cmdCtx.CommandName = cmd.Name()
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo, internalStartTcm, args)
util.HandleCmdErr(cmd, err)

},
Run: RunModuleFunc(internalStartTcm),
}
tcmCmd.Flags().StringVar(&tcmCtx.Executable, "path", "", "the path to the tcm binary file")
tcmCmd.Flags().BoolVar(&tcmCtx.Watchdog, "watchdog", false, "enables the watchdog")

return tcmCmd
}

func newTcmStatusCmd() *cobra.Command {
var tcmCmd = &cobra.Command{
Use: "status",
Short: "Status tcm application",
Long: `Status to the tcm.
tt tcm status`,
Run: RunModuleFunc(internalTcmStatus),
}
return tcmCmd
}

func newTcmStopCmd() *cobra.Command {
var tcmCmd = &cobra.Command{
Use: "stop",
Short: "Stop tcm application",
Long: `Stop to the tcm. tt tcm stop`,
Run: RunModuleFunc(internalTcmStop),
}
return tcmCmd
}

func NewTcmCmd() *cobra.Command {
var tcmCmd = &cobra.Command{
Use: "tcm",
Short: "Manage tcm application",
}
tcmCmd.AddCommand(
newTcmStartCmd(),
newTcmStatusCmd(),
newTcmStopCmd(),
)
return tcmCmd
}

func startTcmInteractive() error {
tcmApp := exec.Command(tcmCtx.Executable)

tcmApp.Stdout = os.Stdout
tcmApp.Stderr = os.Stderr

if err := tcmApp.Run(); err != nil {
if err := tcmApp.Start(); err != nil {
return err
}

return nil
}
if tcmApp == nil || tcmApp.Process == nil {
return errors.New("process is not running")
}

func startTcmUnderWatchDog() error {
wd, err := tcmCmd.NewWatchdog(5 * time.Second)
err := process_utils.CreatePIDFile(tcmPidFile, tcmApp.Process.Pid)
if err != nil {
return err
}

log.Printf("(INFO): Interactive process PID %d written to %s\n", tcmApp.Process.Pid, tcmPidFile)
return nil
}

func startTcmUnderWatchDog() error {
wd := libwatchdog.NewWatchdog(tcmPidFile, watchdogPidFile, 5*time.Second)
if err := wd.Start(tcmCtx.Executable); err != nil {
return err
}

return nil
}

Expand All @@ -87,11 +117,61 @@ func internalStartTcm(cmdCtx *cmdcontext.CmdCtx, args []string) error {
if err := startTcmInteractive(); err != nil {
return err
}
} else {
if err := startTcmUnderWatchDog(); err != nil {
return err
}
}

if err := startTcmUnderWatchDog(); err != nil {
return nil
}

func internalTcmStatus(cmdCtx *cmdcontext.CmdCtx, args []string) error {
pidAbsPath, err := filepath.Abs(tcmPidFile)
if err != nil {
return err
}

if _, err := os.Stat(pidAbsPath); err != nil {
return fmt.Errorf("path does not exist: %v", err)
}

ts := table.NewWriter()
ts.SetOutputMirror(os.Stdout)

ts.AppendHeader(
table.Row{"APPLICATION", "STATUS", "PID"})

ts.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, Align: text.AlignLeft, AlignHeader: text.AlignLeft},
{Number: 2, Align: text.AlignLeft, AlignHeader: text.AlignLeft},
{Number: 3, Align: text.AlignLeft, AlignHeader: text.AlignLeft},
{Number: 4, Align: text.AlignLeft, AlignHeader: text.AlignLeft},
})

status := process_utils.ProcessStatus(pidAbsPath)

ts.AppendRows([]table.Row{
{"TCM", status.Status, status.PID},
})
ts.Render()
return nil
}

func internalTcmStop(cmdCtx *cmdcontext.CmdCtx, args []string) error {
if isExists, _ := process_utils.ExistsAndRecord(watchdogPidFile); isExists {
_, err := process_utils.StopProcess(watchdogPidFile)
if err != nil {
return err
}
log.Println("Watchdog and TCM stopped")
} else {
_, err := process_utils.StopProcess(tcmPidFile)
if err != nil {
return err
}
log.Println("TCM stopped")
}

return nil
}
21 changes: 21 additions & 0 deletions cli/process_utils/process_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ func CheckPIDFile(pidFileName string) error {
return nil
}

// ExistsAndRecord checks if the process with the given pidFileName exists and is alive.
// If it does, returns true, otherwise returns false.
// If something went wrong while trying to read the PID file, returns an error.
func ExistsAndRecord(pidFileName string) (bool, error) {
Comment on lines +107 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a test for the call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if _, err := os.Stat(pidFileName); err == nil {
// The PID file already exists. We have to check if the process is alive.
pid, err := GetPIDFromFile(pidFileName)
if err != nil {
return false, fmt.Errorf(`PID file exists, but PID can't be read. Error: "%v"`, err)
}
if res, _ := IsProcessAlive(pid); res {
return true, nil
}
} else if !os.IsNotExist(err) {
return false, fmt.Errorf(`something went wrong while trying to read the`+
`PID file. Error: "%v"`, err)
}

return false, nil
}

// CreatePIDFile checks that the instance PID file is absent or
// deprecated and creates a new one. Returns an error on failure.
func CreatePIDFile(pidFileName string, pid int) error {
Expand Down
36 changes: 36 additions & 0 deletions cli/process_utils/process_utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package process_utils

import (
"os"
"os/exec"
"testing"

"github.com/stretchr/testify/require"
)

func Test_ExistsAndRecord(t *testing.T) {
testFile := "test.pid"
invalid := "invalid.pid"
cmd := exec.Command("sleep", "10")

t.Cleanup(func() {
os.Remove(testFile)
})

err := cmd.Start()
require.NoError(t, err)

err = CreatePIDFile(testFile, cmd.Process.Pid)
require.NoError(t, err)

status, err := ExistsAndRecord(testFile)
require.NoError(t, err)
require.True(t, status)

err = cmd.Process.Kill()
require.NoError(t, err)

statusInvalid, err := ExistsAndRecord(invalid)
require.False(t, statusInvalid)
require.NoError(t, err)
}
9 changes: 8 additions & 1 deletion cli/tcm/tcm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package tcm

// TcmCtx holds parameters and state for managing the TCM process and its watchdog.
type TcmCtx struct {
// Path to the TCM executable file.
Executable string
Watchdog bool
// Path to the file storing the TCM process PID.
TcmPidFile string
// Flag indicating whether the watchdog is enabled.
Watchdog bool
// Path to the file storing the watchdog process PID.
WathdogPidFile string
}
Loading
Loading