github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/checks_hook_test.go (about) 1 package allocrunner 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/http/httptest" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/hashicorp/go-hclog" 13 "github.com/hashicorp/nomad/ci" 14 "github.com/hashicorp/nomad/client/allocrunner/interfaces" 15 "github.com/hashicorp/nomad/client/serviceregistration/checks/checkstore" 16 "github.com/hashicorp/nomad/client/state" 17 "github.com/hashicorp/nomad/helper/testlog" 18 "github.com/hashicorp/nomad/nomad/mock" 19 "github.com/hashicorp/nomad/nomad/structs" 20 "github.com/hashicorp/nomad/testutil" 21 "github.com/shoenig/test/must" 22 ) 23 24 var ( 25 _ interfaces.RunnerPrerunHook = (*checksHook)(nil) 26 _ interfaces.RunnerUpdateHook = (*checksHook)(nil) 27 _ interfaces.RunnerPreKillHook = (*checksHook)(nil) 28 ) 29 30 func makeCheckStore(logger hclog.Logger) checkstore.Shim { 31 db := state.NewMemDB(logger) 32 checkStore := checkstore.NewStore(logger, db) 33 return checkStore 34 } 35 36 func allocWithNomadChecks(addr, port string, onGroup bool) *structs.Allocation { 37 alloc := mock.Alloc() 38 group := alloc.Job.LookupTaskGroup(alloc.TaskGroup) 39 40 task := "task-one" 41 if onGroup { 42 task = "" 43 } 44 45 services := []*structs.Service{ 46 { 47 Name: "service-one", 48 TaskName: "web", 49 PortLabel: port, 50 AddressMode: "auto", 51 Address: addr, 52 Provider: "nomad", 53 Checks: []*structs.ServiceCheck{ 54 { 55 Name: "check-ok", 56 Type: "http", 57 Path: "/", 58 Protocol: "http", 59 PortLabel: port, 60 AddressMode: "auto", 61 Interval: 250 * time.Millisecond, 62 Timeout: 1 * time.Second, 63 Method: "GET", 64 TaskName: task, 65 }, 66 { 67 Name: "check-error", 68 Type: "http", 69 Path: "/fail", 70 Protocol: "http", 71 PortLabel: port, 72 AddressMode: "auto", 73 Interval: 250 * time.Millisecond, 74 Timeout: 1 * time.Second, 75 Method: "GET", 76 TaskName: task, 77 }, 78 { 79 Name: "check-hang", 80 Type: "http", 81 Path: "/hang", 82 Protocol: "http", 83 PortLabel: port, 84 AddressMode: "auto", 85 Interval: 250 * time.Millisecond, 86 Timeout: 500 * time.Millisecond, 87 Method: "GET", 88 TaskName: task, 89 }, 90 }, 91 }, 92 } 93 94 switch onGroup { 95 case true: 96 group.Tasks[0].Services = nil 97 group.Services = services 98 case false: 99 group.Services = nil 100 group.Tasks[0].Services = services 101 } 102 return alloc 103 } 104 105 func allocWithDifferentNomadChecks(id, addr, port string) *structs.Allocation { 106 alloc := allocWithNomadChecks(addr, port, true) 107 alloc.ID = id 108 group := alloc.Job.LookupTaskGroup(alloc.TaskGroup) 109 110 group.Services[0].Checks[2].Path = "/" // the hanging check is now ok 111 112 // append 4th check, this one is failing 113 group.Services[0].Checks = append(group.Services[0].Checks, &structs.ServiceCheck{ 114 Name: "check-error-2", 115 Type: "http", 116 Path: "/fail", 117 Protocol: "http", 118 PortLabel: port, 119 AddressMode: "auto", 120 Interval: 250 * time.Millisecond, 121 Timeout: 1 * time.Second, 122 Method: "GET", 123 }) 124 return alloc 125 } 126 127 var checkHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 128 switch r.URL.Path { 129 case "/fail": 130 w.WriteHeader(500) 131 _, _ = io.WriteString(w, "500 problem") 132 case "/hang": 133 time.Sleep(2 * time.Second) 134 _, _ = io.WriteString(w, "too slow") 135 default: 136 w.WriteHeader(200) 137 _, _ = io.WriteString(w, "200 ok") 138 } 139 }) 140 141 func TestCheckHook_Checks_ResultsSet(t *testing.T) { 142 ci.Parallel(t) 143 144 logger := testlog.HCLogger(t) 145 146 // create an http server with various responses 147 ts := httptest.NewServer(checkHandler) 148 defer ts.Close() 149 150 cases := []struct { 151 name string 152 onGroup bool 153 }{ 154 {name: "group-level", onGroup: true}, 155 {name: "task-level", onGroup: false}, 156 } 157 158 for _, tc := range cases { 159 checkStore := makeCheckStore(logger) 160 161 // get the address and port for http server 162 tokens := strings.Split(ts.URL, ":") 163 addr, port := strings.TrimPrefix(tokens[1], "//"), tokens[2] 164 165 network := mock.NewNetworkStatus(addr) 166 167 alloc := allocWithNomadChecks(addr, port, tc.onGroup) 168 169 h := newChecksHook(logger, alloc, checkStore, network) 170 171 // initialize is called; observers are created but not started yet 172 must.MapEmpty(t, h.observers) 173 174 // calling pre-run starts the observers 175 err := h.Prerun() 176 must.NoError(t, err) 177 178 testutil.WaitForResultUntil( 179 2*time.Second, 180 func() (bool, error) { 181 results := checkStore.List(alloc.ID) 182 passing, failing, pending := 0, 0, 0 183 for _, result := range results { 184 switch result.Status { 185 case structs.CheckSuccess: 186 passing++ 187 case structs.CheckFailure: 188 failing++ 189 case structs.CheckPending: 190 pending++ 191 } 192 } 193 if passing != 1 || failing != 2 || pending != 0 { 194 fmt.Printf("results %v\n", results) 195 return false, fmt.Errorf( 196 "expected 1 passing, 2 failing, 0 pending, got %d passing, %d failing, %d pending", 197 passing, failing, pending, 198 ) 199 } 200 return true, nil 201 }, 202 func(err error) { 203 t.Fatalf(err.Error()) 204 }, 205 ) 206 207 h.PreKill() // stop observers, cleanup 208 209 // assert shim no longer contains results for the alloc 210 results := checkStore.List(alloc.ID) 211 must.MapEmpty(t, results) 212 } 213 } 214 215 func TestCheckHook_Checks_UpdateSet(t *testing.T) { 216 ci.Parallel(t) 217 218 logger := testlog.HCLogger(t) 219 220 // create an http server with various responses 221 ts := httptest.NewServer(checkHandler) 222 defer ts.Close() 223 224 // get the address and port for http server 225 tokens := strings.Split(ts.URL, ":") 226 addr, port := strings.TrimPrefix(tokens[1], "//"), tokens[2] 227 228 shim := makeCheckStore(logger) 229 230 network := mock.NewNetworkStatus(addr) 231 232 alloc := allocWithNomadChecks(addr, port, true) 233 234 h := newChecksHook(logger, alloc, shim, network) 235 236 // calling pre-run starts the observers 237 err := h.Prerun() 238 must.NoError(t, err) 239 240 // initial set of checks 241 testutil.WaitForResultUntil( 242 2*time.Second, 243 func() (bool, error) { 244 results := shim.List(alloc.ID) 245 passing, failing, pending := 0, 0, 0 246 for _, result := range results { 247 switch result.Status { 248 case structs.CheckSuccess: 249 passing++ 250 case structs.CheckFailure: 251 failing++ 252 case structs.CheckPending: 253 pending++ 254 } 255 } 256 if passing != 1 || failing != 2 || pending != 0 { 257 fmt.Printf("results %v\n", results) 258 return false, fmt.Errorf( 259 "(initial set) expected 1 passing, 2 failing, 0 pending, got %d passing, %d failing, %d pending", 260 passing, failing, pending, 261 ) 262 } 263 return true, nil 264 }, 265 func(err error) { 266 t.Fatalf(err.Error()) 267 }, 268 ) 269 270 request := &interfaces.RunnerUpdateRequest{ 271 Alloc: allocWithDifferentNomadChecks(alloc.ID, addr, port), 272 } 273 274 err = h.Update(request) 275 must.NoError(t, err) 276 277 // updated set of checks 278 testutil.WaitForResultUntil( 279 2*time.Second, 280 func() (bool, error) { 281 results := shim.List(alloc.ID) 282 passing, failing, pending := 0, 0, 0 283 for _, result := range results { 284 switch result.Status { 285 case structs.CheckSuccess: 286 passing++ 287 case structs.CheckFailure: 288 failing++ 289 case structs.CheckPending: 290 pending++ 291 } 292 } 293 if passing != 2 || failing != 2 || pending != 0 { 294 fmt.Printf("results %v\n", results) 295 return false, fmt.Errorf( 296 "(updated set) expected 2 passing, 2 failing, 0 pending, got %d passing, %d failing, %d pending", 297 passing, failing, pending, 298 ) 299 } 300 return true, nil 301 }, 302 func(err error) { 303 t.Fatalf(err.Error()) 304 }, 305 ) 306 307 h.PreKill() // stop observers, cleanup 308 309 // assert shim no longer contains results for the alloc 310 results := shim.List(alloc.ID) 311 must.MapEmpty(t, results) 312 }