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