The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6544
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === We created a package called subprocess, under the shared directory for process management to address the issue: #5826 Some new features * You can start a process and configure it in Go using 'NewProcess(..)' * You can manage this process using functions like `Pid(), Stop(), Start(), Restart(), Reload(), Save(path string), Signal(int64), Wait() (int64, error)` * You can now serialize and deserialize configurations for a process by calling 'Save()' and 'ImportProcess(..)'
From a688e7ccb1c3749a1caeae8f6d707c21f6ae7bb4 Mon Sep 17 00:00:00 2001 From: Rishabh Thakkar <rishabh.thak...@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.thak...@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.thak...@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.thak...@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)
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel