add stop command support

master
codeskyblue 8 years ago
parent 522f96dcce
commit c7db38cb7a

@ -122,7 +122,7 @@ type Process struct {
*FSM `json:"-"`
Program `json:"program"`
cmd *kexec.KCommand
stopC chan int
stopC chan syscall.Signal
retryLeft int
Status string `json:"status"`
}
@ -152,9 +152,12 @@ func (p *Process) waitNextRetry() {
}
func (p *Process) stopCommand() {
if p.cmd != nil {
p.cmd.Terminate(syscall.SIGKILL)
if p.cmd == nil {
return
}
p.cmd.Terminate(syscall.SIGKILL)
p.cmd = nil
time.Sleep(200 * time.Millisecond)
p.SetState(Stopped)
}
@ -163,7 +166,9 @@ func (p *Process) IsRunning() bool {
}
func (p *Process) startCommand() {
p.cmd = kexec.CommandString("echo hello world && sleep 4 && echo end")
p.stopCommand()
log.Println("start cmd:", p.Name, p.Command)
p.cmd = kexec.CommandString(p.Command)
p.cmd.Stdout = os.Stdout
p.SetState(Running)
@ -182,6 +187,7 @@ func (p *Process) startCommand() {
}
p.waitNextRetry()
case <-p.stopC:
log.Println("recv stop command")
p.stopCommand()
}
}()
@ -191,7 +197,7 @@ func NewProcess(pg Program) *Process {
pr := &Process{
FSM: NewFSM(Stopped),
Program: pg,
stopC: make(chan int),
stopC: make(chan syscall.Signal),
retryLeft: pg.StartRetries,
Status: string(Stopped),
}
@ -209,7 +215,10 @@ func NewProcess(pg Program) *Process {
pr.AddHandler(Fatal, StartEvent, pr.startCommand)
pr.AddHandler(Running, StopEvent, func() {
pr.cmd.Terminate(syscall.SIGKILL)
select {
case pr.stopC <- syscall.SIGTERM:
case <-time.After(200 * time.Millisecond):
}
}).AddHandler(Running, RestartEvent, func() {
go func() {
pr.Operate(StopEvent)

@ -64,16 +64,16 @@
<td v-text="p.program.name"></td>
<td v-text="p.status"></td>
<td>
<button v-on:click="cmdStart(p.program.name)" class="btn btn-default btn-xs">
<button v-on:click="cmdStart(p.program.name)" class="btn btn-default btn-xs" :disabled="p.status=='running'">
<span class="glyphicon glyphicon-play"></span> Start
</button>
<button class="btn btn-default btn-xs" :disabled="p.status=='stopped'">
<button class="btn btn-default btn-xs" v-on:click="cmdStop(p.program.name)" :disabled="!canStop(p.status)">
<span class="glyphicon glyphicon-stop"></span> Stop
</button>
<button class="btn btn-default btn-xs" disabled="true">
<span class="glyphicon glyphicon-minus"></span> Tailf
</button>
<button class="btn btn-default btn-xs">
<button class="btn btn-default btn-xs" disabled="true">
<span class="glyphicon glyphicon-cog"></span> Setting
</button>
</td>
@ -116,22 +116,26 @@
</div>
<div class="form-group">
<label>Command</label>
<input type="text" name="command" value="redis-server --port 7788" class="form-control" placeholder="shell command, ex: redis-server --port 6379">
<input type="text" name="command" value="echo redis-server --port 7788" class="form-control" placeholder="shell command, ex: redis-server --port 6379">
</div>
<div class="form-group">
<label>Directory</label>
<input type="text" name="dir" class="form-control" placeholder="directory, default is /">
</div>
<div class="form-group">
<label>Fail Retries</label>
<input style="max-width: 5em" type="number" name="retries" class="form-control" min="0" step="1" value="3">
</div>
<div class="checkbox">
<label>
<input name="autostart" type="checkbox"> Auto start
</label>
</div>
<button type="submit" class="btn btn-Wdefault">Submit</button>
<!-- <button type="submit" class="btn btn-Wdefault">Submit</button> -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
<button type="submit" class="btn btn-primary">Add program</button>
</div>
</form>
</div>
@ -142,7 +146,6 @@
<!-- /.modal -->
</div>
<script src="/res/js/jquery-3.1.0.min.js"></script>
<script src="/res/js/jquery.form.min.js"></script>
<script src="/res/bootstrap-3.3.5/js/bootstrap.min.js"></script>
<script src="/res/js/moment.min.js"></script>
<script src="/res/js/underscore-min.js"></script>

@ -82,6 +82,22 @@ var vm = new Vue({
}
})
},
cmdStop: function(name) {
$.ajax({
url: "/api/programs/" + name + "/stop",
method: 'post',
success: function(data) {
console.log(data);
}
})
},
canStop: function(status) {
switch (status) {
case "running":
case "retry wait":
return true;
}
}
}
})
@ -113,7 +129,6 @@ $(function() {
url: url,
data: data,
success: function(data) {
console.log(data);
if (data.status === 0) {
$("#newProgram").modal('hide');
} else {

@ -7,10 +7,12 @@ import (
"io/ioutil"
"net/http"
"os"
"os/signal"
"path/filepath"
"reflect"
"strconv"
"sync"
"syscall"
"time"
"github.com/go-yaml/yaml"
@ -36,7 +38,6 @@ func (s *Supervisor) newProcess(pg Program) *Process {
p := NewProcess(pg)
origFunc := p.StateChange
p.StateChange = func(oldState, newState FSMState) {
log.Println(newState)
s.broadcastEvent(string(newState))
origFunc(oldState, newState)
}
@ -48,10 +49,8 @@ func (s *Supervisor) broadcastEvent(event string) {
defer s.mu.Unlock()
validEventCs := make([]chan string, 0, len(s.eventCs))
for _, c := range s.eventCs {
log.Println("start send events")
select {
case c <- event:
log.Println("Send events")
validEventCs = append(validEventCs, c)
case <-time.After(500 * time.Millisecond):
log.Println("Chan closed, remove from queue")
@ -61,6 +60,8 @@ func (s *Supervisor) broadcastEvent(event string) {
}
func (s *Supervisor) addOrUpdateProgram(pg Program) error {
defer s.broadcastEvent("add or update")
origPg, ok := s.pgMap[pg.Name]
if ok {
if !reflect.DeepEqual(origPg, &pg) {
@ -173,11 +174,17 @@ func (s *Supervisor) hGetProgram(w http.ResponseWriter, r *http.Request) {
}
func (s *Supervisor) hAddProgram(w http.ResponseWriter, r *http.Request) {
retries, err := strconv.Atoi(r.FormValue("retries"))
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
pg := Program{
Name: r.FormValue("name"),
Command: r.FormValue("command"),
Dir: r.FormValue("dir"),
AutoStart: r.FormValue("autostart") == "on",
Name: r.FormValue("name"),
Command: r.FormValue("command"),
Dir: r.FormValue("dir"),
AutoStart: r.FormValue("autostart") == "on",
StartRetries: retries,
// TODO: missing other values
}
if pg.Dir == "" {
@ -211,7 +218,6 @@ func (s *Supervisor) hAddProgram(w http.ResponseWriter, r *http.Request) {
}
func (s *Supervisor) hStartProgram(w http.ResponseWriter, r *http.Request) {
log.Println("Hello")
name := mux.Vars(r)["name"]
proc, ok := s.procMap[name]
var data []byte
@ -230,6 +236,25 @@ func (s *Supervisor) hStartProgram(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
func (s *Supervisor) hStopProgram(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
proc, ok := s.procMap[name]
var data []byte
if !ok {
data, _ = json.Marshal(map[string]interface{}{
"status": 1,
"error": fmt.Sprintf("Process %s not exists", strconv.Quote(name)),
})
} else {
proc.Operate(StopEvent)
data, _ = json.Marshal(map[string]interface{}{
"status": 0,
"name": name,
})
}
w.Write(data)
}
var upgrader = websocket.Upgrader{}
func (s *Supervisor) wsEvents(w http.ResponseWriter, r *http.Request) {
@ -244,9 +269,8 @@ func (s *Supervisor) wsEvents(w http.ResponseWriter, r *http.Request) {
s.eventCs = append(s.eventCs, ch)
go func() {
for message := range ch {
log.Println(message)
// Question: type 1 ?
c.WriteMessage(1, []byte(message))
// c.WriteMessage(mt, message)
}
close(ch)
}()
@ -265,6 +289,20 @@ func (s *Supervisor) wsEvents(w http.ResponseWriter, r *http.Request) {
}
}
func (s *Supervisor) catchExitSignal() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-c
fmt.Printf("Got signal: %v, stopping all running process\n", sig)
for _, proc := range s.procMap {
proc.stopCommand()
}
fmt.Println("Finished. Exit with code 0")
os.Exit(0)
}()
}
func init() {
suv := &Supervisor{
ConfigDir: filepath.Join(UserHomeDir(), ".gosuv"),
@ -275,11 +313,14 @@ func init() {
if err := suv.loadDB(); err != nil {
log.Fatal(err)
}
suv.catchExitSignal()
r := mux.NewRouter()
r.HandleFunc("/", suv.hIndex)
r.HandleFunc("/api/programs", suv.hGetProgram).Methods("GET")
r.HandleFunc("/api/programs", suv.hAddProgram).Methods("POST")
r.HandleFunc("/api/programs/{name}/start", suv.hStartProgram).Methods("POST")
r.HandleFunc("/api/programs/{name}/stop", suv.hStopProgram).Methods("POST")
r.HandleFunc("/ws/events", suv.wsEvents)
fs := http.FileServer(http.Dir("res"))

Loading…
Cancel
Save