Files
houston/client/operation.go

503 lines
12 KiB
Go
Raw Normal View History

2023-05-21 23:37:54 +09:00
package client
import (
"archive/zip"
2023-11-13 16:43:56 +09:00
"bufio"
2023-05-21 23:37:54 +09:00
"context"
2023-11-16 19:47:30 +09:00
"encoding/binary"
2023-05-21 23:37:54 +09:00
"encoding/json"
"errors"
"fmt"
"io"
2023-11-16 19:47:30 +09:00
"math"
2023-05-21 23:37:54 +09:00
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
2023-05-21 23:37:54 +09:00
"syscall"
"time"
2023-05-22 02:13:03 +09:00
2023-11-16 19:47:30 +09:00
"github.com/prometheus/client_golang/prometheus"
2023-06-14 00:13:51 +09:00
"repositories.action2quare.com/ayo/gocommon/logger"
2023-11-16 19:47:30 +09:00
"repositories.action2quare.com/ayo/gocommon/metric"
2023-05-22 02:13:03 +09:00
"repositories.action2quare.com/ayo/houston/shared"
"repositories.action2quare.com/ayo/houston/shared/protos"
2023-05-21 23:37:54 +09:00
)
func lastExecutionArgs(verpath string) []string {
argf, err := os.Open(path.Join(verpath, "@args"))
if os.IsNotExist(err) {
argf, err = os.Open(path.Clean(path.Join(verpath, "..", "@args")))
if os.IsNotExist(err) {
return nil
}
}
defer argf.Close()
var out []string
dec := json.NewDecoder(argf)
dec.Decode(&out)
return out
}
var errUploadZipLogFailed = errors.New("not ok")
func (hc *houstonClient) uploadZipLogFile(zipFile string, name string, version string) error {
zf, err := os.Open(zipFile)
if err != nil {
return err
}
if zf == nil {
return errUploadZipLogFailed
}
defer zf.Close()
2023-06-14 01:50:40 +09:00
req, err := http.NewRequest("POST", hc.config.HttpAddress+"/upload", zf)
if err != nil {
2023-06-14 00:13:51 +09:00
logger.Println(err)
}
req.Header.Set("Houston-Service-Name", name)
req.Header.Set("Houston-Service-Version", version)
req.Header.Set("Houston-Service-Filename", path.Base(zipFile))
req.Header.Set("Content-Type", "application/zip")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errUploadZipLogFailed
}
return nil
}
2023-06-14 14:16:47 +09:00
func zipLogFiles(storageRoot string, req *shared.UploadRequest, start, except string) (string, []string, error) {
root := path.Join(storageRoot, req.Name, req.Version)
2023-05-23 10:57:24 +09:00
matches, err := filepath.Glob(path.Join(root, req.Filter))
if err != nil {
return "", nil, err
}
if len(matches) == 0 {
return "", nil, nil
}
2023-05-28 21:21:37 +09:00
for i, file := range matches {
file = filepath.ToSlash(file)
matches[i] = file
}
2023-05-23 10:57:24 +09:00
root = path.Join(root, path.Dir(req.Filter))
2023-05-28 21:21:37 +09:00
zipFileName := path.Join(os.TempDir(), path.Base(matches[0])) + ".zip"
f, err := os.OpenFile(zipFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
2023-05-23 10:57:24 +09:00
if err != nil {
return "", nil, err
2023-05-21 23:37:54 +09:00
}
2023-05-23 10:57:24 +09:00
defer f.Close()
2023-05-21 23:37:54 +09:00
2023-05-23 10:57:24 +09:00
w := zip.NewWriter(f)
defer w.Close()
oldestFile := ""
for i, file := range matches {
2023-05-23 10:57:24 +09:00
if file == root {
continue
}
if len(except) > 0 && file >= except {
matches = matches[:i]
2023-05-23 10:57:24 +09:00
break
}
if len(start) > 0 && file < start {
continue
}
if len(oldestFile) == 0 {
oldestFile = path.Base(file)
2023-05-21 23:37:54 +09:00
}
2023-05-23 10:57:24 +09:00
relative := file[len(root)+1:]
fw, err := w.Create(relative)
if err != nil {
2023-06-14 00:13:51 +09:00
logger.Println(err)
2023-05-23 10:57:24 +09:00
return "", nil, err
2023-05-21 23:37:54 +09:00
}
2023-05-23 10:57:24 +09:00
src, err := os.Open(file)
if err != nil {
2023-06-14 00:13:51 +09:00
logger.Println(err)
2023-05-23 10:57:24 +09:00
return "", nil, err
2023-05-21 23:37:54 +09:00
}
2023-05-23 10:57:24 +09:00
defer src.Close()
2023-05-21 23:37:54 +09:00
2023-05-23 10:57:24 +09:00
if _, err = io.Copy(fw, src); err != nil {
2023-06-14 00:13:51 +09:00
logger.Println(err)
2023-05-23 10:57:24 +09:00
return "", nil, err
2023-05-21 23:37:54 +09:00
}
}
2023-05-23 10:57:24 +09:00
return f.Name(), matches, nil
2023-05-21 23:37:54 +09:00
}
2023-06-14 14:16:47 +09:00
func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) *procmeta {
2023-06-27 09:44:56 +09:00
if len(req.Args) == 0 {
return nil
}
2023-06-14 14:16:47 +09:00
verpath := path.Join(storageRoot, req.Name, req.Version)
2023-05-23 10:57:24 +09:00
fi, err := os.Stat(verpath)
if err == nil && fi.IsDir() {
2023-11-08 09:21:09 +09:00
exefile := "./" + path.Clean(strings.TrimPrefix(req.Args[0], "/"))
os.Chmod(path.Join(verpath, exefile), 0777)
2023-11-08 09:21:09 +09:00
exef, _ := os.Executable()
cmd := exec.Command(path.Join(path.Dir(exef), verpath, exefile), req.Args[1:]...)
2023-05-23 10:57:24 +09:00
cmd.Dir = verpath
stdin, _ := cmd.StdinPipe()
return &procmeta{
2023-11-13 16:43:56 +09:00
cmd: cmd,
name: req.Name,
args: req.Args,
version: req.Version,
state: protos.ProcessState_Stopped,
stdin: stdin,
2023-05-21 23:37:54 +09:00
}
}
2023-05-23 10:57:24 +09:00
return nil
2023-05-21 23:37:54 +09:00
}
func makeLogFilePrefix(meta *procmeta) string {
2023-11-13 16:43:56 +09:00
now := time.Now().UTC()
ext := path.Ext(meta.args[0])
nameonly := path.Base(meta.args[0])
if len(ext) > 0 {
nameonly = nameonly[:len(nameonly)-len(ext)]
}
ts := now.Format("2006-01-02T15-04-05")
return path.Join(meta.cmd.Dir, "logs", fmt.Sprintf("%s_%s", nameonly, ts))
2023-11-13 16:43:56 +09:00
}
2023-05-23 10:57:24 +09:00
func (hc *houstonClient) launch(meta *procmeta) error {
stdout, err := meta.cmd.StdoutPipe()
2023-05-21 23:37:54 +09:00
if err != nil {
return err
}
err = os.MkdirAll(path.Join(meta.cmd.Dir, "logs"), 0775)
2023-05-21 23:37:54 +09:00
if err != nil {
return err
}
2023-11-16 19:47:30 +09:00
stdReader := func(childProcName string, r io.ReadCloser) {
2023-05-21 23:37:54 +09:00
defer func() {
2023-11-13 16:43:56 +09:00
reco := recover()
if reco != nil {
logger.Println(reco)
2023-05-23 10:57:24 +09:00
}
2023-05-21 23:37:54 +09:00
}()
2023-11-13 16:43:56 +09:00
defer r.Close()
2023-05-23 10:57:24 +09:00
2023-11-13 16:43:56 +09:00
reader := bufio.NewReader(r)
thisFileSize := 0
logFileIndex := 0
2023-05-23 10:57:24 +09:00
logFileNamePrefix := makeLogFilePrefix(meta)
logFileName := fmt.Sprintf("%s_%d.log", logFileNamePrefix, logFileIndex)
2023-11-13 16:43:56 +09:00
targetFile, err := os.Create(logFileName)
if err != nil {
logger.Println("failed to create log file :", logFileName)
return
2023-05-21 23:37:54 +09:00
}
exef, _ := os.Executable()
linkePath := path.Join(path.Dir(exef), path.Dir(logFileName), meta.name+".log")
os.Remove(linkePath)
os.Symlink(path.Base(targetFile.Name()), linkePath)
2023-05-21 23:37:54 +09:00
defer func() {
2023-11-13 16:43:56 +09:00
if targetFile != nil {
targetFile.Close()
}
2023-05-21 23:37:54 +09:00
}()
2023-11-16 19:47:30 +09:00
readingMetric := false
var metricBuffer []byte
metricValues := make(map[string]metricValueAccessor)
2023-05-21 23:37:54 +09:00
for {
2023-11-13 16:43:56 +09:00
buff, err := reader.ReadBytes('\n')
2023-05-21 23:37:54 +09:00
if err != nil {
break
}
2023-11-13 16:43:56 +09:00
2023-11-16 19:47:30 +09:00
if readingMetric {
metricBuffer = append(metricBuffer, buff...)
} else if buff[0] == metric.METRIC_HEAD_INLINE {
readingMetric = true
metricBuffer = append(metricBuffer, buff[1:]...)
}
if readingMetric {
if metricBuffer[len(metricBuffer)-2] == metric.METRIC_TAIL_INLINE {
readingMetric = false
metricBuffer = metricBuffer[:len(metricBuffer)-2]
if metricBuffer[0] == '{' {
var metric metric.MetricDescription
json.Unmarshal(metricBuffer, &metric)
exporter := newExporterForPrometheus()
accessor := exporter.registMetric(childProcName, metric)
prometheus.MustRegister(exporter)
metricValues[metric.Key] = accessor
} else {
keybytes := metricBuffer[:8]
valbits := binary.BigEndian.Uint64(metricBuffer[8:])
val := math.Float64frombits(valbits)
if accessor, ok := metricValues[string(keybytes)]; ok {
accessor.set(val)
}
}
metricBuffer = metricBuffer[:0]
}
continue
}
2023-11-13 16:43:56 +09:00
for written := 0; written < len(buff); {
n, err := targetFile.Write(buff)
if err != nil {
logger.Println("write log file failed :", logFileName, err)
break
} else {
written += n
thisFileSize += n
}
}
if thisFileSize > 5*1024*1024 {
2023-11-13 16:43:56 +09:00
logFileIndex++
logFileName = fmt.Sprintf("%s_%d.log", logFileNamePrefix, logFileIndex)
2023-11-13 16:43:56 +09:00
nextTargetFile, err := os.Create(logFileName)
if err != nil {
logger.Println("failed to create log file :", logFileName)
} else {
targetFile.Close()
targetFile = nextTargetFile
os.Remove(linkePath)
os.Symlink(path.Base(targetFile.Name()), linkePath)
2023-11-13 16:43:56 +09:00
thisFileSize = 0
}
2023-05-23 10:57:24 +09:00
}
2023-05-21 23:37:54 +09:00
}
}
2023-11-16 19:47:30 +09:00
go stdReader(meta.name, stdout)
2023-05-21 23:37:54 +09:00
2023-11-08 09:21:09 +09:00
logger.Println("startChildProcess :", meta.cmd.Args)
2023-05-23 10:57:24 +09:00
err = meta.cmd.Start()
if err == nil {
meta.state = protos.ProcessState_Running
}
return err
2023-05-21 23:37:54 +09:00
}
var errPrepareprocessLaunchFailed = errors.New("prepareProcessLaunch failed")
2023-06-14 01:50:40 +09:00
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest, op protos.OperationClient) error {
2023-05-21 23:37:54 +09:00
if req.Version == "latest" {
// 최신 버전을 찾음
2023-06-14 14:16:47 +09:00
latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
2023-05-21 23:37:54 +09:00
if err != nil {
return err
}
req.Version = latest
}
2023-06-14 14:16:47 +09:00
meta := prepareProcessLaunch(hc.config.StorageRoot, req)
if meta == nil {
return errPrepareprocessLaunchFailed
}
2023-05-23 10:57:24 +09:00
if err := hc.launch(meta); err != nil {
return err
2023-05-21 23:37:54 +09:00
}
2023-05-23 10:57:24 +09:00
// launch가 성공하면 args 저장. this and parent folder
vers := hc.deploys[req.Name]
for _, ver := range vers {
if ver.Version == req.Version {
2023-11-13 16:43:56 +09:00
ver.Args = meta.args
}
}
2023-06-14 14:16:47 +09:00
if argfile, err := os.Create(path.Join(hc.config.StorageRoot, req.Name, "@args")); err == nil {
2023-05-23 10:57:24 +09:00
enc := json.NewEncoder(argfile)
2023-11-08 09:21:09 +09:00
enc.Encode(req.Args)
2023-05-23 10:57:24 +09:00
argfile.Close()
}
2023-06-14 14:16:47 +09:00
if argfile, err := os.Create(path.Join(hc.config.StorageRoot, req.Name, req.Version, "@args")); err == nil {
2023-05-23 10:57:24 +09:00
enc := json.NewEncoder(argfile)
2023-11-08 09:21:09 +09:00
enc.Encode(req.Args)
2023-05-23 10:57:24 +09:00
argfile.Close()
}
2023-05-21 23:37:54 +09:00
2023-05-23 10:57:24 +09:00
hc.childProcs = append(hc.childProcs, meta)
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
2023-05-21 23:37:54 +09:00
2023-05-23 10:57:24 +09:00
return nil
2023-05-21 23:37:54 +09:00
}
var errNoRunningProcess = errors.New("no running processed")
2023-06-14 01:50:40 +09:00
func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest, op protos.OperationClient) error {
2023-05-21 23:37:54 +09:00
if req.Version == "latest" {
// 최신 버전을 찾음
2023-06-14 14:16:47 +09:00
latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
2023-05-21 23:37:54 +09:00
if err != nil {
return err
}
req.Version = latest
}
var remains []*procmeta
var killing []*procmeta
for _, proc := range hc.childProcs {
if proc.state != protos.ProcessState_Running {
continue
}
if req.Pid != 0 {
if req.Pid == int32(proc.cmd.Process.Pid) {
// 해당 pid만 제거
killing = append(killing, proc)
} else {
remains = append(remains, proc)
}
} else if proc.name == req.Name {
if len(req.Version) == 0 {
// program 다 정지
killing = append(killing, proc)
} else if req.Version == proc.version {
// program의 특정 버전만 정지
killing = append(killing, proc)
} else {
// 해당 사항 없음
remains = append(remains, proc)
}
} else {
// 해당 사항 없음
remains = append(remains, proc)
}
}
if len(killing) > 0 {
for _, proc := range killing {
proc.state = protos.ProcessState_Stopping
2023-05-21 23:37:54 +09:00
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill)
}
}
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
for _, proc := range killing {
proc.cmd.Wait()
proc.cmd.Process.Release()
2023-05-21 23:37:54 +09:00
}
hc.childProcs = remains
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
return nil
}
return errNoRunningProcess
}
2023-06-14 01:50:40 +09:00
func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest, op protos.OperationClient) error {
2023-05-21 23:37:54 +09:00
for _, proc := range hc.childProcs {
2023-06-26 11:26:57 +09:00
if proc.cmd.Process.Pid == int(req.Pid) {
if len(req.Config) > 0 {
// config.json를 먼저 다운로드 시도
2023-06-27 11:55:24 +09:00
root := proc.cmd.Dir
2023-10-24 20:08:48 +09:00
if _, err := download(root, hc.makeDownloadUrl(req.Config), "", nil); err != nil {
2023-06-26 11:26:57 +09:00
return err
}
2023-05-21 23:37:54 +09:00
}
2023-06-27 09:44:56 +09:00
proc.state = protos.ProcessState_Restart
2023-06-26 11:26:57 +09:00
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
2023-06-27 11:17:16 +09:00
hc.exitChan <- proc.cmd
2023-05-21 23:37:54 +09:00
2023-06-26 11:26:57 +09:00
break
2023-05-21 23:37:54 +09:00
}
}
2023-06-26 11:26:57 +09:00
2023-05-21 23:37:54 +09:00
return nil
}
func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
if req.Version == "latest" {
// 최신 버전을 찾음
2023-06-14 14:16:47 +09:00
latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
2023-05-21 23:37:54 +09:00
if err != nil {
return err
}
req.Version = latest
}
2023-06-14 00:13:51 +09:00
logger.Println("uploadFiles req :", *req)
2023-05-23 10:57:24 +09:00
for _, child := range hc.childProcs {
if child.version == req.Version && child.name == req.Name {
2023-06-14 00:13:51 +09:00
logger.Println("uploadFiles found :", child.version, child.name)
2023-11-13 16:43:56 +09:00
go func() {
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, "", "")
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, child.name, child.version); err == nil {
// 마지막거 빼고 삭제
for i := 0; i < len(srcFiles)-1; i++ {
os.Remove(srcFiles[i])
}
} else {
logger.Println("uploadZipLogFile failed :", err)
}
} else if err != nil {
logger.Println("zipLogFiles failed :", err)
}
}()
return nil
}
}
// 실행 중이 아닌 폴더에서도 대상을 찾는다
// 전체 파일을 대상으로
2023-06-14 14:16:47 +09:00
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, "", "")
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err != nil {
2023-06-14 00:13:51 +09:00
logger.Println("uploadZipLogFile failed :", err)
2023-05-21 23:37:54 +09:00
}
} else if err != nil {
2023-06-14 00:13:51 +09:00
logger.Println("zipLogFiles failed :", err)
2023-05-21 23:37:54 +09:00
}
return nil
}