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 }