github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/client/driver/rkt.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "log" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/hashicorp/go-version" 18 "github.com/hashicorp/nomad/client/allocdir" 19 "github.com/hashicorp/nomad/client/config" 20 cstructs "github.com/hashicorp/nomad/client/driver/structs" 21 "github.com/hashicorp/nomad/client/fingerprint" 22 "github.com/hashicorp/nomad/nomad/structs" 23 "github.com/mitchellh/mapstructure" 24 ) 25 26 var ( 27 reRktVersion = regexp.MustCompile(`rkt version (\d[.\d]+)`) 28 reAppcVersion = regexp.MustCompile(`appc version (\d[.\d]+)`) 29 ) 30 31 const ( 32 // minRktVersion is the earliest supported version of rkt. rkt added support 33 // for CPU and memory isolators in 0.14.0. We cannot support an earlier 34 // version to maintain an uniform interface across all drivers 35 minRktVersion = "0.14.0" 36 37 // bytesToMB is the conversion from bytes to megabytes. 38 bytesToMB = 1024 * 1024 39 ) 40 41 // RktDriver is a driver for running images via Rkt 42 // We attempt to chose sane defaults for now, with more configuration available 43 // planned in the future 44 type RktDriver struct { 45 DriverContext 46 fingerprint.StaticFingerprinter 47 } 48 49 type RktDriverConfig struct { 50 ImageName string `mapstructure:"image"` 51 Args []string `mapstructure:"args"` 52 } 53 54 // rktHandle is returned from Start/Open as a handle to the PID 55 type rktHandle struct { 56 proc *os.Process 57 image string 58 logger *log.Logger 59 killTimeout time.Duration 60 waitCh chan *cstructs.WaitResult 61 doneCh chan struct{} 62 } 63 64 // rktPID is a struct to map the pid running the process to the vm image on 65 // disk 66 type rktPID struct { 67 Pid int 68 Image string 69 KillTimeout time.Duration 70 } 71 72 // NewRktDriver is used to create a new exec driver 73 func NewRktDriver(ctx *DriverContext) Driver { 74 return &RktDriver{DriverContext: *ctx} 75 } 76 77 func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 78 // Only enable if we are root when running on non-windows systems. 79 if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { 80 d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling") 81 return false, nil 82 } 83 84 outBytes, err := exec.Command("rkt", "version").Output() 85 if err != nil { 86 return false, nil 87 } 88 out := strings.TrimSpace(string(outBytes)) 89 90 rktMatches := reRktVersion.FindStringSubmatch(out) 91 appcMatches := reAppcVersion.FindStringSubmatch(out) 92 if len(rktMatches) != 2 || len(appcMatches) != 2 { 93 return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches) 94 } 95 96 node.Attributes["driver.rkt"] = "1" 97 node.Attributes["driver.rkt.version"] = rktMatches[1] 98 node.Attributes["driver.rkt.appc.version"] = appcMatches[1] 99 100 minVersion, _ := version.NewVersion(minRktVersion) 101 currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"]) 102 if currentVersion.LessThan(minVersion) { 103 // Do not allow rkt < 0.14.0 104 d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion) 105 node.Attributes["driver.rkt"] = "0" 106 } 107 return true, nil 108 } 109 110 // Run an existing Rkt image. 111 func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 112 var driverConfig RktDriverConfig 113 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 114 return nil, err 115 } 116 // Validate that the config is valid. 117 img := driverConfig.ImageName 118 if img == "" { 119 return nil, fmt.Errorf("Missing ACI image for rkt") 120 } 121 122 // Get the tasks local directory. 123 taskName := d.DriverContext.taskName 124 taskDir, ok := ctx.AllocDir.TaskDirs[taskName] 125 if !ok { 126 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 127 } 128 taskLocal := filepath.Join(taskDir, allocdir.TaskLocal) 129 130 // Build the command. 131 var cmdArgs []string 132 133 // Add the given trust prefix 134 trustPrefix, trustCmd := task.Config["trust_prefix"] 135 if trustCmd { 136 var outBuf, errBuf bytes.Buffer 137 cmd := exec.Command("rkt", "trust", fmt.Sprintf("--prefix=%s", trustPrefix)) 138 cmd.Stdout = &outBuf 139 cmd.Stderr = &errBuf 140 if err := cmd.Run(); err != nil { 141 return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s", 142 err, outBuf.String(), errBuf.String()) 143 } 144 d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix) 145 } else { 146 // Disble signature verification if the trust command was not run. 147 cmdArgs = append(cmdArgs, "--insecure-options=all") 148 } 149 150 d.taskEnv.SetAllocDir(filepath.Join("/", allocdir.SharedAllocName)). 151 SetTaskLocalDir(filepath.Join("/", allocdir.TaskLocal)).Build() 152 153 for k, v := range d.taskEnv.EnvMap() { 154 cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%v", k, v)) 155 } 156 157 // Append the run command. 158 cmdArgs = append(cmdArgs, "run", "--mds-register=false", img) 159 160 // Mount allc and task dirs 161 local, ok := ctx.AllocDir.TaskDirs[task.Name] 162 if !ok { 163 return nil, fmt.Errorf("Failed to find task local directory: %v", task.Name) 164 } 165 cmdArgs = append(cmdArgs, fmt.Sprintf("--volume %s,kind=empty,readOnly=false,source=%s --mount volume=data,target=%s", task.Name, local, ctx.AllocDir.SharedDir)) 166 167 // Check if the user has overriden the exec command. 168 if execCmd, ok := task.Config["command"]; ok { 169 cmdArgs = append(cmdArgs, fmt.Sprintf("--exec=%v", execCmd)) 170 } 171 172 if task.Resources.MemoryMB == 0 { 173 return nil, fmt.Errorf("Memory limit cannot be zero") 174 } 175 if task.Resources.CPU == 0 { 176 return nil, fmt.Errorf("CPU limit cannot be zero") 177 } 178 179 // Add memory isolator 180 cmdArgs = append(cmdArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB)*bytesToMB)) 181 182 // Add CPU isolator 183 cmdArgs = append(cmdArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU))) 184 185 // Add user passed arguments. 186 if len(driverConfig.Args) != 0 { 187 parsed := d.taskEnv.ParseAndReplace(driverConfig.Args) 188 189 // Need to start arguments with "--" 190 if len(parsed) > 0 { 191 cmdArgs = append(cmdArgs, "--") 192 } 193 194 for _, arg := range parsed { 195 cmdArgs = append(cmdArgs, fmt.Sprintf("%v", arg)) 196 } 197 } 198 199 // Create files to capture stdin and out. 200 stdoutFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stdout", taskName)) 201 stderrFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stderr", taskName)) 202 203 stdo, err := os.OpenFile(stdoutFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) 204 if err != nil { 205 return nil, fmt.Errorf("Error opening file to redirect stdout: %v", err) 206 } 207 208 stde, err := os.OpenFile(stderrFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) 209 if err != nil { 210 return nil, fmt.Errorf("Error opening file to redirect stderr: %v", err) 211 } 212 213 cmd := exec.Command("rkt", cmdArgs...) 214 cmd.Stdout = stdo 215 cmd.Stderr = stde 216 217 if err := cmd.Start(); err != nil { 218 return nil, fmt.Errorf("Error running rkt: %v", err) 219 } 220 221 d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmd.Args) 222 h := &rktHandle{ 223 proc: cmd.Process, 224 image: img, 225 logger: d.logger, 226 killTimeout: d.DriverContext.KillTimeout(task), 227 doneCh: make(chan struct{}), 228 waitCh: make(chan *cstructs.WaitResult, 1), 229 } 230 go h.run() 231 return h, nil 232 } 233 234 func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 235 // Parse the handle 236 pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:")) 237 qpid := &rktPID{} 238 if err := json.Unmarshal(pidBytes, qpid); err != nil { 239 return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err) 240 } 241 242 // Find the process 243 proc, err := os.FindProcess(qpid.Pid) 244 if proc == nil || err != nil { 245 return nil, fmt.Errorf("failed to find Rkt PID %d: %v", qpid.Pid, err) 246 } 247 248 // Return a driver handle 249 h := &rktHandle{ 250 proc: proc, 251 image: qpid.Image, 252 logger: d.logger, 253 killTimeout: qpid.KillTimeout, 254 doneCh: make(chan struct{}), 255 waitCh: make(chan *cstructs.WaitResult, 1), 256 } 257 258 go h.run() 259 return h, nil 260 } 261 262 func (h *rktHandle) ID() string { 263 // Return a handle to the PID 264 pid := &rktPID{ 265 Pid: h.proc.Pid, 266 Image: h.image, 267 KillTimeout: h.killTimeout, 268 } 269 data, err := json.Marshal(pid) 270 if err != nil { 271 h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err) 272 } 273 return fmt.Sprintf("Rkt:%s", string(data)) 274 } 275 276 func (h *rktHandle) WaitCh() chan *cstructs.WaitResult { 277 return h.waitCh 278 } 279 280 func (h *rktHandle) Update(task *structs.Task) error { 281 // Update is not possible 282 return nil 283 } 284 285 // Kill is used to terminate the task. We send an Interrupt 286 // and then provide a 5 second grace period before doing a Kill. 287 func (h *rktHandle) Kill() error { 288 h.proc.Signal(os.Interrupt) 289 select { 290 case <-h.doneCh: 291 return nil 292 case <-time.After(h.killTimeout): 293 return h.proc.Kill() 294 } 295 } 296 297 func (h *rktHandle) run() { 298 ps, err := h.proc.Wait() 299 close(h.doneCh) 300 code := 0 301 if !ps.Success() { 302 // TODO: Better exit code parsing. 303 code = 1 304 } 305 h.waitCh <- cstructs.NewWaitResult(code, 0, err) 306 close(h.waitCh) 307 }