[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