-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
api: New API with health endpoint (#139)
- Loading branch information
Showing
6 changed files
with
208 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
|
||
log "github.com/sirupsen/logrus" | ||
|
||
"github.com/algorand/conduit/conduit/pipeline" | ||
) | ||
|
||
// StatusProvider is a subset of the Pipeline interface required by the health handler. | ||
type StatusProvider interface { | ||
Status() (pipeline.Status, error) | ||
} | ||
|
||
func makeHealthHandler(p StatusProvider) func(w http.ResponseWriter, r *http.Request) { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
status, err := p.Status() | ||
if err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
fmt.Fprintf(w, `{"error": "%s"}`, err) | ||
return | ||
} | ||
w.WriteHeader(http.StatusOK) | ||
data, _ := json.Marshal(status) | ||
fmt.Fprint(w, string(data)) | ||
} | ||
} | ||
|
||
// StartServer starts an http server that exposes a health check endpoint. | ||
// A callback is returned that can be used to gracefully shutdown the server. | ||
func StartServer(logger *log.Logger, p StatusProvider, address string) (func(ctx context.Context), error) { | ||
mux := http.NewServeMux() | ||
mux.HandleFunc("/health", makeHealthHandler(p)) | ||
|
||
srv := &http.Server{ | ||
Addr: address, | ||
Handler: mux, | ||
} | ||
|
||
go func() { | ||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { | ||
logger.Fatalf("failed to start API server: %s", err) | ||
} | ||
}() | ||
|
||
shutdownCallback := func(ctx context.Context) { | ||
if err := srv.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) { | ||
logger.Fatalf("failed to shutdown API server: %s", err) | ||
} | ||
} | ||
return shutdownCallback, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
"github.com/sirupsen/logrus/hooks/test" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/algorand/conduit/conduit/pipeline" | ||
) | ||
|
||
type mockStatusProvider struct { | ||
s pipeline.Status | ||
err error | ||
} | ||
|
||
func (m mockStatusProvider) Status() (pipeline.Status, error) { | ||
if m.err != nil { | ||
return pipeline.Status{}, m.err | ||
} | ||
return m.s, nil | ||
} | ||
|
||
func TestStartServer_BadAddress(t *testing.T) { | ||
l, h := test.NewNullLogger() | ||
// capture the fatal log if any. | ||
l.ExitFunc = func(int) {} | ||
sp := mockStatusProvider{} | ||
|
||
shutdown, err := StartServer(l, sp, "bad address") | ||
defer shutdown(context.Background()) | ||
require.NoError(t, err) | ||
time.Sleep(1 * time.Millisecond) | ||
|
||
require.Len(t, h.Entries, 1) | ||
require.Equal(t, h.LastEntry().Level, logrus.FatalLevel) | ||
} | ||
|
||
func TestStartServer_GracefulShutdown(t *testing.T) { | ||
l, h := test.NewNullLogger() | ||
// capture the fatal log if any. | ||
l.ExitFunc = func(int) {} | ||
sp := mockStatusProvider{} | ||
shutdown, err := StartServer(l, sp, "bad address") | ||
defer shutdown(context.Background()) | ||
require.NoError(t, err) | ||
require.Len(t, h.Entries, 0) | ||
} | ||
|
||
func TestStartServer_HealthCheck(t *testing.T) { | ||
l, _ := test.NewNullLogger() | ||
// capture the fatal log if any. | ||
l.ExitFunc = func(int) {} | ||
sp := mockStatusProvider{ | ||
s: pipeline.Status{ | ||
Round: 999, | ||
}, | ||
} | ||
|
||
// Find an open port... | ||
listener, err := net.Listen("tcp", ":0") | ||
addr := listener.Addr().String() | ||
listener.Close() | ||
require.NoError(t, err) | ||
|
||
// Start server. | ||
shutdown, err := StartServer(l, sp, addr) | ||
defer shutdown(context.Background()) | ||
require.NoError(t, err) | ||
|
||
// Make request. | ||
resp, err := http.Get("http://" + addr + "/health") | ||
require.NoError(t, err) | ||
|
||
// Make sure we got the right response. | ||
require.Equal(t, http.StatusOK, resp.StatusCode) | ||
var respStatus pipeline.Status | ||
json.NewDecoder(resp.Body).Decode(&respStatus) | ||
require.NoError(t, err) | ||
require.Equal(t, sp.s, respStatus) | ||
} | ||
|
||
func TestHealthHandlerError(t *testing.T) { | ||
sp := mockStatusProvider{ | ||
err: fmt.Errorf("some error"), | ||
} | ||
handler := makeHealthHandler(sp) | ||
rec := httptest.NewRecorder() | ||
handler(rec, nil) | ||
|
||
// validate response | ||
resp := rec.Result() | ||
require.Equal(t, http.StatusInternalServerError, resp.StatusCode) | ||
require.Contains(t, rec.Body.String(), "some error") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters