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

Reply via email to