put different file to different folder

master
codeskyblue 8 years ago
parent 88d6cfd008
commit 4c60901c65

@ -31,10 +31,13 @@ For binary version, just click
$ gosuv stop timetest
program "timetest" stopped
$ gosuv tail -n 2 -f timetest
$ gosuv tail -n 2 timetest
line 1
line 2
line ...
$ gosuv remove timetest
# remove program which named timetest
# see more usage
$ gosuv help

@ -17,4 +17,4 @@ then
fi
SHA=$(git rev-parse HEAD)
exec go build -ldflags "-X main.GOSUV_VERSION=$VERSION"
exec go build -ldflags "-X main.GOSUV_VERSION=$VERSION" "$@"

@ -0,0 +1,14 @@
package config
import (
"os"
"path/filepath"
)
var (
GOSUV_HOME = os.ExpandEnv("$HOME/.gosuv")
GOSUV_SOCK_PATH = filepath.Join(GOSUV_HOME, "gosuv.sock")
GOSUV_CONFIG = filepath.Join(GOSUV_HOME, "gosuv.json")
GOSUV_PROGRAM_CONFIG = filepath.Join(GOSUV_HOME, "programs.json")
CMDPLUGIN_DIR = filepath.Join(GOSUV_HOME, "cmdplugin")
)

@ -14,7 +14,9 @@ import (
"github.com/codegangsta/cli"
"github.com/codegangsta/inject"
. "github.com/codeskyblue/gosuv/config"
pb "github.com/codeskyblue/gosuv/gosuvpb"
. "github.com/codeskyblue/gosuv/utils"
"github.com/qiniu/log"
"golang.org/x/net/context"
"google.golang.org/grpc"
@ -22,17 +24,6 @@ import (
var GOSUV_VERSION = "UNKNOWN"
var (
GOSUV_HOME = os.ExpandEnv("$HOME/.gosuv")
GOSUV_SOCK_PATH = filepath.Join(GOSUV_HOME, "gosuv.sock")
GOSUV_CONFIG = filepath.Join(GOSUV_HOME, "gosuv.json")
GOSUV_PROGRAM_CONFIG = filepath.Join(GOSUV_HOME, "programs.json")
)
var (
CMDPLUGIN_DIR = filepath.Join(GOSUV_HOME, "cmdplugin")
)
func MkdirIfNoExists(dir string) error {
dir = os.ExpandEnv(dir)
if _, err := os.Stat(dir); err != nil {

@ -3,206 +3,15 @@ package main
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"sync"
"syscall"
"time"
"github.com/codeskyblue/kexec"
"github.com/qiniu/log"
. "github.com/codeskyblue/gosuv/config"
. "github.com/codeskyblue/gosuv/program"
)
var ErrGoTimeout = errors.New("GoTimeoutFunc")
func GoFunc(f func() error) chan error {
ch := make(chan error)
go func() {
ch <- f()
}()
return ch
}
func GoTimeoutFunc(timeout time.Duration, f func() error) chan error {
ch := make(chan error)
go func() {
var err error
select {
case err = <-GoFunc(f):
ch <- err
case <-time.After(timeout):
log.Debugf("timeout: %v", f)
ch <- ErrGoTimeout
}
}()
return ch
}
const (
ST_RUNNING = "RUNNING"
ST_STOPPED = "STOPPED"
ST_FATAL = "FATAL"
ST_RETRYWAIT = "RETRYWAIT" // some like python-supervisor EXITED
)
type Event int
const (
EVENT_START = Event(iota)
EVENT_STOP
)
type ProgramInfo struct {
Name string `json:"name"`
Command []string `json:"command"`
Environ []string `json:"environ"`
Dir string `json:"directory"`
AutoStart bool `json:"autostart"` // change to *bool, which support unexpected
StartRetries int `json:"startretries"`
StartSeconds int `json:"startsecs"`
}
func (p *ProgramInfo) buildCmd() *kexec.KCommand {
cmd := kexec.Command(p.Command[0], p.Command[1:]...)
cmd.Dir = p.Dir
cmd.Env = append(os.Environ(), p.Environ...)
return cmd
}
type Program struct {
*kexec.KCommand `json:"-"`
Status string `json:"state"`
Sig chan os.Signal `json:"-"`
Info *ProgramInfo `json:"info"`
retry int
stopc chan bool
}
func NewProgram(info *ProgramInfo) *Program {
// set default values
if info.StartRetries == 0 {
info.StartRetries = 3
}
if info.StartSeconds == 0 {
info.StartSeconds = 3
}
return &Program{
Status: ST_STOPPED,
Sig: make(chan os.Signal),
Info: info,
stopc: make(chan bool),
}
}
func (p *Program) setStatus(st string) {
// TODO: status change hook
p.Status = st
}
func (p *Program) InputData(event Event) {
switch event {
case EVENT_START:
if p.Status == ST_STOPPED || p.Status == ST_FATAL {
go p.RunWithRetry()
}
case EVENT_STOP:
if p.Status == ST_RUNNING || p.Status == ST_RETRYWAIT {
p.Stop()
}
}
}
func (p *Program) createLog() (*os.File, error) {
logDir := filepath.Join(GOSUV_HOME, "logs")
os.MkdirAll(logDir, 0755) // just do it, err ignore it
logFile := p.logFilePath()
return os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
}
func (p *Program) sleep(d time.Duration) {
select {
case <-p.stopc:
return
case <-time.After(time.Second * 2):
}
}
func (p *Program) logFilePath() string {
logDir := filepath.Join(GOSUV_HOME, "logs")
return filepath.Join(logDir, p.Info.Name+".log")
}
func (p *Program) RunWithRetry() {
for p.retry = 0; p.retry < p.Info.StartRetries+1; p.retry += 1 {
// wait program to exit
errc := GoFunc(p.Run)
var err error
PROGRAM_WAIT:
// Here is RUNNING State
select {
case err = <-errc:
log.Info(p.Info.Name, err)
case <-time.After(time.Second * time.Duration(p.Info.StartSeconds)): // reset retry
p.retry = 0
goto PROGRAM_WAIT
case <-p.stopc:
return
}
// Enter RETRY_WAIT State
if p.retry < p.Info.StartRetries {
p.setStatus(ST_RETRYWAIT)
select {
case <-p.stopc:
return
case <-time.After(time.Second * 2):
}
}
}
p.setStatus(ST_FATAL)
}
func (p *Program) Run() (err error) {
if err = p.Start(); err != nil {
return
}
p.setStatus(ST_RUNNING)
defer func() {
if out, ok := p.Cmd.Stdout.(io.Closer); ok {
out.Close()
}
log.Warnf("program finish: %v", err)
}()
err = p.Wait()
return
}
func (p *Program) Stop() error {
select {
case p.stopc <- true: // stopc may not recevied
case <-time.After(time.Millisecond * 50):
}
p.Terminate(syscall.SIGKILL)
p.setStatus(ST_STOPPED)
return nil
}
func (p *Program) Start() error {
p.KCommand = p.Info.buildCmd()
logFd, err := p.createLog()
if err != nil {
return err
}
p.Cmd.Stdout = logFd
p.Cmd.Stderr = logFd
return p.Cmd.Start()
}
var programTable *ProgramTable
func initProgramTable() {
@ -220,7 +29,7 @@ type ProgramTable struct {
}
var (
ErrProgramDuplicate = errors.New("program duplicate")
ErrProgramDuplicate = errors.New("program duplicated")
ErrProgramNotExists = errors.New("program not exists")
)
@ -244,6 +53,7 @@ func (pt *ProgramTable) loadConfig() error {
return err
}
defer cfgFd.Close()
table := make(map[string]*ProgramInfo)
if err = json.NewDecoder(cfgFd).Decode(&table); err != nil {
return err

@ -0,0 +1,178 @@
package program
import (
"io"
"os"
"path/filepath"
"sync"
"syscall"
"time"
. "github.com/codeskyblue/gosuv/config"
. "github.com/codeskyblue/gosuv/utils"
"github.com/codeskyblue/kexec"
"github.com/qiniu/log"
)
const (
ST_RUNNING = "RUNNING"
ST_STOPPED = "STOPPED"
ST_FATAL = "FATAL"
ST_RETRYWAIT = "RETRYWAIT" // some like python-supervisor EXITED
)
type Event int
const (
EVENT_START = Event(iota)
EVENT_STOP
)
type ProgramInfo struct {
Name string `json:"name"`
Command []string `json:"command"`
Environ []string `json:"environ"`
Dir string `json:"directory"`
AutoStart bool `json:"autostart"` // change to *bool, which support unexpected
StartRetries int `json:"startretries"`
StartSeconds int `json:"startsecs"`
LogDir string `json:"logdir"`
}
func (p *ProgramInfo) buildCmd() *kexec.KCommand {
cmd := kexec.Command(p.Command[0], p.Command[1:]...)
cmd.Dir = p.Dir
cmd.Env = append(os.Environ(), p.Environ...)
return cmd
}
type Program struct {
*kexec.KCommand `json:"-"`
Status string `json:"state"`
Sig chan os.Signal `json:"-"`
Info *ProgramInfo `json:"info"`
mu sync.Mutex
retry int
stopC chan bool
}
func NewProgram(info *ProgramInfo) *Program {
// set default values
if info.StartRetries == 0 {
info.StartRetries = 3
}
if info.StartSeconds == 0 {
info.StartSeconds = 3
}
return &Program{
Status: ST_STOPPED,
Sig: make(chan os.Signal),
Info: info,
stopC: make(chan bool),
}
}
// TODO: status change hook
func (p *Program) setStatus(st string) {
p.Status = st
}
// STOP and START Should not run parallel
func (p *Program) InputData(event Event) {
p.mu.Lock()
defer p.mu.Unlock()
switch event {
case EVENT_START:
if p.Status == ST_STOPPED || p.Status == ST_FATAL {
go p.RunWithRetry()
}
case EVENT_STOP:
if p.Status == ST_RUNNING || p.Status == ST_RETRYWAIT {
p.stop()
}
}
}
func (p *Program) createLog() (*os.File, error) {
logDir := filepath.Join(p.Info.LogDir, "logs")
os.MkdirAll(logDir, 0755) // just do it, err ignore it
logFile := p.LogFilePath()
return os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
}
func (p *Program) LogFilePath() string {
logDir := p.Info.LogDir
if logDir == "" {
logDir = filepath.Join(GOSUV_HOME, "logs")
}
return filepath.Join(logDir, p.Info.Name+".log")
}
func (p *Program) RunWithRetry() {
for p.retry = 0; p.retry < p.Info.StartRetries+1; p.retry += 1 {
// wait program to exit
errc := GoFunc(p.run)
var err error
PROGRAM_WAIT:
// Here is RUNNING State
select {
case err = <-errc:
log.Info(p.Info.Name, err)
case <-time.After(time.Second * time.Duration(p.Info.StartSeconds)): // reset retry
p.retry = 0
goto PROGRAM_WAIT
case <-p.stopC:
return
}
// Enter RETRY_WAIT State
if p.retry < p.Info.StartRetries {
p.setStatus(ST_RETRYWAIT)
select {
case <-p.stopC:
return
case <-time.After(time.Second * 2):
}
}
}
p.setStatus(ST_FATAL)
}
func (p *Program) start() error {
p.KCommand = p.Info.buildCmd()
logFd, err := p.createLog()
if err != nil {
return err
}
p.Cmd.Stdout = logFd
p.Cmd.Stderr = logFd
return p.Cmd.Start()
}
func (p *Program) run() (err error) {
if err = p.start(); err != nil {
return
}
p.setStatus(ST_RUNNING)
defer func() {
if out, ok := p.Cmd.Stdout.(io.Closer); ok {
out.Close()
}
log.Warnf("program finish: %v", err)
}()
err = p.Wait()
return
}
func (p *Program) stop() error {
select {
case p.stopC <- true: // stopC may not recevied, when in FATAL, in a very low probality
case <-time.After(time.Millisecond * 200): // 0.2s
}
p.Terminate(syscall.SIGKILL)
p.setStatus(ST_STOPPED)
return nil
}

@ -0,0 +1,7 @@
package program
import "testing"
func TestProgramCreate(t *testing.T) {
}

@ -7,6 +7,7 @@ import (
"os/signal"
"syscall"
. "github.com/codeskyblue/gosuv/config"
pb "github.com/codeskyblue/gosuv/gosuvpb"
"github.com/qiniu/log"
"google.golang.org/grpc"

@ -7,7 +7,9 @@ import (
"os/exec"
"time"
. "github.com/codeskyblue/gosuv/config"
pb "github.com/codeskyblue/gosuv/gosuvpb"
. "github.com/codeskyblue/gosuv/program"
"golang.org/x/net/context"
)
@ -103,7 +105,7 @@ func (c *PbProgram) Tail(in *pb.TailRequest, stream pb.Program_TailServer) (err
if in.Follow {
args = append(args, "-f")
}
cmd := exec.Command("tail", append(args, program.logFilePath())...)
cmd := exec.Command("tail", append(args, program.LogFilePath())...)
rd, err := cmd.StdoutPipe()
go cmd.Run()
defer func() {

@ -5,6 +5,7 @@
N=1
while true
do
sleep 5
exit 1
echo "Hello $N"
sleep 2
N=$(expr $N + 1)
done

@ -0,0 +1,33 @@
package utils
import (
"errors"
"time"
"github.com/qiniu/log"
)
var ErrGoTimeout = errors.New("GoTimeoutFunc")
func GoFunc(f func() error) chan error {
ch := make(chan error)
go func() {
ch <- f()
}()
return ch
}
func GoTimeoutFunc(timeout time.Duration, f func() error) chan error {
ch := make(chan error)
go func() {
var err error
select {
case err = <-GoFunc(f):
ch <- err
case <-time.After(timeout):
log.Debugf("timeout: %v", f)
ch <- ErrGoTimeout
}
}()
return ch
}
Loading…
Cancel
Save