github.com/hernad/nomad@v1.6.112/command/job_scaling_events_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package command 5 6 import ( 7 "fmt" 8 "strings" 9 "testing" 10 11 "github.com/hernad/nomad/api" 12 "github.com/hernad/nomad/ci" 13 "github.com/hernad/nomad/command/agent" 14 "github.com/hernad/nomad/helper/pointer" 15 "github.com/hernad/nomad/nomad/mock" 16 "github.com/hernad/nomad/nomad/structs" 17 "github.com/hernad/nomad/testutil" 18 "github.com/mitchellh/cli" 19 "github.com/shoenig/test/must" 20 ) 21 22 func TestJobScalingEventsCommand_Run(t *testing.T) { 23 ci.Parallel(t) 24 srv, client, url := testServer(t, true, nil) 25 defer srv.Shutdown() 26 testutil.WaitForResult(func() (bool, error) { 27 nodes, _, err := client.Nodes().List(nil) 28 if err != nil { 29 return false, err 30 } 31 if len(nodes) == 0 { 32 return false, fmt.Errorf("missing node") 33 } 34 if _, ok := nodes[0].Drivers["mock_driver"]; !ok { 35 return false, fmt.Errorf("mock_driver not ready") 36 } 37 return true, nil 38 }, func(err error) { 39 t.Fatalf("err: %s", err) 40 }) 41 42 ui := cli.NewMockUi() 43 cmd := &JobScalingEventsCommand{Meta: Meta{Ui: ui}} 44 45 // Register a test job and ensure it is running before moving on. 46 resp, _, err := client.Jobs().Register(testJob("scale_events_test_job"), nil) 47 if err != nil { 48 t.Fatalf("err: %s", err) 49 } 50 if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 { 51 t.Fatalf("expected waitForSuccess exit code 0, got: %d", code) 52 } 53 54 // List events without passing the jobID which should result in an error. 55 if code := cmd.Run([]string{"-address=" + url}); code != 1 { 56 t.Fatalf("expected cmd run exit code 1, got: %d", code) 57 } 58 if out := ui.ErrorWriter.String(); !strings.Contains(out, "This command takes one argument: <job_id>") { 59 t.Fatalf("Expected argument error: %v", out) 60 } 61 62 // List events for the job, which should present zero. 63 if code := cmd.Run([]string{"-address=" + url, "scale_events_test_job"}); code != 0 { 64 t.Fatalf("expected cmd run exit code 0, got: %d", code) 65 } 66 if out := ui.OutputWriter.String(); !strings.Contains(out, "No events found") { 67 t.Fatalf("Expected no events output but got: %v", out) 68 } 69 70 // Perform a scaling action to generate an event. 71 _, _, err = client.Jobs().Scale( 72 "scale_events_test_job", 73 "group1", pointer.Of(2), 74 "searchable custom test message", false, nil, nil) 75 if err != nil { 76 t.Fatalf("err: %s", err) 77 } 78 79 // List the scaling events which should include an entry. 80 if code := cmd.Run([]string{"-address=" + url, "scale_events_test_job"}); code != 0 { 81 t.Fatalf("expected cmd run exit code 0, got: %d", code) 82 } 83 if out := ui.OutputWriter.String(); !strings.Contains(out, "Task Group Count PrevCount Date") { 84 t.Fatalf("Expected table headers but got: %v", out) 85 } 86 87 // List the scaling events with verbose flag to search for our message as 88 // well as the verbose table headers. 89 if code := cmd.Run([]string{"-address=" + url, "-verbose", "scale_events_test_job"}); code != 0 { 90 t.Fatalf("expected cmd run exit code 0, got: %d", code) 91 } 92 out := ui.OutputWriter.String() 93 if !strings.Contains(out, "searchable custom test message") { 94 t.Fatalf("Expected to find scaling message but got: %v", out) 95 } 96 if !strings.Contains(out, "Eval ID") { 97 t.Fatalf("Expected to verbose table headers: %v", out) 98 } 99 } 100 101 func TestJobScalingEventsCommand_ACL(t *testing.T) { 102 ci.Parallel(t) 103 104 // Start server with ACL enabled. 105 srv, _, url := testServer(t, true, func(c *agent.Config) { 106 c.ACL.Enabled = true 107 }) 108 defer srv.Shutdown() 109 110 // Create a job. 111 job := mock.MinJob() 112 state := srv.Agent.Server().State() 113 err := state.UpsertJob(structs.MsgTypeTestSetup, 100, nil, job) 114 must.NoError(t, err) 115 116 testCases := []struct { 117 name string 118 jobPrefix bool 119 aclPolicy string 120 expectedErr string 121 }{ 122 { 123 name: "no token", 124 aclPolicy: "", 125 expectedErr: api.PermissionDeniedErrorContent, 126 }, 127 { 128 name: "missing read-job or read-job-scaling", 129 aclPolicy: ` 130 namespace "default" { 131 capabilities = ["submit-job"] 132 } 133 `, 134 expectedErr: api.PermissionDeniedErrorContent, 135 }, 136 { 137 name: "read-job-scaling allowed", 138 aclPolicy: ` 139 namespace "default" { 140 capabilities = ["read-job-scaling"] 141 } 142 `, 143 }, 144 { 145 name: "read-job allowed", 146 aclPolicy: ` 147 namespace "default" { 148 capabilities = ["read-job"] 149 } 150 `, 151 }, 152 { 153 name: "job prefix requires list-job", 154 jobPrefix: true, 155 aclPolicy: ` 156 namespace "default" { 157 capabilities = ["read-job-scaling"] 158 } 159 `, 160 expectedErr: "job not found", 161 }, 162 { 163 name: "job prefix works with list-job", 164 jobPrefix: true, 165 aclPolicy: ` 166 namespace "default" { 167 capabilities = ["read-job-scaling","list-jobs"] 168 } 169 `, 170 }, 171 } 172 173 for i, tc := range testCases { 174 t.Run(tc.name, func(t *testing.T) { 175 ui := cli.NewMockUi() 176 cmd := &JobScalingEventsCommand{Meta: Meta{Ui: ui}} 177 args := []string{ 178 "-address", url, 179 } 180 181 if tc.aclPolicy != "" { 182 // Create ACL token with test case policy and add it to the 183 // command. 184 policyName := nonAlphaNum.ReplaceAllString(tc.name, "-") 185 token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy) 186 args = append(args, "-token", token.SecretID) 187 } 188 189 // Add job ID or job ID prefix to the command. 190 if tc.jobPrefix { 191 args = append(args, job.ID[:3]) 192 } else { 193 args = append(args, job.ID) 194 } 195 196 // Run command. 197 code := cmd.Run(args) 198 if tc.expectedErr == "" { 199 must.Zero(t, code) 200 } else { 201 must.One(t, code) 202 must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr) 203 } 204 }) 205 } 206 }