open-nomad/plugins/drivers/testutils/testing_test.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)
}