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