github.com/hernad/nomad@v1.6.112/e2e/e2eutil/e2ejob.go (about)

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