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 }