diff --git a/api/common/api.go b/api/common/api.go index 3b1cb326..a543fc88 100644 --- a/api/common/api.go +++ b/api/common/api.go @@ -300,9 +300,9 @@ func (api *API) authHandler(h http.Handler, lggr *logging.ZapEventLogger) http.H } wrap := func(w http.ResponseWriter, r *http.Request) { - // We let CORS preflight requests pass through the next - // handler. - if r.Method == http.MethodOptions { + // We let CORS preflight and Health requests pass through to + // the next handler. + if r.Method == http.MethodOptions || (r.Method == http.MethodGet && r.URL.Path == "/health") { h.ServeHTTP(w, r) return } @@ -853,3 +853,8 @@ func (api *API) Headers() map[string][]string { func (api *API) SetKeepAlivesEnabled(b bool) { api.server.SetKeepAlivesEnabled(b) } + + +func (api *API) HealthHandler(w http.ResponseWriter, r *http.Request){ + api.SendResponse(w,http.StatusNoContent,nil,nil) +} diff --git a/api/pinsvcapi/pinsvcapi.go b/api/pinsvcapi/pinsvcapi.go index 127d1672..23a98961 100644 --- a/api/pinsvcapi/pinsvcapi.go +++ b/api/pinsvcapi/pinsvcapi.go @@ -22,9 +22,9 @@ import ( "go.uber.org/multierr" logging "github.com/ipfs/go-log/v2" + rpc "github.com/libp2p/go-libp2p-gorpc" "github.com/libp2p/go-libp2p/core/host" peer "github.com/libp2p/go-libp2p/core/peer" - rpc "github.com/libp2p/go-libp2p-gorpc" ) var ( @@ -178,6 +178,12 @@ func (api *API) routes(c *rpc.Client) []common.Route { Pattern: "/token", HandlerFunc: api.GenerateTokenHandler, }, + { + Name: "Health", + Method: "GET", + Pattern: "/health", + HandlerFunc: api.HealthHandler, + }, } } diff --git a/api/pinsvcapi/pinsvcapi_test.go b/api/pinsvcapi/pinsvcapi_test.go index 545c4e7c..dc787fd1 100644 --- a/api/pinsvcapi/pinsvcapi_test.go +++ b/api/pinsvcapi/pinsvcapi_test.go @@ -251,3 +251,19 @@ func TestAPIRemovePinEndpoint(t *testing.T) { test.BothEndpoints(t, tf) } + +func TestHealthEndpoint(t *testing.T) { + ctx := context.Background() + svcapi := testAPI(t) + defer svcapi.Shutdown(ctx) + + tf := func(t *testing.T, url test.URLFunc) { + errResp := api.Error{} + test.MakeGet(t, svcapi, url(svcapi)+"/health", &errResp) + if errResp.Code != 0 || errResp.Message != "" { + t.Error("expected no errors") + } + } + + test.BothEndpoints(t, tf) +} \ No newline at end of file diff --git a/api/rest/client/client.go b/api/rest/client/client.go index b4a173d4..2b2fce7c 100644 --- a/api/rest/client/client.go +++ b/api/rest/client/client.go @@ -122,6 +122,9 @@ type Client interface { // returns collected CIDs. If local is true, it would garbage collect // only on contacted peer, otherwise on all peers' IPFS daemons. RepoGC(ctx context.Context, local bool) (api.GlobalRepoGC, error) + + // Health returns no content when everything is ok, and an error otherwise + Health(ctx context.Context) (error) } // Config allows to configure the parameters to connect diff --git a/api/rest/client/lbclient.go b/api/rest/client/lbclient.go index e4d4f32c..bab3935a 100644 --- a/api/rest/client/lbclient.go +++ b/api/rest/client/lbclient.go @@ -5,8 +5,10 @@ import ( "sync/atomic" "github.com/ipfs-cluster/ipfs-cluster/api" + files "github.com/ipfs/boxo/files" shell "github.com/ipfs/go-ipfs-api" + peer "github.com/libp2p/go-libp2p/core/peer" ) @@ -553,3 +555,12 @@ func (lc *loadBalancingClient) IPFS(ctx context.Context) *shell.Shell { return s } + +func (lc *loadBalancingClient) Health(ctx context.Context) (error) { + call := func(c Client) error { + err := c.Health(ctx) + return err + } + err := lc.retry(0, call) + return err +} \ No newline at end of file diff --git a/api/rest/client/methods.go b/api/rest/client/methods.go index 9e46d6a7..7f82f713 100644 --- a/api/rest/client/methods.go +++ b/api/rest/client/methods.go @@ -697,3 +697,10 @@ func (c *defaultClient) AddMultiFile( ) return err } + +func (c *defaultClient) Health(ctx context.Context) (error) { + ctx, span := trace.StartSpan(ctx, "client/Health") + defer span.End() + err := c.do(ctx, "GET", "/health", nil, nil, nil) + return err +} diff --git a/api/rest/client/methods_test.go b/api/rest/client/methods_test.go index 27badf2e..0773db9d 100644 --- a/api/rest/client/methods_test.go +++ b/api/rest/client/methods_test.go @@ -903,3 +903,19 @@ func TestRepoGC(t *testing.T) { testClients(t, api, testF) } + +func TestHealth(t *testing.T) { + ctx := context.Background() + api := testAPI(t) + defer shutdown(api) + + testF := func(t *testing.T, c Client) { + err := c.Health(ctx) + if err != nil { + t.Log(err) + t.Error("expected no errors") + } + } + + testClients(t, api, testF) +} \ No newline at end of file diff --git a/api/rest/restapi.go b/api/rest/restapi.go index 14afc2e9..e6adeb44 100644 --- a/api/rest/restapi.go +++ b/api/rest/restapi.go @@ -204,6 +204,12 @@ func (api *API) routes(c *rpc.Client) []common.Route { Pattern: "/token", HandlerFunc: api.GenerateTokenHandler, }, + { + Name: "Health", + Method: "GET", + Pattern: "/health", + HandlerFunc: api.HealthHandler, + }, } } diff --git a/api/rest/restapi_test.go b/api/rest/restapi_test.go index d338d042..cad5f367 100644 --- a/api/rest/restapi_test.go +++ b/api/rest/restapi_test.go @@ -844,3 +844,20 @@ func TestAPIIPFSGCEndpoint(t *testing.T) { test.BothEndpoints(t, tf) } + + +func TestHealthEndpoint(t *testing.T) { + ctx := context.Background() + rest := testAPI(t) + defer rest.Shutdown(ctx) + + tf := func(t *testing.T, url test.URLFunc) { + errResp := api.Error{} + test.MakeGet(t, rest, url(rest)+"/health", &errResp) + if errResp.Code != 0 || errResp.Message != "" { + t.Error("expected no errors") + } + } + + test.BothEndpoints(t, tf) +} \ No newline at end of file