github.com/manicqin/nomad@v0.9.5/plugins/drivers/testutils/testing.go (about)

     1  package testutils
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  
    12  	testing "github.com/mitchellh/go-testing-interface"
    13  
    14  	hclog "github.com/hashicorp/go-hclog"
    15  	plugin "github.com/hashicorp/go-plugin"
    16  	"github.com/hashicorp/nomad/client/allocdir"
    17  	"github.com/hashicorp/nomad/client/config"
    18  	"github.com/hashicorp/nomad/client/logmon"
    19  	"github.com/hashicorp/nomad/client/taskenv"
    20  	"github.com/hashicorp/nomad/helper/testlog"
    21  	"github.com/hashicorp/nomad/helper/uuid"
    22  	"github.com/hashicorp/nomad/nomad/mock"
    23  	"github.com/hashicorp/nomad/nomad/structs"
    24  	"github.com/hashicorp/nomad/plugins/base"
    25  	"github.com/hashicorp/nomad/plugins/drivers"
    26  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  type DriverHarness struct {
    31  	drivers.DriverPlugin
    32  	client *plugin.GRPCClient
    33  	server *plugin.GRPCServer
    34  	t      testing.T
    35  	logger hclog.Logger
    36  	impl   drivers.DriverPlugin
    37  }
    38  
    39  func (d *DriverHarness) Impl() drivers.DriverPlugin {
    40  	return d.impl
    41  }
    42  func NewDriverHarness(t testing.T, d drivers.DriverPlugin) *DriverHarness {
    43  	logger := testlog.HCLogger(t).Named("driver_harness")
    44  
    45  	pd := drivers.NewDriverPlugin(d, logger)
    46  
    47  	client, server := plugin.TestPluginGRPCConn(t,
    48  		map[string]plugin.Plugin{
    49  			base.PluginTypeDriver: pd,
    50  			base.PluginTypeBase:   &base.PluginBase{Impl: d},
    51  			"logmon":              logmon.NewPlugin(logmon.NewLogMon(logger.Named("logmon"))),
    52  		},
    53  	)
    54  
    55  	raw, err := client.Dispense(base.PluginTypeDriver)
    56  	if err != nil {
    57  		t.Fatalf("err dispensing plugin: %v", err)
    58  	}
    59  
    60  	dClient := raw.(drivers.DriverPlugin)
    61  	h := &DriverHarness{
    62  		client:       client,
    63  		server:       server,
    64  		DriverPlugin: dClient,
    65  		logger:       logger,
    66  		t:            t,
    67  		impl:         d,
    68  	}
    69  
    70  	return h
    71  }
    72  
    73  func (h *DriverHarness) Kill() {
    74  	h.client.Close()
    75  	h.server.Stop()
    76  }
    77  
    78  // MkAllocDir creates a temporary directory and allocdir structure.
    79  // If enableLogs is set to true a logmon instance will be started to write logs
    80  // to the LogDir of the task
    81  // A cleanup func is returned and should be defered so as to not leak dirs
    82  // between tests.
    83  func (h *DriverHarness) MkAllocDir(t *drivers.TaskConfig, enableLogs bool) func() {
    84  	dir, err := ioutil.TempDir("", "nomad_driver_harness-")
    85  	require.NoError(h.t, err)
    86  	t.AllocDir = dir
    87  
    88  	allocDir := allocdir.NewAllocDir(h.logger, dir)
    89  	require.NoError(h.t, allocDir.Build())
    90  	taskDir := allocDir.NewTaskDir(t.Name)
    91  
    92  	caps, err := h.Capabilities()
    93  	require.NoError(h.t, err)
    94  
    95  	fsi := caps.FSIsolation
    96  	require.NoError(h.t, taskDir.Build(fsi == drivers.FSIsolationChroot, config.DefaultChrootEnv))
    97  
    98  	task := &structs.Task{
    99  		Name: t.Name,
   100  		Env:  t.Env,
   101  	}
   102  
   103  	// Create the mock allocation
   104  	alloc := mock.Alloc()
   105  	if t.Resources != nil {
   106  		alloc.AllocatedResources.Tasks[task.Name] = t.Resources.NomadResources
   107  	}
   108  
   109  	taskBuilder := taskenv.NewBuilder(mock.Node(), alloc, task, "global")
   110  	SetEnvvars(taskBuilder, fsi, taskDir, config.DefaultConfig())
   111  
   112  	taskEnv := taskBuilder.Build()
   113  	if t.Env == nil {
   114  		t.Env = taskEnv.Map()
   115  	} else {
   116  		for k, v := range taskEnv.Map() {
   117  			if _, ok := t.Env[k]; !ok {
   118  				t.Env[k] = v
   119  			}
   120  		}
   121  	}
   122  
   123  	//logmon
   124  	if enableLogs {
   125  		lm := logmon.NewLogMon(h.logger.Named("logmon"))
   126  		if runtime.GOOS == "windows" {
   127  			id := uuid.Generate()[:8]
   128  			t.StdoutPath = fmt.Sprintf("//./pipe/%s-%s.stdout", t.Name, id)
   129  			t.StderrPath = fmt.Sprintf("//./pipe/%s-%s.stderr", t.Name, id)
   130  		} else {
   131  			t.StdoutPath = filepath.Join(taskDir.LogDir, fmt.Sprintf(".%s.stdout.fifo", t.Name))
   132  			t.StderrPath = filepath.Join(taskDir.LogDir, fmt.Sprintf(".%s.stderr.fifo", t.Name))
   133  		}
   134  		err = lm.Start(&logmon.LogConfig{
   135  			LogDir:        taskDir.LogDir,
   136  			StdoutLogFile: fmt.Sprintf("%s.stdout", t.Name),
   137  			StderrLogFile: fmt.Sprintf("%s.stderr", t.Name),
   138  			StdoutFifo:    t.StdoutPath,
   139  			StderrFifo:    t.StderrPath,
   140  			MaxFiles:      10,
   141  			MaxFileSizeMB: 10,
   142  			FileExtension: "",
   143  		})
   144  		require.NoError(h.t, err)
   145  
   146  		return func() {
   147  			lm.Stop()
   148  			h.client.Close()
   149  			allocDir.Destroy()
   150  		}
   151  	}
   152  
   153  	return func() {
   154  		h.client.Close()
   155  		allocDir.Destroy()
   156  	}
   157  }
   158  
   159  // WaitUntilStarted will block until the task for the given ID is in the running
   160  // state or the timeout is reached
   161  func (h *DriverHarness) WaitUntilStarted(taskID string, timeout time.Duration) error {
   162  	deadline := time.Now().Add(timeout)
   163  	var lastState drivers.TaskState
   164  	for {
   165  		status, err := h.InspectTask(taskID)
   166  		if err != nil {
   167  			return err
   168  		}
   169  		if status.State == drivers.TaskStateRunning {
   170  			return nil
   171  		}
   172  		lastState = status.State
   173  		if time.Now().After(deadline) {
   174  			return fmt.Errorf("task never transitioned to running, currently '%s'", lastState)
   175  		}
   176  		time.Sleep(100 * time.Millisecond)
   177  	}
   178  }
   179  
   180  // MockDriver is used for testing.
   181  // Each function can be set as a closure to make assertions about how data
   182  // is passed through the base plugin layer.
   183  type MockDriver struct {
   184  	base.MockPlugin
   185  	TaskConfigSchemaF  func() (*hclspec.Spec, error)
   186  	FingerprintF       func(context.Context) (<-chan *drivers.Fingerprint, error)
   187  	CapabilitiesF      func() (*drivers.Capabilities, error)
   188  	RecoverTaskF       func(*drivers.TaskHandle) error
   189  	StartTaskF         func(*drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error)
   190  	WaitTaskF          func(context.Context, string) (<-chan *drivers.ExitResult, error)
   191  	StopTaskF          func(string, time.Duration, string) error
   192  	DestroyTaskF       func(string, bool) error
   193  	InspectTaskF       func(string) (*drivers.TaskStatus, error)
   194  	TaskStatsF         func(context.Context, string, time.Duration) (<-chan *drivers.TaskResourceUsage, error)
   195  	TaskEventsF        func(context.Context) (<-chan *drivers.TaskEvent, error)
   196  	SignalTaskF        func(string, string) error
   197  	ExecTaskF          func(string, []string, time.Duration) (*drivers.ExecTaskResult, error)
   198  	ExecTaskStreamingF func(context.Context, string, *drivers.ExecOptions) (*drivers.ExitResult, error)
   199  	MockNetworkManager
   200  }
   201  
   202  type MockNetworkManager struct {
   203  	CreateNetworkF  func(string) (*drivers.NetworkIsolationSpec, bool, error)
   204  	DestroyNetworkF func(string, *drivers.NetworkIsolationSpec) error
   205  }
   206  
   207  func (m *MockNetworkManager) CreateNetwork(id string) (*drivers.NetworkIsolationSpec, bool, error) {
   208  	return m.CreateNetworkF(id)
   209  }
   210  func (m *MockNetworkManager) DestroyNetwork(id string, spec *drivers.NetworkIsolationSpec) error {
   211  	return m.DestroyNetworkF(id, spec)
   212  }
   213  
   214  func (d *MockDriver) TaskConfigSchema() (*hclspec.Spec, error) { return d.TaskConfigSchemaF() }
   215  func (d *MockDriver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
   216  	return d.FingerprintF(ctx)
   217  }
   218  func (d *MockDriver) Capabilities() (*drivers.Capabilities, error) { return d.CapabilitiesF() }
   219  func (d *MockDriver) RecoverTask(h *drivers.TaskHandle) error      { return d.RecoverTaskF(h) }
   220  func (d *MockDriver) StartTask(c *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
   221  	return d.StartTaskF(c)
   222  }
   223  func (d *MockDriver) WaitTask(ctx context.Context, id string) (<-chan *drivers.ExitResult, error) {
   224  	return d.WaitTaskF(ctx, id)
   225  }
   226  func (d *MockDriver) StopTask(taskID string, timeout time.Duration, signal string) error {
   227  	return d.StopTaskF(taskID, timeout, signal)
   228  }
   229  func (d *MockDriver) DestroyTask(taskID string, force bool) error {
   230  	return d.DestroyTaskF(taskID, force)
   231  }
   232  func (d *MockDriver) InspectTask(taskID string) (*drivers.TaskStatus, error) {
   233  	return d.InspectTaskF(taskID)
   234  }
   235  func (d *MockDriver) TaskStats(ctx context.Context, taskID string, i time.Duration) (<-chan *drivers.TaskResourceUsage, error) {
   236  	return d.TaskStats(ctx, taskID, i)
   237  }
   238  func (d *MockDriver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
   239  	return d.TaskEventsF(ctx)
   240  }
   241  func (d *MockDriver) SignalTask(taskID string, signal string) error {
   242  	return d.SignalTask(taskID, signal)
   243  }
   244  func (d *MockDriver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) {
   245  	return d.ExecTaskF(taskID, cmd, timeout)
   246  }
   247  
   248  func (d *MockDriver) ExecTaskStreaming(ctx context.Context, taskID string, execOpts *drivers.ExecOptions) (*drivers.ExitResult, error) {
   249  	return d.ExecTaskStreamingF(ctx, taskID, execOpts)
   250  }
   251  
   252  // SetEnvvars sets path and host env vars depending on the FS isolation used.
   253  func SetEnvvars(envBuilder *taskenv.Builder, fsi drivers.FSIsolation, taskDir *allocdir.TaskDir, conf *config.Config) {
   254  	// Set driver-specific environment variables
   255  	switch fsi {
   256  	case drivers.FSIsolationNone:
   257  		// Use host paths
   258  		envBuilder.SetAllocDir(taskDir.SharedAllocDir)
   259  		envBuilder.SetTaskLocalDir(taskDir.LocalDir)
   260  		envBuilder.SetSecretsDir(taskDir.SecretsDir)
   261  	default:
   262  		// filesystem isolation; use container paths
   263  		envBuilder.SetAllocDir(allocdir.SharedAllocContainerPath)
   264  		envBuilder.SetTaskLocalDir(allocdir.TaskLocalContainerPath)
   265  		envBuilder.SetSecretsDir(allocdir.TaskSecretsContainerPath)
   266  	}
   267  
   268  	// Set the host environment variables for non-image based drivers
   269  	if fsi != drivers.FSIsolationImage {
   270  		filter := strings.Split(conf.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",")
   271  		envBuilder.SetHostEnvvars(filter)
   272  	}
   273  }