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

     1  package process_tracker
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  
    16  	"github.com/cloudfoundry-incubator/garden"
    17  	"github.com/cloudfoundry/gunk/command_runner"
    18  	"github.com/pivotal-golang/lager"
    19  
    20  	"github.com/cloudfoundry-incubator/garden-linux/iodaemon/link"
    21  	"github.com/cloudfoundry-incubator/garden-linux/process_tracker/writer"
    22  )
    23  
    24  //go:generate counterfeiter -o fake_signaller/fake_signaller.go . Signaller
    25  type Signaller interface {
    26  	Signal(*SignalRequest) error
    27  }
    28  
    29  //go:generate counterfeiter -o fake_msg_sender/fake_msg_sender.go . MsgSender
    30  type MsgSender interface {
    31  	SendMsg(msg []byte) error
    32  }
    33  
    34  type SignalRequest struct {
    35  	Pid    string
    36  	Signal syscall.Signal
    37  	Link   MsgSender
    38  }
    39  
    40  type Process struct {
    41  	logger lager.Logger
    42  
    43  	id string
    44  
    45  	containerPath string
    46  	runner        command_runner.CommandRunner
    47  
    48  	runningLink *sync.Once
    49  	linked      chan struct{}
    50  	link        *link.Link
    51  
    52  	exited     chan struct{}
    53  	exitStatus int
    54  	exitErr    error
    55  
    56  	stdin  writer.FanIn
    57  	stdout writer.FanOut
    58  	stderr writer.FanOut
    59  
    60  	signaller Signaller
    61  }
    62  
    63  func NewProcess(
    64  	logger lager.Logger,
    65  	id string,
    66  	containerPath string,
    67  	runner command_runner.CommandRunner,
    68  	signaller Signaller,
    69  ) *Process {
    70  	return &Process{
    71  		logger: logger,
    72  
    73  		id: id,
    74  
    75  		containerPath: containerPath,
    76  		runner:        runner,
    77  
    78  		runningLink: &sync.Once{},
    79  
    80  		linked: make(chan struct{}),
    81  
    82  		exited: make(chan struct{}),
    83  
    84  		stdin:     writer.NewFanIn(),
    85  		stdout:    writer.NewFanOut(),
    86  		stderr:    writer.NewFanOut(),
    87  		signaller: signaller,
    88  	}
    89  }
    90  
    91  func (p *Process) ID() string {
    92  	return p.id
    93  }
    94  
    95  func (p *Process) Wait() (int, error) {
    96  	<-p.exited
    97  	return p.exitStatus, p.exitErr
    98  }
    99  
   100  func (p *Process) SetTTY(tty garden.TTYSpec) error {
   101  	<-p.linked
   102  
   103  	if tty.WindowSize != nil {
   104  		return p.link.SetWindowSize(tty.WindowSize.Columns, tty.WindowSize.Rows)
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func (p *Process) Signal(signal garden.Signal) error {
   111  	<-p.linked
   112  
   113  	request := &SignalRequest{Pid: p.id, Link: p.link}
   114  
   115  	switch signal {
   116  	case garden.SignalKill:
   117  		request.Signal = syscall.SIGKILL
   118  	case garden.SignalTerminate:
   119  		request.Signal = syscall.SIGTERM
   120  	default:
   121  		return fmt.Errorf("process_tracker: failed to send signal: unknown signal: %d", signal)
   122  	}
   123  
   124  	return p.signaller.Signal(request)
   125  }
   126  
   127  func (p *Process) Spawn(cmd *exec.Cmd, tty *garden.TTYSpec) (ready, active chan error) {
   128  	ready = make(chan error, 1)
   129  	active = make(chan error, 1)
   130  
   131  	spawnPath := path.Join(p.containerPath, "bin", "iodaemon")
   132  	processSock := path.Join(p.containerPath, "processes", fmt.Sprintf("%s.sock", p.ID()))
   133  	straceOutput := path.Join(p.containerPath, "processes", fmt.Sprintf("%s.strace", p.ID()))
   134  
   135  	os.MkdirAll(filepath.Dir(processSock), 0755)
   136  
   137  	flags := []string{
   138  		"-f",
   139  		"-tt",
   140  		"-T",
   141  		"-o",
   142  		straceOutput,
   143  		spawnPath,
   144  	}
   145  
   146  	if tty != nil {
   147  		flags = append(flags, "-tty")
   148  
   149  		if tty.WindowSize != nil {
   150  			flags = append(
   151  				flags,
   152  				fmt.Sprintf("-windowColumns=%d", tty.WindowSize.Columns),
   153  				fmt.Sprintf("-windowRows=%d", tty.WindowSize.Rows),
   154  			)
   155  		}
   156  	}
   157  
   158  	flags = append(flags, "spawn", processSock)
   159  
   160  	spawn := exec.Command("strace", append(flags, cmd.Args...)...)
   161  	spawn.Env = cmd.Env
   162  
   163  	spawnR, err := spawn.StdoutPipe()
   164  	if err != nil {
   165  		ready <- err
   166  		return
   167  	}
   168  	spawnOut := bufio.NewReader(spawnR)
   169  
   170  	spawnErrR, err := spawn.StderrPipe()
   171  	if err != nil {
   172  		ready <- err
   173  		return
   174  	}
   175  	spawnErr := bufio.NewReader(spawnErrR)
   176  
   177  	err = p.runner.Start(spawn)
   178  	if err != nil {
   179  		ready <- err
   180  		return
   181  	}
   182  
   183  	go func() {
   184  		waitFor := func(expectedLog string) error {
   185  			p.logger.Info("waiting for " + expectedLog)
   186  			log, err := spawnOut.ReadBytes('\n')
   187  			if err != nil {
   188  				stderrContents, readErr := ioutil.ReadAll(spawnErr)
   189  				cmdErr := spawn.Wait()
   190  				if readErr != nil {
   191  					p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"log": string(log), "readErr": readErr, "socket": processSock, "cmdErr": cmdErr})
   192  					return err
   193  				}
   194  
   195  				p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"log": string(log), "stderr": string(stderrContents), "socket": processSock, "cmdErr": cmdErr})
   196  				return err
   197  			}
   198  
   199  			if !strings.Contains(string(log), expectedLog) {
   200  				stderrContents, readErr := ioutil.ReadAll(spawnErr)
   201  				cmdErr := spawn.Wait()
   202  				if readErr != nil {
   203  					p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"log": string(log), "readErr": readErr, "socket": processSock, "cmdErr": cmdErr})
   204  					return err
   205  				}
   206  
   207  				p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"stderr": string(stderrContents), "log": string(log), "socket": processSock, "cmdErr": cmdErr})
   208  				return errors.New("mismatched log from iodaemon")
   209  			}
   210  
   211  			return nil
   212  		}
   213  
   214  		if waitFor("ready") != nil {
   215  			return
   216  		}
   217  
   218  		ready <- nil
   219  
   220  		if waitFor("listener-accepted") != nil {
   221  			return
   222  		}
   223  		if waitFor("unix-rights") != nil {
   224  			return
   225  		}
   226  		if waitFor("write-msg-unix") != nil {
   227  			return
   228  		}
   229  		if waitFor("accepted-connection") != nil {
   230  			return
   231  		}
   232  		if waitFor("cmd-start") != nil {
   233  			return
   234  		}
   235  		if waitFor("cmd-started") != nil {
   236  			return
   237  		}
   238  
   239  		if waitFor("active") != nil {
   240  			return
   241  		}
   242  
   243  		active <- nil
   244  
   245  		err = spawn.Wait()
   246  		// delete strace only on clean exit
   247  		if err != nil {
   248  			p.logger.Error("iodaemon-failed", err, lager.Data{"pid": spawn.Process.Pid})
   249  			return
   250  		}
   251  
   252  		os.Remove(straceOutput)
   253  	}()
   254  
   255  	return
   256  }
   257  
   258  func (p *Process) Link() {
   259  	p.runningLink.Do(p.runLinker)
   260  }
   261  
   262  func (p *Process) Attach(processIO garden.ProcessIO) {
   263  	if processIO.Stdin != nil {
   264  		p.stdin.AddSource(processIO.Stdin)
   265  	}
   266  
   267  	if processIO.Stdout != nil {
   268  		p.stdout.AddSink(processIO.Stdout)
   269  	}
   270  
   271  	if processIO.Stderr != nil {
   272  		p.stderr.AddSink(processIO.Stderr)
   273  	}
   274  }
   275  
   276  // This is guarded by runningLink so will only run once per Process per garden.
   277  func (p *Process) runLinker() {
   278  	processSock := path.Join(p.containerPath, "processes", fmt.Sprintf("%s.sock", p.ID()))
   279  
   280  	p.logger.Info("creating-link-to-iodaemon", lager.Data{"socket-path": processSock})
   281  	link, err := link.Create(p.logger.Session("link-create"), processSock, p.stdout, p.stderr)
   282  	if err != nil {
   283  		p.logger.Error("creating-link-failed", err, lager.Data{"socket-path": processSock})
   284  		p.completed(-1, err)
   285  		return
   286  	}
   287  	p.logger.Info("created-link-to-iodaemon", lager.Data{"socket-path": processSock, "err": err})
   288  
   289  	p.stdin.AddSink(link)
   290  
   291  	p.link = link
   292  	close(p.linked)
   293  
   294  	p.completed(p.link.Wait())
   295  
   296  	// don't leak stdin pipe
   297  	p.stdin.Close()
   298  }
   299  
   300  func (p *Process) completed(exitStatus int, err error) {
   301  	p.exitStatus = exitStatus
   302  	p.exitErr = err
   303  	close(p.exited)
   304  }