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