github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/e2e/e2eutil/e2ejob.go (about)

     1  package e2eutil
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hashicorp/nomad/api"
    16  	"github.com/hashicorp/nomad/e2e/framework"
    17  	"github.com/hashicorp/nomad/helper/discover"
    18  	"github.com/hashicorp/nomad/helper/uuid"
    19  	"github.com/hashicorp/nomad/nomad/structs"
    20  	"github.com/hashicorp/nomad/testutil"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  type e2eJob struct {
    26  	framework.TC
    27  	jobfile string
    28  	jobID   string
    29  }
    30  
    31  func (e *e2eJob) Name() string {
    32  	return filepath.Base(e.jobfile)
    33  }
    34  
    35  // Ensure cluster has leader and at least 1 client node
    36  // in a ready state before running tests
    37  func (j *e2eJob) BeforeAll(f *framework.F) {
    38  	WaitForLeader(f.T(), j.Nomad())
    39  	WaitForNodesReady(f.T(), j.Nomad(), 1)
    40  	j.jobID = "e2eutil-" + uuid.Generate()[0:8]
    41  }
    42  
    43  func (j *e2eJob) TestJob(f *framework.F) {
    44  	file, err := os.Open(j.jobfile)
    45  	t := f.T()
    46  	require.NoError(t, err)
    47  
    48  	scanner := bufio.NewScanner(file)
    49  	var e2eJobLine string
    50  	for scanner.Scan() {
    51  		if strings.HasPrefix(scanner.Text(), "//e2e:") {
    52  			e2eJobLine = scanner.Text()[6:]
    53  		}
    54  		require.NoError(t, scanner.Err())
    55  	}
    56  
    57  	switch {
    58  	case strings.HasPrefix(e2eJobLine, "batch"):
    59  		parseBatchJobLine(t, j, e2eJobLine).Run(f)
    60  	case strings.HasPrefix(e2eJobLine, "service"):
    61  		parseServiceJobLine(t, j, e2eJobLine).Run(f)
    62  	default:
    63  		require.Fail(t, "could not parse e2e job line: %q", e2eJobLine)
    64  	}
    65  }
    66  
    67  type e2eBatchJob struct {
    68  	*e2eJob
    69  
    70  	shouldFail bool
    71  }
    72  
    73  func (j *e2eBatchJob) Run(f *framework.F) {
    74  	t := f.T()
    75  	require := require.New(t)
    76  	nomadClient := j.Nomad()
    77  
    78  	allocs := RegisterAndWaitForAllocs(f.T(), nomadClient, j.jobfile, j.jobID, "")
    79  	require.Equal(1, len(allocs))
    80  	allocID := allocs[0].ID
    81  
    82  	// wait for the job to stop
    83  	WaitForAllocStopped(t, nomadClient, allocID)
    84  	alloc, _, err := nomadClient.Allocations().Info(allocID, nil)
    85  	require.NoError(err)
    86  	if j.shouldFail {
    87  		require.NotEqual(structs.AllocClientStatusComplete, alloc.ClientStatus)
    88  	} else {
    89  		require.Equal(structs.AllocClientStatusComplete, alloc.ClientStatus)
    90  	}
    91  }
    92  
    93  type e2eServiceJob struct {
    94  	*e2eJob
    95  
    96  	script          string
    97  	runningDuration time.Duration
    98  }
    99  
   100  func (j *e2eServiceJob) Run(f *framework.F) {
   101  	t := f.T()
   102  	nomadClient := j.Nomad()
   103  
   104  	allocs := RegisterAndWaitForAllocs(f.T(), nomadClient, j.jobfile, j.jobID, "")
   105  	require.Equal(t, 1, len(allocs))
   106  	allocID := allocs[0].ID
   107  
   108  	var alloc *api.Allocation
   109  	WaitForAllocRunning(t, nomadClient, allocID)
   110  	testutil.AssertUntil(j.runningDuration, func() (bool, error) {
   111  		var err error
   112  		alloc, _, err = nomadClient.Allocations().Info(allocID, nil)
   113  		if err != nil {
   114  			return false, err
   115  		}
   116  
   117  		return alloc.ClientStatus == structs.AllocClientStatusRunning, fmt.Errorf("expected status running, but was: %s", alloc.ClientStatus)
   118  	}, func(err error) {
   119  		require.NoError(t, err, "failed to keep alloc running")
   120  	})
   121  
   122  	scriptPath := filepath.Join(filepath.Dir(j.jobfile), j.script)
   123  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   124  	defer cancel()
   125  	cmd := exec.CommandContext(ctx, scriptPath)
   126  	nmdBin, err := discover.NomadExecutable()
   127  	assert.NoError(t, err)
   128  	cmd.Env = append(os.Environ(),
   129  		"NOMAD_BIN="+nmdBin,
   130  		"NOMAD_ALLOC_ID="+allocID,
   131  		"NOMAD_ADDR="+nomadClient.Address(),
   132  	)
   133  
   134  	assert.NoError(t, cmd.Start())
   135  	waitCh := make(chan error)
   136  	go func() {
   137  		select {
   138  		case waitCh <- cmd.Wait():
   139  		case <-ctx.Done():
   140  		}
   141  	}()
   142  
   143  	select {
   144  	case <-ctx.Done():
   145  	case err := <-waitCh:
   146  		assert.NoError(t, err)
   147  		assert.Zero(t, cmd.ProcessState.ExitCode())
   148  	}
   149  
   150  	// stop the job
   151  	_, _, err = nomadClient.Jobs().Deregister(j.jobID, false, nil)
   152  	require.NoError(t, err)
   153  	WaitForAllocStopped(t, nomadClient, allocID)
   154  }
   155  
   156  //e2e:batch fail=false
   157  //e2e:service running=5s check=script.sh
   158  
   159  func NewE2EJob(jobfile string) framework.TestCase {
   160  	return &e2eJob{
   161  		jobfile: jobfile,
   162  	}
   163  
   164  }
   165  
   166  func parseServiceJobLine(t *testing.T, j *e2eJob, line string) *e2eServiceJob {
   167  	job := &e2eServiceJob{
   168  		e2eJob:          j,
   169  		runningDuration: time.Second * 5,
   170  	}
   171  	for _, options := range strings.Split(line, " ")[1:] {
   172  		o := strings.SplitN(options, "=", 2)
   173  		switch o[0] {
   174  		case "script":
   175  			job.script = o[1]
   176  		case "running":
   177  			dur, err := time.ParseDuration(o[1])
   178  			if err != nil {
   179  				t.Logf("could not parse running duration %q for e2e job spec: %v", o[1], err)
   180  			} else {
   181  				job.runningDuration = dur
   182  			}
   183  		}
   184  	}
   185  
   186  	return job
   187  }
   188  
   189  func parseBatchJobLine(t *testing.T, j *e2eJob, line string) *e2eBatchJob {
   190  	job := &e2eBatchJob{
   191  		e2eJob: j,
   192  	}
   193  	for _, options := range strings.Split(line, " ")[1:] {
   194  		o := strings.SplitN(options, "=", 2)
   195  		switch o[0] {
   196  		case "shouldFail":
   197  			job.shouldFail, _ = strconv.ParseBool(o[1])
   198  		}
   199  	}
   200  
   201  	return job
   202  }