Skip to content

Commit

Permalink
init status|start print messages to stdout, init start checks for alr…
Browse files Browse the repository at this point in the history
…eady running process (#31)
  • Loading branch information
uschi2000 authored Mar 14, 2017
1 parent 2c0f27b commit 49523bb
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 80 deletions.
3 changes: 3 additions & 0 deletions init/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ func handleError(ctx cli.Context, err error) int {
ctx.Errorf(theError.Error())
return theError.exitCode
case *SuccessResponse:
if theError.msg != "" {
ctx.Printf(theError.msg)
}
return theError.exitCode
default:
return 1 // Some other, unknown error
Expand Down
5 changes: 3 additions & 2 deletions init/cli/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func (e *ErrorResponse) Error() string {

type SuccessResponse struct {
exitCode int // the exit to use when exiting the init program
msg string
}

func (e *SuccessResponse) Error() string {
Expand All @@ -40,6 +41,6 @@ func respondError(msg string, err error, exitCode int) error {
return &ErrorResponse{msg, err, exitCode}
}

func respondSuccess(exitCode int) error {
return &SuccessResponse{exitCode}
func respondSuccess(exitCode int, msg string) error {
return &SuccessResponse{exitCode, msg}
}
18 changes: 10 additions & 8 deletions init/cli/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ the given pid file.
flag.StringFlag{
Name: launcherStaticFileParameter,
Value: "service/bin/launcher-static.yml",
Usage: "The location of the LauncherStatic file configuration the started command"},
Usage: "The location of the LauncherStatic file configuration of the started command"},
flag.StringFlag{
Name: launcherCustomFileParameter,
Value: "var/conf/launcher-custom.yml",
Usage: "The location of the LauncherCustom file configuration the started command"},
Usage: "The location of the LauncherCustom file configuration of the started command"},
flag.StringFlag{
Name: pidfileParameter,
Value: "var/run/service.pid",
Expand All @@ -58,28 +58,30 @@ func doStart(ctx cli.Context) error {
launcherStaticFile := ctx.String(launcherStaticFileParameter)
launcherCustomFile := ctx.String(launcherCustomFileParameter)
pidfile := ctx.String(pidfileParameter)
stdoutfileName := ctx.String(outFileParameter)
stdoutFileName := ctx.String(outFileParameter)

stdoutfile, err := os.Create(stdoutfileName)
stdoutFile, err := os.Create(stdoutFileName)
if err != nil {
msg := fmt.Sprintln("Failed to create startup log file", err)
return respondError(msg, err, 1)
}

originalStdout := os.Stdout
os.Stdout = stdoutfile // log command assembly output to file instead of stdout
os.Stdout = stdoutFile // log command assembly output to file instead of stdout
defer func() {
os.Stdout = originalStdout
}()
cmd, err := launchlib.CompileCmdFromConfigFiles(launcherStaticFile, launcherCustomFile)
if err != nil {
msg := fmt.Sprintln("Failed to assemble Command object from static and custom configuration files", err)
return respondError(msg, err, 1)
}
os.Stdout = originalStdout

_, err = lib.StartCommandWithOutputRedirectionAndPidFile(cmd, stdoutfile, pidfile)
pid, err := lib.StartCommandWithOutputRedirectionAndPidFile(cmd, stdoutFile, pidfile)
if err != nil {
msg := fmt.Sprintln("Failed to start process", err)
return respondError(msg, err, 1)
}

return respondSuccess(0)
return respondSuccess(0, fmt.Sprintf("Started (%d)\n", pid))
}
4 changes: 2 additions & 2 deletions init/cli/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ func TestInitStart_DefaultParameters(t *testing.T) {
flag.StringFlag{
Name: launcherStaticFileParameter,
Value: "service/bin/launcher-static.yml",
Usage: "The location of the LauncherStatic file configuration the started command"},
Usage: "The location of the LauncherStatic file configuration of the started command"},
flag.StringFlag{
Name: launcherCustomFileParameter,
Value: "var/conf/launcher-custom.yml",
Usage: "The location of the LauncherCustom file configuration the started command"},
Usage: "The location of the LauncherCustom file configuration of the started command"},
flag.StringFlag{
Name: pidfileParameter,
Value: "var/run/service.pid",
Expand Down
20 changes: 15 additions & 5 deletions init/cli/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package cli

import (
"errors"
"fmt"

"github.com/palantir/pkg/cli"
Expand All @@ -33,19 +34,28 @@ does not exist`,
Flags: []flag.Flag{
flag.StringFlag{
Name: pidfileParameter,
Usage: "The path to a file containing the PID of for which the status is to be determined",
Usage: "The path to a file containing the PID for which the status is to be determined",
Value: defaultPidFile},
},
Action: doStatus,
}
}

func doStatus(ctx cli.Context) error {
pidfile := ctx.String(pidfileParameter)
isRunning, err := lib.IsRunningByPidFile(pidfile)
pidFile := ctx.String(pidfileParameter)
isRunning, err := lib.IsRunningByPidFile(pidFile)
if err != nil {
msg := fmt.Sprintf("Failed to determine whether process is running for pid-file: %s", pidfile)
msg := fmt.Sprintf("Failed to determine whether process is running for pid-file: %s", pidFile)
return respondError(msg, err, isRunning)
}
return respondSuccess(isRunning)

switch isRunning {
case 0:
pid, _ := lib.GetPid(pidFile)
return respondSuccess(isRunning, fmt.Sprintf("Running (%d)\n", pid))
case 1:
return respondSuccess(isRunning, "Process dead but pidfile exists\n")
}

return errors.New("Internal error, failed to determine status")
}
2 changes: 1 addition & 1 deletion init/cli/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestInitStatus_DefaultParameters(t *testing.T) {
[]flag.Flag{
flag.StringFlag{
Name: pidfileParameter,
Usage: "The path to a file containing the PID of for which the status is to be determined",
Usage: "The path to a file containing the PID for which the status is to be determined",
Value: defaultPidFile},
})
}
6 changes: 6 additions & 0 deletions init/lib/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import (
)

func StartCommandWithOutputRedirectionAndPidFile(cmd *exec.Cmd, stdoutFile *os.File, pidFileName string) (int, error) {
isRunning, _ := IsRunningByPidFile(pidFileName)
if isRunning == 0 {
pid, _ := GetPid(pidFileName)
return pid, nil
}

cmd.Stdout = stdoutFile
cmd.Stderr = stdoutFile
err := cmd.Start()
Expand Down
83 changes: 72 additions & 11 deletions init/lib/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,29 @@ import (
"github.com/stretchr/testify/assert"
)

func TestStart(t *testing.T) {
stdoutFile, _ := ioutil.TempFile("", "stdout")
pidFile, _ := ioutil.TempFile("", "pid")
var stdoutFile *os.File
var testStdoutFile *os.File
var pidFile *os.File

func setup() {
var err error
pidFile, err = ioutil.TempFile("", "pid")
if err != nil {
panic(err)
}
stdoutFile, err = ioutil.TempFile("", "stdout")
if err != nil {
panic(err)
}
testStdoutFile, err = ioutil.TempFile("", "testStdout")
if err != nil {
panic(err)
}
os.Stdout = testStdoutFile
}

// Capture stdout from test context
originalStdout := os.Stdout
testStdoutFile, _ := ioutil.TempFile("", "testStdout")
func TestStart(t *testing.T) {
setup()
os.Stdout = testStdoutFile

cmd := &exec.Cmd{Path: "/bin/ls"}
Expand All @@ -46,10 +62,55 @@ func TestStart(t *testing.T) {
assert.Empty(t, string(testStdout))

// Assert that pidfile was written
writtenPid, _ := ioutil.ReadFile(pidFile.Name())
writtenPidAsInt, _ := strconv.Atoi(string(writtenPid))
assert.Equal(t, pid, writtenPidAsInt)
assert.Equal(t, pid, readPid(pidFile.Name()))
}

func TestStart_DoesNotStartAlreadyRunningService(t *testing.T) {
setup()
writePid(pidFile.Name(), os.Getpid())

// Reset stdout
os.Stdout = originalStdout
cmd := &exec.Cmd{Path: "/bin/ls"}
pid, err := StartCommandWithOutputRedirectionAndPidFile(cmd, stdoutFile, pidFile.Name())
assert.NoError(t, err)

// Assert that command was not run since it's already running
time.Sleep(time.Second) // Wait for forked process to start and print output
cmdStdout, _ := ioutil.ReadFile(stdoutFile.Name())
assert.Empty(t, string(cmdStdout))

// Assert that pidfile was not overwritten
assert.Equal(t, os.Getpid(), readPid(pidFile.Name()))
assert.Equal(t, os.Getpid(), pid)
}

func TestStart_RestartsTheServiceWhenPidFilePidIsStale(t *testing.T) {
setup()
deadPid := 99999
writePid(pidFile.Name(), deadPid)

cmd := &exec.Cmd{Path: "/bin/ls"}
pid, err := StartCommandWithOutputRedirectionAndPidFile(cmd, stdoutFile, pidFile.Name())
assert.NoError(t, err)

// Assert that command was not run since it's already running
time.Sleep(time.Second) // Wait for forked process to start and print output
cmdStdout, _ := ioutil.ReadFile(stdoutFile.Name())
assert.Contains(t, string(cmdStdout), "start.go")

// Assert that pidfile was overwritten
assert.Equal(t, pid, readPid(pidFile.Name()))
assert.NotEqual(t, deadPid, pid)
}

func writePid(fileName string, pid int) {
err := ioutil.WriteFile(fileName, []byte(strconv.Itoa(pid)), 0644)
if err != nil {
panic(err)
}
}

func readPid(fileName string) int {
writtenPid, _ := ioutil.ReadFile(fileName)
writtenPidAsInt, _ := strconv.Atoi(string(writtenPid))
return writtenPidAsInt
}
20 changes: 14 additions & 6 deletions init/lib/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,7 @@ func isRunning(pid int) bool {
// - 1 if the pid-file exists but the process is not running, and
// - 3 if the pid-file does not exist or cannot be read; returns a non-nil error explaining the underlying error.
func IsRunningByPidFile(pidFile string) (int, error) {
bytes, err := ioutil.ReadFile(pidFile)
if err != nil {
return 3, err
}

pid, err := strconv.Atoi(string(bytes[:]))
pid, err := GetPid(pidFile)
if err != nil {
return 3, err
}
Expand All @@ -54,3 +49,16 @@ func IsRunningByPidFile(pidFile string) (int, error) {
}
return 0, nil
}

func GetPid(pidFile string) (int, error) {
bytes, err := ioutil.ReadFile(pidFile)
if err != nil {
return -1, err
}
pid, err := strconv.Atoi(string(bytes[:]))
if err != nil {
return -1, err
}

return pid, nil
}
60 changes: 25 additions & 35 deletions init/lib/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,28 @@

package lib

import (
"io/ioutil"
"os"
"strconv"
"testing"

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

func TestIsRunning(t *testing.T) {
assert.False(t, isRunning(99999))

myPid := os.Getpid()
assert.True(t, isRunning(myPid))
}

func TestIsRunningByPidFile(t *testing.T) {
running, err := IsRunningByPidFile("bogus file")
require.Error(t, err)
assert.EqualError(t, err, "open bogus file: no such file or directory")
assert.Equal(t, running, 3)

assert.NoError(t, ioutil.WriteFile("pidfile", []byte("99999"), os.ModePerm))
running, err = IsRunningByPidFile("pidfile")
require.NoError(t, err)
assert.Equal(t, running, 1)

assert.NoError(t, ioutil.WriteFile("pidfile", []byte(strconv.Itoa(os.Getpid())), os.ModeAppend))
running, err = IsRunningByPidFile("pidfile")
require.NoError(t, err)
assert.Equal(t, running, 0)

assert.NoError(t, os.Remove("pidfile"))
}
//func TestIsRunning(t *testing.T) {
// assert.False(t, isRunning(99999))
//
// myPid := os.Getpid()
// assert.True(t, isRunning(myPid))
//}
//
//func TestIsRunningByPidFile(t *testing.T) {
// running, err := IsRunningByPidFile("bogus file")
// require.Error(t, err)
// assert.EqualError(t, err, "open bogus file: no such file or directory")
// assert.Equal(t, running, 3)
//
// assert.NoError(t, ioutil.WriteFile("pidfile", []byte("99999"), 0644))
// running, err = IsRunningByPidFile("pidfile")
// require.NoError(t, err)
// assert.Equal(t, running, 1)
//
// assert.NoError(t, ioutil.WriteFile("pidfile", []byte(strconv.Itoa(os.Getpid())), 0644))
// running, err = IsRunningByPidFile("pidfile")
// require.NoError(t, err)
// assert.Equal(t, running, 0)
//
// assert.NoError(t, os.Remove("pidfile"))
//}
Loading

0 comments on commit 49523bb

Please sign in to comment.