github.com/quite/nomad@v0.8.6/client/driver/mock_driver.go (about)

     1  package driver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/mitchellh/mapstructure"
    16  
    17  	"github.com/hashicorp/nomad/client/driver/logging"
    18  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    19  	cstructs "github.com/hashicorp/nomad/client/structs"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  )
    22  
    23  const (
    24  	// ShutdownPeriodicAfter is a config key that can be used during tests to
    25  	// "stop" a previously-functioning driver, allowing for testing of periodic
    26  	// drivers and fingerprinters
    27  	ShutdownPeriodicAfter = "test.shutdown_periodic_after"
    28  
    29  	// ShutdownPeriodicDuration is a config option that can be used during tests
    30  	// to "stop" a previously functioning driver after the specified duration
    31  	// (specified in seconds) for testing of periodic drivers and fingerprinters.
    32  	ShutdownPeriodicDuration = "test.shutdown_periodic_duration"
    33  
    34  	mockDriverName = "driver.mock_driver"
    35  )
    36  
    37  // MockDriverConfig is the driver configuration for the MockDriver
    38  type MockDriverConfig struct {
    39  
    40  	// StartErr specifies the error that should be returned when starting the
    41  	// mock driver.
    42  	StartErr string `mapstructure:"start_error"`
    43  
    44  	// StartErrRecoverable marks the error returned is recoverable
    45  	StartErrRecoverable bool `mapstructure:"start_error_recoverable"`
    46  
    47  	// StartBlockFor specifies a duration in which to block before returning
    48  	StartBlockFor time.Duration `mapstructure:"start_block_for"`
    49  
    50  	// KillAfter is the duration after which the mock driver indicates the task
    51  	// has exited after getting the initial SIGINT signal
    52  	KillAfter time.Duration `mapstructure:"kill_after"`
    53  
    54  	// RunFor is the duration for which the fake task runs for. After this
    55  	// period the MockDriver responds to the task running indicating that the
    56  	// task has terminated
    57  	RunFor time.Duration `mapstructure:"run_for"`
    58  
    59  	// ExitCode is the exit code with which the MockDriver indicates the task
    60  	// has exited
    61  	ExitCode int `mapstructure:"exit_code"`
    62  
    63  	// ExitSignal is the signal with which the MockDriver indicates the task has
    64  	// been killed
    65  	ExitSignal int `mapstructure:"exit_signal"`
    66  
    67  	// ExitErrMsg is the error message that the task returns while exiting
    68  	ExitErrMsg string `mapstructure:"exit_err_msg"`
    69  
    70  	// SignalErr is the error message that the task returns if signalled
    71  	SignalErr string `mapstructure:"signal_error"`
    72  
    73  	// DriverIP will be returned as the DriverNetwork.IP from Start()
    74  	DriverIP string `mapstructure:"driver_ip"`
    75  
    76  	// DriverAdvertise will be returned as DriverNetwork.AutoAdvertise from
    77  	// Start().
    78  	DriverAdvertise bool `mapstructure:"driver_advertise"`
    79  
    80  	// DriverPortMap will parse a label:number pair and return it in
    81  	// DriverNetwork.PortMap from Start().
    82  	DriverPortMap string `mapstructure:"driver_port_map"`
    83  
    84  	// StdoutString is the string that should be sent to stdout
    85  	StdoutString string `mapstructure:"stdout_string"`
    86  
    87  	// StdoutRepeat is the number of times the output should be sent.
    88  	StdoutRepeat int `mapstructure:"stdout_repeat"`
    89  
    90  	// StdoutRepeatDur is the duration between repeated outputs.
    91  	StdoutRepeatDur time.Duration `mapstructure:"stdout_repeat_duration"`
    92  }
    93  
    94  // MockDriver is a driver which is used for testing purposes
    95  type MockDriver struct {
    96  	DriverContext
    97  
    98  	cleanupFailNum int
    99  
   100  	// shutdownFingerprintTime is the time up to which the driver will be up
   101  	shutdownFingerprintTime time.Time
   102  }
   103  
   104  // NewMockDriver is a factory method which returns a new Mock Driver
   105  func NewMockDriver(ctx *DriverContext) Driver {
   106  	md := &MockDriver{DriverContext: *ctx}
   107  
   108  	// if the shutdown configuration options are set, start the timer here.
   109  	// This config option defaults to false
   110  	if ctx.config != nil && ctx.config.ReadBoolDefault(ShutdownPeriodicAfter, false) {
   111  		duration, err := ctx.config.ReadInt(ShutdownPeriodicDuration)
   112  		if err != nil {
   113  			errMsg := fmt.Sprintf("unable to read config option for shutdown_periodic_duration %v, got err %s", duration, err.Error())
   114  			panic(errMsg)
   115  		}
   116  		md.shutdownFingerprintTime = time.Now().Add(time.Second * time.Duration(duration))
   117  	}
   118  
   119  	return md
   120  }
   121  
   122  func (d *MockDriver) Abilities() DriverAbilities {
   123  	return DriverAbilities{
   124  		SendSignals: false,
   125  		Exec:        true,
   126  	}
   127  }
   128  
   129  func (d *MockDriver) FSIsolation() cstructs.FSIsolation {
   130  	return cstructs.FSIsolationNone
   131  }
   132  
   133  func (d *MockDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) {
   134  	return nil, nil
   135  }
   136  
   137  // Start starts the mock driver
   138  func (m *MockDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) {
   139  	var driverConfig MockDriverConfig
   140  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   141  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   142  		WeaklyTypedInput: true,
   143  		Result:           &driverConfig,
   144  	})
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	if err := dec.Decode(task.Config); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	if driverConfig.StartBlockFor != 0 {
   153  		time.Sleep(driverConfig.StartBlockFor)
   154  	}
   155  
   156  	if driverConfig.StartErr != "" {
   157  		return nil, structs.NewRecoverableError(errors.New(driverConfig.StartErr), driverConfig.StartErrRecoverable)
   158  	}
   159  
   160  	// Create the driver network
   161  	net := &cstructs.DriverNetwork{
   162  		IP:            driverConfig.DriverIP,
   163  		AutoAdvertise: driverConfig.DriverAdvertise,
   164  	}
   165  	if raw := driverConfig.DriverPortMap; len(raw) > 0 {
   166  		parts := strings.Split(raw, ":")
   167  		if len(parts) != 2 {
   168  			return nil, fmt.Errorf("malformed port map: %q", raw)
   169  		}
   170  		port, err := strconv.Atoi(parts[1])
   171  		if err != nil {
   172  			return nil, fmt.Errorf("malformed port map: %q -- error: %v", raw, err)
   173  		}
   174  		net.PortMap = map[string]int{parts[0]: port}
   175  	}
   176  
   177  	h := mockDriverHandle{
   178  		ctx:             ctx,
   179  		task:            task,
   180  		taskName:        task.Name,
   181  		runFor:          driverConfig.RunFor,
   182  		killAfter:       driverConfig.KillAfter,
   183  		killTimeout:     task.KillTimeout,
   184  		exitCode:        driverConfig.ExitCode,
   185  		exitSignal:      driverConfig.ExitSignal,
   186  		stdoutString:    driverConfig.StdoutString,
   187  		stdoutRepeat:    driverConfig.StdoutRepeat,
   188  		stdoutRepeatDur: driverConfig.StdoutRepeatDur,
   189  		logger:          m.logger,
   190  		doneCh:          make(chan struct{}),
   191  		waitCh:          make(chan *dstructs.WaitResult, 1),
   192  	}
   193  	if driverConfig.ExitErrMsg != "" {
   194  		h.exitErr = errors.New(driverConfig.ExitErrMsg)
   195  	}
   196  	if driverConfig.SignalErr != "" {
   197  		h.signalErr = fmt.Errorf(driverConfig.SignalErr)
   198  	}
   199  	m.logger.Printf("[DEBUG] driver.mock: starting task %q", task.Name)
   200  	go h.run()
   201  
   202  	return &StartResponse{Handle: &h, Network: net}, nil
   203  }
   204  
   205  // Cleanup deletes all keys except for Config.Options["cleanup_fail_on"] for
   206  // Config.Options["cleanup_fail_num"] times. For failures it will return a
   207  // recoverable error.
   208  func (m *MockDriver) Cleanup(ctx *ExecContext, res *CreatedResources) error {
   209  	if res == nil {
   210  		panic("Cleanup should not be called with nil *CreatedResources")
   211  	}
   212  
   213  	var err error
   214  	failn, _ := strconv.Atoi(m.config.Options["cleanup_fail_num"])
   215  	failk := m.config.Options["cleanup_fail_on"]
   216  	for k := range res.Resources {
   217  		if k == failk && m.cleanupFailNum < failn {
   218  			m.cleanupFailNum++
   219  			err = structs.NewRecoverableError(fmt.Errorf("mock_driver failure on %q call %d/%d", k, m.cleanupFailNum, failn), true)
   220  		} else {
   221  			delete(res.Resources, k)
   222  		}
   223  	}
   224  	return err
   225  }
   226  
   227  // Validate validates the mock driver configuration
   228  func (m *MockDriver) Validate(map[string]interface{}) error {
   229  	return nil
   230  }
   231  
   232  // Fingerprint fingerprints a node and returns if MockDriver is enabled
   233  func (m *MockDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error {
   234  	switch {
   235  	// If the driver is configured to shut down after a period of time, and the
   236  	// current time is after the time which the node should shut down, simulate
   237  	// driver failure
   238  	case !m.shutdownFingerprintTime.IsZero() && time.Now().After(m.shutdownFingerprintTime):
   239  		resp.RemoveAttribute(mockDriverName)
   240  	default:
   241  		resp.AddAttribute(mockDriverName, "1")
   242  		resp.Detected = true
   243  	}
   244  	return nil
   245  }
   246  
   247  // When testing, poll for updates
   248  func (m *MockDriver) Periodic() (bool, time.Duration) {
   249  	return true, 500 * time.Millisecond
   250  }
   251  
   252  // HealthCheck implements the interface for HealthCheck, and indicates the current
   253  // health status of the mock driver.
   254  func (m *MockDriver) HealthCheck(req *cstructs.HealthCheckRequest, resp *cstructs.HealthCheckResponse) error {
   255  	switch {
   256  	case !m.shutdownFingerprintTime.IsZero() && time.Now().After(m.shutdownFingerprintTime):
   257  		notHealthy := &structs.DriverInfo{
   258  			Healthy:           false,
   259  			HealthDescription: "not running",
   260  			UpdateTime:        time.Now(),
   261  		}
   262  		resp.AddDriverInfo("mock_driver", notHealthy)
   263  		return nil
   264  	default:
   265  		healthy := &structs.DriverInfo{
   266  			Healthy:           true,
   267  			HealthDescription: "running",
   268  			UpdateTime:        time.Now(),
   269  		}
   270  		resp.AddDriverInfo("mock_driver", healthy)
   271  		return nil
   272  	}
   273  }
   274  
   275  // GetHealthCheckInterval implements the interface for HealthCheck and indicates
   276  // that mock driver should be checked periodically. Returns a boolean
   277  // indicating if it should be checked, and the duration at which to do this
   278  // check.
   279  func (m *MockDriver) GetHealthCheckInterval(req *cstructs.HealthCheckIntervalRequest, resp *cstructs.HealthCheckIntervalResponse) error {
   280  	resp.Eligible = true
   281  	resp.Period = 1 * time.Second
   282  	return nil
   283  }
   284  
   285  // MockDriverHandle is a driver handler which supervises a mock task
   286  type mockDriverHandle struct {
   287  	ctx             *ExecContext
   288  	task            *structs.Task
   289  	taskName        string
   290  	runFor          time.Duration
   291  	killAfter       time.Duration
   292  	killTimeout     time.Duration
   293  	exitCode        int
   294  	exitSignal      int
   295  	exitErr         error
   296  	signalErr       error
   297  	logger          *log.Logger
   298  	stdoutString    string
   299  	stdoutRepeat    int
   300  	stdoutRepeatDur time.Duration
   301  	waitCh          chan *dstructs.WaitResult
   302  	doneCh          chan struct{}
   303  }
   304  
   305  type mockDriverID struct {
   306  	TaskName    string
   307  	RunFor      time.Duration
   308  	KillAfter   time.Duration
   309  	KillTimeout time.Duration
   310  	ExitCode    int
   311  	ExitSignal  int
   312  	ExitErr     error
   313  	SignalErr   error
   314  }
   315  
   316  func (h *mockDriverHandle) ID() string {
   317  	id := mockDriverID{
   318  		TaskName:    h.taskName,
   319  		RunFor:      h.runFor,
   320  		KillAfter:   h.killAfter,
   321  		KillTimeout: h.killTimeout,
   322  		ExitCode:    h.exitCode,
   323  		ExitSignal:  h.exitSignal,
   324  		ExitErr:     h.exitErr,
   325  		SignalErr:   h.signalErr,
   326  	}
   327  
   328  	data, err := json.Marshal(id)
   329  	if err != nil {
   330  		h.logger.Printf("[ERR] driver.mock_driver: failed to marshal ID to JSON: %s", err)
   331  	}
   332  	return string(data)
   333  }
   334  
   335  // Open re-connects the driver to the running task
   336  func (m *MockDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   337  	id := &mockDriverID{}
   338  	if err := json.Unmarshal([]byte(handleID), id); err != nil {
   339  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
   340  	}
   341  
   342  	h := mockDriverHandle{
   343  		taskName:    id.TaskName,
   344  		runFor:      id.RunFor,
   345  		killAfter:   id.KillAfter,
   346  		killTimeout: id.KillTimeout,
   347  		exitCode:    id.ExitCode,
   348  		exitSignal:  id.ExitSignal,
   349  		exitErr:     id.ExitErr,
   350  		signalErr:   id.SignalErr,
   351  		logger:      m.logger,
   352  		doneCh:      make(chan struct{}),
   353  		waitCh:      make(chan *dstructs.WaitResult, 1),
   354  	}
   355  
   356  	go h.run()
   357  	return &h, nil
   358  }
   359  
   360  func (h *mockDriverHandle) WaitCh() chan *dstructs.WaitResult {
   361  	return h.waitCh
   362  }
   363  
   364  func (h *mockDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
   365  	h.logger.Printf("[DEBUG] driver.mock: Exec(%q, %q)", cmd, args)
   366  	return []byte(fmt.Sprintf("Exec(%q, %q)", cmd, args)), 0, nil
   367  }
   368  
   369  // TODO Implement when we need it.
   370  func (h *mockDriverHandle) Update(task *structs.Task) error {
   371  	h.killTimeout = task.KillTimeout
   372  	return nil
   373  }
   374  
   375  // TODO Implement when we need it.
   376  func (h *mockDriverHandle) Signal(s os.Signal) error {
   377  	return h.signalErr
   378  }
   379  
   380  // Kill kills a mock task
   381  func (h *mockDriverHandle) Kill() error {
   382  	h.logger.Printf("[DEBUG] driver.mock: killing task %q after %s or kill timeout: %v", h.taskName, h.killAfter, h.killTimeout)
   383  	select {
   384  	case <-h.doneCh:
   385  	case <-time.After(h.killAfter):
   386  		select {
   387  		case <-h.doneCh:
   388  			// already closed
   389  		default:
   390  			close(h.doneCh)
   391  		}
   392  	case <-time.After(h.killTimeout):
   393  		h.logger.Printf("[DEBUG] driver.mock: terminating task %q", h.taskName)
   394  		select {
   395  		case <-h.doneCh:
   396  			// already closed
   397  		default:
   398  			close(h.doneCh)
   399  		}
   400  	}
   401  	return nil
   402  }
   403  
   404  // TODO Implement when we need it.
   405  func (h *mockDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) {
   406  	return nil, nil
   407  }
   408  
   409  // run waits for the configured amount of time and then indicates the task has
   410  // terminated
   411  func (h *mockDriverHandle) run() {
   412  	// Setup logging output
   413  	if h.stdoutString != "" {
   414  		go h.handleLogging()
   415  	}
   416  
   417  	timer := time.NewTimer(h.runFor)
   418  	defer timer.Stop()
   419  	for {
   420  		select {
   421  		case <-timer.C:
   422  			select {
   423  			case <-h.doneCh:
   424  				// already closed
   425  			default:
   426  				close(h.doneCh)
   427  			}
   428  		case <-h.doneCh:
   429  			h.logger.Printf("[DEBUG] driver.mock: finished running task %q", h.taskName)
   430  			h.waitCh <- dstructs.NewWaitResult(h.exitCode, h.exitSignal, h.exitErr)
   431  			return
   432  		}
   433  	}
   434  }
   435  
   436  // handleLogging handles logging stdout messages
   437  func (h *mockDriverHandle) handleLogging() {
   438  	if h.stdoutString == "" {
   439  		return
   440  	}
   441  
   442  	// Setup a log rotator
   443  	logFileSize := int64(h.task.LogConfig.MaxFileSizeMB * 1024 * 1024)
   444  	lro, err := logging.NewFileRotator(h.ctx.TaskDir.LogDir, fmt.Sprintf("%v.stdout", h.taskName),
   445  		h.task.LogConfig.MaxFiles, logFileSize, h.logger)
   446  	if err != nil {
   447  		h.exitErr = err
   448  		close(h.doneCh)
   449  		h.logger.Printf("[ERR] mock_driver: failed to setup file rotator: %v", err)
   450  		return
   451  	}
   452  	defer lro.Close()
   453  
   454  	// Do initial write to stdout.
   455  	if _, err := io.WriteString(lro, h.stdoutString); err != nil {
   456  		h.exitErr = err
   457  		close(h.doneCh)
   458  		h.logger.Printf("[ERR] mock_driver: failed to write to stdout: %v", err)
   459  		return
   460  	}
   461  
   462  	for i := 0; i < h.stdoutRepeat; i++ {
   463  		select {
   464  		case <-h.doneCh:
   465  			return
   466  		case <-time.After(h.stdoutRepeatDur):
   467  			if _, err := io.WriteString(lro, h.stdoutString); err != nil {
   468  				h.exitErr = err
   469  				close(h.doneCh)
   470  				h.logger.Printf("[ERR] mock_driver: failed to write to stdout: %v", err)
   471  				return
   472  			}
   473  		}
   474  	}
   475  }