github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/mock_driver.go (about)

     1  // +build nomad_test
     2  
     3  package driver
     4  
     5  import (
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"time"
    12  
    13  	"github.com/mitchellh/mapstructure"
    14  
    15  	"github.com/hashicorp/nomad/client/config"
    16  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    17  	"github.com/hashicorp/nomad/client/fingerprint"
    18  	cstructs "github.com/hashicorp/nomad/client/structs"
    19  	"github.com/hashicorp/nomad/nomad/structs"
    20  )
    21  
    22  // Add the mock driver to the list of builtin drivers
    23  func init() {
    24  	BuiltinDrivers["mock_driver"] = NewMockDriver
    25  }
    26  
    27  // MockDriverConfig is the driver configuration for the MockDriver
    28  type MockDriverConfig struct {
    29  
    30  	// StartErr specifies the error that should be returned when starting the
    31  	// mock driver.
    32  	StartErr string `mapstructure:"start_error"`
    33  
    34  	// StartErrRecoverable marks the error returned is recoverable
    35  	StartErrRecoverable bool `mapstructure:"start_error_recoverable"`
    36  
    37  	// KillAfter is the duration after which the mock driver indicates the task
    38  	// has exited after getting the initial SIGINT signal
    39  	KillAfter time.Duration `mapstructure:"kill_after"`
    40  
    41  	// RunFor is the duration for which the fake task runs for. After this
    42  	// period the MockDriver responds to the task running indicating that the
    43  	// task has terminated
    44  	RunFor time.Duration `mapstructure:"run_for"`
    45  
    46  	// ExitCode is the exit code with which the MockDriver indicates the task
    47  	// has exited
    48  	ExitCode int `mapstructure:"exit_code"`
    49  
    50  	// ExitSignal is the signal with which the MockDriver indicates the task has
    51  	// been killed
    52  	ExitSignal int `mapstructure:"exit_signal"`
    53  
    54  	// ExitErrMsg is the error message that the task returns while exiting
    55  	ExitErrMsg string `mapstructure:"exit_err_msg"`
    56  
    57  	// SignalErr is the error message that the task returns if signalled
    58  	SignalErr string `mapstructure:"signal_error"`
    59  }
    60  
    61  // MockDriver is a driver which is used for testing purposes
    62  type MockDriver struct {
    63  	DriverContext
    64  	fingerprint.StaticFingerprinter
    65  }
    66  
    67  // NewMockDriver is a factory method which returns a new Mock Driver
    68  func NewMockDriver(ctx *DriverContext) Driver {
    69  	return &MockDriver{DriverContext: *ctx}
    70  }
    71  
    72  func (d *MockDriver) Abilities() DriverAbilities {
    73  	return DriverAbilities{
    74  		SendSignals: false,
    75  	}
    76  }
    77  
    78  // Start starts the mock driver
    79  func (m *MockDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
    80  	var driverConfig MockDriverConfig
    81  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
    82  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
    83  		WeaklyTypedInput: true,
    84  		Result:           &driverConfig,
    85  	})
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	if err := dec.Decode(task.Config); err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	if driverConfig.StartErr != "" {
    94  		return nil, structs.NewRecoverableError(errors.New(driverConfig.StartErr), driverConfig.StartErrRecoverable)
    95  	}
    96  
    97  	h := mockDriverHandle{
    98  		taskName:    task.Name,
    99  		runFor:      driverConfig.RunFor,
   100  		killAfter:   driverConfig.KillAfter,
   101  		killTimeout: task.KillTimeout,
   102  		exitCode:    driverConfig.ExitCode,
   103  		exitSignal:  driverConfig.ExitSignal,
   104  		logger:      m.logger,
   105  		doneCh:      make(chan struct{}),
   106  		waitCh:      make(chan *dstructs.WaitResult, 1),
   107  	}
   108  	if driverConfig.ExitErrMsg != "" {
   109  		h.exitErr = errors.New(driverConfig.ExitErrMsg)
   110  	}
   111  	if driverConfig.SignalErr != "" {
   112  		h.signalErr = fmt.Errorf(driverConfig.SignalErr)
   113  	}
   114  	m.logger.Printf("[DEBUG] driver.mock: starting task %q", task.Name)
   115  	go h.run()
   116  	return &h, nil
   117  }
   118  
   119  // Validate validates the mock driver configuration
   120  func (m *MockDriver) Validate(map[string]interface{}) error {
   121  	return nil
   122  }
   123  
   124  // Fingerprint fingerprints a node and returns if MockDriver is enabled
   125  func (m *MockDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
   126  	node.Attributes["driver.mock_driver"] = "1"
   127  	return true, nil
   128  }
   129  
   130  // MockDriverHandle is a driver handler which supervises a mock task
   131  type mockDriverHandle struct {
   132  	taskName    string
   133  	runFor      time.Duration
   134  	killAfter   time.Duration
   135  	killTimeout time.Duration
   136  	exitCode    int
   137  	exitSignal  int
   138  	exitErr     error
   139  	signalErr   error
   140  	logger      *log.Logger
   141  	waitCh      chan *dstructs.WaitResult
   142  	doneCh      chan struct{}
   143  }
   144  
   145  type mockDriverID struct {
   146  	TaskName    string
   147  	RunFor      time.Duration
   148  	KillAfter   time.Duration
   149  	KillTimeout time.Duration
   150  	ExitCode    int
   151  	ExitSignal  int
   152  	ExitErr     error
   153  	SignalErr   error
   154  }
   155  
   156  func (h *mockDriverHandle) ID() string {
   157  	id := mockDriverID{
   158  		TaskName:    h.taskName,
   159  		RunFor:      h.runFor,
   160  		KillAfter:   h.killAfter,
   161  		KillTimeout: h.killAfter,
   162  		ExitCode:    h.exitCode,
   163  		ExitSignal:  h.exitSignal,
   164  		ExitErr:     h.exitErr,
   165  		SignalErr:   h.signalErr,
   166  	}
   167  
   168  	data, err := json.Marshal(id)
   169  	if err != nil {
   170  		h.logger.Printf("[ERR] driver.mock_driver: failed to marshal ID to JSON: %s", err)
   171  	}
   172  	return string(data)
   173  }
   174  
   175  // Open re-connects the driver to the running task
   176  func (m *MockDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   177  	id := &mockDriverID{}
   178  	if err := json.Unmarshal([]byte(handleID), id); err != nil {
   179  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
   180  	}
   181  
   182  	h := mockDriverHandle{
   183  		taskName:    id.TaskName,
   184  		runFor:      id.RunFor,
   185  		killAfter:   id.KillAfter,
   186  		killTimeout: id.KillTimeout,
   187  		exitCode:    id.ExitCode,
   188  		exitSignal:  id.ExitSignal,
   189  		exitErr:     id.ExitErr,
   190  		signalErr:   id.SignalErr,
   191  		logger:      m.logger,
   192  		doneCh:      make(chan struct{}),
   193  		waitCh:      make(chan *dstructs.WaitResult, 1),
   194  	}
   195  
   196  	go h.run()
   197  	return &h, nil
   198  }
   199  
   200  func (h *mockDriverHandle) WaitCh() chan *dstructs.WaitResult {
   201  	return h.waitCh
   202  }
   203  
   204  // TODO Implement when we need it.
   205  func (h *mockDriverHandle) Update(task *structs.Task) error {
   206  	return nil
   207  }
   208  
   209  // TODO Implement when we need it.
   210  func (h *mockDriverHandle) Signal(s os.Signal) error {
   211  	return h.signalErr
   212  }
   213  
   214  // Kill kills a mock task
   215  func (h *mockDriverHandle) Kill() error {
   216  	h.logger.Printf("[DEBUG] driver.mock: killing task %q after kill timeout: %v", h.taskName, h.killTimeout)
   217  	select {
   218  	case <-h.doneCh:
   219  	case <-time.After(h.killAfter):
   220  		close(h.doneCh)
   221  	case <-time.After(h.killTimeout):
   222  		h.logger.Printf("[DEBUG] driver.mock: terminating task %q", h.taskName)
   223  		close(h.doneCh)
   224  	}
   225  	return nil
   226  }
   227  
   228  // TODO Implement when we need it.
   229  func (h *mockDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) {
   230  	return nil, nil
   231  }
   232  
   233  // run waits for the configured amount of time and then indicates the task has
   234  // terminated
   235  func (h *mockDriverHandle) run() {
   236  	timer := time.NewTimer(h.runFor)
   237  	defer timer.Stop()
   238  	for {
   239  		select {
   240  		case <-timer.C:
   241  			close(h.doneCh)
   242  		case <-h.doneCh:
   243  			h.logger.Printf("[DEBUG] driver.mock: finished running task %q", h.taskName)
   244  			h.waitCh <- dstructs.NewWaitResult(h.exitCode, h.exitSignal, h.exitErr)
   245  			return
   246  		}
   247  	}
   248  }