github.com/djenriquez/nomad-1@v0.8.1/client/driver/rkt.go (about) 1 // +build linux 2 3 package driver 4 5 import ( 6 "bytes" 7 "context" 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "log" 12 "math/rand" 13 "net" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "regexp" 18 "runtime" 19 "strconv" 20 "strings" 21 "syscall" 22 "time" 23 24 appcschema "github.com/appc/spec/schema" 25 rktv1 "github.com/rkt/rkt/api/v1" 26 27 "github.com/hashicorp/go-plugin" 28 "github.com/hashicorp/go-version" 29 "github.com/hashicorp/nomad/client/allocdir" 30 "github.com/hashicorp/nomad/client/config" 31 "github.com/hashicorp/nomad/client/driver/env" 32 "github.com/hashicorp/nomad/client/driver/executor" 33 dstructs "github.com/hashicorp/nomad/client/driver/structs" 34 cstructs "github.com/hashicorp/nomad/client/structs" 35 "github.com/hashicorp/nomad/helper" 36 "github.com/hashicorp/nomad/helper/fields" 37 "github.com/hashicorp/nomad/nomad/structs" 38 "github.com/mitchellh/mapstructure" 39 ) 40 41 var ( 42 reRktVersion = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`) 43 reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`) 44 ) 45 46 const ( 47 // minRktVersion is the earliest supported version of rkt. rkt added support 48 // for CPU and memory isolators in 0.14.0. We cannot support an earlier 49 // version to maintain an uniform interface across all drivers 50 minRktVersion = "1.27.0" 51 52 // The key populated in the Node Attributes to indicate the presence of the 53 // Rkt driver 54 rktDriverAttr = "driver.rkt" 55 56 // rktVolumesConfigOption is the key for enabling the use of custom 57 // bind volumes. 58 rktVolumesConfigOption = "rkt.volumes.enabled" 59 rktVolumesConfigDefault = true 60 61 // rktCmd is the command rkt is installed as. 62 rktCmd = "rkt" 63 64 // rktNetworkDeadline is how long to wait for container network to start 65 rktNetworkDeadline = 1 * time.Minute 66 ) 67 68 // RktDriver is a driver for running images via Rkt 69 // We attempt to chose sane defaults for now, with more configuration available 70 // planned in the future 71 type RktDriver struct { 72 DriverContext 73 74 // A tri-state boolean to know if the fingerprinting has happened and 75 // whether it has been successful 76 fingerprintSuccess *bool 77 } 78 79 type RktDriverConfig struct { 80 ImageName string `mapstructure:"image"` 81 Command string `mapstructure:"command"` 82 Args []string `mapstructure:"args"` 83 TrustPrefix string `mapstructure:"trust_prefix"` 84 DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers 85 DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers 86 Net []string `mapstructure:"net"` // Networks for the containers 87 PortMapRaw []map[string]string `mapstructure:"port_map"` // 88 PortMap map[string]string `mapstructure:"-"` // A map of host port and the port name defined in the image manifest file 89 Volumes []string `mapstructure:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container[:readOnly] 90 InsecureOptions []string `mapstructure:"insecure_options"` // list of args for --insecure-options 91 92 NoOverlay bool `mapstructure:"no_overlay"` // disable overlayfs for rkt run 93 Debug bool `mapstructure:"debug"` // Enable debug option for rkt command 94 Group string `mapstructure:"group"` // Group override for the container 95 } 96 97 // rktHandle is returned from Start/Open as a handle to the PID 98 type rktHandle struct { 99 uuid string 100 env *env.TaskEnv 101 taskDir *allocdir.TaskDir 102 pluginClient *plugin.Client 103 executorPid int 104 executor executor.Executor 105 logger *log.Logger 106 killTimeout time.Duration 107 maxKillTimeout time.Duration 108 waitCh chan *dstructs.WaitResult 109 doneCh chan struct{} 110 } 111 112 // rktPID is a struct to map the pid running the process to the vm image on 113 // disk 114 type rktPID struct { 115 UUID string 116 PluginConfig *PluginReattachConfig 117 ExecutorPid int 118 KillTimeout time.Duration 119 MaxKillTimeout time.Duration 120 } 121 122 // Retrieve pod status for the pod with the given UUID. 123 func rktGetStatus(uuid string, logger *log.Logger) (*rktv1.Pod, error) { 124 statusArgs := []string{ 125 "status", 126 "--format=json", 127 uuid, 128 } 129 var outBuf, errBuf bytes.Buffer 130 cmd := exec.Command(rktCmd, statusArgs...) 131 cmd.Stdout = &outBuf 132 cmd.Stderr = &errBuf 133 if err := cmd.Run(); err != nil { 134 if outBuf.Len() > 0 { 135 logger.Printf("[DEBUG] driver.rkt: status output for UUID %s: %q", uuid, elide(outBuf)) 136 } 137 if errBuf.Len() == 0 { 138 return nil, err 139 } 140 logger.Printf("[DEBUG] driver.rkt: status error output for UUID %s: %q", uuid, elide(errBuf)) 141 return nil, fmt.Errorf("%s. stderr: %q", err, elide(errBuf)) 142 } 143 var status rktv1.Pod 144 if err := json.Unmarshal(outBuf.Bytes(), &status); err != nil { 145 return nil, err 146 } 147 return &status, nil 148 } 149 150 // Retrieves a pod manifest 151 func rktGetManifest(uuid string) (*appcschema.PodManifest, error) { 152 statusArgs := []string{ 153 "cat-manifest", 154 uuid, 155 } 156 var outBuf bytes.Buffer 157 cmd := exec.Command(rktCmd, statusArgs...) 158 cmd.Stdout = &outBuf 159 cmd.Stderr = ioutil.Discard 160 if err := cmd.Run(); err != nil { 161 return nil, err 162 } 163 var manifest appcschema.PodManifest 164 if err := json.Unmarshal(outBuf.Bytes(), &manifest); err != nil { 165 return nil, err 166 } 167 return &manifest, nil 168 } 169 170 func rktGetDriverNetwork(uuid string, driverConfigPortMap map[string]string, logger *log.Logger) (*cstructs.DriverNetwork, error) { 171 deadline := time.Now().Add(rktNetworkDeadline) 172 var lastErr error 173 try := 0 174 175 for time.Now().Before(deadline) { 176 try++ 177 if status, err := rktGetStatus(uuid, logger); err == nil { 178 for _, net := range status.Networks { 179 if !net.IP.IsGlobalUnicast() { 180 continue 181 } 182 183 // Get the pod manifest so we can figure out which ports are exposed 184 var portmap map[string]int 185 manifest, err := rktGetManifest(uuid) 186 if err == nil { 187 portmap, err = rktManifestMakePortMap(manifest, driverConfigPortMap) 188 if err != nil { 189 lastErr = fmt.Errorf("could not create manifest-based portmap: %v", err) 190 return nil, lastErr 191 } 192 } else { 193 lastErr = fmt.Errorf("could not get pod manifest: %v", err) 194 return nil, lastErr 195 } 196 197 // This is a successful landing. 198 return &cstructs.DriverNetwork{ 199 PortMap: portmap, 200 IP: status.Networks[0].IP.String(), 201 }, nil 202 } 203 204 if len(status.Networks) == 0 { 205 lastErr = fmt.Errorf("no networks found") 206 } else { 207 lastErr = fmt.Errorf("no good driver networks out of %d returned", len(status.Networks)) 208 } 209 } else { 210 lastErr = fmt.Errorf("getting status failed: %v", err) 211 } 212 213 waitTime := getJitteredNetworkRetryTime() 214 logger.Printf("[DEBUG] driver.rkt: getting network info for pod UUID %s failed attempt %d: %v. Sleeping for %v", uuid, try, lastErr, waitTime) 215 time.Sleep(waitTime) 216 } 217 return nil, fmt.Errorf("timed out, last error: %v", lastErr) 218 } 219 220 // Given a rkt/appc pod manifest and driver portmap configuration, create 221 // a driver portmap. 222 func rktManifestMakePortMap(manifest *appcschema.PodManifest, configPortMap map[string]string) (map[string]int, error) { 223 if len(manifest.Apps) == 0 { 224 return nil, fmt.Errorf("manifest has no apps") 225 } 226 if len(manifest.Apps) != 1 { 227 return nil, fmt.Errorf("manifest has multiple apps!") 228 } 229 app := manifest.Apps[0] 230 if app.App == nil { 231 return nil, fmt.Errorf("specified app has no App object") 232 } 233 234 portMap := make(map[string]int) 235 for svc, name := range configPortMap { 236 for _, port := range app.App.Ports { 237 if port.Name.String() == name { 238 portMap[svc] = int(port.Port) 239 } 240 } 241 } 242 return portMap, nil 243 } 244 245 // rktRemove pod after it has exited. 246 func rktRemove(uuid string) error { 247 errBuf := &bytes.Buffer{} 248 cmd := exec.Command(rktCmd, "rm", uuid) 249 cmd.Stdout = ioutil.Discard 250 cmd.Stderr = errBuf 251 if err := cmd.Run(); err != nil { 252 if msg := errBuf.String(); len(msg) > 0 { 253 return fmt.Errorf("error removing pod: %s", msg) 254 } 255 return err 256 } 257 258 return nil 259 } 260 261 // NewRktDriver is used to create a new rkt driver 262 func NewRktDriver(ctx *DriverContext) Driver { 263 return &RktDriver{DriverContext: *ctx} 264 } 265 266 func (d *RktDriver) FSIsolation() cstructs.FSIsolation { 267 return cstructs.FSIsolationImage 268 } 269 270 // Validate is used to validate the driver configuration 271 func (d *RktDriver) Validate(config map[string]interface{}) error { 272 fd := &fields.FieldData{ 273 Raw: config, 274 Schema: map[string]*fields.FieldSchema{ 275 "image": { 276 Type: fields.TypeString, 277 Required: true, 278 }, 279 "command": { 280 Type: fields.TypeString, 281 }, 282 "args": { 283 Type: fields.TypeArray, 284 }, 285 "trust_prefix": { 286 Type: fields.TypeString, 287 }, 288 "dns_servers": { 289 Type: fields.TypeArray, 290 }, 291 "dns_search_domains": { 292 Type: fields.TypeArray, 293 }, 294 "net": { 295 Type: fields.TypeArray, 296 }, 297 "port_map": { 298 Type: fields.TypeArray, 299 }, 300 "debug": { 301 Type: fields.TypeBool, 302 }, 303 "volumes": { 304 Type: fields.TypeArray, 305 }, 306 "no_overlay": { 307 Type: fields.TypeBool, 308 }, 309 "insecure_options": { 310 Type: fields.TypeArray, 311 }, 312 "group": { 313 Type: fields.TypeString, 314 }, 315 }, 316 } 317 318 if err := fd.Validate(); err != nil { 319 return err 320 } 321 322 return nil 323 } 324 325 func (d *RktDriver) Abilities() DriverAbilities { 326 return DriverAbilities{ 327 SendSignals: false, 328 Exec: true, 329 } 330 } 331 332 func (d *RktDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 333 // Only enable if we are root when running on non-windows systems. 334 if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { 335 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 336 d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling") 337 } 338 d.fingerprintSuccess = helper.BoolToPtr(false) 339 resp.RemoveAttribute(rktDriverAttr) 340 return nil 341 } 342 343 outBytes, err := exec.Command(rktCmd, "version").Output() 344 if err != nil { 345 d.fingerprintSuccess = helper.BoolToPtr(false) 346 return nil 347 } 348 out := strings.TrimSpace(string(outBytes)) 349 350 rktMatches := reRktVersion.FindStringSubmatch(out) 351 appcMatches := reAppcVersion.FindStringSubmatch(out) 352 if len(rktMatches) != 2 || len(appcMatches) != 2 { 353 d.fingerprintSuccess = helper.BoolToPtr(false) 354 resp.RemoveAttribute(rktDriverAttr) 355 return fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches) 356 } 357 358 minVersion, _ := version.NewVersion(minRktVersion) 359 currentVersion, _ := version.NewVersion(rktMatches[1]) 360 if currentVersion.LessThan(minVersion) { 361 // Do not allow ancient rkt versions 362 if d.fingerprintSuccess == nil { 363 // Only log on first failure 364 d.logger.Printf("[WARN] driver.rkt: unsupported rkt version %s; please upgrade to >= %s", 365 currentVersion, minVersion) 366 } 367 d.fingerprintSuccess = helper.BoolToPtr(false) 368 resp.RemoveAttribute(rktDriverAttr) 369 return nil 370 } 371 372 // Output version information when the fingerprinter first sees rkt 373 if info, ok := req.Node.Drivers["rkt"]; ok && info != nil && !info.Detected { 374 d.logger.Printf("[DEBUG] driver.rkt: detect version: %s", strings.Replace(out, "\n", " ", -1)) 375 } 376 resp.AddAttribute(rktDriverAttr, "1") 377 resp.AddAttribute("driver.rkt.version", rktMatches[1]) 378 resp.AddAttribute("driver.rkt.appc.version", appcMatches[1]) 379 resp.Detected = true 380 381 // Advertise if this node supports rkt volumes 382 if d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault) { 383 resp.AddAttribute("driver."+rktVolumesConfigOption, "1") 384 } 385 d.fingerprintSuccess = helper.BoolToPtr(true) 386 return nil 387 } 388 389 func (d *RktDriver) Periodic() (bool, time.Duration) { 390 return true, 15 * time.Second 391 } 392 393 func (d *RktDriver) Prestart(ctx *ExecContext, task *structs.Task) (*PrestartResponse, error) { 394 return nil, nil 395 } 396 397 // Run an existing Rkt image. 398 func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 399 var driverConfig RktDriverConfig 400 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 401 return nil, err 402 } 403 404 driverConfig.PortMap = mapMergeStrStr(driverConfig.PortMapRaw...) 405 406 // ACI image 407 img := driverConfig.ImageName 408 409 // Global arguments given to both prepare and run-prepared 410 globalArgs := make([]string, 0, 50) 411 412 // Add debug option to rkt command. 413 debug := driverConfig.Debug 414 415 // Add the given trust prefix 416 trustPrefix := driverConfig.TrustPrefix 417 insecure := false 418 if trustPrefix != "" { 419 var outBuf, errBuf bytes.Buffer 420 cmd := exec.Command(rktCmd, "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix), fmt.Sprintf("--debug=%t", debug)) 421 cmd.Stdout = &outBuf 422 cmd.Stderr = &errBuf 423 if err := cmd.Run(); err != nil { 424 return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s", 425 err, outBuf.String(), errBuf.String()) 426 } 427 d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix) 428 } else { 429 // Disable signature verification if the trust command was not run. 430 insecure = true 431 } 432 433 // if we have a selective insecure_options, prefer them 434 // insecure options are rkt's global argument, so we do this before the actual "run" 435 if len(driverConfig.InsecureOptions) > 0 { 436 globalArgs = append(globalArgs, fmt.Sprintf("--insecure-options=%s", strings.Join(driverConfig.InsecureOptions, ","))) 437 } else if insecure { 438 globalArgs = append(globalArgs, "--insecure-options=all") 439 } 440 441 // debug is rkt's global argument, so add it before the actual "run" 442 globalArgs = append(globalArgs, fmt.Sprintf("--debug=%t", debug)) 443 444 prepareArgs := make([]string, 0, 50) 445 runArgs := make([]string, 0, 50) 446 447 prepareArgs = append(prepareArgs, globalArgs...) 448 prepareArgs = append(prepareArgs, "prepare") 449 runArgs = append(runArgs, globalArgs...) 450 runArgs = append(runArgs, "run-prepared") 451 452 // disable overlayfs 453 if driverConfig.NoOverlay { 454 prepareArgs = append(prepareArgs, "--no-overlay=true") 455 } 456 457 // Convert underscores to dashes in task names for use in volume names #2358 458 sanitizedName := strings.Replace(task.Name, "_", "-", -1) 459 460 // Mount /alloc 461 allocVolName := fmt.Sprintf("%s-%s-alloc", d.DriverContext.allocID, sanitizedName) 462 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", allocVolName, ctx.TaskDir.SharedAllocDir)) 463 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", allocVolName, ctx.TaskEnv.EnvMap[env.AllocDir])) 464 465 // Mount /local 466 localVolName := fmt.Sprintf("%s-%s-local", d.DriverContext.allocID, sanitizedName) 467 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", localVolName, ctx.TaskDir.LocalDir)) 468 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", localVolName, ctx.TaskEnv.EnvMap[env.TaskLocalDir])) 469 470 // Mount /secrets 471 secretsVolName := fmt.Sprintf("%s-%s-secrets", d.DriverContext.allocID, sanitizedName) 472 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", secretsVolName, ctx.TaskDir.SecretsDir)) 473 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", secretsVolName, ctx.TaskEnv.EnvMap[env.SecretsDir])) 474 475 // Mount arbitrary volumes if enabled 476 if len(driverConfig.Volumes) > 0 { 477 if enabled := d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault); !enabled { 478 return nil, fmt.Errorf("%s is false; cannot use rkt volumes: %+q", rktVolumesConfigOption, driverConfig.Volumes) 479 } 480 for i, rawvol := range driverConfig.Volumes { 481 parts := strings.Split(rawvol, ":") 482 readOnly := "false" 483 // job spec: 484 // volumes = ["/host/path:/container/path[:readOnly]"] 485 // the third parameter is optional, mount is read-write by default 486 if len(parts) == 3 { 487 if parts[2] == "readOnly" { 488 d.logger.Printf("[DEBUG] Mounting %s:%s as readOnly", parts[0], parts[1]) 489 readOnly = "true" 490 } else { 491 d.logger.Printf("[WARN] Unknown volume parameter '%s' ignored for mount %s", parts[2], parts[0]) 492 } 493 } else if len(parts) != 2 { 494 return nil, fmt.Errorf("invalid rkt volume: %q", rawvol) 495 } 496 volName := fmt.Sprintf("%s-%s-%d", d.DriverContext.allocID, sanitizedName, i) 497 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s,readOnly=%s", volName, parts[0], readOnly)) 498 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, parts[1])) 499 } 500 } 501 502 // Inject environment variables 503 for k, v := range ctx.TaskEnv.Map() { 504 prepareArgs = append(prepareArgs, fmt.Sprintf("--set-env=%s=%s", k, v)) 505 } 506 507 // Image is set here, because the commands that follow apply to it 508 prepareArgs = append(prepareArgs, img) 509 510 // Check if the user has overridden the exec command. 511 if driverConfig.Command != "" { 512 prepareArgs = append(prepareArgs, fmt.Sprintf("--exec=%v", driverConfig.Command)) 513 } 514 515 // Add memory isolator 516 prepareArgs = append(prepareArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB))) 517 518 // Add CPU isolator 519 prepareArgs = append(prepareArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU))) 520 521 // Add DNS servers 522 if len(driverConfig.DNSServers) == 1 && (driverConfig.DNSServers[0] == "host" || driverConfig.DNSServers[0] == "none") { 523 // Special case single item lists with the special values "host" or "none" 524 runArgs = append(runArgs, fmt.Sprintf("--dns=%s", driverConfig.DNSServers[0])) 525 } else { 526 for _, ip := range driverConfig.DNSServers { 527 if err := net.ParseIP(ip); err == nil { 528 msg := fmt.Errorf("invalid ip address for container dns server %q", ip) 529 d.logger.Printf("[DEBUG] driver.rkt: %v", msg) 530 return nil, msg 531 } else { 532 runArgs = append(runArgs, fmt.Sprintf("--dns=%s", ip)) 533 } 534 } 535 } 536 537 // set DNS search domains 538 for _, domain := range driverConfig.DNSSearchDomains { 539 runArgs = append(runArgs, fmt.Sprintf("--dns-search=%s", domain)) 540 } 541 542 // set network 543 network := strings.Join(driverConfig.Net, ",") 544 if network != "" { 545 runArgs = append(runArgs, fmt.Sprintf("--net=%s", network)) 546 } 547 548 // Setup port mapping and exposed ports 549 if len(task.Resources.Networks) == 0 { 550 d.logger.Println("[DEBUG] driver.rkt: No network interfaces are available") 551 if len(driverConfig.PortMap) > 0 { 552 return nil, fmt.Errorf("Trying to map ports but no network interface is available") 553 } 554 } else if network == "host" { 555 // Port mapping is skipped when host networking is used. 556 d.logger.Println("[DEBUG] driver.rkt: Ignoring port_map when using --net=host") 557 } else { 558 // TODO add support for more than one network 559 network := task.Resources.Networks[0] 560 for _, port := range network.ReservedPorts { 561 var containerPort string 562 563 mapped, ok := driverConfig.PortMap[port.Label] 564 if !ok { 565 // If the user doesn't have a mapped port using port_map, driver stops running container. 566 return nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 567 } 568 containerPort = mapped 569 570 hostPortStr := strconv.Itoa(port.Value) 571 572 d.logger.Printf("[DEBUG] driver.rkt: exposed port %s", containerPort) 573 // Add port option to rkt run arguments. rkt allows multiple port args 574 prepareArgs = append(prepareArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 575 } 576 577 for _, port := range network.DynamicPorts { 578 // By default we will map the allocated port 1:1 to the container 579 var containerPort string 580 581 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 582 containerPort = mapped 583 } else { 584 // If the user doesn't have mapped a port using port_map, driver stops running container. 585 return nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 586 } 587 588 hostPortStr := strconv.Itoa(port.Value) 589 590 d.logger.Printf("[DEBUG] driver.rkt: exposed port %s", containerPort) 591 // Add port option to rkt run arguments. rkt allows multiple port args 592 prepareArgs = append(prepareArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 593 } 594 595 } 596 597 // If a user has been specified for the task, pass it through to the user 598 if task.User != "" { 599 prepareArgs = append(prepareArgs, fmt.Sprintf("--user=%s", task.User)) 600 } 601 602 // There's no task-level parameter for groups so check the driver 603 // config for a custom group 604 if driverConfig.Group != "" { 605 prepareArgs = append(prepareArgs, fmt.Sprintf("--group=%s", driverConfig.Group)) 606 } 607 608 // Add user passed arguments. 609 if len(driverConfig.Args) != 0 { 610 parsed := ctx.TaskEnv.ParseAndReplace(driverConfig.Args) 611 612 // Need to start arguments with "--" 613 if len(parsed) > 0 { 614 prepareArgs = append(prepareArgs, "--") 615 } 616 617 for _, arg := range parsed { 618 prepareArgs = append(prepareArgs, fmt.Sprintf("%v", arg)) 619 } 620 } 621 622 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%s-executor.out", task.Name)) 623 executorConfig := &dstructs.ExecutorConfig{ 624 LogFile: pluginLogFile, 625 LogLevel: d.config.LogLevel, 626 } 627 628 execIntf, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 629 if err != nil { 630 return nil, err 631 } 632 633 absPath, err := GetAbsolutePath(rktCmd) 634 if err != nil { 635 return nil, err 636 } 637 638 var outBuf, errBuf bytes.Buffer 639 cmd := exec.Command(rktCmd, prepareArgs...) 640 cmd.Stdout = &outBuf 641 cmd.Stderr = &errBuf 642 d.logger.Printf("[DEBUG] driver.rkt: preparing pod %q for task %q with: %v", img, d.taskName, prepareArgs) 643 if err := cmd.Run(); err != nil { 644 return nil, fmt.Errorf("Error preparing rkt pod: %s\n\nOutput: %s\n\nError: %s", 645 err, outBuf.String(), errBuf.String()) 646 } 647 uuid := strings.TrimSpace(outBuf.String()) 648 d.logger.Printf("[DEBUG] driver.rkt: pod %q for task %q prepared. (UUID: %s)", img, d.taskName, uuid) 649 runArgs = append(runArgs, uuid) 650 651 // The task's environment is set via --set-env flags above, but the rkt 652 // command itself needs an evironment with PATH set to find iptables. 653 eb := env.NewEmptyBuilder() 654 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 655 rktEnv := eb.SetHostEnvvars(filter).Build() 656 executorCtx := &executor.ExecutorContext{ 657 TaskEnv: rktEnv, 658 Driver: "rkt", 659 Task: task, 660 TaskDir: ctx.TaskDir.Dir, 661 LogDir: ctx.TaskDir.LogDir, 662 } 663 if err := execIntf.SetContext(executorCtx); err != nil { 664 pluginClient.Kill() 665 return nil, fmt.Errorf("failed to set executor context: %v", err) 666 } 667 668 execCmd := &executor.ExecCommand{ 669 Cmd: absPath, 670 Args: runArgs, 671 } 672 ps, err := execIntf.LaunchCmd(execCmd) 673 if err != nil { 674 pluginClient.Kill() 675 return nil, err 676 } 677 678 d.logger.Printf("[DEBUG] driver.rkt: started ACI %q (UUID: %s) for task %q with: %v", img, uuid, d.taskName, runArgs) 679 maxKill := d.DriverContext.config.MaxKillTimeout 680 h := &rktHandle{ 681 uuid: uuid, 682 env: rktEnv, 683 taskDir: ctx.TaskDir, 684 pluginClient: pluginClient, 685 executor: execIntf, 686 executorPid: ps.Pid, 687 logger: d.logger, 688 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 689 maxKillTimeout: maxKill, 690 doneCh: make(chan struct{}), 691 waitCh: make(chan *dstructs.WaitResult, 1), 692 } 693 go h.run() 694 695 // Only return a driver network if *not* using host networking 696 var driverNetwork *cstructs.DriverNetwork 697 if network != "host" { 698 d.logger.Printf("[DEBUG] driver.rkt: retrieving network information for pod %q (UUID: %s) for task %q", img, uuid, d.taskName) 699 driverNetwork, err = rktGetDriverNetwork(uuid, driverConfig.PortMap, d.logger) 700 if err != nil && !pluginClient.Exited() { 701 d.logger.Printf("[WARN] driver.rkt: network status retrieval for pod %q (UUID: %s) for task %q failed. Last error: %v", img, uuid, d.taskName, err) 702 703 // If a portmap was given, this turns into a fatal error 704 if len(driverConfig.PortMap) != 0 { 705 pluginClient.Kill() 706 return nil, fmt.Errorf("Trying to map ports but driver could not determine network information") 707 } 708 } 709 } 710 711 return &StartResponse{Handle: h, Network: driverNetwork}, nil 712 } 713 714 func (d *RktDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 715 716 func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 717 // Parse the handle 718 pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:")) 719 id := &rktPID{} 720 if err := json.Unmarshal(pidBytes, id); err != nil { 721 return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err) 722 } 723 724 pluginConfig := &plugin.ClientConfig{ 725 Reattach: id.PluginConfig.PluginConfig(), 726 } 727 exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 728 if err != nil { 729 d.logger.Println("[ERR] driver.rkt: error connecting to plugin so destroying plugin pid and user pid") 730 if e := destroyPlugin(id.PluginConfig.Pid, id.ExecutorPid); e != nil { 731 d.logger.Printf("[ERR] driver.rkt: error destroying plugin and executor pid: %v", e) 732 } 733 return nil, fmt.Errorf("error connecting to plugin: %v", err) 734 } 735 736 // The task's environment is set via --set-env flags in Start, but the rkt 737 // command itself needs an evironment with PATH set to find iptables. 738 eb := env.NewEmptyBuilder() 739 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 740 rktEnv := eb.SetHostEnvvars(filter).Build() 741 742 ver, _ := exec.Version() 743 d.logger.Printf("[DEBUG] driver.rkt: version of executor: %v", ver.Version) 744 // Return a driver handle 745 h := &rktHandle{ 746 uuid: id.UUID, 747 env: rktEnv, 748 taskDir: ctx.TaskDir, 749 pluginClient: pluginClient, 750 executorPid: id.ExecutorPid, 751 executor: exec, 752 logger: d.logger, 753 killTimeout: id.KillTimeout, 754 maxKillTimeout: id.MaxKillTimeout, 755 doneCh: make(chan struct{}), 756 waitCh: make(chan *dstructs.WaitResult, 1), 757 } 758 go h.run() 759 return h, nil 760 } 761 762 func (h *rktHandle) ID() string { 763 // Return a handle to the PID 764 pid := &rktPID{ 765 UUID: h.uuid, 766 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 767 KillTimeout: h.killTimeout, 768 MaxKillTimeout: h.maxKillTimeout, 769 ExecutorPid: h.executorPid, 770 } 771 data, err := json.Marshal(pid) 772 if err != nil { 773 h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err) 774 } 775 return fmt.Sprintf("Rkt:%s", string(data)) 776 } 777 778 func (h *rktHandle) WaitCh() chan *dstructs.WaitResult { 779 return h.waitCh 780 } 781 782 func (h *rktHandle) Update(task *structs.Task) error { 783 // Store the updated kill timeout. 784 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 785 h.executor.UpdateTask(task) 786 787 // Update is not possible 788 return nil 789 } 790 791 func (h *rktHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 792 if h.uuid == "" { 793 return nil, 0, fmt.Errorf("unable to find rkt pod UUID") 794 } 795 // enter + UUID + cmd + args... 796 enterArgs := make([]string, 3+len(args)) 797 enterArgs[0] = "enter" 798 enterArgs[1] = h.uuid 799 enterArgs[2] = cmd 800 copy(enterArgs[3:], args) 801 return executor.ExecScript(ctx, h.taskDir.Dir, h.env, nil, rktCmd, enterArgs) 802 } 803 804 func (h *rktHandle) Signal(s os.Signal) error { 805 return fmt.Errorf("Rkt does not support signals") 806 } 807 808 // Kill is used to terminate the task. We send an Interrupt 809 // and then provide a 5 second grace period before doing a Kill. 810 func (h *rktHandle) Kill() error { 811 h.executor.ShutDown() 812 select { 813 case <-h.doneCh: 814 return nil 815 case <-time.After(h.killTimeout): 816 return h.executor.Exit() 817 } 818 } 819 820 func (h *rktHandle) Stats() (*cstructs.TaskResourceUsage, error) { 821 return nil, DriverStatsNotImplemented 822 } 823 824 func (h *rktHandle) run() { 825 ps, werr := h.executor.Wait() 826 close(h.doneCh) 827 if ps.ExitCode == 0 && werr != nil { 828 if e := killProcess(h.executorPid); e != nil { 829 h.logger.Printf("[ERR] driver.rkt: error killing user process: %v", e) 830 } 831 } 832 833 // Exit the executor 834 if err := h.executor.Exit(); err != nil { 835 h.logger.Printf("[ERR] driver.rkt: error killing executor: %v", err) 836 } 837 h.pluginClient.Kill() 838 839 // Remove the pod 840 if err := rktRemove(h.uuid); err != nil { 841 h.logger.Printf("[ERR] driver.rkt: error removing pod (UUID: %q) - must gc manually: %v", h.uuid, err) 842 } else { 843 h.logger.Printf("[DEBUG] driver.rkt: removed pod (UUID: %q)", h.uuid) 844 } 845 846 // Send the results 847 h.waitCh <- dstructs.NewWaitResult(ps.ExitCode, 0, werr) 848 close(h.waitCh) 849 } 850 851 // Create a time with a 0 to 100ms jitter for rktGetDriverNetwork retries 852 func getJitteredNetworkRetryTime() time.Duration { 853 return time.Duration(900+rand.Intn(100)) * time.Millisecond 854 } 855 856 // Conditionally elide a buffer to an arbitrary length 857 func elideToLen(inBuf bytes.Buffer, length int) bytes.Buffer { 858 if inBuf.Len() > length { 859 inBuf.Truncate(length) 860 inBuf.WriteString("...") 861 } 862 return inBuf 863 } 864 865 // Conditionally elide a buffer to an 80 character string 866 func elide(inBuf bytes.Buffer) string { 867 tempBuf := elideToLen(inBuf, 80) 868 return tempBuf.String() 869 }