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

     1  package driver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hashicorp/consul-template/signals"
    14  	"github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/go-plugin"
    16  	"github.com/hashicorp/nomad/client/allocdir"
    17  	"github.com/hashicorp/nomad/client/config"
    18  	"github.com/hashicorp/nomad/client/driver/env"
    19  	"github.com/hashicorp/nomad/client/driver/executor"
    20  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    21  	cstructs "github.com/hashicorp/nomad/client/structs"
    22  	"github.com/hashicorp/nomad/helper/discover"
    23  	"github.com/hashicorp/nomad/nomad/structs"
    24  )
    25  
    26  // cgroupsMounted returns true if the cgroups are mounted on a system otherwise
    27  // returns false
    28  func cgroupsMounted(node *structs.Node) bool {
    29  	_, ok := node.Attributes["unique.cgroup.mountpoint"]
    30  	return ok
    31  }
    32  
    33  // createExecutor launches an executor plugin and returns an instance of the
    34  // Executor interface
    35  func createExecutor(w io.Writer, clientConfig *config.Config,
    36  	executorConfig *dstructs.ExecutorConfig) (executor.Executor, *plugin.Client, error) {
    37  
    38  	c, err := json.Marshal(executorConfig)
    39  	if err != nil {
    40  		return nil, nil, fmt.Errorf("unable to create executor config: %v", err)
    41  	}
    42  	bin, err := discover.NomadExecutable()
    43  	if err != nil {
    44  		return nil, nil, fmt.Errorf("unable to find the nomad binary: %v", err)
    45  	}
    46  
    47  	config := &plugin.ClientConfig{
    48  		Cmd: exec.Command(bin, "executor", string(c)),
    49  	}
    50  	config.HandshakeConfig = HandshakeConfig
    51  	config.Plugins = GetPluginMap(w, clientConfig.LogLevel)
    52  	config.MaxPort = clientConfig.ClientMaxPort
    53  	config.MinPort = clientConfig.ClientMinPort
    54  
    55  	// setting the setsid of the plugin process so that it doesn't get signals sent to
    56  	// the nomad client.
    57  	if config.Cmd != nil {
    58  		isolateCommand(config.Cmd)
    59  	}
    60  
    61  	executorClient := plugin.NewClient(config)
    62  	rpcClient, err := executorClient.Client()
    63  	if err != nil {
    64  		return nil, nil, fmt.Errorf("error creating rpc client for executor plugin: %v", err)
    65  	}
    66  
    67  	raw, err := rpcClient.Dispense("executor")
    68  	if err != nil {
    69  		return nil, nil, fmt.Errorf("unable to dispense the executor plugin: %v", err)
    70  	}
    71  	executorPlugin := raw.(executor.Executor)
    72  	return executorPlugin, executorClient, nil
    73  }
    74  
    75  func createExecutorWithConfig(config *plugin.ClientConfig, w io.Writer) (executor.Executor, *plugin.Client, error) {
    76  	config.HandshakeConfig = HandshakeConfig
    77  
    78  	// Setting this to DEBUG since the log level at the executor server process
    79  	// is already set, and this effects only the executor client.
    80  	config.Plugins = GetPluginMap(w, "DEBUG")
    81  
    82  	executorClient := plugin.NewClient(config)
    83  	rpcClient, err := executorClient.Client()
    84  	if err != nil {
    85  		return nil, nil, fmt.Errorf("error creating rpc client for executor plugin: %v", err)
    86  	}
    87  
    88  	raw, err := rpcClient.Dispense("executor")
    89  	if err != nil {
    90  		return nil, nil, fmt.Errorf("unable to dispense the executor plugin: %v", err)
    91  	}
    92  	executorPlugin, ok := raw.(*ExecutorRPC)
    93  	if !ok {
    94  		return nil, nil, fmt.Errorf("unexpected executor rpc type: %T", raw)
    95  	}
    96  	// 0.6 Upgrade path: Deregister services from the executor as the Nomad
    97  	// client agent now handles all Consul interactions. Ignore errors as
    98  	// this shouldn't cause the alloc to fail and there's nothing useful to
    99  	// do with them.
   100  	executorPlugin.DeregisterServices()
   101  	return executorPlugin, executorClient, nil
   102  }
   103  
   104  // killProcess kills a process with the given pid
   105  func killProcess(pid int) error {
   106  	proc, err := os.FindProcess(pid)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	return proc.Kill()
   111  }
   112  
   113  // destroyPlugin kills the plugin with the given pid and also kills the user
   114  // process
   115  func destroyPlugin(pluginPid int, userPid int) error {
   116  	var merr error
   117  	if err := killProcess(pluginPid); err != nil {
   118  		merr = multierror.Append(merr, err)
   119  	}
   120  
   121  	if err := killProcess(userPid); err != nil {
   122  		merr = multierror.Append(merr, err)
   123  	}
   124  	return merr
   125  }
   126  
   127  // validateCommand validates that the command only has a single value and
   128  // returns a user friendly error message telling them to use the passed
   129  // argField.
   130  func validateCommand(command, argField string) error {
   131  	trimmed := strings.TrimSpace(command)
   132  	if len(trimmed) == 0 {
   133  		return fmt.Errorf("command empty: %q", command)
   134  	}
   135  
   136  	if len(trimmed) != len(command) {
   137  		return fmt.Errorf("command contains extra white space: %q", command)
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  // GetKillTimeout returns the kill timeout to use given the tasks desired kill
   144  // timeout and the operator configured max kill timeout.
   145  func GetKillTimeout(desired, max time.Duration) time.Duration {
   146  	maxNanos := max.Nanoseconds()
   147  	desiredNanos := desired.Nanoseconds()
   148  
   149  	// Make the minimum time between signal and kill, 1 second.
   150  	if desiredNanos <= 0 {
   151  		desiredNanos = (1 * time.Second).Nanoseconds()
   152  	}
   153  
   154  	// Protect against max not being set properly.
   155  	if maxNanos <= 0 {
   156  		maxNanos = (10 * time.Second).Nanoseconds()
   157  	}
   158  
   159  	if desiredNanos < maxNanos {
   160  		return time.Duration(desiredNanos)
   161  	}
   162  
   163  	return max
   164  }
   165  
   166  // GetAbsolutePath returns the absolute path of the passed binary by resolving
   167  // it in the path and following symlinks.
   168  func GetAbsolutePath(bin string) (string, error) {
   169  	lp, err := exec.LookPath(bin)
   170  	if err != nil {
   171  		return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err)
   172  	}
   173  
   174  	return filepath.EvalSymlinks(lp)
   175  }
   176  
   177  // getExecutorUser returns the user of the task, defaulting to
   178  // dstructs.DefaultUnprivilegedUser if none was given.
   179  func getExecutorUser(task *structs.Task) string {
   180  	if task.User == "" {
   181  		return dstructs.DefaultUnpriviledgedUser
   182  	}
   183  	return task.User
   184  }
   185  
   186  // SetEnvvars sets path and host env vars depending on the FS isolation used.
   187  func SetEnvvars(envBuilder *env.Builder, fsi cstructs.FSIsolation, taskDir *allocdir.TaskDir, conf *config.Config) {
   188  	// Set driver-specific environment variables
   189  	switch fsi {
   190  	case cstructs.FSIsolationNone:
   191  		// Use host paths
   192  		envBuilder.SetAllocDir(taskDir.SharedAllocDir)
   193  		envBuilder.SetTaskLocalDir(taskDir.LocalDir)
   194  		envBuilder.SetSecretsDir(taskDir.SecretsDir)
   195  	default:
   196  		// filesystem isolation; use container paths
   197  		envBuilder.SetAllocDir(allocdir.SharedAllocContainerPath)
   198  		envBuilder.SetTaskLocalDir(allocdir.TaskLocalContainerPath)
   199  		envBuilder.SetSecretsDir(allocdir.TaskSecretsContainerPath)
   200  	}
   201  
   202  	// Set the host environment variables for non-image based drivers
   203  	if fsi != cstructs.FSIsolationImage {
   204  		filter := strings.Split(conf.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",")
   205  		envBuilder.SetHostEnvvars(filter)
   206  	}
   207  }
   208  
   209  // getTaskKillSignal looks up the signal specified for the task if it has been
   210  // specified. If it is not supported on the platform, returns an error.
   211  func getTaskKillSignal(signal string) (os.Signal, error) {
   212  	if signal == "" {
   213  		return os.Interrupt, nil
   214  	}
   215  
   216  	taskKillSignal := signals.SignalLookup[signal]
   217  	if taskKillSignal == nil {
   218  		return nil, fmt.Errorf("Signal %s is not supported", signal)
   219  	}
   220  
   221  	return taskKillSignal, nil
   222  }