github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/e2e/e2eutil/job.go (about)

     1  package e2eutil
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os/exec"
     9  	"regexp"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  // Register registers a jobspec from a file but with a unique ID.
    18  // The caller is responsible for recording that ID for later cleanup.
    19  func Register(jobID, jobFilePath string) error {
    20  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    21  	defer cancel()
    22  	return register(jobID, jobFilePath, exec.CommandContext(ctx, "nomad", "job", "run", "-detach", "-"))
    23  }
    24  
    25  // RegisterWithArgs registers a jobspec from a file but with a unique ID. The
    26  // optional args are added to the run command. The caller is responsible for
    27  // recording that ID for later cleanup.
    28  func RegisterWithArgs(jobID, jobFilePath string, args ...string) error {
    29  
    30  	baseArgs := []string{"job", "run", "-detach"}
    31  	for i := range args {
    32  		baseArgs = append(baseArgs, args[i])
    33  	}
    34  	baseArgs = append(baseArgs, "-")
    35  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    36  	defer cancel()
    37  
    38  	return register(jobID, jobFilePath, exec.CommandContext(ctx, "nomad", baseArgs...))
    39  }
    40  
    41  func register(jobID, jobFilePath string, cmd *exec.Cmd) error {
    42  	stdin, err := cmd.StdinPipe()
    43  	if err != nil {
    44  		return fmt.Errorf("could not open stdin?: %w", err)
    45  	}
    46  
    47  	content, err := ioutil.ReadFile(jobFilePath)
    48  	if err != nil {
    49  		return fmt.Errorf("could not open job file: %w", err)
    50  	}
    51  
    52  	// hack off the first line to replace with our unique ID
    53  	var re = regexp.MustCompile(`(?m)^job ".*" \{`)
    54  	jobspec := re.ReplaceAllString(string(content),
    55  		fmt.Sprintf("job \"%s\" {", jobID))
    56  
    57  	go func() {
    58  		defer stdin.Close()
    59  		io.WriteString(stdin, jobspec)
    60  	}()
    61  
    62  	out, err := cmd.CombinedOutput()
    63  	if err != nil {
    64  		return fmt.Errorf("could not register job: %w\n%v", err, string(out))
    65  	}
    66  	return nil
    67  }
    68  
    69  // PeriodicForce forces a periodic job to dispatch
    70  func PeriodicForce(jobID string) error {
    71  	// nomad job periodic force
    72  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    73  	defer cancel()
    74  	cmd := exec.CommandContext(ctx, "nomad", "job", "periodic", "force", jobID)
    75  
    76  	out, err := cmd.CombinedOutput()
    77  	if err != nil {
    78  		return fmt.Errorf("could not register job: %w\n%v", err, string(out))
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  // Dispatch dispatches a parameterized job
    85  func Dispatch(jobID string, meta map[string]string, payload string) error {
    86  	// nomad job periodic force
    87  	args := []string{"job", "dispatch"}
    88  	for k, v := range meta {
    89  		args = append(args, "-meta", fmt.Sprintf("%v=%v", k, v))
    90  	}
    91  	args = append(args, jobID)
    92  	if payload != "" {
    93  		args = append(args, "-")
    94  	}
    95  
    96  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    97  	defer cancel()
    98  	cmd := exec.CommandContext(ctx, "nomad", args...)
    99  	cmd.Stdin = strings.NewReader(payload)
   100  
   101  	out, err := cmd.CombinedOutput()
   102  	if err != nil {
   103  		return fmt.Errorf("could not dispatch job: %w\n%v", err, string(out))
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  // JobInspectTemplate runs nomad job inspect and formats the output
   110  // using the specified go template
   111  func JobInspectTemplate(jobID, template string) (string, error) {
   112  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   113  	defer cancel()
   114  	cmd := exec.CommandContext(ctx, "nomad", "job", "inspect", "-t", template, jobID)
   115  	out, err := cmd.CombinedOutput()
   116  	if err != nil {
   117  		return "", fmt.Errorf("could not inspect job: %w\n%v", err, string(out))
   118  	}
   119  	outStr := string(out)
   120  	outStr = strings.TrimSuffix(outStr, "\n")
   121  	return outStr, nil
   122  }
   123  
   124  // RegisterFromJobspec registers a jobspec from a string, also with a unique
   125  // ID. The caller is responsible for recording that ID for later cleanup.
   126  func RegisterFromJobspec(jobID, jobspec string) error {
   127  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   128  	defer cancel()
   129  	cmd := exec.CommandContext(ctx, "nomad", "job", "run", "-detach", "-")
   130  	stdin, err := cmd.StdinPipe()
   131  	if err != nil {
   132  		return fmt.Errorf("could not open stdin?: %w", err)
   133  	}
   134  
   135  	// hack off the first line to replace with our unique ID
   136  	var re = regexp.MustCompile(`^job "\w+" \{`)
   137  	jobspec = re.ReplaceAllString(jobspec,
   138  		fmt.Sprintf("job \"%s\" {", jobID))
   139  
   140  	go func() {
   141  		defer stdin.Close()
   142  		io.WriteString(stdin, jobspec)
   143  	}()
   144  
   145  	out, err := cmd.CombinedOutput()
   146  	if err != nil {
   147  		return fmt.Errorf("could not register job: %w\n%v", err, string(out))
   148  	}
   149  	return nil
   150  }
   151  
   152  func ChildrenJobSummary(jobID string) ([]map[string]string, error) {
   153  	out, err := Command("nomad", "job", "status", jobID)
   154  	if err != nil {
   155  		return nil, fmt.Errorf("nomad job status failed: %w", err)
   156  	}
   157  
   158  	section, err := GetSection(out, "Children Job Summary")
   159  	if err != nil {
   160  		section, err = GetSection(out, "Parameterized Job Summary")
   161  		if err != nil {
   162  			return nil, fmt.Errorf("could not find children job summary section: %w", err)
   163  		}
   164  	}
   165  
   166  	summary, err := ParseColumns(section)
   167  	if err != nil {
   168  		return nil, fmt.Errorf("could not parse children job summary section: %w", err)
   169  	}
   170  
   171  	return summary, nil
   172  }
   173  
   174  func PreviouslyLaunched(jobID string) ([]map[string]string, error) {
   175  	out, err := Command("nomad", "job", "status", jobID)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("nomad job status failed: %w", err)
   178  	}
   179  
   180  	section, err := GetSection(out, "Previously Launched Jobs")
   181  	if err != nil {
   182  		return nil, fmt.Errorf("could not find previously launched jobs section: %w", err)
   183  	}
   184  
   185  	summary, err := ParseColumns(section)
   186  	if err != nil {
   187  		return nil, fmt.Errorf("could not parse previously launched jobs section: %w", err)
   188  	}
   189  
   190  	return summary, nil
   191  }
   192  
   193  func DispatchedJobs(jobID string) ([]map[string]string, error) {
   194  	out, err := Command("nomad", "job", "status", jobID)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("nomad job status failed: %w", err)
   197  	}
   198  
   199  	section, err := GetSection(out, "Dispatched Jobs")
   200  	if err != nil {
   201  		return nil, fmt.Errorf("could not find previously launched jobs section: %w", err)
   202  	}
   203  
   204  	summary, err := ParseColumns(section)
   205  	if err != nil {
   206  		return nil, fmt.Errorf("could not parse previously launched jobs section: %w", err)
   207  	}
   208  
   209  	return summary, nil
   210  }
   211  
   212  func StopJob(jobID string, args ...string) error {
   213  
   214  	// Build our argument list in the correct order, ensuring the jobID is last
   215  	// and the Nomad subcommand are first.
   216  	baseArgs := []string{"job", "stop"}
   217  	for i := range args {
   218  		baseArgs = append(baseArgs, args[i])
   219  	}
   220  	baseArgs = append(baseArgs, jobID)
   221  
   222  	// Execute the command. We do not care about the stdout, only stderr.
   223  	_, err := Command("nomad", baseArgs...)
   224  
   225  	if err != nil {
   226  		// When stopping a job and monitoring the resulting deployment, we
   227  		// expect that the monitor fails and exits with status code one because
   228  		// technically the deployment has failed. Overwrite the error to be
   229  		// nil.
   230  		if strings.Contains(err.Error(), "Description = Cancelled because job is stopped") ||
   231  			strings.Contains(err.Error(), "Description = Failed due to progress deadline") {
   232  			err = nil
   233  		}
   234  	}
   235  	return err
   236  }
   237  
   238  // CleanupJobsAndGC stops and purges the list of jobIDs and runs a
   239  // system gc. Returns a func so that the return value can be used
   240  // in t.Cleanup
   241  func CleanupJobsAndGC(t *testing.T, jobIDs *[]string) func() {
   242  	return func() {
   243  		for _, jobID := range *jobIDs {
   244  			err := StopJob(jobID, "-purge", "-detach")
   245  			assert.NoError(t, err)
   246  		}
   247  		_, err := Command("nomad", "system", "gc")
   248  		assert.NoError(t, err)
   249  	}
   250  }
   251  
   252  // CleanupJobsAndGCWithContext stops and purges the list of jobIDs and runs a
   253  // system gc. The passed context allows callers to cancel the execution of the
   254  // cleanup as they desire. This is useful for tests which attempt to remove the
   255  // job as part of their run, but may fail before that point is reached.
   256  func CleanupJobsAndGCWithContext(t *testing.T, ctx context.Context, jobIDs *[]string) {
   257  
   258  	// Check the context before continuing. If this has been closed return,
   259  	// otherwise fallthrough and complete the work.
   260  	select {
   261  	case <-ctx.Done():
   262  		return
   263  	default:
   264  	}
   265  	for _, jobID := range *jobIDs {
   266  		err := StopJob(jobID, "-purge", "-detach")
   267  		assert.NoError(t, err)
   268  	}
   269  	_, err := Command("nomad", "system", "gc")
   270  	assert.NoError(t, err)
   271  }