[lxc-devel] [lxd/master] Implementing Background Process Manager
abbykrish on Github
lxc-bot at linuxcontainers.org
Tue Dec 3 23:08:51 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 787 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191203/35980426/attachment-0001.bin>
-------------- next part --------------
From a688e7ccb1c3749a1caeae8f6d707c21f6ae7bb4 Mon Sep 17 00:00:00 2001
From: Rishabh Thakkar <rishabh.thakkar at gmail.com>
Date: Thu, 21 Nov 2019 18:35:51 -0600
Subject: [PATCH 1/2] shared: Created package for background process management
Signed-off-by: ribsthakkar <rishabh.thakkar at gmail.com>
---
shared/bgpm/manager.go | 32 ++++++++++
shared/bgpm/proc.go | 136 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 168 insertions(+)
create mode 100644 shared/bgpm/manager.go
create mode 100644 shared/bgpm/proc.go
diff --git a/shared/bgpm/manager.go b/shared/bgpm/manager.go
new file mode 100644
index 0000000000..a4f12ce7e8
--- /dev/null
+++ b/shared/bgpm/manager.go
@@ -0,0 +1,32 @@
+package bgpm
+
+import (
+ "fmt"
+ "gopkg.in/yaml.v2"
+ "io/ioutil"
+)
+
+func NewProcess(name string, args []string, stdin string, stdout string, stderr string) (*Process, error) {
+ proc := Process{
+ Name: name,
+ Args: args,
+ Stdin: stdin,
+ Stdout: stdout,
+ Stderr: stderr,
+ }
+ return &proc, nil
+}
+
+func ImportProcess(path string) (*Process, error) {
+ dat, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to read file %s: %s", path, err)
+ }
+ proc := Process{}
+
+ err = yaml.Unmarshal(dat, &proc)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse Process YAML: %s", err)
+ }
+ return &proc, nil
+}
diff --git a/shared/bgpm/proc.go b/shared/bgpm/proc.go
new file mode 100644
index 0000000000..db025907e3
--- /dev/null
+++ b/shared/bgpm/proc.go
@@ -0,0 +1,136 @@
+package bgpm
+
+import (
+ "fmt"
+ "gopkg.in/yaml.v2"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strings"
+ "syscall"
+)
+
+type Process struct {
+ pid int64
+ Name string `yaml:"name"`
+ Args []string `yaml:"args,flow"`
+ Stdin string `yaml:"stdin"`
+ Stdout string `yaml:"stdout"`
+ Stderr string `yaml:"stderr"`
+}
+
+func (p *Process) Pid() (int64, error) {
+ return p.pid, nil
+}
+func (p *Process) Stop() error {
+ pr, _ := os.FindProcess(int(p.pid))
+ err := pr.Signal(syscall.Signal(0))
+ if err == nil {
+ err = pr.Kill()
+ if err != nil {
+ return fmt.Errorf("Could not kill process: %s", err)
+ }
+ return nil
+ } else if err == syscall.ESRCH { //ESRCH error is if process could not be found
+ return fmt.Errorf("Process is not running. Could not kill process")
+ }
+ return fmt.Errorf("Could not kill process: %s", err)
+}
+func (p *Process) Start() error {
+ args := strings.Join(p.Args, " ")
+ cmd := exec.Command(p.Name, args)
+
+ if p.Stdout != "" {
+ out, err := os.Create(p.Stdout)
+ if err != nil {
+ return fmt.Errorf("Unable to open stdout file: %s", err)
+ }
+ defer out.Close()
+ cmd.Stdout = out
+ }
+ if p.Stdin != "" {
+ in, err := os.Open(p.Stdin)
+ if err != nil {
+ return fmt.Errorf("Unable to open stdin file: %s", err)
+ }
+ defer in.Close()
+ cmd.Stdin = in
+ }
+ if p.Stderr != "" {
+ sterr, err := os.Create(p.Stderr)
+ if err != nil {
+ return fmt.Errorf("Unable to open stderr file: %s", err)
+ }
+ defer sterr.Close()
+ cmd.Stderr = sterr
+ }
+ err := cmd.Start()
+ if err != nil {
+ return fmt.Errorf("Unable to start process: ", err)
+ }
+ p.pid = int64(cmd.Process.Pid)
+ return nil
+}
+func (p *Process) Restart() error {
+ err := p.Stop()
+ if err != nil {
+ return fmt.Errorf("Unable to stop process: %s", err)
+ }
+ err = p.Start()
+ if err != nil {
+ return fmt.Errorf("Unable to start process: %s", err)
+ }
+ return nil
+}
+func (p *Process) Reload() error {
+ pr, _ := os.FindProcess(int(p.pid))
+ err := pr.Signal(syscall.Signal(0))
+ if err == nil {
+ err = pr.Signal(syscall.SIGHUP)
+ if err != nil {
+ return fmt.Errorf("Could not reload process: %s", err)
+ }
+ return nil
+ } else if err == syscall.ESRCH { //ESRCH error is if process could not be found
+ return fmt.Errorf("Process is not running. Could not reload process")
+ }
+ return fmt.Errorf("Could not reload process: %s", err)
+}
+func (p *Process) Save(path string) error {
+ dat, err := yaml.Marshal(p)
+ if err != nil {
+ return fmt.Errorf("Unable to serialize process struct to YAML: %s", err)
+ }
+ err = ioutil.WriteFile(path, dat, 0644)
+ if err != nil {
+ return fmt.Errorf("Unable to write to file %s: %s", path, err)
+ }
+ return nil
+}
+func (p *Process) Signal(signal int64) error {
+ pr, _ := os.FindProcess(int(p.pid))
+ err := pr.Signal(syscall.Signal(0))
+ if err == nil {
+ err = pr.Signal(syscall.Signal(signal))
+ if err != nil {
+ return fmt.Errorf("Could not signal process: %s", err)
+ }
+ return nil
+ } else if err == syscall.ESRCH { //ESRCH error is if process could not be found
+ return fmt.Errorf("Process is not running. Could not signal process")
+ }
+ return fmt.Errorf("Could not signal process: %s", err)
+}
+func (p *Process) Wait() (int64, error) {
+ pr, _ := os.FindProcess(int(p.pid))
+ err := pr.Signal(syscall.Signal(0))
+ if err == nil {
+ procstate, err := pr.Wait()
+ if err != nil {
+ return -1, fmt.Errorf("Could not wait on process: %s", err)
+ }
+ exitcode := int64(procstate.Sys().(syscall.WaitStatus).ExitStatus())
+ return exitcode, nil
+ }
+ return 0, fmt.Errorf("Process is not running. Could not wait")
+}
From 77cab09945bb44eba926b4dc53c010337daf57d7 Mon Sep 17 00:00:00 2001
From: Rishabh Thakkar <rishabh.thakkar at gmail.com>
Date: Sat, 30 Nov 2019 01:28:13 -0600
Subject: [PATCH 2/2] shared: test: Added testing for background process
manager
Signed-off-by: Rishabh Thakkar <rishabh.thakkar at gmail.com>
---
shared/subprocess/bgpm_test.go | 154 ++++++++++++++++++++
shared/{bgpm => subprocess}/manager.go | 2 +-
shared/{bgpm => subprocess}/proc.go | 4 +-
shared/subprocess/testscript/exit1.py | 6 +
shared/subprocess/testscript/signal.py | 15 ++
shared/subprocess/testscript/stoprestart.py | 5 +
6 files changed, 183 insertions(+), 3 deletions(-)
create mode 100644 shared/subprocess/bgpm_test.go
rename shared/{bgpm => subprocess}/manager.go (97%)
rename shared/{bgpm => subprocess}/proc.go (97%)
create mode 100644 shared/subprocess/testscript/exit1.py
create mode 100644 shared/subprocess/testscript/signal.py
create mode 100644 shared/subprocess/testscript/stoprestart.py
diff --git a/shared/subprocess/bgpm_test.go b/shared/subprocess/bgpm_test.go
new file mode 100644
index 0000000000..08e27880cf
--- /dev/null
+++ b/shared/subprocess/bgpm_test.go
@@ -0,0 +1,154 @@
+package subprocess
+
+import (
+ "io"
+ "os"
+ "testing"
+ "time"
+ "strings"
+)
+func TestSignalHandling(t *testing.T) {
+ var a []string
+ a = append(a, "testscript/signal.py")
+ var file *os.File
+ p, err := NewProcess("python", a, "", "testscript/signal_out.txt", "")
+ if err != nil {
+ t.Error("Failed process creation: ", err)
+ }
+ err = p.Start()
+ if err != nil {
+ t.Error("Failed to start process ", err)
+ }
+ time.Sleep(2 * time.Second)
+ p.Reload()
+ time.Sleep(2 * time.Second)
+ p.Signal(10)
+ ecode, err := p.Wait()
+ if err != nil {
+ t.Error("Failed to get exit code ", err)
+ } else if ecode != 1 {
+ t.Error("Exit code is not 1: ", ecode)
+ }
+ file, err = os.OpenFile("testscript/signal_out.txt", os.O_RDWR, 0644)
+ if err != nil {
+ t.Error("Could not open file ", err)
+ }
+ defer file.Close()
+
+ var text = make([]byte, 1024)
+ for {
+ _, err = file.Read(text)
+ // Break if finally arrived at end of file
+ if err == io.EOF {
+ break
+ }
+ // Break if error occured
+ if err != nil && err != io.EOF {
+ t.Error("Error in reading file ", err)
+ }
+ }
+ if !strings.Contains(string(text), "Called with signal 1"){
+ t.Errorf("Reload failed. File output mismatch. Got %s", string(text))
+ }
+
+ if !strings.Contains(string(text), "Called with signal 10"){
+ t.Errorf("Signal failed. File output mismatch. Got %s", string(text))
+ }
+ err = os.Remove("testscript/signal_out.txt")
+ if err != nil {
+ t.Error("Could not delete file ", err)
+ }
+}
+
+//tests newprocess, start, stop, save, import, restart, wait
+func TestStopRestart(t *testing.T) {
+ var a []string
+ a = append(a, "testscript/stoprestart.py")
+ p, err := NewProcess("python", a, "", "", "")
+ if err != nil {
+ t.Error("Failed process creation: ", err)
+ }
+ err = p.Start()
+ if err != nil {
+ t.Error("Failed to start process: ", err)
+ }
+ err = p.Stop()
+ if err != nil {
+ t.Error("Failed to stop process: ", err)
+ }
+ err = p.Save("testscript/test2.yaml")
+ if err != nil {
+ t.Error("Failed to save process: ", err)
+ }
+ p, err = ImportProcess("testscript/test2.yaml")
+ if err != nil {
+ t.Error("Failed to import process: ", err)
+ }
+ err = p.Start()
+ if err != nil {
+ t.Error("Failed to start process: ", err)
+ }
+ err = p.Restart()
+ if err != nil {
+ t.Error("Failed to restart process: ", err)
+ }
+ exitcode, err := p.Wait()
+ if err != nil {
+ t.Error("Could not wait for process: ", err)
+ } else if exitcode != 0 {
+ t.Error("Exit code expected to be 0")
+ }
+ err = os.Remove("testscript/test2.yaml")
+ if err != nil {
+ t.Error("Could not delete file: ", err)
+ }
+}
+
+func TestProcessStartWaitExit(t *testing.T) {
+ var a []string
+ var file *os.File
+ var exp string
+ var text []byte
+ a = append(a, "testscript/exit1.py")
+ p, err := NewProcess("python", a, "", "testscript/out.txt", "")
+ if err != nil {
+ t.Error("Failed process creation: ", err)
+ }
+ err = p.Start()
+ if err != nil {
+ t.Error("Failed to start process: ", err)
+ }
+ ecode, err := p.Wait()
+ if err != nil {
+ t.Error("Failed to get exit code: ", err)
+ } else if ecode != 1 {
+ t.Error("Exit code is not 1: ", ecode)
+ }
+ file, err = os.OpenFile("testscript/out.txt", os.O_RDWR, 0644)
+ if err != nil {
+ t.Error("Could not open file: ", err)
+ }
+ defer file.Close()
+ exp = "hello again\nwaiting now\n"
+ // Read file, line by line
+ text = make([]byte, len(exp))
+ for {
+ _, err = file.Read(text)
+ // Break if finally arrived at end of file
+ if err == io.EOF {
+ break
+ }
+ // Break if error occured
+ if err != nil && err != io.EOF {
+ t.Error("Error reading file: ", err)
+ }
+ }
+ if string(text) != exp {
+ t.Errorf("File output mismatch Expected %s got %s", "hello again\nwaiting now\n", string(text))
+ }
+ // Cleanup
+ err = os.Remove("testscript/out.txt")
+ if err != nil {
+ t.Error("Could not delete file: ", err)
+ }
+}
diff --git a/shared/bgpm/manager.go b/shared/subprocess/manager.go
similarity index 97%
rename from shared/bgpm/manager.go
rename to shared/subprocess/manager.go
index a4f12ce7e8..624c1cc1ce 100644
--- a/shared/bgpm/manager.go
+++ b/shared/subprocess/manager.go
@@ -1,4 +1,4 @@
-package bgpm
+package subprocess
import (
"fmt"
diff --git a/shared/bgpm/proc.go b/shared/subprocess/proc.go
similarity index 97%
rename from shared/bgpm/proc.go
rename to shared/subprocess/proc.go
index db025907e3..675b4096a9 100644
--- a/shared/bgpm/proc.go
+++ b/shared/subprocess/proc.go
@@ -1,4 +1,4 @@
-package bgpm
+package subprocess
import (
"fmt"
@@ -66,7 +66,7 @@ func (p *Process) Start() error {
}
err := cmd.Start()
if err != nil {
- return fmt.Errorf("Unable to start process: ", err)
+ return fmt.Errorf("Unable to start process: %s", err)
}
p.pid = int64(cmd.Process.Pid)
return nil
diff --git a/shared/subprocess/testscript/exit1.py b/shared/subprocess/testscript/exit1.py
new file mode 100644
index 0000000000..d1636e2f32
--- /dev/null
+++ b/shared/subprocess/testscript/exit1.py
@@ -0,0 +1,6 @@
+from time import sleep
+
+print('hello again')
+print('waiting now')
+sleep(4)
+exit(1)
\ No newline at end of file
diff --git a/shared/subprocess/testscript/signal.py b/shared/subprocess/testscript/signal.py
new file mode 100644
index 0000000000..274c9a8f4e
--- /dev/null
+++ b/shared/subprocess/testscript/signal.py
@@ -0,0 +1,15 @@
+import signal, os
+from time import sleep
+
+def handler(signum, frame):
+ print ("Called with signal %d"%signum)
+ return
+
+
+
+signal.signal(signal.SIGHUP, handler)
+signal.signal(10, handler)
+
+signal.pause()
+signal.pause()
+exit(1)
\ No newline at end of file
diff --git a/shared/subprocess/testscript/stoprestart.py b/shared/subprocess/testscript/stoprestart.py
new file mode 100644
index 0000000000..e2ba493aa1
--- /dev/null
+++ b/shared/subprocess/testscript/stoprestart.py
@@ -0,0 +1,5 @@
+from time import sleep
+
+print('hello again')
+print('waiting now')
+sleep(5)
More information about the lxc-devel
mailing list