diff options
Diffstat (limited to 'service/health')
-rw-r--r-- | service/health/config.go | 32 | ||||
-rw-r--r-- | service/health/config_test.go | 46 | ||||
-rw-r--r-- | service/health/service.go | 116 | ||||
-rw-r--r-- | service/health/service_test.go | 317 |
4 files changed, 0 insertions, 511 deletions
diff --git a/service/health/config.go b/service/health/config.go deleted file mode 100644 index 60a52d6e..00000000 --- a/service/health/config.go +++ /dev/null @@ -1,32 +0,0 @@ -package health - -import ( - "errors" - "strings" - - "github.com/spiral/roadrunner/service" -) - -// Config configures the health service -type Config struct { - // Address to listen on - Address string -} - -// Hydrate the config -func (c *Config) Hydrate(cfg service.Config) error { - if err := cfg.Unmarshal(c); err != nil { - return err - } - return c.Valid() -} - -// Valid validates the configuration. -func (c *Config) Valid() error { - // Validate the address - if c.Address != "" && !strings.Contains(c.Address, ":") { - return errors.New("malformed http server address") - } - - return nil -} diff --git a/service/health/config_test.go b/service/health/config_test.go deleted file mode 100644 index ba7d7c12..00000000 --- a/service/health/config_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package health - -import ( - json "github.com/json-iterator/go" - "testing" - - "github.com/spiral/roadrunner/service" - "github.com/stretchr/testify/assert" -) - -type mockCfg struct{ cfg string } - -func (cfg *mockCfg) Get(name string) service.Config { return nil } -func (cfg *mockCfg) Unmarshal(out interface{}) error { - j := json.ConfigCompatibleWithStandardLibrary - return j.Unmarshal([]byte(cfg.cfg), out) -} - -func Test_Config_Hydrate_Error1(t *testing.T) { - cfg := &mockCfg{`{"address": "localhost:8080"}`} - c := &Config{} - - assert.NoError(t, c.Hydrate(cfg)) - assert.Equal(t, "localhost:8080", c.Address) -} - -func Test_Config_Hydrate_Error2(t *testing.T) { - cfg := &mockCfg{`{"dir": "/dir/"`} - c := &Config{} - - assert.Error(t, c.Hydrate(cfg)) -} - -func Test_Config_Hydrate_Valid1(t *testing.T) { - cfg := &mockCfg{`{"address": "localhost"}`} - c := &Config{} - - assert.Error(t, c.Hydrate(cfg)) -} - -func Test_Config_Hydrate_Valid2(t *testing.T) { - cfg := &mockCfg{`{"address": ":1111"}`} - c := &Config{} - - assert.NoError(t, c.Hydrate(cfg)) -} diff --git a/service/health/service.go b/service/health/service.go deleted file mode 100644 index ce127340..00000000 --- a/service/health/service.go +++ /dev/null @@ -1,116 +0,0 @@ -package health - -import ( - "context" - "fmt" - "github.com/sirupsen/logrus" - "net/http" - "sync" - "time" - - rrhttp "github.com/spiral/roadrunner/service/http" -) - -const ( - // ID declares public service name. - ID = "health" - // maxHeaderSize declares max header size for prometheus server - maxHeaderSize = 1024 * 1024 * 100 // 104MB -) - -// Service to serve an endpoint for checking the health of the worker pool -type Service struct { - cfg *Config - log *logrus.Logger - mu sync.Mutex - http *http.Server - httpService *rrhttp.Service -} - -// Init health service -func (s *Service) Init(cfg *Config, r *rrhttp.Service, log *logrus.Logger) (bool, error) { - // Ensure the httpService is set - if r == nil { - return false, nil - } - - s.cfg = cfg - s.log = log - s.httpService = r - return true, nil -} - -// Serve the health endpoint -func (s *Service) Serve() error { - // Configure and start the http server - s.mu.Lock() - s.http = &http.Server{ - Addr: s.cfg.Address, - Handler: s, - IdleTimeout: time.Hour * 24, - ReadTimeout: time.Minute * 60, - MaxHeaderBytes: maxHeaderSize, - ReadHeaderTimeout: time.Minute * 60, - WriteTimeout: time.Minute * 60, - } - s.mu.Unlock() - - err := s.http.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - return err - } - - return nil -} - -// Stop the health endpoint -func (s *Service) Stop() { - s.mu.Lock() - defer s.mu.Unlock() - - if s.http != nil { - // gracefully stop the server - go func() { - err := s.http.Shutdown(context.Background()) - if err != nil && err != http.ErrServerClosed { - s.log.Error(fmt.Errorf("error shutting down the metrics server: error %v", err)) - } - }() - } -} - -// ServeHTTP returns the health of the pool of workers -func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { - status := http.StatusOK - if !s.isHealthy() { - status = http.StatusInternalServerError - } - w.WriteHeader(status) -} - -// isHealthy checks the server, pool and ensures at least one worker is active -func (s *Service) isHealthy() bool { - httpService := s.httpService - if httpService == nil { - return false - } - - server := httpService.Server() - if server == nil { - return false - } - - pool := server.Pool() - if pool == nil { - return false - } - - // Ensure at least one worker is active - for _, w := range pool.Workers() { - if w.State().IsActive() { - return true - } - } - - return false -} diff --git a/service/health/service_test.go b/service/health/service_test.go deleted file mode 100644 index fc743a62..00000000 --- a/service/health/service_test.go +++ /dev/null @@ -1,317 +0,0 @@ -package health - -import ( - json "github.com/json-iterator/go" - "io/ioutil" - "net/http" - "testing" - "time" - - "github.com/sirupsen/logrus" - "github.com/sirupsen/logrus/hooks/test" - "github.com/spiral/roadrunner/service" - rrhttp "github.com/spiral/roadrunner/service/http" - "github.com/stretchr/testify/assert" -) - -type testCfg struct { - healthCfg string - httpCfg string - target string -} - -func (cfg *testCfg) Get(name string) service.Config { - if name == ID { - return &testCfg{target: cfg.healthCfg} - } - - if name == rrhttp.ID { - return &testCfg{target: cfg.httpCfg} - } - - return nil -} - -func (cfg *testCfg) Unmarshal(out interface{}) error { - j := json.ConfigCompatibleWithStandardLibrary - err := j.Unmarshal([]byte(cfg.target), out) - return err -} - -func TestService_Serve(t *testing.T) { - logger, _ := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - c := service.NewContainer(logger) - c.Register(rrhttp.ID, &rrhttp.Service{}) - c.Register(ID, &Service{}) - - assert.NoError(t, c.Init(&testCfg{ - healthCfg: `{ - "address": "localhost:2116" - }`, - httpCfg: `{ - "address": "localhost:2115", - "workers":{ - "command": "php ../../tests/http/client.php echo pipes", - "pool": {"numWorkers": 1} - } - }`, - })) - - s, status := c.Get(ID) - assert.NotNil(t, s) - assert.Equal(t, service.StatusOK, status) - - hS, httpStatus := c.Get(rrhttp.ID) - assert.NotNil(t, hS) - assert.Equal(t, service.StatusOK, httpStatus) - - go func() { - err := c.Serve() - if err != nil { - t.Errorf("serve error: %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - defer c.Stop() - - _, res, err := get("http://localhost:2116/") - assert.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) -} - -func TestService_Serve_DeadWorker(t *testing.T) { - logger, _ := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - c := service.NewContainer(logger) - c.Register(rrhttp.ID, &rrhttp.Service{}) - c.Register(ID, &Service{}) - - assert.NoError(t, c.Init(&testCfg{ - healthCfg: `{ - "address": "localhost:2117" - }`, - httpCfg: `{ - "address": "localhost:2118", - "workers":{ - "command": "php ../../tests/http/slow-client.php echo pipes 1000", - "pool": {"numWorkers": 1} - } - }`, - })) - - s, status := c.Get(ID) - assert.NotNil(t, s) - assert.Equal(t, service.StatusOK, status) - - hS, httpStatus := c.Get(rrhttp.ID) - assert.NotNil(t, hS) - assert.Equal(t, service.StatusOK, httpStatus) - - go func() { - err := c.Serve() - if err != nil { - t.Errorf("server error: %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - defer c.Stop() - - // Kill the worker - httpSvc := hS.(*rrhttp.Service) - err := httpSvc.Server().Workers()[0].Kill() - if err != nil { - t.Errorf("error killing the worker: error %v", err) - } - - // Check health check - _, res, err := get("http://localhost:2117/") - assert.NoError(t, err) - assert.Equal(t, http.StatusInternalServerError, res.StatusCode) -} - -func TestService_Serve_DeadWorkerStillHealthy(t *testing.T) { - logger, _ := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - c := service.NewContainer(logger) - c.Register(rrhttp.ID, &rrhttp.Service{}) - c.Register(ID, &Service{}) - - assert.NoError(t, c.Init(&testCfg{ - healthCfg: `{ - "address": "localhost:2119" - }`, - httpCfg: `{ - "address": "localhost:2120", - "workers":{ - "command": "php ../../tests/http/client.php echo pipes", - "pool": {"numWorkers": 2} - } - }`, - })) - - s, status := c.Get(ID) - assert.NotNil(t, s) - assert.Equal(t, service.StatusOK, status) - - hS, httpStatus := c.Get(rrhttp.ID) - assert.NotNil(t, hS) - assert.Equal(t, service.StatusOK, httpStatus) - - go func() { - err := c.Serve() - if err != nil { - t.Errorf("serve error: %v", err) - } - }() - time.Sleep(time.Second * 1) - defer c.Stop() - - // Kill one of the workers - httpSvc := hS.(*rrhttp.Service) - err := httpSvc.Server().Workers()[0].Kill() - if err != nil { - t.Errorf("error killing the worker: error %v", err) - } - - // Check health check - _, res, err := get("http://localhost:2119/") - assert.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) -} - -func TestService_Serve_NoHTTPService(t *testing.T) { - logger, _ := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - c := service.NewContainer(logger) - c.Register(ID, &Service{}) - - assert.NoError(t, c.Init(&testCfg{ - healthCfg: `{ - "address": "localhost:2121" - }`, - })) - - s, status := c.Get(ID) - assert.NotNil(t, s) - assert.Equal(t, service.StatusInactive, status) -} - -func TestService_Serve_NoServer(t *testing.T) { - logger, _ := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - healthSvc := &Service{} - - c := service.NewContainer(logger) - c.Register(rrhttp.ID, &rrhttp.Service{}) - c.Register(ID, healthSvc) - - assert.NoError(t, c.Init(&testCfg{ - healthCfg: `{ - "address": "localhost:2122" - }`, - httpCfg: `{ - "address": "localhost:2123", - "workers":{ - "command": "php ../../tests/http/client.php echo pipes", - "pool": {"numWorkers": 1} - } - }`, - })) - - s, status := c.Get(ID) - assert.NotNil(t, s) - assert.Equal(t, service.StatusOK, status) - - hS, httpStatus := c.Get(rrhttp.ID) - assert.NotNil(t, hS) - assert.Equal(t, service.StatusOK, httpStatus) - - go func() { - err := c.Serve() - if err != nil { - t.Errorf("serve error: %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - defer c.Stop() - - // Set the httpService to nil - healthSvc.httpService = nil - - _, res, err := get("http://localhost:2122/") - assert.NoError(t, err) - assert.Equal(t, http.StatusInternalServerError, res.StatusCode) -} - -func TestService_Serve_NoPool(t *testing.T) { - logger, _ := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - httpSvc := &rrhttp.Service{} - - c := service.NewContainer(logger) - c.Register(rrhttp.ID, httpSvc) - c.Register(ID, &Service{}) - - assert.NoError(t, c.Init(&testCfg{ - healthCfg: `{ - "address": "localhost:2124" - }`, - httpCfg: `{ - "address": "localhost:2125", - "workers":{ - "command": "php ../../tests/http/client.php echo pipes", - "pool": {"numWorkers": 1} - } - }`, - })) - - s, status := c.Get(ID) - assert.NotNil(t, s) - assert.Equal(t, service.StatusOK, status) - - hS, httpStatus := c.Get(rrhttp.ID) - assert.NotNil(t, hS) - assert.Equal(t, service.StatusOK, httpStatus) - - go func() { - err := c.Serve() - if err != nil { - t.Errorf("serve error: %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - defer c.Stop() - - // Stop the pool - httpSvc.Server().Stop() - - _, res, err := get("http://localhost:2124/") - assert.NoError(t, err) - assert.Equal(t, http.StatusInternalServerError, res.StatusCode) -} - -// get request and return body -func get(url string) (string, *http.Response, error) { - r, err := http.Get(url) - if err != nil { - return "", nil, err - } - - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", nil, err - } - - err = r.Body.Close() - if err != nil { - return "", nil, err - } - return string(b), r, err -} |