github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/executor/executor.go (about)

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