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

     1  package e2eutil
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/api"
    11  	"github.com/hashicorp/nomad/testutil"
    12  	"github.com/kr/pretty"
    13  )
    14  
    15  // WaitForAllocStatusExpected polls 'nomad job status' and exactly compares
    16  // the status of all allocations (including any previous versions) against the
    17  // expected list.
    18  func WaitForAllocStatusExpected(jobID, ns string, expected []string) error {
    19  	err := WaitForAllocStatusComparison(
    20  		func() ([]string, error) { return AllocStatuses(jobID, ns) },
    21  		func(got []string) bool { return reflect.DeepEqual(got, expected) },
    22  		nil,
    23  	)
    24  	if err != nil {
    25  		allocs, _ := AllocsForJob(jobID, ns)
    26  		err = fmt.Errorf("%v\nallocs: %v", err, pretty.Sprint(allocs))
    27  	}
    28  	return err
    29  }
    30  
    31  // WaitForAllocStatusComparison is a convenience wrapper that polls the query
    32  // function until the comparison function returns true.
    33  func WaitForAllocStatusComparison(query func() ([]string, error), comparison func([]string) bool, wc *WaitConfig) error {
    34  	var got []string
    35  	var err error
    36  	interval, retries := wc.OrDefault()
    37  	testutil.WaitForResultRetries(retries, func() (bool, error) {
    38  		time.Sleep(interval)
    39  		got, err = query()
    40  		if err != nil {
    41  			return false, err
    42  		}
    43  		return comparison(got), nil
    44  	}, func(e error) {
    45  		err = fmt.Errorf("alloc status check failed: got %#v", got)
    46  	})
    47  	return err
    48  }
    49  
    50  // AllocsForJob returns a slice of key->value maps, each describing the values
    51  // of the 'nomad job status' Allocations section (not actual
    52  // structs.Allocation objects, query the API if you want those)
    53  func AllocsForJob(jobID, ns string) ([]map[string]string, error) {
    54  
    55  	var nsArg = []string{}
    56  	if ns != "" {
    57  		nsArg = []string{"-namespace", ns}
    58  	}
    59  
    60  	cmd := []string{"nomad", "job", "status"}
    61  	params := []string{"-verbose", "-all-allocs", jobID}
    62  	cmd = append(cmd, nsArg...)
    63  	cmd = append(cmd, params...)
    64  
    65  	out, err := Command(cmd[0], cmd[1:]...)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("'nomad job status' failed: %w", err)
    68  	}
    69  
    70  	section, err := GetSection(out, "Allocations")
    71  	if err != nil {
    72  		return nil, fmt.Errorf("could not find Allocations section: %w", err)
    73  	}
    74  
    75  	allocs, err := ParseColumns(section)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("could not parse Allocations section: %w", err)
    78  	}
    79  	return allocs, nil
    80  }
    81  
    82  // AllocTaskEventsForJob returns a map of allocation IDs containing a map of
    83  // Task Event key value pairs
    84  func AllocTaskEventsForJob(jobID, ns string) (map[string][]map[string]string, error) {
    85  	allocs, err := AllocsForJob(jobID, ns)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	results := make(map[string][]map[string]string)
    91  	for _, alloc := range allocs {
    92  		results[alloc["ID"]] = make([]map[string]string, 0)
    93  
    94  		cmd := []string{"nomad", "alloc", "status", alloc["ID"]}
    95  		out, err := Command(cmd[0], cmd[1:]...)
    96  		if err != nil {
    97  			return nil, fmt.Errorf("querying alloc status: %w", err)
    98  		}
    99  
   100  		section, err := GetSection(out, "Recent Events:")
   101  		if err != nil {
   102  			return nil, fmt.Errorf("could not find Recent Events section: %w", err)
   103  		}
   104  
   105  		events, err := ParseColumns(section)
   106  		if err != nil {
   107  			return nil, fmt.Errorf("could not parse recent events section: %w", err)
   108  		}
   109  		results[alloc["ID"]] = events
   110  	}
   111  
   112  	return results, nil
   113  }
   114  
   115  // AllocsForNode returns a slice of key->value maps, each describing the values
   116  // of the 'nomad node status' Allocations section (not actual
   117  // structs.Allocation objects, query the API if you want those)
   118  func AllocsForNode(nodeID string) ([]map[string]string, error) {
   119  
   120  	out, err := Command("nomad", "node", "status", "-verbose", nodeID)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("'nomad node status' failed: %w", err)
   123  	}
   124  
   125  	section, err := GetSection(out, "Allocations")
   126  	if err != nil {
   127  		return nil, fmt.Errorf("could not find Allocations section: %w", err)
   128  	}
   129  
   130  	allocs, err := ParseColumns(section)
   131  	if err != nil {
   132  		return nil, fmt.Errorf("could not parse Allocations section: %w", err)
   133  	}
   134  	return allocs, nil
   135  }
   136  
   137  // AllocStatuses returns a slice of client statuses
   138  func AllocStatuses(jobID, ns string) ([]string, error) {
   139  
   140  	allocs, err := AllocsForJob(jobID, ns)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	statuses := []string{}
   146  	for _, alloc := range allocs {
   147  		statuses = append(statuses, alloc["Status"])
   148  	}
   149  	return statuses, nil
   150  }
   151  
   152  // AllocStatusesRescheduled is a helper function that pulls
   153  // out client statuses only from rescheduled allocs.
   154  func AllocStatusesRescheduled(jobID, ns string) ([]string, error) {
   155  
   156  	var nsArg = []string{}
   157  	if ns != "" {
   158  		nsArg = []string{"-namespace", ns}
   159  	}
   160  
   161  	cmd := []string{"nomad", "job", "status"}
   162  	params := []string{"-verbose", jobID}
   163  	cmd = append(cmd, nsArg...)
   164  	cmd = append(cmd, params...)
   165  
   166  	out, err := Command(cmd[0], cmd[1:]...)
   167  	if err != nil {
   168  		return nil, fmt.Errorf("nomad job status failed: %w", err)
   169  	}
   170  
   171  	section, err := GetSection(out, "Allocations")
   172  	if err != nil {
   173  		return nil, fmt.Errorf("could not find Allocations section: %w", err)
   174  	}
   175  
   176  	allocs, err := ParseColumns(section)
   177  	if err != nil {
   178  		return nil, fmt.Errorf("could not parse Allocations section: %w", err)
   179  	}
   180  
   181  	statuses := []string{}
   182  	for _, alloc := range allocs {
   183  
   184  		allocID := alloc["ID"]
   185  
   186  		cmd := []string{"nomad", "alloc", "status"}
   187  		params := []string{"-json", allocID}
   188  		cmd = append(cmd, nsArg...)
   189  		cmd = append(cmd, params...)
   190  
   191  		// reschedule tracker isn't exposed in the normal CLI output
   192  		out, err := Command(cmd[0], cmd[1:]...)
   193  		if err != nil {
   194  			return nil, fmt.Errorf("nomad alloc status failed: %w", err)
   195  		}
   196  
   197  		dec := json.NewDecoder(strings.NewReader(out))
   198  		alloc := &api.Allocation{}
   199  		err = dec.Decode(alloc)
   200  		if err != nil {
   201  			return nil, fmt.Errorf("could not decode alloc status JSON: %w", err)
   202  		}
   203  
   204  		if (alloc.RescheduleTracker != nil &&
   205  			len(alloc.RescheduleTracker.Events) > 0) || alloc.FollowupEvalID != "" {
   206  			statuses = append(statuses, alloc.ClientStatus)
   207  		}
   208  	}
   209  	return statuses, nil
   210  }
   211  
   212  type LogStream int
   213  
   214  const (
   215  	LogsStdErr LogStream = iota
   216  	LogsStdOut
   217  )
   218  
   219  func AllocLogs(allocID string, logStream LogStream) (string, error) {
   220  	cmd := []string{"nomad", "alloc", "logs"}
   221  	if logStream == LogsStdErr {
   222  		cmd = append(cmd, "-stderr")
   223  	}
   224  	cmd = append(cmd, allocID)
   225  	return Command(cmd[0], cmd[1:]...)
   226  }
   227  
   228  func AllocTaskLogs(allocID, task string, logStream LogStream) (string, error) {
   229  	cmd := []string{"nomad", "alloc", "logs"}
   230  	if logStream == LogsStdErr {
   231  		cmd = append(cmd, "-stderr")
   232  	}
   233  	cmd = append(cmd, allocID, task)
   234  	return Command(cmd[0], cmd[1:]...)
   235  }
   236  
   237  // AllocExec is a convenience wrapper that runs 'nomad alloc exec' with the
   238  // passed execCmd via '/bin/sh -c', retrying if the task isn't ready
   239  func AllocExec(allocID, taskID, execCmd, ns string, wc *WaitConfig) (string, error) {
   240  	var got string
   241  	var err error
   242  	interval, retries := wc.OrDefault()
   243  
   244  	var nsArg = []string{}
   245  	if ns != "" {
   246  		nsArg = []string{"-namespace", ns}
   247  	}
   248  
   249  	cmd := []string{"nomad", "exec"}
   250  	params := []string{"-task", taskID, allocID, "/bin/sh", "-c", execCmd}
   251  	cmd = append(cmd, nsArg...)
   252  	cmd = append(cmd, params...)
   253  
   254  	testutil.WaitForResultRetries(retries, func() (bool, error) {
   255  		time.Sleep(interval)
   256  		got, err = Command(cmd[0], cmd[1:]...)
   257  		return err == nil, err
   258  	}, func(e error) {
   259  		err = fmt.Errorf("exec failed: '%s': %v\nGot: %v", strings.Join(cmd, " "), e, got)
   260  	})
   261  	return got, err
   262  }
   263  
   264  // WaitForAllocFile is a helper that grabs a file via alloc fs and tests its
   265  // contents; useful for checking the results of rendered templates
   266  func WaitForAllocFile(allocID, path string, test func(string) bool, wc *WaitConfig) error {
   267  	var err error
   268  	var out string
   269  	interval, retries := wc.OrDefault()
   270  
   271  	testutil.WaitForResultRetries(retries, func() (bool, error) {
   272  		time.Sleep(interval)
   273  		out, err = Command("nomad", "alloc", "fs", allocID, path)
   274  		if err != nil {
   275  			return false, fmt.Errorf("could not get file %q from allocation %q: %v",
   276  				path, allocID, err)
   277  		}
   278  		return test(out), nil
   279  	}, func(e error) {
   280  		err = fmt.Errorf("test for file content failed: got %#v\nerror: %v", out, e)
   281  	})
   282  	return err
   283  }