github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/drivers/java/driver.go (about) 1 package java 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "runtime" 10 "time" 11 12 "github.com/hashicorp/consul-template/signals" 13 hclog "github.com/hashicorp/go-hclog" 14 "github.com/hashicorp/nomad/client/fingerprint" 15 "github.com/hashicorp/nomad/drivers/shared/eventer" 16 "github.com/hashicorp/nomad/drivers/shared/executor" 17 "github.com/hashicorp/nomad/drivers/shared/resolvconf" 18 "github.com/hashicorp/nomad/helper/pluginutils/loader" 19 "github.com/hashicorp/nomad/plugins/base" 20 "github.com/hashicorp/nomad/plugins/drivers" 21 "github.com/hashicorp/nomad/plugins/drivers/utils" 22 "github.com/hashicorp/nomad/plugins/shared/hclspec" 23 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 24 ) 25 26 const ( 27 // pluginName is the name of the plugin 28 pluginName = "java" 29 30 // fingerprintPeriod is the interval at which the driver will send fingerprint responses 31 fingerprintPeriod = 30 * time.Second 32 33 // The key populated in Node Attributes to indicate presence of the Java driver 34 driverAttr = "driver.java" 35 driverVersionAttr = "driver.java.version" 36 37 // taskHandleVersion is the version of task handle which this driver sets 38 // and understands how to decode driver state 39 taskHandleVersion = 1 40 ) 41 42 var ( 43 // PluginID is the java plugin metadata registered in the plugin 44 // catalog. 45 PluginID = loader.PluginID{ 46 Name: pluginName, 47 PluginType: base.PluginTypeDriver, 48 } 49 50 // PluginConfig is the java driver factory function registered in the 51 // plugin catalog. 52 PluginConfig = &loader.InternalPluginConfig{ 53 Config: map[string]interface{}{}, 54 Factory: func(ctx context.Context, l hclog.Logger) interface{} { return NewDriver(ctx, l) }, 55 } 56 57 // pluginInfo is the response returned for the PluginInfo RPC 58 pluginInfo = &base.PluginInfoResponse{ 59 Type: base.PluginTypeDriver, 60 PluginApiVersions: []string{drivers.ApiVersion010}, 61 PluginVersion: "0.1.0", 62 Name: pluginName, 63 } 64 65 // configSpec is the hcl specification returned by the ConfigSchema RPC 66 configSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 67 "default_pid_mode": hclspec.NewDefault( 68 hclspec.NewAttr("default_pid_mode", "string", false), 69 hclspec.NewLiteral(`"private"`), 70 ), 71 "default_ipc_mode": hclspec.NewDefault( 72 hclspec.NewAttr("default_ipc_mode", "string", false), 73 hclspec.NewLiteral(`"private"`), 74 ), 75 }) 76 77 // taskConfigSpec is the hcl specification for the driver config section of 78 // a taskConfig within a job. It is returned in the TaskConfigSchema RPC 79 taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 80 // It's required for either `class` or `jar_path` to be set, 81 // but that's not expressable in hclspec. Marking both as optional 82 // and setting checking explicitly later 83 "class": hclspec.NewAttr("class", "string", false), 84 "class_path": hclspec.NewAttr("class_path", "string", false), 85 "jar_path": hclspec.NewAttr("jar_path", "string", false), 86 "jvm_options": hclspec.NewAttr("jvm_options", "list(string)", false), 87 "args": hclspec.NewAttr("args", "list(string)", false), 88 "pid_mode": hclspec.NewAttr("pid_mode", "string", false), 89 "ipc_mode": hclspec.NewAttr("ipc_mode", "string", false), 90 }) 91 92 // capabilities is returned by the Capabilities RPC and indicates what 93 // optional features this driver supports 94 capabilities = &drivers.Capabilities{ 95 SendSignals: false, 96 Exec: false, 97 FSIsolation: drivers.FSIsolationNone, 98 NetIsolationModes: []drivers.NetIsolationMode{ 99 drivers.NetIsolationModeHost, 100 drivers.NetIsolationModeGroup, 101 }, 102 MountConfigs: drivers.MountConfigSupportNone, 103 } 104 105 _ drivers.DriverPlugin = (*Driver)(nil) 106 ) 107 108 func init() { 109 if runtime.GOOS == "linux" { 110 capabilities.FSIsolation = drivers.FSIsolationChroot 111 capabilities.MountConfigs = drivers.MountConfigSupportAll 112 } 113 } 114 115 // Config is the driver configuration set by the SetConfig RPC call 116 type Config struct { 117 // DefaultModePID is the default PID isolation set for all tasks using 118 // exec-based task drivers. 119 DefaultModePID string `codec:"default_pid_mode"` 120 121 // DefaultModeIPC is the default IPC isolation set for all tasks using 122 // exec-based task drivers. 123 DefaultModeIPC string `codec:"default_ipc_mode"` 124 } 125 126 func (c *Config) validate() error { 127 switch c.DefaultModePID { 128 case executor.IsolationModePrivate, executor.IsolationModeHost: 129 default: 130 return fmt.Errorf("default_pid_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, c.DefaultModePID) 131 } 132 133 switch c.DefaultModeIPC { 134 case executor.IsolationModePrivate, executor.IsolationModeHost: 135 default: 136 return fmt.Errorf("default_ipc_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, c.DefaultModeIPC) 137 } 138 139 return nil 140 } 141 142 // TaskConfig is the driver configuration of a taskConfig within a job 143 type TaskConfig struct { 144 Class string `codec:"class"` 145 ClassPath string `codec:"class_path"` 146 JarPath string `codec:"jar_path"` 147 JvmOpts []string `codec:"jvm_options"` 148 Args []string `codec:"args"` // extra arguments to java executable 149 ModePID string `codec:"pid_mode"` 150 ModeIPC string `codec:"ipc_mode"` 151 } 152 153 func (tc *TaskConfig) validate() error { 154 switch tc.ModePID { 155 case "", executor.IsolationModePrivate, executor.IsolationModeHost: 156 default: 157 return fmt.Errorf("pid_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, tc.ModePID) 158 159 } 160 161 switch tc.ModeIPC { 162 case "", executor.IsolationModePrivate, executor.IsolationModeHost: 163 default: 164 return fmt.Errorf("ipc_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, tc.ModeIPC) 165 } 166 167 return nil 168 } 169 170 // TaskState is the state which is encoded in the handle returned in 171 // StartTask. This information is needed to rebuild the taskConfig state and handler 172 // during recovery. 173 type TaskState struct { 174 ReattachConfig *pstructs.ReattachConfig 175 TaskConfig *drivers.TaskConfig 176 Pid int 177 StartedAt time.Time 178 } 179 180 // Driver is a driver for running images via Java 181 type Driver struct { 182 // eventer is used to handle multiplexing of TaskEvents calls such that an 183 // event can be broadcast to all callers 184 eventer *eventer.Eventer 185 186 // config is the driver configuration set by the SetConfig RPC 187 config Config 188 189 // tasks is the in memory datastore mapping taskIDs to taskHandle 190 tasks *taskStore 191 192 // ctx is the context for the driver. It is passed to other subsystems to 193 // coordinate shutdown 194 ctx context.Context 195 196 // nomadConf is the client agent's configuration 197 nomadConfig *base.ClientDriverConfig 198 199 // logger will log to the Nomad agent 200 logger hclog.Logger 201 } 202 203 func NewDriver(ctx context.Context, logger hclog.Logger) drivers.DriverPlugin { 204 logger = logger.Named(pluginName) 205 return &Driver{ 206 eventer: eventer.NewEventer(ctx, logger), 207 tasks: newTaskStore(), 208 ctx: ctx, 209 logger: logger, 210 } 211 } 212 213 func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) { 214 return pluginInfo, nil 215 } 216 217 func (d *Driver) ConfigSchema() (*hclspec.Spec, error) { 218 return configSpec, nil 219 } 220 221 func (d *Driver) SetConfig(cfg *base.Config) error { 222 // unpack, validate, and set agent plugin config 223 var config Config 224 if len(cfg.PluginConfig) != 0 { 225 if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil { 226 return err 227 } 228 } 229 if err := config.validate(); err != nil { 230 return err 231 } 232 d.config = config 233 234 if cfg != nil && cfg.AgentConfig != nil { 235 d.nomadConfig = cfg.AgentConfig.Driver 236 } 237 return nil 238 } 239 240 func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) { 241 return taskConfigSpec, nil 242 } 243 244 func (d *Driver) Capabilities() (*drivers.Capabilities, error) { 245 return capabilities, nil 246 } 247 248 func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) { 249 ch := make(chan *drivers.Fingerprint) 250 go d.handleFingerprint(ctx, ch) 251 return ch, nil 252 } 253 254 func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) { 255 ticker := time.NewTimer(0) 256 for { 257 select { 258 case <-ctx.Done(): 259 return 260 case <-d.ctx.Done(): 261 return 262 case <-ticker.C: 263 ticker.Reset(fingerprintPeriod) 264 ch <- d.buildFingerprint() 265 } 266 } 267 } 268 269 func (d *Driver) buildFingerprint() *drivers.Fingerprint { 270 fp := &drivers.Fingerprint{ 271 Attributes: map[string]*pstructs.Attribute{}, 272 Health: drivers.HealthStateHealthy, 273 HealthDescription: drivers.DriverHealthy, 274 } 275 276 if runtime.GOOS == "linux" { 277 // Only enable if w are root and cgroups are mounted when running on linux system 278 if !utils.IsUnixRoot() { 279 fp.Health = drivers.HealthStateUndetected 280 fp.HealthDescription = drivers.DriverRequiresRootMessage 281 return fp 282 } 283 284 mount, err := fingerprint.FindCgroupMountpointDir() 285 if err != nil { 286 fp.Health = drivers.HealthStateUnhealthy 287 fp.HealthDescription = drivers.NoCgroupMountMessage 288 d.logger.Warn(fp.HealthDescription, "error", err) 289 return fp 290 } 291 292 if mount == "" { 293 fp.Health = drivers.HealthStateUnhealthy 294 fp.HealthDescription = drivers.CgroupMountEmpty 295 return fp 296 } 297 } 298 if runtime.GOOS == "darwin" { 299 _, err := checkForMacJVM() 300 if err != nil { 301 // return no error, as it isn't an error to not find java, it just means we 302 // can't use it. 303 304 fp.Health = drivers.HealthStateUndetected 305 fp.HealthDescription = "" 306 d.logger.Trace("macOS jvm not found", "error", err) 307 return fp 308 } 309 } 310 311 version, runtime, vm, err := javaVersionInfo() 312 if err != nil { 313 // return no error, as it isn't an error to not find java, it just means we 314 // can't use it. 315 fp.Health = drivers.HealthStateUndetected 316 fp.HealthDescription = "" 317 return fp 318 } 319 320 fp.Attributes[driverAttr] = pstructs.NewBoolAttribute(true) 321 fp.Attributes[driverVersionAttr] = pstructs.NewStringAttribute(version) 322 fp.Attributes["driver.java.runtime"] = pstructs.NewStringAttribute(runtime) 323 fp.Attributes["driver.java.vm"] = pstructs.NewStringAttribute(vm) 324 325 return fp 326 } 327 328 func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { 329 if handle == nil { 330 return fmt.Errorf("handle cannot be nil") 331 } 332 333 // COMPAT(0.10): pre 0.9 upgrade path check 334 if handle.Version == 0 { 335 return d.recoverPre09Task(handle) 336 } 337 338 // If already attached to handle there's nothing to recover. 339 if _, ok := d.tasks.Get(handle.Config.ID); ok { 340 d.logger.Debug("nothing to recover; task already exists", 341 "task_id", handle.Config.ID, 342 "task_name", handle.Config.Name, 343 ) 344 return nil 345 } 346 347 var taskState TaskState 348 if err := handle.GetDriverState(&taskState); err != nil { 349 d.logger.Error("failed to decode taskConfig state from handle", "error", err, "task_id", handle.Config.ID) 350 return fmt.Errorf("failed to decode taskConfig state from handle: %v", err) 351 } 352 353 plugRC, err := pstructs.ReattachConfigToGoPlugin(taskState.ReattachConfig) 354 if err != nil { 355 d.logger.Error("failed to build ReattachConfig from taskConfig state", "error", err, "task_id", handle.Config.ID) 356 return fmt.Errorf("failed to build ReattachConfig from taskConfig state: %v", err) 357 } 358 359 execImpl, pluginClient, err := executor.ReattachToExecutor(plugRC, 360 d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID)) 361 if err != nil { 362 d.logger.Error("failed to reattach to executor", "error", err, "task_id", handle.Config.ID) 363 return fmt.Errorf("failed to reattach to executor: %v", err) 364 } 365 366 h := &taskHandle{ 367 exec: execImpl, 368 pid: taskState.Pid, 369 pluginClient: pluginClient, 370 taskConfig: taskState.TaskConfig, 371 procState: drivers.TaskStateRunning, 372 startedAt: taskState.StartedAt, 373 exitResult: &drivers.ExitResult{}, 374 logger: d.logger, 375 } 376 377 d.tasks.Set(taskState.TaskConfig.ID, h) 378 379 go h.run() 380 return nil 381 } 382 383 func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) { 384 if _, ok := d.tasks.Get(cfg.ID); ok { 385 return nil, nil, fmt.Errorf("task with ID %q already started", cfg.ID) 386 } 387 388 var driverConfig TaskConfig 389 if err := cfg.DecodeDriverConfig(&driverConfig); err != nil { 390 return nil, nil, fmt.Errorf("failed to decode driver config: %v", err) 391 } 392 393 if err := driverConfig.validate(); err != nil { 394 return nil, nil, fmt.Errorf("failed driver config validation: %v", err) 395 } 396 397 if driverConfig.Class == "" && driverConfig.JarPath == "" { 398 return nil, nil, fmt.Errorf("jar_path or class must be specified") 399 } 400 401 absPath, err := GetAbsolutePath("java") 402 if err != nil { 403 return nil, nil, fmt.Errorf("failed to find java binary: %s", err) 404 } 405 406 args := javaCmdArgs(driverConfig) 407 408 d.logger.Info("starting java task", "driver_cfg", hclog.Fmt("%+v", driverConfig), "args", args) 409 410 handle := drivers.NewTaskHandle(taskHandleVersion) 411 handle.Config = cfg 412 413 pluginLogFile := filepath.Join(cfg.TaskDir().Dir, "executor.out") 414 executorConfig := &executor.ExecutorConfig{ 415 LogFile: pluginLogFile, 416 LogLevel: "debug", 417 FSIsolation: capabilities.FSIsolation == drivers.FSIsolationChroot, 418 } 419 420 exec, pluginClient, err := executor.CreateExecutor( 421 d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID), 422 d.nomadConfig, executorConfig) 423 if err != nil { 424 return nil, nil, fmt.Errorf("failed to create executor: %v", err) 425 } 426 427 user := cfg.User 428 if user == "" { 429 user = "nobody" 430 } 431 432 if cfg.DNS != nil { 433 dnsMount, err := resolvconf.GenerateDNSMount(cfg.TaskDir().Dir, cfg.DNS) 434 if err != nil { 435 return nil, nil, fmt.Errorf("failed to build mount for resolv.conf: %v", err) 436 } 437 cfg.Mounts = append(cfg.Mounts, dnsMount) 438 } 439 440 execCmd := &executor.ExecCommand{ 441 Cmd: absPath, 442 Args: args, 443 Env: cfg.EnvList(), 444 User: user, 445 ResourceLimits: true, 446 Resources: cfg.Resources, 447 TaskDir: cfg.TaskDir().Dir, 448 StdoutPath: cfg.StdoutPath, 449 StderrPath: cfg.StderrPath, 450 Mounts: cfg.Mounts, 451 Devices: cfg.Devices, 452 NetworkIsolation: cfg.NetworkIsolation, 453 ModePID: executor.IsolationMode(d.config.DefaultModePID, driverConfig.ModePID), 454 ModeIPC: executor.IsolationMode(d.config.DefaultModeIPC, driverConfig.ModeIPC), 455 } 456 457 ps, err := exec.Launch(execCmd) 458 if err != nil { 459 pluginClient.Kill() 460 return nil, nil, fmt.Errorf("failed to launch command with executor: %v", err) 461 } 462 463 h := &taskHandle{ 464 exec: exec, 465 pid: ps.Pid, 466 pluginClient: pluginClient, 467 taskConfig: cfg, 468 procState: drivers.TaskStateRunning, 469 startedAt: time.Now().Round(time.Millisecond), 470 logger: d.logger, 471 } 472 473 driverState := TaskState{ 474 ReattachConfig: pstructs.ReattachConfigFromGoPlugin(pluginClient.ReattachConfig()), 475 Pid: ps.Pid, 476 TaskConfig: cfg, 477 StartedAt: h.startedAt, 478 } 479 480 if err := handle.SetDriverState(&driverState); err != nil { 481 d.logger.Error("failed to start task, error setting driver state", "error", err) 482 exec.Shutdown("", 0) 483 pluginClient.Kill() 484 return nil, nil, fmt.Errorf("failed to set driver state: %v", err) 485 } 486 487 d.tasks.Set(cfg.ID, h) 488 go h.run() 489 return handle, nil, nil 490 } 491 492 func javaCmdArgs(driverConfig TaskConfig) []string { 493 args := []string{} 494 // Look for jvm options 495 if len(driverConfig.JvmOpts) != 0 { 496 args = append(args, driverConfig.JvmOpts...) 497 } 498 499 // Add the classpath 500 if driverConfig.ClassPath != "" { 501 args = append(args, "-cp", driverConfig.ClassPath) 502 } 503 504 // Add the jar 505 if driverConfig.JarPath != "" { 506 args = append(args, "-jar", driverConfig.JarPath) 507 } 508 509 // Add the class 510 if driverConfig.Class != "" { 511 args = append(args, driverConfig.Class) 512 } 513 514 // Add any args 515 if len(driverConfig.Args) != 0 { 516 args = append(args, driverConfig.Args...) 517 } 518 519 return args 520 } 521 522 func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) { 523 handle, ok := d.tasks.Get(taskID) 524 if !ok { 525 return nil, drivers.ErrTaskNotFound 526 } 527 528 ch := make(chan *drivers.ExitResult) 529 go d.handleWait(ctx, handle, ch) 530 531 return ch, nil 532 } 533 534 func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) { 535 defer close(ch) 536 var result *drivers.ExitResult 537 ps, err := handle.exec.Wait(ctx) 538 if err != nil { 539 result = &drivers.ExitResult{ 540 Err: fmt.Errorf("executor: error waiting on process: %v", err), 541 } 542 } else { 543 result = &drivers.ExitResult{ 544 ExitCode: ps.ExitCode, 545 Signal: ps.Signal, 546 } 547 } 548 549 select { 550 case <-ctx.Done(): 551 return 552 case <-d.ctx.Done(): 553 return 554 case ch <- result: 555 } 556 } 557 558 func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error { 559 handle, ok := d.tasks.Get(taskID) 560 if !ok { 561 return drivers.ErrTaskNotFound 562 } 563 564 if err := handle.exec.Shutdown(signal, timeout); err != nil { 565 if handle.pluginClient.Exited() { 566 return nil 567 } 568 return fmt.Errorf("executor Shutdown failed: %v", err) 569 } 570 571 return nil 572 } 573 574 func (d *Driver) DestroyTask(taskID string, force bool) error { 575 handle, ok := d.tasks.Get(taskID) 576 if !ok { 577 return drivers.ErrTaskNotFound 578 } 579 580 if handle.IsRunning() && !force { 581 return fmt.Errorf("cannot destroy running task") 582 } 583 584 if !handle.pluginClient.Exited() { 585 if err := handle.exec.Shutdown("", 0); err != nil { 586 handle.logger.Error("destroying executor failed", "err", err) 587 } 588 589 handle.pluginClient.Kill() 590 } 591 592 d.tasks.Delete(taskID) 593 return nil 594 } 595 596 func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) { 597 handle, ok := d.tasks.Get(taskID) 598 if !ok { 599 return nil, drivers.ErrTaskNotFound 600 } 601 602 return handle.TaskStatus(), nil 603 } 604 605 func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) { 606 handle, ok := d.tasks.Get(taskID) 607 if !ok { 608 return nil, drivers.ErrTaskNotFound 609 } 610 611 return handle.exec.Stats(ctx, interval) 612 } 613 614 func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) { 615 return d.eventer.TaskEvents(ctx) 616 } 617 618 func (d *Driver) SignalTask(taskID string, signal string) error { 619 handle, ok := d.tasks.Get(taskID) 620 if !ok { 621 return drivers.ErrTaskNotFound 622 } 623 624 sig := os.Interrupt 625 if s, ok := signals.SignalLookup[signal]; ok { 626 sig = s 627 } else { 628 d.logger.Warn("unknown signal to send to task, using SIGINT instead", "signal", signal, "task_id", handle.taskConfig.ID) 629 630 } 631 return handle.exec.Signal(sig) 632 } 633 634 func (d *Driver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) { 635 if len(cmd) == 0 { 636 return nil, fmt.Errorf("error cmd must have at least one value") 637 } 638 handle, ok := d.tasks.Get(taskID) 639 if !ok { 640 return nil, drivers.ErrTaskNotFound 641 } 642 643 out, exitCode, err := handle.exec.Exec(time.Now().Add(timeout), cmd[0], cmd[1:]) 644 if err != nil { 645 return nil, err 646 } 647 648 return &drivers.ExecTaskResult{ 649 Stdout: out, 650 ExitResult: &drivers.ExitResult{ 651 ExitCode: exitCode, 652 }, 653 }, nil 654 } 655 656 var _ drivers.ExecTaskStreamingRawDriver = (*Driver)(nil) 657 658 func (d *Driver) ExecTaskStreamingRaw(ctx context.Context, 659 taskID string, 660 command []string, 661 tty bool, 662 stream drivers.ExecTaskStream) error { 663 664 if len(command) == 0 { 665 return fmt.Errorf("error cmd must have at least one value") 666 } 667 handle, ok := d.tasks.Get(taskID) 668 if !ok { 669 return drivers.ErrTaskNotFound 670 } 671 672 return handle.exec.ExecStreaming(ctx, command, tty, stream) 673 } 674 675 // GetAbsolutePath returns the absolute path of the passed binary by resolving 676 // it in the path and following symlinks. 677 func GetAbsolutePath(bin string) (string, error) { 678 lp, err := exec.LookPath(bin) 679 if err != nil { 680 return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err) 681 } 682 683 return filepath.EvalSymlinks(lp) 684 }