From f039033077a9f75f5ed09d91eac7515f15e07cc8 Mon Sep 17 00:00:00 2001 From: Evan Date: Sat, 13 Nov 2021 12:59:08 +0800 Subject: [PATCH] chore --- configui.go | 75 ++++++++++++++++++++++++++++++----------------------- errors.go | 24 ++++++++++++++--- handler.go | 63 +++++++++++++++++++++++++++++++++++++------- muzan.go | 58 +++++++++++++++++++++++++++++++---------- server.go | 26 ++++++++++++++++++- util.go | 9 +++++++ 6 files changed, 196 insertions(+), 59 deletions(-) diff --git a/configui.go b/configui.go index a4d22d5..7b08535 100644 --- a/configui.go +++ b/configui.go @@ -77,8 +77,7 @@ type ConfigUI struct { LogLevel klog.Llevel `json:"log_level"` // Running commands - Onitachi []*Oni `json:"onitachi"` - oniIndex map[string]int `json:"-"` + Onitachi map[string]*Oni `json:"onitachi"` TmplFS embed.FS `json:"-"` tmpl *engine.Engine @@ -149,7 +148,7 @@ func New() *ConfigUI { cmdTimeout: time.Second * 10, LogLevel: klog.Lerror | klog.Linfo, log: klog.Sub("ConfigUI"), - oniIndex: make(map[string]int), + Onitachi: make(map[string]*Oni), } } @@ -170,6 +169,8 @@ func (cui *ConfigUI) File(file_name string) (*File, error) { } func (cui *ConfigUI) LoadConfig(confstr string) error { + cui.configLock.Lock() + defer cui.configLock.Unlock() tmpConf := &ConfigUI{} err := json.Unmarshal([]byte(confstr), tmpConf) if err != nil { @@ -192,31 +193,15 @@ func (cui *ConfigUI) LoadConfig(confstr string) error { } } - // construct oni - for _, o := range tmpConf.Onitachi { - if o.Cmd == "" { - continue - } - if o.Name == "" { - o.Name = o.Cmd - } - // update if exist - j, ok := cui.oniIndex[o.Name] - if ok { - cui.Onitachi[j].Cmd = o.Cmd - cui.Onitachi[j].Args = o.Args - cui.Onitachi[j].Envs = o.Envs - cui.Onitachi[j].Dir = o.Dir - cui.Onitachi[j].Policy = o.Policy - } else { - cui.oniIndex[o.Name] = len(cui.Onitachi) - cui.Onitachi = append(cui.Onitachi, o) + // del oni dry run + for k, v := range cui.Onitachi { + _, ok := tmpConf.Onitachi[k] + if !ok && v.State == STARTED { + return ErrorNoDeleteRunning.New(k) } } // copy - cui.configLock.Lock() - defer cui.configLock.Unlock() cui.fileIndex = tmpIndex cui.Files = tmpConf.Files cui.AllowIP = tmpConf.AllowIP @@ -228,6 +213,39 @@ func (cui *ConfigUI) LoadConfig(confstr string) error { cui.ResultBellow = tmpConf.ResultBellow cui.SHELL = tmpConf.SHELL + cui.log = klog.Sub(cui.AppName) + cui.LogLevel = tmpConf.LogLevel + if cui.LogLevel == 0 { + cui.LogLevel = klog.Lerror | klog.Linfo + } + klog.LEVEL = cui.LogLevel + cui.LogPath = tmpConf.LogPath + cui.setLog() + + // construct oni - update existing, del + for k, v := range cui.Onitachi { + n, ok := tmpConf.Onitachi[k] + if !ok { + delete(cui.Onitachi, k) + } else { + v.Name = k + v.Cmd = n.Cmd + v.Args = n.Args + v.Envs = n.Envs + v.Dir = n.Dir + v.Policy = n.Policy + } + } + // construct oni - append new + for k, v := range tmpConf.Onitachi { + _, ok := cui.Onitachi[k] + if !ok { + v.Name = k + v.Init(cui) + cui.Onitachi[k] = v + } + } + cui.Actions = tmpConf.Actions for i := range cui.Actions { if cui.Actions[i].Name == "" { @@ -254,15 +272,6 @@ func (cui *ConfigUI) LoadConfig(confstr string) error { cui.cmdTimeout = ct } - cui.log = klog.Sub(cui.AppName) - - cui.LogLevel = tmpConf.LogLevel - if cui.LogLevel == 0 { - cui.LogLevel = klog.Lerror | klog.Linfo - } - klog.LEVEL = cui.LogLevel - cui.LogPath = tmpConf.LogPath - cui.setLog() // fmt.Printf("%+v", cui) return nil } diff --git a/errors.go b/errors.go index e61cc6b..2d6b290 100644 --- a/errors.go +++ b/errors.go @@ -6,6 +6,12 @@ import ( "kumoly.io/lib/ksrv" ) +var ErrorServerReloading = ksrv.Error{ + Code: http.StatusServiceUnavailable, + ID: "ErrorServerReloading", + Message: "server is reloading", +} + var ErrorOniHasNoPID = ksrv.Error{ Code: http.StatusBadRequest, ID: "ErrorOniHasNoPID", @@ -19,9 +25,9 @@ var ErrorOniNotStarted = ksrv.Error{ } var ErrorOniHasStarted = ksrv.Error{ - Code: http.StatusConflict, - ID: "ErrorOniHasStarted", - Message: "oni has started", + Code: http.StatusConflict, + ID: "ErrorOniHasStarted", + Tmpl: "%s has started", } var ErrorOniNotFound = ksrv.Error{ @@ -35,3 +41,15 @@ var ErrorOniNotValid = ksrv.Error{ ID: "ErrorOniNotValid", Message: "oni no name or cmd", } + +var ErrorNoDeleteRunning = ksrv.Error{ + Code: http.StatusBadRequest, + ID: "ErrorNoDeleteRunning", + Tmpl: "cannot delete running: %s", +} + +var ErrorOniNoLog = ksrv.Error{ + Code: http.StatusNotFound, + ID: "ErrorOniNoLog", + Tmpl: "%s has no logs", +} diff --git a/handler.go b/handler.go index 2ed4c29..9c74cc1 100644 --- a/handler.go +++ b/handler.go @@ -266,26 +266,24 @@ func (cui *ConfigUI) GetOni(w http.ResponseWriter, r *http.Request) { ksrv.JSON(w, cui.Onitachi) return } - i, ok := cui.oniIndex[name] + oni, ok := cui.Onitachi[name] if !ok { panic(ErrorOniNotFound.New(name)) } - oni := cui.Onitachi[i] ksrv.JSON(w, oni) } func (cui *ConfigUI) OniStart(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") - i, ok := cui.oniIndex[name] + oni, ok := cui.Onitachi[name] if !ok { panic(ErrorOniNotFound.New(name)) } - oni := cui.Onitachi[i] switch oni.State { case "": oni.Init(cui) case STARTED: - panic(ErrorOniHasStarted) + panic(ErrorOniHasStarted.New(name)) } err := oni.Start() if err != nil { @@ -296,17 +294,64 @@ func (cui *ConfigUI) OniStart(w http.ResponseWriter, r *http.Request) { func (cui *ConfigUI) OniStop(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") - i, ok := cui.oniIndex[name] + oni, ok := cui.Onitachi[name] if !ok { panic(ErrorOniNotFound.New(name)) } - oni := cui.Onitachi[i] switch oni.State { case STARTED: - oni.Stop() + err := oni.Stop() + if err != nil { + panic(err) + } default: panic(ErrorOniNotStarted.New(name)) } - oni.Start() w.Write([]byte("ok")) } + +func (cui *ConfigUI) OniLog(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + oni, ok := cui.Onitachi[name] + if !ok { + panic(ErrorOniNotFound.New(name)) + } + if oni.LogFile == "" { + panic(ErrorOniNoLog.New(name)) + } + stat, err := os.Stat(oni.LogFile) + if err != nil { + panic(err) + } + data, err := os.ReadFile(oni.LogFile) + if err != nil { + panic(err) + } + ksrv.JSON(w, map[string]string{ + "name": name, + "data": string(data), + "delta": strconv.Itoa(int(stat.Size())), + }) +} + +func (cui *ConfigUI) OniKill(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + oni, ok := cui.Onitachi[name] + if !ok { + panic(ErrorOniNotFound.New(name)) + } + switch oni.State { + case STARTED: + err := oni.Kill() + if err != nil { + panic(err) + } + default: + panic(ErrorOniNotStarted.New(name)) + } + w.Write([]byte("ok")) +} + +func (cui *ConfigUI) OniAttach(w http.ResponseWriter, r *http.Request) { + +} diff --git a/muzan.go b/muzan.go index afccc3f..2ec21cc 100644 --- a/muzan.go +++ b/muzan.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "syscall" ) type State string @@ -29,7 +30,7 @@ const ( ) type Oni struct { - Name string `json:"name"` + Name string `json:"-"` Cmd string `json:"cmd"` Dir string `json:"dir"` Args []string `json:"args"` @@ -51,21 +52,12 @@ type Oni struct { listeners []io.Writer } -func (oni *Oni) Init(cui *ConfigUI) error { - var err error +func (oni *Oni) Init(cui *ConfigUI) { oni.parent = cui oni.running = make(chan struct{}, 1) oni.listeners = make([]io.Writer, 0) oni.StateChange(READY) - if cui.LogPath != "" { - logpath := filepath.Join(filepath.Dir(cui.LogPath), oni.Name+".log") - oni.LogFile = logpath - oni.log, err = os.OpenFile(logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - return err - } - } - return err + oni.SetLog() } func (oni *Oni) Start() error { @@ -83,7 +75,9 @@ func (oni *Oni) Start() error { default: return ErrorOniHasStarted } + var err error cmd := exec.Command(oni.Cmd, oni.Args...) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.Env = oni.Envs cmd.Dir = oni.Dir var out io.Writer @@ -95,7 +89,7 @@ func (oni *Oni) Start() error { cmd.Stderr = out cmd.Stdout = out oni.cmd = cmd - err := oni.cmd.Start() + err = oni.cmd.Start() if err != nil { oni.end(err) return err @@ -114,7 +108,19 @@ func (oni *Oni) Stop() error { if oni.cmd == nil { return nil } + return oni.cmd.Process.Signal(syscall.SIGTERM) +} + +func (oni *Oni) Kill() error { + if oni.cmd == nil { + return nil + } + pgid, err := syscall.Getpgid(oni.cmd.Process.Pid) + if err == nil { + return syscall.Kill(-pgid, 15) // note the minus sign + } return oni.cmd.Process.Kill() + // return oni.cmd.Process.Kill() } func (oni *Oni) end(v interface{}) { @@ -124,6 +130,12 @@ func (oni *Oni) end(v interface{}) { oni.Error = fmt.Sprint(v) oni.StateChange(ERROR) } + if oni.log != nil { + file, ok := oni.log.(*os.File) + if ok { + file.Close() + } + } if len(oni.running) == 1 { <-oni.running } @@ -150,3 +162,23 @@ func (oni *Oni) StateChange(state State) { // } // } } + +func (oni *Oni) SetLog() error { + var err error + if oni.log != nil { + file, ok := oni.log.(*os.File) + if ok { + file.Close() + } + } + if oni.parent.LogPath != "" { + logpath := filepath.Join(filepath.Dir(oni.parent.LogPath), oni.Name+".log") + oni.LogFile = logpath + oni.log, err = os.OpenFile(logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + oni.end(err) + return err + } + } + return err +} diff --git a/server.go b/server.go index 5561ff6..a8b878d 100644 --- a/server.go +++ b/server.go @@ -31,6 +31,9 @@ func (cui *ConfigUI) middleware(next http.Handler) http.Handler { panic("permission denied") } } + if MutexLocked(&cui.configLock) { + panic(ErrorServerReloading) + } next.ServeHTTP(rw, r) }), ) @@ -122,7 +125,28 @@ func (cui *ConfigUI) mux() *http.ServeMux { }) cuiR.HandleFunc("/api/oni/stop", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { - cui.OniStart(w, r) + cui.OniStop(w, r) + } else { + w.WriteHeader(404) + } + }) + cuiR.HandleFunc("/api/oni/kill", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + cui.OniKill(w, r) + } else { + w.WriteHeader(404) + } + }) + cuiR.HandleFunc("/api/oni/log", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + cui.OniLog(w, r) + } else { + w.WriteHeader(404) + } + }) + cuiR.HandleFunc("/api/oni/attach", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + cui.OniAttach(w, r) } else { w.WriteHeader(404) } diff --git a/util.go b/util.go index 36cf0cc..4fe70b5 100644 --- a/util.go +++ b/util.go @@ -6,7 +6,9 @@ import ( "io" "os" "path/filepath" + "reflect" "strings" + "sync" ) func bundle(buf io.Writer, paths []string, rootDir string, flat bool) error { @@ -81,3 +83,10 @@ func mkdir(args ...interface{}) error { } return os.MkdirAll(path, mode) } + +const mutexLocked = 1 + +func MutexLocked(m *sync.Mutex) bool { + state := reflect.ValueOf(m).Elem().FieldByName("state") + return state.Int()&mutexLocked == mutexLocked +}