Initial QSfera import
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package config
|
||||
|
||||
var config = map[string]string{
|
||||
"bin": "/usr/bin/qsfera",
|
||||
"url": "https://localhost:9200",
|
||||
"retry": "5",
|
||||
"adminUsername": "",
|
||||
"adminPassword": "",
|
||||
}
|
||||
|
||||
func Set(key string, value string) {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
func Get(key string) string {
|
||||
return config[key]
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package qsfera
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"ocwrapper/common"
|
||||
"ocwrapper/log"
|
||||
"ocwrapper/qsfera/config"
|
||||
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
var cmd *exec.Cmd
|
||||
var retryCount = 0
|
||||
var stopSignal = false
|
||||
var EnvConfigs = []string{}
|
||||
|
||||
func Start(envMap []string) {
|
||||
// wait for the log scanner to finish
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
stopSignal = false
|
||||
if retryCount == 0 {
|
||||
defer common.Wg.Done()
|
||||
}
|
||||
|
||||
cmd = exec.Command(config.Get("bin"), "server")
|
||||
if envMap == nil {
|
||||
cmd.Env = append(os.Environ(), EnvConfigs...)
|
||||
} else {
|
||||
cmd.Env = append(os.Environ(), envMap...)
|
||||
}
|
||||
|
||||
logs, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
output, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
logScanner := bufio.NewScanner(logs)
|
||||
outputScanner := bufio.NewScanner(output)
|
||||
outChan := make(chan string)
|
||||
|
||||
// Read the logs when the 'Qsfera server' command is running
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for logScanner.Scan() {
|
||||
outChan <- logScanner.Text()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for outputScanner.Scan() {
|
||||
outChan <- outputScanner.Text()
|
||||
}
|
||||
}()
|
||||
|
||||
// Fetch logs from the channel and print them
|
||||
go func() {
|
||||
for s := range outChan {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
status := exitErr.Sys().(syscall.WaitStatus)
|
||||
// retry only if qsfera server exited with code > 0
|
||||
// -1 exit code means that the process was killed by a signal (syscall.SIGINT)
|
||||
if status.ExitStatus() > 0 && !stopSignal {
|
||||
waitUntilCompleteShutdown()
|
||||
|
||||
log.Println(fmt.Sprintf("qsfera server exited with code %v", status.ExitStatus()))
|
||||
|
||||
// retry to start qsfera server
|
||||
retryCount++
|
||||
maxRetry, _ := strconv.Atoi(config.Get("retry"))
|
||||
if retryCount <= maxRetry {
|
||||
wg.Wait()
|
||||
close(outChan)
|
||||
log.Println(fmt.Sprintf("Retry starting qsfera server... (retry %v)", retryCount))
|
||||
// wait 500 milliseconds before retrying
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
Start(envMap)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
close(outChan)
|
||||
}
|
||||
|
||||
func Stop() (bool, string) {
|
||||
log.Println("Stopping Qsfera server...")
|
||||
stopSignal = true
|
||||
|
||||
if cmd == nil {
|
||||
return true, "Qsfera server is not running"
|
||||
}
|
||||
|
||||
err := cmd.Process.Signal(syscall.SIGINT)
|
||||
if err != nil {
|
||||
if !strings.HasSuffix(err.Error(), "process already finished") {
|
||||
log.Fatalln(err)
|
||||
} else {
|
||||
return true, "Qsfera server is already stopped"
|
||||
}
|
||||
}
|
||||
cmd.Process.Wait()
|
||||
success, message := waitUntilCompleteShutdown()
|
||||
|
||||
cmd = nil
|
||||
return success, message
|
||||
}
|
||||
|
||||
func Restart(envMap []string) (bool, string) {
|
||||
Stop()
|
||||
|
||||
log.Println("Restarting Qsfera server...")
|
||||
common.Wg.Add(1)
|
||||
go Start(envMap)
|
||||
|
||||
return WaitForConnection()
|
||||
}
|
||||
|
||||
func IsQsferaRunning() bool {
|
||||
if cmd != nil {
|
||||
return cmd.Process.Pid > 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func waitAllServices(startTime time.Time, timeout time.Duration) {
|
||||
timeoutS := timeout * time.Second
|
||||
|
||||
c := exec.Command(config.Get("bin"), "list")
|
||||
_, err := c.CombinedOutput()
|
||||
if err != nil {
|
||||
if time.Since(startTime) <= timeoutS {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
waitAllServices(startTime, timeout)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Println("All services are up")
|
||||
}
|
||||
|
||||
func WaitForConnection() (bool, string) {
|
||||
waitAllServices(time.Now(), 30)
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
// 30 seconds timeout
|
||||
timeoutValue := 30 * time.Second
|
||||
|
||||
client := http.Client{
|
||||
Timeout: timeoutValue,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
if config.Get("adminUsername") != "" && config.Get("adminPassword") != "" {
|
||||
req, _ = http.NewRequest("GET", config.Get("url")+"/graph/v1.0/me/drives", nil)
|
||||
req.SetBasicAuth(config.Get("adminUsername"), config.Get("adminPassword"))
|
||||
} else {
|
||||
req, _ = http.NewRequest("GET", config.Get("url")+"/ocs/v1.php/cloud/capabilities?format=json", nil)
|
||||
}
|
||||
|
||||
timeout := time.After(timeoutValue)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
log.Println(fmt.Sprintf("%v seconds timeout waiting for Qsfera server", int64(timeoutValue.Seconds())))
|
||||
return false, "Timeout waiting for Qsfera server to start"
|
||||
default:
|
||||
req.Header.Set("X-Request-ID", "ocwrapper-"+strconv.Itoa(int(time.Now().UnixMilli())))
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil || res.StatusCode != 200 {
|
||||
// 500 milliseconds poll interval
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Println("Qsfera server is ready to accept requests")
|
||||
return true, "Qsfera server is up and running"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilCompleteShutdown() (bool, string) {
|
||||
timeout := 30 * time.Second
|
||||
startTime := time.Now()
|
||||
|
||||
c := exec.Command("sh", "-c", "ps ax | grep 'qsfera server' | grep -v grep | awk '{print $1}'")
|
||||
output, err := c.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
for strings.TrimSpace(string(output)) != "" {
|
||||
output, _ = c.CombinedOutput()
|
||||
log.Println("Process found. Waiting...")
|
||||
|
||||
if time.Since(startTime) >= timeout {
|
||||
log.Println(fmt.Sprintf("Unable to kill Qsfera server after %v seconds", int64(timeout.Seconds())))
|
||||
return false, "Timeout waiting for Qsfera server to stop"
|
||||
}
|
||||
}
|
||||
return true, "Qsfera server stopped successfully"
|
||||
}
|
||||
|
||||
func RunCommand(command string, inputs []string) (int, string) {
|
||||
logs := new(strings.Builder)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// build the command
|
||||
cmdArgs := strings.Split(command, " ")
|
||||
c := exec.CommandContext(ctx, config.Get("bin"), cmdArgs...)
|
||||
|
||||
// Start the command with a pty (pseudo terminal)
|
||||
// This is required to interact with the command
|
||||
ptyF, err := pty.Start(c)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer ptyF.Close()
|
||||
|
||||
for _, input := range inputs {
|
||||
fmt.Fprintf(ptyF, "%s\n", input)
|
||||
}
|
||||
|
||||
var cmdOutput string
|
||||
if err := c.Wait(); err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
cmdOutput = "Command timed out:\n"
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the logs from the pty
|
||||
io.Copy(logs, ptyF)
|
||||
cmdOutput += logs.String()
|
||||
|
||||
// TODO: find if there is a better way to remove stdins from the output
|
||||
cmdOutput = strings.TrimLeft(cmdOutput, strings.Join(inputs, "\r\n"))
|
||||
|
||||
return c.ProcessState.ExitCode(), cmdOutput
|
||||
}
|
||||
|
||||
func RunRawCommand(command string, inputs []string) (int, string) {
|
||||
logs := new(strings.Builder)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fmt.Print("Running command: ", command)
|
||||
|
||||
c := exec.CommandContext(ctx, "bash", "-c", command)
|
||||
|
||||
ptyF, err := pty.Start(c)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer ptyF.Close()
|
||||
|
||||
for _, input := range inputs {
|
||||
fmt.Fprintf(ptyF, "%s\n", input)
|
||||
}
|
||||
|
||||
var cmdOutput string
|
||||
if err := c.Wait(); err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
cmdOutput = "Command timed out:\n"
|
||||
}
|
||||
}
|
||||
|
||||
io.Copy(logs, ptyF)
|
||||
cmdOutput += logs.String()
|
||||
cmdOutput = strings.TrimLeft(cmdOutput, strings.Join(inputs, "\r\n"))
|
||||
|
||||
return c.ProcessState.ExitCode(), cmdOutput
|
||||
}
|
||||
Reference in New Issue
Block a user