Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Readiness Endpoint #678

Merged
merged 1 commit into from
Jan 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/internal/consumer/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ func (cc *Coordinator) Start() error {
if err != nil {
return errors.New("Error starting consumer module: " + err.Error())
}
// All consumers started, Burrow is ready to serve requests
// set the readiness probe
cc.App.AppReady = true
return nil
}

Expand Down
21 changes: 20 additions & 1 deletion core/internal/httpserver/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ func (hc *Coordinator) Configure() {
// This is a catchall for undefined URLs
hc.router.NotFound = &defaultHandler{}

// This is a healthcheck URL. Please don't change it
// This is a healthcheck and readiness URLs. Please don't change it
hc.router.GET("/burrow/admin", hc.handleAdmin)
hc.router.GET("/burrow/admin/ready", hc.handleReady)

hc.router.Handler(http.MethodGet, "/metrics", hc.handlePrometheusMetrics())

Expand Down Expand Up @@ -296,6 +297,24 @@ func (hc *Coordinator) handleAdmin(w http.ResponseWriter, r *http.Request, _ htt
w.Write([]byte("GOOD"))
}

// handleReady will use the AppReady bool from the ApplicationContext to determine
// whether Burrow is ready to serve requests
func (hc *Coordinator) handleReady(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Add CORS header, if configured
corsHeader := viper.GetString("general.access-control-allow-origin")
if corsHeader != "" {
w.Header().Set("Access-Control-Allow-Origin", corsHeader)
}

if hc.App.AppReady {
w.WriteHeader(http.StatusOK)
w.Write([]byte("READY"))
} else {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("STARTING"))
}
}

func (hc *Coordinator) getLogLevel(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
requestInfo := makeRequestInfo(r)
hc.writeResponse(w, r, http.StatusOK, httpResponseLogLevel{
Expand Down
22 changes: 22 additions & 0 deletions core/internal/httpserver/coordinator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func fixtureConfiguredCoordinator() *Coordinator {
LogLevel: &logLevel,
StorageChannel: make(chan *protocol.StorageRequest),
EvaluatorChannel: make(chan *protocol.EvaluatorRequest),
AppReady: false,
},
}

Expand All @@ -58,6 +59,27 @@ func TestHttpServer_handleAdmin(t *testing.T) {
assert.Equalf(t, "GOOD", rr.Body.String(), "Expected response body to be 'GOOD', not '%v'", rr.Body.String())
}

func TestHttpServer_handleReady(t *testing.T) {
coordinator := fixtureConfiguredCoordinator()

// Set up a request
req, err := http.NewRequest("GET", "/burrow/admin/ready", nil)
assert.NoError(t, err, "Expected request setup to return no error")

// Call the handler via httprouter, the app is not ready so we expect "STARTING" and HTTP 503
rr := httptest.NewRecorder()
coordinator.router.ServeHTTP(rr, req)
assert.Equalf(t, http.StatusServiceUnavailable, rr.Code, "Expected response code to be 503, not %v", rr.Code)
assert.Equalf(t, "STARTING", rr.Body.String(), "Expected response body to be 'STARTING', not '%v'", rr.Body.String())

// Change the AppReady, and try again
coordinator.App.AppReady = true
rr = httptest.NewRecorder()
coordinator.router.ServeHTTP(rr, req)
assert.Equalf(t, http.StatusOK, rr.Code, "Expected response code to be 200, not %v", rr.Code)
assert.Equalf(t, "READY", rr.Body.String(), "Expected response body to be 'READY', not '%v'", rr.Body.String())
}

func TestHttpServer_getClusterList(t *testing.T) {
coordinator := fixtureConfiguredCoordinator()

Expand Down
3 changes: 3 additions & 0 deletions core/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ type ApplicationContext struct {
// This is the channel over which any module should send storage requests for storage of offsets and group
// information, or to fetch the same information. It is serviced by the storage Coordinator.
StorageChannel chan *StorageRequest

// This is a boolean flag which is set by the last subsystem, the consumer, in order to signal when Burrow is ready
AppReady bool
}

// Module is a common interface for all modules so that they can be manipulated by the coordinators in the same way.
Expand Down