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 }