github.com/emate/nomad@v0.8.2-wo-binpacking/client/driver/executor/executor.go (about)

     1  package executor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"log"
     9  	"net"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"syscall"
    18  	"time"
    19  
    20  	"github.com/armon/circbuf"
    21  	"github.com/hashicorp/go-multierror"
    22  	"github.com/mitchellh/go-ps"
    23  	"github.com/shirou/gopsutil/process"
    24  
    25  	"github.com/hashicorp/nomad/client/allocdir"
    26  	"github.com/hashicorp/nomad/client/driver/env"
    27  	"github.com/hashicorp/nomad/client/driver/logging"
    28  	"github.com/hashicorp/nomad/client/stats"
    29  	shelpers "github.com/hashicorp/nomad/helper/stats"
    30  	"github.com/hashicorp/nomad/nomad/structs"
    31  
    32  	syslog "github.com/RackSec/srslog"
    33  
    34  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    35  	cstructs "github.com/hashicorp/nomad/client/structs"
    36  )
    37  
    38  const (
    39  	// pidScanInterval is the interval at which the executor scans the process
    40  	// tree for finding out the pids that the executor and it's child processes
    41  	// have forked
    42  	pidScanInterval = 5 * time.Second
    43  )
    44  
    45  var (
    46  	// The statistics the basic executor exposes
    47  	ExecutorBasicMeasuredMemStats = []string{"RSS", "Swap"}
    48  	ExecutorBasicMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"}
    49  )
    50  
    51  // Executor is the interface which allows a driver to launch and supervise
    52  // a process
    53  type Executor interface {
    54  	SetContext(ctx *ExecutorContext) error
    55  	LaunchCmd(command *ExecCommand) (*ProcessState, error)
    56  	LaunchSyslogServer() (*SyslogServerState, error)
    57  	Wait() (*ProcessState, error)
    58  	ShutDown() error
    59  	Exit() error
    60  	UpdateLogConfig(logConfig *structs.LogConfig) error
    61  	UpdateTask(task *structs.Task) error
    62  	Version() (*ExecutorVersion, error)
    63  	Stats() (*cstructs.TaskResourceUsage, error)
    64  	Signal(s os.Signal) error
    65  	Exec(deadline time.Time, cmd string, args []string) ([]byte, int, error)
    66  }
    67  
    68  // ExecutorContext holds context to configure the command user
    69  // wants to run and isolate it
    70  type ExecutorContext struct {
    71  	// TaskEnv holds information about the environment of a Task
    72  	TaskEnv *env.TaskEnv
    73  
    74  	// Task is the task whose executor is being launched
    75  	Task *structs.Task
    76  
    77  	// TaskDir is the host path to the task's root
    78  	TaskDir string
    79  
    80  	// LogDir is the host path where logs should be written
    81  	LogDir string
    82  
    83  	// Driver is the name of the driver that invoked the executor
    84  	Driver string
    85  
    86  	// PortUpperBound is the upper bound of the ports that we can use to start
    87  	// the syslog server
    88  	PortUpperBound uint
    89  
    90  	// PortLowerBound is the lower bound of the ports that we can use to start
    91  	// the syslog server
    92  	PortLowerBound uint
    93  }
    94  
    95  // ExecCommand holds the user command, args, and other isolation related
    96  // settings.
    97  type ExecCommand struct {
    98  	// Cmd is the command that the user wants to run.
    99  	Cmd string
   100  
   101  	// Args is the args of the command that the user wants to run.
   102  	Args []string
   103  
   104  	// TaskKillSignal is an optional field which signal to kill the process
   105  	TaskKillSignal os.Signal
   106  
   107  	// FSIsolation determines whether the command would be run in a chroot.
   108  	FSIsolation bool
   109  
   110  	// User is the user which the executor uses to run the command.
   111  	User string
   112  
   113  	// ResourceLimits determines whether resource limits are enforced by the
   114  	// executor.
   115  	ResourceLimits bool
   116  }
   117  
   118  // ProcessState holds information about the state of a user process.
   119  type ProcessState struct {
   120  	Pid             int
   121  	ExitCode        int
   122  	Signal          int
   123  	IsolationConfig *dstructs.IsolationConfig
   124  	Time            time.Time
   125  }
   126  
   127  // nomadPid holds a pid and it's cpu percentage calculator
   128  type nomadPid struct {
   129  	pid           int
   130  	cpuStatsTotal *stats.CpuStats
   131  	cpuStatsUser  *stats.CpuStats
   132  	cpuStatsSys   *stats.CpuStats
   133  }
   134  
   135  // SyslogServerState holds the address and isolation information of a launched
   136  // syslog server
   137  type SyslogServerState struct {
   138  	IsolationConfig *dstructs.IsolationConfig
   139  	Addr            string
   140  }
   141  
   142  // ExecutorVersion is the version of the executor
   143  type ExecutorVersion struct {
   144  	Version string
   145  }
   146  
   147  func (v *ExecutorVersion) GoString() string {
   148  	return v.Version
   149  }
   150  
   151  // UniversalExecutor is an implementation of the Executor which launches and
   152  // supervises processes. In addition to process supervision it provides resource
   153  // and file system isolation
   154  type UniversalExecutor struct {
   155  	cmd     exec.Cmd
   156  	ctx     *ExecutorContext
   157  	command *ExecCommand
   158  
   159  	pids                map[int]*nomadPid
   160  	pidLock             sync.RWMutex
   161  	exitState           *ProcessState
   162  	processExited       chan interface{}
   163  	fsIsolationEnforced bool
   164  
   165  	lre         *logging.FileRotator
   166  	lro         *logging.FileRotator
   167  	rotatorLock sync.Mutex
   168  
   169  	syslogServer *logging.SyslogServer
   170  	syslogChan   chan *logging.SyslogMessage
   171  
   172  	resConCtx resourceContainerContext
   173  
   174  	totalCpuStats  *stats.CpuStats
   175  	userCpuStats   *stats.CpuStats
   176  	systemCpuStats *stats.CpuStats
   177  	logger         *log.Logger
   178  }
   179  
   180  // NewExecutor returns an Executor
   181  func NewExecutor(logger *log.Logger) Executor {
   182  	if err := shelpers.Init(); err != nil {
   183  		logger.Printf("[ERR] executor: unable to initialize stats: %v", err)
   184  	}
   185  
   186  	exec := &UniversalExecutor{
   187  		logger:         logger,
   188  		processExited:  make(chan interface{}),
   189  		totalCpuStats:  stats.NewCpuStats(),
   190  		userCpuStats:   stats.NewCpuStats(),
   191  		systemCpuStats: stats.NewCpuStats(),
   192  		pids:           make(map[int]*nomadPid),
   193  	}
   194  
   195  	return exec
   196  }
   197  
   198  // Version returns the api version of the executor
   199  func (e *UniversalExecutor) Version() (*ExecutorVersion, error) {
   200  	return &ExecutorVersion{Version: "1.1.0"}, nil
   201  }
   202  
   203  // SetContext is used to set the executors context and should be the first call
   204  // after launching the executor.
   205  func (e *UniversalExecutor) SetContext(ctx *ExecutorContext) error {
   206  	e.ctx = ctx
   207  	return nil
   208  }
   209  
   210  // LaunchCmd launches the main process and returns its state. It also
   211  // configures an applies isolation on certain platforms.
   212  func (e *UniversalExecutor) LaunchCmd(command *ExecCommand) (*ProcessState, error) {
   213  	e.logger.Printf("[INFO] executor: launching command %v %v", command.Cmd, strings.Join(command.Args, " "))
   214  
   215  	// Ensure the context has been set first
   216  	if e.ctx == nil {
   217  		return nil, fmt.Errorf("SetContext must be called before launching a command")
   218  	}
   219  
   220  	e.command = command
   221  
   222  	// setting the user of the process
   223  	if command.User != "" {
   224  		e.logger.Printf("[DEBUG] executor: running command as %s", command.User)
   225  		if err := e.runAs(command.User); err != nil {
   226  			return nil, err
   227  		}
   228  	}
   229  
   230  	// set the task dir as the working directory for the command
   231  	e.cmd.Dir = e.ctx.TaskDir
   232  
   233  	// start command in separate process group
   234  	if err := e.setNewProcessGroup(); err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	// configuring the chroot, resource container, and start the plugin
   239  	// process in the chroot.
   240  	if err := e.configureIsolation(); err != nil {
   241  		return nil, err
   242  	}
   243  	// Apply ourselves into the resource container. The executor MUST be in
   244  	// the resource container before the user task is started, otherwise we
   245  	// are subject to a fork attack in which a process escapes isolation by
   246  	// immediately forking.
   247  	if err := e.applyLimits(os.Getpid()); err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	// Setup the loggers
   252  	if err := e.configureLoggers(); err != nil {
   253  		return nil, err
   254  	}
   255  	e.cmd.Stdout = e.lro
   256  	e.cmd.Stderr = e.lre
   257  
   258  	// Look up the binary path and make it executable
   259  	absPath, err := e.lookupBin(e.ctx.TaskEnv.ReplaceEnv(command.Cmd))
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	if err := e.makeExecutable(absPath); err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	path := absPath
   269  
   270  	// Determine the path to run as it may have to be relative to the chroot.
   271  	if e.fsIsolationEnforced {
   272  		rel, err := filepath.Rel(e.ctx.TaskDir, path)
   273  		if err != nil {
   274  			return nil, fmt.Errorf("failed to determine relative path base=%q target=%q: %v", e.ctx.TaskDir, path, err)
   275  		}
   276  		path = rel
   277  	}
   278  
   279  	// Set the commands arguments
   280  	e.cmd.Path = path
   281  	e.cmd.Args = append([]string{e.cmd.Path}, e.ctx.TaskEnv.ParseAndReplace(command.Args)...)
   282  	e.cmd.Env = e.ctx.TaskEnv.List()
   283  
   284  	// Start the process
   285  	if err := e.cmd.Start(); err != nil {
   286  		return nil, fmt.Errorf("failed to start command path=%q --- args=%q: %v", path, e.cmd.Args, err)
   287  	}
   288  	go e.collectPids()
   289  	go e.wait()
   290  	ic := e.resConCtx.getIsolationConfig()
   291  	return &ProcessState{Pid: e.cmd.Process.Pid, ExitCode: -1, IsolationConfig: ic, Time: time.Now()}, nil
   292  }
   293  
   294  // Exec a command inside a container for exec and java drivers.
   295  func (e *UniversalExecutor) Exec(deadline time.Time, name string, args []string) ([]byte, int, error) {
   296  	ctx, cancel := context.WithDeadline(context.Background(), deadline)
   297  	defer cancel()
   298  	return ExecScript(ctx, e.cmd.Dir, e.ctx.TaskEnv, e.cmd.SysProcAttr, name, args)
   299  }
   300  
   301  // ExecScript executes cmd with args and returns the output, exit code, and
   302  // error. Output is truncated to client/driver/structs.CheckBufSize
   303  func ExecScript(ctx context.Context, dir string, env *env.TaskEnv, attrs *syscall.SysProcAttr,
   304  	name string, args []string) ([]byte, int, error) {
   305  	name = env.ReplaceEnv(name)
   306  	cmd := exec.CommandContext(ctx, name, env.ParseAndReplace(args)...)
   307  
   308  	// Copy runtime environment from the main command
   309  	cmd.SysProcAttr = attrs
   310  	cmd.Dir = dir
   311  	cmd.Env = env.List()
   312  
   313  	// Capture output
   314  	buf, _ := circbuf.NewBuffer(int64(dstructs.CheckBufSize))
   315  	cmd.Stdout = buf
   316  	cmd.Stderr = buf
   317  
   318  	if err := cmd.Run(); err != nil {
   319  		exitErr, ok := err.(*exec.ExitError)
   320  		if !ok {
   321  			// Non-exit error, return it and let the caller treat
   322  			// it as a critical failure
   323  			return nil, 0, err
   324  		}
   325  
   326  		// Some kind of error happened; default to critical
   327  		exitCode := 2
   328  		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
   329  			exitCode = status.ExitStatus()
   330  		}
   331  
   332  		// Don't return the exitError as the caller only needs the
   333  		// output and code.
   334  		return buf.Bytes(), exitCode, nil
   335  	}
   336  	return buf.Bytes(), 0, nil
   337  }
   338  
   339  // configureLoggers sets up the standard out/error file rotators
   340  func (e *UniversalExecutor) configureLoggers() error {
   341  	e.rotatorLock.Lock()
   342  	defer e.rotatorLock.Unlock()
   343  
   344  	logFileSize := int64(e.ctx.Task.LogConfig.MaxFileSizeMB * 1024 * 1024)
   345  	if e.lro == nil {
   346  		lro, err := logging.NewFileRotator(e.ctx.LogDir, fmt.Sprintf("%v.stdout", e.ctx.Task.Name),
   347  			e.ctx.Task.LogConfig.MaxFiles, logFileSize, e.logger)
   348  		if err != nil {
   349  			return fmt.Errorf("error creating new stdout log file for %q: %v", e.ctx.Task.Name, err)
   350  		}
   351  		e.lro = lro
   352  	}
   353  
   354  	if e.lre == nil {
   355  		lre, err := logging.NewFileRotator(e.ctx.LogDir, fmt.Sprintf("%v.stderr", e.ctx.Task.Name),
   356  			e.ctx.Task.LogConfig.MaxFiles, logFileSize, e.logger)
   357  		if err != nil {
   358  			return fmt.Errorf("error creating new stderr log file for %q: %v", e.ctx.Task.Name, err)
   359  		}
   360  		e.lre = lre
   361  	}
   362  	return nil
   363  }
   364  
   365  // Wait waits until a process has exited and returns it's exitcode and errors
   366  func (e *UniversalExecutor) Wait() (*ProcessState, error) {
   367  	<-e.processExited
   368  	return e.exitState, nil
   369  }
   370  
   371  // COMPAT: prior to Nomad 0.3.2, UpdateTask didn't exist.
   372  // UpdateLogConfig updates the log configuration
   373  func (e *UniversalExecutor) UpdateLogConfig(logConfig *structs.LogConfig) error {
   374  	e.ctx.Task.LogConfig = logConfig
   375  	if e.lro == nil {
   376  		return fmt.Errorf("log rotator for stdout doesn't exist")
   377  	}
   378  	e.lro.MaxFiles = logConfig.MaxFiles
   379  	e.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   380  
   381  	if e.lre == nil {
   382  		return fmt.Errorf("log rotator for stderr doesn't exist")
   383  	}
   384  	e.lre.MaxFiles = logConfig.MaxFiles
   385  	e.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024)
   386  	return nil
   387  }
   388  
   389  func (e *UniversalExecutor) UpdateTask(task *structs.Task) error {
   390  	e.ctx.Task = task
   391  
   392  	// Updating Log Config
   393  	e.rotatorLock.Lock()
   394  	if e.lro != nil && e.lre != nil {
   395  		fileSize := int64(task.LogConfig.MaxFileSizeMB * 1024 * 1024)
   396  		e.lro.MaxFiles = task.LogConfig.MaxFiles
   397  		e.lro.FileSize = fileSize
   398  		e.lre.MaxFiles = task.LogConfig.MaxFiles
   399  		e.lre.FileSize = fileSize
   400  	}
   401  	e.rotatorLock.Unlock()
   402  	return nil
   403  }
   404  
   405  func (e *UniversalExecutor) wait() {
   406  	defer close(e.processExited)
   407  	err := e.cmd.Wait()
   408  	ic := e.resConCtx.getIsolationConfig()
   409  	if err == nil {
   410  		e.exitState = &ProcessState{Pid: 0, ExitCode: 0, IsolationConfig: ic, Time: time.Now()}
   411  		return
   412  	}
   413  
   414  	e.lre.Close()
   415  	e.lro.Close()
   416  
   417  	exitCode := 1
   418  	var signal int
   419  	if exitErr, ok := err.(*exec.ExitError); ok {
   420  		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
   421  			exitCode = status.ExitStatus()
   422  			if status.Signaled() {
   423  				// bash(1) uses the lower 7 bits of a uint8
   424  				// to indicate normal program failure (see
   425  				// <sysexits.h>). If a process terminates due
   426  				// to a signal, encode the signal number to
   427  				// indicate which signal caused the process
   428  				// to terminate.  Mirror this exit code
   429  				// encoding scheme.
   430  				const exitSignalBase = 128
   431  				signal = int(status.Signal())
   432  				exitCode = exitSignalBase + signal
   433  			}
   434  		}
   435  	} else {
   436  		e.logger.Printf("[WARN] executor: unexpected Cmd.Wait() error type: %v", err)
   437  	}
   438  
   439  	e.exitState = &ProcessState{Pid: 0, ExitCode: exitCode, Signal: signal, IsolationConfig: ic, Time: time.Now()}
   440  }
   441  
   442  var (
   443  	// finishedErr is the error message received when trying to kill and already
   444  	// exited process.
   445  	finishedErr = "os: process already finished"
   446  
   447  	// noSuchProcessErr is the error message received when trying to kill a non
   448  	// existing process (e.g. when killing a process group).
   449  	noSuchProcessErr = "no such process"
   450  )
   451  
   452  // ClientCleanup is the cleanup routine that a Nomad Client uses to remove the
   453  // remnants of a child UniversalExecutor.
   454  func ClientCleanup(ic *dstructs.IsolationConfig, pid int) error {
   455  	return clientCleanup(ic, pid)
   456  }
   457  
   458  // Exit cleans up the alloc directory, destroys resource container and kills the
   459  // user process
   460  func (e *UniversalExecutor) Exit() error {
   461  	var merr multierror.Error
   462  	if e.syslogServer != nil {
   463  		e.syslogServer.Shutdown()
   464  	}
   465  
   466  	if e.lre != nil {
   467  		e.lre.Close()
   468  	}
   469  
   470  	if e.lro != nil {
   471  		e.lro.Close()
   472  	}
   473  
   474  	// If the executor did not launch a process, return.
   475  	if e.command == nil {
   476  		return nil
   477  	}
   478  
   479  	// Prefer killing the process via the resource container.
   480  	if e.cmd.Process != nil && !e.command.ResourceLimits {
   481  		proc, err := os.FindProcess(e.cmd.Process.Pid)
   482  		if err != nil {
   483  			e.logger.Printf("[ERR] executor: can't find process with pid: %v, err: %v",
   484  				e.cmd.Process.Pid, err)
   485  		} else if err := e.cleanupChildProcesses(proc); err != nil && err.Error() != finishedErr {
   486  			merr.Errors = append(merr.Errors,
   487  				fmt.Errorf("can't kill process with pid: %v, err: %v", e.cmd.Process.Pid, err))
   488  		}
   489  	}
   490  
   491  	if e.command.ResourceLimits {
   492  		if err := e.resConCtx.executorCleanup(); err != nil {
   493  			merr.Errors = append(merr.Errors, err)
   494  		}
   495  	}
   496  	return merr.ErrorOrNil()
   497  }
   498  
   499  // Shutdown sends an interrupt signal to the user process
   500  func (e *UniversalExecutor) ShutDown() error {
   501  	if e.cmd.Process == nil {
   502  		return fmt.Errorf("executor.shutdown error: no process found")
   503  	}
   504  	proc, err := os.FindProcess(e.cmd.Process.Pid)
   505  	if err != nil {
   506  		return fmt.Errorf("executor.shutdown failed to find process: %v", err)
   507  	}
   508  	return e.shutdownProcess(proc)
   509  }
   510  
   511  // pidStats returns the resource usage stats per pid
   512  func (e *UniversalExecutor) pidStats() (map[string]*cstructs.ResourceUsage, error) {
   513  	stats := make(map[string]*cstructs.ResourceUsage)
   514  	e.pidLock.RLock()
   515  	pids := make(map[int]*nomadPid, len(e.pids))
   516  	for k, v := range e.pids {
   517  		pids[k] = v
   518  	}
   519  	e.pidLock.RUnlock()
   520  	for pid, np := range pids {
   521  		p, err := process.NewProcess(int32(pid))
   522  		if err != nil {
   523  			e.logger.Printf("[TRACE] executor: unable to create new process with pid: %v", pid)
   524  			continue
   525  		}
   526  		ms := &cstructs.MemoryStats{}
   527  		if memInfo, err := p.MemoryInfo(); err == nil {
   528  			ms.RSS = memInfo.RSS
   529  			ms.Swap = memInfo.Swap
   530  			ms.Measured = ExecutorBasicMeasuredMemStats
   531  		}
   532  
   533  		cs := &cstructs.CpuStats{}
   534  		if cpuStats, err := p.Times(); err == nil {
   535  			cs.SystemMode = np.cpuStatsSys.Percent(cpuStats.System * float64(time.Second))
   536  			cs.UserMode = np.cpuStatsUser.Percent(cpuStats.User * float64(time.Second))
   537  			cs.Measured = ExecutorBasicMeasuredCpuStats
   538  
   539  			// calculate cpu usage percent
   540  			cs.Percent = np.cpuStatsTotal.Percent(cpuStats.Total() * float64(time.Second))
   541  		}
   542  		stats[strconv.Itoa(pid)] = &cstructs.ResourceUsage{MemoryStats: ms, CpuStats: cs}
   543  	}
   544  
   545  	return stats, nil
   546  }
   547  
   548  // lookupBin looks for path to the binary to run by looking for the binary in
   549  // the following locations, in-order: task/local/, task/, based on host $PATH.
   550  // The return path is absolute.
   551  func (e *UniversalExecutor) lookupBin(bin string) (string, error) {
   552  	// Check in the local directory
   553  	local := filepath.Join(e.ctx.TaskDir, allocdir.TaskLocal, bin)
   554  	if _, err := os.Stat(local); err == nil {
   555  		return local, nil
   556  	}
   557  
   558  	// Check at the root of the task's directory
   559  	root := filepath.Join(e.ctx.TaskDir, bin)
   560  	if _, err := os.Stat(root); err == nil {
   561  		return root, nil
   562  	}
   563  
   564  	// Check the $PATH
   565  	if host, err := exec.LookPath(bin); err == nil {
   566  		return host, nil
   567  	}
   568  
   569  	return "", fmt.Errorf("binary %q could not be found", bin)
   570  }
   571  
   572  // makeExecutable makes the given file executable for root,group,others.
   573  func (e *UniversalExecutor) makeExecutable(binPath string) error {
   574  	if runtime.GOOS == "windows" {
   575  		return nil
   576  	}
   577  
   578  	fi, err := os.Stat(binPath)
   579  	if err != nil {
   580  		if os.IsNotExist(err) {
   581  			return fmt.Errorf("binary %q does not exist", binPath)
   582  		}
   583  		return fmt.Errorf("specified binary is invalid: %v", err)
   584  	}
   585  
   586  	// If it is not executable, make it so.
   587  	perm := fi.Mode().Perm()
   588  	req := os.FileMode(0555)
   589  	if perm&req != req {
   590  		if err := os.Chmod(binPath, perm|req); err != nil {
   591  			return fmt.Errorf("error making %q executable: %s", binPath, err)
   592  		}
   593  	}
   594  	return nil
   595  }
   596  
   597  // getFreePort returns a free port ready to be listened on between upper and
   598  // lower bounds
   599  func (e *UniversalExecutor) getListener(lowerBound uint, upperBound uint) (net.Listener, error) {
   600  	if runtime.GOOS == "windows" {
   601  		return e.listenerTCP(lowerBound, upperBound)
   602  	}
   603  
   604  	return e.listenerUnix()
   605  }
   606  
   607  // listenerTCP creates a TCP listener using an unused port between an upper and
   608  // lower bound
   609  func (e *UniversalExecutor) listenerTCP(lowerBound uint, upperBound uint) (net.Listener, error) {
   610  	for i := lowerBound; i <= upperBound; i++ {
   611  		addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i))
   612  		if err != nil {
   613  			return nil, err
   614  		}
   615  		l, err := net.ListenTCP("tcp", addr)
   616  		if err != nil {
   617  			continue
   618  		}
   619  		return l, nil
   620  	}
   621  	return nil, fmt.Errorf("No free port found")
   622  }
   623  
   624  // listenerUnix creates a Unix domain socket
   625  func (e *UniversalExecutor) listenerUnix() (net.Listener, error) {
   626  	f, err := ioutil.TempFile("", "plugin")
   627  	if err != nil {
   628  		return nil, err
   629  	}
   630  	path := f.Name()
   631  
   632  	if err := f.Close(); err != nil {
   633  		return nil, err
   634  	}
   635  	if err := os.Remove(path); err != nil {
   636  		return nil, err
   637  	}
   638  
   639  	return net.Listen("unix", path)
   640  }
   641  
   642  // collectPids collects the pids of the child processes that the executor is
   643  // running every 5 seconds
   644  func (e *UniversalExecutor) collectPids() {
   645  	// Fire the timer right away when the executor starts from there on the pids
   646  	// are collected every scan interval
   647  	timer := time.NewTimer(0)
   648  	defer timer.Stop()
   649  	for {
   650  		select {
   651  		case <-timer.C:
   652  			pids, err := e.getAllPids()
   653  			if err != nil {
   654  				e.logger.Printf("[DEBUG] executor: error collecting pids: %v", err)
   655  			}
   656  			e.pidLock.Lock()
   657  
   658  			// Adding pids which are not being tracked
   659  			for pid, np := range pids {
   660  				if _, ok := e.pids[pid]; !ok {
   661  					e.pids[pid] = np
   662  				}
   663  			}
   664  			// Removing pids which are no longer present
   665  			for pid := range e.pids {
   666  				if _, ok := pids[pid]; !ok {
   667  					delete(e.pids, pid)
   668  				}
   669  			}
   670  			e.pidLock.Unlock()
   671  			timer.Reset(pidScanInterval)
   672  		case <-e.processExited:
   673  			return
   674  		}
   675  	}
   676  }
   677  
   678  // scanPids scans all the pids on the machine running the current executor and
   679  // returns the child processes of the executor.
   680  func (e *UniversalExecutor) scanPids(parentPid int, allPids []ps.Process) (map[int]*nomadPid, error) {
   681  	processFamily := make(map[int]struct{})
   682  	processFamily[parentPid] = struct{}{}
   683  
   684  	// A mapping of pids to their parent pids. It is used to build the process
   685  	// tree of the executing task
   686  	pidsRemaining := make(map[int]int, len(allPids))
   687  	for _, pid := range allPids {
   688  		pidsRemaining[pid.Pid()] = pid.PPid()
   689  	}
   690  
   691  	for {
   692  		// flag to indicate if we have found a match
   693  		foundNewPid := false
   694  
   695  		for pid, ppid := range pidsRemaining {
   696  			_, childPid := processFamily[ppid]
   697  
   698  			// checking if the pid is a child of any of the parents
   699  			if childPid {
   700  				processFamily[pid] = struct{}{}
   701  				delete(pidsRemaining, pid)
   702  				foundNewPid = true
   703  			}
   704  		}
   705  
   706  		// not scanning anymore if we couldn't find a single match
   707  		if !foundNewPid {
   708  			break
   709  		}
   710  	}
   711  
   712  	res := make(map[int]*nomadPid)
   713  	for pid := range processFamily {
   714  		np := nomadPid{
   715  			pid:           pid,
   716  			cpuStatsTotal: stats.NewCpuStats(),
   717  			cpuStatsUser:  stats.NewCpuStats(),
   718  			cpuStatsSys:   stats.NewCpuStats(),
   719  		}
   720  		res[pid] = &np
   721  	}
   722  	return res, nil
   723  }
   724  
   725  // aggregatedResourceUsage aggregates the resource usage of all the pids and
   726  // returns a TaskResourceUsage data point
   727  func (e *UniversalExecutor) aggregatedResourceUsage(pidStats map[string]*cstructs.ResourceUsage) *cstructs.TaskResourceUsage {
   728  	ts := time.Now().UTC().UnixNano()
   729  	var (
   730  		systemModeCPU, userModeCPU, percent float64
   731  		totalRSS, totalSwap                 uint64
   732  	)
   733  
   734  	for _, pidStat := range pidStats {
   735  		systemModeCPU += pidStat.CpuStats.SystemMode
   736  		userModeCPU += pidStat.CpuStats.UserMode
   737  		percent += pidStat.CpuStats.Percent
   738  
   739  		totalRSS += pidStat.MemoryStats.RSS
   740  		totalSwap += pidStat.MemoryStats.Swap
   741  	}
   742  
   743  	totalCPU := &cstructs.CpuStats{
   744  		SystemMode: systemModeCPU,
   745  		UserMode:   userModeCPU,
   746  		Percent:    percent,
   747  		Measured:   ExecutorBasicMeasuredCpuStats,
   748  		TotalTicks: e.systemCpuStats.TicksConsumed(percent),
   749  	}
   750  
   751  	totalMemory := &cstructs.MemoryStats{
   752  		RSS:      totalRSS,
   753  		Swap:     totalSwap,
   754  		Measured: ExecutorBasicMeasuredMemStats,
   755  	}
   756  
   757  	resourceUsage := cstructs.ResourceUsage{
   758  		MemoryStats: totalMemory,
   759  		CpuStats:    totalCPU,
   760  	}
   761  	return &cstructs.TaskResourceUsage{
   762  		ResourceUsage: &resourceUsage,
   763  		Timestamp:     ts,
   764  		Pids:          pidStats,
   765  	}
   766  }
   767  
   768  // Signal sends the passed signal to the task
   769  func (e *UniversalExecutor) Signal(s os.Signal) error {
   770  	if e.cmd.Process == nil {
   771  		return fmt.Errorf("Task not yet run")
   772  	}
   773  
   774  	e.logger.Printf("[DEBUG] executor: sending signal %s to PID %d", s, e.cmd.Process.Pid)
   775  	err := e.cmd.Process.Signal(s)
   776  	if err != nil {
   777  		e.logger.Printf("[ERR] executor: sending signal %v failed: %v", s, err)
   778  		return err
   779  	}
   780  
   781  	return nil
   782  }
   783  
   784  func (e *UniversalExecutor) LaunchSyslogServer() (*SyslogServerState, error) {
   785  	// Ensure the context has been set first
   786  	if e.ctx == nil {
   787  		return nil, fmt.Errorf("SetContext must be called before launching the Syslog Server")
   788  	}
   789  
   790  	e.syslogChan = make(chan *logging.SyslogMessage, 2048)
   791  	l, err := e.getListener(e.ctx.PortLowerBound, e.ctx.PortUpperBound)
   792  	if err != nil {
   793  		return nil, err
   794  	}
   795  	e.logger.Printf("[DEBUG] syslog-server: launching syslog server on addr: %v", l.Addr().String())
   796  	if err := e.configureLoggers(); err != nil {
   797  		return nil, err
   798  	}
   799  
   800  	e.syslogServer = logging.NewSyslogServer(l, e.syslogChan, e.logger)
   801  	go e.syslogServer.Start()
   802  	go e.collectLogs(e.lre, e.lro)
   803  	syslogAddr := fmt.Sprintf("%s://%s", l.Addr().Network(), l.Addr().String())
   804  	return &SyslogServerState{Addr: syslogAddr}, nil
   805  }
   806  
   807  func (e *UniversalExecutor) collectLogs(we io.Writer, wo io.Writer) {
   808  	for logParts := range e.syslogChan {
   809  		// If the severity of the log line is err then we write to stderr
   810  		// otherwise all messages go to stdout
   811  		if logParts.Severity == syslog.LOG_ERR {
   812  			e.lre.Write(logParts.Message)
   813  			e.lre.Write([]byte{'\n'})
   814  		} else {
   815  			e.lro.Write(logParts.Message)
   816  			e.lro.Write([]byte{'\n'})
   817  		}
   818  	}
   819  }