github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/process_tracker/process_tracker_test.go (about)

     1  package process_tracker_test
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"path/filepath"
    13  
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  	"github.com/onsi/gomega/gbytes"
    17  	"github.com/pivotal-golang/lager/lagertest"
    18  
    19  	"github.com/cloudfoundry-incubator/garden"
    20  	"github.com/cloudfoundry-incubator/garden-linux/process_tracker"
    21  	"github.com/cloudfoundry/gunk/command_runner/linux_command_runner"
    22  )
    23  
    24  var _ = Describe("Process tracker", func() {
    25  	var (
    26  		testLogger     *lagertest.TestLogger
    27  		processTracker process_tracker.ProcessTracker
    28  		tmpdir         string
    29  		signaller      process_tracker.Signaller
    30  	)
    31  
    32  	BeforeEach(func() {
    33  		var err error
    34  
    35  		testLogger = lagertest.NewTestLogger("process-tracker-tests")
    36  
    37  		tmpdir, err = ioutil.TempDir("", "process-tracker-tests")
    38  		Expect(err).ToNot(HaveOccurred())
    39  
    40  		err = os.MkdirAll(filepath.Join(tmpdir, "bin"), 0755)
    41  		Expect(err).ToNot(HaveOccurred())
    42  
    43  		err = copyFile(iodaemonBin, filepath.Join(tmpdir, "bin", "iodaemon"))
    44  		Expect(err).ToNot(HaveOccurred())
    45  
    46  		signaller = &process_tracker.LinkSignaller{}
    47  
    48  		processTracker = process_tracker.New(testLogger, tmpdir, linux_command_runner.New())
    49  	})
    50  
    51  	AfterEach(func() {
    52  		os.RemoveAll(tmpdir)
    53  	})
    54  
    55  	Describe("Running processes", func() {
    56  		It("runs the process and returns its exit code", func() {
    57  			cmd := exec.Command("bash", "-c", "exit 42")
    58  
    59  			process, err := processTracker.Run("555", cmd, garden.ProcessIO{}, nil, signaller)
    60  			Expect(err).NotTo(HaveOccurred())
    61  
    62  			status, err := process.Wait()
    63  			Expect(err).ToNot(HaveOccurred())
    64  			Expect(status).To(Equal(42))
    65  		})
    66  
    67  		It("logs its progress to the given logger", func() {
    68  
    69  			cmd := exec.Command("bash", "-c", "exit 42")
    70  
    71  			process, err := processTracker.Run("555", cmd, garden.ProcessIO{}, nil, signaller)
    72  			Expect(err).NotTo(HaveOccurred())
    73  
    74  			Eventually(testLogger).Should(gbytes.Say("run-spawning"))
    75  
    76  			status, err := process.Wait()
    77  			Expect(err).ToNot(HaveOccurred())
    78  			Expect(status).To(Equal(42))
    79  
    80  		})
    81  
    82  		Describe("signalling a running process", func() {
    83  			var (
    84  				process garden.Process
    85  				stdout  *gbytes.Buffer
    86  				cmd     *exec.Cmd
    87  			)
    88  
    89  			JustBeforeEach(func() {
    90  				var err error
    91  				cmd = exec.Command(testPrintBin)
    92  
    93  				stdout = gbytes.NewBuffer()
    94  				process, err = processTracker.Run(
    95  					"2", cmd,
    96  					garden.ProcessIO{
    97  						Stdout: io.MultiWriter(stdout, GinkgoWriter),
    98  						Stderr: GinkgoWriter,
    99  					}, nil, signaller)
   100  				Expect(err).NotTo(HaveOccurred())
   101  
   102  				Eventually(stdout).Should(gbytes.Say("pid"))
   103  			})
   104  
   105  			AfterEach(func() {
   106  				if cmd.ProcessState != nil && !cmd.ProcessState.Exited() {
   107  					cmd.Process.Signal(os.Kill)
   108  				}
   109  			})
   110  
   111  			Context("when the signaller is an LinkSignaller", func() {
   112  				It("sends a kill message on the extra file descriptor", func(done Done) {
   113  					Expect(process.Signal(garden.SignalKill)).To(Succeed())
   114  					Eventually(stdout).Should(gbytes.Say("Received: killed"))
   115  					close(done)
   116  				}, 2.0)
   117  
   118  				It("kills the process with a terminate signal", func(done Done) {
   119  					Expect(process.Signal(garden.SignalTerminate)).To(Succeed())
   120  					Eventually(stdout).Should(gbytes.Say("Received: terminated"))
   121  					close(done)
   122  				}, 2.0)
   123  
   124  				Context("when an unsupported signal is sent", func() {
   125  					AfterEach(func() {
   126  						Expect(process.Signal(garden.SignalKill)).To(Succeed())
   127  					})
   128  
   129  					It("return error", func() {
   130  						Expect(process.Signal(garden.Signal(999))).To(MatchError(HaveSuffix("failed to send signal: unknown signal: 999")))
   131  					})
   132  				})
   133  			})
   134  		})
   135  
   136  		It("streams the process's stdout and stderr", func() {
   137  			cmd := exec.Command(
   138  				"/bin/bash",
   139  				"-c",
   140  				"echo 'hi out' && echo 'hi err' >&2",
   141  			)
   142  
   143  			stdout := gbytes.NewBuffer()
   144  			stderr := gbytes.NewBuffer()
   145  
   146  			_, err := processTracker.Run("40", cmd, garden.ProcessIO{
   147  				Stdout: stdout,
   148  				Stderr: stderr,
   149  			}, nil, signaller)
   150  			Expect(err).NotTo(HaveOccurred())
   151  
   152  			Eventually(stdout).Should(gbytes.Say("hi out\n"))
   153  			Eventually(stderr).Should(gbytes.Say("hi err\n"))
   154  		})
   155  
   156  		It("streams input to the process", func() {
   157  			stdout := gbytes.NewBuffer()
   158  
   159  			_, err := processTracker.Run("50", exec.Command("cat"), garden.ProcessIO{
   160  				Stdin:  bytes.NewBufferString("stdin-line1\nstdin-line2\n"),
   161  				Stdout: stdout,
   162  			}, nil, signaller)
   163  			Expect(err).NotTo(HaveOccurred())
   164  
   165  			Eventually(stdout).Should(gbytes.Say("stdin-line1\nstdin-line2\n"))
   166  		})
   167  
   168  		Context("when there is an error reading the stdin stream", func() {
   169  			It("does not close the process's stdin", func() {
   170  				pipeR, pipeW := io.Pipe()
   171  				stdout := gbytes.NewBuffer()
   172  
   173  				process, err := processTracker.Run("60", exec.Command("cat"), garden.ProcessIO{
   174  					Stdin:  pipeR,
   175  					Stdout: stdout,
   176  				}, nil, signaller)
   177  				Expect(err).NotTo(HaveOccurred())
   178  
   179  				pipeW.Write([]byte("Hello stdin!"))
   180  				Eventually(stdout).Should(gbytes.Say("Hello stdin!"))
   181  
   182  				pipeW.CloseWithError(errors.New("Failed"))
   183  				Consistently(stdout, 0.1).ShouldNot(gbytes.Say("."))
   184  
   185  				pipeR, pipeW = io.Pipe()
   186  				processTracker.Attach(process.ID(), garden.ProcessIO{
   187  					Stdin: pipeR,
   188  				})
   189  
   190  				pipeW.Write([]byte("Hello again, stdin!"))
   191  				Eventually(stdout).Should(gbytes.Say("Hello again, stdin!"))
   192  
   193  				pipeW.Close()
   194  				exitStatus, err := process.Wait()
   195  				Expect(err).ToNot(HaveOccurred())
   196  				Expect(exitStatus).To(Equal(0))
   197  			})
   198  
   199  			It("supports attaching more than once", func() {
   200  				pipeR, pipeW := io.Pipe()
   201  				stdout := gbytes.NewBuffer()
   202  
   203  				process, err := processTracker.Run("70", exec.Command("cat"), garden.ProcessIO{
   204  					Stdin:  pipeR,
   205  					Stdout: stdout,
   206  				}, nil, signaller)
   207  				Expect(err).NotTo(HaveOccurred())
   208  
   209  				pipeW.Write([]byte("Hello stdin!"))
   210  				Eventually(stdout).Should(gbytes.Say("Hello stdin!"))
   211  
   212  				pipeW.CloseWithError(errors.New("Failed"))
   213  				Consistently(stdout, 0.1).ShouldNot(gbytes.Say("."))
   214  
   215  				pipeR, pipeW = io.Pipe()
   216  				_, err = processTracker.Attach(process.ID(), garden.ProcessIO{
   217  					Stdin: pipeR,
   218  				})
   219  				Expect(err).ToNot(HaveOccurred())
   220  
   221  				pipeW.Write([]byte("Hello again, stdin!"))
   222  				Eventually(stdout).Should(gbytes.Say("Hello again, stdin!"))
   223  
   224  				pipeR, pipeW = io.Pipe()
   225  
   226  				_, err = processTracker.Attach(process.ID(), garden.ProcessIO{
   227  					Stdin: pipeR,
   228  				})
   229  				Expect(err).ToNot(HaveOccurred())
   230  
   231  				pipeW.Write([]byte("Hello again again, stdin!"))
   232  				Eventually(stdout, "1s").Should(gbytes.Say("Hello again again, stdin!"))
   233  
   234  				pipeW.Close()
   235  				Expect(process.Wait()).To(Equal(0))
   236  			})
   237  		})
   238  
   239  		Context("with a tty", func() {
   240  			It("forwards TTY signals to the process", func() {
   241  				cmd := exec.Command("/bin/bash", "-c", `
   242  				trap "stty size; exit 123" SIGWINCH
   243  				stty size
   244  				read
   245  			`)
   246  
   247  				stdout := gbytes.NewBuffer()
   248  
   249  				process, err := processTracker.Run("90", cmd, garden.ProcessIO{
   250  					Stdout: stdout,
   251  				}, &garden.TTYSpec{
   252  					WindowSize: &garden.WindowSize{
   253  						Columns: 95,
   254  						Rows:    13,
   255  					},
   256  				}, signaller)
   257  				Expect(err).NotTo(HaveOccurred())
   258  
   259  				Eventually(stdout).Should(gbytes.Say("13 95"))
   260  
   261  				process.SetTTY(garden.TTYSpec{
   262  					WindowSize: &garden.WindowSize{
   263  						Columns: 101,
   264  						Rows:    27,
   265  					},
   266  				})
   267  
   268  				Eventually(stdout).Should(gbytes.Say("27 101"))
   269  				Expect(process.Wait()).To(Equal(123))
   270  			})
   271  
   272  			Describe("when a window size is not specified", func() {
   273  				It("picks a default window size", func() {
   274  					cmd := exec.Command("/bin/bash", "-c", `
   275  					stty size
   276  				`)
   277  
   278  					stdout := gbytes.NewBuffer()
   279  
   280  					_, err := processTracker.Run("100", cmd, garden.ProcessIO{
   281  						Stdout: stdout,
   282  					}, &garden.TTYSpec{}, signaller)
   283  					Expect(err).NotTo(HaveOccurred())
   284  
   285  					Eventually(stdout).Should(gbytes.Say("24 80"))
   286  				})
   287  			})
   288  		})
   289  
   290  		Context("when spawning fails", func() {
   291  			Context("because the binary doesn't exist", func() {
   292  				It("returns the error", func() {
   293  					_, err := processTracker.Run("200", exec.Command("/bin/does-not-exist"), garden.ProcessIO{}, nil, signaller)
   294  					Expect(err).To(MatchError(ContainSubstring("executable /bin/does-not-exist not found")))
   295  				})
   296  			})
   297  
   298  			Context("because the binary is corrupted", func() {
   299  				var (
   300  					corruptedBinary string
   301  					tmpDir          string
   302  				)
   303  
   304  				BeforeEach(func() {
   305  					var err error
   306  					tmpDir, err = ioutil.TempDir("", "")
   307  					Expect(err).NotTo(HaveOccurred())
   308  
   309  					corruptedBinary = path.Join(tmpDir, "corrupted")
   310  					Expect(ioutil.WriteFile(corruptedBinary, []byte("fail-to-exec"), 0755)).To(Succeed())
   311  				})
   312  
   313  				AfterEach(func() {
   314  					Expect(os.RemoveAll(tmpDir)).To(Succeed())
   315  				})
   316  
   317  				It("returns the error", func() {
   318  					_, err := processTracker.Run("200", exec.Command(corruptedBinary), garden.ProcessIO{}, nil, signaller)
   319  					Expect(err).To(MatchError(ContainSubstring(fmt.Sprintf("executable %s failed to start", corruptedBinary))))
   320  				})
   321  			})
   322  		})
   323  	})
   324  
   325  	Describe("Restoring processes", func() {
   326  		It("tracks the restored process", func() {
   327  			processTracker.Restore("2", signaller)
   328  
   329  			activeProcesses := processTracker.ActiveProcesses()
   330  			Expect(activeProcesses).To(HaveLen(1))
   331  			Expect(activeProcesses[0].ID()).To(Equal("2"))
   332  		})
   333  	})
   334  
   335  	Describe("Attaching to running processes", func() {
   336  		It("streams stdout, stdin, and stderr", func() {
   337  			cmd := exec.Command("bash", "-c", `
   338  			stuff=$(cat)
   339  			echo "hi stdout" $stuff
   340  			echo "hi stderr" $stuff >&2
   341  		`)
   342  
   343  			process, err := processTracker.Run("855", cmd, garden.ProcessIO{}, nil, signaller)
   344  			Expect(err).NotTo(HaveOccurred())
   345  
   346  			stdout := gbytes.NewBuffer()
   347  			stderr := gbytes.NewBuffer()
   348  
   349  			process, err = processTracker.Attach(process.ID(), garden.ProcessIO{
   350  				Stdin:  bytes.NewBufferString("this-is-stdin"),
   351  				Stdout: stdout,
   352  				Stderr: stderr,
   353  			})
   354  			Expect(err).NotTo(HaveOccurred())
   355  
   356  			Eventually(stdout).Should(gbytes.Say("hi stdout this-is-stdin"))
   357  			Eventually(stderr).Should(gbytes.Say("hi stderr this-is-stdin"))
   358  		})
   359  	})
   360  
   361  	Describe("Listing active process IDs", func() {
   362  		It("includes running process IDs", func() {
   363  			stdin1, stdinWriter1 := io.Pipe()
   364  			stdin2, stdinWriter2 := io.Pipe()
   365  
   366  			Expect(processTracker.ActiveProcesses()).To(BeEmpty())
   367  
   368  			process1, err := processTracker.Run("9955", exec.Command("cat"), garden.ProcessIO{
   369  				Stdin: stdin1,
   370  			}, nil, signaller)
   371  			Expect(err).ToNot(HaveOccurred())
   372  
   373  			Eventually(processTracker.ActiveProcesses).Should(ConsistOf(process1))
   374  
   375  			process2, err := processTracker.Run("9956", exec.Command("cat"), garden.ProcessIO{
   376  				Stdin: stdin2,
   377  			}, nil, signaller)
   378  			Expect(err).ToNot(HaveOccurred())
   379  
   380  			Eventually(processTracker.ActiveProcesses).Should(ConsistOf(process1, process2))
   381  
   382  			stdinWriter1.Close()
   383  			Eventually(processTracker.ActiveProcesses).Should(ConsistOf(process2))
   384  
   385  			stdinWriter2.Close()
   386  			Eventually(processTracker.ActiveProcesses).Should(BeEmpty())
   387  		})
   388  	})
   389  })
   390  
   391  func copyFile(src, dst string) error {
   392  	s, err := os.Open(src)
   393  	if err != nil {
   394  		return err
   395  	}
   396  
   397  	defer s.Close()
   398  
   399  	d, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0755)
   400  	if err != nil {
   401  		return err
   402  	}
   403  
   404  	_, err = io.Copy(d, s)
   405  	if err != nil {
   406  		d.Close()
   407  		return err
   408  	}
   409  
   410  	return d.Close()
   411  }