github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/driver.go (about) 1 package driver 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "strings" 9 10 "github.com/ncodes/nomad/client/allocdir" 11 "github.com/ncodes/nomad/client/config" 12 "github.com/ncodes/nomad/client/driver/env" 13 "github.com/ncodes/nomad/client/fingerprint" 14 "github.com/ncodes/nomad/nomad/structs" 15 16 dstructs "github.com/ncodes/nomad/client/driver/structs" 17 cstructs "github.com/ncodes/nomad/client/structs" 18 ) 19 20 var ( 21 // BuiltinDrivers contains the built in registered drivers 22 // which are available for allocation handling 23 BuiltinDrivers = map[string]Factory{ 24 "docker": NewDockerDriver, 25 "exec": NewExecDriver, 26 "raw_exec": NewRawExecDriver, 27 "java": NewJavaDriver, 28 "qemu": NewQemuDriver, 29 "rkt": NewRktDriver, 30 } 31 32 // DriverStatsNotImplemented is the error to be returned if a driver doesn't 33 // implement stats. 34 DriverStatsNotImplemented = errors.New("stats not implemented for driver") 35 ) 36 37 // NewDriver is used to instantiate and return a new driver 38 // given the name and a logger 39 func NewDriver(name string, ctx *DriverContext) (Driver, error) { 40 // Lookup the factory function 41 factory, ok := BuiltinDrivers[name] 42 if !ok { 43 return nil, fmt.Errorf("unknown driver '%s'", name) 44 } 45 46 // Instantiate the driver 47 f := factory(ctx) 48 return f, nil 49 } 50 51 // Factory is used to instantiate a new Driver 52 type Factory func(*DriverContext) Driver 53 54 // CreatedResources is a map of resources (eg downloaded images) created by a driver 55 // that must be cleaned up. 56 type CreatedResources struct { 57 Resources map[string][]string 58 } 59 60 func NewCreatedResources() *CreatedResources { 61 return &CreatedResources{Resources: make(map[string][]string)} 62 } 63 64 // Add a new resource if it doesn't already exist. 65 func (r *CreatedResources) Add(k, v string) { 66 if r.Resources == nil { 67 r.Resources = map[string][]string{k: []string{v}} 68 return 69 } 70 existing, ok := r.Resources[k] 71 if !ok { 72 // Key doesn't exist, create it 73 r.Resources[k] = []string{v} 74 return 75 } 76 for _, item := range existing { 77 if item == v { 78 // resource exists, return 79 return 80 } 81 } 82 83 // Resource type exists but value did not, append it 84 r.Resources[k] = append(existing, v) 85 return 86 } 87 88 // Remove a resource. Return true if removed, otherwise false. 89 // 90 // Removes the entire key if the needle is the last value in the list. 91 func (r *CreatedResources) Remove(k, needle string) bool { 92 haystack := r.Resources[k] 93 for i, item := range haystack { 94 if item == needle { 95 r.Resources[k] = append(haystack[:i], haystack[i+1:]...) 96 if len(r.Resources[k]) == 0 { 97 delete(r.Resources, k) 98 } 99 return true 100 } 101 } 102 return false 103 } 104 105 // Copy returns a new deep copy of CreatedResrouces. 106 func (r *CreatedResources) Copy() *CreatedResources { 107 if r == nil { 108 return nil 109 } 110 111 newr := CreatedResources{ 112 Resources: make(map[string][]string, len(r.Resources)), 113 } 114 for k, v := range r.Resources { 115 newv := make([]string, len(v)) 116 copy(newv, v) 117 newr.Resources[k] = newv 118 } 119 return &newr 120 } 121 122 // Merge another CreatedResources into this one. If the other CreatedResources 123 // is nil this method is a noop. 124 func (r *CreatedResources) Merge(o *CreatedResources) { 125 if o == nil { 126 return 127 } 128 129 for k, v := range o.Resources { 130 // New key 131 if len(r.Resources[k]) == 0 { 132 r.Resources[k] = v 133 continue 134 } 135 136 // Existing key 137 OUTER: 138 for _, item := range v { 139 for _, existing := range r.Resources[k] { 140 if item == existing { 141 // Found it, move on 142 continue OUTER 143 } 144 } 145 146 // New item, append it 147 r.Resources[k] = append(r.Resources[k], item) 148 } 149 } 150 } 151 152 // Driver is used for execution of tasks. This allows Nomad 153 // to support many pluggable implementations of task drivers. 154 // Examples could include LXC, Docker, Qemu, etc. 155 type Driver interface { 156 // Drivers must support the fingerprint interface for detection 157 fingerprint.Fingerprint 158 159 // Prestart prepares the task environment and performs expensive 160 // intialization steps like downloading images. 161 // 162 // CreatedResources may be non-nil even when an error occurs. 163 Prestart(*ExecContext, *structs.Task) (*CreatedResources, error) 164 165 // Start is used to being task execution 166 Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) 167 168 // Open is used to re-open a handle to a task 169 Open(ctx *ExecContext, handleID string) (DriverHandle, error) 170 171 // Cleanup is called to remove resources which were created for a task 172 // and no longer needed. Cleanup is not called if CreatedResources is 173 // nil. 174 // 175 // If Cleanup returns a recoverable error it may be retried. On retry 176 // it will be passed the same CreatedResources, so all successfully 177 // cleaned up resources should be removed. 178 Cleanup(*ExecContext, *CreatedResources) error 179 180 // Drivers must validate their configuration 181 Validate(map[string]interface{}) error 182 183 // Abilities returns the abilities of the driver 184 Abilities() DriverAbilities 185 186 // FSIsolation returns the method of filesystem isolation used 187 FSIsolation() cstructs.FSIsolation 188 } 189 190 // DriverAbilities marks the abilities the driver has. 191 type DriverAbilities struct { 192 // SendSignals marks the driver as being able to send signals 193 SendSignals bool 194 } 195 196 // LogEventFn is a callback which allows Drivers to emit task events. 197 type LogEventFn func(message string, args ...interface{}) 198 199 // DriverContext is a means to inject dependencies such as loggers, configs, and 200 // node attributes into a Driver without having to change the Driver interface 201 // each time we do it. Used in conjection with Factory, above. 202 type DriverContext struct { 203 taskName string 204 allocID string 205 config *config.Config 206 logger *log.Logger 207 node *structs.Node 208 taskEnv *env.TaskEnvironment 209 210 emitEvent LogEventFn 211 } 212 213 // NewEmptyDriverContext returns a DriverContext with all fields set to their 214 // zero value. 215 func NewEmptyDriverContext() *DriverContext { 216 return &DriverContext{} 217 } 218 219 // NewDriverContext initializes a new DriverContext with the specified fields. 220 // This enables other packages to create DriverContexts but keeps the fields 221 // private to the driver. If we want to change this later we can gorename all of 222 // the fields in DriverContext. 223 func NewDriverContext(taskName, allocID string, config *config.Config, node *structs.Node, 224 logger *log.Logger, taskEnv *env.TaskEnvironment, eventEmitter LogEventFn) *DriverContext { 225 return &DriverContext{ 226 taskName: taskName, 227 allocID: allocID, 228 config: config, 229 node: node, 230 logger: logger, 231 taskEnv: taskEnv, 232 emitEvent: eventEmitter, 233 } 234 } 235 236 // DriverHandle is an opaque handle into a driver used for task 237 // manipulation 238 type DriverHandle interface { 239 // Returns an opaque handle that can be used to re-open the handle 240 ID() string 241 242 // WaitCh is used to return a channel used wait for task completion 243 WaitCh() chan *dstructs.WaitResult 244 245 // Update is used to update the task if possible and update task related 246 // configurations. 247 Update(task *structs.Task) error 248 249 // Kill is used to stop the task 250 Kill() error 251 252 // Stats returns aggregated stats of the driver 253 Stats() (*cstructs.TaskResourceUsage, error) 254 255 // Signal is used to send a signal to the task 256 Signal(s os.Signal) error 257 } 258 259 // ExecContext is a task's execution context 260 type ExecContext struct { 261 // TaskDir contains information about the task directory structure. 262 TaskDir *allocdir.TaskDir 263 } 264 265 // NewExecContext is used to create a new execution context 266 func NewExecContext(td *allocdir.TaskDir) *ExecContext { 267 return &ExecContext{ 268 TaskDir: td, 269 } 270 } 271 272 // GetTaskEnv converts the alloc dir, the node, task and alloc into a 273 // TaskEnvironment. 274 func GetTaskEnv(taskDir *allocdir.TaskDir, node *structs.Node, 275 task *structs.Task, alloc *structs.Allocation, conf *config.Config, 276 vaultToken string) (*env.TaskEnvironment, error) { 277 278 env := env.NewTaskEnvironment(node). 279 SetTaskMeta(alloc.Job.CombinedTaskMeta(alloc.TaskGroup, task.Name)). 280 SetJobName(alloc.Job.Name). 281 SetEnvvars(task.Env). 282 SetTaskName(task.Name) 283 284 // Vary paths by filesystem isolation used 285 drv, err := NewDriver(task.Driver, NewEmptyDriverContext()) 286 if err != nil { 287 return nil, err 288 } 289 switch drv.FSIsolation() { 290 case cstructs.FSIsolationNone: 291 // Use host paths 292 env.SetAllocDir(taskDir.SharedAllocDir) 293 env.SetTaskLocalDir(taskDir.LocalDir) 294 env.SetSecretsDir(taskDir.SecretsDir) 295 default: 296 // filesystem isolation; use container paths 297 env.SetAllocDir(allocdir.SharedAllocContainerPath) 298 env.SetTaskLocalDir(allocdir.TaskLocalContainerPath) 299 env.SetSecretsDir(allocdir.TaskSecretsContainerPath) 300 } 301 302 if task.Resources != nil { 303 env.SetMemLimit(task.Resources.MemoryMB). 304 SetCpuLimit(task.Resources.CPU). 305 SetNetworks(task.Resources.Networks) 306 } 307 308 if alloc != nil { 309 env.SetAlloc(alloc) 310 } 311 312 if task.Vault != nil { 313 env.SetVaultToken(vaultToken, task.Vault.Env) 314 } 315 316 // Set the host environment variables for non-image based drivers 317 if drv.FSIsolation() != cstructs.FSIsolationImage { 318 filter := strings.Split(conf.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 319 env.AppendHostEnvvars(filter) 320 } 321 322 return env.Build(), nil 323 } 324 325 func mapMergeStrInt(maps ...map[string]int) map[string]int { 326 out := map[string]int{} 327 for _, in := range maps { 328 for key, val := range in { 329 out[key] = val 330 } 331 } 332 return out 333 } 334 335 func mapMergeStrStr(maps ...map[string]string) map[string]string { 336 out := map[string]string{} 337 for _, in := range maps { 338 for key, val := range in { 339 out[key] = val 340 } 341 } 342 return out 343 }