github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/mock_driver.go (about)

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