github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/job_plan_test.go (about) 1 package command 2 3 import ( 4 "io/ioutil" 5 "os" 6 "strconv" 7 "strings" 8 "testing" 9 10 "github.com/hashicorp/nomad/api" 11 "github.com/hashicorp/nomad/testutil" 12 "github.com/mitchellh/cli" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestPlanCommand_Implements(t *testing.T) { 17 t.Parallel() 18 var _ cli.Command = &JobRunCommand{} 19 } 20 21 func TestPlanCommand_Fails(t *testing.T) { 22 t.Parallel() 23 24 // Create a server 25 s := testutil.NewTestServer(t, nil) 26 defer s.Stop() 27 28 ui := cli.NewMockUi() 29 cmd := &JobPlanCommand{Meta: Meta{Ui: ui, flagAddress: "http://" + s.HTTPAddr}} 30 31 // Fails on misuse 32 if code := cmd.Run([]string{"some", "bad", "args"}); code != 255 { 33 t.Fatalf("expected exit code 1, got: %d", code) 34 } 35 if out := ui.ErrorWriter.String(); !strings.Contains(out, commandErrorText(cmd)) { 36 t.Fatalf("expected help output, got: %s", out) 37 } 38 ui.ErrorWriter.Reset() 39 40 // Fails when specified file does not exist 41 if code := cmd.Run([]string{"/unicorns/leprechauns"}); code != 255 { 42 t.Fatalf("expect exit 255, got: %d", code) 43 } 44 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error getting job struct") { 45 t.Fatalf("expect getting job struct error, got: %s", out) 46 } 47 ui.ErrorWriter.Reset() 48 49 // Fails on invalid HCL 50 fh1, err := ioutil.TempFile("", "nomad") 51 if err != nil { 52 t.Fatalf("err: %s", err) 53 } 54 defer os.Remove(fh1.Name()) 55 if _, err := fh1.WriteString("nope"); err != nil { 56 t.Fatalf("err: %s", err) 57 } 58 if code := cmd.Run([]string{fh1.Name()}); code != 255 { 59 t.Fatalf("expect exit 255, got: %d", code) 60 } 61 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error getting job struct") { 62 t.Fatalf("expect parsing error, got: %s", out) 63 } 64 ui.ErrorWriter.Reset() 65 66 // Fails on invalid job spec 67 fh2, err := ioutil.TempFile("", "nomad") 68 if err != nil { 69 t.Fatalf("err: %s", err) 70 } 71 defer os.Remove(fh2.Name()) 72 if _, err := fh2.WriteString(`job "job1" {}`); err != nil { 73 t.Fatalf("err: %s", err) 74 } 75 if code := cmd.Run([]string{fh2.Name()}); code != 255 { 76 t.Fatalf("expect exit 255, got: %d", code) 77 } 78 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error during plan") { 79 t.Fatalf("expect validation error, got: %s", out) 80 } 81 ui.ErrorWriter.Reset() 82 83 // Fails on connection failure (requires a valid job) 84 fh3, err := ioutil.TempFile("", "nomad") 85 if err != nil { 86 t.Fatalf("err: %s", err) 87 } 88 defer os.Remove(fh3.Name()) 89 _, err = fh3.WriteString(` 90 job "job1" { 91 type = "service" 92 datacenters = [ "dc1" ] 93 group "group1" { 94 count = 1 95 task "task1" { 96 driver = "exec" 97 resources { 98 cpu = 1000 99 memory = 512 100 } 101 } 102 } 103 }`) 104 if err != nil { 105 t.Fatalf("err: %s", err) 106 } 107 if code := cmd.Run([]string{"-address=nope", fh3.Name()}); code != 255 { 108 t.Fatalf("expected exit code 255, got: %d", code) 109 } 110 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error during plan") { 111 t.Fatalf("expected failed query error, got: %s", out) 112 } 113 } 114 115 func TestPlanCommand_From_STDIN(t *testing.T) { 116 t.Parallel() 117 stdinR, stdinW, err := os.Pipe() 118 if err != nil { 119 t.Fatalf("err: %s", err) 120 } 121 122 ui := cli.NewMockUi() 123 cmd := &JobPlanCommand{ 124 Meta: Meta{Ui: ui}, 125 JobGetter: JobGetter{testStdin: stdinR}, 126 } 127 128 go func() { 129 stdinW.WriteString(` 130 job "job1" { 131 type = "service" 132 datacenters = [ "dc1" ] 133 group "group1" { 134 count = 1 135 task "task1" { 136 driver = "exec" 137 resources { 138 cpu = 1000 139 memory = 512 140 } 141 } 142 } 143 }`) 144 stdinW.Close() 145 }() 146 147 args := []string{"-"} 148 if code := cmd.Run(args); code != 255 { 149 t.Fatalf("expected exit code 255, got %d: %q", code, ui.ErrorWriter.String()) 150 } 151 152 if out := ui.ErrorWriter.String(); !strings.Contains(out, "connection refused") { 153 t.Fatalf("expected connection refused error, got: %s", out) 154 } 155 ui.ErrorWriter.Reset() 156 } 157 158 func TestPlanCommand_From_URL(t *testing.T) { 159 t.Parallel() 160 ui := cli.NewMockUi() 161 cmd := &JobPlanCommand{ 162 Meta: Meta{Ui: ui}, 163 } 164 165 args := []string{"https://example.com/foo/bar"} 166 if code := cmd.Run(args); code != 255 { 167 t.Fatalf("expected exit code 255, got %d: %q", code, ui.ErrorWriter.String()) 168 } 169 170 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error getting jobfile") { 171 t.Fatalf("expected error getting jobfile, got: %s", out) 172 } 173 } 174 175 func TestPlanCommad_Preemptions(t *testing.T) { 176 t.Parallel() 177 ui := cli.NewMockUi() 178 cmd := &JobPlanCommand{Meta: Meta{Ui: ui}} 179 require := require.New(t) 180 181 // Only one preempted alloc 182 resp1 := &api.JobPlanResponse{ 183 Annotations: &api.PlanAnnotations{ 184 PreemptedAllocs: []*api.AllocationListStub{ 185 { 186 ID: "alloc1", 187 JobID: "jobID1", 188 TaskGroup: "meta", 189 JobType: "batch", 190 Namespace: "test", 191 }, 192 }, 193 }, 194 } 195 cmd.addPreemptions(resp1) 196 out := ui.OutputWriter.String() 197 require.Contains(out, "Alloc ID") 198 require.Contains(out, "alloc1") 199 200 // Less than 10 unique job ids 201 var preemptedAllocs []*api.AllocationListStub 202 for i := 0; i < 12; i++ { 203 job_id := "job" + strconv.Itoa(i%4) 204 alloc := &api.AllocationListStub{ 205 ID: "alloc", 206 JobID: job_id, 207 TaskGroup: "meta", 208 JobType: "batch", 209 Namespace: "test", 210 } 211 preemptedAllocs = append(preemptedAllocs, alloc) 212 } 213 214 resp2 := &api.JobPlanResponse{ 215 Annotations: &api.PlanAnnotations{ 216 PreemptedAllocs: preemptedAllocs, 217 }, 218 } 219 ui.OutputWriter.Reset() 220 cmd.addPreemptions(resp2) 221 out = ui.OutputWriter.String() 222 require.Contains(out, "Job ID") 223 require.Contains(out, "Namespace") 224 225 // More than 10 unique job IDs 226 preemptedAllocs = make([]*api.AllocationListStub, 0) 227 var job_type string 228 for i := 0; i < 20; i++ { 229 job_id := "job" + strconv.Itoa(i) 230 if i%2 == 0 { 231 job_type = "service" 232 } else { 233 job_type = "batch" 234 } 235 alloc := &api.AllocationListStub{ 236 ID: "alloc", 237 JobID: job_id, 238 TaskGroup: "meta", 239 JobType: job_type, 240 Namespace: "test", 241 } 242 preemptedAllocs = append(preemptedAllocs, alloc) 243 } 244 245 resp3 := &api.JobPlanResponse{ 246 Annotations: &api.PlanAnnotations{ 247 PreemptedAllocs: preemptedAllocs, 248 }, 249 } 250 ui.OutputWriter.Reset() 251 cmd.addPreemptions(resp3) 252 out = ui.OutputWriter.String() 253 require.Contains(out, "Job Type") 254 require.Contains(out, "batch") 255 require.Contains(out, "service") 256 }