open-nomad/plugins/drivers/plugin_test.go

245 lines
5.2 KiB
Go

package drivers
import (
"bytes"
"context"
"sync"
"testing"
"time"
cstructs "github.com/hashicorp/nomad/client/structs"
"github.com/hashicorp/nomad/nomad/structs"
pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
"github.com/stretchr/testify/require"
"github.com/ugorji/go/codec"
)
type testDriverState struct {
Pid int
Log string
}
func TestBaseDriver_Fingerprint(t *testing.T) {
t.Parallel()
require := require.New(t)
fingerprints := []*Fingerprint{
{
Attributes: map[string]*pstructs.Attribute{"foo": pstructs.NewStringAttribute("bar")},
Health: HealthStateUnhealthy,
HealthDescription: "starting up",
},
{
Attributes: map[string]*pstructs.Attribute{"foo": pstructs.NewStringAttribute("bar")},
Health: HealthStateHealthy,
HealthDescription: "running",
},
}
var complete bool
impl := &MockDriver{
FingerprintF: func(ctx context.Context) (<-chan *Fingerprint, error) {
ch := make(chan *Fingerprint)
go func() {
defer close(ch)
ch <- fingerprints[0]
time.Sleep(500 * time.Millisecond)
ch <- fingerprints[1]
complete = 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)
wg.Wait()
require.True(complete)
}
func TestBaseDriver_RecoverTask(t *testing.T) {
t.Parallel()
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 *TaskHandle) error {
var actual testDriverState
require.NoError(h.GetDriverState(&actual))
require.Equal(state, actual)
return nil
},
}
harness := NewDriverHarness(t, impl)
defer harness.Kill()
handle := &TaskHandle{
DriverState: buf.Bytes(),
}
err := harness.RecoverTask(handle)
require.NoError(err)
}
func TestBaseDriver_StartTask(t *testing.T) {
t.Parallel()
require := require.New(t)
cfg := &TaskConfig{
ID: "foo",
}
state := &testDriverState{Pid: 1, Log: "log"}
var handle *TaskHandle
impl := &MockDriver{
StartTaskF: func(c *TaskConfig) (*TaskHandle, *cstructs.DriverNetwork, error) {
handle = NewTaskHandle("test")
handle.Config = c
handle.State = 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) {
t.Parallel()
require := require.New(t)
result := &ExitResult{ExitCode: 1, Signal: 9}
signalTask := make(chan struct{})
impl := &MockDriver{
WaitTaskF: func(_ context.Context, id string) (<-chan *ExitResult, error) {
ch := make(chan *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) {
t.Parallel()
require := require.New(t)
now := time.Now().UTC().Truncate(time.Millisecond)
events := []*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 *TaskEvent, error) {
ch := make(chan *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")
}
}
}