feature: Adds backend system and basic API.
This adds the backend API, as well as backend infrastructure, including autostarting and basic communication between the Goroutine + Application.
This commit is contained in:
parent
611d7f24f8
commit
0b73b4aa47
12 changed files with 777 additions and 44 deletions
237
backend/api/backendruntime/runtime.go
Normal file
237
backend/api/backendruntime/runtime.go
Normal file
|
@ -0,0 +1,237 @@
|
|||
package backendruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"git.terah.dev/imterah/hermes/backendlauncher"
|
||||
"git.terah.dev/imterah/hermes/commonbackend"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
var (
|
||||
AvailableBackends []*Backend
|
||||
RunningBackends map[uint]*Runtime
|
||||
TempDir string
|
||||
)
|
||||
|
||||
func init() {
|
||||
RunningBackends = make(map[uint]*Runtime)
|
||||
}
|
||||
|
||||
func handleCommand(commandType string, command interface{}, sock net.Conn, rtcChan chan interface{}) {
|
||||
bytes, err := commonbackend.Marshal(commandType, command)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to marshal message: %s", err.Error())
|
||||
rtcChan <- fmt.Errorf("failed to marshal message: %s", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := sock.Write(bytes); err != nil {
|
||||
log.Warnf("Failed to write message: %s", err.Error())
|
||||
rtcChan <- fmt.Errorf("failed to write message: %s", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, data, err := commonbackend.Unmarshal(sock)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to unmarshal message: %s", err.Error())
|
||||
rtcChan <- fmt.Errorf("failed to unmarshal message: %s", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rtcChan <- data
|
||||
}
|
||||
|
||||
func (runtime *Runtime) goRoutineHandler() error {
|
||||
log.Debug("Starting up backend runtime")
|
||||
log.Debug("Running socket acquisition")
|
||||
|
||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||
|
||||
sockPath, sockListener, err := backendlauncher.GetUnixSocket(TempDir)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtime.currentListener = sockListener
|
||||
|
||||
log.Debugf("Acquired unix socket at: %s", sockPath)
|
||||
|
||||
go func() {
|
||||
log.Debug("Creating new goroutine for socket connection handling")
|
||||
|
||||
for {
|
||||
log.Debug("Waiting for Unix socket connections...")
|
||||
sock, err := runtime.currentListener.Accept()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to accept Unix socket connection in a backend runtime instance: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("Recieved connection. Initializing...")
|
||||
|
||||
defer sock.Close()
|
||||
|
||||
for {
|
||||
commandRaw := <-runtime.RuntimeCommands
|
||||
|
||||
log.Debug("Got message from server")
|
||||
|
||||
switch command := commandRaw.(type) {
|
||||
case *commonbackend.AddProxy:
|
||||
handleCommand("addProxy", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.BackendStatusRequest:
|
||||
handleCommand("backendStatusRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.BackendStatusResponse:
|
||||
handleCommand("backendStatusResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.CheckClientParameters:
|
||||
handleCommand("checkClientParameters", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.CheckParametersResponse:
|
||||
handleCommand("checkParametersResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.CheckServerParameters:
|
||||
handleCommand("checkServerParameters", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyClientConnection:
|
||||
handleCommand("proxyClientConnection", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyConnectionsRequest:
|
||||
handleCommand("proxyConnectionsRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyConnectionsResponse:
|
||||
handleCommand("proxyConnectionsResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyInstanceResponse:
|
||||
handleCommand("proxyInstanceResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyInstanceRequest:
|
||||
handleCommand("proxyInstanceRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyStatusRequest:
|
||||
handleCommand("proxyStatusRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyStatusResponse:
|
||||
handleCommand("proxyStatusResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.RemoveProxy:
|
||||
handleCommand("removeProxy", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.Start:
|
||||
handleCommand("start", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.Stop:
|
||||
handleCommand("stop", command, sock, runtime.RuntimeCommands)
|
||||
default:
|
||||
log.Warnf("Recieved unknown command type from channel: %q", command)
|
||||
runtime.RuntimeCommands <- fmt.Errorf("unknown command recieved")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
log.Debug("Starting process...")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
runtime.currentProcess = exec.CommandContext(ctx, runtime.ProcessPath)
|
||||
runtime.currentProcess.Env = append(runtime.currentProcess.Env, fmt.Sprintf("HERMES_API_SOCK=%s", sockPath), fmt.Sprintf("HERMES_LOG_LEVEL=%s", logLevel))
|
||||
|
||||
runtime.currentProcess.Stdout = runtime.logger
|
||||
runtime.currentProcess.Stderr = runtime.logger
|
||||
|
||||
err := runtime.currentProcess.Run()
|
||||
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
if err.ExitCode() != -1 && err.ExitCode() != 0 {
|
||||
log.Warnf("A backend process died with exit code '%d' and with error '%s'", err.ExitCode(), err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Warnf("A backend process died with error: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Debug("Process exited gracefully.")
|
||||
}
|
||||
|
||||
if !runtime.isRuntimeRunning {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("Sleeping 5 seconds, and then restarting process")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Start() error {
|
||||
if runtime.isRuntimeRunning {
|
||||
return fmt.Errorf("runtime already running")
|
||||
}
|
||||
|
||||
runtime.RuntimeCommands = make(chan interface{})
|
||||
|
||||
runtime.logger = &writeLogger{
|
||||
Runtime: runtime,
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := runtime.goRoutineHandler()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed during execution of runtime: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
runtime.isRuntimeRunning = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Stop() error {
|
||||
if !runtime.isRuntimeRunning {
|
||||
return fmt.Errorf("runtime not running")
|
||||
}
|
||||
|
||||
runtime.isRuntimeRunning = false
|
||||
|
||||
if runtime.currentProcess != nil && runtime.currentProcess.Cancel != nil {
|
||||
err := runtime.currentProcess.Cancel()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Warn("Failed to kill process (Stop recieved), currentProcess or currentProcess.Cancel is nil")
|
||||
}
|
||||
|
||||
if runtime.currentListener != nil {
|
||||
err := runtime.currentListener.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop listener: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Warn("Failed to kill listener, as the listener is nil")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBackend(path string) *Runtime {
|
||||
return &Runtime{
|
||||
ProcessPath: path,
|
||||
}
|
||||
}
|
||||
|
||||
func Init(backends []*Backend) error {
|
||||
var err error
|
||||
TempDir, err = os.MkdirTemp("", "hermes-sockets-")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AvailableBackends = backends
|
||||
|
||||
return nil
|
||||
}
|
34
backend/api/backendruntime/struct.go
Normal file
34
backend/api/backendruntime/struct.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package backendruntime
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
Name string `validate:"required"`
|
||||
Path string `validate:"required"`
|
||||
}
|
||||
|
||||
type Runtime struct {
|
||||
isRuntimeRunning bool
|
||||
logger *writeLogger
|
||||
currentProcess *exec.Cmd
|
||||
currentListener net.Listener
|
||||
|
||||
ProcessPath string
|
||||
Logs []string
|
||||
RuntimeCommands chan interface{}
|
||||
}
|
||||
|
||||
type writeLogger struct {
|
||||
Runtime *Runtime
|
||||
}
|
||||
|
||||
func (writer writeLogger) Write(p []byte) (n int, err error) {
|
||||
logSplit := strings.Split(string(p), "\n")
|
||||
writer.Runtime.Logs = append(writer.Runtime.Logs, logSplit...)
|
||||
|
||||
return len(p), err
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue