github.com/observiq/bindplane-agent@v1.51.0/internal/service/service_windows_test.go (about) 1 // Copyright observIQ, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package service 16 17 import ( 18 "errors" 19 "testing" 20 "time" 21 22 "github.com/observiq/bindplane-agent/internal/service/mocks" 23 "github.com/stretchr/testify/mock" 24 "github.com/stretchr/testify/require" 25 "go.uber.org/zap" 26 "golang.org/x/sys/windows/svc" 27 ) 28 29 func TestWindowsServiceHandler(t *testing.T) { 30 t.Run("Normal start/stop", func(t *testing.T) { 31 rSvc := &mocks.MockRunnableService{} 32 33 rSvc.On("Start", mock.Anything).Return(nil) 34 rSvc.On("Error").Return((<-chan error)(make(chan error))) 35 rSvc.On("Stop", mock.Anything).Return(nil) 36 37 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 38 39 changeChan := make(chan svc.ChangeRequest) 40 statusChan := make(chan svc.Status, 6) 41 svcHandlerDone := make(chan struct{}) 42 43 var isSvcSpecificStatus bool 44 var statusCode uint32 45 go func() { 46 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 47 close(svcHandlerDone) 48 }() 49 50 select { 51 case status := <-statusChan: 52 require.Equal(t, svc.Status{State: svc.StartPending}, status) 53 case <-time.After(time.Second): 54 t.Fatalf("Timed out waiting for service status change to start pending") 55 } 56 57 select { 58 case status := <-statusChan: 59 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 60 case <-time.After(time.Second): 61 t.Fatalf("Timed out waiting for service status change to running") 62 } 63 64 changeChan <- svc.ChangeRequest{ 65 Cmd: svc.Interrogate, 66 CurrentStatus: svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, 67 } 68 69 select { 70 case status := <-statusChan: 71 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 72 case <-time.After(time.Second): 73 t.Fatalf("Timed out waiting for interrogate response") 74 } 75 76 changeChan <- svc.ChangeRequest{ 77 Cmd: svc.Stop, 78 } 79 80 select { 81 case status := <-statusChan: 82 require.Equal(t, svc.Status{State: svc.StopPending}, status) 83 case <-time.After(time.Second): 84 t.Fatalf("Timed out waiting for status change to stop pending") 85 } 86 87 select { 88 case status := <-statusChan: 89 require.Equal(t, svc.Status{State: svc.Stopped}, status) 90 case <-time.After(time.Second): 91 t.Fatalf("Timed out waiting for status change to stopped") 92 } 93 94 select { 95 case <-svcHandlerDone: // OK 96 case <-time.After(time.Second): 97 t.Fatalf("Timed out waiting for service handler to return") 98 } 99 100 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 101 require.Equal(t, uint32(0), statusCode, "status code was not 0") 102 }) 103 104 t.Run("Start fails", func(t *testing.T) { 105 rSvc := &mocks.MockRunnableService{} 106 107 startError := errors.New("Failed to start service") 108 109 rSvc.On("Start", mock.Anything).Return(startError) 110 rSvc.On("Error").Return((<-chan error)(make(chan error))) 111 rSvc.On("Stop", mock.Anything).Return(nil) 112 113 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 114 115 changeChan := make(chan svc.ChangeRequest) 116 statusChan := make(chan svc.Status, 6) 117 svcHandlerDone := make(chan struct{}) 118 119 var isSvcSpecificStatus bool 120 var statusCode uint32 121 go func() { 122 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 123 close(svcHandlerDone) 124 }() 125 126 select { 127 case status := <-statusChan: 128 require.Equal(t, svc.Status{State: svc.StartPending}, status) 129 case <-time.After(time.Second): 130 t.Fatalf("Timed out waiting for service status change to start pending") 131 } 132 133 select { 134 case <-svcHandlerDone: // OK 135 case <-time.After(time.Second): 136 t.Fatalf("Timed out waiting for service handler to return") 137 } 138 139 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 140 require.Equal(t, statusCodeServiceException, statusCode, "status code was not ServiceException") 141 }) 142 143 t.Run("Unexpected service error", func(t *testing.T) { 144 rSvc := &mocks.MockRunnableService{} 145 146 svcErr := errors.New("service unexpectedly failed") 147 errChan := make(chan error, 1) 148 errChan <- svcErr 149 150 rSvc.On("Start", mock.Anything).Return(nil) 151 rSvc.On("Error").Return((<-chan error)(errChan)) 152 rSvc.On("Stop", mock.Anything).Return(nil) 153 154 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 155 156 changeChan := make(chan svc.ChangeRequest) 157 statusChan := make(chan svc.Status, 6) 158 svcHandlerDone := make(chan struct{}) 159 160 var isSvcSpecificStatus bool 161 var statusCode uint32 162 go func() { 163 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 164 close(svcHandlerDone) 165 }() 166 167 select { 168 case status := <-statusChan: 169 require.Equal(t, svc.Status{State: svc.StartPending}, status) 170 case <-time.After(time.Second): 171 t.Fatalf("Timed out waiting for service status change to start pending") 172 } 173 174 select { 175 case status := <-statusChan: 176 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 177 case <-time.After(time.Second): 178 t.Fatalf("Timed out waiting for service status change to running") 179 } 180 181 select { 182 case status := <-statusChan: 183 require.Equal(t, svc.Status{State: svc.StopPending}, status) 184 case <-time.After(time.Second): 185 t.Fatalf("Timed out waiting for status change to stop pending") 186 } 187 188 select { 189 case status := <-statusChan: 190 require.Equal(t, svc.Status{State: svc.Stopped}, status) 191 case <-time.After(time.Second): 192 t.Fatalf("Timed out waiting for status change to stopped") 193 } 194 195 select { 196 case <-svcHandlerDone: // OK 197 case <-time.After(time.Second): 198 t.Fatalf("Timed out waiting for service handler to return") 199 } 200 201 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 202 require.Equal(t, statusCodeServiceException, statusCode, "status code was not ServiceException") 203 }) 204 205 t.Run("Shutdown error", func(t *testing.T) { 206 rSvc := &mocks.MockRunnableService{} 207 stopError := errors.New("Failed to start service") 208 209 rSvc.On("Start", mock.Anything).Return(nil) 210 rSvc.On("Error").Return((<-chan error)(make(chan error))) 211 rSvc.On("Stop", mock.Anything).Return(stopError) 212 213 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 214 215 changeChan := make(chan svc.ChangeRequest) 216 statusChan := make(chan svc.Status, 6) 217 svcHandlerDone := make(chan struct{}) 218 219 var isSvcSpecificStatus bool 220 var statusCode uint32 221 go func() { 222 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 223 close(svcHandlerDone) 224 }() 225 226 select { 227 case status := <-statusChan: 228 require.Equal(t, svc.Status{State: svc.StartPending}, status) 229 case <-time.After(time.Second): 230 t.Fatalf("Timed out waiting for service status change to start pending") 231 } 232 233 select { 234 case status := <-statusChan: 235 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 236 case <-time.After(time.Second): 237 t.Fatalf("Timed out waiting for service status change to running") 238 } 239 240 changeChan <- svc.ChangeRequest{ 241 Cmd: svc.Interrogate, 242 CurrentStatus: svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, 243 } 244 245 select { 246 case status := <-statusChan: 247 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 248 case <-time.After(time.Second): 249 t.Fatalf("Timed out waiting for interrogate response") 250 } 251 252 changeChan <- svc.ChangeRequest{ 253 Cmd: svc.Stop, 254 } 255 256 select { 257 case status := <-statusChan: 258 require.Equal(t, svc.Status{State: svc.StopPending}, status) 259 case <-time.After(time.Second): 260 t.Fatalf("Timed out waiting for status change to stop pending") 261 } 262 263 select { 264 case status := <-statusChan: 265 require.Equal(t, svc.Status{State: svc.Stopped}, status) 266 case <-time.After(time.Second): 267 t.Fatalf("Timed out waiting for status change to stopped") 268 } 269 270 select { 271 case <-svcHandlerDone: // OK 272 case <-time.After(time.Second): 273 t.Fatalf("Timed out waiting for service handler to return") 274 } 275 276 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 277 require.Equal(t, statusCodeServiceException, statusCode, "status code was not ServiceException") 278 }) 279 280 t.Run("Shutdown takes too long", func(t *testing.T) { 281 setWindowsServiceTimeout(t, 10*time.Millisecond) 282 rSvc := &mocks.MockRunnableService{} 283 284 blockStopChan := make(chan struct{}, 1) 285 t.Cleanup(func() { 286 blockStopChan <- struct{}{} 287 }) 288 289 rSvc.On("Start", mock.Anything).Return(nil) 290 rSvc.On("Error").Return((<-chan error)(make(chan error))) 291 rSvc.On("Stop", mock.Anything).Run(func(args mock.Arguments) { <-blockStopChan }).Return(nil) 292 293 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 294 295 changeChan := make(chan svc.ChangeRequest) 296 statusChan := make(chan svc.Status, 6) 297 svcHandlerDone := make(chan struct{}) 298 299 var isSvcSpecificStatus bool 300 var statusCode uint32 301 go func() { 302 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 303 close(svcHandlerDone) 304 }() 305 306 select { 307 case status := <-statusChan: 308 require.Equal(t, svc.Status{State: svc.StartPending}, status) 309 case <-time.After(time.Second): 310 t.Fatalf("Timed out waiting for service status change to start pending") 311 } 312 313 select { 314 case status := <-statusChan: 315 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 316 case <-time.After(time.Second): 317 t.Fatalf("Timed out waiting for service status change to running") 318 } 319 320 changeChan <- svc.ChangeRequest{ 321 Cmd: svc.Interrogate, 322 CurrentStatus: svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, 323 } 324 325 select { 326 case status := <-statusChan: 327 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 328 case <-time.After(time.Second): 329 t.Fatalf("Timed out waiting for interrogate response") 330 } 331 332 changeChan <- svc.ChangeRequest{ 333 Cmd: svc.Stop, 334 } 335 336 select { 337 case status := <-statusChan: 338 require.Equal(t, svc.Status{State: svc.StopPending}, status) 339 case <-time.After(time.Second): 340 t.Fatalf("Timed out waiting for status change to stop pending") 341 } 342 343 select { 344 case status := <-statusChan: 345 require.Equal(t, svc.Status{State: svc.Stopped}, status) 346 case <-time.After(time.Second): 347 t.Fatalf("Timed out waiting for status change to stopped") 348 } 349 350 select { 351 case <-svcHandlerDone: // OK 352 case <-time.After(time.Second): 353 t.Fatalf("Timed out waiting for service handler to return") 354 } 355 356 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 357 require.Equal(t, statusCodeServiceException, statusCode, "status code was not ServiceException") 358 }) 359 360 t.Run("Shutdown error after unexpected error", func(t *testing.T) { 361 rSvc := &mocks.MockRunnableService{} 362 stopError := errors.New("Failed to start service") 363 svcErr := errors.New("service unexpectedly failed") 364 errChan := make(chan error, 1) 365 errChan <- svcErr 366 367 rSvc.On("Start", mock.Anything).Return(nil) 368 rSvc.On("Error").Return((<-chan error)(errChan)) 369 rSvc.On("Stop", mock.Anything).Return(stopError) 370 371 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 372 373 changeChan := make(chan svc.ChangeRequest) 374 statusChan := make(chan svc.Status, 6) 375 svcHandlerDone := make(chan struct{}) 376 377 var isSvcSpecificStatus bool 378 var statusCode uint32 379 go func() { 380 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 381 close(svcHandlerDone) 382 }() 383 384 select { 385 case status := <-statusChan: 386 require.Equal(t, svc.Status{State: svc.StartPending}, status) 387 case <-time.After(time.Second): 388 t.Fatalf("Timed out waiting for service status change to start pending") 389 } 390 391 select { 392 case status := <-statusChan: 393 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 394 case <-time.After(time.Second): 395 t.Fatalf("Timed out waiting for service status change to running") 396 } 397 398 select { 399 case status := <-statusChan: 400 require.Equal(t, svc.Status{State: svc.StopPending}, status) 401 case <-time.After(time.Second): 402 t.Fatalf("Timed out waiting for status change to stop pending") 403 } 404 405 select { 406 case status := <-statusChan: 407 require.Equal(t, svc.Status{State: svc.Stopped}, status) 408 case <-time.After(time.Second): 409 t.Fatalf("Timed out waiting for status change to stopped") 410 } 411 412 select { 413 case <-svcHandlerDone: // OK 414 case <-time.After(time.Second): 415 t.Fatalf("Timed out waiting for service handler to return") 416 } 417 418 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 419 require.Equal(t, statusCodeServiceException, statusCode, "status code was not ServiceException") 420 }) 421 422 t.Run("Unhandled command", func(t *testing.T) { 423 rSvc := &mocks.MockRunnableService{} 424 425 rSvc.On("Start", mock.Anything).Return(nil) 426 rSvc.On("Error").Return((<-chan error)(make(chan error))) 427 rSvc.On("Stop", mock.Anything).Return(nil) 428 429 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 430 431 changeChan := make(chan svc.ChangeRequest) 432 statusChan := make(chan svc.Status, 6) 433 svcHandlerDone := make(chan struct{}) 434 435 var isSvcSpecificStatus bool 436 var statusCode uint32 437 go func() { 438 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 439 close(svcHandlerDone) 440 }() 441 442 select { 443 case status := <-statusChan: 444 require.Equal(t, svc.Status{State: svc.StartPending}, status) 445 case <-time.After(time.Second): 446 t.Fatalf("Timed out waiting for service status change to start pending") 447 } 448 449 select { 450 case status := <-statusChan: 451 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 452 case <-time.After(time.Second): 453 t.Fatalf("Timed out waiting for service status change to running") 454 } 455 456 changeChan <- svc.ChangeRequest{ 457 Cmd: svc.Interrogate, 458 CurrentStatus: svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, 459 } 460 461 select { 462 case status := <-statusChan: 463 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 464 case <-time.After(time.Second): 465 t.Fatalf("Timed out waiting for interrogate response") 466 } 467 468 changeChan <- svc.ChangeRequest{ 469 Cmd: svc.DeviceEvent, 470 } 471 472 select { 473 case status := <-statusChan: 474 require.Equal(t, svc.Status{State: svc.StopPending}, status) 475 case <-time.After(time.Second): 476 t.Fatalf("Timed out waiting for status change to stop pending") 477 } 478 479 select { 480 case status := <-statusChan: 481 require.Equal(t, svc.Status{State: svc.Stopped}, status) 482 case <-time.After(time.Second): 483 t.Fatalf("Timed out waiting for status change to stopped") 484 } 485 486 select { 487 case <-svcHandlerDone: // OK 488 case <-time.After(time.Second): 489 t.Fatalf("Timed out waiting for service handler to return") 490 } 491 492 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 493 require.Equal(t, uint32(statusCodeInvalidServiceCommand), statusCode, "status code was not InvalidServiceCommand") 494 }) 495 496 t.Run("Unhandled command with shutdown error", func(t *testing.T) { 497 rSvc := &mocks.MockRunnableService{} 498 499 stopError := errors.New("Failed to start service") 500 rSvc.On("Start", mock.Anything).Return(nil) 501 rSvc.On("Error").Return((<-chan error)(make(chan error))) 502 rSvc.On("Stop", mock.Anything).Return(stopError) 503 504 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 505 506 changeChan := make(chan svc.ChangeRequest) 507 statusChan := make(chan svc.Status, 6) 508 svcHandlerDone := make(chan struct{}) 509 510 var isSvcSpecificStatus bool 511 var statusCode uint32 512 go func() { 513 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{"service-name"}, changeChan, statusChan) 514 close(svcHandlerDone) 515 }() 516 517 select { 518 case status := <-statusChan: 519 require.Equal(t, svc.Status{State: svc.StartPending}, status) 520 case <-time.After(time.Second): 521 t.Fatalf("Timed out waiting for service status change to start pending") 522 } 523 524 select { 525 case status := <-statusChan: 526 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 527 case <-time.After(time.Second): 528 t.Fatalf("Timed out waiting for service status change to running") 529 } 530 531 changeChan <- svc.ChangeRequest{ 532 Cmd: svc.Interrogate, 533 CurrentStatus: svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, 534 } 535 536 select { 537 case status := <-statusChan: 538 require.Equal(t, svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}, status) 539 case <-time.After(time.Second): 540 t.Fatalf("Timed out waiting for interrogate response") 541 } 542 543 changeChan <- svc.ChangeRequest{ 544 Cmd: svc.DeviceEvent, 545 } 546 547 select { 548 case status := <-statusChan: 549 require.Equal(t, svc.Status{State: svc.StopPending}, status) 550 case <-time.After(time.Second): 551 t.Fatalf("Timed out waiting for status change to stop pending") 552 } 553 554 select { 555 case status := <-statusChan: 556 require.Equal(t, svc.Status{State: svc.Stopped}, status) 557 case <-time.After(time.Second): 558 t.Fatalf("Timed out waiting for status change to stopped") 559 } 560 561 select { 562 case <-svcHandlerDone: // OK 563 case <-time.After(time.Second): 564 t.Fatalf("Timed out waiting for service handler to return") 565 } 566 567 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 568 require.Equal(t, uint32(statusCodeServiceException), statusCode, "status code was not ServiceException") 569 }) 570 571 t.Run("No service name", func(t *testing.T) { 572 rSvc := &mocks.MockRunnableService{} 573 574 svcHandler := newWindowsServiceHandler(zap.NewNop(), rSvc) 575 576 changeChan := make(chan svc.ChangeRequest) 577 statusChan := make(chan svc.Status, 6) 578 svcHandlerDone := make(chan struct{}) 579 580 var isSvcSpecificStatus bool 581 var statusCode uint32 582 go func() { 583 isSvcSpecificStatus, statusCode = svcHandler.Execute([]string{}, changeChan, statusChan) 584 close(svcHandlerDone) 585 }() 586 587 select { 588 case <-svcHandlerDone: // OK 589 case <-time.After(time.Second): 590 t.Fatalf("Timed out waiting for service handler to return") 591 } 592 593 require.Equal(t, false, isSvcSpecificStatus, "status code marked as service specific") 594 require.Equal(t, uint32(statusCodeInvalidServiceName), statusCode, "status code was not InvalidServiceName") 595 }) 596 } 597 598 func setWindowsServiceTimeout(t *testing.T, d time.Duration) { 599 old := windowsServiceShutdownTimeout 600 windowsServiceShutdownTimeout = d 601 t.Cleanup(func() { 602 windowsServiceShutdownTimeout = old 603 }) 604 }