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