1package agent 2 3import ( 4 "fmt" 5 "io/ioutil" 6 "net" 7 "os" 8 "testing" 9 10 "github.com/hashicorp/serf/serf" 11) 12 13const eventScript = `#!/bin/sh 14RESULT_FILE="%s" 15echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE} 16echo $SERF_TAG_DC >> ${RESULT_FILE} 17echo $SERF_TAG_BAD_TAG >> ${RESULT_FILE} 18echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE} 19echo $os_env_var >> ${RESULT_FILE} 20while read line; do 21 printf "${line}\n" >>${RESULT_FILE} 22done 23` 24 25const userEventScript = `#!/bin/sh 26RESULT_FILE="%s" 27echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE} 28echo $SERF_TAG_DC >> ${RESULT_FILE} 29echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE} 30echo $SERF_EVENT $SERF_USER_LTIME "$@" >>${RESULT_FILE} 31while read line; do 32 printf "${line}\n" >>${RESULT_FILE} 33done 34` 35 36const queryScript = `#!/bin/sh 37RESULT_FILE="%s" 38echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE} 39echo $SERF_TAG_DC >> ${RESULT_FILE} 40echo $SERF_EVENT $SERF_QUERY_NAME "$@" >>${RESULT_FILE} 41echo $SERF_EVENT $SERF_QUERY_LTIME "$@" >>${RESULT_FILE} 42while read line; do 43 printf "${line}\n" >>${RESULT_FILE} 44done 45` 46 47// testEventScript creates an event script that can be used with the 48// agent. It returns the path to the event script itself and a path to 49// the file that will contain the events that that script receives. 50func testEventScript(t *testing.T, script string) (string, string) { 51 scriptFile, err := ioutil.TempFile("", "serf") 52 if err != nil { 53 t.Fatalf("err: %v", err) 54 } 55 defer scriptFile.Close() 56 57 if err := scriptFile.Chmod(0755); err != nil { 58 t.Fatalf("err: %v", err) 59 } 60 61 resultFile, err := ioutil.TempFile("", "serf-result") 62 if err != nil { 63 t.Fatalf("err: %v", err) 64 } 65 defer resultFile.Close() 66 67 _, err = scriptFile.Write([]byte( 68 fmt.Sprintf(script, resultFile.Name()))) 69 if err != nil { 70 t.Fatalf("err: %v", err) 71 } 72 73 return scriptFile.Name(), resultFile.Name() 74} 75 76func TestScriptEventHandler(t *testing.T) { 77 os.Setenv("os_env_var", "os-env-foo") 78 79 script, results := testEventScript(t, eventScript) 80 81 h := &ScriptEventHandler{ 82 SelfFunc: func() serf.Member { 83 return serf.Member{ 84 Name: "ourname", 85 Tags: map[string]string{"role": "ourrole", "dc": "east-aws", "bad-tag": "bad"}, 86 } 87 }, 88 Scripts: []EventScript{ 89 { 90 EventFilter: EventFilter{ 91 Event: "*", 92 }, 93 Script: script, 94 }, 95 }, 96 } 97 98 event := serf.MemberEvent{ 99 Type: serf.EventMemberJoin, 100 Members: []serf.Member{ 101 { 102 Name: "foo", 103 Addr: net.ParseIP("1.2.3.4"), 104 Tags: map[string]string{"role": "bar", "foo": "bar"}, 105 }, 106 }, 107 } 108 109 h.HandleEvent(event) 110 111 result, err := ioutil.ReadFile(results) 112 if err != nil { 113 t.Fatalf("err: %v", err) 114 } 115 116 expected1 := "ourname ourrole\neast-aws\nbad\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\trole=bar,foo=bar\n" 117 expected2 := "ourname ourrole\neast-aws\nbad\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\tfoo=bar,role=bar\n" 118 if string(result) != expected1 && string(result) != expected2 { 119 t.Fatalf("bad: %#v. Expected: %#v or %v", string(result), expected1, expected2) 120 } 121} 122 123func TestScriptUserEventHandler(t *testing.T) { 124 script, results := testEventScript(t, userEventScript) 125 126 h := &ScriptEventHandler{ 127 SelfFunc: func() serf.Member { 128 return serf.Member{ 129 Name: "ourname", 130 Tags: map[string]string{"role": "ourrole", "dc": "east-aws"}, 131 } 132 }, 133 Scripts: []EventScript{ 134 { 135 EventFilter: EventFilter{ 136 Event: "*", 137 }, 138 Script: script, 139 }, 140 }, 141 } 142 143 userEvent := serf.UserEvent{ 144 LTime: 1, 145 Name: "baz", 146 Payload: []byte("foobar"), 147 Coalesce: true, 148 } 149 150 h.HandleEvent(userEvent) 151 152 result, err := ioutil.ReadFile(results) 153 if err != nil { 154 t.Fatalf("err: %v", err) 155 } 156 157 expected := "ourname ourrole\neast-aws\nuser baz\nuser 1\nfoobar\n" 158 if string(result) != expected { 159 t.Fatalf("bad: %#v. Expected: %#v", string(result), expected) 160 } 161} 162 163func TestScriptQueryEventHandler(t *testing.T) { 164 script, results := testEventScript(t, queryScript) 165 166 h := &ScriptEventHandler{ 167 SelfFunc: func() serf.Member { 168 return serf.Member{ 169 Name: "ourname", 170 Tags: map[string]string{"role": "ourrole", "dc": "east-aws"}, 171 } 172 }, 173 Scripts: []EventScript{ 174 { 175 EventFilter: EventFilter{ 176 Event: "*", 177 }, 178 Script: script, 179 }, 180 }, 181 } 182 183 query := &serf.Query{ 184 LTime: 42, 185 Name: "uptime", 186 Payload: []byte("load average"), 187 } 188 189 h.HandleEvent(query) 190 191 result, err := ioutil.ReadFile(results) 192 if err != nil { 193 t.Fatalf("err: %v", err) 194 } 195 196 expected := "ourname ourrole\neast-aws\nquery uptime\nquery 42\nload average\n" 197 if string(result) != expected { 198 t.Fatalf("bad: %#v. Expected: %#v", string(result), expected) 199 } 200} 201 202func TestEventScriptInvoke(t *testing.T) { 203 testCases := []struct { 204 script EventScript 205 event serf.Event 206 invoke bool 207 }{ 208 { 209 EventScript{EventFilter{"*", ""}, "script.sh"}, 210 serf.MemberEvent{}, 211 true, 212 }, 213 { 214 EventScript{EventFilter{"user", ""}, "script.sh"}, 215 serf.MemberEvent{}, 216 false, 217 }, 218 { 219 EventScript{EventFilter{"user", "deploy"}, "script.sh"}, 220 serf.UserEvent{Name: "deploy"}, 221 true, 222 }, 223 { 224 EventScript{EventFilter{"user", "deploy"}, "script.sh"}, 225 serf.UserEvent{Name: "restart"}, 226 false, 227 }, 228 { 229 EventScript{EventFilter{"member-join", ""}, "script.sh"}, 230 serf.MemberEvent{Type: serf.EventMemberJoin}, 231 true, 232 }, 233 { 234 EventScript{EventFilter{"member-join", ""}, "script.sh"}, 235 serf.MemberEvent{Type: serf.EventMemberLeave}, 236 false, 237 }, 238 { 239 EventScript{EventFilter{"member-reap", ""}, "script.sh"}, 240 serf.MemberEvent{Type: serf.EventMemberReap}, 241 true, 242 }, 243 { 244 EventScript{EventFilter{"query", "deploy"}, "script.sh"}, 245 &serf.Query{Name: "deploy"}, 246 true, 247 }, 248 { 249 EventScript{EventFilter{"query", "uptime"}, "script.sh"}, 250 &serf.Query{Name: "deploy"}, 251 false, 252 }, 253 { 254 EventScript{EventFilter{"query", ""}, "script.sh"}, 255 &serf.Query{Name: "deploy"}, 256 true, 257 }, 258 } 259 260 for _, tc := range testCases { 261 result := tc.script.Invoke(tc.event) 262 if result != tc.invoke { 263 t.Errorf("bad: %#v", tc) 264 } 265 } 266} 267 268func TestEventScriptValid(t *testing.T) { 269 testCases := []struct { 270 Event string 271 Valid bool 272 }{ 273 {"member-join", true}, 274 {"member-leave", true}, 275 {"member-failed", true}, 276 {"member-update", true}, 277 {"member-reap", true}, 278 {"user", true}, 279 {"User", false}, 280 {"member", false}, 281 {"query", true}, 282 {"Query", false}, 283 {"*", true}, 284 } 285 286 for _, tc := range testCases { 287 script := EventScript{EventFilter: EventFilter{Event: tc.Event}} 288 if script.Valid() != tc.Valid { 289 t.Errorf("bad: %#v", tc) 290 } 291 } 292} 293 294func TestParseEventScript(t *testing.T) { 295 testCases := []struct { 296 v string 297 err bool 298 results []EventScript 299 }{ 300 { 301 "script.sh", 302 false, 303 []EventScript{{EventFilter{"*", ""}, "script.sh"}}, 304 }, 305 306 { 307 "member-join=script.sh", 308 false, 309 []EventScript{{EventFilter{"member-join", ""}, "script.sh"}}, 310 }, 311 312 { 313 "foo,bar=script.sh", 314 false, 315 []EventScript{ 316 {EventFilter{"foo", ""}, "script.sh"}, 317 {EventFilter{"bar", ""}, "script.sh"}, 318 }, 319 }, 320 321 { 322 "user:deploy=script.sh", 323 false, 324 []EventScript{{EventFilter{"user", "deploy"}, "script.sh"}}, 325 }, 326 327 { 328 "foo,user:blah,bar,query:tubez=script.sh", 329 false, 330 []EventScript{ 331 {EventFilter{"foo", ""}, "script.sh"}, 332 {EventFilter{"user", "blah"}, "script.sh"}, 333 {EventFilter{"bar", ""}, "script.sh"}, 334 {EventFilter{"query", "tubez"}, "script.sh"}, 335 }, 336 }, 337 338 { 339 "query:load=script.sh", 340 false, 341 []EventScript{{EventFilter{"query", "load"}, "script.sh"}}, 342 }, 343 344 { 345 "query=script.sh", 346 false, 347 []EventScript{{EventFilter{"query", ""}, "script.sh"}}, 348 }, 349 } 350 351 for _, tc := range testCases { 352 results := ParseEventScript(tc.v) 353 if results == nil { 354 t.Errorf("result should not be nil") 355 continue 356 } 357 358 if len(results) != len(tc.results) { 359 t.Errorf("bad: %#v", results) 360 continue 361 } 362 363 for i, r := range results { 364 expected := tc.results[i] 365 366 if r.Event != expected.Event { 367 t.Errorf("Events not equal: %s %s", r.Event, expected.Event) 368 } 369 370 if r.Name != expected.Name { 371 t.Errorf("User events not equal: %s %s", r.Name, expected.Name) 372 } 373 374 if r.Script != expected.Script { 375 t.Errorf("Scripts not equal: %s %s", r.Script, expected.Script) 376 } 377 } 378 } 379} 380 381func TestParseEventFilter(t *testing.T) { 382 testCases := []struct { 383 v string 384 results []EventFilter 385 }{ 386 { 387 "", 388 []EventFilter{EventFilter{"*", ""}}, 389 }, 390 391 { 392 "member-join", 393 []EventFilter{EventFilter{"member-join", ""}}, 394 }, 395 396 { 397 "member-reap", 398 []EventFilter{EventFilter{"member-reap", ""}}, 399 }, 400 401 { 402 "foo,bar", 403 []EventFilter{ 404 EventFilter{"foo", ""}, 405 EventFilter{"bar", ""}, 406 }, 407 }, 408 409 { 410 "user:deploy", 411 []EventFilter{EventFilter{"user", "deploy"}}, 412 }, 413 414 { 415 "foo,user:blah,bar", 416 []EventFilter{ 417 EventFilter{"foo", ""}, 418 EventFilter{"user", "blah"}, 419 EventFilter{"bar", ""}, 420 }, 421 }, 422 423 { 424 "query:load", 425 []EventFilter{EventFilter{"query", "load"}}, 426 }, 427 } 428 429 for _, tc := range testCases { 430 results := ParseEventFilter(tc.v) 431 if results == nil { 432 t.Errorf("result should not be nil") 433 continue 434 } 435 436 if len(results) != len(tc.results) { 437 t.Errorf("bad: %#v", results) 438 continue 439 } 440 441 for i, r := range results { 442 expected := tc.results[i] 443 444 if r.Event != expected.Event { 445 t.Errorf("Events not equal: %s %s", r.Event, expected.Event) 446 } 447 448 if r.Name != expected.Name { 449 t.Errorf("User events not equal: %s %s", r.Name, expected.Name) 450 } 451 } 452 } 453} 454