297 lines
6.8 KiB
Go
297 lines
6.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package testutils
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-msgpack/codec"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/plugins/drivers"
|
|
pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var _ drivers.DriverPlugin = (*MockDriver)(nil)
|
|
|
|
// Very simple test to ensure the test harness works as expected
|
|
func TestDriverHarness(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
handle := &drivers.TaskHandle{Config: &drivers.TaskConfig{Name: "mock"}}
|
|
d := &MockDriver{
|
|
StartTaskF: func(task *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
|
|
return handle, nil, nil
|
|
},
|
|
}
|
|
harness := NewDriverHarness(t, d)
|
|
defer harness.Kill()
|
|
actual, _, err := harness.StartTask(&drivers.TaskConfig{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, handle.Config.Name, actual.Config.Name)
|
|
}
|
|
|
|
type testDriverState struct {
|
|
Pid int
|
|
Log string
|
|
}
|
|
|
|
func TestBaseDriver_Fingerprint(t *testing.T) {
|
|
ci.Parallel(t)
|
|
require := require.New(t)
|
|
|
|
fingerprints := []*drivers.Fingerprint{
|
|
{
|
|
Attributes: map[string]*pstructs.Attribute{"foo": pstructs.NewStringAttribute("bar")},
|
|
Health: drivers.HealthStateUnhealthy,
|
|
HealthDescription: "starting up",
|
|
},
|
|
{
|
|
Attributes: map[string]*pstructs.Attribute{"foo": pstructs.NewStringAttribute("bar")},
|
|
Health: drivers.HealthStateHealthy,
|
|
HealthDescription: "running",
|
|
},
|
|
}
|
|
|
|
var complete atomic.Value
|
|
complete.Store(false)
|
|
|
|
impl := &MockDriver{
|
|
FingerprintF: func(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
|
|
ch := make(chan *drivers.Fingerprint)
|
|
go func() {
|
|
defer close(ch)
|
|
ch <- fingerprints[0]
|
|
time.Sleep(500 * time.Millisecond)
|
|
ch <- fingerprints[1]
|
|
complete.Store(true)
|
|
}()
|
|
return ch, nil
|
|
},
|
|
}
|
|
|
|
harness := NewDriverHarness(t, impl)
|
|
defer harness.Kill()
|
|
|
|
ch, err := harness.Fingerprint(context.Background())
|
|
require.NoError(err)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
select {
|
|
case f := <-ch:
|
|
require.Exactly(f, fingerprints[0])
|
|
case <-time.After(1 * time.Second):
|
|
require.Fail("did not receive fingerprint[0]")
|
|
}
|
|
select {
|
|
case f := <-ch:
|
|
require.Exactly(f, fingerprints[1])
|
|
case <-time.After(1 * time.Second):
|
|
require.Fail("did not receive fingerprint[1]")
|
|
}
|
|
}()
|
|
require.False(complete.Load().(bool))
|
|
wg.Wait()
|
|
require.True(complete.Load().(bool))
|
|
}
|
|
|
|
func TestBaseDriver_RecoverTask(t *testing.T) {
|
|
ci.Parallel(t)
|
|
require := require.New(t)
|
|
|
|
// build driver state and encode it into proto msg
|
|
state := testDriverState{Pid: 1, Log: "foo"}
|
|
var buf bytes.Buffer
|
|
enc := codec.NewEncoder(&buf, structs.MsgpackHandle)
|
|
enc.Encode(state)
|
|
|
|
// mock the RecoverTask driver call
|
|
impl := &MockDriver{
|
|
RecoverTaskF: func(h *drivers.TaskHandle) error {
|
|
var actual testDriverState
|
|
require.NoError(h.GetDriverState(&actual))
|
|
require.Equal(state, actual)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
harness := NewDriverHarness(t, impl)
|
|
defer harness.Kill()
|
|
|
|
handle := &drivers.TaskHandle{
|
|
DriverState: buf.Bytes(),
|
|
}
|
|
err := harness.RecoverTask(handle)
|
|
require.NoError(err)
|
|
}
|
|
|
|
func TestBaseDriver_StartTask(t *testing.T) {
|
|
ci.Parallel(t)
|
|
require := require.New(t)
|
|
|
|
cfg := &drivers.TaskConfig{
|
|
ID: "foo",
|
|
}
|
|
state := &testDriverState{Pid: 1, Log: "log"}
|
|
var handle *drivers.TaskHandle
|
|
impl := &MockDriver{
|
|
StartTaskF: func(c *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
|
|
handle = drivers.NewTaskHandle(1)
|
|
handle.Config = c
|
|
handle.State = drivers.TaskStateRunning
|
|
handle.SetDriverState(state)
|
|
return handle, nil, nil
|
|
},
|
|
}
|
|
|
|
harness := NewDriverHarness(t, impl)
|
|
defer harness.Kill()
|
|
resp, _, err := harness.StartTask(cfg)
|
|
require.NoError(err)
|
|
require.Equal(cfg.ID, resp.Config.ID)
|
|
require.Equal(handle.State, resp.State)
|
|
|
|
var actualState testDriverState
|
|
require.NoError(resp.GetDriverState(&actualState))
|
|
require.Equal(*state, actualState)
|
|
|
|
}
|
|
|
|
func TestBaseDriver_WaitTask(t *testing.T) {
|
|
ci.Parallel(t)
|
|
require := require.New(t)
|
|
|
|
result := &drivers.ExitResult{ExitCode: 1, Signal: 9}
|
|
|
|
signalTask := make(chan struct{})
|
|
|
|
impl := &MockDriver{
|
|
WaitTaskF: func(_ context.Context, id string) (<-chan *drivers.ExitResult, error) {
|
|
ch := make(chan *drivers.ExitResult)
|
|
go func() {
|
|
<-signalTask
|
|
ch <- result
|
|
}()
|
|
return ch, nil
|
|
},
|
|
}
|
|
|
|
harness := NewDriverHarness(t, impl)
|
|
defer harness.Kill()
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
var finished bool
|
|
go func() {
|
|
defer wg.Done()
|
|
ch, err := harness.WaitTask(context.TODO(), "foo")
|
|
require.NoError(err)
|
|
actualResult := <-ch
|
|
finished = true
|
|
require.Exactly(result, actualResult)
|
|
}()
|
|
require.False(finished)
|
|
close(signalTask)
|
|
wg.Wait()
|
|
require.True(finished)
|
|
}
|
|
|
|
func TestBaseDriver_TaskEvents(t *testing.T) {
|
|
ci.Parallel(t)
|
|
require := require.New(t)
|
|
|
|
now := time.Now().UTC().Truncate(time.Millisecond)
|
|
events := []*drivers.TaskEvent{
|
|
{
|
|
TaskID: "abc",
|
|
Timestamp: now,
|
|
Annotations: map[string]string{"foo": "bar"},
|
|
Message: "starting",
|
|
},
|
|
{
|
|
TaskID: "xyz",
|
|
Timestamp: now.Add(2 * time.Second),
|
|
Annotations: map[string]string{"foo": "bar"},
|
|
Message: "starting",
|
|
},
|
|
{
|
|
TaskID: "xyz",
|
|
Timestamp: now.Add(3 * time.Second),
|
|
Annotations: map[string]string{"foo": "bar"},
|
|
Message: "running",
|
|
},
|
|
{
|
|
TaskID: "abc",
|
|
Timestamp: now.Add(4 * time.Second),
|
|
Annotations: map[string]string{"foo": "bar"},
|
|
Message: "running",
|
|
},
|
|
}
|
|
|
|
impl := &MockDriver{
|
|
TaskEventsF: func(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
|
|
ch := make(chan *drivers.TaskEvent)
|
|
go func() {
|
|
defer close(ch)
|
|
for _, event := range events {
|
|
ch <- event
|
|
}
|
|
}()
|
|
return ch, nil
|
|
},
|
|
}
|
|
|
|
harness := NewDriverHarness(t, impl)
|
|
defer harness.Kill()
|
|
|
|
ch, err := harness.TaskEvents(context.Background())
|
|
require.NoError(err)
|
|
|
|
for _, event := range events {
|
|
select {
|
|
case actual := <-ch:
|
|
require.Exactly(actual, event)
|
|
case <-time.After(500 * time.Millisecond):
|
|
require.Fail("failed to receive event")
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestBaseDriver_Capabilities(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
capabilities := &drivers.Capabilities{
|
|
NetIsolationModes: []drivers.NetIsolationMode{
|
|
drivers.NetIsolationModeHost,
|
|
drivers.NetIsolationModeGroup,
|
|
},
|
|
MustInitiateNetwork: true,
|
|
SendSignals: true,
|
|
Exec: true,
|
|
FSIsolation: drivers.FSIsolationNone,
|
|
}
|
|
d := &MockDriver{
|
|
CapabilitiesF: func() (*drivers.Capabilities, error) {
|
|
return capabilities, nil
|
|
},
|
|
}
|
|
|
|
harness := NewDriverHarness(t, d)
|
|
defer harness.Kill()
|
|
|
|
caps, err := harness.Capabilities()
|
|
require.NoError(t, err)
|
|
require.Equal(t, capabilities, caps)
|
|
}
|