github.com/smithx10/nomad@v0.9.1-rc1/client/allocrunner/taskrunner/logmon_hook.go (about)

     1  package taskrunner
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"path/filepath"
     8  	"runtime"
     9  
    10  	hclog "github.com/hashicorp/go-hclog"
    11  	plugin "github.com/hashicorp/go-plugin"
    12  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
    13  	"github.com/hashicorp/nomad/client/logmon"
    14  	"github.com/hashicorp/nomad/helper/uuid"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    17  )
    18  
    19  const (
    20  	// logmonReattachKey is the HookData key where logmon's reattach config
    21  	// is stored.
    22  	logmonReattachKey = "reattach_config"
    23  )
    24  
    25  // logmonHook launches logmon and manages task logging
    26  type logmonHook struct {
    27  	// logmon is the handle to the log monitor process for the task.
    28  	logmon             logmon.LogMon
    29  	logmonPluginClient *plugin.Client
    30  
    31  	config *logmonHookConfig
    32  
    33  	logger hclog.Logger
    34  }
    35  
    36  type logmonHookConfig struct {
    37  	logDir     string
    38  	stdoutFifo string
    39  	stderrFifo string
    40  }
    41  
    42  func newLogMonHook(cfg *logmonHookConfig, logger hclog.Logger) *logmonHook {
    43  	hook := &logmonHook{
    44  		config: cfg,
    45  		logger: logger,
    46  	}
    47  
    48  	return hook
    49  }
    50  
    51  func newLogMonHookConfig(taskName, logDir string) *logmonHookConfig {
    52  	cfg := &logmonHookConfig{
    53  		logDir: logDir,
    54  	}
    55  	if runtime.GOOS == "windows" {
    56  		id := uuid.Generate()[:8]
    57  		cfg.stdoutFifo = fmt.Sprintf("//./pipe/%s-%s.stdout", taskName, id)
    58  		cfg.stderrFifo = fmt.Sprintf("//./pipe/%s-%s.stderr", taskName, id)
    59  	} else {
    60  		cfg.stdoutFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stdout.fifo", taskName))
    61  		cfg.stderrFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stderr.fifo", taskName))
    62  	}
    63  	return cfg
    64  }
    65  
    66  func (*logmonHook) Name() string {
    67  	return "logmon"
    68  }
    69  
    70  func (h *logmonHook) launchLogMon(reattachConfig *plugin.ReattachConfig) error {
    71  	l, c, err := logmon.LaunchLogMon(h.logger, reattachConfig)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	h.logmon = l
    77  	h.logmonPluginClient = c
    78  	return nil
    79  }
    80  
    81  func reattachConfigFromHookData(data map[string]string) (*plugin.ReattachConfig, error) {
    82  	if data == nil || data[logmonReattachKey] == "" {
    83  		return nil, nil
    84  	}
    85  
    86  	var cfg pstructs.ReattachConfig
    87  	err := json.Unmarshal([]byte(data[logmonReattachKey]), &cfg)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return pstructs.ReattachConfigToGoPlugin(&cfg)
    93  }
    94  
    95  func (h *logmonHook) Prestart(ctx context.Context,
    96  	req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
    97  
    98  	// Attempt to reattach to logmon
    99  	if h.logmonPluginClient == nil {
   100  		reattachConfig, err := reattachConfigFromHookData(req.PreviousState)
   101  		if err != nil {
   102  			h.logger.Error("failed to load reattach config", "error", err)
   103  			return err
   104  		}
   105  		if reattachConfig != nil {
   106  			if err := h.launchLogMon(reattachConfig); err != nil {
   107  				h.logger.Warn("failed to reattach to logmon process", "error", err)
   108  			}
   109  		}
   110  
   111  	}
   112  
   113  	// We did not reattach to a plugin and one is still not running.
   114  	if h.logmonPluginClient == nil || h.logmonPluginClient.Exited() {
   115  		if err := h.launchLogMon(nil); err != nil {
   116  			// Retry errors launching logmon as logmon may have crashed on start and
   117  			// subsequent attempts will start a new one.
   118  			h.logger.Error("failed to launch logmon process", "error", err)
   119  			return structs.NewRecoverableError(err, true)
   120  		}
   121  	}
   122  
   123  	err := h.logmon.Start(&logmon.LogConfig{
   124  		LogDir:        h.config.logDir,
   125  		StdoutLogFile: fmt.Sprintf("%s.stdout", req.Task.Name),
   126  		StderrLogFile: fmt.Sprintf("%s.stderr", req.Task.Name),
   127  		StdoutFifo:    h.config.stdoutFifo,
   128  		StderrFifo:    h.config.stderrFifo,
   129  		MaxFiles:      req.Task.LogConfig.MaxFiles,
   130  		MaxFileSizeMB: req.Task.LogConfig.MaxFileSizeMB,
   131  	})
   132  	if err != nil {
   133  		h.logger.Error("failed to start logmon", "error", err)
   134  		return err
   135  	}
   136  
   137  	rCfg := pstructs.ReattachConfigFromGoPlugin(h.logmonPluginClient.ReattachConfig())
   138  	jsonCfg, err := json.Marshal(rCfg)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	resp.State = map[string]string{logmonReattachKey: string(jsonCfg)}
   143  	return nil
   144  }
   145  
   146  func (h *logmonHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, _ *interfaces.TaskStopResponse) error {
   147  
   148  	// It's possible that Stop was called without calling Prestart on agent
   149  	// restarts. Attempt to reattach to an existing logmon.
   150  	if h.logmon == nil || h.logmonPluginClient == nil {
   151  		if err := h.reattach(req); err != nil {
   152  			h.logger.Trace("error reattaching to logmon when stopping", "error", err)
   153  		}
   154  	}
   155  
   156  	if h.logmon != nil {
   157  		h.logmon.Stop()
   158  	}
   159  	if h.logmonPluginClient != nil {
   160  		h.logmonPluginClient.Kill()
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  // reattach to a running logmon if possible. Will not start a new logmon.
   167  func (h *logmonHook) reattach(req *interfaces.TaskStopRequest) error {
   168  	reattachConfig, err := reattachConfigFromHookData(req.ExistingState)
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	// Give up if there's no reattach config
   174  	if reattachConfig == nil {
   175  		return nil
   176  	}
   177  
   178  	return h.launchLogMon(reattachConfig)
   179  }